Skip to content

Commit 56b4e5c

Browse files
committed
Add test for rejecting registrations without a matching reservation
1 parent d8b85c7 commit 56b4e5c

1 file changed

Lines changed: 100 additions & 0 deletions

File tree

lib/state/mod.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,3 +670,103 @@ impl Watchable<()> for State {
670670
tokio_stream::wrappers::WatchStream::new(self.tip.watch().clone())
671671
}
672672
}
673+
674+
#[cfg(test)]
675+
mod tests {
676+
use super::{Error, State, error};
677+
use crate::types::{
678+
Address, BitName, FilledOutput, FilledOutputContent, FilledTransaction,
679+
Hash, MutableBitNameData, OutPoint, Output, OutputContent, Transaction,
680+
TxData, Txid,
681+
};
682+
683+
/// Open a fresh state in a unique temporary directory.
684+
fn new_state() -> (sneed::Env, State) {
685+
let path = std::env::temp_dir().join(format!(
686+
"plain_bitnames_registration_{}",
687+
std::process::id()
688+
));
689+
let _remove_result = std::fs::remove_dir_all(&path);
690+
std::fs::create_dir_all(&path).unwrap();
691+
let env = {
692+
let mut opts = heed::EnvOpenOptions::new();
693+
opts.map_size(10 * 1024 * 1024).max_dbs(State::NUM_DBS);
694+
unsafe { sneed::Env::open(&opts, &path) }.unwrap()
695+
};
696+
let state = State::new(&env).unwrap();
697+
(env, state)
698+
}
699+
700+
/// Build a BitName registration that registers `name_hash` with
701+
/// `revealed_nonce`, while spending a single reservation that commits to
702+
/// `reservation_commitment`.
703+
fn registration_tx(
704+
name_hash: BitName,
705+
revealed_nonce: Hash,
706+
reservation_commitment: Hash,
707+
) -> FilledTransaction {
708+
let address = Address([0; 20]);
709+
let mut transaction = Transaction::new(
710+
vec![OutPoint::Regular {
711+
txid: Txid([0; 32]),
712+
vout: 0,
713+
}],
714+
vec![Output::new(address, OutputContent::BitName)],
715+
);
716+
transaction.data = Some(TxData::BitNameRegistration {
717+
name_hash,
718+
revealed_nonce,
719+
bitname_data: Box::new(MutableBitNameData::default()),
720+
});
721+
let reservation = FilledOutput::new(
722+
address,
723+
FilledOutputContent::BitNameReservation(
724+
Txid([0; 32]),
725+
reservation_commitment,
726+
),
727+
);
728+
FilledTransaction {
729+
transaction,
730+
spent_utxos: vec![reservation],
731+
}
732+
}
733+
734+
/// A registration whose spent reservation does not commit to the
735+
/// registered name must be rejected. Otherwise it passes validation and
736+
/// later panics in `apply_registration`, which fails to find the
737+
/// reservation to burn.
738+
#[test]
739+
fn validate_bitnames_rejects_registration_without_matching_reservation() {
740+
let (env, state) = new_state();
741+
let rotxn = env.read_txn().unwrap();
742+
let name_hash = BitName([7; 32]);
743+
let revealed_nonce: Hash = [3; 32];
744+
let implied_commitment: Hash =
745+
blake3::keyed_hash(&revealed_nonce, &name_hash.0).into();
746+
747+
// The reservation commits to something other than the registered name.
748+
let mismatched_commitment: Hash = [0; 32];
749+
assert_ne!(mismatched_commitment, implied_commitment);
750+
let tx =
751+
registration_tx(name_hash, revealed_nonce, mismatched_commitment);
752+
let err = state.validate_bitnames(&rotxn, &tx).expect_err(
753+
"registration without a matching reservation must be rejected",
754+
);
755+
assert!(
756+
matches!(
757+
err,
758+
Error::BitName(
759+
error::BitName::NoReservationForRegistration { bitname }
760+
) if bitname == name_hash
761+
),
762+
"unexpected error: {err:?}"
763+
);
764+
765+
// The same registration burning the matching reservation is accepted.
766+
let tx =
767+
registration_tx(name_hash, revealed_nonce, implied_commitment);
768+
state.validate_bitnames(&rotxn, &tx).expect(
769+
"registration burning the matching reservation should validate",
770+
);
771+
}
772+
}

0 commit comments

Comments
 (0)