From 19fd7b562f424f43ded4c6f83d10b462cb3b6813 Mon Sep 17 00:00:00 2001 From: Nicolas Di Prima Date: Wed, 9 Jan 2019 10:24:04 +0100 Subject: [PATCH 01/14] closes #379 --- cardano/src/block/block.rs | 6 +++--- chain-core/src/property.rs | 2 +- chain-impl-mockchain/src/block.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cardano/src/block/block.rs b/cardano/src/block/block.rs index 9e154fafa..fee02977e 100644 --- a/cardano/src/block/block.rs +++ b/cardano/src/block/block.rs @@ -210,10 +210,10 @@ impl core::property::Block for Block { self.get_header().compute_hash() } - fn parent_id(&self) -> &Self::Id { + fn parent_id(&self) -> Self::Id { match self { - Block::MainBlock(ref block) => &block.header.previous_header, - Block::BoundaryBlock(ref block) => &block.header.previous_header, + Block::MainBlock(ref block) => block.header.previous_header.clone(), + Block::BoundaryBlock(ref block) => block.header.previous_header.clone(), } } fn date(&self) -> Self::Date { diff --git a/chain-core/src/property.rs b/chain-core/src/property.rs index 8e2d73052..ce5639447 100644 --- a/chain-core/src/property.rs +++ b/chain-core/src/property.rs @@ -64,7 +64,7 @@ pub trait Block: Serializable { /// get the parent block identifier (the previous block in the /// blockchain). - fn parent_id(&self) -> &Self::Id; + fn parent_id(&self) -> Self::Id; /// get the block date of the block fn date(&self) -> Self::Date; diff --git a/chain-impl-mockchain/src/block.rs b/chain-impl-mockchain/src/block.rs index 1a2e0a2b8..d2882b7ac 100644 --- a/chain-impl-mockchain/src/block.rs +++ b/chain-impl-mockchain/src/block.rs @@ -32,8 +32,8 @@ impl property::Block for Block { } /// Id of the parent block. - fn parent_id(&self) -> &Self::Id { - &self.parent_hash + fn parent_id(&self) -> Self::Id { + self.parent_hash } /// Date of the block. From e09f2002c992f97594972bfc5b0ecb33b846809f Mon Sep 17 00:00:00 2001 From: Amias Channer <38949453+amias-iohk@users.noreply.github.com> Date: Wed, 9 Jan 2019 11:17:58 +0000 Subject: [PATCH 02/14] artifacting of test results updates to test runs to explictly archive test results --- .circleci/config.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 65162d3fe..7e30fa72c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -80,6 +80,9 @@ jobs: command: cargo junit --name ~/test-results/${CIRCLE_JOB}/results.xml - store_test_results: path: ~/test-results + - store_artifacts: + path: ~/test-results + destination: test_results test_nightly: docker: - image: rustlang/rust:nightly @@ -102,6 +105,9 @@ jobs: command: cargo junit --name ~/test-results/${CIRCLE_JOB}/results.xml - store_test_results: path: ~/test-results + - store_artifacts: + path: ~/test-results + destination: test_results coverage: docker: - image: ragnaroek/kcov:v33 From 39a0f55f68b5eac8472a2f1d2953139bba50c90e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 9 Jan 2019 12:56:48 +0100 Subject: [PATCH 03/14] Remove Hermes module Hermes is no longer part of rust-cardano. --- module.nix | 65 ------------------------------------------------------ 1 file changed, 65 deletions(-) delete mode 100644 module.nix diff --git a/module.nix b/module.nix deleted file mode 100644 index b62f5ecac..000000000 --- a/module.nix +++ /dev/null @@ -1,65 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - build = import ./. { /* inherit pkgs; */ }; - - cfg = config.services.hermes; - -in - -{ - - options = { - - services.hermes = { - - port = mkOption { - type = types.int; - default = 3080; - description = "The TCP port on which Hermes listens for HTTP connections."; - }; - - networks = mkOption { - type = types.listOf types.str; - default = [ "mainnet" ]; - example = [ "testnet" ]; - description = "The networks to support."; - }; - - }; - - }; - - config = { - - environment.systemPackages = [ build ]; - - users.users.hermes = { - description = "Hermes server user"; - isSystemUser = true; - group = "hermes"; - }; - - users.groups.hermes = {}; - - system.activationScripts.hermes = stringAfter [ "users" ] - '' - install -d -m 0755 -o hermes -g hermes /var/lib/hermes - ''; - - systemd.services.hermes = { - description = "Hermes Web Service"; - after = [ "syslog.target" "network.target" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - User = "hermes"; - ExecStart = "${build}/bin/hermes start --port ${toString cfg.port} --networks-dir /var/lib/hermes/networks --template ${toString cfg.networks}"; - }; - }; - - }; - -} From 39fc5cc052c24c583d8785664bbd282c45fa1273 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Wed, 9 Jan 2019 00:39:52 +0200 Subject: [PATCH 04/14] chain-core: add property traits for blockchain data entities Add empty property traits that are used to denote types representing specific entities of the blockchain data model: - BlockId implemented by block identifier type, - BlockDate implemented by block date type, - TransactionId implemented by transaction identifier type, - Header implemented by block header type. These continue the system of existing entity identification traits such as Block, Transaction, and Ledger, except that the new traits do not (yet) feature any methods. --- cardano/src/block/date.rs | 5 +++++ cardano/src/block/types.rs | 6 +++++- cardano/src/lib.rs | 3 +++ cardano/src/tx.rs | 24 +++++++++++++----------- chain-core/src/property.rs | 21 ++++++++++++++++++--- chain-impl-mockchain/src/block.rs | 6 ++++-- chain-impl-mockchain/src/key.rs | 3 +++ chain-impl-mockchain/src/transaction.rs | 2 ++ 8 files changed, 53 insertions(+), 17 deletions(-) diff --git a/cardano/src/block/date.rs b/cardano/src/block/date.rs index 7d878e375..1fa518fe2 100644 --- a/cardano/src/block/date.rs +++ b/cardano/src/block/date.rs @@ -1,5 +1,7 @@ use super::types::{EpochId, EpochSlotId, SlotId}; +use chain_core::property; + use std::{ cmp::{Ord, Ordering}, error::Error, @@ -16,6 +18,9 @@ pub enum BlockDate { Boundary(EpochId), Normal(EpochSlotId), } + +impl property::BlockDate for BlockDate {} + impl ::std::ops::Sub for BlockDate { type Output = usize; fn sub(self, rhs: Self) -> Self::Output { diff --git a/cardano/src/block/types.rs b/cardano/src/block/types.rs index 07a8f825d..de6c4cf73 100644 --- a/cardano/src/block/types.rs +++ b/cardano/src/block/types.rs @@ -1,5 +1,6 @@ use super::normal::SscPayload; use cbor_event::{self, de::Deserializer, se::Serializer}; +use chain_core::property; use hash::Blake2b256; use util::try_from_slice::TryFromSlice; @@ -35,7 +36,7 @@ impl fmt::Display for Version { } } -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[cfg_attr(feature = "generic-serialization", derive(Serialize, Deserialize))] pub struct HeaderHash(Blake2b256); impl HeaderHash { @@ -47,6 +48,9 @@ impl HeaderHash { self.0.as_hash_bytes() } } + +impl property::BlockId for HeaderHash {} + impl fmt::Display for HeaderHash { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) diff --git a/cardano/src/lib.rs b/cardano/src/lib.rs index 373c43561..5fcd3bdab 100644 --- a/cardano/src/lib.rs +++ b/cardano/src/lib.rs @@ -15,11 +15,14 @@ //! #![cfg_attr(feature = "with-bench", feature(test))] +extern crate chain_core; + #[cfg(feature = "generic-serialization")] #[macro_use] extern crate serde_derive; #[cfg(feature = "generic-serialization")] extern crate serde; + #[cfg(test)] extern crate serde_json; #[cfg(test)] diff --git a/cardano/src/tx.rs b/cardano/src/tx.rs index d1d0f549c..f8f2aa7e0 100644 --- a/cardano/src/tx.rs +++ b/cardano/src/tx.rs @@ -11,24 +11,26 @@ use std::{ io::{BufRead, Write}, }; -use hash::Blake2b256; +use crate::{ + address::{AddrType, Attributes, ExtendedAddr, SpendingData}, + coin::{self, Coin}, + config::ProtocolMagic, + hash::Blake2b256, + hdwallet::{Signature, XPrv, XPub, SIGNATURE_SIZE, XPUB_SIZE}, + merkle, redeem, + tags::SigningTag, +}; use cbor_event::{self, de::Deserializer, se::Serializer}; -use config::ProtocolMagic; -use merkle; -use redeem; -use tags::SigningTag; - -use address::{AddrType, Attributes, ExtendedAddr, SpendingData}; -use coin::{self, Coin}; -use hdwallet::{Signature, XPrv, XPub, SIGNATURE_SIZE, XPUB_SIZE}; - -use core; +use chain_core::property; // Transaction IDs are either a hash of the CBOR serialisation of a // given Tx, or a hash of a redeem address. pub type TxId = Blake2b256; +// FIXME: This is dodgy because TxId is not currently a dedicated type. +impl property::TransactionId for TxId {} + pub fn redeem_pubkey_to_txid( pubkey: &redeem::PublicKey, protocol_magic: ProtocolMagic, diff --git a/chain-core/src/property.rs b/chain-core/src/property.rs index ce5639447..edff6af83 100644 --- a/chain-core/src/property.rs +++ b/chain-core/src/property.rs @@ -35,6 +35,21 @@ //! is selected to write a block in the chain. //! +use std::hash::Hash; + +/// Trait identifying the block identifier type. +pub trait BlockId: Eq + Hash {} + +/// Trait identifying the block date type. +pub trait BlockDate: Eq + Ord {} + +/// Trait identifying the transaction identifier type. +pub trait TransactionId: Eq + Hash {} + +/// Trait identifying the block header type. +/// TODO: provide header in the data model. +pub trait Header {} + /// Block property /// /// a block is part of a chain of block called Blockchain. @@ -51,13 +66,13 @@ pub trait Block: Serializable { /// /// In bitcoin this block is a SHA2 256bits. For Cardano's /// blockchain it is Blake2b 256bits. - type Id; + type Id: BlockId; /// the block date (also known as a block number) represents the /// absolute position of the block in the chain. This can be used /// for random access (if the storage algorithm allows it) or for /// identifying the position of a block in a given epoch or era. - type Date; + type Date: BlockDate; /// return the Block's identifier. fn id(&self) -> Self::Id; @@ -81,7 +96,7 @@ pub trait Transaction: Serializable { type Output; /// a unique identifier of the transaction. For 2 different transactions /// we must have 2 different `Id` values. - type Id; + type Id: TransactionId; fn inputs<'a>(&'a self) -> std::slice::Iter<'a, Self::Input>; fn outputs<'a>(&'a self) -> std::slice::Iter<'a, Self::Output>; diff --git a/chain-impl-mockchain/src/block.rs b/chain-impl-mockchain/src/block.rs index d2882b7ac..0c02bfa64 100644 --- a/chain-impl-mockchain/src/block.rs +++ b/chain-impl-mockchain/src/block.rs @@ -1,6 +1,6 @@ //! Representation of the block in the mockchain. -use crate::key::*; -use crate::transaction::*; +use crate::key::Hash; +use crate::transaction::SignedTransaction; use chain_core::property; /// Non unique identifier of the transaction position in the @@ -9,6 +9,8 @@ use chain_core::property; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] pub struct SlotId(u32, u32); +impl property::BlockDate for SlotId {} + /// `Block` is an element of the blockchain it contains multiple /// transaction and a reference to the parent block. Alongside /// with the position of that block in the chain. diff --git a/chain-impl-mockchain/src/key.rs b/chain-impl-mockchain/src/key.rs index 8f8599dd3..54fd2bebb 100644 --- a/chain-impl-mockchain/src/key.rs +++ b/chain-impl-mockchain/src/key.rs @@ -3,6 +3,7 @@ //! use cardano::hash; use cardano::hdwallet as crypto; +use chain_core::property; // TODO: this public key contains the chain code in it too // during serialisation this might not be needed @@ -53,6 +54,8 @@ impl AsRef<[u8]> for Hash { } } +impl property::BlockId for Hash {} + /// Cryptographic signature. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct Signature(pub crypto::Signature<()>); diff --git a/chain-impl-mockchain/src/transaction.rs b/chain-impl-mockchain/src/transaction.rs index a8e8108bc..73c66354f 100644 --- a/chain-impl-mockchain/src/transaction.rs +++ b/chain-impl-mockchain/src/transaction.rs @@ -73,6 +73,8 @@ impl AsRef<[u8]> for TransactionId { } } +impl property::TransactionId for TransactionId {} + /// Transaction, transaction maps old unspent tokens into the /// set of the new addresses. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] From b109296d457c41cb3a71ace7d02f4265c9760955 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Wed, 9 Jan 2019 15:28:00 +0200 Subject: [PATCH 05/14] chain-core: provide block header Connecting property trait Header to the data model, and providing implementations. --- cardano/src/block/block.rs | 8 ++++++++ chain-core/src/property.rs | 13 ++++++++++++- chain-impl-mockchain/src/block.rs | 9 +++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/cardano/src/block/block.rs b/cardano/src/block/block.rs index fee02977e..ff851abde 100644 --- a/cardano/src/block/block.rs +++ b/cardano/src/block/block.rs @@ -80,6 +80,8 @@ pub enum BlockHeader { MainBlockHeader(normal::BlockHeader), } +impl core::property::Header for BlockHeader {} + /// BlockHeaders is a vector of block headers, as produced by /// MsgBlocks. #[derive(Debug, Clone)] @@ -205,6 +207,7 @@ impl fmt::Display for Block { impl core::property::Block for Block { type Id = HeaderHash; type Date = BlockDate; + type Header = BlockHeader; fn id(&self) -> Self::Id { self.get_header().compute_hash() @@ -222,7 +225,12 @@ impl core::property::Block for Block { Block::BoundaryBlock(ref block) => block.header.consensus.epoch.into(), } } + + fn header(&self) -> BlockHeader { + self.get_header() + } } + impl core::property::HasTransaction for Block { fn transactions<'a>(&'a self) -> std::slice::Iter<'a, TxAux> { match self { diff --git a/chain-core/src/property.rs b/chain-core/src/property.rs index edff6af83..766ab708e 100644 --- a/chain-core/src/property.rs +++ b/chain-core/src/property.rs @@ -47,9 +47,10 @@ pub trait BlockDate: Eq + Ord {} pub trait TransactionId: Eq + Hash {} /// Trait identifying the block header type. -/// TODO: provide header in the data model. pub trait Header {} +impl Header for () {} + /// Block property /// /// a block is part of a chain of block called Blockchain. @@ -74,6 +75,13 @@ pub trait Block: Serializable { /// identifying the position of a block in a given epoch or era. type Date: BlockDate; + /// The block header. If provided by the blockchain, the header + /// can be used to transmit block's metadata via a network protocol + /// or in other uses where the full content of the block is not desirable. + /// An implementation that does not feature headers can use the unit + /// type `()`. + type Header: Header; + /// return the Block's identifier. fn id(&self) -> Self::Id; @@ -83,6 +91,9 @@ pub trait Block: Serializable { /// get the block date of the block fn date(&self) -> Self::Date; + + /// Gets the block's header. + fn header(&self) -> Self::Header; } /// define a transaction within the blockchain. This transaction can be used diff --git a/chain-impl-mockchain/src/block.rs b/chain-impl-mockchain/src/block.rs index 0c02bfa64..d1e2ab2c9 100644 --- a/chain-impl-mockchain/src/block.rs +++ b/chain-impl-mockchain/src/block.rs @@ -1,6 +1,6 @@ //! Representation of the block in the mockchain. -use crate::key::Hash; -use crate::transaction::SignedTransaction; +use crate::key::*; +use crate::transaction::*; use chain_core::property; /// Non unique identifier of the transaction position in the @@ -25,6 +25,7 @@ pub struct Block { impl property::Block for Block { type Id = Hash; type Date = SlotId; + type Header = (); /// Identifier of the block, currently the hash of the /// serialized transaction. @@ -42,6 +43,10 @@ impl property::Block for Block { fn date(&self) -> Self::Date { self.slot_id } + + fn header(&self) -> Self::Header { + () + } } impl chain_core::property::Serializable for Block { From 9c6980e787773ffe399fb16b044db33876813dc9 Mon Sep 17 00:00:00 2001 From: Amias Channer <38949453+amias-iohk@users.noreply.github.com> Date: Wed, 9 Jan 2019 13:28:42 +0000 Subject: [PATCH 06/14] added test result retention to test --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7e30fa72c..35f059aac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -54,6 +54,9 @@ jobs: done - store_test_results: path: ~/test-results + - store_artifacts: + path: ~/test-results + destination: test_results - persist_to_workspace: root: "." paths: From 83cf43c2aa230c59f94efb0c3b69a2d3fad4bebf Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Mon, 7 Jan 2019 10:37:14 +0200 Subject: [PATCH 07/14] Initial code for network-grpc server Moved from the internal project and adapted to chain-core definitions. The abstract interface is defined in network_core, to be moved to its own crate. --- Cargo.toml | 1 + network-grpc/Cargo.toml | 30 +++++ network-grpc/build.rs | 15 +++ network-grpc/proto/node.proto | 80 ++++++++++++ network-grpc/proto/types.proto | 28 +++++ network-grpc/src/lib.rs | 27 +++++ network-grpc/src/network_core.rs | 44 +++++++ network-grpc/src/server.rs | 201 +++++++++++++++++++++++++++++++ 8 files changed, 426 insertions(+) create mode 100644 network-grpc/Cargo.toml create mode 100644 network-grpc/build.rs create mode 100644 network-grpc/proto/node.proto create mode 100644 network-grpc/proto/types.proto create mode 100644 network-grpc/src/lib.rs create mode 100644 network-grpc/src/network_core.rs create mode 100644 network-grpc/src/server.rs diff --git a/Cargo.toml b/Cargo.toml index 4fc521e4e..180751008 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "chain-core", "chain-impl-mockchain", "cardano", + "network-grpc", "protocol", "protocol-tokio", "storage-units", diff --git a/network-grpc/Cargo.toml b/network-grpc/Cargo.toml new file mode 100644 index 000000000..62d41e882 --- /dev/null +++ b/network-grpc/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "network-grpc" +version = "0.1.0-dev" +authors = [ + "Nicolas Di Prima ", + "Vincent Hanquez ", + "Mikhail Zabaluev ", + "Eelco Dolstra ", + "Alexander Vershilov ", +] +edition = "2018" + +[dependencies] +chain-core = { path = "../chain-core" } +bytes = "0.4" +futures = "0.1" +prost = "0.4" +prost-derive = "0.4" +tokio = "0.1" +tokio-connect = { git = "https://github.com/carllerche/tokio-connect" } +tower-h2 = { git = "https://github.com/tower-rs/tower-h2" } +tower-util = { git = "https://github.com/tower-rs/tower" } + +[dependencies.tower-grpc] +git = "https://github.com/tower-rs/tower-grpc" +rev = "7ffa9d3b610d1c84559a811d08a25bd1670babed" + +[build-dependencies.tower-grpc-build] +git = "https://github.com/tower-rs/tower-grpc" +rev = "7ffa9d3b610d1c84559a811d08a25bd1670babed" diff --git a/network-grpc/build.rs b/network-grpc/build.rs new file mode 100644 index 000000000..90541aa7e --- /dev/null +++ b/network-grpc/build.rs @@ -0,0 +1,15 @@ +extern crate tower_grpc_build; + +use std::io::{stderr, Write}; +use std::process; + +fn main() { + tower_grpc_build::Config::new() + .enable_client(true) + .enable_server(true) + .build(&["proto/node.proto"], &["proto/"]) + .unwrap_or_else(|e| { + writeln!(stderr(), "{}", e).unwrap(); + process::exit(1) + }); +} diff --git a/network-grpc/proto/node.proto b/network-grpc/proto/node.proto new file mode 100644 index 000000000..8880d64f7 --- /dev/null +++ b/network-grpc/proto/node.proto @@ -0,0 +1,80 @@ +syntax = "proto3"; + +package iohk.jormungandr; + +import "types.proto"; + +message TipRequest {} + +message TipResponse { + cardano.BlockDate blockdate = 1; + cardano.HeaderHash hash = 2; +} + +// Parameters for GetBlocks and GetHeaders +message GetBlocksRequest { + // The hash of the tip to trace the chain from. + cardano.HeaderHash tip = 1; + // The offset back in the chain to start block or header retrieval from, + // going backwards in the chain. + uint64 offset = 2; + // Maximum number of blocks or headers to retrieve. + uint64 size = 3; +} + +message ProposeTransactionsRequest { + repeated bytes ids = 1; +} + +message ProposeTransactionsResponse { + // Status of each proposed transaction. + enum Status { + // The transaction ID is new to the recipient node. + NEW = 0; + // The proposed transaction has already been recorded by the node. + ALREADY_EXISTS = 1; + } + + message Item { + bytes id = 1; + Status status = 2; + } + + repeated Item items = 1; +} + +message RecordTransactionRequest { + cardano.Transaction tx = 1; +} + +message RecordTransactionResponse { + // The result of processing a transaction. + enum Result { + // The transaction has been accepted. + ACCEPTED = 0; + // Unknown/internal error. + UNKNOWN_ERROR = 1; + // The signature is invalid. + INVALID_SIGNATURE = 2; + // The proposed transaction would result in a double spend. + DOUBLE_SPEND = 3; + // The proposed transaction has already been recorded by the node. + ALREADY_EXISTS = 4; + } + + Result result = 1; + bytes id = 2; +} + +service Node { + rpc Tip (TipRequest) returns (TipResponse); + rpc GetBlocks (GetBlocksRequest) returns (stream cardano.Block) { + option idempotency_level = NO_SIDE_EFFECTS; + } + rpc GetHeaders (GetBlocksRequest) returns (stream cardano.Header) { + option idempotency_level = NO_SIDE_EFFECTS; + } + rpc StreamBlocksToTip (cardano.HeaderHashes) returns (stream cardano.Block); + rpc ProposeTransactions (ProposeTransactionsRequest) returns (ProposeTransactionsResponse); + rpc RecordTransaction (RecordTransactionRequest) returns (RecordTransactionResponse); +} diff --git a/network-grpc/proto/types.proto b/network-grpc/proto/types.proto new file mode 100644 index 000000000..85b573bf0 --- /dev/null +++ b/network-grpc/proto/types.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package cardano; + +message BlockDate { + uint64 epoch = 1; + uint32 slot = 2; +} + +message HeaderHash { + bytes hash = 1; +} + +message HeaderHashes { + repeated bytes hashes = 1; +} + +message Block { + bytes content = 1; +} + +message Header { + bytes content = 1; +} + +message Transaction { + bytes content = 1; +} \ No newline at end of file diff --git a/network-grpc/src/lib.rs b/network-grpc/src/lib.rs new file mode 100644 index 000000000..4968e624b --- /dev/null +++ b/network-grpc/src/lib.rs @@ -0,0 +1,27 @@ +extern crate chain_core; +extern crate prost; +#[macro_use] +extern crate prost_derive; +extern crate tokio_connect; +extern crate tower_grpc; +extern crate tower_h2; +extern crate tower_util; + +// Included generated protobuf/gRPC code, +// namespaced into submodules corresponding to the .proto package names + +mod cardano { + include!(concat!(env!("OUT_DIR"), "/cardano.rs")); +} + +#[allow(dead_code)] +mod iohk { + pub mod jormungandr { + include!(concat!(env!("OUT_DIR"), "/iohk.jormungandr.rs")); + } +} + +pub mod server; + +// TODO: replace with network_core crate +mod network_core; diff --git a/network-grpc/src/network_core.rs b/network-grpc/src/network_core.rs new file mode 100644 index 000000000..44cd0acb6 --- /dev/null +++ b/network-grpc/src/network_core.rs @@ -0,0 +1,44 @@ +use chain_core::property; + +use futures::prelude::*; + +use std::collections::HashMap; + +// NOTE: protobuf-derived definitions used in would-be abstract core API +use super::iohk::jormungandr as gen; + +pub struct ProposeTransactionsResponse { + // TODO: define fully + _items: HashMap, +} + +pub struct RecordTransactionResponse { + // TODO: define + _id: Id, + _result: gen::record_transaction_response::Result, +} + +pub trait Node { + type Error; + type BlockId: property::Serializable; + type BlockDate; + type Block: property::Block; + type Header; + + type TipFuture: Future; + type BlocksStream: Stream; + type BlocksFuture: Future; + type HeadersStream: Stream; + type HeadersFuture: Future; + type ProposeTransactionsFuture: Future< + Item = ProposeTransactionsResponse, + Error = Self::Error, + >; + type RecordTransactionFuture: Future< + Item = RecordTransactionResponse, + Error = Self::Error, + >; + + fn tip(&mut self) -> Self::TipFuture; + fn stream_blocks_to_tip(&mut self, from: &[Self::BlockId]) -> Self::BlocksFuture; +} diff --git a/network-grpc/src/server.rs b/network-grpc/src/server.rs new file mode 100644 index 000000000..3d0c81be8 --- /dev/null +++ b/network-grpc/src/server.rs @@ -0,0 +1,201 @@ +use chain_core::property::Serializable; + +use futures::prelude::*; +use futures::try_ready; +use tower_grpc::{self, Code, Request, Status}; + +use std::marker::PhantomData; + +use super::cardano as cardano_proto; +use super::iohk::jormungandr as gen; +use super::network_core; + +enum ResponseState { + Future(F), + Err(Status), +} + +pub struct FutureResponse { + state: ResponseState, + _phantom: PhantomData, +} + +impl FutureResponse +where + F: Future, + T: From<::Item>, + tower_grpc::Error: From<::Error>, +{ + fn new(future: F) -> Self { + FutureResponse { + state: ResponseState::Future(future), + _phantom: PhantomData, + } + } +} + +impl FutureResponse { + fn error(status: Status) -> Self { + FutureResponse { + state: ResponseState::Err(status), + _phantom: PhantomData, + } + } +} + +impl Future for FutureResponse +where + F: Future, + T: From<::Item>, + tower_grpc::Error: From<::Error>, +{ + type Item = tower_grpc::Response; + type Error = tower_grpc::Error; + + fn poll(&mut self) -> Poll { + match self.state { + ResponseState::Future(ref mut f) => { + let item = try_ready!(f.poll()); + let response = tower_grpc::Response::new(item.into()); + Ok(Async::Ready(response)) + } + ResponseState::Err(ref status) => Err(tower_grpc::Error::Grpc(status.clone())), + } + } +} + +pub struct ResponseStream { + inner: S, + _phantom: PhantomData, +} + +impl ResponseStream +where + S: Stream, + T: From<::Item>, + tower_grpc::Error: From<::Error>, +{ + pub fn new(stream: S) -> Self { + ResponseStream { + inner: stream, + _phantom: PhantomData, + } + } +} + +impl From for ResponseStream +where + S: Stream, + T: From<::Item>, + tower_grpc::Error: From<::Error>, +{ + fn from(stream: S) -> Self { + ResponseStream::new(stream) + } +} + +impl Stream for ResponseStream +where + S: Stream, + T: From<::Item>, + tower_grpc::Error: From<::Error>, +{ + type Item = T; + type Error = tower_grpc::Error; + + fn poll(&mut self) -> Poll, tower_grpc::Error> { + let maybe_item = try_ready!(self.inner.poll()); + Ok(Async::Ready(maybe_item.map(|item| item.into()))) + } +} + +#[derive(Clone)] +pub struct GrpcServer { + node: T, +} + +fn deserialize_hashes( + pb: &cardano_proto::HeaderHashes, +) -> Result, ::Error> { + pb.hashes + .iter() + .map(|v| Serializable::deserialize(&v[..])) + .collect() +} + +impl gen::server::Node for GrpcServer +where + T: network_core::Node + Clone, + tower_grpc::Error: From<::Error>, + cardano_proto::Block: From<::Block>, + cardano_proto::Header: From<::Header>, + gen::TipResponse: From<::BlockId>, + gen::ProposeTransactionsResponse: + From::BlockId>>, + gen::RecordTransactionResponse: + From::BlockId>>, +{ + type TipFuture = FutureResponse::TipFuture>; + type GetBlocksStream = + ResponseStream::BlocksStream>; + type GetBlocksFuture = + FutureResponse::BlocksFuture>; + type GetHeadersStream = + ResponseStream::HeadersStream>; + type GetHeadersFuture = + FutureResponse::HeadersFuture>; + type StreamBlocksToTipStream = + ResponseStream::BlocksStream>; + type StreamBlocksToTipFuture = + FutureResponse::BlocksFuture>; + type ProposeTransactionsFuture = FutureResponse< + gen::ProposeTransactionsResponse, + ::ProposeTransactionsFuture, + >; + type RecordTransactionFuture = FutureResponse< + gen::RecordTransactionResponse, + ::RecordTransactionFuture, + >; + + fn tip(&mut self, _request: Request) -> Self::TipFuture { + FutureResponse::new(self.node.tip()) + } + + fn get_blocks(&mut self, _request: Request) -> Self::GetBlocksFuture { + unimplemented!() + } + + fn get_headers(&mut self, _request: Request) -> Self::GetHeadersFuture { + unimplemented!() + } + + fn stream_blocks_to_tip( + &mut self, + from: Request, + ) -> Self::StreamBlocksToTipFuture { + let hashes = match deserialize_hashes(from.get_ref()) { + Ok(hashes) => hashes, + Err(e) => { + // FIXME: log the error + // (preferably with tower facilities outside of this implementation) + let status = Status::with_code_and_message(Code::InvalidArgument, format!("{}", e)); + return FutureResponse::error(status); + } + }; + FutureResponse::new(self.node.stream_blocks_to_tip(&hashes)) + } + + fn propose_transactions( + &mut self, + _request: Request, + ) -> Self::ProposeTransactionsFuture { + unimplemented!() + } + + fn record_transaction( + &mut self, + _request: Request, + ) -> Self::RecordTransactionFuture { + unimplemented!() + } +} From 055372211a39257a745218960abc1f88583e08b4 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Tue, 8 Jan 2019 08:29:40 +0200 Subject: [PATCH 08/14] Split Serializable into directional traits serialization and deserialization impose different bounds on the type and might have different specific error types. Trait Serialize may be blanket-implemented for the reference type, while Deserialize may not. --- cardano/src/block/block.rs | 7 ++++++- cardano/src/tx.rs | 13 ++++++++++-- chain-core/src/property.rs | 28 +++++++++++++++++-------- chain-impl-mockchain/src/block.rs | 16 +++++++++----- chain-impl-mockchain/src/transaction.rs | 28 ++++++++++++++++--------- network-grpc/src/network_core.rs | 4 ++-- network-grpc/src/server.rs | 8 +++---- 7 files changed, 71 insertions(+), 33 deletions(-) diff --git a/cardano/src/block/block.rs b/cardano/src/block/block.rs index ff851abde..027ea2e7e 100644 --- a/cardano/src/block/block.rs +++ b/cardano/src/block/block.rs @@ -239,7 +239,8 @@ impl core::property::HasTransaction for Block { } } } -impl core::property::Serializable for Block { + +impl core::property::Serialize for Block { type Error = cbor_event::Error; fn serialize(&self, writer: W) -> Result<(), Self::Error> { @@ -248,6 +249,10 @@ impl core::property::Serializable for Block { serializer.finalize(); Ok(()) } +} + +impl core::property::Deserialize for Block { + type Error = cbor_event::Error; fn deserialize(reader: R) -> Result { let mut deserializer = cbor_event::de::Deserializer::from(reader); diff --git a/cardano/src/tx.rs b/cardano/src/tx.rs index f8f2aa7e0..a956ca688 100644 --- a/cardano/src/tx.rs +++ b/cardano/src/tx.rs @@ -626,7 +626,7 @@ impl cbor_event::de::Deserialize for TxProof { } } -impl core::property::Serializable for Tx { +impl core::property::Serialize for Tx { type Error = cbor_event::Error; fn serialize(&self, writer: W) -> Result<(), Self::Error> { @@ -635,13 +635,18 @@ impl core::property::Serializable for Tx { serializer.finalize(); Ok(()) } +} + +impl core::property::Deserialize for Tx { + type Error = cbor_event::Error; fn deserialize(reader: R) -> Result { let mut deserializer = cbor_event::de::Deserializer::from(reader); deserializer.deserialize::() } } -impl core::property::Serializable for TxAux { + +impl core::property::Serialize for TxAux { type Error = cbor_event::Error; fn serialize(&self, writer: W) -> Result<(), Self::Error> { @@ -650,6 +655,10 @@ impl core::property::Serializable for TxAux { serializer.finalize(); Ok(()) } +} + +impl core::property::Deserialize for TxAux { + type Error = cbor_event::Error; fn deserialize(reader: R) -> Result { let mut deserializer = cbor_event::de::Deserializer::from(reader); diff --git a/chain-core/src/property.rs b/chain-core/src/property.rs index 766ab708e..720be4088 100644 --- a/chain-core/src/property.rs +++ b/chain-core/src/property.rs @@ -61,7 +61,7 @@ impl Header for () {} /// recent block to the furthest/oldest block. /// /// The Oldest block is called the Genesis Block. -pub trait Block: Serializable { +pub trait Block: Serialize + Deserialize { /// the Block identifier. It must be unique. This mean that /// 2 different blocks have 2 different identifiers. /// @@ -100,7 +100,7 @@ pub trait Block: Serializable { /// for the UTxO model. However it can also be used for any other elements that /// the blockchain has (a transaction type to add Stacking Pools and so on...). /// -pub trait Transaction: Serializable { +pub trait Transaction: Serialize + Deserialize { /// the input type of the transaction (if none use `()`). type Input; /// the output type of the transaction (if none use `()`). @@ -222,33 +222,43 @@ pub trait LeaderSelection { fn is_leader_at(&self, date: ::Date) -> Result; } -/// Define that an object can be written in a `Write` object or read from the -/// `Read` object. -pub trait Serializable: Sized { +/// Define that an object can be written to a `Write` object. +pub trait Serialize { type Error: std::error::Error + From; fn serialize(&self, writer: W) -> Result<(), Self::Error>; +} + +/// Define that an object can be read from a `Read` object. +pub trait Deserialize: Sized { + type Error: std::error::Error + From; fn deserialize(reader: R) -> Result; } +impl Serialize for &T { + type Error = T::Error; + + fn serialize(&self, writer: W) -> Result<(), T::Error> { + (**self).serialize(writer) + } +} + #[cfg(feature = "property-test-api")] pub mod testing { use super::*; use quickcheck::{Arbitrary, Gen}; - use std::io::Cursor; /// test that any arbitrary given object can serialize and deserialize /// back into itself (i.e. it is a bijection, or a one to one match /// between the serialized bytes and the object) pub fn serialization_bijection(t: T) -> bool where - T: Arbitrary + Serializable + Eq, + T: Arbitrary + Serialize + Deserialize + Eq, { let mut vec = Vec::new(); t.serialize(&mut vec).unwrap(); - let cursor = Cursor::new(vec); - let decoded_t = ::deserialize(cursor).unwrap(); + let decoded_t = T::deserialize(&mut &vec[..]).unwrap(); decoded_t == t } diff --git a/chain-impl-mockchain/src/block.rs b/chain-impl-mockchain/src/block.rs index d1e2ab2c9..d3034436a 100644 --- a/chain-impl-mockchain/src/block.rs +++ b/chain-impl-mockchain/src/block.rs @@ -49,20 +49,26 @@ impl property::Block for Block { } } -impl chain_core::property::Serializable for Block { +impl property::Serialize for Block { // FIXME: decide on appropriate format for mock blockchain type Error = bincode::Error; - fn deserialize(reader: R) -> Result { - bincode::deserialize_from(reader) - } - fn serialize(&self, writer: W) -> Result<(), Self::Error> { bincode::serialize_into(writer, self) } } +impl property::Deserialize for Block { + // FIXME: decide on appropriate format for mock blockchain + + type Error = bincode::Error; + + fn deserialize(reader: R) -> Result { + bincode::deserialize_from(reader) + } +} + impl property::HasTransaction for Block { fn transactions<'a>(&'a self) -> std::slice::Iter<'a, SignedTransaction> { self.transactions.iter() diff --git a/chain-impl-mockchain/src/transaction.rs b/chain-impl-mockchain/src/transaction.rs index 73c66354f..b6f86fbdd 100644 --- a/chain-impl-mockchain/src/transaction.rs +++ b/chain-impl-mockchain/src/transaction.rs @@ -99,18 +99,22 @@ impl property::Transaction for Transaction { } } -impl property::Serializable for Transaction { +impl property::Serialize for Transaction { type Error = bincode::Error; - fn deserialize(reader: R) -> Result { - bincode::deserialize_from(reader) - } - fn serialize(&self, writer: W) -> Result<(), bincode::Error> { bincode::serialize_into(writer, self) } } +impl property::Deserialize for Transaction { + type Error = bincode::Error; + + fn deserialize(reader: R) -> Result { + bincode::deserialize_from(reader) + } +} + /// Each transaction must be signed in order to be executed /// by the ledger. `SignedTransaction` represents such a transaction. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] @@ -119,18 +123,22 @@ pub struct SignedTransaction { pub witnesses: Vec, } -impl property::Serializable for SignedTransaction { +impl property::Serialize for SignedTransaction { type Error = bincode::Error; - fn deserialize(reader: R) -> Result { - bincode::deserialize_from(reader) - } - fn serialize(&self, writer: W) -> Result<(), Self::Error> { bincode::serialize_into(writer, self) } } +impl property::Deserialize for SignedTransaction { + type Error = bincode::Error; + + fn deserialize(reader: R) -> Result { + bincode::deserialize_from(reader) + } +} + impl property::Transaction for SignedTransaction { type Input = UtxoPointer; type Output = Output; diff --git a/network-grpc/src/network_core.rs b/network-grpc/src/network_core.rs index 44cd0acb6..70823a941 100644 --- a/network-grpc/src/network_core.rs +++ b/network-grpc/src/network_core.rs @@ -19,8 +19,8 @@ pub struct RecordTransactionResponse { } pub trait Node { - type Error; - type BlockId: property::Serializable; + type Error: std::error::Error; + type BlockId: property::Deserialize; type BlockDate; type Block: property::Block; type Header; diff --git a/network-grpc/src/server.rs b/network-grpc/src/server.rs index 3d0c81be8..81755b220 100644 --- a/network-grpc/src/server.rs +++ b/network-grpc/src/server.rs @@ -1,4 +1,4 @@ -use chain_core::property::Serializable; +use chain_core::property::{Block, Deserialize, Serialize}; use futures::prelude::*; use futures::try_ready; @@ -114,12 +114,12 @@ pub struct GrpcServer { node: T, } -fn deserialize_hashes( +fn deserialize_hashes( pb: &cardano_proto::HeaderHashes, -) -> Result, ::Error> { +) -> Result, H::Error> { pb.hashes .iter() - .map(|v| Serializable::deserialize(&v[..])) + .map(|v| H::deserialize(&mut &v[..])) .collect() } From 5619a24e11c80264e739ece389a2a7aa8fd7711c Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Tue, 8 Jan 2019 08:30:28 +0200 Subject: [PATCH 09/14] network-grpc: Redesign for abstract network serialization We cannot rely on From impls to convert between chain types associated with the Node impl and protobuf data types, as these would have to be defined somewhere that is gnostic of both the gRPC network protocol and the concrete chain implementation. Part of the solution is changing the protobuf definitions for basic chain data types to allow opaque serialization. Thankfully, the only such message type that sported specific fields was BlockDate, so this is redefined to carry opaque bytes. At the same time HeaderHash is renamed to BlockId to match the naming used by the chain abstraction traits. --- network-grpc/proto/node.proto | 8 +- network-grpc/proto/types.proto | 13 +- network-grpc/src/network_core.rs | 35 +++-- network-grpc/src/server.rs | 218 +++++++++++++++++++++++-------- 4 files changed, 194 insertions(+), 80 deletions(-) diff --git a/network-grpc/proto/node.proto b/network-grpc/proto/node.proto index 8880d64f7..6a8ce1c8d 100644 --- a/network-grpc/proto/node.proto +++ b/network-grpc/proto/node.proto @@ -7,14 +7,14 @@ import "types.proto"; message TipRequest {} message TipResponse { - cardano.BlockDate blockdate = 1; - cardano.HeaderHash hash = 2; + cardano.BlockId id = 1; + cardano.BlockDate blockdate = 2; } // Parameters for GetBlocks and GetHeaders message GetBlocksRequest { // The hash of the tip to trace the chain from. - cardano.HeaderHash tip = 1; + cardano.BlockId tip = 1; // The offset back in the chain to start block or header retrieval from, // going backwards in the chain. uint64 offset = 2; @@ -74,7 +74,7 @@ service Node { rpc GetHeaders (GetBlocksRequest) returns (stream cardano.Header) { option idempotency_level = NO_SIDE_EFFECTS; } - rpc StreamBlocksToTip (cardano.HeaderHashes) returns (stream cardano.Block); + rpc StreamBlocksToTip (cardano.BlockIds) returns (stream cardano.Block); rpc ProposeTransactions (ProposeTransactionsRequest) returns (ProposeTransactionsResponse); rpc RecordTransaction (RecordTransactionRequest) returns (RecordTransactionResponse); } diff --git a/network-grpc/proto/types.proto b/network-grpc/proto/types.proto index 85b573bf0..5f826c445 100644 --- a/network-grpc/proto/types.proto +++ b/network-grpc/proto/types.proto @@ -2,17 +2,16 @@ syntax = "proto3"; package cardano; -message BlockDate { - uint64 epoch = 1; - uint32 slot = 2; +message BlockId { + bytes id = 1; } -message HeaderHash { - bytes hash = 1; +message BlockIds { + repeated bytes ids = 1; } -message HeaderHashes { - repeated bytes hashes = 1; +message BlockDate { + bytes content = 1; } message Block { diff --git a/network-grpc/src/network_core.rs b/network-grpc/src/network_core.rs index 70823a941..01b502efc 100644 --- a/network-grpc/src/network_core.rs +++ b/network-grpc/src/network_core.rs @@ -2,11 +2,23 @@ use chain_core::property; use futures::prelude::*; -use std::collections::HashMap; +use std::{collections::HashMap, fmt}; // NOTE: protobuf-derived definitions used in would-be abstract core API use super::iohk::jormungandr as gen; +/// Represents errors that can be returned by the node implementation. +#[derive(Debug)] +pub struct Error(); // TODO: define specific error variants and details + +impl std::error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "unknown network node error") + } +} + pub struct ProposeTransactionsResponse { // TODO: define fully _items: HashMap, @@ -19,26 +31,27 @@ pub struct RecordTransactionResponse { } pub trait Node { - type Error: std::error::Error; - type BlockId: property::Deserialize; + type BlockId; type BlockDate; type Block: property::Block; type Header; - type TipFuture: Future; - type BlocksStream: Stream; - type BlocksFuture: Future; - type HeadersStream: Stream; - type HeadersFuture: Future; + type TipFuture: Future; + type GetBlocksStream: Stream; + type GetBlocksFuture: Future; + type GetHeadersStream: Stream; + type GetHeadersFuture: Future; + type StreamBlocksToTipStream: Stream; + type StreamBlocksToTipFuture: Future; type ProposeTransactionsFuture: Future< Item = ProposeTransactionsResponse, - Error = Self::Error, + Error = Error, >; type RecordTransactionFuture: Future< Item = RecordTransactionResponse, - Error = Self::Error, + Error = Error, >; fn tip(&mut self) -> Self::TipFuture; - fn stream_blocks_to_tip(&mut self, from: &[Self::BlockId]) -> Self::BlocksFuture; + fn stream_blocks_to_tip(&mut self, from: &[Self::BlockId]) -> Self::StreamBlocksToTipFuture; } diff --git a/network-grpc/src/server.rs b/network-grpc/src/server.rs index 81755b220..8f20a1b89 100644 --- a/network-grpc/src/server.rs +++ b/network-grpc/src/server.rs @@ -1,7 +1,7 @@ use chain_core::property::{Block, Deserialize, Serialize}; use futures::prelude::*; -use futures::try_ready; +use tower_grpc::Error::Grpc as GrpcError; use tower_grpc::{self, Code, Request, Status}; use std::marker::PhantomData; @@ -22,9 +22,7 @@ pub struct FutureResponse { impl FutureResponse where - F: Future, - T: From<::Item>, - tower_grpc::Error: From<::Error>, + F: Future + ConvertResponse, { fn new(future: F) -> Self { FutureResponse { @@ -43,23 +41,62 @@ impl FutureResponse { } } +fn convert_error(e: network_core::Error) -> tower_grpc::Error { + let status = Status::with_code_and_message(Code::Unknown, format!("{}", e)); + GrpcError(status) +} + +pub trait ConvertResponse: Future { + fn convert_item(item: Self::Item) -> Result; +} + +pub trait ConvertStream: Stream { + fn convert_item(item: Self::Item) -> Result; +} + +fn poll_and_convert_response( + future: &mut F, +) -> Poll, tower_grpc::Error> +where + F: Future + ConvertResponse, +{ + match future.poll() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(item)) => { + let item = F::convert_item(item)?; + let response = tower_grpc::Response::new(item); + Ok(Async::Ready(response)) + } + Err(e) => Err(convert_error(e)), + } +} + +fn poll_and_convert_stream(stream: &mut S) -> Poll, tower_grpc::Error> +where + S: Stream + ConvertStream, +{ + match stream.poll() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(None)) => Ok(Async::Ready(None)), + Ok(Async::Ready(Some(item))) => { + let item = S::convert_item(item)?; + Ok(Async::Ready(Some(item))) + } + Err(e) => Err(convert_error(e)), + } +} + impl Future for FutureResponse where - F: Future, - T: From<::Item>, - tower_grpc::Error: From<::Error>, + F: Future + ConvertResponse, { type Item = tower_grpc::Response; type Error = tower_grpc::Error; fn poll(&mut self) -> Poll { match self.state { - ResponseState::Future(ref mut f) => { - let item = try_ready!(f.poll()); - let response = tower_grpc::Response::new(item.into()); - Ok(Async::Ready(response)) - } - ResponseState::Err(ref status) => Err(tower_grpc::Error::Grpc(status.clone())), + ResponseState::Future(ref mut f) => poll_and_convert_response(f), + ResponseState::Err(ref status) => Err(GrpcError(status.clone())), } } } @@ -71,9 +108,7 @@ pub struct ResponseStream { impl ResponseStream where - S: Stream, - T: From<::Item>, - tower_grpc::Error: From<::Error>, + S: Stream + ConvertStream, { pub fn new(stream: S) -> Self { ResponseStream { @@ -83,29 +118,15 @@ where } } -impl From for ResponseStream -where - S: Stream, - T: From<::Item>, - tower_grpc::Error: From<::Error>, -{ - fn from(stream: S) -> Self { - ResponseStream::new(stream) - } -} - impl Stream for ResponseStream where - S: Stream, - T: From<::Item>, - tower_grpc::Error: From<::Error>, + S: Stream + ConvertStream, { type Item = T; type Error = tower_grpc::Error; fn poll(&mut self) -> Poll, tower_grpc::Error> { - let maybe_item = try_ready!(self.inner.poll()); - Ok(Async::Ready(maybe_item.map(|item| item.into()))) + poll_and_convert_stream(&mut self.inner) } } @@ -114,40 +135,121 @@ pub struct GrpcServer { node: T, } -fn deserialize_hashes( - pb: &cardano_proto::HeaderHashes, -) -> Result, H::Error> { - pb.hashes - .iter() - .map(|v| H::deserialize(&mut &v[..])) - .collect() +fn deserialize_block_ids(pb: &cardano_proto::BlockIds) -> Result, H::Error> { + pb.ids.iter().map(|v| H::deserialize(&mut &v[..])).collect() +} + +fn serialize_to_bytes(obj: T) -> Result, tower_grpc::Error> +where + T: Serialize, +{ + let mut bytes = Vec::new(); + match obj.serialize(&mut bytes) { + Ok(()) => Ok(bytes), + Err(_e) => { + // FIXME: log the error + let status = Status::with_code(Code::Unknown); + Err(GrpcError(status)) + } + } +} + +impl ConvertResponse> for F +where + F: Future, + S: Stream + ConvertStream, +{ + fn convert_item(item: S) -> Result, tower_grpc::Error> { + let stream = ResponseStream::new(item); + Ok(stream) + } +} + +impl ConvertResponse for F +where + F: Future, + I: Serialize, + D: Serialize, +{ + fn convert_item(item: (I, D)) -> Result { + let id = serialize_to_bytes(item.0)?; + let blockdate = serialize_to_bytes(item.1)?; + let response = gen::TipResponse { + id: Some(cardano_proto::BlockId { id }), + blockdate: Some(cardano_proto::BlockDate { content: blockdate }), + }; + Ok(response) + } +} + +impl ConvertStream for S +where + S: Stream, + B: Block + Serialize, +{ + fn convert_item(item: Self::Item) -> Result { + let content = serialize_to_bytes(item)?; + Ok(cardano_proto::Block { content }) + } +} + +impl ConvertStream for S +where + S: Stream, + H: Serialize, // FIXME: this needs more bounds to only work for headers +{ + fn convert_item(item: Self::Item) -> Result { + let content = serialize_to_bytes(item)?; + Ok(cardano_proto::Header { content }) + } +} + +impl ConvertResponse for F +where + F: Future, Error = network_core::Error>, + I: Serialize, +{ + fn convert_item( + _item: Self::Item, + ) -> Result { + unimplemented!(); + } +} + +impl ConvertResponse for F +where + F: Future, Error = network_core::Error>, + I: Serialize, +{ + fn convert_item( + _item: Self::Item, + ) -> Result { + unimplemented!(); + } } impl gen::server::Node for GrpcServer where T: network_core::Node + Clone, - tower_grpc::Error: From<::Error>, - cardano_proto::Block: From<::Block>, - cardano_proto::Header: From<::Header>, - gen::TipResponse: From<::BlockId>, - gen::ProposeTransactionsResponse: - From::BlockId>>, - gen::RecordTransactionResponse: - From::BlockId>>, + ::BlockId: Serialize + Deserialize, + ::BlockDate: Serialize, + ::Header: Serialize, { type TipFuture = FutureResponse::TipFuture>; type GetBlocksStream = - ResponseStream::BlocksStream>; + ResponseStream::GetBlocksStream>; type GetBlocksFuture = - FutureResponse::BlocksFuture>; + FutureResponse::GetBlocksFuture>; type GetHeadersStream = - ResponseStream::HeadersStream>; + ResponseStream::GetHeadersStream>; type GetHeadersFuture = - FutureResponse::HeadersFuture>; + FutureResponse::GetHeadersFuture>; type StreamBlocksToTipStream = - ResponseStream::BlocksStream>; - type StreamBlocksToTipFuture = - FutureResponse::BlocksFuture>; + ResponseStream::StreamBlocksToTipStream>; + type StreamBlocksToTipFuture = FutureResponse< + Self::StreamBlocksToTipStream, + ::StreamBlocksToTipFuture, + >; type ProposeTransactionsFuture = FutureResponse< gen::ProposeTransactionsResponse, ::ProposeTransactionsFuture, @@ -171,10 +273,10 @@ where fn stream_blocks_to_tip( &mut self, - from: Request, + from: Request, ) -> Self::StreamBlocksToTipFuture { - let hashes = match deserialize_hashes(from.get_ref()) { - Ok(hashes) => hashes, + let block_ids = match deserialize_block_ids(from.get_ref()) { + Ok(block_ids) => block_ids, Err(e) => { // FIXME: log the error // (preferably with tower facilities outside of this implementation) @@ -182,7 +284,7 @@ where return FutureResponse::error(status); } }; - FutureResponse::new(self.node.stream_blocks_to_tip(&hashes)) + FutureResponse::new(self.node.stream_blocks_to_tip(&block_ids)) } fn propose_transactions( From 0ebdfde20c449a686a5b5c4ed57dc759b98bfae6 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Wed, 9 Jan 2019 00:48:25 +0200 Subject: [PATCH 10/14] network-grpc: Use chain data model traits Use chain-core property traits identifying chain entity types to type-check implementations of protocol marshallers. This is useful in preventing accidental use of a wrong value in serialization code of the network protocol. --- network-grpc/src/network_core.rs | 6 +++--- network-grpc/src/server.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/network-grpc/src/network_core.rs b/network-grpc/src/network_core.rs index 01b502efc..ec6251e45 100644 --- a/network-grpc/src/network_core.rs +++ b/network-grpc/src/network_core.rs @@ -31,10 +31,10 @@ pub struct RecordTransactionResponse { } pub trait Node { - type BlockId; - type BlockDate; + type BlockId: property::BlockId; + type BlockDate: property::BlockDate; type Block: property::Block; - type Header; + type Header: property::Header; type TipFuture: Future; type GetBlocksStream: Stream; diff --git a/network-grpc/src/server.rs b/network-grpc/src/server.rs index 8f20a1b89..ef9866bfa 100644 --- a/network-grpc/src/server.rs +++ b/network-grpc/src/server.rs @@ -1,4 +1,4 @@ -use chain_core::property::{Block, Deserialize, Serialize}; +use chain_core::property::{Block, BlockDate, BlockId, Deserialize, Header, Serialize}; use futures::prelude::*; use tower_grpc::Error::Grpc as GrpcError; @@ -168,8 +168,8 @@ where impl ConvertResponse for F where F: Future, - I: Serialize, - D: Serialize, + I: BlockId + Serialize, + D: BlockDate + Serialize, { fn convert_item(item: (I, D)) -> Result { let id = serialize_to_bytes(item.0)?; @@ -196,7 +196,7 @@ where impl ConvertStream for S where S: Stream, - H: Serialize, // FIXME: this needs more bounds to only work for headers + H: Header + Serialize, { fn convert_item(item: Self::Item) -> Result { let content = serialize_to_bytes(item)?; @@ -207,7 +207,7 @@ where impl ConvertResponse for F where F: Future, Error = network_core::Error>, - I: Serialize, + I: BlockId + Serialize, { fn convert_item( _item: Self::Item, @@ -219,7 +219,7 @@ where impl ConvertResponse for F where F: Future, Error = network_core::Error>, - I: Serialize, + I: BlockId + Serialize, { fn convert_item( _item: Self::Item, From 6c35fe4f3cb65cd9b469494daa789b50d6c6c7d5 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Wed, 9 Jan 2019 14:16:10 +0200 Subject: [PATCH 11/14] network-grpc: Flatten and simplify the protocol The named wrapper types are redundant, both for their intended purpose and in the wire format. Field ids in a message sufficiently identify the purpose and format of the field value. Therefore, the type definitions in types.proto have been removed. The proto package name has been changed to iohk.chain.node to reflect the now-generic nature of the protocol. --- network-grpc/proto/node.proto | 37 ++++++++++---- network-grpc/proto/types.proto | 27 ---------- network-grpc/src/lib.rs | 11 +--- network-grpc/src/network_core.rs | 2 +- network-grpc/src/server.rs | 88 ++++++++++++++++---------------- 5 files changed, 72 insertions(+), 93 deletions(-) delete mode 100644 network-grpc/proto/types.proto diff --git a/network-grpc/proto/node.proto b/network-grpc/proto/node.proto index 6a8ce1c8d..8508b3226 100644 --- a/network-grpc/proto/node.proto +++ b/network-grpc/proto/node.proto @@ -1,20 +1,18 @@ syntax = "proto3"; -package iohk.jormungandr; - -import "types.proto"; +package iohk.chain.node; message TipRequest {} message TipResponse { - cardano.BlockId id = 1; - cardano.BlockDate blockdate = 2; + bytes id = 1; + string blockdate = 2; } // Parameters for GetBlocks and GetHeaders message GetBlocksRequest { - // The hash of the tip to trace the chain from. - cardano.BlockId tip = 1; + // The id of the tip to trace the chain from. + bytes tip = 1; // The offset back in the chain to start block or header retrieval from, // going backwards in the chain. uint64 offset = 2; @@ -22,6 +20,23 @@ message GetBlocksRequest { uint64 size = 3; } +message StreamBlocksToTipRequest { + // The ids of alternative chain forks to start the blocks from. + repeated bytes from = 1; +} + +// Representation of a block. +message Block { + // The entire content of the block. + bytes content = 1; +} + +// Representation of a block header. +message Header { + // The entire content of the block header. + bytes content = 1; +} + message ProposeTransactionsRequest { repeated bytes ids = 1; } @@ -44,7 +59,7 @@ message ProposeTransactionsResponse { } message RecordTransactionRequest { - cardano.Transaction tx = 1; + bytes tx = 1; } message RecordTransactionResponse { @@ -68,13 +83,13 @@ message RecordTransactionResponse { service Node { rpc Tip (TipRequest) returns (TipResponse); - rpc GetBlocks (GetBlocksRequest) returns (stream cardano.Block) { + rpc GetBlocks (GetBlocksRequest) returns (stream Block) { option idempotency_level = NO_SIDE_EFFECTS; } - rpc GetHeaders (GetBlocksRequest) returns (stream cardano.Header) { + rpc GetHeaders (GetBlocksRequest) returns (stream Header) { option idempotency_level = NO_SIDE_EFFECTS; } - rpc StreamBlocksToTip (cardano.BlockIds) returns (stream cardano.Block); + rpc StreamBlocksToTip (StreamBlocksToTipRequest) returns (stream Block); rpc ProposeTransactions (ProposeTransactionsRequest) returns (ProposeTransactionsResponse); rpc RecordTransaction (RecordTransactionRequest) returns (RecordTransactionResponse); } diff --git a/network-grpc/proto/types.proto b/network-grpc/proto/types.proto deleted file mode 100644 index 5f826c445..000000000 --- a/network-grpc/proto/types.proto +++ /dev/null @@ -1,27 +0,0 @@ -syntax = "proto3"; - -package cardano; - -message BlockId { - bytes id = 1; -} - -message BlockIds { - repeated bytes ids = 1; -} - -message BlockDate { - bytes content = 1; -} - -message Block { - bytes content = 1; -} - -message Header { - bytes content = 1; -} - -message Transaction { - bytes content = 1; -} \ No newline at end of file diff --git a/network-grpc/src/lib.rs b/network-grpc/src/lib.rs index 4968e624b..18cab019c 100644 --- a/network-grpc/src/lib.rs +++ b/network-grpc/src/lib.rs @@ -8,17 +8,10 @@ extern crate tower_h2; extern crate tower_util; // Included generated protobuf/gRPC code, -// namespaced into submodules corresponding to the .proto package names - -mod cardano { - include!(concat!(env!("OUT_DIR"), "/cardano.rs")); -} #[allow(dead_code)] -mod iohk { - pub mod jormungandr { - include!(concat!(env!("OUT_DIR"), "/iohk.jormungandr.rs")); - } +mod gen { + include!(concat!(env!("OUT_DIR"), "/iohk.chain.node.rs")); } pub mod server; diff --git a/network-grpc/src/network_core.rs b/network-grpc/src/network_core.rs index ec6251e45..bedab9893 100644 --- a/network-grpc/src/network_core.rs +++ b/network-grpc/src/network_core.rs @@ -5,7 +5,7 @@ use futures::prelude::*; use std::{collections::HashMap, fmt}; // NOTE: protobuf-derived definitions used in would-be abstract core API -use super::iohk::jormungandr as gen; +use super::gen; /// Represents errors that can be returned by the node implementation. #[derive(Debug)] diff --git a/network-grpc/src/server.rs b/network-grpc/src/server.rs index ef9866bfa..3c25ee525 100644 --- a/network-grpc/src/server.rs +++ b/network-grpc/src/server.rs @@ -6,18 +6,13 @@ use tower_grpc::{self, Code, Request, Status}; use std::marker::PhantomData; -use super::cardano as cardano_proto; -use super::iohk::jormungandr as gen; +use super::gen; use super::network_core; -enum ResponseState { - Future(F), +pub enum FutureResponse { + Pending(F), Err(Status), -} - -pub struct FutureResponse { - state: ResponseState, - _phantom: PhantomData, + Complete(PhantomData), } impl FutureResponse @@ -25,19 +20,13 @@ where F: Future + ConvertResponse, { fn new(future: F) -> Self { - FutureResponse { - state: ResponseState::Future(future), - _phantom: PhantomData, - } + FutureResponse::Pending(future) } } impl FutureResponse { fn error(status: Status) -> Self { - FutureResponse { - state: ResponseState::Err(status), - _phantom: PhantomData, - } + FutureResponse::Err(status) } } @@ -94,9 +83,16 @@ where type Error = tower_grpc::Error; fn poll(&mut self) -> Poll { - match self.state { - ResponseState::Future(ref mut f) => poll_and_convert_response(f), - ResponseState::Err(ref status) => Err(GrpcError(status.clone())), + let res = match self { + FutureResponse::Pending(f) => poll_and_convert_response(f), + FutureResponse::Err(status) => Err(GrpcError(status.clone())), + FutureResponse::Complete(_) => panic!("polled a finished response"), + }; + if let Ok(Async::NotReady) = res { + Ok(Async::NotReady) + } else { + *self = FutureResponse::Complete(PhantomData); + res } } } @@ -135,8 +131,16 @@ pub struct GrpcServer { node: T, } -fn deserialize_block_ids(pb: &cardano_proto::BlockIds) -> Result, H::Error> { - pb.ids.iter().map(|v| H::deserialize(&mut &v[..])).collect() +fn deserialize_vec(pb: &[Vec]) -> Result, tower_grpc::Error> { + match pb.iter().map(|v| H::deserialize(&mut &v[..])).collect() { + Ok(v) => Ok(v), + Err(e) => { + // FIXME: log the error + // (preferably with tower facilities outside of this implementation) + let status = Status::with_code_and_message(Code::InvalidArgument, format!("{}", e)); + Err(GrpcError(status)) + } + } } fn serialize_to_bytes(obj: T) -> Result, tower_grpc::Error> @@ -169,38 +173,35 @@ impl ConvertResponse for F where F: Future, I: BlockId + Serialize, - D: BlockDate + Serialize, + D: BlockDate + ToString, { fn convert_item(item: (I, D)) -> Result { let id = serialize_to_bytes(item.0)?; - let blockdate = serialize_to_bytes(item.1)?; - let response = gen::TipResponse { - id: Some(cardano_proto::BlockId { id }), - blockdate: Some(cardano_proto::BlockDate { content: blockdate }), - }; + let blockdate = item.1.to_string(); + let response = gen::TipResponse { id, blockdate }; Ok(response) } } -impl ConvertStream for S +impl ConvertStream for S where S: Stream, B: Block + Serialize, { - fn convert_item(item: Self::Item) -> Result { + fn convert_item(item: Self::Item) -> Result { let content = serialize_to_bytes(item)?; - Ok(cardano_proto::Block { content }) + Ok(gen::Block { content }) } } -impl ConvertStream for S +impl ConvertStream for S where S: Stream, H: Header + Serialize, { - fn convert_item(item: Self::Item) -> Result { + fn convert_item(item: Self::Item) -> Result { let content = serialize_to_bytes(item)?; - Ok(cardano_proto::Header { content }) + Ok(gen::Header { content }) } } @@ -232,20 +233,19 @@ impl gen::server::Node for GrpcServer where T: network_core::Node + Clone, ::BlockId: Serialize + Deserialize, - ::BlockDate: Serialize, + ::BlockDate: ToString, ::Header: Serialize, { type TipFuture = FutureResponse::TipFuture>; - type GetBlocksStream = - ResponseStream::GetBlocksStream>; + type GetBlocksStream = ResponseStream::GetBlocksStream>; type GetBlocksFuture = FutureResponse::GetBlocksFuture>; type GetHeadersStream = - ResponseStream::GetHeadersStream>; + ResponseStream::GetHeadersStream>; type GetHeadersFuture = FutureResponse::GetHeadersFuture>; type StreamBlocksToTipStream = - ResponseStream::StreamBlocksToTipStream>; + ResponseStream::StreamBlocksToTipStream>; type StreamBlocksToTipFuture = FutureResponse< Self::StreamBlocksToTipStream, ::StreamBlocksToTipFuture, @@ -273,16 +273,14 @@ where fn stream_blocks_to_tip( &mut self, - from: Request, + req: Request, ) -> Self::StreamBlocksToTipFuture { - let block_ids = match deserialize_block_ids(from.get_ref()) { + let block_ids = match deserialize_vec(&req.get_ref().from) { Ok(block_ids) => block_ids, - Err(e) => { - // FIXME: log the error - // (preferably with tower facilities outside of this implementation) - let status = Status::with_code_and_message(Code::InvalidArgument, format!("{}", e)); + Err(GrpcError(status)) => { return FutureResponse::error(status); } + Err(e) => panic!("unexpected error {:?}", e), }; FutureResponse::new(self.node.stream_blocks_to_tip(&block_ids)) } From 7b017212e63b7a83d6c6e0e6be354da92ec81c43 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Wed, 9 Jan 2019 23:51:25 +0200 Subject: [PATCH 12/14] Update tower-grpc dependency Sync to the latest commit in the master branch. --- network-grpc/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network-grpc/Cargo.toml b/network-grpc/Cargo.toml index 62d41e882..7a1258592 100644 --- a/network-grpc/Cargo.toml +++ b/network-grpc/Cargo.toml @@ -23,8 +23,8 @@ tower-util = { git = "https://github.com/tower-rs/tower" } [dependencies.tower-grpc] git = "https://github.com/tower-rs/tower-grpc" -rev = "7ffa9d3b610d1c84559a811d08a25bd1670babed" +rev = "6f355226654ac5ad90209db3e308223c662248cb" [build-dependencies.tower-grpc-build] git = "https://github.com/tower-rs/tower-grpc" -rev = "7ffa9d3b610d1c84559a811d08a25bd1670babed" +rev = "6f355226654ac5ad90209db3e308223c662248cb" From 7b2a8d8e28be29aa21a8b0bcf8fe2eb194a3ecb6 Mon Sep 17 00:00:00 2001 From: Alexander Vershilov Date: Wed, 9 Jan 2019 18:21:14 +0300 Subject: [PATCH 13/14] mockchain: Make transaction checker more strict. Check if number of signatures matches the number of inputs in the mockchain. --- chain-impl-mockchain/src/error.rs | 12 +++- chain-impl-mockchain/src/key.rs | 3 + chain-impl-mockchain/src/ledger.rs | 77 ++++++++++++++++++++++++- chain-impl-mockchain/src/transaction.rs | 2 +- 4 files changed, 91 insertions(+), 3 deletions(-) diff --git a/chain-impl-mockchain/src/error.rs b/chain-impl-mockchain/src/error.rs index c80a016c8..9ed049ca5 100644 --- a/chain-impl-mockchain/src/error.rs +++ b/chain-impl-mockchain/src/error.rs @@ -1,7 +1,7 @@ //! Errors taht may happen in the ledger and mockchain. use crate::transaction::*; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Error { /// If the Ledger could not find the given input in the UTxO list it will /// report this error. @@ -36,6 +36,11 @@ pub enum Error { /// Transaction sum is not equal to zero, this means that we /// either generate or lose some money during the transaction. TransactionSumIsNonZero(u64, u64), + + /// Transaction does not have enough signatures. + /// First value represents number of inputs (required signatures) + /// Send value represents actual number of singatures. + NotEnoughSignatures(usize, usize), } impl std::fmt::Display for Error { @@ -49,6 +54,11 @@ impl std::fmt::Display for Error { Error::InvalidSignature(_, _, _) => write!(f, "Input is not signed properly"), Error::InvalidTxSignature(_) => write!(f, "Transaction was not signed"), Error::TransactionSumIsNonZero(_, _) => write!(f, "Transaction sum is non zero"), + Error::NotEnoughSignatures(required, actual) => write!( + f, + "Transaction has not enough signatures: {} out of {}", + actual, required + ), } } } diff --git a/chain-impl-mockchain/src/key.rs b/chain-impl-mockchain/src/key.rs index 54fd2bebb..d0e30e43b 100644 --- a/chain-impl-mockchain/src/key.rs +++ b/chain-impl-mockchain/src/key.rs @@ -38,6 +38,9 @@ impl PrivateKey { pub fn sign(&self, data: &[u8]) -> Signature { Signature(self.0.sign(data)) } + pub fn normalize_bytes(xprv: [u8; crypto::XPRV_SIZE]) -> Self { + PrivateKey(crypto::XPrv::normalize_bytes(xprv)) + } } /// Hash that is used as an address of the various components. diff --git a/chain-impl-mockchain/src/ledger.rs b/chain-impl-mockchain/src/ledger.rs index 91deb0380..5bd137a14 100644 --- a/chain-impl-mockchain/src/ledger.rs +++ b/chain-impl-mockchain/src/ledger.rs @@ -22,7 +22,7 @@ impl Ledger { } /// Diff of the ledger state. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Diff { /// List of the outputs that were spent in the transaction. spent_outputs: HashMap, @@ -64,6 +64,14 @@ impl property::Ledger for Ledger { let mut diff = Diff::new(); let id = transaction.id(); + // 0. verify that number of signatures matches number of + // transactions + if transaction.tx.inputs.len() > transaction.witnesses.len() { + return Err(Error::NotEnoughSignatures( + transaction.tx.inputs.len(), + transaction.witnesses.len(), + )); + } // 1. validate transaction without looking into the context // and that each input is validated by the matching key. for (input, witness) in transaction @@ -140,6 +148,9 @@ impl property::Ledger for Ledger { mod test { use super::*; + use crate::address::Address; + use crate::key::{Hash, PrivateKey}; + use cardano::hdwallet as crypto; use quickcheck::{Arbitrary, Gen}; impl Arbitrary for Ledger { @@ -150,4 +161,68 @@ mod test { } } + #[test] + pub fn tx_no_witness() -> () { + use chain_core::property::Ledger; + let pk1 = PrivateKey::normalize_bytes([0; crypto::XPRV_SIZE]); + let user1_address = Address::new(&pk1.public()); + let tx0_id = TransactionId(Hash::hash_bytes(&[0])); + let utxo0 = UtxoPointer { + transaction_id: tx0_id, + output_index: 0, + }; + let ledger = crate::ledger::Ledger::new( + vec![(utxo0, Output(user1_address, Value(1)))] + .iter() + .cloned() + .collect(), + ); + let tx = Transaction { + inputs: vec![utxo0], + outputs: vec![Output(user1_address, Value(1))], + }; + let signed_tx = SignedTransaction { + tx: tx, + witnesses: vec![], + }; + assert_eq!( + Err(Error::NotEnoughSignatures(1, 0)), + ledger.diff_transaction(&signed_tx) + ) + } + + #[test] + pub fn tx_wrong_witness() -> () { + use chain_core::property::Ledger; + use chain_core::property::Transaction; + let pk1 = PrivateKey::normalize_bytes([0; crypto::XPRV_SIZE]); + let user1_address = Address::new(&pk1.public()); + let tx0_id = TransactionId(Hash::hash_bytes(&[0])); + let utxo0 = UtxoPointer { + transaction_id: tx0_id, + output_index: 0, + }; + let ledger = crate::ledger::Ledger::new( + vec![(utxo0, Output(user1_address, Value(1)))] + .iter() + .cloned() + .collect(), + ); + let output0 = Output(user1_address, Value(1)); + let tx = crate::transaction::Transaction { + inputs: vec![utxo0], + outputs: vec![output0], + }; + let pk2 = PrivateKey::normalize_bytes([1; crypto::XPRV_SIZE]); + let witness = Witness::new(tx.id(), &pk2); + let signed_tx = SignedTransaction { + tx: tx, + witnesses: vec![witness.clone()], + }; + assert_eq!( + Err(Error::InvalidSignature(utxo0, output0, witness)), + ledger.diff_transaction(&signed_tx) + ) + } + } diff --git a/chain-impl-mockchain/src/transaction.rs b/chain-impl-mockchain/src/transaction.rs index 73c66354f..d66e2a59a 100644 --- a/chain-impl-mockchain/src/transaction.rs +++ b/chain-impl-mockchain/src/transaction.rs @@ -66,7 +66,7 @@ pub struct Output(pub Address, pub Value); /// Id of the transaction. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] -pub struct TransactionId(Hash); +pub struct TransactionId(pub Hash); impl AsRef<[u8]> for TransactionId { fn as_ref(&self) -> &[u8] { self.0.as_ref() From ef17b3fa85467782039423ee8cd1abad04bcca1a Mon Sep 17 00:00:00 2001 From: Alexander Vershilov Date: Wed, 9 Jan 2019 14:37:59 +0300 Subject: [PATCH 14/14] Introduces SignedBlock for the mockchain. --- chain-impl-mockchain/src/block.rs | 98 +++++++++++++++++++++++++ chain-impl-mockchain/src/environment.rs | 2 +- 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/chain-impl-mockchain/src/block.rs b/chain-impl-mockchain/src/block.rs index d3034436a..88af50288 100644 --- a/chain-impl-mockchain/src/block.rs +++ b/chain-impl-mockchain/src/block.rs @@ -1,6 +1,7 @@ //! Representation of the block in the mockchain. use crate::key::*; use crate::transaction::*; +use bincode; use chain_core::property; /// Non unique identifier of the transaction position in the @@ -75,12 +76,99 @@ impl property::HasTransaction for Block { } } +/// `Block` is an element of the blockchain it contains multiple +/// transaction and a reference to the parent block. Alongside +/// with the position of that block in the chain. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct SignedBlock { + /// Internal block. + block: Block, + /// Public key used to sign the block. + public_key: PublicKey, + /// List of cryptographic signatures that verifies the block. + signature: Signature, +} + +impl SignedBlock { + /// Create a new signed block. + pub fn new(block: Block, pkey: PrivateKey) -> Self { + use chain_core::property::Block; + let block_id = block.id(); + SignedBlock { + block: block, + public_key: pkey.public(), + signature: pkey.sign(block_id.as_ref()), + } + } + + /// Verify if block is correctly signed by the key. + /// Return `false` if there is no such signature or + /// if it can't be verified. + pub fn verify(&self) -> bool { + use chain_core::property::Block; + let block_id = self.block.id(); + self.public_key.verify(block_id.as_ref(), &self.signature) + } +} + +impl property::Serialize for SignedBlock { + type Error = bincode::Error; + + fn serialize(&self, writer: W) -> Result<(), Self::Error> { + bincode::serialize_into(writer, self) + } +} + +impl property::Deserialize for SignedBlock { + type Error = bincode::Error; + + fn deserialize(reader: R) -> Result { + bincode::deserialize_from(reader) + } +} + +impl property::Block for SignedBlock { + type Id = ::Id; + type Date = ::Date; + type Header = ::Header; + + /// Identifier of the block, currently the hash of the + /// serialized transaction. + fn id(&self) -> Self::Id { + self.block.id() + } + + fn header(&self) -> Self::Header { + self.block.header() + } + + /// Id of the parent block. + fn parent_id(&self) -> Self::Id { + self.block.parent_id() + } + + /// Date of the block. + fn date(&self) -> Self::Date { + self.block.date() + } +} + #[cfg(test)] mod test { use super::*; use quickcheck::{Arbitrary, Gen}; + quickcheck! { + fn block_serialization_bijection(b: Block) -> bool { + property::testing::serialization_bijection(b) + } + + fn signed_block_serialization_bijection(b: SignedBlock) -> bool { + property::testing::serialization_bijection(b) + } + } + impl Arbitrary for Block { fn arbitrary(g: &mut G) -> Self { Block { @@ -91,6 +179,16 @@ mod test { } } + impl Arbitrary for SignedBlock { + fn arbitrary(g: &mut G) -> Self { + SignedBlock { + block: Arbitrary::arbitrary(g), + public_key: Arbitrary::arbitrary(g), + signature: Arbitrary::arbitrary(g), + } + } + } + impl Arbitrary for SlotId { fn arbitrary(g: &mut G) -> Self { SlotId(Arbitrary::arbitrary(g), Arbitrary::arbitrary(g)) diff --git a/chain-impl-mockchain/src/environment.rs b/chain-impl-mockchain/src/environment.rs index f0b206a16..b07e41781 100644 --- a/chain-impl-mockchain/src/environment.rs +++ b/chain-impl-mockchain/src/environment.rs @@ -26,7 +26,7 @@ pub struct Environment { impl Environment { /// Create new environment. pub fn new() -> Self { - let g = StdGen::new(thread_rng(), 10); + let g = StdGen::new(rand::thread_rng(), 10); Environment { ledger: Ledger::new(HashMap::new()), gen: g,