From 6a9ca1a22668f7cefd9b102d534d9e516b52d646 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Fri, 12 Mar 2021 09:39:37 +0200 Subject: [PATCH 01/12] primitives - remove unused `ValidatorError` --- primitives/src/validator.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/primitives/src/validator.rs b/primitives/src/validator.rs index 1dc9badbc..0024665d9 100644 --- a/primitives/src/validator.rs +++ b/primitives/src/validator.rs @@ -6,14 +6,6 @@ use std::fmt; use crate::{targeting::Value, BalancesMap, BigNum, DomainError, ToETHChecksum}; use std::convert::TryFrom; -#[derive(Debug)] -pub enum ValidatorError { - None, - InvalidRootHash, - InvalidSignature, - InvalidTransition, -} - #[derive(Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[serde(transparent)] pub struct ValidatorId( From aaabc5add2e55cdbf860bdd07545351d46595493 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Fri, 12 Mar 2021 17:32:46 +0200 Subject: [PATCH 02/12] primitives - Channel v5 & Campaing + move structs --- primitives/src/campaign.rs | 101 +++++++++++++++++++++++++++++++++++ primitives/src/channel_v5.rs | 15 ++++++ primitives/src/lib.rs | 2 + 3 files changed, 118 insertions(+) create mode 100644 primitives/src/campaign.rs create mode 100644 primitives/src/channel_v5.rs diff --git a/primitives/src/campaign.rs b/primitives/src/campaign.rs new file mode 100644 index 000000000..0d671ca94 --- /dev/null +++ b/primitives/src/campaign.rs @@ -0,0 +1,101 @@ +use crate::{channel_v5::Channel, targeting::Rules, AdUnit, BigNum, EventSubmission, SpecValidators}; + +use chrono::{ + serde::{ts_milliseconds, ts_milliseconds_option}, + DateTime, Utc, +}; +use serde::{Deserialize, Serialize}; + +pub use pricing::{Pricing, PricingBounds}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Campaign { + channel: Channel, + spec: CampaignSpec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CampaignSpec { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub title: Option, + pub validators: SpecValidators, + /// Event pricing bounds + #[serde(default, skip_serializing_if = "Option::is_none")] + pub pricing_bounds: Option, + /// EventSubmission object, applies to event submission (POST /channel/:id/events) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub event_submission: Option, + /// A millisecond timestamp of when the campaign was created + #[serde(with = "ts_milliseconds")] + pub created: DateTime, + /// A millisecond timestamp representing the time you want this campaign to become active (optional) + /// Used by the AdViewManager & Targeting AIP#31 + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "ts_milliseconds_option" + )] + pub active_from: Option>, + /// A random number to ensure the campaignSpec hash is unique + #[serde(default, skip_serializing_if = "Option::is_none")] + pub nonce: Option, + /// A millisecond timestamp of when the campaign should enter a withdraw period + /// (no longer accept any events other than CHANNEL_CLOSE) + /// A sane value should be lower than channel.validUntil * 1000 and higher than created + /// It's recommended to set this at least one month prior to channel.validUntil * 1000 + #[serde(with = "ts_milliseconds")] + pub withdraw_period_start: DateTime, + /// An array of AdUnit (optional) + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub ad_units: Vec, + #[serde(default)] + pub targeting_rules: Rules, +} + +mod pricing { + use crate::BigNum; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] + pub struct Pricing { + pub max: BigNum, + pub min: BigNum, + } + + #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] + #[serde(rename_all = "UPPERCASE")] + pub struct PricingBounds { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub impression: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub click: Option, + } + + impl PricingBounds { + pub fn to_vec(&self) -> Vec<(&str, Pricing)> { + let mut vec = Vec::new(); + + if let Some(pricing) = self.impression.as_ref() { + vec.push(("IMPRESSION", pricing.clone())); + } + + if let Some(pricing) = self.click.as_ref() { + vec.push(("CLICK", pricing.clone())) + } + + vec + } + + pub fn get(&self, event_type: &str) -> Option<&Pricing> { + match event_type { + "IMPRESSION" => self.impression.as_ref(), + "CLICK" => self.click.as_ref(), + _ => None, + } + } + } +} +// TODO: Move SpecValidators (spec::Validators?) + +// TODO: Postgres Campaign +// TODO: Postgres CampaignSpec diff --git a/primitives/src/channel_v5.rs b/primitives/src/channel_v5.rs new file mode 100644 index 000000000..5bb40af91 --- /dev/null +++ b/primitives/src/channel_v5.rs @@ -0,0 +1,15 @@ +use serde::{Serialize, Deserialize}; + +use crate::{ValidatorId as Address, BigNum, ChannelId, ValidatorId}; +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Channel { + pub id: ChannelId, + pub leader: ValidatorId, + pub follower: ValidatorId, + pub guardian: Address, + pub token: Address, + pub nonce: BigNum, +} + +// TODO: Postgres Channel diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index fe1a6dc7a..2c923cfee 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -8,6 +8,8 @@ mod ad_unit; pub mod adapter; pub mod balances_map; pub mod big_num; +pub mod campaign; +pub mod channel_v5; pub mod channel; pub mod channel_validator; pub mod config; From a4b2ebb1d3a3ff95721bac502ab172f1a29980a3 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Fri, 12 Mar 2021 18:19:37 +0200 Subject: [PATCH 03/12] primitives - ToHex & address::Address --- primitives/src/address.rs | 125 ++++++++++++++++++++ primitives/src/lib.rs | 44 +++++-- primitives/src/validator.rs | 223 ++++++++++++++---------------------- 3 files changed, 245 insertions(+), 147 deletions(-) create mode 100644 primitives/src/address.rs diff --git a/primitives/src/address.rs b/primitives/src/address.rs new file mode 100644 index 000000000..874e1c1d6 --- /dev/null +++ b/primitives/src/address.rs @@ -0,0 +1,125 @@ +use serde::{Deserialize, Serialize, Serializer}; +use std::fmt; + +use crate::{ToHex, targeting::Value, DomainError, ToETHChecksum}; +use std::convert::TryFrom; + +#[derive(Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[serde(transparent)] +pub struct Address( + #[serde( + deserialize_with = "ser::from_str", + serialize_with = "SerHex::::serialize" + )] + [u8; 20], +); + +impl Address { + pub fn as_bytes(&self) -> &[u8; 20] { + &self.0 + } +} + +impl Serialize for Address { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let checksum = self.to_checksum(); + serializer.serialize_str(&checksum) + } +} + +impl fmt::Debug for Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Address({})", self.to_hex_prefixed()) + } +} + +impl ToETHChecksum for Address {} + +impl From<&[u8; 20]> for Address { + fn from(bytes: &[u8; 20]) -> Self { + Self(*bytes) + } +} + +impl AsRef<[u8]> for Address { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl TryFrom<&str> for Address { + type Error = DomainError; + fn try_from(value: &str) -> Result { + let hex_value = match value { + value if value.len() == 42 => Ok(&value[2..]), + value if value.len() == 40 => Ok(value), + _ => Err(DomainError::InvalidArgument( + "invalid validator id length".to_string(), + )), + }?; + + let result = hex::decode(hex_value).map_err(|_| { + DomainError::InvalidArgument("Failed to deserialize validator id".to_string()) + })?; + + if result.len() != 20 { + return Err(DomainError::InvalidArgument(format!( + "Invalid validator id value {}", + value + ))); + } + + let mut id: [u8; 20] = [0; 20]; + id.copy_from_slice(&result[..]); + Ok(Self(id)) + } +} + +impl TryFrom<&String> for Address { + type Error = DomainError; + + fn try_from(value: &String) -> Result { + Address::try_from(value.as_str()) + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_checksum()) + } +} + +impl TryFrom for Address { + type Error = DomainError; + + fn try_from(value: Value) -> Result { + let string = value.try_string().map_err(|err| { + DomainError::InvalidArgument(format!("Value is not a string: {}", err)) + })?; + + Self::try_from(&string) + } +} + + +mod ser { + use hex::FromHex; + use serde::{Deserialize, Deserializer}; + + pub(super) fn from_str<'de, D>(deserializer: D) -> Result<[u8; 20], D::Error> + where + D: Deserializer<'de>, + { + let validator_id = String::deserialize(deserializer)?; + if validator_id.is_empty() || validator_id.len() != 42 { + return Err(serde::de::Error::custom( + "invalid validator id length".to_string(), + )); + } + + <[u8; 20] as FromHex>::from_hex(&validator_id[2..]).map_err(serde::de::Error::custom) + } +} diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 2c923cfee..76b6e77aa 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -1,10 +1,22 @@ #![deny(rust_2018_idioms)] #![deny(clippy::all)] -use std::error; -use std::fmt; +use std::{error, fmt}; +pub use self::{ + address::Address, + ad_slot::AdSlot, + ad_unit::AdUnit, + balances_map::BalancesMap, + big_num::BigNum, + channel::{Channel, ChannelId, ChannelSpec, SpecValidator, SpecValidators}, + config::Config, + event_submission::EventSubmission, + ipfs::IPFS, + validator::{ValidatorDesc, ValidatorId}, +}; mod ad_slot; mod ad_unit; +pub mod address; pub mod adapter; pub mod balances_map; pub mod big_num; @@ -44,16 +56,6 @@ pub mod analytics; mod eth_checksum; pub mod validator; -pub use self::ad_slot::AdSlot; -pub use self::ad_unit::AdUnit; -pub use self::balances_map::BalancesMap; -pub use self::big_num::BigNum; -pub use self::channel::{Channel, ChannelId, ChannelSpec, SpecValidator, SpecValidators}; -pub use self::config::Config; -pub use self::event_submission::EventSubmission; -pub use self::ipfs::IPFS; -pub use self::validator::{ValidatorDesc, ValidatorId}; - #[derive(Debug, PartialEq, Eq)] pub enum DomainError { InvalidArgument(String), @@ -84,3 +86,21 @@ pub trait ToETHChecksum: AsRef<[u8]> { } impl ToETHChecksum for &[u8; 20] {} + +pub trait ToHex { + // Hex encoded `String`, **without** __Checksum__ming the string + fn to_hex(&self) -> String; + + // Hex encoded `0x` prefixed `String`, **without** __Checksum__ming the string + fn to_hex_prefixed(&self) -> String; +} + +impl> ToHex for T { + fn to_hex(&self) -> String { + hex::encode(self.as_ref()) + } + + fn to_hex_prefixed(&self) -> String { + format!("0x{}", self.as_ref().to_hex()) + } +} diff --git a/primitives/src/validator.rs b/primitives/src/validator.rs index 0024665d9..791415725 100644 --- a/primitives/src/validator.rs +++ b/primitives/src/validator.rs @@ -1,106 +1,58 @@ -use chrono::{DateTime, Utc}; -use hex::FromHex; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt; +use serde::{Deserialize, Serialize}; +use std::{convert::TryFrom, fmt}; -use crate::{targeting::Value, BalancesMap, BigNum, DomainError, ToETHChecksum}; -use std::convert::TryFrom; +use crate::{targeting::Value, Address, BigNum, DomainError, ToETHChecksum, ToHex}; -#[derive(Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub use messages::*; + +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[serde(transparent)] -pub struct ValidatorId( - #[serde( - deserialize_with = "validator_id_from_str", - serialize_with = "SerHex::::serialize" - )] - [u8; 20], -); +pub struct ValidatorId(Address); impl fmt::Debug for ValidatorId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ValidatorId({})", self.to_hex_prefix_string()) - } -} - -fn validator_id_from_str<'de, D>(deserializer: D) -> Result<[u8; 20], D::Error> -where - D: Deserializer<'de>, -{ - let validator_id = String::deserialize(deserializer)?; - if validator_id.is_empty() || validator_id.len() != 42 { - return Err(serde::de::Error::custom( - "invalid validator id length".to_string(), - )); + write!(f, "ValidatorId({})", self.to_hex_prefixed()) } - - <[u8; 20] as FromHex>::from_hex(&validator_id[2..]).map_err(serde::de::Error::custom) } impl ValidatorId { pub fn inner(&self) -> &[u8; 20] { - &self.0 + &self.0.as_bytes() } /// To Hex non-`0x` prefixed string without **Checksum**ing the string + /// For backwards compatibility + /// TODO: Remove once we change all places this method is used at pub fn to_hex_non_prefix_string(&self) -> String { - hex::encode(self.0) + self.0.to_hex() } /// To Hex `0x` prefixed string **without** __Checksum__ing the string + /// For backwards compatibility + /// TODO: Remove once we change all places this method is used at pub fn to_hex_prefix_string(&self) -> String { - format!("0x{}", self.to_hex_non_prefix_string()) + self.0.to_hex_prefixed() } } impl ToETHChecksum for ValidatorId {} -impl Serialize for ValidatorId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let checksum = self.to_checksum(); - serializer.serialize_str(&checksum) - } -} - impl From<&[u8; 20]> for ValidatorId { fn from(bytes: &[u8; 20]) -> Self { - Self(*bytes) + Self(Address::from(bytes)) } } impl AsRef<[u8]> for ValidatorId { fn as_ref(&self) -> &[u8] { - &self.0 + &self.0.as_ref() } } impl TryFrom<&str> for ValidatorId { type Error = DomainError; fn try_from(value: &str) -> Result { - let hex_value = match value { - value if value.len() == 42 => Ok(&value[2..]), - value if value.len() == 40 => Ok(value), - _ => Err(DomainError::InvalidArgument( - "invalid validator id length".to_string(), - )), - }?; - - let result = hex::decode(hex_value).map_err(|_| { - DomainError::InvalidArgument("Failed to deserialize validator id".to_string()) - })?; - - if result.len() != 20 { - return Err(DomainError::InvalidArgument(format!( - "Invalid validator id value {}", - value - ))); - } - - let mut id: [u8; 20] = [0; 20]; - id.copy_from_slice(&result[..]); - Ok(Self(id)) + Address::try_from(value).map(Self) } } @@ -108,7 +60,7 @@ impl TryFrom<&String> for ValidatorId { type Error = DomainError; fn try_from(value: &String) -> Result { - ValidatorId::try_from(value.as_str()) + Address::try_from(value).map(Self) } } @@ -122,11 +74,7 @@ impl TryFrom for ValidatorId { type Error = DomainError; fn try_from(value: Value) -> Result { - let string = value.try_string().map_err(|err| { - DomainError::InvalidArgument(format!("Value is not a string: {}", err)) - })?; - - Self::try_from(&string) + Address::try_from(value).map(Self) } } @@ -142,73 +90,78 @@ pub struct ValidatorDesc { // Validator Message Types -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Accounting { - #[serde(rename = "lastEvAggr")] - pub last_event_aggregate: DateTime, - pub balances_before_fees: BalancesMap, - pub balances: BalancesMap, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct ApproveState { - pub state_root: String, - pub signature: String, - pub is_healthy: bool, - #[serde(default)] - pub exhausted: bool, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct NewState { - pub state_root: String, - pub signature: String, - pub balances: BalancesMap, - #[serde(default)] - pub exhausted: bool, -} - -#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct RejectState { - pub reason: String, - pub state_root: String, - pub signature: String, - pub balances: Option, - pub timestamp: Option>, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Heartbeat { - pub signature: String, - pub state_root: String, - pub timestamp: DateTime, -} - -impl Heartbeat { - pub fn new(signature: String, state_root: String) -> Self { - Self { - signature, - state_root, - timestamp: Utc::now(), +mod messages { + use chrono::{DateTime, Utc}; + use serde::{Serialize, Deserialize}; + use crate::BalancesMap; + + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + pub struct Accounting { + #[serde(rename = "lastEvAggr")] + pub last_event_aggregate: DateTime, + pub balances_before_fees: BalancesMap, + pub balances: BalancesMap, + } + + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + pub struct ApproveState { + pub state_root: String, + pub signature: String, + pub is_healthy: bool, + #[serde(default)] + pub exhausted: bool, + } + + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + pub struct NewState { + pub state_root: String, + pub signature: String, + pub balances: BalancesMap, + #[serde(default)] + pub exhausted: bool, + } + + #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + pub struct RejectState { + pub reason: String, + pub state_root: String, + pub signature: String, + pub balances: Option, + pub timestamp: Option>, + } + + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + pub struct Heartbeat { + pub signature: String, + pub state_root: String, + pub timestamp: DateTime, + } + + impl Heartbeat { + pub fn new(signature: String, state_root: String) -> Self { + Self { + signature, + state_root, + timestamp: Utc::now(), + } } } -} -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(tag = "type")] -pub enum MessageTypes { - ApproveState(ApproveState), - NewState(NewState), - RejectState(RejectState), - Heartbeat(Heartbeat), - Accounting(Accounting), + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] + #[serde(tag = "type")] + pub enum MessageTypes { + ApproveState(ApproveState), + NewState(NewState), + RejectState(RejectState), + Heartbeat(Heartbeat), + Accounting(Accounting), + } } - #[cfg(feature = "postgres")] pub mod postgres { use super::ValidatorId; From e5ed95967a96b2e7a79cf4126c9edb10b932fc11 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Mon, 15 Mar 2021 11:34:41 +0200 Subject: [PATCH 04/12] primitives - Address - improvements --- primitives/src/address.rs | 106 ++++++++++++++++++++---------------- primitives/src/validator.rs | 10 ++-- 2 files changed, 65 insertions(+), 51 deletions(-) diff --git a/primitives/src/address.rs b/primitives/src/address.rs index 874e1c1d6..dba0c1fe3 100644 --- a/primitives/src/address.rs +++ b/primitives/src/address.rs @@ -1,14 +1,25 @@ +use hex::{FromHex, FromHexError}; use serde::{Deserialize, Serialize, Serializer}; -use std::fmt; - -use crate::{ToHex, targeting::Value, DomainError, ToETHChecksum}; -use std::convert::TryFrom; +use std::{convert::TryFrom, fmt}; +use thiserror::Error; + +use crate::{targeting::Value, DomainError, ToETHChecksum, ToHex}; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Expected prefix `0x`")] + BadPrefix, + #[error("Expected length of 40 without or 42 with a `0x` prefix")] + Length, + #[error("Invalid hex")] + Hex(#[from] FromHexError), +} #[derive(Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[serde(transparent)] pub struct Address( #[serde( - deserialize_with = "ser::from_str", + deserialize_with = "de::from_bytes_insensitive", serialize_with = "SerHex::::serialize" )] [u8; 20], @@ -30,6 +41,12 @@ impl Serialize for Address { } } +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_checksum()) + } +} + impl fmt::Debug for Address { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Address({})", self.to_hex_prefixed()) @@ -51,44 +68,18 @@ impl AsRef<[u8]> for Address { } impl TryFrom<&str> for Address { - type Error = DomainError; - fn try_from(value: &str) -> Result { - let hex_value = match value { - value if value.len() == 42 => Ok(&value[2..]), - value if value.len() == 40 => Ok(value), - _ => Err(DomainError::InvalidArgument( - "invalid validator id length".to_string(), - )), - }?; - - let result = hex::decode(hex_value).map_err(|_| { - DomainError::InvalidArgument("Failed to deserialize validator id".to_string()) - })?; + type Error = Error; - if result.len() != 20 { - return Err(DomainError::InvalidArgument(format!( - "Invalid validator id value {}", - value - ))); - } - - let mut id: [u8; 20] = [0; 20]; - id.copy_from_slice(&result[..]); - Ok(Self(id)) + fn try_from(value: &str) -> Result { + Ok(Self(from_bytes(value, Prefix::Insensitive)?)) } } impl TryFrom<&String> for Address { - type Error = DomainError; + type Error = Error; fn try_from(value: &String) -> Result { - Address::try_from(value.as_str()) - } -} - -impl fmt::Display for Address { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_checksum()) + Self::try_from(value.as_str()) } } @@ -100,26 +91,47 @@ impl TryFrom for Address { DomainError::InvalidArgument(format!("Value is not a string: {}", err)) })?; - Self::try_from(&string) + Self::try_from(&string).map_err(|err| DomainError::InvalidArgument(err.to_string())) } } - -mod ser { - use hex::FromHex; +mod de { + use super::{from_bytes, Prefix}; use serde::{Deserialize, Deserializer}; - pub(super) fn from_str<'de, D>(deserializer: D) -> Result<[u8; 20], D::Error> + /// Deserializes the bytes with our without a `0x` prefix (insensitive) + pub(super) fn from_bytes_insensitive<'de, D>(deserializer: D) -> Result<[u8; 20], D::Error> where D: Deserializer<'de>, { let validator_id = String::deserialize(deserializer)?; - if validator_id.is_empty() || validator_id.len() != 42 { - return Err(serde::de::Error::custom( - "invalid validator id length".to_string(), - )); - } - <[u8; 20] as FromHex>::from_hex(&validator_id[2..]).map_err(serde::de::Error::custom) + from_bytes(validator_id, Prefix::Insensitive).map_err(serde::de::Error::custom) + } +} + +pub enum Prefix { + // with `0x` prefix + With, + // without `0x` prefix + Without, + /// Insensitive to a `0x` prefixed, it allows values with or without a prefix + Insensitive, +} + +pub fn from_bytes>(from: T, prefix: Prefix) -> Result<[u8; 20], Error> { + let bytes = from.as_ref(); + + let from_hex = + |hex_bytes: &[u8]| <[u8; 20] as FromHex>::from_hex(hex_bytes).map_err(Error::Hex); + + // this length check guards against `panic!` when we call `slice.split_at()` + match (prefix, bytes.len()) { + (Prefix::With, 42) | (Prefix::Insensitive, 42) => match bytes.split_at(2) { + (b"0x", hex_bytes) => from_hex(hex_bytes), + _ => Err(Error::BadPrefix), + }, + (Prefix::Without, 40) | (Prefix::Insensitive, 40) => from_hex(bytes), + _ => Err(Error::Length), } } diff --git a/primitives/src/validator.rs b/primitives/src/validator.rs index 791415725..75fdafa47 100644 --- a/primitives/src/validator.rs +++ b/primitives/src/validator.rs @@ -52,7 +52,9 @@ impl AsRef<[u8]> for ValidatorId { impl TryFrom<&str> for ValidatorId { type Error = DomainError; fn try_from(value: &str) -> Result { - Address::try_from(value).map(Self) + Address::try_from(value) + .map_err(|err| DomainError::InvalidArgument(err.to_string())) + .map(Self) } } @@ -60,7 +62,7 @@ impl TryFrom<&String> for ValidatorId { type Error = DomainError; fn try_from(value: &String) -> Result { - Address::try_from(value).map(Self) + Self::try_from(value.as_str()) } } @@ -91,9 +93,9 @@ pub struct ValidatorDesc { // Validator Message Types mod messages { - use chrono::{DateTime, Utc}; - use serde::{Serialize, Deserialize}; use crate::BalancesMap; + use chrono::{DateTime, Utc}; + use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] From fc5f7e9b6fd25cd96fcd74290e48ce830306eaf2 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Mon, 15 Mar 2021 11:44:12 +0200 Subject: [PATCH 05/12] primitives - channel v5 - use Address --- primitives/src/channel_v5.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/primitives/src/channel_v5.rs b/primitives/src/channel_v5.rs index 5bb40af91..dbdc234a9 100644 --- a/primitives/src/channel_v5.rs +++ b/primitives/src/channel_v5.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; -use crate::{ValidatorId as Address, BigNum, ChannelId, ValidatorId}; +use crate::{BigNum, ChannelId, ValidatorId, Address}; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Channel { From 4a803abd97b41f0cc6c63e9499609ae85bd5cb59 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Mon, 15 Mar 2021 13:15:50 +0200 Subject: [PATCH 06/12] primitives - campaign - spec Validators and matching --- primitives/src/campaign.rs | 148 +++++++++++++++++++++++++++++++++++-- 1 file changed, 143 insertions(+), 5 deletions(-) diff --git a/primitives/src/campaign.rs b/primitives/src/campaign.rs index 0d671ca94..7ce2b0335 100644 --- a/primitives/src/campaign.rs +++ b/primitives/src/campaign.rs @@ -1,4 +1,6 @@ -use crate::{channel_v5::Channel, targeting::Rules, AdUnit, BigNum, EventSubmission, SpecValidators}; +use crate::{ + channel_v5::Channel, targeting::Rules, AdUnit, BigNum, EventSubmission, ValidatorDesc, +}; use chrono::{ serde::{ts_milliseconds, ts_milliseconds_option}, @@ -7,18 +9,41 @@ use chrono::{ use serde::{Deserialize, Serialize}; pub use pricing::{Pricing, PricingBounds}; +pub use spec::{ValidatorRole, Validators}; #[derive(Debug, Serialize, Deserialize)] pub struct Campaign { - channel: Channel, - spec: CampaignSpec, + pub channel: Channel, + pub spec: CampaignSpec, +} + +impl Campaign { + /// Matches the Channel.leader to the Campaign.spec.leader + /// If they match it returns `Some`, otherwise, it returns `None` + pub fn leader<'a>(&'a self) -> Option<&'a ValidatorDesc> { + if self.channel.leader == self.spec.validators.leader().id { + Some(self.spec.validators.leader()) + } else { + None + } + } + + /// Matches the Channel.follower to the Campaign.spec.follower + /// If they match it returns `Some`, otherwise, it returns `None` + pub fn follower<'a>(&'a self) -> Option<&'a ValidatorDesc> { + if self.channel.follower == self.spec.validators.follower().id { + Some(self.spec.validators.follower()) + } else { + None + } + } } #[derive(Debug, Serialize, Deserialize)] pub struct CampaignSpec { #[serde(default, skip_serializing_if = "Option::is_none")] pub title: Option, - pub validators: SpecValidators, + pub validators: Validators, /// Event pricing bounds #[serde(default, skip_serializing_if = "Option::is_none")] pub pricing_bounds: Option, @@ -95,7 +120,120 @@ mod pricing { } } } -// TODO: Move SpecValidators (spec::Validators?) +// TODO: Double check if we require all the methods and enums, as some parts are now in the `Campaign` +// This includes the matching of the Channel leader & follower to the Spec Validators +pub mod spec { + use crate::{ValidatorDesc, ValidatorId}; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] + /// A (leader, follower) tuple + pub struct Validators(ValidatorDesc, ValidatorDesc); + + #[derive(Debug)] + pub enum ValidatorRole<'a> { + Leader(&'a ValidatorDesc), + Follower(&'a ValidatorDesc), + } + + impl<'a> ValidatorRole<'a> { + pub fn validator(&self) -> &'a ValidatorDesc { + match self { + ValidatorRole::Leader(validator) => validator, + ValidatorRole::Follower(validator) => validator, + } + } + } + + impl Validators { + pub fn new(leader: ValidatorDesc, follower: ValidatorDesc) -> Self { + Self(leader, follower) + } + + pub fn leader(&self) -> &ValidatorDesc { + &self.0 + } + + pub fn follower(&self) -> &ValidatorDesc { + &self.1 + } + + pub fn find(&self, validator_id: &ValidatorId) -> Option> { + if &self.leader().id == validator_id { + Some(ValidatorRole::Leader(&self.leader())) + } else if &self.follower().id == validator_id { + Some(ValidatorRole::Follower(&self.follower())) + } else { + None + } + } + + pub fn find_index(&self, validator_id: &ValidatorId) -> Option { + if &self.leader().id == validator_id { + Some(0) + } else if &self.follower().id == validator_id { + Some(1) + } else { + None + } + } + + pub fn iter(&self) -> Iter<'_> { + Iter::new(&self) + } + } + + impl From<(ValidatorDesc, ValidatorDesc)> for Validators { + fn from((leader, follower): (ValidatorDesc, ValidatorDesc)) -> Self { + Self(leader, follower) + } + } + + /// Fixed size iterator of 2, as we need an iterator in couple of occasions + impl<'a> IntoIterator for &'a Validators { + type Item = &'a ValidatorDesc; + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } + } + + pub struct Iter<'a> { + validators: &'a Validators, + index: u8, + } + + impl<'a> Iter<'a> { + fn new(validators: &'a Validators) -> Self { + Self { + validators, + index: 0, + } + } + } + + impl<'a> Iterator for Iter<'a> { + type Item = &'a ValidatorDesc; + + fn next(&mut self) -> Option { + match self.index { + 0 => { + self.index += 1; + + Some(self.validators.leader()) + } + 1 => { + self.index += 1; + + Some(self.validators.follower()) + } + _ => None, + } + } + } +} // TODO: Postgres Campaign // TODO: Postgres CampaignSpec +// TODO: Postgres Validators From fa4f084ec52fb30b66b56cb7f8c20830973caedf Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Mon, 15 Mar 2021 13:26:22 +0200 Subject: [PATCH 07/12] rustfmt --- primitives/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 76b6e77aa..4e202f2ca 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -1,10 +1,9 @@ #![deny(rust_2018_idioms)] #![deny(clippy::all)] -use std::{error, fmt}; pub use self::{ - address::Address, ad_slot::AdSlot, ad_unit::AdUnit, + address::Address, balances_map::BalancesMap, big_num::BigNum, channel::{Channel, ChannelId, ChannelSpec, SpecValidator, SpecValidators}, @@ -13,16 +12,17 @@ pub use self::{ ipfs::IPFS, validator::{ValidatorDesc, ValidatorId}, }; +use std::{error, fmt}; mod ad_slot; mod ad_unit; -pub mod address; pub mod adapter; +pub mod address; pub mod balances_map; pub mod big_num; pub mod campaign; -pub mod channel_v5; pub mod channel; +pub mod channel_v5; pub mod channel_validator; pub mod config; pub mod event_submission; From defa9ab485ef54edfd63b2b841d2ad8dee763974 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Tue, 16 Mar 2021 11:38:01 +0200 Subject: [PATCH 08/12] primitives - campaign - merge spec and change fields --- primitives/Cargo.toml | 2 +- primitives/src/campaign.rs | 86 +++++++++++++++++++------------------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 5c4c4758c..34e57549d 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -13,7 +13,7 @@ serde = { version = "^1.0", features = ['derive'] } serde_json = "1.0" serde-hex = "0.1.0" serde_millis = "0.1.1" -# Used prefixes on field for targeting::Input +# Used prefixes on field for targeting::Input, and `campaign::Active` serde_with = "1.6" # Configuration toml = "0.5" diff --git a/primitives/src/campaign.rs b/primitives/src/campaign.rs index 7ce2b0335..30acb3baa 100644 --- a/primitives/src/campaign.rs +++ b/primitives/src/campaign.rs @@ -1,5 +1,5 @@ use crate::{ - channel_v5::Channel, targeting::Rules, AdUnit, BigNum, EventSubmission, ValidatorDesc, + channel_v5::Channel, targeting::Rules, AdUnit, Address, EventSubmission, ValidatorDesc, }; use chrono::{ @@ -7,74 +7,76 @@ use chrono::{ DateTime, Utc, }; use serde::{Deserialize, Serialize}; +use serde_with::with_prefix; pub use pricing::{Pricing, PricingBounds}; -pub use spec::{ValidatorRole, Validators}; +pub use validators::{ValidatorRole, Validators}; + +with_prefix!(prefix_active "active_"); #[derive(Debug, Serialize, Deserialize)] pub struct Campaign { pub channel: Channel, - pub spec: CampaignSpec, -} - -impl Campaign { - /// Matches the Channel.leader to the Campaign.spec.leader - /// If they match it returns `Some`, otherwise, it returns `None` - pub fn leader<'a>(&'a self) -> Option<&'a ValidatorDesc> { - if self.channel.leader == self.spec.validators.leader().id { - Some(self.spec.validators.leader()) - } else { - None - } - } - - /// Matches the Channel.follower to the Campaign.spec.follower - /// If they match it returns `Some`, otherwise, it returns `None` - pub fn follower<'a>(&'a self) -> Option<&'a ValidatorDesc> { - if self.channel.follower == self.spec.validators.follower().id { - Some(self.spec.validators.follower()) - } else { - None - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct CampaignSpec { + pub creator: Address, + pub validators: Validators, #[serde(default, skip_serializing_if = "Option::is_none")] pub title: Option, - pub validators: Validators, /// Event pricing bounds #[serde(default, skip_serializing_if = "Option::is_none")] pub pricing_bounds: Option, /// EventSubmission object, applies to event submission (POST /channel/:id/events) #[serde(default, skip_serializing_if = "Option::is_none")] pub event_submission: Option, + /// An array of AdUnit (optional) + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub ad_units: Vec, + #[serde(default)] + pub targeting_rules: Rules, /// A millisecond timestamp of when the campaign was created #[serde(with = "ts_milliseconds")] pub created: DateTime, /// A millisecond timestamp representing the time you want this campaign to become active (optional) /// Used by the AdViewManager & Targeting AIP#31 + #[serde(flatten, with = "prefix_active")] + pub active: Active, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Active { #[serde( default, skip_serializing_if = "Option::is_none", with = "ts_milliseconds_option" )] - pub active_from: Option>, - /// A random number to ensure the campaignSpec hash is unique - #[serde(default, skip_serializing_if = "Option::is_none")] - pub nonce: Option, + pub from: Option>, /// A millisecond timestamp of when the campaign should enter a withdraw period /// (no longer accept any events other than CHANNEL_CLOSE) /// A sane value should be lower than channel.validUntil * 1000 and higher than created /// It's recommended to set this at least one month prior to channel.validUntil * 1000 #[serde(with = "ts_milliseconds")] - pub withdraw_period_start: DateTime, - /// An array of AdUnit (optional) - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub ad_units: Vec, - #[serde(default)] - pub targeting_rules: Rules, + pub active_to: DateTime, +} + +impl Campaign { + /// Matches the Channel.leader to the Campaign.spec.leader + /// If they match it returns `Some`, otherwise, it returns `None` + pub fn leader<'a>(&'a self) -> Option<&'a ValidatorDesc> { + if self.channel.leader == self.validators.leader().id { + Some(self.validators.leader()) + } else { + None + } + } + + /// Matches the Channel.follower to the Campaign.spec.follower + /// If they match it returns `Some`, otherwise, it returns `None` + pub fn follower<'a>(&'a self) -> Option<&'a ValidatorDesc> { + if self.channel.follower == self.validators.follower().id { + Some(self.validators.follower()) + } else { + None + } + } } mod pricing { @@ -121,8 +123,8 @@ mod pricing { } } // TODO: Double check if we require all the methods and enums, as some parts are now in the `Campaign` -// This includes the matching of the Channel leader & follower to the Spec Validators -pub mod spec { +// This includes the matching of the Channel leader & follower to the Validators +pub mod validators { use crate::{ValidatorDesc, ValidatorId}; use serde::{Deserialize, Serialize}; From 9ae51cc0e31f65ab5761cdd526ef36ce98f32033 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Tue, 16 Mar 2021 15:05:48 +0200 Subject: [PATCH 09/12] primitives - Cargo - update deps and remove duplicates --- Cargo.lock | 22 +++++++++++----------- primitives/Cargo.toml | 8 +++----- primitives/src/campaign.rs | 4 ++-- primitives/src/eth_checksum.rs | 32 ++++++++++++++++++++++++++++---- primitives/src/merkle_tree.rs | 14 +++++++------- validator_worker/Cargo.toml | 2 +- 6 files changed, 52 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28b7bdb5d..a669e5708 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2232,9 +2232,9 @@ dependencies = [ [[package]] name = "num" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-bigint", "num-complex", @@ -2246,9 +2246,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf" +checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" dependencies = [ "autocfg 1.0.1", "num-integer", @@ -2258,11 +2258,12 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" dependencies = [ "num-traits", + "serde", ] [[package]] @@ -2299,14 +2300,15 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ "autocfg 1.0.1", "num-bigint", "num-integer", "num-traits", + "serde", ] [[package]] @@ -2766,14 +2768,12 @@ dependencies = [ "lazy_static", "merkletree", "num", - "num-bigint", "num-derive", "num-traits", "parse-display", "postgres-types 0.2.0", "pretty_assertions", "rand 0.8.2", - "rust-crypto", "serde", "serde-hex", "serde_json", @@ -2784,7 +2784,7 @@ dependencies = [ "slog-term", "thiserror", "time 0.1.43", - "tiny-keccak 1.5.0", + "tiny-keccak 2.0.2", "tokio-postgres 0.7.0", "toml", "url", diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 34e57549d..136de67bd 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -31,12 +31,10 @@ parse-display = "^0.4.1" cid = "0.6" hex = "0.4" merkletree = "0.10.0" -tiny-keccak = "1.5" -rust-crypto = "0.2" -url = { version = "=2.2", features = ["serde"]} +tiny-keccak = { version = "^2.0", features = ["keccak"] } +url = { version = "=2.2", features = ["serde"] } # Numbers - BigNum, Numbers, Traits and Derives -num-bigint = { version = "^0.3", features = ["serde"] } -num = "0.3" +num = { version = "0.4", features = ["serde", "num-bigint"] } num-traits = "0.2" num-derive = "0.3" # Fixtures diff --git a/primitives/src/campaign.rs b/primitives/src/campaign.rs index 30acb3baa..3fcbecaeb 100644 --- a/primitives/src/campaign.rs +++ b/primitives/src/campaign.rs @@ -60,7 +60,7 @@ pub struct Active { impl Campaign { /// Matches the Channel.leader to the Campaign.spec.leader /// If they match it returns `Some`, otherwise, it returns `None` - pub fn leader<'a>(&'a self) -> Option<&'a ValidatorDesc> { + pub fn leader(&self) -> Option<&'_ ValidatorDesc> { if self.channel.leader == self.validators.leader().id { Some(self.validators.leader()) } else { @@ -70,7 +70,7 @@ impl Campaign { /// Matches the Channel.follower to the Campaign.spec.follower /// If they match it returns `Some`, otherwise, it returns `None` - pub fn follower<'a>(&'a self) -> Option<&'a ValidatorDesc> { + pub fn follower(&self) -> Option<&'_ ValidatorDesc> { if self.channel.follower == self.validators.follower().id { Some(self.validators.follower()) } else { diff --git a/primitives/src/eth_checksum.rs b/primitives/src/eth_checksum.rs index a23d05d20..20c204e73 100644 --- a/primitives/src/eth_checksum.rs +++ b/primitives/src/eth_checksum.rs @@ -1,12 +1,16 @@ -use crypto::{digest::Digest, sha3::Sha3}; +use tiny_keccak::{Hasher, Keccak}; pub fn checksum(address: &str) -> String { let address = address.trim_start_matches("0x").to_lowercase(); let address_hash = { - let mut hasher = Sha3::keccak256(); - hasher.input(address.as_bytes()); - hasher.result_str() + let mut hasher = Keccak::v256(); + let mut result: [u8; 32] = [0; 32]; + + hasher.update(address.as_bytes()); + hasher.finalize(&mut result); + + hex::encode(result) }; address @@ -26,3 +30,23 @@ pub fn checksum(address: &str) -> String { acc }) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn it_checksums() { + let expected_checksum = "0xce07CbB7e054514D590a0262C93070D838bFBA2e"; + + let non_checksummed = expected_checksum.to_lowercase(); + + assert_eq!(expected_checksum, checksum(&non_checksummed)); + + let non_prefixed = non_checksummed + .strip_prefix("0x") + .expect("should have prefix"); + + assert_eq!(expected_checksum, checksum(&non_prefixed)) + } +} diff --git a/primitives/src/merkle_tree.rs b/primitives/src/merkle_tree.rs index 0788560f6..2bcac4a74 100644 --- a/primitives/src/merkle_tree.rs +++ b/primitives/src/merkle_tree.rs @@ -1,9 +1,7 @@ use merkletree::{hash::Algorithm, merkle, merkle::VecStore, proof::Proof}; -use std::fmt; -use std::hash::Hasher; -use std::iter::FromIterator; +use std::{fmt, iter::FromIterator}; use thiserror::Error; -use tiny_keccak::Keccak; +use tiny_keccak::{Hasher, Keccak}; #[derive(Clone)] struct KeccakAlgorithm(Keccak); @@ -16,7 +14,7 @@ impl fmt::Debug for KeccakAlgorithm { impl KeccakAlgorithm { pub fn new() -> KeccakAlgorithm { - KeccakAlgorithm(Keccak::new_keccak256()) + KeccakAlgorithm(Keccak::v256()) } } @@ -26,7 +24,7 @@ impl Default for KeccakAlgorithm { } } -impl Hasher for KeccakAlgorithm { +impl std::hash::Hasher for KeccakAlgorithm { #[inline] fn write(&mut self, msg: &[u8]) { self.0.update(msg) @@ -50,7 +48,7 @@ impl Algorithm for KeccakAlgorithm { #[inline] fn reset(&mut self) { - self.0 = Keccak::new_keccak256() + self.0 = Keccak::v256() } fn leaf(&mut self, leaf: MerkleItem) -> MerkleItem { @@ -58,6 +56,8 @@ impl Algorithm for KeccakAlgorithm { } fn node(&mut self, left: MerkleItem, right: MerkleItem, _height: usize) -> MerkleItem { + use std::hash::Hasher; + // This is a check for odd number of leaves items // left == right since the right is a duplicate of left // return the item unencoded as the JS impl diff --git a/validator_worker/Cargo.toml b/validator_worker/Cargo.toml index 0291e2a3c..8111aa359 100644 --- a/validator_worker/Cargo.toml +++ b/validator_worker/Cargo.toml @@ -13,7 +13,7 @@ path = "src/lib.rs" primitives = { path = "../primitives" } adapter = { version = "0.1", path = "../adapter" } chrono = { version = "0.4", features = ["serde"] } -num = "0.3" +num = "0.4" num-traits = "0.2" # To/From Hex hex = "0.4" From 98790b582d37aa2e69f357b1cf4c23f0d464ac29 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Tue, 16 Mar 2021 17:46:23 +0200 Subject: [PATCH 10/12] primitives - channel_v5 - `Nonce` & Channel `fn id()` --- Cargo.lock | 117 +++++++++++++++++++++++++++++++++-- primitives/Cargo.toml | 4 ++ primitives/src/channel_v5.rs | 111 ++++++++++++++++++++++++++++++++- 3 files changed, 223 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a669e5708..67d278194 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -407,7 +407,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" dependencies = [ "either", - "radium", + "radium 0.3.0", +] + +[[package]] +name = "bitvec" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f682656975d3a682daff957be4ddeb65d6ad656737cd821f2d00685ae466af1" +dependencies = [ + "funty", + "radium 0.6.2", + "tap", + "wyz", ] [[package]] @@ -539,6 +551,12 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" +[[package]] +name = "byte-slice-cast" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c1bf4a04a88c54f589125563643d773f3254b5c38571395e2b591c693bbc81" + [[package]] name = "byte-tools" version = "0.2.0" @@ -1099,6 +1117,19 @@ dependencies = [ "tiny-keccak 2.0.2", ] +[[package]] +name = "ethbloom" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779864b9c7f7ead1f092972c3257496c6a84b46dba2ce131dd8a282cb2cc5972" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash 0.7.0", + "impl-rlp", + "impl-serde", + "tiny-keccak 2.0.2", +] + [[package]] name = "ethereum-types" version = "0.4.2" @@ -1123,7 +1154,21 @@ dependencies = [ "fixed-hash 0.7.0", "impl-rlp", "impl-serde", - "primitive-types", + "primitive-types 0.8.0", + "uint 0.9.0", +] + +[[package]] +name = "ethereum-types" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64b5df66a228d85e4b17e5d6c6aa43b0310898ffe8a85988c4c032357aaabfd" +dependencies = [ + "ethbloom 0.11.0", + "fixed-hash 0.7.0", + "impl-rlp", + "impl-serde", + "primitive-types 0.9.0", "uint 0.9.0", ] @@ -1294,6 +1339,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "futures" version = "0.1.30" @@ -1772,7 +1823,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 1.3.6", +] + +[[package]] +name = "impl-codec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df170efa359aebdd5cb7fe78edcc67107748e4737bdca8a8fb40d15ea7a877ed" +dependencies = [ + "parity-scale-codec 2.0.1", ] [[package]] @@ -2430,8 +2490,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79602888a81ace83e3d1d4b2873286c1f5f906c84db667594e8db8da3506c383" dependencies = [ "arrayvec 0.5.2", - "bitvec", - "byte-slice-cast", + "bitvec 0.17.4", + "byte-slice-cast 0.3.5", + "serde", +] + +[[package]] +name = "parity-scale-codec" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cd3dab59b5cf4bc81069ade0fc470341a1ef3ad5fa73e5a8943bed2ec12b2e8" +dependencies = [ + "arrayvec 0.5.2", + "bitvec 0.20.2", + "byte-slice-cast 1.0.0", "serde", ] @@ -2748,7 +2820,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3824ae2c5e27160113b9e029a10ec9e3f0237bad8029f69c7724393c9fdefd8" dependencies = [ "fixed-hash 0.7.0", - "impl-codec", + "impl-codec 0.4.2", + "impl-rlp", + "impl-serde", + "uint 0.9.0", +] + +[[package]] +name = "primitive-types" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2415937401cb030a2a0a4d922483f945fa068f52a7dbb22ce0fe5f2b6f6adace" +dependencies = [ + "fixed-hash 0.7.0", + "impl-codec 0.5.0", "impl-rlp", "impl-serde", "uint 0.9.0", @@ -2762,6 +2847,8 @@ dependencies = [ "bytes 1.0.1", "chrono", "cid", + "ethabi", + "ethereum-types 0.11.0", "fake", "futures 0.3.12", "hex", @@ -2865,6 +2952,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + [[package]] name = "rand" version = "0.3.23" @@ -3843,6 +3936,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempdir" version = "0.3.7" @@ -4617,3 +4716,9 @@ dependencies = [ "winapi 0.2.8", "winapi-build", ] + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 136de67bd..7fa47099e 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -25,6 +25,10 @@ slog-async = "^2.3.0" thiserror = "^1.0" chrono = { version = "0.4", features = ["serde"] } time = "0.1.42" +# For encoding the Channel to a ChannelId +ethabi = "13.0.0" +# For the nonce U256 +ethereum-types = "0.11" # Macro for easier derive of Display & FromStr parse-display = "^0.4.1" # CID & multihash / multibase diff --git a/primitives/src/channel_v5.rs b/primitives/src/channel_v5.rs index dbdc234a9..9c6ba6259 100644 --- a/primitives/src/channel_v5.rs +++ b/primitives/src/channel_v5.rs @@ -1,15 +1,120 @@ +use ethereum_types::U256; use serde::{Deserialize, Serialize}; +use std::fmt; + +use crate::{Address, ChannelId, ValidatorId}; -use crate::{BigNum, ChannelId, ValidatorId, Address}; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Channel { - pub id: ChannelId, pub leader: ValidatorId, pub follower: ValidatorId, pub guardian: Address, pub token: Address, - pub nonce: BigNum, + pub nonce: Nonce, +} + +impl Channel { + pub fn id(&self) -> ChannelId { + use ethabi::{encode, Token}; + use tiny_keccak::{Hasher, Keccak}; + + let tokens = [ + Token::Address(self.leader.as_bytes().into()), + Token::Address(self.follower.as_bytes().into()), + Token::Address(self.guardian.as_bytes().into()), + Token::Address(self.token.as_bytes().into()), + Token::FixedBytes(self.nonce.to_bytes().to_vec()), + ]; + + let mut channel_id = [0_u8; 32]; + let mut hasher = Keccak::v256(); + hasher.update(&encode(&tokens)); + hasher.finalize(&mut channel_id); + + ChannelId::from(channel_id) + } +} + +/// The nonce is an Unsigned 256 number +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Nonce(pub U256); + +impl Nonce { + /// In Big-Endian + pub fn to_bytes(&self) -> [u8; 32] { + // the impl of From uses BigEndian + self.0.into() + } +} + +impl fmt::Display for Nonce { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0.to_string()) + } +} + +impl fmt::Debug for Nonce { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Nonce({})", self.0.to_string()) + } +} + +impl From for Nonce { + fn from(value: u64) -> Self { + Self(U256::from(value)) + } +} + +impl From for Nonce { + fn from(value: u32) -> Self { + Self(U256::from(value)) + } +} + +// The U256 implementation deserializes the value from a hex String value with a prefix `0x...` +// This is why we we need to impl it our selves +impl<'de> Deserialize<'de> for Nonce { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let string = String::deserialize(deserializer)?; + + U256::from_dec_str(&string) + .map_err(serde::de::Error::custom) + .map(Nonce) + } +} + +// The U256 implementation serializes the value as a hex String value with a prefix `0x...` +// This is why we we need to impl it our selves +impl Serialize for Nonce { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.to_string().serialize(serializer) + } +} +#[cfg(test)] +mod test { + use super::*; + use serde_json::{from_value, to_value, Value}; + + #[test] + fn de_serializes_nonce() { + let nonce_str = "12345"; + let json = Value::String(nonce_str.into()); + + let nonce: Nonce = from_value(json.clone()).expect("Should deserialize a Nonce"); + let expected_nonce = Nonce::from(12345_u64); + + assert_eq!(&expected_nonce, &nonce); + assert_eq!(json, to_value(nonce).expect("Should serialize a Nonce")); + assert_eq!(nonce_str, &nonce.to_string()); + assert_eq!("Nonce(12345)", &format!("{:?}", nonce)); + } } // TODO: Postgres Channel From 7ea437b5b1c7503a91af19b1d072e6d4b8068a2d Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Tue, 16 Mar 2021 17:46:51 +0200 Subject: [PATCH 11/12] primitives - ValidatorId to_address & as_bytes --- primitives/src/validator.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/primitives/src/validator.rs b/primitives/src/validator.rs index 75fdafa47..158024067 100644 --- a/primitives/src/validator.rs +++ b/primitives/src/validator.rs @@ -16,6 +16,14 @@ impl fmt::Debug for ValidatorId { } impl ValidatorId { + pub fn as_bytes(&self) -> &[u8; 20] { + self.0.as_bytes() + } + + pub fn to_address(&self) -> Address { + self.0 + } + pub fn inner(&self) -> &[u8; 20] { &self.0.as_bytes() } From 564ba9c94b39a8fe3bf79f53b810a8a77ba7b6e1 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Tue, 16 Mar 2021 17:49:15 +0200 Subject: [PATCH 12/12] primtivies campaign::Active - fix `to` name --- primitives/src/campaign.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/src/campaign.rs b/primitives/src/campaign.rs index 3fcbecaeb..94c4b8630 100644 --- a/primitives/src/campaign.rs +++ b/primitives/src/campaign.rs @@ -54,7 +54,7 @@ pub struct Active { /// A sane value should be lower than channel.validUntil * 1000 and higher than created /// It's recommended to set this at least one month prior to channel.validUntil * 1000 #[serde(with = "ts_milliseconds")] - pub active_to: DateTime, + pub to: DateTime, } impl Campaign {