Skip to content

Commit

Permalink
Fix redundant locking (ordinals#3342)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Mar 22, 2024
1 parent 9f0790a commit 5e0cfc7
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 48 deletions.
2 changes: 1 addition & 1 deletion crates/test-bitcoincore-rpc/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ impl Api for Server {
txid: output.txid,
};
assert!(state.utxos.contains_key(&output));
state.locked.insert(output);
assert!(state.locked.insert(output));
}

Ok(true)
Expand Down
10 changes: 1 addition & 9 deletions src/subcommand/wallet/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,7 @@ impl Mint {
],
};

let inscriptions = wallet
.inscriptions()
.keys()
.map(|satpoint| satpoint.outpoint)
.collect::<Vec<OutPoint>>();

if !bitcoin_client.lock_unspent(&inscriptions)? {
bail!("failed to lock UTXOs");
}
wallet.lock_non_cardinal_outputs()?;

let unsigned_transaction =
fund_raw_transaction(bitcoin_client, self.fee_rate, &unfunded_transaction)?;
Expand Down
40 changes: 2 additions & 38 deletions src/subcommand/wallet/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,43 +127,13 @@ impl Send {
})))
}

fn lock_non_cardinal_outputs(
bitcoin_client: &Client,
inscriptions: &BTreeMap<SatPoint, Vec<InscriptionId>>,
runic_outputs: &BTreeSet<OutPoint>,
unspent_outputs: &BTreeMap<OutPoint, TxOut>,
) -> Result {
let all_inscription_outputs = inscriptions
.keys()
.map(|satpoint| satpoint.outpoint)
.collect::<HashSet<OutPoint>>();

let locked_outputs = unspent_outputs
.keys()
.filter(|utxo| all_inscription_outputs.contains(utxo))
.chain(runic_outputs.iter())
.cloned()
.collect::<Vec<OutPoint>>();

if !bitcoin_client.lock_unspent(&locked_outputs)? {
bail!("failed to lock UTXOs");
}

Ok(())
}

fn create_unsigned_send_amount_transaction(
wallet: &Wallet,
destination: Address,
amount: Amount,
fee_rate: FeeRate,
) -> Result<Transaction> {
Self::lock_non_cardinal_outputs(
wallet.bitcoin_client(),
wallet.inscriptions(),
&wallet.get_runic_outputs()?,
wallet.utxos(),
)?;
wallet.lock_non_cardinal_outputs()?;

let unfunded_transaction = Transaction {
version: 2,
Expand Down Expand Up @@ -243,17 +213,11 @@ impl Send {
"sending runes with `ord send` requires index created with `--index-runes` flag",
);

let unspent_outputs = wallet.utxos();
let inscriptions = wallet.inscriptions();
let runic_outputs = wallet.get_runic_outputs()?;
let bitcoin_client = wallet.bitcoin_client();

Self::lock_non_cardinal_outputs(
bitcoin_client,
inscriptions,
&runic_outputs,
unspent_outputs,
)?;
wallet.lock_non_cardinal_outputs()?;

let (id, entry, _parent) = wallet
.get_rune(spaced_rune.rune)?
Expand Down
29 changes: 29 additions & 0 deletions src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,35 @@ impl Wallet {
&self.locked_utxos
}

pub(crate) fn lock_non_cardinal_outputs(&self) -> Result {
let inscriptions = self
.inscriptions()
.keys()
.map(|satpoint| satpoint.outpoint)
.collect::<HashSet<OutPoint>>();

let locked = self
.locked_utxos()
.keys()
.cloned()
.collect::<HashSet<OutPoint>>();

let outputs = self
.utxos()
.keys()
.filter(|utxo| inscriptions.contains(utxo))
.chain(self.get_runic_outputs()?.iter())
.cloned()
.filter(|utxo| !locked.contains(utxo))
.collect::<Vec<OutPoint>>();

if !self.bitcoin_client().lock_unspent(&outputs)? {
bail!("failed to lock UTXOs");
}

Ok(())
}

pub(crate) fn inscriptions(&self) -> &BTreeMap<SatPoint, Vec<InscriptionId>> {
&self.inscriptions
}
Expand Down
87 changes: 87 additions & 0 deletions tests/wallet/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,90 @@ fn minting_rune_with_no_rune_index_fails() {
.expected_stderr("error: `ord wallet etch` requires index created with `--index-runes` flag\n")
.run_and_extract_stdout();
}

#[test]
fn minting_rune_and_then_sending_works() {
let bitcoin_rpc_server = test_bitcoincore_rpc::builder()
.network(Network::Regtest)
.build();

let ord_rpc_server =
TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]);

bitcoin_rpc_server.mine_blocks(1);

create_wallet(&bitcoin_rpc_server, &ord_rpc_server);

batch(
&bitcoin_rpc_server,
&ord_rpc_server,
Batchfile {
etch: Some(Etch {
divisibility: 0,
rune: SpacedRune {
rune: Rune(RUNE),
spacers: 0,
},
premine: "111".parse().unwrap(),
symbol: '¢',
mint: Some(ord::wallet::inscribe::BatchMint {
term: Some(10),
limit: "21".parse().unwrap(),
deadline: None,
}),
}),
inscriptions: vec![BatchEntry {
file: "inscription.jpeg".into(),
..Default::default()
}],
..Default::default()
},
);

let balance = CommandBuilder::new("--chain regtest --index-runes wallet balance")
.bitcoin_rpc_server(&bitcoin_rpc_server)
.ord_rpc_server(&ord_rpc_server)
.run_and_deserialize_output::<ord::subcommand::wallet::balance::Output>();

assert_eq!(
*balance.runes.unwrap().first_key_value().unwrap().1,
111_u128
);

let output = CommandBuilder::new(format!(
"--chain regtest --index-runes wallet mint --fee-rate 1 --rune {}",
Rune(RUNE)
))
.bitcoin_rpc_server(&bitcoin_rpc_server)
.ord_rpc_server(&ord_rpc_server)
.run_and_deserialize_output::<mint::Output>();

bitcoin_rpc_server.mine_blocks(1);

let balance = CommandBuilder::new("--chain regtest --index-runes wallet balance")
.bitcoin_rpc_server(&bitcoin_rpc_server)
.ord_rpc_server(&ord_rpc_server)
.run_and_deserialize_output::<ord::subcommand::wallet::balance::Output>();

assert_eq!(
*balance.runes.unwrap().first_key_value().unwrap().1,
132_u128
);

pretty_assert_eq!(
output.pile,
Pile {
amount: 21,
divisibility: 0,
symbol: Some('¢'),
}
);

CommandBuilder::new(format!(
"--regtest --index-runes wallet send bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 5{} --fee-rate 1",
Rune(RUNE)
))
.bitcoin_rpc_server(&bitcoin_rpc_server)
.ord_rpc_server(&ord_rpc_server)
.run_and_deserialize_output::<ord::subcommand::wallet::send::Output>();
}

0 comments on commit 5e0cfc7

Please sign in to comment.