From 0a6392e6f58f211b1793fede1febbd3499d01542 Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Thu, 15 Feb 2024 12:47:55 +0200 Subject: [PATCH 1/2] Fix input/output indexes to be represented as a u32 Bitcoin's consensus rules allows for indexes larger than u16, which would result in an overflow prior to this fix. This recently happened with a transaction on testnet: https://blockstream.info/testnet/tx/ca3b75556430e1adf9e9790bce9c73a3d9afdb42305588e64c65b258c06c05c9 Based on @junderw's https://github.com/mempool/electrs/pull/75. Thanks! This change requires a full database reindex and bumps the DB_VERSION. --- src/elements/asset.rs | 22 +++++++++++----------- src/elements/peg.rs | 4 ++-- src/new_index/db.rs | 2 +- src/new_index/mempool.rs | 8 ++++---- src/new_index/schema.rs | 38 +++++++++++++++++++------------------- src/util/transaction.rs | 2 +- 6 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/elements/asset.rs b/src/elements/asset.rs index 149ebd703..d47b9bc4c 100644 --- a/src/elements/asset.rs +++ b/src/elements/asset.rs @@ -74,9 +74,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, } @@ -108,7 +108,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, @@ -157,7 +157,7 @@ impl LiquidAsset { #[derive(Serialize, Deserialize, Debug)] pub struct IssuingInfo { pub txid: FullHash, - pub vin: u16, + pub vin: u32, pub is_reissuance: bool, // None for blinded issuances pub issued_amount: Option, @@ -167,7 +167,7 @@ pub struct IssuingInfo { #[derive(Serialize, Deserialize, Debug)] 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: value, }), )); @@ -277,7 +277,7 @@ fn index_tx_assets( pegin.asset, 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, @@ -321,9 +321,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 184d474cf..78bc0dde5 100644 --- a/src/elements/peg.rs +++ b/src/elements/peg.rs @@ -52,7 +52,7 @@ impl PegoutValue { #[derive(Serialize, Deserialize, Debug)] pub struct PeginInfo { pub txid: FullHash, - pub vin: u16, + pub vin: u32, pub value: u64, } @@ -60,6 +60,6 @@ pub struct PeginInfo { #[derive(Serialize, Deserialize, Debug)] pub struct PegoutInfo { pub txid: FullHash, - pub vout: u16, + pub vout: u32, pub value: u64, } diff --git a/src/new_index/db.rs b/src/new_index/db.rs index 3646890b8..c93ac1e12 100644 --- a/src/new_index/db.rs +++ b/src/new_index/db.rs @@ -11,7 +11,7 @@ use crate::config::Config; use crate::new_index::db_metrics::RocksDbMetrics; use crate::util::{bincode, spawn_thread, Bytes}; -static DB_VERSION: u32 = 2; +static DB_VERSION: u32 = 3; #[derive(Debug, Eq, PartialEq)] pub struct DBRow { diff --git a/src/new_index/mempool.rs b/src/new_index/mempool.rs index a28fa193d..b82fbe2aa 100644 --- a/src/new_index/mempool.rs +++ b/src/new_index/mempool.rs @@ -198,7 +198,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, @@ -386,9 +386,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.amount_value(), }), ) @@ -407,7 +407,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.amount_value(), }), ) diff --git a/src/new_index/schema.rs b/src/new_index/schema.rs index 797dcd57d..cccb8bbc3 100644 --- a/src/new_index/schema.rs +++ b/src/new_index/schema.rs @@ -1089,7 +1089,7 @@ impl ChainQuery { outpoint, SpendingInput { txid: deserialize(&edge.spending_txid).expect("failed to parse Txid"), - vin: edge.spending_vin as u32, + vin: edge.spending_vin, confirmed: Some(header.into()), }, )) @@ -1327,7 +1327,7 @@ fn index_transaction( confirmed_height, TxHistoryInfo::Funding(FundingInfo { txid, - vout: txo_index as u16, + vout: txo_index as u32, value: txo.value.amount_value(), }), ); @@ -1347,9 +1347,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.amount_value(), }), ); @@ -1357,9 +1357,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, confirmed_height, ); rows.push(edge.into_row()); @@ -1478,7 +1478,7 @@ impl TxConfRow { struct TxOutKey { code: u8, txid: FullHash, - vout: u16, + vout: u32, } struct TxOutRow { @@ -1492,7 +1492,7 @@ impl TxOutRow { key: TxOutKey { code: b'O', txid: *txid, - vout: vout as u16, + vout: vout as u32, }, value: serialize(txout), } @@ -1501,7 +1501,7 @@ impl TxOutRow { bincode::serialize_little(&TxOutKey { code: b'O', txid: full_hash(&outpoint.txid[..]), - vout: outpoint.vout as u16, + vout: outpoint.vout, }) .unwrap() } @@ -1591,16 +1591,16 @@ impl BlockRow { #[derive(Serialize, Deserialize, Debug)] pub struct FundingInfo { pub txid: FullHash, - pub vout: u16, + pub vout: u32, pub value: Value, } #[derive(Serialize, Deserialize, Debug)] 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, } @@ -1697,11 +1697,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(_) @@ -1716,13 +1716,13 @@ impl TxHistoryInfo { pub struct TxEdgeKey { code: u8, funding_txid: FullHash, - funding_vout: u16, + funding_vout: u32, } #[derive(Serialize, Deserialize)] pub struct TxEdgeValue { spending_txid: FullHash, - spending_vin: u16, + spending_vin: u32, spending_height: u32, } @@ -1734,9 +1734,9 @@ pub struct TxEdgeRow { impl TxEdgeRow { pub fn new( funding_txid: FullHash, - funding_vout: u16, + funding_vout: u32, spending_txid: FullHash, - spending_vin: u16, + spending_vin: u32, spending_height: u32, ) -> Self { let key = TxEdgeKey { @@ -1753,7 +1753,7 @@ impl TxEdgeRow { } fn key(outpoint: &OutPoint) -> Bytes { - bincode::serialize_little(&(b'S', full_hash(&outpoint.txid[..]), outpoint.vout as u16)) + bincode::serialize_little(&(b'S', full_hash(&outpoint.txid[..]), outpoint.vout)) .unwrap() } diff --git a/src/util/transaction.rs b/src/util/transaction.rs index c63e070e0..e8fbb4c2d 100644 --- a/src/util/transaction.rs +++ b/src/util/transaction.rs @@ -64,7 +64,7 @@ pub fn optional_value_for_newer_blocks( #[derive(Serialize, Deserialize)] pub struct TxInput { pub txid: Txid, - pub vin: u16, + pub vin: u32, } pub fn is_coinbase(txin: &TxIn) -> bool { From 9e7b13e58364373fb0d8bcabfa5f851d49cbcecd Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Thu, 12 Mar 2026 21:20:56 +0200 Subject: [PATCH 2/2] Update the v1->v2 DB migration to used fixed TxEdge V2 structs and not the main TxEdge structs, which were updated for the V3 schema. --- src/bin/db-migrate-v1-to-v2.rs | 52 ++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/bin/db-migrate-v1-to-v2.rs b/src/bin/db-migrate-v1-to-v2.rs index 9289f1d77..606583c13 100644 --- a/src/bin/db-migrate-v1-to-v2.rs +++ b/src/bin/db-migrate-v1-to-v2.rs @@ -11,8 +11,7 @@ use bitcoin::hashes::Hash; use electrs::chain::{BlockHash, Txid}; use electrs::new_index::db::DBFlush; use electrs::new_index::schema::{ - lookup_confirmations, FullHash, Store, TxConfRow as V2TxConfRow, TxEdgeRow as V2TxEdgeRow, - TxHistoryKey, + lookup_confirmations, FullHash, Store, TxConfRow as V2TxConfRow, TxHistoryKey, }; use electrs::util::bincode::{deserialize_big, deserialize_little, serialize_little}; use electrs::{config::Config, metrics::Metrics}; @@ -246,6 +245,55 @@ struct V1TxEdgeKey { spending_vin: u16, } +#[derive(Debug, serde::Serialize)] +struct V2TxEdgeKey { + code: u8, + funding_txid: FullHash, + funding_vout: u16, +} + +#[derive(Debug, serde::Serialize)] +struct V2TxEdgeValue { + spending_txid: FullHash, + spending_vin: u16, + spending_height: u32, +} + +struct V2TxEdgeRow { + key: V2TxEdgeKey, + value: V2TxEdgeValue, +} + +impl V2TxEdgeRow { + fn new( + funding_txid: FullHash, + funding_vout: u16, + spending_txid: FullHash, + spending_vin: u16, + spending_height: u32, + ) -> Self { + Self { + key: V2TxEdgeKey { + code: b'S', + funding_txid, + funding_vout, + }, + value: V2TxEdgeValue { + spending_txid, + spending_vin, + spending_height, + }, + } + } + + fn into_row(self) -> electrs::new_index::DBRow { + electrs::new_index::DBRow { + key: serialize_little(&self.key).unwrap(), + value: serialize_little(&self.value).unwrap(), + } + } +} + /* use bitcoin::hex::DisplayHex;