From db29dbc3f22cca1fa6b632893b82c05204a9d431 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 1 Mar 2023 19:25:11 -0800 Subject: [PATCH 01/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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 fbc4cdec56393136cd9995b71972a70537b529b7 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 22 Mar 2023 13:41:11 -0700 Subject: [PATCH 27/54] finally --- src/index.rs | 3 +- src/index/updater/inscription_updater.rs | 12 +- src/inscription.rs | 8 +- src/subcommand/server.rs | 5 +- src/subcommand/wallet/inscribe.rs | 79 ++++---- tests/core.rs | 222 ++++++++++++++++++++++- tests/wallet/inscribe.rs | 3 +- 7 files changed, 276 insertions(+), 56 deletions(-) diff --git a/src/index.rs b/src/index.rs index a948056997..e2eed7772c 100644 --- a/src/index.rs +++ b/src/index.rs @@ -548,11 +548,10 @@ impl Index { { return Ok(None); } - + 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_transaction(&tx)), ) } diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 6b232d8b88..9d458277ee 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -117,11 +117,11 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { } else { None }; - + floating_inscriptions.push(Flotsam { inscription_id: InscriptionId { txid, - index: 0, + index: 0, // will have to be updated for multi inscriptions }, offset: input_value, origin: Origin::New((0, parent)), @@ -203,7 +203,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.update_inscription_location( input_sat_ranges, // TODO: do something with two inscriptions in the input - inscriptions.next().unwrap(), + dbg!(inscriptions.next().unwrap()), new_satpoint, )?; } @@ -271,9 +271,11 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { } } + dbg!(&parent); + self.id_to_entry.insert( &inscription_id, - &InscriptionEntry { + &dbg!(InscriptionEntry { fee, height: self.height, number: self.next_number, @@ -281,7 +283,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { sat, timestamp: self.timestamp, } - .store(), + .store()), )?; self.next_number += 1; diff --git a/src/inscription.rs b/src/inscription.rs index 5169194db8..28fc7377be 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -40,7 +40,13 @@ impl Inscription { } pub(crate) fn from_transaction(tx: &Transaction) -> Option { - InscriptionParser::parse(&tx.input.get(0)?.witness).ok() + for input in &tx.input { + if let Some(inscription) = Inscription::from_tx_input(input) { + return Some(inscription); + } + } + + None } pub(crate) fn from_tx_input(tx_in: &TxIn) -> Option { diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 4990451952..51d05e8295 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -821,6 +821,9 @@ impl Server { .get_inscription_entry(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; + dbg!(&inscription_id); + dbg!(&entry); + let inscription = index .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; @@ -859,7 +862,7 @@ impl Server { next, number: entry.number, output, - parent: dbg!(entry.parent), + parent: entry.parent, previous, sat: entry.sat, satpoint, diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 8dafcbba1b..40518c5f10 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -143,60 +143,49 @@ impl Inscribe { parent: self.parent, 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)? - .hex; + return Ok(()); + } - let commit = client - .send_raw_transaction(&signed_raw_commit_tx) - .context("Failed to send commit transaction")?; + // if !self.no_backup { + // Inscribe::backup_recovery_key(&client, recovery_key_pair, options.chain().network())?; + // } - log::debug!( - "partially signed reveal tx: {}", - hex::encode(serialize(&partially_signed_reveal_tx)) - ); + let signed_raw_commit_tx = client + .sign_raw_transaction_with_wallet(&unsigned_commit_tx, None, None)? + .hex; - // 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)? - .hex; - // 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(&fully_signed_raw_reveal_tx)) - ); + let commit = client + .send_raw_transaction(&signed_raw_commit_tx) + .context("Failed to send commit transaction")?; - 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")? - }; + 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; - let inscription = InscriptionId { - txid: reveal, - index: commit_input_offset as u32, - }; + 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, - parent: self.parent, - fees, - })?; + let inscription = InscriptionId { + txid: reveal, + index: 0, }; + print_json(Output { + commit, + reveal, + inscription, + parent: self.parent, + fees, + })?; + Ok(()) } diff --git a/tests/core.rs b/tests/core.rs index 8e4c951d38..effc8dd310 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,219 @@ 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("-minrelaytxfee=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), + }; + 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(); + let output: Output = match ord( + &cookiefile, + &ord_data_dir, + rpc_port, + &[ + "wallet", + "inscribe", + "--parent", + &parent_id, + "child.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 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/{parent_id}")) + .send() + .unwrap(); + + assert_regex_match!(response.text().unwrap(), &format!(".*id.*{}.*", parent_id)); + + thread::sleep(Duration::from_secs(10)); + + 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)); +} diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index 0b40f21ab1..78d518d29b 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -11,7 +11,8 @@ fn inscribe_creates_inscriptions() { let Inscribe { inscription, .. } = inscribe(&rpc_server); - assert_eq!(rpc_server.descriptors().len(), 3); + // no backup + assert_eq!(rpc_server.descriptors().len(), 2); let request = TestServer::spawn_with_args(&rpc_server, &[]).request(format!("/content/{inscription}")); From 0d362e4083cad30f9a17ce3aad88cccd0f4c871f Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 22 Mar 2023 14:12:02 -0700 Subject: [PATCH 28/54] cleanup and works --- fuzz/Cargo.lock | 197 +++++++++++++++++-- src/index.rs | 1 - src/index/updater/inscription_updater.rs | 28 +-- src/inscription.rs | 1 + src/subcommand/server.rs | 7 +- src/subcommand/wallet/inscribe.rs | 6 +- src/subcommand/wallet/transaction_builder.rs | 1 - tests/core.rs | 56 +++--- tests/test_server.rs | 1 - tests/wallet/inscribe.rs | 10 +- 10 files changed, 233 insertions(+), 75 deletions(-) diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index f30058f2a7..87e3510802 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -80,6 +80,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -163,6 +178,20 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-compression" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-dup" version = "1.2.2" @@ -380,6 +409,7 @@ dependencies = [ "bitflags", "bytes", "futures-util", + "headers", "http", "http-body", "hyper", @@ -569,12 +599,39 @@ dependencies = [ "syn", ] +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.3.0" @@ -741,6 +798,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.14" @@ -1082,6 +1148,16 @@ dependencies = [ "instant", ] +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1316,6 +1392,31 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64 0.13.1", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1 0.10.5", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.4.0" @@ -1442,9 +1543,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" dependencies = [ "bytes", "futures-channel", @@ -1731,6 +1832,20 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "mp4" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509348cba250e7b852a875100a2ddce7a36ee3abf881a681c756670c1774264d" +dependencies = [ + "byteorder", + "bytes", + "num-rational", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "never" version = "0.1.0" @@ -1790,6 +1905,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -1847,11 +1975,12 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "ord" -version = "0.4.0" +version = "0.5.1" dependencies = [ "anyhow", "axum", "axum-server", + "base64 0.13.1", "bech32", "bip39", "bitcoin", @@ -1866,12 +1995,14 @@ dependencies = [ "hex", "html-escaper", "http", + "hyper", "indicatif", "lazy_static", "log", "mime", "mime_guess", "miniscript", + "mp4", "ord-bitcoincore-rpc", "pulldown-cmark", "redb", @@ -1882,6 +2013,7 @@ dependencies = [ "rustls-acme", "serde", "serde_json", + "serde_yaml", "sys-info", "tempfile", "tokio", @@ -1892,8 +2024,9 @@ dependencies = [ [[package]] name = "ord-bitcoincore-rpc" -version = "0.16.4" -source = "git+https://github.com/casey/rust-bitcoincore-rpc?branch=ord#1bc2638f5a3049495a7aa953c4c6dac44fd1524b" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca77a54d5a14a2731dd85150e24be89c849bdcb5704d23097188d763381d98b8" dependencies = [ "jsonrpc", "log", @@ -1904,8 +2037,9 @@ dependencies = [ [[package]] name = "ord-bitcoincore-rpc-json" -version = "0.16.4" -source = "git+https://github.com/casey/rust-bitcoincore-rpc?branch=ord#1bc2638f5a3049495a7aa953c4c6dac44fd1524b" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18b81ced8ed99f50d3f221a7de8297cbdf9bdf20ca2908a12f6128d38b2284fb" dependencies = [ "bitcoin", "serde", @@ -2061,9 +2195,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.17.3" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28fcd1e73f06ec85bf3280c48c67e731d8290ad3d730f8be9dc07946923005c8" +checksum = "75439f995d07ddfad42b192dfcf3bc66a7ecfd8b4a1f5f6f046aa5c2c5d7677d" dependencies = [ "once_cell", "target-lexicon", @@ -2179,8 +2313,9 @@ dependencies = [ [[package]] name = "redb" -version = "0.11.0" -source = "git+https://github.com/casey/redb.git?branch=ord#826fd633b2e26bb649cf7004406cb469fbb28837" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f210bb101d3a0ddba42f67b12a1d7186e584733ad028f119c8d217d867f03d" dependencies = [ "libc", "pyo3-build-config", @@ -2535,6 +2670,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82e6c8c047aa50a7328632d067bcae6ef38772a79e28daf32f735e0e4f3dd10" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.6.1" @@ -2544,6 +2692,17 @@ dependencies = [ "sha1_smol", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + [[package]] name = "sha1_smol" version = "1.0.0" @@ -2707,7 +2866,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "sha1", + "sha1 0.6.1", "syn", ] @@ -2987,6 +3146,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ + "async-compression", "bitflags", "bytes", "futures-core", @@ -2995,6 +3155,8 @@ dependencies = [ "http-body", "http-range-header", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -3097,6 +3259,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c" + [[package]] name = "untrusted" version = "0.7.1" @@ -3404,3 +3572,8 @@ checksum = "aed2e7a52e3744ab4d0c05c20aa065258e84c49fd4226f5191b2ed29712710b4" dependencies = [ "time 0.3.17", ] + +[[patch.unused]] +name = "redb" +version = "0.11.0" +source = "git+https://github.com/casey/redb.git?branch=ord#826fd633b2e26bb649cf7004406cb469fbb28837" diff --git a/src/index.rs b/src/index.rs index e2eed7772c..be0306983d 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/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 9d458277ee..9d8a6216cc 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -103,21 +103,14 @@ 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() { - // parent has to be in an input before child - // think about specifying a more general approach in a protocol doc/BIP - if floating_inscriptions + // parent has to be in an input before child + // think about specifying a more general approach in a protocol doc/BIP + let parent = inscription.get_parent_id().filter(|&parent_id| { + 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, @@ -202,8 +195,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.update_inscription_location( input_sat_ranges, - // TODO: do something with two inscriptions in the input - dbg!(inscriptions.next().unwrap()), + inscriptions.next().unwrap(), // TODO: do something with two inscriptions in the input new_satpoint, )?; } @@ -271,11 +263,9 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { } } - dbg!(&parent); - self.id_to_entry.insert( &inscription_id, - &dbg!(InscriptionEntry { + &InscriptionEntry { fee, height: self.height, number: self.next_number, @@ -283,7 +273,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { sat, timestamp: self.timestamp, } - .store()), + .store(), )?; self.next_number += 1; diff --git a/src/inscription.rs b/src/inscription.rs index 28fc7377be..037cc24dca 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -618,6 +618,7 @@ mod tests { } #[test] + #[ignore] // we need to do this now for parent-child relationships fn do_not_extract_from_second_input() { let tx = Transaction { version: 0, diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 51d05e8295..684862afc5 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( @@ -821,9 +821,6 @@ impl Server { .get_inscription_entry(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; - dbg!(&inscription_id); - dbg!(&entry); - let inscription = index .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 40518c5f10..14c1c8011e 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -1,4 +1,4 @@ -use bitcoin::{consensus::serialize, SchnorrSig}; +use bitcoin::{SchnorrSig}; use { super::*, @@ -107,7 +107,7 @@ impl Inscribe { .map(Ok) .unwrap_or_else(|| get_change_address(&client))?; - let (unsigned_commit_tx, partially_signed_reveal_tx, recovery_key_pair) = + let (unsigned_commit_tx, partially_signed_reveal_tx, _recovery_key_pair) = Inscribe::create_inscription_transactions( self.satpoint, parent, @@ -412,7 +412,7 @@ impl Inscribe { Ok((unsigned_commit_tx, reveal_tx, recovery_key_pair)) } - fn backup_recovery_key( + fn _backup_recovery_key( client: &Client, recovery_key_pair: TweakedKeyPair, network: Network, 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(), diff --git a/tests/core.rs b/tests/core.rs index effc8dd310..5c99cf82ab 100644 --- a/tests/core.rs +++ b/tests/core.rs @@ -84,8 +84,8 @@ fn get_free_port() -> u16 { } fn ord( - cookiefile: &std::path::PathBuf, - ord_data_dir: &std::path::PathBuf, + cookiefile: &std::path::Path, + ord_data_dir: &std::path::Path, rpc_port: u16, args: &[&str], ) -> Result { @@ -96,16 +96,16 @@ fn ord( .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .current_dir(&ord_data_dir) + .current_dir(ord_data_dir) .arg("--regtest") .arg("--data-dir") - .arg(ord_data_dir.as_path()) + .arg(ord_data_dir) .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() { @@ -150,12 +150,13 @@ fn inscribe_child() { let cookiefile = bitcoin_data_dir.as_path().join("regtest/.cookie"); for attempt in 0.. { - match Client::new( + if Client::new( &format!("127.0.0.1:{rpc_port}"), bitcoincore_rpc::Auth::CookieFile(cookiefile.clone()), - ) { - Ok(_) => break, - _ => (), + ) + .is_ok() + { + break; } if attempt == 500 { @@ -188,11 +189,11 @@ fn inscribe_child() { #[derive(Deserialize, Debug)] struct Output { - commit: String, + _commit: String, inscription: String, - parent: Option, - reveal: String, - fees: u64, + _parent: Option, + _reveal: String, + _fees: u64, } let output: Output = match ord( @@ -214,13 +215,7 @@ fn inscribe_child() { &cookiefile, &ord_data_dir, rpc_port, - &[ - "wallet", - "inscribe", - "--parent", - &parent_id, - "child.txt", - ], + &["wallet", "inscribe", "--parent", &parent_id, "child.txt"], ) { Ok(s) => serde_json::from_str(&s) .unwrap_or_else(|err| panic!("Failed to deserialize JSON: {err}\n{s}")), @@ -229,7 +224,7 @@ fn inscribe_child() { let child_id = output.inscription; let ord_port = 8080; - + rpc_client.generate_to_address(1, &address).unwrap(); let _ord_server = KillOnDrop( @@ -238,7 +233,7 @@ fn inscribe_child() { .stdin(Stdio::null()) // .stdout(Stdio::piped()) // .stderr(Stdio::piped()) - .current_dir(&ord_data_dir) + .current_dir(ord_data_dir.clone()) .arg("--regtest") .arg("--data-dir") .arg(ord_data_dir.as_path()) @@ -275,18 +270,25 @@ fn inscribe_child() { } let response = client - .get(format!("http://127.0.0.1:{ord_port}/inscription/{parent_id}")) + .get(format!( + "http://127.0.0.1:{ord_port}/inscription/{parent_id}" + )) .send() .unwrap(); assert_regex_match!(response.text().unwrap(), &format!(".*id.*{}.*", parent_id)); - + thread::sleep(Duration::from_secs(10)); - + let response = client - .get(format!("http://127.0.0.1:{ord_port}/inscription/{child_id}")) + .get(format!( + "http://127.0.0.1:{ord_port}/inscription/{child_id}" + )) .send() .unwrap(); - assert_regex_match!(response.text().unwrap(), &format!(".*parent.*{}.*", parent_id)); + assert_regex_match!( + response.text().unwrap(), + &format!(".*parent.*{}.*", parent_id) + ); } 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 78d518d29b..fdd45a8150 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -410,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}"), ".*"); let child_output = CommandBuilder::new(format!("wallet inscribe --parent {parent_id} child.png")) .write("child.png", [1; 520]) @@ -425,8 +423,8 @@ fn inscribe_with_parent_inscription() { rpc_server.mine_blocks(1); TestServer::spawn_with_args(&rpc_server, &[]).assert_response_regex( - format!("/inscription/{}", dbg!(child_output.inscription)), - format!(".*parent.*{}", parent_id), + format!("/inscription/{}", child_output.inscription), + format!(".*parent.*{}.*", parent_id), ); } From 6dd7f7b6c7644d6f150612e97ddc17d6cfeb9588 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 22 Mar 2023 14:25:27 -0700 Subject: [PATCH 29/54] fmt --- src/index.rs | 2 +- src/subcommand/wallet/inscribe.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.rs b/src/index.rs index be0306983d..a113672b00 100644 --- a/src/index.rs +++ b/src/index.rs @@ -548,7 +548,7 @@ impl Index { { return Ok(None); } - + Ok( self .get_transaction(inscription_id.txid)? diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 14c1c8011e..8d7fd67208 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -1,4 +1,4 @@ -use bitcoin::{SchnorrSig}; +use bitcoin::SchnorrSig; use { super::*, From 7274ab76b9ba447ea18938ab096e5bf6d2ecff86 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Thu, 23 Mar 2023 12:30:34 -0700 Subject: [PATCH 30/54] quick fix --- src/fee_rate.rs | 2 +- src/subcommand/server.rs | 2 +- src/subcommand/wallet/inscribe.rs | 23 +++++++++------- test-bitcoincore-rpc/src/server.rs | 4 ++- tests/wallet/inscribe.rs | 43 ++++++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/src/fee_rate.rs b/src/fee_rate.rs index 72d30834f5..e8f6c60494 100644 --- a/src/fee_rate.rs +++ b/src/fee_rate.rs @@ -23,7 +23,7 @@ impl TryFrom for FeeRate { } impl FeeRate { - pub(crate) fn fee(&self, vsize: usize) -> Amount { + pub fn fee(&self, vsize: usize) -> Amount { #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] Amount::from_sat((self.0 * vsize as f64).round() as u64) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 684862afc5..831e2caf4d 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -820,7 +820,7 @@ impl Server { let entry = index .get_inscription_entry(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; - + let inscription = index .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 8d7fd67208..910e62dd22 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -265,9 +265,9 @@ 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, output)) = parent.clone() { ( - vec![satpoint.outpoint, OutPoint::null()], + vec![parent_satpoint.outpoint, OutPoint::null()], vec![ TxOut { script_pubkey: output.script_pubkey, @@ -466,13 +466,18 @@ impl Inscribe { let mut reveal_tx = reveal_tx.clone(); 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()); + // only add dummy witness for reveal input/commit output + if txin.previous_output == OutPoint::null() { + txin.witness.push( + Signature::from_slice(&[0; SCHNORR_SIGNATURE_SIZE]) + .unwrap() + .as_ref(), + ); + txin.witness.push(script); + txin.witness.push(&control_block.serialize()); + } else { + txin.witness = Witness::from_vec(vec![vec![0; SCHNORR_SIGNATURE_SIZE]]); + } } fee_rate.fee(reveal_tx.vsize()) diff --git a/test-bitcoincore-rpc/src/server.rs b/test-bitcoincore-rpc/src/server.rs index 0ea66122dd..687c210fe9 100644 --- a/test-bitcoincore-rpc/src/server.rs +++ b/test-bitcoincore-rpc/src/server.rs @@ -249,7 +249,9 @@ impl Api for Server { let mut transaction = Transaction::deserialize(&hex::decode(tx).unwrap()).unwrap(); for input in &mut transaction.input { - input.witness = Witness::from_vec(vec![vec![0; 64]]); + if input.witness.is_empty() { + input.witness = Witness::from_vec(vec![vec![0; 64]]); + } } Ok( diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index fdd45a8150..eac8ddeb3b 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -445,3 +445,46 @@ fn inscribe_with_non_existent_parent_inscription() { .expected_exit_code(1) .run(); } + +#[test] +fn inscribe_with_parent_inscription_and_fee_rate() { + let rpc_server = test_bitcoincore_rpc::spawn(); + create_wallet(&rpc_server); + rpc_server.mine_blocks(1); + + let parent_output = CommandBuilder::new("wallet inscribe --fee-rate 5.0 parent.png") + .write("parent.png", [1; 520]) + .rpc_server(&rpc_server) + .output::(); + + let parent_id = parent_output.inscription; + + let commit_tx = &rpc_server.mempool()[0]; + let reveal_tx = &rpc_server.mempool()[1]; + assert_eq!( + ord::FeeRate::try_from(5.0) + .unwrap() + .fee(commit_tx.vsize() + reveal_tx.vsize()).to_sat(), + parent_output.fees + ); + + rpc_server.mine_blocks(1); + + let child_output = CommandBuilder::new(format!( + "wallet inscribe --fee-rate 5.0 --parent {parent_id} child.png" + )) + .write("child.png", [1; 520]) + .rpc_server(&rpc_server) + .output::(); + + assert_eq!(parent_id, child_output.parent.unwrap()); + + let commit_tx = &rpc_server.mempool()[0]; + let reveal_tx = &rpc_server.mempool()[1]; + assert_eq!( + ord::FeeRate::try_from(5.0) + .unwrap() + .fee(commit_tx.vsize() + reveal_tx.vsize()).to_sat(), + child_output.fees + ); +} From a7dddb53adae5aaee4c7220e11ae704d104d9090 Mon Sep 17 00:00:00 2001 From: ordinally Date: Fri, 24 Mar 2023 09:56:26 -0700 Subject: [PATCH 31/54] Added unit test for inscription with custom fee rate and parent. Fix issue i#1916 --- src/subcommand/server.rs | 2 +- src/subcommand/wallet/inscribe.rs | 79 ++++++++++++++++++++++++++++-- test-bitcoincore-rpc/src/server.rs | 2 +- tests/wallet/inscribe.rs | 6 ++- 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 831e2caf4d..684862afc5 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -820,7 +820,7 @@ impl Server { let entry = index .get_inscription_entry(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; - + let inscription = index .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 910e62dd22..c54aac991f 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -295,6 +295,7 @@ impl Inscribe { &control_block, reveal_fee_rate, inputs.clone(), + commit_input_offset, outputs.clone(), &reveal_script, ); @@ -331,6 +332,7 @@ impl Inscribe { &control_block, reveal_fee_rate, inputs, + commit_input_offset, outputs, &reveal_script, ); @@ -444,6 +446,7 @@ impl Inscribe { control_block: &ControlBlock, fee_rate: FeeRate, inputs: Vec, + commit_input_index: usize, outputs: Vec, script: &Script, ) -> (Transaction, Amount) { @@ -465,9 +468,9 @@ impl Inscribe { let fee = { let mut reveal_tx = reveal_tx.clone(); - for txin in &mut reveal_tx.input { - // only add dummy witness for reveal input/commit output - if txin.previous_output == OutPoint::null() { + for (current_index, txin) in reveal_tx.input.iter_mut().enumerate() { + // add dummy inscription witness for reveal input/commit output + if current_index == commit_input_index { txin.witness.push( Signature::from_slice(&[0; SCHNORR_SIGNATURE_SIZE]) .unwrap() @@ -687,6 +690,76 @@ mod tests { ); } + #[test] + fn inscribe_with_custom_fee_rate_and_parent() { + let utxos = vec![ + (outpoint(1), Amount::from_sat(10_000)), + (outpoint(2), Amount::from_sat(20_000)), + ]; + let mut inscriptions = BTreeMap::new(); + inscriptions.insert( + SatPoint { + outpoint: outpoint(1), + offset: 0, + }, + inscription_id(1), + ); + + let inscription = inscription("text/plain", [b'O'; 100]); + + let satpoint = None; + let commit_address = change(1); + let reveal_address = recipient(); + let fee_rate = 4.0; + + let (commit_tx, reveal_tx, _private_key) = Inscribe::create_inscription_transactions( + satpoint, + Some(( + SatPoint { + outpoint: outpoint(1), + offset: 0, + }, + TxOut { + script_pubkey: change(0).script_pubkey(), + value: 10000, + }, + )), + inscription, + inscriptions, + bitcoin::Network::Signet, + utxos.into_iter().collect(), + [commit_address, change(2)], + reveal_address, + FeeRate::try_from(fee_rate).unwrap(), + FeeRate::try_from(fee_rate).unwrap(), + false, + ) + .unwrap(); + + let sig_vbytes = 17; + let fee = FeeRate::try_from(fee_rate) + .unwrap() + .fee(commit_tx.vsize() + sig_vbytes) + .to_sat(); + + let reveal_value = commit_tx + .output + .iter() + .map(|o| o.value) + .reduce(|acc, i| acc + i) + .unwrap(); + + assert_eq!(reveal_value, 20_000 - fee); + + let sig_vbytes = 16; + let fee = FeeRate::try_from(fee_rate) + .unwrap() + .fee(reveal_tx.vsize() + sig_vbytes) + .to_sat(); + + assert_eq!(fee, commit_tx.output[0].value - reveal_tx.output[1].value,); + } + #[test] fn inscribe_with_commit_fee_rate() { let utxos = vec![ diff --git a/test-bitcoincore-rpc/src/server.rs b/test-bitcoincore-rpc/src/server.rs index 687c210fe9..26d842d460 100644 --- a/test-bitcoincore-rpc/src/server.rs +++ b/test-bitcoincore-rpc/src/server.rs @@ -250,7 +250,7 @@ impl Api for Server { let mut transaction = Transaction::deserialize(&hex::decode(tx).unwrap()).unwrap(); for input in &mut transaction.input { if input.witness.is_empty() { - input.witness = Witness::from_vec(vec![vec![0; 64]]); + input.witness = Witness::from_vec(vec![vec![0; 64]]); } } diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index eac8ddeb3b..cbe4ed9457 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -464,7 +464,8 @@ fn inscribe_with_parent_inscription_and_fee_rate() { assert_eq!( ord::FeeRate::try_from(5.0) .unwrap() - .fee(commit_tx.vsize() + reveal_tx.vsize()).to_sat(), + .fee(commit_tx.vsize() + reveal_tx.vsize()) + .to_sat(), parent_output.fees ); @@ -484,7 +485,8 @@ fn inscribe_with_parent_inscription_and_fee_rate() { assert_eq!( ord::FeeRate::try_from(5.0) .unwrap() - .fee(commit_tx.vsize() + reveal_tx.vsize()).to_sat(), + .fee(commit_tx.vsize() + reveal_tx.vsize()) + .to_sat(), child_output.fees ); } From e9e40e8965503b9f916dd996fd1d28affd030752 Mon Sep 17 00:00:00 2001 From: ordinally Date: Fri, 24 Mar 2023 10:31:24 -0700 Subject: [PATCH 32/54] Remove obsolete TODOs --- src/index/entry.rs | 2 +- src/index/updater/inscription_updater.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/index/entry.rs b/src/index/entry.rs index 5a971c1af7..8a00ae6e2a 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -144,7 +144,7 @@ impl Entry for Option { 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(); diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 9d8a6216cc..c681be8161 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -140,7 +140,6 @@ 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 @@ -195,7 +194,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.update_inscription_location( input_sat_ranges, - inscriptions.next().unwrap(), // TODO: do something with two inscriptions in the input + inscriptions.next().unwrap(), // This will need to change when we implement multiple inscriptions per TX (#1298). new_satpoint, )?; } From 01caaaa22e37a8b9612180393e012a07d8df3caf Mon Sep 17 00:00:00 2001 From: ordinally Date: Fri, 24 Mar 2023 12:09:57 -0700 Subject: [PATCH 33/54] Fix tests --- src/inscription.rs | 8 ++++---- tests/core.rs | 25 ++++++++++++++++--------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/inscription.rs b/src/inscription.rs index 037cc24dca..e4b8ad648f 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -618,8 +618,8 @@ mod tests { } #[test] - #[ignore] // we need to do this now for parent-child relationships - fn do_not_extract_from_second_input() { + fn do_extract_from_second_input() { + let inscription = inscription("foo", [1; 1040]); let tx = Transaction { version: 0, lock_time: bitcoin::PackedLockTime(0), @@ -634,13 +634,13 @@ mod tests { previous_output: OutPoint::null(), script_sig: Script::new(), sequence: Sequence(0), - witness: inscription("foo", [1; 1040]).to_witness(), + witness: inscription.to_witness(), }, ], output: Vec::new(), }; - assert_eq!(Inscription::from_transaction(&tx), None); + assert_eq!(Inscription::from_transaction(&tx), Some(inscription)); } #[test] diff --git a/tests/core.rs b/tests/core.rs index 5c99cf82ab..83d0537576 100644 --- a/tests/core.rs +++ b/tests/core.rs @@ -1,3 +1,5 @@ +use bitcoin::Address; + use { super::*, bitcoincore_rpc::{Client, RpcApi}, @@ -183,17 +185,21 @@ fn inscribe_child() { .get_new_address(None, Some(bitcoincore_rpc::json::AddressType::Bech32m)) .unwrap(); - rpc_client.generate_to_address(101, &address).unwrap(); + let not_ours = Address::from_str("bcrt1qyr2zc4lhadk9k35hwfh2unn7hgvtpwpx8mjx4h").unwrap(); + + rpc_client.generate_to_address(1, &address).unwrap(); + rpc_client.generate_to_address(100, ¬_ours).unwrap(); // need to mine 100 blocks for coins to become spendable. use address outside our wallet to prevent slow rescan fs::write(ord_data_dir.as_path().join("parent.txt"), "Pater").unwrap(); #[derive(Deserialize, Debug)] + #[allow(dead_code)] // required because of the `serde` macro, can't use _ struct Output { - _commit: String, + commit: String, inscription: String, - _parent: Option, - _reveal: String, - _fees: u64, + parent: Option, + reveal: String, + fees: u64, } let output: Output = match ord( @@ -209,13 +215,14 @@ fn inscribe_child() { let parent_id = output.inscription; rpc_client.generate_to_address(1, &address).unwrap(); + thread::sleep(Duration::from_secs(1)); fs::write(ord_data_dir.as_path().join("child.txt"), "Filius").unwrap(); let output: Output = match ord( &cookiefile, &ord_data_dir, rpc_port, - &["wallet", "inscribe", "--parent", &parent_id, "child.txt"], + &["wallet", "inscribe", "--fee-rate", "2", "--parent", &parent_id, "child.txt"], ) { Ok(s) => serde_json::from_str(&s) .unwrap_or_else(|err| panic!("Failed to deserialize JSON: {err}\n{s}")), @@ -223,16 +230,16 @@ fn inscribe_child() { }; let child_id = output.inscription; - let ord_port = 8080; rpc_client.generate_to_address(1, &address).unwrap(); + let ord_port = 8080; let _ord_server = KillOnDrop( Command::new(executable_path("ord")) .env("ORD_INTEGRATION_TEST", "1") .stdin(Stdio::null()) - // .stdout(Stdio::piped()) - // .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) .current_dir(ord_data_dir.clone()) .arg("--regtest") .arg("--data-dir") From 1c1a4080cec7a82d3f6bd26db442e19a2a6b2297 Mon Sep 17 00:00:00 2001 From: ordinally Date: Fri, 24 Mar 2023 12:16:54 -0700 Subject: [PATCH 34/54] fmt --- tests/core.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/core.rs b/tests/core.rs index 83d0537576..aad327712c 100644 --- a/tests/core.rs +++ b/tests/core.rs @@ -222,7 +222,15 @@ fn inscribe_child() { &cookiefile, &ord_data_dir, rpc_port, - &["wallet", "inscribe", "--fee-rate", "2", "--parent", &parent_id, "child.txt"], + &[ + "wallet", + "inscribe", + "--fee-rate", + "2", + "--parent", + &parent_id, + "child.txt", + ], ) { Ok(s) => serde_json::from_str(&s) .unwrap_or_else(|err| panic!("Failed to deserialize JSON: {err}\n{s}")), From 0d54a536c4046952465966c6126b98dcd9f310bc Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 24 Mar 2023 14:47:03 -0700 Subject: [PATCH 35/54] Miscellaneous design improvements (#1968) * blue text only on hover in navbar * white text in navbar * add missing semicolon in css * change navbar background to black * switch search button from text to symbol * improve navbar submit element style --- src/index.rs | 1 - src/subcommand/server.rs | 4 +-- src/subcommand/wallet/transaction_builder.rs | 1 - src/templates.rs | 2 +- static/index.css | 28 +++++++++++++++----- templates/page.html | 2 +- 6 files changed, 26 insertions(+), 12 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/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(), diff --git a/src/templates.rs b/src/templates.rs index f4344309e6..7a8858a869 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -137,7 +137,7 @@ mod tests { rare.txt
- +
diff --git a/static/index.css b/static/index.css index a8faeb6616..36a1551793 100644 --- a/static/index.css +++ b/static/index.css @@ -4,8 +4,8 @@ --dark-fg: #98a3ad; --epic: darkorchid; --legendary: gold; - --light-bg: #292c2f; - --light-fg: #a1adb8; + --light-bg: #000000; + --light-fg: #ffffff; --link: #4169e1; --mythic: #f2a900; --rare: cornflowerblue; @@ -48,10 +48,6 @@ a:hover { text-decoration: underline; } -a:visited { - color: var(--link); -} - dt { font-weight: bold; margin-top: 0.5rem; @@ -70,6 +66,15 @@ nav > :first-child { font-weight: bold; } +nav a:hover { + color: var(--link); + text-decoration: none; +} + +nav a { + color: var(--light-fg); +} + form { display: flex; flex-grow: 1; @@ -87,6 +92,17 @@ input[type=text] { min-width: 0; } +input[type=submit] { + background: none; + border: none; + color: var(--light-fg); + cursor: pointer; + font-weight: bold; + font: inherit; + outline: inherit; + padding: 0; +} + dl { overflow-wrap: break-word; } diff --git a/templates/page.html b/templates/page.html index 8e5a25ed1e..6d1faccff3 100644 --- a/templates/page.html +++ b/templates/page.html @@ -25,7 +25,7 @@ %% }
- +
From ebab8cb4fbd7d3dd4fec06430400b8b08c78076d Mon Sep 17 00:00:00 2001 From: ordinally Date: Fri, 24 Mar 2023 19:07:33 -0700 Subject: [PATCH 36/54] Dummy interface for children --- src/index.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/index.rs b/src/index.rs index a113672b00..b0a6461d07 100644 --- a/src/index.rs +++ b/src/index.rs @@ -556,6 +556,13 @@ impl Index { ) } + pub(crate) fn get_children_by_id( + &self, + inscription_id: InscriptionId, + ) -> Result> { + Ok(Vec::new()) + } + pub(crate) fn get_inscriptions_on_output( &self, outpoint: OutPoint, From a01e2373998b7e2c5f399a803a52a4ea92cded51 Mon Sep 17 00:00:00 2001 From: ordinally Date: Fri, 24 Mar 2023 20:11:51 -0700 Subject: [PATCH 37/54] Naive implementation of get_children_by_id --- src/index.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/index.rs b/src/index.rs index b0a6461d07..aaabf860dc 100644 --- a/src/index.rs +++ b/src/index.rs @@ -560,7 +560,20 @@ impl Index { &self, inscription_id: InscriptionId, ) -> Result> { - Ok(Vec::new()) + let mut children = Vec::new(); + + for (key, entry_value) in self + .database + .begin_read()? + .open_table(INSCRIPTION_ID_TO_INSCRIPTION_ENTRY)? + .iter()? + { + let entry = InscriptionEntry::load(entry_value.value()); + if entry.parent == Some(inscription_id) { + children.push(InscriptionId::load(*key.value())); + } + } + Ok(children) } pub(crate) fn get_inscriptions_on_output( From 58c50200232ec6f2b4931e9b81d0a14612db9ff2 Mon Sep 17 00:00:00 2001 From: ordinally Date: Fri, 24 Mar 2023 20:22:55 -0700 Subject: [PATCH 38/54] Added test --- src/index.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/index.rs b/src/index.rs index aaabf860dc..d2f129e9e2 100644 --- a/src/index.rs +++ b/src/index.rs @@ -2280,5 +2280,9 @@ mod tests { timestamp: 3 }) ); + + // child successfully retrieved from parent + let children = context.index.get_children_by_id(parent_id).unwrap(); + assert_eq!(children, vec![child_id]); } } From 84d94172779d335e5b7793db55c5557089db3262 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 24 Mar 2023 20:31:02 -0700 Subject: [PATCH 39/54] children pages --- src/subcommand/server.rs | 43 +++++++++++++++++++++++++-- src/templates.rs | 2 ++ src/templates/inscription_children.rs | 42 ++++++++++++++++++++++++++ templates/inscription-children.html | 6 ++++ 4 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/templates/inscription_children.rs create mode 100644 templates/inscription-children.html diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 684862afc5..cfa8f60998 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -6,9 +6,10 @@ use { super::*, crate::page_config::PageConfig, crate::templates::{ - BlockHtml, ClockSvg, HomeHtml, InputHtml, InscriptionHtml, InscriptionsHtml, OutputHtml, - PageContent, PageHtml, PreviewAudioHtml, PreviewImageHtml, PreviewPdfHtml, PreviewTextHtml, - PreviewUnknownHtml, PreviewVideoHtml, RangeHtml, RareTxt, SatHtml, TransactionHtml, + BlockHtml, ClockSvg, HomeHtml, InputHtml, InscriptionChildrenHtml, InscriptionHtml, + InscriptionsHtml, OutputHtml, PageContent, PageHtml, PreviewAudioHtml, PreviewImageHtml, + PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, PreviewVideoHtml, RangeHtml, RareTxt, + SatHtml, TransactionHtml, }, axum::{ body, @@ -154,6 +155,14 @@ impl Server { .route("/feed.xml", get(Self::feed)) .route("/input/:block/:transaction/:input", get(Self::input)) .route("/inscription/:inscription_id", get(Self::inscription)) + .route( + "/inscription/:inscription_id/children", + get(Self::inscription_children), + ) + .route( + "/inscription/:inscription_id/children/:index", + get(Self::inscription_child), + ) .route("/inscriptions", get(Self::inscriptions)) .route("/inscriptions/:from", get(Self::inscriptions_from)) .route("/install.sh", get(Self::install_script)) @@ -869,6 +878,34 @@ impl Server { ) } + async fn inscription_children( + Extension(page_config): Extension>, + Extension(index): Extension>, + Path(inscription_id): Path, + ) -> ServerResult> { + + + Ok( + InscriptionChildrenHtml { + parent_id: inscription_id, + + } + .page(page_config, index.has_sat_index()?), + ) + } + + async fn inscription_child( + Extension(page_config): Extension>, + Extension(index): Extension>, + Path(inscription_id): Path, + Path(child_num): Path, + ) -> ServerResult> { + // get nth child of parent + // load the inscription page of that child + + Self::inscription(Extension(page_config), Extension(index), Path(inscription_id)).await + } + async fn inscriptions( Extension(page_config): Extension>, Extension(index): Extension>, diff --git a/src/templates.rs b/src/templates.rs index 7a8858a869..9676bcd6b8 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -18,6 +18,7 @@ pub(crate) use { rare::RareTxt, sat::SatHtml, transaction::TransactionHtml, + inscription_children::InscriptionChildrenHtml, }; mod block; @@ -26,6 +27,7 @@ mod home; mod iframe; mod input; mod inscription; +mod inscription_children; mod inscriptions; mod output; mod preview; diff --git a/src/templates/inscription_children.rs b/src/templates/inscription_children.rs new file mode 100644 index 0000000000..d55e9fcb49 --- /dev/null +++ b/src/templates/inscription_children.rs @@ -0,0 +1,42 @@ +use super::*; + +#[derive(Boilerplate)] +pub(crate) struct InscriptionChildrenHtml { + pub(crate) parent_id: InscriptionId, + pub(crate) children: Vec, +} + + +impl PageContent for InscriptionChildrenHtml { + fn title(&self) -> String { + format!("Children") + } + + fn preview_image_url(&self) -> Option> { + Some(Trusted(format!("/content/{}", self.parent_id))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn without_sat_or_nav_links() { + assert_regex_match!( + InscriptionChildrenHtml { + parent_id: inscription_id(0), + children: vec![inscription_id(1), inscription_id(2), inscription_id(3)] + }, + " +

0{64}i0's children

+
+ + + +
+ " + .unindent() + ); + } +} diff --git a/templates/inscription-children.html b/templates/inscription-children.html new file mode 100644 index 0000000000..7fbcf4ad86 --- /dev/null +++ b/templates/inscription-children.html @@ -0,0 +1,6 @@ +

{{ self.parent_id }}'s children

+
+%% for id in &self.children { + {{Iframe::thumbnail(*id)}} +%% } +
From 96ff43b0643c00e12a06061fed91e9232f5112b0 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 24 Mar 2023 21:27:32 -0700 Subject: [PATCH 40/54] ugly html pages for children --- src/index.rs | 2 ++ src/subcommand/server.rs | 21 +++++++++------- src/templates/inscription.rs | 36 +++++++++++++++++++++++++++ src/templates/inscription_children.rs | 2 +- templates/inscription.html | 6 +++++ 5 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/index.rs b/src/index.rs index d2f129e9e2..21096d5b6c 100644 --- a/src/index.rs +++ b/src/index.rs @@ -573,6 +573,8 @@ impl Index { children.push(InscriptionId::load(*key.value())); } } + + // TODO: do with accumulators and reutrn None Ok(children) } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index cfa8f60998..7f2113b222 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -160,7 +160,7 @@ impl Server { get(Self::inscription_children), ) .route( - "/inscription/:inscription_id/children/:index", + "/inscription/:inscription_id/child/:index", get(Self::inscription_child), ) .route("/inscriptions", get(Self::inscriptions)) @@ -834,6 +834,8 @@ impl Server { .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; + let children = index.get_children_by_id(inscription_id)?; + let satpoint = index .get_inscription_satpoint_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; @@ -861,6 +863,7 @@ impl Server { Ok( InscriptionHtml { chain: page_config.chain, + children, genesis_fee: entry.fee, genesis_height: entry.height, inscription, @@ -883,12 +886,10 @@ impl Server { Extension(index): Extension>, Path(inscription_id): Path, ) -> ServerResult> { - - Ok( InscriptionChildrenHtml { parent_id: inscription_id, - + children: index.get_children_by_id(inscription_id)?, } .page(page_config, index.has_sat_index()?), ) @@ -897,13 +898,15 @@ impl Server { async fn inscription_child( Extension(page_config): Extension>, Extension(index): Extension>, - Path(inscription_id): Path, - Path(child_num): Path, + Path((parent_id, child_num)): Path<(InscriptionId, usize)>, ) -> ServerResult> { - // get nth child of parent - // load the inscription page of that child + let children = index.get_children_by_id(parent_id)?; + + let child_id = children + .get(child_num) + .ok_or_not_found(|| format!("child #{child_num} for parent {parent_id}"))?; - Self::inscription(Extension(page_config), Extension(index), Path(inscription_id)).await + Self::inscription(Extension(page_config), Extension(index), Path(*child_id)).await } async fn inscriptions( diff --git a/src/templates/inscription.rs b/src/templates/inscription.rs index c6c40dcfa9..ef3a563dcf 100644 --- a/src/templates/inscription.rs +++ b/src/templates/inscription.rs @@ -3,6 +3,7 @@ use super::*; #[derive(Boilerplate)] pub(crate) struct InscriptionHtml { pub(crate) chain: Chain, + pub(crate) children: Vec, pub(crate) genesis_fee: u64, pub(crate) genesis_height: u64, pub(crate) inscription: Inscription, @@ -36,6 +37,7 @@ mod tests { assert_regex_match!( InscriptionHtml { chain: Chain::Mainnet, + children: vec![], genesis_fee: 1, genesis_height: 0, inscription: inscription("text/plain;charset=utf-8", "HELLOWORLD"), @@ -96,6 +98,7 @@ mod tests { assert_regex_match!( InscriptionHtml { chain: Chain::Mainnet, + children: vec![], genesis_fee: 1, genesis_height: 0, inscription: inscription("text/plain;charset=utf-8", "HELLOWORLD"), @@ -129,6 +132,39 @@ mod tests { assert_regex_match!( InscriptionHtml { chain: Chain::Mainnet, + children: vec![], + genesis_fee: 1, + genesis_height: 0, + inscription: inscription("text/plain;charset=utf-8", "HELLOWORLD"), + inscription_id: inscription_id(2), + 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), + timestamp: timestamp(0), + }, + " +

Inscription 1

+
+ + + +
+ .* + " + .unindent() + ); + } + + #[test] + fn with_children() { + assert_regex_match!( + InscriptionHtml { + chain: Chain::Mainnet, + children: vec![inscription_id(5), inscription_id(6)], genesis_fee: 1, genesis_height: 0, inscription: inscription("text/plain;charset=utf-8", "HELLOWORLD"), diff --git a/src/templates/inscription_children.rs b/src/templates/inscription_children.rs index d55e9fcb49..c86a6f358c 100644 --- a/src/templates/inscription_children.rs +++ b/src/templates/inscription_children.rs @@ -26,7 +26,7 @@ mod tests { assert_regex_match!( InscriptionChildrenHtml { parent_id: inscription_id(0), - children: vec![inscription_id(1), inscription_id(2), inscription_id(3)] + children: vec![inscription_id(1), inscription_id(2), inscription_id(3)], }, "

0{64}i0's children

diff --git a/templates/inscription.html b/templates/inscription.html index fd1144a6ca..420f82bbaa 100644 --- a/templates/inscription.html +++ b/templates/inscription.html @@ -19,6 +19,12 @@

Inscription {{ self.number }}

parent
{{parent}}
%% } +%% if !self.children.is_empty() { +
children
+%% for (i, child) in self.children.iter().enumerate() { +
{{child}}
+%% } +%% } %% if let Ok(address) = self.chain.address_from_script(&self.output.script_pubkey ) {
address
{{ address }}
From 1419c2bdb379b8e8dfad7eeff28005fc1d6593a3 Mon Sep 17 00:00:00 2001 From: ordinally Date: Sat, 25 Mar 2023 08:41:38 -0700 Subject: [PATCH 41/54] Implement cache for get_children_by_id --- src/index.rs | 36 +++++++++++++++++++-------- src/templates.rs | 2 +- src/templates/inscription_children.rs | 5 ++-- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/index.rs b/src/index.rs index 21096d5b6c..c93d913f95 100644 --- a/src/index.rs +++ b/src/index.rs @@ -16,6 +16,7 @@ use { redb::{Database, ReadableTable, Table, TableDefinition, WriteStrategy, WriteTransaction}, std::collections::HashMap, std::sync::atomic::{self, AtomicBool}, + std::sync::Mutex, }; pub(crate) mod entry; @@ -54,6 +55,7 @@ pub(crate) struct Index { height_limit: Option, reorged: AtomicBool, rpc_url: String, + cached_children_by_id: Mutex>>, } #[derive(Debug, PartialEq)] @@ -241,6 +243,7 @@ impl Index { height_limit: options.height_limit, reorged: AtomicBool::new(false), rpc_url, + cached_children_by_id: Mutex::new(HashMap::new()), }) } @@ -560,21 +563,34 @@ impl Index { &self, inscription_id: InscriptionId, ) -> Result> { - let mut children = Vec::new(); + { + let cache = self.cached_children_by_id.lock().unwrap(); + if let Some(cached_result) = cache.get(&inscription_id) { + return Ok(cached_result.clone()); + } + } - for (key, entry_value) in self + let children = self .database .begin_read()? .open_table(INSCRIPTION_ID_TO_INSCRIPTION_ENTRY)? .iter()? - { - let entry = InscriptionEntry::load(entry_value.value()); - if entry.parent == Some(inscription_id) { - children.push(InscriptionId::load(*key.value())); - } - } - - // TODO: do with accumulators and reutrn None + .map(|(key, entry_value)| { + let entry = InscriptionEntry::load(entry_value.value()); + if entry.parent == Some(inscription_id) { + Ok(InscriptionId::load(*key.value())) + } else { + Err(()) + } + }) + .filter_map(Result::ok) + .collect::>(); + + self + .cached_children_by_id + .lock() + .unwrap() + .insert(inscription_id, children.clone()); Ok(children) } diff --git a/src/templates.rs b/src/templates.rs index 9676bcd6b8..c2915745ce 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -7,6 +7,7 @@ pub(crate) use { iframe::Iframe, input::InputHtml, inscription::InscriptionHtml, + inscription_children::InscriptionChildrenHtml, inscriptions::InscriptionsHtml, output::OutputHtml, page_config::PageConfig, @@ -18,7 +19,6 @@ pub(crate) use { rare::RareTxt, sat::SatHtml, transaction::TransactionHtml, - inscription_children::InscriptionChildrenHtml, }; mod block; diff --git a/src/templates/inscription_children.rs b/src/templates/inscription_children.rs index c86a6f358c..b598eabd1e 100644 --- a/src/templates/inscription_children.rs +++ b/src/templates/inscription_children.rs @@ -2,11 +2,10 @@ use super::*; #[derive(Boilerplate)] pub(crate) struct InscriptionChildrenHtml { - pub(crate) parent_id: InscriptionId, - pub(crate) children: Vec, + pub(crate) parent_id: InscriptionId, + pub(crate) children: Vec, } - impl PageContent for InscriptionChildrenHtml { fn title(&self) -> String { format!("Children") From ccd5f7123c6c4bfc23b5d7d880f565d9e208eba9 Mon Sep 17 00:00:00 2001 From: ordinally Date: Sun, 26 Mar 2023 07:29:53 -0700 Subject: [PATCH 42/54] Update children cache when new child inscriptions are inscribed Formatting Debug logging --- src/index.rs | 1 + src/index/updater.rs | 9 ++-- src/index/updater/inscription_updater.rs | 53 ++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/index.rs b/src/index.rs index c93d913f95..d3a3947905 100644 --- a/src/index.rs +++ b/src/index.rs @@ -591,6 +591,7 @@ impl Index { .lock() .unwrap() .insert(inscription_id, children.clone()); + Ok(children) } diff --git a/src/index/updater.rs b/src/index/updater.rs index 9ce9292b95..c1e8736f7a 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -29,7 +29,7 @@ impl From for BlockData { } } -pub(crate) struct Updater { +pub(crate) struct Updater<'a> { range_cache: HashMap>, height: u64, index_sats: bool, @@ -37,10 +37,11 @@ pub(crate) struct Updater { outputs_cached: u64, outputs_inserted_since_flush: u64, outputs_traversed: u64, + cached_children_by_id: &'a Mutex>>, } -impl Updater { - pub(crate) fn update(index: &Index) -> Result { +impl<'a> Updater<'a> { + pub(crate) fn update(index: &'a Index) -> Result { let wtx = index.begin_write()?; let height = wtx @@ -70,6 +71,7 @@ impl Updater { outputs_cached: 0, outputs_inserted_since_flush: 0, outputs_traversed: 0, + cached_children_by_id: &index.cached_children_by_id, }; updater.update_index(index, wtx) @@ -431,6 +433,7 @@ impl Updater { &mut satpoint_to_inscription_id, block.header.time, value_cache, + &self.cached_children_by_id, )?; if self.index_sats { diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index c681be8161..5500389e26 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -29,6 +29,7 @@ pub(super) struct InscriptionUpdater<'a, 'db, 'tx> { satpoint_to_id: &'a mut Table<'db, 'tx, &'static SatPointValue, &'static InscriptionIdValue>, timestamp: u32, value_cache: &'a mut HashMap, + cached_children_by_id: &'a Mutex>>, } impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { @@ -44,6 +45,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { satpoint_to_id: &'a mut Table<'db, 'tx, &'static SatPointValue, &'static InscriptionIdValue>, timestamp: u32, value_cache: &'a mut HashMap, + cached_children_by_id: &'a Mutex>>, ) -> Result { let next_number = number_to_id .iter()? @@ -67,6 +69,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { satpoint_to_id, timestamp, value_cache, + cached_children_by_id, }) } @@ -103,19 +106,37 @@ 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 inscription_id = InscriptionId { + txid, + index: 0, // will have to be updated for multi inscriptions + }; + // parent has to be in an input before child // think about specifying a more general approach in a protocol doc/BIP + if let Some(parent_candidate) = inscription.get_parent_id() { + log::debug!( + "INDEX: inscription {} has inscribed parent {}", + inscription_id, + parent_candidate + ); + } + let parent = inscription.get_parent_id().filter(|&parent_id| { floating_inscriptions .iter() .any(|flotsam| flotsam.inscription_id == parent_id) }); + if let Some(parent) = parent { + log::debug!( + "INDEX: inscription {} has confirmed parent {}", + inscription_id, + parent + ); + } + floating_inscriptions.push(Flotsam { - inscription_id: InscriptionId { - txid, - index: 0, // will have to be updated for multi inscriptions - }, + inscription_id, offset: input_value, origin: Origin::New((0, parent)), }); @@ -262,6 +283,13 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { } } + log::debug!( + "INDEX: assigned {} for inscription {} at height {}", + &self.next_number, + flotsam.inscription_id, + self.height + ); + self.id_to_entry.insert( &inscription_id, &InscriptionEntry { @@ -275,6 +303,10 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { .store(), )?; + if let Some(parent) = parent { + self.update_cached_children(parent, flotsam.inscription_id); + } + self.next_number += 1; } } @@ -286,4 +318,17 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { Ok(()) } + + fn update_cached_children(&self, parent: InscriptionId, inscription_id: InscriptionId) { + let mut cache = self.cached_children_by_id.lock().unwrap(); + + match cache.get_mut(&parent) { + Some(children) => { + children.push(inscription_id); + } + None => { + cache.insert(parent, vec![inscription_id]); + } + } + } } From 03b608827d65aa8f1fc401369c35c7bf44203545 Mon Sep 17 00:00:00 2001 From: ordinally Date: Sun, 26 Mar 2023 10:42:56 -0700 Subject: [PATCH 43/54] Improvements to children's page --- src/subcommand/server.rs | 5 +++++ src/templates/inscription_children.rs | 6 +++++- templates/inscription-children.html | 4 +++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 7f2113b222..0faf2c58f1 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -886,9 +886,14 @@ impl Server { Extension(index): Extension>, Path(inscription_id): Path, ) -> ServerResult> { + let entry = index + .get_inscription_entry(inscription_id)? + .ok_or_not_found(|| format!("inscription {inscription_id}"))?; + Ok( InscriptionChildrenHtml { parent_id: inscription_id, + parent_number: entry.number, children: index.get_children_by_id(inscription_id)?, } .page(page_config, index.has_sat_index()?), diff --git a/src/templates/inscription_children.rs b/src/templates/inscription_children.rs index b598eabd1e..46c9c83768 100644 --- a/src/templates/inscription_children.rs +++ b/src/templates/inscription_children.rs @@ -3,6 +3,7 @@ use super::*; #[derive(Boilerplate)] pub(crate) struct InscriptionChildrenHtml { pub(crate) parent_id: InscriptionId, + pub(crate) parent_number: u64, pub(crate) children: Vec, } @@ -25,10 +26,13 @@ mod tests { assert_regex_match!( InscriptionChildrenHtml { parent_id: inscription_id(0), + parent_number: 0, children: vec![inscription_id(1), inscription_id(2), inscription_id(3)], }, " -

0{64}i0's children

+

Parent inscription 0

+ +

Children

diff --git a/templates/inscription-children.html b/templates/inscription-children.html index 7fbcf4ad86..5a0bb8f473 100644 --- a/templates/inscription-children.html +++ b/templates/inscription-children.html @@ -1,4 +1,6 @@ -

{{ self.parent_id }}'s children

+

Parent inscription {{ self.parent_number }}

+{{Iframe::main(self.parent_id)}} +

Children

%% for id in &self.children { {{Iframe::thumbnail(*id)}} From 0c533364f0756ec9308561dfd8e39919ade4a000 Mon Sep 17 00:00:00 2001 From: ordinally Date: Sun, 26 Mar 2023 12:28:44 -0700 Subject: [PATCH 44/54] Fix caching of children --- src/index/updater/inscription_updater.rs | 4 +--- src/subcommand/server.rs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 5500389e26..931361763e 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -326,9 +326,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { Some(children) => { children.push(inscription_id); } - None => { - cache.insert(parent, vec![inscription_id]); - } + None => {} // leave the cache empty, so we retrieve the full list of children when required } } } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 0faf2c58f1..b202b82c00 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -887,8 +887,8 @@ impl Server { Path(inscription_id): Path, ) -> ServerResult> { let entry = index - .get_inscription_entry(inscription_id)? - .ok_or_not_found(|| format!("inscription {inscription_id}"))?; + .get_inscription_entry(inscription_id)? + .ok_or_not_found(|| format!("inscription {inscription_id}"))?; Ok( InscriptionChildrenHtml { From d78fd61a313b8b131d78de0e618607c3b8b01716 Mon Sep 17 00:00:00 2001 From: ordinally Date: Sun, 26 Mar 2023 20:48:44 -0700 Subject: [PATCH 45/54] Layout updates Clippy --- src/index/updater.rs | 2 +- src/index/updater/inscription_updater.rs | 8 +++----- src/templates/inscription_children.rs | 6 ++++-- static/index.css | 11 +++++++++++ templates/inscription-children.html | 4 +++- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/index/updater.rs b/src/index/updater.rs index c1e8736f7a..89906bf049 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -433,7 +433,7 @@ impl<'a> Updater<'a> { &mut satpoint_to_inscription_id, block.header.time, value_cache, - &self.cached_children_by_id, + self.cached_children_by_id, )?; if self.index_sats { diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 931361763e..d33535f707 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -322,11 +322,9 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { fn update_cached_children(&self, parent: InscriptionId, inscription_id: InscriptionId) { let mut cache = self.cached_children_by_id.lock().unwrap(); - match cache.get_mut(&parent) { - Some(children) => { - children.push(inscription_id); - } - None => {} // leave the cache empty, so we retrieve the full list of children when required + // only update the cache if it is already populated, so we retrieve the full list of children when required + if let Some(children) = cache.get_mut(&parent) { + children.push(inscription_id); } } } diff --git a/src/templates/inscription_children.rs b/src/templates/inscription_children.rs index 46c9c83768..c4d14fda5a 100644 --- a/src/templates/inscription_children.rs +++ b/src/templates/inscription_children.rs @@ -9,7 +9,7 @@ pub(crate) struct InscriptionChildrenHtml { impl PageContent for InscriptionChildrenHtml { fn title(&self) -> String { - format!("Children") + format!("Children of parent inscription {}", self.parent_number) } fn preview_image_url(&self) -> Option> { @@ -31,7 +31,9 @@ mod tests { }, "

Parent inscription 0

- +
+ +

Children

diff --git a/static/index.css b/static/index.css index 36a1551793..37e83b7875 100644 --- a/static/index.css +++ b/static/index.css @@ -204,6 +204,17 @@ a.mythic { width: 100%; } +.parent { + justify-content: center; +} + +.parent > a { + border-radius: 2%; + margin: 1%; + overflow: hidden; + width: 48%; +} + .inscription { display: flex; justify-content: center; diff --git a/templates/inscription-children.html b/templates/inscription-children.html index 5a0bb8f473..03b42ca82a 100644 --- a/templates/inscription-children.html +++ b/templates/inscription-children.html @@ -1,5 +1,7 @@

Parent inscription {{ self.parent_number }}

-{{Iframe::main(self.parent_id)}} +
+ {{Iframe::thumbnail(self.parent_id)}} +

Children

%% for id in &self.children { From 3d50effa52261d01ae55577267e9c668a13cd122 Mon Sep 17 00:00:00 2001 From: ordinally Date: Sun, 26 Mar 2023 21:53:12 -0700 Subject: [PATCH 46/54] Sort children by inscription number --- Cargo.lock | 16 ++++++++++++++++ Cargo.toml | 1 + src/index.rs | 18 +++++++++++------- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50cf4603db..e4fbd7eee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1093,6 +1093,12 @@ dependencies = [ "syn", ] +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -1723,6 +1729,15 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.5" @@ -2182,6 +2197,7 @@ dependencies = [ "http", "hyper", "indicatif", + "itertools", "lazy_static", "log", "mime", diff --git a/Cargo.toml b/Cargo.toml index 3064f5ed53..b78e6661d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ html-escaper = "0.2.0" http = "0.2.6" hyper = { version = "0.14.24", features = ["http1", "client"] } indicatif = "0.17.1" +itertools = "0.10.0" lazy_static = "1.4.0" log = "0.4.14" mime = "0.3.16" diff --git a/src/index.rs b/src/index.rs index d3a3947905..32062bd38a 100644 --- a/src/index.rs +++ b/src/index.rs @@ -12,6 +12,7 @@ use { bitcoincore_rpc::{json::GetBlockHeaderResult, Auth, Client}, chrono::SubsecRound, indicatif::{ProgressBar, ProgressStyle}, + itertools::Itertools, log::log_enabled, redb::{Database, ReadableTable, Table, TableDefinition, WriteStrategy, WriteTransaction}, std::collections::HashMap, @@ -570,29 +571,32 @@ impl Index { } } - let children = self + let sorted_children = self .database .begin_read()? .open_table(INSCRIPTION_ID_TO_INSCRIPTION_ENTRY)? .iter()? - .map(|(key, entry_value)| { + .filter_map(|(key, entry_value)| { let entry = InscriptionEntry::load(entry_value.value()); if entry.parent == Some(inscription_id) { - Ok(InscriptionId::load(*key.value())) + Some((InscriptionId::load(*key.value()), entry.number)) } else { - Err(()) + None } }) - .filter_map(Result::ok) + .collect::>() + .into_iter() + .sorted_by_key(|&(_id, number)| number) + .map(|(id, _)| id) .collect::>(); self .cached_children_by_id .lock() .unwrap() - .insert(inscription_id, children.clone()); + .insert(inscription_id, sorted_children.clone()); - Ok(children) + Ok(sorted_children) } pub(crate) fn get_inscriptions_on_output( From a3145343d660c1f42e89584aff362c915604b039 Mon Sep 17 00:00:00 2001 From: gmart7t2 <49558347+gmart7t2@users.noreply.github.com> Date: Mon, 27 Mar 2023 15:19:25 -0300 Subject: [PATCH 47/54] Label change and receive addresses correctly (#1847) Co-authored-by: Greg Martin Co-authored-by: raphjaph --- src/subcommand/wallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/subcommand/wallet.rs b/src/subcommand/wallet.rs index 0404f2ad24..bd15307786 100644 --- a/src/subcommand/wallet.rs +++ b/src/subcommand/wallet.rs @@ -136,7 +136,7 @@ fn derive_and_import_descriptor( active: Some(true), range: None, next_index: None, - internal: Some(!change), + internal: Some(change), label: None, })?; From 92157e53bdf6768610b2f08005877f2dde306726 Mon Sep 17 00:00:00 2001 From: gmart7t2 <49558347+gmart7t2@users.noreply.github.com> Date: Mon, 27 Mar 2023 15:29:58 -0300 Subject: [PATCH 48/54] Mandatory fee rate for inscribe (#1897) Co-authored-by: Greg Martin Co-authored-by: raphjaph --- docs/src/guides/inscriptions.md | 2 +- src/subcommand/wallet/inscribe.rs | 6 +-- tests/lib.rs | 2 +- tests/wallet/inscribe.rs | 72 +++++++++++++++++-------------- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/docs/src/guides/inscriptions.md b/docs/src/guides/inscriptions.md index c8824b5077..e9fe3e2993 100644 --- a/docs/src/guides/inscriptions.md +++ b/docs/src/guides/inscriptions.md @@ -166,7 +166,7 @@ Creating Inscriptions To create an inscription with the contents of `FILE`, run: ``` -ord wallet inscribe FILE +ord wallet inscribe --fee-rate FEE_RATE FILE ``` Ord will output two transactions IDs, one for the commit transaction, and one diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index d9b537f82d..1df5c15061 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -30,11 +30,7 @@ struct Output { pub(crate) struct Inscribe { #[clap(long, help = "Inscribe ")] pub(crate) satpoint: Option, - #[clap( - long, - default_value = "1.0", - help = "Use fee rate of sats/vB" - )] + #[clap(long, help = "Use fee rate of sats/vB")] pub(crate) fee_rate: FeeRate, #[clap( long, diff --git a/tests/lib.rs b/tests/lib.rs index 710e6b2a88..0639c15570 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -48,7 +48,7 @@ struct Inscribe { fn inscribe(rpc_server: &test_bitcoincore_rpc::Handle) -> Inscribe { rpc_server.mine_blocks(1); - let output = CommandBuilder::new("wallet inscribe foo.txt") + let output = CommandBuilder::new("wallet inscribe --fee-rate 1 foo.txt") .write("foo.txt", "FOO") .rpc_server(rpc_server) .output(); diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index 16b486037e..50ecca7dda 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -42,7 +42,7 @@ fn inscribe_works_with_huge_expensive_inscriptions() { fn inscribe_fails_if_bitcoin_core_is_too_old() { let rpc_server = test_bitcoincore_rpc::builder().version(230000).build(); - CommandBuilder::new("wallet inscribe hello.txt") + CommandBuilder::new("wallet inscribe hello.txt --fee-rate 1") .write("hello.txt", "HELLOWORLD") .expected_exit_code(1) .expected_stderr("error: Bitcoin Core 24.0.0 or newer required, current version is 23.0.0\n") @@ -58,7 +58,7 @@ fn inscribe_no_backup() { create_wallet(&rpc_server); assert_eq!(rpc_server.descriptors().len(), 2); - CommandBuilder::new("wallet inscribe hello.txt --no-backup") + CommandBuilder::new("wallet inscribe hello.txt --no-backup --fee-rate 1") .write("hello.txt", "HELLOWORLD") .rpc_server(&rpc_server) .output::(); @@ -72,7 +72,7 @@ fn inscribe_unknown_file_extension() { create_wallet(&rpc_server); rpc_server.mine_blocks(1); - CommandBuilder::new("wallet inscribe pepe.xyz") + CommandBuilder::new("wallet inscribe pepe.xyz --fee-rate 1") .write("pepe.xyz", [1; 520]) .rpc_server(&rpc_server) .expected_exit_code(1) @@ -88,7 +88,7 @@ fn inscribe_exceeds_chain_limit() { create_wallet(&rpc_server); rpc_server.mine_blocks(1); - CommandBuilder::new("--chain signet wallet inscribe degenerate.png") + CommandBuilder::new("--chain signet wallet inscribe degenerate.png --fee-rate 1") .write("degenerate.png", [1; 1025]) .rpc_server(&rpc_server) .expected_exit_code(1) @@ -106,7 +106,7 @@ fn regtest_has_no_content_size_limit() { create_wallet(&rpc_server); rpc_server.mine_blocks(1); - CommandBuilder::new("--chain regtest wallet inscribe degenerate.png") + CommandBuilder::new("--chain regtest wallet inscribe degenerate.png --fee-rate 1") .write("degenerate.png", [1; 1025]) .rpc_server(&rpc_server) .stdout_regex(".*") @@ -121,7 +121,7 @@ fn mainnet_has_no_content_size_limit() { create_wallet(&rpc_server); rpc_server.mine_blocks(1); - CommandBuilder::new("wallet inscribe degenerate.png") + CommandBuilder::new("wallet inscribe degenerate.png --fee-rate 1") .write("degenerate.png", [1; 1025]) .rpc_server(&rpc_server) .stdout_regex(".*") @@ -136,7 +136,7 @@ fn inscribe_does_not_use_inscribed_sats_as_cardinal_utxos() { rpc_server.mine_blocks_with_subsidy(1, 100); CommandBuilder::new( - "wallet inscribe degenerate.png" + "wallet inscribe degenerate.png --fee-rate 1" ) .rpc_server(&rpc_server) .write("degenerate.png", [1; 100]) @@ -156,12 +156,14 @@ fn refuse_to_reinscribe_sats() { rpc_server.mine_blocks_with_subsidy(1, 100); - CommandBuilder::new(format!("wallet inscribe --satpoint {reveal}:0:0 hello.txt")) - .write("hello.txt", "HELLOWORLD") - .rpc_server(&rpc_server) - .expected_exit_code(1) - .expected_stderr(format!("error: sat at {reveal}:0:0 already inscribed\n")) - .run(); + CommandBuilder::new(format!( + "wallet inscribe --satpoint {reveal}:0:0 hello.txt --fee-rate 1" + )) + .write("hello.txt", "HELLOWORLD") + .rpc_server(&rpc_server) + .expected_exit_code(1) + .expected_stderr(format!("error: sat at {reveal}:0:0 already inscribed\n")) + .run(); } #[test] @@ -181,7 +183,7 @@ fn refuse_to_inscribe_already_inscribed_utxo() { }; CommandBuilder::new(format!( - "wallet inscribe --satpoint {output}:55555 hello.txt" + "wallet inscribe --satpoint {output}:55555 hello.txt --fee-rate 1" )) .write("hello.txt", "HELLOWORLD") .rpc_server(&rpc_server) @@ -198,11 +200,12 @@ fn inscribe_with_optional_satpoint_arg() { create_wallet(&rpc_server); let txid = rpc_server.mine_blocks(1)[0].txdata[0].txid(); - let Inscribe { inscription, .. } = - CommandBuilder::new(format!("wallet inscribe foo.txt --satpoint {txid}:0:0")) - .write("foo.txt", "FOO") - .rpc_server(&rpc_server) - .output(); + let Inscribe { inscription, .. } = CommandBuilder::new(format!( + "wallet inscribe foo.txt --satpoint {txid}:0:0 --fee-rate 1" + )) + .write("foo.txt", "FOO") + .rpc_server(&rpc_server) + .output(); rpc_server.mine_blocks(1); @@ -262,10 +265,12 @@ fn inscribe_with_commit_fee_rate() { create_wallet(&rpc_server); rpc_server.mine_blocks(1); - CommandBuilder::new("--index-sats wallet inscribe degenerate.png --commit-fee-rate 2.0") - .write("degenerate.png", [1; 520]) - .rpc_server(&rpc_server) - .output::(); + CommandBuilder::new( + "--index-sats wallet inscribe degenerate.png --commit-fee-rate 2.0 --fee-rate 1", + ) + .write("degenerate.png", [1; 520]) + .rpc_server(&rpc_server) + .output::(); let tx1 = &rpc_server.mempool()[0]; let mut fee = 0; @@ -307,7 +312,7 @@ fn inscribe_with_wallet_named_foo() { rpc_server.mine_blocks(1); - CommandBuilder::new("--wallet foo wallet inscribe degenerate.png") + CommandBuilder::new("--wallet foo wallet inscribe degenerate.png --fee-rate 1") .write("degenerate.png", [1; 520]) .rpc_server(&rpc_server) .output::(); @@ -319,14 +324,14 @@ fn inscribe_with_dry_run_flag() { create_wallet(&rpc_server); rpc_server.mine_blocks(1); - CommandBuilder::new("wallet inscribe --dry-run degenerate.png") + CommandBuilder::new("wallet inscribe --dry-run degenerate.png --fee-rate 1") .write("degenerate.png", [1; 520]) .rpc_server(&rpc_server) .output::(); assert!(rpc_server.mempool().is_empty()); - CommandBuilder::new("wallet inscribe degenerate.png") + CommandBuilder::new("wallet inscribe degenerate.png --fee-rate 1") .write("degenerate.png", [1; 520]) .rpc_server(&rpc_server) .output::(); @@ -340,11 +345,12 @@ fn inscribe_with_dry_run_flag_fees_inscrease() { create_wallet(&rpc_server); rpc_server.mine_blocks(1); - let total_fee_dry_run = CommandBuilder::new("wallet inscribe --dry-run degenerate.png") - .write("degenerate.png", [1; 520]) - .rpc_server(&rpc_server) - .output::() - .fees; + let total_fee_dry_run = + CommandBuilder::new("wallet inscribe --dry-run degenerate.png --fee-rate 1") + .write("degenerate.png", [1; 520]) + .rpc_server(&rpc_server) + .output::() + .fees; let total_fee_normal = CommandBuilder::new("wallet inscribe --dry-run degenerate.png --fee-rate 1.1") @@ -368,7 +374,7 @@ fn inscribe_to_specific_destination() { .address; let txid = CommandBuilder::new(format!( - "wallet inscribe --destination {destination} degenerate.png" + "wallet inscribe --destination {destination} degenerate.png --fee-rate 1" )) .write("degenerate.png", [1; 520]) .rpc_server(&rpc_server) @@ -390,7 +396,7 @@ fn inscribe_with_no_limit() { rpc_server.mine_blocks(1); let four_megger = std::iter::repeat(0).take(4_000_000).collect::>(); - CommandBuilder::new("wallet inscribe --no-limit degenerate.png") + CommandBuilder::new("wallet inscribe --no-limit degenerate.png --fee-rate 1") .write("degenerate.png", four_megger) .rpc_server(&rpc_server); } From ab2f178eb1941f29e99f9c0ba8e5340b73bb8363 Mon Sep 17 00:00:00 2001 From: gmart7t2 <49558347+gmart7t2@users.noreply.github.com> Date: Mon, 27 Mar 2023 17:08:26 -0300 Subject: [PATCH 49/54] Add `ord wallet cardinals` command to list the cardinal outputs (#1904) Co-authored-by: Greg Martin Co-authored-by: raphjaph --- src/subcommand/wallet.rs | 6 ++++- src/subcommand/wallet/cardinals.rs | 37 ++++++++++++++++++++++++++++++ tests/wallet.rs | 1 + tests/wallet/cardinals.rs | 23 +++++++++++++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/subcommand/wallet/cardinals.rs create mode 100644 tests/wallet/cardinals.rs diff --git a/src/subcommand/wallet.rs b/src/subcommand/wallet.rs index bd15307786..914805d5af 100644 --- a/src/subcommand/wallet.rs +++ b/src/subcommand/wallet.rs @@ -15,6 +15,7 @@ use { }; pub mod balance; +pub mod cardinals; pub mod create; pub(crate) mod inscribe; pub mod inscriptions; @@ -46,8 +47,10 @@ pub(crate) enum Wallet { Send(send::Send), #[clap(about = "See wallet transactions")] Transactions(transactions::Transactions), - #[clap(about = "List wallet outputs")] + #[clap(about = "List all unspent outputs in wallet")] Outputs, + #[clap(about = "List unspent cardinal outputs in wallet")] + Cardinals, } impl Wallet { @@ -63,6 +66,7 @@ impl Wallet { Self::Send(send) => send.run(options), Self::Transactions(transactions) => transactions.run(options), Self::Outputs => outputs::run(options), + Self::Cardinals => cardinals::run(options), } } } diff --git a/src/subcommand/wallet/cardinals.rs b/src/subcommand/wallet/cardinals.rs new file mode 100644 index 0000000000..32076229a1 --- /dev/null +++ b/src/subcommand/wallet/cardinals.rs @@ -0,0 +1,37 @@ +use {super::*, crate::wallet::Wallet, std::collections::BTreeSet}; + +#[derive(Serialize, Deserialize)] +pub struct Cardinal { + pub output: OutPoint, + pub amount: u64, +} + +pub(crate) fn run(options: Options) -> Result { + let index = Index::open(&options)?; + index.update()?; + + let inscribed_utxos = index + .get_inscriptions(None)? + .keys() + .map(|satpoint| satpoint.outpoint) + .collect::>(); + + let cardinal_utxos = index + .get_unspent_outputs(Wallet::load(&options)?)? + .iter() + .filter_map(|(output, amount)| { + if inscribed_utxos.contains(output) { + None + } else { + Some(Cardinal { + output: *output, + amount: amount.to_sat(), + }) + } + }) + .collect::>(); + + print_json(cardinal_utxos)?; + + Ok(()) +} diff --git a/tests/wallet.rs b/tests/wallet.rs index 31dc1d96a0..3e04064183 100644 --- a/tests/wallet.rs +++ b/tests/wallet.rs @@ -1,6 +1,7 @@ use super::*; mod balance; +mod cardinals; mod create; mod inscribe; mod inscriptions; diff --git a/tests/wallet/cardinals.rs b/tests/wallet/cardinals.rs new file mode 100644 index 0000000000..19053f4389 --- /dev/null +++ b/tests/wallet/cardinals.rs @@ -0,0 +1,23 @@ +use { + super::*, + ord::subcommand::wallet::{cardinals::Cardinal, outputs::Output}, +}; + +#[test] +fn cardinals() { + let rpc_server = test_bitcoincore_rpc::spawn(); + create_wallet(&rpc_server); + + // this creates 2 more cardinal outputs and one inscribed output + inscribe(&rpc_server); + + let all_outputs = CommandBuilder::new("wallet outputs") + .rpc_server(&rpc_server) + .output::>(); + + let cardinal_outputs = CommandBuilder::new("wallet cardinals") + .rpc_server(&rpc_server) + .output::>(); + + assert_eq!(all_outputs.len() - cardinal_outputs.len(), 1); +} From a88e67cb6dbdfa21109784700853718390e5c95e Mon Sep 17 00:00:00 2001 From: raphjaph Date: Mon, 27 Mar 2023 15:59:02 -0700 Subject: [PATCH 50/54] fix tests --- tests/core.rs | 7 +------ tests/wallet/inscribe.rs | 30 +++++++++++++++++------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/core.rs b/tests/core.rs index aad327712c..526def9941 100644 --- a/tests/core.rs +++ b/tests/core.rs @@ -170,11 +170,6 @@ fn inscribe_child() { 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()), @@ -206,7 +201,7 @@ fn inscribe_child() { &cookiefile, &ord_data_dir, rpc_port, - &["wallet", "inscribe", "parent.txt"], + &["wallet", "inscribe", "--fee-rate", "1.0", "parent.txt"], ) { Ok(s) => serde_json::from_str(&s) .unwrap_or_else(|err| panic!("Failed to deserialize JSON: {err}\n{s}")), diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index 84c0c69c42..505f6d83ff 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -408,7 +408,7 @@ fn inscribe_with_parent_inscription() { create_wallet(&rpc_server); rpc_server.mine_blocks(1); - let parent_id = CommandBuilder::new("wallet inscribe parent.png") + let parent_id = CommandBuilder::new("wallet inscribe --fee-rate 1.0 parent.png") .write("parent.png", [1; 520]) .rpc_server(&rpc_server) .output::() @@ -419,10 +419,12 @@ fn inscribe_with_parent_inscription() { TestServer::spawn_with_args(&rpc_server, &[]) .assert_response_regex(format!("/inscription/{parent_id}"), ".*"); - let child_output = CommandBuilder::new(format!("wallet inscribe --parent {parent_id} child.png")) - .write("child.png", [1; 520]) - .rpc_server(&rpc_server) - .output::(); + let child_output = CommandBuilder::new(format!( + "wallet inscribe --fee-rate 1.0 --parent {parent_id} child.png" + )) + .write("child.png", [1; 520]) + .rpc_server(&rpc_server) + .output::(); assert_eq!(parent_id, child_output.parent.unwrap()); @@ -442,14 +444,16 @@ fn inscribe_with_non_existent_parent_inscription() { 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(); + CommandBuilder::new(format!( + "wallet inscribe --fee-rate 1.0 --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(); } #[test] From ca980a581fd79e24ca877684d857dc7ad81c4787 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Mon, 27 Mar 2023 16:51:31 -0700 Subject: [PATCH 51/54] correct test --- src/templates/inscription.rs | 12 +++++++----- templates/inscription.html | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/templates/inscription.rs b/src/templates/inscription.rs index ef3a563dcf..e6de3cca1f 100644 --- a/src/templates/inscription.rs +++ b/src/templates/inscription.rs @@ -180,12 +180,14 @@ mod tests { }, "

Inscription 1

-
- - - -
.* +
+ .* +
children
+
5{64}i5
+
6{64}i6
+ .* +
" .unindent() ); diff --git a/templates/inscription.html b/templates/inscription.html index 420f82bbaa..a6e8e49823 100644 --- a/templates/inscription.html +++ b/templates/inscription.html @@ -20,9 +20,9 @@

Inscription {{ self.number }}

{{parent}}
%% } %% if !self.children.is_empty() { -
children
+
children
%% for (i, child) in self.children.iter().enumerate() { -
{{child}}
+
{{child}}
%% } %% } %% if let Ok(address) = self.chain.address_from_script(&self.output.script_pubkey ) { From 084299465c3d47f56e1e3e070241aa6b3d746d73 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Thu, 30 Mar 2023 17:02:49 -0700 Subject: [PATCH 52/54] see preview of children --- templates/inscription.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/templates/inscription.html b/templates/inscription.html index a6e8e49823..7a11f44cf7 100644 --- a/templates/inscription.html +++ b/templates/inscription.html @@ -21,9 +21,13 @@

Inscription {{ self.number }}

%% } %% if !self.children.is_empty() {
children
+
+
%% for (i, child) in self.children.iter().enumerate() { -
{{child}}
+ {{ Iframe::thumbnail(*child) }} %% } +
+ %% } %% if let Ok(address) = self.chain.address_from_script(&self.output.script_pubkey ) {
address
From 7b19a6da4ee9e641e510f3b5d94bb3921582f267 Mon Sep 17 00:00:00 2001 From: ericatallah Date: Thu, 23 Mar 2023 18:03:57 -0700 Subject: [PATCH 53/54] Why: Some generative art collections have inscribed all art within a single parent inscription to save considerable storage space on the blockchain. The ord explorer is unable to currently support collections inscribed in this manner. This commit adds this support. What: In order to support these generative art collections, we need to be able to inscribe the metadata for children in an agreed upon JSON spec. These child inscriptions are JSON documents containing url parameters that instruct the parent inscription to render a specific item in the collection. How: Since all art is contained in the parent, we added a simple redirect to render the parent inscription with included url parameters. There are 3 constraints which must be in the child inscription before the redirect can happen: The child inscription must have a parent inscription. The child inscription's body data must be valid JSON. The child inscription's body JSON data must contain a field named "use_p", with any value. Proposed JSON standard: ``` { "use_p": 1, "params": ["tokenID=4969"] } ``` Co-authored-by: clrke --- src/subcommand/server.rs | 260 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index b202b82c00..d9d7468261 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1,3 +1,5 @@ +use serde_json::Value; + use { self::{ deserialize_from_str::DeserializeFromStr, @@ -721,6 +723,69 @@ impl Server { Redirect::to("https://docs.ordinals.com/bounty/") } + fn get_parent_url_params_if_child_pointer(inscription: &Inscription) -> Option { + if !inscription.get_parent_id().is_some() { + return None; + } + + if !Self::valid_json(inscription.body()) { + return None; + } + + let json_result: Result = serde_json::from_slice(&inscription.body().unwrap()); + let json: Value = json_result.unwrap(); + + if !json.as_object().unwrap().contains_key("use_p") { + return None; + } + + let parent_url_params = Self::get_url_params_from_json_value(json); + + Some(parent_url_params) + } + + fn valid_json(data: Option<&[u8]>) -> bool { + match data { + Some(bytes) => serde_json::from_slice::(bytes).is_ok(), + None => false, + } + } + + fn get_url_params_from_json_value(json: Value) -> String { + let mut params_str = String::new(); + + if let Some(url_params_field) = json.get("params") { + if let Some(url_params) = url_params_field.as_array() { + let param_strs: Vec<&str> = url_params.into_iter().map(|v| v.as_str().unwrap()).collect(); + params_str = param_strs.join("&"); + } + } + + if params_str.is_empty() { + return params_str; + } + + format!("?{params_str}") + } + + fn get_content_response_if_child_pointer(inscription: &Inscription) -> Option> { + let parent_url_params = Self::get_parent_url_params_if_child_pointer(inscription); + if let Some(url_params) = parent_url_params { + let redirect_uri = format!("/content/{}{}", inscription.get_parent_id().unwrap(), url_params); + return Some(Ok(Redirect::permanent(&redirect_uri).into_response())); + } + None + } + + fn get_preview_response_if_child_pointer(inscription: &Inscription) -> Option> { + let parent_url_params = Self::get_parent_url_params_if_child_pointer(inscription); + if let Some(url_params) = parent_url_params { + let redirect_uri = format!("/preview/{}{}", inscription.get_parent_id().unwrap(), url_params); + return Some(Ok(Redirect::permanent(&redirect_uri).into_response())); + } + None + } + async fn content( Extension(index): Extension>, Extension(config): Extension>, @@ -734,6 +799,10 @@ impl Server { .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; + if let Some(response) = Self::get_content_response_if_child_pointer(&inscription) { + return response; + } + Ok( Self::content_response(inscription) .ok_or_not_found(|| format!("inscription {inscription_id} content"))? @@ -777,6 +846,10 @@ impl Server { .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; + if let Some(response) = Self::get_preview_response_if_child_pointer(&inscription) { + return response; + } + match inscription.media() { Media::Audio => Ok(PreviewAudioHtml { inscription_id }.into_response()), Media::Iframe => Ok( @@ -983,6 +1056,12 @@ mod tests { Self::new_server(test_bitcoincore_rpc::spawn(), None, ord_args, server_args) } + fn new_with_bitcoin_rpc_server( + bitcoin_rpc_server: test_bitcoincore_rpc::Handle, + ) -> Self { + Self::new_server(bitcoin_rpc_server, None, &[], &[]) + } + fn new_with_bitcoin_rpc_server_and_config( bitcoin_rpc_server: test_bitcoincore_rpc::Handle, config: String, @@ -1129,6 +1208,19 @@ mod tests { assert_eq!(response.headers().get(header::LOCATION).unwrap(), location); } + fn assert_redirect_permanent(&self, path: &str, location: &str) { + let response = reqwest::blocking::Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .unwrap() + .get(self.join_url(path)) + .send() + .unwrap(); + + assert_eq!(response.status(), StatusCode::PERMANENT_REDIRECT); + assert_eq!(response.headers().get(header::LOCATION).unwrap(), location); + } + fn mine_blocks(&self, n: u64) -> Vec { let blocks = self.bitcoin_rpc_server.mine_blocks(n); self.index.update().unwrap(); @@ -2528,4 +2620,172 @@ mod tests { &fs::read_to_string("templates/preview-unknown.html").unwrap(), ); } + + #[test] + fn ord_pointer_child_inscription_redirect_to_parent_with_url_params() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + bitcoin_rpc_server.mine_blocks(1); + let parent_txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0)], + witnesses: vec![inscription("text/plain;charset=utf-8", "hello").to_witness()], + ..Default::default() + }); + let parent_inscription = InscriptionId::from(parent_txid); + + bitcoin_rpc_server.mine_blocks(1); + + let child_txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 1, 0), (2, 0, 0)], + witnesses: vec![ + Witness::new(), + inscription_with_parent( + "application/json", + "{\"use_p\":1,\"params\":[\"tokenID=69\"]}", + parent_inscription + ).to_witness() + ], + ..Default::default() + }); + let child_inscription = InscriptionId { + txid: child_txid, + index: 0, + }; + + bitcoin_rpc_server.mine_blocks(1); + + let server = TestServer::new_with_bitcoin_rpc_server(bitcoin_rpc_server); + + server.assert_redirect_permanent( + &format!("/preview/{child_inscription}"), + &format!("/preview/{parent_inscription}?tokenID=69") + ); + server.assert_redirect_permanent( + &format!("/content/{child_inscription}"), + &format!("/content/{parent_inscription}?tokenID=69") + ); + } + + #[test] + fn ord_pointer_child_inscription_redirect_to_parent_without_url_params() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + bitcoin_rpc_server.mine_blocks(1); + let parent_txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0)], + witnesses: vec![inscription("text/plain;charset=utf-8", "hello").to_witness()], + ..Default::default() + }); + let parent_inscription = InscriptionId::from(parent_txid); + + bitcoin_rpc_server.mine_blocks(1); + + let child_txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 1, 0), (2, 0, 0)], + witnesses: vec![ + Witness::new(), + inscription_with_parent( + "application/json", + "{\"use_p\":1}", + parent_inscription + ).to_witness() + ], + ..Default::default() + }); + let child_inscription = InscriptionId { + txid: child_txid, + index: 0, + }; + + bitcoin_rpc_server.mine_blocks(1); + + let server = TestServer::new_with_bitcoin_rpc_server(bitcoin_rpc_server); + + server.assert_redirect_permanent( + &format!("/preview/{child_inscription}"), + &format!("/preview/{parent_inscription}") + ); + server.assert_redirect_permanent( + &format!("/content/{child_inscription}"), + &format!("/content/{parent_inscription}") + ); + } + + #[test] + fn non_pointer_json_child_inscription_does_not_redirect_to_parent() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + bitcoin_rpc_server.mine_blocks(1); + let parent_txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0)], + witnesses: vec![inscription("text/plain;charset=utf-8", "hello").to_witness()], + ..Default::default() + }); + let parent_inscription = InscriptionId::from(parent_txid); + + bitcoin_rpc_server.mine_blocks(1); + + let child_txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 1, 0), (2, 0, 0)], + witnesses: vec![ + Witness::new(), + inscription_with_parent( + "application/json", + "{\"not_ord_pointer\":1}", + parent_inscription + ).to_witness() + ], + ..Default::default() + }); + let child_inscription = InscriptionId { + txid: child_txid, + index: 0, + }; + + bitcoin_rpc_server.mine_blocks(1); + + let server = TestServer::new_with_bitcoin_rpc_server(bitcoin_rpc_server); + + server.assert_response_csp( + format!("/preview/{}", child_inscription), + StatusCode::OK, + "default-src 'self'", + ".*
\\{"not_ord_pointer":1\\}
.*", + ); + } + + #[test] + fn non_pointer_child_inscription_does_not_redirect_to_parent() { + let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); + bitcoin_rpc_server.mine_blocks(1); + let parent_txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0)], + witnesses: vec![inscription("text/plain;charset=utf-8", "hello").to_witness()], + ..Default::default() + }); + let parent_inscription = InscriptionId::from(parent_txid); + + bitcoin_rpc_server.mine_blocks(1); + + let child_txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 1, 0), (2, 0, 0)], + witnesses: vec![ + Witness::new(), + inscription_with_parent("text/plain;charset=utf-8", "child", parent_inscription).to_witness(), + ], + ..Default::default() + }); + let child_inscription = InscriptionId { + txid: child_txid, + index: 0, + }; + + bitcoin_rpc_server.mine_blocks(1); + + let server = TestServer::new_with_bitcoin_rpc_server(bitcoin_rpc_server); + + server.assert_response_csp( + format!("/preview/{}", child_inscription), + StatusCode::OK, + "default-src 'self'", + ".*
child
.*", + ); + } } From 07806c40016161242d73c204e13940665613787e Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 10 Apr 2023 13:50:44 -0700 Subject: [PATCH 54/54] default server to localhost --- src/subcommand/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index d9d7468261..e0b3ab9382 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -94,7 +94,7 @@ impl Display for StaticHtml { pub(crate) struct Server { #[clap( long, - default_value = "0.0.0.0", + default_value = "127.0.0.1", help = "Listen on
for incoming requests." )] address: String,