Skip to content

Commit c2c00b3

Browse files
1440000bytesAsh-L2L
authored andcommitted
Add test for rejecting registrations without a matching reservation
1 parent f0ba71e commit c2c00b3

1 file changed

Lines changed: 85 additions & 8 deletions

File tree

lib/state/mod.rs

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -686,20 +686,25 @@ impl Watchable<()> for State {
686686
mod tests {
687687
use ed25519_dalek::SigningKey;
688688

689-
use super::{Error, State};
690689
use crate::{
691690
authorization,
691+
state::{Error, State, error},
692692
types::{
693-
Address, AuthorizedTransaction, BitcoinOutputContent, FilledOutput,
694-
FilledOutputContent, OutPoint, OutPointKey, Output, OutputContent,
695-
Transaction, VerifyingKey,
693+
Address, AuthorizedTransaction, BitName, BitcoinOutputContent,
694+
FilledOutput, FilledOutputContent, FilledTransaction, Hash,
695+
MutableBitNameData, OutPoint, OutPointKey, Output, OutputContent,
696+
Transaction, TxData, Txid, VerifyingKey,
696697
},
697698
};
698699

699700
/// Open a fresh state in a unique temporary directory.
700-
fn new_state() -> (sneed::Env, State) {
701-
let path = std::env::temp_dir()
702-
.join(format!("plain_bitnames_auth_count_{}", std::process::id()));
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);
703708
let _remove_result = std::fs::remove_dir_all(&path);
704709
std::fs::create_dir_all(&path).unwrap();
705710
let env = {
@@ -738,13 +743,47 @@ mod tests {
738743
outpoint
739744
}
740745

746+
/// Build a BitName registration that registers `name_hash` with
747+
/// `revealed_nonce`, while spending a single reservation that commits to
748+
/// `reservation_commitment`.
749+
fn registration_tx(
750+
name_hash: BitName,
751+
revealed_nonce: Hash,
752+
reservation_commitment: Hash,
753+
) -> FilledTransaction {
754+
let address = Address([0; 20]);
755+
let mut transaction = Transaction::new(
756+
vec![OutPoint::Regular {
757+
txid: Txid([0; 32]),
758+
vout: 0,
759+
}],
760+
vec![Output::new(address, OutputContent::BitName)],
761+
);
762+
transaction.data = Some(TxData::BitNameRegistration {
763+
name_hash,
764+
revealed_nonce,
765+
bitname_data: Box::new(MutableBitNameData::default()),
766+
});
767+
let reservation = FilledOutput::new(
768+
address,
769+
FilledOutputContent::BitNameReservation(
770+
Txid([0; 32]),
771+
reservation_commitment,
772+
),
773+
);
774+
FilledTransaction {
775+
transaction,
776+
spent_utxos: vec![reservation],
777+
}
778+
}
779+
741780
/// A transaction that spends an input without supplying an authorization
742781
/// for it must be rejected. Otherwise the `zip` of authorizations and
743782
/// spent UTXOs silently skips the unauthorized input, allowing any UTXO to
744783
/// be spent without a signature.
745784
#[test]
746785
fn validate_transaction_rejects_missing_authorization() {
747-
let (env, state) = new_state();
786+
let (env, state) = new_state("auth_count");
748787
let signing_key = SigningKey::from_bytes(&[1u8; 32]);
749788
let verifying_key: VerifyingKey = signing_key.verifying_key().into();
750789
let address = authorization::get_address(&verifying_key);
@@ -788,4 +827,42 @@ mod tests {
788827
.validate_transaction(&rotxn, &authorized)
789828
.expect("correctly authorized tx should validate");
790829
}
830+
831+
/// A registration whose spent reservation does not commit to the
832+
/// registered name must be rejected. Otherwise it passes validation and
833+
/// later panics in `apply_registration`, which fails to find the
834+
/// reservation to burn.
835+
#[test]
836+
fn validate_bitnames_rejects_registration_without_matching_reservation() {
837+
let (env, state) = new_state("registration");
838+
let rotxn = env.read_txn().unwrap();
839+
let name_hash = BitName([7; 32]);
840+
let revealed_nonce: Hash = [3; 32];
841+
let implied_commitment: Hash =
842+
blake3::keyed_hash(&revealed_nonce, &name_hash.0).into();
843+
844+
// The reservation commits to something other than the registered name.
845+
let mismatched_commitment: Hash = [0; 32];
846+
assert_ne!(mismatched_commitment, implied_commitment);
847+
let tx =
848+
registration_tx(name_hash, revealed_nonce, mismatched_commitment);
849+
let err = state.validate_bitnames(&rotxn, &tx).expect_err(
850+
"registration without a matching reservation must be rejected",
851+
);
852+
assert!(
853+
matches!(
854+
err,
855+
Error::BitName(
856+
error::BitName::NoReservationForRegistration { bitname }
857+
) if bitname == name_hash
858+
),
859+
"unexpected error: {err:?}"
860+
);
861+
862+
// The same registration burning the matching reservation is accepted.
863+
let tx = registration_tx(name_hash, revealed_nonce, implied_commitment);
864+
state.validate_bitnames(&rotxn, &tx).expect(
865+
"registration burning the matching reservation should validate",
866+
);
867+
}
791868
}

0 commit comments

Comments
 (0)