Skip to content

Commit

Permalink
fix(spend): support multiple inputs with different keys
Browse files Browse the repository at this point in the history
  • Loading branch information
maqi committed Jul 9, 2024
1 parent af43be0 commit 7167cf6
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 35 deletions.
1 change: 1 addition & 0 deletions sn_client/src/audit/tests/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ impl MockNetwork {
recipient,
from_wallet.sk.main_pubkey(),
SpendReason::default(),
None,
)
.map_err(|e| eyre!("failed to create transfer: {}", e))?;
let spends = transfer.all_spend_requests;
Expand Down
40 changes: 32 additions & 8 deletions sn_node/tests/double_spend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,15 @@ async fn cash_note_transfer_double_spend_fail() -> Result<()> {
let to2_unique_key = (amount, to2, DerivationIndex::random(&mut rng));
let to3_unique_key = (amount, to3, DerivationIndex::random(&mut rng));

let transfer_to_2 =
OfflineTransfer::new(some_cash_notes, vec![to2_unique_key], to1, reason.clone())?;
let transfer_to_3 = OfflineTransfer::new(same_cash_notes, vec![to3_unique_key], to1, reason)?;
let transfer_to_2 = OfflineTransfer::new(
some_cash_notes,
vec![to2_unique_key],
to1,
reason.clone(),
None,
)?;
let transfer_to_3 =
OfflineTransfer::new(same_cash_notes, vec![to3_unique_key], to1, reason, None)?;

// send both transfers to the network
// upload won't error out, only error out during verification.
Expand Down Expand Up @@ -116,7 +122,8 @@ async fn genesis_double_spend_fail() -> Result<()> {
);
let change_addr = second_wallet_addr;
let reason = SpendReason::default();
let transfer = OfflineTransfer::new(genesis_cashnote, vec![recipient], change_addr, reason)?;
let transfer =
OfflineTransfer::new(genesis_cashnote, vec![recipient], change_addr, reason, None)?;

// send the transfer to the network which will mark genesis as a double spent
// making its direct descendants unspendable
Expand Down Expand Up @@ -148,6 +155,7 @@ async fn genesis_double_spend_fail() -> Result<()> {
vec![recipient],
change_addr,
reason,
None,
)?;

// send the transfer to the network which should reject it
Expand Down Expand Up @@ -186,6 +194,7 @@ async fn poisoning_old_spend_should_not_affect_descendant() -> Result<()> {
vec![to_2_unique_key],
to1,
reason.clone(),
None,
)?;

info!("Sending 1->2 to the network...");
Expand All @@ -210,8 +219,13 @@ async fn poisoning_old_spend_should_not_affect_descendant() -> Result<()> {
wallet_22.address(),
DerivationIndex::random(&mut rng),
);
let transfer_to_22 =
OfflineTransfer::new(cash_notes_2, vec![to_22_unique_key], to2, reason.clone())?;
let transfer_to_22 = OfflineTransfer::new(
cash_notes_2,
vec![to_22_unique_key],
to2,
reason.clone(),
None,
)?;

client
.send_spends(transfer_to_22.all_spend_requests.iter(), false)
Expand All @@ -232,8 +246,13 @@ async fn poisoning_old_spend_should_not_affect_descendant() -> Result<()> {
wallet_3.address(),
DerivationIndex::random(&mut rng),
);
let transfer_to_3 =
OfflineTransfer::new(cash_notes_1, vec![to_3_unique_key], to1, reason.clone())?; // reuse the old cash notes
let transfer_to_3 = OfflineTransfer::new(
cash_notes_1,
vec![to_3_unique_key],
to1,
reason.clone(),
None,
)?; // reuse the old cash notes
client
.send_spends(transfer_to_3.all_spend_requests.iter(), false)
.await?;
Expand All @@ -260,6 +279,7 @@ async fn poisoning_old_spend_should_not_affect_descendant() -> Result<()> {
vec![to_222_unique_key],
wallet_22.address(),
reason,
None,
)?;
client
.send_spends(transfer_to_222.all_spend_requests.iter(), false)
Expand Down Expand Up @@ -301,6 +321,7 @@ async fn parent_and_child_double_spends_should_lead_to_cashnote_being_invalid()
vec![to_b_unique_key],
wallet_a.address(),
reason.clone(),
None,
)?;

info!("Sending A->B to the network...");
Expand Down Expand Up @@ -330,6 +351,7 @@ async fn parent_and_child_double_spends_should_lead_to_cashnote_being_invalid()
vec![to_c_unique_key],
wallet_b.address(),
reason.clone(),
None,
)?;

