From f94a5c88e41681841cff2ae7a27445e58e6dc191 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Thu, 5 Oct 2023 12:01:52 -0700 Subject: [PATCH] Recognize inscriptions with pushnum opcodes (#2497) --- src/envelope.rs | 201 +++++++++++++++++------ src/index.rs | 37 +++++ src/index/updater/inscription_updater.rs | 2 + src/inscription.rs | 1 + 4 files changed, 188 insertions(+), 53 deletions(-) diff --git a/src/envelope.rs b/src/envelope.rs index 3221648c2d..eb5a5681e0 100644 --- a/src/envelope.rs +++ b/src/envelope.rs @@ -18,11 +18,12 @@ type Result = std::result::Result; type RawEnvelope = Envelope>>; pub(crate) type ParsedEnvelope = Envelope; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Default, PartialEq, Clone)] pub(crate) struct Envelope { pub(crate) payload: T, pub(crate) input: u32, pub(crate) offset: u32, + pub(crate) pushnum: bool, } fn remove_field(fields: &mut BTreeMap<&[u8], Vec<&[u8]>>, field: &[u8]) -> Option> { @@ -97,6 +98,7 @@ impl From for ParsedEnvelope { }, input: envelope.input, offset: envelope.offset, + pushnum: envelope.pushnum, } } } @@ -155,6 +157,8 @@ impl RawEnvelope { return Ok(None); } + let mut pushnum = false; + let mut payload = Vec::new(); loop { @@ -162,11 +166,80 @@ impl RawEnvelope { None => return Ok(None), Some(Instruction::Op(opcodes::all::OP_ENDIF)) => { return Ok(Some(Envelope { - payload, input: input.try_into().unwrap(), offset: offset.try_into().unwrap(), + payload, + pushnum, })); } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_NEG1)) => { + pushnum = true; + payload.push(vec![0x81]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_1)) => { + pushnum = true; + payload.push(vec![1]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_2)) => { + pushnum = true; + payload.push(vec![2]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_3)) => { + pushnum = true; + payload.push(vec![3]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_4)) => { + pushnum = true; + payload.push(vec![4]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_5)) => { + pushnum = true; + payload.push(vec![5]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_6)) => { + pushnum = true; + payload.push(vec![6]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_7)) => { + pushnum = true; + payload.push(vec![7]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_8)) => { + pushnum = true; + payload.push(vec![8]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_9)) => { + pushnum = true; + payload.push(vec![9]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_10)) => { + pushnum = true; + payload.push(vec![10]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_11)) => { + pushnum = true; + payload.push(vec![11]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_12)) => { + pushnum = true; + payload.push(vec![12]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_13)) => { + pushnum = true; + payload.push(vec![13]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_14)) => { + pushnum = true; + payload.push(vec![14]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_15)) => { + pushnum = true; + payload.push(vec![15]); + } + Some(Instruction::Op(opcodes::all::OP_PUSHNUM_16)) => { + pushnum = true; + payload.push(vec![16]); + } Some(Instruction::PushBytes(push)) => { payload.push(push.as_bytes().to_vec()); } @@ -247,9 +320,7 @@ mod tests { Vec::new() ])]), vec![ParsedEnvelope { - payload: Inscription::default(), - input: 0, - offset: 0, + ..Default::default() }] ); } @@ -291,8 +362,7 @@ mod tests { duplicate_field: true, ..Default::default() }, - input: 0, - offset: 0, + ..Default::default() }] ); } @@ -309,8 +379,7 @@ mod tests { ])]), vec![ParsedEnvelope { payload: inscription("text/plain;charset=utf-8", "ord"), - input: 0, - offset: 0, + ..Default::default() }] ); } @@ -329,8 +398,7 @@ mod tests { ])]), vec![ParsedEnvelope { payload: inscription("text/plain;charset=utf-8", "ord"), - input: 0, - offset: 0, + ..Default::default() }] ); } @@ -344,8 +412,7 @@ mod tests { content_type: Some(b"text/plain;charset=utf-8".to_vec()), ..Default::default() }, - input: 0, - offset: 0 + ..Default::default() }], ); } @@ -359,8 +426,7 @@ mod tests { body: Some(b"foo".to_vec()), ..Default::default() }, - input: 0, - offset: 0 + ..Default::default() }], ); } @@ -378,8 +444,7 @@ mod tests { ])]), vec![ParsedEnvelope { payload: inscription("text/plain;charset=utf-8", "foobar"), - input: 0, - offset: 0 + ..Default::default() }], ); } @@ -390,8 +455,7 @@ mod tests { parse(&[envelope(&[b"ord", &[1], b"text/plain;charset=utf-8", &[]])]), vec![ParsedEnvelope { payload: inscription("text/plain;charset=utf-8", ""), - input: 0, - offset: 0 + ..Default::default() }] ); } @@ -412,8 +476,7 @@ mod tests { ])]), vec![ParsedEnvelope { payload: inscription("text/plain;charset=utf-8", ""), - input: 0, - offset: 0 + ..Default::default() }], ); } @@ -436,8 +499,7 @@ mod tests { parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), vec![ParsedEnvelope { payload: inscription("text/plain;charset=utf-8", "ord"), - input: 0, - offset: 0 + ..Default::default() }], ); } @@ -460,8 +522,7 @@ mod tests { parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), vec![ParsedEnvelope { payload: inscription("text/plain;charset=utf-8", "ord"), - input: 0, - offset: 0 + ..Default::default() }], ); } @@ -492,13 +553,12 @@ mod tests { vec![ ParsedEnvelope { payload: inscription("text/plain;charset=utf-8", "foo"), - input: 0, - offset: 0 + ..Default::default() }, ParsedEnvelope { payload: inscription("text/plain;charset=utf-8", "bar"), - input: 0, - offset: 1 + offset: 1, + ..Default::default() }, ], ); @@ -516,8 +576,7 @@ mod tests { ])]), vec![ParsedEnvelope { payload: inscription("text/plain;charset=utf-8", [0b10000000]), - input: 0, - offset: 0 + ..Default::default() },], ); } @@ -572,8 +631,7 @@ mod tests { ])]), vec![ParsedEnvelope { payload: inscription("text/plain;charset=utf-8", "ord"), - input: 0, - offset: 0, + ..Default::default() }], ); } @@ -585,7 +643,7 @@ mod tests { vec![ParsedEnvelope { payload: inscription("foo", [1; 1040]), input: 1, - offset: 0, + ..Default::default() }] ); } @@ -604,13 +662,12 @@ mod tests { vec![ ParsedEnvelope { payload: inscription("foo", [1; 100]), - input: 0, - offset: 0, + ..Default::default() }, ParsedEnvelope { payload: inscription("bar", [1; 100]), - input: 0, offset: 1, + ..Default::default() } ] ); @@ -622,8 +679,7 @@ mod tests { parse(&[envelope(&[b"ord", &[1], b"image/png", &[], &[1; 100]])]), vec![ParsedEnvelope { payload: inscription("image/png", [1; 100]), - input: 0, - offset: 0, + ..Default::default() }] ); } @@ -640,8 +696,7 @@ mod tests { parse(&[witness]), vec![ParsedEnvelope { payload: inscription("foo", [1; 1040]), - input: 0, - offset: 0, + ..Default::default() }] ); } @@ -658,8 +713,7 @@ mod tests { parse(&[witness]), vec![ParsedEnvelope { payload: Inscription::default(), - input: 0, - offset: 0, + ..Default::default() }], ); } @@ -670,8 +724,7 @@ mod tests { parse(&[envelope(&[b"ord", &[9], &[0]])]), vec![ParsedEnvelope { payload: Inscription::default(), - input: 0, - offset: 0, + ..Default::default() }], ); } @@ -685,8 +738,7 @@ mod tests { unrecognized_even_field: true, ..Default::default() }, - input: 0, - offset: 0, + ..Default::default() }], ); } @@ -700,8 +752,7 @@ mod tests { incomplete_field: true, ..Default::default() }, - input: 0, - offset: 0, + ..Default::default() }], ); } @@ -715,8 +766,7 @@ mod tests { metadata: Some(vec![]), ..Default::default() }, - input: 0, - offset: 0, + ..Default::default() }] ); } @@ -731,9 +781,54 @@ mod tests { duplicate_field: true, ..Default::default() }, - input: 0, - offset: 0, + ..Default::default() }] ); } + + #[test] + fn pushnum_opcodes_are_parsed_correctly() { + const PUSHNUMS: &[(opcodes::All, u8)] = &[ + (opcodes::all::OP_PUSHNUM_NEG1, 0x81), + (opcodes::all::OP_PUSHNUM_1, 1), + (opcodes::all::OP_PUSHNUM_2, 2), + (opcodes::all::OP_PUSHNUM_3, 3), + (opcodes::all::OP_PUSHNUM_4, 4), + (opcodes::all::OP_PUSHNUM_5, 5), + (opcodes::all::OP_PUSHNUM_6, 6), + (opcodes::all::OP_PUSHNUM_7, 7), + (opcodes::all::OP_PUSHNUM_8, 8), + (opcodes::all::OP_PUSHNUM_9, 9), + (opcodes::all::OP_PUSHNUM_10, 10), + (opcodes::all::OP_PUSHNUM_11, 11), + (opcodes::all::OP_PUSHNUM_12, 12), + (opcodes::all::OP_PUSHNUM_13, 13), + (opcodes::all::OP_PUSHNUM_14, 14), + (opcodes::all::OP_PUSHNUM_15, 15), + (opcodes::all::OP_PUSHNUM_16, 16), + ]; + + for &(op, value) in PUSHNUMS { + let script = script::Builder::new() + .push_opcode(opcodes::OP_FALSE) + .push_opcode(opcodes::all::OP_IF) + .push_slice(b"ord") + .push_opcode(opcodes::OP_FALSE) + .push_opcode(op) + .push_opcode(opcodes::all::OP_ENDIF) + .into_script(); + + assert_eq!( + parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]), + vec![ParsedEnvelope { + payload: Inscription { + body: Some(vec![value]), + ..Default::default() + }, + pushnum: true, + ..Default::default() + }], + ); + } + } } diff --git a/src/index.rs b/src/index.rs index 817003986b..af6860c814 100644 --- a/src/index.rs +++ b/src/index.rs @@ -2801,6 +2801,43 @@ mod tests { } } + #[test] + fn inscriptions_with_pushnum_opcodes_are_cursed() { + for context in Context::configurations() { + context.mine_blocks(1); + + let script = script::Builder::new() + .push_opcode(opcodes::OP_FALSE) + .push_opcode(opcodes::all::OP_IF) + .push_slice(b"ord") + .push_slice([]) + .push_opcode(opcodes::all::OP_PUSHNUM_1) + .push_opcode(opcodes::all::OP_ENDIF) + .into_script(); + + let witness = Witness::from_slice(&[script.into_bytes(), Vec::new()]); + + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, witness)], + ..Default::default() + }); + + let inscription_id = InscriptionId { txid, index: 0 }; + + context.mine_blocks(1); + + assert_eq!( + context + .index + .get_inscription_entry(inscription_id) + .unwrap() + .unwrap() + .inscription_number, + -1 + ); + } + } + #[test] // https://github.com/ordinals/ord/issues/2062 fn zero_value_transaction_inscription_not_cursed_but_unbound() { diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 6c9f4f7f93..dc8448e148 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -184,6 +184,8 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { Some(Curse::NotInFirstInput) } else if inscription.offset != 0 { Some(Curse::NotAtOffsetZero) + } else if inscription.pushnum { + Some(Curse::Pushnum) } else if inscribed_offsets.contains_key(&offset) { let seq_num = self.id_to_entry.len()?; diff --git a/src/inscription.rs b/src/inscription.rs index b89820ea25..8632eda466 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -17,6 +17,7 @@ pub(crate) enum Curse { IncompleteField, NotAtOffsetZero, NotInFirstInput, + Pushnum, Reinscription, UnrecognizedEvenField, }