From d535fa9c4cb5f70a0accf7d98c92afb2e0236634 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 29 Mar 2024 16:47:10 -0700 Subject: [PATCH 1/2] Set relative lock heights on etching transactions --- crates/ordinals/src/runestone.rs | 1 + crates/test-bitcoincore-rpc/src/server.rs | 25 ++++++++++++++++++++++- src/index/testing.rs | 5 ++--- src/index/updater/rune_updater.rs | 2 +- src/into_usize.rs | 6 ++++++ src/lib.rs | 1 - src/runes.rs | 13 +++++++----- src/subcommand/server.rs | 2 +- src/subcommand/wallet/batch_command.rs | 2 +- src/wallet/batch/plan.rs | 15 +++++++++++--- tests/balances.rs | 4 ++-- tests/json_api.rs | 18 ++++++++-------- tests/lib.rs | 14 ++++++------- tests/runes.rs | 6 +++--- tests/wallet/batch_command.rs | 17 +++++++++------ tests/wallet/selection.rs | 22 ++++++++++++++------ 16 files changed, 104 insertions(+), 49 deletions(-) diff --git a/crates/ordinals/src/runestone.rs b/crates/ordinals/src/runestone.rs index ac0cc86d1b..aa0271574b 100644 --- a/crates/ordinals/src/runestone.rs +++ b/crates/ordinals/src/runestone.rs @@ -21,6 +21,7 @@ enum Payload { impl Runestone { pub const MAGIC_NUMBER: opcodes::All = opcodes::all::OP_PUSHNUM_13; + pub const COMMIT_INTERVAL: u16 = 6; pub fn from_transaction(transaction: &Transaction) -> Option { Self::decipher(transaction).ok().flatten() diff --git a/crates/test-bitcoincore-rpc/src/server.rs b/crates/test-bitcoincore-rpc/src/server.rs index 67fffa18c4..e9ed3fffbf 100644 --- a/crates/test-bitcoincore-rpc/src/server.rs +++ b/crates/test-bitcoincore-rpc/src/server.rs @@ -477,7 +477,30 @@ impl Api for Server { fn send_raw_transaction(&self, tx: String) -> Result { let tx: Transaction = deserialize(&hex::decode(tx).unwrap()).unwrap(); - self.state.lock().unwrap().mempool.push(tx.clone()); + let mut state = self.state.lock().unwrap(); + + for tx_in in &tx.input { + if let Some(lock_time) = tx_in.sequence.to_relative_lock_time() { + match lock_time { + bitcoin::relative::LockTime::Blocks(blocks) => { + if state + .txid_to_block_height + .get(&tx_in.previous_output.txid) + .expect("input has not been miined") + + u32::from(blocks.value()) + > u32::try_from(state.hashes.len()).unwrap() + { + panic!("input is locked"); + } + } + bitcoin::relative::LockTime::Time(_) => { + panic!("time-based relative locktimes are not implemented") + } + } + } + } + + state.mempool.push(tx.clone()); Ok(tx.txid().to_string()) } diff --git a/src/index/testing.rs b/src/index/testing.rs index 8d8fed3d6e..6447be445a 100644 --- a/src/index/testing.rs +++ b/src/index/testing.rs @@ -163,7 +163,7 @@ impl Context { ..default() }); - self.mine_blocks(RUNE_COMMIT_INTERVAL.into()); + self.mine_blocks(Runestone::COMMIT_INTERVAL.into()); let mut witness = Witness::new(); @@ -199,8 +199,7 @@ impl Context { ( txid, RuneId { - block: u64::try_from(block_count + usize::try_from(RUNE_COMMIT_INTERVAL).unwrap() + 1) - .unwrap(), + block: u64::try_from(block_count + Runestone::COMMIT_INTERVAL.into_usize() + 1).unwrap(), tx: 1, }, ) diff --git a/src/index/updater/rune_updater.rs b/src/index/updater/rune_updater.rs index 8490dacf7e..d71e016794 100644 --- a/src/index/updater/rune_updater.rs +++ b/src/index/updater/rune_updater.rs @@ -387,7 +387,7 @@ impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> { let mature = tx_info .confirmations - .map(|confirmations| confirmations >= RUNE_COMMIT_INTERVAL) + .map(|confirmations| confirmations >= Runestone::COMMIT_INTERVAL.into()) .unwrap_or_default(); if taproot && mature { diff --git a/src/into_usize.rs b/src/into_usize.rs index 5d773b9515..9dcac8a66b 100644 --- a/src/into_usize.rs +++ b/src/into_usize.rs @@ -2,6 +2,12 @@ pub(crate) trait IntoUsize { fn into_usize(self) -> usize; } +impl IntoUsize for u16 { + fn into_usize(self) -> usize { + self.try_into().unwrap() + } +} + impl IntoUsize for u32 { fn into_usize(self) -> usize { self.try_into().unwrap() diff --git a/src/lib.rs b/src/lib.rs index b741df41d0..2343af123f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,7 +131,6 @@ pub mod wallet; type Result = std::result::Result; -const RUNE_COMMIT_INTERVAL: u32 = 6; const TARGET_POSTAGE: Amount = Amount::from_sat(10_000); static SHUTTING_DOWN: AtomicBool = AtomicBool::new(false); diff --git a/src/runes.rs b/src/runes.rs index 39386b5290..171e255e47 100644 --- a/src/runes.rs +++ b/src/runes.rs @@ -145,8 +145,11 @@ mod tests { #[test] fn runes_must_be_greater_than_or_equal_to_minimum_for_height() { - let minimum = - Rune::minimum_at_height(Chain::Regtest.network(), Height(RUNE_COMMIT_INTERVAL + 2)).0; + let minimum = Rune::minimum_at_height( + Chain::Regtest.network(), + Height((Runestone::COMMIT_INTERVAL + 2).into()), + ) + .0; { let context = Context::builder() @@ -5211,7 +5214,7 @@ mod tests { ..default() }); - context.mine_blocks(RUNE_COMMIT_INTERVAL.into()); + context.mine_blocks(Runestone::COMMIT_INTERVAL.into()); let mut witness = Witness::new(); @@ -5271,7 +5274,7 @@ mod tests { ..default() }); - context.mine_blocks((RUNE_COMMIT_INTERVAL - 1).into()); + context.mine_blocks((Runestone::COMMIT_INTERVAL - 1).into()); let mut witness = Witness::new(); @@ -5331,7 +5334,7 @@ mod tests { ..default() }); - context.mine_blocks(RUNE_COMMIT_INTERVAL.into()); + context.mine_blocks(Runestone::COMMIT_INTERVAL.into()); let mut witness = Witness::new(); diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 1540089afd..7a1b8e7702 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -2117,7 +2117,7 @@ mod tests { ..default() }); - self.mine_blocks(RUNE_COMMIT_INTERVAL.into()); + self.mine_blocks(Runestone::COMMIT_INTERVAL.into()); let witness = witness.unwrap_or_else(|| { let tapscript = script::Builder::new() diff --git a/src/subcommand/wallet/batch_command.rs b/src/subcommand/wallet/batch_command.rs index 7c0e8727b9..88c77a5ea8 100644 --- a/src/subcommand/wallet/batch_command.rs +++ b/src/subcommand/wallet/batch_command.rs @@ -115,7 +115,7 @@ impl Batch { let current_height = u32::try_from(bitcoin_client.get_block_count()?).unwrap(); - let reveal_height = current_height + 1 + RUNE_COMMIT_INTERVAL; + let reveal_height = current_height + 1 + u32::from(Runestone::COMMIT_INTERVAL); if let Some(terms) = etching.terms { if let Some((start, end)) = terms.offset.and_then(|range| range.start.zip(range.end)) { diff --git a/src/wallet/batch/plan.rs b/src/wallet/batch/plan.rs index 2d935934ec..2f09e1ab62 100644 --- a/src/wallet/batch/plan.rs +++ b/src/wallet/batch/plan.rs @@ -136,7 +136,9 @@ impl Plan { .into_option()?; if let Some(transaction) = transaction { - if u32::try_from(transaction.info.confirmations).unwrap() < RUNE_COMMIT_INTERVAL { + if u32::try_from(transaction.info.confirmations).unwrap() + < Runestone::COMMIT_INTERVAL.into() + { continue; } } @@ -146,7 +148,7 @@ impl Plan { .get_tx_out(&commit_tx.txid(), 0, Some(true))?; if let Some(tx_out) = tx_out { - if tx_out.confirmations >= RUNE_COMMIT_INTERVAL { + if tx_out.confirmations >= Runestone::COMMIT_INTERVAL.into() { break; } } @@ -518,6 +520,7 @@ impl Plan { reveal_outputs.clone(), reveal_inputs.clone(), &reveal_script, + rune.is_some(), ); let mut target_value = reveal_fee; @@ -562,6 +565,7 @@ impl Plan { reveal_outputs.clone(), reveal_inputs, &reveal_script, + rune.is_some(), ); for output in reveal_tx.output.iter() { @@ -713,6 +717,7 @@ impl Plan { output: Vec, input: Vec, script: &Script, + etching: bool, ) -> (Transaction, Amount) { let reveal_tx = Transaction { input: input @@ -721,7 +726,11 @@ impl Plan { previous_output, script_sig: script::Builder::new().into_script(), witness: Witness::new(), - sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + sequence: if etching { + Sequence::from_height(Runestone::COMMIT_INTERVAL) + } else { + Sequence::ENABLE_RBF_NO_LOCKTIME + }, }) .collect(), output, diff --git a/tests/balances.rs b/tests/balances.rs index 9be45d4a19..83c2585999 100644 --- a/tests/balances.rs +++ b/tests/balances.rs @@ -57,7 +57,7 @@ fn with_runes() { SpacedRune::new(Rune(RUNE), 0), vec![( OutPoint { - txid: a.inscribe.reveal, + txid: a.output.reveal, vout: 1 }, Pile { @@ -73,7 +73,7 @@ fn with_runes() { SpacedRune::new(Rune(RUNE + 1), 0), vec![( OutPoint { - txid: b.inscribe.reveal, + txid: b.output.reveal, vout: 1 }, Pile { diff --git a/tests/json_api.rs b/tests/json_api.rs index e9a9e70896..cea115ffe6 100644 --- a/tests/json_api.rs +++ b/tests/json_api.rs @@ -538,7 +538,7 @@ fn get_runes() { bitcoin_rpc_server.mine_blocks(1); - let response = ord_rpc_server.json_request(format!("/rune/{}", a.inscribe.rune.unwrap().rune)); + let response = ord_rpc_server.json_request(format!("/rune/{}", a.output.rune.unwrap().rune)); assert_eq!(response.status(), StatusCode::OK); let rune_json: api::Rune = serde_json::from_str(&response.text().unwrap()).unwrap(); @@ -551,7 +551,7 @@ fn get_runes() { burned: 0, terms: None, divisibility: 0, - etching: a.inscribe.reveal, + etching: a.output.reveal, mints: 0, number: 0, premine: 1000, @@ -565,7 +565,7 @@ fn get_runes() { id: RuneId { block: 11, tx: 1 }, mintable: false, parent: Some(InscriptionId { - txid: a.inscribe.reveal, + txid: a.output.reveal, index: 0, }), } @@ -588,7 +588,7 @@ fn get_runes() { burned: 0, terms: None, divisibility: 0, - etching: a.inscribe.reveal, + etching: a.output.reveal, mints: 0, number: 0, premine: 1000, @@ -607,7 +607,7 @@ fn get_runes() { burned: 0, terms: None, divisibility: 0, - etching: b.inscribe.reveal, + etching: b.output.reveal, mints: 0, number: 1, premine: 1000, @@ -626,7 +626,7 @@ fn get_runes() { burned: 0, terms: None, divisibility: 0, - etching: c.inscribe.reveal, + etching: c.output.reveal, mints: 0, number: 2, premine: 1000, @@ -670,7 +670,7 @@ fn get_runes_balances() { rune0, vec![( OutPoint { - txid: e0.inscribe.reveal, + txid: e0.output.reveal, vout: 1, }, 1000, @@ -682,7 +682,7 @@ fn get_runes_balances() { rune1, vec![( OutPoint { - txid: e1.inscribe.reveal, + txid: e1.output.reveal, vout: 1, }, 1000, @@ -694,7 +694,7 @@ fn get_runes_balances() { rune2, vec![( OutPoint { - txid: e2.inscribe.reveal, + txid: e2.output.reveal, vout: 1, }, 1000, diff --git a/tests/lib.rs b/tests/lib.rs index f35a722f80..ebf0b3fb9f 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -5,7 +5,7 @@ use { bitcoin::{ address::{Address, NetworkUnchecked}, blockdata::constants::COIN_VALUE, - Network, OutPoint, Txid, Witness, + Network, OutPoint, Sequence, Txid, Witness, }, bitcoincore_rpc::bitcoincore_rpc_json::ListDescriptorsResult, chrono::{DateTime, Utc}, @@ -157,7 +157,7 @@ fn drain(bitcoin_rpc_server: &test_bitcoincore_rpc::Handle, ord_rpc_server: &Tes struct Etched { id: RuneId, - inscribe: Batch, + output: Batch, } fn etch( @@ -215,7 +215,7 @@ fn batch( bitcoin_rpc_server.mine_blocks(6); - let inscribe = spawn.run_and_deserialize_output::(); + let output = spawn.run_and_deserialize_output::(); bitcoin_rpc_server.mine_blocks(1); @@ -226,8 +226,8 @@ fn batch( tx: 1, }; - let reveal = inscribe.reveal; - let parent = inscribe.inscriptions[0].id; + let reveal = output.reveal; + let parent = output.inscriptions[0].id; let batch::Etching { divisibility, @@ -364,7 +364,7 @@ fn batch( destination, location, rune, - } = inscribe.rune.clone().unwrap(); + } = output.rune.clone().unwrap(); if premine.to_integer(divisibility).unwrap() > 0 { let destination = destination @@ -417,7 +417,7 @@ fn batch( } } - Etched { inscribe, id } + Etched { output, id } } fn envelope(payload: &[&[u8]]) -> Witness { diff --git a/tests/runes.rs b/tests/runes.rs index ae561609b1..6a499447ff 100644 --- a/tests/runes.rs +++ b/tests/runes.rs @@ -56,7 +56,7 @@ fn one_rune() { block: 8, burned: 0, divisibility: 0, - etching: etch.inscribe.reveal, + etching: etch.output.reveal, id: RuneId { block: 8, tx: 1 }, terms: None, mints: 0, @@ -104,7 +104,7 @@ fn two_runes() { block: 8, burned: 0, divisibility: 0, - etching: a.inscribe.reveal, + etching: a.output.reveal, id: RuneId { block: 8, tx: 1 }, terms: None, mints: 0, @@ -126,7 +126,7 @@ fn two_runes() { block: 16, burned: 0, divisibility: 0, - etching: b.inscribe.reveal, + etching: b.output.reveal, id: RuneId { block: 16, tx: 1 }, terms: None, mints: 0, diff --git a/tests/wallet/batch_command.rs b/tests/wallet/batch_command.rs index bd46adf0fe..af8c278f46 100644 --- a/tests/wallet/batch_command.rs +++ b/tests/wallet/batch_command.rs @@ -1458,7 +1458,7 @@ fn batch_inscribe_can_etch_rune() { }, ); - let parent = batch.inscribe.inscriptions[0].id; + let parent = batch.output.inscriptions[0].id; let request = ord_rpc_server.request(format!("/content/{parent}")); @@ -1480,7 +1480,7 @@ fn batch_inscribe_can_etch_rune() { assert!(bitcoin_rpc_server.state().is_wallet_address( &batch - .inscribe + .output .rune .unwrap() .destination @@ -1488,6 +1488,11 @@ fn batch_inscribe_can_etch_rune() { .require_network(Network::Regtest) .unwrap() )); + + assert_eq!( + bitcoin_rpc_server.tx_by_id(batch.output.reveal).input[0].sequence, + Sequence::from_height(Runestone::COMMIT_INTERVAL) + ); } #[test] @@ -1534,7 +1539,7 @@ fn batch_inscribe_can_etch_rune_with_offset() { }, ); - let parent = batch.inscribe.inscriptions[0].id; + let parent = batch.output.inscriptions[0].id; let request = ord_rpc_server.request(format!("/content/{parent}")); @@ -1556,7 +1561,7 @@ fn batch_inscribe_can_etch_rune_with_offset() { assert!(bitcoin_rpc_server.state().is_wallet_address( &batch - .inscribe + .output .rune .unwrap() .destination @@ -1610,7 +1615,7 @@ fn batch_inscribe_can_etch_rune_with_height() { }, ); - let parent = batch.inscribe.inscriptions[0].id; + let parent = batch.output.inscriptions[0].id; let request = ord_rpc_server.request(format!("/content/{parent}")); @@ -1632,7 +1637,7 @@ fn batch_inscribe_can_etch_rune_with_height() { assert!(bitcoin_rpc_server.state().is_wallet_address( &batch - .inscribe + .output .rune .unwrap() .destination diff --git a/tests/wallet/selection.rs b/tests/wallet/selection.rs index e672dab3be..c55a24edb3 100644 --- a/tests/wallet/selection.rs +++ b/tests/wallet/selection.rs @@ -62,12 +62,22 @@ fn send_satpoint_does_not_send_runic_utxos() { let etched = etch(&bitcoin_rpc_server, &ord_rpc_server, Rune(RUNE)); - CommandBuilder::new(format!("--regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw {}:0", etched.inscribe.rune.unwrap().location.unwrap())) - .bitcoin_rpc_server(&bitcoin_rpc_server) - .ord_rpc_server(&ord_rpc_server) - .expected_stderr("error: runic outpoints may not be sent by satpoint\n") - .expected_exit_code(1) - .run_and_extract_stdout(); + CommandBuilder::new(format!( + " + --regtest + --index-runes + wallet + send + --fee-rate 1 + bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw + {}:0", + etched.output.rune.unwrap().location.unwrap() + )) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .expected_stderr("error: runic outpoints may not be sent by satpoint\n") + .expected_exit_code(1) + .run_and_extract_stdout(); } #[test] From 84c3f903377431e267fdfd8a2e10f76385027d12 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 29 Mar 2024 16:58:13 -0700 Subject: [PATCH 2/2] Adjust --- src/index/testing.rs | 2 +- src/into_usize.rs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/index/testing.rs b/src/index/testing.rs index 6447be445a..6c8c3705e0 100644 --- a/src/index/testing.rs +++ b/src/index/testing.rs @@ -199,7 +199,7 @@ impl Context { ( txid, RuneId { - block: u64::try_from(block_count + Runestone::COMMIT_INTERVAL.into_usize() + 1).unwrap(), + block: u64::try_from(block_count + usize::from(Runestone::COMMIT_INTERVAL) + 1).unwrap(), tx: 1, }, ) diff --git a/src/into_usize.rs b/src/into_usize.rs index 9dcac8a66b..5d773b9515 100644 --- a/src/into_usize.rs +++ b/src/into_usize.rs @@ -2,12 +2,6 @@ pub(crate) trait IntoUsize { fn into_usize(self) -> usize; } -impl IntoUsize for u16 { - fn into_usize(self) -> usize { - self.try_into().unwrap() - } -} - impl IntoUsize for u32 { fn into_usize(self) -> usize { self.try_into().unwrap()