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

Avoid duplicated sections in batches (backport #3882) #3888

Merged
merged 5 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Improved batch construction to reduce the size of the resulting tx.
([\#3882](https://github.com/anoma/namada/pull/3882))
25 changes: 23 additions & 2 deletions crates/sdk/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ use crate::wallet::{Wallet, WalletIo};
use crate::{args, rpc, Namada};

/// A structure holding the signing data to craft a transaction
#[derive(Clone, PartialEq)]
#[derive(Clone)]
pub struct SigningTxData {
/// The address owning the transaction
pub owner: Option<Address>,
Expand All @@ -75,6 +75,27 @@ pub struct SigningTxData {
pub fee_payer: common::PublicKey,
}

impl PartialEq for SigningTxData {
fn eq(&self, other: &Self) -> bool {
if !(self.owner == other.owner
&& self.threshold == other.threshold
&& self.account_public_keys_map == other.account_public_keys_map
&& self.fee_payer == other.fee_payer)
{
return false;
}

// Check equivalence of the public keys ignoring the specific ordering
if self.public_keys.len() != other.public_keys.len() {
return false;
}

self.public_keys
.iter()
.all(|pubkey| other.public_keys.contains(pubkey))
}
}

/// Find the public key for the given address and try to load the keypair
/// for it from the wallet.
///
Expand Down Expand Up @@ -290,7 +311,7 @@ where
Ok(fee_payer_keypair) => {
tx.sign_wrapper(fee_payer_keypair);
}
// The case where tge fee payer also signs the inner transaction
// The case where the fee payer also signs the inner transaction
Err(_)
if signing_data.public_keys.contains(&signing_data.fee_payer) =>
{
Expand Down
116 changes: 108 additions & 8 deletions crates/tx/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,49 @@ impl Tx {

/// Add a new inner tx to the transaction. Returns `false` if the
/// commitments already existed in the collection. This function expects a
/// transaction carrying a single inner tx as input
pub fn add_inner_tx(&mut self, other: Tx, cmt: TxCommitments) -> bool {
if !self.header.batch.insert(cmt) {
/// transaction carrying a single inner tx as input and the provided
/// commitment is assumed to be present in the transaction without further
/// validation
pub fn add_inner_tx(&mut self, other: Tx, mut cmt: TxCommitments) -> bool {
if self.header.batch.contains(&cmt) {
return false;
}

// TODO: avoid duplicated sections to reduce the size of the message
self.sections.extend(other.sections);
for section in other.sections {
// PartialEq implementation of Section relies on an implementation
// on the inner types that doesn't account for the possible salt
// which is the correct behavior for this logic
if let Some(duplicate) =
self.sections.iter().find(|&sec| sec == &section)
{
// Avoid pushing a duplicated section. Adjust the commitment of
// this inner tx for the different section's salt if needed
match duplicate {
Section::Code(_) => {
if cmt.code_hash == section.get_hash() {
cmt.code_hash = duplicate.get_hash();
}
}
Section::Data(_) => {
if cmt.data_hash == section.get_hash() {
cmt.data_hash = duplicate.get_hash();
}
}
Section::ExtraData(_) => {
if cmt.memo_hash == section.get_hash() {
cmt.memo_hash = duplicate.get_hash();
}
}
// Other sections don't have a direct commitment in the
// header
_ => (),
}
} else {
self.sections.push(section);
}
}

true
self.header.batch.insert(cmt)
}

/// Get the transaction header
Expand Down Expand Up @@ -1324,6 +1357,11 @@ mod test {
let data_bytes2 = "WASD".as_bytes();
let memo_bytes2 = "hjkl".as_bytes();

// Some duplicated sections
let code_bytes3 = code_bytes1;
let data_bytes3 = data_bytes2;
let memo_bytes3 = memo_bytes1;

let inner_tx1 = {
let mut tx = Tx::default();

Expand Down Expand Up @@ -1352,10 +1390,25 @@ mod test {
tx
};

let inner_tx3 = {
let mut tx = Tx::default();

let code = Code::new(code_bytes3.to_owned(), None);
tx.set_code(code);

let data = Data::new(data_bytes3.to_owned());
tx.set_data(data);

tx.add_memo(memo_bytes3);

tx
};

let cmt1 = inner_tx1.first_commitments().unwrap().to_owned();
let cmt2 = inner_tx2.first_commitments().unwrap().to_owned();
let mut cmt2 = inner_tx2.first_commitments().unwrap().to_owned();
let mut cmt3 = inner_tx3.first_commitments().unwrap().to_owned();

// Batch `inner_tx1` and `inner_tx1` into `tx`
// Batch `inner_tx1`, `inner_tx2` and `inner_tx3` into `tx`
let tx = {
let mut tx = Tx::default();

Expand All @@ -1364,10 +1417,22 @@ mod test {
assert_eq!(tx.header.batch.len(), 1);

tx.add_inner_tx(inner_tx2, cmt2.clone());
// Update cmt2 with the hash of cmt1 code section
cmt2.code_hash = cmt1.code_hash;
assert_eq!(tx.first_commitments().unwrap(), &cmt1);
assert_eq!(tx.header.batch.len(), 2);
assert_eq!(tx.header.batch.get_index(1).unwrap(), &cmt2);

tx.add_inner_tx(inner_tx3, cmt3.clone());
// Update cmt3 with the hash of cmt1 code and memo sections and the
// hash of cmt2 data section
cmt3.code_hash = cmt1.code_hash;
cmt3.data_hash = cmt2.data_hash;
cmt3.memo_hash = cmt1.memo_hash;
assert_eq!(tx.first_commitments().unwrap(), &cmt1);
assert_eq!(tx.header.batch.len(), 3);
assert_eq!(tx.header.batch.get_index(2).unwrap(), &cmt3);

tx
};

Expand All @@ -1390,5 +1455,40 @@ mod test {

assert!(tx.memo(&cmt2).is_some());
assert_eq!(tx.memo(&cmt2).unwrap(), memo_bytes2);

// Check sections of `inner_tx3`
assert!(tx.code(&cmt3).is_some());
assert_eq!(tx.code(&cmt3).unwrap(), code_bytes3);

assert!(tx.data(&cmt3).is_some());
assert_eq!(tx.data(&cmt3).unwrap(), data_bytes3);

assert!(tx.memo(&cmt3).is_some());
assert_eq!(tx.memo(&cmt3).unwrap(), memo_bytes3);

// Check that the redundant sections have been included only once in the
// batch
assert_eq!(tx.sections.len(), 5);
assert_eq!(
tx.sections
.iter()
.filter(|section| section.code_sec().is_some())
.count(),
1
);
assert_eq!(
tx.sections
.iter()
.filter(|section| section.data().is_some())
.count(),
2
);
assert_eq!(
tx.sections
.iter()
.filter(|section| section.extra_data_sec().is_some())
.count(),
2
);
}
}
Loading