client
Expand All @@ -356,6 +378,7 @@ async fn parent_and_child_double_spends_should_lead_to_cashnote_being_invalid()
vec![to_x_unique_key],
wallet_a.address(),
reason.clone(),
None,
)?; // reuse the old cash notes
client
.send_spends(transfer_to_x.all_spend_requests.iter(), false)
Expand Down Expand Up @@ -383,6 +406,7 @@ async fn parent_and_child_double_spends_should_lead_to_cashnote_being_invalid()
vec![to_y_unique_key],
wallet_b.address(),
reason.clone(),
None,
)?; // reuse the old cash notes
client
.send_spends(transfer_to_y.all_spend_requests.iter(), false)
Expand Down
36 changes: 22 additions & 14 deletions sn_node/tests/spend_simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,13 @@ async fn spend_simulation() -> Result<()> {
if let Some(tx) = tx {
let mut input_cash_notes = Vec::new();
for input in &tx.inputs {
let (status, cashnote) = state
.cashnote_tracker
.get_mut(&input.unique_pubkey)
.ok_or_eyre("Input spend not tracked")?;
*status = SpendStatus::Poisoned;
input_cash_notes.push(cashnote.clone());
// Transaction may contains the `middle payment`
if let Some((status, cashnote)) =
state.cashnote_tracker.get_mut(&input.unique_pubkey)
{
*status = SpendStatus::Poisoned;
input_cash_notes.push(cashnote.clone());
}
}
info!(
"Wallet {id} is attempting to poison a old spend. Marking inputs {:?} as Poisoned",
Expand Down Expand Up @@ -289,15 +290,19 @@ async fn inner_handle_action(
info!(
"TestWallet {our_id} Available CashNotes for local send: {:?}",
available_cash_notes
.iter()
.map(|(c, _)| c.unique_pubkey())
.collect_vec()
);
let mut rng = &mut rand::rngs::OsRng;
let derivation_index = DerivationIndex::random(&mut rng);
let transfer = OfflineTransfer::new(
available_cash_notes,
recipients,
wallet.address(),
SpendReason::default(),
Some((
wallet.key().main_pubkey(),
derivation_index,
wallet.key().derive_key(&derivation_index),
)),
)?;
let recipient_cash_notes = transfer.cash_notes_for_recipient.clone();
let change = transfer.change_cash_note.clone();
Expand Down Expand Up @@ -335,6 +340,7 @@ async fn inner_handle_action(
vec![to],
wallet.address(),
SpendReason::default(),
None,
)?;
info!("TestWallet {our_id} double spending transfer: {transfer:?}");

Expand Down Expand Up @@ -399,11 +405,12 @@ async fn handle_wallet_task_result(
transaction.inputs
);
for input in &transaction.inputs {
let (status, _cashnote) = state
.cashnote_tracker
.get_mut(&input.unique_pubkey)
.ok_or_eyre("Input spend not tracked")?;
*status = SpendStatus::Spent;
// Transaction may contains the `middle payment`
if let Some((status, _cashnote)) =
state.cashnote_tracker.get_mut(&input.unique_pubkey)
{
*status = SpendStatus::Spent;
}
}

// track the change cashnote that is stored by our wallet.
Expand Down Expand Up @@ -579,6 +586,7 @@ async fn init_state(count: usize) -> Result<(Client, State)> {
recipients,
first_wallet.address(),
reason.clone(),
None,
)?;

info!("Sending transfer for all wallets and verifying them");
Expand Down
8 changes: 8 additions & 0 deletions sn_transfers/benches/reissue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ fn bench_reissue_1_to_100(c: &mut Criterion) {
recipients,
starting_main_key.main_pubkey(),
SpendReason::default(),
None,
)
.expect("transfer to succeed");

Expand Down Expand Up @@ -97,6 +98,7 @@ fn bench_reissue_100_to_1(c: &mut Criterion) {
recipients,
starting_main_key.main_pubkey(),
SpendReason::default(),
None,
)
.expect("transfer to succeed");

Expand Down Expand Up @@ -134,12 +136,18 @@ fn bench_reissue_100_to_1(c: &mut Criterion) {
DerivationIndex::random(&mut rng),
)];

let derivation_index = DerivationIndex::random(&mut rng);
// create transfer to merge all of the cashnotes into one
let many_to_one_transfer = OfflineTransfer::new(
many_cashnotes,
one_single_recipient,
starting_main_key.main_pubkey(),
SpendReason::default(),
Some((
starting_main_key.main_pubkey(),
derivation_index,
starting_main_key.derive_key(&derivation_index),
)),
)
.expect("transfer to succeed");

Expand Down
4 changes: 4 additions & 0 deletions sn_transfers/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ pub enum TransferError {
TransferDeserializationFailed,
#[error("The OutputPurpose bearing an invlalid length")]
OutputPurposeTooShort,
#[error("Multiple inputs from different keys without a middle-addr")]
MultipleInputsWithoutMiddleAddr,
#[error("Multiple inputs from different keys without a middle payment")]
MultipleInputsWithoutMiddlePayment,

#[error("Bls error: {0}")]
Blsttc(#[from] bls::error::Error),
Expand Down
6 changes: 0 additions & 6 deletions sn_transfers/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,6 @@ pub fn get_genesis_sk() -> MainSecretKey {

/// Return if provided Spend is genesis spend.
pub fn is_genesis_spend(spend: &SignedSpend) -> bool {
info!(
"Testing genesis against genesis_input {:?} genesis_output {:?} {GENESIS_CASHNOTE_AMOUNT:?}, {:?}",
GENESIS_PK.new_unique_pubkey(&GENESIS_INPUT_DERIVATION_INDEX),
GENESIS_PK.new_unique_pubkey(&GENESIS_OUTPUT_DERIVATION_INDEX),
spend.spend
);
let bytes = spend.spend.to_bytes_for_signing();
spend.spend.unique_pubkey == *GENESIS_SPEND_UNIQUE_KEY
&& GENESIS_SPEND_UNIQUE_KEY.verify(&spend.derived_key_sig, bytes)
Expand Down
Loading

0 comments on commit 7167cf6

Please sign in to comment.