-
Notifications
You must be signed in to change notification settings - Fork 262
feat(lazer) Add response messages for lazer transactions #2743
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -88,3 +88,26 @@ message LazerTransaction { | |||
GovernanceInstruction governance_instruction = 2; | ||||
} | ||||
} | ||||
|
||||
// Response to SignedLazerTransactionResponse from the relayer | ||||
message SignedLazerTransactionResponse { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💬 We will need to expand this a lot but I haven't figured out the best way to do this yet. The difficult part is handling all of the sub-type of what Somehow we should represent
We'll need the same sort of thing for We could define the top-level accept message using something like this (and the same for the rejections): message SignedLazerTransactionAccepted {
oneof details {
PublisherUpdateResponseAccepted publisher_update_accepted = 1;
GovernanceInstructionResponseAccepted governance_instruction_accepted = 2;
}
} and include further details in those specific messages. We could also include a I think @pyth-network/price-feeds-team i'd like to hear your ideas, comments, concerns. Let's make sure we're on the same page now because changing this later will be a big pain. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the API endpoints we just want to return JSON, so I'm not sure if there is any reason to define this type in protobuf. It can be just another type definition in our API. As for the structure, we could use a enum with payload for governance/publisher variants, but we can also just dump all possible response details as optional fields on the top object because the sender already knows what type of transaction it sends. |
||||
// [required] The ID of a transaction derived as sha256 of the payload. | ||||
optional bytes transaction_id = 1; | ||||
|
||||
// [required] Status of the transaction received by the relayer | ||||
oneof status { | ||||
// The transaction was accepted by the relayer | ||||
SignedLazerTransactionAccepted accepted = 2; | ||||
// The transaction was rejected by the relayer | ||||
SignedLazerTransactionRejected rejected = 3; | ||||
} | ||||
|
||||
// Message containing additional details for accepted transactions | ||||
message SignedLazerTransactionAccepted {} | ||||
|
||||
// Message containing details why the transaction was rejected | ||||
message SignedLazerTransactionRejected { | ||||
// [required] Human readable error message | ||||
optional string message = 1; | ||||
} | ||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,10 +1,12 @@ | ||||||
use std::{collections::BTreeMap, time::Duration}; | ||||||
|
||||||
use ::protobuf::MessageField; | ||||||
use crate::transaction::SignedLazerTransaction; | ||||||
use ::protobuf::{Message, MessageField}; | ||||||
use anyhow::{bail, ensure, Context}; | ||||||
use humantime::format_duration; | ||||||
use protobuf::dynamic_value::{dynamic_value, DynamicValue}; | ||||||
use pyth_lazer_protocol::router::TimestampUs; | ||||||
use sha2::{Digest, Sha256}; | ||||||
|
||||||
pub mod transaction_envelope { | ||||||
pub use crate::protobuf::transaction_envelope::*; | ||||||
|
@@ -160,3 +162,23 @@ impl TryFrom<DynamicValue> for serde_value::Value { | |||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
impl SignedLazerTransaction { | ||||||
// The id of a SignedLazerTransaction is calculated as sha256 of its entire payload. This is | ||||||
// an unstable ID (sensitive to any proto changes) are should only be used to correlate | ||||||
// immediate responses sent from the relayer | ||||||
pub fn id(&self) -> anyhow::Result<[u8; 32]> { | ||||||
let mut hasher = Sha256::new(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❔ Is this actually a good idea? I like the simplicity but this creates some problems (e.g. two separate transactions sent at different times could have the same id which is only OK if all the transactions are idempotent). I think we should consider attaching a UUID on the client side - it's a tiny amount of data and IMHO a more resilient way to identify messages. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For governance we have sequence numbers, and for publishers we have publisher timestamps that have to be increasing. Both can be interpreted as a sort of nonce. Therefore, payloads will always be different unless it's literally the same transaction (in which case the second occurrence will be ignored). |
||||||
self.write_to_writer(&mut hasher)?; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will hash both the signature and payload. We need it to be only payload. |
||||||
hasher | ||||||
.finalize() | ||||||
.try_into() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hashing is infallible. |
||||||
.context("failed to calculate the sha256 as transaction id") | ||||||
} | ||||||
|
||||||
pub fn calculate_id_from_bytes(payload: &Vec<u8>) -> anyhow::Result<[u8; 32]> { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
Ok(Sha256::digest(payload) | ||||||
.try_into() | ||||||
.context("failed to calculate the sha256 as transaction id")?) | ||||||
} | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this payload so generic anyway? Couldn't we constrain it at all?
pyth-crosschain/lazer/publisher_sdk/proto/pyth_lazer_transaction.proto
Lines 20 to 30 in 518aa8d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has to be
bytes
because we need to decode it as bytes to verify the signature.