diff --git a/.circleci/config.yml b/.circleci/config.yml index 7867024..9e05c8f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,6 +34,7 @@ jobs: cargo build --features=yubihsm-server cargo build --features=ledgertm cargo build --features=yubihsm-server,ledgertm,softsign + cd tendermint-rs && cargo build --no-default-features && cargo build --features=config && cargo build --features=rpc - run: name: build --release command: | diff --git a/Cargo.lock b/Cargo.lock index dfb35e0..a55a85b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,6 +124,15 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "safemem 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bit-set" version = "0.2.0" @@ -524,6 +533,29 @@ dependencies = [ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hyper" +version = "0.10.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -569,6 +601,11 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lazy_static" version = "1.4.0" @@ -634,6 +671,14 @@ dependencies = [ "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "log" version = "0.4.8" @@ -652,6 +697,14 @@ name = "memchr" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "mime" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nix" version = "0.13.1" @@ -693,6 +746,14 @@ dependencies = [ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num_cpus" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "once_cell" version = "0.1.8" @@ -1034,6 +1095,11 @@ name = "ryu" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "safemem" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "scopeguard" version = "0.3.3" @@ -1336,7 +1402,6 @@ dependencies = [ [[package]] name = "tendermint" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1344,6 +1409,7 @@ dependencies = [ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "hkdf 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.10.16 (registry+https://github.com/rust-lang/crates.io-index)", "prost-amino 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "prost-amino-derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1357,6 +1423,7 @@ dependencies = [ "subtle-encoding 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "tai64 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", "x25519-dalek 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1444,7 +1511,7 @@ dependencies = [ "subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "subtle-encoding 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tendermint 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tendermint 0.10.0", "tiny-bip39 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "yubihsm 0.26.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1459,11 +1526,29 @@ dependencies = [ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "traitobject" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "typeable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "typenum" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicase" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-bidi" version = "0.3.4" @@ -1513,6 +1598,11 @@ dependencies = [ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "void" version = "1.0.2" @@ -1672,6 +1762,7 @@ dependencies = [ "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" "checksum backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5" "checksum backtrace-sys 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "5b3a000b9c543553af61bc01cbfc403b04b5caa9e421033866f2e98061eb3e61" +"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" "checksum bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6e1e6fb1c9e3d6fcdec57216a74eaa03e41f52a22f13a16438251d8e88b89da" "checksum bit-vec 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a4523a10839ffae575fb08aa3423026c8cb4687eef43952afb956229d4f246f7" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" @@ -1721,12 +1812,15 @@ dependencies = [ "checksum hkd32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ec8449a5d1c7ea34a75bc45e91097d2768dfb056f37fa55a465f3c3ba7c0721" "checksum hkdf 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "35e8f9d776bbe83f1ff24951f7cc19140fb7ff8d0378463c4c4955f6b0d3e503" "checksum hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +"checksum hyper 0.10.16 (registry+https://github.com/rust-lang/crates.io-index)" = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" "checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" "checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum ledger 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6c9a2f47929b010a64a4bf9cdfe03b0d02175d44db0b91e16283f5a4a731d52c" "checksum ledger-tendermint 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "798701caf0fe437d7d72939eed5f18ae187e9c414eb5f4e9b8eeceaa16f99a23" @@ -1734,13 +1828,16 @@ dependencies = [ "checksum libusb 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5f990ddd929cbe53de4ecd6cf26e1f4e0c5b9796e4c629d9046570b03738aa53" "checksum libusb-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4c53b6582563d64ad3e692f54ef95239c3ea8069e82c9eb70ca948869a7ad767" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" +"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" "checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" "checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" "checksum once_cell 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "532c29a261168a45ce28948f9537ddd7a5dd272cc513b3017b1e82a88f962c37" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" @@ -1781,6 +1878,7 @@ dependencies = [ "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +"checksum safemem 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b08423011dae9a5ca23f07cf57dac3857f5c885d352b76f6d95f4aea9434d0" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum secp256k1 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1f2c586f44331cd22561405885bb55d869e042715f29d019dbe68558b4f7ec" "checksum secrecy 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb052cf770a381fa9a6ee63038ff9a0b11d30abb53be970672e950649ff0bfb" @@ -1814,14 +1912,16 @@ dependencies = [ "checksum tai64 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f442aa71b3f67bc82699063a608b32331ab5b0fcdd078694d3d4b84716b0942" "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum tendermint 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57b5925a210a2349c18a0bba413507bcd533926f34e1ee5eb730145b55f59f9d" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum tiny-bip39 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c1c5676413eaeb1ea35300a0224416f57abc3bd251657e0fafc12c47ff98c060" "checksum tiny_http 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1661fa0a44c95d01604bd05c66732a446c657efb62b5164a7a083a3b552b4951" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" +"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" +"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" @@ -1829,6 +1929,7 @@ dependencies = [ "checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" "checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" diff --git a/Cargo.toml b/Cargo.toml index 439379d..6b6349d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ categories = ["cryptography"] keywords = ["cosmos", "ed25519", "kms", "key-management", "yubihsm"] edition = "2018" +[workspace] +members = [".", "tendermint-rs"] + [badges] circle-ci = { repository = "tendermint/kms" } @@ -46,6 +49,7 @@ zeroize = "1" [dependencies.tendermint] version = "0.10" +path = "tendermint-rs" features = ["amino-types", "config", "secret-connection"] [dev-dependencies] diff --git a/tendermint-rs/CHANGES.md b/tendermint-rs/CHANGES.md new file mode 100644 index 0000000..39426a1 --- /dev/null +++ b/tendermint-rs/CHANGES.md @@ -0,0 +1,130 @@ +## [0.10.0] (2019-07-30) + +This release is tested against [tendermint v0.31] and known to be compatible +with [tendermint v0.32] aside from one known issue impacting RPC ([#286]). + +- Fix inclusive range incompatibility affecting Rust nightly ([#326]) +- Derive Eq/Ord for (transitive) status types ([#324]) +- Add `TendermintConfig::load_node_key` ([#315]) +- Add `TendermintConfig::load_genesis_file` ([#312]) +- Add `TendermintConfig` and `Error(Kind)` types ([#298]) +- Support `/abci_query` RPC endpoint ([#296]) +- Implement the Tendermint (RFC6962) Merkle tree ([#292]) +- Support `account::Id` generation from ed25519 pubkeys ([#291]) + +## [0.9.0] (2019-06-24) + +This release is compatible with [tendermint v0.31] + +- Reject low order points in Secret Connection handshake ([#279]) +- Add `RemoteErrorCode` enum ([#272]) +- Add `msg_type()` accessor for signature types ([#271]) + +## [0.8.0] (2019-06-20) + +This release is compatible with [tendermint v0.31] + +- `/block_results` RPC endpoint and related types ([#267], [#268]) +- Upgrade to Signatory v0.12 ([#259]) + +## [0.7.0] (2019-04-24) + +This release is compatible with [tendermint v0.31] + +- Initial JSONRPC over HTTP client + `/broadcast_tx_*` endpoints ([#243]) +- Initial RPC support ([#235]) +- Disallow a block height of 0 ([#234]) + +## [0.6.0] (2019-04-16) + +This release is compatible with [tendermint v0.31] + +- Add `tendermint::Address`, `tendermint::account::Id`, `tendermint::Moniker`, + and improve `serde` serializer support ([#228]). + +## [0.5.0] (2019-03-13) + +This release is compatible with [tendermint v0.30] + +- Rename `SecretConnectionKey` to `secret_connection::PublicKey`, add + `secret_connection::PeerId` ([#219]) +- Move `ConsensusState` under `chain::state` ([#205]) + +## 0.4.0 (N/A) + +- Skipped to synchronize versions with `tmkms` + +## 0.3.0 (2019-03-05) + +- Support for secp256k1 keys ([#181]) + +## 0.2.0 (2019-01-23) + +This release is compatible with [tendermint v0.29] + +- Update to x25519-dalek v0.4.4 (#158) +- Consistent ordering of `BlockID` and `Timestamps` in vote and proposal messages (#159) +- Remove `PoisonPillMsg` previously used to shut-down the kms (#162) + +## 0.1.5 (2019-01-18) + +This release is compatible with [tendermint v0.28] + +- Split `PubKeyMsg` into `PubKeyRequest` and `PubKeyResponse` (#141) +- Migrate to Rust 2018 edition (#138) + +## 0.1.4 (2018-12-02) + +- Allow empty BlockIds in validation method (#131) + +## 0.1.3 (2018-12-01) + +- Prefix bech32 encoding of consensus keys with amino prefix (#128) + +## 0.1.2 (2018-11-27) + +- Update to subtle-encoding v0.3 (#124) +- Introduce same validation logic as Tendermint (#110) +- Remove heartbeat (#105) + +## 0.1.1 (2018-11-20) + +- Minor clarifications/fixes (#103) + +## 0.1.0 (2018-11-13) + +- Initial release + +[0.10.0]: https://github.com/tendermint/kms/pull/328 +[tendermint v0.32]: https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#v0320 +[#326]: https://github.com/tendermint/kms/pull/326 +[#324]: https://github.com/tendermint/kms/pull/324 +[#315]: https://github.com/tendermint/kms/pull/315 +[#312]: https://github.com/tendermint/kms/pull/312 +[#298]: https://github.com/tendermint/kms/pull/298 +[#296]: https://github.com/tendermint/kms/pull/296 +[#292]: https://github.com/tendermint/kms/pull/292 +[#291]: https://github.com/tendermint/kms/pull/291 +[#286]: https://github.com/tendermint/kms/pull/286 +[0.9.0]: https://github.com/tendermint/kms/pull/280 +[#279]: https://github.com/tendermint/kms/pull/279 +[#272]: https://github.com/tendermint/kms/pull/272 +[#271]: https://github.com/tendermint/kms/pull/271 +[0.8.0]: https://github.com/tendermint/kms/pull/269 +[#268]: https://github.com/tendermint/kms/pull/268 +[#267]: https://github.com/tendermint/kms/pull/267 +[#259]: https://github.com/tendermint/kms/pull/259 +[0.7.0]: https://github.com/tendermint/kms/pull/247 +[#243]: https://github.com/tendermint/kms/pull/243 +[#235]: https://github.com/tendermint/kms/pull/235 +[#234]: https://github.com/tendermint/kms/pull/234 +[0.6.0]: https://github.com/tendermint/kms/pull/229 +[tendermint v0.31]: https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#v0310 +[#228]: https://github.com/tendermint/kms/pull/228 +[0.5.0]: https://github.com/tendermint/kms/pull/220 +[tendermint v0.30]: https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#v0300 +[#219]: https://github.com/tendermint/kms/pull/219 +[#205]: https://github.com/tendermint/kms/pull/219 +[#181]: https://github.com/tendermint/kms/pull/181 +[tendermint v0.29]: https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#v0290 +[tendermint v0.28]: https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md#v0280 diff --git a/tendermint-rs/Cargo.toml b/tendermint-rs/Cargo.toml new file mode 100644 index 0000000..3b44c57 --- /dev/null +++ b/tendermint-rs/Cargo.toml @@ -0,0 +1,76 @@ +[package] +name = "tendermint" +version = "0.10.0" # Also update `html_root_url` in lib.rs when bumping this +license = "Apache-2.0" +homepage = "https://www.tendermint.com/" +repository = "https://github.com/tendermint/kms/tree/master/crates/tendermint" +readme = "README.md" +categories = ["cryptography", "database"] +keywords = ["blockchain", "bft", "consensus", "cosmos", "tendermint"] +edition = "2018" + +description = """ + Tendermint is a high-performance blockchain consensus engine that powers + Byzantine fault tolerant applications written in any programming language. + This crate provides core types for representing information about Tendermint + blockchain networks, including chain information types, secret connections, + and remote procedure calls (JSONRPC). + """ + +authors = [ + "Ismail Khoffi ", + "ValarDragon ", + "Tony Arcieri ", + "Thane Thomson " +] + +[badges] +circle-ci = { repository = "tendermint/kms" } + +[dependencies] +byteorder = { version = "1.2", optional = true } +bytes = "0.4" +chrono = { version = "0.4", features = ["serde"] } +digest = "0.8" +failure = "0.1" +hkdf = { version = "0.7", optional = true } +hyper = { version = "0.10", optional = true } +prost-amino = { version = "0.4.0", optional = true } +prost-amino-derive = { version = "0.4.0", optional = true } +rand_os = { version = "0.1", optional = true } +ring = { version = "0.14", optional = true } +serde = { version = "1", optional = true, features = ["derive"] } +serde_json = { version = "1", optional = true } +signatory = { version = "0.12", features = ["ed25519", "ecdsa"] } +signatory-dalek = { version = "0.12", optional = true } +sha2 = { version = "0.8", default-features = false } +subtle = "2" +subtle-encoding = { version = "0.3", features = ["bech32-preview"] } +tai64 = { version = "2", optional = true, features = ["chrono"] } +toml = { version = "0.5", optional = true } +uuid = { version = "0.7", optional = true, default-features = false } +x25519-dalek = { version = "0.5", optional = true, default-features = false, features = ["u64_backend"] } +zeroize = { version = "0.9", optional = true } + +[dev-dependencies] +serde_json = "1" + +[features] +default = ["serde", "tai64"] +amino-types = ["prost-amino", "prost-amino-derive"] +config = ["serde", "serde_json", "toml", "zeroize"] +keys = ["signatory-dalek"] +rpc = ["hyper", "rand_os", "serde", "serde_json", "uuid"] +secret-connection = [ + "amino-types", + "byteorder", + "rand_os", + "hkdf", + "ring", + "signatory-dalek", + "x25519-dalek", + "zeroize" +] + +[package.metadata.docs.rs] +all-features = true diff --git a/tendermint-rs/README.md b/tendermint-rs/README.md index 74dbde2..2a0d40d 100644 --- a/tendermint-rs/README.md +++ b/tendermint-rs/README.md @@ -1,5 +1,49 @@ -# MOVED +# tendermint.rs -`tendermint-rs` is now located in its own repository at: +[![Crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +[![Apache 2.0 Licensed][license-image]][license-link] +![Rust 1.35+][rustc-image] -https://github.com/interchainio/tendermint-rs +Rust crate for interacting with [Tendermint]: a high-performance blockchain +consensus engine that powers Byzantine fault tolerant applications written +in any programming language. + +[Documentation][docs-link] + +## Requirements + +- Rust 1.35+ + +## License + +Copyright © 2018-2019 Tendermint + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/tendermint.svg +[crate-link]: https://crates.io/crates/tendermint +[docs-image]: https://docs.rs/tendermint/badge.svg +[docs-link]: https://docs.rs/tendermint/ +[build-image]: https://circleci.com/gh/tendermint/kms.svg?style=shield +[build-link]: https://circleci.com/gh/tendermint/kms +[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg +[license-link]: https://github.com/tendermint/kms/blob/master/LICENSE +[rustc-image]: https://img.shields.io/badge/rustc-1.35+-blue.svg + +[//]: # (general links) + +[Tendermint]: https://github.com/tendermint/tendermint diff --git a/tendermint-rs/src/abci.rs b/tendermint-rs/src/abci.rs new file mode 100644 index 0000000..79efc57 --- /dev/null +++ b/tendermint-rs/src/abci.rs @@ -0,0 +1,34 @@ +//! Application BlockChain Interface (ABCI) +//! +//! NOTE: This module contains types for ABCI responses as consumed from RPC +//! endpoints. It does not contain an ABCI protocol implementation. +//! +//! For that, see: +//! +//! + +#[cfg(feature = "rpc")] +mod code; +#[cfg(feature = "rpc")] +mod data; +#[cfg(feature = "rpc")] +mod gas; +#[cfg(feature = "rpc")] +mod info; +#[cfg(feature = "rpc")] +mod log; +#[cfg(feature = "rpc")] +mod path; +#[cfg(feature = "rpc")] +mod proof; +#[cfg(feature = "rpc")] +mod responses; +#[cfg(any(feature = "config", feature = "rpc"))] +pub mod tag; +pub mod transaction; + +#[cfg(feature = "rpc")] +pub use self::{ + code::Code, data::Data, gas::Gas, info::Info, log::Log, path::Path, proof::Proof, + responses::Responses, transaction::Transaction, +}; diff --git a/tendermint-rs/src/abci/code.rs b/tendermint-rs/src/abci/code.rs new file mode 100644 index 0000000..45d6b37 --- /dev/null +++ b/tendermint-rs/src/abci/code.rs @@ -0,0 +1,71 @@ +use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; + +/// ABCI application response codes. +/// +/// These presently use 0 for success and non-zero for errors: +/// +/// +/// +/// Note that in the future there may potentially be non-zero success codes. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Code { + /// Success + Ok, + + /// Error codes + Err(u32), +} + +impl Code { + /// Was the response OK? + pub fn is_ok(self) -> bool { + match self { + Code::Ok => true, + Code::Err(_) => false, + } + } + + /// Was the response an error? + pub fn is_err(self) -> bool { + !self.is_ok() + } + + /// Get the integer error value for this code + pub fn value(self) -> u32 { + u32::from(self) + } +} + +impl From for Code { + fn from(value: u32) -> Code { + match value { + 0 => Code::Ok, + err => Code::Err(err), + } + } +} + +impl From for u32 { + fn from(code: Code) -> u32 { + match code { + Code::Ok => 0, + Code::Err(err) => err, + } + } +} + +impl<'de> Deserialize<'de> for Code { + fn deserialize>(deserializer: D) -> Result { + Ok(Code::from( + String::deserialize(deserializer)? + .parse::() + .map_err(|e| D::Error::custom(format!("{}", e)))?, + )) + } +} + +impl Serialize for Code { + fn serialize(&self, serializer: S) -> Result { + self.value().serialize(serializer) + } +} diff --git a/tendermint-rs/src/abci/data.rs b/tendermint-rs/src/abci/data.rs new file mode 100644 index 0000000..a52a6e9 --- /dev/null +++ b/tendermint-rs/src/abci/data.rs @@ -0,0 +1,64 @@ +use crate::{Error, ErrorKind}; +use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; +use subtle_encoding::hex; + +/// ABCI transaction data. +/// +/// Transactions are opaque binary blobs which are validated according to +/// application-specific rules. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Data(Vec); + +impl Data { + /// Borrow the data as bytes + pub fn as_bytes(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl AsRef<[u8]> for Data { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Display for Data { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in &self.0 { + write!(f, "{:02X}", byte)?; + } + Ok(()) + } +} + +impl FromStr for Data { + type Err = Error; + + fn from_str(s: &str) -> Result { + // Accept either upper or lower case hex + let bytes = hex::decode_upper(s) + .or_else(|_| hex::decode(s)) + .map_err(|_| ErrorKind::Parse)?; + + Ok(Data(bytes)) + } +} + +impl<'de> Deserialize<'de> for Data { + fn deserialize>(deserializer: D) -> Result { + let bytes = hex::decode(String::deserialize(deserializer)?.as_bytes()) + .map_err(|e| D::Error::custom(format!("{}", e)))?; + + Ok(Self(bytes)) + } +} + +impl Serialize for Data { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} diff --git a/tendermint-rs/src/abci/gas.rs b/tendermint-rs/src/abci/gas.rs new file mode 100644 index 0000000..ec59e55 --- /dev/null +++ b/tendermint-rs/src/abci/gas.rs @@ -0,0 +1,65 @@ +//! Gas: abstract representation for the cost of resources used by nodes when +//! processing transactions. +//! +//! For more information, see: +//! +//! + +use crate::{Error, ErrorKind}; +use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; + +/// Gas: representation of transaction processing resource costs +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] +pub struct Gas(u64); + +impl Gas { + /// Get the inner integer value + pub fn value(self) -> u64 { + self.0 + } +} + +impl From for Gas { + fn from(amount: u64) -> Gas { + Gas(amount) + } +} + +impl From for u64 { + fn from(gas: Gas) -> u64 { + gas.0 + } +} + +impl Display for Gas { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl FromStr for Gas { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Self::from(s.parse::().map_err(|_| ErrorKind::Parse)?)) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Gas { + fn deserialize>(deserializer: D) -> Result { + Ok(Self::from_str(&String::deserialize(deserializer)?) + .map_err(|e| D::Error::custom(format!("{}", e)))?) + } +} + +#[cfg(feature = "serde")] +impl Serialize for Gas { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} diff --git a/tendermint-rs/src/abci/info.rs b/tendermint-rs/src/abci/info.rs new file mode 100644 index 0000000..48c700f --- /dev/null +++ b/tendermint-rs/src/abci/info.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +/// ABCI info +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct Info(String); + +impl AsRef for Info { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl Display for Info { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/tendermint-rs/src/abci/log.rs b/tendermint-rs/src/abci/log.rs new file mode 100644 index 0000000..91c6275 --- /dev/null +++ b/tendermint-rs/src/abci/log.rs @@ -0,0 +1,28 @@ +#[cfg(feature = "serde_json")] +use crate::{Error, ErrorKind}; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +/// ABCI log data +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct Log(String); + +impl Log { + /// Parse the log data as JSON, returning a `serde_json::Value` + #[cfg(feature = "serde_json")] + pub fn parse_json(&self) -> Result { + serde_json::from_str(&self.0).map_err(|_| ErrorKind::Parse.into()) + } +} + +impl AsRef for Log { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl Display for Log { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/tendermint-rs/src/abci/path.rs b/tendermint-rs/src/abci/path.rs new file mode 100644 index 0000000..09f63be --- /dev/null +++ b/tendermint-rs/src/abci/path.rs @@ -0,0 +1,26 @@ +//! Paths to ABCI data + +use crate::error::Error; +use serde::{Deserialize, Serialize}; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; + +/// Path to ABCI data +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct Path(String); + +impl Display for Path { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +impl FromStr for Path { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Path(s.to_owned())) + } +} diff --git a/tendermint-rs/src/abci/proof.rs b/tendermint-rs/src/abci/proof.rs new file mode 100644 index 0000000..8429da8 --- /dev/null +++ b/tendermint-rs/src/abci/proof.rs @@ -0,0 +1,51 @@ +//! ABCI Merkle proofs + +use crate::error::Error; +use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; +use subtle_encoding::{Encoding, Hex}; + +/// ABCI Merkle proofs +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Proof(Vec); + +impl AsRef<[u8]> for Proof { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Display for Proof { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + &Hex::upper_case().encode_to_string(&self.0).unwrap() + ) + } +} + +impl FromStr for Proof { + type Err = Error; + + fn from_str(s: &str) -> Result { + let bytes = Hex::upper_case().decode(s)?; + Ok(Proof(bytes)) + } +} + +impl<'de> Deserialize<'de> for Proof { + fn deserialize>(deserializer: D) -> Result { + let hex = String::deserialize(deserializer)?; + Ok(Self::from_str(&hex).map_err(|e| D::Error::custom(format!("{}", e)))?) + } +} + +impl Serialize for Proof { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} diff --git a/tendermint-rs/src/abci/responses.rs b/tendermint-rs/src/abci/responses.rs new file mode 100644 index 0000000..ac28b2f --- /dev/null +++ b/tendermint-rs/src/abci/responses.rs @@ -0,0 +1,134 @@ +//! ABCI response types used by the `/block_results` RPC endpoint. + +use super::{code::Code, data::Data, gas::Gas, info::Info, log::Log, tag::Tag}; +use crate::{consensus, validator}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::fmt::{self, Display}; + +/// Responses for ABCI calls which occur during block processing. +/// +/// Returned from the `/block_results` RPC endpoint. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Responses { + /// Deliver TX response. + // TODO(tarcieri): remove the `alias` attribute when this lands upstream: + // + #[serde(alias = "DeliverTx")] + #[serde(default, deserialize_with = "deserialize_deliver_tx")] + pub deliver_tx: Vec, + + /// Begin block response. + // TODO(tarcieri): remove the `alias` attribute when this lands upstream: + // + #[serde(alias = "BeginBlock")] + pub begin_block: Option, + + /// End block response. + // TODO(tarcieri): remove the `alias` attribute when this lands upstream: + // + #[serde(alias = "EndBlock")] + pub end_block: Option, +} + +/// Return an empty vec in the event `deliver_tx` is `null` +fn deserialize_deliver_tx<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + Ok(Option::deserialize(deserializer)?.unwrap_or_default()) +} + +/// Deliver TX response. +/// +/// This type corresponds to the `ResponseDeliverTx` proto from: +/// +/// +// TODO(tarcieri): generate this automatically from the proto +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DeliverTx { + /// ABCI application response code + pub code: Option, + + /// ABCI application data + pub data: Option, + + /// ABCI log data (nondeterministic) + pub log: Option, + + /// ABCI info (nondeterministic) + pub info: Option, + + /// Amount of gas wanted + #[serde(default, rename = "gasWanted")] + pub gas_wanted: Gas, + + /// Amount of gas used + #[serde(default, rename = "gasUsed")] + pub gas_used: Gas, + + /// Tags + #[serde(default)] + pub tags: Vec, + + /// Codespace + pub codespace: Option, +} + +/// Begin block response. +/// +/// This type corresponds to the `ResponseBeginBlock` proto from: +/// +/// +// TODO(tarcieri): generate this automatically from the proto +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BeginBlock { + /// Tags + #[serde(default)] + pub tags: Vec, +} + +/// End block response. +/// +/// This type corresponds to the `ResponseEndBlock` proto from: +/// +/// +// TODO(tarcieri): generate this automatically from the proto +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EndBlock { + /// Validator updates + #[serde(deserialize_with = "deserialize_validator_updates")] + pub validator_updates: Vec, + + /// New consensus params + pub consensus_param_updates: Option, + + /// Tags + #[serde(default)] + pub tags: Vec, +} + +/// Return an empty vec in the event `validator_updates` is `null` +fn deserialize_validator_updates<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + Ok(Option::deserialize(deserializer)?.unwrap_or_default()) +} + +/// Codespace +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct Codespace(String); + +impl AsRef for Codespace { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl Display for Codespace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/tendermint-rs/src/abci/tag.rs b/tendermint-rs/src/abci/tag.rs new file mode 100644 index 0000000..a7b313e --- /dev/null +++ b/tendermint-rs/src/abci/tag.rs @@ -0,0 +1,63 @@ +//! Tags + +use crate::error::Error; +use serde::{Deserialize, Serialize}; +use std::{fmt, str::FromStr}; + +/// Tags +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Tag { + /// Key + pub key: Key, + + /// Value + pub value: Value, +} + +/// Tag keys +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)] +pub struct Key(String); + +impl AsRef for Key { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl FromStr for Key { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Key(s.into())) + } +} + +impl fmt::Display for Key { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +/// Tag values +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct Value(String); + +impl AsRef for Value { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl FromStr for Value { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Value(s.into())) + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} diff --git a/tendermint-rs/src/abci/transaction.rs b/tendermint-rs/src/abci/transaction.rs new file mode 100644 index 0000000..bd645dd --- /dev/null +++ b/tendermint-rs/src/abci/transaction.rs @@ -0,0 +1,99 @@ +//! Transactions + +mod hash; + +pub use self::hash::Hash; +use std::slice; +#[cfg(feature = "serde")] +use { + serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}, + subtle_encoding::base64, +}; + +/// Transactions are arbitrary byte arrays whose contents are validated by the +/// underlying Tendermint application. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Transaction(Vec); + +impl Transaction { + /// Create a new raw transaction from a byte vector + pub fn new(into_vec: V) -> Transaction + where + V: Into>, + { + Transaction(into_vec.into()) + } + + /// Convert this transaction into a byte vector + pub fn into_vec(self) -> Vec { + self.0.clone() + } + + /// Borrow the contents of this transaction as a byte slice + pub fn as_bytes(&self) -> &[u8] { + self.0.as_slice() + } +} + +impl AsRef<[u8]> for Transaction { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Transaction { + fn deserialize>(deserializer: D) -> Result { + let bytes = base64::decode(String::deserialize(deserializer)?.as_bytes()) + .map_err(|e| D::Error::custom(format!("{}", e)))?; + + Ok(Self::new(bytes)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for Transaction { + fn serialize(&self, serializer: S) -> Result { + String::from_utf8(base64::encode(self.as_bytes())) + .unwrap() + .serialize(serializer) + } +} + +/// Transaction data is a wrapper for a list of transactions, where +/// transactions are arbitrary byte arrays. +/// +/// +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Clone, Debug)] +pub struct Data { + txs: Option>, +} + +impl Data { + /// Create a new transaction data collection + pub fn new(into_transactions: I) -> Data + where + I: Into>, + { + Data { + txs: Some(into_transactions.into()), + } + } + + /// Convert this collection into a vector + pub fn into_vec(self) -> Vec { + self.iter().cloned().collect() + } + + /// Iterate over the transactions in the collection + pub fn iter(&self) -> slice::Iter { + self.as_ref().iter() + } +} + +impl AsRef<[Transaction]> for Data { + fn as_ref(&self) -> &[Transaction] { + self.txs.as_ref().map(Vec::as_slice).unwrap_or_else(|| &[]) + } +} diff --git a/tendermint-rs/src/abci/transaction/hash.rs b/tendermint-rs/src/abci/transaction/hash.rs new file mode 100644 index 0000000..3bbb316 --- /dev/null +++ b/tendermint-rs/src/abci/transaction/hash.rs @@ -0,0 +1,102 @@ +//! Transaction hashes + +use crate::error::{Error, ErrorKind}; +#[cfg(feature = "serde")] +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + fmt::{self, Debug, Display}, + str::FromStr, +}; +use subtle::{self, ConstantTimeEq}; +use subtle_encoding::hex; + +/// Size of a transaction hash in bytes +pub const LENGTH: usize = 20; + +/// Trannsaction hashes +#[derive(Copy, Clone, Hash)] +pub struct Hash([u8; LENGTH]); + +impl Hash { + /// Create a new transaction hash from raw bytes + pub fn new(bytes: [u8; LENGTH]) -> Hash { + Hash(bytes) + } + + /// Borrow the transaction hash as a byte slice + pub fn as_bytes(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl ConstantTimeEq for Hash { + #[inline] + fn ct_eq(&self, other: &Hash) -> subtle::Choice { + self.as_bytes().ct_eq(other.as_bytes()) + } +} + +impl Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in &self.0 { + write!(f, "{:02X}", byte)?; + } + Ok(()) + } +} + +impl Debug for Hash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "transactionn::Hash({})", self) + } +} + +/// Decode transaction hash from hex +impl FromStr for Hash { + type Err = Error; + + fn from_str(s: &str) -> Result { + // Accept either upper or lower case hex + let bytes = hex::decode_upper(s) + .or_else(|_| hex::decode(s)) + .map_err(|_| ErrorKind::Parse)?; + + if bytes.len() != LENGTH { + Err(ErrorKind::Parse)?; + } + + let mut result_bytes = [0u8; LENGTH]; + result_bytes.copy_from_slice(&bytes); + Ok(Hash(result_bytes)) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Hash { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(|_| { + de::Error::custom(format!( + "expected {}-character hex string, got {:?}", + LENGTH * 2, + s + )) + }) + } +} + +#[cfg(feature = "serde")] +impl Serialize for Hash { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} diff --git a/tendermint-rs/src/account.rs b/tendermint-rs/src/account.rs new file mode 100644 index 0000000..17b603e --- /dev/null +++ b/tendermint-rs/src/account.rs @@ -0,0 +1,146 @@ +//! Tendermint accounts + +use crate::error::{Error, ErrorKind}; +#[cfg(feature = "serde")] +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use sha2::{Digest, Sha256}; +use signatory::{ecdsa::curve::secp256k1, ed25519}; +use std::{ + fmt::{self, Debug, Display}, + str::FromStr, +}; +use subtle::{self, ConstantTimeEq}; +use subtle_encoding::hex; + +/// Size of an account ID in bytes +const LENGTH: usize = 20; + +/// Account IDs +#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct Id([u8; LENGTH]); + +impl Id { + /// Create a new account ID from raw bytes + pub fn new(bytes: [u8; LENGTH]) -> Id { + Id(bytes) + } + + /// Borrow the account ID as a byte slice + pub fn as_bytes(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsRef<[u8]> for Id { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl ConstantTimeEq for Id { + #[inline] + fn ct_eq(&self, other: &Id) -> subtle::Choice { + self.as_bytes().ct_eq(other.as_bytes()) + } +} + +impl Display for Id { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in &self.0 { + write!(f, "{:02X}", byte)?; + } + Ok(()) + } +} + +impl Debug for Id { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "account::Id({})", self) + } +} + +// TODO: should be RIPEMD160(SHA256(pk)) +impl From for Id { + fn from(pk: secp256k1::PublicKey) -> Id { + let digest = Sha256::digest(pk.as_bytes()); + let mut bytes = [0u8; LENGTH]; + bytes.copy_from_slice(&digest[..LENGTH]); + Id(bytes) + } +} + +// SHA256(pk)[:20] +impl From for Id { + fn from(pk: ed25519::PublicKey) -> Id { + let digest = Sha256::digest(pk.as_bytes()); + let mut bytes = [0u8; LENGTH]; + bytes.copy_from_slice(&digest[..LENGTH]); + Id(bytes) + } +} + +/// Decode account ID from hex +impl FromStr for Id { + type Err = Error; + + fn from_str(s: &str) -> Result { + // Accept either upper or lower case hex + let bytes = hex::decode_upper(s) + .or_else(|_| hex::decode(s)) + .map_err(|_| ErrorKind::Parse)?; + + if bytes.len() != LENGTH { + Err(ErrorKind::Parse)?; + } + + let mut result_bytes = [0u8; LENGTH]; + result_bytes.copy_from_slice(&bytes); + Ok(Id(result_bytes)) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Id { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(|_| { + de::Error::custom(format!( + "expected {}-character hex string, got {:?}", + LENGTH * 2, + s + )) + }) + } +} + +#[cfg(feature = "serde")] +impl Serialize for Id { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ed25519_id() { + // test vector for pubkey and id (address) + let pubkey_hex = "14253D61EF42D166D02E68D540D07FDF8D65A9AF0ACAA46302688E788A8521E2"; + let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; + + // decode pubkey and address + let pubkey_bytes = &hex::decode_upper(pubkey_hex).unwrap(); + let id_bytes = Id::from_str(id_hex).expect("expected id_hex to decode properly"); + + // get id for pubkey + let pubkey = ed25519::PublicKey::from_bytes(pubkey_bytes).unwrap(); + let id = Id::from(pubkey); + + assert_eq!(id_bytes.ct_eq(&id).unwrap_u8(), 1); + } +} diff --git a/tendermint-rs/src/amino_types.rs b/tendermint-rs/src/amino_types.rs new file mode 100644 index 0000000..282cdf4 --- /dev/null +++ b/tendermint-rs/src/amino_types.rs @@ -0,0 +1,28 @@ +//! Message types serialized using the Amino serialization format +//! + +#![allow(missing_docs)] + +pub mod block_id; +pub mod ed25519; +pub mod ping; +pub mod proposal; +pub mod remote_error; +pub mod secret_connection; +pub mod signature; +pub mod time; +pub mod validate; +pub mod vote; + +pub use self::{ + block_id::{BlockId, CanonicalBlockId, CanonicalPartSetHeader, PartsSetHeader}, + ed25519::{PubKeyRequest, PubKeyResponse, AMINO_NAME as PUBKEY_AMINO_NAME}, + ping::{PingRequest, PingResponse, AMINO_NAME as PING_AMINO_NAME}, + proposal::{SignProposalRequest, SignedProposalResponse, AMINO_NAME as PROPOSAL_AMINO_NAME}, + remote_error::RemoteError, + secret_connection::AuthSigMessage, + signature::{SignableMsg, SignedMsgType}, + time::TimeMsg, + validate::ConsensusMessage, + vote::{SignVoteRequest, SignedVoteResponse, AMINO_NAME as VOTE_AMINO_NAME}, +}; diff --git a/tendermint-rs/src/amino_types/block_id.rs b/tendermint-rs/src/amino_types/block_id.rs new file mode 100644 index 0000000..4306496 --- /dev/null +++ b/tendermint-rs/src/amino_types/block_id.rs @@ -0,0 +1,102 @@ +use super::validate::{ConsensusMessage, ValidationError, ValidationErrorKind::*}; +use crate::{ + block, + error::Error, + hash, + hash::{Hash, SHA256_HASH_SIZE}, +}; + +#[derive(Clone, PartialEq, Message)] +pub struct BlockId { + #[prost(bytes, tag = "1")] + pub hash: Vec, + #[prost(message, tag = "2")] + pub parts_header: Option, +} + +impl block::ParseId for BlockId { + fn parse_block_id(&self) -> Result { + let hash = Hash::new(hash::Algorithm::Sha256, &self.hash)?; + let parts_header = self + .parts_header + .as_ref() + .and_then(PartsSetHeader::parse_parts_header); + Ok(block::Id::new(hash, parts_header)) + } +} + +impl ConsensusMessage for BlockId { + fn validate_basic(&self) -> Result<(), ValidationError> { + // Hash can be empty in case of POLBlockID in Proposal. + if !self.hash.is_empty() && self.hash.len() != SHA256_HASH_SIZE { + return Err(InvalidHashSize.into()); + } + self.parts_header + .as_ref() + .map_or(Ok(()), ConsensusMessage::validate_basic) + } +} + +#[derive(Clone, PartialEq, Message)] +pub struct CanonicalBlockId { + #[prost(bytes, tag = "1")] + pub hash: Vec, + #[prost(message, tag = "2")] + pub parts_header: Option, +} + +impl block::ParseId for CanonicalBlockId { + fn parse_block_id(&self) -> Result { + let hash = Hash::new(hash::Algorithm::Sha256, &self.hash)?; + let parts_header = self + .parts_header + .as_ref() + .and_then(CanonicalPartSetHeader::parse_parts_header); + Ok(block::Id::new(hash, parts_header)) + } +} + +#[derive(Clone, PartialEq, Message)] +pub struct PartsSetHeader { + #[prost(int64, tag = "1")] + pub total: i64, + #[prost(bytes, tag = "2")] + pub hash: Vec, +} + +impl PartsSetHeader { + fn parse_parts_header(&self) -> Option { + Hash::new(hash::Algorithm::Sha256, &self.hash) + .map(|hash| block::parts::Header::new(self.total as u64, hash)) + .ok() + } +} + +impl ConsensusMessage for PartsSetHeader { + fn validate_basic(&self) -> Result<(), ValidationError> { + if self.total < 0 { + return Err(NegativeTotal.into()); + } + // Hash can be empty in case of POLBlockID.PartsHeader in Proposal. + if !self.hash.is_empty() && self.hash.len() != SHA256_HASH_SIZE { + return Err(InvalidHashSize.into()); + } + Ok(()) + } +} + +#[derive(Clone, PartialEq, Message)] +pub struct CanonicalPartSetHeader { + #[prost(bytes, tag = "1")] + pub hash: Vec, + #[prost(int64, tag = "2")] + pub total: i64, +} + +impl CanonicalPartSetHeader { + fn parse_parts_header(&self) -> Option { + Hash::new(hash::Algorithm::Sha256, &self.hash) + .map(|hash| block::parts::Header::new(self.total as u64, hash)) + .ok() + } +} diff --git a/tendermint-rs/src/amino_types/ed25519.rs b/tendermint-rs/src/amino_types/ed25519.rs new file mode 100644 index 0000000..1de61e4 --- /dev/null +++ b/tendermint-rs/src/amino_types/ed25519.rs @@ -0,0 +1,170 @@ +use crate::public_key::PublicKey; +use signatory::ed25519::PUBLIC_KEY_SIZE; + +// Note:On the golang side this is generic in the sense that it could everything that implements +// github.com/tendermint/tendermint/crypto.PubKey +// While this is meant to be used with different key-types, it currently only uses a PubKeyEd25519 +// version. +// TODO(ismail): make this more generic (by modifying prost and adding a trait for PubKey) + +pub const AMINO_NAME: &str = "tendermint/remotesigner/PubKeyRequest"; + +#[derive(Clone, PartialEq, Message)] +#[amino_name = "tendermint/remotesigner/PubKeyResponse"] +pub struct PubKeyResponse { + #[prost(bytes, tag = "1", amino_name = "tendermint/PubKeyEd25519")] + pub pub_key_ed25519: Vec, +} + +#[derive(Clone, PartialEq, Message)] +#[amino_name = "tendermint/remotesigner/PubKeyRequest"] +pub struct PubKeyRequest {} + +impl From for PublicKey { + // This does not check if the underlying pub_key_ed25519 has the right size. + // The caller needs to make sure that this is actually the case. + fn from(response: PubKeyResponse) -> PublicKey { + let mut public_key = [0u8; PUBLIC_KEY_SIZE]; + public_key.copy_from_slice(response.pub_key_ed25519.as_ref()); + PublicKey::Ed25519(signatory::ed25519::PublicKey::new(public_key)) + } +} + +impl From for PubKeyResponse { + fn from(public_key: PublicKey) -> PubKeyResponse { + match public_key { + PublicKey::Ed25519(ref pk) => PubKeyResponse { + pub_key_ed25519: pk.as_bytes().to_vec(), + }, + PublicKey::Secp256k1(_) => panic!("secp256k1 PubKeyResponse unimplemented"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use prost::Message; + use std::error::Error; + + #[test] + fn test_empty_pubkey_msg() { + // test-vector generated via the following go code: + // + // -------------------------------------------------------------------- + //package main + // + //import ( + // "fmt" + // + // "github.com/tendermint/go-amino" + // "github.com/tendermint/tendermint/crypto" + // "github.com/tendermint/tendermint/privval" + //) + // + //func main() { + // cdc := amino.NewCodec() + // + // cdc.RegisterInterface((*crypto.PubKey)(nil), nil) + // cdc.RegisterConcrete(crypto.PubKeyEd25519{}, + // "tendermint/PubKeyEd25519", nil) + // cdc.RegisterConcrete(&privval.PubKeyRequest{}, + // "tendermint/remotesigner/PubKeyRequest", nil) + // b, _ := cdc.MarshalBinary(&privval.PubKeyRequest{}) + // fmt.Printf("%#v\n\n", b) + //} + // -------------------------------------------------------------------- + // Output: + // []byte{0x4, 0xcb, 0x94, 0xd6, 0x20} + // + // + + let want = vec![0x4, 0xcb, 0x94, 0xd6, 0x20]; + let msg = PubKeyRequest {}; + let mut got = vec![]; + let _have = msg.encode(&mut got); + + assert_eq!(got, want); + + match PubKeyRequest::decode(&want) { + Ok(have) => assert_eq!(have, msg), + Err(err) => assert!(false, err.description().to_string()), + } + } + + #[test] + fn test_ed25519_pubkey_msg() { + // test-vector generated exactly as for test_empty_pubkey_msg + // but with the following modifications: + // cdc.RegisterConcrete(&privval.PubKeyResponse{}, + // "tendermint/remotesigner/PubKeyResponse", nil) + // + // var pubKey [32]byte + // copy(pubKey[:],[]byte{0x79, 0xce, 0xd, 0xe0, 0x43, 0x33, 0x4a, 0xec, 0xe0, 0x8b, 0x7b, + // 0xb5, 0x61, 0xbc, 0xe7, 0xc1, + // 0xd4, 0x69, 0xc3, 0x44, 0x26, 0xec, 0xef, 0xc0, 0x72, 0xa, 0x52, 0x4d, 0x37, 0x32, 0xef, 0xed}) + // + // b, _ = cdc.MarshalBinary(&privval.PubKeyResponse{PubKey: crypto.PubKeyEd25519(pubKey)}) + // fmt.Printf("%#v\n\n", b) + // + let encoded = vec![ + 0x2b, // length + 0x17, 0xe, 0xd5, 0x7c, // prefix + 0xa, 0x25, 0x16, 0x24, 0xde, 0x64, 0x20, 0x79, 0xce, 0xd, 0xe0, 0x43, 0x33, 0x4a, 0xec, + 0xe0, 0x8b, 0x7b, 0xb5, 0x61, 0xbc, 0xe7, 0xc1, 0xd4, 0x69, 0xc3, 0x44, 0x26, 0xec, + 0xef, 0xc0, 0x72, 0xa, 0x52, 0x4d, 0x37, 0x32, 0xef, 0xed, + ]; + + let msg = PubKeyResponse { + pub_key_ed25519: vec![ + 0x79, 0xce, 0xd, 0xe0, 0x43, 0x33, 0x4a, 0xec, 0xe0, 0x8b, 0x7b, 0xb5, 0x61, 0xbc, + 0xe7, 0xc1, 0xd4, 0x69, 0xc3, 0x44, 0x26, 0xec, 0xef, 0xc0, 0x72, 0xa, 0x52, 0x4d, + 0x37, 0x32, 0xef, 0xed, + ], + }; + let mut got = vec![]; + let _have = msg.encode(&mut got); + + assert_eq!(got, encoded); + + match PubKeyResponse::decode(&encoded) { + Ok(have) => assert_eq!(have, msg), + Err(err) => assert!(false, err), + } + } + + #[test] + fn test_into() { + let raw_pk: [u8; PUBLIC_KEY_SIZE] = [ + 0x79, 0xce, 0xd, 0xe0, 0x43, 0x33, 0x4a, 0xec, 0xe0, 0x8b, 0x7b, 0xb5, 0x61, 0xbc, + 0xe7, 0xc1, 0xd4, 0x69, 0xc3, 0x44, 0x26, 0xec, 0xef, 0xc0, 0x72, 0xa, 0x52, 0x4d, + 0x37, 0x32, 0xef, 0xed, + ]; + let want = PublicKey::Ed25519(signatory::ed25519::PublicKey::new(raw_pk)); + let pk = PubKeyResponse { + pub_key_ed25519: vec![ + 0x79, 0xce, 0xd, 0xe0, 0x43, 0x33, 0x4a, 0xec, 0xe0, 0x8b, 0x7b, 0xb5, 0x61, 0xbc, + 0xe7, 0xc1, 0xd4, 0x69, 0xc3, 0x44, 0x26, 0xec, 0xef, 0xc0, 0x72, 0xa, 0x52, 0x4d, + 0x37, 0x32, 0xef, 0xed, + ], + }; + let orig = pk.clone(); + let got: PublicKey = pk.into(); + + assert_eq!(got, want); + + // and back: + let round_trip_pk: PubKeyResponse = got.into(); + assert_eq!(round_trip_pk, orig); + } + + #[test] + #[should_panic] + fn test_empty_into() { + let empty_msg = PubKeyResponse { + pub_key_ed25519: vec![], + }; + // we expect this to panic: + let _got: PublicKey = empty_msg.into(); + } +} diff --git a/tendermint-rs/src/amino_types/ping.rs b/tendermint-rs/src/amino_types/ping.rs new file mode 100644 index 0000000..60fd642 --- /dev/null +++ b/tendermint-rs/src/amino_types/ping.rs @@ -0,0 +1,9 @@ +pub const AMINO_NAME: &str = "tendermint/remotesigner/PingRequest"; + +#[derive(Clone, PartialEq, Message)] +#[amino_name = "tendermint/remotesigner/PingRequest"] +pub struct PingRequest {} + +#[derive(Clone, PartialEq, Message)] +#[amino_name = "tendermint/remotesigner/PingResponse"] +pub struct PingResponse {} diff --git a/tendermint-rs/src/amino_types/proposal.rs b/tendermint-rs/src/amino_types/proposal.rs new file mode 100644 index 0000000..88354b1 --- /dev/null +++ b/tendermint-rs/src/amino_types/proposal.rs @@ -0,0 +1,298 @@ +use super::{ + block_id::{BlockId, CanonicalBlockId, CanonicalPartSetHeader}, + remote_error::RemoteError, + signature::{SignableMsg, SignedMsgType}, + time::TimeMsg, + validate::{ConsensusMessage, ValidationError, ValidationErrorKind::*}, +}; +use crate::{ + block::{self, ParseId}, + chain, consensus, + error::Error, +}; +use bytes::BufMut; +use prost::{EncodeError, Message}; +use signatory::{ed25519, Signature}; + +#[derive(Clone, PartialEq, Message)] +pub struct Proposal { + #[prost(uint32, tag = "1")] + pub msg_type: u32, + #[prost(int64)] + pub height: i64, + #[prost(int64)] + pub round: i64, + #[prost(int64)] + pub pol_round: i64, + #[prost(message)] + pub block_id: Option, + #[prost(message)] + pub timestamp: Option, + #[prost(bytes)] + pub signature: Vec, +} + +// TODO(tony): custom derive proc macro for this e.g. `derive(ParseBlockHeight)` +impl block::ParseHeight for Proposal { + fn parse_block_height(&self) -> Result { + block::Height::try_from_i64(self.height) + } +} + +pub const AMINO_NAME: &str = "tendermint/remotesigner/SignProposalRequest"; + +#[derive(Clone, PartialEq, Message)] +#[amino_name = "tendermint/remotesigner/SignProposalRequest"] +pub struct SignProposalRequest { + #[prost(message, tag = "1")] + pub proposal: Option, +} + +#[derive(Clone, PartialEq, Message)] +struct CanonicalProposal { + #[prost(uint32, tag = "1")] + msg_type: u32, // this is a byte in golang, which is a varint encoded UInt8 (using amino's EncodeUvarint) + #[prost(sfixed64)] + height: i64, + #[prost(sfixed64)] + round: i64, + #[prost(sfixed64)] + pol_round: i64, + #[prost(message)] + block_id: Option, + #[prost(message)] + timestamp: Option, + #[prost(string)] + pub chain_id: String, +} + +impl chain::ParseId for CanonicalProposal { + fn parse_chain_id(&self) -> Result { + self.chain_id.parse() + } +} + +impl block::ParseHeight for CanonicalProposal { + fn parse_block_height(&self) -> Result { + block::Height::try_from_i64(self.height) + } +} + +#[derive(Clone, PartialEq, Message)] +#[amino_name = "tendermint/remotesigner/SignedProposalResponse"] +pub struct SignedProposalResponse { + #[prost(message, tag = "1")] + pub proposal: Option, + #[prost(message, tag = "2")] + pub err: Option, +} + +impl SignableMsg for SignProposalRequest { + fn sign_bytes(&self, chain_id: chain::Id, sign_bytes: &mut B) -> Result + where + B: BufMut, + { + let mut spr = self.clone(); + if let Some(ref mut pr) = spr.proposal { + pr.signature = vec![]; + } + let proposal = spr.proposal.unwrap(); + let cp = CanonicalProposal { + chain_id: chain_id.to_string(), + msg_type: SignedMsgType::Proposal.to_u32(), + height: proposal.height, + block_id: match proposal.block_id { + Some(bid) => Some(CanonicalBlockId { + hash: bid.hash, + parts_header: match bid.parts_header { + Some(psh) => Some(CanonicalPartSetHeader { + hash: psh.hash, + total: psh.total, + }), + None => None, + }, + }), + None => None, + }, + pol_round: proposal.pol_round, + round: proposal.round, + timestamp: proposal.timestamp, + }; + + cp.encode_length_delimited(sign_bytes)?; + Ok(true) + } + fn set_signature(&mut self, sig: &ed25519::Signature) { + if let Some(ref mut prop) = self.proposal { + prop.signature = sig.clone().into_vec(); + } + } + fn validate(&self) -> Result<(), ValidationError> { + match self.proposal { + Some(ref p) => p.validate_basic(), + None => Err(MissingConsensusMessage.into()), + } + } + fn consensus_state(&self) -> Option { + match self.proposal { + Some(ref p) => Some(consensus::State { + height: match block::Height::try_from_i64(p.height) { + Ok(h) => h, + Err(_err) => return None, // TODO(tarcieri): return an error? + }, + round: p.round, + step: 3, + block_id: { + match p.block_id { + Some(ref b) => match b.parse_block_id() { + Ok(id) => Some(id), + Err(_) => None, + }, + None => None, + } + }, + }), + None => None, + } + } + + fn height(&self) -> Option { + self.proposal.as_ref().map(|proposal| proposal.height) + } + + fn msg_type(&self) -> Option { + Some(SignedMsgType::Proposal) + } +} + +impl ConsensusMessage for Proposal { + fn validate_basic(&self) -> Result<(), ValidationError> { + if self.msg_type != SignedMsgType::Proposal.to_u32() { + return Err(InvalidMessageType.into()); + } + if self.height < 0 { + return Err(NegativeHeight.into()); + } + if self.round < 0 { + return Err(NegativeRound.into()); + } + if self.pol_round < -1 { + return Err(NegativePOLRound.into()); + } + // TODO validate proposal's block_id + + // signature will be missing as the KMS provides it + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::amino_types::block_id::PartsSetHeader; + use chrono::{DateTime, Utc}; + use prost::Message; + use std::error::Error; + + #[test] + fn test_serialization() { + let dt = "2018-02-11T07:09:22.765Z".parse::>().unwrap(); + let t = TimeMsg { + seconds: dt.timestamp(), + nanos: dt.timestamp_subsec_nanos() as i32, + }; + let proposal = Proposal { + msg_type: SignedMsgType::Proposal.to_u32(), + height: 12345, + round: 23456, + pol_round: -1, + block_id: Some(BlockId { + hash: "hash".as_bytes().to_vec(), + parts_header: Some(PartsSetHeader { + total: 1000000, + hash: "parts_hash".as_bytes().to_vec(), + }), + }), + timestamp: Some(t), + signature: vec![], + }; + let mut got = vec![]; + + let _have = SignProposalRequest { + proposal: Some(proposal), + } + .encode(&mut got); + // test-vector generated via: + // cdc := amino.NewCodec() + // privval.RegisterRemoteSignerMsg(cdc) + // stamp, _ := time.Parse(time.RFC3339Nano, "2018-02-11T07:09:22.765Z") + // data, _ := cdc.MarshalBinaryLengthPrefixed(privval.SignProposalRequest{Proposal: &types.Proposal{ + // Type: types.ProposalType, // 0x20 + // Height: 12345, + // Round: 23456, + // POLRound: -1, + // BlockID: types.BlockID{ + // Hash: []byte("hash"), + // PartsHeader: types.PartSetHeader{ + // Hash: []byte("parts_hash"), + // Total: 1000000, + // }, + // }, + // Timestamp: stamp, + // }}) + // fmt.Println(strings.Join(strings.Split(fmt.Sprintf("%v", data), " "), ", ")) + let want = vec![ + 66, // len + 189, 228, 152, 226, // prefix + 10, 60, 8, 32, 16, 185, 96, 24, 160, 183, 1, 32, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 1, 42, 24, 10, 4, 104, 97, 115, 104, 18, 16, 8, 192, 132, 61, 18, 10, 112, + 97, 114, 116, 115, 95, 104, 97, 115, 104, 50, 12, 8, 162, 216, 255, 211, 5, 16, 192, + 242, 227, 236, 2, + ]; + + assert_eq!(got, want) + } + + #[test] + fn test_deserialization() { + let dt = "2018-02-11T07:09:22.765Z".parse::>().unwrap(); + let t = TimeMsg { + seconds: dt.timestamp(), + nanos: dt.timestamp_subsec_nanos() as i32, + }; + let proposal = Proposal { + msg_type: SignedMsgType::Proposal.to_u32(), + height: 12345, + round: 23456, + timestamp: Some(t), + + pol_round: -1, + block_id: Some(BlockId { + hash: "hash".as_bytes().to_vec(), + parts_header: Some(PartsSetHeader { + total: 1000000, + hash: "parts_hash".as_bytes().to_vec(), + }), + }), + signature: vec![], + }; + let want = SignProposalRequest { + proposal: Some(proposal), + }; + + let data = vec![ + 66, // len + 189, 228, 152, 226, // prefix + 10, 60, 8, 32, 16, 185, 96, 24, 160, 183, 1, 32, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 1, 42, 24, 10, 4, 104, 97, 115, 104, 18, 16, 8, 192, 132, 61, 18, 10, 112, + 97, 114, 116, 115, 95, 104, 97, 115, 104, 50, 12, 8, 162, 216, 255, 211, 5, 16, 192, + 242, 227, 236, 2, + ]; + + match SignProposalRequest::decode(&data) { + Ok(have) => assert_eq!(have, want), + Err(err) => assert!(false, err.description().to_string()), + } + } +} diff --git a/tendermint-rs/src/amino_types/remote_error.rs b/tendermint-rs/src/amino_types/remote_error.rs new file mode 100644 index 0000000..8c86716 --- /dev/null +++ b/tendermint-rs/src/amino_types/remote_error.rs @@ -0,0 +1,30 @@ +#[derive(Clone, PartialEq, Message)] +pub struct RemoteError { + #[prost(sint32, tag = "1")] + pub code: i32, + #[prost(string, tag = "2")] + pub description: String, +} + +/// Error codes for remote signer failures +// TODO(tarcieri): add these to Tendermint. See corresponding TODO here: +// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(i32)] +pub enum RemoteErrorCode { + /// Generic error code useful when the others don't apply + RemoteSignerError = 1, + + /// Double signing detected + DoubleSignError = 2, +} + +impl RemoteError { + /// Create a new double signing error with the given message + pub fn double_sign(height: i64) -> Self { + RemoteError { + code: RemoteErrorCode::DoubleSignError as i32, + description: format!("double signing requested at height: {}", height), + } + } +} diff --git a/tendermint-rs/src/amino_types/secret_connection.rs b/tendermint-rs/src/amino_types/secret_connection.rs new file mode 100644 index 0000000..e4e5fd7 --- /dev/null +++ b/tendermint-rs/src/amino_types/secret_connection.rs @@ -0,0 +1,9 @@ +//! Message types used by `SecretConnection` + +#[derive(Clone, PartialEq, Message)] +pub struct AuthSigMessage { + #[prost(bytes, tag = "1", amino_name = "tendermint/PubKeyEd25519")] + pub key: Vec, + #[prost(bytes, tag = "2")] + pub sig: Vec, +} diff --git a/tendermint-rs/src/amino_types/signature.rs b/tendermint-rs/src/amino_types/signature.rs new file mode 100644 index 0000000..97932c0 --- /dev/null +++ b/tendermint-rs/src/amino_types/signature.rs @@ -0,0 +1,58 @@ +use super::validate::ValidationError; +use crate::{chain, consensus}; +use bytes::BufMut; +use prost::{DecodeError, EncodeError}; +use signatory::ed25519; + +/// Amino messages which are signable within a Tendermint network +pub trait SignableMsg { + /// Sign this message as bytes + fn sign_bytes( + &self, + chain_id: chain::Id, + sign_bytes: &mut B, + ) -> Result; + + /// Set the Ed25519 signature on the underlying message + fn set_signature(&mut self, sig: &ed25519::Signature); + fn validate(&self) -> Result<(), ValidationError>; + fn consensus_state(&self) -> Option; + fn height(&self) -> Option; + fn msg_type(&self) -> Option; +} + +/// Signed message types. This follows: +/// +#[derive(Copy, Clone, Debug)] +pub enum SignedMsgType { + /// Votes + PreVote, + + /// Commits + PreCommit, + + /// Proposals + Proposal, +} + +impl SignedMsgType { + pub fn to_u32(self) -> u32 { + match self { + // Votes + SignedMsgType::PreVote => 0x01, + SignedMsgType::PreCommit => 0x02, + // Proposals + SignedMsgType::Proposal => 0x20, + } + } + + #[allow(dead_code)] + fn from(data: u32) -> Result { + match data { + 0x01 => Ok(SignedMsgType::PreVote), + 0x02 => Ok(SignedMsgType::PreCommit), + 0x20 => Ok(SignedMsgType::Proposal), + _ => Err(DecodeError::new("Invalid vote type")), + } + } +} diff --git a/tendermint-rs/src/amino_types/time.rs b/tendermint-rs/src/amino_types/time.rs new file mode 100644 index 0000000..af942c1 --- /dev/null +++ b/tendermint-rs/src/amino_types/time.rs @@ -0,0 +1,47 @@ +//! Timestamps + +use crate::{ + error::Error, + time::{ParseTimestamp, Time}, +}; +use chrono::{TimeZone, Utc}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +#[derive(Clone, PartialEq, Message)] +pub struct TimeMsg { + // TODO(ismail): switch to protobuf's well known type as soon as + // https://github.com/tendermint/go-amino/pull/224 was merged + // and tendermint caught up on the latest amino release. + #[prost(int64, tag = "1")] + pub seconds: i64, + #[prost(int32, tag = "2")] + pub nanos: i32, +} + +impl ParseTimestamp for TimeMsg { + fn parse_timestamp(&self) -> Result { + Ok(Utc.timestamp(self.seconds, self.nanos as u32).into()) + } +} + +impl From