Skip to content

Commit

Permalink
Ignore invalid script pubkeys (#3432)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Apr 1, 2024
1 parent e048d10 commit 8c1f6ce
Show file tree
Hide file tree
Showing 8 changed files with 45 additions and 69 deletions.
100 changes: 38 additions & 62 deletions crates/ordinals/src/runestone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,23 @@ impl Runestone {
pub const MAGIC_NUMBER: opcodes::All = opcodes::all::OP_PUSHNUM_13;
pub const COMMIT_INTERVAL: u16 = 6;

pub fn decipher(transaction: &Transaction) -> Result<Option<Artifact>, script::Error> {
let payload = match Runestone::payload(transaction)? {
pub fn decipher(transaction: &Transaction) -> Option<Artifact> {
let payload = match Runestone::payload(transaction) {
Some(Payload::Valid(payload)) => payload,
Some(Payload::Invalid(flaw)) => {
return Ok(Some(Artifact::Cenotaph(Cenotaph {
return Some(Artifact::Cenotaph(Cenotaph {
flaws: flaw.into(),
..default()
})))
}));
}
None => return Ok(None),
None => return None,
};

let Some(integers) = Runestone::integers(&payload) else {
return Ok(Some(Artifact::Cenotaph(Cenotaph {
return Some(Artifact::Cenotaph(Cenotaph {
flaws: Flaw::Varint.into(),
..default()
})));
}));
};

let Message {
Expand Down Expand Up @@ -110,19 +110,19 @@ impl Runestone {
}

if flaws != 0 {
return Ok(Some(Artifact::Cenotaph(Cenotaph {
return Some(Artifact::Cenotaph(Cenotaph {
flaws,
mint,
etching: etching.and_then(|etching| etching.rune),
})));
}));
}

Ok(Some(Artifact::Runestone(Self {
Some(Artifact::Runestone(Self {
edicts,
etching,
mint,
pointer,
})))
}))
}

pub fn encipher(&self) -> ScriptBuf {
Expand Down Expand Up @@ -189,13 +189,13 @@ impl Runestone {
builder.into_script()
}

fn payload(transaction: &Transaction) -> Result<Option<Payload>, script::Error> {
fn payload(transaction: &Transaction) -> Option<Payload> {
// search transaction outputs for payload
for output in &transaction.output {
let mut instructions = output.script_pubkey.instructions();

// payload starts with OP_RETURN
if instructions.next().transpose()? != Some(Instruction::Op(opcodes::all::OP_RETURN)) {
if instructions.next() != Some(Ok(Instruction::Op(opcodes::all::OP_RETURN))) {
continue;
}

Expand All @@ -214,18 +214,18 @@ impl Runestone {
payload.extend_from_slice(push.as_bytes());
}
Ok(Instruction::Op(_)) => {
return Ok(Some(Payload::Invalid(Flaw::Opcode)));
return Some(Payload::Invalid(Flaw::Opcode));
}
Err(_) => {
return Ok(Some(Payload::Invalid(Flaw::InvalidScript)));
return Some(Payload::Invalid(Flaw::InvalidScript));
}
}
}

return Ok(Some(Payload::Valid(payload)));
return Some(Payload::Valid(payload));
}

Ok(None)
None
}

fn integers(payload: &[u8]) -> Option<Vec<u128>> {
Expand Down Expand Up @@ -275,7 +275,6 @@ mod tests {
version: 2,
})
.unwrap()
.unwrap()
}

fn payload(integers: &[u128]) -> Vec<u8> {
Expand All @@ -289,17 +288,19 @@ mod tests {
}

#[test]
fn decipher_returns_an_error_if_first_opcode_is_malformed() {
assert!(Runestone::decipher(&Transaction {
input: Vec::new(),
output: vec![TxOut {
script_pubkey: ScriptBuf::from_bytes(vec![opcodes::all::OP_PUSHBYTES_4.to_u8()]),
value: 0,
}],
lock_time: LockTime::ZERO,
version: 2,
})
.is_err());
fn decipher_returns_none_if_first_opcode_is_malformed() {
assert_eq!(
Runestone::decipher(&Transaction {
input: Vec::new(),
output: vec![TxOut {
script_pubkey: ScriptBuf::from_bytes(vec![opcodes::all::OP_PUSHBYTES_4.to_u8()]),
value: 0,
}],
lock_time: LockTime::ZERO,
version: 2,
}),
None,
);
}

#[test]
Expand All @@ -311,7 +312,7 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
}),
Ok(None)
None,
);
}

Expand All @@ -327,7 +328,7 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
}),
Ok(None)
None,
);
}

Expand All @@ -345,7 +346,7 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
}),
Ok(None)
None,
);
}

Expand All @@ -364,24 +365,10 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
}),
Ok(None)
None,
);
}

#[test]
fn deciphering_valid_runestone_with_invalid_script_returns_script_error() {
Runestone::decipher(&Transaction {
input: Vec::new(),
output: vec![TxOut {
script_pubkey: ScriptBuf::from_bytes(vec![opcodes::all::OP_PUSHBYTES_4.to_u8()]),
value: 0,
}],
lock_time: LockTime::ZERO,
version: 2,
})
.unwrap_err();
}

#[test]
fn deciphering_valid_runestone_with_invalid_script_postfix_returns_invalid_payload() {
let mut script_pubkey = script::Builder::new()
Expand All @@ -402,7 +389,7 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
}),
Ok(Some(Payload::Invalid(Flaw::InvalidScript)))
Some(Payload::Invalid(Flaw::InvalidScript))
);
}

