Skip to content

Commit

Permalink
Set maxburnamount when burning and require at least Bitcoin Core 25 (
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Nov 26, 2024
1 parent a009a4e commit f150529
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 23 deletions.
7 changes: 6 additions & 1 deletion crates/mockcore/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ pub trait Api {
) -> Result<Value, jsonrpc_core::Error>;

#[rpc(name = "sendrawtransaction")]
fn send_raw_transaction(&self, tx: String) -> Result<String, jsonrpc_core::Error>;
fn send_raw_transaction(
&self,
tx: String,
maxfeerate: Option<()>,
maxburnamount: Option<f64>,
) -> Result<String, jsonrpc_core::Error>;

#[rpc(name = "sendtoaddress")]
fn send_to_address(
Expand Down
2 changes: 1 addition & 1 deletion crates/mockcore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub fn builder() -> Builder {
Builder {
fail_lock_unspent: false,
network: Network::Bitcoin,
version: 240000,
version: 250000,
}
}

Expand Down
31 changes: 29 additions & 2 deletions crates/mockcore/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use {
super::*,
base64::Engine,
bitcoin::{consensus::Decodable, psbt::Psbt, Witness},
bitcoin::{consensus::Decodable, opcodes, psbt::Psbt, script::Instruction, Witness},
bitcoincore_rpc::json::StringOrStringArray,
};

Expand Down Expand Up @@ -506,9 +506,36 @@ impl Api for Server {
)
}

fn send_raw_transaction(&self, tx: String) -> Result<String, jsonrpc_core::Error> {
fn send_raw_transaction(
&self,
tx: String,
maxfeerate: Option<()>,
maxburnamount: Option<f64>,
) -> Result<String, jsonrpc_core::Error> {
assert!(
maxfeerate.is_none(),
"sendrawtransaction: maxfeerate is not supported"
);

let tx: Transaction = deserialize(&hex::decode(tx).unwrap()).unwrap();

let burnt = tx
.output
.iter()
.filter(|tx_out| {
tx_out.script_pubkey.instructions().next()
== Some(Ok(Instruction::Op(opcodes::all::OP_RETURN)))
})
.map(|tx_out| tx_out.value)
.sum::<Amount>();

let maxburnamount = Amount::from_btc(maxburnamount.unwrap_or_default()).unwrap();

assert!(
burnt <= maxburnamount,
"burnt amount greater than maxburnamount: {burnt} > {maxburnamount}",
);

let mut state = self.state.lock().unwrap();

for tx_in in &tx.input {
Expand Down
35 changes: 26 additions & 9 deletions src/subcommand/wallet/burn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,25 @@ impl Burn {

let mut builder = script::Builder::new().push_opcode(opcodes::all::OP_RETURN);

if let Some(metadata) = metadata {
let push: &script::PushBytes = metadata.as_slice().try_into().with_context(|| {
format!(
"metadata length {} over maximum {}",
metadata.len(),
u32::MAX
)
})?;
// add empty metadata if none is supplied so we can add padding
let metadata = metadata.unwrap_or_default();

let push: &script::PushBytes = metadata.as_slice().try_into().with_context(|| {
format!(
"metadata length {} over maximum {}",
metadata.len(),
u32::MAX
)
})?;
builder = builder.push_slice(push);

// pad OP_RETURN script to least five bytes to ensure transaction base size
// is greater than 64 bytes
let padding = 5usize.saturating_sub(builder.as_script().len());
if padding > 0 {
// subtract one byte push opcode from padding length
let padding = vec![0; padding - 1];
let push: &script::PushBytes = padding.as_slice().try_into().unwrap();
builder = builder.push_slice(push);
}

Expand All @@ -93,8 +104,14 @@ impl Burn {
script_pubkey,
)?;

let base_size = unsigned_transaction.base_size();
assert!(
base_size >= 65,
"transaction base size less than minimum standard tx nonwitness size: {base_size} < 65",
);

let (txid, psbt, fee) =
wallet.sign_and_broadcast_transaction(unsigned_transaction, self.dry_run)?;
wallet.sign_and_broadcast_transaction(unsigned_transaction, self.dry_run, Some(value))?;

Ok(Some(Box::new(send::Output {
txid,
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/wallet/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl Send {
};

let (txid, psbt, fee) =
wallet.sign_and_broadcast_transaction(unsigned_transaction, self.dry_run)?;
wallet.sign_and_broadcast_transaction(unsigned_transaction, self.dry_run, None)?;

Ok(Some(Box::new(Output {
txid,
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/wallet/split.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ impl Split {
let unsigned_transaction = consensus::encode::deserialize(&unsigned_transaction)?;

let (txid, psbt, fee) =
wallet.sign_and_broadcast_transaction(unsigned_transaction, self.dry_run)?;
wallet.sign_and_broadcast_transaction(unsigned_transaction, self.dry_run, None)?;

Ok(Some(Box::new(Output { txid, psbt, fee })))
}
Expand Down
27 changes: 22 additions & 5 deletions src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ impl Wallet {
}

pub(crate) fn check_version(client: Client) -> Result<Client> {
const MIN_VERSION: usize = 240000;
const MIN_VERSION: usize = 250000;

let bitcoin_version = client.version()?;
if bitcoin_version < MIN_VERSION {
Expand Down Expand Up @@ -743,6 +743,7 @@ impl Wallet {
&self,
unsigned_transaction: Transaction,
dry_run: bool,
burn_amount: Option<Amount>,
) -> Result<(Txid, String, u64)> {
let unspent_outputs = self.utxos();

Expand Down Expand Up @@ -777,10 +778,7 @@ impl Wallet {
.hex
.ok_or_else(|| anyhow!("unable to sign transaction"))?;

(
self.bitcoin_client().send_raw_transaction(&signed_tx)?,
psbt,
)
(self.send_raw_transaction(&signed_tx, burn_amount)?, psbt)
};

let mut fee = 0;
Expand All @@ -797,4 +795,23 @@ impl Wallet {

Ok((txid, psbt, fee))
}

fn send_raw_transaction<R: bitcoincore_rpc::RawTx>(
&self,
tx: R,
burn_amount: Option<Amount>,
) -> Result<Txid> {
let mut arguments = vec![tx.raw_hex().into()];

if let Some(burn_amount) = burn_amount {
arguments.push(serde_json::Value::Null);
arguments.push(burn_amount.to_btc().into());
}

Ok(
self
.bitcoin_client()
.call("sendrawtransaction", &arguments)?,
)
}
}
2 changes: 1 addition & 1 deletion tests/wallet/burn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn inscriptions_can_be_burned() {
<span title=burned>🔥</span>
</dd>
<dt>value</dt>
<dd>9922</dd>
<dd>9918</dd>
.*
<dt>content length</dt>
<dd>3 bytes</dd>
Expand Down
4 changes: 2 additions & 2 deletions tests/wallet/inscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ fn metaprotocol_appears_on_inscription_page() {

#[test]
fn inscribe_fails_if_bitcoin_core_is_too_old() {
let core = mockcore::builder().version(230000).build();
let core = mockcore::builder().version(240000).build();
let ord = TestServer::spawn(&core);

CommandBuilder::new("wallet inscribe --file hello.txt --fee-rate 1")
.write("hello.txt", "HELLOWORLD")
.expected_exit_code(1)
.expected_stderr("error: Bitcoin Core 24.0.0 or newer required, current version is 23.0.0\n")
.expected_stderr("error: Bitcoin Core 25.0.0 or newer required, current version is 24.0.0\n")
.core(&core)
.ord(&ord)
.run_and_extract_stdout();
Expand Down

0 comments on commit f150529

Please sign in to comment.