Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add /relay_tx endpoint #1050

Merged
merged 48 commits into from
Sep 26, 2024
Merged

Add /relay_tx endpoint #1050

merged 48 commits into from
Sep 26, 2024

Conversation

JesseAbram
Copy link
Member

@JesseAbram JesseAbram commented Sep 12, 2024

Related #1033

Closes #899

  • Passes a message first to relay_tx which must be a validator not signer then has the relayer select the signers
  • Passes messages from signer back to relayer then to client

Points of discussion

  • If we want randomness in the signing session we now can have the relayer pass randomness that can be used in session id creation

@JesseAbram JesseAbram changed the title add relay tx endpoint Add relay tx endpoint Sep 19, 2024
@JesseAbram JesseAbram marked this pull request as ready for review September 20, 2024 20:04
Copy link
Contributor

@ameba23 ameba23 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally all looks very good and great that this got done so quickly as its quite a big feature.

I am not done with reading over the changes to the tests so leaving this as a comment for now and will get to that later or tomorrow.

crates/client/src/client.rs Outdated Show resolved Hide resolved
validators.truncate(threshold as usize);
let selected_signers: Vec<_> = {
let mut cloned_signers = signers.clone();
// TODO: temp remove dave for now until test dave is spun up correctly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After looking into this i think needing this is caused by a problem that i introduced, so sorry about that.

What i think it is the issue is that the pre-generated keyshares are being given to alice, bob and charlie, but now that we have random signer selection on jumpstart, dave is actually chosen as an initial signer but is never given a keyshare.

I have made a PR to hopefully fix that and we should be able to get rid of this pop.

#1061

crates/threshold-signature-server/src/user/api.rs Outdated Show resolved Hide resolved
@@ -524,3 +632,65 @@ pub fn check_hash_pointer_out_of_bounds(
_ => Ok(()),
}
}

