Skip to content

Commit

Permalink
Vindicate cursed inscriptions (ordinals#2950)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey committed Jan 5, 2024
1 parent 16aa7ef commit b9b1ddd
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 43 deletions.
247 changes: 235 additions & 12 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,18 @@ pub enum List {
#[derive(Copy, Clone)]
pub(crate) enum Statistic {
Schema = 0,
BlessedInscriptions,
Commits,
CursedInscriptions,
IndexRunes,
IndexSats,
LostSats,
OutputsTraversed,
ReservedRunes,
Runes,
SatRanges,
UnboundInscriptions,
IndexTransactions,
BlessedInscriptions = 1,
Commits = 2,
CursedInscriptions = 3,
IndexRunes = 4,
IndexSats = 5,
LostSats = 6,
OutputsTraversed = 7,
ReservedRunes = 8,
Runes = 9,
SatRanges = 10,
UnboundInscriptions = 11,
IndexTransactions = 12,
}

impl Statistic {
Expand Down Expand Up @@ -5568,4 +5568,227 @@ mod tests {
);
}
}

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

// Before the jubilee, an inscription on a sat using a pushnum opcode is
// cursed and not vindicated.

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);

let entry = context
.index
.get_inscription_entry(inscription_id)
.unwrap()
.unwrap();

assert!(Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Cursed));

assert!(!Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Vindicated));

let sat = entry.sat;

assert_eq!(entry.inscription_number, -1);

// Before the jubilee, reinscription on the same sat is not cursed and
// not vindicated.

let inscription = Inscription::default();

let txid = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(2, 1, 0, inscription.to_witness())],
..Default::default()
});

context.mine_blocks(1);

let inscription_id = InscriptionId { txid, index: 0 };

let entry = context
.index
.get_inscription_entry(inscription_id)
.unwrap()
.unwrap();

assert_eq!(entry.inscription_number, 0);

assert!(!Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Cursed));

assert!(!Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Vindicated));

assert_eq!(sat, entry.sat);

// Before the jubilee, a third reinscription on the same sat is cursed
// and not vindicated.

let inscription = Inscription::default();

let txid = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(3, 1, 0, inscription.to_witness())],
..Default::default()
});

context.mine_blocks(1);

let inscription_id = InscriptionId { txid, index: 0 };

let entry = context
.index
.get_inscription_entry(inscription_id)
.unwrap()
.unwrap();

assert!(Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Cursed));

assert!(!Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Vindicated));

assert_eq!(entry.inscription_number, -2);

assert_eq!(sat, entry.sat);
}
}

#[test]
fn post_jubilee_first_reinscription_after_vindicated_inscription_not_vindicated() {
for context in Context::configurations() {
context.mine_blocks(110);
// After the jubilee, an inscription on a sat using a pushnum opcode is
// vindicated and not cursed.

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);

let entry = context
.index
.get_inscription_entry(inscription_id)
.unwrap()
.unwrap();

assert!(!Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Cursed));

assert!(Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Vindicated));

let sat = entry.sat;

assert_eq!(entry.inscription_number, 0);

// After the jubilee, a reinscription on the same is not cursed and not
// vindicated.

let inscription = Inscription::default();

let txid = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(111, 1, 0, inscription.to_witness())],
..Default::default()
});

context.mine_blocks(1);

let inscription_id = InscriptionId { txid, index: 0 };

let entry = context
.index
.get_inscription_entry(inscription_id)
.unwrap()
.unwrap();

assert!(!Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Cursed));

assert!(!Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Vindicated));

assert_eq!(entry.inscription_number, 1);

assert_eq!(sat, entry.sat);

// After the jubilee, a third reinscription on the same is vindicated and
// not cursed.

let inscription = Inscription::default();

let txid = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(112, 1, 0, inscription.to_witness())],
..Default::default()
});

context.mine_blocks(1);

let inscription_id = InscriptionId { txid, index: 0 };

let entry = context
.index
.get_inscription_entry(inscription_id)
.unwrap()
.unwrap();

assert!(!Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Cursed));

assert!(Charm::charms(entry.charms)
.iter()
.any(|charm| *charm == Charm::Vindicated));

assert_eq!(entry.inscription_number, 2);

assert_eq!(sat, entry.sat);
}
}
}
27 changes: 17 additions & 10 deletions src/index/updater/inscription_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ enum Origin {
pointer: Option<u64>,
reinscription: bool,
unbound: bool,
vindicated: bool,
},
Old {
old_satpoint: SatPoint,
Expand Down Expand Up @@ -76,6 +77,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
let mut floating_inscriptions = Vec::new();
let mut id_counter = 0;
let mut inscribed_offsets = BTreeMap::new();
let jubilant = self.height >= self.chain.jubilee_height();
let mut total_input_value = 0;
let total_output_value = tx.output.iter().map(|txout| txout.value).sum::<u64>();

Expand Down Expand Up @@ -142,9 +144,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
index: id_counter,
};

let curse = if self.height >= self.chain.jubilee_height() {
None
} else if inscription.payload.unrecognized_even_field {
let curse = if inscription.payload.unrecognized_even_field {
Some(Curse::UnrecognizedEvenField)
} else if inscription.payload.duplicate_field {
Some(Curse::DuplicateField)
Expand All @@ -167,17 +167,18 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
let initial_inscription_sequence_number =
self.id_to_sequence_number.get(id.store())?.unwrap().value();

let initial_inscription_is_cursed = InscriptionEntry::load(
let entry = InscriptionEntry::load(
self
.sequence_number_to_entry
.get(initial_inscription_sequence_number)?
.unwrap()
.value(),
)
.inscription_number
< 0;
);

let initial_inscription_was_cursed_or_vindicated =
entry.inscription_number < 0 || Charm::Vindicated.is_set(entry.charms);

if initial_inscription_is_cursed {
if initial_inscription_was_cursed_or_vindicated {
None
} else {
Some(Curse::Reinscription)
Expand All @@ -201,13 +202,14 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
inscription_id,
offset,
origin: Origin::New {
reinscription: inscribed_offsets.get(&offset).is_some(),
cursed: curse.is_some(),
cursed: curse.is_some() && !jubilant,
fee: 0,
hidden: inscription.payload.hidden(),
parent: inscription.payload.parent(),
pointer: inscription.payload.pointer(),
reinscription: inscribed_offsets.get(&offset).is_some(),
unbound,
vindicated: curse.is_some() && jubilant,
},
});

Expand Down Expand Up @@ -404,6 +406,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
pointer: _,
reinscription,
unbound,
vindicated,
} => {
let inscription_number = if cursed {
let number: i32 = self.cursed_inscription_count.try_into().unwrap();
Expand Down