Skip to content

Commit

Permalink
Introduce subcoin-utxo-snapshot
Browse files Browse the repository at this point in the history
  • Loading branch information
liuchengxu committed Nov 12, 2024
1 parent 597285f commit 1e59a2d
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 61 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ members = [
"crates/subcoin-runtime-primitives",
"crates/subcoin-service",
"crates/subcoin-test-service",
"crates/subcoin-utxo-snapshot",
]

default-members = ["crates/subcoin-node"]
Expand Down Expand Up @@ -119,6 +120,7 @@ subcoin-runtime = { path = "crates/subcoin-runtime" }
subcoin-runtime-primitives = { path = "crates/subcoin-runtime-primitives", default-features = false }
subcoin-service = { path = "crates/subcoin-service" }
subcoin-test-service = { path = "crates/subcoin-test-service" }
subcoin-utxo-snapshot = { path = "crates/subcoin-utxo-snapshot" }

[profile.release]
panic = "abort"
Expand Down
11 changes: 11 additions & 0 deletions crates/subcoin-crypto/src/muhash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::chacha20_block;
use num_bigint::{BigUint, ToBigUint};
use num_traits::One;
use sha2::{Digest, Sha256};
use std::fmt::Write;

// Function to hash a 32-byte array into a 3072-bit number using 6 ChaCha20 operations
fn data_to_num3072(data: &[u8; 32]) -> BigUint {
Expand Down Expand Up @@ -67,6 +68,16 @@ impl MuHash3072 {
bytes384.resize(384, 0); // Ensure it is exactly 384 bytes
Sha256::digest(&bytes384).to_vec()
}

/// Returns the value of `muhash` in Bitcoin Core's dumptxoutset output.
pub fn txoutset_muhash(&self) -> String {
let finalized = self.digest();

finalized.iter().rev().fold(String::new(), |mut output, b| {
let _ = write!(output, "{b:02x}");
output
})
}
}

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions crates/subcoin-node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ subcoin-primitives = { workspace = true }
subcoin-rpc = { workspace = true }
subcoin-runtime = { workspace = true }
subcoin-service = { workspace = true }
subcoin-utxo-snapshot = { workspace = true }
substrate-frame-rpc-system = { workspace = true }
substrate-prometheus-endpoint = { workspace = true }
tracing = { workspace = true }
Expand Down
85 changes: 25 additions & 60 deletions crates/subcoin-node/src/commands/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use sc_client_api::{HeaderBackend, StorageProvider};
use serde::Serialize;
use sp_core::storage::StorageKey;
use sp_core::Decode;
use std::fmt::Write as _;
use std::fs::File;
use std::io::{Stdout, Write};
use std::path::PathBuf;
Expand All @@ -15,6 +14,7 @@ use std::time::{Duration, Instant};
use subcoin_primitives::runtime::Coin;
use subcoin_primitives::{BackendExt, CoinStorageKey};
use subcoin_service::FullClient;
use subcoin_utxo_snapshot::UtxoSnapshotGenerator;

const FINAL_STORAGE_PREFIX_LEN: usize = 32;

Expand Down Expand Up @@ -284,30 +284,6 @@ fn fetch_utxo_set_at(
))
}

// Equivalent function in Rust for serializing an OutPoint and Coin
//
// https://github.com/bitcoin/bitcoin/blob/6f9db1ebcab4064065ccd787161bf2b87e03cc1f/src/kernel/coinstats.cpp#L51
fn tx_out_ser(outpoint: bitcoin::OutPoint, coin: &Coin) -> bitcoin::io::Result<Vec<u8>> {
let mut data = Vec::new();

// Serialize the OutPoint (txid and vout)
outpoint.consensus_encode(&mut data)?;

// Serialize the coin's height and coinbase flag
let height_and_coinbase = (coin.height << 1) | (coin.is_coinbase as u32);
height_and_coinbase.consensus_encode(&mut data)?;

let txout = bitcoin::TxOut {
value: bitcoin::Amount::from_sat(coin.amount),
script_pubkey: bitcoin::ScriptBuf::from_bytes(coin.script_pubkey.clone()),
};

// Serialize the actual UTXO (value and script)
txout.consensus_encode(&mut data)?;

Ok(data)
}

