validator/xmr: Add aux chain checks

This commit is contained in:
parazyd
2025-05-21 20:48:06 +02:00
committed by skoupidi
parent ea74788153
commit dc4e461379
3 changed files with 207 additions and 2 deletions

102
Cargo.lock generated
View File

@@ -907,6 +907,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
[[package]]
name = "byte-slice-cast"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d"
[[package]]
name = "bytecheck"
version = "0.6.12"
@@ -1257,6 +1263,26 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const_format"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "constant_time_eq"
version = "0.3.1"
@@ -1798,6 +1824,7 @@ dependencies = [
"pin-project-lite",
"plotters",
"prettytable-rs",
"primitive-types",
"rand 0.8.5",
"randomx",
"rcgen",
@@ -1805,6 +1832,8 @@ dependencies = [
"rustls-pemfile",
"semver",
"serde",
"sha2",
"simplelog",
"sled-overlay",
"smol",
"socket2",
@@ -3420,7 +3449,7 @@ dependencies = [
"rand 0.8.5",
"sinsemilla",
"subtle",
"uint",
"uint 0.9.5",
]
[[package]]
@@ -3772,6 +3801,26 @@ dependencies = [
"png",
]
[[package]]
name = "impl-codec"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14"
dependencies = [
"parity-scale-codec",
]
[[package]]
name = "impl-trait-for-tuples"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
name = "incrementalmerkletree"
version = "0.8.2"
@@ -4780,6 +4829,34 @@ dependencies = [
"sha2",
]
[[package]]
name = "parity-scale-codec"
version = "3.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa"
dependencies = [
"arrayvec",
"bitvec",
"byte-slice-cast",
"const_format",
"impl-trait-for-tuples",
"parity-scale-codec-derive",
"rustversion",
"serde",
]
[[package]]
name = "parity-scale-codec-derive"
version = "3.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
name = "parking"
version = "2.2.1"
@@ -5173,6 +5250,17 @@ dependencies = [
"elliptic-curve",
]
[[package]]
name = "primitive-types"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5"
dependencies = [
"fixed-hash",
"impl-codec",
"uint 0.10.0",
]
[[package]]
name = "priority-queue"
version = "2.5.0"
@@ -8154,6 +8242,18 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "uint"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e"
dependencies = [
"byteorder",
"crunchy",
"hex",
"static_assertions",
]
[[package]]
name = "unarray"
version = "0.1.4"

View File

@@ -88,6 +88,7 @@ hex = {version = "0.4.3", optional = true}
serde = {version = "1.0.219", features = ["derive"], optional = true}
tinyjson = {version = "2.5.1", optional = true}
httparse = {version = "1.10.1", optional = true}
primitive-types = {version = "0.13.1", optional = true}
semver = {version = "1.0.26", optional = true}
structopt = {version= "0.3.26", optional = true}
structopt-toml = {version= "0.5.1", optional = true}
@@ -118,6 +119,7 @@ blake3 = {version = "1.8.2", features = ["rayon"], optional = true}
crypto_api_chachapoly = {version = "0.5.0", optional = true}
halo2_proofs = {version = "0.3.1", features = ["circuit-params"], optional = true}
halo2_gadgets = {version = "0.3.1", features = ["circuit-params"], optional = true}
sha2 = {version = "0.10.9", optional = true}
# Smart contract runtime
darkfi-sdk = {path = "src/sdk", optional = true}
@@ -172,8 +174,10 @@ validator = [
"hex",
"lazy_static",
"monero",
"primitive-types",
"randomx",
"smol",
"sha2",
"blockchain",
"system",

View File

@@ -19,9 +19,18 @@
use std::{io, iter};
use monero::{consensus::Encodable as XmrEncodable, cryptonote::hash::Hashable, VarInt};
use log::warn;
use monero::{
blockdata::transaction::{ExtraField, RawExtraField, SubField},
consensus::Encodable as XmrEncodable,
cryptonote::hash::Hashable,
VarInt,
};
use primitive_types::U256;
use sha2::{Digest, Sha256};
use tiny_keccak::{Hasher, Keccak};
use super::merkle_tree_parameters::MerkleTreeParameters;
use crate::{
blockchain::{
header_store::HeaderHash,
@@ -31,6 +40,7 @@ use crate::{
MoneroPowData,
},
},
Error,
Error::MoneroMergeMineError,
Result,
};
@@ -136,3 +146,94 @@ pub fn construct_monero_data(
aux_chain_merkle_proof,
})
}
fn check_aux_chains(
monero_data: &MoneroPowData,
merge_mining_params: VarInt,
aux_chain_merkle_root: &monero::Hash,
darkfi_hash: HeaderHash,
darkfi_genesis_hash: HeaderHash,
) -> bool {
let df_hash = monero::Hash::from_slice(darkfi_hash.as_slice());
if merge_mining_params == VarInt(0) {
// Interpret 0 as only 1 chain
if df_hash == *aux_chain_merkle_root {
return true
}
}
let merkle_tree_params = MerkleTreeParameters::from_varint(merge_mining_params);
if merkle_tree_params.number_of_chains() == 0 {
return false
}
let hash_position = U256::from_little_endian(
&Sha256::new()
.chain_update(darkfi_genesis_hash.as_slice())
.chain_update(merkle_tree_params.aux_nonce().to_le_bytes())
.chain_update((109_u8).to_le_bytes())
.finalize(),
)
.low_u32() %
u32::from(merkle_tree_params.number_of_chains());
let (merkle_root, pos) = monero_data
.aux_chain_merkle_proof
.calculate_root_with_pos(&df_hash, merkle_tree_params.number_of_chains());
if hash_position != pos {
return false
}
merkle_root == *aux_chain_merkle_root
}
// Parsing an extra field from bytes will always return an extra field with sub-fields
// that could be read, even if it does not represent the original extra field. As per
// Monero consensus rules, an error here will not represent a failure to deserialize a
// block, so no need to error here.
fn parse_extra_field_truncate_on_error(raw_extra_field: &RawExtraField) -> ExtraField {
match ExtraField::try_parse(raw_extra_field) {
Ok(val) => val,
Err(val) => {
warn!(
target: "validator::xmr::helpers",
"[MERGEMINING] Some sub-fields could not be parsed from the Monero coinbase",
);
val
}
}
}
/// Extracts the Monero block hash from the coinbase transaction's extra field
pub fn extract_aux_merkle_root_from_block(monero: &monero::Block) -> Result<Option<monero::Hash>> {
// When we extract the merge mining hash, we do not care if
// the extra field can be parsed without error.
let extra_field = parse_extra_field_truncate_on_error(&monero.miner_tx.prefix.extra);
// Only one merge mining tag is allowed
let merge_mining_hashes: Vec<monero::Hash> = extra_field
.0
.iter()
.filter_map(|item| {
if let SubField::MergeMining(_depth, merge_mining_hash) = item {
Some(*merge_mining_hash)
} else {
None
}
})
.collect();
if merge_mining_hashes.len() > 1 {
return Err(Error::MoneroMergeMineError(
"More than one merge mining tag found in coinbase".to_string(),
))
}
if let Some(merge_mining_hash) = merge_mining_hashes.into_iter().next() {
Ok(Some(merge_mining_hash))
} else {
Ok(None)
}
}