Skip to content

Commit b1e2185

Browse files
committed
Fix incorrect calculation of sidechain wealth
1 parent 4a72634 commit b1e2185

2 files changed

Lines changed: 134 additions & 35 deletions

File tree

lib/state/mod.rs

Lines changed: 128 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ impl State {
582582
.ok_or(AmountOverflowError)?;
583583
}
584584
if let InPoint::Withdrawal { .. } = spent_output.inpoint {
585-
total_withdrawal_stxo_value = total_deposit_stxo_value
585+
total_withdrawal_stxo_value = total_withdrawal_stxo_value
586586
.checked_add(spent_output.output.get_value())
587587
.ok_or(AmountOverflowError)?;
588588
}
@@ -692,28 +692,38 @@ mod tests {
692692
types::{
693693
Address, AuthorizedTransaction, BitName, BitcoinOutputContent,
694694
FilledOutput, FilledOutputContent, FilledTransaction, Hash,
695-
MutableBitNameData, OutPoint, OutPointKey, Output, OutputContent,
696-
Transaction, TxData, Txid, VerifyingKey,
695+
InPoint, MutableBitNameData, OutPoint, OutPointKey, Output,
696+
OutputContent, SpentOutput, Transaction, TxData, Txid,
697+
VerifyingKey,
697698
},
698699
};
699700

