From 7d4f484a6950b5e3f3939a1e40b82cbcfa03b77c Mon Sep 17 00:00:00 2001 From: Remoun Metyas Date: Mon, 18 Apr 2022 17:21:42 -0700 Subject: [PATCH 01/11] Restructure mc-common lib.rs --- common/src/hash.rs | 31 +++++++++++++++++++++ common/src/hasher_builder.rs | 5 ++-- common/src/lib.rs | 52 +++++++++--------------------------- common/src/node_id.rs | 5 ++-- 4 files changed, 50 insertions(+), 43 deletions(-) create mode 100644 common/src/hash.rs diff --git a/common/src/hash.rs b/common/src/hash.rs new file mode 100644 index 0000000000..c2774bd8f1 --- /dev/null +++ b/common/src/hash.rs @@ -0,0 +1,31 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +//! Hash-based containers and helpers. + +use crate::hasher_builder::HasherBuilder; + +/// A HashMap that replaces the default hasher with an implementation that +/// relies on mcrand for randomess. +/// See [hashbrown::HashMap] and [HasherBuilder] +pub type HashMap = hashbrown::HashMap; + +/// A HashSet that replaces the default hasher with an implementation that +/// relies on mcrand for randomess. +/// See [hashbrown::HashSet] and [HasherBuilder] +pub type HashSet = hashbrown::HashSet; + +/// Hash type +pub type Hash = [u8; 32]; + +/// Note: This is only used by servers, for logging (maybe to anonymize logs?) +/// Please don't use it in e.g. transaction validation math, or actual hashing +/// of the blocks in the blockchain, where you should be specific about what +/// hash you are using. +pub fn fast_hash(data: &[u8]) -> Hash { + use sha3::Digest; + + let hash = sha3::Sha3_256::digest(data); + let mut output = [0u8; 32]; + output.copy_from_slice(hash.as_slice()); + output +} diff --git a/common/src/hasher_builder.rs b/common/src/hasher_builder.rs index bfb94e2af9..a2f0f5c133 100644 --- a/common/src/hasher_builder.rs +++ b/common/src/hasher_builder.rs @@ -1,7 +1,7 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation -//! This file provides a no-std Hasher object that is wired to take seeds from -//! mcrand RdRandRng. This hasher is used in `mc_common::HashMap` in and out of +//! A no_std-compatible [BuildHasher] object that is wired to take seeds +//! from [McRng]. This hasher is used in [super::HashMap] in and out of //! the enclave. use core::hash::BuildHasher; @@ -9,6 +9,7 @@ use mc_crypto_rand::McRng; use rand_core::RngCore; use siphasher::sip::SipHasher13; +/// A no_std-compatible [BuildHasher] that is wired to take seeds from [McRng]. #[derive(Clone)] pub struct HasherBuilder { k0: u64, diff --git a/common/src/lib.rs b/common/src/lib.rs index 5fed67828c..46ce42c5fe 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -5,49 +5,23 @@ #![cfg_attr(not(any(test, feature = "std")), no_std)] #![deny(missing_docs)] #![warn(unused_extern_crates)] - extern crate alloc; -use sha3::Digest; - -mod hasher_builder; -mod node_id; -mod responder_id; - +pub mod hash; +pub mod hasher_builder; +pub mod logger; pub mod lru; -pub use lru::LruCache; - +pub mod node_id; +pub mod responder_id; pub mod time; -pub use node_id::NodeID; -pub use responder_id::{ResponderId, ResponderIdParseError}; - -/// A HashMap that replaces the default hasher with an implementation that -/// relies on mcrand for randomess. -/// See [hashbrown::HashMap] and [hasher_builder::HasherBuilder] -pub type HashMap = hashbrown::HashMap; - -/// A HashSet that replaces the default hasher with an implementation that -/// relies on mcrand for randomess. -/// See [hashbrown::HashSet] and [hasher_builder::HasherBuilder] -pub type HashSet = hashbrown::HashSet; - -/// Hash type -pub type Hash = [u8; 32]; - -/// Note: This is only used by servers, for logging (maybe to anonymize logs?) -/// Please don't use it in e.g. transaction validation math, or actual hashing -/// of the blocks in the blockchain, where you should be specific about what -/// hash you are using. -pub fn fast_hash(data: &[u8]) -> Hash { - let hash = sha3::Sha3_256::digest(data); - let mut output = [0u8; 32]; - - output.copy_from_slice(hash.as_slice()); - output -} - -pub mod logger; +pub use crate::{ + hash::{fast_hash, Hash, HashMap, HashSet}, + hasher_builder::HasherBuilder, + lru::LruCache, + node_id::{NodeID, NodeIDError}, + responder_id::{ResponderId, ResponderIdParseError}, +}; // Loggers cfg_if::cfg_if! { @@ -56,6 +30,6 @@ cfg_if::cfg_if! { pub mod sentry; - pub use panic_handler::setup_panic_handler; + pub use crate::panic_handler::setup_panic_handler; } } diff --git a/common/src/node_id.rs b/common/src/node_id.rs index b321aaea2f..421955517d 100644 --- a/common/src/node_id.rs +++ b/common/src/node_id.rs @@ -1,8 +1,8 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation -//! The Node ID type +//! The Node ID types. -use crate::responder_id::ResponderId; +use crate::ResponderId; use binascii::ConvertError as BinConvertError; use core::{ cmp::Ordering, @@ -15,6 +15,7 @@ use mc_crypto_digestible::Digestible; use mc_crypto_keys::{Ed25519Public, KeyError}; use serde::{Deserialize, Serialize}; +/// [NodeID] errors. #[derive( Clone, Copy, Debug, Deserialize, Display, Hash, Eq, Ord, PartialEq, PartialOrd, Serialize, )] From 74203afdb48f3947c045644a3ff5533d23027fa8 Mon Sep 17 00:00:00 2001 From: Remoun Metyas Date: Thu, 21 Apr 2022 14:02:30 -0700 Subject: [PATCH 02/11] Remove unnecessary build-deps --- Cargo.lock | 24 +++++++++--------------- ledger/streaming/client/Cargo.toml | 6 ------ ledger/streaming/publisher/Cargo.toml | 6 ------ 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b62126bc3..bb72bc7c55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.5", + "getrandom 0.2.6", "once_cell", "version_check 0.9.3", ] @@ -438,7 +438,7 @@ dependencies = [ "percent-encoding 2.1.0", "regex", "ring", - "time 0.3.5", + "time 0.3.9", "tracing", ] @@ -535,7 +535,7 @@ dependencies = [ "itoa 1.0.1", "num-integer", "ryu", - "time 0.3.5", + "time 0.3.9", ] [[package]] @@ -742,9 +742,9 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b04ce3d2372d05d1ef4ea3fdf427da6ae3c17ca06d688a107b5344836276bc3" dependencies = [ - "proc-macro2 1.0.36", - "quote 1.0.15", - "syn 1.0.86", + "proc-macro2 1.0.37", + "quote 1.0.18", + "syn 1.0.91", ] [[package]] @@ -5128,7 +5128,6 @@ dependencies = [ name = "mc-ledger-streaming-client" version = "1.3.0-pre0" dependencies = [ - "cargo-emit", "displaydoc", "futures 0.3.21", "grpcio", @@ -5140,8 +5139,6 @@ dependencies = [ "mc-ledger-db", "mc-ledger-streaming-api", "mc-transaction-core", - "mc-util-build-grpc", - "mc-util-build-script", "mc-util-from-random", "mc-util-grpc", "mc-util-uri", @@ -5160,7 +5157,6 @@ version = "1.3.0-pre0" dependencies = [ "async_executors", "aws-sdk-s3", - "cargo-emit", "displaydoc", "flo_stream", "futures 0.3.21", @@ -5171,8 +5167,6 @@ dependencies = [ "mc-crypto-rand", "mc-ledger-db", "mc-ledger-streaming-api", - "mc-util-build-grpc", - "mc-util-build-script", "mc-util-grpc", "mc-util-uri", "protobuf", @@ -8648,9 +8642,9 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ - "proc-macro2 1.0.36", - "quote 1.0.15", - "syn 1.0.86", + "proc-macro2 1.0.37", + "quote 1.0.18", + "syn 1.0.91", ] [[package]] diff --git a/ledger/streaming/client/Cargo.toml b/ledger/streaming/client/Cargo.toml index f0acfe074c..45de1f86b8 100644 --- a/ledger/streaming/client/Cargo.toml +++ b/ledger/streaming/client/Cargo.toml @@ -28,12 +28,6 @@ reqwest = { version = "0.11", default-features = false, features = ["rustls-tls" tokio = "1.17.0" url = "2.2" -[build-dependencies] -mc-util-build-grpc = { path = "../../../util/build/grpc" } -mc-util-build-script = { path = "../../../util/build/script" } - -cargo-emit = "0.2" - [dev-dependencies] mc-common = { path = "../../../common", features = ["loggers"] } mc-ledger-db = { path = "../../../ledger/db", features = ["test_utils"] } diff --git a/ledger/streaming/publisher/Cargo.toml b/ledger/streaming/publisher/Cargo.toml index 845cb9ced3..f8a021e893 100644 --- a/ledger/streaming/publisher/Cargo.toml +++ b/ledger/streaming/publisher/Cargo.toml @@ -29,12 +29,6 @@ grpcio = "0.10" protobuf = "2.22" tokio = { version = "1.16", features = ["fs"], optional = true } -[build-dependencies] -mc-util-build-grpc = { path = "../../../util/build/grpc" } -mc-util-build-script = { path = "../../../util/build/script" } - -cargo-emit = "0.2" - [dev-dependencies] mc-common = { path = "../../../common", features = ["loggers"] } mc-crypto-rand = { path = "../../../crypto/rand" } From 61e410bf222afb0160020f5e1f20c39057621ff4 Mon Sep 17 00:00:00 2001 From: Remoun Metyas Date: Fri, 22 Apr 2022 13:44:31 -0700 Subject: [PATCH 03/11] Split out VerificationReport into a separate mc-attest-verifier-types crate. --- Cargo.lock | 16 ++- Cargo.toml | 1 + api/Cargo.toml | 2 +- api/src/convert/verification_report.rs | 5 +- api/src/convert/verification_signature.rs | 2 +- attest/core/Cargo.toml | 1 + attest/core/src/{ias.rs => ias/mod.rs} | 0 attest/core/src/ias/verify.rs | 123 +-------------------- attest/core/src/lib.rs | 6 +- attest/verifier/types/Cargo.toml | 16 +++ attest/verifier/types/src/lib.rs | 9 ++ attest/verifier/types/src/verification.rs | 126 ++++++++++++++++++++++ consensus/enclave/trusted/Cargo.lock | 14 +++ fog/ingest/enclave/trusted/Cargo.lock | 14 +++ fog/ledger/enclave/trusted/Cargo.lock | 14 +++ fog/view/enclave/trusted/Cargo.lock | 14 +++ 16 files changed, 235 insertions(+), 128 deletions(-) rename attest/core/src/{ias.rs => ias/mod.rs} (100%) create mode 100644 attest/verifier/types/Cargo.toml create mode 100644 attest/verifier/types/src/lib.rs create mode 100644 attest/verifier/types/src/verification.rs diff --git a/Cargo.lock b/Cargo.lock index bb72bc7c55..36949ee775 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3077,7 +3077,7 @@ dependencies = [ "displaydoc", "generic-array 0.14.5", "mc-account-keys", - "mc-attest-core", + "mc-attest-verifier-types", "mc-crypto-keys", "mc-crypto-multisig", "mc-crypto-x509-test-vectors", @@ -3158,6 +3158,7 @@ dependencies = [ "displaydoc", "hex", "hex_fmt", + "mc-attest-verifier-types", "mc-common", "mc-crypto-digestible", "mc-crypto-rand", @@ -3256,6 +3257,19 @@ dependencies = [ "sha2 0.10.2", ] +[[package]] +name = "mc-attest-verifier-types" +version = "1.3.0-pre0" +dependencies = [ + "binascii", + "displaydoc", + "hex_fmt", + "mc-crypto-digestible", + "mc-util-encodings", + "prost", + "serde", +] + [[package]] name = "mc-common" version = "1.3.0-pre0" diff --git a/Cargo.toml b/Cargo.toml index d574bb74a6..4eb69880ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "attest/trusted", "attest/untrusted", "attest/verifier", + "attest/verifier/types", "common", "connection", "connection/test-utils", diff --git a/api/Cargo.toml b/api/Cargo.toml index 92d4bbb2a9..0acb89ae6d 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -8,7 +8,7 @@ links = "mc-api" [dependencies] mc-account-keys = { path = "../account-keys" } -mc-attest-core = { path = "../attest/core" } +mc-attest-verifier-types = { path = "../attest/verifier/types" } mc-crypto-keys = { path = "../crypto/keys" } mc-crypto-multisig = { path = "../crypto/multisig" } mc-transaction-core = { path = "../transaction/core" } diff --git a/api/src/convert/verification_report.rs b/api/src/convert/verification_report.rs index 78fc598d11..e90f3e8683 100644 --- a/api/src/convert/verification_report.rs +++ b/api/src/convert/verification_report.rs @@ -3,15 +3,14 @@ //! Convert to/from external::VerificationReport use crate::external; -use mc_attest_core::{VerificationReport, VerificationSignature}; -use protobuf::RepeatedField; +use mc_attest_verifier_types::{VerificationReport, VerificationSignature}; impl From<&VerificationReport> for external::VerificationReport { fn from(src: &VerificationReport) -> Self { let mut dst = external::VerificationReport::new(); dst.set_sig((&src.sig).into()); - dst.set_chain(RepeatedField::from_slice(&src.chain)); + dst.set_chain((&src.chain[..]).into()); dst.set_http_body(src.http_body.clone()); dst } diff --git a/api/src/convert/verification_signature.rs b/api/src/convert/verification_signature.rs index 69aed07530..82f3d49293 100644 --- a/api/src/convert/verification_signature.rs +++ b/api/src/convert/verification_signature.rs @@ -3,7 +3,7 @@ //! Convert to/from external::VerificationSignature use crate::external; -use mc_attest_core::VerificationSignature; +use mc_attest_verifier_types::VerificationSignature; impl From<&VerificationSignature> for external::VerificationSignature { fn from(src: &VerificationSignature) -> Self { diff --git a/attest/core/Cargo.toml b/attest/core/Cargo.toml index d097c15728..46b9ffe31e 100644 --- a/attest/core/Cargo.toml +++ b/attest/core/Cargo.toml @@ -22,6 +22,7 @@ std = [ ] [dependencies] +mc-attest-verifier-types = { path = "../verifier/types" } mc-common = { path = "../../common", default-features = false } mc-crypto-digestible = { path = "../../crypto/digestible" } mc-crypto-rand = { path = "../../crypto/rand" } diff --git a/attest/core/src/ias.rs b/attest/core/src/ias/mod.rs similarity index 100% rename from attest/core/src/ias.rs rename to attest/core/src/ias/mod.rs diff --git a/attest/core/src/ias/verify.rs b/attest/core/src/ias/verify.rs index ea8f73f942..932cd87cd5 100644 --- a/attest/core/src/ias/verify.rs +++ b/attest/core/src/ias/verify.rs @@ -1,8 +1,6 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation -//! Attestation Verification Report handling - -use alloc::vec; +//! Verification for IAS. use super::json::JsonValue; use crate::{ @@ -16,9 +14,11 @@ use crate::{ epid_group_id::EpidGroupId, measurement::Measurement, pib::PlatformInfoBlob, report_data::ReportDataMask, }, + VerificationReport, }; use alloc::{ string::{String, ToString}, + vec, vec::Vec, }; use binascii::{b64decode, b64encode, hex2bin}; @@ -30,13 +30,7 @@ use core::{ result::Result, str, }; -use mc_crypto_digestible::Digestible; use mc_util_encodings::{Error as EncodingError, FromBase64, FromHex, ToBase64}; -use prost::{ - bytes::{Buf, BufMut}, - encoding::{self, DecodeContext, WireType}, - DecodeError, Message, -}; use serde::{Deserialize, Serialize}; // The lengths of the two EPID Pseudonym chunks @@ -458,121 +452,12 @@ impl<'src> TryFrom<&'src VerificationReport> for VerificationReportData { } } -/// A type containing the bytes of the VerificationReport signature -#[derive( - Clone, Debug, Default, Deserialize, Digestible, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, -)] -#[repr(transparent)] -pub struct VerificationSignature(#[digestible(never_omit)] Vec); - -impl AsRef<[u8]> for VerificationSignature { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl From for Vec { - fn from(src: VerificationSignature) -> Vec { - src.0 - } -} - -impl From> for VerificationSignature { - fn from(src: Vec) -> Self { - Self(src) - } -} - -impl From<&[u8]> for VerificationSignature { - fn from(src: &[u8]) -> Self { - src.to_vec().into() - } -} - -impl FromHex for VerificationSignature { - type Error = EncodingError; - - fn from_hex(s: &str) -> Result { - // base64 strlength = 4 * (bytelen / 3) + padding - let mut data = vec![0u8; 3 * ((s.len() + 4) / 4)]; - let buflen = { - let buffer = b64decode(s.as_bytes(), data.as_mut_slice())?; - buffer.len() - }; - data.truncate(buflen); - Ok(VerificationSignature::from(data)) - } -} - -const TAG_SIGNATURE_CONTENTS: u32 = 1; - -impl Message for VerificationSignature { - fn encode_raw(&self, buf: &mut B) - where - B: BufMut, - Self: Sized, - { - encoding::bytes::encode(TAG_SIGNATURE_CONTENTS, &self.0, buf); - } - - fn merge_field( - &mut self, - tag: u32, - wire_type: WireType, - buf: &mut B, - ctx: DecodeContext, - ) -> Result<(), DecodeError> - where - B: Buf, - Self: Sized, - { - if tag == TAG_SIGNATURE_CONTENTS { - encoding::bytes::merge(wire_type, &mut self.0, buf, ctx) - } else { - encoding::skip_field(wire_type, tag, buf, ctx) - } - } - - fn encoded_len(&self) -> usize { - encoding::bytes::encoded_len(TAG_SIGNATURE_CONTENTS, &self.0) - } - - fn clear(&mut self) { - self.0.clear() - } -} - -/// Container for holding the quote verification sent back from IAS. -/// -/// The fields correspond to the data sent from IAS in the -/// [Attestation Verification Report](https://software.intel.com/sites/default/files/managed/7e/3b/ias-api-spec.pdf). -/// -/// This structure is supposed to be filled in from the results of an IAS -/// web request and then validated directly or serialized into an enclave for -/// validation. -#[derive( - Clone, Deserialize, Digestible, Eq, Hash, Message, Ord, PartialEq, PartialOrd, Serialize, -)] -pub struct VerificationReport { - /// Report Signature bytes, from the X-IASReport-Signature HTTP header. - #[prost(message, required)] - pub sig: VerificationSignature, - /// Attestation Report Signing Certificate Chain, as an array of - /// DER-formatted bytes, from the X-IASReport-Signing-Certificate HTTP - /// header. - #[prost(bytes, repeated)] - pub chain: Vec>, - /// The raw report body JSON, as a byte sequence - #[prost(string, required)] - #[digestible(never_omit)] - pub http_body: String, -} - #[cfg(test)] mod test { extern crate std; use super::*; + use crate::VerificationSignature; const IAS_WITH_PIB: &str = include_str!("../../data/test/ias_with_pib.json"); diff --git a/attest/core/src/lib.rs b/attest/core/src/lib.rs index 5ab57dc1ba..350c08913f 100644 --- a/attest/core/src/lib.rs +++ b/attest/core/src/lib.rs @@ -25,9 +25,7 @@ pub use crate::{ QuoteVerifyError, ReportBodyVerifyError, ReportDetailsError, RevocationCause, SgxError, SgxResult, SignatureError, TargetInfoError, VerifyError, }, - ias::verify::{ - EpidPseudonym, VerificationReport, VerificationReportData, VerificationSignature, - }, + ias::verify::{EpidPseudonym, VerificationReportData}, nonce::{IasNonce, Nonce, QuoteNonce}, quote::{Quote, QuoteSignType}, report::Report, @@ -54,5 +52,7 @@ pub use crate::{ }, }; +pub use mc_attest_verifier_types::{VerificationReport, VerificationSignature}; + /// The IAS version we support pub const IAS_VERSION: f64 = 4.0; diff --git a/attest/verifier/types/Cargo.toml b/attest/verifier/types/Cargo.toml new file mode 100644 index 0000000000..a4ba739439 --- /dev/null +++ b/attest/verifier/types/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mc-attest-verifier-types" +version = "1.3.0-pre0" +authors = ["MobileCoin"] +edition = "2018" +description = "This crate contains the type definitions for attestation" + +[dependencies] +mc-crypto-digestible = { path = "../../../crypto/digestible" } +mc-util-encodings = { path = "../../../util/encodings" } + +binascii = "0.1.2" +displaydoc = { version = "0.2", default-features = false } +hex_fmt = "0.3" +prost = { version = "0.10", default-features = false, features = ["prost-derive"] } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } diff --git a/attest/verifier/types/src/lib.rs b/attest/verifier/types/src/lib.rs new file mode 100644 index 0000000000..3c8663261b --- /dev/null +++ b/attest/verifier/types/src/lib.rs @@ -0,0 +1,9 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +//! Data structures for remote attestation. +#![no_std] +extern crate alloc; + +mod verification; + +pub use crate::verification::{VerificationReport, VerificationSignature}; diff --git a/attest/verifier/types/src/verification.rs b/attest/verifier/types/src/verification.rs new file mode 100644 index 0000000000..e89d0eb658 --- /dev/null +++ b/attest/verifier/types/src/verification.rs @@ -0,0 +1,126 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +//! Attestation Verification Report type. + +use alloc::{string::String, vec, vec::Vec}; +use binascii::b64decode; +use mc_crypto_digestible::Digestible; +use mc_util_encodings::{Error as EncodingError, FromHex}; +use prost::{ + bytes::{Buf, BufMut}, + encoding::{self, DecodeContext, WireType}, + DecodeError, Message, +}; +use serde::{Deserialize, Serialize}; + +/// Container for holding the quote verification sent back from IAS. +/// +/// The fields correspond to the data sent from IAS in the +/// [Attestation Verification Report](https://software.intel.com/sites/default/files/managed/7e/3b/ias-api-spec.pdf). +/// +/// This structure is supposed to be filled in from the results of an IAS +/// web request and then validated directly or serialized into an enclave for +/// validation. +#[derive( + Clone, Deserialize, Digestible, Eq, Hash, Message, Ord, PartialEq, PartialOrd, Serialize, +)] +pub struct VerificationReport { + /// Report Signature bytes, from the X-IASReport-Signature HTTP header. + #[prost(message, required, tag = 1)] + pub sig: VerificationSignature, + + /// Attestation Report Signing Certificate Chain, as an array of + /// DER-formatted bytes, from the X-IASReport-Signing-Certificate HTTP + /// header. + #[prost(bytes, repeated, tag = 2)] + pub chain: Vec>, + + /// The raw report body JSON, as a byte sequence + #[prost(string, required, tag = 3)] + #[digestible(never_omit)] + pub http_body: String, +} + +/// A type containing the bytes of the VerificationReport signature +#[derive( + Clone, Debug, Default, Deserialize, Digestible, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, +)] +#[repr(transparent)] +pub struct VerificationSignature(#[digestible(never_omit)] Vec); + +impl AsRef<[u8]> for VerificationSignature { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl From for Vec { + fn from(src: VerificationSignature) -> Vec { + src.0 + } +} + +impl From> for VerificationSignature { + fn from(src: Vec) -> Self { + Self(src) + } +} + +impl From<&[u8]> for VerificationSignature { + fn from(src: &[u8]) -> Self { + src.to_vec().into() + } +} + +impl FromHex for VerificationSignature { + type Error = EncodingError; + + fn from_hex(s: &str) -> Result { + // base64 strlength = 4 * (bytelen / 3) + padding + let mut data = vec![0u8; 3 * ((s.len() + 4) / 4)]; + let buflen = { + let buffer = b64decode(s.as_bytes(), data.as_mut_slice())?; + buffer.len() + }; + data.truncate(buflen); + Ok(VerificationSignature::from(data)) + } +} + +const TAG_SIGNATURE_CONTENTS: u32 = 1; + +impl Message for VerificationSignature { + fn encode_raw(&self, buf: &mut B) + where + B: BufMut, + Self: Sized, + { + encoding::bytes::encode(TAG_SIGNATURE_CONTENTS, &self.0, buf); + } + + fn merge_field( + &mut self, + tag: u32, + wire_type: WireType, + buf: &mut B, + ctx: DecodeContext, + ) -> Result<(), DecodeError> + where + B: Buf, + Self: Sized, + { + if tag == TAG_SIGNATURE_CONTENTS { + encoding::bytes::merge(wire_type, &mut self.0, buf, ctx) + } else { + encoding::skip_field(wire_type, tag, buf, ctx) + } + } + + fn encoded_len(&self) -> usize { + encoding::bytes::encoded_len(TAG_SIGNATURE_CONTENTS, &self.0) + } + + fn clear(&mut self) { + self.0.clear() + } +} diff --git a/consensus/enclave/trusted/Cargo.lock b/consensus/enclave/trusted/Cargo.lock index 9613399838..cff70b9515 100644 --- a/consensus/enclave/trusted/Cargo.lock +++ b/consensus/enclave/trusted/Cargo.lock @@ -697,6 +697,7 @@ dependencies = [ "digest", "displaydoc", "hex_fmt", + "mc-attest-verifier-types", "mc-common", "mc-crypto-digestible", "mc-crypto-rand", @@ -760,6 +761,19 @@ dependencies = [ "sha2", ] +[[package]] +name = "mc-attest-verifier-types" +version = "1.3.0-pre0" +dependencies = [ + "binascii", + "displaydoc", + "hex_fmt", + "mc-crypto-digestible", + "mc-util-encodings", + "prost", + "serde", +] + [[package]] name = "mc-common" version = "1.3.0-pre0" diff --git a/fog/ingest/enclave/trusted/Cargo.lock b/fog/ingest/enclave/trusted/Cargo.lock index af3fe400f8..edd5c7502d 100644 --- a/fog/ingest/enclave/trusted/Cargo.lock +++ b/fog/ingest/enclave/trusted/Cargo.lock @@ -717,6 +717,7 @@ dependencies = [ "digest", "displaydoc", "hex_fmt", + "mc-attest-verifier-types", "mc-common", "mc-crypto-digestible", "mc-crypto-rand", @@ -780,6 +781,19 @@ dependencies = [ "sha2", ] +[[package]] +name = "mc-attest-verifier-types" +version = "1.3.0-pre0" +dependencies = [ + "binascii", + "displaydoc", + "hex_fmt", + "mc-crypto-digestible", + "mc-util-encodings", + "prost", + "serde", +] + [[package]] name = "mc-common" version = "1.3.0-pre0" diff --git a/fog/ledger/enclave/trusted/Cargo.lock b/fog/ledger/enclave/trusted/Cargo.lock index 0ce5509a13..2a57a0d642 100644 --- a/fog/ledger/enclave/trusted/Cargo.lock +++ b/fog/ledger/enclave/trusted/Cargo.lock @@ -721,6 +721,7 @@ dependencies = [ "digest", "displaydoc", "hex_fmt", + "mc-attest-verifier-types", "mc-common", "mc-crypto-digestible", "mc-crypto-rand", @@ -784,6 +785,19 @@ dependencies = [ "sha2", ] +[[package]] +name = "mc-attest-verifier-types" +version = "1.3.0-pre0" +dependencies = [ + "binascii", + "displaydoc", + "hex_fmt", + "mc-crypto-digestible", + "mc-util-encodings", + "prost", + "serde", +] + [[package]] name = "mc-common" version = "1.3.0-pre0" diff --git a/fog/view/enclave/trusted/Cargo.lock b/fog/view/enclave/trusted/Cargo.lock index 2ed2f52c81..289f77a2e7 100644 --- a/fog/view/enclave/trusted/Cargo.lock +++ b/fog/view/enclave/trusted/Cargo.lock @@ -727,6 +727,7 @@ dependencies = [ "digest", "displaydoc", "hex_fmt", + "mc-attest-verifier-types", "mc-common", "mc-crypto-digestible", "mc-crypto-rand", @@ -790,6 +791,19 @@ dependencies = [ "sha2", ] +[[package]] +name = "mc-attest-verifier-types" +version = "1.3.0-pre0" +dependencies = [ + "binascii", + "displaydoc", + "hex_fmt", + "mc-crypto-digestible", + "mc-util-encodings", + "prost", + "serde", +] + [[package]] name = "mc-common" version = "1.3.0-pre0" From da0713f38af4e7acc6eaae65af27e830a6753c0f Mon Sep 17 00:00:00 2001 From: Remoun Metyas Date: Wed, 13 Apr 2022 14:07:16 -0700 Subject: [PATCH 04/11] Split out an mc-consensus-scp-core with the core types, for a smaller footprint --- Cargo.lock | 16 +++ Cargo.toml | 1 + consensus/scp/Cargo.toml | 16 ++- consensus/scp/core/Cargo.toml | 29 ++++++ consensus/scp/{ => core}/src/core_types.rs | 14 +-- consensus/scp/core/src/lib.rs | 19 ++++ consensus/scp/{ => core}/src/msg.rs | 40 ++++---- consensus/scp/{ => core}/src/predicates.rs | 42 ++++---- consensus/scp/{ => core}/src/quorum_set.rs | 106 ++++++++----------- consensus/scp/core/src/test_utils.rs | 114 +++++++++++++++++++++ consensus/scp/play/Cargo.toml | 2 +- consensus/scp/src/lib.rs | 8 +- consensus/scp/src/slot.rs | 46 +++++---- consensus/scp/src/slot_state.rs | 7 +- consensus/scp/src/test_utils.rs | 113 +------------------- 15 files changed, 317 insertions(+), 256 deletions(-) create mode 100644 consensus/scp/core/Cargo.toml rename consensus/scp/{ => core}/src/core_types.rs (95%) create mode 100644 consensus/scp/core/src/lib.rs rename consensus/scp/{ => core}/src/msg.rs (97%) rename consensus/scp/{ => core}/src/predicates.rs (92%) rename consensus/scp/{ => core}/src/quorum_set.rs (95%) create mode 100644 consensus/scp/core/src/test_utils.rs diff --git a/Cargo.lock b/Cargo.lock index 36949ee775..12a1cbbdbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3521,6 +3521,7 @@ dependencies = [ "crossbeam-channel", "maplit", "mc-common", + "mc-consensus-scp-core", "mc-crypto-digestible", "mc-crypto-keys", "mc-util-from-random", @@ -3536,6 +3537,21 @@ dependencies = [ "tempdir", ] +[[package]] +name = "mc-consensus-scp-core" +version = "1.3.0-pre0" +dependencies = [ + "mc-common", + "mc-crypto-digestible", + "mc-crypto-keys", + "mc-util-from-random", + "mc-util-serial", + "mc-util-test-helper", + "rand 0.8.5", + "rand_hc 0.3.1", + "serde", +] + [[package]] name = "mc-consensus-scp-play" version = "1.3.0-pre0" diff --git a/Cargo.toml b/Cargo.toml index 4eb69880ac..2cb40c085f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ members = [ "consensus/enclave/mock", "consensus/mint-client", "consensus/scp", + "consensus/scp/core", "consensus/scp/play", "consensus/service", "consensus/service/config", diff --git a/consensus/scp/Cargo.toml b/consensus/scp/Cargo.toml index 54d6f3c0b1..4b7b8f43ae 100644 --- a/consensus/scp/Cargo.toml +++ b/consensus/scp/Cargo.toml @@ -8,10 +8,11 @@ keywords = ["SCP", "Stellar Consensus Protocol", "Consensus", "Stellar", "Byzant readme = "README.md" [features] -test_utils = [] +test_utils = ["mc-consensus-scp-core/test_utils"] [dependencies] mc-common = { path = "../../common", features = ["log"] } +mc-consensus-scp-core = { path = "core" } mc-crypto-digestible = { path = "../../crypto/digestible", features = ["derive"] } mc-crypto-keys = { path = "../../crypto/keys" } mc-util-from-random = { path = "../../util/from-random" } @@ -27,9 +28,22 @@ serde_json = "1.0" [dev-dependencies] mc-common = { path = "../../common", features = ["loggers"] } +mc-consensus-scp-core = { path = "core", features = ["test_utils"] } mc-util-logger-macros = { path = "../../util/logger-macros" } mc-util-test-helper = { path = "../../util/test-helper" } crossbeam-channel = "0.5" serial_test = "0.6" tempdir = "0.3" + +[[test]] +name = "test_cyclic_networks" +required-features = ["test_utils"] + +[[test]] +name = "test_mesh_networks" +required-features = ["test_utils"] + +[[test]] +name = "test_metamesh_networks" +required-features = ["test_utils"] diff --git a/consensus/scp/core/Cargo.toml b/consensus/scp/core/Cargo.toml new file mode 100644 index 0000000000..557ae8122e --- /dev/null +++ b/consensus/scp/core/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "mc-consensus-scp-core" +version = "1.3.0-pre0" +authors = ["MobileCoin"] +edition = "2018" +readme = "../README.md" + +[features] +default = [] +std = ["mc-common/std", "serde/std"] +test_utils = ["mc-util-test-helper", "rand", "rand_hc"] + +[dependencies] +mc-common = { path = "../../../common", default-features = false } +mc-crypto-digestible = { path = "../../../crypto/digestible", features = ["derive"] } +mc-crypto-keys = { path = "../../../crypto/keys" } +mc-util-from-random = { path = "../../../util/from-random" } +mc-util-test-helper = { path = "../../../util/test-helper", optional = true } + +rand = { version = "0.8", default-features = false, optional = true } +rand_hc = { version = "0.3", default-features = false, optional = true } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } + +[dev-dependencies] +mc-util-serial = { path = "../../../util/serial", features = ["std"] } +mc-util-test-helper = { path = "../../../util/test-helper" } + +rand = "0.8" +rand_hc = "0.3" diff --git a/consensus/scp/src/core_types.rs b/consensus/scp/core/src/core_types.rs similarity index 95% rename from consensus/scp/src/core_types.rs rename to consensus/scp/core/src/core_types.rs index a2799b13b0..cff0829e6f 100644 --- a/consensus/scp/src/core_types.rs +++ b/consensus/scp/core/src/core_types.rs @@ -1,17 +1,17 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation //! Core types for MobileCoin's implementation of SCP. -use mc_crypto_digestible::Digestible; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{ +use alloc::{sync::Arc, vec::Vec}; +use core::{ clone::Clone, cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}, - collections::hash_map::DefaultHasher, fmt, fmt::{Debug, Display}, - hash::{Hash, Hasher}, - sync::Arc, + hash::{BuildHasher, Hash, Hasher}, }; +use mc_common::HasherBuilder; +use mc_crypto_digestible::Digestible; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; /// A generic node identifier. pub trait GenericNodeId: @@ -125,7 +125,7 @@ impl PartialOrd for Ballot { // This makes debugging easier when looking at large ballots. impl fmt::Display for Ballot { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut hasher = DefaultHasher::new(); + let mut hasher = HasherBuilder::default().build_hasher(); self.X.hash(&mut hasher); let hashed_X_values = hasher.finish(); write!(f, "<{}, {}:{:x}>", self.N, self.X.len(), hashed_X_values) diff --git a/consensus/scp/core/src/lib.rs b/consensus/scp/core/src/lib.rs new file mode 100644 index 0000000000..545113dac2 --- /dev/null +++ b/consensus/scp/core/src/lib.rs @@ -0,0 +1,19 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +#![doc = include_str!("../../README.md")] +#![cfg_attr(not(any(test, feature = "std", feature = "test_utils")), no_std)] +#![warn(unused_extern_crates)] +#![allow(non_snake_case)] +#![deny(missing_docs)] + +extern crate alloc; + +pub mod core_types; +pub mod msg; +pub mod predicates; +pub mod quorum_set; +#[cfg(any(test, feature = "test_utils"))] +pub mod test_utils; + +#[doc(inline)] +pub use crate::{core_types::*, msg::*, predicates::*, quorum_set::*}; diff --git a/consensus/scp/src/msg.rs b/consensus/scp/core/src/msg.rs similarity index 97% rename from consensus/scp/src/msg.rs rename to consensus/scp/core/src/msg.rs index 091c9fd9f6..647b5387c9 100644 --- a/consensus/scp/src/msg.rs +++ b/consensus/scp/core/src/msg.rs @@ -6,18 +6,22 @@ use crate::{ msg::Topic::*, quorum_set::QuorumSet, }; -use mc_common::NodeID; -use mc_crypto_digestible::Digestible; -use mc_util_serial::prost::alloc::fmt::Formatter; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{ + +use alloc::{ + collections::BTreeSet, + format, + string::{String, ToString}, +}; +use core::{ cmp, cmp::Ordering, - collections::{hash_map::DefaultHasher, BTreeSet, HashSet}, fmt, - fmt::{Debug, Display}, - hash::{Hash, Hasher}, + fmt::{Debug, Display, Formatter}, + hash::{BuildHasher, Hash, Hasher}, }; +use mc_common::{HashSet, HasherBuilder, NodeID}; +use mc_crypto_digestible::Digestible; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; /// The highest possible ballot counter. pub const INFINITY: u32 = ::max_value(); @@ -629,9 +633,10 @@ impl fmt::Display for Msg { }; // Returns "". + let hasher_builder = HasherBuilder::default(); let format_b_tree_set = |b_tree_set: &BTreeSet| { let hash = { - let mut hasher = DefaultHasher::new(); + let mut hasher = hasher_builder.build_hasher(); b_tree_set.hash(&mut hasher); hasher.finish() }; @@ -677,9 +682,8 @@ impl fmt::Display for Msg { mod msg_tests { use super::*; use crate::test_utils::test_node_id; + use core::iter::FromIterator; use rand::seq::SliceRandom; - use std::iter::FromIterator; - extern crate mc_util_test_helper; #[test] /// Prepare implies "vote_or_accept prepare" for B, P, and PP. @@ -733,7 +737,7 @@ mod msg_tests { ); let votes_or_accepts_prepared = msg.votes_or_accepts_prepared(); - let expected = HashSet::from_iter(vec![Ballot::new(INFINITY, &ballot.X)]); + let expected = HashSet::from_iter([Ballot::new(INFINITY, &ballot.X)]); assert_eq!(votes_or_accepts_prepared, expected); } @@ -754,7 +758,7 @@ mod msg_tests { ); let votes_or_accepts_prepared = msg.votes_or_accepts_prepared(); - let expected = HashSet::from_iter(vec![Ballot::new(INFINITY, &ballot.X)]); + let expected = HashSet::from_iter([Ballot::new(INFINITY, &ballot.X)]); assert_eq!(votes_or_accepts_prepared, expected); } @@ -787,7 +791,7 @@ mod msg_tests { ); let accepts_prepared = msg.accepts_prepared(); - let expected = HashSet::from_iter(vec![prepared, prepared_prime]); + let expected = HashSet::from_iter([prepared, prepared_prime]); assert_eq!(accepts_prepared, expected); } @@ -832,7 +836,7 @@ mod msg_tests { ); let accepts_prepared = msg.accepts_prepared(); - let expected = HashSet::from_iter(vec![Ballot::new(9, &ballot.X)]); + let expected = HashSet::from_iter([Ballot::new(9, &ballot.X)]); assert_eq!(accepts_prepared, expected); } @@ -852,7 +856,7 @@ mod msg_tests { ); let accepts_prepared = msg.accepts_prepared(); - let expected = HashSet::from_iter(vec![Ballot::new(INFINITY, &ballot.X)]); + let expected = HashSet::from_iter([Ballot::new(INFINITY, &ballot.X)]); assert_eq!(accepts_prepared, expected); } @@ -1055,8 +1059,8 @@ mod msg_tests { // NominatePayload serialize/deserialize work as expected. fn nominatepayload_deserialize_works() { let payload = NominatePayload:: { - X: BTreeSet::from_iter(vec![1, 2, 3]), - Y: BTreeSet::from_iter(vec![10, 20, 30]), + X: BTreeSet::from_iter([1, 2, 3]), + Y: BTreeSet::from_iter([10, 20, 30]), }; let serialized_payload = mc_util_serial::serialize(&payload).unwrap(); diff --git a/consensus/scp/src/predicates.rs b/consensus/scp/core/src/predicates.rs similarity index 92% rename from consensus/scp/src/predicates.rs rename to consensus/scp/core/src/predicates.rs index 38670dbbcb..d0959632ae 100644 --- a/consensus/scp/src/predicates.rs +++ b/consensus/scp/core/src/predicates.rs @@ -1,16 +1,12 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation //! Predicates for use in trust decisions for SCP. -use mc_common::NodeID; -use std::{ - collections::{BTreeSet, HashMap, HashSet}, - sync::Arc, -}; - use crate::{ core_types::{Ballot, GenericNodeId, Value}, msg::Msg, }; +use alloc::{collections::BTreeSet, sync::Arc, vec::Vec}; +use mc_common::{HashMap, HashSet, NodeID}; /// An interface for predicates, used for performing searches for quorums and /// blocking sets. See `findQuorum`, `findBlockingSet`. @@ -19,8 +15,8 @@ pub trait Predicate: Clone { type Result; /// Tests whether the predicate is true for a given message. - /// Retruns Some(Predicate) if `msg` satisfies the predicate, `None` - /// otherwise. This allows the predicate to evolve it's state as it is + /// Returns Some(Predicate) if `msg` satisfies the predicate, `None` + /// otherwise. This allows the predicate to evolve its state as it is /// called on more and more messages. fn test(&self, msg: &Msg) -> Option; @@ -183,7 +179,7 @@ impl<'a, V: Value, ID: GenericNodeId> Predicate for FuncPredicate<'a, V, mod predicates_tests { use super::*; use crate::{core_types::*, msg::*, quorum_set::*, test_utils::test_node_id}; - use std::iter::FromIterator; + use core::iter::FromIterator; #[test] // BallotSetPredicate can be used to pick a quorum that intersects with a given @@ -283,7 +279,7 @@ mod predicates_tests { &local_node_id, &msgs, BallotSetPredicate { - ballots: HashSet::from_iter(vec![ballot_1.clone(), ballot_3]), + ballots: HashSet::from_iter([ballot_1.clone(), ballot_3]), test_fn: Arc::new(|msg, ballots| { ballots .intersection(&msg.votes_or_accepts_prepared()) @@ -294,9 +290,9 @@ mod predicates_tests { ); assert_eq!( node_ids, - HashSet::from_iter(vec![test_node_id(1), test_node_id(2), test_node_id(3)]) + HashSet::from_iter([test_node_id(1), test_node_id(2), test_node_id(3)]) ); - assert_eq!(pred.result(), HashSet::from_iter(vec![ballot_1])); + assert_eq!(pred.result(), HashSet::from_iter([ballot_1])); } #[test] @@ -369,7 +365,7 @@ mod predicates_tests { let (node_ids, pred) = local_node_quorum_set.findBlockingSet( &msgs, BallotSetPredicate { - ballots: HashSet::from_iter(vec![ballot_1.clone(), ballot_3]), + ballots: HashSet::from_iter([ballot_1.clone(), ballot_3]), test_fn: Arc::new(|msg, ballots| { ballots .intersection(&msg.votes_or_accepts_prepared()) @@ -380,9 +376,9 @@ mod predicates_tests { ); assert_eq!( node_ids, - HashSet::from_iter(vec![test_node_id(2), test_node_id(3)]) + HashSet::from_iter([test_node_id(2), test_node_id(3)]) ); - assert_eq!(pred.result(), HashSet::from_iter(vec![ballot_1])); + assert_eq!(pred.result(), HashSet::from_iter([ballot_1])); } #[test] @@ -437,8 +433,8 @@ mod predicates_tests { ], ); - let values_1 = BTreeSet::from_iter(vec!["a".to_string(), "A".to_string()]); - let values_2 = BTreeSet::from_iter(vec!["b".to_string(), "B".to_string()]); + let values_1 = BTreeSet::from_iter(["a".to_string(), "A".to_string()]); + let values_2 = BTreeSet::from_iter(["b".to_string(), "B".to_string()]); let mut msgs = HashMap::>::default(); @@ -475,7 +471,7 @@ mod predicates_tests { &local_node_id, &msgs, ValueSetPredicate { - values: BTreeSet::from_iter(vec![ + values: BTreeSet::from_iter([ "a".to_string(), "A".to_string(), "c".to_string(), @@ -489,7 +485,7 @@ mod predicates_tests { ); assert_eq!( node_ids, - HashSet::from_iter(vec![test_node_id(1), test_node_id(2), test_node_id(3)]) + HashSet::from_iter([test_node_id(1), test_node_id(2), test_node_id(3)]) ); assert_eq!(pred.result(), values_1); } @@ -520,8 +516,8 @@ mod predicates_tests { let node_6_quorum_set = QuorumSet::new_with_node_ids(1, vec![test_node_id(5), test_node_id(7)]); - let values_1 = BTreeSet::from_iter(vec!["a".to_string(), "A".to_string()]); - let values_2 = BTreeSet::from_iter(vec!["b".to_string(), "B".to_string()]); + let values_1 = BTreeSet::from_iter(["a".to_string(), "A".to_string()]); + let values_2 = BTreeSet::from_iter(["b".to_string(), "B".to_string()]); let mut msgs = HashMap::>::default(); @@ -557,7 +553,7 @@ mod predicates_tests { let (node_ids, pred) = local_node_quorum_set.findBlockingSet( &msgs, ValueSetPredicate { - values: BTreeSet::from_iter(vec![ + values: BTreeSet::from_iter([ "a".to_string(), "A".to_string(), "c".to_string(), @@ -571,7 +567,7 @@ mod predicates_tests { ); assert_eq!( node_ids, - HashSet::from_iter(vec![test_node_id(2), test_node_id(3)]) + HashSet::from_iter([test_node_id(2), test_node_id(3)]) ); assert_eq!(pred.result(), values_1); } diff --git a/consensus/scp/src/quorum_set.rs b/consensus/scp/core/src/quorum_set.rs similarity index 95% rename from consensus/scp/src/quorum_set.rs rename to consensus/scp/core/src/quorum_set.rs index 3aabc696a3..7be0c6b07d 100644 --- a/consensus/scp/src/quorum_set.rs +++ b/consensus/scp/core/src/quorum_set.rs @@ -4,21 +4,20 @@ //! //! A quorum set includes the members of the network, which a given node trusts //! and depends on. -use mc_common::{NodeID, ResponderId}; -use mc_crypto_digestible::Digestible; -use serde::{Deserialize, Serialize}; -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, - hash::{Hash, Hasher}, - iter::FromIterator, -}; - use crate::{ core_types::{GenericNodeId, Value}, msg::Msg, predicates::Predicate, }; +use alloc::{vec, vec::Vec}; +use core::{ + fmt::Debug, + hash::{Hash, Hasher}, + iter::FromIterator, +}; +use mc_common::{HashMap, HashSet, NodeID, ResponderId}; +use mc_crypto_digestible::Digestible; +use serde::{Deserialize, Serialize}; /// A member in a QuorumSet. Can be either a Node or another QuorumSet. #[derive( @@ -118,7 +117,7 @@ impl QuorumSet { true } - /// Recursively sort the qs and all inner sets + /// Recursively sort the QS and all inner sets pub fn sort(&mut self) { for member in self.members.iter_mut() { if let QuorumSetMember::InnerSet(qs) = member { @@ -301,7 +300,7 @@ impl QuorumSet { &self.members, msgs, pred, - HashSet::from_iter(vec![node_id.clone()]), + HashSet::from_iter([node_id.clone()]), ) } @@ -430,9 +429,26 @@ impl> From<&QuorumSet> for QuorumSet< mod quorum_set_tests { use super::*; use crate::{core_types::*, msg::*, predicates::*, test_utils::test_node_id}; - use mc_common::ResponderId; - use std::collections::hash_map::DefaultHasher; + use core::hash::{BuildHasher, Hash, Hasher}; + use mc_common::HasherBuilder; + + fn assert_quorum_sets_equal(quorum_set_1: &QuorumSet, quorum_set_2: &QuorumSet) { + assert_eq!(quorum_set_1, quorum_set_2); + // qs1 == qs2 must imply hash(qs1) == hash(qs2) + let hasher_builder = HasherBuilder::default(); + let quorum_set_1_hash = { + let mut hasher = hasher_builder.build_hasher(); + quorum_set_1.hash(&mut hasher); + hasher.finish() + }; + let quorum_set_2_hash = { + let mut hasher = hasher_builder.build_hasher(); + quorum_set_2.hash(&mut hasher); + hasher.finish() + }; + assert_eq!(quorum_set_1_hash, quorum_set_2_hash); + } #[test] // quorum sets should sort recursively fn test_quorum_set_sorting() { @@ -457,7 +473,7 @@ mod quorum_set_tests { let mut qs_sorted = qs.clone(); qs_sorted.sort(); - assert_eq!(qs, qs_sorted); + assert_quorum_sets_equal(&qs, &qs_sorted); } #[test] @@ -482,20 +498,7 @@ mod quorum_set_tests { ], ); - assert_eq!(quorum_set_1, quorum_set_2); - - // qs1 == qs2 must imply hash(qs1)==hash(qs2) - let quorum_set_1_hash = { - let mut hasher = DefaultHasher::new(); - quorum_set_1.hash(&mut hasher); - hasher.finish() - }; - let quorum_set_2_hash = { - let mut hasher = DefaultHasher::new(); - quorum_set_2.hash(&mut hasher); - hasher.finish() - }; - assert_eq!(quorum_set_1_hash, quorum_set_2_hash); + assert_quorum_sets_equal(&quorum_set_1, &quorum_set_2); } #[test] @@ -545,20 +548,7 @@ mod quorum_set_tests { )), ], ); - assert_eq!(quorum_set_1, quorum_set_2); - - // qs1 == qs2 must imply hash(qs1)==hash(qs2) - let quorum_set_1_hash = { - let mut hasher = DefaultHasher::new(); - quorum_set_1.hash(&mut hasher); - hasher.finish() - }; - let quorum_set_2_hash = { - let mut hasher = DefaultHasher::new(); - quorum_set_2.hash(&mut hasher); - hasher.finish() - }; - assert_eq!(quorum_set_1_hash, quorum_set_2_hash); + assert_quorum_sets_equal(&quorum_set_1, &quorum_set_2); } #[test] @@ -608,20 +598,7 @@ mod quorum_set_tests { )), ], ); - assert_eq!(quorum_set_1, quorum_set_2); - - // qs1 == qs2 must imply hash(qs1)==hash(qs2) - let quorum_set_1_hash = { - let mut hasher = DefaultHasher::new(); - quorum_set_1.hash(&mut hasher); - hasher.finish() - }; - let quorum_set_2_hash = { - let mut hasher = DefaultHasher::new(); - quorum_set_2.hash(&mut hasher); - hasher.finish() - }; - assert_eq!(quorum_set_1_hash, quorum_set_2_hash); + assert_quorum_sets_equal(&quorum_set_1, &quorum_set_2); } #[test] @@ -718,7 +695,7 @@ mod quorum_set_tests { ); assert_eq!( node_ids, - HashSet::from_iter(vec![test_node_id(2), test_node_id(3)]) + HashSet::from_iter([test_node_id(2), test_node_id(3)]) ); } @@ -818,7 +795,7 @@ mod quorum_set_tests { test_fn: &|_msg| true, }, ); - assert_eq!(node_ids, HashSet::from_iter(vec![])); + assert_eq!(node_ids, HashSet::from_iter([])); } #[test] @@ -882,7 +859,7 @@ mod quorum_set_tests { ); assert_eq!( node_ids, - HashSet::from_iter(vec![ + HashSet::from_iter([ test_node_id(2), test_node_id(3), test_node_id(5), @@ -952,7 +929,7 @@ mod quorum_set_tests { test_fn: &|msg| msg.sender_id != test_node_id(2), }, ); - assert_eq!(node_ids, HashSet::from_iter(vec![])); + assert_eq!(node_ids, HashSet::from_iter([])); } #[test] @@ -1003,7 +980,7 @@ mod quorum_set_tests { Msg::new(test_node_id(3).responder_id, QuorumSet::empty(), 1, topic), ); - let responder_ids: HashSet = HashSet::from_iter(vec![ + let responder_ids: HashSet = HashSet::from_iter([ test_node_id(2).responder_id, test_node_id(3).responder_id, test_node_id(4).responder_id, @@ -1019,10 +996,7 @@ mod quorum_set_tests { let (node_ids, _) = mobilecoind_quorum_set.findBlockingSet(&msgs, fp); assert_eq!( node_ids, - HashSet::from_iter(vec![ - test_node_id(2).responder_id, - test_node_id(3).responder_id - ]) + HashSet::from_iter([test_node_id(2).responder_id, test_node_id(3).responder_id]) ); } diff --git a/consensus/scp/core/src/test_utils.rs b/consensus/scp/core/src/test_utils.rs new file mode 100644 index 0000000000..17e7b24e78 --- /dev/null +++ b/consensus/scp/core/src/test_utils.rs @@ -0,0 +1,114 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +//! Utilities for SCP tests. + +use crate::QuorumSet; +use alloc::vec; +use core::str::FromStr; +use mc_common::{NodeID, ResponderId}; +use mc_crypto_keys::Ed25519Pair; +use mc_util_from_random::FromRandom; +use mc_util_test_helper::SeedableRng; +use rand_hc::Hc128Rng as FixedRng; + +/// Creates NodeID from integer for testing. +pub fn test_node_id(node_id: u32) -> NodeID { + test_node_id_and_signer(node_id).0 +} + +/// Creates NodeID and Signer keypair from integer for testing. +pub fn test_node_id_and_signer(node_id: u32) -> (NodeID, Ed25519Pair) { + let mut seed_bytes = [0u8; 32]; + let node_id_bytes = node_id.to_be_bytes(); + seed_bytes[..node_id_bytes.len()].copy_from_slice(&node_id_bytes[..]); + + let mut seeded_rng: FixedRng = SeedableRng::from_seed(seed_bytes); + let signer_keypair = Ed25519Pair::from_random(&mut seeded_rng); + ( + NodeID { + responder_id: ResponderId::from_str(&format!("node{}.test.com:8443", node_id)).unwrap(), + public_key: signer_keypair.public_key(), + }, + signer_keypair, + ) +} + +/// Three nodes that form a three-node cycle. +/// +/// * Node 1 has the quorum slice {1,2}, where {2} is a blocking set. +/// * Node 2 has the quorum slice {2,3}, where {3} is a blocking set. +/// * Node 3 has the quorum slice {1,3}, where {1} is a blocking set. +/// * The only quorum is the set of all three nodes {1, 2, 3}. +pub fn three_node_cycle() -> ( + (NodeID, QuorumSet), + (NodeID, QuorumSet), + (NodeID, QuorumSet), +) { + let node_1 = ( + test_node_id(1), + QuorumSet::new_with_node_ids(1, vec![test_node_id(2)]), + ); + let node_2 = ( + test_node_id(2), + QuorumSet::new_with_node_ids(1, vec![test_node_id(3)]), + ); + let node_3 = ( + test_node_id(3), + QuorumSet::new_with_node_ids(1, vec![test_node_id(1)]), + ); + (node_1, node_2, node_3) +} + +/// The four-node network from Fig. 2 of the [Stellar Whitepaper](https://www.stellar.org/papers/stellar-consensus-protocol). +/// +/// * Node 1 has the quorum slice {1,2,3}, where {2}, {3}, {2,3} are blocking +/// sets. +/// * Nodes 2,3, and 4 have the quorum slice {2,3,4}. +/// * The only quorum is the set of all nodes {1,2,3,4}. +pub fn fig_2_network() -> ( + (NodeID, QuorumSet), + (NodeID, QuorumSet), + (NodeID, QuorumSet), + (NodeID, QuorumSet), +) { + let node_1 = ( + test_node_id(1), + QuorumSet::new_with_node_ids(2, vec![test_node_id(2), test_node_id(3)]), + ); + let node_2 = ( + test_node_id(2), + QuorumSet::new_with_node_ids(2, vec![test_node_id(3), test_node_id(4)]), + ); + let node_3 = ( + test_node_id(3), + QuorumSet::new_with_node_ids(2, vec![test_node_id(2), test_node_id(4)]), + ); + let node_4 = ( + test_node_id(4), + QuorumSet::new_with_node_ids(2, vec![test_node_id(2), test_node_id(4)]), + ); + + (node_1, node_2, node_3, node_4) +} + +/// A three-node network where the only quorum is the set of all three nodes. +/// Each node is a blocking set for each other. +pub fn three_node_dense_graph() -> ( + (NodeID, QuorumSet), + (NodeID, QuorumSet), + (NodeID, QuorumSet), +) { + let node_1 = ( + test_node_id(1), + QuorumSet::new_with_node_ids(2, vec![test_node_id(2), test_node_id(3)]), + ); + let node_2 = ( + test_node_id(2), + QuorumSet::new_with_node_ids(2, vec![test_node_id(1), test_node_id(3)]), + ); + let node_3 = ( + test_node_id(3), + QuorumSet::new_with_node_ids(2, vec![test_node_id(1), test_node_id(2)]), + ); + (node_1, node_2, node_3) +} diff --git a/consensus/scp/play/Cargo.toml b/consensus/scp/play/Cargo.toml index 61b843c1f6..e99f41cc9a 100644 --- a/consensus/scp/play/Cargo.toml +++ b/consensus/scp/play/Cargo.toml @@ -10,7 +10,7 @@ path = "src/main.rs" [dependencies] mc-common = { path = "../../../common", features = ["loggers"] } -mc-consensus-scp = { path = "../../../consensus/scp" } +mc-consensus-scp = { path = "../../../consensus/scp", features = ["test_utils"] } mc-transaction-core = { path = "../../../transaction/core" } mc-util-uri = { path = "../../../util/uri" } diff --git a/consensus/scp/src/lib.rs b/consensus/scp/src/lib.rs index 179a5b7501..2b4da8e69e 100644 --- a/consensus/scp/src/lib.rs +++ b/consensus/scp/src/lib.rs @@ -4,17 +4,17 @@ #![allow(non_snake_case)] #![deny(missing_docs)] -pub mod core_types; -pub mod msg; pub mod node; -pub mod predicates; -pub mod quorum_set; pub mod scp_log; pub mod slot; pub mod slot_state; +#[cfg(any(test, feature = "test_utils"))] pub mod test_utils; mod utils; +#[doc(inline)] +pub use mc_consensus_scp_core::{core_types, msg, predicates, quorum_set}; + #[doc(inline)] pub use crate::{ core_types::{CombineFn, GenericNodeId, Identifier, SlotIndex, ValidityFn, Value}, diff --git a/consensus/scp/src/slot.rs b/consensus/scp/src/slot.rs index e74a34fad8..6977a3f9e4 100644 --- a/consensus/scp/src/slot.rs +++ b/consensus/scp/src/slot.rs @@ -15,17 +15,17 @@ use crate::{ utils, }; use core::cmp; -use maplit::{btreeset, hashset}; use mc_common::{ logger::{log, o, Logger}, - NodeID, + HashMap, HashSet, NodeID, }; #[cfg(test)] use mockall::*; use serde::{Deserialize, Serialize}; use std::{ - collections::{BTreeSet, HashMap, HashSet}, + collections::BTreeSet, fmt::Display, + iter::FromIterator, sync::Arc, time::{Duration, Instant}, }; @@ -1455,8 +1455,9 @@ impl Slot { // Test if a blocking set has issued "accept nominate" for each value. for value in candidates { // Test if a blocking set has issued "accept nominate(v)". + let values = BTreeSet::from_iter([value.clone()]); let predicate = ValueSetPredicate:: { - values: btreeset! {value.clone()}, + values, test_fn: Arc::new(|msg, values| match msg.accepts_nominated() { None => BTreeSet::default(), Some(values_accepted_nominated) => values @@ -1542,8 +1543,9 @@ impl Slot { let mut results: HashSet> = Default::default(); for ballot in candidates.into_iter() { + let ballots = HashSet::from_iter([ballot.clone()]); let predicate = BallotSetPredicate:: { - ballots: hashset! { ballot.clone()}, + ballots, test_fn: Arc::new(|msg, candidates| { let mut intersections: HashSet> = HashSet::default(); @@ -1810,8 +1812,8 @@ impl Slot { #[cfg(test)] mod nominate_protocol_tests { use super::*; - use crate::{core_types::*, quorum_set::*, test_utils::*}; - use maplit::{btreeset, hashset}; + use crate::test_utils::*; + use maplit::btreeset; use mc_common::logger::test_with_logger; #[test_with_logger] @@ -1941,7 +1943,7 @@ mod nominate_protocol_tests { Y: BTreeSet::default(), }), ); - slot.X = hashset! { 1234}; + slot.X = HashSet::from_iter([1234]); slot.M.insert(msg_1.sender_id.clone(), msg_1); let expected = BTreeSet::default(); assert_eq!(slot.additional_values_accepted_nominated(), expected); @@ -2131,8 +2133,8 @@ mod nominate_protocol_tests { Arc::new(trivial_combine_fn), logger, ); - slot.Y = hashset! { "B"}; - slot.Z = hashset! { "B"}; + slot.Y = HashSet::from_iter(["B"]); + slot.Z = HashSet::from_iter(["B"]); slot.M.insert(msg_2.sender_id.clone(), msg_2); slot.M.insert(msg_3.sender_id.clone(), msg_3); slot.M.insert(msg_4.sender_id.clone(), msg_4); @@ -2142,7 +2144,7 @@ mod nominate_protocol_tests { // the local node to update it's accepted nominated (Y) list from what // the blocking set has agreed on. slot.update_YZ(); - assert_eq!(slot.Y, hashset! { "A", "B", "C", "D"}); + assert_eq!(slot.Y, HashSet::from_iter(["A", "B", "C", "D"])); } #[test_with_logger] @@ -2299,8 +2301,8 @@ mod nominate_protocol_tests { Arc::new(trivial_combine_fn), logger, ); - slot.Y = hashset! { "A", "B", "C", "D"}; - slot.Z = hashset! { "A", "B", "C"}; + slot.Y = HashSet::from_iter(["A", "B", "C", "D"]); + slot.Z = HashSet::from_iter(["A", "B", "C"]); slot.M.insert(msg_1.sender_id.clone(), msg_1); slot.M.insert(msg_3.sender_id.clone(), msg_3); slot.M.insert(msg_4.sender_id.clone(), msg_4); @@ -2309,7 +2311,7 @@ mod nominate_protocol_tests { // Calling updateYZ should add "D" to confirmed nominated (Z) since a quorum // (2,3,4,5) have accepted nominated (Y) it. slot.update_YZ(); - assert_eq!(slot.Z, hashset! { "A", "B", "C", "D"}); + assert_eq!(slot.Z, HashSet::from_iter(["A", "B", "C", "D"])); } #[test_with_logger] @@ -2405,8 +2407,8 @@ mod nominate_protocol_tests { #[cfg(test)] mod ballot_protocol_tests { use super::*; - use crate::{core_types::*, quorum_set::*, test_utils::*}; - use maplit::{btreeset, hashset}; + use crate::test_utils::*; + use maplit::btreeset; use mc_common::logger::test_with_logger; use std::iter::FromIterator; @@ -2689,8 +2691,8 @@ mod ballot_protocol_tests { // adding a Prepare message from node 2 as well. { slot.phase = Phase::NominatePrepare; - slot.X = hashset! { 1337, 1338}; - slot.Y = hashset! { 1234, 5678}; + slot.X = HashSet::from_iter([1337, 1338]); + slot.Y = HashSet::from_iter([1234, 5678]); slot.B = Ballot::new(2, &[1234, 5678]); slot.P = Some(slot.B.clone()); slot.last_sent_msg = slot.out_msg(); @@ -2820,8 +2822,8 @@ mod ballot_protocol_tests { // Initialize slot so that it has issued "confirm prepare(b)". { slot.phase = Phase::Prepare; - slot.X = hashset! { 1337, 1338}; - slot.Y = hashset! { 1234, 5678}; + slot.X = HashSet::from_iter([1337, 1338]); + slot.Y = HashSet::from_iter([1234, 5678]); slot.B = Ballot::new(3, &[1234, 5678]); slot.P = Some(Ballot::new(2, &[1234, 5678])); slot.H = slot.P.clone(); @@ -3576,7 +3578,7 @@ mod ballot_protocol_tests { // Node 1 has issued "vote prepare(b)". { - slot.Y = hashset! { 1234, 5678}; + slot.Y = HashSet::from_iter([1234, 5678]); slot.B = ballot.clone(); slot.last_sent_msg = slot.out_msg(); @@ -4460,7 +4462,7 @@ mod ballot_protocol_tests { #[cfg(test)] mod tests { use super::*; - use crate::{core_types::*, test_utils::*}; + use crate::test_utils::*; use mc_common::logger::test_with_logger; #[test_with_logger] diff --git a/consensus/scp/src/slot_state.rs b/consensus/scp/src/slot_state.rs index d2a329d091..73b75b559f 100644 --- a/consensus/scp/src/slot_state.rs +++ b/consensus/scp/src/slot_state.rs @@ -9,12 +9,9 @@ use crate::{ msg::*, slot::{Phase, Slot}, }; -use mc_common::NodeID; +use mc_common::{HashSet, NodeID}; use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeSet, HashSet}, - fmt::Display, -}; +use std::{collections::BTreeSet, fmt::Display}; /// Serializable slot state used for debugging purposes. #[derive(Clone, Serialize, Deserialize)] diff --git a/consensus/scp/src/test_utils.rs b/consensus/scp/src/test_utils.rs index 608f8e164f..9a347d8c9b 100644 --- a/consensus/scp/src/test_utils.rs +++ b/consensus/scp/src/test_utils.rs @@ -1,13 +1,11 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation //! Utilities for Stellar Consensus Protocol tests. +pub use mc_consensus_scp_core::test_utils::*; + use crate::{core_types::Value, slot::Slot, QuorumSet, SlotIndex}; -use mc_common::{logger::Logger, NodeID, ResponderId}; -use mc_crypto_keys::Ed25519Pair; -use mc_util_from_random::FromRandom; -use rand::SeedableRng; -use rand_hc::Hc128Rng as FixedRng; -use std::{fmt, str::FromStr, sync::Arc}; +use mc_common::{logger::Logger, NodeID}; +use std::{fmt, sync::Arc}; /// Error for transaction validation #[derive(Clone)] @@ -44,29 +42,6 @@ pub fn get_bounded_combine_fn( } } -/// Creates NodeID from integer for testing. -pub fn test_node_id(node_id: u32) -> NodeID { - let (node_id, _signer) = test_node_id_and_signer(node_id); - node_id -} - -/// Creates NodeID and Signer keypair from integer for testing. -pub fn test_node_id_and_signer(node_id: u32) -> (NodeID, Ed25519Pair) { - let mut seed_bytes = [0u8; 32]; - let node_id_bytes = node_id.to_be_bytes(); - seed_bytes[..node_id_bytes.len()].copy_from_slice(&node_id_bytes[..]); - - let mut seeded_rng: FixedRng = SeedableRng::from_seed(seed_bytes); - let signer_keypair = Ed25519Pair::from_random(&mut seeded_rng); - ( - NodeID { - responder_id: ResponderId::from_str(&format!("node{}.test.com:8443", node_id)).unwrap(), - public_key: signer_keypair.public_key(), - }, - signer_keypair, - ) -} - /// Creates a new slot. pub fn get_slot( slot_index: SlotIndex, @@ -83,83 +58,3 @@ pub fn get_slot( logger, ) } - -/// Three nodes that form a three-node cycle. -/// -/// * Node 1 has the quorum slice {1,2}, where {2} is a blocking set. -/// * Node 2 has the quorum slice {2,3}, where {3} is a blocking set. -/// * Node 3 has the quorum slice {1,3}, where {1} is a blocking set. -/// * The only quorum is the set of all three nodes {1, 2, 3}. -pub fn three_node_cycle() -> ( - (NodeID, QuorumSet), - (NodeID, QuorumSet), - (NodeID, QuorumSet), -) { - let node_1 = ( - test_node_id(1), - QuorumSet::new_with_node_ids(1, vec![test_node_id(2)]), - ); - let node_2 = ( - test_node_id(2), - QuorumSet::new_with_node_ids(1, vec![test_node_id(3)]), - ); - let node_3 = ( - test_node_id(3), - QuorumSet::new_with_node_ids(1, vec![test_node_id(1)]), - ); - (node_1, node_2, node_3) -} - -/// The four-node network from Fig. 2 of the [Stellar Whitepaper](https://www.stellar.org/papers/stellar-consensus-protocol). -/// -/// * Node 1 has the quorum slice {1,2,3}, where {2}, {3}, {2,3} are blocking -/// sets. -/// * Nodes 2,3, and 4 have the quorum slice {2,3,4}. -/// * The only quorum is the set of all nodes {1,2,3,4}. -pub fn fig_2_network() -> ( - (NodeID, QuorumSet), - (NodeID, QuorumSet), - (NodeID, QuorumSet), - (NodeID, QuorumSet), -) { - let node_1 = ( - test_node_id(1), - QuorumSet::new_with_node_ids(2, vec![test_node_id(2), test_node_id(3)]), - ); - let node_2 = ( - test_node_id(2), - QuorumSet::new_with_node_ids(2, vec![test_node_id(3), test_node_id(4)]), - ); - let node_3 = ( - test_node_id(3), - QuorumSet::new_with_node_ids(2, vec![test_node_id(2), test_node_id(4)]), - ); - let node_4 = ( - test_node_id(4), - QuorumSet::new_with_node_ids(2, vec![test_node_id(2), test_node_id(4)]), - ); - - (node_1, node_2, node_3, node_4) -} - -/// A three-node network where the only quorum is the set of all three nodes. -/// Each node is a blocking set for each other. -pub fn three_node_dense_graph() -> ( - (NodeID, QuorumSet), - (NodeID, QuorumSet), - (NodeID, QuorumSet), -) { - let node_1 = ( - test_node_id(1), - QuorumSet::new_with_node_ids(2, vec![test_node_id(2), test_node_id(3)]), - ); - let node_2 = ( - test_node_id(2), - QuorumSet::new_with_node_ids(2, vec![test_node_id(1), test_node_id(3)]), - ); - let node_3 = ( - test_node_id(3), - QuorumSet::new_with_node_ids(2, vec![test_node_id(1), test_node_id(2)]), - ); - (node_1, node_2, node_3) -} From cb89f10c52e91cc38b31a2bee4b948a80fea0b5e Mon Sep 17 00:00:00 2001 From: Remoun Metyas Date: Mon, 25 Apr 2022 17:23:44 -0700 Subject: [PATCH 05/11] Temp fix for make_components -- this will get rewritten shortly --- ledger/streaming/api/src/convert/components.rs | 2 +- ledger/streaming/api/src/test_utils/components.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ledger/streaming/api/src/convert/components.rs b/ledger/streaming/api/src/convert/components.rs index 8a245a3108..5b53408aff 100644 --- a/ledger/streaming/api/src/convert/components.rs +++ b/ledger/streaming/api/src/convert/components.rs @@ -55,7 +55,7 @@ mod tests { #[test] fn test_roundtrip() { - let contents = BlockContents::new(vec![], vec![]); + let contents = BlockContents::default(); let block = Block::new_origin_block(&[]); let block_data = BlockData::new(block, contents, None); let quorum_set = make_quorum_set(); diff --git a/ledger/streaming/api/src/test_utils/components.rs b/ledger/streaming/api/src/test_utils/components.rs index 4d9826e996..ac13b63a8c 100644 --- a/ledger/streaming/api/src/test_utils/components.rs +++ b/ledger/streaming/api/src/test_utils/components.rs @@ -13,7 +13,7 @@ pub fn make_components(count: usize) -> Vec { let mut parent: Option = None; (0..count) .map(|i| { - let contents = BlockContents::new(vec![], vec![]); + let contents = BlockContents::default(); let block = if i == 0 { Block::new_origin_block(&[]) } else { From cd10eb3a4e0424bfe0ef5ca3331e97e029bc4479 Mon Sep 17 00:00:00 2001 From: Remoun Metyas Date: Wed, 13 Apr 2022 13:07:14 -0700 Subject: [PATCH 06/11] Add mc_transaction_core::SignedBlockMetadata with members and conversion helpers. --- Cargo.lock | 9 +- api/Cargo.toml | 3 + api/build.rs | 1 + api/proto/blockchain.proto | 28 ++++- api/proto/quorum_set.proto | 27 ++++ api/src/convert/archive_block.rs | 24 +++- api/src/convert/block_metadata.rs | 105 ++++++++++++++++ api/src/convert/mod.rs | 6 +- api/src/convert/node.rs | 31 +++++ api/src/convert/quorum_set.rs | 83 +++++++++++++ connection/src/traits.rs | 3 +- consensus/enclave/Cargo.toml | 3 +- consensus/enclave/trusted/Cargo.lock | 13 ++ fog/ingest/enclave/trusted/Cargo.lock | 13 ++ fog/ledger/enclave/trusted/Cargo.lock | 13 ++ fog/view/enclave/trusted/Cargo.lock | 13 ++ go-grpc-gateway/build.sh | 2 +- transaction/core/Cargo.toml | 10 +- transaction/core/src/blockchain/block_data.rs | 46 +++++-- .../core/src/blockchain/block_metadata.rs | 116 ++++++++++++++++++ transaction/core/src/blockchain/mod.rs | 16 ++- transaction/core/src/domain_separators.rs | 2 +- transaction/core/src/lib.rs | 70 +++-------- transaction/core/src/tx.rs | 64 +++++++--- transaction/std/Cargo.toml | 2 +- 25 files changed, 595 insertions(+), 108 deletions(-) create mode 100644 api/proto/quorum_set.proto create mode 100644 api/src/convert/block_metadata.rs create mode 100644 api/src/convert/node.rs create mode 100644 api/src/convert/quorum_set.rs create mode 100644 transaction/core/src/blockchain/block_metadata.rs diff --git a/Cargo.lock b/Cargo.lock index 12a1cbbdbb..3b5648e789 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3078,6 +3078,8 @@ dependencies = [ "generic-array 0.14.5", "mc-account-keys", "mc-attest-verifier-types", + "mc-common", + "mc-consensus-scp-core", "mc-crypto-keys", "mc-crypto-multisig", "mc-crypto-x509-test-vectors", @@ -5147,11 +5149,7 @@ dependencies = [ "mc-transaction-core", "mc-util-build-grpc", "mc-util-build-script", - "mc-util-from-random", "protobuf", - "rand 0.8.5", - "rand_core 0.6.3", - "rand_hc 0.3.1", ] [[package]] @@ -5194,7 +5192,6 @@ dependencies = [ "mc-api", "mc-common", "mc-crypto-keys", - "mc-crypto-rand", "mc-ledger-db", "mc-ledger-streaming-api", "mc-util-grpc", @@ -5693,7 +5690,9 @@ dependencies = [ "hkdf", "lazy_static", "mc-account-keys", + "mc-attest-verifier-types", "mc-common", + "mc-consensus-scp-core", "mc-crypto-box", "mc-crypto-digestible", "mc-crypto-digestible-test-utils", diff --git a/api/Cargo.toml b/api/Cargo.toml index 0acb89ae6d..cf114a7062 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -9,6 +9,8 @@ links = "mc-api" [dependencies] mc-account-keys = { path = "../account-keys" } mc-attest-verifier-types = { path = "../attest/verifier/types" } +mc-common = { path = "../common" } +mc-consensus-scp-core = { path = "../consensus/scp/core" } mc-crypto-keys = { path = "../crypto/keys" } mc-crypto-multisig = { path = "../crypto/multisig" } mc-transaction-core = { path = "../transaction/core" } @@ -34,6 +36,7 @@ mc-util-build-script = { path = "../util/build/script" } cargo-emit = "0.2.1" [dev-dependencies] +mc-consensus-scp-core = { path = "../consensus/scp/core", features = ["test_utils"] } mc-crypto-x509-test-vectors = { path = "../crypto/x509/test-vectors" } mc-test-vectors-b58-encodings = { path = "../test-vectors/b58-encodings" } mc-transaction-core-test-utils = { path = "../transaction/core/test-utils" } diff --git a/api/build.rs b/api/build.rs index b29adea51b..ccc5aa249d 100644 --- a/api/build.rs +++ b/api/build.rs @@ -18,6 +18,7 @@ fn main() { "blockchain.proto", "external.proto", "printable.proto", + "quorum_set.proto", "watcher.proto", ], ); diff --git a/api/proto/blockchain.proto b/api/proto/blockchain.proto index a23f2df414..517d1d1f98 100644 --- a/api/proto/blockchain.proto +++ b/api/proto/blockchain.proto @@ -4,6 +4,7 @@ syntax = "proto3"; import "external.proto"; +import "quorum_set.proto"; package blockchain; @@ -70,10 +71,32 @@ message BlockSignature { uint64 signed_at = 3; } +message BlockMetadata { + /// The Block ID. + BlockID block_id = 1; + + /// Quorum set configuration at the time of externalization. + quorum_set.QuorumSet quorum_set = 2; + + /// IAS report for the enclave which generated the signature. + external.VerificationReport verification_report = 3; +} + +message SignedBlockMetadata { + /// Metadata signed by the consensus node. + BlockMetadata contents = 1; + + /// Message signing key (signer). + external.Ed25519Public node_key = 2; + + /// Signature using `node_key` over the Digestible encoding of `contents`. + external.Ed25519Signature signature = 3; +} + // Version 1 of an archived block. // Note: The block.version field within the block may or may not be equal to 1. message ArchiveBlockV1 { - // Block + // The block (header). Block block = 1; // Contents of the block. @@ -81,6 +104,9 @@ message ArchiveBlockV1 { // Block signature, when available. BlockSignature signature = 3; + + /// Additional signed metadata about this block. + SignedBlockMetadata metadata = 4; } // An archived block. diff --git a/api/proto/quorum_set.proto b/api/proto/quorum_set.proto new file mode 100644 index 0000000000..92f5d37a69 --- /dev/null +++ b/api/proto/quorum_set.proto @@ -0,0 +1,27 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +// QuorumSet data types. +// Implemented here because the Rust definition is generic, so these types are for the current impl. + +syntax = "proto3"; + +package quorum_set; +import "external.proto"; +option go_package = "mobilecoin/api"; + +message Node { + string responder_id = 1; + external.Ed25519Public public_key = 2; +} + +message QuorumSetMember { + oneof member { + Node node = 1; + QuorumSet inner_set = 2; + } +} + +message QuorumSet { + uint32 threshold = 1; + repeated QuorumSetMember members = 2; +} diff --git a/api/src/convert/archive_block.rs b/api/src/convert/archive_block.rs index d8964e37a4..5d5801c3a9 100644 --- a/api/src/convert/archive_block.rs +++ b/api/src/convert/archive_block.rs @@ -1,7 +1,9 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + //! Convert to/from blockchain::ArchiveBlock -use crate::{blockchain, convert::ConversionError}; -use mc_transaction_core::{Block, BlockContents, BlockData, BlockSignature}; +use crate::{blockchain, ConversionError}; +use mc_transaction_core::{Block, BlockContents, BlockData, BlockSignature, SignedBlockMetadata}; use std::convert::TryFrom; /// Convert mc_transaction_core::BlockData --> blockchain::ArchiveBlock. @@ -14,6 +16,9 @@ impl From<&BlockData> for blockchain::ArchiveBlock { if let Some(signature) = src.signature() { archive_block_v1.set_signature(signature.into()); } + if let Some(metadata) = src.metadata() { + archive_block_v1.set_metadata(metadata.into()); + } archive_block } @@ -43,8 +48,19 @@ impl TryFrom<&blockchain::ArchiveBlock> for BlockData { .map_err(|_| ConversionError::InvalidSignature)?; } + let metadata = archive_block_v1 + .metadata + .as_ref() + .map(SignedBlockMetadata::try_from) + .transpose()?; + if block.contents_hash == block_contents.hash() { - Ok(BlockData::new(block, block_contents, signature)) + Ok(BlockData::new_with_metadata( + block, + block_contents, + signature, + metadata, + )) } else { Err(ConversionError::InvalidContents) } @@ -74,9 +90,11 @@ impl TryFrom<&blockchain::ArchiveBlocks> for Vec { // Ensure blocks_data form a legitimate chain of blocks. if blocks_data .iter() + // Verify that the block ID is consistent with the cached parent ID. .all(|data| data.block().is_block_id_valid()) && blocks_data .windows(2) + // Verify that the cached parent ID match the previous block's ID. .all(|window| window[1].block().parent_id == window[0].block().id) { Ok(blocks_data) diff --git a/api/src/convert/block_metadata.rs b/api/src/convert/block_metadata.rs new file mode 100644 index 0000000000..93080ab6b2 --- /dev/null +++ b/api/src/convert/block_metadata.rs @@ -0,0 +1,105 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +//! Convert to/from blockchain::BlockMetadata. + +use crate::{blockchain, ConversionError}; +use mc_transaction_core::{BlockMetadata, SignedBlockMetadata}; +use std::convert::{TryFrom, TryInto}; + +impl From<&BlockMetadata> for blockchain::BlockMetadata { + fn from(src: &BlockMetadata) -> Self { + let mut proto = Self::new(); + proto.set_block_id(src.block_id().into()); + if let Some(qs) = src.quorum_set() { + proto.set_quorum_set(qs.into()); + } + if let Some(avr) = src.verification_report() { + proto.set_verification_report(avr.into()); + } + proto + } +} + +impl TryFrom<&blockchain::BlockMetadata> for BlockMetadata { + type Error = ConversionError; + + fn try_from(src: &blockchain::BlockMetadata) -> Result { + let block_id = src.get_block_id().try_into()?; + let quorum_set = src.quorum_set.as_ref().map(TryInto::try_into).transpose()?; + let report = src + .verification_report + .as_ref() + .map(TryInto::try_into) + .transpose()?; + Ok(BlockMetadata::new(block_id, quorum_set, report)) + } +} + +impl From<&SignedBlockMetadata> for blockchain::SignedBlockMetadata { + fn from(src: &SignedBlockMetadata) -> Self { + let mut proto = Self::new(); + proto.set_contents(src.contents().into()); + proto.set_node_key(src.node_key().into()); + proto.set_signature(src.signature().into()); + proto + } +} + +impl TryFrom<&blockchain::SignedBlockMetadata> for SignedBlockMetadata { + type Error = ConversionError; + + fn try_from(src: &blockchain::SignedBlockMetadata) -> Result { + let contents = src.get_contents().try_into()?; + let node_key = src.get_node_key().try_into()?; + let signature = src.get_signature().try_into()?; + Ok(SignedBlockMetadata::new(contents, node_key, signature)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mc_attest_verifier_types::VerificationReport; + use mc_consensus_scp_core::test_utils::{test_node_id_and_signer, three_node_dense_graph}; + use mc_transaction_core::Block; + + fn make_metadata() -> BlockMetadata { + let block = Block::new_origin_block(&[]); + let quorum_set = Some(three_node_dense_graph().0 .1); + let verification_report = Some(VerificationReport { + sig: vec![1u8; 32].into(), + chain: vec![vec![1u8; 1], vec![2u8; 2], vec![3u8; 3]], + http_body: "testing".to_owned(), + }); + BlockMetadata::new(block.id, quorum_set, verification_report) + } + + fn make_signed_metadata() -> SignedBlockMetadata { + let key_pair = test_node_id_and_signer(42).1; + SignedBlockMetadata::from_contents_and_keypair(make_metadata(), &key_pair).unwrap() + } + + #[test] + fn block_metadata_round_trip() { + let source = make_metadata(); + + // decode(encode(source)) should be the identity function. + { + let proto = blockchain::BlockMetadata::from(&source); + let recovered = BlockMetadata::try_from(&proto).unwrap(); + assert_eq!(source, recovered); + } + } + + #[test] + fn signed_block_metadata_round_trip() { + let source = make_signed_metadata(); + + // decode(encode(source)) should be the identity function. + { + let proto = blockchain::SignedBlockMetadata::from(&source); + let recovered = SignedBlockMetadata::try_from(&proto).unwrap(); + assert_eq!(source, recovered); + } + } +} diff --git a/api/src/convert/mod.rs b/api/src/convert/mod.rs index 0601a189db..287a9fab3f 100644 --- a/api/src/convert/mod.rs +++ b/api/src/convert/mod.rs @@ -7,14 +7,13 @@ //! over the API. This module provides conversions between "equivalent" types, //! such as `mc_api::blockchain::Block` and `mc_transaction_core::Block`. -mod error; - // blockchain mod archive_block; mod block; mod block_contents; mod block_contents_hash; mod block_id; +mod block_metadata; mod block_signature; // external @@ -27,7 +26,9 @@ mod ed25519_signature; mod key_image; mod mint_config; mod mint_tx; +mod node; mod public_address; +mod quorum_set; mod ring_mlsag; mod ristretto_private; mod signature_rct_bulletproofs; @@ -44,6 +45,7 @@ mod verification_report; mod verification_signature; mod watcher; +mod error; pub use self::error::ConversionError; use std::path::PathBuf; diff --git a/api/src/convert/node.rs b/api/src/convert/node.rs new file mode 100644 index 0000000000..663c749049 --- /dev/null +++ b/api/src/convert/node.rs @@ -0,0 +1,31 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +//! Convert between Rust and proto representations of NodeID. + +use crate::{quorum_set::Node as NodeProto, ConversionError}; +use core::convert::TryFrom; +use mc_common::{NodeID, ResponderId}; +use std::{convert::TryInto, str::FromStr}; + +impl From<&NodeID> for NodeProto { + fn from(node: &NodeID) -> NodeProto { + let mut proto = NodeProto::new(); + proto.responder_id = node.responder_id.to_string(); + proto.set_public_key((&node.public_key).into()); + proto + } +} + +impl TryFrom<&NodeProto> for NodeID { + type Error = ConversionError; + + fn try_from(proto: &NodeProto) -> Result { + let responder_id = ResponderId::from_str(&proto.responder_id) + .map_err(|_| ConversionError::InvalidContents)?; + let public_key = proto.get_public_key().try_into()?; + Ok(NodeID { + responder_id, + public_key, + }) + } +} diff --git a/api/src/convert/quorum_set.rs b/api/src/convert/quorum_set.rs new file mode 100644 index 0000000000..dc77fc25ec --- /dev/null +++ b/api/src/convert/quorum_set.rs @@ -0,0 +1,83 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +//! Convert between Rust and proto representations of QuorumSet. + +use crate::{ + quorum_set::{ + QuorumSet as QuorumSetProto, QuorumSetMember as QuorumSetMemberProto, + QuorumSetMember_oneof_member, + }, + ConversionError, +}; +use mc_common::NodeID; +use mc_consensus_scp_core::{QuorumSet, QuorumSetMember}; +use std::convert::{Into, TryFrom, TryInto}; + +// mc_consensus_scp::QuorumSet +impl From<&QuorumSetMember> for QuorumSetMemberProto { + fn from(member: &QuorumSetMember) -> QuorumSetMemberProto { + use QuorumSetMember::*; + let mut proto = QuorumSetMemberProto::new(); + match member { + Node(id) => proto.set_node(id.into()), + InnerSet(qs) => proto.set_inner_set(qs.into()), + } + proto + } +} + +impl From<&QuorumSet> for QuorumSetProto { + fn from(qs: &QuorumSet) -> QuorumSetProto { + let mut proto = QuorumSetProto::new(); + proto.threshold = qs.threshold; + proto.set_members(qs.members.iter().map(Into::into).collect()); + proto + } +} + +impl TryFrom<&QuorumSetMemberProto> for QuorumSetMember { + type Error = ConversionError; + + fn try_from(proto: &QuorumSetMemberProto) -> Result { + use QuorumSetMember::*; + use QuorumSetMember_oneof_member::*; + match proto.member.as_ref() { + Some(node(id)) => Ok(Node(id.try_into()?)), + Some(inner_set(qs)) => Ok(InnerSet(qs.try_into()?)), + None => Err(ConversionError::ObjectMissing), + } + } +} + +impl TryFrom<&QuorumSetProto> for QuorumSet { + type Error = ConversionError; + + fn try_from(proto: &QuorumSetProto) -> Result { + let members = proto + .members + .iter() + .map(TryFrom::try_from) + .collect::, _>>()?; + Ok(Self { + threshold: proto.threshold, + members, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mc_consensus_scp_core::test_utils::three_node_dense_graph; + + #[test] + fn test_roundtrip() { + let set = three_node_dense_graph().0 .1; + assert!(set.is_valid()); + + let proto = QuorumSetProto::from(&set); + let set2 = QuorumSet::try_from(&proto).expect("scp::QuorumSet from proto"); + assert_eq!(set, set2); + assert!(set2.is_valid()); + } +} diff --git a/connection/src/traits.rs b/connection/src/traits.rs index ae8613a81c..b75551c1d7 100644 --- a/connection/src/traits.rs +++ b/connection/src/traits.rs @@ -7,11 +7,10 @@ use grpcio::Error as GrpcError; use mc_attest_core::VerificationReport; use mc_consensus_api::consensus_common::LastBlockInfoResponse; use mc_transaction_core::{tokens::Mob, tx::Tx, Block, BlockID, BlockIndex, Token, TokenId}; -use mc_util_serial::prost::alloc::fmt::Formatter; use mc_util_uri::ConnectionUri; use std::{ collections::BTreeMap, - fmt::{Debug, Display, Result as FmtResult}, + fmt::{Debug, Display, Formatter, Result as FmtResult}, hash::Hash, iter::FromIterator, ops::Range, diff --git a/consensus/enclave/Cargo.toml b/consensus/enclave/Cargo.toml index 2619b4685d..9b0591ab76 100644 --- a/consensus/enclave/Cargo.toml +++ b/consensus/enclave/Cargo.toml @@ -23,7 +23,8 @@ mc-transaction-core = { path = "../../transaction/core" } mc-util-serial = { path = "../../util/serial" } [build-dependencies] -cargo-emit = "0.2.1" mc-util-build-script = { path = "../../util/build/script" } mc-util-build-sgx = { path = "../../util/build/sgx" } + +cargo-emit = "0.2.1" pkg-config = "0.3" diff --git a/consensus/enclave/trusted/Cargo.lock b/consensus/enclave/trusted/Cargo.lock index cff70b9515..8f8c74c9b3 100644 --- a/consensus/enclave/trusted/Cargo.lock +++ b/consensus/enclave/trusted/Cargo.lock @@ -884,6 +884,17 @@ dependencies = [ "sha2", ] +[[package]] +name = "mc-consensus-scp-core" +version = "1.3.0-pre0" +dependencies = [ + "mc-common", + "mc-crypto-digestible", + "mc-crypto-keys", + "mc-util-from-random", + "serde", +] + [[package]] name = "mc-crypto-ake-enclave" version = "1.3.0-pre0" @@ -1191,7 +1202,9 @@ dependencies = [ "hkdf", "lazy_static", "mc-account-keys", + "mc-attest-verifier-types", "mc-common", + "mc-consensus-scp-core", "mc-crypto-box", "mc-crypto-digestible", "mc-crypto-hashes", diff --git a/fog/ingest/enclave/trusted/Cargo.lock b/fog/ingest/enclave/trusted/Cargo.lock index edd5c7502d..eae3f8d51c 100644 --- a/fog/ingest/enclave/trusted/Cargo.lock +++ b/fog/ingest/enclave/trusted/Cargo.lock @@ -814,6 +814,17 @@ dependencies = [ "slog", ] +[[package]] +name = "mc-consensus-scp-core" +version = "1.3.0-pre0" +dependencies = [ + "mc-common", + "mc-crypto-digestible", + "mc-crypto-keys", + "mc-util-from-random", + "serde", +] + [[package]] name = "mc-crypto-ake-enclave" version = "1.3.0-pre0" @@ -1303,7 +1314,9 @@ dependencies = [ "hkdf", "lazy_static", "mc-account-keys", + "mc-attest-verifier-types", "mc-common", + "mc-consensus-scp-core", "mc-crypto-box", "mc-crypto-digestible", "mc-crypto-hashes", diff --git a/fog/ledger/enclave/trusted/Cargo.lock b/fog/ledger/enclave/trusted/Cargo.lock index 2a57a0d642..5005e18104 100644 --- a/fog/ledger/enclave/trusted/Cargo.lock +++ b/fog/ledger/enclave/trusted/Cargo.lock @@ -818,6 +818,17 @@ dependencies = [ "slog", ] +[[package]] +name = "mc-consensus-scp-core" +version = "1.3.0-pre0" +dependencies = [ + "mc-common", + "mc-crypto-digestible", + "mc-crypto-keys", + "mc-util-from-random", + "serde", +] + [[package]] name = "mc-crypto-ake-enclave" version = "1.3.0-pre0" @@ -1288,7 +1299,9 @@ dependencies = [ "hkdf", "lazy_static", "mc-account-keys", + "mc-attest-verifier-types", "mc-common", + "mc-consensus-scp-core", "mc-crypto-box", "mc-crypto-digestible", "mc-crypto-hashes", diff --git a/fog/view/enclave/trusted/Cargo.lock b/fog/view/enclave/trusted/Cargo.lock index 289f77a2e7..5a62571d31 100644 --- a/fog/view/enclave/trusted/Cargo.lock +++ b/fog/view/enclave/trusted/Cargo.lock @@ -824,6 +824,17 @@ dependencies = [ "slog", ] +[[package]] +name = "mc-consensus-scp-core" +version = "1.3.0-pre0" +dependencies = [ + "mc-common", + "mc-crypto-digestible", + "mc-crypto-keys", + "mc-util-from-random", + "serde", +] + [[package]] name = "mc-crypto-ake-enclave" version = "1.3.0-pre0" @@ -1322,7 +1333,9 @@ dependencies = [ "hkdf", "lazy_static", "mc-account-keys", + "mc-attest-verifier-types", "mc-common", + "mc-consensus-scp-core", "mc-crypto-box", "mc-crypto-digestible", "mc-crypto-hashes", diff --git a/go-grpc-gateway/build.sh b/go-grpc-gateway/build.sh index 3d44592a28..96dec9d458 100755 --- a/go-grpc-gateway/build.sh +++ b/go-grpc-gateway/build.sh @@ -9,7 +9,7 @@ INC="-I ../fog/api/proto -I ../api/proto -I ../fog/report/api/proto -I ../attest mkdir -p docs gen -for proto_file in blockchain external consensus_client consensus_common fog_common ledger view report kex_rng attest; do +for proto_file in blockchain external quorum_set consensus_client consensus_common fog_common ledger view report kex_rng attest; do protoc $INC --grpc-gateway_out ./gen \ --go_out ./gen --go_opt paths=source_relative \ --go-grpc_out ./gen --go-grpc_opt paths=source_relative \ diff --git a/transaction/core/Cargo.toml b/transaction/core/Cargo.toml index 349bc44c07..720b8316ef 100644 --- a/transaction/core/Cargo.toml +++ b/transaction/core/Cargo.toml @@ -23,7 +23,9 @@ zeroize = { version = "1", default-features = false } # MobileCoin dependencies mc-account-keys = { path = "../../account-keys" } +mc-attest-verifier-types = { path = "../../attest/verifier/types", default-features = false } mc-common = { path = "../../common", default-features = false } +mc-consensus-scp-core = { path = "../../consensus/scp/core" } mc-crypto-box = { path = "../../crypto/box" } mc-crypto-digestible = { path = "../../crypto/digestible", features = ["dalek", "derive"] } mc-crypto-hashes = { path = "../../crypto/hashes" } @@ -42,16 +44,12 @@ bulletproofs-og = { version = "3.0.0-pre.1", default-features = false } [target.'cfg(any(target_feature = "avx2", target_feature = "avx"))'.dependencies] curve25519-dalek = { version = "4.0.0-pre.2", default-features = false, features = ["simd_backend", "nightly"] } -[dev-dependencies.proptest] -version = "1.0" # Only works for 0.9.1 or newer -default-features = false -# Enable all default features not known to break code coverage builds -features = ["default-code-coverage"] - [target.'cfg(not(any(target_feature = "avx2", target_feature = "avx")))'.dependencies] curve25519-dalek = { version = "4.0.0-pre.2", default-features = false, features = ["nightly", "u64_backend"] } [dev-dependencies] +# Only works for 0.9.1 or newer +proptest = { version = "1.0", default-features = false, features = ["default-code-coverage"] } rand = "0.8" rand_hc = "0.3" tempdir = "0.3" diff --git a/transaction/core/src/blockchain/block_data.rs b/transaction/core/src/blockchain/block_data.rs index 1d176b91f5..66727914c4 100644 --- a/transaction/core/src/blockchain/block_data.rs +++ b/transaction/core/src/blockchain/block_data.rs @@ -1,21 +1,19 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation -use crate::{Block, BlockContents, BlockSignature}; +use crate::{Block, BlockContents, BlockSignature, SignedBlockMetadata}; use mc_crypto_digestible::Digestible; -use prost::Message; use serde::{Deserialize, Serialize}; /// An object that holds all data included in and associated with a block. -#[derive(Clone, Deserialize, Digestible, Eq, Message, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Digestible, Eq, PartialEq, Serialize)] pub struct BlockData { - #[prost(message, required, tag = "1")] block: Block, - #[prost(message, required, tag = "2")] contents: BlockContents, - #[prost(message, tag = "3")] signature: Option, + + metadata: Option, } impl BlockData { @@ -25,26 +23,56 @@ impl BlockData { /// `block`: The block header /// `contents`: The block contents /// `signature`: A signature over the block + // TODO: Replace this with `new_with_metadata` pub fn new(block: Block, contents: BlockContents, signature: Option) -> Self { Self { block, contents, signature, + metadata: None, + } + } + + // TODO: Replace new() with this variant. + /// Create new block data: + /// + /// Arguments: + /// `block`: The block header + /// `contents`: The block contents + /// `signature`: A signature over the block + /// `metadata`: Signed metadata for the block + pub fn new_with_metadata( + block: Block, + contents: BlockContents, + signature: Option, + // Allows passing `Some(metadata)`, `metadata`, `None`. + metadata: impl Into>, + ) -> Self { + Self { + block, + contents, + signature, + metadata: metadata.into(), } } - /// Get the block + /// Get the block. pub fn block(&self) -> &Block { &self.block } - /// Get the contents + /// Get the contents. pub fn contents(&self) -> &BlockContents { &self.contents } - /// Get the signature + /// Get the signature. pub fn signature(&self) -> &Option { &self.signature } + + /// Get the metadata. + pub fn metadata(&self) -> &Option { + &self.metadata + } } diff --git a/transaction/core/src/blockchain/block_metadata.rs b/transaction/core/src/blockchain/block_metadata.rs new file mode 100644 index 0000000000..1e9dabfe4e --- /dev/null +++ b/transaction/core/src/blockchain/block_metadata.rs @@ -0,0 +1,116 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +use crate::BlockID; +use displaydoc::Display; +use mc_attest_verifier_types::VerificationReport; +use mc_consensus_scp_core::QuorumSet; +use mc_crypto_digestible::{Digestible, MerlinTranscript}; +use mc_crypto_keys::{ + Ed25519Pair, Ed25519Public, Ed25519Signature, SignatureError, Signer, Verifier, +}; +use serde::{Deserialize, Serialize}; + +/// Metadata for a block. +#[derive(Clone, Debug, Deserialize, Digestible, Display, Eq, PartialEq, Serialize)] +pub struct BlockMetadata { + /// The Block ID. + block_id: BlockID, + + /// Quorum set configuration at the time of externalization. + quorum_set: Option, + + /// IAS report for the enclave which generated the signature. + verification_report: Option, +} + +impl BlockMetadata { + /// Instantiate a [BlockMetadata] with the given data. + pub fn new( + block_id: BlockID, + quorum_set: Option, + verification_report: Option, + ) -> Self { + Self { + block_id, + quorum_set, + verification_report, + } + } + + /// Get the [BlockID]. + pub fn block_id(&self) -> &BlockID { + &self.block_id + } + + /// Get the [QuorumSet]. + pub fn quorum_set(&self) -> &Option { + &self.quorum_set + } + + /// Get the Attested [VerificationReport]. + pub fn verification_report(&self) -> &Option { + &self.verification_report + } + + fn digest(&self) -> [u8; 32] { + self.digest32::(b"block_metadata") + } +} + +/// Signed metadata for a block. +#[derive(Clone, Debug, Deserialize, Digestible, Display, Eq, PartialEq, Serialize)] +pub struct SignedBlockMetadata { + /// Metadata signed by the consensus node. + contents: BlockMetadata, + + /// Message signing key (signer). + node_key: Ed25519Public, + + /// Signature using `node_key` over the Digestible encoding of `contents`. + signature: Ed25519Signature, +} + +impl SignedBlockMetadata { + /// Instantiate a [SignedBlockMetadata] with the given data. + pub fn new( + contents: BlockMetadata, + node_key: Ed25519Public, + signature: Ed25519Signature, + ) -> Self { + Self { + contents, + node_key, + signature, + } + } + + /// Instantiate a [SignedBlockMetadata] by signing the given [BlockMetadata] + /// with the given [Ed25519Pair]. + pub fn from_contents_and_keypair( + contents: BlockMetadata, + key_pair: &Ed25519Pair, + ) -> Result { + let signature = key_pair.try_sign(&contents.digest())?; + Ok(Self::new(contents, key_pair.public_key(), signature)) + } + + /// Verify that this signature is over a given block. + pub fn verify(&self, contents: &BlockMetadata) -> Result<(), SignatureError> { + self.node_key.verify(&contents.digest(), &self.signature) + } + + /// Get the [BlockMetadata]. + pub fn contents(&self) -> &BlockMetadata { + &self.contents + } + + /// Get the signing key. + pub fn node_key(&self) -> &Ed25519Public { + &self.node_key + } + + /// Get the signature. + pub fn signature(&self) -> &Ed25519Signature { + &self.signature + } +} diff --git a/transaction/core/src/blockchain/mod.rs b/transaction/core/src/blockchain/mod.rs index 2bc31c0ebd..6b1ba5c416 100644 --- a/transaction/core/src/blockchain/mod.rs +++ b/transaction/core/src/blockchain/mod.rs @@ -6,15 +6,19 @@ mod block; mod block_contents; mod block_data; mod block_id; +mod block_metadata; mod block_signature; mod block_version; -pub use block::*; -pub use block_contents::*; -pub use block_data::*; -pub use block_id::*; -pub use block_signature::*; -pub use block_version::{BlockVersion, BlockVersionError}; +pub use self::{ + block::*, + block_contents::*, + block_data::*, + block_id::*, + block_metadata::*, + block_signature::*, + block_version::{BlockVersion, BlockVersionError}, +}; use displaydoc::Display; diff --git a/transaction/core/src/domain_separators.rs b/transaction/core/src/domain_separators.rs index f5e839a4b1..945e64faa8 100644 --- a/transaction/core/src/domain_separators.rs +++ b/transaction/core/src/domain_separators.rs @@ -50,7 +50,7 @@ pub const TXOUT_CONFIRMATION_NUMBER_DOMAIN_TAG: &str = "mc_tx_out_confirmation_n /// Domain separator for computing the extended message digest pub const EXTENDED_MESSAGE_DOMAIN_TAG: &str = "mc_extended_message"; -// Domain separator for hashing MintConfigTxPrefixs +/// Domain separator for hashing MintConfigTxPrefixs pub const MINT_CONFIG_TX_PREFIX_DOMAIN_TAG: &str = "mc_mint_config_tx_prefix"; /// Domain separator for hashing MintTxPrefixs diff --git a/transaction/core/src/lib.rs b/transaction/core/src/lib.rs index b17cf27bf3..cf83558964 100644 --- a/transaction/core/src/lib.rs +++ b/transaction/core/src/lib.rs @@ -15,73 +15,31 @@ extern crate std; #[macro_use] extern crate lazy_static; -use crate::onetime_keys::create_shared_secret; -use mc_crypto_keys::{KeyError, RistrettoPrivate, RistrettoPublic}; - -mod amount; -mod blockchain; -mod domain_separators; -mod memo; -mod token; -mod tx_error; - +pub mod amount; +pub mod blockchain; pub mod constants; +pub mod domain_separators; pub mod encrypted_fog_hint; pub mod fog_hint; pub mod membership_proofs; +pub mod memo; pub mod mint; pub mod onetime_keys; pub mod range_proofs; pub mod ring_signature; +pub mod token; pub mod tx; +pub mod tx_error; pub mod validation; #[cfg(test)] pub mod proptest_fixtures; -pub use amount::{Amount, AmountError, Commitment, CompressedCommitment, MaskedAmount}; -pub use blockchain::*; -pub use memo::{EncryptedMemo, MemoError, MemoPayload}; -pub use token::{tokens, Token, TokenId}; -pub use tx::MemoContext; -pub use tx_error::{NewMemoError, NewTxError, ViewKeyMatchError}; - -use core::convert::TryFrom; -use mc_account_keys::AccountKey; -use onetime_keys::recover_public_subaddress_spend_key; -use tx::TxOut; - -/// Get the shared secret for a transaction output. -/// -/// # Arguments -/// * `view_key` - The recipient's private View key. -/// * `tx_public_key` - The public key of the transaction. -pub fn get_tx_out_shared_secret( - view_key: &RistrettoPrivate, - tx_public_key: &RistrettoPublic, -) -> RistrettoPublic { - create_shared_secret(tx_public_key, view_key) -} - -/// Helper which checks if a particular subaddress of an account key matches a -/// TxOut -/// -/// This is not the most efficient way to check when you have many subaddresses, -/// for that you should create a table and use -/// recover_public_subaddress_spend_key directly. -/// -/// However some clients are only using one or two subaddresses. -/// Validating that a TxOut is owned by the change subaddress is a frequently -/// needed operation. -pub fn subaddress_matches_tx_out( - acct: &AccountKey, - subaddress_index: u64, - output: &TxOut, -) -> Result { - let sub_addr_spend = recover_public_subaddress_spend_key( - acct.view_private_key(), - &RistrettoPublic::try_from(&output.target_key)?, - &RistrettoPublic::try_from(&output.public_key)?, - ); - Ok(sub_addr_spend == RistrettoPublic::from(&acct.subaddress_spend_private(subaddress_index))) -} +pub use self::{ + amount::{Amount, AmountError, Commitment, CompressedCommitment, MaskedAmount}, + blockchain::*, + memo::{EncryptedMemo, MemoError, MemoPayload}, + token::{tokens, Token, TokenId}, + tx::*, + tx_error::{NewMemoError, NewTxError, ViewKeyMatchError}, +}; diff --git a/transaction/core/src/tx.rs b/transaction/core/src/tx.rs index 60cfb64912..6aade5c6f7 100644 --- a/transaction/core/src/tx.rs +++ b/transaction/core/src/tx.rs @@ -2,34 +2,70 @@ //! Definition of a MobileCoin transaction and a MobileCoin TxOut +use crate::{ + amount::{Amount, AmountError, MaskedAmount}, + domain_separators::TXOUT_CONFIRMATION_NUMBER_DOMAIN_TAG, + encrypted_fog_hint::EncryptedFogHint, + membership_proofs::Range, + memo::{EncryptedMemo, MemoPayload}, + onetime_keys::{ + create_shared_secret, create_tx_out_public_key, create_tx_out_target_key, + recover_public_subaddress_spend_key, + }, + ring_signature::{KeyImage, SignatureRctBulletproofs}, + CompressedCommitment, NewMemoError, NewTxError, ViewKeyMatchError, +}; use alloc::vec::Vec; use core::{convert::TryFrom, fmt}; -use mc_account_keys::PublicAddress; +use mc_account_keys::{AccountKey, PublicAddress}; use mc_common::Hash; use mc_crypto_digestible::{Digestible, MerlinTranscript}; use mc_crypto_hashes::{Blake2b256, Digest}; -use mc_crypto_keys::{CompressedRistrettoPublic, RistrettoPrivate, RistrettoPublic}; +use mc_crypto_keys::{CompressedRistrettoPublic, KeyError, RistrettoPrivate, RistrettoPublic}; use mc_util_repr_bytes::{ derive_prost_message_from_repr_bytes, typenum::U32, GenericArray, ReprBytes, }; use prost::Message; use serde::{Deserialize, Serialize}; -use crate::{ - amount::{Amount, AmountError, MaskedAmount}, - domain_separators::TXOUT_CONFIRMATION_NUMBER_DOMAIN_TAG, - encrypted_fog_hint::EncryptedFogHint, - get_tx_out_shared_secret, - membership_proofs::Range, - memo::{EncryptedMemo, MemoPayload}, - onetime_keys::{create_shared_secret, create_tx_out_public_key, create_tx_out_target_key}, - ring_signature::{KeyImage, SignatureRctBulletproofs}, - CompressedCommitment, NewMemoError, NewTxError, ViewKeyMatchError, -}; - /// Transaction hash length, in bytes. pub const TX_HASH_LEN: usize = 32; +/// Get the shared secret for a transaction output. +/// +/// # Arguments +/// * `view_key` - The recipient's private View key. +/// * `tx_public_key` - The public key of the transaction. +pub fn get_tx_out_shared_secret( + view_key: &RistrettoPrivate, + tx_public_key: &RistrettoPublic, +) -> RistrettoPublic { + create_shared_secret(tx_public_key, view_key) +} + +/// Helper which checks if a particular subaddress of an account key matches a +/// TxOut +/// +/// This is not the most efficient way to check when you have many subaddresses, +/// for that you should create a table and use +/// recover_public_subaddress_spend_key directly. +/// +/// However some clients are only using one or two subaddresses. +/// Validating that a TxOut is owned by the change subaddress is a frequently +/// needed operation. +pub fn subaddress_matches_tx_out( + acct: &AccountKey, + subaddress_index: u64, + output: &TxOut, +) -> Result { + let sub_addr_spend = recover_public_subaddress_spend_key( + acct.view_private_key(), + &RistrettoPublic::try_from(&output.target_key)?, + &RistrettoPublic::try_from(&output.public_key)?, + ); + Ok(sub_addr_spend == RistrettoPublic::from(&acct.subaddress_spend_private(subaddress_index))) +} + #[derive( Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Digestible, )] diff --git a/transaction/std/Cargo.toml b/transaction/std/Cargo.toml index 168a4ef48b..f6b032ea84 100644 --- a/transaction/std/Cargo.toml +++ b/transaction/std/Cargo.toml @@ -25,7 +25,7 @@ mc-crypto-keys = { path = "../../crypto/keys", default-features = false } mc-fog-report-validation = { path = "../../fog/report/validation" } mc-transaction-core = { path = "../../transaction/core" } mc-util-from-random = { path = "../../util/from-random" } -mc-util-serial = { path = "../../util/serial" } +mc-util-serial = { path = "../../util/serial", features = ["std"] } [target.'cfg(any(target_feature = "avx2", target_feature = "avx"))'.dependencies] curve25519-dalek = { version = "4.0.0-pre.2", default-features = false, features = ["simd_backend", "nightly"] } From d0b492ea3d31c23fedf14e05bc995d0f68f756f9 Mon Sep 17 00:00:00 2001 From: Remoun Metyas Date: Mon, 25 Apr 2022 17:37:30 -0700 Subject: [PATCH 07/11] Migrate from BlockStreamComponents/BlockWithQuorumSet to BlockData/ArchiveBlock --- ledger/streaming/api/Cargo.toml | 8 +- ledger/streaming/api/build.rs | 6 +- ledger/streaming/api/proto/quorum_set.proto | 27 ----- .../api/proto/streaming_blocks.proto | 18 +-- ledger/streaming/api/src/components.rs | 35 ------ .../api/src/convert/archive_block.rs | 65 ----------- .../streaming/api/src/convert/components.rs | 75 ------------ ledger/streaming/api/src/convert/mod.rs | 17 --- .../streaming/api/src/convert/quorum_set.rs | 109 ------------------ ledger/streaming/api/src/lib.rs | 11 +- ledger/streaming/api/src/response.rs | 83 ------------- ledger/streaming/api/src/test_utils/blocks.rs | 41 +++++++ .../api/src/test_utils/components.rs | 39 ------- .../streaming/api/src/test_utils/fetcher.rs | 20 ++-- ledger/streaming/api/src/test_utils/mod.rs | 7 +- .../api/src/test_utils/quorum_set.rs | 33 ------ .../streaming/api/src/test_utils/response.rs | 40 +++---- ledger/streaming/api/src/test_utils/stream.rs | 43 ++----- ledger/streaming/api/src/traits.rs | 10 +- ledger/streaming/client/src/backfill.rs | 35 +++--- .../streaming/client/src/block_validator.rs | 85 ++++++-------- ledger/streaming/client/src/grpc.rs | 96 +++++++-------- ledger/streaming/client/src/http_fetcher.rs | 88 +++++++------- ledger/streaming/client/src/ledger_sink.rs | 88 +++++++------- ledger/streaming/client/src/scp_validator.rs | 86 +++++++------- .../streaming/client/src/test_utils/server.rs | 5 +- ledger/streaming/publisher/Cargo.toml | 1 - .../streaming/publisher/src/archive_sink.rs | 56 ++++----- ledger/streaming/publisher/src/grpc.rs | 52 +++------ 29 files changed, 366 insertions(+), 913 deletions(-) delete mode 100644 ledger/streaming/api/proto/quorum_set.proto delete mode 100644 ledger/streaming/api/src/components.rs delete mode 100644 ledger/streaming/api/src/convert/archive_block.rs delete mode 100644 ledger/streaming/api/src/convert/components.rs delete mode 100644 ledger/streaming/api/src/convert/mod.rs delete mode 100644 ledger/streaming/api/src/convert/quorum_set.rs delete mode 100644 ledger/streaming/api/src/response.rs create mode 100644 ledger/streaming/api/src/test_utils/blocks.rs delete mode 100644 ledger/streaming/api/src/test_utils/components.rs delete mode 100644 ledger/streaming/api/src/test_utils/quorum_set.rs diff --git a/ledger/streaming/api/Cargo.toml b/ledger/streaming/api/Cargo.toml index 9b59f43c49..c37217b29f 100644 --- a/ledger/streaming/api/Cargo.toml +++ b/ledger/streaming/api/Cargo.toml @@ -8,7 +8,7 @@ build = "build.rs" edition = "2018" [features] -test_utils = [] +test_utils = ["mc-consensus-scp/test_utils"] [dependencies] mc-api = { path = "../../../api" } @@ -33,9 +33,5 @@ cargo-emit = "0.2" [dev-dependencies] mc-common = { path = "../../../common", features = ["loggers"] } +mc-consensus-scp = { path = "../../../consensus/scp", features = ["test_utils"] } mc-ledger-streaming-api = { path = "../api", features = ["test_utils"] } -mc-util-from-random = { path = "../../../util/from-random" } - -rand = "0.8" -rand_core = "0.6" -rand_hc = "0.3" diff --git a/ledger/streaming/api/build.rs b/ledger/streaming/api/build.rs index 72c6bc79be..576810f5ad 100644 --- a/ledger/streaming/api/build.rs +++ b/ledger/streaming/api/build.rs @@ -21,10 +21,6 @@ fn main() { mc_util_build_grpc::compile_protos_and_generate_mod_rs( &all_proto_dirs, - &[ - "archive_blocks.proto", - "quorum_set.proto", - "streaming_blocks.proto", - ], + &["archive_blocks.proto", "streaming_blocks.proto"], ); } diff --git a/ledger/streaming/api/proto/quorum_set.proto b/ledger/streaming/api/proto/quorum_set.proto deleted file mode 100644 index de4c03ad1a..0000000000 --- a/ledger/streaming/api/proto/quorum_set.proto +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation - -// QuorumSet data types. -// Implemented here because the Rust definition is generic, so these types are for the current impl. - -syntax = "proto3"; - -package quorum_set; - -import "external.proto"; - -message Node { - string responder_id = 1; - external.Ed25519Public public_key = 2; -} - -message QuorumSetMember { - oneof member { - Node node = 1; - QuorumSet inner_set = 2; - } -} - -message QuorumSet { - uint32 threshold = 1; - repeated QuorumSetMember members = 2; -} diff --git a/ledger/streaming/api/proto/streaming_blocks.proto b/ledger/streaming/api/proto/streaming_blocks.proto index 2fa719697d..53043c34d8 100644 --- a/ledger/streaming/api/proto/streaming_blocks.proto +++ b/ledger/streaming/api/proto/streaming_blocks.proto @@ -7,28 +7,12 @@ syntax = "proto3"; package streaming_blocks; import "blockchain.proto"; -import "external.proto"; -import "quorum_set.proto"; message SubscribeRequest { /// Block index to start from. uint64 starting_height = 1; } -message BlockWithQuorumSet { - /// The block data. - blockchain.ArchiveBlock block = 1; - /// QuorumSet when the block was externalized. - quorum_set.QuorumSet quorum_set = 2; - /// Attestation Verification report. - external.VerificationReport report = 3; -} - -message SubscribeResponse { - BlockWithQuorumSet result = 1; - external.Ed25519Signature result_signature = 2; -} - service LedgerUpdates { - rpc Subscribe(SubscribeRequest) returns (stream SubscribeResponse); + rpc Subscribe(SubscribeRequest) returns (stream blockchain.ArchiveBlock); } diff --git a/ledger/streaming/api/src/components.rs b/ledger/streaming/api/src/components.rs deleted file mode 100644 index 2ea15fc0b7..0000000000 --- a/ledger/streaming/api/src/components.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation - -use mc_attest_core::VerificationReport; -use mc_consensus_scp::QuorumSet; -use mc_crypto_digestible::Digestible; -use mc_transaction_core::BlockData; - -/// Wrapper for the components needed for block streaming. -#[derive(Clone, Debug, Digestible, PartialEq, Eq)] -pub struct BlockStreamComponents { - /// The block data. - pub block_data: BlockData, - /// The SCP quorum set for this block. - /// Optional; will be required with a future BlockVersion (see #1682). - pub quorum_set: Option, - /// The AVR for this block. - /// Optional; will be required with a future BlockVersion (see #1682). - pub verification_report: Option, -} - -impl BlockStreamComponents { - /// Instantiate an instance with the given values. - pub fn new( - block_data: BlockData, - // Allows passing `Some(qs)`, `qs`, or `None`. - quorum_set: impl Into>, - verification_report: impl Into>, - ) -> Self { - Self { - block_data, - quorum_set: quorum_set.into(), - verification_report: verification_report.into(), - } - } -} diff --git a/ledger/streaming/api/src/convert/archive_block.rs b/ledger/streaming/api/src/convert/archive_block.rs deleted file mode 100644 index bd6e193fd5..0000000000 --- a/ledger/streaming/api/src/convert/archive_block.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Convert between blockchain::ArchiveBlock and BlockStreamComponents - -use crate::BlockStreamComponents; -use mc_api::{blockchain, ConversionError}; -use mc_transaction_core::BlockData; -use std::convert::TryFrom; - -/// Convert BlockStreamComponents --> blockchain::ArchiveBlock. -impl From<&BlockStreamComponents> for blockchain::ArchiveBlock { - fn from(src: &BlockStreamComponents) -> Self { - // TODO(#1682): Include QuorumSet, VerificationReport. - blockchain::ArchiveBlock::from(&src.block_data) - } -} - -/// Convert from blockchain::ArchiveBlock --> BlockStreamComponents -impl TryFrom<&blockchain::ArchiveBlock> for BlockStreamComponents { - type Error = ConversionError; - - fn try_from(src: &blockchain::ArchiveBlock) -> Result { - let block_data = BlockData::try_from(src)?; - // TODO(#1682): Include QuorumSet, VerificationReport. - Ok(BlockStreamComponents { - block_data, - quorum_set: None, - verification_report: None, - }) - } -} - -// Ideally this would be: -// impl From<&[BlockStreamComponents]> for blockchain::ArchiveBlocks -// but that fails to compile with "error[E0117]: only traits defined in the -// current crate can be implemented for arbitrary types" -/// Convert &\[[BlockStreamComponents]] -> [blockchain::ArchiveBlocks] -#[allow(unused)] -pub fn components_to_archive_blocks(src: &[BlockStreamComponents]) -> blockchain::ArchiveBlocks { - let mut archive_blocks = blockchain::ArchiveBlocks::new(); - // TODO(#1682): Include QuorumSet, VerificationReport. - let blocks = src.iter().map(|c| (&c.block_data).into()); - archive_blocks.set_blocks(blocks.collect()); - archive_blocks -} - -// Ideally this would be: -// impl TryFrom<&blockchain::ArchiveBlocks> for Vec -// but that fails to compile with "error[E0117]: only traits defined in the -// current crate can be implemented for arbitrary types" -/// Convert blockchain::ArchiveBlocks -> Vec -#[allow(unused)] -pub fn archive_blocks_to_components( - src: &blockchain::ArchiveBlocks, -) -> crate::Result> { - let blocks_data = Vec::::try_from(src)?; - // TODO(#1682): Include QuorumSet, VerificationReport. - let components = blocks_data - .into_iter() - .map(|block_data| BlockStreamComponents { - block_data, - quorum_set: None, - verification_report: None, - }) - .collect(); - Ok(components) -} diff --git a/ledger/streaming/api/src/convert/components.rs b/ledger/streaming/api/src/convert/components.rs deleted file mode 100644 index 5b53408aff..0000000000 --- a/ledger/streaming/api/src/convert/components.rs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation - -//! Convert between Rust and proto representations of BlockStreamComponents. - -use crate::{streaming_blocks::BlockWithQuorumSet, BlockStreamComponents}; -use mc_api::ConversionError; -use mc_attest_core::VerificationReport; -use mc_consensus_scp::QuorumSet; -use std::convert::{TryFrom, TryInto}; - -impl From<&BlockStreamComponents> for BlockWithQuorumSet { - fn from(data: &BlockStreamComponents) -> Self { - let mut proto = BlockWithQuorumSet::new(); - proto.set_block((&data.block_data).into()); - // TODO(#1682): Error when fields are missing. - if let Some(ref quorum_set) = data.quorum_set { - proto.set_quorum_set(quorum_set.into()); - } - if let Some(ref report) = data.verification_report { - proto.set_report(report.into()); - } - proto - } -} - -impl TryFrom<&BlockWithQuorumSet> for BlockStreamComponents { - type Error = ConversionError; - - fn try_from(proto: &BlockWithQuorumSet) -> Result { - let block_data = proto.get_block().try_into()?; - // TODO(#1682): Error when fields are missing. - let quorum_set = proto - .quorum_set - .as_ref() - .map(QuorumSet::try_from) - .transpose()?; - let verification_report = proto - .report - .as_ref() - .map(VerificationReport::try_from) - .transpose()?; - Ok(BlockStreamComponents { - block_data, - quorum_set, - verification_report, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::make_quorum_set; - use mc_transaction_core::{Block, BlockContents, BlockData}; - - #[test] - fn test_roundtrip() { - let contents = BlockContents::default(); - let block = Block::new_origin_block(&[]); - let block_data = BlockData::new(block, contents, None); - let quorum_set = make_quorum_set(); - let data = BlockStreamComponents { - block_data, - quorum_set: Some(quorum_set), - verification_report: None, - }; - - let proto = BlockWithQuorumSet::from(&data); - let data2 = BlockStreamComponents::try_from(&proto).expect("Failed to parse proto"); - assert_eq!(data, data2); - - let proto2 = BlockWithQuorumSet::from(&data2); - assert_eq!(proto, proto2); - } -} diff --git a/ledger/streaming/api/src/convert/mod.rs b/ledger/streaming/api/src/convert/mod.rs deleted file mode 100644 index de7f9c6097..0000000000 --- a/ledger/streaming/api/src/convert/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation - -//! Conversions between "API types" and "domain/persistence types" for Ledger -//! Streaming API. -//! -//! gRPC and Protobuf provide a reduced selection of types, and so there are -//! some differences between values stored in the ledger and values transmitted -//! over the API. This module provides conversions between "equivalent" types, -//! such as `mc_ledger_streaming_api::QuorumSet` and -//! `mc_consensus_scp::QuorumSet`. - -mod archive_block; -mod components; -mod quorum_set; - -pub use self::{archive_block::*, components::*, quorum_set::*}; -pub use mc_api::ConversionError; diff --git a/ledger/streaming/api/src/convert/quorum_set.rs b/ledger/streaming/api/src/convert/quorum_set.rs deleted file mode 100644 index 52b4011403..0000000000 --- a/ledger/streaming/api/src/convert/quorum_set.rs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation - -//! Convert between Rust and proto representations of QuorumSet. - -use crate::{ - convert::ConversionError, - quorum_set::{ - Node as NodeProto, QuorumSet as QuorumSetProto, QuorumSetMember as QuorumSetMemberProto, - }, -}; -use mc_common::NodeID; -use mc_consensus_scp::{QuorumSet, QuorumSetMember}; -use std::{ - convert::{Into, TryFrom, TryInto}, - str::FromStr, -}; - -impl From<&NodeID> for NodeProto { - fn from(node: &NodeID) -> NodeProto { - let mut proto = NodeProto::new(); - proto.responder_id = node.responder_id.to_string(); - proto.set_public_key((&node.public_key).into()); - proto - } -} - -impl From<&QuorumSetMember> for QuorumSetMemberProto { - fn from(member: &QuorumSetMember) -> QuorumSetMemberProto { - let mut proto = QuorumSetMemberProto::new(); - match member { - QuorumSetMember::Node(id) => proto.set_node(id.into()), - QuorumSetMember::InnerSet(qs) => proto.set_inner_set(qs.into()), - } - proto - } -} - -impl From<&QuorumSet> for QuorumSetProto { - fn from(qs: &QuorumSet) -> QuorumSetProto { - let mut proto = QuorumSetProto::new(); - proto.threshold = qs.threshold; - let members: Vec = qs.members.iter().map(Into::into).collect(); - proto.set_members(members.into()); - proto - } -} - -impl TryFrom<&NodeProto> for NodeID { - type Error = ConversionError; - - fn try_from(proto: &NodeProto) -> Result { - let responder_id = mc_common::ResponderId::from_str(&proto.responder_id) - .map_err(|_| ConversionError::InvalidContents)?; - let public_key = proto.get_public_key().try_into()?; - Ok(NodeID { - responder_id, - public_key, - }) - } -} - -impl TryFrom<&QuorumSetMemberProto> for QuorumSetMember { - type Error = ConversionError; - - fn try_from(proto: &QuorumSetMemberProto) -> Result { - use crate::quorum_set::QuorumSetMember_oneof_member as oneof; - match proto.member.as_ref() { - None => Err(ConversionError::InvalidContents), - Some(m) => match m { - oneof::node(id) => Ok(QuorumSetMember::Node(id.try_into()?)), - oneof::inner_set(set) => Ok(QuorumSetMember::InnerSet(set.try_into()?)), - }, - } - } -} - -impl TryFrom<&QuorumSetProto> for QuorumSet { - type Error = ConversionError; - - fn try_from(proto: &QuorumSetProto) -> Result { - let members = proto - .members - .iter() - .map(TryFrom::try_from) - .collect::, Self::Error>>()?; - Ok(QuorumSet { - threshold: proto.threshold, - members, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::make_quorum_set; - - #[test] - fn test_roundtrip() { - let set = make_quorum_set(); - let proto = QuorumSetProto::from(&set); - let set2 = QuorumSet::try_from(&proto).expect("conversion from proto failed"); - assert!(set2.is_valid()); - assert_eq!(set, set2); - - let proto2 = QuorumSetProto::from(&set2); - assert_eq!(proto, proto2); - } -} diff --git a/ledger/streaming/api/src/lib.rs b/ledger/streaming/api/src/lib.rs index 2325238db3..2e54b10f43 100644 --- a/ledger/streaming/api/src/lib.rs +++ b/ledger/streaming/api/src/lib.rs @@ -8,15 +8,12 @@ #![deny(missing_docs)] mod autogenerated_code { - use mc_api::{blockchain, external}; + use mc_api::blockchain; // Include the auto-generated code. include!(concat!(env!("OUT_DIR"), "/protos-auto-gen/mod.rs")); } -mod components; -mod convert; mod error; -mod response; mod traits; #[cfg(any(test, feature = "test_utils"))] @@ -24,9 +21,9 @@ pub mod test_utils; pub use crate::{ autogenerated_code::*, - components::BlockStreamComponents, - convert::*, error::{Error, Result}, - response::{make_subscribe_response, parse_subscribe_response}, traits::{BlockFetcher, BlockStream}, }; + +pub use mc_api::blockchain::{ArchiveBlock, ArchiveBlocks}; +pub use mc_transaction_core::{BlockData, BlockIndex}; diff --git a/ledger/streaming/api/src/response.rs b/ledger/streaming/api/src/response.rs deleted file mode 100644 index f2d42c04f9..0000000000 --- a/ledger/streaming/api/src/response.rs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation - -use crate::{streaming_blocks::SubscribeResponse, BlockStreamComponents, Result}; -use mc_crypto_digestible::{Digestible, MerlinTranscript}; -use mc_crypto_keys::{Ed25519Pair, Ed25519Public, Ed25519Signature, Signer, Verifier}; -use std::convert::TryFrom; - -/// Creates a signed SubscribeResponse from the given components and signer. -pub fn make_subscribe_response( - data: &BlockStreamComponents, - signer: &Ed25519Pair, -) -> Result { - let mut proto = SubscribeResponse::new(); - proto.set_result(data.into()); - - let digest = get_digest(data); - let signature = signer.try_sign(&digest)?; - proto.set_result_signature((&signature).into()); - Ok(proto) -} - -/// Parses and validates BlockStreamComponents from a given SubscribeResponse -/// and public key. -pub fn parse_subscribe_response( - proto: &SubscribeResponse, - public_key: &Ed25519Public, -) -> Result { - let result = BlockStreamComponents::try_from(proto.get_result())?; - let result_signature = Ed25519Signature::try_from(proto.get_result_signature())?; - // Validate result against result_signature. - let digest = get_digest(&result); - public_key.verify(&digest, &result_signature)?; - Ok(result) -} - -fn get_digest(data: &BlockStreamComponents) -> [u8; 32] { - data.digest32::(b"block_stream_components") -} - -#[cfg(test)] -mod tests { - use std::assert_matches::assert_matches; - - use super::*; - use crate::{test_utils::make_components, Error}; - use mc_util_from_random::FromRandom; - use rand_core::SeedableRng; - use rand_hc::Hc128Rng; - - #[test] - fn validate() { - let mut rng = Hc128Rng::from_seed([1u8; 32]); - let signer = Ed25519Pair::from_random(&mut rng); - let public_key = signer.public_key(); - let signer2 = Ed25519Pair::from_random(&mut rng); - let public_key2 = signer2.public_key(); - - let components = make_components(2); - let responses: Vec = components - .iter() - .map(|c| make_subscribe_response(&c, &signer)) - .collect::>>() - .expect("make_subscribe_response"); - - responses - .iter() - .zip(components.iter()) - .for_each(|(response, components)| { - let digest = get_digest(components); - let signature = signer.sign(&digest); - assert_eq!(*response.get_result_signature(), (&signature).into()); - - let parsed = parse_subscribe_response(response, &public_key) - .expect("parse_subscribe_response"); - assert_eq!(parsed, *components); - - assert_matches!( - parse_subscribe_response(response, &public_key2), - Err(Error::Signature(_)) - ); - }); - } -} diff --git a/ledger/streaming/api/src/test_utils/blocks.rs b/ledger/streaming/api/src/test_utils/blocks.rs new file mode 100644 index 0000000000..321581ae54 --- /dev/null +++ b/ledger/streaming/api/src/test_utils/blocks.rs @@ -0,0 +1,41 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +//! Test helpers for creating [BlockData]. + +use mc_consensus_scp::test_utils::test_node_id_and_signer; +use mc_transaction_core::{ + tx::TxOutMembershipElement, Block, BlockContents, BlockData, BlockMetadata, BlockVersion, + SignedBlockMetadata, +}; + +/// Generate the specified number of [BlockData] +pub fn make_blocks(count: usize) -> Vec { + let (_node_id, signer) = test_node_id_and_signer(0); + (0..count) + .scan(None, |parent, i| { + let contents = BlockContents::default(); + let block = if i == 0 { + Block::new_origin_block(&[]) + } else { + let root_element = TxOutMembershipElement::default(); + Block::new_with_parent( + BlockVersion::MAX, + parent.as_ref().unwrap(), + &root_element, + &contents, + ) + }; + *parent = Some(block.clone()); + + let quorum_set = None; + let verification_report = None; + let metadata_contents = + BlockMetadata::new(block.id.clone(), quorum_set, verification_report); + let metadata = + SignedBlockMetadata::from_contents_and_keypair(metadata_contents, &signer) + .expect("SignedBlockMetadata"); + let block_data = BlockData::new_with_metadata(block, contents, None, metadata); + Some(block_data) + }) + .collect() +} diff --git a/ledger/streaming/api/src/test_utils/components.rs b/ledger/streaming/api/src/test_utils/components.rs deleted file mode 100644 index ac13b63a8c..0000000000 --- a/ledger/streaming/api/src/test_utils/components.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation - -//! Test helpers for creating [BlockStreamComponents]. - -use super::make_quorum_set; -use crate::BlockStreamComponents; -use mc_transaction_core::{ - tx::TxOutMembershipElement, Block, BlockContents, BlockData, BlockVersion, -}; - -/// Generate the specified number of [BlockStreamComponents] -pub fn make_components(count: usize) -> Vec { - let mut parent: Option = None; - (0..count) - .map(|i| { - let contents = BlockContents::default(); - let block = if i == 0 { - Block::new_origin_block(&[]) - } else { - let root_element = TxOutMembershipElement::default(); - Block::new_with_parent( - BlockVersion::MAX, - parent.as_ref().unwrap(), - &root_element, - &contents, - ) - }; - parent = Some(block.clone()); - let block_data = BlockData::new(block, contents, None); - let quorum_set = Some(make_quorum_set()); - let verification_report = None; - BlockStreamComponents { - block_data, - quorum_set, - verification_report, - } - }) - .collect() -} diff --git a/ledger/streaming/api/src/test_utils/fetcher.rs b/ledger/streaming/api/src/test_utils/fetcher.rs index 59e68bc562..b337184b55 100644 --- a/ledger/streaming/api/src/test_utils/fetcher.rs +++ b/ledger/streaming/api/src/test_utils/fetcher.rs @@ -2,7 +2,7 @@ //! Mock [BlockFetcher] -use crate::{test_utils::make_components, BlockFetcher, BlockStreamComponents, Result}; +use crate::{test_utils::make_blocks, BlockData, BlockFetcher, Result}; use futures::{Future, Stream, StreamExt}; use mc_transaction_core::BlockIndex; use std::ops::Range; @@ -10,30 +10,30 @@ use std::ops::Range; /// Mock implementation of [BlockFetcher]. pub struct MockFetcher { /// Fetch results. - pub results: Vec>, + pub results: Vec>, } impl MockFetcher { - /// Instantiate a [MockFetcher] with the given number of components. + /// Instantiate a [MockFetcher] with the given number of blocks. pub fn new(num: usize) -> Self { - Self::from_components(make_components(num)) + Self::from_blocks(make_blocks(num)) } - /// Instantiate a [MockFetcher] with the given components. - pub fn from_components(components: Vec) -> Self { - let results = components.into_iter().map(Ok).collect(); + /// Instantiate a [MockFetcher] with the given blocks. + pub fn from_blocks(blocks: Vec) -> Self { + let results = blocks.into_iter().map(Ok).collect(); Self { results } } /// Instantiate a [MockFetcher] with the given results. - pub fn from_results(results: Vec>) -> Self { + pub fn from_results(results: Vec>) -> Self { Self { results } } } impl BlockFetcher for MockFetcher { - type Single<'s> = impl Future> + 's; - type Multiple<'s> = impl Stream> + 's; + type Single<'s> = impl Future> + 's; + type Multiple<'s> = impl Stream> + 's; fn fetch_single(&self, index: BlockIndex) -> Self::Single<'_> { let result = self.results[index as usize].clone(); diff --git a/ledger/streaming/api/src/test_utils/mod.rs b/ledger/streaming/api/src/test_utils/mod.rs index 087f4ba7f4..94e25598a6 100644 --- a/ledger/streaming/api/src/test_utils/mod.rs +++ b/ledger/streaming/api/src/test_utils/mod.rs @@ -2,16 +2,15 @@ //! Helpers for tests. -pub mod components; +pub mod blocks; pub mod fetcher; -pub mod quorum_set; pub mod response; pub mod stream; pub use self::{ - components::make_components, + blocks::make_blocks, fetcher::MockFetcher, - quorum_set::make_quorum_set, response::{make_responses, Response, Responses}, stream::MockStream, }; +pub use mc_consensus_scp::test_utils::*; diff --git a/ledger/streaming/api/src/test_utils/quorum_set.rs b/ledger/streaming/api/src/test_utils/quorum_set.rs deleted file mode 100644 index f5942f8e18..0000000000 --- a/ledger/streaming/api/src/test_utils/quorum_set.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation - -//! QuorumSet helpers for tests. - -use mc_consensus_scp::{test_utils::test_node_id, QuorumSet, QuorumSetMember}; - -/// Create a QuorumSet for tests. -pub fn make_quorum_set() -> QuorumSet { - let qs = QuorumSet::new( - 2, - vec![ - QuorumSetMember::Node(test_node_id(1)), - QuorumSetMember::InnerSet(QuorumSet::new( - 2, - vec![ - QuorumSetMember::Node(test_node_id(3)), - QuorumSetMember::Node(test_node_id(4)), - ], - )), - QuorumSetMember::Node(test_node_id(0)), - QuorumSetMember::InnerSet(QuorumSet::new( - 2, - vec![ - QuorumSetMember::Node(test_node_id(5)), - QuorumSetMember::Node(test_node_id(6)), - QuorumSetMember::Node(test_node_id(7)), - ], - )), - ], - ); - assert!(qs.is_valid()); - qs -} diff --git a/ledger/streaming/api/src/test_utils/response.rs b/ledger/streaming/api/src/test_utils/response.rs index c8611a9c39..4af5b1ec1d 100644 --- a/ledger/streaming/api/src/test_utils/response.rs +++ b/ledger/streaming/api/src/test_utils/response.rs @@ -2,44 +2,40 @@ //! Response helper. -use super::make_components; -use crate::{make_subscribe_response, streaming_blocks::SubscribeResponse, Error, Result}; +use super::make_blocks; +use crate::{ArchiveBlock, Error, Result}; use grpcio::WriteFlags; -use mc_crypto_keys::Ed25519Pair; -/// Generate the requested number of [Responses], signed with the given signer. -pub fn make_responses(num_responses: usize, signer: &Ed25519Pair) -> Responses { - make_components(num_responses) +/// Generate the requested number of [Responses]. +pub fn make_responses(num_responses: usize) -> Responses { + make_blocks(num_responses) .into_iter() - .map(|components| make_subscribe_response(&components, signer).into()) + .map(|block_data| ArchiveBlock::from(&block_data).into()) .collect() } -/// Helper for a hard-coded [Result]. +/// Helper for a pre-configured [Result] repsonse. #[derive(Clone, Debug)] -pub struct Response(pub Result); +pub struct Response(pub Result); /// Helper for a list of [Response]s. pub type Responses = Vec; impl Response { /// Maps this [Response] to a tuple with the given [WriteFlags] - pub fn with_write_flags( - self, - flags: WriteFlags, - ) -> grpcio::Result<(SubscribeResponse, WriteFlags)> { - grpcio::Result::::from(self).map(|r| (r, flags)) + pub fn with_write_flags(self, flags: WriteFlags) -> grpcio::Result<(ArchiveBlock, WriteFlags)> { + grpcio::Result::::from(self).map(|r| (r, flags)) } } -impl AsRef> for Response { - fn as_ref(&self) -> &Result { +impl AsRef> for Response { + fn as_ref(&self) -> &Result { &self.0 } } -impl From for Response { - fn from(src: SubscribeResponse) -> Self { +impl From for Response { + fn from(src: ArchiveBlock) -> Self { Self(Ok(src)) } } @@ -50,14 +46,14 @@ impl From for Response { } } -impl From> for Response { - fn from(src: Result) -> Self { +impl From> for Response { + fn from(src: Result) -> Self { Self(src) } } -impl From for grpcio::Result { - fn from(src: Response) -> grpcio::Result { +impl From for grpcio::Result { + fn from(src: Response) -> grpcio::Result { src.0.map_err(|err| grpcio::Error::Codec(err.into())) } } diff --git a/ledger/streaming/api/src/test_utils/stream.rs b/ledger/streaming/api/src/test_utils/stream.rs index 881f76d68b..0b1bfb2b86 100644 --- a/ledger/streaming/api/src/test_utils/stream.rs +++ b/ledger/streaming/api/src/test_utils/stream.rs @@ -2,54 +2,35 @@ //! Mock BlockStream -use crate::{BlockStream, BlockStreamComponents, Result}; +use crate::{BlockData, BlockStream, Result}; use futures::Stream; -use std::iter::FromIterator; -/// Mock implementation of BlockStream, backed by a pre-defined Stream. +/// Mock implementation of BlockStream, backed by pre-defined data. #[derive(Clone, Debug)] pub struct MockStream { - items: Vec>, + items: Vec>, } impl MockStream { - /// Instantiate a MockStream with the given stream. - /// It will be cloned for each `get_block_stream` call. - pub fn new(items: Vec>) -> Self { - Self { items } - } - /// Instantiate a MockStream with the given items. - pub fn from_components(src: Vec) -> Self { - let items: Vec> = src.into_iter().map(Ok).collect(); - Self::from_items(items) + /// A subset of the items will be cloned for each `get_block_stream` call. + pub fn new(items: Vec>) -> Self { + Self { items } } - /// Instantiate a MockStream with the given items. - pub fn from_items(items: Vec>) -> Self { + /// Instantiate a MockStream with the given blocks. + pub fn from_blocks(src: Vec) -> Self { + let items: Vec> = src.into_iter().map(Ok).collect(); Self::new(items) } } impl BlockStream for MockStream { - type Stream<'s> = impl Stream> + 's; + type Stream<'s> = impl Stream> + 's; fn get_block_stream(&self, starting_height: u64) -> Result> { - let items = if starting_height > 0 { - Vec::from_iter( - self.items[starting_height as usize..self.items.len()] - .iter() - .cloned(), - ) - } else { - self.items.clone() - }; + let start_index = starting_height as usize; + let items = self.items.iter().cloned().skip(start_index); Ok(futures::stream::iter(items)) } } - -/// Create a MockStream with the given BlockStreamComponents. -pub fn mock_stream_from_components(src: Vec) -> MockStream { - let items: Vec> = src.into_iter().map(Ok).collect(); - MockStream::new(items) -} diff --git a/ledger/streaming/api/src/traits.rs b/ledger/streaming/api/src/traits.rs index 934136d491..f344002044 100644 --- a/ledger/streaming/api/src/traits.rs +++ b/ledger/streaming/api/src/traits.rs @@ -1,14 +1,14 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation -use crate::{BlockStreamComponents, Result}; +use crate::Result; use futures::{Future, Stream}; -use mc_transaction_core::BlockIndex; +use mc_transaction_core::{BlockData, BlockIndex}; use std::ops::Range; /// A stream of blocks with associated data. pub trait BlockStream { /// The specific type of stream. - type Stream<'s>: Stream> + 's + type Stream<'s>: Stream> + 's where Self: 's; @@ -21,11 +21,11 @@ pub trait BlockStream { /// A helper that can fetch blocks on demand. pub trait BlockFetcher { /// Future for fetching single blocks. - type Single<'s>: Future> + 's + type Single<'s>: Future> + 's where Self: 's; /// Stream for fetching multiple blocks. - type Multiple<'s>: Stream> + 's + type Multiple<'s>: Stream> + 's where Self: 's; diff --git a/ledger/streaming/client/src/backfill.rs b/ledger/streaming/client/src/backfill.rs index 34cdf358a3..1770e1afd3 100644 --- a/ledger/streaming/client/src/backfill.rs +++ b/ledger/streaming/client/src/backfill.rs @@ -5,7 +5,7 @@ use displaydoc::Display; use futures::{FutureExt, Stream, StreamExt}; use mc_common::logger::{log, Logger}; -use mc_ledger_streaming_api::{BlockFetcher, BlockStream, BlockStreamComponents, Result}; +use mc_ledger_streaming_api::{BlockData, BlockFetcher, BlockStream, Result}; use mc_transaction_core::BlockIndex; use std::pin::Pin; @@ -32,7 +32,7 @@ impl BlockStream for BackfillingStream { type Stream<'s> where Self: 's, - = impl Stream> + 's; + = impl Stream> + 's; fn get_block_stream(&self, starting_height: u64) -> Result> { self.upstream @@ -48,12 +48,12 @@ impl BlockStream for BackfillingStream { } } -fn backfill_stream<'s, S: Stream> + 's, F: BlockFetcher>( +fn backfill_stream<'s, S: Stream> + 's, F: BlockFetcher>( upstream: S, starting_height: u64, fetcher: &'s F, logger: Logger, -) -> impl Stream> + 's { +) -> impl Stream> + 's { use futures::stream::{empty, once}; // Track the index of the last received block, so we know whether to filter, @@ -63,11 +63,11 @@ fn backfill_stream<'s, S: Stream> + 's, F: upstream.flat_map( // The stream types are quite different across the different cases, so Box the // intermediate stream. - move |result| -> Pin>>> { + move |result| -> Pin>>> { let next_index = prev_index.map_or_else(|| starting_height, |index| index + 1); match result { - Ok(components) => { - let index = components.block_data.block().index; + Ok(block_data) => { + let index = block_data.block().index; // Check for whether we already yielded this index. if prev_index.is_some() && index <= prev_index.unwrap() { log::info!( @@ -79,7 +79,7 @@ fn backfill_stream<'s, S: Stream> + 's, F: return Box::pin(empty()); } - let item_stream = once(async { Ok(components) }); + let item_stream = once(async { Ok(block_data) }); if index == next_index { // Happy path: We got another consecutive item, so just return that. prev_index = Some(index); @@ -115,24 +115,24 @@ mod tests { use futures::executor::block_on; use mc_common::logger::test_with_logger; use mc_ledger_streaming_api::{ - test_utils::{make_components, MockFetcher, MockStream}, + test_utils::{make_blocks, MockFetcher, MockStream}, Error, }; #[test_with_logger] fn handles_unordered_stream(logger: Logger) { - let mut components = make_components(5); + let mut blocks = make_blocks(5); // Mini-shuffle. - components.swap(0, 2); - components.swap(1, 3); - let upstream = MockStream::from_components(components); + blocks.swap(0, 2); + blocks.swap(1, 3); + let upstream = MockStream::from_blocks(blocks); let fetcher = MockFetcher::new(5); let source = BackfillingStream::new(upstream, fetcher, logger); let result_fut = source .get_block_stream(0) .expect("Failed to start upstream") - .map(|resp| resp.expect("expected no errors").block_data.block().index) + .map(|resp| resp.expect("expected no errors").block().index) .collect::>(); let result = block_on(result_fut); @@ -141,8 +141,7 @@ mod tests { #[test_with_logger] fn backfills_on_error(logger: Logger) { - let mut items: Vec> = - make_components(5).into_iter().map(Ok).collect(); + let mut items: Vec> = make_blocks(5).into_iter().map(Ok).collect(); // Error at the beginning. items[0] = Err(Error::Grpc("start".to_owned())); // Mid-stream error. @@ -150,14 +149,14 @@ mod tests { // Error at the end. items[4] = Err(Error::Grpc("end".to_owned())); - let upstream = MockStream::from_items(items); + let upstream = MockStream::new(items); let fetcher = MockFetcher::new(5); let source = BackfillingStream::new(upstream, fetcher, logger); let result_fut = source .get_block_stream(0) .expect("Failed to start upstream") - .map(|resp| resp.expect("expected no errors").block_data.block().index) + .map(|resp| resp.expect("expected no errors").block().index) .collect::>(); let result = block_on(result_fut); diff --git a/ledger/streaming/client/src/block_validator.rs b/ledger/streaming/client/src/block_validator.rs index acbae0cfb2..3cb0595285 100644 --- a/ledger/streaming/client/src/block_validator.rs +++ b/ledger/streaming/client/src/block_validator.rs @@ -1,6 +1,6 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation -//! Stream that validates block components +//! Stream that validates blocks use futures::{future, Stream, StreamExt}; use mc_common::{ @@ -9,7 +9,7 @@ use mc_common::{ }; use mc_ledger_db::Ledger; use mc_ledger_streaming_api::{ - BlockStream, BlockStreamComponents, Error as StreamError, Result as StreamResult, + BlockData, BlockStream, Error as StreamError, Result as StreamResult, }; use mc_transaction_core::{compute_block_id, ring_signature::KeyImage, BlockID}; @@ -34,7 +34,7 @@ impl BlockValidator { } impl BlockStream for BlockValidator { - type Stream<'s> = impl Stream> + 's; + type Stream<'s> = impl Stream> + 's; /// Get block stream that performs validation fn get_block_stream(&self, starting_height: u64) -> StreamResult> { @@ -57,13 +57,13 @@ impl BlockStream for Blo additional_key_images, starting_height, ), - |state, component| { - match component { - Ok(component) => { + |state, result| { + match result { + Ok(block_data) => { let (ledger, prev_block_id, additional_key_images, starting_height) = state; - let block = component.block_data.block(); - let block_contents = component.block_data.contents(); + let block = block_data.block(); + let block_contents = block_data.contents(); if *starting_height == block.index && ledger.is_none() && block.index > 0 { *prev_block_id = block.parent_id.clone(); @@ -120,7 +120,7 @@ impl BlockStream for Blo } *prev_block_id = block.id.clone(); - future::ready(Some(Ok(component))) + future::ready(Some(Ok(block_data))) } Err(err) => future::ready(Some(Err(err))), } @@ -131,27 +131,23 @@ impl BlockStream for Blo #[cfg(test)] mod tests { + use super::*; use mc_common::logger::{test_with_logger, Logger}; use mc_ledger_db::test_utils::{get_mock_ledger, MockLedger}; - use mc_ledger_streaming_api::test_utils::{ - make_components, make_quorum_set, stream::mock_stream_from_components, - }; - use mc_transaction_core::BlockIndex; - - use super::*; + use mc_ledger_streaming_api::test_utils::{make_blocks, MockStream}; #[test_with_logger] fn validation_without_ledger(logger: Logger) { - let components = make_components(20); - let upstream = mock_stream_from_components(components); + let blocks = make_blocks(20); + let upstream = MockStream::from_blocks(blocks); let ledger: Option = None; let block_validator = BlockValidator::new(upstream, ledger, logger); futures::executor::block_on(async move { let mut stream = block_validator.get_block_stream(2).unwrap(); - let mut index: BlockIndex = 0; + let mut index = 0; while let Some(data) = stream.next().await { - index = data.unwrap().block_data.block().index; + index = data.unwrap().block().index; } assert_eq!(index, 19); }); @@ -159,16 +155,16 @@ mod tests { #[test_with_logger] fn validation_from_zero_block(logger: Logger) { - let components = make_components(20); - let upstream = mock_stream_from_components(components); + let blocks = make_blocks(20); + let upstream = MockStream::from_blocks(blocks); let ledger: Option = None; let block_validator = BlockValidator::new(upstream, ledger, logger); futures::executor::block_on(async move { let mut stream = block_validator.get_block_stream(0).unwrap(); - let mut index: BlockIndex = 0; + let mut index = 0; while let Some(data) = stream.next().await { - index = data.unwrap().block_data.block().index; + index = data.unwrap().block().index; } assert_eq!(index, 19); @@ -178,22 +174,24 @@ mod tests { #[test_with_logger] fn validation_with_ledger(logger: Logger) { let mut mock_ledger = get_mock_ledger(0); - let components = make_components(20); - for i in 0..2 { - let block_data = &components[i].block_data; + let blocks = make_blocks(20); + for block_data in blocks.iter().take(2) { mock_ledger - .append_block(block_data.block(), block_data.contents(), None) + .append_block( + block_data.block(), + block_data.contents(), + block_data.signature().clone(), + ) .unwrap(); } - let ledger = Some(mock_ledger); - let upstream = mock_stream_from_components(components); - let block_validator = BlockValidator::new(upstream, ledger, logger); + let upstream = MockStream::from_blocks(blocks); + let block_validator = BlockValidator::new(upstream, Some(mock_ledger), logger); futures::executor::block_on(async move { let mut stream = block_validator.get_block_stream(2).unwrap(); - let mut index: BlockIndex = 0; + let mut index = 0; while let Some(data) = stream.next().await { - index = data.unwrap().block_data.block().index; + index = data.unwrap().block().index; } assert_eq!(index, 19); @@ -203,9 +201,8 @@ mod tests { #[test_with_logger] fn invalid_previous_blocks_fail(logger: Logger) { let mock_ledger = get_mock_ledger(4); - let ledger = Some(mock_ledger); - let upstream = mock_stream_from_components(make_components(4)); - let block_validator = BlockValidator::new(upstream, ledger, logger.clone()); + let upstream = MockStream::from_blocks(make_blocks(4)); + let block_validator = BlockValidator::new(upstream, Some(mock_ledger), logger.clone()); futures::executor::block_on(async move { let mut stream = block_validator.get_block_stream(2).unwrap(); @@ -219,19 +216,11 @@ mod tests { #[test_with_logger] fn pre_existing_key_images_in_ledger_fail(logger: Logger) { let mock_ledger = get_mock_ledger(4); - let quorum_set = make_quorum_set(); - let mut components = Vec::new(); - for i in 2..4 { - components.push(BlockStreamComponents::new( - mock_ledger.get_block_data(i).unwrap(), - quorum_set.clone(), - None, - )) - } - let ledger = Some(mock_ledger); - - let upstream = mock_stream_from_components(components); - let block_validator = BlockValidator::new(upstream, ledger, logger); + let blocks = (1..3) + .map(|i| mock_ledger.get_block_data(i).unwrap()) + .collect::>(); + let upstream = MockStream::from_blocks(blocks); + let block_validator = BlockValidator::new(upstream, Some(mock_ledger), logger); futures::executor::block_on(async move { let mut stream = block_validator.get_block_stream(1).unwrap(); diff --git a/ledger/streaming/client/src/grpc.rs b/ledger/streaming/client/src/grpc.rs index 5ea0dbd36d..44316c8273 100644 --- a/ledger/streaming/client/src/grpc.rs +++ b/ledger/streaming/client/src/grpc.rs @@ -5,51 +5,38 @@ use displaydoc::Display; use futures::{Stream, StreamExt}; use mc_common::logger::{log, o, Logger}; -use mc_crypto_keys::Ed25519Public; use mc_ledger_streaming_api::{ - parse_subscribe_response, streaming_blocks::SubscribeRequest, - streaming_blocks_grpc::LedgerUpdatesClient, BlockStream, BlockStreamComponents, Result, + streaming_blocks::SubscribeRequest, streaming_blocks_grpc::LedgerUpdatesClient, ArchiveBlock, + BlockData, BlockStream, Result, }; use mc_util_grpc::ConnectionUriGrpcioChannel; use mc_util_uri::ConnectionUri; -use std::{sync::Arc, time::Duration}; +use std::{convert::TryFrom, sync::Arc, time::Duration}; /// A [BlockStream] that streams blocks using the `LedgerUpdates` gRPC API. #[derive(Display)] pub struct GrpcBlockSource { /// The gRPC client client: LedgerUpdatesClient, - /// The public key of the consensus node we're streaming from. - public_key: Ed25519Public, /// A logger object logger: Logger, } impl GrpcBlockSource { - /// Instantiate a [GrpcBlockSource] pulling from the given `uri`, which - /// verifies [SubscribeResponse]s are signed with the given `public_key`. - pub fn new( - uri: &impl ConnectionUri, - env: Arc, - public_key: Ed25519Public, - logger: Logger, - ) -> Self { + /// Instantiate a [GrpcBlockSource] pulling from the given `uri`. + pub fn new(uri: &impl ConnectionUri, env: Arc, logger: Logger) -> Self { let logger = logger.new(o!("uri" => uri.to_string())); let channel = grpcio::ChannelBuilder::default_channel_builder(env).connect_to_uri(uri, &logger); let client = LedgerUpdatesClient::new(channel); - Self { - client, - public_key, - logger, - } + Self { client, logger } } -} - -impl BlockStream for GrpcBlockSource { - type Stream<'s> = impl Stream> + 's; - fn get_block_stream(&self, starting_height: u64) -> Result> { + /// Make the Subscribe gRPC call. + pub fn subscribe( + &self, + starting_height: u64, + ) -> grpcio::Result> { // Set up request. let mut req = SubscribeRequest::new(); req.starting_height = starting_height; @@ -57,11 +44,39 @@ impl BlockStream for GrpcBlockSource { // TODO: Make timeout configurable. let opt = grpcio::CallOption::default().timeout(Duration::from_secs(10)); - let stream = self.client.subscribe_opt(&req, opt)?; - let result = stream.map(move |result| { - log::trace!(&self.logger, "map_subscribe_response"); - let response = result?; - parse_subscribe_response(&response, &self.public_key) + self.client.subscribe_opt(&req, opt) + } +} + +impl BlockStream for GrpcBlockSource { + type Stream<'s> = impl Stream> + 's; + + fn get_block_stream(&self, starting_height: u64) -> Result> { + use futures::stream::iter; + + let logger = &self.logger; + let stream = self.subscribe(starting_height)?; + let result = stream.flat_map(move |result| { + log::trace!( + logger, + "GrpcBlockSource got {}", + match &result { + Ok(block) => { + format!( + "block with index {}", + block.get_v1().get_block().get_index() + ) + } + Err(err) => format!("error: {}", err), + } + ); + match result { + Ok(archive_block) => { + let result = BlockData::try_from(&archive_block).map_err(Into::into); + iter([result]).left_stream() + } + Err(err) => iter([Err(err.into())]).right_stream(), + } }); Ok(result) } @@ -73,26 +88,20 @@ mod tests { use crate::test_utils::setup_test_server; use futures::{executor::block_on, future::ready}; use mc_common::logger::test_with_logger; - use mc_crypto_keys::Ed25519Pair; use mc_ledger_streaming_api::{test_utils::make_responses, Error}; - use mc_util_from_random::FromRandom; - use rand_core::SeedableRng; - use rand_hc::Hc128Rng; #[test_with_logger] fn basic(logger: Logger) { - let mut rng = Hc128Rng::from_seed([1u8; 32]); - let signer = Ed25519Pair::from_random(&mut rng); - let responses = make_responses(3, &signer); + let responses = make_responses(3); let (_server, uri, env) = setup_test_server(responses, None, None); - let source = GrpcBlockSource::new(&uri, env, signer.public_key(), logger.clone()); + let source = GrpcBlockSource::new(&uri, env, logger.clone()); let result_fut = source .get_block_stream(0) .expect("Failed to start block stream") .map(|result| { - let data = result.expect("Error decoding block data"); - data.block_data.block().index + let block_data = result.expect("Error decoding block data"); + block_data.block().index }) .collect(); @@ -102,21 +111,18 @@ mod tests { #[test_with_logger] fn propagates_errors(logger: Logger) { - let mut csprng = Hc128Rng::seed_from_u64(0); - let signer = Ed25519Pair::from_random(&mut csprng); - - let mut responses = make_responses(2, &signer); + let mut responses = make_responses(2); responses.push(Err(Error::Grpc("meh".to_owned())).into()); let (_server, uri, env) = setup_test_server(responses, None, None); - let source = GrpcBlockSource::new(&uri, env, signer.public_key(), logger); + let source = GrpcBlockSource::new(&uri, env, logger); let mut got_error = false; let result_fut = source .get_block_stream(0) .expect("Failed to start block stream") .filter_map(|resp| match resp { - Ok(data) => ready(Some(data.block_data.block().index)), + Ok(block_data) => ready(Some(block_data.block().index)), Err(_) => { got_error = true; ready(None) diff --git a/ledger/streaming/client/src/http_fetcher.rs b/ledger/streaming/client/src/http_fetcher.rs index 8c9756c493..f04a68a19e 100644 --- a/ledger/streaming/client/src/http_fetcher.rs +++ b/ledger/streaming/client/src/http_fetcher.rs @@ -1,23 +1,20 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation -//! A [BlockFetcher] that downloads [ArchiveBlocks] from the given URI. +//! A [BlockFetcher] that downloads [BlockData] from the given URI. use crate::BlockchainUrl; use displaydoc::Display; use futures::{lock::Mutex, Future, FutureExt, Stream, StreamExt}; -use mc_api::blockchain::{ArchiveBlock, ArchiveBlocks}; use mc_common::{ logger::{log, o, Logger}, LruCache, }; -use mc_ledger_streaming_api::{ - archive_blocks_to_components, BlockFetcher, BlockStreamComponents, Error, Result, -}; -use mc_transaction_core::{Block, BlockIndex}; +use mc_ledger_streaming_api::{ArchiveBlock, ArchiveBlocks, BlockFetcher, Error, Result}; +use mc_transaction_core::{Block, BlockData, BlockIndex}; use protobuf::Message; use reqwest::Client; use std::{ - convert::TryFrom, + convert::{TryFrom, TryInto}, ops::Range, sync::{ atomic::{AtomicU64, Ordering}, @@ -37,7 +34,7 @@ pub const DEFAULT_MERGED_BLOCKS_BUCKET_SIZES: &[u64] = &[10000, 1000, 100]; /// Maximum number of pre-fetched blocks to keep in cache. pub const MAX_PREFETCHED_BLOCKS: usize = 10000; -/// A [BlockFetcher] that downloads [ArchiveBlocks] from the given URI. +/// A [BlockFetcher] that downloads [BlockData] from the given URI. #[derive(Debug, Display)] pub struct HttpBlockFetcher { /// The blockchain URL. @@ -49,9 +46,9 @@ pub struct HttpBlockFetcher { /// Merged blocks bucket sizes to attempt fetching. merged_blocks_bucket_sizes: Vec, - /// Cache mapping a [BlockIndex] to [BlockStreamComponents], filled by + /// Cache mapping a [BlockIndex] to [BlockData], filled by /// merged blocks when possible. - cache: Arc>>, + cache: Arc>>, /// Number of successful cache hits when attempting to get block data. /// Used for debugging purposes. @@ -109,7 +106,7 @@ impl HttpBlockFetcher { &self, block_index: BlockIndex, expected_block: Option<&Block>, - ) -> Result { + ) -> Result { // Try and see if we can get this block from our cache. if let Some(cached) = self.get_cached(block_index, expected_block).await { return Ok(cached); @@ -147,18 +144,18 @@ impl HttpBlockFetcher { // Try and get the block. log::debug!( self.logger, - "Attempting to fetch block {} from {}", + "Attempting to fetch block #{} from {}", block_index, url ); let archive_block: ArchiveBlock = self.fetch_protobuf_object(&url).await?; - let components = BlockStreamComponents::try_from(&archive_block)?; + let block_data = BlockData::try_from(&archive_block)?; // If the caller is expecting a specific block, check that we received data for // the block they asked for if let Some(expected_block) = expected_block { - if expected_block != components.block_data.block() { + if expected_block != block_data.block() { return Err(Error::Other(format!( "Block data mismatch (downloaded from {})", url @@ -171,20 +168,20 @@ impl HttpBlockFetcher { log::trace!( self.logger, "Cache miss while getting block #{} (total hits/misses: {}/{})", - components.block_data.block().index, + block_data.block().index, hits, misses ); // Got what we wanted! - Ok(components) + Ok(block_data) } async fn get_cached( &self, block_index: BlockIndex, expected_block: Option<&Block>, - ) -> Option { + ) -> Option { // Sanity test. if let Some(expected_block) = expected_block { assert_eq!(block_index, expected_block.index); @@ -196,12 +193,12 @@ impl HttpBlockFetcher { // assumption that our primary caller, LedgerSyncService, is not // going to try and fetch the same block twice if it managed to get // a valid block. - cache.pop(&block_index).and_then(|components| { - let index = components.block_data.block().index; + cache.pop(&block_index).and_then(|block_data| { + let index = block_data.block().index; // If we expect a specific Block then compare what the cache had with what we // expect. if let Some(expected_block) = expected_block { - if components.block_data.block() == expected_block { + if block_data.block() == expected_block { let hits = self.hits.fetch_add(1, Ordering::SeqCst); let misses = self.misses.load(Ordering::SeqCst); log::trace!( @@ -211,18 +208,18 @@ impl HttpBlockFetcher { hits, misses ); - Some(components) + Some(block_data) } else { log::warn!( self.logger, "Got cached block {:?} but actually requested {:?}! This should not happen", - components.block_data.block(), + block_data.block(), expected_block ); None } } else if index == block_index { - Some(components) + Some(block_data) } else { log::error!( self.logger, @@ -284,14 +281,14 @@ impl HttpBlockFetcher { )) })?; let archive_blocks: ArchiveBlocks = self.fetch_protobuf_object(&url).await?; - let merged = archive_blocks_to_components(&archive_blocks)?; - let result = merged.len(); + let blocks: Vec = (&archive_blocks).try_into()?; + let result = blocks.len(); log::debug!(self.logger, "Got {} merged results from {}", result, url); { let mut cache = self.cache.lock().await; - for components in merged.into_iter() { - cache.put(components.block_data.block().index, components); + for block_data in blocks.into_iter() { + cache.put(block_data.block().index, block_data); } } Ok(result) @@ -300,7 +297,7 @@ impl HttpBlockFetcher { async fn get_range_prefer_merged( &self, indexes: Range, - ) -> impl Stream> + '_ { + ) -> impl Stream> + '_ { let n = indexes.end - indexes.start - 1; for bucket in &self.merged_blocks_bucket_sizes { if *bucket < n { @@ -319,8 +316,8 @@ impl HttpBlockFetcher { } impl BlockFetcher for HttpBlockFetcher { - type Single<'s> = impl Future> + 's; - type Multiple<'s> = impl Stream> + 's; + type Single<'s> = impl Future> + 's; + type Multiple<'s> = impl Stream> + 's; fn fetch_single(&self, index: BlockIndex) -> Self::Single<'_> { self.get_block_data_by_index(index, None) @@ -335,7 +332,7 @@ impl BlockFetcher for HttpBlockFetcher { mod tests { use super::*; use futures::future::ready; - use mc_ledger_streaming_api::{components_to_archive_blocks, test_utils::make_components}; + use mc_ledger_streaming_api::test_utils::make_blocks; use mockito::{mock, server_url}; use std::str::FromStr; @@ -354,7 +351,7 @@ mod tests { #[tokio::test] async fn fetch_single() { - let items = make_components(1); + let items = make_blocks(1); let expected = ArchiveBlock::from(&items[0]); let mock_request = mock("GET", "/00/00/00/00/00/00/00/0000000000000001.pb") .with_body(expected.write_to_bytes().expect("expected.write_to_bytes")) @@ -362,15 +359,14 @@ mod tests { let result = create_fetcher().fetch_single(1).await; mock_request.assert(); - let data = result.expect("expected data"); - // TODO(#1682): Include QuorumSet, VerificationReport. - assert_eq!(data.block_data, items[0].block_data); + let block_data = result.expect("expected data"); + assert_eq!(block_data, items[0]); } #[tokio::test] async fn fetch_multiple_merged() { - let items = make_components(10); - let expected = components_to_archive_blocks(&items); + let items = make_blocks(10); + let expected = ArchiveBlocks::from(&items[..]); let mock_request = mock("GET", "/merged-10/00/00/00/00/00/00/00/0000000000000000.pb") .with_body(expected.write_to_bytes().expect("expected.write_to_bytes")) .create(); @@ -381,10 +377,9 @@ mod tests { .fetch_range(0..10) .enumerate() .for_each_concurrent(None, move |(index, result)| { - let components = + let block_data = result.expect(&format!("unexpected error for item #{}", index + 1)); - // TODO(#1682): Include QuorumSet, VerificationReport. - assert_eq!(components.block_data, items[index].block_data); + assert_eq!(block_data, items[index]); ready(()) }) .await; @@ -393,12 +388,12 @@ mod tests { #[tokio::test] async fn fetch_multiple_no_merged() { - let items = make_components(10); + let items = make_blocks(10); let mock_requests = items .iter() - .map(|components| { - let index = components.block_data.block().index; - let bytes = ArchiveBlock::from(components) + .map(|block_data| { + let index = block_data.block().index; + let bytes = ArchiveBlock::from(block_data) .write_to_bytes() .expect(&format!("expected[{}].write_to_bytes", index)); let path = format!("/00/00/00/00/00/00/00/{:016x}.pb", index); @@ -412,10 +407,9 @@ mod tests { .fetch_range(0..10) .enumerate() .for_each_concurrent(None, move |(index, result)| { - let components = + let block_data = result.expect(&format!("unexpected error for item #{}", index + 1)); - // TODO(#1682): Include QuorumSet, VerificationReport. - assert_eq!(components.block_data, items[index].block_data); + assert_eq!(block_data, items[index]); ready(()) }) .await; diff --git a/ledger/streaming/client/src/ledger_sink.rs b/ledger/streaming/client/src/ledger_sink.rs index a5dc1c62e5..be7e6ce52c 100644 --- a/ledger/streaming/client/src/ledger_sink.rs +++ b/ledger/streaming/client/src/ledger_sink.rs @@ -6,7 +6,7 @@ use futures::stream::{Stream, StreamExt}; use mc_common::logger::{log, Logger}; use mc_ledger_db::Ledger; use mc_ledger_streaming_api::{ - BlockStream, BlockStreamComponents, Error as StreamError, Result as StreamResult, + BlockData, BlockStream, Error as StreamError, Result as StreamResult, }; use tokio::sync::mpsc::{channel, Receiver, Sender}; @@ -31,10 +31,10 @@ pub struct DbStream { /// Object to manage the state of the ledger sink process struct SinkManager { /// Channel to send blocks ledger sink thread to be synced - sender: Sender, + sender: Sender, /// Channel to receive blocks that have been synced - receiver: Receiver, + receiver: Receiver, /// Last block we've received from the upstream last_block_received: u64, @@ -52,8 +52,8 @@ struct SinkManager { impl SinkManager { /// Create new manager for the block sink fn new( - sender: Sender, - receiver: Receiver, + sender: Sender, + receiver: Receiver, last_block_received: u64, last_block_synced: u64, sync_start_height: u64, @@ -81,7 +81,7 @@ impl SinkManager { } impl BlockStream for DbStream { - type Stream<'s> = impl Stream> + 's; + type Stream<'s> = impl Stream> + 's; /// Get block stream that performs block sinking fn get_block_stream(&self, starting_height: u64) -> StreamResult> { @@ -123,12 +123,12 @@ impl BlockStream for DbS // Create the stream let output_stream = futures::stream::unfold((stream, manager), |(mut stream, mut manager)| async move { - if let Some(component) = stream.next().await { - if let Ok(component) = component { - manager.last_block_received = component.block_data.block().index; + if let Some(result) = stream.next().await { + if let Ok(block_data) = result { + manager.last_block_received = block_data.block().index; if manager.can_start_sync() { // If we're above what's in the ledger, starting syncing the blocks - if manager.sender.send(component).await.is_err() { + if manager.sender.send(block_data).await.is_err() { //TODO: Discuss whether thread error should stop stream or // self-heal TODO: it's // possible just to restart the upstream & thread here @@ -141,10 +141,10 @@ impl BlockStream for DbS } } else { // Else pass them through - return Some((Ok(component), (stream, manager))); + return Some((Ok(block_data), (stream, manager))); } } else { - return Some((component, (stream, manager))); + return Some((result, (stream, manager))); } } else { // If we're behind, wait for the rest of the blocks to sync then end @@ -158,9 +158,9 @@ impl BlockStream for DbS return None; } } - if let Some(component) = manager.receiver.recv().await { - manager.last_block_synced = component.block_data.block().index; - Some((Ok(component), (stream, manager))) + if let Some(block_data) = manager.receiver.recv().await { + manager.last_block_synced = block_data.block().index; + Some((Ok(block_data), (stream, manager))) } else { // TODO: Discuss whether we want to heal the stream or not log::error!(manager.logger, "sink thread stopped, ending stream"); @@ -186,42 +186,34 @@ impl DbStream { fn start_sink_thread( mut ledger: impl Ledger + 'static, logger: Logger, -) -> ( - Sender, - Receiver, -) { +) -> (Sender, Receiver) { // Initialize sending and receiving channels let (send_out, rcv_out) = channel(10000); - let (send_in, mut rcv_in): ( - Sender, - Receiver, - ) = channel(10000); + let (send_in, mut rcv_in) = channel::(10000); // Launch ledger sink thread std::thread::spawn(move || { - while let Some(component) = rcv_in.blocking_recv() { - let signature = component.block_data.signature().as_ref().cloned(); + while let Some(block_data) = rcv_in.blocking_recv() { + let signature = block_data.signature().as_ref().cloned(); // If there's an error syncing the blocks, end thread - if let Err(err) = ledger.append_block( - component.block_data.block(), - component.block_data.contents(), - signature, - ) { + if let Err(err) = + ledger.append_block(block_data.block(), block_data.contents(), signature) + { log::error!( logger, "Error {:?} occurred during attempt to write block {}", err, - component.block_data.block().index, + block_data.block().index, ); break; }; // If message channels are broken, end thread - if let Err(err) = send_out.try_send(component) { + if let Err(err) = send_out.try_send(block_data) { log::error!( logger, - "sending component to stream failed with error {:?}", + "sending block data to stream failed with error {:?}", err ); break; @@ -237,20 +229,20 @@ mod tests { use super::*; use mc_common::logger::{test_with_logger, Logger}; use mc_ledger_db::test_utils::get_mock_ledger; - use mc_ledger_streaming_api::test_utils::{make_components, stream}; + use mc_ledger_streaming_api::test_utils::{make_blocks, MockStream}; #[test_with_logger] fn test_sink_from_start_block(logger: Logger) { - let components = make_components(420); - let upstream = stream::mock_stream_from_components(components); + let blocks = make_blocks(420); + let upstream = MockStream::from_blocks(blocks); let dest_ledger = get_mock_ledger(0); let bs = DbStream::new(upstream, dest_ledger, true, logger); let mut go_stream = bs.get_block_stream(0).unwrap(); futures::executor::block_on(async move { let mut count = 0; - while let Some(component) = go_stream.next().await { - assert_eq!(component.unwrap().block_data.block().index, count); + while let Some(block_data) = go_stream.next().await { + assert_eq!(block_data.unwrap().block().index, count); count += 1; } @@ -260,16 +252,16 @@ mod tests { #[test_with_logger] fn test_blocks_lower_than_stored_pass_through(logger: Logger) { - let components = make_components(420); - let upstream = stream::mock_stream_from_components(components); + let blocks = make_blocks(420); + let upstream = MockStream::from_blocks(blocks); let dest_ledger = get_mock_ledger(42); let bs = DbStream::new(upstream, dest_ledger, true, logger); let mut block_stream = bs.get_block_stream(20).unwrap(); futures::executor::block_on(async move { let mut count = 20; - while let Some(component) = block_stream.next().await { - assert_eq!(component.unwrap().block_data.block().index, count); + while let Some(block_data) = block_stream.next().await { + assert_eq!(block_data.unwrap().block().index, count); count += 1; } @@ -279,16 +271,16 @@ mod tests { #[test_with_logger] fn test_can_start_at_arbitrary_valid_height(logger: Logger) { - let components = make_components(420); - let upstream = stream::mock_stream_from_components(components); + let blocks = make_blocks(420); + let upstream = MockStream::from_blocks(blocks); let dest_ledger = get_mock_ledger(42); let bs = DbStream::new(upstream, dest_ledger, true, logger); let mut block_stream = bs.get_block_stream(42).unwrap(); futures::executor::block_on(async move { let mut count = 42; - while let Some(component) = block_stream.next().await { - assert_eq!(component.unwrap().block_data.block().index, count); + while let Some(block_data) = block_stream.next().await { + assert_eq!(block_data.unwrap().block().index, count); count += 1; } @@ -298,8 +290,8 @@ mod tests { #[test_with_logger] fn test_stream_creation_fails_if_requesting_blocks_above_ledger_height(logger: Logger) { - let components = make_components(3); - let upstream = stream::mock_stream_from_components(components); + let blocks = make_blocks(3); + let upstream = MockStream::from_blocks(blocks); let dest_ledger = get_mock_ledger(1); let bs = DbStream::new(upstream, dest_ledger, true, logger); let block_stream = bs.get_block_stream(2); diff --git a/ledger/streaming/client/src/scp_validator.rs b/ledger/streaming/client/src/scp_validator.rs index 78f2f0dc21..026459b3c2 100644 --- a/ledger/streaming/client/src/scp_validator.rs +++ b/ledger/streaming/client/src/scp_validator.rs @@ -11,7 +11,7 @@ use mc_common::{ }; use mc_consensus_scp::{GenericNodeId, QuorumSet, QuorumSetMember, SlotIndex}; use mc_ledger_streaming_api::{ - BlockStream, BlockStreamComponents, Error as StreamError, Result as StreamResult, + BlockData, BlockStream, Error as StreamError, Result as StreamResult, }; use mc_transaction_core::BlockID; use std::future; @@ -46,7 +46,7 @@ pub struct SCPValidationState { local_quorum_set: QuorumSet, /// Highest slot that a given node has externalized. - slots_to_externalized_blocks: HashMap>, + slots_to_externalized_blocks: HashMap>, /// Highest block externalized highest_slot_index: SlotIndex, @@ -91,10 +91,10 @@ impl SCPValidationState { } } - /// Fill slot with received component for certain block_height. If we've + /// Fill slot with received BlockData for certain block_height. If we've /// already recorded a block for that slot, discard it. - pub fn update_slot(&mut self, node_id: ID, component: BlockStreamComponents) { - let index = component.block_data.block().index; + pub fn update_slot(&mut self, node_id: ID, block_data: BlockData) { + let index = block_data.block().index; // If we've already externalized a block at this index, ignore it if index <= self.highest_slot_index && self.highest_slot_index > 0 { @@ -103,7 +103,7 @@ impl SCPValidationState { // Otherwise associate it with the node it was received from let node_map = self.slots_to_externalized_blocks.entry(index).or_default(); - node_map.insert(node_id, component); + node_map.insert(node_id, block_data); } /// After block is externalized, remove records at previous slot @@ -117,7 +117,7 @@ impl SCPValidationState { } /// Determine if we can externalize a block, if so return the block - pub fn attempt_externalize_block(&mut self) -> Option { + pub fn attempt_externalize_block(&mut self) -> Option { // Set our target index one block above the highest block externalized // unless we're recording the genesis block let mut index = self.highest_slot_index + 1; @@ -136,9 +136,9 @@ impl SCPValidationState { // quorum slice for this node let mut selected_node = None; if let Some(block_id) = self.find_quorum(&self.local_quorum_set, index) { - let components = self.slots_to_externalized_blocks.get(&index).unwrap(); - for (node_id, component) in components { - if &component.block_data.block().id == block_id { + let blocks = self.slots_to_externalized_blocks.get(&index).unwrap(); + for (node_id, block_data) in blocks { + if &block_data.block().id == block_id { selected_node = Some(node_id.clone()); break; } @@ -147,7 +147,7 @@ impl SCPValidationState { // If we found quorum and a matching block, return it if let Some(selected_node) = selected_node { - let mut components = self.slots_to_externalized_blocks.remove(&index).unwrap(); + let mut blocks = self.slots_to_externalized_blocks.remove(&index).unwrap(); log::trace!( self.logger, "Node: {} found a quorum slice for block {}", @@ -158,7 +158,7 @@ impl SCPValidationState { // Increment our targets self.highest_slot_index += 1; self.num_blocks_externalized += 1; - return components.remove(&selected_node); + return blocks.remove(&selected_node); } // If not let consumers know @@ -182,10 +182,10 @@ impl SCPValidationState { // what block they externalized match member { QuorumSetMember::Node(node_id) => { - if let Some(component) = tracked_blocks.get(node_id) { + if let Some(block_data) = tracked_blocks.get(node_id) { // Record a vote for the block externalized ballot_map - .entry(&component.block_data.block().id) + .entry(&block_data.block().id) .and_modify(|vec| vec.push(member)) .or_insert_with(|| vec![member]); nodes_counted += 1; @@ -247,7 +247,7 @@ impl BlockStream for SCPVal type Stream<'s> where ID: 's, - = impl Stream> + 's; + = impl Stream> + 's; /// Get block stream that performs validation fn get_block_stream(&self, starting_height: u64) -> StreamResult> { @@ -258,9 +258,7 @@ impl BlockStream for SCPVal log::info!(self.logger, "Generating new stream from {:?}", node_id); let us = upstream.get_block_stream(starting_height).unwrap(); let peer_id = node_id.clone(); - merged_streams.push(Box::pin( - us.map(move |component| (peer_id.clone(), component)), - )); + merged_streams.push(Box::pin(us.map(move |result| (peer_id.clone(), result)))); } // Create SCP validation state object and insert it into stateful stream @@ -270,14 +268,14 @@ impl BlockStream for SCPVal } Ok(merged_streams - .scan(validation_state, |scp_state, (node_id, component)| { - if let Ok(component) = component { - // Put component into underlying state - scp_state.update_slot(node_id, component); + .scan(validation_state, |scp_state, (node_id, result)| { + if let Ok(block_data) = result { + // Put block into underlying state + scp_state.update_slot(node_id, block_data); // Attempt to externalize it if there's quorum - if let Some(component) = scp_state.attempt_externalize_block() { - return future::ready(Some(Ready::Ready(Ok(component)))); + if let Some(block_data) = scp_state.attempt_externalize_block() { + return future::ready(Some(Ready::Ready(Ok(block_data)))); } } @@ -291,9 +289,9 @@ impl BlockStream for SCPVal future::ready(Some(Ready::NotReady)) }) .filter_map(|result| { - // If the component is in ready state forward it to next stream - if let Ready::Ready(component) = result { - return future::ready(Some(component)); + // If the block data is in ready state forward it to next stream + if let Ready::Ready(result) = result { + return future::ready(Some(result)); } // If no ready message was received don't send anything onwards @@ -308,43 +306,41 @@ mod tests { use super::*; use mc_common::logger::{test_with_logger, Logger}; use mc_consensus_scp::test_utils::test_node_id; - use mc_ledger_streaming_api::test_utils::{make_components, stream}; + use mc_ledger_streaming_api::test_utils::{make_blocks, MockStream}; use mc_transaction_core::BlockIndex; #[test_with_logger] fn scp_validates_nodes_in_quorum(logger: Logger) { - let mut nodes = Vec::new(); - for i in 1..10 { - nodes.push(test_node_id(i)); - } + let nodes = (1..10).map(test_node_id).collect::>(); let quorum_set = QuorumSet::new( 5, vec![ - QuorumSetMember::Node(nodes.get(0).unwrap().clone()), - QuorumSetMember::Node(nodes.get(1).unwrap().clone()), - QuorumSetMember::Node(nodes.get(2).unwrap().clone()), - QuorumSetMember::Node(nodes.get(3).unwrap().clone()), + QuorumSetMember::Node(nodes[0].clone()), + QuorumSetMember::Node(nodes[1].clone()), + QuorumSetMember::Node(nodes[2].clone()), + QuorumSetMember::Node(nodes[3].clone()), QuorumSetMember::InnerSet(QuorumSet::new( 2, vec![ - QuorumSetMember::Node(nodes.get(4).unwrap().clone()), - QuorumSetMember::Node(nodes.get(5).unwrap().clone()), - QuorumSetMember::Node(nodes.get(6).unwrap().clone()), + QuorumSetMember::Node(nodes[4].clone()), + QuorumSetMember::Node(nodes[5].clone()), + QuorumSetMember::Node(nodes[6].clone()), ], )), QuorumSetMember::InnerSet(QuorumSet::new( 2, vec![ - QuorumSetMember::Node(nodes.get(7).unwrap().clone()), - QuorumSetMember::Node(nodes.get(8).unwrap().clone()), + QuorumSetMember::Node(nodes[7].clone()), + QuorumSetMember::Node(nodes[8].clone()), ], )), ], ); assert!(quorum_set.is_valid()); - let components = make_components(100); - let s = stream::mock_stream_from_components(components); + + let blocks = make_blocks(100); + let s = MockStream::from_blocks(blocks); let mut upstreams = HashMap::new(); for i in 0..9 { upstreams.insert(nodes.get(i).unwrap().clone(), s.clone()); @@ -356,8 +352,8 @@ mod tests { futures::executor::block_on(async move { let mut scp_stream = validator.get_block_stream(0).unwrap(); let mut index: BlockIndex = 0; - while let Some(component) = scp_stream.next().await { - index = component.unwrap().block_data.block().index; + while let Some(result) = scp_stream.next().await { + index = result.unwrap().block().index; } assert_eq!(index, 99) }); diff --git a/ledger/streaming/client/src/test_utils/server.rs b/ledger/streaming/client/src/test_utils/server.rs index 4d9a665117..fa294b0705 100644 --- a/ledger/streaming/client/src/test_utils/server.rs +++ b/ledger/streaming/client/src/test_utils/server.rs @@ -4,9 +4,10 @@ use futures::{FutureExt, StreamExt}; use mc_ledger_streaming_api::{ - streaming_blocks::{SubscribeRequest, SubscribeResponse}, + streaming_blocks::SubscribeRequest, streaming_blocks_grpc::{create_ledger_updates, LedgerUpdates}, test_utils::Responses, + ArchiveBlock, }; use mc_util_uri::ConnectionUri; use std::{str::FromStr, sync::Arc}; @@ -72,7 +73,7 @@ impl LedgerUpdates for MockLedgerUpdates { &mut self, ctx: grpcio::RpcContext, _req: SubscribeRequest, - sink: grpcio::ServerStreamingSink, + sink: grpcio::ServerStreamingSink, ) { // The sink requires WriteFlags in addition to the value. let responses = self diff --git a/ledger/streaming/publisher/Cargo.toml b/ledger/streaming/publisher/Cargo.toml index f8a021e893..ddfb58ef4b 100644 --- a/ledger/streaming/publisher/Cargo.toml +++ b/ledger/streaming/publisher/Cargo.toml @@ -31,7 +31,6 @@ tokio = { version = "1.16", features = ["fs"], optional = true } [dev-dependencies] mc-common = { path = "../../../common", features = ["loggers"] } -mc-crypto-rand = { path = "../../../crypto/rand" } mc-ledger-streaming-api = { path = "../api", features = ["test_utils"] } async_executors = { version = "0.5", features = ["async_std", "tokio_ct", "tokio_tp"] } diff --git a/ledger/streaming/publisher/src/archive_sink.rs b/ledger/streaming/publisher/src/archive_sink.rs index 956d3acce4..acc623e84d 100644 --- a/ledger/streaming/publisher/src/archive_sink.rs +++ b/ledger/streaming/publisher/src/archive_sink.rs @@ -9,14 +9,10 @@ use crate::ProtoWriter; #[cfg(feature = "s3")] use crate::S3ClientProtoWriter; use futures::{Stream, StreamExt}; -use mc_api::{ - block_num_to_s3block_path, - blockchain::{ArchiveBlock, ArchiveBlocks}, - merged_block_num_to_s3block_path, -}; +use mc_api::{block_num_to_s3block_path, merged_block_num_to_s3block_path}; use mc_common::logger::{log, Logger}; use mc_ledger_db::Ledger; -use mc_ledger_streaming_api::{components_to_archive_blocks, BlockStreamComponents, Error, Result}; +use mc_ledger_streaming_api::{ArchiveBlock, ArchiveBlocks, BlockData, Error, Result}; #[cfg(any(feature = "local", feature = "s3"))] use std::path::PathBuf; use std::{collections::VecDeque, convert::TryFrom}; @@ -104,7 +100,7 @@ impl ArchiveBlockSink { /// The returned value is a `Stream` where the `Output` type is /// `Result<()>`; it is executed entirely for its side effects, while /// propagating errors back to the caller. - pub fn consume<'s, S: Stream>>( + pub fn consume<'s, S: Stream>>( &'s mut self, stream: S, ) -> impl Stream> + 's @@ -119,10 +115,10 @@ impl ArchiveBlockSink { let mut writer = writer.clone(); async move { match result { - Ok(components) => { - let index = components.block_data.block().index; + Ok(block_data) => { + let index = block_data.block().index; let writer_mut = &mut writer; - write_single_block(&components, writer_mut).await?; + write_single_block(&block_data, writer_mut).await?; maybe_write_merged_blocks( index, merged_blocks_bucket_sizes, @@ -166,7 +162,6 @@ async fn maybe_write_merged_blocks<'s, W: ProtoWriter, L: Ledger>( cache.reserve(bucket_usize); while cache.len() < bucket_usize { let index = last_index - (cache.len() as u64); - // TODO(#1682): Include QuorumSet, VerificationReport. // TODO: Switch this blocking call to an async method, or use `spawn_blocking`. let block = ledger.get_block_data(index).map_err(|e| { Error::Other(format!( @@ -174,8 +169,7 @@ async fn maybe_write_merged_blocks<'s, W: ProtoWriter, L: Ledger>( index, e )) })?; - let components = BlockStreamComponents::new(block, None, None); - cache.push_front(components); + cache.push_front(block); } write_merged_block(cache.make_contiguous(), bucket, writer).await?; @@ -183,25 +177,22 @@ async fn maybe_write_merged_blocks<'s, W: ProtoWriter, L: Ledger>( Ok(()) } -async fn write_single_block( - components: &BlockStreamComponents, - writer: &mut W, -) -> Result<()> { - let index = components.block_data.block().index; - let proto = ArchiveBlock::from(components); +async fn write_single_block(block_data: &BlockData, writer: &mut W) -> Result<()> { + let index = block_data.block().index; + let proto = ArchiveBlock::from(block_data); let dest = block_num_to_s3block_path(index); writer.upload(&proto, &dest).await } async fn write_merged_block( - items: &[BlockStreamComponents], + items: &[BlockData], bucket_size: u64, writer: &mut W, ) -> Result<()> { assert_eq!(items.len() as u64, bucket_size); let indexes = items .iter() - .map(|b| b.block_data.block().index) + .map(|block_data| block_data.block().index) .collect::>(); debug_assert!( indexes.windows(2).all(|w| w[0] == w[1] - 1), @@ -209,7 +200,7 @@ async fn write_merged_block( "Expected contiguous block indexes, got {:?}", indexes, ); - let proto: ArchiveBlocks = components_to_archive_blocks(items); + let proto = ArchiveBlocks::from(items); let dest = merged_block_num_to_s3block_path(bucket_size, indexes[0]); writer.upload(&proto, &dest).await } @@ -221,7 +212,7 @@ mod tests { use mc_common::{logger::test_with_logger, HashMap}; use mc_ledger_db::MockLedger; use mc_ledger_streaming_api::{ - test_utils::{make_components, MockStream}, + test_utils::{make_blocks, MockStream}, BlockStream, }; use std::{ @@ -273,12 +264,12 @@ mod tests { fn exercise_basic(logger: Logger) { let writer = MockWriter::new(); let writer_calls = writer.calls.clone(); - let items = make_components(11); - let source = MockStream::from_components(items.clone()); + let items = make_blocks(11); + let source = MockStream::from_blocks(items.clone()); let mut ledger = MockLedger::new(); ledger .expect_get_block_data() - .returning(move |index| Ok(items[index as usize].block_data.clone())); + .returning(move |index| Ok(items[index as usize].clone())); let mut sink = ArchiveBlockSink::new(writer, ledger, [10].to_vec(), logger); @@ -296,12 +287,12 @@ mod tests { fn propagates_ledger_errors(logger: Logger) { let writer = MockWriter::new(); let writer_calls = writer.calls.clone(); - let items = make_components(11); - let source = MockStream::from_components(items.clone()); + let items = make_blocks(11); + let source = MockStream::from_blocks(items.clone()); let mut ledger = MockLedger::new(); ledger.expect_get_block_data().returning(move |index| { if index < 5 { - Ok(items[index as usize].block_data.clone()) + Ok(items[index as usize].clone()) } else { Err(mc_ledger_db::Error::NotFound) } @@ -330,16 +321,15 @@ mod tests { fn propagates_stream_errors(logger: Logger) { let writer = MockWriter::new(); let writer_calls = writer.calls.clone(); - let mut items: Vec> = - make_components(11).into_iter().map(Ok).collect(); + let mut items: Vec> = make_blocks(11).into_iter().map(Ok).collect(); items[1] = Err(Error::Other("test".to_string())); items[8] = Err(Error::Other("test".to_string())); - let source = MockStream::from_items(items.clone()); + let source = MockStream::new(items.clone()); let mut ledger = MockLedger::new(); ledger.expect_get_block_data().returning(move |index| { items[index as usize] .as_ref() - .map(|c| c.block_data.clone()) + .map(|block_data| block_data.clone()) .map_err(|_| mc_ledger_db::Error::NotFound) }); diff --git a/ledger/streaming/publisher/src/grpc.rs b/ledger/streaming/publisher/src/grpc.rs index a4655b2e78..3908075707 100644 --- a/ledger/streaming/publisher/src/grpc.rs +++ b/ledger/streaming/publisher/src/grpc.rs @@ -1,6 +1,6 @@ // Copyright (c) 2018-2022 The MobileCoin Foundation -//! A sink that consumes a [Stream] of [SubscribeResponse]s and publishes the +//! A sink that consumes a [Stream] of [ArchiveBlock]s and publishes the //! results over gRPC [LedgerUpdates]. use flo_stream::{ExpiringPublisher, MessagePublisher, Subscriber}; @@ -8,23 +8,23 @@ use futures::{lock::Mutex, FutureExt, Stream, StreamExt, TryStreamExt}; use grpcio::ServerStreamingSink; use mc_common::logger::Logger; use mc_ledger_streaming_api::{ - streaming_blocks::{SubscribeRequest, SubscribeResponse}, + streaming_blocks::SubscribeRequest, streaming_blocks_grpc::{create_ledger_updates, LedgerUpdates}, - Result, + ArchiveBlock, Result, }; use mc_util_grpc::ConnectionUriGrpcioServer; use mc_util_uri::ConnectionUri; use std::sync::Arc; -/// A sink that consumes a [Stream] of [SubscribeResponse]s and publishes the +/// A sink that consumes a [Stream] of [ArchiveBlock]s and publishes the /// results over gRPC [LedgerUpdates]. pub struct GrpcServerSink { - publisher: Arc>>, + publisher: Arc>>, logger: Logger, } impl GrpcServerSink { - /// Instantiate a sink that publishes [SubscribeResponse]s over the + /// Instantiate a sink that publishes [ArchiveBlock]s over the /// [LedgerUpdates] gRPC API. pub fn new(logger: Logger) -> Self { Self { @@ -34,13 +34,13 @@ impl GrpcServerSink { } } - /// Consume a [Stream] of [SubscribeResponse]s. + /// Consume a [Stream] of [ArchiveBlock]s. /// The returned value is a `Stream` where the `Output` type is /// `Result<()>`; it is executed entirely for its side effects, while /// propagating errors back to the caller. pub fn consume_protos<'a>( &self, - stream: impl Stream> + 'a, + stream: impl Stream> + 'a, ) -> impl Stream> + 'a { let publisher = self.publisher.clone(); stream.and_then(move |data| { @@ -98,11 +98,11 @@ impl GrpcServerSink { #[derive(Clone)] struct PublishHelper { - subscriber: Subscriber, + subscriber: Subscriber, } impl PublishHelper { - pub fn new(subscriber: Subscriber) -> Self { + pub fn new(subscriber: Subscriber) -> Self { Self { subscriber } } } @@ -112,19 +112,14 @@ impl LedgerUpdates for PublishHelper { &mut self, ctx: grpcio::RpcContext, req: SubscribeRequest, - sink: ServerStreamingSink, + sink: ServerStreamingSink, ) { let starting_height = req.starting_height; let stream = self .subscriber .clone() .skip_while(move |resp| { - let block_index = resp - .get_result() - .get_block() - .get_v1() - .get_block() - .get_index(); + let block_index = resp.get_v1().get_block().get_index(); futures::future::ready(block_index < starting_height) }) .map(|resp| Ok((resp, grpcio::WriteFlags::default()))); @@ -164,38 +159,23 @@ mod tests { executor .spawn(async move { - let mut response = SubscribeResponse::new(); + let mut response = ArchiveBlock::new(); let mut client_1 = TestClient::new(&uri, env.clone()); client_1.subscribe().await; - response - .mut_result() - .mut_block() - .mut_v1() - .mut_block() - .set_index(0); + response.mut_v1().mut_block().set_index(0); sender.try_send(Ok(response.clone())).expect("send failed"); let mut client_2 = TestClient::new(&uri, env.clone()); client_2.subscribe().await; - response - .mut_result() - .mut_block() - .mut_v1() - .mut_block() - .set_index(1); + response.mut_v1().mut_block().set_index(1); sender.try_send(Ok(response.clone())).expect("send failed"); let mut client_3 = TestClient::new(&uri, env.clone()); client_3.subscribe().await; - response - .mut_result() - .mut_block() - .mut_v1() - .mut_block() - .set_index(2); + response.mut_v1().mut_block().set_index(2); sender.try_send(Ok(response.clone())).expect("send failed"); assert_eq!(3, client_1.response_count()); From 547b07b87c9546eb251838d14952c2a6098b1bc7 Mon Sep 17 00:00:00 2001 From: iamalwaysuncomfortable Date: Tue, 26 Apr 2022 20:58:38 -0400 Subject: [PATCH 08/11] Benchmark all client components, add log statements --- ledger/db/src/test_utils/mock_ledger.rs | 129 ++++++++++++++++ ledger/streaming/api/Cargo.toml | 1 + ledger/streaming/api/src/test_utils/mod.rs | 2 + .../api/src/test_utils/quorum_set.rs | 33 ++++ ledger/streaming/api/src/test_utils/stream.rs | 33 ++++ ledger/streaming/client/benches/bench.rs | 104 +++++++++++++ .../streaming/client/src/block_validator.rs | 143 ++++++++++-------- ledger/streaming/client/src/ledger_sink.rs | 32 +++- ledger/streaming/client/src/scp_validator.rs | 18 ++- ledger/streaming/publisher/Cargo.toml | 1 + 10 files changed, 422 insertions(+), 74 deletions(-) create mode 100644 ledger/streaming/api/src/test_utils/quorum_set.rs create mode 100644 ledger/streaming/client/benches/bench.rs diff --git a/ledger/db/src/test_utils/mock_ledger.rs b/ledger/db/src/test_utils/mock_ledger.rs index f0b910252f..c1536ec231 100644 --- a/ledger/db/src/test_utils/mock_ledger.rs +++ b/ledger/db/src/test_utils/mock_ledger.rs @@ -5,6 +5,7 @@ use mc_account_keys::AccountKey; use mc_common::{HashMap, HashSet}; use mc_crypto_keys::{CompressedRistrettoPublic, RistrettoPrivate}; use mc_transaction_core::{ + constants::TOTAL_MOB, mint::MintTx, ring_signature::KeyImage, tokens::Mob, @@ -300,11 +301,139 @@ pub fn get_test_ledger_blocks(n_blocks: usize) -> Vec<(Block, BlockContents)> { blocks_and_contents } +/// Get blocks with custom content in order to simulate conditions seen in +/// production +/// +/// * `outputs_per_recipient_per_block` - transaction outputs per account per +/// block +/// * `num_accounts` - number of accounts in the blocks +/// * `num_blocks` - number of simulated blocks to create +/// * `key_images_per_block` - number of key images per block +/// * `max_token_id` - number of distinct token ids in blocks +pub fn get_custom_test_ledger_blocks( + outputs_per_recipient_per_block: usize, + num_accounts: usize, + num_blocks: usize, + key_images_per_block: usize, + max_token_id: u64, +) -> Vec<(Block, BlockContents)> { + let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]); + + // Number of total tx outputs in all blocks + let num_outputs: u64 = + (num_accounts * outputs_per_recipient_per_block * num_blocks * (max_token_id as usize + 1)) + as u64; + assert!(num_outputs >= 16); + + // Initialize other defaults + let picomob_per_output: u64 = (TOTAL_MOB / num_outputs) * 1_000_000_000_000; + let recipients = (0..num_accounts) + .map(|_| AccountKey::random(&mut rng).default_subaddress()) + .collect::>(); + let block_version = BlockVersion::MAX; + let mut blocks_and_contents: Vec<(Block, BlockContents)> = Vec::new(); + let mut previous_block: Option = None; + + // Create the tx outs for all of the simulated blocks + for _ in 0..num_blocks as u64 { + let mut outputs: Vec = Vec::new(); + for recipient in &recipients { + let tx_private_key = RistrettoPrivate::from_random(&mut rng); + for _ in 0..outputs_per_recipient_per_block { + // Create outputs for each token id in round-robin fashion + for token_id in 0..=max_token_id { + let amount = Amount { + value: picomob_per_output, + token_id: token_id.into(), + }; + let output = TxOut::new(amount, recipient, &tx_private_key, Default::default()); + outputs.push(output.unwrap()); + } + } + } + + // Create key images unless we're at the origin block + let key_images: Vec = if previous_block.is_some() { + (0..key_images_per_block) + .map(|_i| KeyImage::from(rng.next_u64())) + .collect() + } else { + Default::default() + }; + + let block_contents = BlockContents { + key_images, + outputs: outputs.clone(), + ..Default::default() + }; + + // Create a block with the desired contents + let block = match previous_block { + Some(parent) => { + Block::new_with_parent(block_version, &parent, &Default::default(), &block_contents) + } + None => Block::new_origin_block(&outputs), + }; + + previous_block = Some(block.clone()); + blocks_and_contents.push((block, block_contents)); + } + blocks_and_contents +} + #[cfg(test)] mod tests { use super::*; use mc_transaction_core::compute_block_id; + #[test] + // `get_custom_test_ledger_blocks` should return blocks that match the + // configuration specified in the arguments and pass all normal + // consistency tests + fn test_custom_block_correctness() { + let blocks_and_transactions = get_custom_test_ledger_blocks(2, 3, 3, 3, 0); + + let blocks: Vec = blocks_and_transactions + .iter() + .map(|(block, _transactions)| block.clone()) + .collect(); + + // Ensure the correct amount of blocks have been created + assert_eq!(blocks_and_transactions.len(), 3); + + // Ensure the origin block id isn't a hash of another block + let origin_block: &Block = blocks.get(0).unwrap(); + assert_eq!(origin_block.parent_id.as_ref(), [0u8; 32]); + assert_eq!(origin_block.index, 0); + + for (block, block_contents) in blocks_and_transactions.iter() { + let derived_block_id = compute_block_id( + block.version, + &block.parent_id, + block.index, + block.cumulative_txo_count, + &block.root_element, + &block.contents_hash, + ); + + // Ensure the block_id matches the id computed via the merlin transcript + assert_eq!(derived_block_id, block.id); + + // Ensure stated block hash matches the computed hash + assert_eq!(block.contents_hash, block_contents.hash()); + + // Ensure the amount of transactions present matches expected amount + assert_eq!(block.cumulative_txo_count, (block.index + 1) * 6); + + // Ensure the correct number of key images exist + if block.index == 0 { + assert_eq!(block_contents.key_images.len(), 0); + } else { + assert_eq!(block_contents.key_images.len(), 3); + } + } + } + #[test] // `get_test_ledger_blocks` should return a valid blockchain of the specified // length. diff --git a/ledger/streaming/api/Cargo.toml b/ledger/streaming/api/Cargo.toml index c37217b29f..3ea92f8830 100644 --- a/ledger/streaming/api/Cargo.toml +++ b/ledger/streaming/api/Cargo.toml @@ -34,4 +34,5 @@ cargo-emit = "0.2" [dev-dependencies] mc-common = { path = "../../../common", features = ["loggers"] } mc-consensus-scp = { path = "../../../consensus/scp", features = ["test_utils"] } +mc-ledger-db = { path = "../../../ledger/db", features = ["test_utils"] } mc-ledger-streaming-api = { path = "../api", features = ["test_utils"] } diff --git a/ledger/streaming/api/src/test_utils/mod.rs b/ledger/streaming/api/src/test_utils/mod.rs index 94e25598a6..f868c9a65f 100644 --- a/ledger/streaming/api/src/test_utils/mod.rs +++ b/ledger/streaming/api/src/test_utils/mod.rs @@ -4,12 +4,14 @@ pub mod blocks; pub mod fetcher; +pub mod quorum_set; pub mod response; pub mod stream; pub use self::{ blocks::make_blocks, fetcher::MockFetcher, + quorum_set::make_quorum_set, response::{make_responses, Response, Responses}, stream::MockStream, }; diff --git a/ledger/streaming/api/src/test_utils/quorum_set.rs b/ledger/streaming/api/src/test_utils/quorum_set.rs new file mode 100644 index 0000000000..f5942f8e18 --- /dev/null +++ b/ledger/streaming/api/src/test_utils/quorum_set.rs @@ -0,0 +1,33 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +//! QuorumSet helpers for tests. + +use mc_consensus_scp::{test_utils::test_node_id, QuorumSet, QuorumSetMember}; + +/// Create a QuorumSet for tests. +pub fn make_quorum_set() -> QuorumSet { + let qs = QuorumSet::new( + 2, + vec![ + QuorumSetMember::Node(test_node_id(1)), + QuorumSetMember::InnerSet(QuorumSet::new( + 2, + vec![ + QuorumSetMember::Node(test_node_id(3)), + QuorumSetMember::Node(test_node_id(4)), + ], + )), + QuorumSetMember::Node(test_node_id(0)), + QuorumSetMember::InnerSet(QuorumSet::new( + 2, + vec![ + QuorumSetMember::Node(test_node_id(5)), + QuorumSetMember::Node(test_node_id(6)), + QuorumSetMember::Node(test_node_id(7)), + ], + )), + ], + ); + assert!(qs.is_valid()); + qs +} diff --git a/ledger/streaming/api/src/test_utils/stream.rs b/ledger/streaming/api/src/test_utils/stream.rs index 0b1bfb2b86..9c925d5e3d 100644 --- a/ledger/streaming/api/src/test_utils/stream.rs +++ b/ledger/streaming/api/src/test_utils/stream.rs @@ -4,6 +4,7 @@ use crate::{BlockData, BlockStream, Result}; use futures::Stream; +use mc_ledger_db::test_utils::mock_ledger::get_custom_test_ledger_blocks; /// Mock implementation of BlockStream, backed by pre-defined data. #[derive(Clone, Debug)] @@ -34,3 +35,35 @@ impl BlockStream for MockStream { Ok(futures::stream::iter(items)) } } + +/// Create a mock stream with blocks that resemble those seen in productoin +/// +/// * `outputs_per_recipient_per_block` - transaction outputs per account per +/// block +/// * `num_accounts` - number of accounts in the simulated blocks +/// * `num_blocks` - number of simulated blocks to create +/// * `key_images_per_block` - number of simulated key images per block +/// +/// Returns a MockStream that when driven will produce blocks with the contents +/// specified in the above parameters +pub fn mock_stream_with_custom_block_contents( + outputs_per_recipient_per_block: usize, + num_accounts: usize, + num_blocks: usize, + key_images_per_block: usize, + max_token_id: u64, +) -> MockStream { + let blocks = get_custom_test_ledger_blocks( + outputs_per_recipient_per_block, + num_accounts, + num_blocks, + key_images_per_block, + max_token_id, + ); + + let block_data: Vec = blocks + .into_iter() + .map(move |(block, block_contents)| BlockData::new(block, block_contents, None)) + .collect(); + MockStream::from_blocks(block_data) +} diff --git a/ledger/streaming/client/benches/bench.rs b/ledger/streaming/client/benches/bench.rs new file mode 100644 index 0000000000..4a9ffa4eb8 --- /dev/null +++ b/ledger/streaming/client/benches/bench.rs @@ -0,0 +1,104 @@ +#![feature(test)] + +extern crate test; +use futures::{executor::block_on, StreamExt}; +use hashbrown::HashMap; +use mc_consensus_scp::test_utils::test_node_id; +use mc_ledger_db::test_utils::get_mock_ledger; +use mc_ledger_streaming_api::{ + test_utils::{make_quorum_set, stream}, + BlockStream, +}; +use mc_ledger_streaming_client::{ + block_validator::BlockValidator, ledger_sink::DbStream, scp_validator::SCPValidator, +}; +use test::Bencher; + +/// Sink 1000 blocks into a ledger +#[bench] +fn bench_ledger_sink_for_1000_blocks(b: &mut Bencher) { + let logger = mc_common::logger::create_test_logger("benchmark:sink_1000_blocks".into()); + let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); + let ledger = get_mock_ledger(0); + let mut downstream_producer = + DbStream::new(upstream_producer.clone(), ledger, true, logger.clone()); + + b.iter(|| { + let producer_ref = &mut downstream_producer; + producer_ref.reinitialize_ledger(get_mock_ledger(0)); + let mut stream = producer_ref.get_block_stream(0).unwrap(); + block_on(async move { + while let Some(_) = stream.next().await { + // Benchmark stream + } + }); + }); +} + +/// Do SCP validation on 1000 blocks +#[bench] +fn bench_scp_validation_for_1000_blocks(b: &mut Bencher) { + let logger = + mc_common::logger::create_test_logger("benchmark:scp_validation_1000_blocks".into()); + let quorum_set = make_quorum_set(); + let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); + let mut upstreams = HashMap::new(); + for i in 0..9 { + upstreams.insert(test_node_id(i), upstream_producer.clone()); + } + let downstream_producer = SCPValidator::new(upstreams, logger, test_node_id(10), quorum_set); + + b.iter(|| { + let stream = &mut downstream_producer.get_block_stream(0).unwrap(); + block_on(async move { + while let Some(_) = stream.next().await { + // Benchmark stream + } + }); + }); +} + +/// Validate 1000 typically sized blocks +#[bench] +fn bench_validation_for_1000_blocks(b: &mut Bencher) { + let logger = mc_common::logger::create_test_logger("benchmark:validate_1000_blocks".into()); + let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); + let ledger = Some(get_mock_ledger(0)); + let downstream_producer = BlockValidator::new(upstream_producer, ledger, logger); + + b.iter(|| { + let stream = &mut downstream_producer.get_block_stream(0).unwrap(); + block_on(async move { + while let Some(_) = stream.next().await { + // Benchmark stream + } + }); + }); +} + +#[bench] +fn bench_integrated_components(b: &mut Bencher) { + let logger = + mc_common::logger::create_test_logger("benchmark:integrated_validation_1000_blocks".into()); + let quorum_set = make_quorum_set(); + let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); + let mut upstreams = HashMap::new(); + let ledger = get_mock_ledger(0); + for i in 0..9 { + upstreams.insert(test_node_id(i), upstream_producer.clone()); + } + let scp_validator = SCPValidator::new(upstreams, logger.clone(), test_node_id(10), quorum_set); + let block_validator = BlockValidator::new(scp_validator, Some(ledger.clone()), logger.clone()); + let mut ledger_sink = DbStream::new(block_validator, ledger, true, logger.clone()); + + b.iter(|| { + let producer_ref = &mut ledger_sink; + producer_ref.reinitialize_ledger(get_mock_ledger(0)); + let mut stream = producer_ref.get_block_stream(0).unwrap(); + block_on(async move { + while let Some(_) = stream.next().await { + // Benchmark stream + } + }); + }); +} diff --git a/ledger/streaming/client/src/block_validator.rs b/ledger/streaming/client/src/block_validator.rs index 3cb0595285..d14b338c2e 100644 --- a/ledger/streaming/client/src/block_validator.rs +++ b/ledger/streaming/client/src/block_validator.rs @@ -50,82 +50,95 @@ impl BlockStream for Blo log::info!(self.logger, "Creating block validation stream"); let stream = self.upstream.get_block_stream(starting_height)?; - Ok(stream.scan( - ( - ledger, - prev_block_id, - additional_key_images, - starting_height, - ), - |state, result| { - match result { - Ok(block_data) => { - let (ledger, prev_block_id, additional_key_images, starting_height) = state; - - let block = block_data.block(); - let block_contents = block_data.contents(); - - if *starting_height == block.index && ledger.is_none() && block.index > 0 { - *prev_block_id = block.parent_id.clone(); - } + Ok( + stream.scan( + ( + ledger, + prev_block_id, + additional_key_images, + starting_height, + self.logger.clone(), + ), + |state, result| { + match result { + Ok(block_data) => { + let ( + ledger, + prev_block_id, + additional_key_images, + starting_height, + logger, + ) = state; + + let block = block_data.block(); + let block_contents = block_data.contents(); + + if *starting_height == block.index + && ledger.is_none() + && block.index > 0 + { + *prev_block_id = block.parent_id.clone(); + } - // Check if parent block matches last block seen - if &block.parent_id != prev_block_id { - return future::ready(Some(Err(StreamError::BlockValidation( - "Block parent ID doesn't match".to_string(), - )))); - } + // Check if parent block matches last block seen + if &block.parent_id != prev_block_id { + return future::ready(Some(Err(StreamError::BlockValidation( + "Block parent ID doesn't match".to_string(), + )))); + } - // Check if key images already in ledger - if let Some(ledger) = ledger { - for key_image in &block_contents.key_images { - // Check if the key image is already in the local ledger. - match ledger.contains_key_image(key_image) { - Ok(contains_key_image) => { - if contains_key_image - || additional_key_images.contains(key_image) - { + // Check if key images already in ledger + if let Some(ledger) = ledger { + for key_image in &block_contents.key_images { + // Check if the key image is already in the local ledger. + match ledger.contains_key_image(key_image) { + Ok(contains_key_image) => { + if contains_key_image + || additional_key_images.contains(key_image) + { + return future::ready(Some(Err( + StreamError::BlockValidation( + "Contains spent key image".to_string(), + ), + ))); + } + } + Err(err) => { return future::ready(Some(Err( - StreamError::BlockValidation( - "Contains spent key image".to_string(), - ), + StreamError::DBAccess(err.to_string()), ))); } } - Err(err) => { - return future::ready(Some(Err(StreamError::DBAccess( - err.to_string(), - )))); - } + additional_key_images.insert(*key_image); } - additional_key_images.insert(*key_image); } - } - // Compute the hash of the block - let derived_block_id = compute_block_id( - block.version, - &block.parent_id, - block.index, - block.cumulative_txo_count, - &block.root_element, - &block_contents.hash(), - ); - - // The block's ID must agree with the merkle hash of its transactions. - if block.id != derived_block_id { - return future::ready(Some(Err(StreamError::BlockValidation( - "Hash of transactions don't match claimed block id".to_string(), - )))); - } + // Compute the hash of the block + let derived_block_id = compute_block_id( + block.version, + &block.parent_id, + block.index, + block.cumulative_txo_count, + &block.root_element, + &block_contents.hash(), + ); + + // The block's ID must agree with the merkle hash of its transactions. + if block.id != derived_block_id { + return future::ready(Some(Err(StreamError::BlockValidation( + "Hash of transactions don't match claimed block id".to_string(), + )))); + } - *prev_block_id = block.id.clone(); - future::ready(Some(Ok(block_data))) + log::debug!(logger, "block {} validated", block.index); + *prev_block_id = block.id.clone(); + future::ready(Some(Ok(block_data))) + } + Err(err) => future::ready(Some(Err(err))), } - Err(err) => future::ready(Some(Err(err))), - } - }, - )) + }, + ), + ) } } diff --git a/ledger/streaming/client/src/ledger_sink.rs b/ledger/streaming/client/src/ledger_sink.rs index be7e6ce52c..569250d239 100644 --- a/ledger/streaming/client/src/ledger_sink.rs +++ b/ledger/streaming/client/src/ledger_sink.rs @@ -116,13 +116,15 @@ impl BlockStream for DbS }; // Get the upstream, start our thread, and initialize our sink management object + log::info!(self.logger, "creating ledger sink stream & thread"); let (tx, rcv) = start_sink_thread(self.ledger.clone(), self.logger.clone()); let manager = SinkManager::new(tx, rcv, 0, 0, sync_start_height, self.logger.clone()); let stream = Box::pin(self.upstream.get_block_stream(starting_height).unwrap()); // Create the stream - let output_stream = - futures::stream::unfold((stream, manager), |(mut stream, mut manager)| async move { + let output_stream = futures::stream::unfold( + (stream, manager), + |(mut stream, mut manager)| async move { if let Some(result) = stream.next().await { if let Ok(block_data) = result { manager.last_block_received = block_data.block().index; @@ -149,16 +151,23 @@ impl BlockStream for DbS } else { // If we're behind, wait for the rest of the blocks to sync then end if manager.is_behind() { - log::debug!( + log::warn!( manager.logger, - "upstream terminated, waiting for the rest of the blocks to sync" + "upstream terminated with {} blocks received, waiting to sync up to block {}", + manager.last_block_received, + manager.last_block_synced, ); } else { - log::warn!(manager.logger, "upstream stopped, ending stream"); + log::warn!(manager.logger, + "upstream ended, ending downstream - blocks received: {}, blocks synced: {}", + manager.last_block_received, + manager.last_block_synced, + ); return None; } } if let Some(block_data) = manager.receiver.recv().await { + log::debug!(manager.logger, "block {} synced", block_data.block().index); manager.last_block_synced = block_data.block().index; Some((Ok(block_data), (stream, manager))) } else { @@ -166,7 +175,8 @@ impl BlockStream for DbS log::error!(manager.logger, "sink thread stopped, ending stream"); None } - }); + }, + ); Ok(Box::pin(output_stream)) } } @@ -181,6 +191,11 @@ impl DbStream { logger, } } + + /// Replace ledger + pub fn reinitialize_ledger(&mut self, ledger: L) { + self.ledger = ledger; + } } fn start_sink_thread( @@ -193,6 +208,7 @@ fn start_sink_thread( // Launch ledger sink thread std::thread::spawn(move || { + log::debug!(logger, "starting ledger sink thread"); while let Some(block_data) = rcv_in.blocking_recv() { let signature = block_data.signature().as_ref().cloned(); @@ -219,6 +235,10 @@ fn start_sink_thread( break; } } + log::debug!( + logger, + "upstream receiver channel ended, ending ledger sink thread" + ); }); (send_in, rcv_out) } diff --git a/ledger/streaming/client/src/scp_validator.rs b/ledger/streaming/client/src/scp_validator.rs index 026459b3c2..ef4c6392a7 100644 --- a/ledger/streaming/client/src/scp_validator.rs +++ b/ledger/streaming/client/src/scp_validator.rs @@ -156,8 +156,10 @@ impl SCPValidationState { ); // Increment our targets - self.highest_slot_index += 1; self.num_blocks_externalized += 1; + if !(self.num_blocks_externalized == 1 && self.highest_slot_index == 0) { + self.highest_slot_index += 1; + } return blocks.remove(&selected_node); } @@ -239,7 +241,7 @@ impl SCPValidationState { /// Check if our recorded quorum is lagging behind what we've received pub fn is_behind(&self) -> bool { - self.highest_block_received() - self.highest_slot_index > MAX_BLOCK_DEFICIT + self.highest_block_received() > self.highest_slot_index + MAX_BLOCK_DEFICIT } } @@ -255,13 +257,18 @@ impl BlockStream for SCPVal let mut merged_streams = stream::SelectAll::new(); for stream_factory in &self.upstreams { let (node_id, upstream) = stream_factory; - log::info!(self.logger, "Generating new stream from {:?}", node_id); + log::debug!(self.logger, "Generating new stream from {:?}", node_id); let us = upstream.get_block_stream(starting_height).unwrap(); let peer_id = node_id.clone(); merged_streams.push(Box::pin(us.map(move |result| (peer_id.clone(), result)))); } // Create SCP validation state object and insert it into stateful stream + log::info!( + self.logger, + "Creating SCP validation stream from {} upstreams", + self.upstreams.len() + ); let mut validation_state = self.scp_validation_state.clone(); if starting_height > 0 { validation_state.set_highest_externalized_slot(starting_height - 1); @@ -275,6 +282,11 @@ impl BlockStream for SCPVal // Attempt to externalize it if there's quorum if let Some(block_data) = scp_state.attempt_externalize_block() { + log::debug!( + scp_state.logger, + "upstreams have consensus on block {}", + block_data.block().index; + ); return future::ready(Some(Ready::Ready(Ok(block_data)))); } } diff --git a/ledger/streaming/publisher/Cargo.toml b/ledger/streaming/publisher/Cargo.toml index ddfb58ef4b..34ba9de429 100644 --- a/ledger/streaming/publisher/Cargo.toml +++ b/ledger/streaming/publisher/Cargo.toml @@ -31,6 +31,7 @@ tokio = { version = "1.16", features = ["fs"], optional = true } [dev-dependencies] mc-common = { path = "../../../common", features = ["loggers"] } +mc-ledger-db = { path = "../../../ledger/db", features = ["test_utils"] } mc-ledger-streaming-api = { path = "../api", features = ["test_utils"] } async_executors = { version = "0.5", features = ["async_std", "tokio_ct", "tokio_tp"] } From e60e20cb6b9444006a61cb465029faedafd2d3c7 Mon Sep 17 00:00:00 2001 From: iamalwaysuncomfortable Date: Fri, 29 Apr 2022 14:47:24 -0400 Subject: [PATCH 09/11] Add bench of end-end pipeline --- Cargo.lock | 1 + ledger/streaming/client/Cargo.toml | 5 +- ledger/streaming/client/benches/bench.rs | 149 ++++++++++++++++---- ledger/streaming/client/src/backfill.rs | 13 ++ ledger/streaming/client/src/http_fetcher.rs | 3 +- ledger/streaming/client/src/ledger_sink.rs | 15 +- ledger/streaming/publisher/src/grpc.rs | 8 +- 7 files changed, 158 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b5648e789..29f830cb41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5166,6 +5166,7 @@ dependencies = [ "mc-crypto-keys", "mc-ledger-db", "mc-ledger-streaming-api", + "mc-ledger-streaming-publisher", "mc-transaction-core", "mc-util-from-random", "mc-util-grpc", diff --git a/ledger/streaming/client/Cargo.toml b/ledger/streaming/client/Cargo.toml index 45de1f86b8..cba600f1cf 100644 --- a/ledger/streaming/client/Cargo.toml +++ b/ledger/streaming/client/Cargo.toml @@ -15,17 +15,18 @@ mc-consensus-scp = { path = "../../../consensus/scp" } mc-crypto-keys = { path = "../../../crypto/keys" } mc-ledger-db = { path = "../../../ledger/db" } mc-ledger-streaming-api = { path = "../api" } +mc-ledger-streaming-publisher = { path = "../publisher" } mc-transaction-core = { path = "../../../transaction/core" } mc-util-grpc = { path = "../../../util/grpc" } mc-util-uri = { path = "../../../util/uri" } displaydoc = "0.2" -futures = "0.3" +futures = { version = "0.3", features = ["thread-pool"] } grpcio = "0.10" hashbrown = "0.12.0" protobuf = "2.22" reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "gzip"] } -tokio = "1.17.0" +tokio = { version = "1.16.0", optional = true } url = "2.2" [dev-dependencies] diff --git a/ledger/streaming/client/benches/bench.rs b/ledger/streaming/client/benches/bench.rs index 4a9ffa4eb8..9e35a437b9 100644 --- a/ledger/streaming/client/benches/bench.rs +++ b/ledger/streaming/client/benches/bench.rs @@ -1,85 +1,98 @@ #![feature(test)] extern crate test; + use futures::{executor::block_on, StreamExt}; use hashbrown::HashMap; +use mc_common::logger::log; use mc_consensus_scp::test_utils::test_node_id; use mc_ledger_db::test_utils::get_mock_ledger; use mc_ledger_streaming_api::{ test_utils::{make_quorum_set, stream}, - BlockStream, + ArchiveBlock, BlockStream, Result as StreamResult, }; use mc_ledger_streaming_client::{ block_validator::BlockValidator, ledger_sink::DbStream, scp_validator::SCPValidator, + BackfillingStream, BlockchainUrl, GrpcBlockSource, HttpBlockFetcher, }; +use mc_ledger_streaming_publisher::GrpcServerSink; +use std::{env, str::FromStr, sync::Arc}; use test::Bencher; -/// Sink 1000 blocks into a ledger +/// Bench sinking 1000 blocks into a ledger #[bench] fn bench_ledger_sink_for_1000_blocks(b: &mut Bencher) { let logger = mc_common::logger::create_test_logger("benchmark:sink_1000_blocks".into()); + + // Create simulated upstream with 1000 realistic blocks and fake ledger to sink + // into let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); let ledger = get_mock_ledger(0); - let mut downstream_producer = - DbStream::new(upstream_producer.clone(), ledger, true, logger.clone()); + // Initialize ledger sink stream + let mut downstream_producer = DbStream::new(upstream_producer, ledger, true, logger.clone()); + + // Benchmark stream b.iter(|| { let producer_ref = &mut downstream_producer; producer_ref.reinitialize_ledger(get_mock_ledger(0)); let mut stream = producer_ref.get_block_stream(0).unwrap(); - block_on(async move { - while let Some(_) = stream.next().await { - // Benchmark stream - } - }); + block_on(async move { while stream.next().await.is_some() {} }); }); } -/// Do SCP validation on 1000 blocks +/// Bench SCP validation on 1000 blocks #[bench] fn bench_scp_validation_for_1000_blocks(b: &mut Bencher) { let logger = mc_common::logger::create_test_logger("benchmark:scp_validation_1000_blocks".into()); + + // Create 9 simulated upstreams with 1000 realistic blocks and simulated quorum + // set let quorum_set = make_quorum_set(); let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); let mut upstreams = HashMap::new(); for i in 0..9 { upstreams.insert(test_node_id(i), upstream_producer.clone()); } + + // Initialize SCP validation stream let downstream_producer = SCPValidator::new(upstreams, logger, test_node_id(10), quorum_set); + // Benchmark stream b.iter(|| { let stream = &mut downstream_producer.get_block_stream(0).unwrap(); - block_on(async move { - while let Some(_) = stream.next().await { - // Benchmark stream - } - }); + block_on(async move { while stream.next().await.is_some() {} }); }); } -/// Validate 1000 typically sized blocks +/// Bench validation of 1000 typically sized blocks #[bench] fn bench_validation_for_1000_blocks(b: &mut Bencher) { let logger = mc_common::logger::create_test_logger("benchmark:validate_1000_blocks".into()); + + // Initialize upstream producer and fake ledger let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); let ledger = Some(get_mock_ledger(0)); + + // Initialize block validation component let downstream_producer = BlockValidator::new(upstream_producer, ledger, logger); + // Benchmark stream b.iter(|| { let stream = &mut downstream_producer.get_block_stream(0).unwrap(); - block_on(async move { - while let Some(_) = stream.next().await { - // Benchmark stream - } - }); + block_on(async move { while stream.next().await.is_some() {} }); }); } +/// Bench full client validation & sink pipeline #[bench] fn bench_integrated_components(b: &mut Bencher) { let logger = mc_common::logger::create_test_logger("benchmark:integrated_validation_1000_blocks".into()); + + // Create 9 simulated upstreams with 1000 realistic blocks, simulated quorum + // set, and fake ledger to sink into let quorum_set = make_quorum_set(); let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); let mut upstreams = HashMap::new(); @@ -87,17 +100,105 @@ fn bench_integrated_components(b: &mut Bencher) { for i in 0..9 { upstreams.insert(test_node_id(i), upstream_producer.clone()); } + + // Initialize stream chain let scp_validator = SCPValidator::new(upstreams, logger.clone(), test_node_id(10), quorum_set); let block_validator = BlockValidator::new(scp_validator, Some(ledger.clone()), logger.clone()); let mut ledger_sink = DbStream::new(block_validator, ledger, true, logger.clone()); + // Benchmark stream chain b.iter(|| { let producer_ref = &mut ledger_sink; producer_ref.reinitialize_ledger(get_mock_ledger(0)); let mut stream = producer_ref.get_block_stream(0).unwrap(); - block_on(async move { - while let Some(_) = stream.next().await { - // Benchmark stream + block_on(async move { while stream.next().await.is_some() {} }); + }); +} + +/// Bench simulated end-end pipeline +#[bench] +fn bench_simulated_pipeline(b: &mut Bencher) { + //Create logger and executor + let logger = + mc_common::logger::create_test_logger("benchmark:integrated_sink_1000_blocks".into()); + let executor = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime"); + let mut archive_blocks: Vec> = vec![]; + + // Attempt to get URL from envar data, if not stop the benchmark + let archive_peer = if let Ok(peer) = env::var("ARCHIVE_PEER") { + log::debug!(logger, "attempting to get real blocks from peer {:?}", peer); + BlockchainUrl::from_str(peer.as_str()).unwrap() + } else { + log::warn!( + logger, + "A valid uri to archive blocks must be specified to perform this test" + ); + return; + }; + + // Get real historical blocks in proto format for test data + let fetcher = HttpBlockFetcher::new(archive_peer.clone(), logger.clone()).unwrap(); + executor.block_on(async { + for i in 0..1000 { + let block_url = archive_peer.block_url(i).unwrap(); + let object: StreamResult = + fetcher.fetch_protobuf_object(&block_url).await; + archive_blocks.push(object); + } + }); + + // Create simulated upstream to be consumed by server sink + let archive_block_stream = futures::stream::iter(archive_blocks); + + // Setup & start test server + let sink = GrpcServerSink::new(logger.clone()); + let env = Arc::new( + grpcio::EnvBuilder::new() + .name_prefix("blockstream benchmark") + .build(), + ); + let uri = + mc_util_uri::ConsensusPeerUri::from_str(&format!("insecure-mcp://localhost:{}", 4242)) + .expect("Failed to parse local server URL"); + let mut server = sink + .create_server(&uri, env.clone()) + .expect("Failed to create server"); + server.start(); + + // Create client side stream chain + let ledger = get_mock_ledger(0); + let source = GrpcBlockSource::new(&uri, env, logger.clone()); + let backfill_stream = BackfillingStream::new(source, fetcher, logger.clone()); + let block_validator = + BlockValidator::new(backfill_stream, Some(ledger.clone()), logger.clone()); + let mut ledger_sink = DbStream::new(block_validator, ledger, true, logger.clone()); + + b.iter(|| { + let producer_ref = &mut ledger_sink; + producer_ref.reinitialize_ledger(get_mock_ledger(0)); + let mut stream = producer_ref.get_block_stream(0).unwrap(); + + // Sink simulated blocks into server broadcast + executor.spawn(sink.consume_protos(archive_block_stream.clone()).for_each( + |result| async move { + if result.is_err() { + println!("Error is {:?}", result); + } + }, + )); + + // Drive consumer stream chain + executor.block_on(async move { + let mut count = 0; + while let Some(block_data) = stream.next().await { + if count == 999 { + // Only unwrap block when necessary to avoid extra work in the benchmark + let index = block_data.unwrap().block().index; + if index == 999 { + break; + } + } + count += 1; } }); }); diff --git a/ledger/streaming/client/src/backfill.rs b/ledger/streaming/client/src/backfill.rs index 1770e1afd3..c2971e4031 100644 --- a/ledger/streaming/client/src/backfill.rs +++ b/ledger/streaming/client/src/backfill.rs @@ -82,11 +82,24 @@ fn backfill_stream<'s, S: Stream> + 's, F: BlockFetcher let item_stream = once(async { Ok(block_data) }); if index == next_index { // Happy path: We got another consecutive item, so just return that. + log::debug!( + logger, + "received properly ordered block: index received {}, prev index {:?}", + index, + prev_index, + ); prev_index = Some(index); Box::pin(item_stream) } else { + log::debug!( + logger, + "received out of order block: index received {}, prev index {:?}", + index, + prev_index, + ); // Need to backfill up to the current index. let start = prev_index.unwrap_or(starting_height); + log::debug!(logger, "starting backfill at {}", start); prev_index = Some(index); let backfill = fetcher.fetch_range(start..index); Box::pin(backfill.chain(item_stream)) diff --git a/ledger/streaming/client/src/http_fetcher.rs b/ledger/streaming/client/src/http_fetcher.rs index f04a68a19e..be08c7c353 100644 --- a/ledger/streaming/client/src/http_fetcher.rs +++ b/ledger/streaming/client/src/http_fetcher.rs @@ -232,7 +232,8 @@ impl HttpBlockFetcher { }) } - async fn fetch_protobuf_object(&self, url: &Url) -> Result + /// Fetch protobuf for block ranges + pub async fn fetch_protobuf_object(&self, url: &Url) -> Result where M: Message, { diff --git a/ledger/streaming/client/src/ledger_sink.rs b/ledger/streaming/client/src/ledger_sink.rs index 569250d239..71be670594 100644 --- a/ledger/streaming/client/src/ledger_sink.rs +++ b/ledger/streaming/client/src/ledger_sink.rs @@ -2,13 +2,16 @@ //! Creates a block sink stream factory -use futures::stream::{Stream, StreamExt}; +use futures::{ + channel::mpsc::{self, Receiver, Sender}, + stream::{Stream, StreamExt}, + SinkExt, +}; use mc_common::logger::{log, Logger}; use mc_ledger_db::Ledger; use mc_ledger_streaming_api::{ BlockData, BlockStream, Error as StreamError, Result as StreamResult, }; -use tokio::sync::mpsc::{channel, Receiver, Sender}; /// A block sink that takes blocks from a passed stream and puts them into /// ledger db. This sink should live downstream from a verification source that @@ -166,7 +169,7 @@ impl BlockStream for DbS return None; } } - if let Some(block_data) = manager.receiver.recv().await { + if let Some(block_data) = manager.receiver.next().await { log::debug!(manager.logger, "block {} synced", block_data.block().index); manager.last_block_synced = block_data.block().index; Some((Ok(block_data), (stream, manager))) @@ -203,13 +206,13 @@ fn start_sink_thread( logger: Logger, ) -> (Sender, Receiver) { // Initialize sending and receiving channels - let (send_out, rcv_out) = channel(10000); - let (send_in, mut rcv_in) = channel::(10000); + let (mut send_out, rcv_out) = mpsc::channel(5000); + let (send_in, mut rcv_in) = mpsc::channel::(5000); // Launch ledger sink thread std::thread::spawn(move || { log::debug!(logger, "starting ledger sink thread"); - while let Some(block_data) = rcv_in.blocking_recv() { + while let Some(block_data) = futures::executor::block_on(async { rcv_in.next().await }) { let signature = block_data.signature().as_ref().cloned(); // If there's an error syncing the blocks, end thread diff --git a/ledger/streaming/publisher/src/grpc.rs b/ledger/streaming/publisher/src/grpc.rs index 3908075707..587bb629b3 100644 --- a/ledger/streaming/publisher/src/grpc.rs +++ b/ledger/streaming/publisher/src/grpc.rs @@ -6,7 +6,7 @@ use flo_stream::{ExpiringPublisher, MessagePublisher, Subscriber}; use futures::{lock::Mutex, FutureExt, Stream, StreamExt, TryStreamExt}; use grpcio::ServerStreamingSink; -use mc_common::logger::Logger; +use mc_common::logger::{log, Logger}; use mc_ledger_streaming_api::{ streaming_blocks::SubscribeRequest, streaming_blocks_grpc::{create_ledger_updates, LedgerUpdates}, @@ -29,7 +29,7 @@ impl GrpcServerSink { pub fn new(logger: Logger) -> Self { Self { // Buffer a few responses. - publisher: Arc::new(Mutex::new(ExpiringPublisher::new(3))), + publisher: Arc::new(Mutex::new(ExpiringPublisher::new(10))), logger, } } @@ -43,6 +43,7 @@ impl GrpcServerSink { stream: impl Stream> + 'a, ) -> impl Stream> + 'a { let publisher = self.publisher.clone(); + log::debug!(self.logger, "Publishing archive blocks to gRPC stream"); stream.and_then(move |data| { let publisher = publisher.clone(); async move { @@ -55,12 +56,13 @@ impl GrpcServerSink { /// Create a [LedgerUpdates] handler. pub fn create_handler(&self) -> impl LedgerUpdates + Clone + Send + Sync + 'static { - let mut publisher = futures::executor::block_on(self.publisher.lock()); + let mut publisher = futures::executor::block_on(async { self.publisher.lock().await }); PublishHelper::new(publisher.subscribe()) } /// Create a [grpcio::Service] with a [LedgerUpdates] handler. pub fn create_service(&self) -> grpcio::Service { + log::debug!(self.logger, "creating gRPC service for block sink"); create_ledger_updates(self.create_handler()) } From 3a789c9a7d6063482270ff3c9a596d07bd6fccd7 Mon Sep 17 00:00:00 2001 From: Remoun Metyas Date: Mon, 2 May 2022 21:17:55 -0700 Subject: [PATCH 10/11] mock_stream_with_custom_block_contents -> MockStream::with_custom_block_contents --- ledger/streaming/api/src/test_utils/stream.rs | 64 +++++++++---------- ledger/streaming/client/benches/bench.rs | 10 +-- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/ledger/streaming/api/src/test_utils/stream.rs b/ledger/streaming/api/src/test_utils/stream.rs index 9c925d5e3d..4cbdd35852 100644 --- a/ledger/streaming/api/src/test_utils/stream.rs +++ b/ledger/streaming/api/src/test_utils/stream.rs @@ -24,6 +24,38 @@ impl MockStream { let items: Vec> = src.into_iter().map(Ok).collect(); Self::new(items) } + + /// Create a mock stream with blocks that resemble those seen in productoin + /// + /// * `outputs_per_recipient_per_block` - transaction outputs per account + /// per block + /// * `num_accounts` - number of accounts in the simulated blocks + /// * `num_blocks` - number of simulated blocks to create + /// * `key_images_per_block` - number of simulated key images per block + /// + /// Returns a MockStream that when driven will produce blocks with the + /// contents specified in the above parameters + pub fn with_custom_block_contents( + outputs_per_recipient_per_block: usize, + num_accounts: usize, + num_blocks: usize, + key_images_per_block: usize, + max_token_id: u64, + ) -> MockStream { + let blocks = get_custom_test_ledger_blocks( + outputs_per_recipient_per_block, + num_accounts, + num_blocks, + key_images_per_block, + max_token_id, + ); + + let block_data: Vec = blocks + .into_iter() + .map(move |(block, block_contents)| BlockData::new(block, block_contents, None)) + .collect(); + MockStream::from_blocks(block_data) + } } impl BlockStream for MockStream { @@ -35,35 +67,3 @@ impl BlockStream for MockStream { Ok(futures::stream::iter(items)) } } - -/// Create a mock stream with blocks that resemble those seen in productoin -/// -/// * `outputs_per_recipient_per_block` - transaction outputs per account per -/// block -/// * `num_accounts` - number of accounts in the simulated blocks -/// * `num_blocks` - number of simulated blocks to create -/// * `key_images_per_block` - number of simulated key images per block -/// -/// Returns a MockStream that when driven will produce blocks with the contents -/// specified in the above parameters -pub fn mock_stream_with_custom_block_contents( - outputs_per_recipient_per_block: usize, - num_accounts: usize, - num_blocks: usize, - key_images_per_block: usize, - max_token_id: u64, -) -> MockStream { - let blocks = get_custom_test_ledger_blocks( - outputs_per_recipient_per_block, - num_accounts, - num_blocks, - key_images_per_block, - max_token_id, - ); - - let block_data: Vec = blocks - .into_iter() - .map(move |(block, block_contents)| BlockData::new(block, block_contents, None)) - .collect(); - MockStream::from_blocks(block_data) -} diff --git a/ledger/streaming/client/benches/bench.rs b/ledger/streaming/client/benches/bench.rs index 9e35a437b9..20ed6d259a 100644 --- a/ledger/streaming/client/benches/bench.rs +++ b/ledger/streaming/client/benches/bench.rs @@ -8,7 +8,7 @@ use mc_common::logger::log; use mc_consensus_scp::test_utils::test_node_id; use mc_ledger_db::test_utils::get_mock_ledger; use mc_ledger_streaming_api::{ - test_utils::{make_quorum_set, stream}, + test_utils::{make_quorum_set, MockStream}, ArchiveBlock, BlockStream, Result as StreamResult, }; use mc_ledger_streaming_client::{ @@ -26,7 +26,7 @@ fn bench_ledger_sink_for_1000_blocks(b: &mut Bencher) { // Create simulated upstream with 1000 realistic blocks and fake ledger to sink // into - let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); + let upstream_producer = MockStream::with_custom_block_contents(1, 3, 1000, 2, 0); let ledger = get_mock_ledger(0); // Initialize ledger sink stream @@ -50,7 +50,7 @@ fn bench_scp_validation_for_1000_blocks(b: &mut Bencher) { // Create 9 simulated upstreams with 1000 realistic blocks and simulated quorum // set let quorum_set = make_quorum_set(); - let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); + let upstream_producer = MockStream::with_custom_block_contents(1, 3, 1000, 2, 0); let mut upstreams = HashMap::new(); for i in 0..9 { upstreams.insert(test_node_id(i), upstream_producer.clone()); @@ -72,7 +72,7 @@ fn bench_validation_for_1000_blocks(b: &mut Bencher) { let logger = mc_common::logger::create_test_logger("benchmark:validate_1000_blocks".into()); // Initialize upstream producer and fake ledger - let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); + let upstream_producer = MockStream::with_custom_block_contents(1, 3, 1000, 2, 0); let ledger = Some(get_mock_ledger(0)); // Initialize block validation component @@ -94,7 +94,7 @@ fn bench_integrated_components(b: &mut Bencher) { // Create 9 simulated upstreams with 1000 realistic blocks, simulated quorum // set, and fake ledger to sink into let quorum_set = make_quorum_set(); - let upstream_producer = stream::mock_stream_with_custom_block_contents(1, 3, 1000, 2, 0); + let upstream_producer = MockStream::with_custom_block_contents(1, 3, 1000, 2, 0); let mut upstreams = HashMap::new(); let ledger = get_mock_ledger(0); for i in 0..9 { From 3c34674d38839c8a5a4b35a8088f51698a7ca15c Mon Sep 17 00:00:00 2001 From: Remoun Metyas Date: Mon, 2 May 2022 21:18:25 -0700 Subject: [PATCH 11/11] Add a couple of asserts --- ledger/streaming/client/benches/bench.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ledger/streaming/client/benches/bench.rs b/ledger/streaming/client/benches/bench.rs index 20ed6d259a..fbe4666f56 100644 --- a/ledger/streaming/client/benches/bench.rs +++ b/ledger/streaming/client/benches/bench.rs @@ -181,9 +181,7 @@ fn bench_simulated_pipeline(b: &mut Bencher) { // Sink simulated blocks into server broadcast executor.spawn(sink.consume_protos(archive_block_stream.clone()).for_each( |result| async move { - if result.is_err() { - println!("Error is {:?}", result); - } + result.expect("unexpected error"); }, )); @@ -194,9 +192,7 @@ fn bench_simulated_pipeline(b: &mut Bencher) { if count == 999 { // Only unwrap block when necessary to avoid extra work in the benchmark let index = block_data.unwrap().block().index; - if index == 999 { - break; - } + assert_eq!(index, 999); } count += 1; }