From 5b2075b2ec631d8126571add525a836a6dc6d932 Mon Sep 17 00:00:00 2001 From: junderw Date: Sun, 4 Feb 2024 21:50:43 +0900 Subject: [PATCH 01/34] Fix: Output index should be u32 to prevent clobbering when over 65536 outputs. --- src/elements/asset.rs | 22 +++++++++--------- src/elements/peg.rs | 4 ++-- src/new_index/mempool.rs | 8 +++---- src/new_index/schema.rs | 50 ++++++++++++++++++++-------------------- src/util/transaction.rs | 2 +- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/elements/asset.rs b/src/elements/asset.rs index b6cd704f..5b6a6bd7 100644 --- a/src/elements/asset.rs +++ b/src/elements/asset.rs @@ -71,9 +71,9 @@ pub struct IssuedAsset { #[derive(Serialize, Deserialize, Debug)] pub struct AssetRow { pub issuance_txid: FullHash, - pub issuance_vin: u16, + pub issuance_vin: u32, pub prev_txid: FullHash, - pub prev_vout: u16, + pub prev_vout: u32, pub issuance: Bytes, // bincode does not like dealing with AssetIssuance, deserialization fails with "invalid type: sequence, expected a struct" pub reissuance_token: FullHash, } @@ -105,7 +105,7 @@ impl IssuedAsset { }, issuance_prevout: OutPoint { txid: deserialize(&asset.prev_txid).unwrap(), - vout: asset.prev_vout as u32, + vout: asset.prev_vout, }, contract_hash, reissuance_token, @@ -155,7 +155,7 @@ impl LiquidAsset { #[cfg_attr(test, derive(PartialEq, Eq))] pub struct IssuingInfo { pub txid: FullHash, - pub vin: u16, + pub vin: u32, pub is_reissuance: bool, // None for blinded issuances pub issued_amount: Option, @@ -166,7 +166,7 @@ pub struct IssuingInfo { #[cfg_attr(test, derive(PartialEq, Eq))] pub struct BurningInfo { pub txid: FullHash, - pub vout: u16, + pub vout: u32, pub value: u64, } @@ -251,7 +251,7 @@ fn index_tx_assets( pegout.asset.explicit().unwrap(), TxHistoryInfo::Pegout(PegoutInfo { txid, - vout: txo_index as u16, + vout: txo_index as u32, value: pegout.value, }), )); @@ -262,7 +262,7 @@ fn index_tx_assets( asset_id, TxHistoryInfo::Burning(BurningInfo { txid, - vout: txo_index as u16, + vout: txo_index as u32, value, }), )); @@ -277,7 +277,7 @@ fn index_tx_assets( pegin.asset.explicit().unwrap(), TxHistoryInfo::Pegin(PeginInfo { txid, - vin: txi_index as u16, + vin: txi_index as u32, value: pegin.value, }), )); @@ -302,7 +302,7 @@ fn index_tx_assets( asset_id, TxHistoryInfo::Issuing(IssuingInfo { txid, - vin: txi_index as u16, + vin: txi_index as u32, is_reissuance, issued_amount, token_amount, @@ -319,9 +319,9 @@ fn index_tx_assets( asset_id, AssetRow { issuance_txid: txid, - issuance_vin: txi_index as u16, + issuance_vin: txi_index as u32, prev_txid: full_hash(&txi.previous_output.txid[..]), - prev_vout: txi.previous_output.vout as u16, + prev_vout: txi.previous_output.vout, issuance: serialize(&txi.asset_issuance), reissuance_token: full_hash(&reissuance_token.into_inner()[..]), }, diff --git a/src/elements/peg.rs b/src/elements/peg.rs index cd339e60..316c5dba 100644 --- a/src/elements/peg.rs +++ b/src/elements/peg.rs @@ -55,7 +55,7 @@ impl PegoutValue { #[cfg_attr(test, derive(PartialEq, Eq))] pub struct PeginInfo { pub txid: FullHash, - pub vin: u16, + pub vin: u32, pub value: u64, } @@ -64,6 +64,6 @@ pub struct PeginInfo { #[cfg_attr(test, derive(PartialEq, Eq))] pub struct PegoutInfo { pub txid: FullHash, - pub vout: u16, + pub vout: u32, pub value: u64, } diff --git a/src/new_index/mempool.rs b/src/new_index/mempool.rs index ccd50fe1..139612d4 100644 --- a/src/new_index/mempool.rs +++ b/src/new_index/mempool.rs @@ -210,7 +210,7 @@ impl Mempool { Some(Utxo { txid: deserialize(&info.txid).expect("invalid txid"), - vout: info.vout as u32, + vout: info.vout, value: info.value, confirmed: None, @@ -452,9 +452,9 @@ impl Mempool { compute_script_hash(&prevout.script_pubkey), TxHistoryInfo::Spending(SpendingInfo { txid: txid_bytes, - vin: input_index as u16, + vin: input_index, prev_txid: full_hash(&txi.previous_output.txid[..]), - prev_vout: txi.previous_output.vout as u16, + prev_vout: txi.previous_output.vout, value: prevout.value, }), ) @@ -473,7 +473,7 @@ impl Mempool { compute_script_hash(&txo.script_pubkey), TxHistoryInfo::Funding(FundingInfo { txid: txid_bytes, - vout: index as u16, + vout: index as u32, value: txo.value, }), ) diff --git a/src/new_index/schema.rs b/src/new_index/schema.rs index 7efc9099..a52a7671 100644 --- a/src/new_index/schema.rs +++ b/src/new_index/schema.rs @@ -936,7 +936,7 @@ impl ChainQuery { let txid: Txid = deserialize(&edge.key.spending_txid).unwrap(); self.tx_confirming_block(&txid).map(|b| SpendingInput { txid, - vin: edge.key.spending_vin as u32, + vin: edge.key.spending_vin, confirmed: Some(b), }) }) @@ -1164,7 +1164,7 @@ fn index_transaction( confirmed_height, TxHistoryInfo::Funding(FundingInfo { txid, - vout: txo_index as u16, + vout: txo_index as u32, value: txo.value, }), ); @@ -1190,9 +1190,9 @@ fn index_transaction( confirmed_height, TxHistoryInfo::Spending(SpendingInfo { txid, - vin: txi_index as u16, + vin: txi_index as u32, prev_txid: full_hash(&txi.previous_output.txid[..]), - prev_vout: txi.previous_output.vout as u16, + prev_vout: txi.previous_output.vout, value: prev_txo.value, }), ); @@ -1200,9 +1200,9 @@ fn index_transaction( let edge = TxEdgeRow::new( full_hash(&txi.previous_output.txid[..]), - txi.previous_output.vout as u16, + txi.previous_output.vout, txid, - txi_index as u16, + txi_index as u32, ); rows.push(edge.into_row()); } @@ -1322,7 +1322,7 @@ impl TxConfRow { struct TxOutKey { code: u8, txid: FullHash, - vout: u16, + vout: u32, } struct TxOutRow { @@ -1336,7 +1336,7 @@ impl TxOutRow { key: TxOutKey { code: b'O', txid: *txid, - vout: vout as u16, + vout: vout as u32, }, value: serialize(txout), } @@ -1345,7 +1345,7 @@ impl TxOutRow { bincode_util::serialize_little(&TxOutKey { code: b'O', txid: full_hash(&outpoint.txid[..]), - vout: outpoint.vout as u16, + vout: outpoint.vout, }) .unwrap() } @@ -1436,7 +1436,7 @@ impl BlockRow { #[cfg_attr(test, derive(PartialEq, Eq))] pub struct FundingInfo { pub txid: FullHash, - pub vout: u16, + pub vout: u32, pub value: Value, } @@ -1444,9 +1444,9 @@ pub struct FundingInfo { #[cfg_attr(test, derive(PartialEq, Eq))] pub struct SpendingInfo { pub txid: FullHash, // spending transaction - pub vin: u16, + pub vin: u32, pub prev_txid: FullHash, // funding transaction - pub prev_vout: u16, + pub prev_vout: u32, pub value: Value, } @@ -1546,11 +1546,11 @@ impl TxHistoryInfo { match self { TxHistoryInfo::Funding(ref info) => OutPoint { txid: deserialize(&info.txid).unwrap(), - vout: info.vout as u32, + vout: info.vout, }, TxHistoryInfo::Spending(ref info) => OutPoint { txid: deserialize(&info.prev_txid).unwrap(), - vout: info.prev_vout as u32, + vout: info.prev_vout, }, #[cfg(feature = "liquid")] TxHistoryInfo::Issuing(_) @@ -1565,9 +1565,9 @@ impl TxHistoryInfo { struct TxEdgeKey { code: u8, funding_txid: FullHash, - funding_vout: u16, + funding_vout: u32, spending_txid: FullHash, - spending_vin: u16, + spending_vin: u32, } struct TxEdgeRow { @@ -1577,9 +1577,9 @@ struct TxEdgeRow { impl TxEdgeRow { fn new( funding_txid: FullHash, - funding_vout: u16, + funding_vout: u32, spending_txid: FullHash, - spending_vin: u16, + spending_vin: u32, ) -> Self { let key = TxEdgeKey { code: b'S', @@ -1593,7 +1593,7 @@ impl TxEdgeRow { fn filter(outpoint: &OutPoint) -> Bytes { // TODO build key without using bincode? [ b"S", &outpoint.txid[..], outpoint.vout?? ].concat() - bincode_util::serialize_little(&(b'S', full_hash(&outpoint.txid[..]), outpoint.vout as u16)) + bincode_util::serialize_little(&(b'S', full_hash(&outpoint.txid[..]), outpoint.vout)) .unwrap() } @@ -1730,7 +1730,7 @@ mod tests { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // vout - 0, 3, + 0, 0, 0, 3, // Value variant (Explicit) 0, 0, 0, 0, 0, 0, 0, 2, // number of tuple elements @@ -1746,7 +1746,7 @@ mod tests { 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 0, 3, + 0, 0, 0, 3, // Value variant (Null) 0, 0, 0, 0, 0, 0, 0, 1, // number of tuple elements @@ -1760,10 +1760,10 @@ mod tests { 0, 0, 0, 1, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, - 0, 12, + 0, 0, 0, 12, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, - 0, 9, + 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 1, 14, 0, 0, 0, 0, 0, 0, 0, @@ -1776,10 +1776,10 @@ mod tests { 0, 0, 0, 1, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, - 0, 12, + 0, 0, 0, 12, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, 98, 101, 101, 102, - 0, 9, + 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 1, 0, ], diff --git a/src/util/transaction.rs b/src/util/transaction.rs index c9ff29ae..be801c73 100644 --- a/src/util/transaction.rs +++ b/src/util/transaction.rs @@ -48,7 +48,7 @@ impl From> for TransactionStatus { #[derive(Serialize, Deserialize)] pub struct TxInput { pub txid: Txid, - pub vin: u16, + pub vin: u32, } pub fn is_coinbase(txin: &TxIn) -> bool { From 055ac9f4ab88aef71f9d9451dfbfaeb8563d185d Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 23 Mar 2024 09:08:45 +0000 Subject: [PATCH 02/34] Add testmempoolaccept endpoint --- src/daemon.rs | 26 ++++++++++++++++++++++++++ src/new_index/query.rs | 6 +++++- src/rest.rs | 12 ++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/daemon.rs b/src/daemon.rs index b794a1f9..01215a59 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -117,6 +117,26 @@ struct NetworkInfo { relayfee: f64, // in BTC/kB } +#[derive(Serialize, Deserialize, Debug)] +struct MempoolFees { + base: f64, + #[serde(rename = "effective-feerate")] + effective_feerate: f64, + #[serde(rename = "effective-includes")] + effective_includes: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct MempoolAcceptResult { + txid: String, + wtxid: String, + allowed: Option, + vsize: Option, + fees: Option, + #[serde(rename = "reject-reason")] + reject_reason: Option, +} + pub trait CookieGetter: Send + Sync { fn get(&self) -> Result>; } @@ -582,6 +602,12 @@ impl Daemon { .chain_err(|| "failed to parse txid") } + pub fn test_mempool_accept(&self, txhex: Vec) -> Result> { + let result = self.request("testmempoolaccept", json!([txhex]))?; + serde_json::from_value::>(result) + .chain_err(|| "invalid testmempoolaccept reply") + } + // Get estimated feerates for the provided confirmation targets using a batch RPC request // Missing estimates are logged but do not cause a failure, whatever is available is returned #[allow(clippy::float_cmp)] diff --git a/src/new_index/query.rs b/src/new_index/query.rs index 3003a256..f7d2c78d 100644 --- a/src/new_index/query.rs +++ b/src/new_index/query.rs @@ -6,7 +6,7 @@ use std::time::{Duration, Instant}; use crate::chain::{Network, OutPoint, Transaction, TxOut, Txid}; use crate::config::Config; -use crate::daemon::Daemon; +use crate::daemon::{Daemon, MempoolAcceptResult}; use crate::errors::*; use crate::new_index::{ChainQuery, Mempool, ScriptStats, SpendingInput, Utxo}; use crate::util::{is_spendable, BlockId, Bytes, TransactionStatus}; @@ -87,6 +87,10 @@ impl Query { Ok(txid) } + pub fn test_mempool_accept(&self, txhex: Vec) -> Result> { + self.daemon.test_mempool_accept(txhex) + } + pub fn utxo(&self, scripthash: &[u8]) -> Result> { let mut utxos = self.chain.utxo( scripthash, diff --git a/src/rest.rs b/src/rest.rs index df085441..f75f34a0 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -1202,6 +1202,18 @@ fn handle_request( .map_err(|err| HttpError::from(err.description().to_string()))?; http_message(StatusCode::OK, txid.to_hex(), 0) } + (&Method::POST, Some(&"txs"), Some(&"test"), None, None, None) => { + let txhexes: Vec = String::from_utf8(body.to_vec())? + .split(',') + .map(|s| s.to_string()) + .collect(); + + let result = query + .test_mempool_accept(txhexes) + .map_err(|err| HttpError::from(err.description().to_string()))?; + + json_response(result, TTL_SHORT) + } (&Method::GET, Some(&"txs"), Some(&"outspends"), None, None, None) => { let txid_strings: Vec<&str> = query_params .get("txids") From 946ea714eda165d6c77e5066ef8bfefff9c24622 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 24 Mar 2024 05:30:24 +0000 Subject: [PATCH 03/34] testmempoolaccept add maxfeerate param --- src/daemon.rs | 12 ++++++++++-- src/new_index/query.rs | 8 ++++++-- src/rest.rs | 9 ++++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 01215a59..7d4dafa3 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -602,8 +602,16 @@ impl Daemon { .chain_err(|| "failed to parse txid") } - pub fn test_mempool_accept(&self, txhex: Vec) -> Result> { - let result = self.request("testmempoolaccept", json!([txhex]))?; + pub fn test_mempool_accept( + &self, + txhex: Vec, + maxfeerate: Option, + ) -> Result> { + let params = match maxfeerate { + Some(rate) => json!([txhex, format!("{:.8}", rate)]), + None => json!([txhex]), + }; + let result = self.request("testmempoolaccept", params)?; serde_json::from_value::>(result) .chain_err(|| "invalid testmempoolaccept reply") } diff --git a/src/new_index/query.rs b/src/new_index/query.rs index f7d2c78d..c64cd6f9 100644 --- a/src/new_index/query.rs +++ b/src/new_index/query.rs @@ -87,8 +87,12 @@ impl Query { Ok(txid) } - pub fn test_mempool_accept(&self, txhex: Vec) -> Result> { - self.daemon.test_mempool_accept(txhex) + pub fn test_mempool_accept( + &self, + txhex: Vec, + maxfeerate: Option, + ) -> Result> { + self.daemon.test_mempool_accept(txhex, maxfeerate) } pub fn utxo(&self, scripthash: &[u8]) -> Result> { diff --git a/src/rest.rs b/src/rest.rs index f75f34a0..cb620d4a 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -1207,9 +1207,16 @@ fn handle_request( .split(',') .map(|s| s.to_string()) .collect(); + let maxfeerate = query_params + .get("maxfeerate") + .map(|s| { + s.parse::() + .map_err(|_| HttpError::from("Invalid maxfeerate".to_string())) + }) + .transpose()?; let result = query - .test_mempool_accept(txhexes) + .test_mempool_accept(txhexes, maxfeerate) .map_err(|err| HttpError::from(err.description().to_string()))?; json_response(result, TTL_SHORT) From 569af75849cc9e359325b0ba25606e4c6310de95 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 24 Mar 2024 06:34:15 +0000 Subject: [PATCH 04/34] Add testmempoolaccept pre-checks --- src/rest.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/rest.rs b/src/rest.rs index cb620d4a..5ab38b7a 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -1215,6 +1215,19 @@ fn handle_request( }) .transpose()?; + // pre-checks + txhexes.iter().try_for_each(|txhex| { + // each transaction must be of reasonable size (more than 60 bytes, within 400kWU standardness limit) + if !(120..800_000).contains(&txhex.len()) { + Result::Err(HttpError::from("Invalid transaction size".to_string())) + } else { + // must be a valid hex string + Vec::::from_hex(txhex) + .map_err(|_| HttpError::from("Invalid transaction hex".to_string())) + .map(|_| ()) + } + })?; + let result = query .test_mempool_accept(txhexes, maxfeerate) .map_err(|err| HttpError::from(err.description().to_string()))?; From f7385ce392472e3cc6e49a3bac72b1b0cf298c1a Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 25 Mar 2024 05:03:38 +0000 Subject: [PATCH 05/34] testmempoolaccept JSON input, tx limit, error indices --- src/rest.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/rest.rs b/src/rest.rs index 5ab38b7a..71478312 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -1203,10 +1203,15 @@ fn handle_request( http_message(StatusCode::OK, txid.to_hex(), 0) } (&Method::POST, Some(&"txs"), Some(&"test"), None, None, None) => { - let txhexes: Vec = String::from_utf8(body.to_vec())? - .split(',') - .map(|s| s.to_string()) - .collect(); + let txhexes: Vec = + serde_json::from_str(String::from_utf8(body.to_vec())?.as_str())?; + + if txhexes.len() > 25 { + Result::Err(HttpError::from( + "Exceeded maximum of 25 transactions".to_string(), + ))? + } + let maxfeerate = query_params .get("maxfeerate") .map(|s| { @@ -1216,14 +1221,19 @@ fn handle_request( .transpose()?; // pre-checks - txhexes.iter().try_for_each(|txhex| { + txhexes.iter().enumerate().try_for_each(|(index, txhex)| { // each transaction must be of reasonable size (more than 60 bytes, within 400kWU standardness limit) if !(120..800_000).contains(&txhex.len()) { - Result::Err(HttpError::from("Invalid transaction size".to_string())) + Result::Err(HttpError::from(format!( + "Invalid transaction size for item {}", + index + ))) } else { // must be a valid hex string Vec::::from_hex(txhex) - .map_err(|_| HttpError::from("Invalid transaction hex".to_string())) + .map_err(|_| { + HttpError::from(format!("Invalid transaction hex for item {}", index)) + }) .map(|_| ()) } })?; From ac32e4b1c3053c4a86fe4415e062ff48a1e5775e Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 25 Mar 2024 05:54:33 +0000 Subject: [PATCH 06/34] testmempoolaccept maxfeerate f32 -> f64 --- src/daemon.rs | 2 +- src/new_index/query.rs | 2 +- src/rest.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 7d4dafa3..254c168e 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -605,7 +605,7 @@ impl Daemon { pub fn test_mempool_accept( &self, txhex: Vec, - maxfeerate: Option, + maxfeerate: Option, ) -> Result> { let params = match maxfeerate { Some(rate) => json!([txhex, format!("{:.8}", rate)]), diff --git a/src/new_index/query.rs b/src/new_index/query.rs index c64cd6f9..3e314fd1 100644 --- a/src/new_index/query.rs +++ b/src/new_index/query.rs @@ -90,7 +90,7 @@ impl Query { pub fn test_mempool_accept( &self, txhex: Vec, - maxfeerate: Option, + maxfeerate: Option, ) -> Result> { self.daemon.test_mempool_accept(txhex, maxfeerate) } diff --git a/src/rest.rs b/src/rest.rs index 71478312..eab06de5 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -1215,7 +1215,7 @@ fn handle_request( let maxfeerate = query_params .get("maxfeerate") .map(|s| { - s.parse::() + s.parse::() .map_err(|_| HttpError::from("Invalid maxfeerate".to_string())) }) .transpose()?; From e6b0d9ffaa8af71ca3d53e20c99369219ff51d22 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 16 Apr 2024 14:03:05 +0900 Subject: [PATCH 07/34] ops: Adjust limits on Fremont site --- start | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/start b/start index 16139f7b..945c6b7c 100755 --- a/start +++ b/start @@ -93,6 +93,10 @@ do ELECTRUM_TXS_LIMIT=9000 MAIN_LOOP_DELAY=14000 fi + if [ "${LOCATION}" = "fmt" ];then + UTXOS_LIMIT=9000 + ELECTRUM_TXS_LIMIT=9000 + fi # Run the popular address txt file generator before each run POPULAR_SCRIPTS_FOLDER="${HOME}/popular-scripts/${NETWORK}" From 1abe9c4e51498abe613e4bd390498d49a2eb6a15 Mon Sep 17 00:00:00 2001 From: Gustavo Spier Landtreter Date: Sat, 20 Apr 2024 01:54:17 -0300 Subject: [PATCH 08/34] Fixed regression introduced by #51 Commit merged as part of #51 introduced a regression that prevents the daemon from breaking from its startup wait loop when running in `regtest` mode, and the blockchain already contains 1 or more blocks (apart from genesis). This commit fixes the regression by only checking the equivalence between blocks and headers as the wait condition when running in `regtest` mode. --- src/daemon.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 254c168e..3f370bf8 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -341,10 +341,10 @@ impl Daemon { let mempool = daemon.getmempoolinfo()?; let ibd_done = if network.is_regtest() { - info.blocks == 0 && info.headers == 0 + info.blocks == info.headers } else { - false - } || !info.initialblockdownload.unwrap_or(false); + !info.initialblockdownload.unwrap_or(false) + }; if mempool.loaded && ibd_done && info.blocks == info.headers { break; From cd9efdff46f51325e4922d04cfb4c2c3b64c2a79 Mon Sep 17 00:00:00 2001 From: wiz Date: Fri, 26 Apr 2024 00:19:53 +0900 Subject: [PATCH 09/34] ops: Increase limits on node213/node214 --- start | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/start b/start index 945c6b7c..25330d24 100755 --- a/start +++ b/start @@ -93,6 +93,14 @@ do ELECTRUM_TXS_LIMIT=9000 MAIN_LOOP_DELAY=14000 fi + if [ "${NODENAME}" = "node213" ];then + UTXOS_LIMIT=9000 + ELECTRUM_TXS_LIMIT=9000 + fi + if [ "${NODENAME}" = "node214" ];then + UTXOS_LIMIT=9000 + ELECTRUM_TXS_LIMIT=9000 + fi if [ "${LOCATION}" = "fmt" ];then UTXOS_LIMIT=9000 ELECTRUM_TXS_LIMIT=9000 From c2445a3462d5c46a91d0578af6ec438218dce887 Mon Sep 17 00:00:00 2001 From: junderw Date: Wed, 1 May 2024 21:08:03 +0900 Subject: [PATCH 10/34] Shorten mempool lock holding for update --- src/bin/electrs.rs | 21 +++++++++- src/new_index/mempool.rs | 91 ++++++++++++++++++++++++---------------- 2 files changed, 75 insertions(+), 37 deletions(-) diff --git a/src/bin/electrs.rs b/src/bin/electrs.rs index 91a51eef..ec4b387a 100644 --- a/src/bin/electrs.rs +++ b/src/bin/electrs.rs @@ -74,7 +74,18 @@ fn run_server(config: Arc) -> Result<()> { &metrics, Arc::clone(&config), ))); - mempool.write().unwrap().update(&daemon)?; + loop { + match Mempool::update(&mempool, &daemon) { + Ok(_) => break, + Err(e) => { + warn!( + "Error performing initial mempool update, trying again in 5 seconds: {}", + e.display_chain() + ); + signal.wait(Duration::from_secs(5), false)?; + } + } + } #[cfg(feature = "liquid")] let asset_db = config.asset_db_path.as_ref().map(|db_dir| { @@ -136,7 +147,13 @@ fn run_server(config: Arc) -> Result<()> { }; // Update mempool - mempool.write().unwrap().update(&daemon)?; + if let Err(e) = Mempool::update(&mempool, &daemon) { + // Log the error if the result is an Err + warn!( + "Error updating mempool, skipping mempool update: {}", + e.display_chain() + ); + } // Update subscribed clients electrum_server.notify(); diff --git a/src/new_index/mempool.rs b/src/new_index/mempool.rs index c3841d52..b706b3d3 100644 --- a/src/new_index/mempool.rs +++ b/src/new_index/mempool.rs @@ -9,7 +9,7 @@ use elements::{encode::serialize, AssetId}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::iter::FromIterator; use std::ops::Bound::{Excluded, Unbounded}; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use std::time::{Duration, Instant}; use crate::chain::{deserialize, Network, OutPoint, Transaction, TxOut, Txid}; @@ -343,46 +343,67 @@ impl Mempool { &self.backlog_stats.0 } - pub fn update(&mut self, daemon: &Daemon) -> Result<()> { - let _timer = self.latency.with_label_values(&["update"]).start_timer(); - let new_txids = daemon + pub fn unique_txids(&self) -> HashSet { + return HashSet::from_iter(self.txstore.keys().cloned()); + } + + pub fn update(mempool: &RwLock, daemon: &Daemon) -> Result<()> { + // 1. Start the metrics timer and get the current mempool txids + // [LOCK] Takes read lock for whole scope. + let (_timer, old_txids) = { + let mempool = mempool.read().unwrap(); + ( + mempool.latency.with_label_values(&["update"]).start_timer(), + mempool.unique_txids(), + ) + }; + + // 2. Get all the mempool txids from the RPC. + // [LOCK] No lock taken. Wait for RPC request. Get lists of remove/add txes. + let all_txids = daemon .getmempooltxids() .chain_err(|| "failed to update mempool from daemon")?; - let old_txids = HashSet::from_iter(self.txstore.keys().cloned()); - let to_remove: HashSet<&Txid> = old_txids.difference(&new_txids).collect(); - - // Download and add new transactions from bitcoind's mempool - let txids: Vec<&Txid> = new_txids.difference(&old_txids).collect(); - let to_add = match daemon.gettransactions(&txids) { - Ok(txs) => txs, - Err(err) => { - warn!("failed to get {} transactions: {}", txids.len(), err); // e.g. new block or RBF - return Ok(()); // keep the mempool until next update() + let txids_to_remove: HashSet<&Txid> = old_txids.difference(&all_txids).collect(); + let txids_to_add: Vec<&Txid> = all_txids.difference(&old_txids).collect(); + + // 3. Remove missing transactions. Even if we are unable to download new transactions from + // the daemon, we still want to remove the transactions that are no longer in the mempool. + // [LOCK] Write lock is released at the end of the call to remove(). + mempool.write().unwrap().remove(txids_to_remove); + + // 4. Download the new transactions from the daemon's mempool + // [LOCK] No lock taken, waiting for RPC response. + let txs_to_add = daemon + .gettransactions(&txids_to_add) + .chain_err(|| format!("failed to get {} transactions", txids_to_add.len()))?; + + // 4. Update local mempool to match daemon's state + // [LOCK] Takes Write lock for whole scope. + { + let mut mempool = mempool.write().unwrap(); + // Add new transactions + if txs_to_add.len() > mempool.add(txs_to_add) { + debug!("Mempool update added less transactions than expected"); } - }; - // Add new transactions - if to_add.len() > self.add(to_add) { - debug!("Mempool update added less transactions than expected"); - } - // Remove missing transactions - self.remove(to_remove); - self.count - .with_label_values(&["txs"]) - .set(self.txstore.len() as f64); + mempool + .count + .with_label_values(&["txs"]) + .set(mempool.txstore.len() as f64); + + // Update cached backlog stats (if expired) + if mempool.backlog_stats.1.elapsed() + > Duration::from_secs(mempool.config.mempool_backlog_stats_ttl) + { + let _timer = mempool + .latency + .with_label_values(&["update_backlog_stats"]) + .start_timer(); + mempool.backlog_stats = (BacklogStats::new(&mempool.feeinfo), Instant::now()); + } - // Update cached backlog stats (if expired) - if self.backlog_stats.1.elapsed() - > Duration::from_secs(self.config.mempool_backlog_stats_ttl) - { - let _timer = self - .latency - .with_label_values(&["update_backlog_stats"]) - .start_timer(); - self.backlog_stats = (BacklogStats::new(&self.feeinfo), Instant::now()); + Ok(()) } - - Ok(()) } pub fn add_by_txid(&mut self, daemon: &Daemon, txid: &Txid) -> Result<()> { From 9e0ecad4d4f07c10dad35b91d4aa5b8355052e34 Mon Sep 17 00:00:00 2001 From: junderw Date: Wed, 1 May 2024 23:53:39 +0900 Subject: [PATCH 11/34] Prevent duplicate history events --- src/new_index/mempool.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/new_index/mempool.rs b/src/new_index/mempool.rs index b706b3d3..8a94962f 100644 --- a/src/new_index/mempool.rs +++ b/src/new_index/mempool.rs @@ -439,8 +439,12 @@ impl Mempool { // Phase 1: add to txstore for tx in txs { let txid = tx.txid(); - txids.push(txid); - self.txstore.insert(txid, tx); + // Only push if it doesn't already exist. + // This is important now that update doesn't lock during + // the entire function body. + if self.txstore.insert(txid, tx).is_none() { + txids.push(txid); + } } // Phase 2: index history and spend edges (some txos can be missing) From d486a36539d5c3eac4ce87d78906ceafd85eed0d Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 6 May 2024 22:24:09 +0900 Subject: [PATCH 12/34] Add Testnet4 support --- src/chain.rs | 9 +++++++++ src/config.rs | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/chain.rs b/src/chain.rs index de726186..0627512e 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -32,6 +32,8 @@ pub enum Network { #[cfg(not(feature = "liquid"))] Testnet, #[cfg(not(feature = "liquid"))] + Testnet4, + #[cfg(not(feature = "liquid"))] Regtest, #[cfg(not(feature = "liquid"))] Signet, @@ -135,6 +137,8 @@ pub fn bitcoin_genesis_hash(network: BNetwork) -> bitcoin::BlockHash { genesis_block(BNetwork::Bitcoin).block_hash(); static ref TESTNET_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Testnet).block_hash(); + static ref TESTNET4_GENESIS: bitcoin::BlockHash = + genesis_block(BNetwork::Testnet4).block_hash(); static ref REGTEST_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Regtest).block_hash(); static ref SIGNET_GENESIS: bitcoin::BlockHash = @@ -143,6 +147,7 @@ pub fn bitcoin_genesis_hash(network: BNetwork) -> bitcoin::BlockHash { match network { BNetwork::Bitcoin => *BITCOIN_GENESIS, BNetwork::Testnet => *TESTNET_GENESIS, + BNetwork::Testnet4 => *TESTNET4_GENESIS, BNetwork::Regtest => *REGTEST_GENESIS, BNetwork::Signet => *SIGNET_GENESIS, } @@ -174,6 +179,8 @@ impl From<&str> for Network { #[cfg(not(feature = "liquid"))] "testnet" => Network::Testnet, #[cfg(not(feature = "liquid"))] + "testnet4" => Network::Testnet4, + #[cfg(not(feature = "liquid"))] "regtest" => Network::Regtest, #[cfg(not(feature = "liquid"))] "signet" => Network::Signet, @@ -196,6 +203,7 @@ impl From for BNetwork { match network { Network::Bitcoin => BNetwork::Bitcoin, Network::Testnet => BNetwork::Testnet, + Network::Testnet4 => BNetwork::Testnet4, Network::Regtest => BNetwork::Regtest, Network::Signet => BNetwork::Signet, } @@ -208,6 +216,7 @@ impl From for Network { match network { BNetwork::Bitcoin => Network::Bitcoin, BNetwork::Testnet => Network::Testnet, + BNetwork::Testnet4 => Network::Testnet4, BNetwork::Regtest => Network::Regtest, BNetwork::Signet => Network::Signet, } diff --git a/src/config.rs b/src/config.rs index 8278d985..a27bbf28 100644 --- a/src/config.rs +++ b/src/config.rs @@ -353,6 +353,8 @@ impl Config { Network::Regtest => 18443, #[cfg(not(feature = "liquid"))] Network::Signet => 38332, + #[cfg(not(feature = "liquid"))] + Network::Testnet4 => 48332, #[cfg(feature = "liquid")] Network::Liquid => 7041, @@ -365,6 +367,8 @@ impl Config { #[cfg(not(feature = "liquid"))] Network::Testnet => 60001, #[cfg(not(feature = "liquid"))] + Network::Testnet4 => 40001, + #[cfg(not(feature = "liquid"))] Network::Regtest => 60401, #[cfg(not(feature = "liquid"))] Network::Signet => 60601, @@ -385,6 +389,8 @@ impl Config { Network::Regtest => 3002, #[cfg(not(feature = "liquid"))] Network::Signet => 3003, + #[cfg(not(feature = "liquid"))] + Network::Testnet4 => 3004, #[cfg(feature = "liquid")] Network::Liquid => 3000, @@ -401,6 +407,8 @@ impl Config { #[cfg(not(feature = "liquid"))] Network::Regtest => 24224, #[cfg(not(feature = "liquid"))] + Network::Testnet4 => 44224, + #[cfg(not(feature = "liquid"))] Network::Signet => 54224, #[cfg(feature = "liquid")] @@ -449,6 +457,8 @@ impl Config { #[cfg(not(feature = "liquid"))] Network::Testnet => daemon_dir.push("testnet3"), #[cfg(not(feature = "liquid"))] + Network::Testnet4 => daemon_dir.push("testnet4"), + #[cfg(not(feature = "liquid"))] Network::Regtest => daemon_dir.push("regtest"), #[cfg(not(feature = "liquid"))] Network::Signet => daemon_dir.push("signet"), From 14e427d8e8c5a33b29bc3b1a7cef0292a5c7f243 Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 6 May 2024 23:00:54 +0900 Subject: [PATCH 13/34] Update start script for testnet4 instance --- start | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/start b/start index 25330d24..867590cd 100755 --- a/start +++ b/start @@ -43,6 +43,10 @@ case "${1}" in NETWORK=testnet THREADS=$((NPROC / 6)) ;; + testnet4) + NETWORK=testnet4 + THREADS=$((NPROC / 6)) + ;; signet) NETWORK=signet THREADS=$((NPROC / 6)) @@ -60,7 +64,7 @@ case "${1}" in THREADS=$((NPROC / 6)) ;; *) - echo "Usage: $0 (mainnet|testnet|signet|liquid|liquidtestnet)" + echo "Usage: $0 (mainnet|testnet|testnet4|signet|liquid|liquidtestnet)" exit 1 ;; esac From cdb60c948a6c54009d5c5a355da1be92590f99e5 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 6 May 2024 15:01:03 +0000 Subject: [PATCH 14/34] Configurable network magic --- src/bin/electrs.rs | 1 + src/bin/tx-fingerprint-stats.rs | 1 + src/config.rs | 10 ++++++++++ src/daemon.rs | 6 +++++- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/bin/electrs.rs b/src/bin/electrs.rs index 91a51eef..a4c0408b 100644 --- a/src/bin/electrs.rs +++ b/src/bin/electrs.rs @@ -50,6 +50,7 @@ fn run_server(config: Arc) -> Result<()> { config.daemon_rpc_addr, config.cookie_getter(), config.network_type, + config.magic, signal.clone(), &metrics, )?); diff --git a/src/bin/tx-fingerprint-stats.rs b/src/bin/tx-fingerprint-stats.rs index 55cb6797..5b38561f 100644 --- a/src/bin/tx-fingerprint-stats.rs +++ b/src/bin/tx-fingerprint-stats.rs @@ -35,6 +35,7 @@ fn main() { config.daemon_rpc_addr, config.cookie_getter(), config.network_type, + config.magic, signal, &metrics, ) diff --git a/src/config.rs b/src/config.rs index a27bbf28..ac87c598 100644 --- a/src/config.rs +++ b/src/config.rs @@ -33,6 +33,7 @@ pub struct Config { // See below for the documentation of each field: pub log: stderrlog::StdErrLog, pub network_type: Network, + pub magic: Option, pub db_path: PathBuf, pub daemon_dir: PathBuf, pub blocks_dir: PathBuf, @@ -137,6 +138,11 @@ impl Config { .help(&network_help) .takes_value(true), ) + .arg( + Arg::with_name("magic") + .long("magic") + .takes_value(true), + ) .arg( Arg::with_name("electrum_rpc_addr") .long("electrum-rpc-addr") @@ -328,6 +334,9 @@ impl Config { let network_name = m.value_of("network").unwrap_or("mainnet"); let network_type = Network::from(network_name); + let magic: Option = m + .value_of("magic") + .map(|s| u32::from_str_radix(s, 16).expect("invalid network magic")); let db_dir = Path::new(m.value_of("db_dir").unwrap_or("./db")); let db_path = db_dir.join(network_name); @@ -496,6 +505,7 @@ impl Config { let config = Config { log, network_type, + magic, db_path, daemon_dir, blocks_dir, diff --git a/src/daemon.rs b/src/daemon.rs index 3f370bf8..b8bde690 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -284,6 +284,7 @@ pub struct Daemon { daemon_dir: PathBuf, blocks_dir: PathBuf, network: Network, + magic: Option, conn: Mutex, message_id: Counter, // for monotonic JSONRPC 'id' signal: Waiter, @@ -300,6 +301,7 @@ impl Daemon { daemon_rpc_addr: SocketAddr, cookie_getter: Arc, network: Network, + magic: Option, signal: Waiter, metrics: &Metrics, ) -> Result { @@ -307,6 +309,7 @@ impl Daemon { daemon_dir, blocks_dir, network, + magic, conn: Mutex::new(Connection::new( daemon_rpc_addr, cookie_getter, @@ -367,6 +370,7 @@ impl Daemon { daemon_dir: self.daemon_dir.clone(), blocks_dir: self.blocks_dir.clone(), network: self.network, + magic: self.magic, conn: Mutex::new(self.conn.lock().unwrap().reconnect()?), message_id: Counter::new(), signal: self.signal.clone(), @@ -387,7 +391,7 @@ impl Daemon { } pub fn magic(&self) -> u32 { - self.network.magic() + self.magic.unwrap_or_else(|| self.network.magic()) } fn call_jsonrpc(&self, method: &str, request: &Value) -> Result { From 69bfa5beff609f218753f459e6a8892e65a09714 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 6 May 2024 15:01:15 +0000 Subject: [PATCH 15/34] Hardcode testnet4 genesis hash --- src/chain.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index 0627512e..8abf9a4a 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + #[cfg(not(feature = "liquid"))] // use regular Bitcoin data structures pub use bitcoin::{ blockdata::{opcodes, script, witness::Witness}, @@ -131,25 +133,25 @@ pub fn genesis_hash(network: Network) -> BlockHash { return liquid_genesis_hash(network); } -pub fn bitcoin_genesis_hash(network: BNetwork) -> bitcoin::BlockHash { +pub fn bitcoin_genesis_hash(network: Network) -> bitcoin::BlockHash { lazy_static! { static ref BITCOIN_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Bitcoin).block_hash(); static ref TESTNET_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Testnet).block_hash(); static ref TESTNET4_GENESIS: bitcoin::BlockHash = - genesis_block(BNetwork::Testnet4).block_hash(); + BlockHash::from_str("00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043").unwrap(); static ref REGTEST_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Regtest).block_hash(); static ref SIGNET_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Signet).block_hash(); } match network { - BNetwork::Bitcoin => *BITCOIN_GENESIS, - BNetwork::Testnet => *TESTNET_GENESIS, - BNetwork::Testnet4 => *TESTNET4_GENESIS, - BNetwork::Regtest => *REGTEST_GENESIS, - BNetwork::Signet => *SIGNET_GENESIS, + Network::Bitcoin => *BITCOIN_GENESIS, + Network::Testnet => *TESTNET_GENESIS, + Network::Testnet4 => *TESTNET4_GENESIS, + Network::Regtest => *REGTEST_GENESIS, + Network::Signet => *SIGNET_GENESIS, } } @@ -203,7 +205,7 @@ impl From for BNetwork { match network { Network::Bitcoin => BNetwork::Bitcoin, Network::Testnet => BNetwork::Testnet, - Network::Testnet4 => BNetwork::Testnet4, + Network::Testnet4 => BNetwork::Testnet, Network::Regtest => BNetwork::Regtest, Network::Signet => BNetwork::Signet, } @@ -216,7 +218,6 @@ impl From for Network { match network { BNetwork::Bitcoin => Network::Bitcoin, BNetwork::Testnet => Network::Testnet, - BNetwork::Testnet4 => Network::Testnet4, BNetwork::Regtest => Network::Regtest, BNetwork::Signet => Network::Signet, } From c6b8be94b7c576117d74ef3397a90a1d659a99c6 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 7 May 2024 00:08:59 +0900 Subject: [PATCH 16/34] ops: Add magic bytes for testnet4 to start script --- start | 2 ++ 1 file changed, 2 insertions(+) diff --git a/start b/start index 867590cd..ae4ee6b0 100755 --- a/start +++ b/start @@ -45,6 +45,7 @@ case "${1}" in ;; testnet4) NETWORK=testnet4 + MAGIC=283f161c THREADS=$((NPROC / 6)) ;; signet) @@ -152,6 +153,7 @@ do --precache-threads "${THREADS}" \ --cookie "${RPC_USER}:${RPC_PASS}" \ --cors '*' \ + --magic "${MAGIC}" \ --address-search \ --utxos-limit "${UTXOS_LIMIT}" \ --electrum-txs-limit "${ELECTRUM_TXS_LIMIT}" \ From 181785b5c1cb6a9280e8212ddfcc99bd508fabe4 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 6 May 2024 15:16:00 +0000 Subject: [PATCH 17/34] Allow empty magic --- src/config.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config.rs b/src/config.rs index ac87c598..a5e903ce 100644 --- a/src/config.rs +++ b/src/config.rs @@ -141,6 +141,7 @@ impl Config { .arg( Arg::with_name("magic") .long("magic") + .default_value("") .takes_value(true), ) .arg( @@ -336,6 +337,7 @@ impl Config { let network_type = Network::from(network_name); let magic: Option = m .value_of("magic") + .filter(|s| !s.is_empty()) .map(|s| u32::from_str_radix(s, 16).expect("invalid network magic")); let db_dir = Path::new(m.value_of("db_dir").unwrap_or("./db")); let db_path = db_dir.join(network_name); From 17dc10e0edea401e3f663530d1f057e7a6ae52a5 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 6 May 2024 16:05:13 +0000 Subject: [PATCH 18/34] Fix testnet4 address lookups --- src/rest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rest.rs b/src/rest.rs index eab06de5..2e061b11 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -1668,7 +1668,7 @@ fn address_to_scripthash(addr: &str, network: Network) -> Result Date: Tue, 7 May 2024 01:39:27 +0900 Subject: [PATCH 19/34] ops: Remove UTXO limit for testnet4 --- start | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/start b/start index ae4ee6b0..7e5cd80f 100755 --- a/start +++ b/start @@ -102,7 +102,11 @@ do UTXOS_LIMIT=9000 ELECTRUM_TXS_LIMIT=9000 fi - if [ "${NODENAME}" = "node214" ];then + if [ "${NODENAME}" = "node213" ];then + UTXOS_LIMIT=9000 + ELECTRUM_TXS_LIMIT=9000 + fi + if [ "${NETWORK}" = "testnet4" ];then UTXOS_LIMIT=9000 ELECTRUM_TXS_LIMIT=9000 fi From 97e87086547062a629b47d251e4da97d4edc2434 Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 7 May 2024 22:19:02 +0900 Subject: [PATCH 20/34] Fix Testnet4 addition --- src/chain.rs | 15 ++++++++++++--- src/daemon.rs | 1 + src/elements/peg.rs | 8 +++++++- src/rest.rs | 5 ++++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index 8abf9a4a..ccb2b353 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -128,7 +128,7 @@ impl Network { pub fn genesis_hash(network: Network) -> BlockHash { #[cfg(not(feature = "liquid"))] - return bitcoin_genesis_hash(network.into()); + return bitcoin_genesis_hash(network); #[cfg(feature = "liquid")] return liquid_genesis_hash(network); } @@ -139,13 +139,16 @@ pub fn bitcoin_genesis_hash(network: Network) -> bitcoin::BlockHash { genesis_block(BNetwork::Bitcoin).block_hash(); static ref TESTNET_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Testnet).block_hash(); - static ref TESTNET4_GENESIS: bitcoin::BlockHash = - BlockHash::from_str("00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043").unwrap(); + static ref TESTNET4_GENESIS: bitcoin::BlockHash = bitcoin::BlockHash::from_str( + "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043" + ) + .unwrap(); static ref REGTEST_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Regtest).block_hash(); static ref SIGNET_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Signet).block_hash(); } + #[cfg(not(feature = "liquid"))] match network { Network::Bitcoin => *BITCOIN_GENESIS, Network::Testnet => *TESTNET_GENESIS, @@ -153,6 +156,12 @@ pub fn bitcoin_genesis_hash(network: Network) -> bitcoin::BlockHash { Network::Regtest => *REGTEST_GENESIS, Network::Signet => *SIGNET_GENESIS, } + #[cfg(feature = "liquid")] + match network { + Network::Liquid => *BITCOIN_GENESIS, + Network::LiquidTestnet => *TESTNET_GENESIS, + Network::LiquidRegtest => *REGTEST_GENESIS, + } } #[cfg(feature = "liquid")] diff --git a/src/daemon.rs b/src/daemon.rs index b8bde690..f04045d0 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -295,6 +295,7 @@ pub struct Daemon { } impl Daemon { + #[allow(clippy::too_many_arguments)] pub fn new( daemon_dir: PathBuf, blocks_dir: PathBuf, diff --git a/src/elements/peg.rs b/src/elements/peg.rs index cd339e60..7956973f 100644 --- a/src/elements/peg.rs +++ b/src/elements/peg.rs @@ -19,7 +19,13 @@ pub fn get_pegout_data( let pegged_asset_id = network.pegged_asset()?; txout.pegout_data().filter(|pegout| { pegout.asset == Asset::Explicit(*pegged_asset_id) - && pegout.genesis_hash == bitcoin_genesis_hash(parent_network) + && pegout.genesis_hash + == bitcoin_genesis_hash(match parent_network { + BNetwork::Bitcoin => Network::Liquid, + BNetwork::Testnet => Network::LiquidTestnet, + BNetwork::Signet => return false, + BNetwork::Regtest => Network::LiquidRegtest, + }) }) } diff --git a/src/rest.rs b/src/rest.rs index 2e061b11..ce8bef96 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -1668,7 +1668,10 @@ fn address_to_scripthash(addr: &str, network: Network) -> Result Date: Tue, 7 May 2024 22:19:02 +0900 Subject: [PATCH 21/34] Fix Testnet4 addition --- src/chain.rs | 15 ++++++++++++--- src/daemon.rs | 1 + src/elements/peg.rs | 8 +++++++- src/rest.rs | 5 ++++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index 8abf9a4a..ccb2b353 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -128,7 +128,7 @@ impl Network { pub fn genesis_hash(network: Network) -> BlockHash { #[cfg(not(feature = "liquid"))] - return bitcoin_genesis_hash(network.into()); + return bitcoin_genesis_hash(network); #[cfg(feature = "liquid")] return liquid_genesis_hash(network); } @@ -139,13 +139,16 @@ pub fn bitcoin_genesis_hash(network: Network) -> bitcoin::BlockHash { genesis_block(BNetwork::Bitcoin).block_hash(); static ref TESTNET_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Testnet).block_hash(); - static ref TESTNET4_GENESIS: bitcoin::BlockHash = - BlockHash::from_str("00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043").unwrap(); + static ref TESTNET4_GENESIS: bitcoin::BlockHash = bitcoin::BlockHash::from_str( + "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043" + ) + .unwrap(); static ref REGTEST_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Regtest).block_hash(); static ref SIGNET_GENESIS: bitcoin::BlockHash = genesis_block(BNetwork::Signet).block_hash(); } + #[cfg(not(feature = "liquid"))] match network { Network::Bitcoin => *BITCOIN_GENESIS, Network::Testnet => *TESTNET_GENESIS, @@ -153,6 +156,12 @@ pub fn bitcoin_genesis_hash(network: Network) -> bitcoin::BlockHash { Network::Regtest => *REGTEST_GENESIS, Network::Signet => *SIGNET_GENESIS, } + #[cfg(feature = "liquid")] + match network { + Network::Liquid => *BITCOIN_GENESIS, + Network::LiquidTestnet => *TESTNET_GENESIS, + Network::LiquidRegtest => *REGTEST_GENESIS, + } } #[cfg(feature = "liquid")] diff --git a/src/daemon.rs b/src/daemon.rs index b8bde690..f04045d0 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -295,6 +295,7 @@ pub struct Daemon { } impl Daemon { + #[allow(clippy::too_many_arguments)] pub fn new( daemon_dir: PathBuf, blocks_dir: PathBuf, diff --git a/src/elements/peg.rs b/src/elements/peg.rs index cd339e60..7956973f 100644 --- a/src/elements/peg.rs +++ b/src/elements/peg.rs @@ -19,7 +19,13 @@ pub fn get_pegout_data( let pegged_asset_id = network.pegged_asset()?; txout.pegout_data().filter(|pegout| { pegout.asset == Asset::Explicit(*pegged_asset_id) - && pegout.genesis_hash == bitcoin_genesis_hash(parent_network) + && pegout.genesis_hash + == bitcoin_genesis_hash(match parent_network { + BNetwork::Bitcoin => Network::Liquid, + BNetwork::Testnet => Network::LiquidTestnet, + BNetwork::Signet => return false, + BNetwork::Regtest => Network::LiquidRegtest, + }) }) } diff --git a/src/rest.rs b/src/rest.rs index 2e061b11..ce8bef96 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -1668,7 +1668,10 @@ fn address_to_scripthash(addr: &str, network: Network) -> Result Date: Tue, 7 May 2024 14:48:01 +0900 Subject: [PATCH 22/34] Feat: Order history entries in the order they appear in block. --- src/elements/asset.rs | 11 +++++----- src/new_index/db.rs | 6 +++++- src/new_index/schema.rs | 46 ++++++++++++++++++++++++++++++++++------- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/elements/asset.rs b/src/elements/asset.rs index b6cd704f..6eeed728 100644 --- a/src/elements/asset.rs +++ b/src/elements/asset.rs @@ -174,17 +174,16 @@ pub struct BurningInfo { pub fn index_confirmed_tx_assets( tx: &Transaction, confirmed_height: u32, + tx_position: u32, network: Network, parent_network: BNetwork, rows: &mut Vec, ) { let (history, issuances) = index_tx_assets(tx, network, parent_network); - rows.extend( - history.into_iter().map(|(asset_id, info)| { - asset_history_row(&asset_id, confirmed_height, info).into_row() - }), - ); + rows.extend(history.into_iter().map(|(asset_id, info)| { + asset_history_row(&asset_id, confirmed_height, tx_position, info).into_row() + })); // the initial issuance is kept twice: once in the history index under I, // and once separately under i for asset lookup with some more associated metadata. @@ -336,12 +335,14 @@ fn index_tx_assets( fn asset_history_row( asset_id: &AssetId, confirmed_height: u32, + tx_position: u32, txinfo: TxHistoryInfo, ) -> TxHistoryRow { let key = TxHistoryKey { code: b'I', hash: full_hash(&asset_id.into_inner()[..]), confirmed_height, + tx_position, txinfo, }; TxHistoryRow { key } diff --git a/src/new_index/db.rs b/src/new_index/db.rs index 5e4b37a1..4fe9020c 100644 --- a/src/new_index/db.rs +++ b/src/new_index/db.rs @@ -5,7 +5,11 @@ use std::path::Path; use crate::config::Config; use crate::util::{bincode_util, Bytes}; -static DB_VERSION: u32 = 1; +/// Each version will break any running instance with a DB that has a differing version. +/// It will also break if light mode is enabled or disabled. +// 1 = Original DB (since fork from Blockstream) +// 2 = Add tx position to TxHistory rows and place Spending before Funding +static DB_VERSION: u32 = 2; #[derive(Debug, Eq, PartialEq)] pub struct DBRow { diff --git a/src/new_index/schema.rs b/src/new_index/schema.rs index 00ee3e89..bcee4793 100644 --- a/src/new_index/schema.rs +++ b/src/new_index/schema.rs @@ -1246,9 +1246,16 @@ fn index_blocks( .par_iter() // serialization is CPU-intensive .map(|b| { let mut rows = vec![]; - for tx in &b.block.txdata { + for (idx, tx) in b.block.txdata.iter().enumerate() { let height = b.entry.height() as u32; - index_transaction(tx, height, previous_txos_map, &mut rows, iconfig); + index_transaction( + tx, + height, + idx as u32, + previous_txos_map, + &mut rows, + iconfig, + ); } rows.push(BlockRow::new_done(full_hash(&b.entry.hash()[..])).into_row()); // mark block as "indexed" rows @@ -1261,6 +1268,7 @@ fn index_blocks( fn index_transaction( tx: &Transaction, confirmed_height: u32, + tx_position: u32, previous_txos_map: &HashMap, rows: &mut Vec, iconfig: &IndexerConfig, @@ -1276,6 +1284,7 @@ fn index_transaction( let history = TxHistoryRow::new( &txo.script_pubkey, confirmed_height, + tx_position, TxHistoryInfo::Funding(FundingInfo { txid, vout: txo_index as u16, @@ -1302,6 +1311,7 @@ fn index_transaction( let history = TxHistoryRow::new( &prev_txo.script_pubkey, confirmed_height, + tx_position, TxHistoryInfo::Spending(SpendingInfo { txid, vin: txi_index as u16, @@ -1326,6 +1336,7 @@ fn index_transaction( asset::index_confirmed_tx_assets( tx, confirmed_height, + tx_position, iconfig.network, iconfig.parent_network, rows, @@ -1567,8 +1578,11 @@ pub struct SpendingInfo { #[derive(Serialize, Deserialize, Debug)] #[cfg_attr(test, derive(PartialEq, Eq))] pub enum TxHistoryInfo { - Funding(FundingInfo), + // If a spend and a fund for the same scripthash + // occur in the same tx, spends should come first. + // This ordering comes from the enum order. Spending(SpendingInfo), + Funding(FundingInfo), #[cfg(feature = "liquid")] Issuing(asset::IssuingInfo), @@ -1602,6 +1616,7 @@ pub struct TxHistoryKey { pub code: u8, // H for script history or I for asset history (elements only) pub hash: FullHash, // either a scripthash (always on bitcoin) or an asset id (elements only) pub confirmed_height: u32, // MUST be serialized as big-endian (for correct scans). + pub tx_position: u32, // MUST be serialized as big-endian (for correct scans). Position in block. pub txinfo: TxHistoryInfo, } @@ -1610,11 +1625,17 @@ pub struct TxHistoryRow { } impl TxHistoryRow { - fn new(script: &Script, confirmed_height: u32, txinfo: TxHistoryInfo) -> Self { + fn new( + script: &Script, + confirmed_height: u32, + tx_position: u32, + txinfo: TxHistoryInfo, + ) -> Self { let key = TxHistoryKey { code: b'H', hash: compute_script_hash(script), confirmed_height, + tx_position, txinfo, }; TxHistoryRow { key } @@ -1845,8 +1866,10 @@ mod tests { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // confirmed_height 0, 0, 0, 2, + // tx_position + 0, 0, 0, 3, // TxHistoryInfo variant (Funding) - 0, 0, 0, 0, + 0, 0, 0, 1, // FundingInfo // txid 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -1865,7 +1888,8 @@ mod tests { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 2, - 0, 0, 0, 0, + 0, 0, 0, 3, + 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 3, @@ -1879,7 +1903,8 @@ mod tests { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 2, - 0, 0, 0, 1, + 0, 0, 0, 3, + 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 12, @@ -1895,7 +1920,8 @@ mod tests { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 2, - 0, 0, 0, 1, + 0, 0, 0, 3, + 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 12, @@ -1912,6 +1938,7 @@ mod tests { code: b'H', hash: [1; 32], confirmed_height: 2, + tx_position: 3, txinfo: super::TxHistoryInfo::Funding(super::FundingInfo { txid: [2; 32], vout: 3, @@ -1924,6 +1951,7 @@ mod tests { code: b'H', hash: [1; 32], confirmed_height: 2, + tx_position: 3, txinfo: super::TxHistoryInfo::Funding(super::FundingInfo { txid: [2; 32], vout: 3, @@ -1936,6 +1964,7 @@ mod tests { code: b'H', hash: [1; 32], confirmed_height: 2, + tx_position: 3, txinfo: super::TxHistoryInfo::Spending(super::SpendingInfo { txid: [18; 32], vin: 12, @@ -1950,6 +1979,7 @@ mod tests { code: b'H', hash: [1; 32], confirmed_height: 2, + tx_position: 3, txinfo: super::TxHistoryInfo::Spending(super::SpendingInfo { txid: [18; 32], vin: 12, From 3dab4c7eb96a3e831aba0a2f0198adb59a6bfc80 Mon Sep 17 00:00:00 2001 From: junderw Date: Fri, 10 May 2024 06:17:12 +0900 Subject: [PATCH 23/34] Use u16 for tx position --- src/elements/asset.rs | 4 ++-- src/new_index/schema.rs | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/elements/asset.rs b/src/elements/asset.rs index 6eeed728..01873a30 100644 --- a/src/elements/asset.rs +++ b/src/elements/asset.rs @@ -174,7 +174,7 @@ pub struct BurningInfo { pub fn index_confirmed_tx_assets( tx: &Transaction, confirmed_height: u32, - tx_position: u32, + tx_position: u16, network: Network, parent_network: BNetwork, rows: &mut Vec, @@ -335,7 +335,7 @@ fn index_tx_assets( fn asset_history_row( asset_id: &AssetId, confirmed_height: u32, - tx_position: u32, + tx_position: u16, txinfo: TxHistoryInfo, ) -> TxHistoryRow { let key = TxHistoryKey { diff --git a/src/new_index/schema.rs b/src/new_index/schema.rs index bcee4793..b0a7998e 100644 --- a/src/new_index/schema.rs +++ b/src/new_index/schema.rs @@ -1251,7 +1251,7 @@ fn index_blocks( index_transaction( tx, height, - idx as u32, + idx as u16, previous_txos_map, &mut rows, iconfig, @@ -1268,14 +1268,14 @@ fn index_blocks( fn index_transaction( tx: &Transaction, confirmed_height: u32, - tx_position: u32, + tx_position: u16, previous_txos_map: &HashMap, rows: &mut Vec, iconfig: &IndexerConfig, ) { // persist history index: - // H{funding-scripthash}{funding-height}F{funding-txid:vout} → "" - // H{funding-scripthash}{spending-height}S{spending-txid:vin}{funding-txid:vout} → "" + // H{funding-scripthash}{spending-height}{spending-block-pos}S{spending-txid:vin}{funding-txid:vout} → "" + // H{funding-scripthash}{funding-height}{funding-block-pos}F{funding-txid:vout} → "" // persist "edges" for fast is-this-TXO-spent check // S{funding-txid:vout}{spending-txid:vin} → "" let txid = full_hash(&tx.txid()[..]); @@ -1616,7 +1616,7 @@ pub struct TxHistoryKey { pub code: u8, // H for script history or I for asset history (elements only) pub hash: FullHash, // either a scripthash (always on bitcoin) or an asset id (elements only) pub confirmed_height: u32, // MUST be serialized as big-endian (for correct scans). - pub tx_position: u32, // MUST be serialized as big-endian (for correct scans). Position in block. + pub tx_position: u16, // MUST be serialized as big-endian (for correct scans). Position in block. pub txinfo: TxHistoryInfo, } @@ -1628,7 +1628,7 @@ impl TxHistoryRow { fn new( script: &Script, confirmed_height: u32, - tx_position: u32, + tx_position: u16, txinfo: TxHistoryInfo, ) -> Self { let key = TxHistoryKey { @@ -1867,7 +1867,7 @@ mod tests { // confirmed_height 0, 0, 0, 2, // tx_position - 0, 0, 0, 3, + 0, 3, // TxHistoryInfo variant (Funding) 0, 0, 0, 1, // FundingInfo @@ -1888,7 +1888,7 @@ mod tests { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 2, - 0, 0, 0, 3, + 0, 3, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -1903,7 +1903,7 @@ mod tests { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 2, - 0, 0, 0, 3, + 0, 3, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, @@ -1920,7 +1920,7 @@ mod tests { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 2, - 0, 0, 0, 3, + 0, 3, 0, 0, 0, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, From f7b77a3db0710ac97fd64916755c35c84de3f42e Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 13 May 2024 18:42:08 +0000 Subject: [PATCH 24/34] Fix tx order in address summaries --- src/new_index/schema.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/new_index/schema.rs b/src/new_index/schema.rs index b0a7998e..eba60588 100644 --- a/src/new_index/schema.rs +++ b/src/new_index/schema.rs @@ -533,23 +533,23 @@ impl ChainQuery { let rows = self .history_iter_scan_reverse(code, hash) .map(TxHistoryRow::from_row) - .map(|row| (row.get_txid(), row.key.txinfo)) - .skip_while(|(txid, _)| { + .map(|row| (row.get_txid(), row.key.txinfo, row.key.tx_position)) + .skip_while(|(txid, _, _)| { // skip until we reach the last_seen_txid last_seen_txid.map_or(false, |last_seen_txid| last_seen_txid != txid) }) - .skip_while(|(txid, _)| { + .skip_while(|(txid, _, _)| { // skip the last_seen_txid itself last_seen_txid.map_or(false, |last_seen_txid| last_seen_txid == txid) }) - .filter_map(|(txid, info)| { + .filter_map(|(txid, info, tx_position)| { self.tx_confirming_block(&txid) - .map(|b| (txid, info, b.height, b.time)) + .map(|b| (txid, info, b.height, b.time, tx_position)) }); // collate utxo funding/spending events by transaction let mut map: HashMap = HashMap::new(); - for (txid, info, height, time) in rows { + for (txid, info, height, time, tx_position) in rows { if !map.contains_key(&txid) && map.len() == limit { break; } @@ -565,6 +565,7 @@ impl ChainQuery { value: info.value.try_into().unwrap_or(0), height, time, + tx_position, }); } #[cfg(not(feature = "liquid"))] @@ -578,6 +579,7 @@ impl ChainQuery { value: 0_i64.saturating_sub(info.value.try_into().unwrap_or(0)), height, time, + tx_position, }); } #[cfg(feature = "liquid")] @@ -587,6 +589,7 @@ impl ChainQuery { value: 0, height, time, + tx_position, }); } #[cfg(feature = "liquid")] @@ -596,6 +599,7 @@ impl ChainQuery { value: 0, height, time, + tx_position, }); } #[cfg(feature = "liquid")] @@ -606,7 +610,11 @@ impl ChainQuery { let mut tx_summaries = map.into_values().collect::>(); tx_summaries.sort_by(|a, b| { if a.height == b.height { - a.value.cmp(&b.value) + if a.tx_position == b.tx_position { + a.value.cmp(&b.value) + } else { + b.tx_position.cmp(&a.tx_position) + } } else { b.height.cmp(&a.height) } @@ -1702,6 +1710,7 @@ pub struct TxHistorySummary { height: usize, value: i64, time: u32, + tx_position: u16, } #[derive(Serialize, Deserialize)] From c7c873c23427f55c9e2911fbb56d956356e3c22e Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 27 Jul 2024 16:22:03 +0900 Subject: [PATCH 25/34] Feat: batching of RPC requests. --- rust-toolchain | 2 +- src/electrum/server.rs | 29 ++++++++++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/rust-toolchain b/rust-toolchain index bfe79d0b..d456f745 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.70 +1.80 diff --git a/src/electrum/server.rs b/src/electrum/server.rs index ae427cd2..820c8961 100644 --- a/src/electrum/server.rs +++ b/src/electrum/server.rs @@ -521,18 +521,25 @@ impl Connection { trace!("RPC {:?}", msg); match msg { Message::Request(line) => { - let cmd: Value = from_str(&line).chain_err(|| "invalid JSON format")?; - let reply = match ( - cmd.get("method"), - cmd.get("params").unwrap_or(&empty_params), - cmd.get("id"), - ) { - (Some(Value::String(method)), Value::Array(params), Some(id)) => { - self.handle_command(method, params, id)? - } - _ => bail!("invalid command: {}", cmd), + let cmd: [Value; 1] = [from_str(&line).chain_err(|| "invalid JSON format")?]; + let cmds = match &cmd[..] { + [Value::Array(arr)] => arr, + x => x, }; - self.send_values(&[reply])? + let mut replies = Vec::with_capacity(cmds.len()); + for cmd in cmds { + replies.push(match ( + cmd.get("method"), + cmd.get("params").unwrap_or(&empty_params), + cmd.get("id"), + ) { + (Some(Value::String(method)), Value::Array(params), Some(id)) => { + self.handle_command(method, params, id)? + } + _ => bail!("invalid command: {}", cmd), + }); + } + self.send_values(&replies)? } Message::PeriodicUpdate => { let values = self From c98f83da03ea1ba5dff908e2986769e865d5a9f4 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 27 Jul 2024 16:32:06 +0900 Subject: [PATCH 26/34] Fix clippy --- src/electrum/client.rs | 1 - src/electrum/server.rs | 24 ++++++++++++------------ src/elements/asset.rs | 7 ++----- src/new_index/mempool.rs | 7 ++----- src/new_index/schema.rs | 2 +- src/util/block.rs | 2 +- src/util/transaction.rs | 18 ++++++------------ 7 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/electrum/client.rs b/src/electrum/client.rs index 04d6ffba..02cc2d15 100644 --- a/src/electrum/client.rs +++ b/src/electrum/client.rs @@ -3,7 +3,6 @@ use std::convert::TryFrom; use bitcoin::hashes::Hash; pub use electrum_client::client::Client; -pub use electrum_client::Error as ElectrumError; pub use electrum_client::ServerFeaturesRes; use crate::chain::BlockHash; diff --git a/src/electrum/server.rs b/src/electrum/server.rs index 820c8961..8970444b 100644 --- a/src/electrum/server.rs +++ b/src/electrum/server.rs @@ -189,7 +189,7 @@ impl Connection { .chain_err(|| "discovery is disabled")?; let features = params - .get(0) + .first() .chain_err(|| "missing features param")? .clone(); let features = serde_json::from_value(features).chain_err(|| "invalid features")?; @@ -203,7 +203,7 @@ impl Connection { } fn blockchain_block_header(&self, params: &[Value]) -> Result { - let height = usize_from_value(params.get(0), "height")?; + let height = usize_from_value(params.first(), "height")?; let cp_height = usize_from_value_or(params.get(1), "cp_height", 0)?; let raw_header_hex: String = self @@ -226,7 +226,7 @@ impl Connection { } fn blockchain_block_headers(&self, params: &[Value]) -> Result { - let start_height = usize_from_value(params.get(0), "start_height")?; + let start_height = usize_from_value(params.first(), "start_height")?; let count = MAX_HEADERS.min(usize_from_value(params.get(1), "count")?); let cp_height = usize_from_value_or(params.get(2), "cp_height", 0)?; let heights: Vec = (start_height..(start_height + count)).collect(); @@ -261,7 +261,7 @@ impl Connection { } fn blockchain_estimatefee(&self, params: &[Value]) -> Result { - let conf_target = usize_from_value(params.get(0), "blocks_count")?; + let conf_target = usize_from_value(params.first(), "blocks_count")?; let fee_rate = self .query .estimate_fee(conf_target as u16) @@ -277,7 +277,7 @@ impl Connection { } fn blockchain_scripthash_subscribe(&mut self, params: &[Value]) -> Result { - let script_hash = hash_from_value(params.get(0)).chain_err(|| "bad script_hash")?; + let script_hash = hash_from_value(params.first()).chain_err(|| "bad script_hash")?; let history_txids = get_history(&self.query, &script_hash[..], self.txs_limit)?; let status_hash = get_status_hash(history_txids, &self.query) @@ -295,7 +295,7 @@ impl Connection { #[cfg(not(feature = "liquid"))] fn blockchain_scripthash_get_balance(&self, params: &[Value]) -> Result { - let script_hash = hash_from_value(params.get(0)).chain_err(|| "bad script_hash")?; + let script_hash = hash_from_value(params.first()).chain_err(|| "bad script_hash")?; let (chain_stats, mempool_stats) = self.query.stats(&script_hash[..]); Ok(json!({ @@ -305,7 +305,7 @@ impl Connection { } fn blockchain_scripthash_get_history(&self, params: &[Value]) -> Result { - let script_hash = hash_from_value(params.get(0)).chain_err(|| "bad script_hash")?; + let script_hash = hash_from_value(params.first()).chain_err(|| "bad script_hash")?; let history_txids = get_history(&self.query, &script_hash[..], self.txs_limit)?; Ok(json!(history_txids @@ -323,7 +323,7 @@ impl Connection { } fn blockchain_scripthash_listunspent(&self, params: &[Value]) -> Result { - let script_hash = hash_from_value(params.get(0)).chain_err(|| "bad script_hash")?; + let script_hash = hash_from_value(params.first()).chain_err(|| "bad script_hash")?; let utxos = self.query.utxo(&script_hash[..])?; let to_json = |utxo: Utxo| { @@ -351,7 +351,7 @@ impl Connection { } fn blockchain_transaction_broadcast(&self, params: &[Value]) -> Result { - let tx = params.get(0).chain_err(|| "missing tx")?; + let tx = params.first().chain_err(|| "missing tx")?; let tx = tx.as_str().chain_err(|| "non-string tx")?.to_string(); let txid = self.query.broadcast_raw(&tx)?; if let Err(e) = self.chan.sender().try_send(Message::PeriodicUpdate) { @@ -361,7 +361,7 @@ impl Connection { } fn blockchain_transaction_get(&self, params: &[Value]) -> Result { - let tx_hash = Txid::from(hash_from_value(params.get(0)).chain_err(|| "bad tx_hash")?); + let tx_hash = Txid::from(hash_from_value(params.first()).chain_err(|| "bad tx_hash")?); let verbose = match params.get(1) { Some(value) => value.as_bool().chain_err(|| "non-bool verbose value")?, None => false, @@ -380,7 +380,7 @@ impl Connection { } fn blockchain_transaction_get_merkle(&self, params: &[Value]) -> Result { - let txid = Txid::from(hash_from_value(params.get(0)).chain_err(|| "bad tx_hash")?); + let txid = Txid::from(hash_from_value(params.first()).chain_err(|| "bad tx_hash")?); let height = usize_from_value(params.get(1), "height")?; let blockid = self .query @@ -399,7 +399,7 @@ impl Connection { } fn blockchain_transaction_id_from_pos(&self, params: &[Value]) -> Result { - let height = usize_from_value(params.get(0), "height")?; + let height = usize_from_value(params.first(), "height")?; let tx_pos = usize_from_value(params.get(1), "tx_pos")?; let want_merkle = bool_from_value_or(params.get(2), "merkle", false)?; diff --git a/src/elements/asset.rs b/src/elements/asset.rs index 01873a30..53ccd6ba 100644 --- a/src/elements/asset.rs +++ b/src/elements/asset.rs @@ -204,10 +204,7 @@ pub fn index_mempool_tx_assets( ) { let (history, issuances) = index_tx_assets(tx, network, parent_network); for (asset_id, info) in history { - asset_history - .entry(asset_id) - .or_insert_with(Vec::new) - .push(info); + asset_history.entry(asset_id).or_default().push(info); } for (asset_id, issuance) in issuances { asset_issuance.insert(asset_id, issuance); @@ -386,7 +383,7 @@ pub fn lookup_asset( Ok(if let Some(row) = row { let reissuance_token = parse_asset_id(&row.reissuance_token); - let meta = meta.map(Clone::clone).or_else(|| match registry { + let meta = meta.cloned().or_else(|| match registry { Some(AssetRegistryLock::RwLock(rwlock)) => { rwlock.read().unwrap().get(asset_id).cloned() } diff --git a/src/new_index/mempool.rs b/src/new_index/mempool.rs index 8a94962f..8836334f 100644 --- a/src/new_index/mempool.rs +++ b/src/new_index/mempool.rs @@ -407,7 +407,7 @@ impl Mempool { } pub fn add_by_txid(&mut self, daemon: &Daemon, txid: &Txid) -> Result<()> { - if self.txstore.get(txid).is_none() { + if !self.txstore.contains_key(txid) { if let Ok(tx) = daemon.getmempooltx(txid) { if self.add(vec![tx]) == 0 { return Err(format!( @@ -524,10 +524,7 @@ impl Mempool { // Index funding/spending history entries and spend edges for (scripthash, entry) in funding.chain(spending) { - self.history - .entry(scripthash) - .or_insert_with(Vec::new) - .push(entry); + self.history.entry(scripthash).or_default().push(entry); } for (i, txi) in tx.input.iter().enumerate() { self.edges.insert(txi.previous_output, (txid, i as u32)); diff --git a/src/new_index/schema.rs b/src/new_index/schema.rs index eba60588..ef46c195 100644 --- a/src/new_index/schema.rs +++ b/src/new_index/schema.rs @@ -1654,7 +1654,7 @@ impl TxHistoryRow { } fn prefix_end(code: u8, hash: &[u8]) -> Bytes { - bincode_util::serialize_big(&(code, full_hash(hash), std::u32::MAX)).unwrap() + bincode_util::serialize_big(&(code, full_hash(hash), u32::MAX)).unwrap() } fn prefix_height(code: u8, hash: &[u8], height: u32) -> Bytes { diff --git a/src/util/block.rs b/src/util/block.rs index 7292efcc..66d5a5c5 100644 --- a/src/util/block.rs +++ b/src/util/block.rs @@ -238,7 +238,7 @@ impl HeaderList { // Use the timestamp as the mtp of the genesis block. // Matches bitcoind's behaviour: bitcoin-cli getblock `bitcoin-cli getblockhash 0` | jq '.time == .mediantime' if height == 0 { - self.headers.get(0).unwrap().header.time + self.headers.first().unwrap().header.time } else if height > self.len() - 1 { 0 } else { diff --git a/src/util/transaction.rs b/src/util/transaction.rs index c9ff29ae..247cc67e 100644 --- a/src/util/transaction.rs +++ b/src/util/transaction.rs @@ -340,18 +340,12 @@ pub(super) mod sigops { let last_witness = witness.last(); match (witness_version, witness_program.len()) { (0, 20) => 1, - (0, 32) => { - if let Some(n) = last_witness - .map(|sl| sl.iter().map(|v| Ok(*v))) - .map(script::Script::from_byte_iter) - // I only return Ok 2 lines up, so there is no way to error - .map(|s| count_sigops(&s.unwrap(), true)) - { - n - } else { - 0 - } - } + (0, 32) => last_witness + .map(|sl| sl.iter().map(|v| Ok(*v))) + .map(script::Script::from_byte_iter) + // I only return Ok 2 lines up, so there is no way to error + .map(|s| count_sigops(&s.unwrap(), true)) + .unwrap_or_default(), _ => 0, } } From a1e41800f9189d4b6e84a4f47ab959d6df8dafec Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 27 Jul 2024 16:52:40 +0900 Subject: [PATCH 27/34] Return each error and success separately --- src/electrum/server.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/electrum/server.rs b/src/electrum/server.rs index 8970444b..f3238439 100644 --- a/src/electrum/server.rs +++ b/src/electrum/server.rs @@ -534,9 +534,26 @@ impl Connection { cmd.get("id"), ) { (Some(Value::String(method)), Value::Array(params), Some(id)) => { - self.handle_command(method, params, id)? + self.handle_command(method, params, id) + .unwrap_or_else(|err| { + json!({ + "error": { + "code": 1, + "message": format!("{method} RPC error: {err}") + }, + "id": id, + "jsonrpc": "2.0" + }) + }) } - _ => bail!("invalid command: {}", cmd), + _ => json!({ + "error": { + "code": -32600, + "message": format!("invalid request: {cmd}") + }, + "id": null, + "jsonrpc": "2.0" + }), }); } self.send_values(&replies)? From 0c993501740476e4eaaf7dd6b1f2c41db203e960 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 27 Jul 2024 17:21:21 +0900 Subject: [PATCH 28/34] Return response when invalid JSON --- src/electrum/server.rs | 72 +++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/electrum/server.rs b/src/electrum/server.rs index f3238439..06e152f9 100644 --- a/src/electrum/server.rs +++ b/src/electrum/server.rs @@ -521,42 +521,56 @@ impl Connection { trace!("RPC {:?}", msg); match msg { Message::Request(line) => { - let cmd: [Value; 1] = [from_str(&line).chain_err(|| "invalid JSON format")?]; - let cmds = match &cmd[..] { - [Value::Array(arr)] => arr, - x => x, - }; - let mut replies = Vec::with_capacity(cmds.len()); - for cmd in cmds { - replies.push(match ( - cmd.get("method"), - cmd.get("params").unwrap_or(&empty_params), - cmd.get("id"), - ) { - (Some(Value::String(method)), Value::Array(params), Some(id)) => { - self.handle_command(method, params, id) - .unwrap_or_else(|err| { - json!({ - "error": { - "code": 1, - "message": format!("{method} RPC error: {err}") - }, - "id": id, - "jsonrpc": "2.0" + if let Ok(json_value) = from_str(&line) { + let json_value: [Value; 1] = [json_value]; + let cmds = match &json_value[..] { + [Value::Array(arr)] => arr, + x => x, + }; + let mut replies = Vec::with_capacity(cmds.len()); + for cmd in cmds { + replies.push(match ( + cmd.get("method"), + cmd.get("params").unwrap_or(&empty_params), + cmd.get("id"), + ) { + (Some(Value::String(method)), Value::Array(params), Some(id)) => { + self.handle_command(method, params, id) + .unwrap_or_else(|err| { + json!({ + "error": { + "code": 1, + "message": format!("{method} RPC error: {err}") + }, + "id": id, + "jsonrpc": "2.0" + }) }) - }) - } - _ => json!({ + } + _ => json!({ + "error": { + "code": -32600, + "message": format!("invalid request: {cmd}") + }, + "id": null, + "jsonrpc": "2.0" + }), + }); + } + self.send_values(&replies)? + } else { + // serde_json was unable to parse + self.send_values(&[ + json!({ "error": { "code": -32600, - "message": format!("invalid request: {cmd}") + "message": format!("invalid request: {line}") }, "id": null, "jsonrpc": "2.0" - }), - }); + }) + ])? } - self.send_values(&replies)? } Message::PeriodicUpdate => { let values = self From 50a6cd7ad4ba1a4c3ca51ee6ceb8e4839ef5950a Mon Sep 17 00:00:00 2001 From: junderw Date: Sun, 28 Jul 2024 14:34:15 +0900 Subject: [PATCH 29/34] Refactor --- src/electrum/server.rs | 107 +++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/src/electrum/server.rs b/src/electrum/server.rs index 06e152f9..d1302b23 100644 --- a/src/electrum/server.rs +++ b/src/electrum/server.rs @@ -513,7 +513,6 @@ impl Connection { } fn handle_replies(&mut self, shutdown: crossbeam_channel::Receiver<()>) -> Result<()> { - let empty_params = json!([]); loop { crossbeam_channel::select! { recv(self.chan.receiver()) -> msg => { @@ -521,56 +520,8 @@ impl Connection { trace!("RPC {:?}", msg); match msg { Message::Request(line) => { - if let Ok(json_value) = from_str(&line) { - let json_value: [Value; 1] = [json_value]; - let cmds = match &json_value[..] { - [Value::Array(arr)] => arr, - x => x, - }; - let mut replies = Vec::with_capacity(cmds.len()); - for cmd in cmds { - replies.push(match ( - cmd.get("method"), - cmd.get("params").unwrap_or(&empty_params), - cmd.get("id"), - ) { - (Some(Value::String(method)), Value::Array(params), Some(id)) => { - self.handle_command(method, params, id) - .unwrap_or_else(|err| { - json!({ - "error": { - "code": 1, - "message": format!("{method} RPC error: {err}") - }, - "id": id, - "jsonrpc": "2.0" - }) - }) - } - _ => json!({ - "error": { - "code": -32600, - "message": format!("invalid request: {cmd}") - }, - "id": null, - "jsonrpc": "2.0" - }), - }); - } - self.send_values(&replies)? - } else { - // serde_json was unable to parse - self.send_values(&[ - json!({ - "error": { - "code": -32600, - "message": format!("invalid request: {line}") - }, - "id": null, - "jsonrpc": "2.0" - }) - ])? - } + let result = self.handle_line(&line); + self.send_values(&[result])? } Message::PeriodicUpdate => { let values = self @@ -592,6 +543,48 @@ impl Connection { } } + #[inline] + fn handle_line(&mut self, line: &String) -> Value { + if let Ok(json_value) = from_str(line) { + match json_value { + Value::Array(mut arr) => { + for cmd in &mut arr { + // Replace each cmd with its response in-memory. + *cmd = self.handle_value(cmd); + } + Value::Array(arr) + } + cmd => self.handle_value(&cmd), + } + } else { + // serde_json was unable to parse + invalid_json_rpc(line) + } + } + + #[inline] + fn handle_value(&mut self, value: &Value) -> Value { + match ( + value.get("method"), + value.get("params").unwrap_or(&json!([])), + value.get("id"), + ) { + (Some(Value::String(method)), Value::Array(params), Some(id)) => self + .handle_command(method, params, id) + .unwrap_or_else(|err| { + json!({ + "error": { + "code": 1, + "message": format!("{method} RPC error: {err}") + }, + "id": id, + "jsonrpc": "2.0" + }) + }), + _ => invalid_json_rpc(value), + } + } + fn handle_requests( mut reader: BufReader, tx: crossbeam_channel::Sender, @@ -667,6 +660,18 @@ impl Connection { } } +#[inline] +fn invalid_json_rpc(input: impl core::fmt::Display) -> Value { + json!({ + "error": { + "code": -32600, + "message": format!("invalid request: {input}") + }, + "id": null, + "jsonrpc": "2.0" + }) +} + fn get_history( query: &Query, scripthash: &[u8], From ae3310c0693ca057e14d6510241f1533cd20b3e4 Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 5 Aug 2024 17:58:45 -0400 Subject: [PATCH 30/34] Bump version string to v3.0.0-beta --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db6d368a..d4f1f0fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -822,7 +822,7 @@ dependencies = [ [[package]] name = "mempool-electrs" -version = "3.0.0-dev" +version = "3.0.0-beta" dependencies = [ "arrayref", "base64 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index 58c5b579..f5a7d4d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mempool-electrs" -version = "3.0.0-dev" +version = "3.0.0-beta" authors = [ "Roman Zeyde ", "Nadav Ivgi ", From 570071a1617c6730394dde095563e7e34b4d579f Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 30 Aug 2024 20:11:47 +0000 Subject: [PATCH 31/34] Add support for anchor output type --- src/rest.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/rest.rs b/src/rest.rs index ce8bef96..82dd3e68 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -355,6 +355,8 @@ impl TxOutValue { "v0_p2wsh" } else if is_v1_p2tr(script) { "v1_p2tr" + } else if is_anchor(script) { + "anchor" } else if script.is_provably_unspendable() { "provably_unspendable" } else if is_bare_multisig(script) { @@ -405,6 +407,15 @@ fn is_bare_multisig(script: &Script) -> bool { && script[0] <= script[len - 2] } +fn is_anchor(script: &Script) -> bool { + let len = script.len(); + len == 4 + && script[0] == opcodes::all::OP_PUSHNUM_1.into_u8() + && script[1] == opcodes::all::OP_PUSHBYTES_2.into_u8() + && script[2] == 0x4e + && script[3] == 0x73 +} + #[derive(Serialize)] struct UtxoValue { txid: Txid, From 1a21b532222a9fde064f80ff8e83d44b9d053e84 Mon Sep 17 00:00:00 2001 From: junderw Date: Thu, 5 Sep 2024 00:07:10 +0900 Subject: [PATCH 32/34] Handle popular-script when lack of H columns --- src/bin/popular-scripts.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/bin/popular-scripts.rs b/src/bin/popular-scripts.rs index 9005f5b8..db928d06 100644 --- a/src/bin/popular-scripts.rs +++ b/src/bin/popular-scripts.rs @@ -95,8 +95,7 @@ fn run_iterator( "Thread ({thread_id:?}) Seeking DB to beginning of tx histories for b'H' + {}", hex::encode([first_byte]) ); - // H = 72 - let mut compare_vec: Vec = vec![72, first_byte]; + let mut compare_vec: Vec = vec![b'H', first_byte]; iter.seek(&compare_vec); // Seek to beginning of our section // Insert the byte of the next section for comparing @@ -122,7 +121,7 @@ fn run_iterator( while iter.valid() { let key = iter.key().unwrap(); - if is_finished(key) { + if key.is_empty() || key[0] != b'H' || is_finished(key) { // We have left the txhistory section, // but we need to check the final scripthash send_if_popular( From e3eeaa55e83031ce66cf057d4b79c700ffa6539f Mon Sep 17 00:00:00 2001 From: wiz Date: Thu, 5 Sep 2024 13:59:22 +0900 Subject: [PATCH 33/34] Bump version to 3.0.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4f1f0fe..8a668d48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -822,7 +822,7 @@ dependencies = [ [[package]] name = "mempool-electrs" -version = "3.0.0-beta" +version = "3.0.0" dependencies = [ "arrayref", "base64 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index f5a7d4d8..84129dc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mempool-electrs" -version = "3.0.0-beta" +version = "3.0.0" authors = [ "Roman Zeyde ", "Nadav Ivgi ", From 055aba1e8d0d8f4cf7080014c89e3b3055e3d0b7 Mon Sep 17 00:00:00 2001 From: wiz Date: Thu, 5 Sep 2024 14:00:16 +0900 Subject: [PATCH 34/34] Bump version to 3.1.0-dev --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a668d48..1243f4ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -822,7 +822,7 @@ dependencies = [ [[package]] name = "mempool-electrs" -version = "3.0.0" +version = "3.1.0-dev" dependencies = [ "arrayref", "base64 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index 84129dc7..f41a588a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mempool-electrs" -version = "3.0.0" +version = "3.1.0-dev" authors = [ "Roman Zeyde ", "Nadav Ivgi ",