700-
/// Open a fresh state in a unique temporary directory.
701-
fn new_state(dir_name_suffix: &str) -> (sneed::Env, State) {
702-
let dir_name = if dir_name_suffix.is_empty() {
703-
format!("plain_bitnames_{}", std::process::id())
704-
} else {
705-
format!("plain_bitnames_{dir_name_suffix}_{}", std::process::id())
706-
};
707-
let path = std::env::temp_dir().join(dir_name);
708-
let _remove_result = std::fs::remove_dir_all(&path);
709-
std::fs::create_dir_all(&path).unwrap();
710-
let env = {
711-
let mut opts = heed::EnvOpenOptions::new();
712-
opts.map_size(10 * 1024 * 1024).max_dbs(State::NUM_DBS);
713-
unsafe { sneed::Env::open(&opts, &path) }.unwrap()
714-
};
715-
let state = State::new(&env).unwrap();
716-
(env, state)
701+
fn temp_env_path(test_name: &str) -> anyhow::Result<std::path::PathBuf> {
702+
let mut path = std::env::temp_dir();
703+
let nanos = std::time::SystemTime::now()
704+
.duration_since(std::time::UNIX_EPOCH)?
705+
.as_nanos();
706+
path.push(format!(
707+
"bitnames-{test_name}-{}-{nanos}",
708+
std::process::id()
709+
));
710+
Ok(path)
711+
}
712+
713+
// open a fresh state-backed env in a unique temp dir
714+
fn temp_env(test_name: &str) -> anyhow::Result<sneed::Env> {
715+
let path = temp_env_path(test_name)?;
716+
std::fs::create_dir_all(&path)?;
717+
let mut opts = heed::EnvOpenOptions::new();
718+
opts.map_size(16 * 1024 * 1024).max_dbs(State::NUM_DBS);
719+
let res = unsafe { sneed::Env::open(&opts, &path) }?;
720+
Ok(res)
721+
}
722+
723+
fn fresh_state(test_name: &str) -> anyhow::Result<(sneed::Env, State)> {
724+
let env = temp_env(test_name)?;
725+
let state = State::new(&env)?;
726+
Ok((env, state))
717727
}
718728

719729
/// Fund `address` with a single bitcoin UTXO of `value` sats, returning its
@@ -782,8 +792,9 @@ mod tests {
782792
/// spent UTXOs silently skips the unauthorized input, allowing any UTXO to
783793
/// be spent without a signature.
784794
#[test]
785-
fn validate_transaction_rejects_missing_authorization() {
786-
let (env, state) = new_state("auth_count");
795+
fn validate_transaction_rejects_missing_authorization() -> anyhow::Result<()>
796+
{
797+
let (env, state) = fresh_state("auth_count")?;
787798
let signing_key = SigningKey::from_bytes(&[1u8; 32]);
788799
let verifying_key: VerifyingKey = signing_key.verifying_key().into();
789800
let address = authorization::get_address(&verifying_key);
@@ -804,11 +815,11 @@ mod tests {
804815
transaction: transaction.clone(),
805816
authorizations: Vec::new(),
806817
};
807-
let rotxn = env.read_txn().unwrap();
818+
let rotxn = env.read_txn()?;
808819
let err = state
809820
.validate_transaction(&rotxn, &unauthorized)
810821
.expect_err("tx with no authorizations must be rejected");
811-
assert!(
822+
anyhow::ensure!(
812823
matches!(
813824
err,
814825
Error::WrongNumberOfAuthorizations {
@@ -821,21 +832,22 @@ mod tests {
821832

822833
// The same transaction with a valid authorization is accepted.
823834
let authorized =
824-
authorization::authorize(&[(address, &signing_key)], transaction)
825-
.unwrap();
835+
authorization::authorize(&[(address, &signing_key)], transaction)?;
826836
state
827837
.validate_transaction(&rotxn, &authorized)
828838
.expect("correctly authorized tx should validate");
839+
Ok(())
829840
}
830841

831842
/// A registration whose spent reservation does not commit to the
832843
/// registered name must be rejected. Otherwise it passes validation and
833844
/// later panics in `apply_registration`, which fails to find the
834845
/// reservation to burn.
835846
#[test]
836-
fn validate_bitnames_rejects_registration_without_matching_reservation() {
837-
let (env, state) = new_state("registration");
838-
let rotxn = env.read_txn().unwrap();
847+
fn validate_bitnames_rejects_registration_without_matching_reservation()
848+
-> anyhow::Result<()> {
849+
let (env, state) = fresh_state("registration")?;
850+
let rotxn = env.read_txn()?;
839851
let name_hash = BitName([7; 32]);
840852
let revealed_nonce: Hash = [3; 32];
841853
let implied_commitment: Hash =
@@ -849,7 +861,7 @@ mod tests {
849861
let err = state.validate_bitnames(&rotxn, &tx).expect_err(
850862
"registration without a matching reservation must be rejected",
851863
);
852-
assert!(
864+
anyhow::ensure!(
853865
matches!(
854866
err,
855867
Error::BitName(
@@ -864,5 +876,91 @@ mod tests {
864876
state.validate_bitnames(&rotxn, &tx).expect(
865877
"registration burning the matching reservation should validate",
866878
);
879+
Ok(())
880+
}
881+
882+
#[test]
883+
fn sidechain_wealth() -> anyhow::Result<()> {
884+
use std::str::FromStr;
885+
886+
use bitcoin::hashes::Hash as _;
887+
888+
let value_output = |sats: u64| FilledOutput {
889+
address: Address::ALL_ZEROS,
890+
content: FilledOutputContent::Bitcoin(BitcoinOutputContent(
891+
bitcoin::Amount::from_sat(sats),
892+
)),
893+
memo: Vec::new(),
894+
};
895+
let (env, state) = fresh_state("sidechain-wealth")?;
896+
{
897+
let mut rwtxn = env.write_txn()?;
898+
899+
// One unspent DEPOSIT UTXO: 50 sats.
900+
let deposit_utxo_op = OutPoint::Deposit(bitcoin::OutPoint {
901+
txid: bitcoin::Txid::from_str(
902+
"0000000000000000000000000000000000000000000000000000000000000001",
903+
)?,
904+
vout: 0,
905+
});
906+
state.utxos.put(
907+
&mut rwtxn,
908+
&OutPointKey::from(&deposit_utxo_op),
909+
&value_output(50),
910+
)?;
911+
912+
// Two spent DEPOSIT STXOs: 100 + 100 sats.
913+
for (i, sats) in [(2u8, 100u64), (3u8, 100u64)] {
914+
let op = OutPoint::Deposit(bitcoin::OutPoint {
915+
txid: bitcoin::Txid::from_byte_array([i; 32]),
916+
vout: 0,
917+
});
918+
let stxo = SpentOutput {
919+
output: value_output(sats),
920+
inpoint: InPoint::Regular {
921+
txid: [i; 32].into(),
922+
vin: 0,
923+
},
924+
};
925+
state
926+
.stxos
927+
.put(&mut rwtxn, &OutPointKey::from(&op), &stxo)?;
928+
}
929+
930+
// Two WITHDRAWAL STXOs: 10 + 10 sats
931+
for (i, sats) in [(4u8, 10u64), (5u8, 10u64)] {
932+
let op = OutPoint::Regular {
933+
txid: [i; 32].into(),
934+
vout: 0,
935+
};
936+
let stxo = SpentOutput {
937+
output: value_output(sats),
938+
inpoint: InPoint::Withdrawal {
939+
m6id: crate::types::M6id(
940+
bitcoin::Txid::from_byte_array([i; 32]),
941+
),
942+
},
943+
};
944+
state
945+
.stxos
946+
.put(&mut rwtxn, &OutPointKey::from(&op), &stxo)?;
947+
}
948+
949+
rwtxn.commit()?;
950+
}
951+
952+
let rotxn = env.read_txn()?;
953+
let sidechain_wealth = state.sidechain_wealth(&rotxn)?;
954+
955+
// Correct value: deposit UTXO 50 + deposit STXOs 200 - withdrawal
956+
// STXOs 20 = 230 sats.
957+
let expected_sidechain_wealth = bitcoin::Amount::from_sat(230);
958+
anyhow::ensure!(
959+
sidechain_wealth == expected_sidechain_wealth,
960+
"Expected sidechain wealth ({}), but computed ({})",
961+
expected_sidechain_wealth,
962+
sidechain_wealth,
963+
);
964+
Ok(())
867965
}
868966
}

lib/state/two_way_peg_data.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,7 +1317,6 @@ mod tests {
13171317
merkle_root,
13181318
prev_side_hash: None,
13191319
prev_main_hash: main0,
1320-
roots: Vec::new(),
13211320
};
13221321
{
13231322
let mut rwtxn = env.write_txn().unwrap();
@@ -1334,7 +1333,6 @@ mod tests {
13341333
merkle_root,
13351334
prev_side_hash: Some(genesis.hash()),
13361335
prev_main_hash: main1,
1337-
roots: Vec::new(),
13381336
};
13391337
let deposit_outpoint = bitcoin::OutPoint {
13401338
txid: bitcoin::Txid::from_byte_array([2; 32]),
@@ -1351,11 +1349,14 @@ mod tests {
13511349
events: vec![BlockEvent::Deposit(Deposit {
13521350
tx_index: 0,
13531351
outpoint: deposit_outpoint,
1354-
output: Output {
1352+
output: FilledOutput {
13551353
address: Address::ALL_ZEROS,
1356-
content: OutputContent::Value(
1357-
bitcoin::Amount::from_sat(1000),
1354+
content: FilledOutputContent::Bitcoin(
1355+
BitcoinOutputContent(
1356+
bitcoin::Amount::from_sat(1000),
1357+
),
13581358
),
1359+
memo: Vec::new(),
13591360
},
13601361
})],
13611362
},

0 commit comments

Comments
 (0)