From db29dbc3f22cca1fa6b632893b82c05204a9d431 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 1 Mar 2023 19:25:11 -0800 Subject: [PATCH 01/34] enable collections through parent inscriptions method #783 --- src/index.rs | 4 +- src/index/updater.rs | 3 + src/index/updater/inscription_updater.rs | 40 +++-- src/inscription.rs | 61 +++++++- src/subcommand/preview.rs | 1 + src/subcommand/server.rs | 8 +- src/subcommand/wallet/inscribe.rs | 187 +++++++++++++++++------ src/test.rs | 2 +- tests/lib.rs | 1 + tests/wallet/inscribe.rs | 43 +++++- 10 files changed, 281 insertions(+), 69 deletions(-) diff --git a/src/index.rs b/src/index.rs index e4c02d853c..9b93b2db36 100644 --- a/src/index.rs +++ b/src/index.rs @@ -23,7 +23,7 @@ mod fetcher; mod rtx; mod updater; -const SCHEMA_VERSION: u64 = 3; +const SCHEMA_VERSION: u64 = 4; macro_rules! define_table { ($name:ident, $key:ty, $value:ty) => { @@ -33,6 +33,7 @@ macro_rules! define_table { define_table! { HEIGHT_TO_BLOCK_HASH, u64, &BlockHashValue } define_table! { INSCRIPTION_ID_TO_INSCRIPTION_ENTRY, &InscriptionIdValue, InscriptionEntryValue } +define_table! { INSCRIPTION_ID_TO_PARENT_ID, &InscriptionIdValue, &InscriptionIdValue } define_table! { INSCRIPTION_ID_TO_SATPOINT, &InscriptionIdValue, &SatPointValue } define_table! { INSCRIPTION_NUMBER_TO_INSCRIPTION_ID, u64, &InscriptionIdValue } define_table! { OUTPOINT_TO_SAT_RANGES, &OutPointValue, &[u8] } @@ -204,6 +205,7 @@ impl Index { tx.open_table(HEIGHT_TO_BLOCK_HASH)?; tx.open_table(INSCRIPTION_ID_TO_INSCRIPTION_ENTRY)?; + tx.open_table(INSCRIPTION_ID_TO_PARENT_ID)?; tx.open_table(INSCRIPTION_ID_TO_SATPOINT)?; tx.open_table(INSCRIPTION_NUMBER_TO_INSCRIPTION_ID)?; tx.open_table(OUTPOINT_TO_VALUE)?; diff --git a/src/index/updater.rs b/src/index/updater.rs index d6c1223326..6e578088b5 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -404,6 +404,7 @@ impl Updater { let mut inscription_id_to_inscription_entry = wtx.open_table(INSCRIPTION_ID_TO_INSCRIPTION_ENTRY)?; + let mut inscription_id_to_parent_id = wtx.open_table(INSCRIPTION_ID_TO_PARENT_ID)?; let mut inscription_id_to_satpoint = wtx.open_table(INSCRIPTION_ID_TO_SATPOINT)?; let mut inscription_number_to_inscription_id = wtx.open_table(INSCRIPTION_NUMBER_TO_INSCRIPTION_ID)?; @@ -421,6 +422,7 @@ impl Updater { &mut inscription_id_to_satpoint, value_receiver, &mut inscription_id_to_inscription_entry, + &mut inscription_id_to_parent_id, lost_sats, &mut inscription_number_to_inscription_id, &mut outpoint_to_value, @@ -519,6 +521,7 @@ impl Updater { outpoint_to_sat_ranges.insert(&OutPoint::null().store(), lost_sat_ranges.as_slice())?; } } else { + // move coinbase to end for (tx, txid) in block.txdata.iter().skip(1).chain(block.txdata.first()) { lost_sats += inscription_updater.index_transaction_inscriptions(tx, *txid, None)?; } diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 575ccf7ca7..14c93a8ffb 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -4,6 +4,7 @@ pub(super) struct Flotsam { inscription_id: InscriptionId, offset: u64, origin: Origin, + parent: Option, } enum Origin { @@ -17,6 +18,7 @@ pub(super) struct InscriptionUpdater<'a, 'db, 'tx> { id_to_satpoint: &'a mut Table<'db, 'tx, &'static InscriptionIdValue, &'static SatPointValue>, value_receiver: &'a mut Receiver, id_to_entry: &'a mut Table<'db, 'tx, &'static InscriptionIdValue, InscriptionEntryValue>, + id_to_parent_id: &'a mut Table<'db, 'tx, &'static InscriptionIdValue, &'static InscriptionIdValue>, lost_sats: u64, next_number: u64, number_to_id: &'a mut Table<'db, 'tx, u64, &'static InscriptionIdValue>, @@ -34,6 +36,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { id_to_satpoint: &'a mut Table<'db, 'tx, &'static InscriptionIdValue, &'static SatPointValue>, value_receiver: &'a mut Receiver, id_to_entry: &'a mut Table<'db, 'tx, &'static InscriptionIdValue, InscriptionEntryValue>, + id_to_parent_id: &'a mut Table<'db, 'tx, &'static InscriptionIdValue, &'static InscriptionIdValue>, lost_sats: u64, number_to_id: &'a mut Table<'db, 'tx, u64, &'static InscriptionIdValue>, outpoint_to_value: &'a mut Table<'db, 'tx, &'static OutPointValue, u64>, @@ -55,6 +58,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { id_to_satpoint, value_receiver, id_to_entry, + id_to_parent_id, lost_sats, next_number, number_to_id, @@ -70,13 +74,15 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { pub(super) fn index_transaction_inscriptions( &mut self, tx: &Transaction, - txid: Txid, + txid: Txid, // we can calulcate this from tx. Is this expensive? input_sat_ranges: Option<&VecDeque<(u64, u64)>>, ) -> Result { let mut inscriptions = Vec::new(); + // go through all flotsam and ensure parent is there let mut input_value = 0; for tx_in in &tx.input { + // if coinbase if tx_in.previous_output.is_null() { input_value += Height(self.height).subsidy(); } else { @@ -87,6 +93,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { offset: input_value + old_satpoint.offset, inscription_id, origin: Origin::Old(old_satpoint), + parent: None, }); } @@ -108,14 +115,27 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { } } - if inscriptions.iter().all(|flotsam| flotsam.offset != 0) - && Inscription::from_transaction(tx).is_some() - { - inscriptions.push(Flotsam { - inscription_id: txid.into(), - offset: 0, - origin: Origin::New(input_value - tx.output.iter().map(|txout| txout.value).sum::()), - }); + // make sure no re-inscriptions + if inscriptions.iter().all(|flotsam| flotsam.offset != 0) { + if let Some(inscription) = Inscription::from_transaction(tx) { + + let parent = if let Some(parent_id) = inscription.get_parent_id() { + if inscriptions.iter().any(|flotsam| flotsam.inscription_id == parent_id) { + Some(parent_id) + } else { + None + } + } else { + None + }; + + inscriptions.push(Flotsam { + inscription_id: txid.into(), + offset: 0, + origin: Origin::New(input_value - tx.output.iter().map(|txout| txout.value).sum::()), + parent, + }); + } }; let is_coinbase = tx @@ -218,6 +238,8 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { } } + + self.id_to_entry.insert( &inscription_id, &InscriptionEntry { diff --git a/src/inscription.rs b/src/inscription.rs index d0fba77016..cd6ae1d88e 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -15,24 +15,42 @@ const PROTOCOL_ID: &[u8] = b"ord"; const BODY_TAG: &[u8] = &[]; const CONTENT_TYPE_TAG: &[u8] = &[1]; +const PARENT_TAG: &[u8] = &[3]; #[derive(Debug, PartialEq, Clone)] pub(crate) struct Inscription { - body: Option>, + parent: Option>, content_type: Option>, + body: Option>, } impl Inscription { #[cfg(test)] - pub(crate) fn new(content_type: Option>, body: Option>) -> Self { - Self { content_type, body } + pub(crate) fn new( + parent: Option>, + content_type: Option>, + body: Option>, + ) -> Self { + Self { + parent, + content_type, + body, + } } pub(crate) fn from_transaction(tx: &Transaction) -> Option { + // let mut inscriptions = Vec::new(); + // for input in tx.input { + // InscriptionParser::parse(input.witness).ok() + // } InscriptionParser::parse(&tx.input.get(0)?.witness).ok() } - pub(crate) fn from_file(chain: Chain, path: impl AsRef) -> Result { + pub(crate) fn from_file( + chain: Chain, + path: impl AsRef, + parent: Option, + ) -> Result { let path = path.as_ref(); let body = fs::read(path).with_context(|| format!("io error reading {}", path.display()))?; @@ -46,7 +64,14 @@ impl Inscription { let content_type = Media::content_type_for_path(path)?; + let parent = if let Some(inscription_id) = parent { + Some(inscription_id.to_string().into_bytes()) + } else { + None + }; + Ok(Self { + parent, body: Some(body), content_type: Some(content_type.into()), }) @@ -58,6 +83,10 @@ impl Inscription { .push_opcode(opcodes::all::OP_IF) .push_slice(PROTOCOL_ID); + if let Some(parent) = &self.parent { + builder = builder.push_slice(PARENT_TAG).push_slice(parent); + } + if let Some(content_type) = &self.content_type { builder = builder .push_slice(CONTENT_TYPE_TAG) @@ -106,6 +135,14 @@ impl Inscription { str::from_utf8(self.content_type.as_ref()?).ok() } + pub(crate) fn get_parent_id(&self) -> Option { + if let Some(vec) = &self.parent { + InscriptionId::from_str(str::from_utf8(&vec).unwrap()).ok() + } else { + None + } + } + #[cfg(test)] pub(crate) fn to_witness(&self) -> Witness { let builder = script::Builder::new(); @@ -222,6 +259,7 @@ impl<'a> InscriptionParser<'a> { let body = fields.remove(BODY_TAG); let content_type = fields.remove(CONTENT_TYPE_TAG); + let parent = fields.remove(PARENT_TAG); for tag in fields.keys() { if let Some(lsb) = tag.first() { @@ -231,7 +269,11 @@ impl<'a> InscriptionParser<'a> { } } - return Ok(Some(Inscription { body, content_type })); + return Ok(Some(Inscription { + body, + content_type, + parent, + })); } Ok(None) @@ -358,7 +400,7 @@ mod tests { b"ord", &[1], b"text/plain;charset=utf-8", - &[3], + &[5], b"bar", &[], b"ord", @@ -372,6 +414,7 @@ mod tests { assert_eq!( InscriptionParser::parse(&envelope(&[b"ord", &[1], b"text/plain;charset=utf-8"])), Ok(Inscription { + parent: None, content_type: Some(b"text/plain;charset=utf-8".to_vec()), body: None, }), @@ -383,6 +426,7 @@ mod tests { assert_eq!( InscriptionParser::parse(&envelope(&[b"ord", &[], b"foo"])), Ok(Inscription { + parent: None, content_type: None, body: Some(b"foo".to_vec()), }), @@ -705,6 +749,7 @@ mod tests { witness.push( &Inscription { + parent: None, content_type: None, body: None, } @@ -716,6 +761,7 @@ mod tests { assert_eq!( InscriptionParser::parse(&witness).unwrap(), Inscription { + parent: None, content_type: None, body: None, } @@ -725,8 +771,9 @@ mod tests { #[test] fn unknown_odd_fields_are_ignored() { assert_eq!( - InscriptionParser::parse(&envelope(&[b"ord", &[3], &[0]])), + InscriptionParser::parse(&envelope(&[b"ord", &[5], &[0]])), Ok(Inscription { + parent: None, content_type: None, body: None, }), diff --git a/src/subcommand/preview.rs b/src/subcommand/preview.rs index d9f402e47e..13ba8d1730 100644 --- a/src/subcommand/preview.rs +++ b/src/subcommand/preview.rs @@ -86,6 +86,7 @@ impl Preview { dry_run: false, no_limit: false, destination: None, + parent: None, }, )), } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 63adafb300..c16fbf3a31 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1971,6 +1971,7 @@ mod tests { fn content_response_no_content() { assert_eq!( Server::content_response(Inscription::new( + None, Some("text/plain".as_bytes().to_vec()), None )), @@ -1981,6 +1982,7 @@ mod tests { #[test] fn content_response_with_content() { let (headers, body) = Server::content_response(Inscription::new( + None, Some("text/plain".as_bytes().to_vec()), Some(vec![1, 2, 3]), )) @@ -1993,7 +1995,7 @@ mod tests { #[test] fn content_response_no_content_type() { let (headers, body) = - Server::content_response(Inscription::new(None, Some(Vec::new()))).unwrap(); + Server::content_response(Inscription::new(None, None, Some(Vec::new()))).unwrap(); assert_eq!(headers["content-type"], "application/octet-stream"); assert!(body.is_empty()); @@ -2291,7 +2293,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: Inscription::new(Some("foo/bar".as_bytes().to_vec()), None).to_witness(), + witness: Inscription::new(None, Some("foo/bar".as_bytes().to_vec()), None).to_witness(), ..Default::default() }); @@ -2313,7 +2315,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: Inscription::new(Some("image/png".as_bytes().to_vec()), None).to_witness(), + witness: Inscription::new(None, Some("image/png".as_bytes().to_vec()), None).to_witness(), ..Default::default() }); diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 915613e8fe..d072382ea8 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -22,6 +22,7 @@ use { struct Output { commit: Txid, inscription: InscriptionId, + parent: Option, reveal: Txid, fees: u64, } @@ -54,12 +55,12 @@ pub(crate) struct Inscribe { pub(crate) dry_run: bool, #[clap(long, help = "Send inscription to .")] pub(crate) destination: Option
, + #[clap(long, help = "Establish parent relationship with .")] + pub(crate) parent: Option, } impl Inscribe { pub(crate) fn run(self, options: Options) -> Result { - let inscription = Inscription::from_file(options.chain(), &self.file)?; - let index = Index::open(&options)?; index.update()?; @@ -69,6 +70,34 @@ impl Inscribe { let inscriptions = index.get_inscriptions(None)?; + let (parent, commit_input_offset) = if let Some(parent_id) = self.parent { + if let Some(satpoint) = index.get_inscription_satpoint_by_id(parent_id)? { + if !utxos.contains_key(&satpoint.outpoint) { + return Err(anyhow!(format!( + "unrelated parent {parent_id} not accepting mailman's child" // for the germans: "Kuckuckskind" + ))); + } + + let output = index + .get_transaction(satpoint.outpoint.txid)? + .expect("not found") + .output + .into_iter() + .nth(satpoint.outpoint.vout.try_into().unwrap()) + .expect("current transaction output"); + + (Some((satpoint, output)), 1) + } else { + return Err(anyhow!(format!( + "specified parent {parent_id} does not exist" + ))); + } + } else { + (None, 0) + }; + + let inscription = Inscription::from_file(options.chain(), &self.file, self.parent)?; + let commit_tx_change = [get_change_address(&client)?, get_change_address(&client)?]; let reveal_tx_destination = self @@ -76,9 +105,10 @@ impl Inscribe { .map(Ok) .unwrap_or_else(|| get_change_address(&client))?; - let (unsigned_commit_tx, reveal_tx, recovery_key_pair) = + let (unsigned_commit_tx, partially_signed_reveal_tx, recovery_key_pair) = Inscribe::create_inscription_transactions( self.satpoint, + parent, inscription, inscriptions, options.chain().network(), @@ -91,18 +121,19 @@ impl Inscribe { )?; utxos.insert( - reveal_tx.input[0].previous_output, - Amount::from_sat(unsigned_commit_tx.output[0].value), + partially_signed_reveal_tx.input[commit_input_offset].previous_output, + Amount::from_sat(unsigned_commit_tx.output[commit_input_offset].value), ); - let fees = - Self::calculate_fee(&unsigned_commit_tx, &utxos) + Self::calculate_fee(&reveal_tx, &utxos); + let fees = Self::calculate_fee(&unsigned_commit_tx, &utxos) + + Self::calculate_fee(&partially_signed_reveal_tx, &utxos); if self.dry_run { print_json(Output { commit: unsigned_commit_tx.txid(), - reveal: reveal_tx.txid(), - inscription: reveal_tx.txid().into(), + reveal: partially_signed_reveal_tx.txid(), + inscription: partially_signed_reveal_tx.txid().into(), + parent: self.parent, fees, })?; } else { @@ -118,18 +149,33 @@ impl Inscribe { .send_raw_transaction(&signed_raw_commit_tx) .context("Failed to send commit transaction")?; - let reveal = client - .send_raw_transaction(&reveal_tx) - .context("Failed to send reveal transaction")?; + let reveal = if self.parent.is_some() { + let fully_signed_raw_reveal_tx = client + .sign_raw_transaction_with_wallet(&partially_signed_reveal_tx, None, None)? + .hex; + + client + .send_raw_transaction(&fully_signed_raw_reveal_tx) + .context("Failed to send reveal transaction")? + } else { + client + .send_raw_transaction(&partially_signed_reveal_tx) + .context("Failed to send reveal transaction")? + }; print_json(Output { commit, reveal, inscription: reveal.into(), + parent: self.parent, fees, })?; }; + // if self.parent.is_some() { + // println!("{}", partially_signed_reveal_tx.raw_hex()); + // } + Ok(()) } @@ -144,6 +190,7 @@ impl Inscribe { fn create_inscription_transactions( satpoint: Option, + parent: Option<(SatPoint, TxOut)>, inscription: Inscription, inscriptions: BTreeMap, network: Network, @@ -207,17 +254,41 @@ impl Inscribe { let commit_tx_address = Address::p2tr_tweaked(taproot_spend_info.output_key(), network); + let (mut inputs, mut outputs, commit_input_offset) = if let Some((satpoint, output)) = parent { + ( + vec![satpoint.outpoint, OutPoint::null()], + vec![ + TxOut { + script_pubkey: output.script_pubkey, + value: output.value, + }, + TxOut { + script_pubkey: destination.script_pubkey(), + value: 0, + }, + ], + 1, + ) + } else { + ( + vec![OutPoint::null()], + vec![TxOut { + script_pubkey: destination.script_pubkey(), + value: 0, + }], + 0, + ) + }; + let (_, reveal_fee) = Self::build_reveal_transaction( &control_block, reveal_fee_rate, - OutPoint::null(), - TxOut { - script_pubkey: destination.script_pubkey(), - value: 0, - }, + inputs.clone(), + outputs.clone(), &reveal_script, ); + // watch out that parent and inscription preserved let unsigned_commit_tx = TransactionBuilder::build_transaction_with_value( satpoint, inscriptions, @@ -235,26 +306,35 @@ impl Inscribe { .find(|(_vout, output)| output.script_pubkey == commit_tx_address.script_pubkey()) .expect("should find sat commit/inscription output"); + inputs[commit_input_offset] = OutPoint { + txid: unsigned_commit_tx.txid(), + vout: vout.try_into().unwrap(), + }; + + outputs[commit_input_offset] = TxOut { + script_pubkey: destination.script_pubkey(), + value: output.value, + }; + let (mut reveal_tx, fee) = Self::build_reveal_transaction( &control_block, reveal_fee_rate, - OutPoint { - txid: unsigned_commit_tx.txid(), - vout: vout.try_into().unwrap(), - }, - TxOut { - script_pubkey: destination.script_pubkey(), - value: output.value, - }, + inputs, + outputs, &reveal_script, ); - reveal_tx.output[0].value = reveal_tx.output[0] + reveal_tx.output[commit_input_offset].value = reveal_tx.output[commit_input_offset] .value .checked_sub(fee.to_sat()) .context("commit transaction output value insufficient to pay transaction fee")?; - if reveal_tx.output[0].value < reveal_tx.output[0].script_pubkey.dust_value().to_sat() { + if reveal_tx.output[commit_input_offset].value + < reveal_tx.output[commit_input_offset] + .script_pubkey + .dust_value() + .to_sat() + { bail!("commit transaction output would be dust"); } @@ -262,10 +342,10 @@ impl Inscribe { let signature_hash = sighash_cache .taproot_script_spend_signature_hash( - 0, - &Prevouts::All(&[output]), + commit_input_offset, + &Prevouts::One(commit_input_offset, output), TapLeafHash::from_script(&reveal_script, LeafVersion::TapScript), - SchnorrSighashType::Default, + SchnorrSighashType::AllPlusAnyoneCanPay, ) .expect("signature hash should compute"); @@ -276,7 +356,7 @@ impl Inscribe { ); let witness = sighash_cache - .witness_mut(0) + .witness_mut(commit_input_offset) .expect("getting mutable witness reference should work"); witness.push(signature.as_ref()); witness.push(reveal_script); @@ -335,18 +415,21 @@ impl Inscribe { fn build_reveal_transaction( control_block: &ControlBlock, fee_rate: FeeRate, - input: OutPoint, - output: TxOut, + inputs: Vec, + outputs: Vec, script: &Script, ) -> (Transaction, Amount) { let reveal_tx = Transaction { - input: vec![TxIn { - previous_output: input, - script_sig: script::Builder::new().into_script(), - witness: Witness::new(), - sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, - }], - output: vec![output], + input: inputs + .iter() + .map(|outpoint| TxIn { + previous_output: *outpoint, + script_sig: script::Builder::new().into_script(), + witness: Witness::new(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + }) + .collect(), + output: outputs, lock_time: PackedLockTime::ZERO, version: 1, }; @@ -354,13 +437,15 @@ impl Inscribe { let fee = { let mut reveal_tx = reveal_tx.clone(); - reveal_tx.input[0].witness.push( - Signature::from_slice(&[0; SCHNORR_SIGNATURE_SIZE]) - .unwrap() - .as_ref(), - ); - reveal_tx.input[0].witness.push(script); - reveal_tx.input[0].witness.push(&control_block.serialize()); + for txin in &mut reveal_tx.input { + txin.witness.push( + Signature::from_slice(&[0; SCHNORR_SIGNATURE_SIZE]) + .unwrap() + .as_ref(), + ); + txin.witness.push(script); + txin.witness.push(&control_block.serialize()); + } fee_rate.fee(reveal_tx.vsize()) }; @@ -382,6 +467,7 @@ mod tests { let (commit_tx, reveal_tx, _private_key) = Inscribe::create_inscription_transactions( Some(satpoint(1, 0)), + None, inscription, BTreeMap::new(), Network::Bitcoin, @@ -413,6 +499,7 @@ mod tests { let (commit_tx, reveal_tx, _) = Inscribe::create_inscription_transactions( Some(satpoint(1, 0)), + None, inscription, BTreeMap::new(), Network::Bitcoin, @@ -448,6 +535,7 @@ mod tests { let error = Inscribe::create_inscription_transactions( satpoint, + None, inscription, inscriptions, Network::Bitcoin, @@ -490,6 +578,7 @@ mod tests { assert!(Inscribe::create_inscription_transactions( satpoint, + None, inscription, inscriptions, Network::Bitcoin, @@ -526,6 +615,7 @@ mod tests { let (commit_tx, reveal_tx, _private_key) = Inscribe::create_inscription_transactions( satpoint, + None, inscription, inscriptions, bitcoin::Network::Signet, @@ -588,6 +678,7 @@ mod tests { let (commit_tx, reveal_tx, _private_key) = Inscribe::create_inscription_transactions( satpoint, + None, inscription, inscriptions, bitcoin::Network::Signet, @@ -637,6 +728,7 @@ mod tests { let error = Inscribe::create_inscription_transactions( satpoint, + None, inscription, BTreeMap::new(), Network::Bitcoin, @@ -668,6 +760,7 @@ mod tests { let (_commit_tx, reveal_tx, _private_key) = Inscribe::create_inscription_transactions( satpoint, + None, inscription, BTreeMap::new(), Network::Bitcoin, diff --git a/src/test.rs b/src/test.rs index 27a8d45f83..a374d1fad4 100644 --- a/src/test.rs +++ b/src/test.rs @@ -101,7 +101,7 @@ pub(crate) fn tx_out(value: u64, address: Address) -> TxOut { } pub(crate) fn inscription(content_type: &str, body: impl AsRef<[u8]>) -> Inscription { - Inscription::new(Some(content_type.into()), Some(body.as_ref().into())) + Inscription::new(None, Some(content_type.into()), Some(body.as_ref().into())) } pub(crate) fn inscription_id(n: u32) -> InscriptionId { diff --git a/tests/lib.rs b/tests/lib.rs index 710e6b2a88..b871a03588 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -43,6 +43,7 @@ struct Inscribe { inscription: String, reveal: Txid, fees: u64, + parent: Option, } fn inscribe(rpc_server: &test_bitcoincore_rpc::Handle) -> Inscribe { diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index 16b486037e..eda850f150 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -335,7 +335,7 @@ fn inscribe_with_dry_run_flag() { } #[test] -fn inscribe_with_dry_run_flag_fees_inscrease() { +fn inscribe_with_dry_run_flag_fees_increase() { let rpc_server = test_bitcoincore_rpc::spawn(); create_wallet(&rpc_server); rpc_server.mine_blocks(1); @@ -394,3 +394,44 @@ fn inscribe_with_no_limit() { .write("degenerate.png", four_megger) .rpc_server(&rpc_server); } + +#[test] +fn inscribe_with_parent_inscription() { + let rpc_server = test_bitcoincore_rpc::spawn(); + create_wallet(&rpc_server); + rpc_server.mine_blocks(1); + + let parent_id = CommandBuilder::new("wallet inscribe parent.png") + .write("parent.png", [1; 520]) + .rpc_server(&rpc_server) + .output::() + .inscription; + + rpc_server.mine_blocks(1); + + assert_eq!( + parent_id, + CommandBuilder::new(format!("wallet inscribe --parent {parent_id} child.png")) + .write("child.png", [1; 520]) + .rpc_server(&rpc_server) + .output::() + .parent + .unwrap() + ); +} + +#[test] +fn inscribe_with_non_existent_parent_inscription() { + let rpc_server = test_bitcoincore_rpc::spawn(); + create_wallet(&rpc_server); + rpc_server.mine_blocks(1); + + let parent_id = "3ac40a8f3c0d295386e1e597467a1ee0578df780834be885cd62337c2ed738a5i0"; + + CommandBuilder::new(format!("wallet inscribe --parent {parent_id} child.png")) + .write("child.png", [1; 520]) + .rpc_server(&rpc_server) + .expected_stderr(format!("error: specified parent {parent_id} does not exist\n")) + .expected_exit_code(1) + .run(); +} From d1080430745320b6f2f1800f39ff782f6788e9d3 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Thu, 2 Mar 2023 23:12:44 +0100 Subject: [PATCH 02/34] Update src/inscription.rs --- src/inscription.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inscription.rs b/src/inscription.rs index cd6ae1d88e..26039bd4dd 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -65,7 +65,7 @@ impl Inscription { let content_type = Media::content_type_for_path(path)?; let parent = if let Some(inscription_id) = parent { - Some(inscription_id.to_string().into_bytes()) + Some(inscription_id.store()) } else { None }; From 12533658158518bd1120a5e681b6262f828f1a4e Mon Sep 17 00:00:00 2001 From: raphjaph Date: Thu, 2 Mar 2023 23:13:51 +0100 Subject: [PATCH 03/34] Update src/inscription.rs --- src/inscription.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inscription.rs b/src/inscription.rs index 26039bd4dd..d080ae332b 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -137,7 +137,7 @@ impl Inscription { pub(crate) fn get_parent_id(&self) -> Option { if let Some(vec) = &self.parent { - InscriptionId::from_str(str::from_utf8(&vec).unwrap()).ok() + InscriptionId::load(todo!()) } else { None } From 33cf4b51225ecb715bfed5ae0e28d702c9ad7706 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Thu, 2 Mar 2023 14:24:59 -0800 Subject: [PATCH 04/34] review changes --- src/index/entry.rs | 115 +++++++++++++++++++++++ src/index/updater/inscription_updater.rs | 2 + 2 files changed, 117 insertions(+) diff --git a/src/index/entry.rs b/src/index/entry.rs index 15ff3d8ecb..86403cbcf7 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -26,10 +26,12 @@ pub(crate) struct InscriptionEntry { pub(crate) fee: u64, pub(crate) height: u64, pub(crate) number: u64, + // pub(crate) parent: Option, pub(crate) sat: Option, pub(crate) timestamp: u32, } +// pub(crate) type InscriptionEntryValue = (u64, u64, u64, (u128, u128), u64, u32); pub(crate) type InscriptionEntryValue = (u64, u64, u64, u64, u32); impl Entry for InscriptionEntry { @@ -143,3 +145,116 @@ impl Entry for SatRange { n.to_le_bytes()[0..11].try_into().unwrap() } } + +pub(super) type TxidValue = [u8; 32]; + +impl Entry for Txid { + type Value = TxidValue; + + fn load(value: Self::Value) -> Self { + Txid::from_inner(value) + } + + fn store(self) -> Self::Value { + Txid::into_inner(self) + } +} + +impl Entry for Option { + type Value = (u128, u128, u32); + + fn load(value: Self::Value) -> Self { + if (0, 0, u32::MAX) == value { + None + } else { + let (head, tail, index) = value; + debug_assert_eq!(index, 0); + let head_array = head.to_le_bytes(); + let tail_array = tail.to_le_bytes(); + let array = [ + head_array[0], + head_array[1], + head_array[2], + head_array[3], + head_array[4], + head_array[5], + head_array[6], + head_array[7], + head_array[8], + head_array[9], + head_array[10], + head_array[11], + head_array[12], + head_array[13], + head_array[14], + head_array[15], + tail_array[0], + tail_array[1], + tail_array[2], + tail_array[3], + tail_array[4], + tail_array[5], + tail_array[6], + tail_array[7], + tail_array[8], + tail_array[9], + tail_array[10], + tail_array[11], + tail_array[12], + tail_array[13], + tail_array[14], + tail_array[15], + ]; + let txid = Txid::load(array); + // TODO: do we want to handle inscriptions not at index 0 + Some(InscriptionId::from(txid)) + } + } + + fn store(self) -> Self::Value { + if let Some(inscription_id) = self { + let txid_entry = inscription_id.txid.store(); + let head = u128::from_le_bytes([ + txid_entry[0], + txid_entry[1], + txid_entry[2], + txid_entry[3], + txid_entry[4], + txid_entry[5], + txid_entry[6], + txid_entry[7], + txid_entry[8], + txid_entry[9], + txid_entry[10], + txid_entry[11], + txid_entry[12], + txid_entry[13], + txid_entry[14], + txid_entry[15], + ]); + + let tail = u128::from_le_bytes([ + txid_entry[16 + 0], + txid_entry[16 + 1], + txid_entry[16 + 2], + txid_entry[16 + 3], + txid_entry[16 + 4], + txid_entry[16 + 5], + txid_entry[16 + 6], + txid_entry[16 + 7], + txid_entry[16 + 8], + txid_entry[16 + 9], + txid_entry[16 + 10], + txid_entry[16 + 11], + txid_entry[16 + 12], + txid_entry[16 + 13], + txid_entry[16 + 14], + txid_entry[16 + 15], + ]); + + (head, tail, inscription_id.index) + } else { + (0, 0, u32::MAX) + } + } +} diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 14c93a8ffb..a94e86ce75 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -7,7 +7,9 @@ pub(super) struct Flotsam { parent: Option, } +// change name to Jetsam or more poetic german word enum Origin { + // put Some(parent_id) in Origin::New() New(u64), Old(SatPoint), } From a43c376e54c5bbaadf289d81a6c496e2b14f1af7 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 3 Mar 2023 13:21:47 -0800 Subject: [PATCH 05/34] stash --- src/index.rs | 2 +- src/index/entry.rs | 2 +- src/inscription.rs | 13 +++++-------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/index.rs b/src/index.rs index 9b93b2db36..366f27a741 100644 --- a/src/index.rs +++ b/src/index.rs @@ -18,7 +18,7 @@ use { std::sync::atomic::{self, AtomicBool}, }; -mod entry; +pub(crate) mod entry; mod fetcher; mod rtx; mod updater; diff --git a/src/index/entry.rs b/src/index/entry.rs index 86403cbcf7..5492fa1af6 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -1,6 +1,6 @@ use super::*; -pub(super) trait Entry: Sized { +pub(crate) trait Entry: Sized { type Value; fn load(value: Self::Value) -> Self; diff --git a/src/inscription.rs b/src/inscription.rs index d080ae332b..7b7d8dde7e 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -1,5 +1,6 @@ use { super::*, + crate::index::entry::Entry, bitcoin::{ blockdata::{ opcodes, @@ -64,14 +65,8 @@ impl Inscription { let content_type = Media::content_type_for_path(path)?; - let parent = if let Some(inscription_id) = parent { - Some(inscription_id.store()) - } else { - None - }; - Ok(Self { - parent, + parent: parent.store(), body: Some(body), content_type: Some(content_type.into()), }) @@ -137,7 +132,9 @@ impl Inscription { pub(crate) fn get_parent_id(&self) -> Option { if let Some(vec) = &self.parent { - InscriptionId::load(todo!()) + Some(InscriptionId::load( + vec.clone().try_into().expect("expected a [u8; 36]"), + )) } else { None } From c781e555c976affb2e57c163951afebeffe98b36 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Mon, 6 Mar 2023 18:06:57 -0800 Subject: [PATCH 06/34] stash --- src/index/entry.rs | 146 ++++++++++++++++++++++----------------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/src/index/entry.rs b/src/index/entry.rs index 5492fa1af6..a8541a1f69 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -87,79 +87,6 @@ impl Entry for InscriptionId { } } -pub(super) type OutPointValue = [u8; 36]; - -impl Entry for OutPoint { - type Value = OutPointValue; - - fn load(value: Self::Value) -> Self { - Decodable::consensus_decode(&mut io::Cursor::new(value)).unwrap() - } - - fn store(self) -> Self::Value { - let mut value = [0; 36]; - self.consensus_encode(&mut value.as_mut_slice()).unwrap(); - value - } -} - -pub(super) type SatPointValue = [u8; 44]; - -impl Entry for SatPoint { - type Value = SatPointValue; - - fn load(value: Self::Value) -> Self { - Decodable::consensus_decode(&mut io::Cursor::new(value)).unwrap() - } - - fn store(self) -> Self::Value { - let mut value = [0; 44]; - self.consensus_encode(&mut value.as_mut_slice()).unwrap(); - value - } -} - -pub(super) type SatRange = (u64, u64); - -impl Entry for SatRange { - type Value = [u8; 11]; - - fn load([b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10]: Self::Value) -> Self { - let raw_base = u64::from_le_bytes([b0, b1, b2, b3, b4, b5, b6, 0]); - - // 51 bit base - let base = raw_base & ((1 << 51) - 1); - - let raw_delta = u64::from_le_bytes([b6, b7, b8, b9, b10, 0, 0, 0]); - - // 33 bit delta - let delta = raw_delta >> 3; - - (base, base + delta) - } - - fn store(self) -> Self::Value { - let base = self.0; - let delta = self.1 - self.0; - let n = u128::from(base) | u128::from(delta) << 51; - n.to_le_bytes()[0..11].try_into().unwrap() - } -} - -pub(super) type TxidValue = [u8; 32]; - -impl Entry for Txid { - type Value = TxidValue; - - fn load(value: Self::Value) -> Self { - Txid::from_inner(value) - } - - fn store(self) -> Self::Value { - Txid::into_inner(self) - } -} - impl Entry for Option { type Value = (u128, u128, u32); @@ -258,3 +185,76 @@ impl Entry for Option { } } } + +pub(super) type OutPointValue = [u8; 36]; + +impl Entry for OutPoint { + type Value = OutPointValue; + + fn load(value: Self::Value) -> Self { + Decodable::consensus_decode(&mut io::Cursor::new(value)).unwrap() + } + + fn store(self) -> Self::Value { + let mut value = [0; 36]; + self.consensus_encode(&mut value.as_mut_slice()).unwrap(); + value + } +} + +pub(super) type SatPointValue = [u8; 44]; + +impl Entry for SatPoint { + type Value = SatPointValue; + + fn load(value: Self::Value) -> Self { + Decodable::consensus_decode(&mut io::Cursor::new(value)).unwrap() + } + + fn store(self) -> Self::Value { + let mut value = [0; 44]; + self.consensus_encode(&mut value.as_mut_slice()).unwrap(); + value + } +} + +pub(super) type SatRange = (u64, u64); + +impl Entry for SatRange { + type Value = [u8; 11]; + + fn load([b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10]: Self::Value) -> Self { + let raw_base = u64::from_le_bytes([b0, b1, b2, b3, b4, b5, b6, 0]); + + // 51 bit base + let base = raw_base & ((1 << 51) - 1); + + let raw_delta = u64::from_le_bytes([b6, b7, b8, b9, b10, 0, 0, 0]); + + // 33 bit delta + let delta = raw_delta >> 3; + + (base, base + delta) + } + + fn store(self) -> Self::Value { + let base = self.0; + let delta = self.1 - self.0; + let n = u128::from(base) | u128::from(delta) << 51; + n.to_le_bytes()[0..11].try_into().unwrap() + } +} + +pub(super) type TxidValue = [u8; 32]; + +impl Entry for Txid { + type Value = TxidValue; + + fn load(value: Self::Value) -> Self { + Txid::from_inner(value) + } + + fn store(self) -> Self::Value { + Txid::into_inner(self) + } +} From d2eee5586df57125a9e05d00b85215cb726482e5 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 7 Mar 2023 20:20:02 +0100 Subject: [PATCH 07/34] Update src/inscription.rs Co-authored-by: ericatallah --- src/inscription.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/inscription.rs b/src/inscription.rs index 7b7d8dde7e..c2ae2ad694 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -132,9 +132,11 @@ impl Inscription { pub(crate) fn get_parent_id(&self) -> Option { if let Some(vec) = &self.parent { - Some(InscriptionId::load( - vec.clone().try_into().expect("expected a [u8; 36]"), - )) + if let Some(vec2) = vec.clone().try_into().ok() { + Some(InscriptionId::load(vec2)) + } else { + None + } } else { None } From 1bd6333381ef4c7f13e26b45919e51700ce61bbd Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 7 Mar 2023 11:27:10 -0800 Subject: [PATCH 08/34] small clean up --- src/inscription.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/inscription.rs b/src/inscription.rs index 7ec7fd6928..54710d2632 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -140,11 +140,11 @@ impl Inscription { pub(crate) fn get_parent_id(&self) -> Option { if let Some(vec) = &self.parent { - if let Some(vec2) = vec.clone().try_into().ok() { - Some(InscriptionId::load(vec2)) - } else { - None - } + vec + .clone() + .try_into() + .ok() + .map(|vec| InscriptionId::load(vec)) } else { None } From af9344ee3c537606508cbd1f72f46bd24e330359 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 7 Mar 2023 11:54:04 -0800 Subject: [PATCH 09/34] stashing --- src/subcommand/server.rs | 1 + src/templates/inscription.rs | 4 ++++ templates/inscription.html | 4 ++++ tests/wallet/inscribe.rs | 31 ++++++++++++++++++++++--------- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index c16fbf3a31..bc6217653a 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -859,6 +859,7 @@ impl Server { next, number: entry.number, output, + parent: entry.parent, previous, sat: entry.sat, satpoint, diff --git a/src/templates/inscription.rs b/src/templates/inscription.rs index 0f396903fe..c6c40dcfa9 100644 --- a/src/templates/inscription.rs +++ b/src/templates/inscription.rs @@ -10,6 +10,7 @@ pub(crate) struct InscriptionHtml { pub(crate) next: Option, pub(crate) number: u64, pub(crate) output: TxOut, + pub(crate) parent: Option, pub(crate) previous: Option, pub(crate) sat: Option, pub(crate) satpoint: SatPoint, @@ -42,6 +43,7 @@ mod tests { next: None, number: 1, output: tx_out(1, address()), + parent: None, previous: None, sat: None, satpoint: satpoint(1, 0), @@ -102,6 +104,7 @@ mod tests { number: 1, output: tx_out(1, address()), previous: None, + parent: None, sat: Some(Sat(1)), satpoint: satpoint(1, 0), timestamp: timestamp(0), @@ -133,6 +136,7 @@ mod tests { next: Some(inscription_id(3)), number: 1, output: tx_out(1, address()), + parent: None, previous: Some(inscription_id(1)), sat: None, satpoint: satpoint(1, 0), diff --git a/templates/inscription.html b/templates/inscription.html index 37573f3706..fd1144a6ca 100644 --- a/templates/inscription.html +++ b/templates/inscription.html @@ -15,6 +15,10 @@

