From 5311e24085fb368c766bfac55bd5201c2d47168c Mon Sep 17 00:00:00 2001 From: raph Date: Wed, 17 Apr 2024 22:15:34 +0200 Subject: [PATCH 01/14] Resume cycles through all pending etchings (#3566) --- src/subcommand/wallet/resume.rs | 29 +++++++---- src/wallet.rs | 82 ++++++++++++++++++-------------- src/wallet/batch/plan.rs | 12 +++-- src/wallet/wallet_constructor.rs | 20 ++++---- tests/lib.rs | 2 +- tests/wallet/batch_command.rs | 2 +- tests/wallet/resume.rs | 44 +++-------------- 7 files changed, 97 insertions(+), 94 deletions(-) diff --git a/src/subcommand/wallet/resume.rs b/src/subcommand/wallet/resume.rs index d844591b50..3f235d0421 100644 --- a/src/subcommand/wallet/resume.rs +++ b/src/subcommand/wallet/resume.rs @@ -6,13 +6,26 @@ pub struct ResumeOutput { } pub(crate) fn run(wallet: Wallet) -> SubcommandResult { - let outputs: Result> = wallet - .pending_etchings()? - .into_iter() - .map(|(rune, entry)| { - wallet.wait_for_maturation(&rune, entry.commit, entry.reveal, entry.output) - }) - .collect(); + let mut etchings = Vec::new(); + loop { + if SHUTTING_DOWN.load(atomic::Ordering::Relaxed) { + break; + } - outputs.map(|etchings| Some(Box::new(ResumeOutput { etchings }) as Box)) + for (rune, entry) in wallet.pending_etchings()? { + if wallet.is_mature(&entry.commit)? { + etchings.push(wallet.send_etching(rune, &entry)?); + } + } + + if wallet.pending_etchings()?.is_empty() { + break; + } + + if !wallet.integration_test() { + thread::sleep(Duration::from_secs(5)); + } + } + + Ok(Some(Box::new(ResumeOutput { etchings }) as Box)) } diff --git a/src/wallet.rs b/src/wallet.rs index 121f6089dd..9b066d68cc 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -296,44 +296,52 @@ impl Wallet { self.settings.integration_test() } - pub(crate) fn wait_for_maturation( - &self, - rune: &Rune, - commit: Transaction, - reveal: Transaction, - output: batch::Output, - ) -> Result { - eprintln!("Waiting for rune commitment {} to mature…", commit.txid()); + pub(crate) fn is_mature(&self, commit: &Transaction) -> Result { + let transaction = self + .bitcoin_client() + .get_transaction(&commit.txid(), Some(true)) + .into_option()?; + + if let Some(transaction) = transaction { + if u32::try_from(transaction.info.confirmations).unwrap() + 1 + >= Runestone::COMMIT_CONFIRMATIONS.into() + { + let tx_out = self + .bitcoin_client() + .get_tx_out(&commit.txid(), 0, Some(true))?; + + if let Some(tx_out) = tx_out { + if tx_out.confirmations + 1 >= Runestone::COMMIT_CONFIRMATIONS.into() { + return Ok(true); + } + } else { + bail!("rune commitment spent, can't send reveal tx"); + } + } + } - self.save_etching(rune, &commit, &reveal, output.clone())?; + Ok(false) + } + + pub(crate) fn wait_for_maturation(&self, rune: Rune) -> Result { + let Some(entry) = self.load_etching(rune)? else { + bail!("no etching found"); + }; + + eprintln!( + "Waiting for rune {} commitment {} to mature…", + rune, + entry.commit.txid() + ); loop { if SHUTTING_DOWN.load(atomic::Ordering::Relaxed) { eprintln!("Suspending batch. Run `ord wallet resume` to continue."); - return Ok(output); + return Ok(entry.output); } - let transaction = self - .bitcoin_client() - .get_transaction(&commit.txid(), Some(true)) - .into_option()?; - - if let Some(transaction) = transaction { - if u32::try_from(transaction.info.confirmations).unwrap() + 1 - >= Runestone::COMMIT_CONFIRMATIONS.into() - { - let tx_out = self - .bitcoin_client() - .get_tx_out(&commit.txid(), 0, Some(true))?; - - if let Some(tx_out) = tx_out { - if tx_out.confirmations + 1 >= Runestone::COMMIT_CONFIRMATIONS.into() { - break; - } - } else { - bail!("rune commitment spent, can't send reveal tx"); - } - } + if self.is_mature(&entry.commit)? { + break; } if !self.integration_test() { @@ -341,12 +349,16 @@ impl Wallet { } } - match self.bitcoin_client().send_raw_transaction(&reveal) { + self.send_etching(rune, &entry) + } + + pub(crate) fn send_etching(&self, rune: Rune, entry: &EtchingEntry) -> Result { + match self.bitcoin_client().send_raw_transaction(&entry.reveal) { Ok(txid) => txid, Err(err) => { return Err(anyhow!( "Failed to send reveal transaction: {err}\nCommit tx {} will be recovered once mined", - commit.txid() + entry.commit.txid() )) } }; @@ -355,7 +367,7 @@ impl Wallet { Ok(batch::Output { reveal_broadcast: true, - ..output + ..entry.output.clone() }) } @@ -646,7 +658,7 @@ impl Wallet { ) } - pub(crate) fn clear_etching(&self, rune: &Rune) -> Result { + pub(crate) fn clear_etching(&self, rune: Rune) -> Result { let wtx = self.database.begin_write()?; wtx.open_table(RUNE_TO_ETCHING)?.remove(rune.0)?; diff --git a/src/wallet/batch/plan.rs b/src/wallet/batch/plan.rs index 26244fe31f..704edc1ccd 100644 --- a/src/wallet/batch/plan.rs +++ b/src/wallet/batch/plan.rs @@ -137,10 +137,10 @@ impl Plan { let commit = consensus::encode::deserialize::(&signed_commit_tx)?; let reveal = consensus::encode::deserialize::(&signed_reveal_tx)?; - Ok(Some(Box::new(wallet.wait_for_maturation( + wallet.save_etching( &rune_info.rune.rune, - commit.clone(), - reveal.clone(), + &commit, + &reveal, self.output( commit.txid(), None, @@ -151,7 +151,11 @@ impl Plan { self.inscriptions.clone(), rune.clone(), ), - )?))) + )?; + + Ok(Some(Box::new( + wallet.wait_for_maturation(rune_info.rune.rune)?, + ))) } else { let reveal = match wallet .bitcoin_client() diff --git a/src/wallet/wallet_constructor.rs b/src/wallet/wallet_constructor.rs index 1db7ee5c6b..ffa4332407 100644 --- a/src/wallet/wallet_constructor.rs +++ b/src/wallet/wallet_constructor.rs @@ -190,14 +190,18 @@ impl WalletConstructor { let mut utxos = BTreeMap::new(); for outpoint in outpoints { - let txout = bitcoin_client - .get_raw_transaction(&outpoint.txid, None)? - .output - .get(TryInto::::try_into(outpoint.vout).unwrap()) - .cloned() - .ok_or_else(|| anyhow!("Invalid output index"))?; - - utxos.insert(OutPoint::new(outpoint.txid, outpoint.vout), txout); + let Some(tx_out) = bitcoin_client.get_tx_out(&outpoint.txid, outpoint.vout, Some(false))? + else { + continue; + }; + + utxos.insert( + OutPoint::new(outpoint.txid, outpoint.vout), + TxOut { + value: tx_out.value.to_sat(), + script_pubkey: ScriptBuf::from_bytes(tx_out.script_pub_key.hex), + }, + ); } Ok(utxos) diff --git a/tests/lib.rs b/tests/lib.rs index ffde8862e0..99727d9b3d 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -200,7 +200,7 @@ fn batch(core: &mockcore::Handle, ord: &TestServer, batchfile: batch::File) -> E assert_regex_match!( buffer, - "Waiting for rune commitment [[:xdigit:]]{64} to mature…\n" + "Waiting for rune .* commitment [[:xdigit:]]{64} to mature…\n" ); core.mine_blocks(5); diff --git a/tests/wallet/batch_command.rs b/tests/wallet/batch_command.rs index 2197eec2d8..3a91f9b423 100644 --- a/tests/wallet/batch_command.rs +++ b/tests/wallet/batch_command.rs @@ -2664,7 +2664,7 @@ fn batch_inscribe_errors_if_pending_etchings() { assert_regex_match!( buffer, - "Waiting for rune commitment [[:xdigit:]]{64} to mature…\n" + "Waiting for rune .* commitment [[:xdigit:]]{64} to mature…\n" ); core.mine_blocks(1); diff --git a/tests/wallet/resume.rs b/tests/wallet/resume.rs index 54299aac8f..3658b789e8 100644 --- a/tests/wallet/resume.rs +++ b/tests/wallet/resume.rs @@ -56,7 +56,7 @@ fn wallet_resume() { assert_regex_match!( buffer, - "Waiting for rune commitment [[:xdigit:]]{64} to mature…\n" + "Waiting for rune AAAAAAAAAAAAA commitment [[:xdigit:]]{64} to mature…\n" ); core.mine_blocks(1); @@ -83,23 +83,11 @@ fn wallet_resume() { core.mine_blocks(6); - let mut spawn = CommandBuilder::new("--regtest --index-runes wallet resume") + let output = CommandBuilder::new("--regtest --index-runes wallet resume") .temp_dir(tempdir) .core(&core) .ord(&ord) - .spawn(); - - let mut buffer = String::new(); - let mut reader = BufReader::new(spawn.child.stderr.as_mut().unwrap()); - - reader.read_line(&mut buffer).unwrap(); - - assert_regex_match!( - buffer, - "Waiting for rune commitment [[:xdigit:]]{64} to mature…\n" - ); - - let output = spawn.run_and_deserialize_output::(); + .run_and_deserialize_output::(); assert_eq!( output @@ -167,7 +155,7 @@ fn resume_suspended() { assert_regex_match!( buffer, - "Waiting for rune commitment [[:xdigit:]]{64} to mature…\n" + "Waiting for rune AAAAAAAAAAAAA commitment [[:xdigit:]]{64} to mature…\n" ); core.mine_blocks(1); @@ -198,15 +186,7 @@ fn resume_suspended() { .ord(&ord) .spawn(); - let mut buffer = String::new(); - - BufReader::new(spawn.child.stderr.as_mut().unwrap()) - .read_line(&mut buffer) - .unwrap(); - - assert_regex_match!(buffer, "Waiting for rune commitment .* to mature…\n"); - - buffer.clear(); + thread::sleep(Duration::from_secs(1)); signal::kill( Pid::from_raw(spawn.child.id().try_into().unwrap()), @@ -223,17 +203,7 @@ fn resume_suspended() { "Shutting down gracefully. Press again to shutdown immediately.\n" ); - buffer.clear(); - reader.read_line(&mut buffer).unwrap(); - - assert_eq!( - buffer, - "Suspending batch. Run `ord wallet resume` to continue.\n" - ); - - let output = spawn.run_and_deserialize_output::(); - - assert!(!output.etchings.first().unwrap().reveal_broadcast); + spawn.child.wait().unwrap(); } #[test] @@ -284,7 +254,7 @@ fn commitment_output_is_locked() { assert_regex_match!( buffer, - "Waiting for rune commitment [[:xdigit:]]{64} to mature…\n" + "Waiting for rune AAAAAAAAAAAAA commitment [[:xdigit:]]{64} to mature…\n" ); let commitment = core.mempool()[0].txid(); From e247b69adb0f0158e20df7dec0bf78e6966c417b Mon Sep 17 00:00:00 2001 From: twosatsmaxi <112330467+twosatsmaxi@users.noreply.github.com> Date: Thu, 18 Apr 2024 02:20:56 +0530 Subject: [PATCH 02/14] Generate sample batch.yaml in env command (#3530) --- src/subcommand/env.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/subcommand/env.rs b/src/subcommand/env.rs index 53f4c1863f..b107f14084 100644 --- a/src/subcommand/env.rs +++ b/src/subcommand/env.rs @@ -1,4 +1,4 @@ -use {super::*, colored::Colorize, std::net::TcpListener}; +use {super::*, crate::wallet::batch, colored::Colorize, std::net::TcpListener}; struct KillOnDrop(process::Child); @@ -64,6 +64,32 @@ rpcport={bitcoind_port} ), )?; + fs::write(absolute.join("inscription.txt"), "FOO")?; + + let yaml = serde_yaml::to_string(&batch::File { + etching: Some(batch::Etching { + divisibility: 0, + rune: "FOO".parse::().unwrap(), + supply: "2000".parse().unwrap(), + premine: "1000".parse().unwrap(), + symbol: '¢', + terms: Some(batch::Terms { + amount: "1000".parse().unwrap(), + cap: 1, + ..default() + }), + turbo: false, + }), + inscriptions: vec![batch::Entry { + file: Some("env/inscription.txt".into()), + ..default() + }], + ..default() + }) + .unwrap(); + + fs::write(absolute.join("batch.yaml"), yaml)?; + let _bitcoind = KillOnDrop( Command::new("bitcoind") .arg(format!("-conf={}", absolute.join("bitcoin.conf").display())) From 3d969a600f88a5890504bd11d7e166c4de726348 Mon Sep 17 00:00:00 2001 From: Javier Villanueva Date: Wed, 17 Apr 2024 23:25:30 +0100 Subject: [PATCH 03/14] Add default content proxy and decompress to env command (#3509) --- src/subcommand/env.rs | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/subcommand/env.rs b/src/subcommand/env.rs index b107f14084..39e9cb8da9 100644 --- a/src/subcommand/env.rs +++ b/src/subcommand/env.rs @@ -17,6 +17,16 @@ impl Drop for KillOnDrop { pub(crate) struct Env { #[arg(default_value = "env", help = "Create env in .")] directory: PathBuf, + #[arg( + long, + help = "Decompress encoded content. Currently only supports brotli. Be careful using this on production instances. A decompressed inscription may be arbitrarily large, making decompression a DoS vector." + )] + pub(crate) decompress: bool, + #[arg( + long, + help = "Proxy `/content/INSCRIPTION_ID` requests to `/content/INSCRIPTION_ID` if the inscription is not present on current chain." + )] + pub(crate) content_proxy: Option, } #[derive(Serialize)] @@ -117,16 +127,27 @@ rpcport={bitcoind_port} let ord = std::env::current_exe()?; - let _ord = KillOnDrop( - Command::new(&ord) - .arg("--datadir") - .arg(&absolute) - .arg("server") - .arg("--polling-interval=100ms") - .arg("--http-port") - .arg(ord_port.to_string()) - .spawn()?, - ); + let decompress = self.decompress; + let content_proxy = self.content_proxy.map(|url| url.to_string()); + + let mut command = Command::new(&ord); + let ord_server = command + .arg("--datadir") + .arg(&absolute) + .arg("server") + .arg("--polling-interval=100ms") + .arg("--http-port") + .arg(ord_port.to_string()); + + if decompress { + ord_server.arg("--decompress"); + } + + if let Some(content_proxy) = content_proxy { + ord_server.arg("--content-proxy").arg(content_proxy); + } + + let _ord = KillOnDrop(ord_server.spawn()?); thread::sleep(Duration::from_millis(250)); From 879cee6559718f7bd683194ac17daf00367ae3cb Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Thu, 18 Apr 2024 14:28:31 -0700 Subject: [PATCH 04/14] Address runes review comments (#3605) - Use inclusive range in Index::get_runes_in_block - Remove out-of-date comment - Use checked_pow in Decimal Display impl - Use checked_pow in impl FromStr for Decimal --- src/decimal.rs | 7 +++++-- src/index.rs | 6 +++--- src/index/updater/rune_updater.rs | 3 +-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/decimal.rs b/src/decimal.rs index 687cd3ae56..226ef65390 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -26,7 +26,7 @@ impl Decimal { impl Display for Decimal { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let magnitude = 10u128.pow(self.scale.into()); + let magnitude = 10u128.checked_pow(self.scale.into()).ok_or(fmt::Error)?; let integer = self.value / magnitude; let mut fraction = self.value % magnitude; @@ -68,7 +68,10 @@ impl FromStr for Decimal { } else { let trailing_zeros = decimal.chars().rev().take_while(|c| *c == '0').count(); let significant_digits = decimal.chars().count() - trailing_zeros; - let decimal = decimal.parse::()? / 10u128.pow(u32::try_from(trailing_zeros).unwrap()); + let decimal = decimal.parse::()? + / 10u128 + .checked_pow(u32::try_from(trailing_zeros).unwrap()) + .context("excessive trailing zeros")?; (decimal, u8::try_from(significant_digits).unwrap()) }; diff --git a/src/index.rs b/src/index.rs index d6f8dfcc12..bb2569d6e7 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1782,12 +1782,12 @@ impl Index { }; let max_id = RuneId { - block: block_height + 1, - tx: 0, + block: block_height, + tx: u32::MAX, }; let runes = rune_id_to_rune_entry - .range(min_id.store()..max_id.store())? + .range(min_id.store()..=max_id.store())? .map(|result| result.map(|(_, entry)| RuneEntry::load(entry.value()).spaced_rune)) .collect::, StorageError>>()?; diff --git a/src/index/updater/rune_updater.rs b/src/index/updater/rune_updater.rs index f185a897c2..cce4ae552c 100644 --- a/src/index/updater/rune_updater.rs +++ b/src/index/updater/rune_updater.rs @@ -142,8 +142,7 @@ impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> { .unwrap_or_default(); // assign all un-allocated runes to the default output, or the first non - // OP_RETURN output if there is no default, or if the default output is - // too large + // OP_RETURN output if there is no default if let Some(vout) = pointer .map(|pointer| pointer.into_usize()) .inspect(|&pointer| assert!(pointer < allocated.len())) From 861c9d8ff8a03312667cc98ec1fd31a4b20aba41 Mon Sep 17 00:00:00 2001 From: oxSaturn Date: Fri, 19 Apr 2024 05:34:26 +0800 Subject: [PATCH 05/14] Remove duplicated word (#3598) --- docs/src/guides/batch-inscribing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/guides/batch-inscribing.md b/docs/src/guides/batch-inscribing.md index 9ccb539cb0..ca527e0420 100644 --- a/docs/src/guides/batch-inscribing.md +++ b/docs/src/guides/batch-inscribing.md @@ -1,7 +1,7 @@ Batch Inscribing ================ -Multiple inscriptions can be created inscriptions at the same time using the +Multiple inscriptions can be created at the same time using the [pointer field](./../inscriptions/pointer.md). This is especially helpful for collections, or other cases when multiple inscriptions should share the same parent, since the parent can passed into a reveal transaction that creates From ea4f86020ba61d7c41adf226e2651ad8ceb0ce27 Mon Sep 17 00:00:00 2001 From: raph Date: Fri, 19 Apr 2024 00:17:15 +0200 Subject: [PATCH 06/14] Show premine percentage (#3567) --- src/subcommand/server.rs | 2 ++ src/templates/rune.rs | 2 ++ templates/rune.html | 2 ++ tests/lib.rs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index b1fe73ac89..0ee4c84bd2 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -2833,6 +2833,8 @@ mod tests {
340282366920938463463374607431768211455\u{A0}%
premine
340282366920938463463374607431768211455\u{A0}%
+
premine percentage
+
100%
burned
0\u{A0}%
divisibility
diff --git a/src/templates/rune.rs b/src/templates/rune.rs index 9e4aaf404a..c4333315fa 100644 --- a/src/templates/rune.rs +++ b/src/templates/rune.rs @@ -87,6 +87,8 @@ mod tests {
100.123456889\u{A0}%
premine
0.123456789\u{A0}%
+
premine percentage
+
0.12%
burned
123456789.123456789\u{A0}%
divisibility
diff --git a/templates/rune.html b/templates/rune.html index 63f4f8e86d..98a9b1ac00 100644 --- a/templates/rune.html +++ b/templates/rune.html @@ -54,6 +54,8 @@

{{ self.entry.spaced_rune }}

{{ self.entry.pile(self.entry.supply()) }}
premine
{{ self.entry.pile(self.entry.premine) }}
+
premine percentage
+
{{ Decimal { value: ((self.entry.premine as f64 / self.entry.supply() as f64) * 10000.0) as u128, scale: 2 } }}%
burned
{{ self.entry.pile(self.entry.burned) }}
divisibility
diff --git a/tests/lib.rs b/tests/lib.rs index 99727d9b3d..8c53a04cd3 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -336,6 +336,8 @@ fn batch(core: &mockcore::Handle, ord: &TestServer, batchfile: batch::File) -> E
{premine} {symbol}
premine
{premine} {symbol}
+
premine percentage
+
.*
burned
0 {symbol}
divisibility
From 3b081a2a63334f4291cce6c68b65a96e46d73c5e Mon Sep 17 00:00:00 2001 From: 0xLugon Date: Fri, 19 Apr 2024 06:04:29 +0700 Subject: [PATCH 07/14] Add back runes balances API (#3571) --- src/subcommand/server.rs | 37 +++++++++++++++++++++ tests/json_api.rs | 70 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 0ee4c84bd2..a3cfb7bd8d 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -255,6 +255,7 @@ impl Server { .route("/rune/:rune", get(Self::rune)) .route("/runes", get(Self::runes)) .route("/runes/:page", get(Self::runes_paginated)) + .route("/runes/balances", get(Self::runes_balances)) .route("/sat/:sat", get(Self::sat)) .route("/search", get(Self::search_by_query)) .route("/search/*query", get(Self::search_by_path)) @@ -740,6 +741,34 @@ impl Server { }) } + async fn runes_balances( + Extension(index): Extension>, + AcceptJson(accept_json): AcceptJson, + ) -> ServerResult { + task::block_in_place(|| { + Ok(if accept_json { + Json( + index + .get_rune_balance_map()? + .into_iter() + .map(|(rune, balances)| { + ( + rune, + balances + .into_iter() + .map(|(outpoint, pile)| (outpoint, pile.amount)) + .collect(), + ) + }) + .collect::>>(), + ) + .into_response() + } else { + StatusCode::NOT_FOUND.into_response() + }) + }) + } + async fn home( Extension(server_config): Extension>, Extension(index): Extension>, @@ -2546,6 +2575,14 @@ mod tests { ); } + #[test] + fn html_runes_balances_not_found() { + TestServer::builder() + .chain(Chain::Regtest) + .build() + .assert_response("/runes/balances", StatusCode::NOT_FOUND, ""); + } + #[test] fn fallback() { let server = TestServer::new(); diff --git a/tests/json_api.rs b/tests/json_api.rs index a43b2aac81..cd42c19d6a 100644 --- a/tests/json_api.rs +++ b/tests/json_api.rs @@ -635,3 +635,73 @@ fn get_runes() { } ); } + +#[test] +fn get_runes_balances() { + let core = mockcore::builder().network(Network::Regtest).build(); + + let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]); + + create_wallet(&core, &ord); + + core.mine_blocks(3); + + let rune0 = Rune(RUNE); + let rune1 = Rune(RUNE + 1); + let rune2 = Rune(RUNE + 2); + + let e0 = etch(&core, &ord, rune0); + let e1 = etch(&core, &ord, rune1); + let e2 = etch(&core, &ord, rune2); + + core.mine_blocks(1); + + let rune_balances: BTreeMap> = vec![ + ( + rune0, + vec![( + OutPoint { + txid: e0.output.reveal, + vout: 1, + }, + 1000, + )] + .into_iter() + .collect(), + ), + ( + rune1, + vec![( + OutPoint { + txid: e1.output.reveal, + vout: 1, + }, + 1000, + )] + .into_iter() + .collect(), + ), + ( + rune2, + vec![( + OutPoint { + txid: e2.output.reveal, + vout: 1, + }, + 1000, + )] + .into_iter() + .collect(), + ), + ] + .into_iter() + .collect(); + + let response = ord.json_request("/runes/balances"); + assert_eq!(response.status(), StatusCode::OK); + + let runes_balance_json: BTreeMap> = + serde_json::from_str(&response.text().unwrap()).unwrap(); + + pretty_assert_eq!(runes_balance_json, rune_balances); +} From 97b7364fbe290d9a79094370e2e00adcec6e3129 Mon Sep 17 00:00:00 2001 From: raph Date: Fri, 19 Apr 2024 20:28:42 +0200 Subject: [PATCH 08/14] Remove timeout for wallet client (#3621) --- src/wallet/wallet_constructor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wallet/wallet_constructor.rs b/src/wallet/wallet_constructor.rs index ffa4332407..b42c979d19 100644 --- a/src/wallet/wallet_constructor.rs +++ b/src/wallet/wallet_constructor.rs @@ -33,6 +33,7 @@ impl WalletConstructor { Self { ord_client: reqwest::blocking::ClientBuilder::new() + .timeout(None) .default_headers(headers.clone()) .build()?, name, From 036ef6880a6532929c2f57dac6fd12632e07c895 Mon Sep 17 00:00:00 2001 From: Felipe Lincoln Date: Fri, 19 Apr 2024 15:37:46 -0300 Subject: [PATCH 09/14] Add `dry-run` flag to `resume` command (#3592) --- src/subcommand/wallet.rs | 4 +-- src/subcommand/wallet/resume.rs | 53 ++++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/subcommand/wallet.rs b/src/subcommand/wallet.rs index 28b51b0806..b555ee0f9c 100644 --- a/src/subcommand/wallet.rs +++ b/src/subcommand/wallet.rs @@ -66,7 +66,7 @@ pub(crate) enum Subcommand { #[command(about = "Restore wallet")] Restore(restore::Restore), #[command(about = "Resume pending etchings")] - Resume, + Resume(resume::Resume), #[command(about = "List wallet satoshis")] Sats(sats::Sats), #[command(about = "Send sat or inscription")] @@ -109,7 +109,7 @@ impl WalletCommand { Subcommand::Mint(mint) => mint.run(wallet), Subcommand::Outputs => outputs::run(wallet), Subcommand::Receive(receive) => receive.run(wallet), - Subcommand::Resume => resume::run(wallet), + Subcommand::Resume(resume) => resume.run(wallet), Subcommand::Sats(sats) => sats.run(wallet), Subcommand::Send(send) => send.run(wallet), Subcommand::Transactions(transactions) => transactions.run(wallet), diff --git a/src/subcommand/wallet/resume.rs b/src/subcommand/wallet/resume.rs index 3f235d0421..7a59df706b 100644 --- a/src/subcommand/wallet/resume.rs +++ b/src/subcommand/wallet/resume.rs @@ -4,28 +4,47 @@ use super::*; pub struct ResumeOutput { pub etchings: Vec, } +#[derive(Debug, Parser)] +pub(crate) struct Resume { + #[arg(long, help = "Don't broadcast transactions.")] + pub(crate) dry_run: bool, +} -pub(crate) fn run(wallet: Wallet) -> SubcommandResult { - let mut etchings = Vec::new(); - loop { - if SHUTTING_DOWN.load(atomic::Ordering::Relaxed) { - break; - } +impl Resume { + pub(crate) fn run(self, wallet: Wallet) -> SubcommandResult { + let mut etchings = Vec::new(); + loop { + if SHUTTING_DOWN.load(atomic::Ordering::Relaxed) { + break; + } - for (rune, entry) in wallet.pending_etchings()? { - if wallet.is_mature(&entry.commit)? { - etchings.push(wallet.send_etching(rune, &entry)?); + for (rune, entry) in wallet.pending_etchings()? { + if self.dry_run { + etchings.push(batch::Output { + reveal_broadcast: false, + ..entry.output.clone() + }); + continue; + }; + + if wallet.is_mature(&entry.commit)? { + etchings.push(wallet.send_etching(rune, &entry)?); + } } - } - if wallet.pending_etchings()?.is_empty() { - break; - } + if wallet.pending_etchings()?.is_empty() { + break; + } + + if self.dry_run { + break; + } - if !wallet.integration_test() { - thread::sleep(Duration::from_secs(5)); + if !wallet.integration_test() { + thread::sleep(Duration::from_secs(5)); + } } - } - Ok(Some(Box::new(ResumeOutput { etchings }) as Box)) + Ok(Some(Box::new(ResumeOutput { etchings }) as Box)) + } } From e85c3258f4bf2801d3d77cce7f397387810e4ccf Mon Sep 17 00:00:00 2001 From: Felipe Lincoln Date: Fri, 19 Apr 2024 16:36:52 -0300 Subject: [PATCH 10/14] Clear etching when rune commitment is spent (#3618) --- src/subcommand/wallet/resume.rs | 2 +- src/wallet.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/subcommand/wallet/resume.rs b/src/subcommand/wallet/resume.rs index 7a59df706b..3cea3f0fa4 100644 --- a/src/subcommand/wallet/resume.rs +++ b/src/subcommand/wallet/resume.rs @@ -27,7 +27,7 @@ impl Resume { continue; }; - if wallet.is_mature(&entry.commit)? { + if wallet.is_mature(rune, &entry.commit)? { etchings.push(wallet.send_etching(rune, &entry)?); } } diff --git a/src/wallet.rs b/src/wallet.rs index 9b066d68cc..5a4f7b9411 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -296,7 +296,7 @@ impl Wallet { self.settings.integration_test() } - pub(crate) fn is_mature(&self, commit: &Transaction) -> Result { + pub(crate) fn is_mature(&self, rune: Rune, commit: &Transaction) -> Result { let transaction = self .bitcoin_client() .get_transaction(&commit.txid(), Some(true)) @@ -315,6 +315,7 @@ impl Wallet { return Ok(true); } } else { + self.clear_etching(rune)?; bail!("rune commitment spent, can't send reveal tx"); } } @@ -340,7 +341,7 @@ impl Wallet { return Ok(entry.output); } - if self.is_mature(&entry.commit)? { + if self.is_mature(rune, &entry.commit)? { break; } From 1fd7aef8389a372c0f5fd1258343baf58093457b Mon Sep 17 00:00:00 2001 From: 0xLugon Date: Sat, 20 Apr 2024 02:51:20 +0700 Subject: [PATCH 11/14] Add test Rune cannot be minted less than limit amount (#3556) --- src/runes.rs | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/runes.rs b/src/runes.rs index 75a3436cbf..aea423d8f8 100644 --- a/src/runes.rs +++ b/src/runes.rs @@ -3909,6 +3909,100 @@ mod tests { ); } + #[test] + fn rune_cannot_be_minted_less_than_limit_amount() { + let context = Context::builder().arg("--index-runes").build(); + + let (txid0, id) = context.etch( + Runestone { + etching: Some(Etching { + rune: Some(Rune(RUNE)), + terms: Some(Terms { + amount: Some(1000), + cap: Some(100), + ..default() + }), + ..default() + }), + ..default() + }, + 1, + ); + + context.assert_runes( + [( + id, + RuneEntry { + block: id.block, + etching: txid0, + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, + timestamp: id.block, + mints: 0, + terms: Some(Terms { + amount: Some(1000), + cap: Some(100), + ..default() + }), + ..default() + }, + )], + [], + ); + + let txid1 = context.core.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, Witness::new())], + outputs: 2, + op_return: Some( + Runestone { + mint: Some(id), + edicts: vec![Edict { + id, + amount: 111, + output: 0, + }], + ..default() + } + .encipher(), + ), + ..default() + }); + + context.mine_blocks(1); + + context.assert_runes( + [( + id, + RuneEntry { + block: id.block, + etching: txid0, + terms: Some(Terms { + amount: Some(1000), + cap: Some(100), + ..default() + }), + mints: 1, + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, + premine: 0, + timestamp: id.block, + ..default() + }, + )], + [( + OutPoint { + txid: txid1, + vout: 0, + }, + vec![(id, 1000)], + )], + ); + } + #[test] fn etching_with_amount_can_be_minted() { let context = Context::builder().arg("--index-runes").build(); From d062a99d74f1d81aed4af9a6279bfb5651a000ae Mon Sep 17 00:00:00 2001 From: zmeyer44 <54515037+zmeyer44@users.noreply.github.com> Date: Fri, 19 Apr 2024 15:56:26 -0400 Subject: [PATCH 12/14] Update recursion.md with consistant syntax (#3585) --- docs/src/inscriptions/recursion.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/inscriptions/recursion.md b/docs/src/inscriptions/recursion.md index a740a29e89..69365a19b1 100644 --- a/docs/src/inscriptions/recursion.md +++ b/docs/src/inscriptions/recursion.md @@ -40,7 +40,7 @@ The recursive endpoints are: - `/r/blocktime`: UNIX time stamp of latest block. - `/r/children/`: the first 100 child inscription ids. - `/r/children//`: the set of 100 child inscription ids on ``. -- `/r/inscription/:inscription_id`: information about an inscription +- `/r/inscription/`: information about an inscription - `/r/metadata/`: JSON string containing the hex-encoded CBOR metadata. - `/r/sat/`: the first 100 inscription ids on a sat. - `/r/sat//`: the set of 100 inscription ids on ``. From 8a15488f0515a53169795bf39a24245e07bed549 Mon Sep 17 00:00:00 2001 From: raph Date: Fri, 19 Apr 2024 23:40:09 +0200 Subject: [PATCH 13/14] Check rune minimum at height before sending (#3626) --- src/wallet.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wallet.rs b/src/wallet.rs index 5a4f7b9411..c75767e6d8 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -305,6 +305,11 @@ impl Wallet { if let Some(transaction) = transaction { if u32::try_from(transaction.info.confirmations).unwrap() + 1 >= Runestone::COMMIT_CONFIRMATIONS.into() + && rune + >= Rune::minimum_at_height( + self.chain().network(), + Height(u32::try_from(self.bitcoin_client().get_block_count()? + 1).unwrap()), + ) { let tx_out = self .bitcoin_client() From 08bbf566502533217d7c5d24287e9f93b3bfc8a8 Mon Sep 17 00:00:00 2001 From: raph Date: Sat, 20 Apr 2024 00:04:36 +0200 Subject: [PATCH 14/14] Release 0.18.3 (#3625) --- CHANGELOG.md | 22 ++++++++++++++++++++++ Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 2 +- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a1660bcfa..75f6108fcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ Changelog ========= +[0.18.3](https://github.com/ordinals/ord/releases/tag/0.18.3) - 2023-04-19 +-------------------------------------------------------------------------- + +### Added +- Add `dry-run` flag to `resume` command ([#3592](https://github.com/ordinals/ord/pull/3592) by [felipelincoln](https://github.com/felipelincoln)) +- Add back runes balances API ([#3571](https://github.com/ordinals/ord/pull/3571) by [lugondev](https://github.com/lugondev)) +- Show premine percentage ([#3567](https://github.com/ordinals/ord/pull/3567) by [raphjaph](https://github.com/raphjaph)) +- Add default content proxy and decompress to env command ([#3509](https://github.com/ordinals/ord/pull/3509) by [jahvi](https://github.com/jahvi)) + +### Changed +- Resume cycles through all pending etchings ([#3566](https://github.com/ordinals/ord/pull/3566) by [raphjaph](https://github.com/raphjaph)) + +### Misc +- Check rune minimum at height before sending ([#3626](https://github.com/ordinals/ord/pull/3626) by [raphjaph](https://github.com/raphjaph)) +- Update recursion.md with consistant syntax ([#3585](https://github.com/ordinals/ord/pull/3585) by [zmeyer44](https://github.com/zmeyer44)) +- Add test Rune cannot be minted less than limit amount ([#3556](https://github.com/ordinals/ord/pull/3556) by [lugondev](https://github.com/lugondev)) +- Clear etching when rune commitment is spent ([#3618](https://github.com/ordinals/ord/pull/3618) by [felipelincoln](https://github.com/felipelincoln)) +- Remove timeout for wallet client ([#3621](https://github.com/ordinals/ord/pull/3621) by [raphjaph](https://github.com/raphjaph)) +- Remove duplicated word ([#3598](https://github.com/ordinals/ord/pull/3598) by [oxSaturn](https://github.com/oxSaturn)) +- Address runes review comments ([#3605](https://github.com/ordinals/ord/pull/3605) by [casey](https://github.com/casey)) +- Generate sample batch.yaml in env command ([#3530](https://github.com/ordinals/ord/pull/3530) by [twosatsmaxi](https://github.com/twosatsmaxi)) + [0.18.2](https://github.com/ordinals/ord/releases/tag/0.18.2) - 2023-04-17 -------------------------------------------------------------------------- diff --git a/Cargo.lock b/Cargo.lock index 4ecc53c413..4db78ae39c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -297,7 +297,7 @@ dependencies = [ "js-sys", "lazy_static", "log", - "rustls 0.22.3", + "rustls 0.22.4", "rustls-pki-types", "thiserror", "wasm-bindgen", @@ -429,7 +429,7 @@ dependencies = [ "http-body", "hyper", "pin-project-lite", - "rustls 0.21.10", + "rustls 0.21.11", "rustls-pemfile", "tokio", "tokio-rustls", @@ -1387,7 +1387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d8a2499f0fecc0492eb3e47eab4e92da7875e1028ad2528f214ac3346ca04e" dependencies = [ "futures-io", - "rustls 0.22.3", + "rustls 0.22.4", "rustls-pki-types", ] @@ -2270,7 +2270,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ord" -version = "0.18.2" +version = "0.18.3" dependencies = [ "anyhow", "async-trait", @@ -2315,7 +2315,7 @@ dependencies = [ "reqwest", "rss", "rust-embed", - "rustls 0.22.3", + "rustls 0.22.4", "rustls-acme", "serde", "serde-hex", @@ -2886,9 +2886,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" dependencies = [ "log", "ring 0.17.8", @@ -2898,9 +2898,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring 0.17.8", @@ -3338,9 +3338,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.30.10" +version = "0.30.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d7c217777061d5a2d652aea771fb9ba98b6dade657204b08c4b9604d11555b" +checksum = "87341a165d73787554941cd5ef55ad728011566fe714e987d1b976c15dbc3a83" dependencies = [ "cfg-if 1.0.0", "core-foundation-sys", @@ -3504,7 +3504,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.10", + "rustls 0.21.11", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index d5b2385655..fac6cc4d77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ord" description = "◉ Ordinal wallet and block explorer" -version = "0.18.2" +version = "0.18.3" license = "CC0-1.0" edition = "2021" autotests = false