diff --git a/Cargo.lock b/Cargo.lock index c893492c17..b60d26251a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -385,7 +385,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -402,7 +402,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -582,7 +582,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -917,7 +917,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1181,7 +1181,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1319,6 +1319,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "enum-primitive-derive" version = "0.2.2" @@ -1404,7 +1413,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1588,7 +1597,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -1893,6 +1902,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -2253,6 +2275,7 @@ dependencies = [ "kash-merkle", "kash-muhash", "kash-notify", + "kash-oracle", "kash-pow", "kash-txscript", "kash-txscript-errors", @@ -2292,12 +2315,14 @@ dependencies = [ "kash-math", "kash-merkle", "kash-muhash", + "kash-oracle", "kash-txscript-errors", "kash-utils", "rand 0.8.5", "secp256k1", "serde", "serde-wasm-bindgen", + "serde_cbor", "serde_json", "smallvec", "thiserror", @@ -2472,6 +2497,7 @@ dependencies = [ "kash-consensus-core", "kash-core", "kash-notify", + "kash-oracle", "kash-rpc-core", "kash-utils", "log", @@ -2646,11 +2672,14 @@ dependencies = [ "kash-consensus-core", "kash-consensusmanager", "kash-core", + "kash-database", "kash-hashes", "kash-mining-errors", "kash-muhash", + "kash-oracle", "kash-txscript", "kash-utils", + "kash-utxoindex", "log", "parking_lot", "rand 0.8.5", @@ -2711,6 +2740,21 @@ dependencies = [ "workflow-log", ] +[[package]] +name = "kash-oracle" +version = "0.13.3" +dependencies = [ + "borsh", + "hex", + "openssl", + "rand 0.8.5", + "reqwest", + "serde", + "serde-wasm-bindgen", + "thiserror", + "wasm-bindgen", +] + [[package]] name = "kash-os" version = "0.0.2" @@ -2791,6 +2835,7 @@ dependencies = [ "kash-hashes", "kash-math", "kash-mining-errors", + "kash-oracle", "kash-utils", "kash-utils-tower", "log", @@ -2938,6 +2983,7 @@ dependencies = [ "kash-merkle", "kash-muhash", "kash-notify", + "kash-oracle", "kash-pow", "kash-rpc-core", "kash-txscript", @@ -3554,7 +3600,7 @@ dependencies = [ "cfg-if 1.0.0", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -3565,7 +3611,7 @@ checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -3578,7 +3624,7 @@ dependencies = [ "macroific_core", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -4012,7 +4058,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -4113,7 +4159,7 @@ checksum = "70df726c43c645ef1dde24c7ae14692036ebe5457c92c5f0ec4cfceb99634ff6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -4182,7 +4228,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -4280,7 +4326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -4317,9 +4363,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -4351,7 +4397,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.41", + "syn 2.0.48", "tempfile", "which", ] @@ -4366,7 +4412,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -4380,9 +4426,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -4552,6 +4598,44 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "reqwest" +version = "0.11.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +dependencies = [ + "base64 0.21.5", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "ring" version = "0.17.7" @@ -4842,6 +4926,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.193" @@ -4850,7 +4944,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -4864,6 +4958,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.8.26" @@ -5100,9 +5206,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.41" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -5125,6 +5231,27 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.8.1" @@ -5169,22 +5296,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -5295,7 +5422,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -5407,7 +5534,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -5480,7 +5607,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] @@ -5743,7 +5870,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -5777,7 +5904,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6072,6 +6199,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if 1.0.0", + "windows-sys 0.48.0", +] + [[package]] name = "workflow-async-trait" version = "0.1.68" @@ -6535,7 +6672,7 @@ checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.48", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4657d1a05d..1ff12a97e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ members = [ "metrics/perf_monitor", "metrics/core", "utils/alloc", + "oracle", ] [workspace.package] @@ -126,6 +127,7 @@ kash-wrpc-server = { version = "0.13.3", path = "rpc/wrpc/server" } kash-wrpc-wasm = { version = "0.13.3", path = "rpc/wrpc/wasm" } kashd = { version = "0.13.3", path = "kashd" } kash-alloc = { version = "0.13.3", path = "utils/alloc" } +kash-oracle = { version = "0.13.3", path = "oracle" } # external aes = "0.8.3" diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index 1a6259c7bc..0ca502e787 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -26,6 +26,7 @@ kash-math.workspace = true kash-merkle.workspace = true kash-muhash.workspace = true kash-notify.workspace = true +kash-oracle.workspace = true kash-pow.workspace = true kash-txscript.workspace = true kash-utils.workspace = true diff --git a/consensus/core/Cargo.toml b/consensus/core/Cargo.toml index d0bc568265..082ba86838 100644 --- a/consensus/core/Cargo.toml +++ b/consensus/core/Cargo.toml @@ -22,6 +22,7 @@ kash-hashes.workspace = true kash-math.workspace = true kash-merkle.workspace = true kash-muhash.workspace = true +kash-oracle.workspace = true kash-txscript-errors.workspace = true kash-utils.workspace = true rand.workspace = true @@ -35,6 +36,7 @@ wasm-bindgen.workspace = true workflow-core.workspace = true workflow-log.workspace = true workflow-wasm.workspace = true +serde_cbor = "0.11.2" [dev-dependencies] criterion.workspace = true diff --git a/consensus/core/src/api/mod.rs b/consensus/core/src/api/mod.rs index a4e8358daf..32009083e2 100644 --- a/consensus/core/src/api/mod.rs +++ b/consensus/core/src/api/mod.rs @@ -48,6 +48,7 @@ pub trait ConsensusApi: Send + Sync { miner_data: MinerData, tx_selector: Box, build_mode: TemplateBuildMode, + target_block_time: u64, ) -> Result { unimplemented!() } diff --git a/consensus/core/src/block.rs b/consensus/core/src/block.rs index c48238c744..213f61cfb1 100644 --- a/consensus/core/src/block.rs +++ b/consensus/core/src/block.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use crate::tx::reserve_state::ReserveRatioState; use crate::{ coinbase::MinerData, header::Header, @@ -74,7 +75,7 @@ pub trait TemplateTransactionSelector { /// Expected to return a batch of transactions which were not previously selected. /// The batch will typically contain sufficient transactions to fill the block /// mass (along with the previously unrejected txs), or will drain the selector - fn select_transactions(&mut self) -> Vec; + fn select_transactions(&mut self, rs: ReserveRatioState) -> Vec; /// Should be used to report invalid transactions obtained from the *most recent* /// `select_transactions` call. Implementors should use this call to internally diff --git a/consensus/core/src/config/genesis.rs b/consensus/core/src/config/genesis.rs index b90486e209..2d4f054542 100644 --- a/consensus/core/src/config/genesis.rs +++ b/consensus/core/src/config/genesis.rs @@ -2,6 +2,7 @@ use crate::tx::TransactionAction; use crate::{block::Block, header::Header, subnets::SUBNETWORK_ID_COINBASE, tx::Transaction}; use kash_hashes::{Hash, ZERO_HASH}; use kash_muhash::EMPTY_MUHASH; +use kash_oracle::pricing_record::PricingRecord; /// The constants uniquely representing the genesis block #[derive(Clone, Debug)] @@ -47,6 +48,7 @@ impl From<&GenesisBlock> for Header { 0.into(), 0, ZERO_HASH, + PricingRecord::default(), ) } } diff --git a/consensus/core/src/errors/tx.rs b/consensus/core/src/errors/tx.rs index e5675b2287..082e01c909 100644 --- a/consensus/core/src/errors/tx.rs +++ b/consensus/core/src/errors/tx.rs @@ -17,6 +17,12 @@ pub enum TxRuleError { #[error("a non coinbase transaction has a payload")] NonCoinbaseTxHasPayload, + #[error("Invalid asset conversion types")] + InvalidAssetConversionTypes, + + #[error("Invalid asset conversion amount")] + InvalidAssetConversionAmount, + #[error("transaction version {0} is unknown")] UnknownTxVersion(u16), diff --git a/consensus/core/src/hashing/header.rs b/consensus/core/src/hashing/header.rs index 2e283fe938..664daaa939 100644 --- a/consensus/core/src/hashing/header.rs +++ b/consensus/core/src/hashing/header.rs @@ -38,6 +38,7 @@ pub fn hash(header: &Header) -> Hash { mod tests { use super::*; use crate::{blockhash, BlueWorkType}; + use kash_oracle::pricing_record::PricingRecord; #[test] fn test_header_hashing() { @@ -54,6 +55,7 @@ mod tests { 0.into(), 0, Default::default(), + PricingRecord::default(), ); assert_ne!(blockhash::NONE, header.hash); } diff --git a/consensus/core/src/header.rs b/consensus/core/src/header.rs index f451df0c5d..085900bd0e 100644 --- a/consensus/core/src/header.rs +++ b/consensus/core/src/header.rs @@ -2,7 +2,11 @@ use crate::{hashing, BlueWorkType}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use js_sys::{Array, Object}; use kash_hashes::Hash; +use kash_oracle::pricing_record::create_random_pricing_record; +use kash_oracle::pricing_record::PricingRecord; use kash_utils::hex::ToHex; +use rand::rngs::SmallRng; +use rand::SeedableRng; use serde::{Deserialize, Serialize}; use serde_wasm_bindgen::*; use wasm_bindgen::prelude::*; @@ -34,6 +38,8 @@ pub struct Header { pub blue_score: u64, #[wasm_bindgen(skip)] pub pruning_point: Hash, + #[wasm_bindgen(skip)] + pub pricing_record: PricingRecord, } impl Header { @@ -51,6 +57,7 @@ impl Header { blue_work: BlueWorkType, blue_score: u64, pruning_point: Hash, + pricing_record: PricingRecord, ) -> Self { let mut header = Self { hash: Default::default(), // Temp init before the finalize below @@ -66,6 +73,7 @@ impl Header { blue_work, blue_score, pruning_point, + pricing_record, }; header.finalize(); header @@ -100,6 +108,7 @@ impl Header { blue_work: 0.into(), blue_score: 0, pruning_point: Default::default(), + pricing_record: create_random_pricing_record(&mut SmallRng::seed_from_u64(0)), } } } @@ -249,6 +258,9 @@ impl TryFrom for Header { }) .collect::>, Error>>()?; + let pricing_record: PricingRecord = + object.get_value("pricingRecord")?.try_into().map_err(|err| Error::convert("pricingRecord", err))?; + let header = Self { hash: object.get_value("hash")?.try_into().unwrap_or_default(), version: object.get_u16("version")?, @@ -272,6 +284,7 @@ impl TryFrom for Header { blue_work: object.get_value("blueWork")?.try_into().map_err(|err| Error::convert("blueWork", err))?, blue_score: object.get_u64("blueScore")?, pruning_point: object.get_value("pruningPoint")?.try_into().map_err(|err| Error::convert("pruningPoint", err))?, + pricing_record, }; Ok(header) @@ -302,6 +315,7 @@ mod tests { Uint192([0x1234567890abcfed, 0xc0dec0ffeec0ffee, 0x1234567890abcdef]), u64::MAX, Default::default(), + create_random_pricing_record(&mut SmallRng::seed_from_u64(0)), ); let json = serde_json::to_string(&header).unwrap(); println!("{}", json); diff --git a/consensus/core/src/tx.rs b/consensus/core/src/tx.rs index a90e7c59c1..b909794cf2 100644 --- a/consensus/core/src/tx.rs +++ b/consensus/core/src/tx.rs @@ -1,3 +1,5 @@ +pub mod asset_conversion; +pub mod reserve_state; mod script_public_key; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -146,8 +148,10 @@ pub enum TransactionAction { MintKUSD, /// Staking KSH to get KRV: KSH -> KRV StakeKSH, + /// Redeeming KSH using KUSD: KUSD -> KSH + RedeemViaKUSD, /// Redeeming KSH using KRV: KRV -> KSH - RedeemKSH, + RedeemViaKRV, } impl TransactionAction { @@ -160,7 +164,17 @@ impl TransactionAction { TransactionAction::TransferKRV => (AssetType::KRV, AssetType::KRV), TransactionAction::MintKUSD => (AssetType::KSH, AssetType::KUSD), TransactionAction::StakeKSH => (AssetType::KSH, AssetType::KRV), - TransactionAction::RedeemKSH => (AssetType::KRV, AssetType::KSH), + TransactionAction::RedeemViaKUSD => (AssetType::KUSD, AssetType::KSH), + TransactionAction::RedeemViaKRV => (AssetType::KRV, AssetType::KSH), + } + } + + pub fn is_transfer(&self) -> bool { + match self { + TransactionAction::TransferKSH => true, + TransactionAction::TransferKUSD => true, + TransactionAction::TransferKRV => true, + _ => false, } } } @@ -174,7 +188,8 @@ impl From for u32 { TransactionAction::TransferKRV => 2, TransactionAction::MintKUSD => 3, TransactionAction::StakeKSH => 4, - TransactionAction::RedeemKSH => 5, + TransactionAction::RedeemViaKUSD => 5, + TransactionAction::RedeemViaKRV => 6, } } } @@ -188,7 +203,8 @@ impl From for TransactionAction { 2 => TransactionAction::TransferKRV, 3 => TransactionAction::MintKUSD, 4 => TransactionAction::StakeKSH, - 5 => TransactionAction::RedeemKSH, + 5 => TransactionAction::RedeemViaKUSD, + 6 => TransactionAction::RedeemViaKRV, _ => TransactionAction::TransferKSH, // Handle unknown values gracefully } } @@ -203,7 +219,8 @@ impl From for TransactionAction { "TransferKRV" => TransactionAction::TransferKRV, "MintKUSD" => TransactionAction::MintKUSD, "StakeKSH" => TransactionAction::StakeKSH, - "RedeemKSH" => TransactionAction::RedeemKSH, + "RedeemViaKUSD" => TransactionAction::RedeemViaKUSD, + "RedeemViaKRV" => TransactionAction::RedeemViaKRV, _ => TransactionAction::TransferKSH, // Handle unknown values gracefully } } @@ -219,7 +236,8 @@ impl TryFrom<&String> for TransactionAction { "TransferKRV" => Ok(TransactionAction::TransferKRV), "MintKUSD" => Ok(TransactionAction::MintKUSD), "StakeKSH" => Ok(TransactionAction::StakeKSH), - "RedeemKSH" => Ok(TransactionAction::RedeemKSH), + "RedeemViaKUSD" => Ok(TransactionAction::RedeemViaKUSD), + "RedeemViaKRV" => Ok(TransactionAction::RedeemViaKRV), _ => Err(TxRuleError::InvalidTransactionType(value.clone())), } } @@ -237,7 +255,8 @@ impl TryFrom for TransactionAction { 2 => Ok(TransactionAction::TransferKRV), 3 => Ok(TransactionAction::MintKUSD), 4 => Ok(TransactionAction::StakeKSH), - 5 => Ok(TransactionAction::RedeemKSH), + 5 => Ok(TransactionAction::RedeemViaKUSD), + 6 => Ok(TransactionAction::RedeemViaKRV), _ => Err(JsValue::from_str("Invalid TransactionAction value")), } } else { diff --git a/consensus/core/src/tx/asset_conversion.rs b/consensus/core/src/tx/asset_conversion.rs new file mode 100644 index 0000000000..9156841434 --- /dev/null +++ b/consensus/core/src/tx/asset_conversion.rs @@ -0,0 +1,23 @@ +use crate::asset_type::AssetType; +use serde::{Deserialize, Serialize}; +use serde_cbor; + +#[derive(Serialize, Deserialize)] +pub struct AssetConversionDetails { + pub to_asset_type: AssetType, + pub from_asset_type: AssetType, + pub to_amount: u64, + pub from_amount: u64, +} + +pub struct AssetConversionSerializer; + +impl AssetConversionSerializer { + pub fn serialize(details: &AssetConversionDetails) -> Vec { + serde_cbor::to_vec(details).expect("Serialization failed") + } + + pub fn deserialize(data: &[u8]) -> AssetConversionDetails { + serde_cbor::from_slice(data).expect("Deserialization failed") + } +} diff --git a/consensus/core/src/tx/reserve_state.rs b/consensus/core/src/tx/reserve_state.rs new file mode 100644 index 0000000000..1948ece044 --- /dev/null +++ b/consensus/core/src/tx/reserve_state.rs @@ -0,0 +1,197 @@ +use crate::asset_type::AssetType; +use crate::tx::{Transaction, TransactionAction}; +use consensus_core::tx::asset_conversion::AssetConversionSerializer; +use kash_oracle::pricing_record::PricingRecord; +use std::sync::Arc; + +#[derive(Debug, Clone, Default)] +pub struct ReserveRatioState { + ksh_supply: u64, + kusd_supply: u64, + krv_supply: u64, + pr: PricingRecord, + transaction_count: u64, +} + +#[derive(Debug)] +pub enum ReserveRatioError { + MissingRates, + NegativeReserveValue, + NegativeLiabilities, + NegativeTotalReserveCoins, + ZeroAssetsAndLiabilities, + CalculationError, + NegativeReserveRatio, + RatioBelowMinimum(f64), + RatioAboveMaximum(f64), + Other(String), +} + +impl ReserveRatioState { + // Initialize a new instance of ReserveRatioState + pub fn new(ksh_supply: u64, kusd_supply: u64, krv_supply: u64, pr: PricingRecord) -> Self { + Self { ksh_supply, kusd_supply, krv_supply, pr, transaction_count: 0 } + } + + pub fn get_reserve_rate(&self) -> f64 { + let total_reserve_value = self.pr.ksh as f64 * self.krv_supply as f64; + + if self.kusd_supply == 0 { + return 0.0; + } + + total_reserve_value / self.kusd_supply as f64 + } + + // Method to check if the reserve ratio is satisfied for a given transaction + pub fn reserve_ratio_satisfied(&self, tx_type: TransactionAction) -> Result { + if self.pr.has_unset_field() { + return Err(ReserveRatioError::MissingRates); + } + + // Early exit if no KSH in the reserve for certain transaction types + if self.ksh_supply == 0 && tx_type != TransactionAction::MintKUSD { + return Err(ReserveRatioError::Other("No KSH in reserve.".to_string())); + } + + // Converting assets, liabilities and reserves + let reserve_value = self.ksh_supply as f64 * self.pr.ksh as f64; + let reserve_value_ma = self.ksh_supply as f64 * self.pr.ksh_ma as f64; + let liabilities = self.kusd_supply as f64; + let total_reserve_coins = self.krv_supply as f64; + + // Check for negative values + if reserve_value < 0.0 || reserve_value_ma < 0.0 { + return Err(ReserveRatioError::NegativeReserveValue); + } + if liabilities < 0.0 { + return Err(ReserveRatioError::NegativeLiabilities); + } + if total_reserve_coins < 0.0 { + return Err(ReserveRatioError::NegativeTotalReserveCoins); + } + + // Handling zero assets and liabilities + if reserve_value == 0.0 && liabilities == 0.0 { + return Err(ReserveRatioError::ZeroAssetsAndLiabilities); + } + + let reserve_ratio = reserve_value / liabilities; + let reserve_ratio_ma = reserve_value_ma / liabilities; + + // Check for NaN and negative ratios + if reserve_ratio.is_nan() || reserve_ratio < 0.0 || reserve_ratio_ma.is_nan() || reserve_ratio_ma < 0.0 { + return Err(ReserveRatioError::NegativeReserveRatio); + } + + match tx_type { + // KSH <--> KRV + TransactionAction::RedeemViaKRV => { + // No specific reserve ratio requirement for redeeming KSH + if reserve_ratio < 4.0 || reserve_ratio_ma < 4.0 { + return Ok(false); + } + } + TransactionAction::StakeKSH => { + // Ensure the reserve ratio does not exceed 8.0 + if reserve_ratio >= 8.0 || reserve_ratio_ma >= 8.0 { + return Ok(false); + } + } + + // KSH <--> KUSD + TransactionAction::MintKUSD => { + // No specific reserve ratio requirement for redeeming KSH + if reserve_ratio < 4.0 || reserve_ratio_ma < 4.0 { + return Ok(false); + } + } + TransactionAction::RedeemViaKUSD => { + // No specific reserve ratio requirement for redeeming KSH + } + + TransactionAction::TransferKSH | TransactionAction::TransferKUSD | TransactionAction::TransferKRV => { + // No specific reserve ratio requirement for regular transfers + } + } + Ok(true) + } + + // Method to check if a transaction can be added to the state + pub fn can_add_transaction(&mut self, tx: Arc) -> bool { + let details = AssetConversionSerializer::deserialize(&tx.payload); + + // Save the current state for rollback + let original_ksh_supply = self.ksh_supply; + let original_kusd_supply = self.kusd_supply; + let original_krv_supply = self.krv_supply; + + // Temporarily update the state based on the transaction type + match details.from_asset_type { + AssetType::KSH => self.ksh_supply -= details.from_amount, + AssetType::KUSD => self.kusd_supply -= details.from_amount, + AssetType::KRV => self.krv_supply -= details.from_amount, + } + + match details.to_asset_type { + AssetType::KSH => self.ksh_supply += details.to_amount, + AssetType::KUSD => self.kusd_supply += details.to_amount, + AssetType::KRV => self.krv_supply += details.to_amount, + } + + // Check if the updated state satisfies the reserve ratio + let is_satisfied = self.reserve_ratio_satisfied(tx.action).unwrap_or(false); + + // Rollback to the original state regardless of the outcome + self.ksh_supply = original_ksh_supply; + self.kusd_supply = original_kusd_supply; + self.krv_supply = original_krv_supply; + + is_satisfied + } + + // Method to maybe add a transaction to the state + pub fn maybe_add_transaction(&mut self, tx: Arc) -> Result<(), ReserveRatioError> { + let details = AssetConversionSerializer::deserialize(&tx.payload); + + // Save the current state for rollback + let original_ksh_supply = self.ksh_supply; + let original_kusd_supply = self.kusd_supply; + let original_krv_supply = self.krv_supply; + + // Temporarily update the state based on the transaction type + match details.from_asset_type { + AssetType::KSH => self.ksh_supply -= details.from_amount, + AssetType::KUSD => self.kusd_supply -= details.from_amount, + AssetType::KRV => self.krv_supply -= details.from_amount, + } + + match details.to_asset_type { + AssetType::KSH => self.ksh_supply += details.to_amount, + AssetType::KUSD => self.kusd_supply += details.to_amount, + AssetType::KRV => self.krv_supply += details.to_amount, + } + + if tx.action.is_transfer() { + // Update the transaction count + self.transaction_count += 1; + return Ok(()); + } + + // Check if the updated state satisfies the reserve ratio + let is_satisfied = self.reserve_ratio_satisfied(tx.action)?; + + // Rollback to the original state if the transaction cannot be added + if !is_satisfied { + self.ksh_supply = original_ksh_supply; + self.kusd_supply = original_kusd_supply; + self.krv_supply = original_krv_supply; + return Err(ReserveRatioError::Other("Reserve ratio not satisfied.".to_string())); + } + + // Update the transaction count + self.transaction_count += 1; + + Ok(()) + } +} diff --git a/consensus/src/consensus/mod.rs b/consensus/src/consensus/mod.rs index 52a7037692..fc3a098bb7 100644 --- a/consensus/src/consensus/mod.rs +++ b/consensus/src/consensus/mod.rs @@ -404,8 +404,9 @@ impl ConsensusApi for Consensus { miner_data: MinerData, tx_selector: Box, build_mode: TemplateBuildMode, + target_block_time: u64, ) -> Result { - self.virtual_processor.build_block_template(miner_data, tx_selector, build_mode) + self.virtual_processor.build_block_template(miner_data, tx_selector, build_mode, target_block_time) } fn validate_and_insert_block(&self, block: Block) -> BlockValidationFutures { diff --git a/consensus/src/model/stores/mod.rs b/consensus/src/model/stores/mod.rs index b9ce123837..9760c9618c 100644 --- a/consensus/src/model/stores/mod.rs +++ b/consensus/src/model/stores/mod.rs @@ -17,6 +17,7 @@ pub mod pruning_utxoset; pub mod reachability; pub mod relations; pub mod statuses; +pub mod supply; pub mod tips; pub mod utxo_diffs; pub mod utxo_multisets; diff --git a/consensus/src/model/stores/supply.rs b/consensus/src/model/stores/supply.rs new file mode 100644 index 0000000000..7a53b62601 --- /dev/null +++ b/consensus/src/model/stores/supply.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +use kash_database::{ + prelude::{CachedDbItem, StoreResult, DB}, + registry::DatabaseStorePrefixes, +}; + +/// Reader API for `CirculatingSupplyStore`. +pub trait CirculatingSupplyStoreReader { + fn get(&self) -> StoreResult; +} + +/// A DB + cache implementation of `UtxoIndexTipsStore` trait +#[derive(Clone)] +pub struct DbCirculatingSupplyStore { + db: Arc, + access: CachedDbItem, +} + +impl DbCirculatingSupplyStore { + pub fn new(db: Arc) -> Self { + Self { db: Arc::clone(&db), access: CachedDbItem::new(db, DatabaseStorePrefixes::CirculatingSupply.into()) } + } +} + +impl CirculatingSupplyStoreReader for DbCirculatingSupplyStore { + fn get(&self) -> StoreResult { + self.access.read() + } +} + +/// Represents the circulating supply for each asset type. +#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] +pub struct AssetCirculatingSupply { + pub ksh_supply: u64, + pub kusd_supply: u64, + pub krv_supply: u64, +} diff --git a/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs b/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs index 24a09a9546..76f6bf503b 100644 --- a/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs +++ b/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs @@ -53,6 +53,7 @@ impl BlockBodyProcessor { if let Err(e) = self.transaction_validator.validate_tx_in_isolation(tx) { return Err(RuleError::TxInIsolationValidationFailed(tx.id(), e)); } + // TODO: Get coin supply and price Record to init reserve ratio state, and check with tx } Ok(()) } @@ -178,6 +179,7 @@ mod tests { 0.into(), 9, Default::default(), + Default::default(), ), vec![ Transaction::new( diff --git a/consensus/src/pipeline/virtual_processor/processor.rs b/consensus/src/pipeline/virtual_processor/processor.rs index a2b7eb97ca..26805ec6a3 100644 --- a/consensus/src/pipeline/virtual_processor/processor.rs +++ b/consensus/src/pipeline/virtual_processor/processor.rs @@ -70,7 +70,7 @@ use kash_consensus_notify::{ root::ConsensusNotificationRoot, }; use kash_consensusmanager::SessionLock; -use kash_core::{debug, info, time::unix_now, trace, warn}; +use kash_core::{debug, info, trace, warn}; use kash_database::prelude::{StoreError, StoreResultEmptyTuple, StoreResultExtensions}; use kash_hashes::Hash; use kash_muhash::MuHash; @@ -78,6 +78,8 @@ use kash_notify::{events::EventType, notifier::Notify}; use crossbeam_channel::{Receiver as CrossbeamReceiver, Sender as CrossbeamSender}; use itertools::Itertools; +use kash_consensus_core::tx::reserve_state::ReserveRatioState; +use kash_oracle::pricing_record::PricingRecord; use kash_utils::binary_heap::BinaryHeapExtensions; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use rand::{seq::SliceRandom, Rng}; @@ -848,15 +850,24 @@ impl VirtualStateProcessor { miner_data: MinerData, mut tx_selector: Box, build_mode: TemplateBuildMode, + target_block_time: u64, ) -> Result { // // TODO: tests // + // TODO: Get coin supply from utxoindex + let ksh_supply = 0; + let kusd_supply = 0; + let ksh_price = 0; + let pr = PricingRecord::load_sync(target_block_time).unwrap(); + + let rs = ReserveRatioState::new(ksh_supply, kusd_supply, ksh_price, pr.clone()); + // We call for the initial tx batch before acquiring the virtual read lock, // optimizing for the common case where all txs are valid. Following selection calls // are called within the lock in order to preserve validness of already validated txs - let mut txs = tx_selector.select_transactions(); + let mut txs = tx_selector.select_transactions(rs.clone()); let virtual_read = self.virtual_stores.read(); let virtual_state = virtual_read.state.get().unwrap(); @@ -878,7 +889,7 @@ impl VirtualStateProcessor { while has_rejections { has_rejections = false; - let next_batch = tx_selector.select_transactions(); // Note that once next_batch is empty the loop will exit + let next_batch = tx_selector.select_transactions(rs.clone()); // Note that once next_batch is empty the loop will exit let next_batch_results = self.validate_block_template_transactions_in_parallel(&next_batch, &virtual_state, &virtual_utxo_view); for (tx, res) in next_batch.into_iter().zip(next_batch_results) { @@ -904,7 +915,7 @@ impl VirtualStateProcessor { drop(virtual_read); // Build the template - self.build_block_template_from_virtual_state(virtual_state, miner_data, txs) + self.build_block_template_from_virtual_state(virtual_state, miner_data, txs, target_block_time, pr) } pub(crate) fn validate_block_template_transactions( @@ -932,6 +943,8 @@ impl VirtualStateProcessor { virtual_state: Arc, miner_data: MinerData, mut txs: Vec, + target_block_time: u64, + pr: PricingRecord, ) -> Result { // [`calc_block_parents`] can use deep blocks below the pruning point for this calculation, so we // need to hold the pruning lock. @@ -959,21 +972,20 @@ impl VirtualStateProcessor { let accepted_id_merkle_root = kash_merkle::calc_merkle_root(virtual_state.accepted_tx_ids.iter().copied()); let utxo_commitment = virtual_state.multiset.clone().finalize(); - // Past median time is the exclusive lower bound for valid block time, so we increase by 1 to get the valid min - let min_block_time = virtual_state.past_median_time + 1; let header = Header::new_finalized( version, parents_by_level, hash_merkle_root, accepted_id_merkle_root, utxo_commitment, - u64::max(min_block_time, unix_now()), + target_block_time, virtual_state.bits, 0, virtual_state.daa_score, virtual_state.ghostdag_data.blue_work, virtual_state.ghostdag_data.blue_score, header_pruning_point, + pr, ); let selected_parent_hash = virtual_state.ghostdag_data.selected_parent; let selected_parent_timestamp = self.headers_store.get_timestamp(selected_parent_hash).unwrap(); diff --git a/consensus/src/pipeline/virtual_processor/test_block_builder.rs b/consensus/src/pipeline/virtual_processor/test_block_builder.rs index 9cabe9c9e8..897d9ac87e 100644 --- a/consensus/src/pipeline/virtual_processor/test_block_builder.rs +++ b/consensus/src/pipeline/virtual_processor/test_block_builder.rs @@ -7,7 +7,9 @@ use kash_consensus_core::{ block::BlockTemplate, blockhash::ORIGIN, coinbase::MinerData, errors::block::RuleError, tx::Transaction, utxo::utxo_view::UtxoViewComposition, }; +use kash_core::time::unix_now; use kash_hashes::Hash; +use kash_oracle::pricing_record::PricingRecord; use super::VirtualStateProcessor; @@ -61,6 +63,6 @@ impl TestBlockBuilder { let pov_virtual_utxo_view = (&virtual_read.utxo_set).compose(accumulated_diff); self.validate_block_template_transactions(&txs, &pov_virtual_state, &pov_virtual_utxo_view)?; drop(virtual_read); - self.build_block_template_from_virtual_state(pov_virtual_state, miner_data, txs) + self.build_block_template_from_virtual_state(pov_virtual_state, miner_data, txs, unix_now(), PricingRecord::default()) } } diff --git a/consensus/src/pipeline/virtual_processor/tests.rs b/consensus/src/pipeline/virtual_processor/tests.rs index 476ab666ad..ca793779ea 100644 --- a/consensus/src/pipeline/virtual_processor/tests.rs +++ b/consensus/src/pipeline/virtual_processor/tests.rs @@ -1,4 +1,5 @@ use crate::{consensus::test_consensus::TestConsensus, model::services::reachability::ReachabilityService}; +use kash_consensus_core::tx::reserve_state::ReserveRatioState; use kash_consensus_core::{ api::ConsensusApi, block::{Block, BlockTemplate, MutableBlock, TemplateBuildMode, TemplateTransactionSelector}, @@ -23,7 +24,7 @@ impl OnetimeTxSelector { } impl TemplateTransactionSelector for OnetimeTxSelector { - fn select_transactions(&mut self) -> Vec { + fn select_transactions(&mut self, _rs: ReserveRatioState) -> Vec { self.txs.take().unwrap() } @@ -108,6 +109,7 @@ impl TestContext { self.miner_data.clone(), Box::new(OnetimeTxSelector::new(Default::default())), TemplateBuildMode::Standard, + timestamp, ) .unwrap(); t.block.header.timestamp = timestamp; diff --git a/consensus/src/processes/mod.rs b/consensus/src/processes/mod.rs index fb1490deaa..b783e8617a 100644 --- a/consensus/src/processes/mod.rs +++ b/consensus/src/processes/mod.rs @@ -9,6 +9,7 @@ pub mod pruning; pub mod pruning_proof; pub mod reachability; pub mod relations; +mod reserve_manager; pub mod sync; pub mod transaction_validator; pub mod traversal_manager; diff --git a/consensus/src/processes/parents_builder.rs b/consensus/src/processes/parents_builder.rs index 002395c2b2..21a5e93180 100644 --- a/consensus/src/processes/parents_builder.rs +++ b/consensus/src/processes/parents_builder.rs @@ -313,6 +313,7 @@ mod tests { blue_work: 0.into(), blue_score: 0, pruning_point: 1.into(), + pricing_record: Default::default(), }), block_level: 0, }, @@ -342,6 +343,7 @@ mod tests { blue_work: 0.into(), blue_score: 0, pruning_point: 1.into(), + pricing_record: Default::default(), }), block_level: 0, }, @@ -371,6 +373,7 @@ mod tests { blue_work: 0.into(), blue_score: 0, pruning_point: 1.into(), + pricing_record: Default::default(), }), block_level: 0, }, @@ -473,6 +476,7 @@ mod tests { blue_work: 0.into(), blue_score: 0, pruning_point: 1.into(), + pricing_record: Default::default(), }), block_level: test_block.block_level, }, @@ -535,6 +539,7 @@ mod tests { blue_work: 0.into(), blue_score: 0, pruning_point: 1.into(), + pricing_record: Default::default(), }), block_level: 0, }, @@ -576,6 +581,7 @@ mod tests { blue_work: 0.into(), blue_score: 0, pruning_point: 1.into(), + pricing_record: Default::default(), }), block_level: test_block.block_level, }, diff --git a/consensus/src/processes/reserve_manager.rs b/consensus/src/processes/reserve_manager.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/consensus/src/processes/reserve_manager.rs @@ -0,0 +1 @@ + diff --git a/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs b/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs index 4fefb827b9..e6db6ba3be 100644 --- a/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs +++ b/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs @@ -1,4 +1,5 @@ use crate::constants::{MAX_SOMPI, TX_VERSION}; +use kash_consensus_core::tx::asset_conversion::AssetConversionSerializer; use kash_consensus_core::tx::Transaction; use std::collections::HashSet; @@ -107,9 +108,20 @@ fn check_gas(tx: &Transaction) -> TxResult<()> { } fn check_transaction_payload(tx: &Transaction) -> TxResult<()> { - // This should be revised if subnetworks are activated (along with other validations that weren't copied from kashd) - if !tx.is_coinbase() && !tx.payload.is_empty() { - return Err(TxRuleError::NonCoinbaseTxHasPayload); + if tx.action.is_transfer() { + if !tx.is_coinbase() && !tx.payload.is_empty() { + return Err(TxRuleError::NonCoinbaseTxHasPayload); + } + } else { + let details = AssetConversionSerializer::deserialize(&tx.payload); + + let (expected_from, expected_to) = tx.action.asset_transfer_types(); + if details.from_asset_type != expected_from || details.to_asset_type != expected_to { + return Err(TxRuleError::InvalidAssetConversionTypes); + } + if details.from_amount == 0 || details.to_amount == 0 { + return Err(TxRuleError::InvalidAssetConversionAmount); + } } Ok(()) } diff --git a/consensus/src/test_helpers.rs b/consensus/src/test_helpers.rs index 8dd160019c..78ebf15a6f 100644 --- a/consensus/src/test_helpers.rs +++ b/consensus/src/test_helpers.rs @@ -8,6 +8,7 @@ use kash_consensus_core::{ utxo::utxo_collection::UtxoCollection, }; use kash_hashes::{Hash, HASH_SIZE}; +use kash_oracle::pricing_record::create_random_pricing_record; use rand::{rngs::SmallRng, seq::SliceRandom, Rng}; pub fn header_from_precomputed_hash(hash: Hash, parents: Vec) -> Header { @@ -112,6 +113,7 @@ pub fn generate_random_header(rng: &mut SmallRng, parent_amount: usize) -> Heade rng.gen::().into(), rng.gen(), generate_random_hash(rng), + create_random_pricing_record(rng), ) } diff --git a/mining/Cargo.toml b/mining/Cargo.toml index 9f3ddc5a54..b112645cba 100644 --- a/mining/Cargo.toml +++ b/mining/Cargo.toml @@ -12,12 +12,14 @@ kash-addresses.workspace = true kash-consensus-core.workspace = true kash-consensusmanager.workspace = true kash-core.workspace = true +kash-database.workspace = true kash-hashes.workspace = true kash-mining-errors.workspace = true kash-muhash.workspace = true kash-txscript.workspace = true kash-utils.workspace = true - +kash-utxoindex.workspace = true +kash-oracle.workspace = true futures-util.workspace = true itertools.workspace = true log.workspace = true diff --git a/mining/src/block_template/builder.rs b/mining/src/block_template/builder.rs index 3af8375e07..6295ff54ff 100644 --- a/mining/src/block_template/builder.rs +++ b/mining/src/block_template/builder.rs @@ -11,6 +11,7 @@ use kash_core::{ debug, time::{unix_now, Stopwatch}, }; +use std::cmp::max; pub(crate) struct BlockTemplateBuilder { policy: Policy, @@ -94,8 +95,13 @@ impl BlockTemplateBuilder { ) -> BuilderResult { let _sw = Stopwatch::<20>::with_threshold("build_block_template op"); debug!("Considering {} transactions for a new block template", transactions.len()); + + // Past median time is the exclusive lower bound for valid block time, so we increase by 1 to get the valid min + let min_block_time = consensus.get_virtual_past_median_time() + 1; + let target_block_time = max(min_block_time, unix_now()); + let selector = Box::new(TransactionsSelector::new(self.policy.clone(), transactions)); - Ok(consensus.build_block_template(miner_data.clone(), selector, build_mode)?) + Ok(consensus.build_block_template(miner_data.clone(), selector, build_mode, target_block_time)?) } /// modify_block_template clones an existing block template, modifies it to the requested coinbase data and updates the timestamp diff --git a/mining/src/block_template/selector.rs b/mining/src/block_template/selector.rs index 37ebf331ba..8725ef4433 100644 --- a/mining/src/block_template/selector.rs +++ b/mining/src/block_template/selector.rs @@ -8,6 +8,7 @@ use super::{ model::tx::{CandidateList, SelectableTransaction, SelectableTransactions, TransactionIndex}, policy::Policy, }; +use kash_consensus_core::tx::reserve_state::ReserveRatioState; use kash_consensus_core::{ block::TemplateTransactionSelector, subnets::SubnetworkId, @@ -57,7 +58,6 @@ impl TransactionsSelector { let _sw = Stopwatch::<100>::with_threshold("TransactionsSelector::new op"); // Sort the transactions by subnetwork_id. transactions.sort_by(|a, b| a.tx.subnetwork_id.cmp(&b.tx.subnetwork_id)); - // Create the object without selectable transactions let mut selector = Self { policy, @@ -103,7 +103,7 @@ impl TransactionsSelector { /// select_transactions loops over the candidate transactions /// and appends the ones that will be included in the next block into /// selected_txs. - pub(crate) fn select_transactions(&mut self) -> Vec { + pub(crate) fn select_transactions(&mut self, mut reserve_ratio_state: ReserveRatioState) -> Vec { let _sw = Stopwatch::<15>::with_threshold("select_transaction op"); let mut rng = rand::thread_rng(); @@ -134,6 +134,11 @@ impl TransactionsSelector { } let selected_tx = &self.transactions[selected_candidate.index]; + // Check if the transaction is valid to reserve_ratio_state + if let Err(_) = reserve_ratio_state.maybe_add_transaction(selected_tx.tx.clone()) { + continue; + } + // Enforce maximum transaction mass per block. // Also check for overflow. let next_total_mass = self.total_mass.checked_add(selected_tx.calculated_mass); @@ -230,8 +235,8 @@ impl TransactionsSelector { } impl TemplateTransactionSelector for TransactionsSelector { - fn select_transactions(&mut self) -> Vec { - self.select_transactions() + fn select_transactions(&mut self, rs: ReserveRatioState) -> Vec { + self.select_transactions(rs) } fn reject_selection(&mut self, tx_id: TransactionId) { @@ -288,7 +293,7 @@ mod tests { let (mut kept, mut rejected) = (HashSet::new(), HashSet::new()); let mut reject_count = 32; for i in 0..10 { - let selected_txs = selector.select_transactions(); + let selected_txs = selector.select_transactions(ReserveRatioState::new(10000, 2000, 2000, Default::default())); if i > 0 { assert_eq!( selected_txs.len(), diff --git a/mining/src/testutils/consensus_mock.rs b/mining/src/testutils/consensus_mock.rs index 8dc3ea4340..1074f29d01 100644 --- a/mining/src/testutils/consensus_mock.rs +++ b/mining/src/testutils/consensus_mock.rs @@ -15,10 +15,11 @@ use kash_consensus_core::{ tx::{MutableTransaction, Transaction, TransactionId, TransactionOutpoint, UtxoEntry}, utxo::utxo_collection::UtxoCollection, }; -use kash_core::time::unix_now; use kash_hashes::ZERO_HASH; use kash_consensus_core::asset_type::AssetType::KSH; +use kash_consensus_core::tx::reserve_state::ReserveRatioState; +use kash_oracle::pricing_record::PricingRecord; use parking_lot::RwLock; use std::{collections::HashMap, sync::Arc}; @@ -78,12 +79,12 @@ impl ConsensusApi for ConsensusMock { miner_data: MinerData, mut tx_selector: Box, _build_mode: TemplateBuildMode, + target_block_time: u64, ) -> Result { - let mut txs = tx_selector.select_transactions(); + let mut txs = tx_selector.select_transactions(ReserveRatioState::default()); let coinbase_manager = CoinbaseManagerMock::new(); let coinbase = coinbase_manager.expected_coinbase_transaction(miner_data.clone()); txs.insert(0, coinbase.tx); - let now = unix_now(); let hash_merkle_root = calc_hash_merkle_root(txs.iter()); let header = Header::new_finalized( BLOCK_VERSION, @@ -91,17 +92,18 @@ impl ConsensusApi for ConsensusMock { hash_merkle_root, ZERO_HASH, ZERO_HASH, - now, + target_block_time, 123456789u32, 0, 0, 0.into(), 0, ZERO_HASH, + PricingRecord::default(), ); let mutable_block = MutableBlock::new(header, txs); - Ok(BlockTemplate::new(mutable_block, miner_data, coinbase.has_red_reward, now, 0, ZERO_HASH)) + Ok(BlockTemplate::new(mutable_block, miner_data, coinbase.has_red_reward, target_block_time, 0, ZERO_HASH)) } fn validate_mempool_transaction(&self, mutable_tx: &mut MutableTransaction) -> TxResult<()> { diff --git a/oracle/Cargo.toml b/oracle/Cargo.toml new file mode 100644 index 0000000000..e9801222e0 --- /dev/null +++ b/oracle/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "kash-oracle" +version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +edition.workspace = true +include.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +borsh = { workspace = true, features = ["rc"] } +hex = "0.4.3" +rand = "0.8.5" +thiserror = "1.0.56" +openssl = "0.10" +serde = { version = "1.0.193", features = ["derive"] } +reqwest = { version = "0.11.23", features = ["blocking", "json"] } +wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] } +serde-wasm-bindgen.workspace = true diff --git a/oracle/src/constrants.rs b/oracle/src/constrants.rs new file mode 100644 index 0000000000..ef1a004257 --- /dev/null +++ b/oracle/src/constrants.rs @@ -0,0 +1,12 @@ +/// The acceptable time difference (in seconds) between the block timestamp and the +/// pricing record timestamp for the pricing record to be considered valid. +pub const PRICING_RECORD_VALID_TIME_DIFF_FROM_BLOCK: u64 = 120 * 1000; // 2 minutes + +pub const MAINNET_ORACLE_URLS: &'static [&'static str] = &["https://oracle-1.kashnet.org", "https://oracle-2.kashnet.org"]; + +pub const TESTNET_ORACLE_URLS: &'static [&'static str] = + &["https://oracle-1.testnet.kashnet.org", "https://oracle-2.testnet.kashnet.org"]; + +pub const ORACLE_URLS_PUBKEYS: &'static str = "-----BEGIN PUBLIC KEY-----\n\ + UNFILLED\n\ + -----END PUBLIC KEY-----"; diff --git a/oracle/src/errors.rs b/oracle/src/errors.rs new file mode 100644 index 0000000000..74fdf2872c --- /dev/null +++ b/oracle/src/errors.rs @@ -0,0 +1,37 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum OracleError { + #[error("Invalid public key")] + InvalidPublicKey, + + #[error("Signature verification failed")] + SignatureVerificationFailed, + + #[error("Data serialization error")] + SerializationError, + + #[error("Data load error: {0}")] + DataLoadError(String), + + #[error("Data deserialization error")] + DeserializationError, + + #[error("Invalid pricing record data")] + InvalidPricingRecord, + + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + + #[error("Configuration error: {0}")] + Configuration(String), + + #[error("Timestamp is too far in the future")] + TimestampTooFarInFuture, + + #[error("Timestamp is too old")] + TimestampTooOld, + + #[error("Missing rates in pricing record")] + MissingRates, +} diff --git a/oracle/src/lib.rs b/oracle/src/lib.rs new file mode 100644 index 0000000000..4f2cfe0a95 --- /dev/null +++ b/oracle/src/lib.rs @@ -0,0 +1,3 @@ +mod constrants; +mod errors; +pub mod pricing_record; diff --git a/oracle/src/pricing_record.rs b/oracle/src/pricing_record.rs new file mode 100644 index 0000000000..fa1a02ee1d --- /dev/null +++ b/oracle/src/pricing_record.rs @@ -0,0 +1,243 @@ +use crate::constrants::{ORACLE_URLS_PUBKEYS, PRICING_RECORD_VALID_TIME_DIFF_FROM_BLOCK, TESTNET_ORACLE_URLS}; +use crate::errors::OracleError; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use hex; +use openssl::{hash::MessageDigest, sign::Verifier, x509::X509}; +use rand::rngs::SmallRng; +use rand::Rng; +use serde::{Deserialize, Serialize}; +use serde_wasm_bindgen::from_value; +use std::error::Error; +use std::fmt; +use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; + +#[wasm_bindgen] +#[derive(Serialize, Deserialize, Clone, Debug, Default, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq)] +pub struct PricingRecord { + pub ksh: u64, + pub ksh_ma: u64, + pub kusd: u64, + pub kusd_ma: u64, + pub krv: u64, + pub krv_ma: u64, + pub timestamp: u64, + #[wasm_bindgen(skip)] + pub signature: Vec, +} + +#[derive(Deserialize)] +pub struct PricingRecordResponse { + success: bool, + error: String, + data: PricingRecord, +} + +impl fmt::Display for PricingRecord { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "PricingRecord {{ ksh: {}, ksh_ma: {}, kusd: {}, kusd_ma: {}, krv: {}, krv_ma: {}, timestamp: {}, signature: {:?} }}", + self.ksh, self.ksh_ma, self.kusd, self.kusd_ma, self.krv, self.krv_ma, self.timestamp, self.signature + ) + } +} + +impl FromStr for PricingRecord { + type Err = String; + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split(',').collect(); + if parts.len() != 8 { + return Err("Incorrect number of fields".to_string()); + } + + let ksh = parts[0].parse().map_err(|_| "Invalid ksh value".to_string())?; + let ksh_ma = parts[1].parse().map_err(|_| "Invalid ksh_ma value".to_string())?; + let kusd = parts[2].parse().map_err(|_| "Invalid kusd value".to_string())?; + let kusd_ma = parts[3].parse().map_err(|_| "Invalid kusd_ma value".to_string())?; + let krv = parts[4].parse().map_err(|_| "Invalid krv value".to_string())?; + let krv_ma = parts[5].parse().map_err(|_| "Invalid krv_ma value".to_string())?; + let timestamp = parts[6].parse().map_err(|_| "Invalid timestamp value".to_string())?; + let signature = hex::decode(parts[7]).map_err(|_| "Invalid signature format".to_string())?; + + Ok(PricingRecord { ksh, ksh_ma, kusd, kusd_ma, krv, krv_ma, timestamp, signature }) + } +} + +impl PricingRecord { + pub fn new() -> Self { + Self::default() + } + + /// Synchronously loads a pricing record. + /// + /// This method uses the synchronous version of HTTP requests to load the pricing record. + /// It iterates through each Oracle URL and uses the first valid response. + /// + /// Parameters: + /// - `timestamp`: The block timestamp. + /// - `past_median_time`: The median timestamp of the last blocks. + /// + /// Returns: + /// - A `Result` containing `PricingRecord` if successful, or an error if the operation fails. + pub fn load_sync(timestamp: u64) -> Result> { + let client = reqwest::blocking::Client::new(); + let mut last_error = String::new(); + + for url in TESTNET_ORACLE_URLS.iter() { + let request_url = format!("{}/price/?timestamp={}", url, timestamp); + + // Attempt to send request to the Oracle. + match client.get(&request_url).send() { + Ok(resp) => { + // Attempt to parse the JSON response. + match resp.json::() { + Ok(response) if response.success => { + return Ok(response.data); + } + Ok(response) => last_error = format!("Oracle error at {}: {}", url, response.error), + Err(err) => last_error = format!("Failed to parse response from {}: {}", url, err), + } + } + Err(err) => { + last_error = format!("Error sending request to Oracle at {}: {}", url, err); + } + } + } + + // If all requests fail, return an accumulated error. + Err(Box::new(OracleError::DataLoadError(last_error))) + } + + /// Asynchronously loads a pricing record. + /// + /// This method uses asynchronous HTTP requests to load the pricing record. + /// It iterates through each Oracle URL and uses the first valid response. + /// + /// Parameters: + /// - `timestamp`: The block timestamp. + /// - `past_median_time`: The median timestamp of the last blocks. + /// + /// Returns: + /// - A `Result` containing `PricingRecord` if successful, or an error if the operation fails. + pub async fn load(timestamp: u64) -> Result> { + let client = reqwest::Client::new(); + let mut last_error = String::new(); + + for url in TESTNET_ORACLE_URLS.iter() { + let request_url = format!("{}/price/?timestamp={}", url, timestamp); + + // Attempt to send request to the Oracle. + match client.get(&request_url).send().await { + Ok(resp) => { + // Attempt to parse the JSON response. + match resp.json::().await { + Ok(response) if response.success => { + return Ok(response.data); + } + Ok(response) => last_error = format!("Oracle error at {}: {}", url, response.error), + Err(err) => last_error = format!("Failed to parse response from {}: {}", url, err), + } + } + Err(err) => { + last_error = format!("Error sending request to Oracle at {}: {}", url, err); + } + } + } + + // If all requests fail, return an accumulated error. + Err(Box::new(OracleError::DataLoadError(last_error))) + } + pub fn equal(&self, other: &Self) -> bool { + self.ksh == other.ksh + && self.ksh_ma == other.ksh_ma + && self.kusd == other.kusd + && self.kusd_ma == other.kusd_ma + && self.krv == other.krv + && self.krv_ma == other.krv_ma + && self.timestamp == other.timestamp + && self.signature == other.signature + } + + /// Checks if the record is empty. + pub fn empty(&self) -> bool { + *self == Self::default() + } + + /// Verifies the signature against a public key. + pub fn verify_signature(&self, public_key: &str) -> Result { + if public_key.is_empty() { + return Err(OracleError::InvalidPublicKey); + } + + let public_key = X509::from_pem(public_key.as_bytes()) + .map_err(|_| OracleError::InvalidPublicKey)? + .public_key() + .map_err(|_| OracleError::InvalidPublicKey)?; + + let message = + format!("{},{},{},{},{},{},{}", self.ksh, self.ksh_ma, self.kusd, self.kusd_ma, self.krv, self.krv_ma, self.timestamp); + + let mut verifier = + Verifier::new(MessageDigest::sha256(), &public_key).map_err(|_| OracleError::SignatureVerificationFailed)?; + verifier.update(message.as_bytes()).map_err(|_| OracleError::SignatureVerificationFailed)?; + verifier.verify(&self.signature).map_err(|_| OracleError::SignatureVerificationFailed) + } + + /// Checks if any of the rates are missing. + pub fn has_unset_field(&self) -> bool { + self.ksh == 0 || self.ksh_ma == 0 || self.kusd == 0 || self.kusd_ma == 0 || self.krv == 0 || self.krv_ma == 0 + } + + /// Validates the record based on network type, hard fork version, block timestamp, and last block timestamp. + pub fn valid(&self, block_timestamp: u64, past_median_time: u64) -> Result { + if self.empty() { + return Ok(true); + } + + if self.has_unset_field() { + return Err(OracleError::InvalidPricingRecord); + } + + if !self.verify_signature(ORACLE_URLS_PUBKEYS)? { + return Err(OracleError::SignatureVerificationFailed); + } + + // Validate the timestamp + if self.timestamp > block_timestamp + PRICING_RECORD_VALID_TIME_DIFF_FROM_BLOCK { + return Err(OracleError::TimestampTooFarInFuture); + } + + if self.timestamp <= past_median_time { + return Err(OracleError::TimestampTooOld); + } + + Ok(true) + } +} + +impl TryFrom for PricingRecord { + type Error = OracleError; + + fn try_from(value: JsValue) -> Result { + from_value(value).map_err(|_| OracleError::DeserializationError) + } +} + +// Helper function to create a random, but reasonable PricingRecord +pub fn create_random_pricing_record(rng: &mut SmallRng) -> PricingRecord { + let random_signature: Vec = (0..64).map(|_| rng.gen::()).collect(); + + PricingRecord { + ksh: rng.gen_range(80..100), + ksh_ma: rng.gen_range(75..95), + kusd: rng.gen_range(1..100), + kusd_ma: rng.gen_range(1..100), + krv: rng.gen_range(1..100), + krv_ma: rng.gen_range(1..100), + timestamp: rng.gen_range(1_600_000_000..1_700_000_000), // Example timestamp range + signature: random_signature, // Randomly generated, may not be a valid signature + } +} diff --git a/protocol/p2p/Cargo.toml b/protocol/p2p/Cargo.toml index 9a838283f5..a7ec0d495c 100644 --- a/protocol/p2p/Cargo.toml +++ b/protocol/p2p/Cargo.toml @@ -24,6 +24,7 @@ kash-consensus-core.workspace = true kash-mining-errors.workspace = true kash-hashes.workspace = true kash-math.workspace = true +kash-oracle.workspace = true kash-utils.workspace = true kash-utils-tower.workspace = true diff --git a/protocol/p2p/proto/p2p.proto b/protocol/p2p/proto/p2p.proto index 1fb2918e61..7d954697c9 100644 --- a/protocol/p2p/proto/p2p.proto +++ b/protocol/p2p/proto/p2p.proto @@ -78,6 +78,18 @@ message BlockHeader{ bytes blueWork = 10; Hash pruningPoint = 14; uint64 blueScore = 13; + PricingRecord pricingRecord = 15; +} + +message PricingRecord { + uint64 ksh = 1; + uint64 ksh_ma = 2; + uint64 kusd = 3; + uint64 kusd_ma = 4; + uint64 krv = 5; + uint64 krv_ma = 6; + uint64 timestamp = 7; + bytes signature = 8; } message BlockLevelParents { diff --git a/protocol/p2p/src/convert/header.rs b/protocol/p2p/src/convert/header.rs index 8161e72d7d..57498fcebe 100644 --- a/protocol/p2p/src/convert/header.rs +++ b/protocol/p2p/src/convert/header.rs @@ -25,6 +25,7 @@ impl From<&Header> for protowire::BlockHeader { blue_work: item.blue_work.to_be_bytes_var(), blue_score: item.blue_score, pruning_point: Some(item.pruning_point.into()), + pricing_record: Some(item.pricing_record.clone().into()), } } } @@ -56,6 +57,7 @@ impl TryFrom for Header { BlueWorkType::from_be_bytes_var(&item.blue_work)?, item.blue_score, item.pruning_point.try_into_ex()?, + item.pricing_record.try_into_ex()?, )) } } diff --git a/protocol/p2p/src/convert/mod.rs b/protocol/p2p/src/convert/mod.rs index 738dac70e1..9e21c74cf3 100644 --- a/protocol/p2p/src/convert/mod.rs +++ b/protocol/p2p/src/convert/mod.rs @@ -7,6 +7,7 @@ pub mod messages; pub mod model; pub mod net_address; pub mod option; +mod pricing_record; pub mod pruning; pub mod subnets; pub mod trusted; diff --git a/protocol/p2p/src/convert/pricing_record.rs b/protocol/p2p/src/convert/pricing_record.rs new file mode 100644 index 0000000000..859d3e31fd --- /dev/null +++ b/protocol/p2p/src/convert/pricing_record.rs @@ -0,0 +1,54 @@ +use crate::convert::error::ConversionError; +use crate::convert::option::TryFromOptionEx; +use crate::pb as protowire; +use kash_oracle::pricing_record; + +impl From for protowire::PricingRecord { + fn from(record: pricing_record::PricingRecord) -> Self { + Self { + ksh: record.ksh, + ksh_ma: record.ksh_ma, + kusd: record.kusd, + kusd_ma: record.kusd_ma, + krv: record.krv, + krv_ma: record.krv_ma, + timestamp: record.timestamp, + signature: record.signature, + } + } +} + +impl From for pricing_record::PricingRecord { + fn from(record: protowire::PricingRecord) -> Self { + Self { + ksh: record.ksh, + ksh_ma: record.ksh_ma, + kusd: record.kusd, + kusd_ma: record.kusd_ma, + krv: record.krv, + krv_ma: record.krv_ma, + timestamp: record.timestamp, + signature: record.signature, + } + } +} + +impl TryFromOptionEx> for pricing_record::PricingRecord { + type Error = ConversionError; + + fn try_from_ex(value: Option) -> Result { + match value { + Some(proto_record) => Ok(pricing_record::PricingRecord { + ksh: proto_record.ksh, + ksh_ma: proto_record.ksh_ma, + kusd: proto_record.kusd, + kusd_ma: proto_record.kusd_ma, + krv: proto_record.krv, + krv_ma: proto_record.krv_ma, + timestamp: proto_record.timestamp, + signature: proto_record.signature, + }), + None => Err(ConversionError::NoneValue), + } + } +} diff --git a/rpc/grpc/core/Cargo.toml b/rpc/grpc/core/Cargo.toml index 5139d9b46c..7807538f7c 100644 --- a/rpc/grpc/core/Cargo.toml +++ b/rpc/grpc/core/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true [dependencies] kash-consensus-core.workspace = true kash-core.workspace = true +kash-oracle.workspace = true kash-notify.workspace = true kash-rpc-core.workspace = true kash-utils.workspace = true diff --git a/rpc/grpc/core/proto/rpc.proto b/rpc/grpc/core/proto/rpc.proto index fd01706122..f14a75ebb2 100644 --- a/rpc/grpc/core/proto/rpc.proto +++ b/rpc/grpc/core/proto/rpc.proto @@ -36,6 +36,7 @@ message RpcBlockHeader { string blueWork = 10; string pruningPoint = 14; uint64 blueScore = 13; + string pricingRecord = 15; } message RpcBlockLevelParents { diff --git a/rpc/grpc/core/src/convert/header.rs b/rpc/grpc/core/src/convert/header.rs index 648e0b0b0f..cf9e689cc3 100644 --- a/rpc/grpc/core/src/convert/header.rs +++ b/rpc/grpc/core/src/convert/header.rs @@ -1,5 +1,6 @@ use crate::protowire; use crate::{from, try_from}; +use kash_oracle::pricing_record::PricingRecord; use kash_rpc_core::{FromRpcHex, RpcError, RpcHash, RpcResult, ToRpcHex}; use std::str::FromStr; @@ -21,6 +22,7 @@ from!(item: &kash_rpc_core::RpcHeader, protowire::RpcBlockHeader, { blue_work: item.blue_work.to_rpc_hex(), blue_score: item.blue_score, pruning_point: item.pruning_point.to_string(), + pricing_record: item.pricing_record.to_string(), } }); @@ -45,6 +47,7 @@ try_from!(item: &protowire::RpcBlockHeader, kash_rpc_core::RpcHeader, { kash_rpc_core::RpcBlueWorkType::from_rpc_hex(&item.blue_work)?, item.blue_score, RpcHash::from_str(&item.pruning_point)?, + PricingRecord::from_str(&item.pricing_record)?, ) }); @@ -119,6 +122,7 @@ mod tests { 459912.into(), 1928374, new_unique(), + Default::default(), ); let p: protowire::RpcBlockHeader = (&r).into(); let r2: RpcHeader = (&p).try_into().unwrap(); diff --git a/simpa/src/simulator/miner.rs b/simpa/src/simulator/miner.rs index 265d2660bc..ad41f94c94 100644 --- a/simpa/src/simulator/miner.rs +++ b/simpa/src/simulator/miner.rs @@ -10,6 +10,7 @@ use kash_consensus_core::block::{Block, TemplateBuildMode, TemplateTransactionSe use kash_consensus_core::coinbase::MinerData; use kash_consensus_core::sign::sign; use kash_consensus_core::subnets::SUBNETWORK_ID_NATIVE; +use kash_consensus_core::tx::reserve_state::ReserveRatioState; use kash_consensus_core::tx::{ MutableTransaction, ScriptPublicKey, ScriptVec, Transaction, TransactionAction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, @@ -36,7 +37,7 @@ impl OnetimeTxSelector { } impl TemplateTransactionSelector for OnetimeTxSelector { - fn select_transactions(&mut self) -> Vec { + fn select_transactions(&mut self, _rs: ReserveRatioState) -> Vec { self.txs.take().unwrap() } @@ -125,7 +126,12 @@ impl Miner { let session = self.consensus.acquire_session(); let mut block_template = self .consensus - .build_block_template(self.miner_data.clone(), Box::new(OnetimeTxSelector::new(txs)), TemplateBuildMode::Standard) + .build_block_template( + self.miner_data.clone(), + Box::new(OnetimeTxSelector::new(txs)), + TemplateBuildMode::Standard, + timestamp, + ) .expect("simulation txs are selected in sync with virtual state and are expected to be valid"); drop(session); block_template.block.header.timestamp = timestamp; // Use simulation time rather than real time diff --git a/testing/integration/Cargo.toml b/testing/integration/Cargo.toml index 3f4bf5f6a9..f144a9dc74 100644 --- a/testing/integration/Cargo.toml +++ b/testing/integration/Cargo.toml @@ -18,6 +18,7 @@ kash-core.workspace = true kash-grpc-client.workspace = true kash-grpc-core.workspace = true kash-hashes.workspace = true +kash-oracle.workspace = true kash-math.workspace = true kash-merkle.workspace = true kash-notify.workspace = true diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index 5d80a65a2a..64ea0eaee2 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -56,6 +56,7 @@ use kash_database::prelude::{CachePolicy, ConnBuilder}; use kash_index_processor::service::IndexService; use kash_math::Uint256; use kash_muhash::MuHash; +use kash_oracle::pricing_record::PricingRecord; use kash_txscript::caches::TxScriptCacheCounters; use kash_utxoindex::api::{UtxoIndexApi, UtxoIndexProxy}; use kash_utxoindex::UtxoIndex; @@ -717,6 +718,7 @@ struct RPCBlockHeader { BlueScore: u64, BlueWork: String, PruningPoint: String, + PricingRecord: String, } #[allow(non_snake_case)] @@ -1128,6 +1130,7 @@ fn rpc_header_to_header(rpc_header: &RPCBlockHeader) -> Header { BlueWorkType::from_hex(&rpc_header.BlueWork).unwrap(), rpc_header.BlueScore, Hash::from_str(&rpc_header.PruningPoint).unwrap(), + PricingRecord::from_str(&rpc_header.PricingRecord).unwrap(), ) } @@ -1457,6 +1460,7 @@ async fn difficulty_test() { blue_work: 0.into(), blue_score: 0, pruning_point: 0.into(), + pricing_record: PricingRecord::default(), }; // Stage 0 diff --git a/wallet/core/src/storage/transaction/kind.rs b/wallet/core/src/storage/transaction/kind.rs index 2202ca133e..3e5aff5588 100644 --- a/wallet/core/src/storage/transaction/kind.rs +++ b/wallet/core/src/storage/transaction/kind.rs @@ -6,7 +6,7 @@ use crate::imports::*; pub use kash_consensus_core::tx::TransactionId; // Do not change the order of the variants in this enum. -seal! { 0x598a, { +seal! { 0x93c6, { #[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum TransactionKind {