Skip to content

Commit

Permalink
Fix reinscriptions charm (#2793)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Dec 1, 2023
1 parent 733b74d commit 6f64528
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 9 deletions.
1 change: 0 additions & 1 deletion src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3520,7 +3520,6 @@ mod tests {

let txid = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(1, 0, 0, witness)],

..Default::default()
});

Expand Down
22 changes: 15 additions & 7 deletions src/index/updater/inscription_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
) -> Result {
let mut envelopes = ParsedEnvelope::from_transaction(tx).into_iter().peekable();
let mut floating_inscriptions = Vec::new();
let mut id_counter = 0;
let mut inscribed_offsets = BTreeMap::new();
let mut total_input_value = 0;
let mut id_counter = 0;
let total_output_value = tx.output.iter().map(|txout| txout.value).sum::<u64>();

for (input_index, tx_in) in tx.input.iter().enumerate() {
// skip subsidy since no inscriptions possible
Expand Down Expand Up @@ -134,8 +135,6 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
index: id_counter,
};

let inscribed_offset = inscribed_offsets.get(&offset);

let curse = if self.height >= self.chain.jubilee_height() {
None
} else if inscription.payload.unrecognized_even_field {
Expand All @@ -154,7 +153,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
Some(Curse::Pushnum)
} else if inscription.stutter {
Some(Curse::Stutter)
} else if let Some((id, count)) = inscribed_offset {
} else if let Some((id, count)) = inscribed_offsets.get(&offset) {
if *count > 1 {
Some(Curse::Reinscription)
} else {
Expand Down Expand Up @@ -183,11 +182,17 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {

let unbound = current_input_value == 0 || curse == Some(Curse::UnrecognizedEvenField);

let offset = inscription
.payload
.pointer()
.filter(|&pointer| pointer < total_output_value)
.unwrap_or(offset);

floating_inscriptions.push(Flotsam {
inscription_id,
offset,
origin: Origin::New {
reinscription: inscribed_offset.is_some(),
reinscription: inscribed_offsets.get(&offset).is_some(),
cursed: curse.is_some(),
fee: 0,
hidden: inscription.payload.hidden(),
Expand All @@ -197,6 +202,11 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
},
});

inscribed_offsets
.entry(offset)
.or_insert((inscription_id, 0))
.1 += 1;

envelopes.next();
id_counter += 1;
}
Expand All @@ -222,8 +232,6 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
}

// still have to normalize over inscription size
let total_output_value = tx.output.iter().map(|txout| txout.value).sum::<u64>();

for flotsam in &mut floating_inscriptions {
if let Flotsam {
origin: Origin::New { ref mut fee, .. },
Expand Down
2 changes: 1 addition & 1 deletion src/inscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl Inscription {
})
}

fn pointer_value(pointer: u64) -> Vec<u8> {
pub(crate) fn pointer_value(pointer: u64) -> Vec<u8> {
let mut bytes = pointer.to_le_bytes().to_vec();

while bytes.last().copied() == Some(0) {
Expand Down
137 changes: 137 additions & 0 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4331,6 +4331,143 @@ next
);
}

#[test]
fn charm_reinscription_in_same_tx_input() {
let server = TestServer::new_with_regtest();

server.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([1])
.push_slice(b"text/plain;charset=utf-8")
.push_slice([])
.push_slice(b"foo")
.push_opcode(opcodes::all::OP_ENDIF)
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_slice(b"ord")
.push_slice([1])
.push_slice(b"text/plain;charset=utf-8")
.push_slice([])
.push_slice(b"bar")
.push_opcode(opcodes::all::OP_ENDIF)
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_slice(b"ord")
.push_slice([1])
.push_slice(b"text/plain;charset=utf-8")
.push_slice([])
.push_slice(b"qix")
.push_opcode(opcodes::all::OP_ENDIF)
.into_script();

let witness = Witness::from_slice(&[script.into_bytes(), Vec::new()]);

let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(1, 0, 0, witness)],
..Default::default()
});

server.mine_blocks(1);

let id = InscriptionId { txid, index: 0 };
server.assert_response_regex(
format!("/inscription/{id}"),
StatusCode::OK,
format!(
".*<h1>Inscription 0</h1>.*
<dl>
<dt>id</dt>
<dd class=monospace>{id}</dd>
<dt>output value</dt>
.*
</dl>
.*
"
),
);

let id = InscriptionId { txid, index: 1 };
server.assert_response_regex(
format!("/inscription/{id}"),
StatusCode::OK,
".*
<span title=reinscription>♻️</span>
<span title=cursed>👹</span>.*",
);

let id = InscriptionId { txid, index: 2 };
server.assert_response_regex(
format!("/inscription/{id}"),
StatusCode::OK,
".*
<span title=reinscription>♻️</span>
<span title=cursed>👹</span>.*",
);
}

