Skip to content

Commit

Permalink
Add stuttering curse (ordinals#2745)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Nov 28, 2023
1 parent 533e767 commit 28ccd62
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 40 deletions.
184 changes: 144 additions & 40 deletions src/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ use {
super::*,
bitcoin::blockdata::{
opcodes,
script::{self, Instruction, Instructions},
script::{
self,
Instruction::{self, Op, PushBytes},
Instructions,
},
},
std::iter::Peekable,
};

pub(crate) const PROTOCOL_ID: [u8; 3] = *b"ord";
Expand All @@ -22,10 +27,11 @@ pub(crate) type ParsedEnvelope = Envelope<Inscription>;

#[derive(Debug, Default, PartialEq, Clone)]
pub(crate) struct Envelope<T> {
pub(crate) payload: T,
pub(crate) input: u32,
pub(crate) offset: u32,
pub(crate) payload: T,
pub(crate) pushnum: bool,
pub(crate) stutter: bool,
}

fn remove_field(fields: &mut BTreeMap<&[u8], Vec<&[u8]>>, field: &[u8]) -> Option<Vec<u8>> {
Expand Down Expand Up @@ -111,6 +117,7 @@ impl From<RawEnvelope> for ParsedEnvelope {
input: envelope.input,
offset: envelope.offset,
pushnum: envelope.pushnum,
stutter: envelope.stutter,
}
}
}
Expand Down Expand Up @@ -142,31 +149,47 @@ impl RawEnvelope {
fn from_tapscript(tapscript: &Script, input: usize) -> Result<Vec<Self>> {
let mut envelopes = Vec::new();

let mut instructions = tapscript.instructions();
let mut instructions = tapscript.instructions().peekable();

while let Some(instruction) = instructions.next() {
if instruction? == Instruction::PushBytes((&[]).into()) {
if let Some(envelope) = Self::from_instructions(&mut instructions, input, envelopes.len())?
{
let mut stuttered = false;
while let Some(instruction) = instructions.next().transpose()? {
if instruction == PushBytes((&[]).into()) {
let (stutter, envelope) =
Self::from_instructions(&mut instructions, input, envelopes.len(), stuttered)?;
if let Some(envelope) = envelope {
envelopes.push(envelope);
} else {
stuttered = stutter;
}
}
}

Ok(envelopes)
}

fn accept(instructions: &mut Peekable<Instructions>, instruction: Instruction) -> Result<bool> {
if instructions.peek() == Some(&Ok(instruction)) {
instructions.next().transpose()?;
Ok(true)
} else {
Ok(false)
}
}

fn from_instructions(
instructions: &mut Instructions,
instructions: &mut Peekable<Instructions>,
input: usize,
offset: usize,
) -> Result<Option<Self>> {
if instructions.next().transpose()? != Some(Instruction::Op(opcodes::all::OP_IF)) {
return Ok(None);
stutter: bool,
) -> Result<(bool, Option<Self>)> {
if !Self::accept(instructions, Op(opcodes::all::OP_IF))? {
let stutter = instructions.peek() == Some(&Ok(PushBytes((&[]).into())));
return Ok((stutter, None));
}

if instructions.next().transpose()? != Some(Instruction::PushBytes((&PROTOCOL_ID).into())) {
return Ok(None);
if !Self::accept(instructions, PushBytes((&PROTOCOL_ID).into()))? {
let stutter = instructions.peek() == Some(&Ok(PushBytes((&[]).into())));
return Ok((stutter, None));
}

let mut pushnum = false;
Expand All @@ -175,87 +198,91 @@ impl RawEnvelope {

loop {
match instructions.next().transpose()? {
None => return Ok(None),
Some(Instruction::Op(opcodes::all::OP_ENDIF)) => {
return Ok(Some(Envelope {
input: input.try_into().unwrap(),
offset: offset.try_into().unwrap(),
payload,
pushnum,
}));
None => return Ok((false, None)),
Some(Op(opcodes::all::OP_ENDIF)) => {
return Ok((
false,
Some(Envelope {
input: input.try_into().unwrap(),
offset: offset.try_into().unwrap(),
payload,
pushnum,
stutter,
}),
));
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_NEG1)) => {
Some(Op(opcodes::all::OP_PUSHNUM_NEG1)) => {
pushnum = true;
payload.push(vec![0x81]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_1)) => {
Some(Op(opcodes::all::OP_PUSHNUM_1)) => {
pushnum = true;
payload.push(vec![1]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_2)) => {
Some(Op(opcodes::all::OP_PUSHNUM_2)) => {
pushnum = true;
payload.push(vec![2]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_3)) => {
Some(Op(opcodes::all::OP_PUSHNUM_3)) => {
pushnum = true;
payload.push(vec![3]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_4)) => {
Some(Op(opcodes::all::OP_PUSHNUM_4)) => {
pushnum = true;
payload.push(vec![4]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_5)) => {
Some(Op(opcodes::all::OP_PUSHNUM_5)) => {
pushnum = true;
payload.push(vec![5]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_6)) => {
Some(Op(opcodes::all::OP_PUSHNUM_6)) => {
pushnum = true;
payload.push(vec![6]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_7)) => {
Some(Op(opcodes::all::OP_PUSHNUM_7)) => {
pushnum = true;
payload.push(vec![7]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_8)) => {
Some(Op(opcodes::all::OP_PUSHNUM_8)) => {
pushnum = true;
payload.push(vec![8]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_9)) => {
Some(Op(opcodes::all::OP_PUSHNUM_9)) => {
pushnum = true;
payload.push(vec![9]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_10)) => {
Some(Op(opcodes::all::OP_PUSHNUM_10)) => {
pushnum = true;
payload.push(vec![10]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_11)) => {
Some(Op(opcodes::all::OP_PUSHNUM_11)) => {
pushnum = true;
payload.push(vec![11]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_12)) => {
Some(Op(opcodes::all::OP_PUSHNUM_12)) => {
pushnum = true;
payload.push(vec![12]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_13)) => {
Some(Op(opcodes::all::OP_PUSHNUM_13)) => {
pushnum = true;
payload.push(vec![13]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_14)) => {
Some(Op(opcodes::all::OP_PUSHNUM_14)) => {
pushnum = true;
payload.push(vec![14]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_15)) => {
Some(Op(opcodes::all::OP_PUSHNUM_15)) => {
pushnum = true;
payload.push(vec![15]);
}
Some(Instruction::Op(opcodes::all::OP_PUSHNUM_16)) => {
Some(Op(opcodes::all::OP_PUSHNUM_16)) => {
pushnum = true;
payload.push(vec![16]);
}
Some(Instruction::PushBytes(push)) => {
Some(PushBytes(push)) => {
payload.push(push.as_bytes().to_vec());
}
Some(_) => return Ok(None),
Some(_) => return Ok((false, None)),
}
}
}
Expand Down Expand Up @@ -895,4 +922,81 @@ mod tests {
);
}
}

#[test]
fn stuttering() {
let script = script::Builder::new()
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_slice(b"ord")
.push_opcode(opcodes::all::OP_ENDIF)
.into_script();

assert_eq!(
parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]),
vec![ParsedEnvelope {
payload: Default::default(),
stutter: true,
..Default::default()
}],
);

let script = script::Builder::new()
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_slice(b"ord")
.push_opcode(opcodes::all::OP_ENDIF)
.into_script();

assert_eq!(
parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]),
vec![ParsedEnvelope {
payload: Default::default(),
stutter: true,
..Default::default()
}],
);

let script = script::Builder::new()
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_slice(b"ord")
.push_opcode(opcodes::all::OP_ENDIF)
.into_script();

assert_eq!(
parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]),
vec![ParsedEnvelope {
payload: Default::default(),
stutter: true,
..Default::default()
}],
);

let script = script::Builder::new()
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_AND)
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_slice(b"ord")
.push_opcode(opcodes::all::OP_ENDIF)
.into_script();

assert_eq!(
parse(&[Witness::from_slice(&[script.into_bytes(), Vec::new()])]),
vec![ParsedEnvelope {
payload: Default::default(),
stutter: false,
..Default::default()
}],
);
}
}
38 changes: 38 additions & 0 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3117,7 +3117,45 @@ mod tests {
}

#[test]
fn inscriptions_with_stutter_are_cursed() {
for context in Context::configurations() {
context.mine_blocks(1);

let script = script::Builder::new()
.push_opcode(opcodes::OP_FALSE)
.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
);
}
}

// https://github.com/ordinals/ord/issues/2062
#[test]
fn zero_value_transaction_inscription_not_cursed_but_unbound() {
for context in Context::configurations() {
context.mine_blocks(1);
Expand Down
3 changes: 3 additions & 0 deletions src/index/updater/inscription_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ enum Curse {
Pointer,
Pushnum,
Reinscription,
Stutter,
UnrecognizedEvenField,
}

Expand Down Expand Up @@ -148,6 +149,8 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
Some(Curse::Pointer)
} else if inscription.pushnum {
Some(Curse::Pushnum)
} else if inscription.stutter {
Some(Curse::Stutter)
} else if let Some((id, count)) = inscribed_offset {
if *count > 1 {
Some(Curse::Reinscription)
Expand Down

0 comments on commit 28ccd62

Please sign in to comment.