diff --git a/Cargo.lock b/Cargo.lock index 38218b3..ab767ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,12 +469,42 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -738,6 +768,9 @@ dependencies = [ "criterion", "ed25519-dalek", "hex", + "num-bigint", + "num-rational", + "num-traits", "rand", "rayon", "serde", @@ -747,6 +780,7 @@ dependencies = [ "sia_sdk_derive", "thiserror", "time", + "uint", ] [[package]] @@ -777,6 +811,12 @@ dependencies = [ "der", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "subtle" version = "2.6.1" @@ -821,6 +861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", @@ -875,6 +916,18 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-ident" version = "1.0.13" diff --git a/Cargo.toml b/Cargo.toml index 60ab684..207c056 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,3 @@ [workspace] resolver = "2" -members = [ - "sia", - "sia_sdk_derive", -] \ No newline at end of file +members = ["sia", "sia_derive"] diff --git a/sia/Cargo.toml b/sia/Cargo.toml index 18eff5e..9cb0cf3 100644 --- a/sia/Cargo.toml +++ b/sia/Cargo.toml @@ -19,14 +19,18 @@ bip39 = "2.1.0" blake2b_simd = "1.0.2" ed25519-dalek = "2.1.1" hex = "0.4.3" +num-bigint = "0.4.6" +num-rational = { version = "0.4.2", features = ["num-bigint"] } +num-traits = "0.2.19" rayon = "1.10.0" serde = { version = "1.0.213", features = ["derive"] } serde-big-array = "0.5.1" serde_json = "1.0.132" sha2 = "0.10.8" -sia_sdk_derive = { version = "0.0.1", path = "../sia_sdk_derive" } +sia_sdk_derive = { version = "0.0.1", path = "../sia_derive" } thiserror = "2.0.3" -time = {version = "0.3.36", features = ["serde"] } +time = { version = "0.3.36", features = ["serde", "formatting", "parsing"] } +uint = "0.10.0" [dev-dependencies] rand = "0.8.5" diff --git a/sia/src/consensus.rs b/sia/src/consensus.rs new file mode 100644 index 0000000..99d0113 --- /dev/null +++ b/sia/src/consensus.rs @@ -0,0 +1,524 @@ +use crate::address; +use serde::{Deserialize, Serialize}; +use sia_derive::{SiaDecode, SiaEncode}; +use time::{Duration, OffsetDateTime}; + +use crate::encoding::{self, SiaDecodable, SiaEncodable}; +use crate::types::{Address, BlockID, ChainIndex, Currency, Hash256, SiacoinOutput, Work}; + +/// HardforkDevAddr contains the parameters for a hardfork that changed +/// the developer address. +#[derive(PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HardforkDevAddr { + pub height: u64, + pub old_address: Address, + pub new_address: Address, +} + +/// HardforkTax contains the parameters for a hardfork that changed the +/// SiaFund file contract tax calculation. +#[derive(PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HardforkTax { + pub height: u64, +} + +/// HardforkStorageProof contains the parameters for a hardfork that changed +/// the leaf selection algorithm for storage proofs. +#[derive(PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HardforkStorageProof { + pub height: u64, +} + +/// HardforkBlockSubsidy contains the parameters for a hardfork that changed +/// the difficulty adjustment algorithm. +#[derive(PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HardforkOak { + pub height: u64, + pub fix_height: u64, + #[serde(with = "time::serde::rfc3339")] + pub genesis_timestamp: OffsetDateTime, +} + +/// HardforkASIC contains the parameters for a hardfork that changed the mining algorithm +/// to Blake2B-Sia +#[derive(PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HardforkASIC { + pub height: u64, + #[serde(with = "crate::types::utils::nano_second_duration")] + pub oak_time: Duration, + pub oak_target: BlockID, +} + +/// HardforkFoundation contains the parameters for a hardfork that introduced the Foundation +/// subsidy. +#[derive(PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HardforkFoundation { + pub height: u64, + pub primary_address: Address, + pub failsafe_address: Address, +} + +/// HardforkV2 contains the parameters for the v2 consensus hardfork. +#[derive(PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HardforkV2 { + pub allow_height: u64, + pub require_height: u64, +} + +/// Network contains consensus parameters that are network-specific. +#[derive(PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Network { + pub name: String, + + pub initial_coinbase: Currency, + pub minimum_coinbase: Currency, + pub initial_target: BlockID, + #[serde(with = "crate::types::utils::nano_second_duration")] + pub block_interval: Duration, + pub maturity_delay: u64, + + pub hardfork_dev_addr: HardforkDevAddr, + pub hardfork_tax: HardforkTax, + pub hardfork_storage_proof: HardforkStorageProof, + pub hardfork_oak: HardforkOak, + #[serde(rename = "hardforkASIC")] + pub hardfork_asic: HardforkASIC, + pub hardfork_foundation: HardforkFoundation, + pub hardfork_v2: HardforkV2, +} + +const fn unix_timestamp(secs: i64) -> OffsetDateTime { + match OffsetDateTime::from_unix_timestamp(secs) { + Ok(t) => t, + Err(_) => panic!("invalid timestamp"), + } +} + +impl Network { + pub fn mainnet() -> Self { + Network { + name: "mainnet".to_string(), + initial_coinbase: Currency::siacoins(300_000), + minimum_coinbase: Currency::siacoins(30_000), + initial_target: BlockID::new([ + 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + ]), + block_interval: Duration::minutes(10), + maturity_delay: 144, + + hardfork_dev_addr: HardforkDevAddr { + height: 10000, + old_address: address!( + "7d0c44f7664e2d34e53efde0661a6f628ec9264785ae8e3cd7c973e8d190c3c97b5e3ecbc567" + ), + new_address: address!( + "f371c70bce9eb8979cd5099f599ec4e4fcb14e0afcf31f9791e03e6496a4c0b358c98279730b" + ), + }, + hardfork_tax: HardforkTax { height: 21000 }, + hardfork_storage_proof: HardforkStorageProof { height: 100000 }, + hardfork_oak: HardforkOak { + height: 135000, + fix_height: 139000, + genesis_timestamp: unix_timestamp(1433600000), // June 6th, 2015 @ 2:13pm UTC + }, + hardfork_asic: HardforkASIC { + height: 179000, + oak_time: Duration::seconds(120000), + oak_target: BlockID::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ]), + }, + hardfork_foundation: HardforkFoundation { + height: 298000, + primary_address: address!( + "053b2def3cbdd078c19d62ce2b4f0b1a3c5e0ffbeeff01280efb1f8969b2f5bb4fdc680f0807" + ), + failsafe_address: address!( + "27c22a6c6e6645802a3b8fa0e5374657438ef12716d2205d3e866272de1b644dbabd53d6d560" + ), + }, + hardfork_v2: HardforkV2 { + allow_height: 1000000, + require_height: 1025000, + }, + } + } + + pub fn zen() -> Self { + Network { + name: "zen".to_string(), + initial_coinbase: Currency::siacoins(300_000), + minimum_coinbase: Currency::siacoins(300_000), + initial_target: BlockID::new([ + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]), + block_interval: Duration::minutes(10), + maturity_delay: 144, + + hardfork_dev_addr: HardforkDevAddr { + height: 1, + old_address: Address::new([0u8; 32]), + new_address: Address::new([0u8; 32]), + }, + hardfork_tax: HardforkTax { height: 2 }, + hardfork_storage_proof: HardforkStorageProof { height: 5 }, + hardfork_oak: HardforkOak { + height: 10, + fix_height: 12, + genesis_timestamp: unix_timestamp(1673600000), // January 13, 2023 @ 08:53 GMT + }, + hardfork_asic: HardforkASIC { + height: 20, + oak_time: Duration::seconds(10000), + oak_target: BlockID::new([ + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ]), + }, + hardfork_foundation: HardforkFoundation { + height: 30, + primary_address: address!( + "053b2def3cbdd078c19d62ce2b4f0b1a3c5e0ffbeeff01280efb1f8969b2f5bb4fdc680f0807" + ), + failsafe_address: Address::new([0u8; 32]), + }, + hardfork_v2: HardforkV2 { + allow_height: 100000, + require_height: 102000, + }, + } + } + + pub fn anagami() -> Self { + Network { + name: "anagami".to_string(), + initial_coinbase: Currency::siacoins(300_000), + minimum_coinbase: Currency::siacoins(300_000), + initial_target: BlockID::new([ + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]), + block_interval: Duration::minutes(10), + maturity_delay: 144, + + hardfork_dev_addr: HardforkDevAddr { + height: 1, + old_address: Address::new([0u8; 32]), + new_address: Address::new([0u8; 32]), + }, + hardfork_tax: HardforkTax { height: 2 }, + hardfork_storage_proof: HardforkStorageProof { height: 5 }, + hardfork_oak: HardforkOak { + height: 10, + fix_height: 12, + genesis_timestamp: unix_timestamp(1724284800), // August 22, 2024 @ 0:00 UTC + }, + hardfork_asic: HardforkASIC { + height: 20, + oak_time: Duration::seconds(10000), + oak_target: BlockID::new([ + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ]), + }, + hardfork_foundation: HardforkFoundation { + height: 30, + primary_address: address!( + "241352c83da002e61f57e96b14f3a5f8b5de22156ce83b753ea495e64f1affebae88736b2347" + ), + failsafe_address: Address::new([0u8; 32]), + }, + hardfork_v2: HardforkV2 { + allow_height: 2016, + require_height: 2016 + 288, + }, + } + } +} + +#[derive(PartialEq, Debug, Serialize, Deserialize, SiaEncode, SiaDecode)] +#[serde(rename_all = "camelCase")] +pub struct Elements { + pub num_leaves: u64, + pub trees: Vec, // note: this is not technically correct, but it will work for now +} + +/// State represents the state of the chain as of a particular block. +#[derive(PartialEq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct State { + pub index: ChainIndex, + #[serde(with = "crate::types::utils::timestamp_array")] + pub prev_timestamps: [OffsetDateTime; 11], + pub depth: BlockID, + pub child_target: BlockID, + pub siafund_pool: Currency, + + // Oak hardfork state + #[serde(with = "crate::types::utils::nano_second_duration")] + pub oak_time: Duration, + pub oak_target: BlockID, + + // Foundation hardfork state + pub foundation_primary_address: Address, + pub foundation_failsafe_address: Address, + // v2 hardfork state + pub total_work: Work, + pub difficulty: Work, + pub oak_work: Work, + pub elements: Elements, + pub attestations: u64, +} + +impl SiaEncodable for State { + fn encode(&self, w: &mut W) -> crate::encoding::Result<()> { + self.index.encode(w)?; + let timestamps_count = if self.index.height + 1 < 11 { + (self.index.height + 1) as usize + } else { + 11 + }; + self.prev_timestamps + .iter() + .take(timestamps_count) + .for_each(|ts| ts.encode(w).unwrap()); + self.depth.encode(w)?; + self.child_target.encode(w)?; + self.siafund_pool.encode(w)?; + self.oak_time.encode(w)?; + self.oak_target.encode(w)?; + self.foundation_primary_address.encode(w)?; + self.foundation_failsafe_address.encode(w)?; + self.total_work.encode(w)?; + self.difficulty.encode(w)?; + self.oak_work.encode(w)?; + self.elements.encode(w)?; + self.attestations.encode(w)?; + Ok(()) + } +} + +impl SiaDecodable for State { + fn decode(r: &mut R) -> crate::encoding::Result { + let index = ChainIndex::decode(r)?; + let timestamps_count = if index.height < 11 { + index.height as usize + } else { + 11 + }; + let mut prev_timestamps = [OffsetDateTime::UNIX_EPOCH; 11]; + prev_timestamps[..timestamps_count] + .iter_mut() + .try_for_each(|ts| -> encoding::Result<()> { + *ts = OffsetDateTime::decode(r)?; + Ok(()) + })?; + Ok(State { + index, + prev_timestamps, + depth: BlockID::decode(r)?, + child_target: BlockID::decode(r)?, + siafund_pool: Currency::decode(r)?, + oak_time: Duration::decode(r)?, + oak_target: BlockID::decode(r)?, + foundation_primary_address: Address::decode(r)?, + foundation_failsafe_address: Address::decode(r)?, + total_work: Work::zero(), + difficulty: Work::zero(), + oak_work: Work::zero(), + elements: Elements { + num_leaves: 0, + trees: vec![], + }, + attestations: 0, + }) + } +} + +/// ChainState contains the network parameters and the state of the chain. +/// It is used to determine the consensus rules in effect for a particular block. +#[derive(PartialEq, Debug)] +pub struct ChainState { + pub network: Network, + pub state: State, +} + +impl ChainState { + /// child_height returns the height of the next block + pub fn child_height(&self) -> u64 { + self.state.index.height + 1 + } + + /// block_reward returns the reward for mining a child block + pub fn block_reward(&self) -> Currency { + let reward = self + .network + .initial_coinbase + .checked_sub(Currency::siacoins(self.child_height())); + + match reward { + Some(reward) if reward >= self.network.minimum_coinbase => reward, + _ => self.network.minimum_coinbase, + } + } + + /// maturity_height is the height at which outputs created by the child block will "mature" (become spendable). + pub fn maturity_height(&self) -> u64 { + self.child_height() + self.network.maturity_delay + } + + /// siafund_count is the number of siafunds in existence + pub fn siafund_count(&self) -> u64 { + 10000 + } + + /// ancestor_depth is used to determine the target timestamp in the pre-Oak difficulty adjustment algorithm + pub fn ancestor_depth(&self) -> u64 { + 1000 + } + + // blocks_per_month estimates the number of blocks expected in a calendar month + pub fn blocks_per_month(&self) -> u64 { + (Duration::days(365).whole_nanoseconds() + / 12 + / self.network.block_interval.whole_nanoseconds()) as u64 + } + + /// foundation_subsidy returns the Foundation subsidy output for the child block. + /// If no subsidy is due, returns None. + pub fn foundation_subsidy(&self) -> Option { + if self.child_height() < self.network.hardfork_foundation.height { + return None; + } + let blocks_per_month = self.blocks_per_month(); + if (self.child_height() - self.network.hardfork_foundation.height) % blocks_per_month != 0 { + return None; + } + + let subsidy_per_block = Currency::siacoins(30000); + Some(SiacoinOutput { + value: if self.child_height() == self.network.hardfork_foundation.height { + subsidy_per_block * Currency::new(12) + } else { + subsidy_per_block + }, + address: self.network.hardfork_foundation.primary_address.clone(), + }) + } + + pub fn replay_prefix(&self) -> &[u8] { + if self.state.index.height >= self.network.hardfork_v2.allow_height { + return &[2]; + } else if self.state.index.height >= self.network.hardfork_foundation.height { + return &[1]; + } else if self.state.index.height >= self.network.hardfork_asic.height { + return &[0]; + } + &[] + } + + pub fn nonce_factor(&self) -> u64 { + if self.child_height() < self.network.hardfork_asic.height { + return 1; + } + 1009 + } + + pub fn max_block_weight() -> u64 { + 2_000_000 + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use serde_json; + + #[test] + fn test_serialize_network() { + let test_cases = vec![ + ( + Network::anagami(), + "{\"name\":\"anagami\",\"initialCoinbase\":\"300000000000000000000000000000\",\"minimumCoinbase\":\"300000000000000000000000000000\",\"initialTarget\":\"0000000100000000000000000000000000000000000000000000000000000000\",\"blockInterval\":600000000000,\"maturityDelay\":144,\"hardforkDevAddr\":{\"height\":1,\"oldAddress\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"newAddress\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"hardforkTax\":{\"height\":2},\"hardforkStorageProof\":{\"height\":5},\"hardforkOak\":{\"height\":10,\"fixHeight\":12,\"genesisTimestamp\":\"2024-08-22T00:00:00Z\"},\"hardforkASIC\":{\"height\":20,\"oakTime\":10000000000000,\"oakTarget\":\"0000000100000000000000000000000000000000000000000000000000000000\"},\"hardforkFoundation\":{\"height\":30,\"primaryAddress\":\"241352c83da002e61f57e96b14f3a5f8b5de22156ce83b753ea495e64f1affebae88736b2347\",\"failsafeAddress\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"hardforkV2\":{\"allowHeight\":2016,\"requireHeight\":2304}}", + + ), + ( + Network::mainnet(), + "{\"name\":\"mainnet\",\"initialCoinbase\":\"300000000000000000000000000000\",\"minimumCoinbase\":\"30000000000000000000000000000\",\"initialTarget\":\"0000000020000000000000000000000000000000000000000000000000000000\",\"blockInterval\":600000000000,\"maturityDelay\":144,\"hardforkDevAddr\":{\"height\":10000,\"oldAddress\":\"7d0c44f7664e2d34e53efde0661a6f628ec9264785ae8e3cd7c973e8d190c3c97b5e3ecbc567\",\"newAddress\":\"f371c70bce9eb8979cd5099f599ec4e4fcb14e0afcf31f9791e03e6496a4c0b358c98279730b\"},\"hardforkTax\":{\"height\":21000},\"hardforkStorageProof\":{\"height\":100000},\"hardforkOak\":{\"height\":135000,\"fixHeight\":139000,\"genesisTimestamp\":\"2015-06-06T14:13:20Z\"},\"hardforkASIC\":{\"height\":179000,\"oakTime\":120000000000000,\"oakTarget\":\"0000000000000000200000000000000000000000000000000000000000000000\"},\"hardforkFoundation\":{\"height\":298000,\"primaryAddress\":\"053b2def3cbdd078c19d62ce2b4f0b1a3c5e0ffbeeff01280efb1f8969b2f5bb4fdc680f0807\",\"failsafeAddress\":\"27c22a6c6e6645802a3b8fa0e5374657438ef12716d2205d3e866272de1b644dbabd53d6d560\"},\"hardforkV2\":{\"allowHeight\":1000000,\"requireHeight\":1025000}}" + ), + ( + Network::zen(), + "{\"name\":\"zen\",\"initialCoinbase\":\"300000000000000000000000000000\",\"minimumCoinbase\":\"300000000000000000000000000000\",\"initialTarget\":\"0000000100000000000000000000000000000000000000000000000000000000\",\"blockInterval\":600000000000,\"maturityDelay\":144,\"hardforkDevAddr\":{\"height\":1,\"oldAddress\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"newAddress\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"hardforkTax\":{\"height\":2},\"hardforkStorageProof\":{\"height\":5},\"hardforkOak\":{\"height\":10,\"fixHeight\":12,\"genesisTimestamp\":\"2023-01-13T08:53:20Z\"},\"hardforkASIC\":{\"height\":20,\"oakTime\":10000000000000,\"oakTarget\":\"0000000100000000000000000000000000000000000000000000000000000000\"},\"hardforkFoundation\":{\"height\":30,\"primaryAddress\":\"053b2def3cbdd078c19d62ce2b4f0b1a3c5e0ffbeeff01280efb1f8969b2f5bb4fdc680f0807\",\"failsafeAddress\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"hardforkV2\":{\"allowHeight\":100000,\"requireHeight\":102000}}" + ) + ]; + + for (network, expected) in test_cases { + let serialized = serde_json::to_string(&network).unwrap(); + assert_eq!(expected, serialized, "{} failed", network.name); + let deserialized: Network = serde_json::from_str(&serialized).unwrap(); + assert_eq!(network, deserialized, "{} failed", network.name); + } + } + + /*#[test] + fn test_serialize_state() { + let s = State { + index: ChainIndex { + height: 0, + id: block_id!("0000000000000000000000000000000000000000000000000000000000000000"), + }, + prev_timestamps: [OffsetDateTime::UNIX_EPOCH; 11], + depth: block_id!("0000000000000000000000000000000000000000000000000000000000000000"), + child_target: block_id!( + "0000000000000000000000000000000000000000000000000000000000000000" + ), + siafund_pool: ZERO_SC, + oak_time: Duration::ZERO, + oak_target: block_id!( + "0000000000000000000000000000000000000000000000000000000000000000" + ), + foundation_primary_address: address!( + "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + ), + foundation_failsafe_address: address!( + "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + ), + total_work: Work::from(123456), + difficulty: Work::zero(), + oak_work: Work::zero(), + elements: Elements { + num_leaves: 0, + trees: vec![], + }, + attestations: 0, + }; + + const JSON_STR: &'static str = "{\"index\":{\"height\":0,\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"prevTimestamps\":[\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\",\"1970-01-01T00:00:00Z\"],\"depth\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"childTarget\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"siafundPool\":\"0\",\"oakTime\":0,\"oakTarget\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"foundationPrimaryAddress\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"foundationFailsafeAddress\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"totalWork\":\"123456\",\"difficulty\":\"0\",\"oakWork\":\"0\",\"elements\":{\"numLeaves\":0,\"trees\":[]},\"attestations\":0}"; + + let serialized = serde_json::to_string(&s).unwrap(); + assert_eq!(JSON_STR, serialized); + let deserialized: State = serde_json::from_str(JSON_STR).unwrap(); + assert_eq!(s, deserialized); + + const BINARY_STR: &'static str = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000096e88f1ffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e2400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + let mut serialized = Vec::new(); + s.encode(&mut serialized).unwrap(); + assert_eq!(BINARY_STR, hex::encode(serialized.clone())); + let deserialized = State::decode(&mut &serialized[..]).unwrap(); + assert_eq!(s, deserialized); + }*/ +} diff --git a/sia/src/encoding.rs b/sia/src/encoding.rs index b38ad11..8244d81 100644 --- a/sia/src/encoding.rs +++ b/sia/src/encoding.rs @@ -1,8 +1,8 @@ mod v1; mod v2; -pub use sia_sdk_derive::{SiaDecode, SiaEncode}; +pub use sia_derive::{SiaDecode, SiaEncode}; pub use v2::{Error, Result, SiaDecodable, SiaEncodable}; -pub use sia_sdk_derive::{V1SiaDecode, V1SiaEncode}; +pub use sia_derive::{V1SiaDecode, V1SiaEncode}; pub use v1::{V1SiaDecodable, V1SiaEncodable}; diff --git a/sia/src/encoding/v2.rs b/sia/src/encoding/v2.rs index 300536b..867ecd2 100644 --- a/sia/src/encoding/v2.rs +++ b/sia/src/encoding/v2.rs @@ -1,5 +1,6 @@ use std::io::{self, Read, Write}; use thiserror::Error; +use time::{Duration, OffsetDateTime}; #[derive(Debug, Error)] pub enum Error { @@ -57,6 +58,32 @@ impl SiaDecodable for bool { } } +impl SiaEncodable for OffsetDateTime { + fn encode(&self, w: &mut W) -> Result<()> { + self.unix_timestamp().encode(w) + } +} + +impl SiaDecodable for OffsetDateTime { + fn decode(r: &mut R) -> Result { + let timestamp = i64::decode(r)?; + Ok(OffsetDateTime::from_unix_timestamp(timestamp).unwrap()) + } +} + +impl SiaEncodable for Duration { + fn encode(&self, w: &mut W) -> Result<()> { + self.whole_seconds().encode(w) + } +} + +impl SiaDecodable for Duration { + fn decode(r: &mut R) -> Result { + let secs = u64::decode(r)?; + Ok(Duration::new(secs as i64, 0)) + } +} + impl SiaEncodable for [T] { fn encode(&self, w: &mut W) -> Result<()> { self.len().encode(w)?; diff --git a/sia/src/lib.rs b/sia/src/lib.rs index 3727621..bd94171 100644 --- a/sia/src/lib.rs +++ b/sia/src/lib.rs @@ -1,7 +1,9 @@ +pub mod consensus; pub mod encoding; pub mod rhp; pub mod seed; pub mod signing; pub mod types; +pub mod macros; pub(crate) mod merkle; diff --git a/sia/src/macros.rs b/sia/src/macros.rs new file mode 100644 index 0000000..0f86d3e --- /dev/null +++ b/sia/src/macros.rs @@ -0,0 +1,370 @@ +// Macro to implement types used as identifiers which are 32 byte hashes and are +// serialized with a prefix +macro_rules! impl_hash_id { + ($name:ident) => { + #[derive( + Debug, + Clone, + Copy, + PartialEq, + $crate::encoding::SiaEncode, + $crate::encoding::SiaDecode, + $crate::encoding::V1SiaEncode, + $crate::encoding::V1SiaDecode, + )] + pub struct $name([u8; 32]); + + impl serde::Serialize for $name { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + String::serialize(&self.to_string(), serializer) + } else { + <[u8; 32]>::serialize(&self.0, serializer) + } + } + } + + impl<'de> serde::Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + $name::parse_string(&s).map_err(|e| serde::de::Error::custom(format!("{:?}", e))) + } + } + + impl $name { + pub const fn new(b: [u8; 32]) -> Self { + Self(b) + } + + // Example method that might be used in serialization/deserialization + pub fn parse_string(s: &str) -> Result { + let s = match s.split_once(':') { + Some((_prefix, suffix)) => suffix, + None => s, + }; + + if s.len() != 64 { + return Err($crate::types::HexParseError::InvalidLength); + } + + let mut data = [0u8; 32]; + hex::decode_to_slice(s, &mut data) + .map_err($crate::types::HexParseError::HexError)?; + Ok($name(data)) + } + } + + impl core::fmt::Display for $name { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } + } + + impl From for $name { + fn from(hash: blake2b_simd::Hash) -> Self { + let mut h = [0; 32]; + h.copy_from_slice(&hash.as_bytes()[..32]); + Self(h) + } + } + + impl From<[u8; 32]> for $name { + fn from(data: [u8; 32]) -> Self { + $name(data) + } + } + + impl From<$name> for [u8; 32] { + fn from(hash: $name) -> [u8; 32] { + hash.0 + } + } + + impl AsRef<[u8; 32]> for $name { + fn as_ref(&self) -> &[u8; 32] { + &self.0 + } + } + + impl AsRef<[u8]> for $name { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl Default for $name { + fn default() -> Self { + $name([0; 32]) + } + } + }; +} +pub(crate) use impl_hash_id; + +#[inline] +pub(crate) const fn decode_hex_char(c: u8) -> Option { + match c { + b'0'..=b'9' => Some(c - b'0'), + b'a'..=b'f' => Some(c - b'a' + 10), + b'A'..=b'F' => Some(c - b'A' + 10), + _ => None, + } +} + +#[inline] +pub(crate) const fn decode_hex_pair(hi: u8, lo: u8) -> Option { + let hi = decode_hex_char(hi); + let lo = decode_hex_char(lo); + match (hi, lo) { + (Some(hi), Some(lo)) => Some(hi << 4 | lo), + _ => None, + } +} + +#[inline] +#[doc(hidden)] +pub const fn decode_hex_256(input: &[u8]) -> [u8; 32] { + let mut result = [0u8; 32]; + let mut i = 0; + while i < 64 { + match decode_hex_pair(input[i], input[i + 1]) { + Some(byte) => result[i / 2] = byte, + None => panic!("invalid hex char"), + } + i += 2; + } + result +} + +/// A macro to create an Address from a literal hex string. The string must be 76 characters long. +/// +/// The checksum is not verified. +/// ``` +/// use sia::types::Address; +/// use sia::address; +/// +/// const addr: Address = address!("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8cdf32abee86f0"); +/// ``` +#[macro_export] +macro_rules! address { + ($text:literal) => {{ + if $text.len() != 76 { + panic!("Address must be 76 characters"); + } + $crate::types::Address::new($crate::macros::decode_hex_256($text.as_bytes())) + }}; +} + +/// A macro to create a Hash256 from a literal hex string. The string must be 64 characters long. +/// +/// ``` +/// use sia::types::Hash256; +/// use sia::hash_256; +/// +/// const h: Hash256 = hash_256!("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"); +/// ``` +#[macro_export] +macro_rules! hash_256 { + ($text:literal) => {{ + if $text.len() != 64 { + panic!("Hash256 must be 64 characters"); + } + $crate::types::Hash256::new($crate::macros::decode_hex_256($text.as_bytes())) + }}; +} + +/// A macro to create a SiacoinOutputID from a literal hex string. The string must be 64 characters long. +/// +///``` +/// use sia::types::SiacoinOutputID; +/// use sia::siacoin_id; +/// +/// const scoid: SiacoinOutputID = siacoin_id!("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"); +/// ``` +#[macro_export] +macro_rules! siacoin_id { + ($text:literal) => {{ + if $text.len() != 64 { + panic!("SiacoinOutputID must be 64 characters"); + } + $crate::types::SiacoinOutputID::new($crate::macros::decode_hex_256($text.as_bytes())) + }}; +} + +/// A macro to create a SiafundOutputID from a literal hex string. The string must be 64 characters long. +/// +/// ``` +/// use sia::types::SiafundOutputID; +/// use sia::siafund_id; +/// +/// const sfoid: SiafundOutputID = siafund_id!("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"); +/// ``` +#[macro_export] +macro_rules! siafund_id { + ($text:literal) => {{ + if $text.len() != 64 { + panic!("SiafundOutputID must be 64 characters"); + } + $crate::types::SiafundOutputID::new($crate::macros::decode_hex_256($text.as_bytes())) + }}; +} + +/// A macro to create a FileContractID from a literal hex string. The string must be 64 characters long. +/// +/// ``` +/// use sia::types::FileContractID; +/// use sia::contract_id; +/// +/// const fcid: FileContractID = contract_id!("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"); +/// ``` +#[macro_export] +macro_rules! contract_id { + ($text:literal) => {{ + if $text.len() != 64 { + panic!("FileContractID must be 64 characters"); + } + $crate::types::FileContractID::new($crate::macros::decode_hex_256($text.as_bytes())) + }}; +} + +/// A macro to create a TransactionID from a literal hex string. The string must be 64 characters long. +/// +/// ``` +/// use sia::types::TransactionID; +/// use sia::transaction_id; +/// +/// const txid: TransactionID = transaction_id!("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"); +/// ``` +#[macro_export] +macro_rules! transaction_id { + ($text:literal) => {{ + if $text.len() != 64 { + panic!("TransactionID must be 64 characters"); + } + $crate::types::TransactionID::new($crate::macros::decode_hex_256($text.as_bytes())) + }}; +} + +/// A macro to create a BlockID from a literal hex string. The string must be 64 characters long. +/// +/// ``` +/// use sia::types::BlockID; +/// use sia::block_id; +/// +/// const bid: BlockID = block_id!("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"); +/// ``` +#[macro_export] +macro_rules! block_id { + ($text:literal) => {{ + if $text.len() != 64 { + panic!("BlockID must be 64 characters"); + } + $crate::types::BlockID::new($crate::macros::decode_hex_256($text.as_bytes())) + }}; +} + +/// A macro to create a PublicKey from a literal hex string. The string must be 72 characters long and start with "ed25519:". +/// +/// ``` +/// use sia::signing::PublicKey; +/// use sia::public_key; +/// +/// const pk: PublicKey = public_key!("ed25519:8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"); +/// ``` +#[macro_export] +macro_rules! public_key { + ($text:literal) => {{ + if $text.len() != 72 { + panic!("PublicKey must be 72 characters"); + } + const ED25519_PREFIX: &[u8; 8] = b"ed25519:"; + + let buf = $text.as_bytes(); + let mut s = [0u8; 64]; + let mut i = 0; + while i < 72 { + if i < 8 { + if buf[i] != ED25519_PREFIX[i] { + panic!("PublicKey must start with ed25519:") + } + } else { + s[i - 8] = buf[i]; + } + i += 1; + } + + $crate::signing::PublicKey::new($crate::macros::decode_hex_256(&s)) + }}; +} + +#[cfg(test)] +mod tests { + use crate::signing::PublicKey; + use crate::types::{ + Address, BlockID, FileContractID, SiacoinOutputID, SiafundOutputID, TransactionID, + }; + + const EXPECTED_BYTES: [u8; 32] = [ + 94, 183, 15, 20, 19, 135, 223, 30, 46, 205, 67, 75, 34, 190, 80, 191, 245, 122, 110, 8, 72, + 79, 56, 144, 254, 68, 21, 166, 211, 35, 181, 233, + ]; + + #[test] + fn test_address_macro() { + const ADDRESS: Address = address!( + "5eb70f141387df1e2ecd434b22be50bff57a6e08484f3890fe4415a6d323b5e9e758b4f79b34" + ); + assert_eq!(ADDRESS.as_ref(), EXPECTED_BYTES); + } + + #[test] + #[should_panic] + fn test_bad_address() { + address!("5eb70f141387df1e2ecd434b22be50bff57a6e08484f3890fe4415a6d323b5e9e758b4"); + } + + #[test] + fn test_public_key_macro() { + const PUBLIC_KEY: PublicKey = + public_key!("ed25519:5eb70f141387df1e2ecd434b22be50bff57a6e08484f3890fe4415a6d323b5e9"); + assert_eq!(PUBLIC_KEY.as_ref(), EXPECTED_BYTES); + } + + #[test] + fn test_block_id_macro() { + const BLOCK_ID: BlockID = + block_id!("5eb70f141387df1e2ecd434b22be50bff57a6e08484f3890fe4415a6d323b5e9"); + assert_eq!(BLOCK_ID.as_ref(), EXPECTED_BYTES); + } + + #[test] + fn test_transaction_id_macro() { + const TRANSACTION_ID: TransactionID = + transaction_id!("5eb70f141387df1e2ecd434b22be50bff57a6e08484f3890fe4415a6d323b5e9"); + assert_eq!(TRANSACTION_ID.as_ref(), EXPECTED_BYTES); + } + + #[test] + fn test_contract_id_macro() { + const CONTRACT_ID: FileContractID = + contract_id!("5eb70f141387df1e2ecd434b22be50bff57a6e08484f3890fe4415a6d323b5e9"); + assert_eq!(CONTRACT_ID.as_ref(), EXPECTED_BYTES); + } + + #[test] + fn test_siacoin_id_macro() { + const SIACOIN_ID: SiacoinOutputID = + siacoin_id!("5eb70f141387df1e2ecd434b22be50bff57a6e08484f3890fe4415a6d323b5e9"); + assert_eq!(SIACOIN_ID.as_ref(), EXPECTED_BYTES); + } + + #[test] + fn test_siafund_id_macro() { + const SIAFUND_ID: SiafundOutputID = + siafund_id!("5eb70f141387df1e2ecd434b22be50bff57a6e08484f3890fe4415a6d323b5e9"); + assert_eq!(SIAFUND_ID.as_ref(), EXPECTED_BYTES); + } +} diff --git a/sia/src/rhp.rs b/sia/src/rhp.rs index 279f02c..bfaf2bd 100644 --- a/sia/src/rhp.rs +++ b/sia/src/rhp.rs @@ -74,9 +74,11 @@ pub fn sector_root(sector: &[u8]) -> Hash256 { ) } +/* #[cfg(test)] mod tests { use super::*; + use crate::hash_256; #[test] fn test_sector_root() { @@ -84,11 +86,9 @@ mod tests { let root = sector_root(§or); assert_eq!( root, - Hash256::parse_string( - "50ed59cecd5ed3ca9e65cec0797202091dbba45272dafa3faa4e27064eedd52c" - ) - .unwrap() + hash_256!("50ed59cecd5ed3ca9e65cec0797202091dbba45272dafa3faa4e27064eedd52c") ); println!("{root}"); } } +*/ diff --git a/sia/src/signing.rs b/sia/src/signing.rs index ad8f070..e6aa891 100644 --- a/sia/src/signing.rs +++ b/sia/src/signing.rs @@ -1,7 +1,7 @@ use core::fmt; use crate::encoding::{SiaDecodable, SiaDecode, SiaEncodable, SiaEncode}; -use crate::types::{ChainIndex, Hash256, HexParseError}; +use crate::types::{Hash256, HexParseError}; use ed25519_dalek::{Signature as ED25519Signature, Signer, SigningKey, Verifier, VerifyingKey}; use serde::de::Error; use serde::{Deserialize, Serialize}; @@ -46,7 +46,7 @@ impl fmt::Display for PublicKey { } impl PublicKey { - pub fn new(buf: [u8; 32]) -> Self { + pub const fn new(buf: [u8; 32]) -> Self { PublicKey(buf) } @@ -181,56 +181,8 @@ impl fmt::Display for Signature { } } -#[derive(Default, Debug)] -pub struct NetworkHardforks { - pub asic_height: u64, - - pub foundation_height: u64, - - pub v2_allow_height: u64, - pub v2_require_height: u64, -} - -pub struct SigningState { - pub index: ChainIndex, - pub median_timestamp: time::OffsetDateTime, - pub hardforks: NetworkHardforks, -} - -impl SigningState { - pub fn new( - index: ChainIndex, - median_timestamp: time::OffsetDateTime, - hardforks: NetworkHardforks, - ) -> Self { - SigningState { - index, - median_timestamp, - hardforks, - } - } - - pub fn replay_prefix(&self) -> &[u8] { - if self.index.height >= self.hardforks.v2_allow_height { - return &[2]; - } else if self.index.height >= self.hardforks.foundation_height { - return &[1]; - } else if self.index.height >= self.hardforks.asic_height { - return &[0]; - } - &[] - } -} - #[cfg(test)] mod tests { - use std::vec; - - use crate::types::v1::{ - CoveredFields, FileContract, FileContractRevision, SiacoinInput, SiafundInput, - StorageProof, Transaction, TransactionSignature, UnlockConditions, - }; - use crate::types::{Address, Currency, FileContractID, Leaf, SiacoinOutput, SiafundOutput}; use super::*; @@ -257,208 +209,4 @@ mod tests { ); assert_eq!(public_key_deserialized, public_key); } - - #[test] - fn test_transaction_sign_verify() { - let unsigned_transaction = Transaction { - siacoin_inputs: vec![SiacoinInput { - parent_id: Default::default(), - unlock_conditions: UnlockConditions { - timelock: 0, - public_keys: vec![], - signatures_required: 0, - }, - }], - siacoin_outputs: vec![SiacoinOutput { - value: Currency::new(0), - address: Address::from([0u8; 32]), - }], - file_contracts: vec![FileContract { - file_size: 0, - file_merkle_root: Default::default(), - window_start: 0, - window_end: 0, - payout: Currency::new(0), - valid_proof_outputs: vec![], - missed_proof_outputs: vec![], - unlock_hash: Hash256::default(), - revision_number: 0, - }], - file_contract_revisions: vec![FileContractRevision { - unlock_conditions: UnlockConditions { - timelock: 0, - public_keys: vec![], - signatures_required: 0, - }, - parent_id: Default::default(), - revision_number: 0, - file_size: 0, - file_merkle_root: Default::default(), - window_start: 0, - window_end: 0, - valid_proof_outputs: vec![], - missed_proof_outputs: vec![], - unlock_hash: Default::default(), - }], - storage_proofs: vec![StorageProof { - parent_id: FileContractID::from([0u8; 32]), - leaf: Leaf::from([0u8; 64]), - proof: vec![], - }], - siafund_inputs: vec![SiafundInput { - parent_id: Default::default(), - unlock_conditions: UnlockConditions { - timelock: 0, - public_keys: vec![], - signatures_required: 0, - }, - claim_address: Address::from([0u8; 32]), - }], - siafund_outputs: vec![SiafundOutput { - value: 0, - address: Address::from([0u8; 32]), - }], - miner_fees: vec![Currency::new(0)], - arbitrary_data: vec![vec![1, 2, 3]], - signatures: vec![TransactionSignature { - parent_id: Default::default(), - public_key_index: 0, - timelock: 0, - covered_fields: Default::default(), - signature: Default::default(), - }], - }; - - let key = PrivateKey::from([ - 114, 152, 250, 154, 63, 214, 160, 97, 24, 74, 157, 172, 159, 191, 32, 141, 56, 178, - 117, 28, 166, 64, 121, 47, 18, 79, 248, 41, 232, 126, 231, 140, 94, 19, 124, 209, 145, - 85, 91, 26, 80, 172, 5, 203, 35, 91, 64, 126, 9, 173, 7, 54, 83, 206, 215, 33, 39, 150, - 60, 53, 203, 125, 192, 147, - ]); - - let mut state = SigningState::new( - ChainIndex { - id: Default::default(), - height: 1, - }, - time::OffsetDateTime::UNIX_EPOCH, // not relevant - NetworkHardforks { - asic_height: 10, - foundation_height: 100, - v2_allow_height: 1000, - v2_require_height: 10000, - }, - ); - - // various test-cases for the individual hardfork heights - struct TestCase { - height: u64, - whole_transaction: bool, - sig_string: String, - } - let test_cases = [ - TestCase { - height: 1, - whole_transaction: true, - sig_string: "eaeeea529878fc861ac77a6e64ea3faea35b0804f655f1f7a6486ddd5b10530621ec7b8fdcf23ad66f7405ab2345d63b570d3855540693977462aa466167c704".to_string(), - }, - TestCase { - height: 10, - whole_transaction: true, - sig_string: "50c2a831c57d71bbb4cdd91c50aa119f755ff52c85b695254df5e4f3fb6619e0b5120d1fb460d149d60d15e4fff81d6b05ba470ab0f9d63dbcd2d8b64b68810d".to_string(), - }, - TestCase { - height: 100, - whole_transaction: true, - sig_string: "edb9e4035e5d194b1cc94aaa1330850cef728fcc92d11d4c0ce8d65d7a6ce81ada8a39e3df3a0052ee452c34a43f2b13217b567a74ac0a30ee84e381854d9406".to_string(), - }, - TestCase { - height: 1000, - whole_transaction: true, - sig_string: "98fe8fb15bd788a6f43cf24f8de3b57556e01d66f87dd2649d7ff8c1e2d48a007468a6965a95ccd3db907128197e1f0e37735593176d610cca859717eac2fd05".to_string(), - }, - TestCase { - height: 10000, - whole_transaction: true, - sig_string: "98fe8fb15bd788a6f43cf24f8de3b57556e01d66f87dd2649d7ff8c1e2d48a007468a6965a95ccd3db907128197e1f0e37735593176d610cca859717eac2fd05".to_string(), - }, - TestCase { - height: 1, - whole_transaction: false, - sig_string: "1d2f0cda9aafbe3ac87b0facf7fdf40c322b7413291fe63c3971f962755fe71c35e638a56eb3a26199a5dbc09244d8a2e4311fc263b34b793772e95e2b663b01".to_string(), - }, - TestCase { - height: 10, - whole_transaction: false, - sig_string: "b0c3b86c36db9200ef6fecd31442652af277aa17859e895d8bf9ce517e93d1765a46eb13790aa6c06fa03df6d2be9032eaa965cccf79223c1fc7e8d4b8e3eb0a".to_string(), - }, - TestCase { - height: 100, - whole_transaction: false, - sig_string: "c6e1f4fee7b9c1a141d6780e61af883a669127f5fa7569a7b69d1b88d434c0336690075b0a5048aba3be2b81751430f0229a3e6e7fcdd3c080ac7435a0c4500d".to_string(), - }, - TestCase { - height: 1000, - whole_transaction: false, - sig_string: "c1547a6f8193329f00dd3409ef3f3d2df961cf606075eef28fe2d87ff446baa3ac7ae65e1773e40c5bef5f1d9452a6ec3d78579dea20b544c5f8f672ee96130f".to_string(), - }, - TestCase { - height: 10000, - whole_transaction: false, - sig_string: "c1547a6f8193329f00dd3409ef3f3d2df961cf606075eef28fe2d87ff446baa3ac7ae65e1773e40c5bef5f1d9452a6ec3d78579dea20b544c5f8f672ee96130f".to_string(), - }, - ]; - - for tc in test_cases { - // update state - state.index.height = tc.height; - - // covered fields are either the whole transaction or all fields - let covered_fields = if tc.whole_transaction { - CoveredFields::whole_transaction() - } else { - CoveredFields { - whole_transaction: false, - siacoin_inputs: vec![0], - siacoin_outputs: vec![0], - file_contracts: vec![0], - file_contract_revisions: vec![0], - storage_proofs: vec![0], - siafund_inputs: vec![0], - siafund_outputs: vec![0], - miner_fees: vec![0], - arbitrary_data: vec![0], - signatures: vec![0], - } - }; - - // sign and check signature - let signature = unsigned_transaction - .sign(&state, &covered_fields, Hash256::default(), 1, 100, &key) - .unwrap(); - assert_eq!( - hex::encode(signature.signature.clone()), - tc.sig_string, - "height: {}", - tc.height - ); - - // manually build the sig_hash and check the signature - let sig_hash = if tc.whole_transaction { - unsigned_transaction - .whole_sig_hash(&state, &Hash256::default(), 1, 100, &Vec::new()) - .unwrap() - } else { - unsigned_transaction - .partial_sig_hash(&state, &covered_fields) - .unwrap() - }; - let sig = Signature::new(signature.signature.try_into().unwrap()); - assert!( - key.public_key().verify(&sig_hash, &sig), - "height: {}", - tc.height - ); - } - } } diff --git a/sia/src/types.rs b/sia/src/types.rs index bb6214e..02da9eb 100644 --- a/sia/src/types.rs +++ b/sia/src/types.rs @@ -2,10 +2,13 @@ mod common; mod currency; mod specifier; mod spendpolicy; // exposed in v2 types +mod work; pub use common::*; pub use currency::*; pub use specifier::*; +pub use work::*; +pub(crate) mod utils; pub mod v1; pub mod v2; diff --git a/sia/src/types/common.rs b/sia/src/types/common.rs index 656d551..92ed8f8 100644 --- a/sia/src/types/common.rs +++ b/sia/src/types/common.rs @@ -2,125 +2,25 @@ use core::fmt; use blake2b_simd::Params; use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; use crate::encoding::{ self, SiaDecodable, SiaDecode, SiaEncodable, SiaEncode, V1SiaDecodable, V1SiaDecode, V1SiaEncodable, V1SiaEncode, }; +use crate::macros::impl_hash_id; use crate::types::currency::Currency; +use crate::types::v1; -// Macro to implement types used as identifiers which are 32 byte hashes and are -// serialized with a prefix -#[macro_export] -macro_rules! ImplHashID { - ($name:ident) => { - #[derive( - Debug, - Clone, - Copy, - PartialEq, - $crate::encoding::SiaEncode, - $crate::encoding::SiaDecode, - $crate::encoding::V1SiaEncode, - $crate::encoding::V1SiaDecode, - )] - pub struct $name([u8; 32]); - - impl serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result { - if serializer.is_human_readable() { - String::serialize(&self.to_string(), serializer) - } else { - <[u8; 32]>::serialize(&self.0, serializer) - } - } - } - - impl<'de> serde::Deserialize<'de> for $name { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - $name::parse_string(&s).map_err(|e| serde::de::Error::custom(format!("{:?}", e))) - } - } - - impl $name { - // Example method that might be used in serialization/deserialization - pub fn parse_string(s: &str) -> Result { - let s = match s.split_once(':') { - Some((_prefix, suffix)) => suffix, - None => s, - }; - - if s.len() != 64 { - return Err($crate::types::HexParseError::InvalidLength); - } - - let mut data = [0u8; 32]; - hex::decode_to_slice(s, &mut data) - .map_err($crate::types::HexParseError::HexError)?; - Ok($name(data)) - } - } - - impl core::fmt::Display for $name { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{}", hex::encode(self.0)) - } - } - - impl From for $name { - fn from(hash: blake2b_simd::Hash) -> Self { - let mut h = [0; 32]; - h.copy_from_slice(&hash.as_bytes()[..32]); - Self(h) - } - } - - impl From<[u8; 32]> for $name { - fn from(data: [u8; 32]) -> Self { - $name(data) - } - } - - impl From<$name> for [u8; 32] { - fn from(hash: $name) -> [u8; 32] { - hash.0 - } - } - - impl AsRef<[u8; 32]> for $name { - fn as_ref(&self) -> &[u8; 32] { - &self.0 - } - } - - impl AsRef<[u8]> for $name { - fn as_ref(&self) -> &[u8] { - &self.0 - } - } - - impl Default for $name { - fn default() -> Self { - $name([0; 32]) - } - } - }; -} - -ImplHashID!(Hash256); -ImplHashID!(BlockID); -ImplHashID!(SiacoinOutputID); -ImplHashID!(SiafundOutputID); -ImplHashID!(FileContractID); -ImplHashID!(TransactionID); -ImplHashID!(AttestationID); +impl_hash_id!(Hash256); +impl_hash_id!(BlockID); +impl_hash_id!(SiacoinOutputID); +impl_hash_id!(SiafundOutputID); +impl_hash_id!(FileContractID); +impl_hash_id!(TransactionID); +impl_hash_id!(AttestationID); #[derive(Debug, PartialEq, SiaEncode, SiaDecode, Serialize, Deserialize)] - pub struct ChainIndex { pub height: u64, pub id: BlockID, @@ -132,6 +32,41 @@ impl fmt::Display for ChainIndex { } } +/// A Block is a collection of transactions +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Block { + #[serde(rename = "parentID")] + pub parent_id: BlockID, + pub nonce: u64, + #[serde(with = "time::serde::rfc3339")] + pub timestamp: OffsetDateTime, + pub miner_payouts: Vec, + pub transactions: Vec, +} + +impl V1SiaEncodable for Block { + fn encode_v1(&self, w: &mut W) -> encoding::Result<()> { + self.parent_id.encode(w)?; + self.nonce.encode(w)?; + self.timestamp.encode(w)?; + self.miner_payouts.encode_v1(w)?; + self.transactions.encode_v1(w) + } +} + +impl V1SiaDecodable for Block { + fn decode_v1(r: &mut R) -> encoding::Result { + Ok(Block { + parent_id: BlockID::decode(r)?, + nonce: u64::decode(r)?, + timestamp: OffsetDateTime::decode(r)?, + miner_payouts: Vec::::decode_v1(r)?, + transactions: Vec::::decode_v1(r)?, + }) + } +} + /// encapsulates the various errors that can occur when parsing a Sia object /// from a string #[derive(Debug, PartialEq)] @@ -144,7 +79,7 @@ pub enum HexParseError { } /// An address that can be used to receive UTXOs -#[derive(Debug, PartialEq, Clone, SiaEncode, V1SiaEncode, SiaDecode, V1SiaDecode)] +#[derive(Default, Debug, PartialEq, Clone, SiaEncode, V1SiaEncode, SiaDecode, V1SiaDecode)] pub struct Address([u8; 32]); impl<'de> Deserialize<'de> for Address { @@ -167,7 +102,7 @@ impl Serialize for Address { } impl Address { - pub fn new(addr: [u8; 32]) -> Address { + pub const fn new(addr: [u8; 32]) -> Address { Address(addr) } @@ -230,6 +165,8 @@ impl fmt::Display for Address { } } +/// A SiacoinOutput is a Siacoin UTXO that can be spent using the unlock conditions +/// for Address #[derive( Debug, PartialEq, Serialize, Deserialize, SiaEncode, SiaDecode, V1SiaEncode, V1SiaDecode, )] @@ -239,6 +176,8 @@ pub struct SiacoinOutput { pub address: Address, } +/// A SiafundOutput is a Siafund UTXO that can be spent using the unlock conditions +/// for Address #[derive(Debug, PartialEq, Serialize, Deserialize, SiaEncode, SiaDecode)] #[serde(rename_all = "camelCase")] pub struct SiafundOutput { @@ -267,9 +206,22 @@ impl V1SiaDecodable for SiafundOutput { } } +/// A Leaf is a 64-byte piece of data that is stored in a Merkle tree. #[derive(Debug, PartialEq, Clone, SiaEncode, V1SiaEncode, SiaDecode, V1SiaDecode)] pub struct Leaf([u8; 64]); +impl Leaf { + pub fn parse_string(s: &str) -> Result { + if s.len() != 128 { + return Err(HexParseError::InvalidLength); + } + + let mut data = [0u8; 64]; + hex::decode_to_slice(s, &mut data).map_err(HexParseError::HexError)?; + Ok(Leaf(data)) + } +} + impl From<[u8; 64]> for Leaf { fn from(data: [u8; 64]) -> Self { Leaf(data) @@ -312,6 +264,8 @@ pub struct StateElement { #[cfg(test)] mod tests { + use crate::{address, block_id, public_key, siacoin_id}; + use super::*; #[test] @@ -337,7 +291,9 @@ mod tests { fn test_serialize_address() { let addr_str = "8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"; let checksum = "df32abee86f0"; - let address = Address(hex::decode(addr_str).unwrap().try_into().unwrap()); + let address = address!( + "8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8cdf32abee86f0" + ); // binary let mut addr_serialized: Vec = Vec::new(); @@ -352,4 +308,56 @@ mod tests { assert_eq!(addr_serialized, format!("\"{0}{1}\"", addr_str, checksum)); // serialize assert_eq!(addr_deserialized, address); // deserialize } + + #[test] + fn test_serialize_block() { + let b = Block{ + parent_id: block_id!("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"), + nonce: 1236112, + timestamp: OffsetDateTime::UNIX_EPOCH, + miner_payouts: vec![ + SiacoinOutput{ + value: Currency::new(57234234623612361), + address: address!("000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69"), + } + ], + transactions: vec![ + v1::Transaction { + siacoin_inputs: vec![ + v1::SiacoinInput{ + parent_id: siacoin_id!("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c"), + unlock_conditions: v1::UnlockConditions::standard_unlock_conditions(public_key!("ed25519:8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c")), + } + ], + siacoin_outputs: vec![ + SiacoinOutput{ + value: Currency::new(67856467336433871), + address: address!("000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69"), + } + ], + file_contracts: Vec::new(), + file_contract_revisions: Vec::new(), + storage_proofs: Vec::new(), + siafund_inputs: Vec::new(), + siafund_outputs: Vec::new(), + miner_fees: Vec::new(), + arbitrary_data: Vec::new(), + signatures: Vec::new(), + }, + ], + }; + + const BINARY_STR: &'static str = "8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c90dc120000000000000000000000000001000000000000000700000000000000cb563bafbb55c90000000000000000000000000000000000000000000000000000000000000000010000000000000001000000000000008fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c000000000000000001000000000000006564323535313900000000000000000020000000000000008fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c010000000000000001000000000000000700000000000000f11318f74d10cf000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + let mut serialized = Vec::new(); + b.encode_v1(&mut serialized).unwrap(); + assert_eq!(serialized, hex::decode(BINARY_STR).unwrap()); + let deserialized = Block::decode_v1(&mut &serialized[..]).unwrap(); + assert_eq!(deserialized, b); + + const JSON_STR: &'static str = "{\"parentID\":\"8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\",\"nonce\":1236112,\"timestamp\":\"1970-01-01T00:00:00Z\",\"minerPayouts\":[{\"value\":\"57234234623612361\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"}],\"transactions\":[{\"siacoinInputs\":[{\"parentID\":\"8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\",\"unlockConditions\":{\"timelock\":0,\"publicKeys\":[\"ed25519:8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c\"],\"signaturesRequired\":1}}],\"siacoinOutputs\":[{\"value\":\"67856467336433871\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"}]}]}"; + let serialized = serde_json::to_string(&b).unwrap(); + assert_eq!(serialized, JSON_STR); + let deserialized: Block = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized, b); + } } diff --git a/sia/src/types/currency.rs b/sia/src/types/currency.rs index eb24716..c474c86 100644 --- a/sia/src/types/currency.rs +++ b/sia/src/types/currency.rs @@ -1,5 +1,5 @@ use core::num::ParseIntError; -use core::ops::{Add, Deref, DerefMut, Div, Mul, Sub}; +use core::ops::{Add, Deref, DerefMut, Div, Mul, Rem, Sub}; use std::io::Write; use std::iter::Sum; @@ -11,8 +11,10 @@ use crate::encoding::{self, SiaDecodable, SiaEncodable, V1SiaDecodable, V1SiaEnc const SIACOIN_PRECISION_I32: i32 = 24; const SIACOIN_PRECISION_U32: u32 = 24; +pub const ZERO_SC: Currency = Currency(0); + // Currency represents a quantity of Siacoins as Hastings. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Currency(u128); // TODO: To distinguish between v1 and v2 currencies we could make Currency an @@ -104,7 +106,7 @@ where } impl Currency { - pub fn new(value: u128) -> Self { + pub const fn new(value: u128) -> Self { Currency(value) } @@ -173,7 +175,7 @@ impl Currency { /// # Returns /// /// Returns a `Currency` instance representing the specified amount of Siacoins. - pub fn siacoins(n: u64) -> Self { + pub const fn siacoins(n: u64) -> Self { Currency::new((n as u128) * 10u128.pow(SIACOIN_PRECISION_U32)) } @@ -236,6 +238,14 @@ impl Sum for Currency { } } +impl Rem for Currency { + type Output = Self; + + fn rem(self, rhs: Self) -> Self::Output { + Self(self.0 % rhs.0) + } +} + #[derive(Debug, PartialEq)] pub enum CurrencyParseError { ParseIntErr(ParseIntError), diff --git a/sia/src/types/spendpolicy.rs b/sia/src/types/spendpolicy.rs index 4f0d738..0a76172 100644 --- a/sia/src/types/spendpolicy.rs +++ b/sia/src/types/spendpolicy.rs @@ -391,23 +391,16 @@ impl SiaDecodable for SpendPolicy { /// A policy that has been satisfied by a set of preimages and signatures. pub struct SatisfiedPolicy { pub policy: SpendPolicy, - pub preimages: Vec>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub signatures: Vec, -} - -impl SatisfiedPolicy { - /// Create a new satisfied policy from a policy, preimages, and signatures. - pub fn new(policy: SpendPolicy, preimages: Vec>, signatures: Vec) -> Self { - Self { - policy, - preimages, - signatures, - } - } + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub preimages: Vec, } #[cfg(test)] mod tests { + use crate::{hash_256, public_key}; + use super::*; #[test] @@ -621,4 +614,81 @@ mod tests { assert_eq!(deserialized_binary, *policy, "test case {}", i); } } + + #[test] + fn test_satisfied_policy_encoding() { + struct TestCase { + policy: SatisfiedPolicy, + json: &'static str, + binary: &'static str, + } + let test_cases = vec![ + TestCase{ + policy: SatisfiedPolicy{ + policy: SpendPolicy::public_key(public_key!("ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29")), + signatures: vec![Signature::parse_string("867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c").unwrap()], + preimages: vec![], + }, + json: "{\"policy\":{\"type\":\"pk\",\"policy\":\"ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29\"},\"signatures\":[\"867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c\"]}", + binary: "01033b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da290100000000000000867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c0000000000000000", + }, + TestCase{ + policy: SatisfiedPolicy{ + policy: SpendPolicy::threshold(1, vec![ + SpendPolicy::public_key(public_key!("ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29")), + SpendPolicy::hash(hash_256!("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8")), + ]), + signatures: vec![Signature::parse_string("867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c").unwrap()], + preimages: vec![hash_256!("0102030000000000000000000000000000000000000000000000000000000000")], + }, + json: "{\"policy\":{\"type\":\"thresh\",\"policy\":{\"n\":1,\"of\":[{\"policy\":\"ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29\",\"type\":\"pk\"},{\"policy\":\"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8\",\"type\":\"h\"}]}},\"signatures\":[\"867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c\"],\"preimages\":[\"0102030000000000000000000000000000000000000000000000000000000000\"]}", + binary: "01050102033b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29040e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a80100000000000000867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c01000000000000000102030000000000000000000000000000000000000000000000000000000000", + }, + TestCase{ + policy: SatisfiedPolicy{ + policy: SpendPolicy::hash(hash_256!("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8")), + signatures: vec![], + preimages: vec![hash_256!("0405060000000000000000000000000000000000000000000000000000000000")], + }, + json: "{\"policy\":{\"type\":\"h\",\"policy\":\"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8\"},\"preimages\":[\"0405060000000000000000000000000000000000000000000000000000000000\"]}", + binary: "01040e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8000000000000000001000000000000000405060000000000000000000000000000000000000000000000000000000000", + }, + TestCase{ + policy: SatisfiedPolicy{ + policy: SpendPolicy::public_key(public_key!("ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29")), + signatures: vec![], + preimages: vec![], + }, + json: "{\"policy\":{\"type\":\"pk\",\"policy\":\"ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29\"}}", + binary: "01033b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da2900000000000000000000000000000000", + }, + TestCase{ + policy: SatisfiedPolicy{ + policy: SpendPolicy::hash(hash_256!("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8")), + signatures: vec![], + preimages: vec![], + }, + json: "{\"policy\":{\"type\":\"h\",\"policy\":\"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8\"}}", + binary: "01040e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a800000000000000000000000000000000", + } + ]; + + for tc in test_cases { + let mut serialized = Vec::new(); + tc.policy.encode(&mut serialized).unwrap(); + assert_eq!( + hex::encode(serialized.clone()), + tc.binary, + "binary serialization failed" + ); + let deserialized = SatisfiedPolicy::decode(&mut &serialized[..]).unwrap(); + assert_eq!(deserialized, tc.policy, "binary deserialization failed"); + + // json + let serialized = serde_json::to_string(&tc.policy).unwrap(); + assert_eq!(serialized, tc.json, "json serialization failed"); + let deserialized: SatisfiedPolicy = serde_json::from_str(&tc.json).unwrap(); + assert_eq!(deserialized, tc.policy, "json deserialization failed"); + } + } } diff --git a/sia/src/types/utils.rs b/sia/src/types/utils.rs new file mode 100644 index 0000000..39e1c83 --- /dev/null +++ b/sia/src/types/utils.rs @@ -0,0 +1,118 @@ +/// helper module for base64 serialization +pub(crate) mod base64 { + use base64::engine::general_purpose::STANDARD; + use base64::Engine; + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(v: &[u8], s: S) -> Result { + let base64 = STANDARD.encode(v); + s.serialize_str(&base64) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { + let base64 = String::deserialize(d)?; + STANDARD + .decode(base64.as_bytes()) + .map_err(|e| serde::de::Error::custom(e.to_string())) + } +} + +pub(crate) mod timestamp_array { + use core::fmt; + + use serde::de::{SeqAccess, Visitor}; + use serde::{Deserializer, Serializer}; + use time::OffsetDateTime; + + pub fn serialize(timestamps: &[OffsetDateTime; 11], serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::SerializeSeq; + let mut seq = serializer.serialize_seq(Some(11))?; + for timestamp in timestamps { + let rfc3339_string = timestamp + .format(&time::format_description::well_known::Rfc3339) + .map_err(serde::ser::Error::custom)?; + seq.serialize_element(&rfc3339_string)?; + } + seq.end() + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<[OffsetDateTime; 11], D::Error> + where + D: Deserializer<'de>, + { + struct TimestampVisitor; + + impl<'de> Visitor<'de> for TimestampVisitor { + type Value = [OffsetDateTime; 11]; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an array of 11 RC3339 timestamps") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut timestamps = [OffsetDateTime::UNIX_EPOCH; 11]; + + let mut idx = 0; + while let Some(value) = seq.next_element::()? { + if idx >= 11 { + return Err(serde::de::Error::custom("too many timestamps")); + } + timestamps[idx] = OffsetDateTime::parse( + &value, + &time::format_description::well_known::Rfc3339, + ) + .map_err(serde::de::Error::custom)?; + idx += 1; + } + Ok(timestamps) + } + } + deserializer.deserialize_seq(TimestampVisitor) + } +} + +/// helper module for Vec> base64 serialization +pub(crate) mod vec_base64 { + use base64::engine::general_purpose::STANDARD; + use base64::Engine as _; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(v: &[Vec], serializer: S) -> Result + where + S: Serializer, + { + let encoded: Vec = v.iter().map(|bytes| STANDARD.encode(bytes)).collect(); + encoded.serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + let encoded: Vec = Vec::deserialize(deserializer)?; + encoded + .into_iter() + .map(|s| STANDARD.decode(s).map_err(serde::de::Error::custom)) + .collect() + } +} + +pub(crate) mod nano_second_duration { + use serde::{Deserialize, Deserializer, Serializer}; + use time::Duration; + + pub fn serialize(v: &Duration, s: S) -> Result { + s.serialize_u64(v.whole_nanoseconds() as u64) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + let nanos = u64::deserialize(d)?; + Ok(Duration::nanoseconds(nanos as i64)) + } +} diff --git a/sia/src/types/v1.rs b/sia/src/types/v1.rs index 2e66f49..f2aef17 100644 --- a/sia/src/types/v1.rs +++ b/sia/src/types/v1.rs @@ -1,15 +1,20 @@ use core::fmt; +use std::ops::Deref; use blake2b_simd::Params; +use num_bigint::BigInt; +use num_rational::Ratio; +use num_traits::{FromPrimitive, ToPrimitive}; use serde::de::Error; use serde::{Deserialize, Serialize}; +use crate::consensus::ChainState; use crate::encoding::{ self, SiaDecodable, SiaDecode, SiaEncodable, SiaEncode, V1SiaDecodable, V1SiaDecode, V1SiaEncodable, V1SiaEncode, }; use crate::merkle::{Accumulator, LEAF_HASH_PREFIX}; -use crate::signing::{PrivateKey, PublicKey, SigningState}; +use crate::signing::{PrivateKey, PublicKey}; use crate::types::{specifier, Specifier}; use super::currency::Currency; @@ -179,10 +184,23 @@ pub struct FileContract { pub payout: Currency, pub valid_proof_outputs: Vec, pub missed_proof_outputs: Vec, - pub unlock_hash: Hash256, + pub unlock_hash: Address, pub revision_number: u64, } +impl FileContract { + pub fn tax(&self, cs: &ChainState) -> Currency { + let payout = BigInt::from_u128(*self.payout.deref()).unwrap(); + let tax = if cs.child_height() < cs.network.hardfork_tax.height { + let tax = Ratio::from(payout) * Ratio::from_float(0.039).unwrap(); + tax.numer() / tax.denom() + } else { + payout * 39 / 1000 + }; + Currency::new((&tax - &tax % cs.siafund_count()).to_u128().unwrap()) + } +} + #[derive(Debug, PartialEq, Serialize, Deserialize, V1SiaEncode, V1SiaDecode)] #[serde(rename_all = "camelCase")] pub struct FileContractRevision { @@ -197,7 +215,7 @@ pub struct FileContractRevision { pub window_end: u64, pub valid_proof_outputs: Vec, pub missed_proof_outputs: Vec, - pub unlock_hash: Hash256, + pub unlock_hash: Address, } #[derive(Debug, PartialEq, Serialize, Deserialize, V1SiaEncode, V1SiaDecode)] @@ -213,15 +231,25 @@ pub struct StorageProof { #[serde(rename_all = "camelCase")] pub struct CoveredFields { pub whole_transaction: bool, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siacoin_inputs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siacoin_outputs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub file_contracts: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub file_contract_revisions: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub storage_proofs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siafund_inputs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siafund_outputs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub miner_fees: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub arbitrary_data: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub signatures: Vec, } @@ -242,41 +270,36 @@ pub struct TransactionSignature { pub public_key_index: u64, pub timelock: u64, pub covered_fields: CoveredFields, - #[serde(with = "base64")] + #[serde(with = "crate::types::utils::base64")] pub signature: Vec, } -/// Helper module for base64 serialization -mod base64 { - use base64::engine::general_purpose::STANDARD; - use base64::Engine; - use serde::{Deserialize, Deserializer, Serializer}; - - pub fn serialize(v: &[u8], s: S) -> Result { - let base64 = STANDARD.encode(v); - s.serialize_str(&base64) - } - - pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { - let base64 = String::deserialize(d)?; - STANDARD - .decode(base64.as_bytes()) - .map_err(|e| serde::de::Error::custom(e.to_string())) - } -} - #[derive(Default, Debug, PartialEq, Serialize, Deserialize, V1SiaEncode, V1SiaDecode)] #[serde(rename_all = "camelCase")] pub struct Transaction { + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siacoin_inputs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siacoin_outputs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub file_contracts: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub file_contract_revisions: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub storage_proofs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siafund_inputs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siafund_outputs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub miner_fees: Vec, + #[serde( + default, + skip_serializing_if = "Vec::is_empty", + with = "crate::types::utils::vec_base64" + )] pub arbitrary_data: Vec>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub signatures: Vec, } @@ -298,7 +321,7 @@ impl Transaction { pub(crate) fn whole_sig_hash( &self, - chain: &SigningState, + cs: &ChainState, parent_id: &Hash256, public_key_index: u64, timelock: u64, @@ -308,7 +331,7 @@ impl Transaction { state.update(&(self.siacoin_inputs.len() as u64).to_le_bytes()); for input in self.siacoin_inputs.iter() { - state.update(chain.replay_prefix()); + state.update(cs.replay_prefix()); input.encode_v1(&mut state)?; } @@ -319,7 +342,7 @@ impl Transaction { state.update(&(self.siafund_inputs.len() as u64).to_le_bytes()); for input in self.siafund_inputs.iter() { - state.update(chain.replay_prefix()); + state.update(cs.replay_prefix()); input.encode_v1(&mut state)?; } @@ -345,7 +368,7 @@ impl Transaction { pub(crate) fn partial_sig_hash( &self, - chain: &SigningState, + cs: &ChainState, covered_fields: &CoveredFields, ) -> Result { let mut state = Params::new().hash_length(32).to_state(); @@ -356,7 +379,7 @@ impl Transaction { "siacoin_inputs index out of bounds".to_string(), )); } - state.update(chain.replay_prefix()); + state.update(cs.replay_prefix()); self.siacoin_inputs[i].encode_v1(&mut state)?; } @@ -402,7 +425,7 @@ impl Transaction { "siafund_inputs index out of bounds".to_string(), )); } - state.update(chain.replay_prefix()); + state.update(cs.replay_prefix()); self.siafund_inputs[i].encode_v1(&mut state)?; } @@ -446,7 +469,7 @@ impl Transaction { pub fn sign( &self, - state: &SigningState, + cs: &ChainState, covered_fields: &CoveredFields, parent_id: Hash256, public_key_index: u64, @@ -455,14 +478,14 @@ impl Transaction { ) -> Result { let sig_hash = if covered_fields.whole_transaction { self.whole_sig_hash( - state, + cs, &parent_id, public_key_index, timelock, &covered_fields.signatures, ) } else { - self.partial_sig_hash(state, covered_fields) + self.partial_sig_hash(cs, covered_fields) }?; Ok(TransactionSignature { @@ -505,12 +528,18 @@ impl Transaction { #[cfg(test)] mod tests { use super::*; + use crate::consensus::{ + Elements, HardforkASIC, HardforkDevAddr, HardforkFoundation, HardforkOak, + HardforkStorageProof, HardforkTax, HardforkV2, Network, State, + }; use crate::seed::Seed; - use crate::signing::{NetworkHardforks, PrivateKey, PublicKey, Signature}; - use crate::types::{BlockID, ChainIndex}; + use crate::signing::{PrivateKey, PublicKey, Signature}; + use crate::types::{BlockID, ChainIndex, Work}; + use crate::{address, contract_id, hash_256, siacoin_id, siafund_id}; use serde::de::DeserializeOwned; use std::fmt::Debug; use std::vec; + use time::{Duration, OffsetDateTime}; /// test_serialize_json is a helper to test serialization and deserialization of a struct to and from JSON. fn test_serialize_json( @@ -618,17 +647,17 @@ mod tests { let binary_str = "000100000000000000010000000000000002000000000000000200000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; test_serialize_v1(&cf, binary_str); - let json_str = "{\"wholeTransaction\":false,\"siacoinInputs\":[1],\"siacoinOutputs\":[2,3],\"fileContracts\":[],\"fileContractRevisions\":[],\"storageProofs\":[],\"siafundInputs\":[],\"siafundOutputs\":[],\"minerFees\":[],\"arbitraryData\":[],\"signatures\":[]}"; + let json_str = + "{\"wholeTransaction\":false,\"siacoinInputs\":[1],\"siacoinOutputs\":[2,3]}"; test_serialize_json(&cf, json_str); } #[test] fn test_serialize_siacoin_input() { let siacoin_input = SiacoinInput { - parent_id: SiacoinOutputID::parse_string( - "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24", - ) - .unwrap(), + parent_id: siacoin_id!( + "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24" + ), unlock_conditions: UnlockConditions::new( 123, vec![PublicKey::new([ @@ -658,10 +687,9 @@ mod tests { #[test] fn test_serialize_siafund_input() { let siafund_input = SiafundInput { - parent_id: SiafundOutputID::parse_string( - "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24", - ) - .unwrap(), + parent_id: siafund_id!( + "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24" + ), unlock_conditions: UnlockConditions::new( 123, vec![PublicKey::new([ @@ -699,10 +727,9 @@ mod tests { #[test] fn test_serialize_transaction_signature() { let signature = TransactionSignature { - parent_id: Hash256::parse_string( - "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24", - ) - .unwrap(), + parent_id: hash_256!( + "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24" + ), public_key_index: 1, timelock: 2, covered_fields: CoveredFields { @@ -724,7 +751,7 @@ mod tests { ]); test_serialize_v1(&signature, binary_str.as_str()); - let json_str = "{\"parentID\":\"b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24\",\"publicKeyIndex\":1,\"timelock\":2,\"coveredFields\":{\"wholeTransaction\":true,\"siacoinInputs\":[],\"siacoinOutputs\":[],\"fileContracts\":[],\"fileContractRevisions\":[],\"storageProofs\":[],\"siafundInputs\":[],\"siafundOutputs\":[],\"minerFees\":[],\"arbitraryData\":[],\"signatures\":[]},\"signature\":\"AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw==\"}"; + let json_str = "{\"parentID\":\"b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24\",\"publicKeyIndex\":1,\"timelock\":2,\"coveredFields\":{\"wholeTransaction\":true},\"signature\":\"AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw==\"}"; test_serialize_json(&signature, json_str); } @@ -753,7 +780,7 @@ mod tests { 0, 0, 0, 0, 0, 0, ]), }], - unlock_hash: Hash256::from([ + unlock_hash: Address::from([ 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), @@ -772,7 +799,7 @@ mod tests { ]); test_serialize_v1(&contract, binary_str.as_str()); - let json_str = "{\"filesize\":1,\"fileMerkleRoot\":\"0101010000000000000000000000000000000000000000000000000000000000\",\"windowStart\":2,\"windowEnd\":3,\"payout\":\"456\",\"validProofOutputs\":[{\"value\":\"789\",\"address\":\"02020200000000000000000000000000000000000000000000000000000000008749787b31db\"}],\"missedProofOutputs\":[{\"value\":\"101112\",\"address\":\"0303030000000000000000000000000000000000000000000000000000000000c596d559a239\"}],\"unlockHash\":\"0404040000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":4}"; + let json_str = "{\"filesize\":1,\"fileMerkleRoot\":\"0101010000000000000000000000000000000000000000000000000000000000\",\"windowStart\":2,\"windowEnd\":3,\"payout\":\"456\",\"validProofOutputs\":[{\"value\":\"789\",\"address\":\"02020200000000000000000000000000000000000000000000000000000000008749787b31db\"}],\"missedProofOutputs\":[{\"value\":\"101112\",\"address\":\"0303030000000000000000000000000000000000000000000000000000000000c596d559a239\"}],\"unlockHash\":\"04040400000000000000000000000000000000000000000000000000000000006c604f10c928\",\"revisionNumber\":4}"; test_serialize_json(&contract, json_str); } @@ -814,7 +841,7 @@ mod tests { .into()], 1, ), - unlock_hash: Hash256::from([ + unlock_hash: Address::from([ 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), @@ -837,31 +864,24 @@ mod tests { ]); test_serialize_v1(&revision, binary_str.as_str()); - let json_str = "{\"parentID\":\"0908070000000000000000000000000000000000000000000000000000000000\",\"unlockConditions\":{\"timelock\":123,\"publicKeys\":[\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"],\"signaturesRequired\":1},\"revisionNumber\":4,\"filesize\":1,\"fileMerkleRoot\":\"0101010000000000000000000000000000000000000000000000000000000000\",\"windowStart\":2,\"windowEnd\":3,\"validProofOutputs\":[{\"value\":\"789\",\"address\":\"02020200000000000000000000000000000000000000000000000000000000008749787b31db\"}],\"missedProofOutputs\":[{\"value\":\"789\",\"address\":\"0303030000000000000000000000000000000000000000000000000000000000c596d559a239\"}],\"unlockHash\":\"0404040000000000000000000000000000000000000000000000000000000000\"}"; + let json_str = "{\"parentID\":\"0908070000000000000000000000000000000000000000000000000000000000\",\"unlockConditions\":{\"timelock\":123,\"publicKeys\":[\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"],\"signaturesRequired\":1},\"revisionNumber\":4,\"filesize\":1,\"fileMerkleRoot\":\"0101010000000000000000000000000000000000000000000000000000000000\",\"windowStart\":2,\"windowEnd\":3,\"validProofOutputs\":[{\"value\":\"789\",\"address\":\"02020200000000000000000000000000000000000000000000000000000000008749787b31db\"}],\"missedProofOutputs\":[{\"value\":\"789\",\"address\":\"0303030000000000000000000000000000000000000000000000000000000000c596d559a239\"}],\"unlockHash\":\"04040400000000000000000000000000000000000000000000000000000000006c604f10c928\"}"; test_serialize_json(&revision, json_str); } #[test] fn test_serialize_storage_proof() { let storage_proof = StorageProof { - parent_id: FileContractID::parse_string( - "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24", - ) - .unwrap(), + parent_id: contract_id!( + "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24" + ), leaf: Leaf::from([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, ]), proof: vec![ - Hash256::parse_string( - "0102030000000000000000000000000000000000000000000000000000000000", - ) - .unwrap(), - Hash256::parse_string( - "0405060000000000000000000000000000000000000000000000000000000000", - ) - .unwrap(), + hash_256!("0102030000000000000000000000000000000000000000000000000000000000"), + hash_256!("0405060000000000000000000000000000000000000000000000000000000000"), ], }; @@ -883,25 +903,103 @@ mod tests { #[test] fn test_serialize_transaction() { let transaction = Transaction { - siacoin_inputs: Vec::new(), - siacoin_outputs: Vec::new(), - file_contracts: Vec::new(), - file_contract_revisions: Vec::new(), - storage_proofs: Vec::new(), + siacoin_inputs: vec![SiacoinInput { + parent_id: siacoin_id!( + "750d22eff727689d1d8d1c83e513a30bb68ee7f9125a4dafc882459e34c2069d" + ), + unlock_conditions: UnlockConditions { + timelock: 0, + public_keys: vec![UnlockKey::parse_string( + "ed25519:800ed6c2760e3e4ba1ff00128585c8cf8fed2e3dc1e3da1eb92d49f405bd6360", + ) + .unwrap()], + signatures_required: 6312611591377486220, + }, + }], + siacoin_outputs: vec![SiacoinOutput { + value: Currency::new(890415399000000000000000000000000), + address: address!("480a064b5fca13002a7fe575845154bbf0b3af4cc4f147cbed387d43cce3568ae2497366eaa7"), + }], + file_contracts: vec![ + FileContract{ + file_size: 0, + file_merkle_root: Hash256::default(), + window_start: 10536451586783908586, + window_end: 9324702155635244357, + payout: Currency::new(0), + unlock_hash: Address::default(), + revision_number: 9657412421282982780, + valid_proof_outputs: vec![ + SiacoinOutput{ + value: Currency::new(1933513214000000000000000000000000), + address: address!("944524fff2c49c401e748db37cfda7569fa6df35b704fe716394f2ac3f40ce87b4506e9906f0"), + } + ], + missed_proof_outputs: vec![ + SiacoinOutput{ + value: Currency::new(2469287901000000000000000000000000), + address: address!("1df67838262d7109ffcd9018f183b1eb33f05659a274b89ea6b52ff3617d34a770e9dd071d2e"), + } + ] + } + ], + file_contract_revisions: vec![ + FileContractRevision{ + parent_id: contract_id!( + "e4e26d93771d3bbb3d9dd306105d77cfb3a6254d1cc3495903af6e013442c63c"), + unlock_conditions: UnlockConditions { timelock: 0, public_keys: vec![UnlockKey::parse_string("ed25519:e6b9cde4eb058f8ecbb083d99779cb0f6d518d5386f019af6ead09fa52de8567").unwrap()], signatures_required: 206644730660526450 }, + revision_number: 10595710523108536025, + file_size: 0, + file_merkle_root: Hash256::default(), + window_start: 4348934140507359445, + window_end: 14012366839994454386, + valid_proof_outputs: vec![ + SiacoinOutput{ + value: Currency::new(2435858510000000000000000000000000), + address: address!("543bc0eda69f728d0a0fbce08e5bfc5ed7b961300e0af226949e135f7d12e32f0544e5262d6f"), + } + ], + missed_proof_outputs: vec![ + SiacoinOutput{ + value: Currency::new(880343701000000000000000000000000), + address: address!("7b7f9aee981fe0d93bb3f49c6233cf847ebdd39d7dc5253f7fc330df2167073b35f035703237"), + }, + ], + unlock_hash: Address::default(), + } + ], + storage_proofs: vec![ + StorageProof{ + parent_id: contract_id!( + "c0b9e98c9e03a2740c75d673871c1ee91f36d1bb329ff3ddbf1dfa8c6e1a64eb"), + leaf: Leaf::parse_string("b78fa521dc62d9ced82bc3b61e0aa5a5c221d6cca5db63d94c9879543fb98c0a971094a89cd4408487ae32902248d321b545f9a051729aa0bb1725b848e3d453").unwrap(), + proof: vec![ + hash_256!("fe08c0a061475e7e5dec19e717cf98792fa7b555d0b5d3540a05db09f59ab8de"), + ], + } + ], siafund_inputs: Vec::new(), siafund_outputs: Vec::new(), - miner_fees: Vec::new(), - arbitrary_data: Vec::new(), - signatures: Vec::new(), + miner_fees: vec![ + Currency::new(241119475000000000000000000000000), + ], + arbitrary_data: vec![ + vec![218,200,115,32,113,20,37,140,46,52,124,250,115,248,15,207,230,147,17,102,81,78,144,211,153,233,151,247,38,0,42,86] + ], + signatures: vec![ + TransactionSignature{ + parent_id: hash_256!("06d1fca03c5ddd9b09116db1b97c5451f7dc792b05362969f83e3e8dc1007f46"), + public_key_index: 6088345341283457116, + timelock: 2014247885072555224, + covered_fields: CoveredFields::whole_transaction(), + signature: vec![217,115,68,40,102,107,151,212,97,49,173,137,152,107,47,114,106,144,88,2,23,254,236,109,51,2,231,60,142,149,36,247,23,170,230,250,169,130,46,160,4,225,98,159,77,3,6,157,147,237,70,27,160,151,11,174,160,226,179,163,21,139,141,72], + }, + ], }; - let binary_str = hex::encode([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]); - test_serialize_v1(&transaction, binary_str.as_str()); + let binary_str = "0100000000000000750d22eff727689d1d8d1c83e513a30bb68ee7f9125a4dafc882459e34c2069d00000000000000000100000000000000656432353531390000000000000000002000000000000000800ed6c2760e3e4ba1ff00128585c8cf8fed2e3dc1e3da1eb92d49f405bd63608c8111f5cbe69a5701000000000000000e000000000000002be69f532be55cd4697c87000000480a064b5fca13002a7fe575845154bbf0b3af4cc4f147cbed387d43cce3568a010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ea125bc7e4fe3892453df5c634ff6781000000000000000001000000000000000e000000000000005f545e24bc638098ba76be000000944524fff2c49c401e748db37cfda7569fa6df35b704fe716394f2ac3f40ce8701000000000000000e0000000000000079becb8684da8f5198dafd0000001df67838262d7109ffcd9018f183b1eb33f05659a274b89ea6b52ff3617d34a700000000000000000000000000000000000000000000000000000000000000007c6b04146e0506860100000000000000e4e26d93771d3bbb3d9dd306105d77cfb3a6254d1cc3495903af6e013442c63c00000000000000000100000000000000656432353531390000000000000000002000000000000000e6b9cde4eb058f8ecbb083d99779cb0f6d518d5386f019af6ead09fa52de85677219baa54926de02d9aec01095860b9300000000000000000000000000000000000000000000000000000000000000000000000000000000d58882248f845a3c7211557866ed75c201000000000000000e000000000000007818db5664f130fd08a70e000000543bc0eda69f728d0a0fbce08e5bfc5ed7b961300e0af226949e135f7d12e32f01000000000000000e000000000000002b677fe9d9b27794cd88b50000007b7f9aee981fe0d93bb3f49c6233cf847ebdd39d7dc5253f7fc330df2167073b00000000000000000000000000000000000000000000000000000000000000000100000000000000c0b9e98c9e03a2740c75d673871c1ee91f36d1bb329ff3ddbf1dfa8c6e1a64ebb78fa521dc62d9ced82bc3b61e0aa5a5c221d6cca5db63d94c9879543fb98c0a971094a89cd4408487ae32902248d321b545f9a051729aa0bb1725b848e3d4530100000000000000fe08c0a061475e7e5dec19e717cf98792fa7b555d0b5d3540a05db09f59ab8de0000000000000000000000000000000001000000000000000e000000000000000be35b0b076a36b5d2bfd300000001000000000000002000000000000000dac873207114258c2e347cfa73f80fcfe6931166514e90d399e997f726002a56010000000000000006d1fca03c5ddd9b09116db1b97c5451f7dc792b05362969f83e3e8dc1007f465cd81a99d4257e54d8dc548fc70bf41b0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000d9734428666b97d46131ad89986b2f726a90580217feec6d3302e73c8e9524f717aae6faa9822ea004e1629f4d03069d93ed461ba0970baea0e2b3a3158b8d48"; + test_serialize_v1(&transaction, binary_str); - let json_str = "{\"siacoinInputs\":[],\"siacoinOutputs\":[],\"fileContracts\":[],\"fileContractRevisions\":[],\"storageProofs\":[],\"siafundInputs\":[],\"siafundOutputs\":[],\"minerFees\":[],\"arbitraryData\":[],\"signatures\":[]}"; + let json_str = "{\"siacoinInputs\":[{\"parentID\":\"750d22eff727689d1d8d1c83e513a30bb68ee7f9125a4dafc882459e34c2069d\",\"unlockConditions\":{\"timelock\":0,\"publicKeys\":[\"ed25519:800ed6c2760e3e4ba1ff00128585c8cf8fed2e3dc1e3da1eb92d49f405bd6360\"],\"signaturesRequired\":6312611591377486220}}],\"siacoinOutputs\":[{\"value\":\"890415399000000000000000000000000\",\"address\":\"480a064b5fca13002a7fe575845154bbf0b3af4cc4f147cbed387d43cce3568ae2497366eaa7\"}],\"fileContracts\":[{\"filesize\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"windowStart\":10536451586783908586,\"windowEnd\":9324702155635244357,\"payout\":\"0\",\"validProofOutputs\":[{\"value\":\"1933513214000000000000000000000000\",\"address\":\"944524fff2c49c401e748db37cfda7569fa6df35b704fe716394f2ac3f40ce87b4506e9906f0\"}],\"missedProofOutputs\":[{\"value\":\"2469287901000000000000000000000000\",\"address\":\"1df67838262d7109ffcd9018f183b1eb33f05659a274b89ea6b52ff3617d34a770e9dd071d2e\"}],\"unlockHash\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"revisionNumber\":9657412421282982780}],\"fileContractRevisions\":[{\"parentID\":\"e4e26d93771d3bbb3d9dd306105d77cfb3a6254d1cc3495903af6e013442c63c\",\"unlockConditions\":{\"timelock\":0,\"publicKeys\":[\"ed25519:e6b9cde4eb058f8ecbb083d99779cb0f6d518d5386f019af6ead09fa52de8567\"],\"signaturesRequired\":206644730660526450},\"revisionNumber\":10595710523108536025,\"filesize\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"windowStart\":4348934140507359445,\"windowEnd\":14012366839994454386,\"validProofOutputs\":[{\"value\":\"2435858510000000000000000000000000\",\"address\":\"543bc0eda69f728d0a0fbce08e5bfc5ed7b961300e0af226949e135f7d12e32f0544e5262d6f\"}],\"missedProofOutputs\":[{\"value\":\"880343701000000000000000000000000\",\"address\":\"7b7f9aee981fe0d93bb3f49c6233cf847ebdd39d7dc5253f7fc330df2167073b35f035703237\"}],\"unlockHash\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"}],\"storageProofs\":[{\"parentID\":\"c0b9e98c9e03a2740c75d673871c1ee91f36d1bb329ff3ddbf1dfa8c6e1a64eb\",\"leaf\":\"b78fa521dc62d9ced82bc3b61e0aa5a5c221d6cca5db63d94c9879543fb98c0a971094a89cd4408487ae32902248d321b545f9a051729aa0bb1725b848e3d453\",\"proof\":[\"fe08c0a061475e7e5dec19e717cf98792fa7b555d0b5d3540a05db09f59ab8de\"]}],\"minerFees\":[\"241119475000000000000000000000000\"],\"arbitraryData\":[\"2shzIHEUJYwuNHz6c/gPz+aTEWZRTpDTmemX9yYAKlY=\"],\"signatures\":[{\"parentID\":\"06d1fca03c5ddd9b09116db1b97c5451f7dc792b05362969f83e3e8dc1007f46\",\"publicKeyIndex\":6088345341283457116,\"timelock\":2014247885072555224,\"coveredFields\":{\"wholeTransaction\":true},\"signature\":\"2XNEKGZrl9RhMa2JmGsvcmqQWAIX/uxtMwLnPI6VJPcXqub6qYIuoAThYp9NAwadk+1GG6CXC66g4rOjFYuNSA==\"}]}"; test_serialize_json(&transaction, json_str); } @@ -917,17 +1015,63 @@ mod tests { #[test] fn test_transaction_sign() { - let state = SigningState { - index: ChainIndex { - height: 0, - id: BlockID::default(), + let cs = ChainState { + state: State { + index: ChainIndex { + height: 1, + id: BlockID::default(), + }, + prev_timestamps: [OffsetDateTime::UNIX_EPOCH; 11], + depth: BlockID::default(), + child_target: BlockID::default(), + siafund_pool: Currency::new(0), + oak_time: Duration::new(0, 0), + oak_target: BlockID::default(), + foundation_primary_address: Address::new([0u8; 32]), + foundation_failsafe_address: Address::new([0u8; 32]), + total_work: Work::zero(), + difficulty: Work::zero(), + oak_work: Work::zero(), + attestations: 0, + elements: Elements { + num_leaves: 0, + trees: vec![], + }, }, - median_timestamp: time::OffsetDateTime::now_utc(), - hardforks: NetworkHardforks { - asic_height: 0, - foundation_height: 0, - v2_allow_height: 1000, - v2_require_height: 1000, + network: Network { + name: "test".to_string(), + initial_coinbase: Currency::new(0), + minimum_coinbase: Currency::new(0), + initial_target: BlockID::default(), + block_interval: Duration::new(1, 0), + maturity_delay: 0, + hardfork_dev_addr: HardforkDevAddr { + height: 0, + old_address: Address::new([0u8; 32]), + new_address: Address::new([0u8; 32]), + }, + hardfork_tax: HardforkTax { height: 10 }, + hardfork_storage_proof: HardforkStorageProof { height: 0 }, + hardfork_asic: HardforkASIC { + height: 0, + oak_time: Duration::new(0, 0), + oak_target: BlockID::default(), + }, + hardfork_oak: HardforkOak { + height: 0, + fix_height: 0, + genesis_timestamp: OffsetDateTime::UNIX_EPOCH, + }, + hardfork_foundation: HardforkFoundation { + height: 0, + primary_address: Address::new([0u8; 32]), + failsafe_address: Address::new([0u8; 32]), + }, + hardfork_v2: HardforkV2 { + // test was made for pre-v2 + allow_height: 100, + require_height: 100, + }, }, }; let pk = PrivateKey::from_seed(&[ @@ -935,37 +1079,37 @@ mod tests { 157, 52, 25, 242, 96, 131, 16, 187, 22, 232, 107, 17, 205, ]); let test_cases = vec![ - ( - Transaction { - siacoin_inputs: vec![ - SiacoinInput{ - parent_id: SiacoinOutputID::from([32,11,215,36,166,174,135,0,92,215,179,18,74,229,52,154,221,194,213,216,219,47,225,205,251,84,248,2,69,252,37,117]), - unlock_conditions: UnlockConditions::standard_unlock_conditions(pk.public_key()), - } - ], - siacoin_outputs: vec![ - SiacoinOutput{ - value: Currency::new(67856467336433871), - address: Address::parse_string("000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69").unwrap(), - } - ], - file_contracts: Vec::new(), - file_contract_revisions: Vec::new(), - storage_proofs: Vec::new(), - siafund_inputs: Vec::new(), - siafund_outputs: Vec::new(), - miner_fees: Vec::new(), - arbitrary_data: Vec::new(), - signatures: Vec::new(), - }, - "7a5db98318b5ecad2954d41ba2084c908823ebf4000b95543f352478066a0d04bf4829e3e6d086b42ff9d943f68981c479798fd42bf6f63dac254f4294a37609" - ) - ]; + ( + Transaction { + siacoin_inputs: vec![ + SiacoinInput{ + parent_id: SiacoinOutputID::from([32,11,215,36,166,174,135,0,92,215,179,18,74,229,52,154,221,194,213,216,219,47,225,205,251,84,248,2,69,252,37,117]), + unlock_conditions: UnlockConditions::standard_unlock_conditions(pk.public_key()), + } + ], + siacoin_outputs: vec![ + SiacoinOutput{ + value: Currency::new(67856467336433871), + address: address!("000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69"), + } + ], + file_contracts: Vec::new(), + file_contract_revisions: Vec::new(), + storage_proofs: Vec::new(), + siafund_inputs: Vec::new(), + siafund_outputs: Vec::new(), + miner_fees: Vec::new(), + arbitrary_data: Vec::new(), + signatures: Vec::new(), + }, + "7a5db98318b5ecad2954d41ba2084c908823ebf4000b95543f352478066a0d04bf4829e3e6d086b42ff9d943f68981c479798fd42bf6f63dac254f4294a37609" + ) + ]; for (txn, expected) in test_cases { let sig = txn .sign( - &state, + &cs, &CoveredFields::whole_transaction(), Hash256::from(>::into( txn.siacoin_inputs[0].parent_id, @@ -1111,7 +1255,7 @@ mod tests { ]; for (expected_str, public_key) in test_cases { - let expected = Address::parse_string(expected_str).unwrap(); + let expected = Address::parse_string(&expected_str).unwrap(); let public_key = PublicKey::new(public_key.as_slice().try_into().unwrap()); let addr = UnlockConditions::standard_unlock_conditions(public_key).address(); @@ -1133,94 +1277,81 @@ mod tests { let test_addresses = vec![ ( 0, - Address::parse_string( - "16e09f8dc8a100a03ba1f9503e4035661738d1bea0b6cdc9bb012d3cd25edaacfd780909e550", - ) - .unwrap(), + address!( + "16e09f8dc8a100a03ba1f9503e4035661738d1bea0b6cdc9bb012d3cd25edaacfd780909e550" + ), ), ( 1, - Address::parse_string( - "cb016a7018485325fa299bc247113e3792dbea27ee08d2bb57a16cb0804fa449d3a91ee647a1", - ) - .unwrap(), + address!( + "cb016a7018485325fa299bc247113e3792dbea27ee08d2bb57a16cb0804fa449d3a91ee647a1" + ), ), ( 2, - Address::parse_string( - "5eb70f141387df1e2ecd434b22be50bff57a6e08484f3890fe4415a6d323b5e9e758b4f79b34", - ) - .unwrap(), + address!( + "5eb70f141387df1e2ecd434b22be50bff57a6e08484f3890fe4415a6d323b5e9e758b4f79b34" + ), ), ( 3, - Address::parse_string( - "c3bc7bc1431460ed2556874cb63714760120125da758ebbd78198534cb3d25774352fdbb3e8b", - ) - .unwrap(), + address!( + "c3bc7bc1431460ed2556874cb63714760120125da758ebbd78198534cb3d25774352fdbb3e8b" + ), ), ( 4, - Address::parse_string( - "ebc7eae02ecf76e3ba7312bab6b6f71e9d255801a3a3b83f7cc26bd520b2c27a511cd8604e4b", - ) - .unwrap(), + address!( + "ebc7eae02ecf76e3ba7312bab6b6f71e9d255801a3a3b83f7cc26bd520b2c27a511cd8604e4b" + ), ), ( 5, - Address::parse_string( - "fce241a44b944b10f414782dd35f5d96b92aec3d6da92a45ae44b7dc8cfb4b4ba97a34ce7032", - ) - .unwrap(), + address!( + "fce241a44b944b10f414782dd35f5d96b92aec3d6da92a45ae44b7dc8cfb4b4ba97a34ce7032" + ), ), ( 6, - Address::parse_string( - "36d253e7c3af2213eccaf0a61c6d24be8668f72af6e773463f3c41efc8bb70f2b353b90de9dd", - ) - .unwrap(), + address!( + "36d253e7c3af2213eccaf0a61c6d24be8668f72af6e773463f3c41efc8bb70f2b353b90de9dd" + ), ), ( 7, - Address::parse_string( - "c8f85375fb264428c86594863440f856db1da4614d75f4a30e3d9db3dfc88af6995128c6a845", - ) - .unwrap(), + address!( + "c8f85375fb264428c86594863440f856db1da4614d75f4a30e3d9db3dfc88af6995128c6a845" + ), ), ( 8, - Address::parse_string( - "85ef2ba14ee464060570b16bddaac91353961e7545067ccdf868a0ece305f00d2c08ec6844c6", - ) - .unwrap(), + address!( + "85ef2ba14ee464060570b16bddaac91353961e7545067ccdf868a0ece305f00d2c08ec6844c6" + ), ), ( 9, - Address::parse_string( - "9dcf644245eba91e7ea70c47ccadf479e6834c1c1221335e7246e0a6bc40e18362c4faa760b8", - ) - .unwrap(), + address!( + "9dcf644245eba91e7ea70c47ccadf479e6834c1c1221335e7246e0a6bc40e18362c4faa760b8" + ), ), ( 4294967295, - Address::parse_string( - "a906891f0c524fd272a905aa5dd7018c69e5d68222385cbd9d5292f38f021ce4bf00953a0659", - ) - .unwrap(), + address!( + "a906891f0c524fd272a905aa5dd7018c69e5d68222385cbd9d5292f38f021ce4bf00953a0659" + ), ), ( 4294967296, - Address::parse_string( - "b6ab338e624a304add7afe205361ac71821b87559a3b9c5b3735eaafa914eed533613a0af7fa", - ) - .unwrap(), + address!( + "b6ab338e624a304add7afe205361ac71821b87559a3b9c5b3735eaafa914eed533613a0af7fa" + ), ), ( 18446744073709551615, - Address::parse_string( - "832d0e8b5f967677d812d75559c373d930ad16eb90c31c29982a190bb7db9edf9438fd827938", - ) - .unwrap(), + address!( + "832d0e8b5f967677d812d75559c373d930ad16eb90c31c29982a190bb7db9edf9438fd827938" + ), ), ]; @@ -1232,4 +1363,222 @@ mod tests { assert_eq!(addr, expected, "index {}", i); } } + + #[test] + fn test_file_contract_tax() { + struct TestCase { + payout: Currency, + prefork: Currency, + postfork: Currency, + } + let test_cases = vec![ + TestCase { + payout: Currency::new(0), + prefork: Currency::new(0), + postfork: Currency::new(0), + }, + TestCase { + payout: Currency::new(1), + prefork: Currency::new(0), + postfork: Currency::new(0), + }, + TestCase { + payout: Currency::new(2), + prefork: Currency::new(0), + postfork: Currency::new(0), + }, + TestCase { + payout: Currency::new(340282366920938463463374607431768211455), + prefork: Currency::new(13271012309916600075071609689838960000), + postfork: Currency::new(13271012309916600075071609689838960000), + }, + TestCase { + payout: Currency::new(595540520000000000000000000000000), + prefork: Currency::new(23226080280000000000000000000000), + postfork: Currency::new(23226080280000000000000000000000), + }, + TestCase { + payout: Currency::new(103983762872653413852682180916164570605), + prefork: Currency::new(4055366752033483140254605055730410000), + postfork: Currency::new(4055366752033483140254605055730410000), + }, + TestCase { + payout: Currency::new(3827764528000000000000000000000000), + prefork: Currency::new(149282816592000000000000000000000), + postfork: Currency::new(149282816592000000000000000000000), + }, + TestCase { + payout: Currency::new(216196544861605368913933682930139323360), + prefork: Currency::new(8431665249602609387643413634275430000), + postfork: Currency::new(8431665249602609387643413634275430000), + }, + TestCase { + payout: Currency::new(2630651610000000000000000000000000), + prefork: Currency::new(102595412790000000000000000000000), + postfork: Currency::new(102595412790000000000000000000000), + }, + TestCase { + payout: Currency::new(242035606817835258338180817515298863866), + prefork: Currency::new(9439388665895575075189051883096650000), + postfork: Currency::new(9439388665895575075189051883096650000), + }, + TestCase { + payout: Currency::new(3830155529000000000000000000000000), + prefork: Currency::new(149376065631000000000000000000000), + postfork: Currency::new(149376065631000000000000000000000), + }, + TestCase { + payout: Currency::new(126852058180828682198189558142776852406), + prefork: Currency::new(4947230269052318605729392767568290000), + postfork: Currency::new(4947230269052318605729392767568290000), + }, + TestCase { + payout: Currency::new(3497644816000000000000000000000000), + prefork: Currency::new(136408147824000000000000000000000), + postfork: Currency::new(136408147824000000000000000000000), + }, + TestCase { + payout: Currency::new(202288428857506739014982837533616200189), + prefork: Currency::new(7889248725442762821584330663811030000), + postfork: Currency::new(7889248725442762821584330663811030000), + }, + TestCase { + payout: Currency::new(3717108920000000000000000000000000), + prefork: Currency::new(144967247880000000000000000000000), + postfork: Currency::new(144967247880000000000000000000000), + }, + TestCase { + payout: Currency::new(319915558118289110319073160920214552278), + prefork: Currency::new(12476706766613275302443853275888360000), + postfork: Currency::new(12476706766613275302443853275888360000), + }, + TestCase { + payout: Currency::new(2205536583000000000000000000000000), + prefork: Currency::new(86015926737000000000000000000000), + postfork: Currency::new(86015926737000000000000000000000), + }, + TestCase { + payout: Currency::new(138040906509763116024244911687441931808), + prefork: Currency::new(5383595353880761524945551555810230000), + postfork: Currency::new(5383595353880761524945551555810230000), + }, + TestCase { + payout: Currency::new(1110927021000000000000000000000000), + prefork: Currency::new(43326153819000000000000000000000), + postfork: Currency::new(43326153819000000000000000000000), + }, + TestCase { + payout: Currency::new(215175656237682186102553629801543673418), + prefork: Currency::new(8391850593269605257999591562260200000), + postfork: Currency::new(8391850593269605257999591562260200000), + }, + TestCase { + payout: Currency::new(646634091000000000000000000000000), + prefork: Currency::new(25218729549000000000000000000000), + postfork: Currency::new(25218729549000000000000000000000), + }, + TestCase { + payout: Currency::new(128469923315667767051668090462322677166), + prefork: Currency::new(5010327009311042915015055528030580000), + postfork: Currency::new(5010327009311042915015055528030580000), + }, + TestCase { + payout: Currency::new(2889549915000000000000000000000000), + prefork: Currency::new(112692446685000000000000000000000), + postfork: Currency::new(112692446685000000000000000000000), + }, + TestCase { + payout: Currency::new(269364173205837265395254441592734706401), + prefork: Currency::new(10505202755027653350414923222116650000), + postfork: Currency::new(10505202755027653350414923222116650000), + }, + ]; + + let mut cs = ChainState { + state: State { + index: ChainIndex { + height: 1, + id: BlockID::default(), + }, + prev_timestamps: [OffsetDateTime::UNIX_EPOCH; 11], + depth: BlockID::default(), + child_target: BlockID::default(), + siafund_pool: Currency::new(0), + oak_time: Duration::new(0, 0), + oak_target: BlockID::default(), + foundation_primary_address: Address::new([0u8; 32]), + foundation_failsafe_address: Address::new([0u8; 32]), + total_work: Work::zero(), + difficulty: Work::zero(), + oak_work: Work::zero(), + attestations: 0, + elements: Elements { + num_leaves: 0, + trees: vec![], + }, + }, + network: Network { + name: "test".to_string(), + initial_coinbase: Currency::new(0), + minimum_coinbase: Currency::new(0), + initial_target: BlockID::default(), + block_interval: Duration::new(1, 0), + maturity_delay: 0, + hardfork_dev_addr: HardforkDevAddr { + height: 0, + old_address: Address::new([0u8; 32]), + new_address: Address::new([0u8; 32]), + }, + hardfork_tax: HardforkTax { height: 10 }, + hardfork_storage_proof: HardforkStorageProof { height: 0 }, + hardfork_asic: HardforkASIC { + height: 0, + oak_time: Duration::new(0, 0), + oak_target: BlockID::default(), + }, + hardfork_oak: HardforkOak { + height: 0, + fix_height: 0, + genesis_timestamp: OffsetDateTime::UNIX_EPOCH, + }, + hardfork_foundation: HardforkFoundation { + height: 0, + primary_address: Address::new([0u8; 32]), + failsafe_address: Address::new([0u8; 32]), + }, + hardfork_v2: HardforkV2 { + allow_height: 0, + require_height: 0, + }, + }, + }; + + for tc in test_cases.iter() { + let fc = FileContract { + payout: tc.payout, + file_size: 0, + file_merkle_root: Hash256::default(), + window_start: 0, + window_end: 0, + unlock_hash: Address::new([0u8; 32]), + revision_number: 0, + valid_proof_outputs: vec![], + missed_proof_outputs: vec![], + }; + + let tax = fc.tax(&cs); + assert_eq!( + tax, tc.prefork, + "prefork tax incorrect for payout {:?}", + tc.payout + ); + cs.state.index.height = 11; + let tax = fc.tax(&cs); + assert_eq!( + tax, tc.postfork, + "postfork tax incorrect for payout {:?}", + tc.payout + ); + } + } } diff --git a/sia/src/types/v2.rs b/sia/src/types/v2.rs index feb601a..66a6f69 100644 --- a/sia/src/types/v2.rs +++ b/sia/src/types/v2.rs @@ -1,4 +1,9 @@ +use std::io; + +use crate::consensus::ChainState; use crate::encoding::{self, SiaDecodable, SiaDecode, SiaEncodable, SiaEncode}; +use crate::types::ZERO_SC; +use blake2b_simd::Params; use serde::de::{Error, MapAccess, Visitor}; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize}; @@ -26,11 +31,29 @@ pub use super::spendpolicy::*; pub struct Attestation { pub public_key: PublicKey, pub key: String, + #[serde(with = "crate::types::utils::base64")] pub value: Vec, pub signature: Signature, } +impl Attestation { + fn encode_semantics(&self, w: &mut W) -> encoding::Result<()> { + self.public_key.encode(w)?; + self.key.encode(w)?; + self.value.encode(w)?; + [0u8; 64].encode(w) // empty sig + } + + pub fn sig_hash(&self, cs: &ChainState) -> Hash256 { + let mut state = Params::new().hash_length(32).to_state(); + state.update("sia/sig/attestation|".as_bytes()); + state.update(cs.replay_prefix()); + self.encode_semantics(&mut state).unwrap(); + state.finalize().into() + } +} + /// A FileContract is a storage agreement between a renter and a host. It /// consists of a bidirectional payment channel that resolves as either "valid" /// or "missed" depending on whether a valid StorageProof is submitted for the @@ -55,6 +78,39 @@ pub struct FileContract { pub host_signature: Signature, } +impl FileContract { + pub fn tax(&self, cs: &ChainState) -> Currency { + let tax = (self.renter_output.value + self.host_output.value) / Currency::new(25); // 4% + tax - (tax % Currency::new(cs.siafund_count() as u128)) + } + + fn encode_semantics(&self, w: &mut W) -> encoding::Result<()> { + self.capacity.encode(w)?; + self.filesize.encode(w)?; + self.file_merkle_root.encode(w)?; + self.proof_height.encode(w)?; + self.expiration_height.encode(w)?; + self.renter_output.encode(w)?; + self.host_output.encode(w)?; + self.missed_host_value.encode(w)?; + self.total_collateral.encode(w)?; + self.renter_public_key.encode(w)?; + self.host_public_key.encode(w)?; + self.revision_number.encode(w)?; + [0u8; 64].encode(w)?; // nil renter signature + [0u8; 64].encode(w)?; // nil host signature + Ok(()) + } + + pub fn sig_hash(&self, cs: &ChainState) -> Hash256 { + let mut state = Params::new().hash_length(32).to_state(); + state.update("sia/sig/filecontract|".as_bytes()); + state.update(cs.replay_prefix()); + self.encode_semantics(&mut state).unwrap(); + state.finalize().into() + } +} + /// A SiacoinElement is a record of a Siacoin UTXO within the state accumulator. #[derive(Debug, PartialEq, Serialize, Deserialize, SiaEncode, SiaDecode)] #[serde(rename_all = "camelCase")] @@ -94,6 +150,16 @@ pub struct ChainIndexElement { pub chain_index: ChainIndex, } +impl ChainIndexElement { + pub fn encode_semantics(&self, w: &mut W) -> encoding::Result<()> { + self.state_element.leaf_index.encode(w)?; + Vec::::new().encode(w)?; // empty merkle proof + self.id.encode(w)?; + self.chain_index.encode(w)?; + Ok(()) + } +} + /// An AttestationElement is a record of an Attestation within the state /// accumulator. pub struct AttestationElement { @@ -129,6 +195,14 @@ pub struct FileContractRevision { pub revision: FileContract, } +impl FileContractRevision { + fn encode_semantics(&self, w: &mut W) -> encoding::Result<()> { + self.parent.id.encode(w)?; + self.revision.encode_semantics(w)?; + Ok(()) + } +} + /// A FileContractRenewal renews a file contract with optional rollover /// of any unspent funds. #[derive(Debug, PartialEq, Serialize, Deserialize, SiaEncode, SiaDecode)] @@ -144,6 +218,26 @@ pub struct FileContractRenewal { pub host_signature: Signature, } +impl FileContractRenewal { + fn encode_semantics(&self, w: &mut W) -> encoding::Result<()> { + self.final_revision.encode_semantics(w)?; + self.new_contract.encode_semantics(w)?; + self.renter_rollover.encode(w)?; + self.host_rollover.encode(w)?; + [0u8; 64].encode(w)?; // empty renter sig + [0u8; 64].encode(w)?; // empty host sig + Ok(()) + } + + pub fn sig_hash(&self, cs: &ChainState) -> Hash256 { + let mut state = Params::new().hash_length(32).to_state(); + state.update("sia/sig/filecontractrenewal|".as_bytes()); + state.update(cs.replay_prefix()); + self.encode_semantics(&mut state).unwrap(); + state.finalize().into() + } +} + /// A StorageProof asserts the presence of a randomly-selected leaf within the /// Merkle tree of a V2FileContract's data. #[derive(Debug, PartialEq, Serialize, Deserialize, SiaEncode, SiaDecode)] @@ -163,6 +257,15 @@ pub struct StorageProof { pub proof: Vec, } +impl StorageProof { + fn encode_semantics(&self, w: &mut W) -> encoding::Result<()> { + self.proof_index.encode_semantics(w)?; + self.leaf.encode(w)?; + self.proof.encode(w)?; + Ok(()) + } +} + // A ContractResolution closes a v2 file contract's payment channel. There /// are four ways a contract can be resolved: /// @@ -173,9 +276,7 @@ pub struct StorageProof { /// finalized, and a portion of its funds are "rolled over" into a new contract. /// /// 3. The host can submit a storage proof, asserting that it has faithfully -/// stored the contract data for the agreed-upon duration. Typically, a storage -/// proof is only required if the renter is unable or unwilling to sign a -/// finalization or renewal. A storage proof can only be submitted after the +/// stored the contract data for the agreed-upon duration. A storage proof can only be submitted after the /// contract's ProofHeight; this allows the renter (or host) to broadcast the /// latest contract revision prior to the proof. /// @@ -198,7 +299,6 @@ pub struct StorageProof { #[serde(rename_all = "camelCase")] #[allow(clippy::large_enum_variant)] pub enum ContractResolution { - Finalization(Signature), Renewal(FileContractRenewal), StorageProof(StorageProof), Expiration(), @@ -211,6 +311,18 @@ pub struct FileContractResolution { pub resolution: ContractResolution, } +impl FileContractResolution { + fn encode_semantics(&self, w: &mut W) -> encoding::Result<()> { + self.parent.id.encode(w)?; + match &self.resolution { + // type is not encoded in the resolution semantics + ContractResolution::Renewal(renewal) => renewal.encode_semantics(w), + ContractResolution::StorageProof(proof) => proof.encode_semantics(w), + ContractResolution::Expiration() => Ok(()), + } + } +} + impl SiaEncodable for FileContractResolution { fn encode(&self, w: &mut W) -> encoding::Result<()> { self.parent.encode(w)?; @@ -223,11 +335,7 @@ impl SiaEncodable for FileContractResolution { 1u8.encode(w)?; proof.encode(w) } - ContractResolution::Finalization(sig) => { - 2u8.encode(w)?; - sig.encode(w) - } - ContractResolution::Expiration() => 3u8.encode(w), + ContractResolution::Expiration() => 2u8.encode(w), } } } @@ -238,8 +346,7 @@ impl SiaDecodable for FileContractResolution { let resolution = match u8::decode(r)? { 0 => ContractResolution::Renewal(FileContractRenewal::decode(r)?), 1 => ContractResolution::StorageProof(StorageProof::decode(r)?), - 2 => ContractResolution::Finalization(Signature::decode(r)?), - 3 => ContractResolution::Expiration(), + 2 => ContractResolution::Expiration(), _ => { return Err(encoding::Error::Custom( "invalid contract resolution type".to_string(), @@ -262,14 +369,10 @@ impl Serialize for FileContractResolution { &match &self.resolution { ContractResolution::Renewal(_) => "renewal", ContractResolution::StorageProof(_) => "storageProof", - ContractResolution::Finalization(_) => "finalization", ContractResolution::Expiration() => "expiration", }, )?; let resolution = match &self.resolution { - ContractResolution::Finalization(sig) => { - serde_json::to_value(sig).map_err(serde::ser::Error::custom)? - } ContractResolution::Renewal(renewal) => { serde_json::to_value(renewal).map_err(serde::ser::Error::custom)? } @@ -326,9 +429,6 @@ impl<'de> Deserialize<'de> for FileContractResolution { .ok_or_else(|| serde::de::Error::missing_field("resolution"))?; let resolution = match resolution_type.as_str() { - "finalization" => ContractResolution::Finalization( - serde_json::from_value(resolution_value).map_err(Error::custom)?, - ), "renewal" => ContractResolution::Renewal( serde_json::from_value(resolution_value).map_err(Error::custom)?, ), @@ -351,27 +451,213 @@ impl<'de> Deserialize<'de> for FileContractResolution { } /// A Transaction effects a change of blockchain state. -#[derive(Debug, PartialEq, Serialize, Deserialize, SiaEncode, SiaDecode)] +#[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Transaction { + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siacoin_inputs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siacoin_outputs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siafund_inputs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub siafund_outputs: Vec, - pub file_contracts: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub file_contracts: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub file_contract_revisions: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub file_contract_resolutions: Vec, - pub arbitrary_data: Vec>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub attestations: Vec, + #[serde( + default, + with = "crate::types::utils::base64", + skip_serializing_if = "Vec::is_empty" + )] + pub arbitrary_data: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] pub new_foundation_address: Option
, pub miner_fee: Currency, } +impl Transaction { + fn encode_semantics(&self, w: &mut W) -> encoding::Result<()> { + self.siacoin_inputs.len().encode(w)?; + for input in &self.siacoin_inputs { + input.parent.id.encode(w)?; + } + self.siacoin_outputs.encode(w)?; + self.siafund_inputs.len().encode(w)?; + for input in &self.siafund_inputs { + input.parent.id.encode(w)?; + } + self.siafund_outputs.encode(w)?; + self.file_contracts.len().encode(w)?; + for fc in &self.file_contracts { + fc.encode_semantics(w)?; + } + self.file_contract_revisions.len().encode(w)?; + for fcr in &self.file_contract_revisions { + fcr.encode_semantics(w)?; + } + self.file_contract_resolutions.len().encode(w)?; + for fcr in &self.file_contract_resolutions { + fcr.encode_semantics(w)?; + } + self.attestations.encode(w)?; + self.arbitrary_data.encode(w)?; + self.new_foundation_address.encode(w)?; + self.miner_fee.encode(w)?; + Ok(()) + } + + pub fn input_sig_hash(&self, cs: &ChainState) -> Hash256 { + let mut state = Params::new().hash_length(32).to_state(); + state.update("sia/sig/input|".as_bytes()); + state.update(cs.replay_prefix()); + self.encode_semantics(&mut state).unwrap(); + state.finalize().into() + } +} + +const TXN_VERSION: u8 = 2; + +impl SiaEncodable for Transaction { + fn encode(&self, w: &mut W) -> encoding::Result<()> { + TXN_VERSION.encode(w)?; + + let fields = [ + !self.siacoin_inputs.is_empty(), + !self.siacoin_outputs.is_empty(), + !self.siafund_inputs.is_empty(), + !self.siafund_outputs.is_empty(), + !self.file_contracts.is_empty(), + !self.file_contract_revisions.is_empty(), + !self.file_contract_resolutions.is_empty(), + !self.attestations.is_empty(), + !self.arbitrary_data.is_empty(), + self.new_foundation_address.is_some(), + self.miner_fee != ZERO_SC, + ] + .iter() + .enumerate() + .filter(|(_, &condition)| condition) + .map(|(i, _)| 1 << i) + .sum::(); + fields.encode(w)?; + + if !self.siacoin_inputs.is_empty() { + self.siacoin_inputs.encode(w)?; + } + if !self.siacoin_outputs.is_empty() { + self.siacoin_outputs.encode(w)?; + } + if !self.siafund_inputs.is_empty() { + self.siafund_inputs.encode(w)?; + } + if !self.siafund_outputs.is_empty() { + self.siafund_outputs.encode(w)?; + } + if !self.file_contracts.is_empty() { + self.file_contracts.encode(w)?; + } + if !self.file_contract_revisions.is_empty() { + self.file_contract_revisions.encode(w)?; + } + if !self.file_contract_resolutions.is_empty() { + self.file_contract_resolutions.encode(w)?; + } + if !self.attestations.is_empty() { + self.attestations.encode(w)?; + } + if !self.arbitrary_data.is_empty() { + self.arbitrary_data.encode(w)?; + } + if let Some(addr) = &self.new_foundation_address { + addr.encode(w)?; + } + if self.miner_fee != ZERO_SC { + self.miner_fee.encode(w)?; + } + Ok(()) + } +} + +impl SiaDecodable for Transaction { + fn decode(r: &mut R) -> encoding::Result { + let version = u8::decode(r)?; + if version != TXN_VERSION { + return Err(encoding::Error::Custom( + "unsupported transaction version".to_string(), + )); + } + + let fields = u64::decode(r)?; + let mut txn = Transaction { + siacoin_inputs: Vec::new(), + siacoin_outputs: Vec::new(), + siafund_inputs: Vec::new(), + siafund_outputs: Vec::new(), + file_contracts: Vec::new(), + file_contract_revisions: Vec::new(), + file_contract_resolutions: Vec::new(), + attestations: Vec::new(), + arbitrary_data: Vec::new(), + new_foundation_address: None, + miner_fee: ZERO_SC, + }; + if fields & 1 != 0 { + txn.siacoin_inputs = Vec::decode(r)?; + } + if fields & 2 != 0 { + txn.siacoin_outputs = Vec::decode(r)?; + } + if fields & 4 != 0 { + txn.siafund_inputs = Vec::decode(r)?; + } + if fields & 8 != 0 { + txn.siafund_outputs = Vec::decode(r)?; + } + if fields & 16 != 0 { + txn.file_contracts = Vec::decode(r)?; + } + if fields & 32 != 0 { + txn.file_contract_revisions = Vec::decode(r)?; + } + if fields & 64 != 0 { + txn.file_contract_resolutions = Vec::decode(r)?; + } + if fields & 128 != 0 { + txn.attestations = Vec::decode(r)?; + } + if fields & 256 != 0 { + txn.arbitrary_data = Vec::decode(r)?; + } + if fields & 512 != 0 { + txn.new_foundation_address = Some(Address::decode(r)?); + } + if fields & 1024 != 0 { + txn.miner_fee = Currency::decode(r)?; + } + + Ok(txn) + } +} + #[cfg(test)] mod tests { use super::*; + use crate::consensus::{ + Elements, HardforkASIC, HardforkDevAddr, HardforkFoundation, HardforkOak, + HardforkStorageProof, HardforkTax, HardforkV2, Network, State, + }; + use crate::types::Work; + use crate::{address, hash_256, public_key, siacoin_id}; use core::fmt::Debug; use serde::de::DeserializeOwned; use serde::Serialize; + use time::{Duration, OffsetDateTime}; /// test_serialize_json is a helper to test serialization and deserialization of a struct to and from JSON. fn test_serialize_json( @@ -452,10 +738,9 @@ mod tests { v2_file_contract: FileContract { capacity: 7938725446189123975, filesize: 4815560028289493432, - file_merkle_root: Hash256::parse_string( - "dc033023420634ed4c7685c82aa884eebe8415e16c57b6a55c673a5a98fa7b0d", - ) - .unwrap(), + file_merkle_root: hash_256!( + "dc033023420634ed4c7685c82aa884eebe8415e16c57b6a55c673a5a98fa7b0d" + ), proof_height: 6265010746208955018, expiration_height: 5159880069065321628, renter_output: SiacoinOutput { @@ -669,6 +954,67 @@ mod tests { test_serialize_json(&sp, json_str); } + fn test_chain_state() -> ChainState { + ChainState { + state: State { + index: ChainIndex { + height: 1, + id: BlockID::default(), + }, + prev_timestamps: [OffsetDateTime::UNIX_EPOCH; 11], + depth: BlockID::default(), + child_target: BlockID::default(), + siafund_pool: Currency::new(0), + oak_time: Duration::new(0, 0), + oak_target: BlockID::default(), + foundation_primary_address: Address::new([0u8; 32]), + foundation_failsafe_address: Address::new([0u8; 32]), + total_work: Work::zero(), + difficulty: Work::zero(), + oak_work: Work::zero(), + elements: Elements { + num_leaves: 0, + trees: vec![], + }, + attestations: 0, + }, + network: Network { + name: "test".to_string(), + initial_coinbase: Currency::new(0), + minimum_coinbase: Currency::new(0), + initial_target: BlockID::default(), + block_interval: Duration::new(1, 0), + maturity_delay: 0, + hardfork_dev_addr: HardforkDevAddr { + height: 0, + old_address: Address::new([0u8; 32]), + new_address: Address::new([0u8; 32]), + }, + hardfork_tax: HardforkTax { height: 10 }, + hardfork_storage_proof: HardforkStorageProof { height: 0 }, + hardfork_asic: HardforkASIC { + height: 0, + oak_time: Duration::new(0, 0), + oak_target: BlockID::default(), + }, + hardfork_oak: HardforkOak { + height: 0, + fix_height: 0, + genesis_timestamp: OffsetDateTime::UNIX_EPOCH, + }, + hardfork_foundation: HardforkFoundation { + height: 0, + primary_address: Address::new([0u8; 32]), + failsafe_address: Address::new([0u8; 32]), + }, + hardfork_v2: HardforkV2 { + allow_height: 0, + require_height: 0, + }, + }, + } + } + #[test] fn test_serialize_v2_file_contract_resolution() { struct TestCase { @@ -677,92 +1023,87 @@ mod tests { json_str: String, } let test_cases = vec![ - TestCase{ - resolution: ContractResolution::Renewal(FileContractRenewal { - new_contract: FileContract { - capacity: 0, - filesize: 0, - file_merkle_root: Hash256::default(), - proof_height: 0, - expiration_height: 0, - renter_output: SiacoinOutput { - value: Currency::new(0), - address: Address::new([0; 32]), - }, - host_output: SiacoinOutput { - value: Currency::new(0), - address: Address::new([0; 32]), - }, - missed_host_value: Currency::new(0), - total_collateral: Currency::new(0), - host_public_key: PublicKey::new([0; 32]), - renter_public_key: PublicKey::new([0; 32]), - revision_number: 0, - - host_signature: Signature::new([0; 64]), - renter_signature: Signature::new([0; 64]), - }, - final_revision: FileContract { - capacity: 0, - filesize: 0, - file_merkle_root: Hash256::default(), - proof_height: 0, - expiration_height: 0, - renter_output: SiacoinOutput { - value: Currency::new(0), - address: Address::new([0; 32]), - }, - host_output: SiacoinOutput { - value: Currency::new(0), - address: Address::new([0; 32]), - }, - missed_host_value: Currency::new(0), - total_collateral: Currency::new(0), - host_public_key: PublicKey::new([0; 32]), - renter_public_key: PublicKey::new([0; 32]), - revision_number: 0, - - host_signature: Signature::new([0; 64]), - renter_signature: Signature::new([0; 64]), - }, - renter_rollover: Currency::new(0), - host_rollover: Currency::new(0), - renter_signature: Signature::new([0u8; 64]), - host_signature: Signature::new([0u8; 64]), - }), - binary_str: "00000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".to_string(), - json_str: "{\"parent\":{\"stateElement\":{\"leafIndex\":0,\"merkleProof\":[\"0000000000000000000000000000000000000000000000000000000000000000\"]},\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"v2FileContract\":{\"capacity\":0,\"filesize\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}},\"type\":\"renewal\",\"resolution\":{\"finalRevision\":{\"capacity\":0,\"expirationHeight\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"filesize\":0,\"hostOutput\":{\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"value\":\"0\"},\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"missedHostValue\":\"0\",\"proofHeight\":0,\"renterOutput\":{\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"value\":\"0\"},\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"totalCollateral\":\"0\"},\"hostRollover\":\"0\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"newContract\":{\"capacity\":0,\"expirationHeight\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"filesize\":0,\"hostOutput\":{\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"value\":\"0\"},\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"missedHostValue\":\"0\",\"proofHeight\":0,\"renterOutput\":{\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"value\":\"0\"},\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"totalCollateral\":\"0\"},\"renterRollover\":\"0\",\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}}".to_string(), - }, - TestCase{ - resolution: ContractResolution::StorageProof(StorageProof{ - proof_index: ChainIndexElement { - id: BlockID::default(), - chain_index: ChainIndex { - id: BlockID::default(), - height: 0, - }, - state_element: StateElement { - leaf_index: 0, - merkle_proof: vec![Hash256::default()], - }, - }, - leaf: [0u8; 64].into(), - proof: vec![Hash256::default()], - }), - binary_str: "00000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000".to_string(), - json_str: "{\"parent\":{\"stateElement\":{\"leafIndex\":0,\"merkleProof\":[\"0000000000000000000000000000000000000000000000000000000000000000\"]},\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"v2FileContract\":{\"capacity\":0,\"filesize\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}},\"type\":\"storageProof\",\"resolution\":{\"leaf\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"proof\":[\"0000000000000000000000000000000000000000000000000000000000000000\"],\"proofIndex\":{\"chainIndex\":{\"height\":0,\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"stateElement\":{\"leafIndex\":0,\"merkleProof\":[\"0000000000000000000000000000000000000000000000000000000000000000\"]}}}}".to_string(), - }, - TestCase{ - resolution: ContractResolution::Finalization([0u8;64].into()), - binary_str: "000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".to_string(), - json_str: "{\"parent\":{\"stateElement\":{\"leafIndex\":0,\"merkleProof\":[\"0000000000000000000000000000000000000000000000000000000000000000\"]},\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"v2FileContract\":{\"capacity\":0,\"filesize\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}},\"type\":\"finalization\",\"resolution\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}".to_string(), - }, - TestCase{ - resolution: ContractResolution::Expiration(), - binary_str: "0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003".to_string(), - json_str: "{\"parent\":{\"stateElement\":{\"leafIndex\":0,\"merkleProof\":[\"0000000000000000000000000000000000000000000000000000000000000000\"]},\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"v2FileContract\":{\"capacity\":0,\"filesize\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}},\"type\":\"expiration\",\"resolution\":{}}".to_string(), - } - ]; + TestCase{ + resolution: ContractResolution::Renewal(FileContractRenewal { + new_contract: FileContract { + capacity: 0, + filesize: 0, + file_merkle_root: Hash256::default(), + proof_height: 0, + expiration_height: 0, + renter_output: SiacoinOutput { + value: Currency::new(0), + address: Address::new([0; 32]), + }, + host_output: SiacoinOutput { + value: Currency::new(0), + address: Address::new([0; 32]), + }, + missed_host_value: Currency::new(0), + total_collateral: Currency::new(0), + host_public_key: PublicKey::new([0; 32]), + renter_public_key: PublicKey::new([0; 32]), + revision_number: 0, + + host_signature: Signature::new([0; 64]), + renter_signature: Signature::new([0; 64]), + }, + final_revision: FileContract { + capacity: 0, + filesize: 0, + file_merkle_root: Hash256::default(), + proof_height: 0, + expiration_height: 0, + renter_output: SiacoinOutput { + value: Currency::new(0), + address: Address::new([0; 32]), + }, + host_output: SiacoinOutput { + value: Currency::new(0), + address: Address::new([0; 32]), + }, + missed_host_value: Currency::new(0), + total_collateral: Currency::new(0), + host_public_key: PublicKey::new([0; 32]), + renter_public_key: PublicKey::new([0; 32]), + revision_number: 0, + + host_signature: Signature::new([0; 64]), + renter_signature: Signature::new([0; 64]), + }, + renter_rollover: Currency::new(0), + host_rollover: Currency::new(0), + renter_signature: Signature::new([0u8; 64]), + host_signature: Signature::new([0u8; 64]), + }), + binary_str: "00000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".to_string(), + json_str: "{\"parent\":{\"stateElement\":{\"leafIndex\":0,\"merkleProof\":[\"0000000000000000000000000000000000000000000000000000000000000000\"]},\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"v2FileContract\":{\"capacity\":0,\"filesize\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}},\"type\":\"renewal\",\"resolution\":{\"finalRevision\":{\"capacity\":0,\"expirationHeight\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"filesize\":0,\"hostOutput\":{\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"value\":\"0\"},\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"missedHostValue\":\"0\",\"proofHeight\":0,\"renterOutput\":{\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"value\":\"0\"},\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"totalCollateral\":\"0\"},\"hostRollover\":\"0\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"newContract\":{\"capacity\":0,\"expirationHeight\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"filesize\":0,\"hostOutput\":{\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"value\":\"0\"},\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"missedHostValue\":\"0\",\"proofHeight\":0,\"renterOutput\":{\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\",\"value\":\"0\"},\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"totalCollateral\":\"0\"},\"renterRollover\":\"0\",\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}}".to_string(), + }, + TestCase{ + resolution: ContractResolution::StorageProof(StorageProof{ + proof_index: ChainIndexElement { + id: BlockID::default(), + chain_index: ChainIndex { + id: BlockID::default(), + height: 0, + }, + state_element: StateElement { + leaf_index: 0, + merkle_proof: vec![Hash256::default()], + }, + }, + leaf: [0u8; 64].into(), + proof: vec![Hash256::default()], + }), + binary_str: "00000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000".to_string(), + json_str: "{\"parent\":{\"stateElement\":{\"leafIndex\":0,\"merkleProof\":[\"0000000000000000000000000000000000000000000000000000000000000000\"]},\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"v2FileContract\":{\"capacity\":0,\"filesize\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}},\"type\":\"storageProof\",\"resolution\":{\"leaf\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"proof\":[\"0000000000000000000000000000000000000000000000000000000000000000\"],\"proofIndex\":{\"chainIndex\":{\"height\":0,\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"stateElement\":{\"leafIndex\":0,\"merkleProof\":[\"0000000000000000000000000000000000000000000000000000000000000000\"]}}}}".to_string(), + }, + TestCase{ + resolution: ContractResolution::Expiration(), + binary_str: "0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002".to_string(), + json_str: "{\"parent\":{\"stateElement\":{\"leafIndex\":0,\"merkleProof\":[\"0000000000000000000000000000000000000000000000000000000000000000\"]},\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"v2FileContract\":{\"capacity\":0,\"filesize\":0,\"fileMerkleRoot\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"renterSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"hostSignature\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}},\"type\":\"expiration\",\"resolution\":{}}".to_string(), + } + ]; for tc in test_cases { let fcr = FileContractResolution { parent: FileContractElement { @@ -802,4 +1143,259 @@ mod tests { test_serialize_json(&fcr, tc.json_str.as_str()); } } + + #[test] + fn test_file_contract_tax() { + struct TestCase { + output_value: Currency, + tax: Currency, + } + let test_cases = vec![ + TestCase { + output_value: Currency::new(0), + tax: Currency::new(0), + }, + TestCase { + output_value: Currency::new(0), + tax: Currency::new(0), + }, + TestCase { + output_value: Currency::new(1), + tax: Currency::new(0), + }, + TestCase { + output_value: Currency::new(170141183460469231731687303715884105727), + tax: Currency::new(13611294676837538538534984297270720000), + }, + TestCase { + output_value: Currency::new(805949712500000000000000000000000), + tax: Currency::new(64475977000000000000000000000000), + }, + TestCase { + output_value: Currency::new(151318823166141058930638084278357033939), + tax: Currency::new(12105505853291284714451046742268560000), + }, + TestCase { + output_value: Currency::new(2087287149000000000000000000000000), + tax: Currency::new(166982971920000000000000000000000), + }, + TestCase { + output_value: Currency::new(19054329256128693174495496952954959442), + tax: Currency::new(1524346340490295453959639756236390000), + }, + TestCase { + output_value: Currency::new(1463835551500000000000000000000000), + tax: Currency::new(117106844120000000000000000000000), + }, + TestCase { + output_value: Currency::new(34808017277671855105213753517013880976), + tax: Currency::new(2784641382213748408417100281361110000), + }, + TestCase { + output_value: Currency::new(1456475819000000000000000000000000), + tax: Currency::new(116518065520000000000000000000000), + }, + TestCase { + output_value: Currency::new(4050009160038948139568737574567615426), + tax: Currency::new(324000732803115851165499005965400000), + }, + TestCase { + output_value: Currency::new(611362349000000000000000000000000), + tax: Currency::new(48908987920000000000000000000000), + }, + TestCase { + output_value: Currency::new(59744399278834191323014460064531501644), + tax: Currency::new(4779551942306735305841156805162520000), + }, + TestCase { + output_value: Currency::new(1971395366500000000000000000000000), + tax: Currency::new(157711629320000000000000000000000), + }, + TestCase { + output_value: Currency::new(129395477943018813820173885271365401215), + tax: Currency::new(10351638235441505105613910821709230000), + }, + TestCase { + output_value: Currency::new(1562430843000000000000000000000000), + tax: Currency::new(124994467440000000000000000000000), + }, + TestCase { + output_value: Currency::new(33394010960011557818205782768368594560), + tax: Currency::new(2671520876800924625456462621469480000), + }, + TestCase { + output_value: Currency::new(1464305596000000000000000000000000), + tax: Currency::new(117144447680000000000000000000000), + }, + TestCase { + output_value: Currency::new(33699424149038914903292787212706936885), + tax: Currency::new(2695953931923113192263422977016550000), + }, + TestCase { + output_value: Currency::new(455795805000000000000000000000000), + tax: Currency::new(36463664400000000000000000000000), + }, + TestCase { + output_value: Currency::new(88567642754201788868008131876936390234), + tax: Currency::new(7085411420336143109440650550154910000), + }, + TestCase { + output_value: Currency::new(359253930000000000000000000000000), + tax: Currency::new(28740314400000000000000000000000), + }, + TestCase { + output_value: Currency::new(56501907684312465044405566127405468273), + tax: Currency::new(4520152614744997203552445290192430000), + }, + ]; + + let cs = test_chain_state(); + for tc in test_cases.iter() { + let fc = FileContract { + capacity: 0, + filesize: 0, + file_merkle_root: Hash256::default(), + revision_number: 0, + proof_height: 0, + expiration_height: 0, + missed_host_value: Currency::new(0), + total_collateral: Currency::new(0), + host_public_key: PublicKey::new([0u8; 32]), + renter_public_key: PublicKey::new([0u8; 32]), + host_signature: Signature::new([0u8; 64]), + renter_signature: Signature::new([0u8; 64]), + renter_output: SiacoinOutput { + value: tc.output_value, + address: Address::default(), + }, + host_output: SiacoinOutput { + value: tc.output_value, + address: Address::default(), + }, + }; + + let tax = fc.tax(&cs); + assert_eq!( + tax, tc.tax, + "prefork tax incorrect for payout {:?}", + tc.output_value + ); + } + } + + #[test] + fn test_attestation_sig_hash() { + let cs = test_chain_state(); + let a = Attestation { + public_key: PublicKey::new([ + 119, 70, 48, 66, 126, 125, 116, 9, 234, 170, 136, 51, 123, 122, 142, 138, 198, 136, + 19, 32, 194, 144, 129, 104, 130, 246, 58, 195, 16, 72, 139, 112, + ]), + key: "2d341885482102f2".to_string(), + value: vec![113, 1, 70, 231, 190, 215, 117, 38], + signature: Signature::default(), + }; + let sig_hash = a.sig_hash(&cs); + assert_eq!( + hex::encode(sig_hash), + "5c4201fd4c261a1a3deb25130bd8f06d7d87a46281fd022252152844336a7c17" + ) + } + + #[test] + fn test_file_contract_sig_hash() { + let cs = test_chain_state(); + let s = "{\"capacity\":15437388468662742744,\"filesize\":16009828729725979578,\"fileMerkleRoot\":\"2d3b2c7b78f04eb1b66ac467ae7831081f7b495a2964b943c2c750e70180785a\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"2825068531000000000000000000000000\",\"address\":\"cf620149bcbde171fcd9611a32ba29e2e97687f3b1562c1fe14c504f642690c9d8f5ec3cbeaf\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"renterSignature\":\"bee650e82a5534269bef42be0dd9a0b0f6c465b31437567075275d2188c685b2b65ef0fc7f369c780a758786f480da4d55459a1d85215f64aa47db1e79b1b8de\",\"hostSignature\":\"c32cf921ee00344d76e96e6f2dd306bd9c21226c83bb2ac55ce69b8d991a2f2212f645341c720f2fd8a7c57edef9b32f26a2c29c55958a45fd5c0d56a2addae3\"}"; + + let fc: FileContract = serde_json::from_str(s).unwrap(); + let sig_hash = fc.sig_hash(&cs); + assert_eq!( + hex::encode(sig_hash), + "0b9b74e471b8936e0045e752a1a22a77b7c17807ec98a1a3f272f6d917790325" + ); + } + + #[test] + fn test_contract_renewal_sig_hash() { + let cs = test_chain_state(); + let s = "{\"finalRevision\":{\"capacity\":11959377077631068749,\"filesize\":10613077956865333006,\"fileMerkleRoot\":\"9482be576a5d68b6ad311b359b12070fae3df71fc0d2ec480a5ba8c3f4c9ad40\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"107325855000000000000000000000000\",\"address\":\"cc590b0901f908ee76ea2c8497b9c29d7f2250db4427b7b1dfd8d5c0a368845c4a70d66c8998\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":18446744073709551615,\"renterSignature\":\"a71062c50866e8e834143340117efc49adbe7c8e50775c2ef6c2d8fd84470231928115e377288d588d93342946adbcbc83c5e3be01d4d22accf59c86f44ad523\",\"hostSignature\":\"a277c4b62d6ab7eead9d02c9e6243810baa0af9a8fef8b70b2f33df83fc8e67b94b2247b53c5e6d3a93a6197d41dd07652b75896757cf1dfeb059edca458de40\"},\"newContract\":{\"capacity\":2615073250361876210,\"filesize\":14611382285114970285,\"fileMerkleRoot\":\"5942d80cb6816da220a9576d62f3979b5e4f96b769cf785bcddf31698afb1432\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"4046664893000000000000000000000000\",\"address\":\"41b74682d50aed617224d17162150c3854cc290b522c20d62260466ef13a95e212b7d9c6778b\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"renterSignature\":\"2614ae9d0b8300c98eadc1ffcab414298e30c678c0072359b1794623a04a68ef2474fbb1492071313b6f768946a52978b520c219a23649bfef9b9d0baabc7331\",\"hostSignature\":\"9c14a4f1428ad5bd72075c626fdf7dcf24143eda3a9cbecbc16ca751f0c88ee09e04a7f4f79718fb87df6debdd1d94d97bd00bcfa4fa77b3de582ad26eb45139\"},\"renterRollover\":\"3597839704000000000000000000000000\",\"hostRollover\":\"3099140907000000000000000000000000\",\"renterSignature\":\"4906067918dc6e7951c4e42b5b5bc1c3a3e3f02bddd10bf981275ccd0e8a5a067d35d4b5c2115256454490fed011e5c11ea35cb378eb6ada168b65d614515a44\",\"hostSignature\":\"5414a4c1e32f17b275148f3935a53b30cc30d1a0ff43e88f71e8308209282528a2942779638a6e2329314101e5ea79627d98b0f1b19830bf0ea89f670f7798bf\"}"; + + let fcr: FileContractRenewal = serde_json::from_str(s).unwrap(); + let sig_hash = fcr.sig_hash(&cs); + assert_eq!( + hex::encode(sig_hash), + "f20822926c53eac2bf91d54e5b39f7de76739226b131448e5508f077ebbccf3b" + ); + } + + #[test] + fn test_input_sig_hash() { + let cs = test_chain_state(); + let s = "{\"siacoinInputs\":[{\"parent\":{\"id\":\"af88fb86ace93d500549ad5d3ccae9d75184c932d8b9f6b39b22dc1beb19cb2b\",\"stateElement\":{\"leafIndex\":17247661272366213787,\"merkleProof\":[\"419c0046c59819e4541a3810134dac5c975eb61cb12382c0fbad713257a9efd3\"]},\"siacoinOutput\":{\"value\":\"3088711336000000000000000000000000\",\"address\":\"c6b4adae9284845d1075bc33a68d281bf02ed406206f58a50d13191af7b6f617933bf733cd42\"},\"maturityHeight\":0},\"satisfiedPolicy\":{\"policy\":{\"type\":\"pk\",\"policy\":\"ed25519:e655ec65952c4953c904c9ee16961dfb02e8689f1f82fcfd9e387c8ea1104a2b\"},\"signatures\":[\"7ba834e5e33ca4ac7ad7dbd2b3bd42c7ae62db25263feb2a51777f5c8d5569b1eb28e349544689d3f96242a66d33dcdebeab4e5fa30c906fd2caaf83c16f3bfe\"]}}],\"siacoinOutputs\":[{\"value\":\"1597553234000000000000000000000000\",\"address\":\"bc99db1f50a653604797de23d535c9fbf73b493ab70089c7716c1e5c5fc2d0a578575d367eb0\"}],\"fileContracts\":[{\"capacity\":10874342648285931495,\"filesize\":13810078500867640614,\"fileMerkleRoot\":\"424b9304a9fccf94945ef14c6377060be59fb7d58ac8c952d67be05c67d863a2\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"1556050983000000000000000000000000\",\"address\":\"4505c61036d22e5075c676ea5906a645eb1cfa9a6a53d933ecfa198654b7d3f0fe74bdfbdaef\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":18446744073709551615,\"renterSignature\":\"84d8091517ff09f11df171b21a1414200ea7074664c7e27d9de13c44bbb6cbc0de6de5783c6955065a77035bb2d92974fb5f11aa0dae9ac50dbddbb17d63d7fc\",\"hostSignature\":\"2fad832ddb5d6b11893851df4ecbd9a4b512a7a425dda67ac0da1604a0e8781d04e29cef928f31be706e6fc81089048ea87366449450e3da941a0b04487582f2\"}],\"fileContractRevisions\":[{\"parent\":{\"id\":\"22e9e60d81478b36fc44ee67b8d8ba874d196fc6e9c56bfacd5fcecdde39b736\",\"stateElement\":{\"leafIndex\":12023426945600912207,\"merkleProof\":[\"a0efcbdbe1f8e1b4fb6615eccac5948303d5a2c5afedda0baf658e5bbe41896e\"]},\"v2FileContract\":{\"capacity\":10776473623907646048,\"filesize\":7973631990249562263,\"fileMerkleRoot\":\"acd56f68224de7828076cb53f3476413819c8c78dde2461422c5a96644fba623\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"842083234000000000000000000000000\",\"address\":\"150084c56cd2bc38e80220eb69a485b5c298ee3c9f7fcc046c6b975ea8fb70f9098afe1ad85b\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":18446744073709551615,\"renterSignature\":\"94dd2555507b81cfd927b36891a9b37465c00cb46a61d96ede5f760f1c5a5cadabb8c7719adb5cb088eec92d9a9c52497973fc2b5d0c54a2dfa04e35490e25b5\",\"hostSignature\":\"d66133988a412e5542535e9a6c3815396c46020a4ad693074d6681049b39c438af9dd395478566e193263e8acfa915848ec23ff47a95eca51bac4ede2a92f634\"}},\"revision\":{\"capacity\":6034084714889303577,\"filesize\":12020548219123782123,\"fileMerkleRoot\":\"2ce9c19107be51ab776221d61300c6c1aa32c2195246473eb15d7db12ec0943f\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"2247488091000000000000000000000000\",\"address\":\"6f7add99e7eba38429b101ff31190aa3a148c0087a3ad775790a8e01a90f71a94ca9ba2ffc71\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":18446744073709551615,\"renterSignature\":\"7fb556fef4bfea24f7b9472b1b9874be600795da4535107d3910085a3e34c5818a60bd77d0bcbd92e6dbec604f1b67d827ba02072e83abfac217a2294157c53f\",\"hostSignature\":\"77664782e2c1dd42a3669f11f0519630561a4c0b48552c89bb74f79367f21fd73b6bc5ea112502f81bbc9e3df3b916158486c2e17145b95e5307ca56fb37fc6c\"}}],\"fileContractResolutions\":[{\"parent\":{\"id\":\"6b8527472e1143e07099e3f516f56ac2b46860f13652582b2abb6fffeac1f5d9\",\"stateElement\":{\"leafIndex\":14811616376775388856,\"merkleProof\":[\"bf4325ff8d463ba3bf62e9f697911a4bf8038971298440f4a2c5e010730ac594\"]},\"v2FileContract\":{\"capacity\":6642268570724229223,\"filesize\":15896577861535581162,\"fileMerkleRoot\":\"6e19255c03515dbbee1280f8e13e7378810243042a01a439849c0ddbc0fd7eac\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"2825432965000000000000000000000000\",\"address\":\"2ca7f5d7cbd79fbf4b5f2f5a4ead652342dd0413e39e9406b2935c53162d9347359d0477b320\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":18446744073709551615,\"renterSignature\":\"169330702f83d499de6bff18cc7c2474785e02b8101baf8427407e9f6cc3ee6e7768b7e6ef929a6510c1b0ac1c24eab8090af84c27c772c80201fe93da535e17\",\"hostSignature\":\"29bf20483727c327e36c3085a87d91ad3072ca82f5d62da1d9895980320ab7a65d133bb147d55bd97c9f5a36a85afbfb8d9209ac82afaca18df64054394f0f08\"}},\"type\":\"expiration\",\"resolution\":{}},{\"parent\":{\"id\":\"2dd1bedefe81f2914ddf9f61c9d714d134d0af7a75d1c1322474d5e8eea342f2\",\"stateElement\":{\"leafIndex\":11863728860462350395,\"merkleProof\":[\"91a391d6104f76e439580a90f56fec3c07d01ad5535315416243ae6d8e17289d\"]},\"v2FileContract\":{\"capacity\":3158332065060007910,\"filesize\":11561455863259225844,\"fileMerkleRoot\":\"b4db7aa14dc851a348e0981c3ed1161dc286bca85424da42609b3c12f5f31ee6\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"2522086295000000000000000000000000\",\"address\":\"36e4ed130d7a9f0cbcdd0ed6fa78738dcd9eb6e779fc7501dd06f7cf968ce215e33778f0c79e\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":18446744073709551615,\"renterSignature\":\"ff79f46439dd108ebe2c42434a11b916f7dee6f018798435b3c34a92ca121d3e21c0282d4efba4982a36e2cfe146e3c8ee31ba545911857a93d947faef543e2f\",\"hostSignature\":\"7f45f2f977e574ea980a0842f48d216b6680d129a98056cafa7f108487457bde8e0cd1cc5d74ba3187170f214115827bc961f0163203c479c3d4ab7e19f3d31f\"}},\"type\":\"storageProof\",\"resolution\":{\"proofIndex\":{\"id\":\"90da2d2ed68b716be7617b8c35c713ad66584a1dbf564cc44c09b6f3815e1d79\",\"stateElement\":{\"leafIndex\":1814794313179331469,\"merkleProof\":[\"8a3990c651140b9486c09559c54623a4e8ca5c2705a7de2b0b54de6d97e6d01d\"]},\"chainIndex\":{\"height\":17891680254001945312,\"id\":\"e5b6bf4a036b93d8a178ffe86ee209502a0ff3d45d2b3216052267b7a5561d21\"}},\"leaf\":\"c5e078423107b4d2c0ab10509404c525343e96eff45ea6353039194470aec04e3994d52cd2d9a6cbbd3709387207f59f063bec4e7266f6fa0c6dfcf7d7634a1c\",\"proof\":[\"f900e345a663fb6ec9eca5721a81e25fae111e50f38aeb76fd6258f2d4cecd86\"]}},{\"parent\":{\"id\":\"49d9f6a8cfae42cd9cc4d792b1799ab2752be164cd86ce5e474fe3731c31b492\",\"stateElement\":{\"leafIndex\":376572300431080610,\"merkleProof\":[\"b2945edc55afbc2344d201baaed373fc0a737c00216186c5e724c3e7d681a705\"]},\"v2FileContract\":{\"capacity\":14165001982102687892,\"filesize\":5514344128076756235,\"fileMerkleRoot\":\"3bcf704f911a925eea72629d71352e1f402fc7d3b632f0df6e203089a6fd9092\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"1548819653000000000000000000000000\",\"address\":\"c1f4c5d401041e4eb68455283ce6705f2dcdc5ac4d700f7dc233b13f91bbfa986aec255c62bb\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":18446744073709551615,\"renterSignature\":\"ac99ce31b11341a8835e2666eed317b0042d45ce6ccb52a11a3d2d6c7ec050f432ea8e1fef95b3481da64c5b2b3a772459db094a68d4a3cf52f2e928f3dd7b55\",\"hostSignature\":\"c8a097593157a24390c5fc77bed87c54355e03572ff2b49137801c51ac55ff092141dfb46505808fa97ee22e839d682f9de7f595896338ebf1c4e1885067747c\"}},\"type\":\"renewal\",\"resolution\":{\"finalRevision\":{\"capacity\":6539007356562521256,\"filesize\":17290867016333557028,\"fileMerkleRoot\":\"ebaa3f96d6572cc3caf3036468ddf52ef6fbfcc3eacef4cffd6eabcd43d1b91a\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"2415984205000000000000000000000000\",\"address\":\"1300755d91ac9d739e168f5e54eb2aba9aa0c29cc29142ab4babf0e92be83f17cde6a5ff4ed5\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":18446744073709551615,\"renterSignature\":\"a1489731bddba377854020ae706127938ce95c617d22db29b9f9aa331ee27dbfb625b67b3418647b40d4ab4f0075a69a10d66840a3cec4b3c86112ce7e648761\",\"hostSignature\":\"4181fc7c46f09d0124c365a1aeba76f906d99afb3f49d6637f54fc4e2746776a8f3aa343bc823c705c11a581a87ddb918d9bccec0cb2deb46aa4627cf5cf3e80\"},\"newContract\":{\"capacity\":2724141139581307456,\"filesize\":2682841277844293416,\"fileMerkleRoot\":\"62ea0682d909fb36df6557ea2d8174b20db44c0ad1ed692b7b11b40893147e36\",\"proofHeight\":0,\"expirationHeight\":0,\"renterOutput\":{\"value\":\"4004958805000000000000000000000000\",\"address\":\"cc6a403530f6da43fb262723a9536a96666bd74075d64dd7d0eb65df5c494c24b115cdee00c5\"},\"hostOutput\":{\"value\":\"0\",\"address\":\"000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69\"},\"missedHostValue\":\"0\",\"totalCollateral\":\"0\",\"renterPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"hostPublicKey\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":0,\"renterSignature\":\"ed98b9027f795d3eeafac4928963a55189066fa8a447fd70041d498d6d1072ad5247d36cd657e1f5163889f14591e89159daca5c8c3e645af9b26ac782990cfc\",\"hostSignature\":\"841fff506bfbdc5c9f6dcda6de472dd6bc7e73e5f0974e0bc979fdcc24059246590ef2f6af2b75a786c27f78f43a27e5da91b320736a5cbb7bb15be8375e372e\"},\"renterRollover\":\"3037399761000000000000000000000000\",\"hostRollover\":\"2211277679000000000000000000000000\",\"renterSignature\":\"745f21dae4f1fba04c2d9b09ae69a9e5f096cf26707447fb3b1c17e486b71589b7dc5fda40ef08eac2122b51606b2fbe84211c5f6507631456ac0dd0ee164ef3\",\"hostSignature\":\"0510b572606c3144a4472538b722c2420165efda406b92b74457459d5c5b90a622490d7a70bd7e5e40b031fadd9d3edd90c13226869d8d358512aab5b0145a11\"}}],\"attestations\":[{\"publicKey\":\"ed25519:269ba257f490941f5fcfde5313e326fa205a1a8d91715d91f892581d7282bf21\",\"key\":\"629109f07df18f46\",\"value\":\"Adg+GFWhwVo=\",\"signature\":\"d44eb9a001803f52814adae65da4dc195d760e7afb00b9593716e54a116db7b98ad8e774e101098d31abca3b389a01a08438d7a33508f1f665d2d48f4524509d\"}],\"minerFee\":\"2208027072000000000000000000000000\"}"; + let txn: Transaction = serde_json::from_str(s).unwrap(); + let sig_hash = txn.input_sig_hash(&cs); + assert_eq!( + hex::encode(sig_hash), + "aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422" + ); + } + + #[test] + fn test_transaction_serialization() { + let txn = Transaction { + siacoin_inputs: vec![], + siacoin_outputs: vec![], + siafund_inputs: vec![], + siafund_outputs: vec![], + file_contracts: vec![], + file_contract_revisions: vec![], + file_contract_resolutions: vec![], + attestations: vec![], + new_foundation_address: None, + arbitrary_data: vec![], + miner_fee: Currency::new(0), + }; + test_serialize_json(&txn, "{\"minerFee\":\"0\"}"); + test_serialize(&txn, "020000000000000000"); + + let txn = Transaction { + siacoin_inputs: vec![SiacoinInput { + parent: SiacoinElement{ + id: siacoin_id!("aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422"), + state_element: StateElement{ + leaf_index: 12312413, + merkle_proof: vec![hash_256!("aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422")] + }, + siacoin_output: SiacoinOutput{ + value: Currency::new(100), + address: address!("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8cdf32abee86f0") + }, + maturity_height: 0, + }, + satisfied_policy: SatisfiedPolicy { + policy: SpendPolicy::public_key(public_key!("ed25519:aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422")), + signatures: vec![ + Signature::parse_string("aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422").unwrap() + ], + preimages: vec![], + }, + }, + ], + siacoin_outputs: vec![SiacoinOutput { + value: Currency::new(100), + address: address!("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8cdf32abee86f0") + }], + siafund_inputs: vec![], + siafund_outputs: vec![], + file_contracts: vec![], + file_contract_revisions: vec![], + file_contract_resolutions: vec![], + attestations: vec![], + new_foundation_address: None, + arbitrary_data: vec![], + miner_fee: Currency::new(123581), + }; + test_serialize(&txn, "02030400000000000001000000000000005ddfbb00000000000100000000000000aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422640000000000000000000000000000008fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c00000000000000000103aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f14220100000000000000aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f142200000000000000000100000000000000640000000000000000000000000000008fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8cbde20100000000000000000000000000"); + test_serialize_json(&txn, "{\"siacoinInputs\":[{\"parent\":{\"stateElement\":{\"leafIndex\":12312413,\"merkleProof\":[\"aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422\"]},\"id\":\"aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422\",\"siacoinOutput\":{\"value\":\"100\",\"address\":\"8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8cdf32abee86f0\"},\"maturityHeight\":0},\"satisfiedPolicy\":{\"policy\":{\"type\":\"pk\",\"policy\":\"ed25519:aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422\"},\"signatures\":[\"aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422aefb1120a3f69fc8293caeb0bd36b4637d6fdf12f2f60494a2875358552f1422\"]}}],\"siacoinOutputs\":[{\"value\":\"100\",\"address\":\"8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8cdf32abee86f0\"}],\"minerFee\":\"123581\"}"); + } } diff --git a/sia/src/types/work.rs b/sia/src/types/work.rs new file mode 100644 index 0000000..8bb36ba --- /dev/null +++ b/sia/src/types/work.rs @@ -0,0 +1,59 @@ +use serde::{Deserialize, Serialize}; +use uint::construct_uint; + +use crate::encoding::{self, SiaDecodable, SiaEncodable, V1SiaDecodable, V1SiaEncodable}; + +construct_uint! { + /// Work is a 256-bit unsigned integer. + pub struct Work(4); +} + +impl From<&[u8; 32]> for Work { + fn from(bytes: &[u8; 32]) -> Self { + Work::from_big_endian(bytes) + } +} + +impl SiaEncodable for Work { + fn encode(&self, w: &mut W) -> encoding::Result<()> { + self.to_big_endian().encode(w) + } +} + +impl SiaDecodable for Work { + fn decode(r: &mut R) -> encoding::Result { + Ok(Work::from_big_endian(&<[u8; 32]>::decode(r).map_err( + |_| encoding::Error::Custom("invalid work".to_string()), + )?)) + } +} + +impl V1SiaEncodable for Work { + fn encode_v1(&self, w: &mut W) -> encoding::Result<()> { + self.to_big_endian().encode_v1(w) + } +} + +impl V1SiaDecodable for Work { + fn decode_v1(r: &mut R) -> encoding::Result { + Ok(Work::from_big_endian(&<[u8; 32]>::decode_v1(r)?)) + } +} + +impl Serialize for Work { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for Work { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Work::from_dec_str(&String::deserialize(deserializer)?).map_err(serde::de::Error::custom) + } +} diff --git a/sia_sdk_derive/Cargo.toml b/sia_derive/Cargo.toml similarity index 91% rename from sia_sdk_derive/Cargo.toml rename to sia_derive/Cargo.toml index ae20da4..9b3238f 100644 --- a/sia_sdk_derive/Cargo.toml +++ b/sia_derive/Cargo.toml @@ -11,8 +11,9 @@ keywords = ["sia", "decentralized", "blockchain", "depin", "storage"] [lib] proc-macro = true +name = "sia_derive" [dependencies] syn = "2.0" quote = "1.0" -proc-macro2 = "1.0" \ No newline at end of file +proc-macro2 = "1.0" diff --git a/sia_sdk_derive/src/lib.rs b/sia_derive/src/lib.rs similarity index 89% rename from sia_sdk_derive/src/lib.rs rename to sia_derive/src/lib.rs index 50b7088..0c1fa4e 100644 --- a/sia_sdk_derive/src/lib.rs +++ b/sia_derive/src/lib.rs @@ -11,9 +11,12 @@ pub fn derive_sia_encode(input: TokenStream) -> TokenStream { Data::Struct(data) => { let fields = match &data.fields { Fields::Named(fields) => { - let encodes = fields.named.iter().map(|f| { - let name = &f.ident; - quote! { self.#name.encode(w)?; } + let encodes = fields.named.iter().filter_map(|f| match f.vis { + syn::Visibility::Public(_) => { + let name = &f.ident; + Some(quote! { self.#name.encode(w)?; }) + } + _ => None, }); quote! { #(#encodes)* } } @@ -55,10 +58,13 @@ pub fn derive_sia_decode(input: TokenStream) -> TokenStream { Data::Struct(data) => { let fields = match &data.fields { Fields::Named(fields) => { - let decodes = fields.named.iter().map(|f| { - let name = &f.ident; - let ty = &f.ty; - quote! { #name: <#ty>::decode(r)?, } + let decodes = fields.named.iter().filter_map(|f| match f.vis { + syn::Visibility::Public(_) => { + let name = &f.ident; + let ty = &f.ty; + Some(quote! { #name: <#ty>::decode(r)?, }) + } + _ => None, }); quote! { Ok(Self {