#[test]
fn charm_reinscription_in_same_tx_with_pointer() {
let server = TestServer::new_with_regtest();

server.mine_blocks(3);

let cursed_inscription = inscription("text/plain", "bar");
let reinscription: Inscription = InscriptionTemplate {
pointer: Some(0),
..Default::default()
}
.into();

let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[
(1, 0, 0, inscription("text/plain", "foo").to_witness()),
(2, 0, 0, cursed_inscription.to_witness()),
(3, 0, 0, reinscription.to_witness()),
],
..Default::default()
});

server.mine_blocks(1);

let id = InscriptionId { txid, index: 0 };
server.assert_response_regex(
format!("/inscription/{id}"),
StatusCode::OK,
format!(
".*<h1>Inscription 0</h1>.*
<dl>
<dt>id</dt>
<dd class=monospace>{id}</dd>
<dt>output value</dt>
.*
</dl>
.*
"
),
);

let id = InscriptionId { txid, index: 1 };
server.assert_response_regex(
format!("/inscription/{id}"),
StatusCode::OK,
".*
<span title=cursed>👹</span>.*",
);

let id = InscriptionId { txid, index: 2 };
server.assert_response_regex(
format!("/inscription/{id}"),
StatusCode::OK,
".*
<span title=reinscription>♻️</span>
<span title=cursed>👹</span>.*",
);
}

#[test]
fn charm_unbound() {
let server = TestServer::new_with_regtest();
Expand Down
13 changes: 13 additions & 0 deletions src/subcommand/wallet/inscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ mod tests {

let child_inscription = InscriptionTemplate {
parent: Some(parent_inscription),
..Default::default()
}
.into();

Expand Down Expand Up @@ -887,14 +888,17 @@ inscriptions:
let inscriptions = vec![
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
];
Expand Down Expand Up @@ -985,14 +989,17 @@ inscriptions:
let inscriptions = vec![
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
];
Expand Down Expand Up @@ -1060,14 +1067,17 @@ inscriptions:
let inscriptions = vec![
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
];
Expand Down Expand Up @@ -1227,14 +1237,17 @@ inscriptions:
let inscriptions = vec![
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
InscriptionTemplate {
parent: Some(parent),
..Default::default()
}
.into(),
];
Expand Down
2 changes: 2 additions & 0 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,14 @@ pub(crate) fn tx_out(value: u64, address: Address) -> TxOut {
#[derive(Default, Debug)]
pub(crate) struct InscriptionTemplate {
pub(crate) parent: Option<InscriptionId>,
pub(crate) pointer: Option<u64>,
}

impl From<InscriptionTemplate> for Inscription {
fn from(template: InscriptionTemplate) -> Self {
Self {
parent: template.parent.map(|id| id.parent_value()),
pointer: template.pointer.map(Inscription::pointer_value),
..Default::default()
}
}
Expand Down

0 comments on commit 6f64528

Please sign in to comment.