// Custom serializer for total_amount to display 8 decimal places
fn serialize_as_btc<S>(amount: &u64, serializer: S) -> Result<S::Ok, S::Error>
where
Expand Down Expand Up @@ -350,7 +326,7 @@ async fn gettxoutsetinfo(
let mut muhash = subcoin_crypto::muhash::MuHash3072::new();

for (txid, vout, coin) in utxo_iter {
let data = tx_out_ser(bitcoin::OutPoint { txid, vout }, &coin)
let data = subcoin_utxo_snapshot::tx_out_ser(bitcoin::OutPoint { txid, vout }, &coin)
.map_err(|err| sc_cli::Error::Application(Box::new(err)))?;
muhash.insert(&data);

Expand All @@ -375,19 +351,14 @@ async fn gettxoutsetinfo(
}

// Hash the combined hash of all UTXOs
let finalized = muhash.digest();

let utxo_set_hash = finalized.iter().rev().fold(String::new(), |mut output, b| {
let _ = write!(output, "{b:02x}");
output
});
let muhash = muhash.txoutset_muhash();

let tx_out_set_info = TxOutSetInfo {
height: block_number,
bestblock: bitcoin_block_hash,
txouts,
bogosize,
muhash: utxo_set_hash,
muhash,
total_amount,
};

Expand Down Expand Up @@ -492,7 +463,7 @@ async fn dumptxoutset(

let _ = file.write(data.as_slice())?;

UtxoSetOutput::Binary(file)
UtxoSetOutput::Snapshot(UtxoSnapshotGenerator::new(file))
} else {
println!("Dumping UTXO set at #{block_number},{bitcoin_block_hash}");
UtxoSetOutput::Stdout(std::io::stdout())
Expand All @@ -509,49 +480,43 @@ async fn dumptxoutset(
}

enum UtxoSetOutput {
Binary(File),
Snapshot(UtxoSnapshotGenerator),
Csv(File),
Stdout(Stdout),
}

impl UtxoSetOutput {
fn write(&mut self, txid: bitcoin::Txid, vout: u32, coin: Coin) -> std::io::Result<()> {
let Coin {
is_coinbase,
amount,
height,
script_pubkey,
} = coin;

let outpoint = bitcoin::OutPoint { txid, vout };

match self {
Self::Binary(ref mut file) => {
let mut data = Vec::new();

let amount = txoutset::Amount::new(amount);

let code = txoutset::Code {
height,
Self::Snapshot(snapshot_generator) => {
snapshot_generator.write_utxo_entry(txid, vout, coin)?;
}
Self::Csv(ref mut file) => {
let Coin {
is_coinbase,
};
let script = txoutset::Script::from_bytes(script_pubkey);
amount,
height,
script_pubkey,
} = coin;

outpoint.consensus_encode(&mut data)?;
code.consensus_encode(&mut data)?;
amount.consensus_encode(&mut data)?;
script.consensus_encode(&mut data)?;
let outpoint = bitcoin::OutPoint { txid, vout };

let _ = file.write(data.as_slice())?;
}
Self::Csv(ref mut file) => {
let script_pubkey = hex::encode(script_pubkey.as_slice());
writeln!(
file,
"{outpoint},{is_coinbase},{height},{amount},{script_pubkey}",
)?;
}
Self::Stdout(ref mut stdout) => {
let Coin {
is_coinbase,
amount,
height,
script_pubkey,
} = coin;

let outpoint = bitcoin::OutPoint { txid, vout };

let script_pubkey = hex::encode(script_pubkey.as_slice());
writeln!(
stdout,
Expand Down
2 changes: 2 additions & 0 deletions crates/subcoin-runtime-primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ scale-info = { workspace = true, default-features = false }
sp-api = { workspace = true, default-features = false }
sp-runtime = { workspace = true, default-features = false }
sp-std = { workspace = true, default-features = false }
serde = { workspace = true, optional = true }

[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"serde",
"sp-api/std",
"sp-runtime/std",
"sp-std/std",
Expand Down
3 changes: 2 additions & 1 deletion crates/subcoin-runtime-primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const HALVING_INTERVAL: u32 = 210_000;
const MAX_SCRIPT_SIZE: usize = 10_000;

/// Unspent transaction output.
#[derive(Debug, TypeInfo, Encode, Decode)]
#[derive(Debug, Clone, PartialEq, Eq, TypeInfo, Encode, Decode)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct Coin {
/// Whether the coin is from a coinbase transaction.
pub is_coinbase: bool,
Expand Down
13 changes: 13 additions & 0 deletions crates/subcoin-utxo-snapshot/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "subcoin-utxo-snapshot"
version.workspace = true
authors.workspace = true
edition.workspace = true
repository.workspace = true
license.workspace = true

[dependencies]
bitcoin = { workspace = true }
serde = { workspace = true }
subcoin-primitives = { workspace = true }
txoutset = { workspace = true }
Loading

0 comments on commit 1e59a2d

Please sign in to comment.