Inscription {{ self.number }}

id
{{ self.inscription_id }}
+%% if let Some(parent) = self.parent { +
parent
+
{{parent}}
+%% } %% if let Ok(address) = self.chain.address_from_script(&self.output.script_pubkey ) {
address
{{ address }}
diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index eda850f150..9bdcada61b 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -409,14 +409,25 @@ fn inscribe_with_parent_inscription() { rpc_server.mine_blocks(1); - assert_eq!( - parent_id, - CommandBuilder::new(format!("wallet inscribe --parent {parent_id} child.png")) - .write("child.png", [1; 520]) - .rpc_server(&rpc_server) - .output::() - .parent - .unwrap() + TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex( + format!("/inscription/{parent_id}"), + format!(".*"), + ); + + let child_output = CommandBuilder::new(format!("wallet inscribe --parent {parent_id} child.png")) + .write("child.png", [1; 520]) + .rpc_server(&rpc_server) + .output::(); + + rpc_server.mine_blocks(1); + + assert_eq!(parent_id, child_output.parent.unwrap()); + + println!("{}", child_output.inscription); + + TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex( + format!("/inscription/{}", child_output.inscription), + format!(".*parent.*{}", parent_id), ); } @@ -431,7 +442,9 @@ fn inscribe_with_non_existent_parent_inscription() { CommandBuilder::new(format!("wallet inscribe --parent {parent_id} child.png")) .write("child.png", [1; 520]) .rpc_server(&rpc_server) - .expected_stderr(format!("error: specified parent {parent_id} does not exist\n")) + .expected_stderr(format!( + "error: specified parent {parent_id} does not exist\n" + )) .expected_exit_code(1) .run(); } From 96a08c29b477c7a789f138957bb04b9bf8d54586 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 7 Mar 2023 13:11:08 -0800 Subject: [PATCH 10/34] stash --- src/index.rs | 52 ++++++++++++++++++++++++ src/index/entry.rs | 1 + src/index/updater.rs | 1 + src/index/updater/inscription_updater.rs | 12 +++--- src/subcommand/server.rs | 2 +- src/test.rs | 6 +++ tests/test_server.rs | 1 + tests/wallet/inscribe.rs | 6 +-- 8 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/index.rs b/src/index.rs index a6948ac182..c2e3d9bb47 100644 --- a/src/index.rs +++ b/src/index.rs @@ -2190,4 +2190,56 @@ mod tests { ); } } + + #[test] + fn test_inscription_with_parent() { + // for context in Context::configurations() { + let context = Context::builder().build(); + + context.mine_blocks(1); + + let parent_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0)], + witness: inscription("text/plain", "parent").to_witness(), + ..Default::default() + }); + + let parent_id = InscriptionId::from(parent_txid); + + context.mine_blocks(1); + + assert_eq!( + context.index.get_inscription_entry(parent_id).unwrap(), + Some(InscriptionEntry { + fee: 0, + height: 2, + number: 0, + parent: None, + sat: None, + timestamp: 2 + }) + ); + + let child_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 1, 0)], + witness: inscription_with_parent("text/plain", "child", parent_id).to_witness(), + ..Default::default() + }); + + let child_id = InscriptionId::from(child_txid); + + context.mine_blocks(1); + + assert_eq!( + context.index.get_inscription_entry(child_id).unwrap(), + Some(InscriptionEntry { + fee: 0, + height: 2, + number: 0, + parent: Some(parent_id), + sat: None, + timestamp: 2 + }) + ); + } } diff --git a/src/index/entry.rs b/src/index/entry.rs index d670ad5bf9..f910112db6 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -22,6 +22,7 @@ impl Entry for BlockHash { } } +#[derive(Debug, PartialEq)] pub(crate) struct InscriptionEntry { pub(crate) fee: u64, pub(crate) height: u64, diff --git a/src/index/updater.rs b/src/index/updater.rs index 69ad34b249..9ce9292b95 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -65,6 +65,7 @@ impl Updater { range_cache: HashMap::new(), height, index_sats: index.has_sat_index()?, + sat_ranges_since_flush: 0, outputs_cached: 0, outputs_inserted_since_flush: 0, diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index c9613092f1..248b91ffae 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -4,13 +4,13 @@ pub(super) struct Flotsam { inscription_id: InscriptionId, offset: u64, origin: Origin, - parent: Option, + // parent: Option, } // change name to Jetsam or more poetic german word enum Origin { // put Some(parent_id) in Origin::New() - New(u64), + New((u64, Option)), Old(SatPoint), } @@ -92,7 +92,6 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { offset: input_value + old_satpoint.offset, inscription_id, origin: Origin::Old(old_satpoint), - parent: None, }); } @@ -133,8 +132,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { inscriptions.push(Flotsam { inscription_id: txid.into(), offset: 0, - origin: Origin::New(input_value - tx.output.iter().map(|txout| txout.value).sum::()), - parent, + origin: Origin::New((input_value - tx.output.iter().map(|txout| txout.value).sum::(), parent)), }); } }; @@ -219,7 +217,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { Origin::Old(old_satpoint) => { self.satpoint_to_id.remove(&old_satpoint.store())?; } - Origin::New(fee) => { + Origin::New((fee, parent)) => { self .number_to_id .insert(&self.next_number, &inscription_id)?; @@ -245,7 +243,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { fee, height: self.height, number: self.next_number, - parent: None, + parent, sat, timestamp: self.timestamp, } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index bc6217653a..905aa69484 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -859,7 +859,7 @@ impl Server { next, number: entry.number, output, - parent: entry.parent, + parent: dbg!(entry.parent), previous, sat: entry.sat, satpoint, diff --git a/src/test.rs b/src/test.rs index a374d1fad4..a22c65d5f5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -104,6 +104,12 @@ pub(crate) fn inscription(content_type: &str, body: impl AsRef<[u8]>) -> Inscrip Inscription::new(None, Some(content_type.into()), Some(body.as_ref().into())) } +pub(crate) fn inscription_with_parent(content_type: &str, body: impl AsRef<[u8]>, parent: InscriptionId) -> Inscription { + let mut vec = parent.txid.to_vec(); + vec.push(parent.index.try_into().unwrap()); + Inscription::new(Some(vec), Some(content_type.into()), Some(body.as_ref().into())) +} + pub(crate) fn inscription_id(n: u32) -> InscriptionId { let hex = format!("{n:x}"); diff --git a/tests/test_server.rs b/tests/test_server.rs index d99fb3ff93..ac8e5d98c0 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -74,6 +74,7 @@ impl TestServer { thread::sleep(Duration::from_millis(25)); } + dbg!(path.as_ref()); let response = reqwest::blocking::get(self.url().join(path.as_ref()).unwrap()).unwrap(); assert_eq!(response.status(), StatusCode::OK); assert_regex_match!(response.text().unwrap(), regex.as_ref()); diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index 9bdcada61b..c9cf34c6bc 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -419,14 +419,12 @@ fn inscribe_with_parent_inscription() { .rpc_server(&rpc_server) .output::(); - rpc_server.mine_blocks(1); - assert_eq!(parent_id, child_output.parent.unwrap()); - println!("{}", child_output.inscription); + rpc_server.mine_blocks(1); TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex( - format!("/inscription/{}", child_output.inscription), + format!("/inscription/{}", dbg!(child_output.inscription)), format!(".*parent.*{}", parent_id), ); } From 01bc48eff41e87e2e221809b2c3881a01cd67ea7 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 7 Mar 2023 13:48:57 -0800 Subject: [PATCH 11/34] quick fix --- tests/wallet/inscribe.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index c9cf34c6bc..55ddc4846c 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -409,10 +409,10 @@ fn inscribe_with_parent_inscription() { rpc_server.mine_blocks(1); - TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex( - format!("/inscription/{parent_id}"), - format!(".*"), - ); + // TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex( + // format!("/inscription/{parent_id}"), + // format!(".*"), + // ); let child_output = CommandBuilder::new(format!("wallet inscribe --parent {parent_id} child.png")) .write("child.png", [1; 520]) From a5029a9cbf33e6899e06afb290aa5ed201ee850a Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 7 Mar 2023 15:19:10 -0800 Subject: [PATCH 12/34] fixed database stuff --- src/index.rs | 102 ++++++++-------- src/index/entry.rs | 142 ++++++++++++++++------- src/index/updater/inscription_updater.rs | 13 ++- src/inscription.rs | 26 +---- src/subcommand/wallet/inscribe.rs | 5 +- src/test.rs | 6 - tests/wallet/inscribe.rs | 4 +- 7 files changed, 167 insertions(+), 131 deletions(-) diff --git a/src/index.rs b/src/index.rs index c2e3d9bb47..0be4dc31de 100644 --- a/src/index.rs +++ b/src/index.rs @@ -2191,55 +2191,55 @@ mod tests { } } - #[test] - fn test_inscription_with_parent() { - // for context in Context::configurations() { - let context = Context::builder().build(); - - context.mine_blocks(1); - - let parent_txid = context.rpc_server.broadcast_tx(TransactionTemplate { - inputs: &[(1, 0, 0)], - witness: inscription("text/plain", "parent").to_witness(), - ..Default::default() - }); - - let parent_id = InscriptionId::from(parent_txid); - - context.mine_blocks(1); - - assert_eq!( - context.index.get_inscription_entry(parent_id).unwrap(), - Some(InscriptionEntry { - fee: 0, - height: 2, - number: 0, - parent: None, - sat: None, - timestamp: 2 - }) - ); - - let child_txid = context.rpc_server.broadcast_tx(TransactionTemplate { - inputs: &[(2, 1, 0)], - witness: inscription_with_parent("text/plain", "child", parent_id).to_witness(), - ..Default::default() - }); - - let child_id = InscriptionId::from(child_txid); - - context.mine_blocks(1); - - assert_eq!( - context.index.get_inscription_entry(child_id).unwrap(), - Some(InscriptionEntry { - fee: 0, - height: 2, - number: 0, - parent: Some(parent_id), - sat: None, - timestamp: 2 - }) - ); - } + // #[test] + // fn test_inscription_with_parent() { + // // for context in Context::configurations() { + // let context = Context::builder().build(); + // + // context.mine_blocks(1); + // + // let parent_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + // inputs: &[(1, 0, 0)], + // witness: inscription("text/plain", "parent").to_witness(), + // ..Default::default() + // }); + // + // let parent_id = InscriptionId::from(parent_txid); + // + // context.mine_blocks(1); + // + // assert_eq!( + // context.index.get_inscription_entry(parent_id).unwrap(), + // Some(InscriptionEntry { + // fee: 0, + // height: 2, + // number: 0, + // parent: None, + // sat: None, + // timestamp: 2 + // }) + // ); + // + // let child_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + // inputs: &[(2, 1, 0)], + // witness: inscription_with_parent("text/plain", "child", parent_id).to_witness(), + // ..Default::default() + // }); + // + // let child_id = InscriptionId::from(child_txid); + // + // context.mine_blocks(1); + // + // assert_eq!( + // context.index.get_inscription_entry(child_id).unwrap(), + // Some(InscriptionEntry { + // fee: 0, + // height: 2, + // number: 0, + // parent: Some(parent_id), + // sat: None, + // timestamp: 2 + // }) + // ); + // } } diff --git a/src/index/entry.rs b/src/index/entry.rs index f910112db6..5a971c1af7 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -89,17 +89,19 @@ impl Entry for InscriptionId { } } +type ParentValue = (u128, u128, u32); + impl Entry for Option { - type Value = (u128, u128, u32); + type Value = ParentValue; fn load(value: Self::Value) -> Self { if (0, 0, u32::MAX) == value { None } else { let (head, tail, index) = value; - debug_assert_eq!(index, 0); let head_array = head.to_le_bytes(); let tail_array = tail.to_le_bytes(); + let index_array = index.to_be_bytes(); let array = [ head_array[0], head_array[1], @@ -133,55 +135,22 @@ impl Entry for Option { tail_array[13], tail_array[14], tail_array[15], + index_array[0], + index_array[1], + index_array[2], + index_array[3], ]; - let txid = Txid::load(array); - // TODO: do we want to handle inscriptions not at index 0 - Some(InscriptionId::from(txid)) + + Some(InscriptionId::load(array)) } } - + // TODO: test head and tail byte order fn store(self) -> Self::Value { if let Some(inscription_id) = self { let txid_entry = inscription_id.txid.store(); - let head = u128::from_le_bytes([ - txid_entry[0], - txid_entry[1], - txid_entry[2], - txid_entry[3], - txid_entry[4], - txid_entry[5], - txid_entry[6], - txid_entry[7], - txid_entry[8], - txid_entry[9], - txid_entry[10], - txid_entry[11], - txid_entry[12], - txid_entry[13], - txid_entry[14], - txid_entry[15], - ]); - - let tail = u128::from_le_bytes([ - txid_entry[16 + 0], - txid_entry[16 + 1], - txid_entry[16 + 2], - txid_entry[16 + 3], - txid_entry[16 + 4], - txid_entry[16 + 5], - txid_entry[16 + 6], - txid_entry[16 + 7], - txid_entry[16 + 8], - txid_entry[16 + 9], - txid_entry[16 + 10], - txid_entry[16 + 11], - txid_entry[16 + 12], - txid_entry[16 + 13], - txid_entry[16 + 14], - txid_entry[16 + 15], - ]); - - (head, tail, inscription_id.index) + let little_end = u128::from_le_bytes(txid_entry[..16].try_into().unwrap()); + let big_end = u128::from_le_bytes(txid_entry[16..].try_into().unwrap()); + (little_end, big_end, inscription_id.index) } else { (0, 0, u32::MAX) } @@ -260,3 +229,86 @@ impl Entry for Txid { Txid::into_inner(self) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parent_entry() { + let inscription_id: Option = None; + + assert_eq!(inscription_id.store(), (0, 0, u32::MAX)); + assert_eq!( + as Entry>::load((0, 0, u32::MAX)), + inscription_id + ); + + let inscription_id = Some( + "0000000000000000000000000000000000000000000000000000000000000000i0" + .parse::() + .unwrap(), + ); + + assert_eq!(inscription_id.store(), (0, 0, 0)); + assert_eq!( + as Entry>::load((0, 0, 0)), + inscription_id + ); + + let inscription_id = Some( + "ffffffffffffffffffffffffffffffff00000000000000000000000000000000i0" + .parse::() + .unwrap(), + ); + + assert_eq!(inscription_id.store(), (0, u128::MAX, 0)); + assert_eq!( + as Entry>::load((0, u128::MAX, 0)), + inscription_id + ); + } + + #[test] + fn parent_entry_individual_byte_order() { + let inscription_id = Some( + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefi0" + .parse::() + .unwrap(), + ); + + assert_eq!( + inscription_id.store(), + ( + 0x0123456789abcdef0123456789abcdef, + 0x0123456789abcdef0123456789abcdef, + 0 + ) + ); + + assert_eq!( + as Entry>::load(( + 0x0123456789abcdef0123456789abcdef, + 0x0123456789abcdef0123456789abcdef, + 0 + )), + inscription_id + ); + } + + #[test] + fn parent_entry_index() { + let inscription_id = Some( + "0000000000000000000000000000000000000000000000000000000000000000i1" + .parse::() + .unwrap(), + ); + + assert_eq!(inscription_id.store(), (0, 0, 1)); + + assert_eq!( + as Entry>::load((0, 0, 1)), + inscription_id + ); + } +} diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 248b91ffae..a41a28204e 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -73,15 +73,13 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { pub(super) fn index_transaction_inscriptions( &mut self, tx: &Transaction, - txid: Txid, // we can calulcate this from tx. Is this expensive? + txid: Txid, input_sat_ranges: Option<&VecDeque<(u64, u64)>>, ) -> Result { let mut inscriptions = Vec::new(); - // go through all flotsam and ensure parent is there let mut input_value = 0; for tx_in in &tx.input { - // if coinbase if tx_in.previous_output.is_null() { input_value += Height(self.height).subsidy(); } else { @@ -113,7 +111,8 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { } } - // make sure no re-inscriptions + // TODO: find a different way to + // TODO: handle re-inscriptions properly if inscriptions.iter().all(|flotsam| flotsam.offset != 0) { if let Some(inscription) = Inscription::from_transaction(tx) { let parent = if let Some(parent_id) = inscription.get_parent_id() { @@ -132,7 +131,10 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { inscriptions.push(Flotsam { inscription_id: txid.into(), offset: 0, - origin: Origin::New((input_value - tx.output.iter().map(|txout| txout.value).sum::(), parent)), + origin: Origin::New(( + input_value - tx.output.iter().map(|txout| txout.value).sum::(), + parent, + )), }); } }; @@ -169,6 +171,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.update_inscription_location( input_sat_ranges, + // TODO: something with two inscriptions in the input inscriptions.next().unwrap(), new_satpoint, )?; diff --git a/src/inscription.rs b/src/inscription.rs index 54710d2632..2a810e969b 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -20,7 +20,7 @@ const PARENT_TAG: &[u8] = &[3]; #[derive(Debug, PartialEq, Clone)] pub(crate) struct Inscription { - parent: Option>, + parent: Option, content_type: Option>, body: Option>, } @@ -28,7 +28,7 @@ pub(crate) struct Inscription { impl Inscription { #[cfg(test)] pub(crate) fn new( - parent: Option>, + parent: Option, content_type: Option>, body: Option>, ) -> Self { @@ -65,14 +65,6 @@ impl Inscription { let content_type = Media::content_type_for_path(path)?; - let parent = if let Some(inscription_id) = parent { - let mut vec = inscription_id.txid.to_vec(); - vec.push(inscription_id.index.try_into().unwrap()); - Some(vec) - } else { - None - }; - Ok(Self { parent, body: Some(body), @@ -87,7 +79,7 @@ impl Inscription { .push_slice(PROTOCOL_ID); if let Some(parent) = &self.parent { - builder = builder.push_slice(PARENT_TAG).push_slice(parent); + builder = builder.push_slice(PARENT_TAG).push_slice(&parent.store()); } if let Some(content_type) = &self.content_type { @@ -139,15 +131,7 @@ impl Inscription { } pub(crate) fn get_parent_id(&self) -> Option { - if let Some(vec) = &self.parent { - vec - .clone() - .try_into() - .ok() - .map(|vec| InscriptionId::load(vec)) - } else { - None - } + self.parent } #[cfg(test)] @@ -279,7 +263,7 @@ impl<'a> InscriptionParser<'a> { return Ok(Some(Inscription { body, content_type, - parent, + parent: parent.map(|parent| InscriptionId::load(parent.as_slice().try_into().unwrap())), })); } diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 54c3da4f96..584521dffb 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -123,7 +123,10 @@ impl Inscribe { utxos.insert( partially_signed_reveal_tx.input[commit_input_offset].previous_output, Amount::from_sat( - unsigned_commit_tx.output[partially_signed_reveal_tx.input[commit_input_offset].previous_output.vout as usize].value, + unsigned_commit_tx.output[partially_signed_reveal_tx.input[commit_input_offset] + .previous_output + .vout as usize] + .value, ), ); diff --git a/src/test.rs b/src/test.rs index a22c65d5f5..a374d1fad4 100644 --- a/src/test.rs +++ b/src/test.rs @@ -104,12 +104,6 @@ pub(crate) fn inscription(content_type: &str, body: impl AsRef<[u8]>) -> Inscrip Inscription::new(None, Some(content_type.into()), Some(body.as_ref().into())) } -pub(crate) fn inscription_with_parent(content_type: &str, body: impl AsRef<[u8]>, parent: InscriptionId) -> Inscription { - let mut vec = parent.txid.to_vec(); - vec.push(parent.index.try_into().unwrap()); - Inscription::new(Some(vec), Some(content_type.into()), Some(body.as_ref().into())) -} - pub(crate) fn inscription_id(n: u32) -> InscriptionId { let hex = format!("{n:x}"); diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index 55ddc4846c..0b40f21ab1 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -410,8 +410,8 @@ fn inscribe_with_parent_inscription() { rpc_server.mine_blocks(1); // TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex( - // format!("/inscription/{parent_id}"), - // format!(".*"), + // format!("/inscription/{parent_id}"), + // format!(".*"), // ); let child_output = CommandBuilder::new(format!("wallet inscribe --parent {parent_id} child.png")) From ff6c809917a3996dd1e2216d02c1bb509ba96f21 Mon Sep 17 00:00:00 2001 From: Ordinally Date: Wed, 8 Mar 2023 20:25:28 +0100 Subject: [PATCH 13/34] Fix sighashflag issue ``` error: Failed to send reveal transaction because: JSON-RPC error: RPC error response: RpcError { code: -26, message: "non-mandatory-script-verify-flag (Invalid Schnorr signature)", data: None } ``` --- src/subcommand/wallet/inscribe.rs | 53 +++++++++++++++++-------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 584521dffb..07f8631a86 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -259,31 +259,32 @@ impl Inscribe { let commit_tx_address = Address::p2tr_tweaked(taproot_spend_info.output_key(), network); - let (mut inputs, mut outputs, commit_input_offset) = if let Some((satpoint, output)) = parent { - ( - vec![satpoint.outpoint, OutPoint::null()], - vec![ - TxOut { - script_pubkey: output.script_pubkey, - value: output.value, - }, - TxOut { + let (mut inputs, mut outputs, commit_input_offset) = + if let Some((satpoint, output)) = parent.clone() { + ( + vec![satpoint.outpoint, OutPoint::null()], + vec![ + TxOut { + script_pubkey: output.script_pubkey, + value: output.value, + }, + TxOut { + script_pubkey: destination.script_pubkey(), + value: 0, + }, + ], + 1, + ) + } else { + ( + vec![OutPoint::null()], + vec![TxOut { script_pubkey: destination.script_pubkey(), value: 0, - }, - ], - 1, - ) - } else { - ( - vec![OutPoint::null()], - vec![TxOut { - script_pubkey: destination.script_pubkey(), - value: 0, - }], - 0, - ) - }; + }], + 0, + ) + }; let (_, reveal_fee) = Self::build_reveal_transaction( &control_block, @@ -350,7 +351,11 @@ impl Inscribe { commit_input_offset, &Prevouts::One(commit_input_offset, output), TapLeafHash::from_script(&reveal_script, LeafVersion::TapScript), - SchnorrSighashType::AllPlusAnyoneCanPay, + if let Some(_parent) = parent { + SchnorrSighashType::AllPlusAnyoneCanPay + } else { + SchnorrSighashType::Default + }, ) .expect("signature hash should compute"); From 340fdef80da259acca77b6aed8449514488ccbf6 Mon Sep 17 00:00:00 2001 From: Ordinally Date: Wed, 8 Mar 2023 20:42:08 +0100 Subject: [PATCH 14/34] Fix signature error ``` thread 'main' panicked at 'signature hash should compute: PrevoutKind', src/subcommand/wallet/inscribe.rs:360:8 ``` --- src/subcommand/wallet/inscribe.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 07f8631a86..4c6f6e5e90 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -346,18 +346,22 @@ impl Inscribe { let mut sighash_cache = SighashCache::new(&mut reveal_tx); - let signature_hash = sighash_cache - .taproot_script_spend_signature_hash( + let signature_hash = if let Some(_parent) = parent { + sighash_cache.taproot_script_spend_signature_hash( commit_input_offset, &Prevouts::One(commit_input_offset, output), TapLeafHash::from_script(&reveal_script, LeafVersion::TapScript), - if let Some(_parent) = parent { - SchnorrSighashType::AllPlusAnyoneCanPay - } else { - SchnorrSighashType::Default - }, + SchnorrSighashType::AllPlusAnyoneCanPay, ) - .expect("signature hash should compute"); + } else { + sighash_cache.taproot_script_spend_signature_hash( + commit_input_offset, + &Prevouts::All(&[output]), + TapLeafHash::from_script(&reveal_script, LeafVersion::TapScript), + SchnorrSighashType::Default, + ) + } + .expect("signature hash should compute"); let signature = secp256k1.sign_schnorr( &secp256k1::Message::from_slice(signature_hash.as_inner()) From ca16f448ba35599c118080029a568426c4689d35 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 8 Mar 2023 13:35:51 -0800 Subject: [PATCH 15/34] refector re-inscription handling --- src/index/updater/inscription_updater.rs | 145 ++++++++++++++--------- src/inscription.rs | 8 +- 2 files changed, 92 insertions(+), 61 deletions(-) diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index a41a28204e..71d5901eeb 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -1,15 +1,15 @@ -use super::*; +use {super::*, std::collections::BTreeSet}; +#[derive(Clone, Copy)] pub(super) struct Flotsam { inscription_id: InscriptionId, offset: u64, origin: Origin, - // parent: Option, } // change name to Jetsam or more poetic german word +#[derive(Clone, Copy)] enum Origin { - // put Some(parent_id) in Origin::New() New((u64, Option)), Old(SatPoint), } @@ -76,68 +76,99 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { txid: Txid, input_sat_ranges: Option<&VecDeque<(u64, u64)>>, ) -> Result { - let mut inscriptions = Vec::new(); - + let mut floating_inscriptions = Vec::new(); + let mut inscribed_offsets = BTreeSet::new(); let mut input_value = 0; for tx_in in &tx.input { + // skip subsidy since no inscriptions possible if tx_in.previous_output.is_null() { input_value += Height(self.height).subsidy(); - } else { - for (old_satpoint, inscription_id) in - Index::inscriptions_on_output(self.satpoint_to_id, tx_in.previous_output)? - { - inscriptions.push(Flotsam { - offset: input_value + old_satpoint.offset, - inscription_id, - origin: Origin::Old(old_satpoint), + continue; + } + + // find existing inscriptions on input aka transfers + for (old_satpoint, inscription_id) in + Index::inscriptions_on_output(self.satpoint_to_id, tx_in.previous_output)? + { + floating_inscriptions.push(Flotsam { + offset: input_value + old_satpoint.offset, + inscription_id, + origin: Origin::Old(old_satpoint), + }); + + inscribed_offsets.insert(input_value + old_satpoint.offset); + } + + // find new inscriptions + if let Some(inscription) = Inscription::from_tx_input(tx_in) { + // ignore new inscriptions on already inscribed offset (sats) + if !inscribed_offsets.contains(&input_value) { + let parent = if let Some(parent_id) = inscription.get_parent_id() { + // parent has to be in an input before child + // think about specifying a more general approach in a protocol doc/BIP + if floating_inscriptions + .iter() + .any(|flotsam| flotsam.inscription_id == parent_id) + { + Some(parent_id) + } else { + None + } + } else { + None + }; + + floating_inscriptions.push(Flotsam { + inscription_id: InscriptionId { + txid, + index: input_value as u32, // TODO: is index a sat offset or and number of inscriptions offset + }, + offset: input_value, + origin: Origin::New((0, parent)), }); } + } - input_value += if let Some(value) = self.value_cache.remove(&tx_in.previous_output) { - value - } else if let Some(value) = self - .outpoint_to_value - .remove(&tx_in.previous_output.store())? - { - value.value() - } else { - self.value_receiver.blocking_recv().ok_or_else(|| { - anyhow!( - "failed to get transaction for {}", - tx_in.previous_output.txid - ) - })? - } + // different ways to get the utxo set (input amount) + input_value += if let Some(value) = self.value_cache.remove(&tx_in.previous_output) { + value + } else if let Some(value) = self + .outpoint_to_value + .remove(&tx_in.previous_output.store())? + { + value.value() + } else { + self.value_receiver.blocking_recv().ok_or_else(|| { + anyhow!( + "failed to get transaction for {}", + tx_in.previous_output.txid + ) + })? } } - // TODO: find a different way to - // TODO: handle re-inscriptions properly - if inscriptions.iter().all(|flotsam| flotsam.offset != 0) { - if let Some(inscription) = Inscription::from_transaction(tx) { - let parent = if let Some(parent_id) = inscription.get_parent_id() { - if inscriptions - .iter() - .any(|flotsam| flotsam.inscription_id == parent_id) - { - Some(parent_id) - } else { - None + let mut floating_inscriptions = floating_inscriptions + .into_iter() + .map(|flotsam| { + if let Flotsam { + inscription_id, + offset, + origin: Origin::New((_, parent)), + } = flotsam + { + Flotsam { + inscription_id, + offset, + origin: Origin::New(( + input_value - tx.output.iter().map(|txout| txout.value).sum::(), + parent, + )), } } else { - None - }; - - inscriptions.push(Flotsam { - inscription_id: txid.into(), - offset: 0, - origin: Origin::New(( - input_value - tx.output.iter().map(|txout| txout.value).sum::(), - parent, - )), - }); - } - }; + flotsam + } + }) + .collect::>(); let is_coinbase = tx .input @@ -146,11 +177,11 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { .unwrap_or_default(); if is_coinbase { - inscriptions.append(&mut self.flotsam); + floating_inscriptions.append(&mut self.flotsam); } - inscriptions.sort_by_key(|flotsam| flotsam.offset); - let mut inscriptions = inscriptions.into_iter().peekable(); + floating_inscriptions.sort_by_key(|flotsam| flotsam.offset); + let mut inscriptions = floating_inscriptions.into_iter().peekable(); let mut output_value = 0; for (vout, tx_out) in tx.output.iter().enumerate() { @@ -171,7 +202,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.update_inscription_location( input_sat_ranges, - // TODO: something with two inscriptions in the input + // TODO: do something with two inscriptions in the input inscriptions.next().unwrap(), new_satpoint, )?; diff --git a/src/inscription.rs b/src/inscription.rs index 2a810e969b..1d53c85cdd 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -40,13 +40,13 @@ impl Inscription { } pub(crate) fn from_transaction(tx: &Transaction) -> Option { - // let mut inscriptions = Vec::new(); - // for input in tx.input { - // InscriptionParser::parse(input.witness).ok() - // } InscriptionParser::parse(&tx.input.get(0)?.witness).ok() } + pub(crate) fn from_tx_input(tx_in: &TxIn) -> Option { + InscriptionParser::parse(&tx_in.witness).ok() + } + pub(crate) fn from_file( chain: Chain, path: impl AsRef, From 791e21cc6bedc33af746ef13cbc27dd03c3b673a Mon Sep 17 00:00:00 2001 From: ordinally Date: Wed, 8 Mar 2023 22:37:43 +0100 Subject: [PATCH 16/34] More robust parsing --- src/inscription.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/inscription.rs b/src/inscription.rs index 2a810e969b..279380bda2 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -263,7 +263,15 @@ impl<'a> InscriptionParser<'a> { return Ok(Some(Inscription { body, content_type, - parent: parent.map(|parent| InscriptionId::load(parent.as_slice().try_into().unwrap())), + parent: match parent { + None => None, + Some(bytes) => { + if bytes.len() != 36 { + return Err(InscriptionError::InvalidInscription) + } + Some(InscriptionId::load(bytes.as_slice().try_into().unwrap())) + } + }, })); } From 4e4cf9f96dbb30cd1f439ff0703832ca51bfc7b0 Mon Sep 17 00:00:00 2001 From: ordinally Date: Fri, 10 Mar 2023 13:33:34 +0100 Subject: [PATCH 17/34] Add temporary debug logging and TODO regarding bug in building the reveal TX for inscriptions containing a parent. --- src/subcommand/wallet/inscribe.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 4c6f6e5e90..65db2d389d 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -1,3 +1,5 @@ +use bitcoin::consensus::serialize; + use { super::*, crate::wallet::Wallet, @@ -154,10 +156,20 @@ impl Inscribe { .send_raw_transaction(&signed_raw_commit_tx) .context("Failed to send commit transaction")?; + log::debug!( + "partially signed reveal tx: {}", + hex::encode(serialize(&partially_signed_reveal_tx)) + ); let reveal = if self.parent.is_some() { let fully_signed_raw_reveal_tx = client .sign_raw_transaction_with_wallet(&partially_signed_reveal_tx, None, None)? .hex; + // TODO: there is a bug here, the fully signed reveal TX no longer contains + // the inscription data + log::debug!( + "fully signed reveal tx: {}", + hex::encode(serialize(&fully_signed_raw_reveal_tx)) + ); client .send_raw_transaction(&fully_signed_raw_reveal_tx) From 24f5bdbdf619bbbdac5a29f6bea954eb3b7c71c8 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 10 Mar 2023 10:07:51 -0800 Subject: [PATCH 18/34] inscription index now correct --- src/index.rs | 162 +++++++++++++---------- src/index/updater/inscription_updater.rs | 8 +- src/subcommand/server.rs | 40 +++--- src/test.rs | 4 + test-bitcoincore-rpc/src/lib.rs | 4 +- test-bitcoincore-rpc/src/state.rs | 6 +- 6 files changed, 123 insertions(+), 101 deletions(-) diff --git a/src/index.rs b/src/index.rs index 0be4dc31de..f9b3927601 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1032,7 +1032,7 @@ mod tests { let inscription = inscription("text/plain;charset=utf-8", "hello"); let template = TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription.to_witness(), + witnesses: vec![inscription.to_witness()], ..Default::default() }; @@ -1378,7 +1378,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -1403,7 +1403,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -1447,7 +1447,7 @@ mod tests { let first_txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); @@ -1455,7 +1455,7 @@ mod tests { let second_txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(2, 0, 0)], - witness: inscription("text/png", [1; 100]).to_witness(), + witnesses: vec![inscription("text/png", [1; 100]).to_witness()], ..Default::default() }); let second_inscription_id = InscriptionId::from(second_txid); @@ -1502,7 +1502,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -1551,7 +1551,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -1595,7 +1595,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -1632,7 +1632,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], fee: 50 * COIN_VALUE, - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -1661,7 +1661,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], fee: 50 * COIN_VALUE, - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -1687,7 +1687,7 @@ mod tests { let first_txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], fee: 50 * COIN_VALUE, - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let first_inscription_id = InscriptionId::from(first_txid); @@ -1698,7 +1698,7 @@ mod tests { let second_txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(3, 0, 0)], fee: 50 * COIN_VALUE, - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let second_inscription_id = InscriptionId::from(second_txid); @@ -1812,7 +1812,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(2, 0, 0)], outputs: 2, - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -1844,7 +1844,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], outputs: 2, - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], output_values: &[0, 50 * COIN_VALUE], ..Default::default() }); @@ -1870,7 +1870,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], fee: 50 * COIN_VALUE, - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -1979,7 +1979,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); @@ -2038,7 +2038,7 @@ mod tests { let first = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); @@ -2071,7 +2071,7 @@ mod tests { let second = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(2, 1, 0)], - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); @@ -2110,7 +2110,7 @@ mod tests { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -2137,7 +2137,7 @@ mod tests { for i in 0..103 { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(i + 1, 0, 0)], - witness: inscription("text/plain", "hello").to_witness(), + witnesses: vec![inscription("text/plain", "hello").to_witness()], ..Default::default() }); ids.push(InscriptionId::from(txid)); @@ -2191,55 +2191,75 @@ mod tests { } } - // #[test] - // fn test_inscription_with_parent() { - // // for context in Context::configurations() { - // let context = Context::builder().build(); - // - // context.mine_blocks(1); - // - // let parent_txid = context.rpc_server.broadcast_tx(TransactionTemplate { - // inputs: &[(1, 0, 0)], - // witness: inscription("text/plain", "parent").to_witness(), - // ..Default::default() - // }); - // - // let parent_id = InscriptionId::from(parent_txid); - // - // context.mine_blocks(1); - // - // assert_eq!( - // context.index.get_inscription_entry(parent_id).unwrap(), - // Some(InscriptionEntry { - // fee: 0, - // height: 2, - // number: 0, - // parent: None, - // sat: None, - // timestamp: 2 - // }) - // ); - // - // let child_txid = context.rpc_server.broadcast_tx(TransactionTemplate { - // inputs: &[(2, 1, 0)], - // witness: inscription_with_parent("text/plain", "child", parent_id).to_witness(), - // ..Default::default() - // }); - // - // let child_id = InscriptionId::from(child_txid); - // - // context.mine_blocks(1); - // - // assert_eq!( - // context.index.get_inscription_entry(child_id).unwrap(), - // Some(InscriptionEntry { - // fee: 0, - // height: 2, - // number: 0, - // parent: Some(parent_id), - // sat: None, - // timestamp: 2 - // }) - // ); - // } + #[test] + fn test_inscription_with_parent() { + // for context in Context::configurations() { + let context = Context::builder().build(); + + context.mine_blocks(1); + + let parent_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0)], + witnesses: vec![inscription("text/plain", "parent").to_witness()], + ..Default::default() + }); + + let parent_id = InscriptionId::from(parent_txid); + + context.mine_blocks(1); + + assert_eq!( + context.index.get_inscription_entry(parent_id).unwrap(), + Some(InscriptionEntry { + fee: 0, + height: 2, + number: 0, + parent: None, + sat: None, + timestamp: 2 + }) + ); + + let child_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 1, 0), (2, 0, 0)], + witnesses: vec![ + Witness::new(), + inscription_with_parent("text/plain", "child", parent_id).to_witness(), + ], + ..Default::default() + }); + + let child_id = InscriptionId { + txid: child_txid, + index: 0, + }; + + context.mine_blocks(1); + + // parent is transferred successfully + context.index.assert_inscription_location( + parent_id, + SatPoint { + outpoint: OutPoint { + txid: child_txid, + vout: 0, + }, + offset: 0, + }, + 50 * COIN_VALUE, + ); + + // child inscription successfully added to database + assert_eq!( + context.index.get_inscription_entry(child_id).unwrap(), + Some(InscriptionEntry { + fee: 0, + height: 3, + number: 1, + parent: Some(parent_id), + sat: None, + timestamp: 3 + }) + ); + } } diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 71d5901eeb..5a8c393ed6 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -1,6 +1,6 @@ use {super::*, std::collections::BTreeSet}; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub(super) struct Flotsam { inscription_id: InscriptionId, offset: u64, @@ -8,7 +8,7 @@ pub(super) struct Flotsam { } // change name to Jetsam or more poetic german word -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] enum Origin { New((u64, Option)), Old(SatPoint), @@ -121,7 +121,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { floating_inscriptions.push(Flotsam { inscription_id: InscriptionId { txid, - index: input_value as u32, // TODO: is index a sat offset or and number of inscriptions offset + index: 0, }, offset: input_value, origin: Origin::New((0, parent)), @@ -147,6 +147,8 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { } } + // TODO: inefficient + // calulate genesis fee for new inscriptions let mut floating_inscriptions = floating_inscriptions .into_iter() .map(|flotsam| { diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 905aa69484..01f202aeeb 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -2009,7 +2009,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain;charset=utf-8", "hello").to_witness(), + witnesses: vec![inscription("text/plain;charset=utf-8", "hello").to_witness()], ..Default::default() }); @@ -2030,7 +2030,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain;charset=utf-8", b"\xc3\x28").to_witness(), + witnesses: vec![inscription("text/plain;charset=utf-8", b"\xc3\x28").to_witness()], ..Default::default() }); @@ -2050,11 +2050,11 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription( + witnesses: vec![inscription( "text/plain;charset=utf-8", "", ) - .to_witness(), + .to_witness()], ..Default::default() }); @@ -2075,7 +2075,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("audio/flac", "hello").to_witness(), + witnesses: vec![inscription("audio/flac", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -2096,7 +2096,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("application/pdf", "hello").to_witness(), + witnesses: vec![inscription("application/pdf", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -2117,7 +2117,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("image/png", "hello").to_witness(), + witnesses: vec![inscription("image/png", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -2139,7 +2139,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/html;charset=utf-8", "hello").to_witness(), + witnesses: vec![inscription("text/html;charset=utf-8", "hello").to_witness()], ..Default::default() }); @@ -2160,7 +2160,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/foo", "hello").to_witness(), + witnesses: vec![inscription("text/foo", "hello").to_witness()], ..Default::default() }); @@ -2181,7 +2181,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("video/webm", "hello").to_witness(), + witnesses: vec![inscription("video/webm", "hello").to_witness()], ..Default::default() }); let inscription_id = InscriptionId::from(txid); @@ -2202,7 +2202,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/foo", "hello").to_witness(), + witnesses: vec![inscription("text/foo", "hello").to_witness()], ..Default::default() }); @@ -2222,7 +2222,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/foo", "hello").to_witness(), + witnesses: vec![inscription("text/foo", "hello").to_witness()], ..Default::default() }); @@ -2242,7 +2242,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/foo", "hello").to_witness(), + witnesses: vec![inscription("text/foo", "hello").to_witness()], ..Default::default() }); @@ -2274,7 +2274,7 @@ mod tests { server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/foo", "hello").to_witness(), + witnesses: vec![inscription("text/foo", "hello").to_witness()], ..Default::default() }); @@ -2294,7 +2294,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: Inscription::new(None, Some("foo/bar".as_bytes().to_vec()), None).to_witness(), + witnesses: vec![Inscription::new(None, Some("foo/bar".as_bytes().to_vec()), None).to_witness()], ..Default::default() }); @@ -2316,7 +2316,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: Inscription::new(None, Some("image/png".as_bytes().to_vec()), None).to_witness(), + witnesses: vec![Inscription::new(None, Some("image/png".as_bytes().to_vec()), None).to_witness()], ..Default::default() }); @@ -2338,7 +2338,7 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/foo", "hello").to_witness(), + witnesses: vec![inscription("text/foo", "hello").to_witness()], ..Default::default() }); @@ -2370,7 +2370,7 @@ mod tests { server.mine_blocks(1); server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(i + 1, 0, 0)], - witness: inscription("text/foo", "hello").to_witness(), + witnesses: vec![inscription("text/foo", "hello").to_witness()], ..Default::default() }); } @@ -2392,7 +2392,7 @@ mod tests { server.mine_blocks(1); server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(i + 1, 0, 0)], - witness: inscription("text/foo", "hello").to_witness(), + witnesses: vec![inscription("text/foo", "hello").to_witness()], ..Default::default() }); } @@ -2456,7 +2456,7 @@ mod tests { bitcoin_rpc_server.mine_blocks(1); let txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witness: inscription("text/plain;charset=utf-8", "hello").to_witness(), + witnesses: vec![inscription("text/plain;charset=utf-8", "hello").to_witness()], ..Default::default() }); let inscription = InscriptionId::from(txid); diff --git a/src/test.rs b/src/test.rs index a374d1fad4..9ff0648be3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -104,6 +104,10 @@ pub(crate) fn inscription(content_type: &str, body: impl AsRef<[u8]>) -> Inscrip Inscription::new(None, Some(content_type.into()), Some(body.as_ref().into())) } +pub(crate) fn inscription_with_parent(content_type: &str, body: impl AsRef<[u8]>, parent: InscriptionId) -> Inscription { + Inscription::new(Some(parent), Some(content_type.into()), Some(body.as_ref().into())) +} + pub(crate) fn inscription_id(n: u32) -> InscriptionId { let hex = format!("{n:x}"); diff --git a/test-bitcoincore-rpc/src/lib.rs b/test-bitcoincore-rpc/src/lib.rs index 7051d283d6..d7db02d5a0 100644 --- a/test-bitcoincore-rpc/src/lib.rs +++ b/test-bitcoincore-rpc/src/lib.rs @@ -118,7 +118,7 @@ pub struct TransactionTemplate<'a> { pub inputs: &'a [(usize, usize, usize)], pub output_values: &'a [u64], pub outputs: usize, - pub witness: Witness, + pub witnesses: Vec, } #[derive(Clone, Debug, PartialEq)] @@ -150,7 +150,7 @@ impl<'a> Default for TransactionTemplate<'a> { inputs: &[], output_values: &[], outputs: 1, - witness: Witness::default(), + witnesses: vec![], } } } diff --git a/test-bitcoincore-rpc/src/state.rs b/test-bitcoincore-rpc/src/state.rs index 80f887c003..26f049cc2b 100644 --- a/test-bitcoincore-rpc/src/state.rs +++ b/test-bitcoincore-rpc/src/state.rs @@ -138,11 +138,7 @@ impl State { previous_output: OutPoint::new(tx.txid(), *vout as u32), script_sig: Script::new(), sequence: Sequence::MAX, - witness: if i == 0 { - template.witness.clone() - } else { - Witness::new() - }, + witness: template.witnesses.get(i).map_or(Witness::new(), |i| i.clone()), }); } From 1f33ff782dd20cee8c5f10826e126eb6974ad35d Mon Sep 17 00:00:00 2001 From: ordinally Date: Fri, 10 Mar 2023 20:33:59 +0100 Subject: [PATCH 19/34] Encode schnorr signature with proper sighashtype when inscribing with parent Thanks @ericatallah ! https://github.com/raphjaph/ord/pull/2/commits/c46d003e993f8a41c5aa1ea9e227f9c2a2cd4dcf --- src/subcommand/wallet/inscribe.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 65db2d389d..62f126e650 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -1,4 +1,4 @@ -use bitcoin::consensus::serialize; +use bitcoin::{consensus::serialize, SchnorrSig}; use { super::*, @@ -165,7 +165,7 @@ impl Inscribe { .sign_raw_transaction_with_wallet(&partially_signed_reveal_tx, None, None)? .hex; // TODO: there is a bug here, the fully signed reveal TX no longer contains - // the inscription data + // the inscription data when backup key is in bitcoin core wallet log::debug!( "fully signed reveal tx: {}", hex::encode(serialize(&fully_signed_raw_reveal_tx)) @@ -189,10 +189,6 @@ impl Inscribe { })?; }; - // if self.parent.is_some() { - // println!("{}", partially_signed_reveal_tx.raw_hex()); - // } - Ok(()) } @@ -358,7 +354,7 @@ impl Inscribe { let mut sighash_cache = SighashCache::new(&mut reveal_tx); - let signature_hash = if let Some(_parent) = parent { + let signature_hash = if parent.is_some() { sighash_cache.taproot_script_spend_signature_hash( commit_input_offset, &Prevouts::One(commit_input_offset, output), @@ -384,7 +380,15 @@ impl Inscribe { let witness = sighash_cache .witness_mut(commit_input_offset) .expect("getting mutable witness reference should work"); - witness.push(signature.as_ref()); + if parent.is_some() { + let encoded_sig = SchnorrSig { + sig: signature, + hash_ty: SchnorrSighashType::AllPlusAnyoneCanPay, + }; + witness.push(encoded_sig.to_vec()); + } else { + witness.push(signature.as_ref()); + } witness.push(reveal_script); witness.push(&control_block.serialize()); From 5f82a48bfd8f657d045738dc2f7cf6d904743753 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 10 Mar 2023 12:54:02 -0800 Subject: [PATCH 20/34] stash --- src/subcommand/wallet/inscribe.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 65db2d389d..909732693e 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -160,6 +160,9 @@ impl Inscribe { "partially signed reveal tx: {}", hex::encode(serialize(&partially_signed_reveal_tx)) ); + + // TODO: get Bitcoin Core to attach reveal witness + // after signing replace witness with correct one let reveal = if self.parent.is_some() { let fully_signed_raw_reveal_tx = client .sign_raw_transaction_with_wallet(&partially_signed_reveal_tx, None, None)? From edd0a76ca941ed5a85b80a52274c79584081cef9 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 10 Mar 2023 14:54:34 -0800 Subject: [PATCH 21/34] refactoring --- src/subcommand/wallet/inscribe.rs | 42 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index f321fcd7d6..5880685f75 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -355,27 +355,31 @@ impl Inscribe { bail!("commit transaction output would be dust"); } - let mut sighash_cache = SighashCache::new(&mut reveal_tx); + // NB. This binding is to avoid borrow-checker problems + let prevouts_all_inputs = &[output]; - let signature_hash = if parent.is_some() { - sighash_cache.taproot_script_spend_signature_hash( - commit_input_offset, - &Prevouts::One(commit_input_offset, output), - TapLeafHash::from_script(&reveal_script, LeafVersion::TapScript), + let (prevouts, hash_type) = if parent.is_some() { + ( + Prevouts::One(commit_input_offset, output), SchnorrSighashType::AllPlusAnyoneCanPay, ) } else { - sighash_cache.taproot_script_spend_signature_hash( + (Prevouts::All(prevouts_all_inputs), SchnorrSighashType::Default) + }; + + let mut sighash_cache = SighashCache::new(&mut reveal_tx); + + let message = sighash_cache + .taproot_script_spend_signature_hash( commit_input_offset, - &Prevouts::All(&[output]), + &prevouts, TapLeafHash::from_script(&reveal_script, LeafVersion::TapScript), - SchnorrSighashType::Default, + hash_type, ) - } - .expect("signature hash should compute"); + .expect("signature hash should compute"); - let signature = secp256k1.sign_schnorr( - &secp256k1::Message::from_slice(signature_hash.as_inner()) + let sig = secp256k1.sign_schnorr( + &secp256k1::Message::from_slice(message.as_inner()) .expect("should be cryptographically secure hash"), &key_pair, ); @@ -383,15 +387,9 @@ impl Inscribe { let witness = sighash_cache .witness_mut(commit_input_offset) .expect("getting mutable witness reference should work"); - if parent.is_some() { - let encoded_sig = SchnorrSig { - sig: signature, - hash_ty: SchnorrSighashType::AllPlusAnyoneCanPay, - }; - witness.push(encoded_sig.to_vec()); - } else { - witness.push(signature.as_ref()); - } + + witness.push(SchnorrSig { sig, hash_ty: hash_type }.to_vec()); + witness.push(reveal_script); witness.push(&control_block.serialize()); From 044d6dec14e90d4a40d16bf3f7355d2bfc799848 Mon Sep 17 00:00:00 2001 From: ordinally <11798624+veryordinally@users.noreply.github.com> Date: Fri, 10 Mar 2023 23:25:52 -0800 Subject: [PATCH 22/34] Update src/inscription.rs Ignore malformed inscriptions. Co-authored-by: Clarke Benedict --- src/inscription.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/inscription.rs b/src/inscription.rs index 2bf23a69a0..f5a08b986d 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -263,15 +263,9 @@ impl<'a> InscriptionParser<'a> { return Ok(Some(Inscription { body, content_type, - parent: match parent { - None => None, - Some(bytes) => { - if bytes.len() != 36 { - return Err(InscriptionError::InvalidInscription) - } - Some(InscriptionId::load(bytes.as_slice().try_into().unwrap())) - } - }, + parent: parent.and_then(|parent| { + Some(InscriptionId::load(parent.as_slice().try_into().ok()?)) + }), })); } From aaaffb6aa124de766ac2370e8b843e82c46c9649 Mon Sep 17 00:00:00 2001 From: ordinally Date: Sat, 11 Mar 2023 13:03:05 +0100 Subject: [PATCH 23/34] Fixed formatting --- src/index/updater/inscription_updater.rs | 5 +---- src/inscription.rs | 5 ++--- src/subcommand/server.rs | 8 ++++++-- src/subcommand/wallet/c3.json | 7 +++++++ src/subcommand/wallet/inscribe.rs | 13 +++++++++++-- src/test.rs | 12 ++++++++++-- test-bitcoincore-rpc/src/state.rs | 5 ++++- 7 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 src/subcommand/wallet/c3.json diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 5a8c393ed6..d92cc080e1 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -119,10 +119,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { }; floating_inscriptions.push(Flotsam { - inscription_id: InscriptionId { - txid, - index: 0, - }, + inscription_id: InscriptionId { txid, index: 0 }, offset: input_value, origin: Origin::New((0, parent)), }); diff --git a/src/inscription.rs b/src/inscription.rs index f5a08b986d..5169194db8 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -263,9 +263,8 @@ impl<'a> InscriptionParser<'a> { return Ok(Some(Inscription { body, content_type, - parent: parent.and_then(|parent| { - Some(InscriptionId::load(parent.as_slice().try_into().ok()?)) - }), + parent: parent + .and_then(|parent| Some(InscriptionId::load(parent.as_slice().try_into().ok()?))), })); } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 01f202aeeb..4990451952 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -2294,7 +2294,9 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witnesses: vec![Inscription::new(None, Some("foo/bar".as_bytes().to_vec()), None).to_witness()], + witnesses: vec![ + Inscription::new(None, Some("foo/bar".as_bytes().to_vec()), None).to_witness(), + ], ..Default::default() }); @@ -2316,7 +2318,9 @@ mod tests { let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { inputs: &[(1, 0, 0)], - witnesses: vec![Inscription::new(None, Some("image/png".as_bytes().to_vec()), None).to_witness()], + witnesses: vec![ + Inscription::new(None, Some("image/png".as_bytes().to_vec()), None).to_witness(), + ], ..Default::default() }); diff --git a/src/subcommand/wallet/c3.json b/src/subcommand/wallet/c3.json new file mode 100644 index 0000000000..127130b9a9 --- /dev/null +++ b/src/subcommand/wallet/c3.json @@ -0,0 +1,7 @@ +{ + "commit": "9664939b7b134207e6caf9c9afa050cf570feb9f31b488cb4e20aa599613e783", + "inscription": "e6f437e2090e3a0fa79490dccce7f15e308920b38240d8d276a220e76c0f57c2i0", + "parent": "6fbec3a2ee5bb8adc56522bea8d4efd7adc64a46da5491ca4eb1e59da0d555efi0", + "reveal": "e6f437e2090e3a0fa79490dccce7f15e308920b38240d8d276a220e76c0f57c2", + "fees": 497 +} \ No newline at end of file diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 5880685f75..19c950d7f9 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -364,7 +364,10 @@ impl Inscribe { SchnorrSighashType::AllPlusAnyoneCanPay, ) } else { - (Prevouts::All(prevouts_all_inputs), SchnorrSighashType::Default) + ( + Prevouts::All(prevouts_all_inputs), + SchnorrSighashType::Default, + ) }; let mut sighash_cache = SighashCache::new(&mut reveal_tx); @@ -388,7 +391,13 @@ impl Inscribe { .witness_mut(commit_input_offset) .expect("getting mutable witness reference should work"); - witness.push(SchnorrSig { sig, hash_ty: hash_type }.to_vec()); + witness.push( + SchnorrSig { + sig, + hash_ty: hash_type, + } + .to_vec(), + ); witness.push(reveal_script); witness.push(&control_block.serialize()); diff --git a/src/test.rs b/src/test.rs index 9ff0648be3..59df4a06fb 100644 --- a/src/test.rs +++ b/src/test.rs @@ -104,8 +104,16 @@ pub(crate) fn inscription(content_type: &str, body: impl AsRef<[u8]>) -> Inscrip Inscription::new(None, Some(content_type.into()), Some(body.as_ref().into())) } -pub(crate) fn inscription_with_parent(content_type: &str, body: impl AsRef<[u8]>, parent: InscriptionId) -> Inscription { - Inscription::new(Some(parent), Some(content_type.into()), Some(body.as_ref().into())) +pub(crate) fn inscription_with_parent( + content_type: &str, + body: impl AsRef<[u8]>, + parent: InscriptionId, +) -> Inscription { + Inscription::new( + Some(parent), + Some(content_type.into()), + Some(body.as_ref().into()), + ) } pub(crate) fn inscription_id(n: u32) -> InscriptionId { diff --git a/test-bitcoincore-rpc/src/state.rs b/test-bitcoincore-rpc/src/state.rs index 26f049cc2b..08ea2a09c4 100644 --- a/test-bitcoincore-rpc/src/state.rs +++ b/test-bitcoincore-rpc/src/state.rs @@ -138,7 +138,10 @@ impl State { previous_output: OutPoint::new(tx.txid(), *vout as u32), script_sig: Script::new(), sequence: Sequence::MAX, - witness: template.witnesses.get(i).map_or(Witness::new(), |i| i.clone()), + witness: template + .witnesses + .get(i) + .map_or(Witness::new(), |i| i.clone()), }); } From 3ef11d016408e005e6e9d592fced93784634d3c4 Mon Sep 17 00:00:00 2001 From: ordinally Date: Sat, 11 Mar 2023 14:45:01 +0100 Subject: [PATCH 24/34] Handle child inscription index correctly. Indirect pull from https://github.com/raphjaph/ord/pull/3/commits Props to @ericatallah for contributing this --- src/index.rs | 2 +- src/index/updater/inscription_updater.rs | 19 ++++++++++--------- src/subcommand/wallet/inscribe.rs | 7 ++++++- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/index.rs b/src/index.rs index f9b3927601..bfbb0f95af 100644 --- a/src/index.rs +++ b/src/index.rs @@ -552,7 +552,7 @@ impl Index { Ok( self .get_transaction(inscription_id.txid)? - .and_then(|tx| Inscription::from_transaction(&tx)), + .and_then(|tx| Inscription::from_tx_input(tx.input.get(inscription_id.index as usize)?)), ) } diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index d92cc080e1..efe2891668 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -103,23 +103,26 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { if let Some(inscription) = Inscription::from_tx_input(tx_in) { // ignore new inscriptions on already inscribed offset (sats) if !inscribed_offsets.contains(&input_value) { - let parent = if let Some(parent_id) = inscription.get_parent_id() { + let (parent, input_index) = if let Some(parent_id) = inscription.get_parent_id() { // parent has to be in an input before child // think about specifying a more general approach in a protocol doc/BIP if floating_inscriptions .iter() .any(|flotsam| flotsam.inscription_id == parent_id) { - Some(parent_id) + (Some(parent_id), 1) } else { - None + (None, 0) } } else { - None + (None, 0) }; floating_inscriptions.push(Flotsam { - inscription_id: InscriptionId { txid, index: 0 }, + inscription_id: InscriptionId { + txid, + index: input_index, + }, offset: input_value, origin: Origin::New((0, parent)), }); @@ -146,6 +149,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { // TODO: inefficient // calulate genesis fee for new inscriptions + let total_output_value = tx.output.iter().map(|txout| txout.value).sum::(); let mut floating_inscriptions = floating_inscriptions .into_iter() .map(|flotsam| { @@ -158,10 +162,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { Flotsam { inscription_id, offset, - origin: Origin::New(( - input_value - tx.output.iter().map(|txout| txout.value).sum::(), - parent, - )), + origin: Origin::New((input_value - total_output_value, parent)), } } else { flotsam diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 19c950d7f9..5abb0ea104 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -183,10 +183,15 @@ impl Inscribe { .context("Failed to send reveal transaction")? }; + let inscription = InscriptionId { + txid: reveal, + index: commit_input_offset as u32, + }; + print_json(Output { commit, reveal, - inscription: reveal.into(), + inscription, parent: self.parent, fees, })?; From cb7573bda7cf4c0549271c6d5b7d7c7e99f68d3d Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sun, 12 Mar 2023 17:41:07 -0700 Subject: [PATCH 25/34] rename --- src/subcommand/wallet/inscribe.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 5880685f75..3fb8e0bb39 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -358,7 +358,7 @@ impl Inscribe { // NB. This binding is to avoid borrow-checker problems let prevouts_all_inputs = &[output]; - let (prevouts, hash_type) = if parent.is_some() { + let (prevouts, hash_ty) = if parent.is_some() { ( Prevouts::One(commit_input_offset, output), SchnorrSighashType::AllPlusAnyoneCanPay, @@ -374,7 +374,7 @@ impl Inscribe { commit_input_offset, &prevouts, TapLeafHash::from_script(&reveal_script, LeafVersion::TapScript), - hash_type, + hash_ty, ) .expect("signature hash should compute"); @@ -388,7 +388,7 @@ impl Inscribe { .witness_mut(commit_input_offset) .expect("getting mutable witness reference should work"); - witness.push(SchnorrSig { sig, hash_ty: hash_type }.to_vec()); + witness.push(SchnorrSig { sig, hash_ty }.to_vec()); witness.push(reveal_script); witness.push(&control_block.serialize()); From b05c15f1606e240f453df8d7c87cca5a0f01ab38 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sun, 12 Mar 2023 22:56:58 -0700 Subject: [PATCH 26/34] fix inscriptionId index and remove unused file --- src/index.rs | 3 ++- src/index/updater/inscription_updater.rs | 10 +++++----- src/subcommand/wallet/c3.json | 7 ------- 3 files changed, 7 insertions(+), 13 deletions(-) delete mode 100644 src/subcommand/wallet/c3.json diff --git a/src/index.rs b/src/index.rs index bfbb0f95af..a948056997 100644 --- a/src/index.rs +++ b/src/index.rs @@ -552,7 +552,8 @@ impl Index { Ok( self .get_transaction(inscription_id.txid)? - .and_then(|tx| Inscription::from_tx_input(tx.input.get(inscription_id.index as usize)?)), + // .and_then(|tx| Inscription::from_tx_input(tx.input.get(inscription_id.index as usize)?)), + .and_then(|tx| Inscription::from_transaction(&tx)), ) } diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index efe2891668..6b232d8b88 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -103,25 +103,25 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { if let Some(inscription) = Inscription::from_tx_input(tx_in) { // ignore new inscriptions on already inscribed offset (sats) if !inscribed_offsets.contains(&input_value) { - let (parent, input_index) = if let Some(parent_id) = inscription.get_parent_id() { + let parent = if let Some(parent_id) = inscription.get_parent_id() { // parent has to be in an input before child // think about specifying a more general approach in a protocol doc/BIP if floating_inscriptions .iter() .any(|flotsam| flotsam.inscription_id == parent_id) { - (Some(parent_id), 1) + Some(parent_id) } else { - (None, 0) + None } } else { - (None, 0) + None }; floating_inscriptions.push(Flotsam { inscription_id: InscriptionId { txid, - index: input_index, + index: 0, }, offset: input_value, origin: Origin::New((0, parent)), diff --git a/src/subcommand/wallet/c3.json b/src/subcommand/wallet/c3.json deleted file mode 100644 index 127130b9a9..0000000000 --- a/src/subcommand/wallet/c3.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "commit": "9664939b7b134207e6caf9c9afa050cf570feb9f31b488cb4e20aa599613e783", - "inscription": "e6f437e2090e3a0fa79490dccce7f15e308920b38240d8d276a220e76c0f57c2i0", - "parent": "6fbec3a2ee5bb8adc56522bea8d4efd7adc64a46da5491ca4eb1e59da0d555efi0", - "reveal": "e6f437e2090e3a0fa79490dccce7f15e308920b38240d8d276a220e76c0f57c2", - "fees": 497 -} \ No newline at end of file From d3d0f52e39dde770cdf6b95aa31628777d0d42da Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sun, 19 Mar 2023 17:24:22 -0700 Subject: [PATCH 27/34] psbt inscribe workflow in preparation for collections work --- src/index.rs | 1 - src/subcommand/server.rs | 4 +- src/subcommand/wallet/inscribe.rs | 85 +++++++++++++------- src/subcommand/wallet/transaction_builder.rs | 1 - 4 files changed, 57 insertions(+), 34 deletions(-) diff --git a/src/index.rs b/src/index.rs index e4c02d853c..eb79d8f2a4 100644 --- a/src/index.rs +++ b/src/index.rs @@ -568,7 +568,6 @@ impl Index { .open_table(SATPOINT_TO_INSCRIPTION_ID)?, outpoint, )? - .into_iter() .map(|(_satpoint, inscription_id)| inscription_id) .collect(), ) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 63adafb300..9f79a777a3 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -768,7 +768,7 @@ impl Server { .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; - return match inscription.media() { + match inscription.media() { Media::Audio => Ok(PreviewAudioHtml { inscription_id }.into_response()), Media::Iframe => Ok( Self::content_response(inscription) @@ -809,7 +809,7 @@ impl Server { } Media::Unknown => Ok(PreviewUnknownHtml.into_response()), Media::Video => Ok(PreviewVideoHtml { inscription_id }.into_response()), - }; + } } async fn inscription( diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index d9b537f82d..a6bfa48189 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -9,6 +9,7 @@ use { self, constants::SCHNORR_SIGNATURE_SIZE, rand, schnorr::Signature, Secp256k1, XOnlyPublicKey, }, util::key::PrivateKey, + util::psbt::{self, Input, PartiallySignedTransaction, PsbtSighashType}, util::sighash::{Prevouts, SighashCache}, util::taproot::{ControlBlock, LeafVersion, TapLeafHash, TaprootBuilder}, PackedLockTime, SchnorrSighashType, Witness, @@ -76,7 +77,7 @@ impl Inscribe { .map(Ok) .unwrap_or_else(|| get_change_address(&client))?; - let (unsigned_commit_tx, reveal_tx, recovery_key_pair) = + let (unsigned_commit_tx, reveal_psbt, recovery_key_pair) = Inscribe::create_inscription_transactions( self.satpoint, inscription, @@ -90,6 +91,8 @@ impl Inscribe { self.no_limit, )?; + let reveal_tx = reveal_psbt.extract_tx(); + utxos.insert( reveal_tx.input[0].previous_output, Amount::from_sat( @@ -155,7 +158,7 @@ impl Inscribe { commit_fee_rate: FeeRate, reveal_fee_rate: FeeRate, no_limit: bool, - ) -> Result<(Transaction, Transaction, TweakedKeyPair)> { + ) -> Result<(Transaction, PartiallySignedTransaction, TweakedKeyPair)> { let satpoint = if let Some(satpoint) = satpoint { satpoint } else { @@ -260,6 +263,8 @@ impl Inscribe { bail!("commit transaction output would be dust"); } + let mut reveal_psbt = Self::build_reveal_psbt(reveal_tx.clone()); + let mut sighash_cache = SighashCache::new(&mut reveal_tx); let signature_hash = sighash_cache @@ -284,6 +289,8 @@ impl Inscribe { witness.push(reveal_script); witness.push(&control_block.serialize()); + reveal_psbt.inputs[0].final_script_witness = Some(witness.clone()); + let recovery_key_pair = key_pair.tap_tweak(&secp256k1, taproot_spend_info.merkle_root()); let (x_only_pub_key, _parity) = recovery_key_pair.to_inner().x_only_public_key(); @@ -295,7 +302,7 @@ impl Inscribe { commit_tx_address ); - let reveal_weight = reveal_tx.weight(); + let reveal_weight = reveal_psbt.clone().extract_tx().weight(); if !no_limit && reveal_weight > MAX_STANDARD_TX_WEIGHT.try_into().unwrap() { bail!( @@ -303,7 +310,21 @@ impl Inscribe { ); } - Ok((unsigned_commit_tx, reveal_tx, recovery_key_pair)) + Ok((unsigned_commit_tx, reveal_psbt, recovery_key_pair)) + } + + fn build_reveal_psbt(reveal_tx: Transaction) -> PartiallySignedTransaction { + let mut psbt = PartiallySignedTransaction::from_unsigned_tx(reveal_tx.clone()).unwrap(); + psbt.inputs = vec![Input { + sighash_type: Some(PsbtSighashType::from(SchnorrSighashType::Default)), + ..Default::default() + }]; + psbt.outputs = vec![psbt::Output { + witness_script: Some(reveal_tx.output[0].clone().script_pubkey), + ..Default::default() + }]; + + psbt } fn backup_recovery_key( @@ -353,7 +374,7 @@ impl Inscribe { version: 1, }; - let fee = { + let estimated_fee = { let mut reveal_tx = reveal_tx.clone(); reveal_tx.input[0].witness.push( @@ -367,7 +388,7 @@ impl Inscribe { fee_rate.fee(reveal_tx.vsize()) }; - (reveal_tx, fee) + (reveal_tx, estimated_fee) } } @@ -382,27 +403,29 @@ mod tests { let commit_address = change(0); let reveal_address = recipient(); - let (commit_tx, reveal_tx, _private_key) = Inscribe::create_inscription_transactions( - Some(satpoint(1, 0)), - inscription, - BTreeMap::new(), - Network::Bitcoin, - utxos.into_iter().collect(), - [commit_address, change(1)], - reveal_address, - FeeRate::try_from(1.0).unwrap(), - FeeRate::try_from(1.0).unwrap(), - false, - ) - .unwrap(); + let (unsigned_commit_tx, reveal_psbt, _private_key) = + Inscribe::create_inscription_transactions( + Some(satpoint(1, 0)), + inscription, + BTreeMap::new(), + Network::Bitcoin, + utxos.into_iter().collect(), + [commit_address, change(1)], + reveal_address, + FeeRate::try_from(1.0).unwrap(), + FeeRate::try_from(1.0).unwrap(), + false, + ) + .unwrap(); #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] - let fee = Amount::from_sat((1.0 * (reveal_tx.vsize() as f64)).ceil() as u64); + let reveal_fee = + Amount::from_sat((1.0 * (reveal_psbt.clone().extract_tx().vsize() as f64)).round() as u64); assert_eq!( - reveal_tx.output[0].value, - 20000 - fee.to_sat() - (20000 - commit_tx.output[0].value), + reveal_psbt.extract_tx().output[0].value, + 20000 - (20000 - unsigned_commit_tx.output[0].value + reveal_fee.to_sat()), ); } @@ -413,7 +436,7 @@ mod tests { let commit_address = change(0); let reveal_address = recipient(); - let (commit_tx, reveal_tx, _) = Inscribe::create_inscription_transactions( + let (commit_tx, reveal_psbt, _) = Inscribe::create_inscription_transactions( Some(satpoint(1, 0)), inscription, BTreeMap::new(), @@ -428,7 +451,7 @@ mod tests { .unwrap(); assert!(commit_tx.is_explicitly_rbf()); - assert!(reveal_tx.is_explicitly_rbf()); + assert!(reveal_psbt.extract_tx().is_explicitly_rbf()); } #[test] @@ -526,7 +549,7 @@ mod tests { let reveal_address = recipient(); let fee_rate = 3.3; - let (commit_tx, reveal_tx, _private_key) = Inscribe::create_inscription_transactions( + let (commit_tx, reveal_psbt, _private_key) = Inscribe::create_inscription_transactions( satpoint, inscription, inscriptions, @@ -557,11 +580,11 @@ mod tests { let fee = FeeRate::try_from(fee_rate) .unwrap() - .fee(reveal_tx.vsize()) + .fee(reveal_psbt.clone().extract_tx().vsize()) .to_sat(); assert_eq!( - reveal_tx.output[0].value, + reveal_psbt.extract_tx().output[0].value, 20_000 - fee - (20_000 - commit_tx.output[0].value), ); } @@ -588,7 +611,7 @@ mod tests { let commit_fee_rate = 3.3; let fee_rate = 1.0; - let (commit_tx, reveal_tx, _private_key) = Inscribe::create_inscription_transactions( + let (commit_tx, reveal_psbt, _private_key) = Inscribe::create_inscription_transactions( satpoint, inscription, inscriptions, @@ -617,6 +640,8 @@ mod tests { assert_eq!(reveal_value, 20_000 - fee); + let reveal_tx = reveal_psbt.extract_tx(); + let fee = FeeRate::try_from(fee_rate) .unwrap() .fee(reveal_tx.vsize()) @@ -668,7 +693,7 @@ mod tests { let commit_address = change(0); let reveal_address = recipient(); - let (_commit_tx, reveal_tx, _private_key) = Inscribe::create_inscription_transactions( + let (_commit_tx, reveal_psbt, _private_key) = Inscribe::create_inscription_transactions( satpoint, inscription, BTreeMap::new(), @@ -682,6 +707,6 @@ mod tests { ) .unwrap(); - assert!(reveal_tx.size() >= MAX_STANDARD_TX_WEIGHT as usize); + assert!(reveal_psbt.extract_tx().size() >= MAX_STANDARD_TX_WEIGHT as usize); } } diff --git a/src/subcommand/wallet/transaction_builder.rs b/src/subcommand/wallet/transaction_builder.rs index aed340be39..11f73d4f65 100644 --- a/src/subcommand/wallet/transaction_builder.rs +++ b/src/subcommand/wallet/transaction_builder.rs @@ -427,7 +427,6 @@ impl TransactionBuilder { version: 1, lock_time: PackedLockTime::ZERO, input: (0..inputs) - .into_iter() .map(|_| TxIn { previous_output: OutPoint::null(), script_sig: Script::new(), From fc560854d64187231399b5b51f190deaa61bca51 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sun, 19 Mar 2023 19:29:00 -0700 Subject: [PATCH 28/34] quick fix --- Cargo.toml | 3 +++ src/subcommand/wallet/inscribe.rs | 11 +++++++++-- test-bitcoincore-rpc/src/api.rs | 2 +- test-bitcoincore-rpc/src/server.rs | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a53b46a447..12763f54f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,3 +78,6 @@ path = "tests/lib.rs" [build-dependencies] pulldown-cmark = "0.9.2" + +# [patch.crates-io] +# ord-bitcoincore-rpc = { path = "../ord-rust-bitcoincore-rpc"} diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index f6b8e0fa01..52ed064d82 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -16,7 +16,7 @@ use { util::taproot::{ControlBlock, LeafVersion, TapLeafHash, TaprootBuilder}, PackedLockTime, SchnorrSighashType, Witness, }, - bitcoincore_rpc::bitcoincore_rpc_json::{ImportDescriptors, Timestamp}, + bitcoincore_rpc::bitcoincore_rpc_json::{ SigHashType, ImportDescriptors, Timestamp}, bitcoincore_rpc::Client, std::collections::BTreeSet, }; @@ -166,11 +166,18 @@ impl Inscribe { let reveal = if self.parent.is_some() { let updated_psbt = PartiallySignedTransaction::from_str( &client - .wallet_process_psbt(&reveal_psbt.to_string(), None, None, None)? + .wallet_process_psbt( + &reveal_psbt.to_string(), + None, + Some(SigHashType::from(bitcoin::blockdata::transaction::EcdsaSighashType::SinglePlusAnyoneCanPay)), // TODO: use SchnorrSighashType + None, + )? .psbt, ) .unwrap(); + dbg!(&updated_psbt); + let reveal_tx = updated_psbt.extract_tx(); // TODO: there is a bug here, the fully signed reveal TX no longer contains diff --git a/test-bitcoincore-rpc/src/api.rs b/test-bitcoincore-rpc/src/api.rs index 0cfe161804..5f6373387e 100644 --- a/test-bitcoincore-rpc/src/api.rs +++ b/test-bitcoincore-rpc/src/api.rs @@ -158,7 +158,7 @@ pub trait Api { &self, psbt: String, _sign: Option, - _sighash_type: Option<()>, + _sighash_type: Option, _bip32derivs: Option, ) -> Result; } diff --git a/test-bitcoincore-rpc/src/server.rs b/test-bitcoincore-rpc/src/server.rs index 58d78a4a7d..bfb01bfdb9 100644 --- a/test-bitcoincore-rpc/src/server.rs +++ b/test-bitcoincore-rpc/src/server.rs @@ -594,7 +594,7 @@ impl Api for Server { &self, psbt: String, _sign: Option, - _sighash_type: Option<()>, + _sighash_type: Option, _bip32derivs: Option, ) -> Result { Ok(WalletProcessPsbtResult { From 35917f982b15a80696dc18e0e82f251a0fd30a1b Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sun, 19 Mar 2023 19:32:47 -0700 Subject: [PATCH 29/34] quick fix --- src/subcommand/wallet/inscribe.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 52ed064d82..ef660a23aa 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -169,7 +169,7 @@ impl Inscribe { .wallet_process_psbt( &reveal_psbt.to_string(), None, - Some(SigHashType::from(bitcoin::blockdata::transaction::EcdsaSighashType::SinglePlusAnyoneCanPay)), // TODO: use SchnorrSighashType + Some(SigHashType::from(bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay)), // TODO: use SchnorrSighashType None, )? .psbt, From ec23fe3ff18488dd0551c0d3af79e45278b6b8f0 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Sun, 19 Mar 2023 19:50:45 -0700 Subject: [PATCH 30/34] quick fix --- src/subcommand/wallet/inscribe.rs | 58 ++++++++++++------------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index ef660a23aa..8191b91589 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -16,7 +16,7 @@ use { util::taproot::{ControlBlock, LeafVersion, TapLeafHash, TaprootBuilder}, PackedLockTime, SchnorrSighashType, Witness, }, - bitcoincore_rpc::bitcoincore_rpc_json::{ SigHashType, ImportDescriptors, Timestamp}, + bitcoincore_rpc::bitcoincore_rpc_json::{ImportDescriptors, SigHashType, Timestamp}, bitcoincore_rpc::Client, std::collections::BTreeSet, }; @@ -154,47 +154,35 @@ impl Inscribe { .sign_raw_transaction_with_wallet(&unsigned_commit_tx, None, None)? .hex; - let commit = client - .send_raw_transaction(&signed_raw_commit_tx) - .context("Failed to send commit transaction")?; - - log::debug!( - "partially signed reveal tx: {}", - hex::encode(serialize(&reveal_tx)) - ); + let reveal_tx = if self.parent.is_some() { + println!("{}", reveal_psbt.to_string()); + let result = &client.wallet_process_psbt( + &reveal_psbt.to_string(), + None, + Some(SigHashType::from( + bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay, + )), // TODO: use SchnorrSighashType + None, + )?; + + if !result.complete { + return Err(anyhow!("Bitcoin Core failed to sign psbt")); + } - let reveal = if self.parent.is_some() { - let updated_psbt = PartiallySignedTransaction::from_str( - &client - .wallet_process_psbt( - &reveal_psbt.to_string(), - None, - Some(SigHashType::from(bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay)), // TODO: use SchnorrSighashType - None, - )? - .psbt, - ) - .unwrap(); + let updated_psbt = PartiallySignedTransaction::from_str(&result.psbt).unwrap(); dbg!(&updated_psbt); - let reveal_tx = updated_psbt.extract_tx(); + updated_psbt.extract_tx() + } else { reveal_tx }; - // TODO: there is a bug here, the fully signed reveal TX no longer contains - // the inscription data when backup key is in bitcoin core wallet - log::debug!( - "fully signed reveal tx: {}", - hex::encode(serialize(&reveal_tx)) - ); + let commit = client + .send_raw_transaction(&signed_raw_commit_tx) + .context("Failed to send commit transaction")?; - client + let reveal = client .send_raw_transaction(&reveal_tx) - .context("Failed to send reveal transaction")? - } else { - client - .send_raw_transaction(&reveal_tx) - .context("Failed to send reveal transaction")? - }; + .context("Failed to send reveal transaction")?; let inscription = InscriptionId { txid: reveal, From 6a8bcdf1ab2285f4ad16ac225b33e403cb843f7c Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 21 Mar 2023 14:41:22 -0700 Subject: [PATCH 31/34] quick fix --- src/subcommand/wallet/inscribe.rs | 41 ++++---- tests/core.rs | 152 +++++++++++++++++++++++++++++- 2 files changed, 170 insertions(+), 23 deletions(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 8191b91589..dc602cd65c 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -1,5 +1,3 @@ -use bitcoin::{consensus::serialize, SchnorrSig}; - use { super::*, crate::wallet::Wallet, @@ -14,7 +12,7 @@ use { util::psbt::{self, Input, PartiallySignedTransaction, PsbtSighashType}, util::sighash::{Prevouts, SighashCache}, util::taproot::{ControlBlock, LeafVersion, TapLeafHash, TaprootBuilder}, - PackedLockTime, SchnorrSighashType, Witness, + PackedLockTime, SchnorrSig, SchnorrSighashType, Witness, }, bitcoincore_rpc::bitcoincore_rpc_json::{ImportDescriptors, SigHashType, Timestamp}, bitcoincore_rpc::Client, @@ -108,7 +106,7 @@ impl Inscribe { .map(Ok) .unwrap_or_else(|| get_change_address(&client))?; - let (unsigned_commit_tx, reveal_psbt, recovery_key_pair) = + let (unsigned_commit_tx, reveal_psbt, _recovery_key_pair) = Inscribe::create_inscription_transactions( self.satpoint, parent, @@ -146,9 +144,6 @@ impl Inscribe { fees, })?; } else { - if !self.no_backup { - Inscribe::backup_recovery_key(&client, recovery_key_pair, options.chain().network())?; - } let signed_raw_commit_tx = client .sign_raw_transaction_with_wallet(&unsigned_commit_tx, None, None)? @@ -164,29 +159,31 @@ impl Inscribe { )), // TODO: use SchnorrSighashType None, )?; - - if !result.complete { - return Err(anyhow!("Bitcoin Core failed to sign psbt")); - } - let updated_psbt = PartiallySignedTransaction::from_str(&result.psbt).unwrap(); + // if !result.complete { + // return Err(anyhow!("Bitcoin Core failed to sign psbt")); + // } - dbg!(&updated_psbt); + println!("{}", &result.psbt); - updated_psbt.extract_tx() - } else { reveal_tx }; + let updated_psbt = PartiallySignedTransaction::from_str(&result.psbt).unwrap(); + + dbg!(updated_psbt.extract_tx()) + } else { + reveal_tx + }; let commit = client - .send_raw_transaction(&signed_raw_commit_tx) - .context("Failed to send commit transaction")?; + .send_raw_transaction(&signed_raw_commit_tx) + .context("Failed to send commit transaction")?; - let reveal = client - .send_raw_transaction(&reveal_tx) - .context("Failed to send reveal transaction")?; + let reveal = client + .send_raw_transaction(&reveal_tx) + .context("Failed to send reveal transaction")?; let inscription = InscriptionId { txid: reveal, - index: commit_input_offset as u32, + index: 0, }; print_json(Output { @@ -473,7 +470,7 @@ impl Inscribe { psbt } - fn backup_recovery_key( + fn _backup_recovery_key( client: &Client, recovery_key_pair: TweakedKeyPair, network: Network, diff --git a/tests/core.rs b/tests/core.rs index 8e4c951d38..3be31ea6ec 100644 --- a/tests/core.rs +++ b/tests/core.rs @@ -1,4 +1,8 @@ -use super::*; +use { + super::*, + bitcoincore_rpc::{Client, RpcApi}, + std::ffi::OsString, +}; struct KillOnDrop(std::process::Child); @@ -70,3 +74,149 @@ fn preview() { format!(".*( u16 { + TcpListener::bind("127.0.0.1:0") + .unwrap() + .local_addr() + .unwrap() + .port() +} + +fn ord( + cookiefile: &std::path::PathBuf, + ord_data_dir: &std::path::PathBuf, + rpc_port: u16, + args: &[&str], +) -> Result { + let mut ord = Command::new(executable_path("ord")); + + ord + .env("ORD_INTEGRATION_TEST", "1") + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .current_dir(&ord_data_dir) + .arg("--regtest") + .arg("--data-dir") + .arg(ord_data_dir.as_path()) + .arg("--rpc-url") + .arg(&format!("127.0.0.1:{}", rpc_port)) + .arg("--cookie-file") + .arg(cookiefile.to_str().unwrap()) + .args(args); + + let output = ord.output().unwrap(); + if output.status.success() { + Ok(String::from(str::from_utf8(&output.stdout).unwrap())) + } else { + Err(String::from(str::from_utf8(&output.stderr).unwrap())) + } +} + +#[test] +#[ignore] +fn inscribe_child() { + let rpc_port = get_free_port(); + + let tmp_dir_1 = TempDir::new().unwrap(); + let bitcoin_data_dir = tmp_dir_1.path().join("bitcoin"); + fs::create_dir(&bitcoin_data_dir).unwrap(); + + let tmp_dir_2 = TempDir::new().unwrap(); + let ord_data_dir = tmp_dir_2.path().join("ord"); + fs::create_dir(&ord_data_dir).unwrap(); + + let _bitcoind = KillOnDrop( + Command::new("bitcoind") + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg({ + let mut arg = OsString::from("-datadir="); + arg.push(&bitcoin_data_dir); + arg + }) + .arg("-regtest") + .arg("-txindex") + .arg("-listen=0") + .arg(format!("-rpcport={rpc_port}")) + .spawn() + .expect("failed to spawn `bitcoind`"), + ); + + let cookiefile = bitcoin_data_dir.as_path().join("regtest/.cookie"); + + for attempt in 0.. { + match Client::new( + &format!("127.0.0.1:{rpc_port}"), + bitcoincore_rpc::Auth::CookieFile(cookiefile.clone()), + ) { + Ok(_) => break, + _ => (), + } + + if attempt == 500 { + panic!("Bitcoin Core RPC did not respond"); + } + + thread::sleep(Duration::from_millis(50)); + } + + let _ = ord(&cookiefile, &ord_data_dir, rpc_port, &["wallet", "create"]); + + // get funds in wallet + // inscribe parent + // mine block + // inscribe child with parent + + let rpc_client = Client::new( + &format!("127.0.0.1:{rpc_port}/wallet/ord"), + bitcoincore_rpc::Auth::CookieFile(cookiefile.clone()), + ) + .unwrap(); + + let address = rpc_client + .get_new_address(None, Some(bitcoincore_rpc::json::AddressType::Bech32m)) + .unwrap(); + + rpc_client.generate_to_address(101, &address).unwrap(); + + fs::write(ord_data_dir.as_path().join("parent.txt"), "Pater").unwrap(); + + + #[derive(Deserialize, Debug)] + struct Output { + commit: String, + inscription: String, + parent: Option, + reveal: String, + fees: u64, + } + + let output: Output = match ord( + &cookiefile, + &ord_data_dir, + rpc_port, + &["wallet", "inscribe", "parent.txt"], + ) { + Ok(s) => serde_json::from_str(&s) + .unwrap_or_else(|err| panic!("Failed to deserialize JSON: {err}\n{s}")), + Err(e) => panic!("error inscribing parent: {}", e), + }; + + rpc_client.generate_to_address(1, &address).unwrap(); + + fs::write(ord_data_dir.as_path().join("child.txt"), "Filius").unwrap(); + match ord( + &cookiefile, + &ord_data_dir, + rpc_port, + &["wallet", "inscribe", "--parent", &output.inscription, "child.txt"], + ) { + Ok(s) => println!("{}", s), + Err(e) => panic!("error inscribing child with parent: {}", e), + } + + rpc_client.generate_to_address(1, &address).unwrap(); +} From c04558dcb65d6e5b8017c718dbb7e98dbffe6697 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 21 Mar 2023 15:45:58 -0700 Subject: [PATCH 32/34] stash --- src/subcommand/wallet/inscribe.rs | 41 +++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index dc602cd65c..7966bb8a49 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -79,7 +79,7 @@ impl Inscribe { ))); } - let output = index + let tx_out = index .get_transaction(satpoint.outpoint.txid)? .expect("not found") .output @@ -87,7 +87,7 @@ impl Inscribe { .nth(satpoint.outpoint.vout.try_into().unwrap()) .expect("current transaction output"); - (Some((satpoint, output)), 1) + (Some((satpoint, tx_out)), 1) } else { return Err(anyhow!(format!( "specified parent {parent_id} does not exist" @@ -106,8 +106,11 @@ impl Inscribe { .map(Ok) .unwrap_or_else(|| get_change_address(&client))?; + let tmp_client = options.bitcoin_rpc_client_for_wallet_command(false)?; + let (unsigned_commit_tx, reveal_psbt, _recovery_key_pair) = Inscribe::create_inscription_transactions( + tmp_client, self.satpoint, parent, inscription, @@ -144,13 +147,11 @@ impl Inscribe { fees, })?; } else { - let signed_raw_commit_tx = client .sign_raw_transaction_with_wallet(&unsigned_commit_tx, None, None)? .hex; let reveal_tx = if self.parent.is_some() { - println!("{}", reveal_psbt.to_string()); let result = &client.wallet_process_psbt( &reveal_psbt.to_string(), None, @@ -164,8 +165,6 @@ impl Inscribe { // return Err(anyhow!("Bitcoin Core failed to sign psbt")); // } - println!("{}", &result.psbt); - let updated_psbt = PartiallySignedTransaction::from_str(&result.psbt).unwrap(); dbg!(updated_psbt.extract_tx()) @@ -208,6 +207,7 @@ impl Inscribe { } fn create_inscription_transactions( + client: Client, satpoint: Option, parent: Option<(SatPoint, TxOut)>, inscription: Inscription, @@ -274,13 +274,13 @@ impl Inscribe { let commit_tx_address = Address::p2tr_tweaked(taproot_spend_info.output_key(), network); let (mut inputs, mut outputs, commit_input_offset) = - if let Some((satpoint, output)) = parent.clone() { + if let Some((parent_satpoint, tx_out)) = parent.clone() { ( - vec![satpoint.outpoint, OutPoint::null()], + vec![parent_satpoint.outpoint, OutPoint::null()], vec![ TxOut { - script_pubkey: output.script_pubkey, - value: output.value, + script_pubkey: tx_out.script_pubkey, + value: tx_out.value, }, TxOut { script_pubkey: destination.script_pubkey(), @@ -349,6 +349,7 @@ impl Inscribe { .checked_sub(fee.to_sat()) .context("commit transaction output value insufficient to pay transaction fee")?; + // sanity check fee if reveal_tx.output[commit_input_offset].value < reveal_tx.output[commit_input_offset] .script_pubkey @@ -375,6 +376,26 @@ impl Inscribe { ) }; + dbg!(&reveal_psbt); + dbg!(bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay.to_u32()); + + let result = &client.wallet_process_psbt( + &reveal_psbt.to_string(), + None, + if parent.is_some() { + Some(SigHashType::from( + bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay, + )) // TODO: use SchnorrSighashType + } else { + None + }, + None, + )?; + + let updated_psbt = PartiallySignedTransaction::from_str(&result.psbt).unwrap(); + + dbg!(updated_psbt.extract_tx()); + let mut sighash_cache = SighashCache::new(&mut reveal_tx); let message = sighash_cache From 047c10f60754b46df91757fab11a7637d297a27e Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 21 Mar 2023 16:29:12 -0700 Subject: [PATCH 33/34] signing psbts works --- src/subcommand/wallet/inscribe.rs | 92 +++++++++++++++++++++++-------- tests/core.rs | 68 +++++++++++++++++++++-- 2 files changed, 132 insertions(+), 28 deletions(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 7966bb8a49..b62c94576c 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -14,7 +14,10 @@ use { util::taproot::{ControlBlock, LeafVersion, TapLeafHash, TaprootBuilder}, PackedLockTime, SchnorrSig, SchnorrSighashType, Witness, }, - bitcoincore_rpc::bitcoincore_rpc_json::{ImportDescriptors, SigHashType, Timestamp}, + bitcoincore_rpc::bitcoincore_rpc_json::{ + CreateRawTransactionInput, ImportDescriptors, SigHashType, Timestamp, + WalletCreateFundedPsbtOptions, + }, bitcoincore_rpc::Client, std::collections::BTreeSet, }; @@ -71,7 +74,7 @@ impl Inscribe { let inscriptions = index.get_inscriptions(None)?; - let (parent, commit_input_offset) = if let Some(parent_id) = self.parent { + let (parent_psbt, commit_input_offset) = if let Some(parent_id) = self.parent { if let Some(satpoint) = index.get_inscription_satpoint_by_id(parent_id)? { if !utxos.contains_key(&satpoint.outpoint) { return Err(anyhow!(format!( @@ -87,7 +90,41 @@ impl Inscribe { .nth(satpoint.outpoint.vout.try_into().unwrap()) .expect("current transaction output"); - (Some((satpoint, tx_out)), 1) + let parent_input = CreateRawTransactionInput { + txid: satpoint.outpoint.txid, + vout: satpoint.outpoint.vout, + sequence: None, + }; + + let outputs = std::iter::once(( + Address::from_script(&tx_out.script_pubkey, options.chain().network()) + .unwrap() + .to_string(), + Amount::from_sat(tx_out.value), + )) + .collect::>(); + + let options = WalletCreateFundedPsbtOptions { + fee_rate: Some(Amount::ZERO), + ..Default::default() + }; + + let parent_psbt = client + .wallet_create_funded_psbt(&[parent_input], &outputs, None, Some(options), None)? + .psbt; + + let result = &client.wallet_process_psbt( + &parent_psbt.clone().to_string(), + None, + Some(SigHashType::from( + bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay, + )), // TODO: use SchnorrSighashType + None, + )?; + + let updated_psbt = PartiallySignedTransaction::from_str(&result.psbt).unwrap(); + + (Some(parent_psbt), 0) } else { return Err(anyhow!(format!( "specified parent {parent_id} does not exist" @@ -112,7 +149,7 @@ impl Inscribe { Inscribe::create_inscription_transactions( tmp_client, self.satpoint, - parent, + None, inscription, inscriptions, options.chain().network(), @@ -138,6 +175,15 @@ impl Inscribe { let fees = Self::calculate_fee(&unsigned_commit_tx, &utxos) + Self::calculate_fee(&reveal_tx, &utxos); + let joined_psbt = if let Some(parent_psbt) = parent_psbt { + dbg!(Some(client.join_psbt(&[ + parent_psbt.to_string(), + reveal_psbt.to_string() + ])?)) + } else { + None + }; + if self.dry_run { print_json(Output { commit: unsigned_commit_tx.txid(), @@ -376,25 +422,25 @@ impl Inscribe { ) }; - dbg!(&reveal_psbt); - dbg!(bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay.to_u32()); - - let result = &client.wallet_process_psbt( - &reveal_psbt.to_string(), - None, - if parent.is_some() { - Some(SigHashType::from( - bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay, - )) // TODO: use SchnorrSighashType - } else { - None - }, - None, - )?; - - let updated_psbt = PartiallySignedTransaction::from_str(&result.psbt).unwrap(); - - dbg!(updated_psbt.extract_tx()); + //dbg!(&reveal_psbt); + //dbg!(bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay.to_u32()); + + //let result = &client.wallet_process_psbt( + // &reveal_psbt.to_string(), + // None, + // if parent.is_some() { + // Some(SigHashType::from( + // bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay, + // )) // TODO: use SchnorrSighashType + // } else { + // None + // }, + // None, + //)?; + + //let updated_psbt = PartiallySignedTransaction::from_str(&result.psbt).unwrap(); + + //dbg!(updated_psbt.extract_tx()); let mut sighash_cache = SighashCache::new(&mut reveal_tx); diff --git a/tests/core.rs b/tests/core.rs index 3be31ea6ec..a29d492f3b 100644 --- a/tests/core.rs +++ b/tests/core.rs @@ -183,7 +183,6 @@ fn inscribe_child() { rpc_client.generate_to_address(101, &address).unwrap(); fs::write(ord_data_dir.as_path().join("parent.txt"), "Pater").unwrap(); - #[derive(Deserialize, Debug)] struct Output { @@ -204,19 +203,78 @@ fn inscribe_child() { .unwrap_or_else(|err| panic!("Failed to deserialize JSON: {err}\n{s}")), Err(e) => panic!("error inscribing parent: {}", e), }; + let parent_id = output.inscription; rpc_client.generate_to_address(1, &address).unwrap(); fs::write(ord_data_dir.as_path().join("child.txt"), "Filius").unwrap(); - match ord( + let output: Output = match ord( &cookiefile, &ord_data_dir, rpc_port, - &["wallet", "inscribe", "--parent", &output.inscription, "child.txt"], + &[ + "wallet", + "inscribe", + "--parent", + &parent_id, + "child.txt", + ], ) { - Ok(s) => println!("{}", s), + Ok(s) => serde_json::from_str(&s) + .unwrap_or_else(|err| panic!("Failed to deserialize JSON: {err}\n{s}")), Err(e) => panic!("error inscribing child with parent: {}", e), - } + }; + let child_id = output.inscription; + let ord_port = 8080; rpc_client.generate_to_address(1, &address).unwrap(); + + let _ord_server = KillOnDrop( + Command::new(executable_path("ord")) + .env("ORD_INTEGRATION_TEST", "1") + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .current_dir(&ord_data_dir) + .arg("--regtest") + .arg("--data-dir") + .arg(ord_data_dir.as_path()) + .arg("--rpc-url") + .arg(&format!("127.0.0.1:{}", rpc_port)) + .arg("--cookie-file") + .arg(cookiefile.to_str().unwrap()) + .arg("server") + .arg("--http-port") + .arg(&format!("{ord_port}")) + .spawn() + .expect("failed to spawn `ord server`"), + ); + + let client = reqwest::blocking::Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .unwrap(); + + for i in 0.. { + match client + .get(format!("http://127.0.0.1:{ord_port}/status")) + .send() + { + Ok(_) => break, + Err(err) => { + if i == 400 { + panic!("server failed to start: {err}"); + } + } + } + + thread::sleep(Duration::from_millis(25)); + } + + let response = client + .get(format!("http://127.0.0.1:{ord_port}/inscription/{child_id}")) + .send() + .unwrap(); + + assert_regex_match!(response.text().unwrap(), &format!(".*parent.*{}", parent_id)); } From 79e8dbd31f246fc80bc93584e31c2701dcc4d0a8 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 21 Mar 2023 17:14:30 -0700 Subject: [PATCH 34/34] quick fix --- src/subcommand/wallet/inscribe.rs | 23 ----------------------- test-bitcoincore-rpc/src/api.rs | 10 +++++++++- test-bitcoincore-rpc/src/lib.rs | 3 ++- test-bitcoincore-rpc/src/server.rs | 14 ++++++++++++++ tests/test_server.rs | 1 - tests/wallet/inscribe.rs | 11 +++++------ 6 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index b62c94576c..037ddd25ef 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -143,11 +143,9 @@ impl Inscribe { .map(Ok) .unwrap_or_else(|| get_change_address(&client))?; - let tmp_client = options.bitcoin_rpc_client_for_wallet_command(false)?; let (unsigned_commit_tx, reveal_psbt, _recovery_key_pair) = Inscribe::create_inscription_transactions( - tmp_client, self.satpoint, None, inscription, @@ -253,7 +251,6 @@ impl Inscribe { } fn create_inscription_transactions( - client: Client, satpoint: Option, parent: Option<(SatPoint, TxOut)>, inscription: Inscription, @@ -422,26 +419,6 @@ impl Inscribe { ) }; - //dbg!(&reveal_psbt); - //dbg!(bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay.to_u32()); - - //let result = &client.wallet_process_psbt( - // &reveal_psbt.to_string(), - // None, - // if parent.is_some() { - // Some(SigHashType::from( - // bitcoin::blockdata::transaction::EcdsaSighashType::AllPlusAnyoneCanPay, - // )) // TODO: use SchnorrSighashType - // } else { - // None - // }, - // None, - //)?; - - //let updated_psbt = PartiallySignedTransaction::from_str(&result.psbt).unwrap(); - - //dbg!(updated_psbt.extract_tx()); - let mut sighash_cache = SighashCache::new(&mut reveal_tx); let message = sighash_cache diff --git a/test-bitcoincore-rpc/src/api.rs b/test-bitcoincore-rpc/src/api.rs index 5f6373387e..2f4399d548 100644 --- a/test-bitcoincore-rpc/src/api.rs +++ b/test-bitcoincore-rpc/src/api.rs @@ -153,7 +153,6 @@ pub trait Api { fn list_wallets(&self) -> Result, jsonrpc_core::Error>; #[rpc(name = "walletprocesspsbt")] - fn wallet_process_psbt( &self, psbt: String, @@ -161,4 +160,13 @@ pub trait Api { _sighash_type: Option, _bip32derivs: Option, ) -> Result; + +// #[rpc(name = "walletcreatefundedpsbt")] +// fn wallet_create_funded_psbt( +// inputs: Vec, +// outputs: HashMap, +// locktime: Option, +// options: Option, +// bip32derivs: Option +// ) -> Result; } diff --git a/test-bitcoincore-rpc/src/lib.rs b/test-bitcoincore-rpc/src/lib.rs index 41399135e1..9fc37dea06 100644 --- a/test-bitcoincore-rpc/src/lib.rs +++ b/test-bitcoincore-rpc/src/lib.rs @@ -18,7 +18,8 @@ use { GetNetworkInfoResult, GetRawTransactionResult, GetTransactionResult, GetTransactionResultDetail, GetTransactionResultDetailCategory, GetWalletInfoResult, ImportDescriptors, ImportMultiResult, ListDescriptorsResult, ListTransactionResult, - ListUnspentResultEntry, LoadWalletResult, SignRawTransactionResult, Timestamp, WalletTxInfo, WalletProcessPsbtResult, + ListUnspentResultEntry, LoadWalletResult, SignRawTransactionResult, Timestamp, + WalletCreateFundedPsbtResult, WalletProcessPsbtResult, WalletCreateFundedPsbtOptions, WalletTxInfo, }, jsonrpc_core::{IoHandler, Value}, jsonrpc_http_server::{CloseHandle, ServerBuilder}, diff --git a/test-bitcoincore-rpc/src/server.rs b/test-bitcoincore-rpc/src/server.rs index bfb01bfdb9..b28db5e9b3 100644 --- a/test-bitcoincore-rpc/src/server.rs +++ b/test-bitcoincore-rpc/src/server.rs @@ -602,4 +602,18 @@ impl Api for Server { complete: true, }) } + +// fn wallet_create_funded_psbt( +// inputs: Vec, +// outputs: HashMap, +// locktime: Option, +// options: Option, +// bip32derivs: Option, +// ) -> Result { +// Ok(WalletCreateFundedPsbtResult { +// psbt: "".into(), +// fee: Amount::ZERO, +// change_position: 0, +// }) +// } } diff --git a/tests/test_server.rs b/tests/test_server.rs index ac8e5d98c0..d99fb3ff93 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -74,7 +74,6 @@ impl TestServer { thread::sleep(Duration::from_millis(25)); } - dbg!(path.as_ref()); let response = reqwest::blocking::get(self.url().join(path.as_ref()).unwrap()).unwrap(); assert_eq!(response.status(), StatusCode::OK); assert_regex_match!(response.text().unwrap(), regex.as_ref()); diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index 0b40f21ab1..630b2f6f27 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -10,8 +10,9 @@ fn inscribe_creates_inscriptions() { create_wallet(&rpc_server); let Inscribe { inscription, .. } = inscribe(&rpc_server); - - assert_eq!(rpc_server.descriptors().len(), 3); + + // two because now no backup + assert_eq!(rpc_server.descriptors().len(), 2); let request = TestServer::spawn_with_args(&rpc_server, &[]).request(format!("/content/{inscription}")); @@ -409,10 +410,8 @@ fn inscribe_with_parent_inscription() { rpc_server.mine_blocks(1); - // TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex( - // format!("/inscription/{parent_id}"), - // format!(".*"), - // ); + // TestServer::spawn_with_args(&rpc_server, &[]) + // .assert_response_regex(format!("/inscription/{parent_id}"), format!(".*")); let child_output = CommandBuilder::new(format!("wallet inscribe --parent {parent_id} child.png")) .write("child.png", [1; 520])