From 99338a68f19bb00591be1851573d4bb941b753dc Mon Sep 17 00:00:00 2001 From: Aleksey Sidorov Date: Fri, 14 Feb 2020 17:01:57 +0300 Subject: [PATCH] Release 1.0.0-rc.1 (#155) --- .travis.yml | 5 +- CHANGELOG.md | 11 +- Cargo.toml | 24 ++- build.rs | 8 +- examples/btc_anchoring.rs | 2 +- guides/newbie.md | 43 ++-- .../exonum_btc_anchoring_plugin/plugin.py | 31 ++- src/api.rs | 200 ++++++++++++------ src/blockchain/errors.rs | 8 +- src/blockchain/mod.rs | 2 +- src/blockchain/schema.rs | 26 +-- src/blockchain/transactions.rs | 32 +-- src/btc/payload.rs | 4 +- src/btc/transaction.rs | 4 +- src/lib.rs | 2 +- src/proto/mod.rs | 6 +- src/proto/service.proto | 4 +- src/service.rs | 36 ++-- src/test_helpers/mod.rs | 104 +++++---- tests/api.rs | 7 +- tests/sync.rs | 26 +-- tests/testnet_tests.rs | 80 ++++--- 22 files changed, 377 insertions(+), 288 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8df765b..e438e09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ addons: - zlib1g-dev rust: - - 1.39.0 + - 1.40.0 cache: npm: true @@ -53,6 +53,9 @@ before_install: - export RUSTC_WRAPPER=sccache jobs: + allow_failures: + - name: publish-with-rust-1.36 + - name: deadlinks include: # Formatting & other lints that do not require compilation - name: lints diff --git a/CHANGELOG.md b/CHANGELOG.md index 48be94e..e817462 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [Unreleased] +## 1.0.0-rc.1 - 2020-02-13 -## 0.13.0-rc.2 - 2018-12-04 +### Breaking changes + +- Updated to the [Exonum 1.0.0-rc.1](https://github.com/exonum/exonum/releases/tag/v1.0.0-rc.1) + release with some minor changes (#155). + + - `api::TransactionProof` layout has been refined. + +## 0.13.0-rc.2 - 2019-12-04 ### Breaking changes diff --git a/Cargo.toml b/Cargo.toml index 83a493c..f09804b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "exonum-btc-anchoring" edition = "2018" -version = "0.13.0-rc.2" +version = "1.0.0-rc.1" authors = ["The Exonum Team "] homepage = "https://exonum.com/doc/advanced/bitcoin-anchoring/" repository = "https://github.com/exonum/exonum-btc-anchoring" @@ -22,17 +22,19 @@ bitcoincore-rpc = "0.7.0" btc-transaction-utils = "0.6" byteorder = "1.3" derive_more = "0.15" -exonum = "0.13.0-rc.2" -exonum-cli = "0.13.0-rc.2" -exonum-crypto = { version = "0.13.0-rc.2", features = ["with-protobuf"] } -exonum-derive = "0.13.0-rc.2" -exonum-merkledb = "0.13.0-rc.2" -exonum-proto = "0.13.0-rc.2" -exonum-supervisor = "0.13.0-rc.2" -exonum-testkit = "0.13.0-rc.2" +exonum = "1.0.0-rc.1" +exonum-cli = "1.0.0-rc.1" +exonum-crypto = { version = "1.0.0-rc.1", features = ["with-protobuf"] } +exonum-derive = "1.0.0-rc.1" +exonum-explorer = "1.0.0-rc.1" +exonum-merkledb = "1.0.0-rc.1" +exonum-proto = "1.0.0-rc.1" +exonum-rust-runtime = "1.0.0-rc.1" +exonum-supervisor = "1.0.0-rc.1" +exonum-testkit = "1.0.0-rc.1" failure = "0.1" failure_derive = "0.1" -hex = "0.4" +hex = "=0.4.0" # 0.4.1 is not compatible with Rust 1.36 jsonrpc = "0.11" log = "0.4" protobuf = { version = "2.8", features = ["with-serde"] } @@ -49,4 +51,4 @@ structopt = "0.3" proptest = "0.9" [build-dependencies] -exonum-build = "0.13.0-rc.2" +exonum-build = "1.0.0-rc.1" diff --git a/build.rs b/build.rs index 0d8bbf1..a854c92 100644 --- a/build.rs +++ b/build.rs @@ -12,15 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use exonum_build::{ProtoSources, ProtobufGenerator}; +use exonum_build::ProtobufGenerator; fn main() { ProtobufGenerator::with_mod_name("protobuf_mod.rs") .with_input_dir("src/proto") - .with_includes(&[ - "src/proto".into(), - ProtoSources::Exonum, - ProtoSources::Crypto, - ]) + .with_crypto() .generate(); } diff --git a/examples/btc_anchoring.rs b/examples/btc_anchoring.rs index e2ad9f2..a54a2c8 100644 --- a/examples/btc_anchoring.rs +++ b/examples/btc_anchoring.rs @@ -17,6 +17,6 @@ use exonum_cli::NodeBuilder; fn main() -> Result<(), failure::Error> { exonum::helpers::init_logger()?; NodeBuilder::new() - .with_service(exonum_btc_anchoring::BtcAnchoringService) + .with_rust_service(exonum_btc_anchoring::BtcAnchoringService) .run() } diff --git a/guides/newbie.md b/guides/newbie.md index 4894519..9ff476d 100644 --- a/guides/newbie.md +++ b/guides/newbie.md @@ -56,35 +56,22 @@ your account. ## Step 2. Compilation and Initial Run -- Be sure to have [`exonum_launcher`](https://github.com/popzxc/exonum-launcher) -installed via `pip` (see `exonum-launcher` README for details). +- Be sure to have [`exonum_launcher`](https://github.com/exonum/exonum-launcher) +installed via `pip3` (see `exonum-launcher` README for details). - Install `exonum_btc_anchoring_plugin` (if you are using `venv`, activate `venv` in which `exonum_launcher` is installed): ```sh - pip install -e exonum-btc-anchoring/launcher - ``` - -- Compile and install `btc_anchoring` example: - - ```sh - cd exonum-btc-anchoring - cargo install --path . --example btc_anchoring --force - ``` - -- Build the `exonum-btc-anchoring` project (it contains a binary that will be used later): - - ```sh - cargo build + pip3 install -e exonum-btc-anchoring/launcher ``` - Run the example: ```sh - RUST_LOG="exonum_btc_anchoring=info" btc_anchoring run-dev -a anchoring + RUST_LOG="exonum_btc_anchoring=info" cargo run --example btc_anchoring run-dev -a target/anchoring ``` - `-a anchoring` here denotes the directory in which the data of the node will be generated. + `-a target/anchoring` here denotes the directory in which the data of the node will be generated. ## Step 3. Deploying and Running @@ -97,10 +84,11 @@ installed via `pip` (see `exonum-launcher` README for details). To obtain `bitcoin_key`, go to the `exonum-btc-anchoring` directory and launch the following command: ```sh - cargo run -- generate-config -o anchoring/sync.toml --bitcoin-rpc-host http://localhost --bitcoin-rpc-user user --bitcoin-rpc-password password + cargo run -- generate-config -o target/anchoring/sync.toml --bitcoin-rpc-host http://localhost:18332 --bitcoin-rpc-user user --bitcoin-rpc-password password ``` - In the code above you should replace `anchoring` with the directory where the data of your node lies. + In the code above you should replace `target/anchoring` with the directory where the data of + your node lies. As a result of this call you will obtain `bitcoin_key`. - Create file `anchoring.yml` with the following contents: @@ -122,18 +110,19 @@ installed via `pip` (see `exonum-launcher` README for details). artifacts: anchoring: runtime: rust - name: "exonum-btc-anchoring:0.12.0" + name: "exonum-btc-anchoring" + version: "1.0.0-rc.1" instances: anchoring: artifact: anchoring config: - network: regtest + network: testnet anchoring_interval: 500 transaction_fee: 10 anchoring_keys: - - bitcoin_key: "0332ab15173cf9ff8ad0946fbd515434bf1f04bb46482453474b6c38b94fa9d7b7" - service_key: "2b89c8e1f3a6a8f18ac3276b87403e39c54c33d8275e9626ab9478df4d6d5bfc" + - bitcoin_key: "02d6086aaccc86e6a711ac84ff21a266684c17d188aa7c4eeab0c0f12133308584" + service_key: "850eb20eebe0b07cf2721ecc9c90aa465a96413dccafad11045a9cb8abf04ed0" ``` Replace `bitcoin_key` and `service_key` with values obtained in the previous step. @@ -177,7 +166,7 @@ setup a funding transaction. ```sh curl --header "Content-Type: application/json" \ --request POST \ - --data '"020000000001051aa3e2a53010c4863becf608fc9bfe1d06c7a2456c820de85ee9e65dc95bcc6300000000171600145058446ed566ce61f9511a28b5f35340bb7bfc9afeffffffbed3ee193e9b73d0b259d60a57cffc2310f228fce4658060e9dffd24514631400000000017160014475caf1c9227353fb8bc10252a634dffbf7d39e1feffffff2787ebd988bfeb449c1482c1877e0598c6d28ef2db426f7c8f83da219d0f23c20000000017160014475caf1c9227353fb8bc10252a634dffbf7d39e1feffffff53a188e10f648e97fa9a4ed84bb0355ad52a301643249217b05f8114e76f579c00000000171600145058446ed566ce61f9511a28b5f35340bb7bfc9afeffffff8676d4e58838387079589229344eea778d0b29d095407f42ecdcf423975537ea0000000017160014475caf1c9227353fb8bc10252a634dffbf7d39e1feffffff0200c817a8040000002200209959c79a416778463632a0a1164e312c5973865dbc9fa038c69fc9e5af71e3cae4c7052a010000001600148e8a01a394875fec3c51c3a8117f9b00b57286d5024730440220287b30723dc3ed88d06f0884611071ba7b547903c2937846befd51cb601701f502206f74e78aa2e74ee8494c22481c1b661f8fcc171f04dfce61ff05a2ab53cbb634012103a54ae66b63b6bdc2089f294d43611ee37deaaf346cffd16f23d5f521dbca757c02473044022012490b04d0623f25661445e9bbcbfc3491b2009763dafdfd15c109e0617278cd022027d9ac08e9877f01b7b6234bd921016b4f7135a32cc25abd2f19a8cddfa692e3012103a208fc3f46c7b815c3237636d21267c2610a975b03dffae657017ab33fa832bd024730440220203514c2473a1ccb3f5722c4afc4347e083669784700cd72d15316d3b8cef3d502207e1120d31288ee91e58499a37f41b88578556474e334d7327b3f47a983be95af012103a208fc3f46c7b815c3237636d21267c2610a975b03dffae657017ab33fa832bd02473044022005bbf3b54f16ccabd7d5d98d26597bc19ce4af14e4952fa571e7d5d83f4a284402202c54adde15a678f7ce57f723e0ea875681e9b5f13aeffcba3871135fc82401de012103a54ae66b63b6bdc2089f294d43611ee37deaaf346cffd16f23d5f521dbca757c02473044022052d4d19bcf133fdcdaf526c0ec0aca362dab5a75ead2fb33b2f7378ef3d8cbcb02206251d0d55680ab0f8463e964755c1a63791e7d6d637a84f907b82c94a18221a4012103a208fc3f46c7b815c3237636d21267c2610a975b03dffae657017ab33fa832bd48000000"' \ + --data '"0200000000010151a7dcd1c2829f9c0a93ae6b054e9777528e88e3e0403c4313cf8cf41b27d1730000000000feffffff0240420f0000000000220020f86c30b7ec3496572220f40b21096b74dc5182942b8811d1bb0b3ab21e52b1337007360000000000160014e16cbf1202193f7de0eb058e0dc2b57cbc63d4040247304402203e23349dcda80acc85e94ada52269baf09624afeb794b696fb53f0f37d130f850220599eaa9bb50d5e14269228f4f5d63826d5554275877b5ffd77eca3cd3b1c408e012102604e1c50f8bdaec165e0bc7b81e608709f510c5bf4b18b6aefaf3996317fd9cf77641900"' \ http://127.0.0.1:8081/api/services/anchoring/add-funds ``` @@ -193,10 +182,10 @@ setup a funding transaction. ```sh cd exonum-btc-anchoring - RUST_LOG="exonum_btc_anchoring=info" cargo run -- run --config anchoring/sync.toml + RUST_LOG="exonum_btc_anchoring=info" cargo run -- run --config target/anchoring/sync.toml ``` - `anchoring/` in the code above means the directory where `sync.toml` was generated earlier. + `target/anchoring/` in the code above means the directory where `sync.toml` was generated earlier. On the `regtest` it will exit with an error, since blocks should be mined manually. The log of the example will show that anchoring was made: diff --git a/launcher/exonum_btc_anchoring_plugin/plugin.py b/launcher/exonum_btc_anchoring_plugin/plugin.py index e2cd748..0ccbac1 100644 --- a/launcher/exonum_btc_anchoring_plugin/plugin.py +++ b/launcher/exonum_btc_anchoring_plugin/plugin.py @@ -8,20 +8,14 @@ from exonum_launcher.instances.instance_spec_loader import InstanceSpecLoader, InstanceSpecLoadError +RUST_RUNTIME_ID = 0 +ANCHORING_ARTIFACT_NAME = "exonum-btc-anchoring" +ANCHORING_ARTIFACT_VERSION = "1.0.0-rc.1" -def import_or_load_module(loader: ProtobufLoader, instance: Instance, module_name: str): - try: - # Try to load module (if it's already compiled) first. - module = ModuleManager.import_service_module( - instance.artifact.name, module_name) - return module - except (ModuleNotFoundError, ImportError): - # If it's not compiled, load & compile protobuf. - loader.load_service_proto_files( - instance.artifact.runtime_id, instance.artifact.name) - module = ModuleManager.import_service_module( - instance.artifact.name, "service") - return module + +def import_anchoring_module(name: str): + return ModuleManager.import_service_module( + ANCHORING_ARTIFACT_NAME, ANCHORING_ARTIFACT_VERSION, name) def bitcoin_network_from_string(network_string: str) -> int: @@ -38,10 +32,13 @@ class AnchoringInstanceSpecLoader(InstanceSpecLoader): def load_spec(self, loader: ProtobufLoader, instance: Instance) -> bytes: try: - service_module = import_or_load_module(loader, instance, "service") - btc_types_module = import_or_load_module( - loader, instance, "btc_types") - exonum_types_module = import_or_load_module(loader, instance, "types") + # Load proto files for the Exonum anchoring service: + loader.load_service_proto_files( + RUST_RUNTIME_ID, ANCHORING_ARTIFACT_NAME, ANCHORING_ARTIFACT_VERSION) + + service_module = import_anchoring_module("service") + btc_types_module = import_anchoring_module("btc_types") + exonum_types_module = import_anchoring_module("types") # Create config message config = service_module.Config() diff --git a/src/api.rs b/src/api.rs index 2249668..332ffca 100644 --- a/src/api.rs +++ b/src/api.rs @@ -12,19 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Anchoring HTTP API implementation. +//! Bitcoin Anchoring HTTP API. +//! +//! Anchoring API is divided into public and private parts, with public part intended for +//! unauthorized use, and private part intended to be used by [sync][sync] module. +//! Private part is implementation detail and should not be used directly. +//! +//! [sync]: ../sync/index.html use btc_transaction_utils::{p2wsh, TxInRef}; -use exonum::{ - blockchain::{BlockProof, IndexCoordinates, SchemaOrigin}, - crypto::Hash, - helpers::Height, - runtime::rust::{ - api::{self, ServiceApiBuilder, ServiceApiState}, - Transaction, - }, -}; +use exonum::{blockchain::BlockProof, crypto::Hash, helpers::Height}; use exonum_merkledb::{ListProof, MapProof}; +use exonum_rust_runtime::{ + api::{self, ServiceApiBuilder, ServiceApiState}, + Broadcaster, +}; use failure::ensure; use serde_derive::{Deserialize, Serialize}; @@ -34,7 +36,7 @@ use std::cmp::{ }; use crate::{ - blockchain::{AddFunds, Schema, SignInput, Transactions}, + blockchain::{AddFunds, BtcAnchoringInterface, Schema, SignInput}, btc, config::Config, }; @@ -43,11 +45,11 @@ use crate::{ #[derive(Debug, Serialize, Deserialize)] pub struct TransactionProof { /// Latest authorized block in the blockchain. - pub latest_authorized_block: BlockProof, + pub block_proof: BlockProof, /// Proof for the whole database table. - pub to_table: MapProof, + pub state_proof: MapProof, /// Proof for the specific transaction in this table. - pub to_transaction: ListProof, + pub transaction_proof: ListProof, } /// State of the next anchoring transaction proposal. @@ -89,7 +91,7 @@ impl AnchoringProposalState { Ok(AnchoringProposalState::InsufficientFunds { total_fee, balance }) } Some(Err(btc::BuilderError::NoInputs)) => Ok(AnchoringProposalState::NoInitialFunds), - Some(Err(e)) => Err(api::Error::InternalError(e.into())), + Some(Err(e)) => Err(api::Error::internal(e)), } } } @@ -107,27 +109,56 @@ impl From for AnchoringChainLength { } } -/// Public API specification for the Exonum Bitcoin anchoring service. +/// Public API part of the Exonum Bitcoin anchoring service. pub trait PublicApi { /// Error type for the current public API implementation. type Error; /// Returns an actual anchoring address. /// - /// `GET /{api_prefix}/address/actual` + /// | Property | Value | + /// |-------------|-------| + /// | Path | `/api/services/{btc_anchoring}/address/actual` | + /// | Method | GET | + /// | Query type | - | + /// | Return type | [`btc::Address`] | + /// + /// [`btc::Address`]: ../btc/struct.Address.html fn actual_address(&self) -> Result; /// Returns the following anchoring address if the node is in the transition state. /// - /// `GET /{api_prefix}/address/following` + /// | Property | Value | + /// |-------------|-------| + /// | Path | `/api/services/{btc_anchoring}/address/following` | + /// | Method | GET | + /// | Query type | - | + /// | Return type | [`Option`] | + /// + /// [`Option`]: ../btc/struct.Address.html fn following_address(&self) -> Result, Self::Error>; /// Returns the latest anchoring transaction if the height is not specified, /// otherwise, return the anchoring transaction with the height that is greater or equal /// to the given one. /// - /// `GET /{api_prefix}/find-transaction` + /// | Property | Value | + /// |-------------|-------| + /// | Path | `/api/services/{btc_anchoring}/find-transaction` | + /// | Method | GET | + /// | Query type | [`FindTransactionQuery`] | + /// | Return type | [`TransactionProof`] | + /// + /// [`FindTransactionQuery`]: struct.FindTransactionQuery.html + /// [`TransactionProof`]: struct.TransactionProof.html fn find_transaction(&self, height: Option) -> Result; /// Returns an actual anchoring configuration. /// - /// `GET /{api_prefix}/config` + /// | Property | Value | + /// |-------------|-------| + /// | Path | `/api/services/{btc_anchoring}/config` | + /// | Method | GET | + /// | Query type | - | + /// | Return type | [`Config`] | + /// + /// [`config`]: ../config/struct.Config.html fn config(&self) -> Result; } @@ -138,45 +169,91 @@ pub trait PrivateApi { /// Creates and broadcasts the `TxSignature` transaction, which is signed /// by the current node, and returns its hash. /// - /// `POST /{api_prefix}/sign-input` + /// | Property | Value | + /// |-------------|-------| + /// | Path | `/api/services/{btc_anchoring}/sign-input` | + /// | Method | POST | + /// | Query type | [`SignInput`] | + /// | Return type | [`Hash`] | + /// + /// [`SignInput`]: ../blockchain/struct.SignInput.html + /// [`Hash`]: https://docs.rs/exonum-crypto/latest/exonum_crypto/struct.Hash.html fn sign_input(&self, sign_input: SignInput) -> Result; /// Adds funds via suitable funding transaction. /// /// Bitcoin transaction should have output with value to the current anchoring address. /// The transaction will be applied if 2/3+1 anchoring nodes sent it. /// - /// `POST /{api_prefix}/add-funds` + /// | Property | Value | + /// |-------------|-------| + /// | Path | `/api/services/{btc_anchoring}/add-funds` | + /// | Method | POST | + /// | Query type | [`AddFunds`] | + /// | Return type | [`Hash`] | + /// + /// [`AddFunds`]: ../blockchain/struct.AddFunds.html + /// [`Hash`]: https://docs.rs/exonum-crypto/latest/exonum_crypto/struct.Hash.html fn add_funds(&self, transaction: btc::Transaction) -> Result; /// Returns a proposal for the next anchoring transaction, if it makes sense. /// If there is not enough satoshis to create a proposal an error is returned. /// - /// `GET /{api_prefix}/anchoring-proposal` + /// | Property | Value | + /// |-------------|-------| + /// | Path | `/api/services/{btc_anchoring}/anchoring-proposal` | + /// | Method | GET | + /// | Query type | - | + /// | Return type | [`AnchoringProposalState`] | + /// + /// [`AnchoringProposalState`]: enum.AnchoringProposalState.html fn anchoring_proposal(&self) -> Result; /// Returns an actual anchoring configuration. /// - /// `GET /{api_prefix}/config` + /// | Property | Value | + /// |-------------|-------| + /// | Path | `/api/services/{btc_anchoring}/config` | + /// | Method | GET | + /// | Query type | - | + /// | Return type | [`Config`] | + /// + /// [`config`]: ../config/struct.Config.html fn config(&self) -> Result; /// Returns an anchoring transaction with the specified index in anchoring transactions chain. /// - /// `GET /{api_prefix}/transaction?index={index}` + /// | Property | Value | + /// |-------------|-------| + /// | Path | `/api/services/{btc_anchoring}/transaction` | + /// | Method | GET | + /// | Query type | [`IndexQuery`] | + /// | Return type | [`Option`] | + /// + /// ['IndexQuery']: struct.IndexQuery.html + /// [`Option`]: ../btc/struct.Transaction.html fn transaction_with_index(&self, index: u64) -> Result, Self::Error>; /// Returns a total number of anchoring transactions in the chain. /// - /// `GET /{api_prefix}/transactions-count` + /// | Property | Value | + /// |-------------|-------| + /// | Path | `/api/services/{btc_anchoring}/transactions-count` | + /// | Method | GET | + /// | Query type | - | + /// | Return type | [`AnchoringChainLength`] | + /// + /// [`AnchoringChainLength`]: struct.AnchoringChainLength.html fn transactions_count(&self) -> Result; } struct ApiImpl<'a>(&'a ServiceApiState<'a>); impl<'a> ApiImpl<'a> { - fn broadcast_transaction( - &self, - transaction: impl Transaction, - ) -> Result { - self.0.generic_broadcaster().send(transaction) + fn broadcaster(&self) -> api::Result> { + self.0.broadcaster().ok_or_else(|| { + api::Error::bad_request() + .title("Invalid broadcast request") + .detail("Node is not a validator") + }) } - fn actual_config(&self) -> Result { + fn actual_config(&self) -> api::Result { Ok(Schema::new(self.0.service_data()).actual_config()) } @@ -228,24 +305,19 @@ impl<'a> ApiImpl<'a> { } fn transaction_proof(&self, tx_index: u64) -> TransactionProof { - let blockchain_schema = self.0.data().for_core(); - let max_height = blockchain_schema.block_hashes_by_height().len() - 1; - let latest_authorized_block = blockchain_schema - .block_and_precommits(Height(max_height)) + let proof = self + .0 + .data() + .proof_for_service_index("transactions_chain") .unwrap(); - - let tx_chain = Schema::new(self.0.service_data()).transactions_chain; - - let to_table = blockchain_schema - .state_hash_aggregator() - .get_proof(SchemaOrigin::Service(self.0.instance().id).coordinate_for(0)); - - let to_transaction = tx_chain.get_proof(tx_index); + let transaction_proof = Schema::new(self.0.service_data()) + .transactions_chain + .get_proof(tx_index); TransactionProof { - latest_authorized_block, - to_table, - to_transaction, + block_proof: proof.block_proof, + state_proof: proof.index_proof, + transaction_proof, } } } @@ -312,7 +384,7 @@ impl<'a> PublicApi for ApiImpl<'a> { } fn config(&self) -> Result { - self.actual_config().map_err(From::from) + self.actual_config().map_err(api::Error::internal) } } @@ -321,16 +393,27 @@ impl<'a> PrivateApi for ApiImpl<'a> { fn sign_input(&self, sign_input: SignInput) -> Result { // Verify Bitcoin signature. - self.verify_sign_input(&sign_input) - .map_err(|e| api::Error::BadRequest(e.to_string()))?; - self.broadcast_transaction(sign_input).map_err(From::from) + self.verify_sign_input(&sign_input).map_err(|e| { + api::Error::bad_request() + .title("Sign input request verification has failed") + .detail(e.to_string()) + })?; + + self.broadcaster()? + .sign_input((), sign_input) + .map_err(|e| api::Error::internal(e).title("Sign input request failed")) } fn add_funds(&self, transaction: btc::Transaction) -> Result { - self.verify_funding_tx(&transaction) - .map_err(|e| api::Error::BadRequest(e.to_string()))?; - self.broadcast_transaction(AddFunds { transaction }) - .map_err(From::from) + self.verify_funding_tx(&transaction).map_err(|e| { + api::Error::bad_request() + .title("Funding tx verification has failed") + .detail(e.to_string()) + })?; + + self.broadcaster()? + .add_funds((), AddFunds { transaction }) + .map_err(|e| api::Error::internal(e).title("Add funds request failed")) } fn anchoring_proposal(&self) -> Result { @@ -367,13 +450,6 @@ pub struct FindTransactionQuery { pub height: Option, } -/// Query parameters for the block header proof request. -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct HeightQuery { - /// Exonum block height. - pub height: Height, -} - /// Query parameters for the anchoring transaction request. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct IndexQuery { diff --git a/src/blockchain/errors.rs b/src/blockchain/errors.rs index 20fa4c0..f712c66 100644 --- a/src/blockchain/errors.rs +++ b/src/blockchain/errors.rs @@ -14,13 +14,13 @@ //! Error types of the BTC anchoring service. -use exonum::runtime::ExecutionError; -use exonum_derive::IntoExecutionError; +use exonum::runtime::{ExecutionError, ExecutionFail}; +use exonum_derive::ExecutionFail; use crate::btc; /// Possible errors during execution of the `sign_input` method. -#[derive(Debug, IntoExecutionError)] +#[derive(Debug, ExecutionFail)] pub enum Error { /// Transaction author is not authorized to sign anchoring transactions. UnauthorizedAnchoringKey = 0, @@ -41,6 +41,6 @@ pub enum Error { impl Error { /// Creates an error instance from the anchoring transaction builder error. pub fn anchoring_builder_error(error: btc::BuilderError) -> ExecutionError { - (Error::AnchoringBuilderError, error).into() + Error::AnchoringBuilderError.with_description(error) } } diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index fce36a3..55e4952 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -14,7 +14,7 @@ //! Blockchain implementation details for the BTC anchoring service. -pub use self::{schema::Schema, transactions::Transactions}; +pub use self::{schema::Schema, transactions::BtcAnchoringInterface}; pub use crate::proto::{AddFunds, SignInput}; use bitcoin::blockdata::script::Script; diff --git a/src/blockchain/schema.rs b/src/blockchain/schema.rs index c7cc3dc..9730825 100644 --- a/src/blockchain/schema.rs +++ b/src/blockchain/schema.rs @@ -14,11 +14,11 @@ //! Information schema for the btc anchoring service. -use exonum::{blockchain::Schema as CoreSchema, crypto::Hash, helpers::Height}; +use exonum::{blockchain::Schema as CoreSchema, helpers::Height}; use exonum_derive::FromAccess; use exonum_merkledb::{ - access::{Access, Prefixed}, - Entry, Fork, ObjectHash, ProofListIndex, ProofMapIndex, + access::{Access, FromAccess, RawAccessMut}, + Entry, ProofListIndex, ProofMapIndex, }; use log::{error, trace}; @@ -57,17 +57,9 @@ pub struct Schema { } impl Schema { - /// Returns object hashes of the stored tables. - pub fn state_hash(&self) -> Vec { - vec![ - self.transactions_chain.object_hash(), - self.spent_funding_transactions.object_hash(), - self.transaction_signatures.object_hash(), - self.actual_config.object_hash(), - self.following_config.object_hash(), - self.unconfirmed_funding_transactions.object_hash(), - self.unspent_funding_transaction.object_hash(), - ] + /// Returns a new schema instance. + pub fn new(access: T) -> Self { + Self::from_root(access).unwrap() } /// Returns an actual anchoring configuration. @@ -197,7 +189,11 @@ impl Schema { } } -impl Schema> { +impl Schema +where + T: Access, + T::Base: RawAccessMut, +{ /// Adds a finalized transaction to the tail of the anchoring transactions. pub(crate) fn push_anchoring_transaction(&mut self, tx: Transaction) { // An unspent funding transaction is always unconditionally added to the anchoring diff --git a/src/blockchain/transactions.rs b/src/blockchain/transactions.rs index c1e3e32..d4d7a5f 100644 --- a/src/blockchain/transactions.rs +++ b/src/blockchain/transactions.rs @@ -17,8 +17,9 @@ pub use crate::proto::{AddFunds, SignInput}; use btc_transaction_utils::{p2wsh::InputSigner, TxInRef}; -use exonum::runtime::{rust::CallContext, DispatcherError, ExecutionError}; -use exonum_derive::exonum_interface; +use exonum::runtime::{CommonError, ExecutionError, ExecutionFail}; +use exonum_derive::{exonum_interface, interface_method}; +use exonum_rust_runtime::ExecutionContext; use log::{info, trace}; use crate::{btc, config::Config, BtcAnchoringService}; @@ -47,7 +48,7 @@ impl SignInput { &public_key.0, self.input_signature.as_ref(), ) - .map_err(|e| (Error::InputVerificationFailed, e).into()) + .map_err(|e| Error::InputVerificationFailed.with_description(e)) } } @@ -85,22 +86,29 @@ impl TransactionConfirmations { /// Exonum BTC anchoring transactions. #[exonum_interface] -pub trait Transactions { +pub trait BtcAnchoringInterface { + /// Value output by the interface. + type Output; + /// Signs a single input of the anchoring transaction proposal. - fn sign_input(&self, context: CallContext<'_>, arg: SignInput) -> Result<(), ExecutionError>; + #[interface_method(id = 0)] + fn sign_input(&self, context: Ctx, arg: SignInput) -> Self::Output; /// Add funds via suitable funding transaction. /// /// Bitcoin transaction should have output with value to the current anchoring address. /// The transaction will be applied if 2/3+1 anchoring nodes sent it. - fn add_funds(&self, context: CallContext<'_>, arg: AddFunds) -> Result<(), ExecutionError>; + #[interface_method(id = 1)] + fn add_funds(&self, context: Ctx, arg: AddFunds) -> Self::Output; } -impl Transactions for BtcAnchoringService { - fn sign_input(&self, context: CallContext<'_>, arg: SignInput) -> Result<(), ExecutionError> { +impl BtcAnchoringInterface> for BtcAnchoringService { + type Output = Result<(), ExecutionError>; + + fn sign_input(&self, context: ExecutionContext<'_>, arg: SignInput) -> Self::Output { let author = context .caller() .author() - .ok_or(DispatcherError::UnauthorizedCaller)?; + .ok_or(CommonError::UnauthorizedCaller)?; let mut schema = Schema::new(context.service_data()); @@ -151,7 +159,7 @@ impl Transactions for BtcAnchoringService { // Check that we have not reached the quorum yet, otherwise we should not do anything. if input_signature_len < quorum { // Add signature to schema. - input_signatures.insert(anchoring_node_id, arg.input_signature.clone()); + input_signatures.insert(anchoring_node_id, arg.input_signature); schema .transaction_signatures .put(&input_id, input_signatures); @@ -195,11 +203,11 @@ impl Transactions for BtcAnchoringService { Ok(()) } - fn add_funds(&self, context: CallContext<'_>, arg: AddFunds) -> Result<(), ExecutionError> { + fn add_funds(&self, context: ExecutionContext<'_>, arg: AddFunds) -> Self::Output { let author = context .caller() .author() - .ok_or(DispatcherError::UnauthorizedCaller)?; + .ok_or(CommonError::UnauthorizedCaller)?; let mut schema = Schema::new(context.service_data()); // Check that author is authorized to sign inputs of the anchoring proposal. diff --git a/src/btc/payload.rs b/src/btc/payload.rs index 666699b..d6128ba 100644 --- a/src/btc/payload.rs +++ b/src/btc/payload.rs @@ -35,8 +35,8 @@ const PAYLOAD_V1_KIND_RECOVER: u8 = 1; /// /// | Position in bytes | Description | /// |-----------------------|---------------------------------------------------| -/// | 0..6 | ASCII-encoded prefix `EXONUM` | -/// | 6 | Version byte, currently is 1 | +/// | 0..6 | ASCII-encoded prefix `EXONUM` | +/// | 6 | Version byte, currently is 1 | /// | 7 | Payload kind: (0 is regular, 1 is recover) | /// | 8..16 | Block height | /// | 16..48 | Block hash | diff --git a/src/btc/transaction.rs b/src/btc/transaction.rs index 788d785..53b502e 100644 --- a/src/btc/transaction.rs +++ b/src/btc/transaction.rs @@ -513,8 +513,8 @@ mod tests { let mut builder = BtcAnchoringTransactionBuilder::new(&redeem_script); builder.additional_funds(funding_tx0.clone()).unwrap(); - builder.additional_funds(funding_tx1.clone()).unwrap(); - builder.additional_funds(funding_tx2.clone()).unwrap(); + builder.additional_funds(funding_tx1).unwrap(); + builder.additional_funds(funding_tx2).unwrap(); builder.fee(1); builder.payload(Height::zero(), funding_tx0.object_hash()); let (tx, inputs) = builder.create().unwrap(); diff --git a/src/lib.rs b/src/lib.rs index b15dd05..21514ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,7 @@ //! fn main() -> Result<(), failure::Error> { //! exonum::helpers::init_logger()?; //! NodeBuilder::new() -//! .with_service(exonum_btc_anchoring::BtcAnchoringService) +//! .with_rust_service(exonum_btc_anchoring::BtcAnchoringService) //! .run() //! } //! ``` diff --git a/src/proto/mod.rs b/src/proto/mod.rs index 8325c43..92b30eb 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -20,8 +20,10 @@ use bitcoin; use btc_transaction_utils; use exonum::{ crypto::{proto::*, Hash, PublicKey}, - impl_serde_hex_for_binary_value, - merkledb::{impl_object_hash_for_binary_value, BinaryKey, BinaryValue, ObjectHash}, + merkledb::{ + impl_object_hash_for_binary_value, impl_serde_hex_for_binary_value, BinaryKey, BinaryValue, + ObjectHash, + }, }; use exonum_derive::{BinaryValue, ObjectHash}; use exonum_proto::ProtobufConvert; diff --git a/src/proto/service.proto b/src/proto/service.proto index 89eed78..53b84ee 100644 --- a/src/proto/service.proto +++ b/src/proto/service.proto @@ -18,7 +18,7 @@ syntax = "proto3"; package exonum.service.btc_anchoring; -import "types.proto"; +import "exonum/crypto/types.proto"; import "btc_types.proto"; // Public keys of an anchoring node. @@ -55,7 +55,7 @@ message Config { // Testnet - 118034699(0x0709110B) // Regtest - 3669344250(0xDAB5BFFA) fixed32 network = 1; - // Bitcoin public keys of nodes from from which the current anchoring redeem script can + // Bitcoin public keys of nodes from from which the current anchoring redeem script can // be calculated. repeated AnchoringKeys anchoring_keys = 2; // Interval in blocks between anchored blocks. diff --git a/src/service.rs b/src/service.rs index 7e294cf..2658c28 100644 --- a/src/service.rs +++ b/src/service.rs @@ -13,37 +13,37 @@ // limitations under the License. use exonum::{ - crypto::Hash, helpers::ValidateInput, merkledb::BinaryValue, - runtime::{ - rust::{api::ServiceApiBuilder, CallContext, Service}, - BlockchainData, DispatcherError, ExecutionError, - }, + runtime::{CommonError, ExecutionContext, ExecutionError}, }; use exonum_derive::{ServiceDispatcher, ServiceFactory}; -use exonum_merkledb::Snapshot; +use exonum_rust_runtime::{api::ServiceApiBuilder, Service}; use exonum_supervisor::Configure; use crate::{ api, - blockchain::{Schema, Transactions}, + blockchain::{BtcAnchoringInterface, Schema}, config::Config, proto, }; /// Bitcoin anchoring service implementation for the Exonum blockchain. #[derive(ServiceFactory, ServiceDispatcher, Debug, Clone, Copy)] -#[service_dispatcher(implements("Transactions", "Configure"))] +#[service_dispatcher(implements("BtcAnchoringInterface", raw = "Configure"))] #[service_factory(proto_sources = "proto")] pub struct BtcAnchoringService; impl Service for BtcAnchoringService { - fn initialize(&self, context: CallContext<'_>, params: Vec) -> Result<(), ExecutionError> { + fn initialize( + &self, + context: ExecutionContext<'_>, + params: Vec, + ) -> Result<(), ExecutionError> { // TODO Use a special type for constructor. [ECR-3222] let config = Config::from_bytes(params.into()) .and_then(ValidateInput::into_validated) - .map_err(DispatcherError::malformed_arguments)?; + .map_err(CommonError::malformed_arguments)?; Schema::new(context.service_data()) .actual_config @@ -51,10 +51,6 @@ impl Service for BtcAnchoringService { Ok(()) } - fn state_hash(&self, data: BlockchainData<&dyn Snapshot>) -> Vec { - Schema::new(data.for_executing_service()).state_hash() - } - fn wire_api(&self, builder: &mut ServiceApiBuilder) { api::wire(builder); } @@ -65,28 +61,26 @@ impl Configure for BtcAnchoringService { fn verify_config( &self, - context: CallContext<'_>, + context: ExecutionContext<'_>, params: Self::Params, ) -> Result<(), ExecutionError> { context .caller() .as_supervisor() - .ok_or(DispatcherError::UnauthorizedCaller)?; + .ok_or(CommonError::UnauthorizedCaller)?; - params - .validate() - .map_err(DispatcherError::malformed_arguments) + params.validate().map_err(CommonError::malformed_arguments) } fn apply_config( &self, - context: CallContext<'_>, + context: ExecutionContext<'_>, params: Self::Params, ) -> Result<(), ExecutionError> { context .caller() .as_supervisor() - .ok_or(DispatcherError::UnauthorizedCaller)?; + .ok_or(CommonError::UnauthorizedCaller)?; let mut schema = Schema::new(context.service_data()); if schema.actual_config().anchoring_address() == params.anchoring_address() { diff --git a/src/test_helpers/mod.rs b/src/test_helpers/mod.rs index bd58c73..356c365 100644 --- a/src/test_helpers/mod.rs +++ b/src/test_helpers/mod.rs @@ -18,21 +18,16 @@ use bitcoin::{self, network::constants::Network}; use bitcoin_hashes::{sha256d::Hash as Sha256dHash, Hash as BitcoinHash}; use btc_transaction_utils::{p2wsh, TxInRef}; use exonum::{ - api, - blockchain::{ - config::InstanceInitParams, BlockProof, ConsensusConfig, IndexCoordinates, SchemaOrigin, - }, - crypto::{self, Hash, PublicKey}, + blockchain::{config::InstanceInitParams, BlockProof, ConsensusConfig}, + crypto::{Hash, KeyPair, PublicKey}, helpers::Height, keys::Keys, messages::{AnyTx, Verified}, - runtime::{ - rust::{ServiceFactory, Transaction}, - InstanceId, SnapshotExt, - }, + runtime::{InstanceId, SnapshotExt, SUPERVISOR_INSTANCE_ID}, }; use exonum_merkledb::{access::Access, MapProof, ObjectHash, Snapshot}; -use exonum_supervisor::{ConfigPropose, SimpleSupervisor}; +use exonum_rust_runtime::{api, ServiceFactory}; +use exonum_supervisor::{ConfigPropose, Supervisor, SupervisorInterface}; use exonum_testkit::{ApiKind, TestKit, TestKitApi, TestKitBuilder, TestNode}; use failure::{ensure, format_err}; use rand::{thread_rng, Rng}; @@ -44,7 +39,7 @@ use crate::{ AnchoringChainLength, AnchoringProposalState, FindTransactionQuery, IndexQuery, PrivateApi, PublicApi, TransactionProof, }, - blockchain::{AddFunds, Schema, SignInput}, + blockchain::{AddFunds, BtcAnchoringInterface, Schema, SignInput}, btc, config::Config, proto::AnchoringKeys, @@ -84,14 +79,9 @@ pub fn create_fake_funding_transaction(address: &btc::Address, value: u64) -> bt } fn gen_validator_keys() -> Keys { - let consensus_keypair = crypto::gen_keypair(); - let service_keypair = crypto::gen_keypair(); - Keys::from_keys( - consensus_keypair.0, - consensus_keypair.1, - service_keypair.0, - service_keypair.1, - ) + let consensus_keypair = KeyPair::random(); + let service_keypair = KeyPair::random(); + Keys::from_keys(consensus_keypair, service_keypair) } #[derive(Debug, Default)] @@ -173,16 +163,20 @@ impl AnchoringTestKit { let anchoring_artifact = BtcAnchoringService.artifact_id(); let inner = TestKitBuilder::validator() .with_keys(validator_keys) - .with_default_rust_service(SimpleSupervisor::new()) + // Supervisor + .with_rust_service(Supervisor) + .with_artifact(Supervisor.artifact_id()) + .with_instance(Supervisor::simple()) + // Anchoring .with_rust_service(BtcAnchoringService) .with_artifact(anchoring_artifact.clone()) .with_instance(InstanceInitParams::new( ANCHORING_INSTANCE_ID, ANCHORING_INSTANCE_NAME, - anchoring_artifact.into(), + anchoring_artifact, anchoring_config, )) - .create(); + .build(); Self { inner, @@ -227,13 +221,13 @@ impl AnchoringTestKit { let actual_config = schema.actual_state().actual_config().clone(); let bitcoin_key = actual_config - .find_bitcoin_key(&service_keypair.0) + .find_bitcoin_key(&service_keypair.public_key()) .unwrap() .1; let btc_private_key = self.anchoring_nodes.private_key(&bitcoin_key); let redeem_script = actual_config.redeem_script(); - let mut signer = p2wsh::InputSigner::new(redeem_script.clone()); + let mut signer = p2wsh::InputSigner::new(redeem_script); for (index, proposal_input) in proposal_inputs.iter().enumerate() { let signature = signer .sign_input( @@ -243,18 +237,14 @@ impl AnchoringTestKit { ) .unwrap(); - signatures.push( + signatures.push(service_keypair.sign_input( + ANCHORING_INSTANCE_ID, SignInput { input: index as u32, input_signature: signature.into(), txid: proposal.id(), - } - .sign( - ANCHORING_INSTANCE_ID, - service_keypair.0, - &service_keypair.1, - ), - ); + }, + )); } } Ok(signatures) @@ -300,15 +290,13 @@ impl AnchoringTestKit { self.actual_anchoring_config() .anchoring_keys .into_iter() - .map(|anchoring_keys| { + .map(move |anchoring_keys| { let node_keypair = self .find_node_by_service_key(anchoring_keys.service_key) .unwrap() .service_keypair(); - add_funds - .clone() - .sign(ANCHORING_INSTANCE_ID, node_keypair.0, &node_keypair.1) + node_keypair.add_funds(ANCHORING_INSTANCE_ID, add_funds.clone()) }) .collect() } @@ -317,12 +305,17 @@ impl AnchoringTestKit { pub fn create_config_change_tx(&self, proposal: ConfigPropose) -> Verified { let initiator_id = self.inner.network().us().validator_id().unwrap(); let keypair = self.inner.validator(initiator_id).service_keypair(); - proposal.sign_for_supervisor(keypair.0, &keypair.1) + keypair.propose_config_change(SUPERVISOR_INSTANCE_ID, proposal) } /// Adds a new auditor node to the testkit network and create Bitcoin keypair for it. pub fn add_node(&mut self) -> AnchoringKeys { - let service_key = self.inner.network_mut().add_node().service_keypair().0; + let service_key = self + .inner + .network_mut() + .add_node() + .service_keypair() + .public_key(); let bitcoin_key = self .anchoring_nodes .add_node(self.actual_anchoring_config().network, service_key); @@ -382,7 +375,7 @@ impl AnchoringTestKit { .network() .nodes() .iter() - .find(|node| node.service_keypair().0 == service_key) + .find(|node| node.service_keypair().public_key() == service_key) } } @@ -464,11 +457,11 @@ impl PrivateApi for TestKitApi { fn validate_table_proof( actual_config: &ConsensusConfig, - latest_authorized_block: &BlockProof, - to_table: MapProof, -) -> Result<(IndexCoordinates, Hash), failure::Error> { + block_proof: &BlockProof, + state_proof: MapProof, +) -> Result<(String, Hash), failure::Error> { // Checks precommits. - for precommit in &latest_authorized_block.precommits { + for precommit in &block_proof.precommits { let validator_id = precommit.as_ref().validator.0 as usize; let _validator_keys = actual_config .validator_keys @@ -480,19 +473,22 @@ fn validate_table_proof( ) })?; ensure!( - precommit.as_ref().block_hash() == &latest_authorized_block.block.object_hash(), + precommit.as_ref().block_hash() == &block_proof.block.object_hash(), "Block hash doesn't match" ); } // Checks state_hash. - let checked_table_proof = to_table.check()?; + let checked_table_proof = state_proof.check()?; ensure!( - checked_table_proof.index_hash() == *latest_authorized_block.block.state_hash(), + checked_table_proof.index_hash() == block_proof.block.state_hash, "State hash doesn't match" ); - let value = checked_table_proof.entries().map(|(a, b)| (*a, *b)).next(); - value.ok_or_else(|| format_err!("Unable to get `to_block_header` entry")) + let (table_name, table_hash) = checked_table_proof + .entries() + .next() + .ok_or_else(|| format_err!("Unable to get `to_block_header` entry"))?; + Ok((table_name.to_owned(), *table_hash)) } /// Proof validation extension. @@ -507,14 +503,16 @@ impl ValidateProof for TransactionProof { type Output = Option<(u64, btc::Transaction)>; fn validate(self, actual_config: &ConsensusConfig) -> Result { - let proof_entry = - validate_table_proof(actual_config, &self.latest_authorized_block, self.to_table)?; - let table_location = IndexCoordinates::new(SchemaOrigin::Service(ANCHORING_INSTANCE_ID), 0); + let proof_entry = validate_table_proof(actual_config, &self.block_proof, self.state_proof)?; + + ensure!( + proof_entry.0 == format!("{}.transactions_chain", ANCHORING_INSTANCE_NAME), + "Invalid table location" + ); - ensure!(proof_entry.0 == table_location, "Invalid table location"); // Validate value. let values = self - .to_transaction + .transaction_proof .check_against_hash(proof_entry.1) .map_err(|e| format_err!("An error occurred {:?}", e))? .entries(); diff --git a/tests/api.rs b/tests/api.rs index e72c2c7..a6145f0 100644 --- a/tests/api.rs +++ b/tests/api.rs @@ -67,6 +67,7 @@ fn actual_address() { .create_blocks_until(Height(anchoring_interval)); let anchoring_api = anchoring_testkit.inner.api(); + assert_eq!( anchoring_api.actual_address().unwrap(), anchoring_testkit @@ -105,7 +106,7 @@ fn following_address() { anchoring_testkit.inner.create_block_with_transaction( anchoring_testkit.create_config_change_tx( ConfigPropose::new(0, anchoring_testkit.inner.height().next()) - .service_config(ANCHORING_INSTANCE_ID, new_cfg.clone()), + .service_config(ANCHORING_INSTANCE_ID, new_cfg), ), ); anchoring_testkit.inner.create_block(); @@ -199,7 +200,7 @@ fn find_transaction_configuration_change() { anchoring_testkit.inner.create_block_with_transaction( anchoring_testkit.create_config_change_tx( ConfigPropose::new(0, anchoring_testkit.inner.height().next()) - .service_config(ANCHORING_INSTANCE_ID, new_cfg.clone()), + .service_config(ANCHORING_INSTANCE_ID, new_cfg), ), ); anchoring_testkit.inner.create_block(); @@ -368,7 +369,7 @@ fn sign_input() { let config = anchoring_testkit.actual_anchoring_config(); let bitcoin_public_key = config - .find_bitcoin_key(&anchoring_testkit.inner.us().service_keypair().0) + .find_bitcoin_key(&anchoring_testkit.inner.us().service_keypair().public_key()) .unwrap() .1; let bitcoin_private_key = anchoring_testkit.node_private_key(&bitcoin_public_key); diff --git a/tests/sync.rs b/tests/sync.rs index 41ba827..34ab373 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -12,14 +12,13 @@ // limitations under the License. use exonum::{ - crypto::{Hash, PublicKey, SecretKey}, + crypto::{Hash, KeyPair}, helpers::Height, merkledb::ObjectHash, - runtime::rust::Transaction, }; use exonum_btc_anchoring::{ api::{AnchoringChainLength, AnchoringProposalState, PrivateApi}, - blockchain::{AddFunds, SignInput}, + blockchain::{AddFunds, BtcAnchoringInterface, SignInput}, btc, config::Config, sync::{ @@ -28,6 +27,7 @@ use exonum_btc_anchoring::{ }, test_helpers::{get_anchoring_schema, AnchoringTestKit, ANCHORING_INSTANCE_ID}, }; +use exonum_rust_runtime::api; use exonum_testkit::TestKitApi; use std::{ @@ -124,7 +124,7 @@ impl BitcoinRelay for FakeBitcoinRelay { /// TODO Implement creating TestkitApi for an arbitrary TestNode. [ECR-3222] #[derive(Debug)] struct FakePrivateApi { - service_keypair: (PublicKey, SecretKey), + service_keypair: KeyPair, inner: TestKitApi, } @@ -143,25 +143,21 @@ impl FakePrivateApi { } impl PrivateApi for FakePrivateApi { - type Error = exonum::api::Error; + type Error = api::Error; fn sign_input(&self, sign_input: SignInput) -> Result { - let signed_tx = sign_input.sign( - ANCHORING_INSTANCE_ID, - self.service_keypair.0, - &self.service_keypair.1, - ); + let signed_tx = self + .service_keypair + .sign_input(ANCHORING_INSTANCE_ID, sign_input); let hash = signed_tx.object_hash(); self.inner.send(signed_tx); Ok(hash) } fn add_funds(&self, transaction: btc::Transaction) -> Result { - let signed_tx = AddFunds { transaction }.sign( - ANCHORING_INSTANCE_ID, - self.service_keypair.0, - &self.service_keypair.1, - ); + let signed_tx = self + .service_keypair + .add_funds(ANCHORING_INSTANCE_ID, AddFunds { transaction }); let hash = signed_tx.object_hash(); self.inner.send(signed_tx); Ok(hash) diff --git a/tests/testnet_tests.rs b/tests/testnet_tests.rs index 6e81886..11570f7 100644 --- a/tests/testnet_tests.rs +++ b/tests/testnet_tests.rs @@ -14,14 +14,11 @@ use exonum::helpers::Height; use exonum::{ - crypto, - explorer::CommittedTransaction, messages::{AnyTx, Verified}, - runtime::SnapshotExt, - runtime::{rust::Transaction, ErrorKind}, + runtime::{ErrorMatch, SnapshotExt}, }; use exonum_btc_anchoring::{ - blockchain::{errors::Error, SignInput}, + blockchain::{errors::Error, BtcAnchoringInterface, SignInput}, btc::{self, BuilderError}, config::Config, test_helpers::{ @@ -29,21 +26,27 @@ use exonum_btc_anchoring::{ ANCHORING_INSTANCE_ID, }, }; +use exonum_crypto::KeyPair; +use exonum_explorer::CommittedTransaction; use exonum_supervisor::ConfigPropose; -fn assert_tx_error(tx: &CommittedTransaction, e: impl Into) { - assert_eq!(tx.status().unwrap_err().kind, e.into(),); +fn assert_tx_error(tx: &CommittedTransaction, e: ErrorMatch) { + assert_eq!( + *tx.status().unwrap_err(), + e.for_service(ANCHORING_INSTANCE_ID) + ); } fn unspent_funding_transaction(anchoring_testkit: &AnchoringTestKit) -> Option { get_anchoring_schema(&anchoring_testkit.inner.snapshot()).unspent_funding_transaction() } -fn change_tx_signature( - tx: Verified, - keypair: (crypto::PublicKey, crypto::SecretKey), -) -> Verified { - Verified::from_value(tx.into_payload(), keypair.0, &keypair.1) +fn change_tx_signature(tx: Verified, keypair: &KeyPair) -> Verified { + Verified::from_value( + tx.into_payload(), + keypair.public_key(), + keypair.secret_key(), + ) } fn test_anchoring_config_change(mut config_change_predicate: F) -> AnchoringTestKit @@ -280,7 +283,10 @@ fn err_spent_funding() { .into_iter() .take(1), ); - assert_tx_error(&block[0], Error::AlreadyUsedFundingTx); + assert_tx_error( + &block[0], + ErrorMatch::from_fail(&Error::AlreadyUsedFundingTx), + ); // Reach the next anchoring height. anchoring_testkit @@ -365,7 +371,10 @@ fn no_anchoring_proposal() { let block = anchoring_testkit .inner .create_block_with_transactions(leftover_signatures); - assert_tx_error(&block[0], Error::UnexpectedProposalTxId); + assert_tx_error( + &block[0], + ErrorMatch::from_fail(&Error::UnexpectedProposalTxId), + ); } #[test] @@ -405,7 +414,10 @@ fn unexpected_anchoring_proposal() { let block = anchoring_testkit .inner .create_block_with_transactions(leftover_signatures); - assert_tx_error(&block[0], Error::UnexpectedProposalTxId); + assert_tx_error( + &block[0], + ErrorMatch::from_fail(&Error::UnexpectedProposalTxId), + ); } #[test] @@ -463,7 +475,7 @@ fn add_anchoring_node_insufficient_funds() { anchoring_testkit.inner.create_block_with_transaction( anchoring_testkit.create_config_change_tx( ConfigPropose::new(0, anchoring_testkit.inner.height().next()) - .service_config(ANCHORING_INSTANCE_ID, new_cfg.clone()), + .service_config(ANCHORING_INSTANCE_ID, new_cfg), ), ); anchoring_testkit.inner.create_block(); @@ -510,7 +522,10 @@ fn funding_tx_err_unsuitable() { let block = anchoring_testkit.inner.create_block_with_transactions( anchoring_testkit.create_funding_confirmation_txs_with(funding_tx), ); - assert_tx_error(&block[0], Error::UnsuitableFundingTx); + assert_tx_error( + &block[0], + ErrorMatch::from_fail(&Error::UnsuitableFundingTx), + ); } #[test] @@ -545,10 +560,13 @@ fn sign_input_err_unauthorized() { .unwrap()[0] .clone(); // Re-sign this transaction by the other keypair. - let malformed_tx = change_tx_signature(tx, crypto::gen_keypair()); + let malformed_tx = change_tx_signature(tx, &KeyPair::random()); // Commit this transaction and check status. let block = testkit.inner.create_block_with_transaction(malformed_tx); - assert_tx_error(&block[0], Error::UnauthorizedAnchoringKey); + assert_tx_error( + &block[0], + ErrorMatch::from_fail(&Error::UnauthorizedAnchoringKey), + ); } #[test] @@ -557,10 +575,13 @@ fn add_funds_err_unauthorized() { // Create add_funds transaction from the anchoring node. let tx = testkit.create_funding_confirmation_txs(100).0[0].clone(); // Re-sign this transaction by the other keypair. - let malformed_tx = change_tx_signature(tx, crypto::gen_keypair()); + let malformed_tx = change_tx_signature(tx, &KeyPair::random()); // Commit this transaction and check status. let block = testkit.inner.create_block_with_transaction(malformed_tx); - assert_tx_error(&block[0], Error::UnauthorizedAnchoringKey); + assert_tx_error( + &block[0], + ErrorMatch::from_fail(&Error::UnauthorizedAnchoringKey), + ); } #[test] @@ -573,13 +594,12 @@ fn sing_input_err_no_such_input() { .parse::() .unwrap(); // Change input ID. - let malformed_tx = { - let keypair = us.service_keypair(); - SignInput { input: 10, ..tx }.sign(ANCHORING_INSTANCE_ID, keypair.0, &keypair.1) - }; + let malformed_tx = us + .service_keypair() + .sign_input(ANCHORING_INSTANCE_ID, SignInput { input: 10, ..tx }); // Commit this transaction and check status. let block = testkit.inner.create_block_with_transaction(malformed_tx); - assert_tx_error(&block[0], Error::NoSuchInput); + assert_tx_error(&block[0], ErrorMatch::from_fail(&Error::NoSuchInput)); } #[test] @@ -592,10 +612,14 @@ fn sign_input_err_input_verification_failed() { // Create sign_input transaction for the first anchoring node. let tx = testkit.create_signature_tx_for_node(&first_node).unwrap()[0].clone(); // Re-sign this transaction by the second anchoring node. - let malformed_tx = change_tx_signature(tx, second_node.service_keypair()); + let malformed_tx = change_tx_signature(tx, &second_node.service_keypair()); // Commit this transaction and check status. let block = testkit.inner.create_block_with_transaction(malformed_tx); - assert_tx_error(&block[0], Error::InputVerificationFailed); + assert_tx_error( + &block[0], + ErrorMatch::from_fail(&Error::InputVerificationFailed) + .with_description_containing("secp: signature failed verification"), + ); } // TODO Implement tests for anchoring recovery [ECR-3581]