From 1bb198a0e3cbca2e1c69e4133c2eced1cdbbf5b1 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 27 Mar 2024 19:53:09 -0700 Subject: [PATCH] Ignore errors in invalid scripts --- src/index/updater/rune_updater.rs | 5 +- src/runes.rs | 35 ++++++++++++++ src/runes/runestone.rs | 76 +++++++++++++++++++++++++------ 3 files changed, 101 insertions(+), 15 deletions(-) diff --git a/src/index/updater/rune_updater.rs b/src/index/updater/rune_updater.rs index 3cecf9401d..45bdecbda7 100644 --- a/src/index/updater/rune_updater.rs +++ b/src/index/updater/rune_updater.rs @@ -362,7 +362,10 @@ impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> { }; for instruction in tapscript.instructions() { - let instruction = instruction?; + // ignore errors, since the extracted script may not be valid + let Ok(instruction) = instruction else { + break; + }; let Some(pushbytes) = instruction.push_bytes() else { continue; diff --git a/src/runes.rs b/src/runes.rs index e9fa662c8d..1c9628d4d5 100644 --- a/src/runes.rs +++ b/src/runes.rs @@ -5392,4 +5392,39 @@ mod tests { context.assert_runes([], []); } + + #[test] + fn tx_commits_to_rune_ignores_invalid_script() { + let context = Context::builder().arg("--index-runes").build(); + + context.mine_blocks(1); + + let runestone = Runestone { + etching: Some(Etching { + rune: Some(Rune(RUNE)), + terms: Some(Terms { + amount: Some(1000), + ..default() + }), + ..default() + }), + ..default() + }; + + let mut witness = Witness::new(); + + witness.push([opcodes::all::OP_PUSHDATA4.to_u8()]); + witness.push([]); + + context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, witness)], + op_return: Some(runestone.encipher()), + outputs: 1, + ..default() + }); + + context.mine_blocks(1); + + context.assert_runes([], []); + } } diff --git a/src/runes/runestone.rs b/src/runes/runestone.rs index 28c2bb1334..4de223e374 100644 --- a/src/runes/runestone.rs +++ b/src/runes/runestone.rs @@ -17,6 +17,7 @@ struct Message { fields: HashMap>, } +#[derive(Debug, PartialEq)] enum Payload { Valid(Vec), Invalid, @@ -264,8 +265,9 @@ impl Runestone { continue; } - // followed by the protocol identifier - if instructions.next().transpose()? != Some(Instruction::Op(MAGIC_NUMBER)) { + // followed by the protocol identifier, ignoring errors, since OP_RETURN + // scripts may be invalid + if instructions.next().transpose().ok().flatten() != Some(Instruction::Op(MAGIC_NUMBER)) { continue; } @@ -273,7 +275,7 @@ impl Runestone { let mut payload = Vec::new(); for result in instructions { - if let Instruction::PushBytes(push) = result? { + if let Ok(Instruction::PushBytes(push)) = result { payload.extend_from_slice(push.as_bytes()); } else { return Ok(Some(Payload::Invalid)); @@ -433,7 +435,7 @@ mod tests { } #[test] - fn deciphering_valid_runestone_with_invalid_script_postfix_returns_script_error() { + fn deciphering_valid_runestone_with_invalid_script_postfix_returns_invalid_payload() { let mut script_pubkey = script::Builder::new() .push_opcode(opcodes::all::OP_RETURN) .push_opcode(MAGIC_NUMBER) @@ -442,16 +444,18 @@ mod tests { script_pubkey.push(opcodes::all::OP_PUSHBYTES_4.to_u8()); - Runestone::decipher(&Transaction { - input: Vec::new(), - output: vec![TxOut { - script_pubkey: ScriptBuf::from_bytes(script_pubkey), - value: 0, - }], - lock_time: LockTime::ZERO, - version: 2, - }) - .unwrap_err(); + assert_eq!( + Runestone::payload(&Transaction { + input: Vec::new(), + output: vec![TxOut { + script_pubkey: ScriptBuf::from_bytes(script_pubkey), + value: 0, + }], + lock_time: LockTime::ZERO, + version: 2, + }), + Ok(Some(Payload::Invalid)) + ); } #[test] @@ -1955,4 +1959,48 @@ mod tests { .cenotaph ); } + + #[test] + fn invalid_scripts_in_op_returns_are_ignored() { + let transaction = Transaction { + version: 2, + lock_time: LockTime::ZERO, + input: vec![TxIn { + previous_output: OutPoint::null(), + script_sig: ScriptBuf::new(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::new(), + }], + output: vec![TxOut { + script_pubkey: ScriptBuf::from(vec![ + opcodes::all::OP_RETURN.to_u8(), + opcodes::all::OP_PUSHBYTES_4.to_u8(), + ]), + value: 0, + }], + }; + + Runestone::decipher(&transaction).unwrap(); + + let transaction = Transaction { + version: 2, + lock_time: LockTime::ZERO, + input: vec![TxIn { + previous_output: OutPoint::null(), + script_sig: ScriptBuf::new(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::new(), + }], + output: vec![TxOut { + script_pubkey: ScriptBuf::from(vec![ + opcodes::all::OP_RETURN.to_u8(), + MAGIC_NUMBER.to_u8(), + opcodes::all::OP_PUSHBYTES_4.to_u8(), + ]), + value: 0, + }], + }; + + Runestone::decipher(&transaction).unwrap(); + } }