Expand Down Expand Up @@ -457,7 +444,6 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
})
.unwrap()
.unwrap(),
Artifact::Cenotaph(Cenotaph {
flaws: Flaw::Opcode.into(),
Expand All @@ -482,7 +468,6 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
})
.unwrap()
.unwrap(),
Artifact::Cenotaph(Cenotaph {
flaws: Flaw::Opcode.into(),
Expand All @@ -506,7 +491,6 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
})
.unwrap()
.unwrap(),
Artifact::Runestone(Runestone::default()),
);
Expand Down Expand Up @@ -545,7 +529,6 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
})
.unwrap()
.unwrap(),
Artifact::Runestone(Runestone {
mint: Some(RuneId::new(1, 1).unwrap()),
Expand Down Expand Up @@ -776,7 +759,6 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
})
.unwrap()
.unwrap(),
Artifact::Cenotaph(Cenotaph {
flaws: Flaw::Varint.into(),
Expand Down Expand Up @@ -1309,7 +1291,6 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
})
.unwrap()
.unwrap(),
Artifact::Runestone(Runestone {
edicts: vec![Edict {
Expand Down Expand Up @@ -1352,7 +1333,6 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
})
.unwrap()
.unwrap(),
Artifact::Runestone(Runestone {
edicts: vec![Edict {
Expand Down Expand Up @@ -1394,7 +1374,6 @@ mod tests {
lock_time: LockTime::ZERO,
version: 2,
})
.unwrap()
.unwrap(),
Artifact::Runestone(Runestone {
edicts: vec![Edict {
Expand Down Expand Up @@ -1676,7 +1655,7 @@ mod tests {
version: 2,
};

let Payload::Valid(payload) = Runestone::payload(&transaction).unwrap().unwrap() else {
let Payload::Valid(payload) = Runestone::payload(&transaction).unwrap() else {
panic!("invalid payload")
};

Expand All @@ -1692,7 +1671,7 @@ mod tests {
};

assert_eq!(
Runestone::decipher(&transaction).unwrap().unwrap(),
Runestone::decipher(&transaction).unwrap(),
Artifact::Runestone(runestone),
);
}
Expand Down Expand Up @@ -2054,8 +2033,7 @@ mod tests {
]),
value: 0,
}],
})
.unwrap(),
}),
None
);

Expand Down Expand Up @@ -2083,7 +2061,6 @@ mod tests {
}
],
})
.unwrap()
.unwrap(),
Artifact::Runestone(Runestone::default()),
);
Expand All @@ -2110,7 +2087,6 @@ mod tests {
value: 0,
}],
})
.unwrap()
.unwrap(),
Artifact::Cenotaph(Cenotaph {
flaws: Flaw::InvalidScript.into(),
Expand Down
2 changes: 1 addition & 1 deletion src/index/updater/rune_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub(super) struct RuneUpdater<'a, 'tx, 'client> {

impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> {
pub(super) fn index_runes(&mut self, tx_index: u32, tx: &Transaction, txid: Txid) -> Result<()> {
let artifact = Runestone::decipher(tx)?;
let artifact = Runestone::decipher(tx);

let mut unallocated = self.unallocated(tx)?;

Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl Decode {

let inscriptions = ParsedEnvelope::from_transaction(&transaction);

let runestone = Runestone::decipher(&transaction)?;
let runestone = Runestone::decipher(&transaction);

if self.compact {
Ok(Some(Box::new(CompactOutput {
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/wallet/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl Mint {

assert_eq!(
Runestone::decipher(&signed_transaction),
Ok(Some(Artifact::Runestone(runestone))),
Some(Artifact::Runestone(runestone)),
);

let transaction = bitcoin_client.send_raw_transaction(&signed_transaction)?;
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 @@ -308,7 +308,7 @@ impl Send {

assert_eq!(
Runestone::decipher(&unsigned_transaction),
Ok(Some(Artifact::Runestone(runestone))),
Some(Artifact::Runestone(runestone)),
);

Ok(unsigned_transaction)
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/batch/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ impl Plan {
let total_fees =
Self::calculate_fee(&unsigned_commit_tx, &utxos) + Self::calculate_fee(&reveal_tx, &utxos);

match (Runestone::decipher(&reveal_tx).unwrap(), runestone) {
match (Runestone::decipher(&reveal_tx), runestone) {
(Some(actual), Some(expected)) => assert_eq!(
actual,
Artifact::Runestone(expected),
Expand Down
2 changes: 1 addition & 1 deletion tests/wallet/batch_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1483,7 +1483,7 @@ fn batch_can_etch_rune() {
Sequence::from_height(Runestone::COMMIT_INTERVAL)
);

let Artifact::Runestone(runestone) = Runestone::decipher(&reveal).unwrap().unwrap() else {
let Artifact::Runestone(runestone) = Runestone::decipher(&reveal).unwrap() else {
panic!();
};

Expand Down
2 changes: 1 addition & 1 deletion tests/wallet/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,7 @@ fn sending_rune_creates_transaction_with_expected_runestone() {
let tx = core.tx_by_id(output.txid);

pretty_assert_eq!(
Runestone::decipher(&tx).unwrap().unwrap(),
Runestone::decipher(&tx).unwrap(),
Artifact::Runestone(Runestone {
pointer: None,
etching: None,
Expand Down

0 comments on commit 8c1f6ce

Please sign in to comment.