-
Notifications
You must be signed in to change notification settings - Fork 35
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
feat: Utilize expected notes from ExecutedTransaction
#329
Changes from 20 commits
4bef43b
04e6d33
86403c0
2dbb681
261e420
add0a13
0c85c91
b36d05a
b0103b0
e0b0152
a90dda9
a2cdb30
e2c0385
479303c
b9e9b39
e32ca06
adf01ea
f2933fd
9b0c0b7
bd59192
57d3c93
1e39716
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,8 @@ use miden_objects::{ | |
crypto::rand::RpoRandomCoin, | ||
notes::{Note, NoteId, NoteType}, | ||
transaction::{ | ||
ExecutedTransaction, InputNotes, OutputNotes, ProvenTransaction, TransactionArgs, | ||
TransactionId, TransactionScript, | ||
ExecutedTransaction, InputNotes, OutputNote, OutputNotes, ProvenTransaction, | ||
TransactionArgs, TransactionId, TransactionScript, | ||
}, | ||
Digest, Felt, Word, | ||
}; | ||
|
@@ -20,78 +20,75 @@ use rand::Rng; | |
use tracing::info; | ||
|
||
use self::transaction_request::{PaymentTransactionData, TransactionRequest, TransactionTemplate}; | ||
use super::{note_screener::NoteRelevance, rpc::NodeRpcClient, Client, FeltRng}; | ||
use super::{rpc::NodeRpcClient, Client, FeltRng}; | ||
use crate::{ | ||
client::NoteScreener, | ||
errors::ClientError, | ||
store::{Store, TransactionFilter}, | ||
store::{InputNoteRecord, Store, TransactionFilter}, | ||
}; | ||
|
||
pub mod transaction_request; | ||
|
||
// TRANSACTION RESULT | ||
// -------------------------------------------------------------------------------------------- | ||
|
||
/// Represents the result of executing a transaction by the client | ||
/// Represents the result of executing a transaction by the client. | ||
/// | ||
/// It contains an [ExecutedTransaction], a list of [Note] that describe the details of the notes | ||
/// created by the transaction execution, and a list of `usize` `relevant_notes` that contain the | ||
/// indices of `output_notes` that are relevant to the client | ||
/// It contains an [ExecutedTransaction], and a list of `relevant_notes` that contains the | ||
/// `output_notes` that the client has to store as input notes, based on the NoteScreener | ||
/// output from filtering the transaction's output notes. | ||
pub struct TransactionResult { | ||
executed_transaction: ExecutedTransaction, | ||
output_notes: Vec<Note>, | ||
relevant_notes: Option<BTreeMap<usize, Vec<(AccountId, NoteRelevance)>>>, | ||
transaction: ExecutedTransaction, | ||
relevant_notes: Vec<InputNoteRecord>, | ||
} | ||
|
||
impl TransactionResult { | ||
pub fn new(executed_transaction: ExecutedTransaction, created_notes: Vec<Note>) -> Self { | ||
Self { | ||
executed_transaction, | ||
output_notes: created_notes, | ||
relevant_notes: None, | ||
/// Screens the output notes to store and track the relevant ones, and instantiates a [TransactionResult] | ||
pub fn new<S: Store>( | ||
transaction: ExecutedTransaction, | ||
note_screener: NoteScreener<S>, | ||
) -> Result<Self, ClientError> { | ||
let mut relevant_notes = vec![]; | ||
|
||
for note in notes_from_output(transaction.output_notes()) { | ||
let account_relevance = note_screener.check_relevance(note)?; | ||
|
||
if !account_relevance.is_empty() { | ||
relevant_notes.push(note.clone().into()); | ||
} | ||
} | ||
} | ||
|
||
pub fn executed_transaction(&self) -> &ExecutedTransaction { | ||
&self.executed_transaction | ||
let tx_result = Self { transaction, relevant_notes }; | ||
|
||
Ok(tx_result) | ||
} | ||
|
||
pub fn created_notes(&self) -> &Vec<Note> { | ||
&self.output_notes | ||
pub fn executed_transaction(&self) -> &ExecutedTransaction { | ||
&self.transaction | ||
} | ||
|
||
pub fn relevant_notes(&self) -> Vec<&Note> { | ||
if let Some(relevant_notes) = &self.relevant_notes { | ||
relevant_notes | ||
.keys() | ||
.map(|note_index| &self.output_notes[*note_index]) | ||
.collect() | ||
} else { | ||
self.created_notes().iter().collect() | ||
} | ||
pub fn created_notes(&self) -> &OutputNotes { | ||
self.transaction.output_notes() | ||
} | ||
|
||
pub fn set_relevant_notes( | ||
&mut self, | ||
relevant_notes: BTreeMap<usize, Vec<(AccountId, NoteRelevance)>>, | ||
) { | ||
self.relevant_notes = Some(relevant_notes); | ||
pub fn relevant_notes(&self) -> &[InputNoteRecord] { | ||
&self.relevant_notes | ||
} | ||
|
||
pub fn block_num(&self) -> u32 { | ||
self.executed_transaction.block_header().block_num() | ||
self.transaction.block_header().block_num() | ||
} | ||
|
||
pub fn transaction_arguments(&self) -> &TransactionArgs { | ||
self.executed_transaction.tx_args() | ||
self.transaction.tx_args() | ||
} | ||
|
||
pub fn account_delta(&self) -> &AccountDelta { | ||
self.executed_transaction.account_delta() | ||
self.transaction.account_delta() | ||
} | ||
|
||
pub fn consumed_notes(&self) -> &InputNotes { | ||
self.executed_transaction.tx_inputs().input_notes() | ||
self.transaction.tx_inputs().input_notes() | ||
} | ||
} | ||
|
||
|
@@ -231,7 +228,6 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> Client | |
let block_num = self.store.get_sync_height()?; | ||
|
||
let note_ids = transaction_request.get_input_note_ids(); | ||
|
||
let output_notes = transaction_request.expected_output_notes().to_vec(); | ||
|
||
// Execute the transaction and get the witness | ||
|
@@ -242,20 +238,28 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> Client | |
transaction_request.into(), | ||
)?; | ||
|
||
// Check that the expected output notes is a subset of the transaction's output notes | ||
let tx_note_ids: BTreeSet<NoteId> = | ||
executed_transaction.output_notes().iter().map(|n| n.id()).collect(); | ||
// Check that the expected output notes matches the transaction outcome. | ||
// We comprae authentication hashes where possible since that involves note IDs + metadata | ||
// (as opposed to just note ID which remains the same regardless of metadata) | ||
let tx_note_auth_hashes: BTreeSet<Digest> = | ||
notes_from_output(executed_transaction.output_notes()) | ||
.map(Note::authentication_hash) | ||
.collect(); | ||
|
||
let missing_note_ids: Vec<NoteId> = output_notes | ||
.iter() | ||
.filter_map(|n| (!tx_note_ids.contains(&n.id())).then_some(n.id())) | ||
.filter_map(|n| { | ||
(!tx_note_auth_hashes.contains(&n.authentication_hash())).then_some(n.id()) | ||
mFragaBA marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
.collect(); | ||
|
||
if !missing_note_ids.is_empty() { | ||
return Err(ClientError::MissingOutputNotes(missing_note_ids)); | ||
} | ||
|
||
Ok(TransactionResult::new(executed_transaction, output_notes)) | ||
let screener = NoteScreener::new(self.store.clone()); | ||
|
||
TransactionResult::new(executed_transaction, screener) | ||
} | ||
|
||
/// Proves the specified transaction witness, submits it to the node, and stores the transaction in | ||
|
@@ -273,19 +277,6 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> Client | |
|
||
self.submit_proven_transaction_request(proven_transaction.clone()).await?; | ||
|
||
let note_screener = NoteScreener::new(self.store.as_ref()); | ||
let mut relevant_notes = BTreeMap::new(); | ||
|
||
for (idx, note) in tx_result.created_notes().iter().enumerate() { | ||
let account_relevance = note_screener.check_relevance(note)?; | ||
if !account_relevance.is_empty() { | ||
relevant_notes.insert(idx, account_relevance); | ||
} | ||
} | ||
|
||
let mut tx_result = tx_result; | ||
tx_result.set_relevant_notes(relevant_notes); | ||
|
||
// Transaction was proven and submitted to the node correctly, persist note details and update account | ||
self.store.apply_transaction(tx_result)?; | ||
|
||
|
@@ -405,7 +396,7 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> Client | |
note_type, | ||
random_coin, | ||
)?; | ||
println!("note tag {}", created_note.metadata().tag()); | ||
|
||
let recipient = created_note | ||
.recipient() | ||
.digest() | ||
|
@@ -442,3 +433,16 @@ impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> Client | |
pub(crate) fn prepare_word(word: &Word) -> String { | ||
word.iter().map(|x| x.as_int().to_string()).collect::<Vec<_>>().join(".") | ||
} | ||
|
||
/// Extracts notes from [OutputNotes] | ||
/// Used for: | ||
/// - checking the relevance of notes to save them as input notes | ||
/// - validate hashes versus expected output notes after a transaction is executed | ||
pub(crate) fn notes_from_output(output_notes: &OutputNotes) -> impl Iterator<Item = &Note> { | ||
output_notes.iter().map(|n| match n { | ||
OutputNote::Full(n) => n, | ||
// The following todo!() applies until we have a way to support flows where we have | ||
// partial details of the note | ||
OutputNote::Header(_) => todo!("For now, all details should be held in OutputNote::Fulls"), | ||
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 which case do we get an 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. The 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. don't think it's blocking yet, but for swap flows I think we create an |
||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -173,9 +173,9 @@ impl PaymentTransactionData { | |
// -------------------------------------------------------------------------------------------- | ||
|
||
pub mod known_script_roots { | ||
pub const P2ID: &str = "0xcdfd70344b952980272119bc02b837d14c07bbfc54f86a254422f39391b77b35"; | ||
pub const P2IDR: &str = "0x41e5727b99a12b36066c09854d39d64dd09d9265c442a9be3626897572bf1745"; | ||
pub const SWAP: &str = "0x5852920f88985b651cf7ef5e48623f898b6c292f4a2c25dd788ff8b46dd90417"; | ||
pub const P2ID: &str = "0x0007b2229f7c8e3205a485a9879f1906798a2e27abd1706eaf58536e7cc3868b"; | ||
pub const P2IDR: &str = "0x418ae31e80b53ddc99179d3cacbc4140c7b36ab04ddb26908b3a6ed2e40061d5"; | ||
pub const SWAP: &str = "0x755c5dd2fd8d2aa8c2896e0e366a45bdc59c6d5ab543909db32ccfaf949c5826"; | ||
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 another PR: Should we find a way to change/calculate these automatically? I guess thanks to the tests we would never forget to update them but it would be a good addition. 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. Yeah, we might have to. Originally this was a 'temporary' solution until the MAST refactors on the compiler. Additionally I thought that these scripts would not change too much, and even though the tests catch these errors there are a couple of things we can do to avoid having to recalculate them:
|
||
} | ||
|
||
#[cfg(test)] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -91,6 +91,10 @@ impl OutputNoteRecord { | |
} | ||
} | ||
|
||
// CONVERSIONS | ||
// ================================================================================================ | ||
|
||
// TODO: Improve conversions by implementing into_parts() | ||
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. should we create an issue for this? 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. Yes, we probably should. Although I think it would belong in |
||
impl From<Note> for OutputNoteRecord { | ||
fn from(note: Note) -> Self { | ||
OutputNoteRecord { | ||
|
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.
should we also provide a way to create a
TransactionResult
without filtering by note relevance?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.
Might be a good idea, but since we provide and use this
TransactionResult
type, I think we should add it if we end up needing it.