diff --git a/Cargo.lock b/Cargo.lock index 83b302df..3c75f3aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,6 +286,7 @@ dependencies = [ name = "dash-pkc" version = "0.0.0" dependencies = [ + "bitcoin-consensus-encoding 0.2.0", "blst", "dash-num", "dash-types", @@ -321,6 +322,7 @@ dependencies = [ name = "dash-primitives" version = "0.0.0" dependencies = [ + "base58ck", "bitcoin-consensus-encoding 0.2.0", "bitcoin-internals", "bitcoin-units", @@ -328,6 +330,7 @@ dependencies = [ "cfg-if", "dash-dev", "dash-num", + "dash-pkc", "dash-pow", "dash-script", "dash-types", @@ -345,7 +348,6 @@ dependencies = [ "base58ck", "bitcoin-consensus-encoding 0.2.0", "bitcoin_hashes", - "dash-types", "hex-literal", "serde", ] diff --git a/contrib/codeql/decl.ql b/contrib/codeql/decl.ql index d02ae038..d5065378 100644 --- a/contrib/codeql/decl.ql +++ b/contrib/codeql/decl.ql @@ -5,7 +5,7 @@ * * @id base-sdk/decl-rules * @name Rules for definition orders - * @description Enforces order of definitions for enums, impls and structs. + * @description Enforces definition order and enum variant structure. * @kind problem * @precision high * @problem.severity warning @@ -36,19 +36,66 @@ predicate outOfOrder( ) } -from TypeItem t, Locatable badItem, string name, string message +/** Holds if `v` is a bare `Unknown` variant without associated data. */ +predicate bareUnknownVariant(Enum e, Variant v) { + isSourceType(e) and + v = e.getVariantList().getAVariant() and + v.getName().getText() = "Unknown" and + not exists(v.getFieldList()) +} + +/** Gets the NumCodec type parameter for enum `e`. */ +string numCodecType(Enum e) { + exists(Impl i | + fileOf(i) = fileOf(e) and + implSelfName(i) = e.getName().getText() and + implTraitName(i) = "NumCodec" and + result = + i.getTrait() + .(PathTypeRepr) + .getPath() + .getSegment() + .getGenericArgList() + .getGenericArg(0) + .(TypeArg) + .getTypeRepr() + .(PathTypeRepr) + .getPath() + .getSegment() + .getIdentifier() + .getText() + ) +} + +from Locatable item, string message where - isSourceType(t) and - (t instanceof Struct or t instanceof Enum) and - not isSerdeInternalType(t) and - not isNotEncodable(t) and - isEvaluatedCrate(fileOf(t)) and - name = t.getName().getText() and - exists(DeclSlot badSlot, int badLine, DeclSlot priorSlot | - outOfOrder(t, badSlot, badLine, priorSlot, badItem) and - message = - fmt("{0} {1} appears after {2}", name, - fmt("{0} (slot {1})", badSlot.toString(), badSlot.getOrder().toString()), - fmt("{0} (slot {1})", priorSlot.toString(), priorSlot.getOrder().toString())) + exists(TypeItem t, string name | + isSourceType(t) and + (t instanceof Struct or t instanceof Enum) and + not isSerdeInternalType(t) and + not isNotEncodable(t) and + isEvaluatedCrate(fileOf(t)) and + name = t.getName().getText() and + exists(DeclSlot badSlot, int badLine, DeclSlot priorSlot | + outOfOrder(t, badSlot, badLine, priorSlot, item) and + message = + fmt("{0} {1} appears after {2}", name, + fmt("{0} (slot {1})", badSlot.toString(), badSlot.getOrder().toString()), + fmt("{0} (slot {1})", priorSlot.toString(), priorSlot.getOrder().toString())) + ) + ) + or + exists(Enum e | + bareUnknownVariant(e, item) and + ( + exists(string ty | + ty = numCodecType(e) and + message = + fmt("{0}::Unknown must carry the raw value (e.g. Unknown({1}))", e.getName().getText(), ty) + ) + or + not exists(numCodecType(e)) and + message = fmt("{0}::Unknown must carry the raw value", e.getName().getText()) + ) ) -select badItem, message +select item, message diff --git a/contrib/semgrep/types.yml b/contrib/semgrep/types.yml new file mode 100644 index 00000000..5357070e --- /dev/null +++ b/contrib/semgrep/types.yml @@ -0,0 +1,9 @@ +rules: + - id: no-newtype-in-types + message: "do not define newtypes in dash-types; use the owning crate instead" + severity: ERROR + languages: [rust] + paths: + include: [/pkgs/types/src/**/*.rs] + exclude: [/pkgs/types/src/hex.rs, /pkgs/types/src/uint.rs] + pattern-regex: '\b(?:make|impl)_(?:bytes|num|type)!\s*\{' diff --git a/pkgs/dev/src/lambda.rs b/pkgs/dev/src/lambda.rs index 46f64f86..25e99a56 100644 --- a/pkgs/dev/src/lambda.rs +++ b/pkgs/dev/src/lambda.rs @@ -62,7 +62,7 @@ where /// /// Panics on txid mismatch. fn assert_txid(raw: &[u8], label: &str) { - let computed = dash_primitives::hash::tx_hash(raw); + let computed = dash_primitives::tx_hash(raw); let expected = TxHash::from_hex(label).unwrap_or_else(|e| panic!("{label}: bad txid hex: {e}")); assert_eq!(computed, expected, "{label}: txid mismatch"); } diff --git a/pkgs/num/src/error.rs b/pkgs/num/src/error.rs deleted file mode 100644 index 93c69d8e..00000000 --- a/pkgs/num/src/error.rs +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Hex parsing error type. - -use core::fmt; - -/// Error returned when parsing a hex string fails. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ParseHexError { - /// The hex string has an odd number of characters. - OddLength, - /// The decoded byte count does not match the expected length. - InvalidLength { expected: usize, got: usize }, - /// A non-hex character was encountered. - InvalidChar(u8), -} - -impl fmt::Display for ParseHexError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::OddLength => write!(f, "hex string has odd length"), - Self::InvalidLength { expected, got } => { - write!(f, "expected {expected} hex chars, got {got}") - } - Self::InvalidChar(c) => { - write!(f, "invalid hex character: {:#04x}", c) - } - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ParseHexError {} diff --git a/pkgs/num/src/hash.rs b/pkgs/num/src/hash.rs index 63c5b4bd..b48a4f1d 100644 --- a/pkgs/num/src/hash.rs +++ b/pkgs/num/src/hash.rs @@ -6,14 +6,40 @@ //! Fixed-size opaque hash blob types. -use crate::ParseHexError; - use core::fmt; use core::hash::Hash; use core::str::FromStr; pub(crate) const HEX_LOWER: [u8; 16] = *b"0123456789abcdef"; +/// Error returned when parsing a hex string fails. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ParseHexError { + /// The hex string has an odd number of characters. + OddLength, + /// The decoded byte count does not match the expected length. + InvalidLength { expected: usize, got: usize }, + /// A non-hex character was encountered. + InvalidChar(u8), +} + +impl fmt::Display for ParseHexError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::OddLength => write!(f, "hex string has odd length"), + Self::InvalidLength { expected, got } => { + write!(f, "expected {expected} hex chars, got {got}") + } + Self::InvalidChar(c) => { + write!(f, "invalid hex character: {:#04x}", c) + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseHexError {} + pub(crate) fn hex_val(c: u8) -> Result { match c { b'0'..=b'9' => Ok(c - b'0'), diff --git a/pkgs/num/src/lib.rs b/pkgs/num/src/lib.rs index bd15afbb..b882d2b4 100644 --- a/pkgs/num/src/lib.rs +++ b/pkgs/num/src/lib.rs @@ -18,7 +18,6 @@ extern crate std; mod arith; mod arith256; mod compact; -mod error; mod hash; #[allow(unused_imports, reason = "ergonomic shim, exports may be unused")] mod prelude; @@ -34,5 +33,4 @@ pub mod __private { pub use arith::ArithInt; pub use arith256::Arith256; pub use compact::{CompactTarget, DecodedTarget}; -pub use error::ParseHexError; -pub use hash::{Hash160, Hash256, Hash512, HashBlob}; +pub use hash::{Hash160, Hash256, Hash512, HashBlob, ParseHexError}; diff --git a/pkgs/num/src/util.rs b/pkgs/num/src/util.rs index 7894ca34..01fca47a 100644 --- a/pkgs/num/src/util.rs +++ b/pkgs/num/src/util.rs @@ -141,11 +141,3 @@ macro_rules! make_hash { $crate::impl_hash!($base, $name); }; } - -/// Convenience alias: generates a `Hash256`-based newtype. -#[macro_export] -macro_rules! make_hash256 { - ($(#[$attr:meta])* $name:ident) => { - $crate::make_hash!($crate::Hash256, $(#[$attr])* $name); - }; -} diff --git a/pkgs/p2p_core/src/lib.rs b/pkgs/p2p_core/src/lib.rs index 4686785b..3257ba9f 100644 --- a/pkgs/p2p_core/src/lib.rs +++ b/pkgs/p2p_core/src/lib.rs @@ -13,26 +13,22 @@ extern crate alloc; extern crate std; mod codec; +mod error; +mod msg; #[allow(unused_imports, reason = "ergonomic shim, exports may be unused")] mod prelude; - -pub mod error; -pub mod msg; -pub mod primitives; -pub mod v2; +mod primitives; +mod v2; pub use error::P2pDecodeError; -pub use msg::DashNetworkMessage; +pub use msg::{ + Addr, AddrV2Entry, AddrV2Msg, CFCheckpt, CFHeaders, CFilter, DashNetworkMessage, FilterType, GetCFCheckpt, + GetCFHeaders, GetCFilters, GetData, GetHeaders, GetHeaders2, GovSync, Headers, Headers2, Inv, NotFound, Ping, Pong, + TimestampedAddr, Version, VersionAddr, +}; pub use primitives::{ - command::CommandString, - compressed_header::CompressionState, - filter_type::FilterType, - inventory::{InvType, Inventory}, - magic::Magic, - mn_list::{MnListDiffPayload, SimplifiedMnListEntry}, - net_addr::{AddrV2, AddrV2Entry, NetAddr, TimestampedAddr}, - protocol_version::ProtocolVersion, - service_flags::ServiceFlags, - short_id::ShortId, - user_agent::UserAgent, + CommandString, CompressionState, DeletedQuorum, GetMnListDiff, InvType, Inventory, Magic, MnListDiff, + MnListDiffPayload, ProtocolVersion, QuorumClSig, ServiceFlags, ShortId, SimplifiedMnListEntry, UserAgent, + UserAgentTooLong, }; +pub use v2::{decode_v2, encode_v2}; diff --git a/pkgs/p2p_core/src/msg/addr.rs b/pkgs/p2p_core/src/msg/addr.rs index ae2b6b5d..f4a2793b 100644 --- a/pkgs/p2p_core/src/msg/addr.rs +++ b/pkgs/p2p_core/src/msg/addr.rs @@ -6,9 +6,72 @@ //! Address messages: addr, addrv2 (getaddr and sendaddrv2 are empty). -use crate::codec::codec_p2p; +use crate::codec::{codec_p2p, impl_p2p}; use crate::prelude::*; -use crate::primitives::net_addr::{AddrV2Entry, TimestampedAddr}; +use crate::primitives::ServiceFlags; + +use dash_primitives::{AddrV2, ServiceV1}; +use dash_types::codec::{self, BaseCodec, DecodeError}; + +use core::fmt; + +/// Timestamped v1 address entry used in `addr` messages. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct TimestampedAddr { + /// Seconds since Unix epoch. + pub time: u32, + /// Advertised services. + pub services: ServiceFlags, + /// IPv4-mapped IPv6 address + port. + pub addr: ServiceV1, +} + +codec_p2p!(TimestampedAddr { time, services, addr }); + +/// BIP155 timestamped v2 address entry used in `addrv2` messages. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct AddrV2Entry { + /// Seconds since Unix epoch. + pub time: u32, + /// Advertised services (CompactSize-encoded on wire). + pub services: ServiceFlags, + /// Network address. + pub addr: AddrV2, + /// Port number (big-endian on wire). + pub port: u16, +} + +impl BaseCodec for AddrV2Entry { + fn decode(data: &mut &[u8]) -> Result { + let time = u32::decode(data)?; + let services = ServiceFlags(codec::read_compact_u64(data)?); + let addr = AddrV2::decode(data)?; + let port = codec::read_u16_be(data)?; + Ok(Self { + time, + services, + addr, + port, + }) + } + + fn encode(&self, buf: &mut Vec) { + self.time.encode(buf); + codec::write_compact_u64(self.services.0, buf); + self.addr.encode(buf); + buf.extend_from_slice(&self.port.to_be_bytes()); + } +} + +impl_p2p!(AddrV2Entry); + +impl fmt::Display for AddrV2Entry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}:{}", self.addr.network, self.port) + } +} /// V1 address announcement carrying timestamped addresses. #[derive(Clone, Debug, Eq, Hash, PartialEq)] diff --git a/pkgs/p2p_core/src/msg/cfcheckpt.rs b/pkgs/p2p_core/src/msg/cfcheckpt.rs deleted file mode 100644 index 6c673139..00000000 --- a/pkgs/p2p_core/src/msg/cfcheckpt.rs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! BIP157 compact filter checkpoint messages: getcfcheckpt, cfcheckpt. - -use crate::codec::codec_p2p; -use crate::prelude::*; -use crate::primitives::filter_type::FilterType; - -use dash_primitives::BlockHash; - -/// Requests evenly-spaced compact filter checkpoints. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct GetCFCheckpt { - /// Filter type. - pub filter_type: FilterType, - /// Stop block hash. - pub stop_hash: BlockHash, -} - -codec_p2p!(GetCFCheckpt { filter_type, stop_hash }); - -/// Response carrying filter header checkpoints at 1000-block intervals. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct CFCheckpt { - /// Filter type. - pub filter_type: FilterType, - /// Stop block hash. - pub stop_hash: BlockHash, - /// Filter headers at every 1000th block. - pub filter_headers: Vec, -} - -codec_p2p!(CFCheckpt { - filter_type, - stop_hash, - filter_headers -}); diff --git a/pkgs/p2p_core/src/msg/cfheaders.rs b/pkgs/p2p_core/src/msg/cfheaders.rs deleted file mode 100644 index 01712c4f..00000000 --- a/pkgs/p2p_core/src/msg/cfheaders.rs +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! BIP157 compact filter header messages: getcfheaders, cfheaders. - -use crate::codec::{codec_p2p, impl_p2p}; -use crate::prelude::*; -use crate::primitives::filter_type::FilterType; - -use bitcoin_units::BlockHeight; -use dash_primitives::BlockHash; -use dash_types::codec::{BaseCodec, DecodeError}; - -/// Requests compact filter headers for a range of blocks. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct GetCFHeaders { - /// Filter type. - pub filter_type: FilterType, - /// Start height (inclusive). - pub start_height: BlockHeight, - /// Stop block hash (inclusive). - pub stop_hash: BlockHash, -} - -impl_p2p!(GetCFHeaders); - -impl BaseCodec for GetCFHeaders { - fn decode(data: &mut &[u8]) -> Result { - Ok(Self { - filter_type: FilterType(u8::decode(data)?), - start_height: BlockHeight::from_u32(u32::decode(data)?), - stop_hash: BlockHash::decode(data)?, - }) - } - - fn encode(&self, buf: &mut Vec) { - self.filter_type.0.encode(buf); - self.start_height.to_u32().encode(buf); - self.stop_hash.encode(buf); - } -} - -/// Response carrying filter headers and their hashes. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct CFHeaders { - /// Filter type. - pub filter_type: FilterType, - /// Hash of the stop block. - pub stop_hash: BlockHash, - /// Previous filter header (for chaining). - pub previous_filter_header: BlockHash, - /// Filter hashes in block-height order. - pub filter_hashes: Vec, -} - -codec_p2p!(CFHeaders { - filter_type, - stop_hash, - previous_filter_header, - filter_hashes, -}); diff --git a/pkgs/p2p_core/src/msg/cfilter.rs b/pkgs/p2p_core/src/msg/cfilter.rs deleted file mode 100644 index 0058f421..00000000 --- a/pkgs/p2p_core/src/msg/cfilter.rs +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! BIP157 compact filter messages: getcfilters, cfilter. - -use crate::codec::{codec_p2p, impl_p2p}; -use crate::prelude::*; -use crate::primitives::filter_type::FilterType; - -use bitcoin_units::BlockHeight; -use dash_primitives::BlockHash; -use dash_types::codec::{BaseCodec, DecodeError}; - -/// Requests compact filters for a range of blocks. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct GetCFilters { - /// Filter type (0 = basic). - pub filter_type: FilterType, - /// Start height (inclusive). - pub start_height: BlockHeight, - /// Stop block hash (inclusive). - pub stop_hash: BlockHash, -} - -impl_p2p!(GetCFilters); - -impl BaseCodec for GetCFilters { - fn decode(data: &mut &[u8]) -> Result { - Ok(Self { - filter_type: FilterType(u8::decode(data)?), - start_height: BlockHeight::from_u32(u32::decode(data)?), - stop_hash: BlockHash::decode(data)?, - }) - } - - fn encode(&self, buf: &mut Vec) { - self.filter_type.0.encode(buf); - self.start_height.to_u32().encode(buf); - self.stop_hash.encode(buf); - } -} - -/// A single compact block filter. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct CFilter { - /// Filter type. - pub filter_type: FilterType, - /// Block hash this filter covers. - pub block_hash: BlockHash, - /// Raw GCS filter data. - pub filter_data: Vec, -} - -codec_p2p!(CFilter { - filter_type, - block_hash, - filter_data -}); diff --git a/pkgs/p2p_core/src/msg/compact_filters.rs b/pkgs/p2p_core/src/msg/compact_filters.rs new file mode 100644 index 00000000..ea44b7ff --- /dev/null +++ b/pkgs/p2p_core/src/msg/compact_filters.rs @@ -0,0 +1,153 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! BIP157 compact block filter types and messages. + +use crate::codec::{codec_p2p, impl_p2p}; +use crate::prelude::*; + +use bitcoin_units::BlockHeight; +use dash_primitives::BlockHash; +use dash_types::codec::{BaseCodec, DecodeError}; + +dash_types::make_num! { + /// BIP157 filter type, encoded as a single byte on the wire. + FilterType, u8, 1 +} + +impl FilterType { + /// Basic filter (the only type defined by BIP158). + pub const BASIC: Self = Self(0); +} + +/// Requests compact filters for a range of blocks. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct GetCFilters { + /// Filter type (0 = basic). + pub filter_type: FilterType, + /// Start height (inclusive). + pub start_height: BlockHeight, + /// Stop block hash (inclusive). + pub stop_hash: BlockHash, +} + +impl_p2p!(GetCFilters); + +impl BaseCodec for GetCFilters { + fn decode(data: &mut &[u8]) -> Result { + Ok(Self { + filter_type: FilterType(u8::decode(data)?), + start_height: BlockHeight::from_u32(u32::decode(data)?), + stop_hash: BlockHash::decode(data)?, + }) + } + + fn encode(&self, buf: &mut Vec) { + self.filter_type.0.encode(buf); + self.start_height.to_u32().encode(buf); + self.stop_hash.encode(buf); + } +} + +/// A single compact block filter. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct CFilter { + /// Filter type. + pub filter_type: FilterType, + /// Block hash this filter covers. + pub block_hash: BlockHash, + /// Raw GCS filter data. + pub filter_data: Vec, +} + +codec_p2p!(CFilter { + filter_type, + block_hash, + filter_data +}); + +/// Requests compact filter headers for a range of blocks. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct GetCFHeaders { + /// Filter type. + pub filter_type: FilterType, + /// Start height (inclusive). + pub start_height: BlockHeight, + /// Stop block hash (inclusive). + pub stop_hash: BlockHash, +} + +impl_p2p!(GetCFHeaders); + +impl BaseCodec for GetCFHeaders { + fn decode(data: &mut &[u8]) -> Result { + Ok(Self { + filter_type: FilterType(u8::decode(data)?), + start_height: BlockHeight::from_u32(u32::decode(data)?), + stop_hash: BlockHash::decode(data)?, + }) + } + + fn encode(&self, buf: &mut Vec) { + self.filter_type.0.encode(buf); + self.start_height.to_u32().encode(buf); + self.stop_hash.encode(buf); + } +} + +/// Response carrying filter headers and their hashes. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct CFHeaders { + /// Filter type. + pub filter_type: FilterType, + /// Hash of the stop block. + pub stop_hash: BlockHash, + /// Previous filter header (for chaining). + pub previous_filter_header: BlockHash, + /// Filter hashes in block-height order. + pub filter_hashes: Vec, +} + +codec_p2p!(CFHeaders { + filter_type, + stop_hash, + previous_filter_header, + filter_hashes, +}); + +/// Requests evenly-spaced compact filter checkpoints. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct GetCFCheckpt { + /// Filter type. + pub filter_type: FilterType, + /// Stop block hash. + pub stop_hash: BlockHash, +} + +codec_p2p!(GetCFCheckpt { filter_type, stop_hash }); + +/// Response carrying filter header checkpoints at 1000-block intervals. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct CFCheckpt { + /// Filter type. + pub filter_type: FilterType, + /// Stop block hash. + pub stop_hash: BlockHash, + /// Filter headers at every 1000th block. + pub filter_headers: Vec, +} + +codec_p2p!(CFCheckpt { + filter_type, + stop_hash, + filter_headers +}); diff --git a/pkgs/p2p_core/src/msg/headers.rs b/pkgs/p2p_core/src/msg/headers.rs index 02214a91..373a369c 100644 --- a/pkgs/p2p_core/src/msg/headers.rs +++ b/pkgs/p2p_core/src/msg/headers.rs @@ -8,7 +8,7 @@ use crate::codec::{codec_p2p, impl_p2p}; use crate::prelude::*; -use crate::primitives::protocol_version::ProtocolVersion; +use crate::primitives::ProtocolVersion; use dash_primitives::{BlockHash, BlockHeader, MerkleRoot}; use dash_types::codec::{self, BaseCodec, DecodeError}; diff --git a/pkgs/p2p_core/src/msg/headers2.rs b/pkgs/p2p_core/src/msg/headers2.rs index 79525b95..d99e920d 100644 --- a/pkgs/p2p_core/src/msg/headers2.rs +++ b/pkgs/p2p_core/src/msg/headers2.rs @@ -8,8 +8,7 @@ use crate::codec::{codec_p2p, impl_p2p}; use crate::prelude::*; -use crate::primitives::compressed_header::CompressionState; -use crate::primitives::protocol_version::ProtocolVersion; +use crate::primitives::{CompressionState, ProtocolVersion}; use dash_primitives::BlockHash; use dash_types::codec::{self, BaseCodec, DecodeError}; diff --git a/pkgs/p2p_core/src/msg/inv.rs b/pkgs/p2p_core/src/msg/inv.rs index f342d65b..e1de5c74 100644 --- a/pkgs/p2p_core/src/msg/inv.rs +++ b/pkgs/p2p_core/src/msg/inv.rs @@ -8,7 +8,7 @@ use crate::codec::codec_p2p; use crate::prelude::*; -use crate::primitives::inventory::Inventory; +use crate::primitives::Inventory; /// Announces available inventory to a peer. #[derive(Clone, Debug, Eq, Hash, PartialEq)] diff --git a/pkgs/p2p_core/src/msg/mnlistdiff.rs b/pkgs/p2p_core/src/msg/mnlistdiff.rs deleted file mode 100644 index d4a18719..00000000 --- a/pkgs/p2p_core/src/msg/mnlistdiff.rs +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Masternode list diff messages: getmnlistd, mnlistdiff. - -use crate::codec::codec_p2p; -use crate::primitives::mn_list::MnListDiffPayload; - -use dash_primitives::BlockHash; - -/// Requests a masternode list diff between two blocks. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct GetMnListDiff { - /// Base block hash (beginning of range). - pub base_block_hash: BlockHash, - /// Target block hash (end of range). - pub block_hash: BlockHash, -} - -codec_p2p!(GetMnListDiff { - base_block_hash, - block_hash -}); - -/// Response carrying the masternode list diff. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct MnListDiff { - /// The full diff payload. - pub payload: MnListDiffPayload, -} - -codec_p2p!(MnListDiff { payload }); diff --git a/pkgs/p2p_core/src/msg/mod.rs b/pkgs/p2p_core/src/msg/mod.rs index 2a28e8d3..429f7f87 100644 --- a/pkgs/p2p_core/src/msg/mod.rs +++ b/pkgs/p2p_core/src/msg/mod.rs @@ -7,34 +7,29 @@ //! P2P message types and dispatch. use crate::prelude::*; -use crate::primitives::command::CommandString; -use crate::primitives::short_id::ShortId; +use crate::primitives::CommandString; +use crate::primitives::ShortId; +use crate::primitives::{GetMnListDiff, MnListDiff}; use bitcoin_consensus_encoding as encoding; pub mod addr; -pub mod cfcheckpt; -pub mod cfheaders; -pub mod cfilter; +pub mod compact_filters; pub mod gov; pub mod headers; pub mod headers2; pub mod inv; -pub mod mnlistdiff; pub mod ping; pub mod version; -pub use addr::{Addr, AddrV2Msg}; -pub use cfcheckpt::{CFCheckpt, GetCFCheckpt}; -pub use cfheaders::{CFHeaders, GetCFHeaders}; -pub use cfilter::{CFilter, GetCFilters}; +pub use addr::{Addr, AddrV2Entry, AddrV2Msg, TimestampedAddr}; +pub use compact_filters::{CFCheckpt, CFHeaders, CFilter, FilterType, GetCFCheckpt, GetCFHeaders, GetCFilters}; pub use gov::GovSync; pub use headers::{GetHeaders, Headers}; pub use headers2::{GetHeaders2, Headers2}; pub use inv::{GetData, Inv, NotFound}; -pub use mnlistdiff::{GetMnListDiff, MnListDiff}; pub use ping::{Ping, Pong}; -pub use version::Version; +pub use version::{Version, VersionAddr}; /// Decode helper: decode from slice, mapping the error. fn decode_msg(payload: &[u8]) -> Result @@ -199,9 +194,9 @@ define_network_messages! { /// Governance sync request. GovSync(GovSync) => GOVSYNC, /// Governance object. - GovObj(dash_primitives::gov::GovObject) => GOVOBJ, + GovObj(dash_primitives::GovObject) => GOVOBJ, /// Governance vote. - GovObjVote(dash_primitives::gov::GovVote) => GOVOBJVOTE, + GovObjVote(dash_primitives::GovVote) => GOVOBJVOTE, /// Request MN list diff. GetMnListDiff(GetMnListDiff) => GETMNLISTD, /// MN list diff. diff --git a/pkgs/p2p_core/src/msg/version.rs b/pkgs/p2p_core/src/msg/version.rs index c5b8c4fc..8ea5f860 100644 --- a/pkgs/p2p_core/src/msg/version.rs +++ b/pkgs/p2p_core/src/msg/version.rs @@ -7,12 +7,32 @@ //! Version handshake message (Dash-extended). use crate::codec::codec_p2p; -use crate::primitives::net_addr::NetAddr; -use crate::primitives::protocol_version::ProtocolVersion; -use crate::primitives::service_flags::ServiceFlags; -use crate::primitives::user_agent::UserAgent; +use crate::primitives::{ProtocolVersion, ServiceFlags, UserAgent}; use dash_num::Hash256; +use dash_primitives::ServiceV1; + +use core::fmt; + +/// Network address with service flags (used inside the version message). +/// +/// Wire format: `u64 services` + `[u8; 16] addr` + `u16 BE port`. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct VersionAddr { + /// Advertised services. + pub services: ServiceFlags, + /// IPv4-mapped IPv6 address + port. + pub addr: ServiceV1, +} + +codec_p2p!(VersionAddr { services, addr }); + +impl fmt::Display for VersionAddr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?} ({})", self.addr, self.services) + } +} /// The `version` message initiates the P2P handshake. /// @@ -28,9 +48,9 @@ pub struct Version { /// Unix timestamp of the sender. pub timestamp: i64, /// Receiver's address as seen by the sender. - pub addr_recv: NetAddr, + pub addr_recv: VersionAddr, /// Sender's own address. - pub addr_send: NetAddr, + pub addr_send: VersionAddr, /// Random nonce for connection deduplication. #[cfg_attr(feature = "serde", serde(with = "dash_types::serialize::str_u64"))] pub nonce: u64, diff --git a/pkgs/p2p_core/src/primitives/filter_type.rs b/pkgs/p2p_core/src/primitives/filter_type.rs deleted file mode 100644 index 884c850d..00000000 --- a/pkgs/p2p_core/src/primitives/filter_type.rs +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Compact block filter type identifier. - -dash_types::make_num! { - /// BIP157 filter type, encoded as a single byte on the wire. - FilterType, u8, 1 -} - -impl FilterType { - /// Basic filter (the only type defined by BIP158). - pub const BASIC: Self = Self(0); -} diff --git a/pkgs/p2p_core/src/primitives/mn_list.rs b/pkgs/p2p_core/src/primitives/mn_list.rs index afec407e..4f77663f 100644 --- a/pkgs/p2p_core/src/primitives/mn_list.rs +++ b/pkgs/p2p_core/src/primitives/mn_list.rs @@ -9,10 +9,11 @@ use crate::codec::{codec_p2p, impl_p2p}; use crate::prelude::*; -use dash_primitives::payload::Commitment; -use dash_primitives::{BlockHash, CService, LlmqType, MnType, Transaction, TxHash}; +use dash_primitives::{ + BlockHash, BlsPublicKeyBytes, BlsSignatureBytes, Commitment, KeyId, LlmqType, MnType, PlatformNodeId, ServiceV1, + Transaction, TxHash, +}; use dash_types::codec::{BaseCodec, DecodeError, NumCodec}; -use dash_types::{BlsPublicKeyBytes, BlsSignatureBytes, KeyId, PlatformNodeId}; use core::fmt; @@ -28,7 +29,7 @@ pub struct SimplifiedMnListEntry { /// Block hash at confirmation depth. pub confirmed_hash: BlockHash, /// Network service address. - pub service: CService, + pub service: ServiceV1, /// BLS operator public key. pub operator_key: BlsPublicKeyBytes, /// Voting key hash (HASH160). @@ -51,7 +52,7 @@ impl BaseCodec for SimplifiedMnListEntry { let version = u16::decode(data)?; let pro_reg_tx_hash = TxHash::decode(data)?; let confirmed_hash = BlockHash::decode(data)?; - let service = CService::decode(data)?; + let service = ServiceV1::decode(data)?; let operator_key = BlsPublicKeyBytes::decode(data)?; let voting_key_id = KeyId::decode(data)?; let is_valid = bool::decode(data)?; @@ -198,6 +199,31 @@ impl fmt::Display for MnListDiffPayload { } } +/// Requests a masternode list diff between two blocks. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct GetMnListDiff { + /// Base block hash (beginning of range). + pub base_block_hash: BlockHash, + /// Target block hash (end of range). + pub block_hash: BlockHash, +} + +codec_p2p!(GetMnListDiff { + base_block_hash, + block_hash +}); + +/// Response carrying the masternode list diff. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct MnListDiff { + /// The full diff payload. + pub payload: MnListDiffPayload, +} + +codec_p2p!(MnListDiff { payload }); + #[cfg(all(test, feature = "serde"))] mod tests { use super::*; diff --git a/pkgs/p2p_core/src/primitives/mod.rs b/pkgs/p2p_core/src/primitives/mod.rs index c07cf207..332c3f70 100644 --- a/pkgs/p2p_core/src/primitives/mod.rs +++ b/pkgs/p2p_core/src/primitives/mod.rs @@ -6,14 +6,22 @@ //! P2P-specific primitive types. -pub mod command; -pub mod compressed_header; -pub mod filter_type; -pub mod inventory; -pub mod magic; -pub mod mn_list; -pub mod net_addr; -pub mod protocol_version; -pub mod service_flags; -pub mod short_id; -pub mod user_agent; +mod command; +mod compressed_header; +mod inventory; +mod magic; +mod mn_list; +mod protocol_version; +mod service_flags; +mod short_id; +mod user_agent; + +pub use command::CommandString; +pub use compressed_header::CompressionState; +pub use inventory::{InvType, Inventory}; +pub use magic::Magic; +pub use mn_list::{DeletedQuorum, GetMnListDiff, MnListDiff, MnListDiffPayload, QuorumClSig, SimplifiedMnListEntry}; +pub use protocol_version::ProtocolVersion; +pub use service_flags::ServiceFlags; +pub use short_id::ShortId; +pub use user_agent::{UserAgent, UserAgentTooLong}; diff --git a/pkgs/p2p_core/src/primitives/net_addr.rs b/pkgs/p2p_core/src/primitives/net_addr.rs deleted file mode 100644 index a7882da9..00000000 --- a/pkgs/p2p_core/src/primitives/net_addr.rs +++ /dev/null @@ -1,159 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Network address types for P2P messages. - -use crate::codec::{codec_p2p, impl_p2p}; -use crate::prelude::*; -use crate::primitives::service_flags::ServiceFlags; - -use dash_primitives::CService; -use dash_primitives::NetworkType; -use dash_types::codec::{self, BaseCodec, DecodeError, NumCodec}; - -use core::fmt; - -/// Network address with service flags (used inside the version message). -/// -/// Wire format: `u64 services` + `[u8; 16] addr` + `u16 BE port`. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct NetAddr { - /// Advertised services. - pub services: ServiceFlags, - /// IPv4-mapped IPv6 address + port. - pub addr: CService, -} - -codec_p2p!(NetAddr { services, addr }); - -impl fmt::Display for NetAddr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?} ({})", self.addr, self.services) - } -} - -/// Timestamped v1 address entry used in `addr` messages. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct TimestampedAddr { - /// Seconds since Unix epoch. - pub time: u32, - /// Advertised services. - pub services: ServiceFlags, - /// IPv4-mapped IPv6 address + port. - pub addr: CService, -} - -codec_p2p!(TimestampedAddr { time, services, addr }); - -/// Maximum serialized address size in ADDRv2 (BIP155). -const MAX_ADDRV2_SIZE: usize = 512; - -/// BIP155 v2 network address supporting multiple transport types. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct AddrV2 { - /// Network transport type. - pub network: NetworkType, - /// Raw address bytes (length depends on network type). - pub addr: Vec, -} - -impl_p2p!(AddrV2); - -impl BaseCodec for AddrV2 { - fn decode(data: &mut &[u8]) -> Result { - let net_byte = u8::decode(data)?; - let network = NetworkType::from_base(net_byte); - let len = codec::read_compact_size(data, MAX_ADDRV2_SIZE)?; - if let Some(expected) = Self::expected_len(network) { - if len != expected { - return Err(DecodeError::InvalidValue { - expected: expected as u64, - actual: len as u64, - }); - } - } - let addr = codec::read_bytes(data, len)?.to_vec(); - Ok(Self { network, addr }) - } - - fn encode(&self, buf: &mut Vec) { - self.network.to_base().encode(buf); - self.addr.encode(buf); - } -} - -impl AddrV2 { - /// Expected byte length for a given network type, if known. - const fn expected_len(net: NetworkType) -> Option { - match net { - NetworkType::Ipv4 => Some(4), - NetworkType::Ipv6 => Some(16), - NetworkType::TorV3 => Some(32), - NetworkType::I2P => Some(32), - NetworkType::Cjdns => Some(16), - NetworkType::Unknown(_) => None, - } - } -} - -/// BIP155 timestamped v2 address entry used in `addrv2` messages. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -pub struct AddrV2Entry { - /// Seconds since Unix epoch. - pub time: u32, - /// Advertised services (CompactSize-encoded on wire). - pub services: ServiceFlags, - /// Network address. - pub addr: AddrV2, - /// Port number (big-endian on wire). - pub port: u16, -} - -impl_p2p!(AddrV2Entry); - -impl BaseCodec for AddrV2Entry { - fn decode(data: &mut &[u8]) -> Result { - let time = u32::decode(data)?; - let services = ServiceFlags(codec::read_compact_u64(data)?); - let net_byte = u8::decode(data)?; - let network = NetworkType::from_base(net_byte); - let len = codec::read_compact_size(data, MAX_ADDRV2_SIZE)?; - if let Some(expected) = AddrV2::expected_len(network) { - if len != expected { - return Err(DecodeError::InvalidValue { - expected: expected as u64, - actual: len as u64, - }); - } - } - let addr = codec::read_bytes(data, len)?.to_vec(); - let port = codec::read_u16_be(data)?; - Ok(Self { - time, - services, - addr: AddrV2 { network, addr }, - port, - }) - } - - fn encode(&self, buf: &mut Vec) { - self.time.encode(buf); - codec::write_compact_size(self.services.0 as usize, buf); - self.addr.network.to_base().encode(buf); - self.addr.addr.encode(buf); - buf.extend_from_slice(&self.port.to_be_bytes()); - } -} - -impl fmt::Display for AddrV2Entry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}:{}", self.addr.network, self.port) - } -} diff --git a/pkgs/p2p_core/src/primitives/short_id.rs b/pkgs/p2p_core/src/primitives/short_id.rs index 736b2eff..2467c1d4 100644 --- a/pkgs/p2p_core/src/primitives/short_id.rs +++ b/pkgs/p2p_core/src/primitives/short_id.rs @@ -6,7 +6,7 @@ //! V2 short ID mapping for BIP324 message framing. -use crate::primitives::command::CommandString; +use crate::primitives::CommandString; // Maps between V2 1-byte short IDs and command strings. // diff --git a/pkgs/p2p_core/src/v2/mod.rs b/pkgs/p2p_core/src/v2/mod.rs index 8f068253..acf6eb97 100644 --- a/pkgs/p2p_core/src/v2/mod.rs +++ b/pkgs/p2p_core/src/v2/mod.rs @@ -11,8 +11,8 @@ use crate::msg::DashNetworkMessage; use crate::prelude::*; -use crate::primitives::command::CommandString; -use crate::primitives::short_id::ShortId; +use crate::primitives::CommandString; +use crate::primitives::ShortId; use crate::P2pDecodeError; /// Encodes a `DashNetworkMessage` into V2 framed bytes. diff --git a/pkgs/p2p_core/tests/addrv2.rs b/pkgs/p2p_core/tests/addrv2.rs index 22018758..73148b70 100644 --- a/pkgs/p2p_core/tests/addrv2.rs +++ b/pkgs/p2p_core/tests/addrv2.rs @@ -9,10 +9,8 @@ #![expect(clippy::panic, reason = "test code")] use bitcoin_consensus_encoding::{decode_from_slice, encode_to_vec}; -use dash_p2p_core::msg::addr::{Addr, AddrV2Msg}; -use dash_p2p_core::primitives::net_addr::{AddrV2, AddrV2Entry}; -use dash_p2p_core::primitives::service_flags::ServiceFlags; -use dash_primitives::NetworkType; +use dash_p2p_core::{Addr, AddrV2Entry, AddrV2Msg, ServiceFlags}; +use dash_primitives::{AddrV2, NetworkType}; use hex_conservative::FromHex; use rstest::rstest; diff --git a/pkgs/params/src/types.rs b/pkgs/params/src/types.rs index 2e1ec827..156417a6 100644 --- a/pkgs/params/src/types.rs +++ b/pkgs/params/src/types.rs @@ -9,7 +9,7 @@ pub(crate) use bitcoin_units::BlockHeight; pub use dash_num::{Arith256, Hash256}; -pub use dash_primitives::hash::double_sha256; +pub use dash_primitives::double_sha256; pub use dash_primitives::{ Block, BlockHash, BlockHeader, MerkleRoot, OutPoint, Script, Transaction, TxHash, TxIn, TxOut, TxType, }; diff --git a/pkgs/pkc/Cargo.toml b/pkgs/pkc/Cargo.toml index e85c8974..bdf0a38c 100644 --- a/pkgs/pkc/Cargo.toml +++ b/pkgs/pkc/Cargo.toml @@ -10,6 +10,9 @@ path = "bench/main.rs" harness = false [dependencies] +bitcoin-consensus-encoding = { version = "0.2", default-features = false, features = [ + "alloc", +] } blst = { version = "0.3", default-features = false, optional = true } dash-num = { version = "0.0.0", path = "../num" } dash-types = { version = "0.0.0", path = "../types", default-features = false } diff --git a/pkgs/pkc/src/bls_chia/pk.rs b/pkgs/pkc/src/bls_chia/pk.rs index 887d0ce9..6b073d07 100644 --- a/pkgs/pkc/src/bls_chia/pk.rs +++ b/pkgs/pkc/src/bls_chia/pk.rs @@ -17,7 +17,7 @@ use blst::blst_p1_affine; #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] #[cfg_attr( feature = "serde", - serde(into = "dash_types::BlsPublicKeyBytes", try_from = "dash_types::BlsPublicKeyBytes",) + serde(into = "crate::BlsPublicKeyBytes", try_from = "crate::BlsPublicKeyBytes",) )] pub struct PublicKey(pub(super) blst_p1_affine); @@ -58,16 +58,16 @@ impl PublicKey { crate::common::bls::impl_hash_via_bytes!(PublicKey); -impl From for dash_types::BlsPublicKeyBytes { +impl From for crate::BlsPublicKeyBytes { fn from(pk: PublicKey) -> Self { Self(pk.to_bytes()) } } -impl TryFrom for PublicKey { +impl TryFrom for PublicKey { type Error = super::error::Error; - fn try_from(bytes: dash_types::BlsPublicKeyBytes) -> Result { + fn try_from(bytes: crate::BlsPublicKeyBytes) -> Result { Self::from_bytes(&bytes.0) } } diff --git a/pkgs/pkc/src/bls_chia/sig.rs b/pkgs/pkc/src/bls_chia/sig.rs index d1d0996b..3b4cad1d 100644 --- a/pkgs/pkc/src/bls_chia/sig.rs +++ b/pkgs/pkc/src/bls_chia/sig.rs @@ -18,7 +18,7 @@ use blst::*; #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] #[cfg_attr( feature = "serde", - serde(into = "dash_types::BlsSignatureBytes", try_from = "dash_types::BlsSignatureBytes",) + serde(into = "crate::BlsSignatureBytes", try_from = "crate::BlsSignatureBytes",) )] pub struct Signature(pub(super) blst_p2_affine); @@ -69,16 +69,16 @@ impl Signature { crate::common::bls::impl_hash_via_bytes!(Signature); -impl From for dash_types::BlsSignatureBytes { +impl From for crate::BlsSignatureBytes { fn from(sig: Signature) -> Self { Self(sig.to_bytes()) } } -impl TryFrom for Signature { +impl TryFrom for Signature { type Error = super::error::Error; - fn try_from(bytes: dash_types::BlsSignatureBytes) -> Result { + fn try_from(bytes: crate::BlsSignatureBytes) -> Result { Self::from_bytes(&bytes.0) } } diff --git a/pkgs/pkc/src/bls_ietf/pk.rs b/pkgs/pkc/src/bls_ietf/pk.rs index dcad5ef0..f2e13129 100644 --- a/pkgs/pkc/src/bls_ietf/pk.rs +++ b/pkgs/pkc/src/bls_ietf/pk.rs @@ -19,7 +19,7 @@ use blst::BLST_ERROR; #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] #[cfg_attr( feature = "serde", - serde(into = "dash_types::BlsPublicKeyBytes", try_from = "dash_types::BlsPublicKeyBytes",) + serde(into = "crate::BlsPublicKeyBytes", try_from = "crate::BlsPublicKeyBytes",) )] pub struct PublicKey(pub(super) min_pk::PublicKey); @@ -81,16 +81,16 @@ impl PublicKey { crate::common::bls::impl_hash_via_bytes!(PublicKey); -impl From for dash_types::BlsPublicKeyBytes { +impl From for crate::BlsPublicKeyBytes { fn from(pk: PublicKey) -> Self { Self(pk.to_bytes()) } } -impl TryFrom for PublicKey { +impl TryFrom for PublicKey { type Error = super::error::Error; - fn try_from(bytes: dash_types::BlsPublicKeyBytes) -> Result { + fn try_from(bytes: crate::BlsPublicKeyBytes) -> Result { Self::from_bytes(&bytes.0) } } diff --git a/pkgs/pkc/src/bls_ietf/sig.rs b/pkgs/pkc/src/bls_ietf/sig.rs index 687ebb5a..e6f88764 100644 --- a/pkgs/pkc/src/bls_ietf/sig.rs +++ b/pkgs/pkc/src/bls_ietf/sig.rs @@ -19,7 +19,7 @@ use blst::BLST_ERROR; #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] #[cfg_attr( feature = "serde", - serde(into = "dash_types::BlsSignatureBytes", try_from = "dash_types::BlsSignatureBytes",) + serde(into = "crate::BlsSignatureBytes", try_from = "crate::BlsSignatureBytes",) )] pub struct Signature(pub(super) min_pk::Signature); @@ -66,16 +66,16 @@ impl Signature { crate::common::bls::impl_hash_via_bytes!(Signature); -impl From for dash_types::BlsSignatureBytes { +impl From for crate::BlsSignatureBytes { fn from(sig: Signature) -> Self { Self(sig.to_bytes()) } } -impl TryFrom for Signature { +impl TryFrom for Signature { type Error = super::error::Error; - fn try_from(bytes: dash_types::BlsSignatureBytes) -> Result { + fn try_from(bytes: crate::BlsSignatureBytes) -> Result { Self::from_bytes(&bytes.0) } } diff --git a/pkgs/pkc/src/k256/pk.rs b/pkgs/pkc/src/k256/pk.rs index d8736ee9..3926de7f 100644 --- a/pkgs/pkc/src/k256/pk.rs +++ b/pkgs/pkc/src/k256/pk.rs @@ -16,10 +16,7 @@ use k256::ecdsa::{self, signature::hazmat::PrehashVerifier}; #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] #[cfg_attr( feature = "serde", - serde( - into = "dash_types::EcdsaPublicKeyBytes", - try_from = "dash_types::EcdsaPublicKeyBytes", - ) + serde(into = "crate::EcdsaPublicKeyBytes", try_from = "crate::EcdsaPublicKeyBytes",) )] pub struct PublicKey(ecdsa::VerifyingKey); @@ -73,16 +70,16 @@ impl core::hash::Hash for PublicKey { } } -impl From for dash_types::EcdsaPublicKeyBytes { +impl From for crate::EcdsaPublicKeyBytes { fn from(pk: PublicKey) -> Self { Self(pk.to_bytes()) } } -impl TryFrom for PublicKey { +impl TryFrom for PublicKey { type Error = super::error::Error; - fn try_from(bytes: dash_types::EcdsaPublicKeyBytes) -> Result { + fn try_from(bytes: crate::EcdsaPublicKeyBytes) -> Result { Self::from_bytes(&bytes.0) } } diff --git a/pkgs/pkc/src/k256/sig.rs b/pkgs/pkc/src/k256/sig.rs index 9437ad30..56cfd9cc 100644 --- a/pkgs/pkc/src/k256/sig.rs +++ b/pkgs/pkc/src/k256/sig.rs @@ -15,10 +15,7 @@ use k256::ecdsa; #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] #[cfg_attr( feature = "serde", - serde( - into = "dash_types::EcdsaSignatureBytes", - try_from = "dash_types::EcdsaSignatureBytes", - ) + serde(into = "crate::EcdsaSignatureBytes", try_from = "crate::EcdsaSignatureBytes",) )] pub struct Signature(ecdsa::Signature); @@ -62,16 +59,16 @@ impl core::hash::Hash for Signature { } } -impl From for dash_types::EcdsaSignatureBytes { +impl From for crate::EcdsaSignatureBytes { fn from(sig: Signature) -> Self { Self(sig.to_compact()) } } -impl TryFrom for Signature { +impl TryFrom for Signature { type Error = super::error::Error; - fn try_from(bytes: dash_types::EcdsaSignatureBytes) -> Result { + fn try_from(bytes: crate::EcdsaSignatureBytes) -> Result { Self::from_compact(&bytes.0) } } diff --git a/pkgs/pkc/src/lib.rs b/pkgs/pkc/src/lib.rs index e9a8a9dd..f40b24df 100644 --- a/pkgs/pkc/src/lib.rs +++ b/pkgs/pkc/src/lib.rs @@ -27,3 +27,23 @@ pub mod k256; pub mod worker; pub use dash_num::Hash256; + +dash_types::make_bytes! { + /// Raw BLS public key bytes (48 bytes, unvalidated). + BlsPublicKeyBytes, 48 +} + +dash_types::make_bytes! { + /// Raw BLS signature bytes (96 bytes, unvalidated). + BlsSignatureBytes, 96 +} + +dash_types::make_bytes! { + /// Raw compressed ECDSA public key bytes (33 bytes, unvalidated). + EcdsaPublicKeyBytes, 33 +} + +dash_types::make_bytes! { + /// Raw compact ECDSA signature bytes (64 bytes, unvalidated). + EcdsaSignatureBytes, 64 +} diff --git a/pkgs/primitives/Cargo.toml b/pkgs/primitives/Cargo.toml index eff30a9a..d12514a6 100644 --- a/pkgs/primitives/Cargo.toml +++ b/pkgs/primitives/Cargo.toml @@ -7,20 +7,30 @@ license = "MIT" [features] default = [] std = [ + "base58ck/std", "bitcoin-consensus-encoding/std", "bitcoin-internals/std", "bitcoin_hashes/std", "bitcoin-units/std", "dash-num/std", + "dash-pkc/std", "dash-script/std", "dash-types/std", "hex-conservative/std", ] -serde = ["dep:serde", "bitcoin-units/serde", "dash-num/serde", "dash-script/serde", "dash-types/serde"] +serde = [ + "dep:serde", + "bitcoin-units/serde", + "dash-num/serde", + "dash-pkc/serde", + "dash-script/serde", + "dash-types/serde", +] full = ["std", "serde"] _internal = [] [dependencies] +base58ck = { version = "0.4", default-features = false, features = ["alloc"] } bitcoin-consensus-encoding = { version = "0.2", default-features = false, features = [ "alloc", ] } @@ -32,6 +42,7 @@ bitcoin-units = { version = "0.3", default-features = false, features = [ "alloc", ] } dash-num = { version = "0.0.0", path = "../num" } +dash-pkc = { version = "0.0.0", path = "../pkc", default-features = false } dash-script = { version = "0.0.0", path = "../script" } dash-types = { version = "0.0.0", path = "../types", default-features = false } cfg-if = "1" diff --git a/pkgs/primitives/src/block.rs b/pkgs/primitives/src/block.rs index 53d4c2d0..e09dbbb9 100644 --- a/pkgs/primitives/src/block.rs +++ b/pkgs/primitives/src/block.rs @@ -6,16 +6,98 @@ //! Dash block (header + transactions). -use crate::block_header::BlockHeader; use crate::codec_type; use crate::prelude::*; use crate::transaction::{Transaction, TxInvalid}; -use crate::validation::MAX_DIP0001_BLOCK_SIZE; +use dash_num::{make_hash, Hash256}; use dash_types::codec::Checkable; use core::fmt; +/// Maximum serialized transaction size (single tx, always 1 MB). +pub const MAX_LEGACY_BLOCK_SIZE: usize = 1_000_000; + +/// Post-DIP0001 maximum block size (2 MB). +pub const MAX_DIP0001_BLOCK_SIZE: usize = 2_000_000; + +make_hash! { + Hash256, + /// Hash of a block header. + BlockHash +} + +make_hash! { + Hash256, + /// Merkle tree root hash. + MerkleRoot +} + +/// A block header. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct BlockHeader { + /// Block version. + pub version: i32, + /// Hash of the previous block header. + pub prev_hash: BlockHash, + /// Merkle root of the transaction tree. + pub merkle_root: MerkleRoot, + /// Block timestamp (unix epoch seconds). + pub time: u32, + /// Compact difficulty target (nBits). + pub bits: u32, + /// Nonce used for proof-of-work. + pub nonce: u32, +} + +codec_type!(BlockHeader { + version, + prev_hash, + merkle_root, + time, + bits, + nonce, +}); + +impl fmt::Display for BlockHeader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "BlockHeader {{ version: {}, prev_hash: {}, time: {} }}", + self.version, self.prev_hash, self.time, + ) + } +} + +/// Block validation failure. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum BlockInvalid { + /// `bad-blk-length` + BadBlockLength { size: usize }, + /// `bad-cb-missing` + MissingCoinbase, + /// `bad-cb-multiple` + MultipleCoinbases { index: usize }, + /// `bad-blk-sigops` + TooManySigops { count: usize, limit: usize }, + /// A contained transaction failed validation. + Transaction { index: usize, error: TxInvalid }, +} + +impl fmt::Display for BlockInvalid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::BadBlockLength { size } => write!(f, "bad-blk-length: {size} bytes"), + Self::MissingCoinbase => write!(f, "bad-cb-missing"), + Self::MultipleCoinbases { index } => write!(f, "bad-cb-multiple: tx {index}"), + Self::TooManySigops { count, limit } => write!(f, "bad-blk-sigops: {count} > {limit}"), + Self::Transaction { index, error } => write!(f, "tx {index}: {error}"), + } + } +} + /// A Dash block: header followed by a vector of transactions. #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] @@ -78,33 +160,6 @@ impl fmt::Display for Block { } } -/// Block validation failure. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum BlockInvalid { - /// `bad-blk-length` - BadBlockLength { size: usize }, - /// `bad-cb-missing` - MissingCoinbase, - /// `bad-cb-multiple` - MultipleCoinbases { index: usize }, - /// `bad-blk-sigops` - TooManySigops { count: usize, limit: usize }, - /// A contained transaction failed validation. - Transaction { index: usize, error: TxInvalid }, -} - -impl fmt::Display for BlockInvalid { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::BadBlockLength { size } => write!(f, "bad-blk-length: {size} bytes"), - Self::MissingCoinbase => write!(f, "bad-cb-missing"), - Self::MultipleCoinbases { index } => write!(f, "bad-cb-multiple: tx {index}"), - Self::TooManySigops { count, limit } => write!(f, "bad-blk-sigops: {count} > {limit}"), - Self::Transaction { index, error } => write!(f, "tx {index}: {error}"), - } - } -} - #[cfg(all(test, feature = "serde"))] #[expect(clippy::panic, clippy::unwrap_used, reason = "test code")] mod tests { diff --git a/pkgs/primitives/src/block_header.rs b/pkgs/primitives/src/block_header.rs deleted file mode 100644 index 92fb1fda..00000000 --- a/pkgs/primitives/src/block_header.rs +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Dash block header (80 bytes). - -use crate::codec_type; -use crate::{BlockHash, MerkleRoot}; - -use core::fmt; - -/// A Dash block header (80 bytes on the wire). -#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct BlockHeader { - /// Block version. - pub version: i32, - /// Hash of the previous block header. - pub prev_hash: BlockHash, - /// Merkle root of the transaction tree. - pub merkle_root: MerkleRoot, - /// Block timestamp (unix epoch seconds). - pub time: u32, - /// Compact difficulty target (nBits). - pub bits: u32, - /// Nonce used for proof-of-work. - pub nonce: u32, -} - -codec_type!(BlockHeader { - version, - prev_hash, - merkle_root, - time, - bits, - nonce, -}); - -impl fmt::Display for BlockHeader { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "BlockHeader {{ version: {}, prev_hash: {}, time: {} }}", - self.version, self.prev_hash, self.time, - ) - } -} diff --git a/pkgs/primitives/src/gov.rs b/pkgs/primitives/src/gov.rs index 74962a2e..b9e44857 100644 --- a/pkgs/primitives/src/gov.rs +++ b/pkgs/primitives/src/gov.rs @@ -7,8 +7,8 @@ //! Governance object and vote types as defined by the Dash protocol. use crate::codec_type; -use crate::outpoint::OutPoint; use crate::prelude::*; +use crate::transaction::OutPoint; use crate::TxHash; use bitcoin_hashes::sha256d; @@ -31,12 +31,12 @@ const PROPOSAL_NAME_CHARS: &[u8] = b"-_abcdefghijklmnopqrstuvwxyz0123456789"; /// Governance object type codes. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum GovObjectType { - /// Unknown or unrecognized type. - Unknown, /// Budget proposal. Proposal, /// Superblock trigger. Trigger, + /// Unknown or unrecognized type. + Unknown(i32), } impl NumCodec for GovObjectType { @@ -44,15 +44,15 @@ impl NumCodec for GovObjectType { match v { 1 => Self::Proposal, 2 => Self::Trigger, - _ => Self::Unknown, + other => Self::Unknown(other), } } fn to_base(&self) -> i32 { match self { - Self::Unknown => 0, Self::Proposal => 1, Self::Trigger => 2, + Self::Unknown(v) => *v, } } } @@ -62,9 +62,9 @@ impl_num!(GovObjectType, i32); impl fmt::Display for GovObjectType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Unknown => write!(f, "unknown"), Self::Proposal => write!(f, "proposal"), Self::Trigger => write!(f, "trigger"), + Self::Unknown(v) => write!(f, "unknown({v})"), } } } diff --git a/pkgs/primitives/src/lib.rs b/pkgs/primitives/src/lib.rs index bcd91732..f0b36c33 100644 --- a/pkgs/primitives/src/lib.rs +++ b/pkgs/primitives/src/lib.rs @@ -12,70 +12,45 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; +mod block; +mod codec; +mod gov; +mod hash; +mod payload; #[allow(unused_imports, reason = "ergonomic shim, exports may be unused")] mod prelude; +mod script; +mod support; +mod transaction; +mod types; + #[doc(hidden)] pub mod __private { pub use dash_types; } - -pub mod block; -pub mod block_header; -pub mod codec; -pub mod gov; -pub mod hash; -pub mod outpoint; -pub mod payload; -pub mod script; #[cfg(feature = "serde")] pub mod serialize; -pub mod support; -pub mod transaction; -pub mod tx_in; -pub mod tx_out; -pub mod tx_types; -pub mod validation; - -dash_num::make_hash256! { - /// Hash of a block header. - BlockHash -} - -dash_num::make_hash256! { - /// SHA256d hash of a serialized transaction. - TxHash -} - -dash_num::make_hash256! { - /// Merkle tree root hash. - MerkleRoot -} - -dash_num::make_hash256! { - /// Hash of serialized transaction inputs. - InputsHash -} - -dash_num::make_hash256! { - /// LLMQ quorum identifier. - QuorumHash -} - -dash_num::make_hash256! { - /// Quorum verification vector hash. - QuorumVvecHash -} -pub use block::Block; -pub use block_header::BlockHeader; +pub use block::{ + Block, BlockHash, BlockHeader, BlockInvalid, MerkleRoot, MAX_DIP0001_BLOCK_SIZE, MAX_LEGACY_BLOCK_SIZE, +}; pub use codec::MAX_SPTX_PAYLOAD_SIZE; -pub use dash_types::AddrV1; -pub use outpoint::OutPoint; -pub use script::Script; -pub use support::{ - CService, DynBitset, ExtendedNetInfo, LlmqType, NetInfoEntry, NetInfoPurpose, NetworkType, RevocationReason, +pub use dash_pkc::{BlsPublicKeyBytes, BlsSignatureBytes, EcdsaPublicKeyBytes, EcdsaSignatureBytes}; +pub use gov::{ + GovData, GovObject, GovObjectType, GovVote, Proposal, ProposalInvalid, Trigger, VoteOutcome, VoteSignal, +}; +pub use hash::{double_sha256, tx_hash}; +pub use payload::{ + AssetLock, AssetLockInvalid, AssetUnlock, AssetUnlockInvalid, CbTxInvalid, CoinbaseCommitment, Commitment, + CommitmentInvalid, FinalCommitment, InputsHash, MnHardFork, MnHardForkInvalid, MnType, PayloadError, PayloadInvalid, + PlatformNodeId, ProRegTx, ProTxInvalid, ProUpRegTx, ProUpRevTx, ProUpServTx, QuorumHash, QuorumVvecHash, + SpecialPayload, TxType, VERSIONBITS_NUM_BITS, +}; +pub use script::{KeyId, Script}; +pub use support::{DynBitset, DynBitsetIterator, LlmqType, RevocationReason}; +pub use transaction::{ + OutPoint, Transaction, TxHash, TxIn, TxInvalid, TxOut, MAX_COINBASE_SCRIPT_SIZE, MAX_TX_EXTRA_PAYLOAD, +}; +pub use types::{ + AddrV1, AddrV2, ExtendedNetInfo, NetInfo, NetInfoEntry, NetInfoPurpose, NetworkType, ServiceV1, ServiceV2, }; -pub use transaction::Transaction; -pub use tx_in::TxIn; -pub use tx_out::TxOut; -pub use tx_types::{MnType, TxType}; diff --git a/pkgs/primitives/src/outpoint.rs b/pkgs/primitives/src/outpoint.rs deleted file mode 100644 index b9d61d07..00000000 --- a/pkgs/primitives/src/outpoint.rs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Transaction outpoint (36 bytes). - -use crate::codec_type; -use crate::TxHash; - -use core::fmt; - -/// A reference to a previous transaction output. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct OutPoint { - /// Transaction hash of the referenced output. - pub hash: TxHash, - /// Index of the referenced output within the transaction. - pub index: u32, -} - -codec_type!(OutPoint { hash, index }); - -impl fmt::Display for OutPoint { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}:{}", self.hash, self.index) - } -} diff --git a/pkgs/primitives/src/payload/assetlock.rs b/pkgs/primitives/src/payload/assetlock.rs index 6390a38f..a0e9c2aa 100644 --- a/pkgs/primitives/src/payload/assetlock.rs +++ b/pkgs/primitives/src/payload/assetlock.rs @@ -8,7 +8,7 @@ use crate::codec::codec_payload; use crate::prelude::*; -use crate::tx_out::TxOut; +use crate::transaction::TxOut; use dash_types::codec::Checkable; diff --git a/pkgs/primitives/src/payload/assetunlock.rs b/pkgs/primitives/src/payload/assetunlock.rs index a6f451c3..29d81a2d 100644 --- a/pkgs/primitives/src/payload/assetunlock.rs +++ b/pkgs/primitives/src/payload/assetunlock.rs @@ -6,11 +6,11 @@ //! AssetUnlock (type 9): Platform to L1. +use super::QuorumHash; use crate::codec::codec_payload; -use crate::QuorumHash; +use dash_pkc::BlsSignatureBytes; use dash_types::codec::Checkable; -use dash_types::BlsSignatureBytes; use core::fmt; diff --git a/pkgs/primitives/src/payload/cbtx.rs b/pkgs/primitives/src/payload/cbtx.rs index 242540be..aa8e31cb 100644 --- a/pkgs/primitives/src/payload/cbtx.rs +++ b/pkgs/primitives/src/payload/cbtx.rs @@ -11,8 +11,8 @@ use crate::prelude::*; use crate::MerkleRoot; use bitcoin_units::BlockHeight; +use dash_pkc::BlsSignatureBytes; use dash_types::codec::{self, BaseCodec, Checkable, DecodeError}; -use dash_types::BlsSignatureBytes; use core::fmt; diff --git a/pkgs/primitives/src/payload/mnhftx.rs b/pkgs/primitives/src/payload/mnhftx.rs index 53eee2f0..e661ac8c 100644 --- a/pkgs/primitives/src/payload/mnhftx.rs +++ b/pkgs/primitives/src/payload/mnhftx.rs @@ -6,15 +6,17 @@ //! MnHardFork hard-fork signal (type 7). +use super::QuorumHash; use crate::codec::codec_payload; -use crate::validation::VERSIONBITS_NUM_BITS; -use crate::QuorumHash; +use dash_pkc::BlsSignatureBytes; use dash_types::codec::Checkable; -use dash_types::BlsSignatureBytes; use core::fmt; +/// Number of version bits available for signalling. +pub const VERSIONBITS_NUM_BITS: u8 = 29; + /// MnHardFork -- hard-fork signal (type 7). #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] diff --git a/pkgs/primitives/src/payload/mod.rs b/pkgs/primitives/src/payload/mod.rs index 14e4b1e4..c3254e66 100644 --- a/pkgs/primitives/src/payload/mod.rs +++ b/pkgs/primitives/src/payload/mod.rs @@ -20,22 +20,263 @@ mod proupservtx; mod quorum; use crate::prelude::*; -use crate::tx_types::TxType; -use crate::validation::ProTxInvalid; +use crate::types::{NetInfoEntry, NetInfoPurpose}; -use dash_types::codec::Checkable; +use dash_num::{make_hash, Hash256}; +use dash_types::codec::{Checkable, NumCodec}; +use dash_types::impl_num; use core::fmt; +/// Maximum operator reward in basis points. +pub(crate) const MAX_OPERATOR_REWARD: u16 = 10_000; + +/// ProTx version: legacy BLS operator keys (v1). +#[expect(unused, reason = "consensus constant")] +pub(crate) const PROTX_VERSION_LEGACY_BLS: u16 = 1; + +/// ProTx version: basic (IETF) BLS operator keys (v2). +pub(crate) const PROTX_VERSION_BASIC_BLS: u16 = 2; + +/// ProTx version: extended network addresses (v3). +pub(crate) const PROTX_VERSION_EXT_ADDR: u16 = 3; + +make_hash! { + Hash256, + /// LLMQ quorum identifier. + QuorumHash +} + +make_hash! { + Hash256, + /// Hash of serialized transaction inputs. + InputsHash +} + +/// Dash transaction type, encoded in the upper 16 bits of the version field. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum TxType { + /// Spend transaction (includes legacy coinbase). + Spend, + /// Masternode registration (type 1). + ProviderRegister, + /// Masternode service address update (type 2). + ProviderUpdateService, + /// Masternode registrar key update (type 3). + ProviderUpdateRegistrar, + /// Masternode revocation (type 4). + ProviderUpdateRevoke, + /// Coinbase commitment special transaction (type 5). + CoinbaseCommitment, + /// LLMQ final commitment (type 6). + QuorumCommitment, + /// Masternode hard fork signal (type 7). + MnhfSignal, + /// Asset lock: L1 to platform (type 8). + AssetLock, + /// Asset unlock: platform to L1 (type 9). + AssetUnlock, + /// Unknown or future transaction type. + Unknown(u16), +} + +impl NumCodec for TxType { + fn from_base(value: u16) -> Self { + match value { + 0 => Self::Spend, + 1 => Self::ProviderRegister, + 2 => Self::ProviderUpdateService, + 3 => Self::ProviderUpdateRegistrar, + 4 => Self::ProviderUpdateRevoke, + 5 => Self::CoinbaseCommitment, + 6 => Self::QuorumCommitment, + 7 => Self::MnhfSignal, + 8 => Self::AssetLock, + 9 => Self::AssetUnlock, + other => Self::Unknown(other), + } + } + + fn to_base(&self) -> u16 { + match self { + Self::Spend => 0, + Self::ProviderRegister => 1, + Self::ProviderUpdateService => 2, + Self::ProviderUpdateRegistrar => 3, + Self::ProviderUpdateRevoke => 4, + Self::CoinbaseCommitment => 5, + Self::QuorumCommitment => 6, + Self::MnhfSignal => 7, + Self::AssetLock => 8, + Self::AssetUnlock => 9, + Self::Unknown(v) => *v, + } + } +} + +impl_num!(TxType, u16); + +impl fmt::Display for TxType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Spend => write!(f, "spend"), + Self::ProviderRegister => write!(f, "provider_register"), + Self::ProviderUpdateService => write!(f, "provider_update_service"), + Self::ProviderUpdateRegistrar => write!(f, "provider_update_registrar"), + Self::ProviderUpdateRevoke => write!(f, "provider_update_revoke"), + Self::CoinbaseCommitment => write!(f, "coinbase_commitment"), + Self::QuorumCommitment => write!(f, "quorum_commitment"), + Self::MnhfSignal => write!(f, "mnhf_signal"), + Self::AssetLock => write!(f, "asset_lock"), + Self::AssetUnlock => write!(f, "asset_unlock"), + Self::Unknown(v) => write!(f, "unknown({v})"), + } + } +} + +/// Masternode type, used in provider registration and update transactions. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum MnType { + /// Regular masternode. + Regular, + /// Evolution (Evo) masternode with platform capabilities. + Evo, + /// Unknown or future masternode type. + Unknown(u16), +} + +impl NumCodec for MnType { + fn from_base(value: u16) -> Self { + match value { + 0 => Self::Regular, + 1 => Self::Evo, + other => Self::Unknown(other), + } + } + + fn to_base(&self) -> u16 { + match self { + Self::Regular => 0, + Self::Evo => 1, + Self::Unknown(v) => *v, + } + } +} + +impl_num!(MnType, u16); + +impl fmt::Display for MnType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Regular => write!(f, "regular"), + Self::Evo => write!(f, "evo"), + Self::Unknown(v) => write!(f, "unknown({v})"), + } + } +} + +/// Provider transaction validation failure. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ProTxInvalid { + /// `bad-protx-version` + BadVersion { version: u16 }, + /// `bad-protx-evo-version` + EvoVersionTooLow { version: u16 }, + /// `bad-protx-type` + BadMnType { mn_type: MnType }, + /// `bad-protx-mode` + BadMode { mode: u16 }, + /// `bad-protx-key-null` + NullKey, + /// `bad-protx-operator-pubkey` + OperatorKeyMismatch, + /// `bad-protx-payee` + BadPayoutScript, + /// `bad-protx-netinfo-version` + NetInfoVersionMismatch, + /// `bad-protx-netinfo-empty` + NetInfoEmpty, + /// `bad-protx-netinfo-bad` + NetInfoInvalid, + /// `bad-protx-payee-reuse` + PayoutKeyReuse, + /// `bad-protx-operator-reward` + OperatorRewardTooHigh { reward: u16 }, + /// `bad-protx-reason` + BadReason { reason: crate::support::RevocationReason }, + /// `bad-protx-platform-fields` + BadPlatformFields, +} + +impl fmt::Display for ProTxInvalid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::BadVersion { version } => write!(f, "bad-protx-version: {version}"), + Self::EvoVersionTooLow { version } => write!(f, "bad-protx-evo-version: {version}"), + Self::BadMnType { mn_type } => write!(f, "bad-protx-type: {mn_type}"), + Self::BadMode { mode } => write!(f, "bad-protx-mode: {mode}"), + Self::NullKey => write!(f, "bad-protx-key-null"), + Self::OperatorKeyMismatch => write!(f, "bad-protx-operator-pubkey"), + Self::BadPayoutScript => write!(f, "bad-protx-payee"), + Self::NetInfoVersionMismatch => write!(f, "bad-protx-netinfo-version"), + Self::NetInfoEmpty => write!(f, "bad-protx-netinfo-empty"), + Self::NetInfoInvalid => write!(f, "bad-protx-netinfo-bad"), + Self::PayoutKeyReuse => write!(f, "bad-protx-payee-reuse"), + Self::OperatorRewardTooHigh { reward } => write!(f, "bad-protx-operator-reward: {reward}"), + Self::BadReason { reason } => write!(f, "bad-protx-reason: {reason}"), + Self::BadPlatformFields => write!(f, "bad-protx-platform-fields"), + } + } +} + +/// Checks that an extended net info payload is trivially valid. +pub(crate) fn check_sptx_netinfo( + entries: &[(NetInfoPurpose, Vec)], + mn_type: MnType, + can_store_platform: bool, +) -> Option { + let has_core = entries + .iter() + .any(|(p, e)| *p == NetInfoPurpose::CoreP2p && !e.is_empty()); + if !has_core { + return Some(ProTxInvalid::NetInfoEmpty); + } + + let has_platform_p2p = entries + .iter() + .any(|(p, e)| *p == NetInfoPurpose::PlatformP2p && !e.is_empty()); + let has_platform_https = entries + .iter() + .any(|(p, e)| *p == NetInfoPurpose::PlatformHttps && !e.is_empty()); + + if mn_type == MnType::Regular && (has_platform_p2p || has_platform_https) { + return Some(ProTxInvalid::NetInfoInvalid); + } + + if can_store_platform && mn_type == MnType::Evo && (!has_platform_p2p || !has_platform_https) { + return Some(ProTxInvalid::NetInfoEmpty); + } + + for (_purpose, group) in entries { + for entry in group { + if matches!(entry, NetInfoEntry::Invalid) { + return Some(ProTxInvalid::NetInfoInvalid); + } + } + } + + None +} + pub use assetlock::{AssetLock, AssetLockInvalid}; pub use assetunlock::{AssetUnlock, AssetUnlockInvalid}; pub use cbtx::{CbTxInvalid, CoinbaseCommitment}; -pub use mnhftx::{MnHardFork, MnHardForkInvalid}; -pub use proregtx::{NetInfo, ProRegTx}; +pub use mnhftx::{MnHardFork, MnHardForkInvalid, VERSIONBITS_NUM_BITS}; +pub use proregtx::{PlatformNodeId, ProRegTx}; pub use proupregtx::ProUpRegTx; pub use prouprevtx::ProUpRevTx; pub use proupservtx::ProUpServTx; -pub use quorum::{Commitment, CommitmentInvalid, FinalCommitment}; +pub use quorum::{Commitment, CommitmentInvalid, FinalCommitment, QuorumVvecHash}; /// A decoded special transaction payload. /// diff --git a/pkgs/primitives/src/payload/proregtx.rs b/pkgs/primitives/src/payload/proregtx.rs index ebc24df1..042f8dcc 100644 --- a/pkgs/primitives/src/payload/proregtx.rs +++ b/pkgs/primitives/src/payload/proregtx.rs @@ -6,32 +6,21 @@ //! ProRegTx registration payload (type 1). +use super::{ + check_sptx_netinfo, InputsHash, MnType, ProTxInvalid, MAX_OPERATOR_REWARD, PROTX_VERSION_BASIC_BLS, + PROTX_VERSION_EXT_ADDR, +}; use crate::codec::impl_payload; use crate::prelude::*; -use crate::script::Script; -use crate::support::CService; -use crate::tx_types::MnType; -use crate::validation::{ - check_sptx_netinfo, ProTxInvalid, MAX_OPERATOR_REWARD, PROTX_VERSION_BASIC_BLS, PROTX_VERSION_EXT_ADDR, -}; -use crate::{InputsHash, TxHash}; +use crate::script::{KeyId, Script}; +use crate::types::{ExtendedNetInfo, NetInfo, ServiceV1}; +use crate::TxHash; +use dash_pkc::BlsPublicKeyBytes; use dash_types::codec::{BaseCodec, Checkable, DecodeError, NumCodec}; -use dash_types::{BlsPublicKeyBytes, KeyId, PlatformNodeId}; use core::fmt; -/// Masternode network info: legacy CService or structured extended format. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub enum NetInfo { - /// ADDRv1 CService (18 bytes). - Legacy(CService), - /// Extended format (v3+) with purpose-grouped entries. - Extended(crate::support::ExtendedNetInfo), -} - /// ProRegTx -- register a new masternode (type 1). /// /// - v1: LegacyBLS @@ -51,7 +40,7 @@ pub struct ProRegTx { pub collateral_hash: TxHash, /// Collateral index. pub collateral_index: u32, - /// Legacy CService or extended NetInfo. + /// Legacy ServiceV1 or extended NetInfo. pub net_info: NetInfo, /// Owner key id (20 bytes). pub key_id_owner: KeyId, @@ -114,9 +103,9 @@ impl BaseCodec for ProRegTx { let collateral_index = u32::decode(data)?; let net_info = if version >= 3 { let raw: Vec = Vec::decode(data)?; - NetInfo::Extended(crate::support::ExtendedNetInfo::decode(&mut &raw[..])?) + NetInfo::Extended(ExtendedNetInfo::decode(&mut &raw[..])?) } else { - NetInfo::Legacy(CService::decode(data)?) + NetInfo::Legacy(ServiceV1::decode(data)?) }; let key_id_owner = KeyId::decode(data)?; let pub_key_operator = BlsPublicKeyBytes::decode(data)?; @@ -265,6 +254,11 @@ impl fmt::Display for ProRegTx { } } +dash_types::make_bytes! { + /// Platform node identifier for Evo masternodes. + PlatformNodeId, 20 +} + #[cfg(all(test, feature = "serde"))] mod tests { use super::*; diff --git a/pkgs/primitives/src/payload/proupregtx.rs b/pkgs/primitives/src/payload/proupregtx.rs index dfc4c603..7f783966 100644 --- a/pkgs/primitives/src/payload/proupregtx.rs +++ b/pkgs/primitives/src/payload/proupregtx.rs @@ -6,14 +6,14 @@ //! ProUpRegTx registrar-update payload (type 3). +use super::{InputsHash, ProTxInvalid}; use crate::codec::codec_payload; use crate::prelude::*; -use crate::script::Script; -use crate::validation::ProTxInvalid; -use crate::{InputsHash, TxHash}; +use crate::script::{KeyId, Script}; +use crate::TxHash; +use dash_pkc::BlsPublicKeyBytes; use dash_types::codec::Checkable; -use dash_types::{BlsPublicKeyBytes, KeyId}; use core::fmt; diff --git a/pkgs/primitives/src/payload/prouprevtx.rs b/pkgs/primitives/src/payload/prouprevtx.rs index af6b9903..f4691626 100644 --- a/pkgs/primitives/src/payload/prouprevtx.rs +++ b/pkgs/primitives/src/payload/prouprevtx.rs @@ -6,13 +6,13 @@ //! ProUpRevTx revocation payload (type 4). +use super::{InputsHash, ProTxInvalid}; use crate::codec::codec_payload; use crate::support::RevocationReason; -use crate::validation::ProTxInvalid; -use crate::{InputsHash, TxHash}; +use crate::TxHash; +use dash_pkc::BlsSignatureBytes; use dash_types::codec::Checkable; -use dash_types::BlsSignatureBytes; use core::fmt; diff --git a/pkgs/primitives/src/payload/proupservtx.rs b/pkgs/primitives/src/payload/proupservtx.rs index 036e8ff0..9e022034 100644 --- a/pkgs/primitives/src/payload/proupservtx.rs +++ b/pkgs/primitives/src/payload/proupservtx.rs @@ -6,17 +6,16 @@ //! ProUpServTx service-update payload (type 2). -use super::proregtx::{check_platform_fields, NetInfo}; +use super::proregtx::{check_platform_fields, PlatformNodeId}; +use super::{check_sptx_netinfo, InputsHash, MnType, ProTxInvalid, PROTX_VERSION_BASIC_BLS, PROTX_VERSION_EXT_ADDR}; use crate::codec::impl_payload; use crate::prelude::*; use crate::script::Script; -use crate::support::CService; -use crate::tx_types::MnType; -use crate::validation::{check_sptx_netinfo, ProTxInvalid, PROTX_VERSION_BASIC_BLS, PROTX_VERSION_EXT_ADDR}; -use crate::{InputsHash, TxHash}; +use crate::types::{ExtendedNetInfo, NetInfo, ServiceV1}; +use crate::TxHash; +use dash_pkc::BlsSignatureBytes; use dash_types::codec::{BaseCodec, Checkable, DecodeError, NumCodec}; -use dash_types::{BlsSignatureBytes, PlatformNodeId}; use core::fmt; @@ -35,7 +34,7 @@ pub struct ProUpServTx { pub mn_type: MnType, /// ProTx hash identifying the masternode. pub pro_tx_hash: TxHash, - /// Legacy CService or extended NetInfo. + /// Legacy ServiceV1 or extended NetInfo. pub net_info: NetInfo, /// Operator payout script. pub script_operator_payout: Script, @@ -68,9 +67,9 @@ impl BaseCodec for ProUpServTx { let pro_tx_hash = TxHash::decode(data)?; let net_info = if version >= 3 { let raw: Vec = Vec::decode(data)?; - NetInfo::Extended(crate::support::ExtendedNetInfo::decode(&mut &raw[..])?) + NetInfo::Extended(ExtendedNetInfo::decode(&mut &raw[..])?) } else { - NetInfo::Legacy(CService::decode(data)?) + NetInfo::Legacy(ServiceV1::decode(data)?) }; let script_operator_payout = Script::decode(data)?; let inputs_hash = InputsHash::decode(data)?; diff --git a/pkgs/primitives/src/payload/quorum.rs b/pkgs/primitives/src/payload/quorum.rs index 147e4d44..f27afe18 100644 --- a/pkgs/primitives/src/payload/quorum.rs +++ b/pkgs/primitives/src/payload/quorum.rs @@ -6,16 +6,23 @@ //! LLMQ final commitment payload (type 6). +use super::QuorumHash; use crate::codec::impl_payload; use crate::prelude::*; use crate::support::{DynBitset, LlmqType}; -use crate::{QuorumHash, QuorumVvecHash}; +use dash_num::{make_hash, Hash256}; +use dash_pkc::{BlsPublicKeyBytes, BlsSignatureBytes}; use dash_types::codec::{BaseCodec, Checkable, DecodeError, NumCodec}; -use dash_types::{BlsPublicKeyBytes, BlsSignatureBytes}; use core::fmt; +make_hash! { + Hash256, + /// Quorum verification vector hash. + QuorumVvecHash +} + /// DKG session output for one LLMQ. /// /// - v1: legacy diff --git a/pkgs/primitives/src/script.rs b/pkgs/primitives/src/script.rs index 31031d63..00493a18 100644 --- a/pkgs/primitives/src/script.rs +++ b/pkgs/primitives/src/script.rs @@ -9,7 +9,7 @@ use crate::prelude::*; use dash_types::codec::{BaseCodec, DecodeError}; -use dash_types::impl_type; +use dash_types::{impl_type, make_bytes}; use core::fmt; @@ -71,3 +71,18 @@ impl fmt::Display for Script { Ok(()) } } + +make_bytes! { + /// 20-byte public key hash (RIPEMD-160 of SHA-256). + KeyId, 20 +} + +impl KeyId { + /// Encode as a Base58Check string with the given version prefix. + pub fn to_base58c(&self, prefix: u8) -> alloc::string::String { + let mut payload = Vec::with_capacity(21); + payload.push(prefix); + payload.extend_from_slice(self.as_bytes()); + base58ck::encode_check(&payload) + } +} diff --git a/pkgs/primitives/src/support.rs b/pkgs/primitives/src/support.rs index 6ad8d961..0c551cb4 100644 --- a/pkgs/primitives/src/support.rs +++ b/pkgs/primitives/src/support.rs @@ -9,7 +9,7 @@ use crate::prelude::*; use dash_types::codec::{self, BaseCodec, DecodeError, NumCodec}; -use dash_types::{impl_num, impl_type, AddrV1}; +use dash_types::{impl_num, impl_type}; use core::fmt; @@ -155,49 +155,6 @@ impl fmt::Display for RevocationReason { } } -/// Network address type (BIP155). -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum NetworkType { - /// IPv4. - Ipv4, - /// IPv6. - Ipv6, - /// Tor v3 hidden service. - TorV3, - /// I2P. - I2P, - /// CJDNS. - Cjdns, - /// Unknown network type. - Unknown(u8), -} - -impl NumCodec for NetworkType { - fn from_base(val: u8) -> Self { - match val { - 1 => Self::Ipv4, - 2 => Self::Ipv6, - 4 => Self::TorV3, - 5 => Self::I2P, - 6 => Self::Cjdns, - other => Self::Unknown(other), - } - } - - fn to_base(&self) -> u8 { - match self { - Self::Ipv4 => 1, - Self::Ipv6 => 2, - Self::TorV3 => 4, - Self::I2P => 5, - Self::Cjdns => 6, - Self::Unknown(v) => *v, - } - } -} - -impl_num!(NetworkType, u8); - /// LSB-first dynamic bitset. #[derive(Clone, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(::serde::Serialize))] @@ -360,166 +317,3 @@ impl Iterator for DynBitsetIterator<'_> { None } } - -/// Legacy CService network address (ADDRv1 format, 18 bytes). -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct CService { - /// 16-byte address (IPv4-mapped IPv6 or native IPv6). - pub addr: AddrV1, - /// Network port (big-endian on the wire). - pub port: u16, -} - -impl_type!(CService); - -impl BaseCodec for CService { - fn decode(data: &mut &[u8]) -> Result { - Ok(Self { - addr: AddrV1::decode(data)?, - port: codec::read_u16_be(data)?, - }) - } - - fn encode(&self, buf: &mut Vec) { - self.addr.encode(buf); - buf.extend_from_slice(&self.port.to_be_bytes()); - } -} - -/// Maximum number of purpose groups. -const MAX_PURPOSES: usize = 8; -/// Maximum entries per purpose. -const MAX_ENTRIES: usize = 8; -/// Purpose tag for an extended network info entry. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum NetInfoPurpose { - /// Core P2P port. - CoreP2p, - /// Platform P2P port. - PlatformP2p, - /// Platform HTTPS port. - PlatformHttps, - /// Unrecognized purpose code. - Unknown(u8), -} - -impl NumCodec for NetInfoPurpose { - fn from_base(val: u8) -> Self { - match val { - 0 => Self::CoreP2p, - 1 => Self::PlatformP2p, - 2 => Self::PlatformHttps, - other => Self::Unknown(other), - } - } - - fn to_base(&self) -> u8 { - match self { - Self::CoreP2p => 0, - Self::PlatformP2p => 1, - Self::PlatformHttps => 2, - Self::Unknown(v) => *v, - } - } -} - -impl_num!(NetInfoPurpose, u8); - -impl fmt::Display for NetInfoPurpose { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::CoreP2p => write!(f, "core_p2p"), - Self::PlatformP2p => write!(f, "platform_p2p"), - Self::PlatformHttps => write!(f, "platform_https"), - Self::Unknown(v) => write!(f, "unknown({v})"), - } - } -} - -/// A single network info entry within a purpose group. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub enum NetInfoEntry { - /// ADDRv1-style IP + port. - Service(CService), - /// Domain name + port. - Domain { - /// The domain name as raw bytes. - name: Vec, - /// Network port (big-endian on wire). - port: u16, - }, - /// Invalid / placeholder entry. - Invalid, -} - -/// Extended network info for v3+ ProRegTx / ProUpServTx. -/// -/// Contains a versioned list of purpose-grouped network entries (core P2P, -/// platform P2P, platform HTTPS). -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct ExtendedNetInfo { - /// Format version. - pub version: u8, - /// Purpose-grouped entries. - pub entries: Vec<(NetInfoPurpose, Vec)>, -} - -impl_type!(ExtendedNetInfo); - -impl BaseCodec for ExtendedNetInfo { - fn decode(data: &mut &[u8]) -> Result { - let version = u8::decode(data)?; - let purpose_count = codec::read_compact_size(data, MAX_PURPOSES)?; - let mut entries = Vec::with_capacity(purpose_count); - for _ in 0..purpose_count { - let purpose = NetInfoPurpose::from_base(u8::decode(data)?); - let entry_count = codec::read_compact_size(data, MAX_ENTRIES)?; - let mut group = Vec::with_capacity(entry_count); - for _ in 0..entry_count { - let entry_type = u8::decode(data)?; - let entry = match entry_type { - 0x01 => NetInfoEntry::Service(CService::decode(data)?), - 0x02 => { - let name: Vec = Vec::decode(data)?; - let port = codec::read_u16_be(data)?; - NetInfoEntry::Domain { name, port } - } - _ => NetInfoEntry::Invalid, - }; - group.push(entry); - } - entries.push((purpose, group)); - } - Ok(Self { version, entries }) - } - - fn encode(&self, buf: &mut Vec) { - self.version.encode(buf); - codec::write_compact_size(self.entries.len(), buf); - for (purpose, group) in &self.entries { - purpose.to_base().encode(buf); - let valid_count = group.iter().filter(|e| !matches!(e, NetInfoEntry::Invalid)).count(); - codec::write_compact_size(valid_count, buf); - for entry in group { - match entry { - NetInfoEntry::Service(svc) => { - 0x01u8.encode(buf); - svc.encode(buf); - } - NetInfoEntry::Domain { name, port } => { - 0x02u8.encode(buf); - name.encode(buf); - buf.extend_from_slice(&port.to_be_bytes()); - } - NetInfoEntry::Invalid => {} - } - } - } - } -} diff --git a/pkgs/primitives/src/transaction.rs b/pkgs/primitives/src/transaction.rs index add404f0..2e6f78d7 100644 --- a/pkgs/primitives/src/transaction.rs +++ b/pkgs/primitives/src/transaction.rs @@ -7,18 +7,163 @@ //! Dash transaction with version/type packing and optional extra payload for //! special transactions. -use crate::outpoint::OutPoint; +use crate::codec_type; +use crate::payload::TxType; use crate::prelude::*; -use crate::tx_in::TxIn; -use crate::tx_out::TxOut; -use crate::tx_types::TxType; -use crate::validation::{MAX_COINBASE_SCRIPT_SIZE, MAX_TX_EXTRA_PAYLOAD}; +use crate::script::Script; +use bitcoin_units::Amount; +use dash_num::{make_hash, Hash256}; use dash_types::codec::{self, BaseCodec, Checkable, DecodeError, NumCodec}; use dash_types::impl_type; use core::fmt; +/// Maximum extra payload size in bytes. +pub const MAX_TX_EXTRA_PAYLOAD: usize = 10_000; + +/// Maximum coinbase script size in bytes. +pub const MAX_COINBASE_SCRIPT_SIZE: usize = 100; + +make_hash! { + Hash256, + /// SHA256d hash of a serialized transaction. + TxHash +} + +/// A reference to a previous transaction output. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct OutPoint { + /// Transaction hash of the referenced output. + pub hash: TxHash, + /// Index of the referenced output within the transaction. + pub index: u32, +} + +codec_type!(OutPoint { hash, index }); + +impl OutPoint { + /// Returns `true` for the null outpoint (all-zero hash, index `u32::MAX`). + pub fn is_null(&self) -> bool { + self.hash.is_null() && self.index == u32::MAX + } +} + +impl fmt::Display for OutPoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.hash, self.index) + } +} + +/// A transaction input. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct TxIn { + /// The outpoint being spent. + pub prevout: OutPoint, + /// Unlocking script. + pub script_sig: Script, + /// Sequence number. + pub sequence: u32, +} + +codec_type!(TxIn { + prevout, + script_sig, + sequence +}); + +impl fmt::Display for TxIn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TxIn {{ prevout: {}, seq: {} }}", self.prevout, self.sequence,) + } +} + +/// A transaction output. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct TxOut { + /// Output value in duffs. + #[cfg_attr(feature = "serde", serde(with = "crate::serialize::amount"))] + pub value: Amount, + /// Locking script. + #[cfg_attr(feature = "serde", serde(rename = "scriptPubKey"))] + pub script_pubkey: Script, +} + +impl_type!(TxOut); + +impl BaseCodec for TxOut { + fn decode(data: &mut &[u8]) -> Result { + let raw = u64::decode(data)?; + let value = Amount::from_sat(raw).map_err(|_| DecodeError::InvalidValue { + expected: Amount::MAX_MONEY.to_sat(), + actual: raw, + })?; + Ok(Self { + value, + script_pubkey: Script::decode(data)?, + }) + } + + fn encode(&self, buf: &mut Vec) { + self.value.to_sat().encode(buf); + self.script_pubkey.encode(buf); + } +} + +impl fmt::Display for TxOut { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TxOut {{ value: {} }}", self.value.to_sat()) + } +} + +/// Transaction validation failure. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TxInvalid { + /// `bad-txns-vin-empty` + EmptyInputs, + /// `bad-txns-vout-empty` + EmptyOutputs, + /// `bad-txns-oversize` + Oversize { size: usize }, + /// `bad-txns-payload-oversize` + PayloadOversize { size: usize }, + /// `bad-txns-vout-toolarge` + OutputTooLarge { index: usize, value: u64 }, + /// `bad-txns-txouttotal-toolarge` + OutputTotalTooLarge { total: u64 }, + /// `bad-txns-inputs-duplicate` + DuplicateInputs { outpoint: OutPoint }, + /// `bad-cb-length` + BadCoinbaseScriptLength { len: usize }, + /// `bad-txns-prevout-null` + NullPrevout { index: usize }, + /// `bad-txns-payload-not-allowed` + PayloadNotAllowed, +} + +impl fmt::Display for TxInvalid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::EmptyInputs => write!(f, "bad-txns-vin-empty"), + Self::EmptyOutputs => write!(f, "bad-txns-vout-empty"), + Self::Oversize { size } => write!(f, "bad-txns-oversize: {size} bytes"), + Self::PayloadOversize { size } => write!(f, "bad-txns-payload-oversize: {size} bytes"), + Self::OutputTooLarge { index, value } => write!(f, "bad-txns-vout-toolarge: output {index} value {value}"), + Self::OutputTotalTooLarge { total } => write!(f, "bad-txns-txouttotal-toolarge: {total}"), + Self::DuplicateInputs { outpoint } => write!(f, "bad-txns-inputs-duplicate: {outpoint}"), + Self::BadCoinbaseScriptLength { len } => write!(f, "bad-cb-length: {len}"), + Self::NullPrevout { index } => write!(f, "bad-txns-prevout-null: input {index}"), + Self::PayloadNotAllowed => write!(f, "bad-txns-payload-not-allowed"), + } + } +} + /// A Dash transaction. /// /// The wire format packs `version` (i16) and `tx_type` (u16) into a single @@ -210,55 +355,6 @@ impl fmt::Display for Transaction { } } -/// Transaction validation failure. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum TxInvalid { - /// `bad-txns-vin-empty` - EmptyInputs, - /// `bad-txns-vout-empty` - EmptyOutputs, - /// `bad-txns-oversize` - Oversize { size: usize }, - /// `bad-txns-payload-oversize` - PayloadOversize { size: usize }, - /// `bad-txns-vout-toolarge` - OutputTooLarge { index: usize, value: u64 }, - /// `bad-txns-txouttotal-toolarge` - OutputTotalTooLarge { total: u64 }, - /// `bad-txns-inputs-duplicate` - DuplicateInputs { outpoint: OutPoint }, - /// `bad-cb-length` - BadCoinbaseScriptLength { len: usize }, - /// `bad-txns-prevout-null` - NullPrevout { index: usize }, - /// `bad-txns-payload-not-allowed` - PayloadNotAllowed, -} - -impl fmt::Display for TxInvalid { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::EmptyInputs => write!(f, "bad-txns-vin-empty"), - Self::EmptyOutputs => write!(f, "bad-txns-vout-empty"), - Self::Oversize { size } => write!(f, "bad-txns-oversize: {size} bytes"), - Self::PayloadOversize { size } => write!(f, "bad-txns-payload-oversize: {size} bytes"), - Self::OutputTooLarge { index, value } => write!(f, "bad-txns-vout-toolarge: output {index} value {value}"), - Self::OutputTotalTooLarge { total } => write!(f, "bad-txns-txouttotal-toolarge: {total}"), - Self::DuplicateInputs { outpoint } => write!(f, "bad-txns-inputs-duplicate: {outpoint}"), - Self::BadCoinbaseScriptLength { len } => write!(f, "bad-cb-length: {len}"), - Self::NullPrevout { index } => write!(f, "bad-txns-prevout-null: input {index}"), - Self::PayloadNotAllowed => write!(f, "bad-txns-payload-not-allowed"), - } - } -} - -impl OutPoint { - /// Returns `true` for the null outpoint (all-zero hash, index `u32::MAX`). - fn is_null(&self) -> bool { - self.hash.is_null() && self.index == u32::MAX - } -} - #[cfg(all(test, feature = "serde"))] mod tests { use super::*; diff --git a/pkgs/primitives/src/tx_in.rs b/pkgs/primitives/src/tx_in.rs deleted file mode 100644 index c4e83115..00000000 --- a/pkgs/primitives/src/tx_in.rs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Transaction input. - -use crate::codec_type; -use crate::outpoint::OutPoint; -use crate::script::Script; - -use core::fmt; - -/// A transaction input. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct TxIn { - /// The outpoint being spent. - pub prevout: OutPoint, - /// Unlocking script. - pub script_sig: Script, - /// Sequence number. - pub sequence: u32, -} - -codec_type!(TxIn { - prevout, - script_sig, - sequence -}); - -impl fmt::Display for TxIn { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "TxIn {{ prevout: {}, seq: {} }}", self.prevout, self.sequence,) - } -} diff --git a/pkgs/primitives/src/tx_out.rs b/pkgs/primitives/src/tx_out.rs deleted file mode 100644 index 7c9912c7..00000000 --- a/pkgs/primitives/src/tx_out.rs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Transaction output. - -use crate::prelude::*; -use crate::script::Script; - -use bitcoin_units::Amount; -use dash_types::codec::{BaseCodec, DecodeError}; -use dash_types::impl_type; - -use core::fmt; - -/// A transaction output. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct TxOut { - /// Output value in duffs. - #[cfg_attr(feature = "serde", serde(with = "crate::serialize::amount"))] - pub value: Amount, - /// Locking script. - #[cfg_attr(feature = "serde", serde(rename = "scriptPubKey"))] - pub script_pubkey: Script, -} - -impl_type!(TxOut); - -impl BaseCodec for TxOut { - fn decode(data: &mut &[u8]) -> Result { - let raw = u64::decode(data)?; - let value = Amount::from_sat(raw).map_err(|_| DecodeError::InvalidValue { - expected: Amount::MAX_MONEY.to_sat(), - actual: raw, - })?; - Ok(Self { - value, - script_pubkey: Script::decode(data)?, - }) - } - - fn encode(&self, buf: &mut Vec) { - self.value.to_sat().encode(buf); - self.script_pubkey.encode(buf); - } -} - -impl fmt::Display for TxOut { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "TxOut {{ value: {} }}", self.value.to_sat()) - } -} diff --git a/pkgs/primitives/src/tx_types.rs b/pkgs/primitives/src/tx_types.rs deleted file mode 100644 index 57e87d32..00000000 --- a/pkgs/primitives/src/tx_types.rs +++ /dev/null @@ -1,134 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Transaction type and masternode type enums. - -use dash_types::codec::NumCodec; -use dash_types::impl_num; - -use core::fmt; - -/// Dash transaction type, encoded in the upper 16 bits of the version field. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum TxType { - /// Spend transaction (includes legacy coinbase). - Spend, - /// Masternode registration (type 1). - ProviderRegister, - /// Masternode service address update (type 2). - ProviderUpdateService, - /// Masternode registrar key update (type 3). - ProviderUpdateRegistrar, - /// Masternode revocation (type 4). - ProviderUpdateRevoke, - /// Coinbase commitment special transaction (type 5). - CoinbaseCommitment, - /// LLMQ final commitment (type 6). - QuorumCommitment, - /// Masternode hard fork signal (type 7). - MnhfSignal, - /// Asset lock: L1 to platform (type 8). - AssetLock, - /// Asset unlock: platform to L1 (type 9). - AssetUnlock, - /// Unknown or future transaction type. - Unknown(u16), -} - -impl NumCodec for TxType { - fn from_base(value: u16) -> Self { - match value { - 0 => Self::Spend, - 1 => Self::ProviderRegister, - 2 => Self::ProviderUpdateService, - 3 => Self::ProviderUpdateRegistrar, - 4 => Self::ProviderUpdateRevoke, - 5 => Self::CoinbaseCommitment, - 6 => Self::QuorumCommitment, - 7 => Self::MnhfSignal, - 8 => Self::AssetLock, - 9 => Self::AssetUnlock, - other => Self::Unknown(other), - } - } - - fn to_base(&self) -> u16 { - match self { - Self::Spend => 0, - Self::ProviderRegister => 1, - Self::ProviderUpdateService => 2, - Self::ProviderUpdateRegistrar => 3, - Self::ProviderUpdateRevoke => 4, - Self::CoinbaseCommitment => 5, - Self::QuorumCommitment => 6, - Self::MnhfSignal => 7, - Self::AssetLock => 8, - Self::AssetUnlock => 9, - Self::Unknown(v) => *v, - } - } -} - -impl_num!(TxType, u16); - -impl fmt::Display for TxType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Spend => write!(f, "spend"), - Self::ProviderRegister => write!(f, "provider_register"), - Self::ProviderUpdateService => write!(f, "provider_update_service"), - Self::ProviderUpdateRegistrar => write!(f, "provider_update_registrar"), - Self::ProviderUpdateRevoke => write!(f, "provider_update_revoke"), - Self::CoinbaseCommitment => write!(f, "coinbase_commitment"), - Self::QuorumCommitment => write!(f, "quorum_commitment"), - Self::MnhfSignal => write!(f, "mnhf_signal"), - Self::AssetLock => write!(f, "asset_lock"), - Self::AssetUnlock => write!(f, "asset_unlock"), - Self::Unknown(v) => write!(f, "unknown({v})"), - } - } -} - -/// Masternode type, used in provider registration and update transactions. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum MnType { - /// Regular masternode. - Regular, - /// Evolution (Evo) masternode with platform capabilities. - Evo, - /// Unknown or future masternode type. - Unknown(u16), -} - -impl NumCodec for MnType { - fn from_base(value: u16) -> Self { - match value { - 0 => Self::Regular, - 1 => Self::Evo, - other => Self::Unknown(other), - } - } - - fn to_base(&self) -> u16 { - match self { - Self::Regular => 0, - Self::Evo => 1, - Self::Unknown(v) => *v, - } - } -} - -impl_num!(MnType, u16); - -impl fmt::Display for MnType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Regular => write!(f, "regular"), - Self::Evo => write!(f, "evo"), - Self::Unknown(v) => write!(f, "unknown({v})"), - } - } -} diff --git a/pkgs/primitives/src/types/addrv1.rs b/pkgs/primitives/src/types/addrv1.rs new file mode 100644 index 00000000..1a88d8f6 --- /dev/null +++ b/pkgs/primitives/src/types/addrv1.rs @@ -0,0 +1,44 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Legacy ADDRv1 address and service types. + +use crate::prelude::*; + +use dash_types::codec::{self, BaseCodec, DecodeError}; +use dash_types::impl_type; + +dash_types::make_bytes! { + /// ADDRv1 IPv4-mapped IPv6 address (16 bytes). + AddrV1, 16 +} + +/// Legacy network address (ADDRv1 format, 18 bytes). +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct ServiceV1 { + /// 16-byte address (IPv4-mapped IPv6 or native IPv6). + pub addr: AddrV1, + /// Network port (big-endian on the wire). + pub port: u16, +} + +impl_type!(ServiceV1); + +impl BaseCodec for ServiceV1 { + fn decode(data: &mut &[u8]) -> Result { + Ok(Self { + addr: AddrV1::decode(data)?, + port: codec::read_u16_be(data)?, + }) + } + + fn encode(&self, buf: &mut Vec) { + self.addr.encode(buf); + buf.extend_from_slice(&self.port.to_be_bytes()); + } +} diff --git a/pkgs/primitives/src/types/addrv2.rs b/pkgs/primitives/src/types/addrv2.rs new file mode 100644 index 00000000..09eef03d --- /dev/null +++ b/pkgs/primitives/src/types/addrv2.rs @@ -0,0 +1,159 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! BIP155 network address types (ADDRv2). + +use crate::prelude::*; + +use dash_types::codec::{self, BaseCodec, DecodeError, NumCodec}; +use dash_types::{impl_num, impl_type}; + +use core::fmt; + +/// Maximum raw address length for any known BIP155 network type. +const MAX_ADDR_LEN: usize = 512; + +/// Network address type (BIP155). +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum NetworkType { + /// IPv4. + Ipv4, + /// IPv6. + Ipv6, + /// Tor v3 hidden service. + TorV3, + /// I2P. + I2P, + /// CJDNS. + Cjdns, + /// Unknown network type. + Unknown(u8), +} + +impl NumCodec for NetworkType { + fn from_base(val: u8) -> Self { + match val { + 1 => Self::Ipv4, + 2 => Self::Ipv6, + 4 => Self::TorV3, + 5 => Self::I2P, + 6 => Self::Cjdns, + other => Self::Unknown(other), + } + } + + fn to_base(&self) -> u8 { + match self { + Self::Ipv4 => 1, + Self::Ipv6 => 2, + Self::TorV3 => 4, + Self::I2P => 5, + Self::Cjdns => 6, + Self::Unknown(v) => *v, + } + } +} + +impl_num!(NetworkType, u8); + +impl NetworkType { + /// Expected byte length for a known network type, or `None` for unknown. + pub const fn expected_len(self) -> Option { + match self { + Self::Ipv4 => Some(4), + Self::Ipv6 => Some(16), + Self::TorV3 => Some(32), + Self::I2P => Some(32), + Self::Cjdns => Some(16), + Self::Unknown(_) => None, + } + } +} + +impl fmt::Display for NetworkType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Ipv4 => f.write_str("ipv4"), + Self::Ipv6 => f.write_str("ipv6"), + Self::TorV3 => f.write_str("torv3"), + Self::I2P => f.write_str("i2p"), + Self::Cjdns => f.write_str("cjdns"), + Self::Unknown(v) => write!(f, "unknown({v})"), + } + } +} + +/// BIP155 network address. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct AddrV2 { + /// Network transport type. + pub network: NetworkType, + /// Raw address bytes (length depends on network type). + pub addr: Vec, +} + +impl_type!(AddrV2); + +impl BaseCodec for AddrV2 { + fn decode(data: &mut &[u8]) -> Result { + let net_byte = u8::decode(data)?; + let network = NetworkType::from_base(net_byte); + let len = codec::read_compact_size(data, MAX_ADDR_LEN)?; + if let Some(expected) = network.expected_len() { + if len != expected { + return Err(DecodeError::InvalidValue { + expected: expected as u64, + actual: len as u64, + }); + } + } + let addr = codec::read_bytes(data, len)?.to_vec(); + Ok(Self { network, addr }) + } + + fn encode(&self, buf: &mut Vec) { + self.network.to_base().encode(buf); + self.addr.encode(buf); + } +} + +impl fmt::Display for AddrV2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.network) + } +} + +/// BIP155 network service (address + port). +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct ServiceV2 { + /// Typed network address. + pub addr: AddrV2, + /// Network port (big-endian on the wire). + pub port: u16, +} + +impl_type!(ServiceV2); + +impl BaseCodec for ServiceV2 { + fn decode(data: &mut &[u8]) -> Result { + let addr = AddrV2::decode(data)?; + let port = codec::read_u16_be(data)?; + Ok(Self { addr, port }) + } + + fn encode(&self, buf: &mut Vec) { + self.addr.encode(buf); + buf.extend_from_slice(&self.port.to_be_bytes()); + } +} + +impl fmt::Display for ServiceV2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.addr, self.port) + } +} diff --git a/pkgs/primitives/src/types/mod.rs b/pkgs/primitives/src/types/mod.rs new file mode 100644 index 00000000..7cd46a8b --- /dev/null +++ b/pkgs/primitives/src/types/mod.rs @@ -0,0 +1,15 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Dash network and address types. + +mod addrv1; +mod addrv2; +mod netinfo; + +pub use addrv1::{AddrV1, ServiceV1}; +pub use addrv2::{AddrV2, NetworkType, ServiceV2}; +pub use netinfo::{ExtendedNetInfo, NetInfo, NetInfoEntry, NetInfoPurpose}; diff --git a/pkgs/primitives/src/types/netinfo.rs b/pkgs/primitives/src/types/netinfo.rs new file mode 100644 index 00000000..0e4f3db3 --- /dev/null +++ b/pkgs/primitives/src/types/netinfo.rs @@ -0,0 +1,164 @@ +// +// Copyright (c) 2026-present, The Dash Core developers +// SPDX-License-Identifier: MIT +// See the accompanying file LICENSE or https://opensource.org/license/MIT +// + +//! Extended network info types for v3+ provider transactions. + +use super::ServiceV1; +use crate::prelude::*; + +use dash_types::codec::{self, BaseCodec, DecodeError, NumCodec}; +use dash_types::{impl_num, impl_type}; + +use core::fmt; + +/// Maximum entries per purpose. +const MAX_ENTRIES: usize = 8; +/// Maximum number of purpose groups. +const MAX_PURPOSES: usize = 8; + +/// Purpose tag for an extended network info entry. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum NetInfoPurpose { + /// Core P2P port. + CoreP2p, + /// Platform P2P port. + PlatformP2p, + /// Platform HTTPS port. + PlatformHttps, + /// Unrecognized purpose code. + Unknown(u8), +} + +impl NumCodec for NetInfoPurpose { + fn from_base(val: u8) -> Self { + match val { + 0 => Self::CoreP2p, + 1 => Self::PlatformP2p, + 2 => Self::PlatformHttps, + other => Self::Unknown(other), + } + } + + fn to_base(&self) -> u8 { + match self { + Self::CoreP2p => 0, + Self::PlatformP2p => 1, + Self::PlatformHttps => 2, + Self::Unknown(v) => *v, + } + } +} + +impl_num!(NetInfoPurpose, u8); + +impl fmt::Display for NetInfoPurpose { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::CoreP2p => write!(f, "core_p2p"), + Self::PlatformP2p => write!(f, "platform_p2p"), + Self::PlatformHttps => write!(f, "platform_https"), + Self::Unknown(v) => write!(f, "unknown({v})"), + } + } +} + +/// A single network info entry within a purpose group. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub enum NetInfoEntry { + /// ADDRv1-style IP + port. + Service(ServiceV1), + /// Domain name + port. + Domain { + /// The domain name as raw bytes. + #[cfg_attr(feature = "serde", serde(with = "dash_types::serialize::utf8"))] + name: Vec, + /// Network port (big-endian on wire). + port: u16, + }, + /// Invalid / placeholder entry. + Invalid, +} + +/// Extended network info for v3+ ProRegTx / ProUpServTx. +/// +/// Contains a versioned list of purpose-grouped network entries (core P2P, +/// platform P2P, platform HTTPS). +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct ExtendedNetInfo { + /// Format version. + pub version: u8, + /// Purpose-grouped entries. + pub entries: Vec<(NetInfoPurpose, Vec)>, +} + +impl_type!(ExtendedNetInfo); + +impl BaseCodec for ExtendedNetInfo { + fn decode(data: &mut &[u8]) -> Result { + let version = u8::decode(data)?; + let purpose_count = codec::read_compact_size(data, MAX_PURPOSES)?; + let mut entries = Vec::with_capacity(purpose_count); + for _ in 0..purpose_count { + let purpose = NetInfoPurpose::from_base(u8::decode(data)?); + let entry_count = codec::read_compact_size(data, MAX_ENTRIES)?; + let mut group = Vec::with_capacity(entry_count); + for _ in 0..entry_count { + let entry_type = u8::decode(data)?; + let entry = match entry_type { + 0x01 => NetInfoEntry::Service(ServiceV1::decode(data)?), + 0x02 => { + let name: Vec = Vec::decode(data)?; + let port = codec::read_u16_be(data)?; + NetInfoEntry::Domain { name, port } + } + _ => NetInfoEntry::Invalid, + }; + group.push(entry); + } + entries.push((purpose, group)); + } + Ok(Self { version, entries }) + } + + fn encode(&self, buf: &mut Vec) { + self.version.encode(buf); + codec::write_compact_size(self.entries.len(), buf); + for (purpose, group) in &self.entries { + purpose.to_base().encode(buf); + let valid_count = group.iter().filter(|e| !matches!(e, NetInfoEntry::Invalid)).count(); + codec::write_compact_size(valid_count, buf); + for entry in group { + match entry { + NetInfoEntry::Service(svc) => { + 0x01u8.encode(buf); + svc.encode(buf); + } + NetInfoEntry::Domain { name, port } => { + 0x02u8.encode(buf); + name.encode(buf); + buf.extend_from_slice(&port.to_be_bytes()); + } + NetInfoEntry::Invalid => {} + } + } + } + } +} + +/// Masternode network info: legacy ServiceV1 or structured extended format. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub enum NetInfo { + /// ADDRv1 ServiceV1 (18 bytes). + Legacy(ServiceV1), + /// Extended format (v3+) with purpose-grouped entries. + Extended(ExtendedNetInfo), +} diff --git a/pkgs/primitives/src/validation.rs b/pkgs/primitives/src/validation.rs deleted file mode 100644 index c554fe80..00000000 --- a/pkgs/primitives/src/validation.rs +++ /dev/null @@ -1,138 +0,0 @@ -// -// Copyright (c) 2026-present, The Dash Core developers -// SPDX-License-Identifier: MIT -// See the accompanying file LICENSE or https://opensource.org/license/MIT -// - -//! Shared validation helpers. -//! -//! Type-specific validation lives in each type's own module. This module -//! provides helpers shared across multiple modules. - -use crate::prelude::*; -use crate::support::{NetInfoEntry, NetInfoPurpose}; -use crate::tx_types::MnType; - -use core::fmt; - -/// Maximum serialized transaction size (single tx, always 1 MB). -#[expect(unused, reason = "consensus constant")] -pub(crate) const MAX_LEGACY_BLOCK_SIZE: usize = 1_000_000; - -/// Post-DIP0001 maximum block size (2 MB). -pub(crate) const MAX_DIP0001_BLOCK_SIZE: usize = 2_000_000; - -/// Maximum extra payload size in bytes. -pub(crate) const MAX_TX_EXTRA_PAYLOAD: usize = 10_000; - -/// Number of version bits available for signalling. -pub(crate) const VERSIONBITS_NUM_BITS: u8 = 29; - -/// Maximum coinbase script size in bytes. -pub(crate) const MAX_COINBASE_SCRIPT_SIZE: usize = 100; - -/// Maximum operator reward in basis points. -pub(crate) const MAX_OPERATOR_REWARD: u16 = 10_000; - -/// ProTx version: legacy BLS operator keys (v1). -#[expect(unused, reason = "consensus constant")] -pub(crate) const PROTX_VERSION_LEGACY_BLS: u16 = 1; - -/// ProTx version: basic (IETF) BLS operator keys (v2). -pub(crate) const PROTX_VERSION_BASIC_BLS: u16 = 2; - -/// ProTx version: extended network addresses (v3). -pub(crate) const PROTX_VERSION_EXT_ADDR: u16 = 3; - -/// Provider transaction validation failure. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ProTxInvalid { - /// `bad-protx-version` - BadVersion { version: u16 }, - /// `bad-protx-evo-version` - EvoVersionTooLow { version: u16 }, - /// `bad-protx-type` - BadMnType { mn_type: MnType }, - /// `bad-protx-mode` - BadMode { mode: u16 }, - /// `bad-protx-key-null` - NullKey, - /// `bad-protx-operator-pubkey` - OperatorKeyMismatch, - /// `bad-protx-payee` - BadPayoutScript, - /// `bad-protx-netinfo-version` - NetInfoVersionMismatch, - /// `bad-protx-netinfo-empty` - NetInfoEmpty, - /// `bad-protx-netinfo-bad` - NetInfoInvalid, - /// `bad-protx-payee-reuse` - PayoutKeyReuse, - /// `bad-protx-operator-reward` - OperatorRewardTooHigh { reward: u16 }, - /// `bad-protx-reason` - BadReason { reason: crate::support::RevocationReason }, - /// `bad-protx-platform-fields` - BadPlatformFields, -} - -impl fmt::Display for ProTxInvalid { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::BadVersion { version } => write!(f, "bad-protx-version: {version}"), - Self::EvoVersionTooLow { version } => write!(f, "bad-protx-evo-version: {version}"), - Self::BadMnType { mn_type } => write!(f, "bad-protx-type: {mn_type}"), - Self::BadMode { mode } => write!(f, "bad-protx-mode: {mode}"), - Self::NullKey => write!(f, "bad-protx-key-null"), - Self::OperatorKeyMismatch => write!(f, "bad-protx-operator-pubkey"), - Self::BadPayoutScript => write!(f, "bad-protx-payee"), - Self::NetInfoVersionMismatch => write!(f, "bad-protx-netinfo-version"), - Self::NetInfoEmpty => write!(f, "bad-protx-netinfo-empty"), - Self::NetInfoInvalid => write!(f, "bad-protx-netinfo-bad"), - Self::PayoutKeyReuse => write!(f, "bad-protx-payee-reuse"), - Self::OperatorRewardTooHigh { reward } => write!(f, "bad-protx-operator-reward: {reward}"), - Self::BadReason { reason } => write!(f, "bad-protx-reason: {reason}"), - Self::BadPlatformFields => write!(f, "bad-protx-platform-fields"), - } - } -} - -/// Checks that an extended net info payload is trivially valid. -pub(crate) fn check_sptx_netinfo( - entries: &[(NetInfoPurpose, Vec)], - mn_type: MnType, - can_store_platform: bool, -) -> Option { - let has_core = entries - .iter() - .any(|(p, e)| *p == NetInfoPurpose::CoreP2p && !e.is_empty()); - if !has_core { - return Some(ProTxInvalid::NetInfoEmpty); - } - - let has_platform_p2p = entries - .iter() - .any(|(p, e)| *p == NetInfoPurpose::PlatformP2p && !e.is_empty()); - let has_platform_https = entries - .iter() - .any(|(p, e)| *p == NetInfoPurpose::PlatformHttps && !e.is_empty()); - - if mn_type == MnType::Regular && (has_platform_p2p || has_platform_https) { - return Some(ProTxInvalid::NetInfoInvalid); - } - - if can_store_platform && mn_type == MnType::Evo && (!has_platform_p2p || !has_platform_https) { - return Some(ProTxInvalid::NetInfoEmpty); - } - - for (_purpose, group) in entries { - for entry in group { - if matches!(entry, NetInfoEntry::Invalid) { - return Some(ProTxInvalid::NetInfoInvalid); - } - } - } - - None -} diff --git a/pkgs/script/Cargo.toml b/pkgs/script/Cargo.toml index ef7efc8c..596fe401 100644 --- a/pkgs/script/Cargo.toml +++ b/pkgs/script/Cargo.toml @@ -10,10 +10,9 @@ std = [ "bitcoin_hashes/std", "base58ck/std", "bitcoin-consensus-encoding/std", - "dash-types/std", ] full = ["std", "serde"] -serde = ["dep:serde", "dash-types/serde"] +serde = ["dep:serde"] _internal = [] [dependencies] @@ -24,7 +23,6 @@ bitcoin-consensus-encoding = { version = "0.2", default-features = false, featur bitcoin_hashes = { version = "0.20", default-features = false, features = [ "alloc", ] } -dash-types = { version = "0.0.0", path = "../types", default-features = false } serde = { version = "1", default-features = false, features = [ "derive", "alloc", diff --git a/pkgs/script/src/lib.rs b/pkgs/script/src/lib.rs index 9bf7e37a..dc504718 100644 --- a/pkgs/script/src/lib.rs +++ b/pkgs/script/src/lib.rs @@ -19,7 +19,6 @@ use crate::opcode::Opcode as Op; use crate::prelude::*; use bitcoin_hashes::{hash160, sha256}; -use dash_types::KeyId; pub mod opcode; @@ -58,8 +57,8 @@ pub enum ScriptKind { P2pk, /// Provably unspendable `OP_RETURN`. OpReturn, - /// Unrecognized or unsupported script. - Unknown, + /// Unrecognized or unsupported script pattern (leading opcode). + Unknown(u8), } /// Classify a scriptPubKey by its leading pattern. @@ -76,7 +75,7 @@ pub fn classify(script: &[u8]) -> ScriptKind { if is_op_return(script) { return ScriptKind::OpReturn; } - ScriptKind::Unknown + ScriptKind::Unknown(script.first().copied().unwrap_or(0)) } /// Returns `true` for P2PKH scripts @@ -154,15 +153,6 @@ pub fn encode_p2sh(hash160: &[u8], prefix: u8) -> Option { encode_base58_check(prefix, hash160) } -/// Encode a `KeyId` as a Base58Check P2PKH address. -pub fn encode_key_id(key_id: &KeyId, prefix: u8) -> String { - let bytes = key_id.to_bytes(); - let mut payload = Vec::with_capacity(HASH160_LEN + 1); - payload.push(prefix); - payload.extend_from_slice(&bytes); - base58ck::encode_check(&payload) -} - /// Derive a Base58Check address from a scriptPubKey. /// /// Returns `None` for `OP_RETURN` and unrecognized scripts. @@ -180,7 +170,7 @@ pub fn derive_address(script: &[u8], p2pkh_version: u8, p2sh_version: u8) -> Opt let h160 = hash160::Hash::from_byte_array(*bitcoin_hashes::ripemd160::Hash::hash(sha.as_ref()).as_byte_array()); encode_p2pkh(h160.as_ref(), p2pkh_version) } - ScriptKind::OpReturn | ScriptKind::Unknown => None, + ScriptKind::OpReturn | ScriptKind::Unknown(_) => None, } } @@ -364,13 +354,13 @@ mod tests { #[test] fn empty_is_unknown() { - assert_eq!(classify(&[]), ScriptKind::Unknown); + assert_eq!(classify(&[]), ScriptKind::Unknown(0)); } #[test] fn arithmetic_script_is_unknown() { let script = hex!("59935b87"); - assert_eq!(classify(&script), ScriptKind::Unknown); + assert_eq!(classify(&script), ScriptKind::Unknown(0x59)); } #[test] diff --git a/pkgs/types/src/lib.rs b/pkgs/types/src/lib.rs index 83d9156c..38e77d72 100644 --- a/pkgs/types/src/lib.rs +++ b/pkgs/types/src/lib.rs @@ -30,38 +30,3 @@ pub mod __private { #[cfg(feature = "serde")] pub use hex_conservative; } - -make_bytes! { - /// ADDRv1 IPv4-mapped IPv6 address (16 bytes). - AddrV1, 16 -} - -make_bytes! { - /// Raw BLS public key bytes (48 bytes, unvalidated). - BlsPublicKeyBytes, 48 -} - -make_bytes! { - /// Raw BLS signature bytes (96 bytes, unvalidated). - BlsSignatureBytes, 96 -} - -make_bytes! { - /// Raw compressed ECDSA public key bytes (33 bytes, unvalidated). - EcdsaPublicKeyBytes, 33 -} - -make_bytes! { - /// Raw compact ECDSA signature bytes (64 bytes, unvalidated). - EcdsaSignatureBytes, 64 -} - -make_bytes! { - /// 20-byte public key hash (RIPEMD-160 of SHA-256). - KeyId, 20 -} - -make_bytes! { - /// Platform node identifier for Evo masternodes. - PlatformNodeId, 20 -} diff --git a/pkgs/types/src/serialize.rs b/pkgs/types/src/serialize.rs index df45e1bc..2a37b23f 100644 --- a/pkgs/types/src/serialize.rs +++ b/pkgs/types/src/serialize.rs @@ -8,6 +8,34 @@ pub use crate::hex::serde as hex; +/// UTF-8 serde for `Vec` fields that hold text. +pub mod utf8 { + use crate::prelude::*; + + use core::str::from_utf8; + + /// Serializes bytes as a UTF-8 string. + /// + /// # Errors + /// + /// Returns a serialization error when bytes are not valid UTF-8. + pub fn serialize(data: &[u8], serializer: S) -> Result { + let s = from_utf8(data).map_err(::serde::ser::Error::custom)?; + serializer.serialize_str(s) + } + + /// Deserializes a string into bytes. + /// + /// # Errors + /// + /// Returns a deserialization error when the input is not + /// a valid string. + pub fn deserialize<'de, D: ::serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + let s = ::deserialize(deserializer)?; + Ok(s.into_bytes()) + } +} + /// Serializes `u64` as a decimal string to avoid JSON precision loss. pub mod str_u64 { /// Serializes a `u64` as a decimal string.