pub async fn pre_sign_checks(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might have missed it somewhere but i can't see a check that the RelayerSignatureRequest::validators_info are actually current signers, or that there are t of them.

I know this is comes from another TS server so should be good, but i would check anyway to avoid slashing the wrong node when things go wrong.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm good point from a security checkpoint I mean like it won't sign it won't sign (im avoiding thinking of slashing since it doesn't exist yet) but I like to catch any errors first and bail out for ddos reasons, so will add that check

pub validators_info: Vec<ValidatorInfo>,
}

/// Returns a threshold of signer's ValidatorInfo from the chain
pub async fn get_signers_from_chain(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If i've understood right, this is only ever called by the TS server, and not by the client. In which case it should probably be in entropy-tss rather than here. Using stuff like rand::thread_rng in here will restrict what platforms entropy-client can be used on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on this? What are the platforms where rand::thread_rng is not available? WASM? TDX hosts?

Copy link
Contributor

@ameba23 ameba23 Sep 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep i was thinking wasm. Although its not really clear what platforms we are trying to support for clients, its good to keep as many doors open as possible. If this was a client function, i would suggest having the rng be passed in as an argument. But since its not, i would say it just shouldn't be in this crate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thank you for the explanation. :)

if resp.status() == 200 {
let signing_result: Result<(String, Signature), String> =
serde_json::from_slice(&chunk)?;
send_back.push(signing_result);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As i mentioned on discord, in the happy case where signing is successful, i would do some processing here to make things simple for the client:

For each Ok((String,Signature)), validate the signatures from the TS nodes, check the strings are all identical, then send back just one response, rather than a vec of responses, signed by the relayer node rather than all signers.

In the error case it probably still makes sense to send a vec of errors, in case there is some mismatching that the client might want to investigate.

This is a 'nice to have' - i'd be happy merging this without it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hear what you are saying but does it make things less secure as we take multiple responses and allow the relayer to select one, I mean probably fine, that being said I think this is good for this PR and let's open an issue and maybe take JS side feedback here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm with Peg here, think it makes sense to cross reference all the signatures and just send back a single signature.

I hear what you are saying but does it make things less secure as we take multiple responses and allow the relayer to select one, I mean probably fine, that being said I think this is good for this PR and let's open an issue and maybe take JS side feedback here

We're already trusting the relayer by sending a message to them in the first place. Plus, if the message is signed by a signer it doesn't really matter what the relayer sends back

@@ -205,6 +205,8 @@ pub async fn spawn_testing_validators(
let dave_id = PartyId::new(SubxtAccountId32(
*get_signer(&dave_kv).await.unwrap().account_id().clone().as_ref(),
));
// TODO: fix
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be hopefully be fixed with #1061

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, can you add an issue number here Jesse?

@@ -278,23 +280,28 @@ pub async fn jump_start_network_with_signer(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
signer: &PairSigner<EntropyConfig, sr25519::Pair>,
) {
) -> ValidatorName {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add to the doccomment: 'Returns the name of a validator who is not a signer.'

Also we should be aware that defaulting to alice might cause confusion if using this with a different chainspec - strictly speaking i think this should be an Option<ValidatorName>.

CHANGELOG.md Outdated
@@ -19,6 +19,10 @@ At the moment this project **does not** adhere to
pallet. The genesis build config was also removed. Additionally, the `new/user/` HTTP endpoint in
the TSS was removed since it was no longer necessary.
- In [#1045](https://github.com/entropyxyz/entropy-core/pull/1045), `ProgramsInfo` now takes `version_number` to maintain backwards compatibility if programs runtime is updated
- In [#1050](https://github.com/entropyxyz/entropy-core/pull/1050), the flow for signing has changed.
A user now sends their request to a validator that is not a signer. This will act as a realyer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correct to say "…to any validator" here? Or is it "…to any validator that is not a signer"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the second one is def correct, as you def can't talk straight to a signer anymore

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it!

Comment on lines 158 to 160
// take only one of the responses
let (signature_base64, signature_of_signature) =
signing_results[0].clone().map_err(ClientError::SigningFailed)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dumb (and paranoid) question: shouldn't we pick a random response here? So we can be sure that there's no way for an attacker to anticipate which validator will reply to us?

Also: why do you need to clone here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmmm ya potentially, can change this, I mean......like it is def better, however I am not sure it is more secure, anyways will change

}

if !sig_recovery_results.contains(&true) {
return Err(ClientError::BadSignature);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this ever happens, how can you tell who the bad guy was? Why not bail earlier, when you're collecting the verification results?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya I mean this relates to peg's earlier comment, I will open an issue in a bit and link it, pretty much how we return the results im open to discuss

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crates/client/src/user.rs Outdated Show resolved Hide resolved
pub validators_info: Vec<ValidatorInfo>,
}

/// Returns a threshold of signer's ValidatorInfo from the chain
pub async fn get_signers_from_chain(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on this? What are the platforms where rand::thread_rng is not available? WASM? TDX hosts?

@HCastano
Copy link
Collaborator

I'm gonna take a look at this tomorrow 🙏

Copy link
Contributor

@ameba23 ameba23 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, and especially great for updating doccomments and adding extra validator info checks. 🌟

I think we gotta make sure client developers are aware that if they are getting an error from one of the checks made by the relayer node (eg: program fails), the right thing to do is try another random non-signing node. We could do this in entropy-client as an example.

//!
//! "[{\"Err\":\"Too many requests - wait a block\"},{\"Err\":\"Too many requests - wait a block\"}]"
//!
//! Curl example for `user/sign_tx`:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! Curl example for `user/sign_tx`:
//! Curl example for `user/relay_tx`:

//! Curl example for `user/sign_tx`:
//! ```text
//! curl -X POST -H "Content-Type: application/json" \
//! -d '{"msg" "0x174...hex encoded signedmessage...","sig":"821754409744cbb878b44bd1e3dc575a4ea721e12d781b074fcdb808fc79fd33dd1928b1a281c0b6261a30536a7c0106a102f27dad1bc3ef475b626f0e57c983","pk":[172,133,159,138,33,110,235,27,50,11,76,118,209,24,218,61,116,7,250,82,52,132,208,169,128,18,109,59,77,13,34,10],"recip":[10,192,41,240,184,83,178,59,237,101,45,109,13,230,155,124,195,141,148,249,55,50,238,252,133,181,134,30,144,247,58,34],"a":[169,94,23,7,19,184,134,70,233,117,2,84,242,135,246,95,159,14,218,125,209,191,175,89,41,196,182,96,117,5,159,98],"nonce":[114,93,158,35,209,188,96,248,85,131,95,237]}' \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this PR, but this example needs updating - i made #1065

@@ -32,17 +32,57 @@
//! is an encrypted, signed message.
//!
//!
//! #### `/user/sign_tx` - POST
//! #### `/user/relay_tx` - POST
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤼‍♂️ 🪩

as promised you get men with bunny ears partying and disco ball for updating the API doccomments. and a medal 🥇 Well actually it seems github doesnt have men with bunny ears so you get men wrestling.

crates/threshold-signature-server/src/user/tests.rs Outdated Show resolved Hide resolved
crates/threshold-signature-server/src/user/tests.rs Outdated Show resolved Hide resolved
}
#[tokio::test]
#[serial]
async fn test_signature_requests_fail_validator_info_wrong() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Great to have these in

CHANGELOG.md Outdated
@@ -19,6 +19,10 @@ At the moment this project **does not** adhere to
pallet. The genesis build config was also removed. Additionally, the `new/user/` HTTP endpoint in
the TSS was removed since it was no longer necessary.
- In [#1045](https://github.com/entropyxyz/entropy-core/pull/1045), `ProgramsInfo` now takes `version_number` to maintain backwards compatibility if programs runtime is updated
- In [#1050](https://github.com/entropyxyz/entropy-core/pull/1050), the flow for signing has changed.
A user now sends their request to any validator that is not a siger. This will act as a realyer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A user now sends their request to any validator that is not a siger. This will act as a realyer.
A user now sends their request to any validator that is not a signer. This will act as a relayer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also say that the endpoint for submitting user signature requests has changed to user/relay_tx

Copy link
Collaborator

@HCastano HCastano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this!

Left lots of comments, but nothing blocking this. Would be nice if you could address or ack them though.

CHANGELOG.md Outdated Show resolved Hide resolved
@@ -169,6 +209,7 @@ pub fn app(app_state: AppState) -> Router {
let mut routes = Router::new()
.route("/generate_network_key", post(generate_network_key))
.route("/user/sign_tx", post(sign_tx))
.route("/user/relay_tx", post(relay_tx))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a follow up, but I would rename these endpoints to sign_message and relay_message to better match some of the newer endpoint names.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree - after all 'the transaction is dead' lol

#[error("Message not sent to a validator")]
NotValidator,
#[error("Relay message can not be sent to a signer")]
RelayMessageSigner,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RelayMessageSigner,
RelayMessageSigner,

How is this error different from NotRelayedFromValidator,?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so one is a message is sent to a signer and is not from a relayer. The other is a relayer is a validator but also a signer, which is technically different,


tracing::Span::current().record("request_author", signed_message.account_id().to_string());

let signers = get_signers_from_chain(&api, &rpc).await?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to grab this info from the get_validators_info(..., signers) call above?

Or maybe to use get_validators_info in the implementation of get_signers_from_chain()

pub async fn get_signers_from_chain(
/// Represents an unparsed transaction request coming from a relayer to a signer.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RelayerSignatureRequest {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably just have this in the body of UserSignatureRequest, something like:

pub struct UserSignatureRequest {
    pub request: RelayerSignatureRequest,
    pub validators_info: Vec<ValidatorInfo>,
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but opposite

get_sign_tx_data(&entropy_api, &rpc, hex::encode(PREIMAGE_SHOULD_SUCCEED), verifying_key)
.await;

validators_info.pop();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here we pop an entry to trigger the "too few signers" error? Do you mind writing a comment explaining this. Can also be done as part of Peg's suggested changes

tss_account,
});

let test_user_res_wrong_validator = submit_transaction_sign_tx_requests(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you be able to add a Test: ... annotation here too?

.unwrap();

let signature_request_responses_fail_not_relayer = mock_client
.post("http://127.0.0.1:3001/user/sign_tx")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the validator IPs above, we can probably use that instead of hardcoding the address and port here

Comment on lines 497 to 516
let mock_client = reqwest::Client::new();

let signed_message = EncryptedSignedMessage::new(
&alice.pair(),
serde_json::to_vec(&signature_request.clone()).unwrap(),
&X25519_PUBLIC_KEYS[0],
&[],
)
.unwrap();

let signature_request_responses_fail_not_relayer = mock_client
.post("http://127.0.0.1:3001/user/sign_tx")
.header("Content-Type", "application/json")
.body(serde_json::to_string(&signed_message).unwrap())
.send()
.await;
assert_eq!(
signature_request_responses_fail_not_relayer.unwrap().text().await.unwrap(),
"Message sent directly to signer"
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep this test as strictly a happy-path test.

Could this be moved to say, test_signature_requests_fail_on_different_conditions, or even to a standalone test? Don't think we need the whole jumpstart or registration setup to trigger this error

@HCastano HCastano changed the title Add relay tx endpoint Add /relay_tx endpoint Sep 24, 2024
@JesseAbram JesseAbram merged commit 9facf6e into master Sep 26, 2024
8 checks passed
@JesseAbram JesseAbram deleted the add-signing-relayer branch September 26, 2024 19:56
ameba23 added a commit that referenced this pull request Oct 2, 2024
* master:
  Bump clap from 4.5.18 to 4.5.19 in the patch-dependencies group (#1091)
  Avoid panic by checking that we have a non-signing validator before selecting one (#1083)
  Fix master build (#1088)
  Small fixes to `test-cli` (#1084)
  Pregenerate keyshares sets for all possible initial signer comittees (#1073)
  Fix master build (#1079)
  Bump reqwest from 0.12.7 to 0.12.8 in the patch-dependencies group (#1082)
  Block tss chain when signer (#1078)
  Run multiple test validator (#1074)
  Bump tempfile from 3.12.0 to 3.13.0 (#1076)
  Bump axum from 0.7.6 to 0.7.7 in the patch-dependencies group (#1075)
  Unignore register and sign integration test, and do a non-mock jumpstart (#1070)
  No unbonding when signer or next signer (#1031)
  Add `/relay_tx` endpoint (#1050)
  Fix how pre-generated keyshares are added for tests (#1061)
  Handle Provisioning Certification Keys (PCKs) (#1051)
  Bump async-trait from 0.1.82 to 0.1.83 in the patch-dependencies group (#1067)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Selecting t from n validators for threshold signing
4 participants