From 4f015480204383650ea27a5da0e1816e6d98d2a9 Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:17:06 -0300 Subject: [PATCH 001/328] Break this project into a workspace (#58) * Break floresta into multiple crates * Make blockchain compile with no-std * Fix clippy linting * Move only: Crate a crates subdir * Create a meta-crate for the libs * Add example for the watch-only wallet * Add more tests in floresta-chain * Fix a compilatio error with florestad * Update readme.md * Make watch-only compile with no-std * Fix a bug with node In some cases we would be stuck at ibd * Complete example * Fix a some bugs on disconnection --- .github/workflows/rust.yml | 3 +- Cargo.lock | 987 +++++++++++------- Cargo.toml | 60 +- crates/floresta-chain/Cargo.toml | 39 + crates/floresta-chain/src/lib.rs | 51 + .../src/pruned_utreexo}/chain_state.rs | 231 ++-- .../src/pruned_utreexo/chain_state_builder.rs | 85 ++ .../src/pruned_utreexo}/chainparams.rs | 18 +- .../src/pruned_utreexo}/chainstore.rs | 14 +- .../src/pruned_utreexo/error.rs | 78 ++ .../floresta-chain/src/pruned_utreexo}/mod.rs | 45 +- .../src/pruned_utreexo}/testdata/headers.zst | Bin .../pruned_utreexo}/testdata/signet_headers | Bin .../testdata/signet_headers.zst | Bin .../pruned_utreexo}/testdata/test_reorg.json | 0 .../src/pruned_utreexo}/udata.rs | 19 +- {cli => crates/floresta-cli}/Cargo.toml | 0 {cli => crates/floresta-cli}/README.md | 0 {cli => crates/floresta-cli}/src/main.rs | 10 +- crates/floresta-common/Cargo.toml | 12 + .../floresta-common/src/constants.rs | 0 crates/floresta-common/src/lib.rs | 83 ++ crates/floresta-electrum/Cargo.toml | 30 + .../src}/electrum_protocol.rs | 38 +- crates/floresta-electrum/src/error.rs | 23 + .../floresta-electrum/src/lib.rs | 0 .../floresta-electrum/src}/request.rs | 0 crates/floresta-watch-only/Cargo.toml | 27 + .../floresta-watch-only/src}/kv_database.rs | 91 +- .../floresta-watch-only/src/lib.rs | 192 ++-- .../src}/memory_database.rs | 72 +- .../floresta-watch-only/src}/merkle.rs | 28 +- crates/floresta-wire/Cargo.toml | 34 + .../floresta-wire/src/cli_wire}/mod.rs | 2 +- crates/floresta-wire/src/lib.rs | 19 + .../src/p2p_wire}/address_man.rs | 28 +- crates/floresta-wire/src/p2p_wire/error.rs | 35 + .../floresta-wire/src/p2p_wire}/mempool.rs | 8 +- .../floresta-wire/src/p2p_wire}/mod.rs | 1 + .../floresta-wire/src/p2p_wire}/node.rs | 384 ++++--- .../src/p2p_wire}/node_context.rs | 5 +- .../src/p2p_wire}/node_interface.rs | 2 +- .../floresta-wire/src/p2p_wire}/peer.rs | 59 +- .../src/p2p_wire}/seeds/mainnet_seeds.json | 0 .../src/p2p_wire}/seeds/regtest_seeds.json | 0 .../src/p2p_wire}/seeds/signet_seeds.json | 0 .../src/p2p_wire}/seeds/testnet_seeds.json | 0 .../src/p2p_wire}/stream_reader.rs | 13 +- crates/floresta/Cargo.toml | 45 + .../floresta/examples/chainstate-builder.rs | 55 + crates/floresta/examples/node.rs | 94 ++ crates/floresta/examples/watch-only.rs | 95 ++ crates/floresta/src/lib.rs | 34 + florestad/Cargo.toml | 54 + .../docs}/assets/Screenshot_ibd.jpg | Bin {docs => florestad/docs}/tutorial(PT-BR).md | 0 {src => florestad/src}/cli.rs | 0 {src => florestad/src}/config_file.rs | 0 {src => florestad/src}/error.rs | 37 +- {src => florestad/src}/json_rpc/mod.rs | 0 {src => florestad/src}/json_rpc/res.rs | 0 {src => florestad/src}/json_rpc/server.rs | 50 +- {src => florestad/src}/main.rs | 72 +- {src => florestad/src}/wallet_input.rs | 0 readme.md | 40 +- src/blockchain/error.rs | 115 -- src/electrum/error.rs | 16 - 67 files changed, 2320 insertions(+), 1213 deletions(-) create mode 100644 crates/floresta-chain/Cargo.toml create mode 100644 crates/floresta-chain/src/lib.rs rename {src/blockchain => crates/floresta-chain/src/pruned_utreexo}/chain_state.rs (91%) create mode 100644 crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs rename {src/blockchain => crates/floresta-chain/src/pruned_utreexo}/chainparams.rs (94%) rename {src/blockchain => crates/floresta-chain/src/pruned_utreexo}/chainstore.rs (95%) create mode 100644 crates/floresta-chain/src/pruned_utreexo/error.rs rename {src/blockchain => crates/floresta-chain/src/pruned_utreexo}/mod.rs (85%) rename {src/blockchain => crates/floresta-chain/src/pruned_utreexo}/testdata/headers.zst (100%) rename {src/blockchain => crates/floresta-chain/src/pruned_utreexo}/testdata/signet_headers (100%) rename {src/blockchain => crates/floresta-chain/src/pruned_utreexo}/testdata/signet_headers.zst (100%) rename {src/blockchain => crates/floresta-chain/src/pruned_utreexo}/testdata/test_reorg.json (100%) rename {src/blockchain => crates/floresta-chain/src/pruned_utreexo}/udata.rs (97%) rename {cli => crates/floresta-cli}/Cargo.toml (100%) rename {cli => crates/floresta-cli}/README.md (100%) rename {cli => crates/floresta-cli}/src/main.rs (96%) create mode 100644 crates/floresta-common/Cargo.toml rename src/version.rs => crates/floresta-common/src/constants.rs (100%) create mode 100644 crates/floresta-common/src/lib.rs create mode 100644 crates/floresta-electrum/Cargo.toml rename {src/electrum => crates/floresta-electrum/src}/electrum_protocol.rs (95%) create mode 100644 crates/floresta-electrum/src/error.rs rename src/electrum/mod.rs => crates/floresta-electrum/src/lib.rs (100%) rename {src/electrum => crates/floresta-electrum/src}/request.rs (100%) create mode 100644 crates/floresta-watch-only/Cargo.toml rename {src/address_cache => crates/floresta-watch-only/src}/kv_database.rs (52%) rename src/address_cache/mod.rs => crates/floresta-watch-only/src/lib.rs (82%) rename {src/address_cache => crates/floresta-watch-only/src}/memory_database.rs (58%) rename {src/address_cache => crates/floresta-watch-only/src}/merkle.rs (96%) create mode 100644 crates/floresta-wire/Cargo.toml rename {src/blockchain/cli_blockchain => crates/floresta-wire/src/cli_wire}/mod.rs (99%) create mode 100644 crates/floresta-wire/src/lib.rs rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/address_man.rs (97%) create mode 100644 crates/floresta-wire/src/p2p_wire/error.rs rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/mempool.rs (90%) rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/mod.rs (94%) rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/node.rs (81%) rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/node_context.rs (90%) rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/node_interface.rs (99%) rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/peer.rs (90%) rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/seeds/mainnet_seeds.json (100%) rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/seeds/regtest_seeds.json (100%) rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/seeds/signet_seeds.json (100%) rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/seeds/testnet_seeds.json (100%) rename {src/blockchain/p2p_blockchain => crates/floresta-wire/src/p2p_wire}/stream_reader.rs (89%) create mode 100644 crates/floresta/Cargo.toml create mode 100644 crates/floresta/examples/chainstate-builder.rs create mode 100644 crates/floresta/examples/node.rs create mode 100644 crates/floresta/examples/watch-only.rs create mode 100644 crates/floresta/src/lib.rs create mode 100644 florestad/Cargo.toml rename {docs => florestad/docs}/assets/Screenshot_ibd.jpg (100%) rename {docs => florestad/docs}/tutorial(PT-BR).md (100%) rename {src => florestad/src}/cli.rs (100%) rename {src => florestad/src}/config_file.rs (100%) rename {src => florestad/src}/error.rs (87%) rename {src => florestad/src}/json_rpc/mod.rs (100%) rename {src => florestad/src}/json_rpc/res.rs (100%) rename {src => florestad/src}/json_rpc/server.rs (87%) rename {src => florestad/src}/main.rs (90%) rename {src => florestad/src}/wallet_input.rs (100%) delete mode 100644 src/blockchain/error.rs delete mode 100644 src/electrum/error.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 03983e73..8ac4388a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -44,7 +44,6 @@ jobs: matrix: rust: [stable, nightly, 1.64] os: [ubuntu-latest, windows-latest, macos-latest] - features: ["experimental-p2p", "cli-blockchain", "json-rpc"] runs-on: ${{ matrix.os }} steps: @@ -53,4 +52,4 @@ jobs: toolchain: ${{ matrix.rust }} - name: Cross compile - run: cargo test --verbose --features ${{ matrix.features }} + run: cargo test --verbose diff --git a/Cargo.lock b/Cargo.lock index cc24b5b1..d8d986d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,23 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "0.7.20" @@ -11,6 +28,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" + [[package]] name = "amplify" version = "3.14.2" @@ -52,20 +84,60 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.71" +name = "anstream" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] [[package]] -name = "argfile" -version = "0.1.5" +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265f5108974489a217d5098cd81666b60480c8dd67302acbbe7cbdd8aa09d638" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "os_str_bytes", + "windows-sys 0.48.0", ] +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + [[package]] name = "async-attributes" version = "1.1.2" @@ -89,9 +161,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ "async-lock", "async-task", @@ -118,22 +190,22 @@ dependencies = [ [[package]] name = "async-io" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg", + "cfg-if 1.0.0", "concurrent-queue", "futures-lite", - "libc", "log", "parking", "polling", + "rustix", "slab", "socket2", "waker-fn", - "windows-sys 0.42.0", ] [[package]] @@ -174,15 +246,15 @@ dependencies = [ [[package]] name = "async-task" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "atomic-waker" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "atty" @@ -209,9 +281,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bech32" @@ -225,21 +297,52 @@ version = "0.29.2" source = "git+https://github.com/Davidson-Souza/rust-bitcoin?rev=a320c6535567acd3771da37759a7644eea5c6eb2#a320c6535567acd3771da37759a7644eea5c6eb2" dependencies = [ "bech32", - "bitcoin_hashes", + "bitcoin_hashes 0.11.0", "bitcoinconsensus", - "secp256k1", + "core2 0.3.3", + "hashbrown 0.8.2", + "secp256k1 0.24.3", "serde", ] +[[package]] +name = "bitcoin" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b36f4c848f6bd9ff208128f08751135846cc23ae57d66ab10a22efff1c675f3c" +dependencies = [ + "bech32", + "bitcoin-private", + "bitcoin_hashes 0.12.0", + "hex_lit", + "secp256k1 0.27.0", +] + +[[package]] +name = "bitcoin-private" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" + [[package]] name = "bitcoin_hashes" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" dependencies = [ + "core2 0.3.3", "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" +dependencies = [ + "bitcoin-private", +] + [[package]] name = "bitcoinconsensus" version = "0.20.2-0.5.0" @@ -267,9 +370,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", "async-lock", @@ -277,13 +380,14 @@ dependencies = [ "atomic-waker", "fastrand", "futures-lite", + "log", ] [[package]] name = "bstr" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" dependencies = [ "memchr", "serde", @@ -303,9 +407,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byteorder" @@ -342,46 +446,57 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.1.8" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" +checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", - "clap_lex", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", "strsim", - "termcolor", ] [[package]] name = "clap_derive" -version = "4.1.8" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] name = "clap_lex" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" -dependencies = [ - "os_str_bytes", -] +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "concurrent-queue" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] @@ -404,15 +519,33 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "core2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239fa3ae9b63c2dc74bd3fa852d4792b8b305ae64eeede946265b6af62f1fff3" +dependencies = [ + "memchr", +] + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" dependencies = [ "libc", ] @@ -428,9 +561,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if 1.0.0", @@ -441,9 +574,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if 1.0.0", ] @@ -470,12 +603,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.5" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" +checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" dependencies = [ "nix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -499,9 +632,9 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -563,13 +696,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -599,49 +732,164 @@ dependencies = [ [[package]] name = "floresta" -version = "0.4.0" +version = "0.1.0" dependencies = [ - "argfile", "async-std", - "bitcoin", - "btcd-rpc", + "bitcoin 0.29.2", + "floresta-chain", + "floresta-common", + "floresta-watch-only", + "floresta-wire", + "hashbrown 0.14.0", + "miniscript 10.0.0", + "rustreexo", +] + +[[package]] +name = "floresta-chain" +version = "0.1.0" +dependencies = [ + "async-std", + "bitcoin 0.29.2", + "core2 0.4.0", + "floresta-common", + "futures 0.3.28", + "hashbrown 0.14.0", + "kv", + "log", + "pretty_assertions", + "rand 0.8.5", + "rustreexo", + "secp256k1 0.27.0", + "serde", + "serde_json", + "sha2", + "spin", + "wasm-bindgen", + "zstd", +] + +[[package]] +name = "floresta-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitcoin 0.29.2", "clap", + "jsonrpc", + "serde", + "serde_json", +] + +[[package]] +name = "floresta-common" +version = "0.1.0" +dependencies = [ + "bitcoin 0.29.2", + "miniscript 9.0.0", + "sha2", +] + +[[package]] +name = "floresta-electrum" +version = "0.1.0" +dependencies = [ + "async-std", + "bitcoin 0.29.2", + "btcd-rpc", + "floresta-chain", + "floresta-common", + "floresta-watch-only", + "futures 0.3.28", + "kv", + "log", + "miniscript 9.0.0", + "pretty_assertions", + "rand 0.8.5", + "rmp-serde", + "rustreexo", + "serde", + "serde_json", + "sha2", + "thiserror", + "toml", +] + +[[package]] +name = "floresta-watch-only" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitcoin 0.29.2", + "floresta-common", + "kv", + "log", + "rand 0.8.5", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "floresta-wire" +version = "0.1.0" +dependencies = [ + "async-std", + "bitcoin 0.29.2", + "btcd-rpc", "ctrlc", - "dirs", "dns-lookup", - "futures 0.3.27", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "jsonrpc-http-server", + "floresta-chain", + "floresta-common", + "futures 0.3.28", "kv", "log", - "miniscript", "oneshot", "pretty_assertions", - "pretty_env_logger", "rand 0.8.5", "rmp-serde", "rustreexo", "serde", "serde_json", "sha2", - "signal-hook", - "slip132", + "thiserror", "toml", - "zstd", ] [[package]] -name = "floresta-cli" +name = "florestad" version = "0.1.0" dependencies = [ "anyhow", - "bitcoin", + "async-std", + "bitcoin 0.29.2", + "btcd-rpc", "clap", - "jsonrpc", + "ctrlc", + "dirs", + "floresta-chain", + "floresta-common", + "floresta-electrum", + "floresta-watch-only", + "floresta-wire", + "futures 0.3.28", + "jsonrpc-core", + "jsonrpc-core-client", + "jsonrpc-derive", + "jsonrpc-http-server", + "kv", + "log", + "miniscript 9.0.0", + "pretty_assertions", + "pretty_env_logger", + "rand 0.8.5", + "rmp-serde", + "rustreexo", "serde", "serde_json", + "sha2", + "slip132", + "toml", ] [[package]] @@ -667,11 +915,11 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ - "percent-encoding 2.2.0", + "percent-encoding 2.3.0", ] [[package]] @@ -692,9 +940,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -707,9 +955,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -717,15 +965,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -735,15 +983,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -756,32 +1004,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] name = "futures-sink" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures 0.1.31", "futures-channel", @@ -820,9 +1068,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -841,9 +1089,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if 1.0.0", "libc", @@ -856,7 +1104,7 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.20", "bstr", "fnv", "log", @@ -877,9 +1125,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.16" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -890,16 +1138,36 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.7", + "tokio-util 0.7.8", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25" +dependencies = [ + "ahash 0.3.8", + "autocfg", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] + [[package]] name = "heck" version = "0.4.1" @@ -930,6 +1198,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "http" version = "0.2.9" @@ -975,9 +1249,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.25" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -1023,9 +1297,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1033,12 +1307,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -1052,30 +1326,31 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "is-terminal" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1095,9 +1370,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1129,7 +1404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2b99d4207e2a04fb4581746903c2bb7eb376f88de9c699d0f3e10feeac0cd3a" dependencies = [ "derive_more", - "futures 0.3.27", + "futures 0.3.28", "hyper", "jsonrpc-core", "jsonrpc-pubsub", @@ -1146,7 +1421,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" dependencies = [ - "futures 0.3.27", + "futures 0.3.28", "futures-executor", "futures-util", "log", @@ -1161,7 +1436,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b51da17abecbdab3e3d4f26b01c5ec075e88d3abe3ab3b05dc9aa69392764ec0" dependencies = [ - "futures 0.3.27", + "futures 0.3.28", "jsonrpc-client-transports", ] @@ -1183,7 +1458,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff" dependencies = [ - "futures 0.3.27", + "futures 0.3.28", "hyper", "jsonrpc-core", "jsonrpc-server-utils", @@ -1199,7 +1474,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240f87695e6c6f62fb37f05c02c04953cf68d6408b8c1c89de85c7a0125b1011" dependencies = [ - "futures 0.3.27", + "futures 0.3.28", "jsonrpc-core", "lazy_static", "log", @@ -1215,7 +1490,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4" dependencies = [ "bytes", - "futures 0.3.27", + "futures 0.3.28", "globset", "jsonrpc-core", "lazy_static", @@ -1256,21 +1531,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -1278,11 +1553,10 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" dependencies = [ - "cfg-if 1.0.0", "value-bag", ] @@ -1323,37 +1597,46 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniscript" version = "9.0.0" source = "git+https://github.com/douglaz/rust-miniscript.git?branch=master-2023-03-30#4a3ba11c2fd5063be960741d557f3f7a28041e1f" dependencies = [ - "bitcoin", + "bitcoin 0.29.2", +] + +[[package]] +name = "miniscript" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1eb102b66b2127a872dbcc73095b7b47aeb9d92f7b03c2b2298253ffc82c7594" +dependencies = [ + "bitcoin 0.30.0", + "bitcoin-private", ] [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1376,9 +1659,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.38" +version = "0.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631" +checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" dependencies = [ "cfg-if 0.1.10", "libc", @@ -1428,9 +1711,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oneshot" @@ -1443,9 +1726,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.45" +version = "0.10.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -1458,13 +1741,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -1475,20 +1758,19 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.25.1+1.1.1t" +version = "111.26.0+1.1.1u" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef9a9cc6ea7d9d5e7c4a913dc4b48d0e359eddf01af1dfec96ba7064b4aba10" +checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.80" +version = "0.9.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" dependencies = [ - "autocfg", "cc", "libc", "openssl-src", @@ -1496,15 +1778,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" -dependencies = [ - "memchr", -] - [[package]] name = "output_vt100" version = "0.1.3" @@ -1522,9 +1795,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -1544,7 +1817,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", ] [[package]] @@ -1556,22 +1829,22 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] @@ -1588,9 +1861,9 @@ checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" @@ -1606,15 +1879,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "polling" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e1f879b2998099c2d69ab9605d145d5b661195627eccc680002c4918a7fb6fa" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", "bitflags", @@ -1623,7 +1896,7 @@ dependencies = [ "libc", "log", "pin-project-lite", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1663,35 +1936,11 @@ dependencies = [ "toml", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -1704,9 +1953,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1770,7 +2019,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", ] [[package]] @@ -1791,26 +2040,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.8", - "redox_syscall", + "getrandom 0.2.10", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ - "aho-corasick", + "aho-corasick 1.0.2", "memchr", - "regex-syntax", + "regex-syntax 0.7.2", ] [[package]] @@ -1819,22 +2077,28 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", ] [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "reqwest" -version = "0.11.14" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -1850,7 +2114,7 @@ dependencies = [ "mime", "native-tls", "once_cell", - "percent-encoding 2.2.0", + "percent-encoding 2.3.0", "pin-project-lite", "serde", "serde_json", @@ -1858,7 +2122,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tower-service", - "url 2.3.1", + "url 2.4.0", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1898,26 +2162,25 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.9" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "rustreexo" version = "0.1.0" -source = "git+https://www.github.com/mit-dci/rustreexo#37a74686dbe0ad2e929925130d04a9aa0b42ca59" +source = "git+https://www.github.com/mit-dci/rustreexo#e70cc1a1d6065b46bf15482584c5523a8ef78c2e" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.12.0", "lazy_static", - "sha2", ] [[package]] @@ -1959,11 +2222,21 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ - "bitcoin_hashes", - "secp256k1-sys", + "bitcoin_hashes 0.11.0", + "secp256k1-sys 0.6.1", "serde", ] +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "bitcoin_hashes 0.12.0", + "secp256k1-sys 0.8.1", +] + [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -1973,11 +2246,20 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ "bitflags", "core-foundation", @@ -1988,9 +2270,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys", "libc", @@ -2004,29 +2286,29 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.155" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71f2b4817415c6d4210bfe1c7bfcf4801b2d904cb4d0e1a8fdb651013c9e86b8" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.155" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d071a94a3fac4aff69d023a7f411e33f40f3483f8c5190b1953822b6b76d7630" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" dependencies = [ "itoa", "ryu", @@ -2047,9 +2329,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -2065,16 +2347,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "signal-hook" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2116,7 +2388,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41a2947cb179006a73896fca01015ee5255c05b8b83e74c5e9d623ed4480abe2" dependencies = [ "amplify", - "bitcoin", + "bitcoin 0.29.2", ] [[package]] @@ -2135,6 +2407,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -2160,9 +2441,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -2171,15 +2452,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.4.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if 1.0.0", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -2193,22 +2475,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -2238,14 +2520,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.26.0" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot 0.12.1", @@ -2253,18 +2534,18 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", ] [[package]] @@ -2304,9 +2585,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -2351,14 +2632,14 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -2416,15 +2697,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -2448,15 +2729,21 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", - "idna 0.3.0", - "percent-encoding 2.2.0", + "idna 0.4.0", + "percent-encoding 2.3.0", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "valuable" version = "0.1.0" @@ -2465,13 +2752,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.0.0-alpha.9" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] +checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" [[package]] name = "vcpkg" @@ -2493,11 +2776,10 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -2515,9 +2797,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -2525,24 +2807,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -2552,9 +2834,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2562,28 +2844,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -2626,7 +2908,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.0", + "windows-targets", ] [[package]] @@ -2635,37 +2917,22 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", + "windows-targets", ] [[package]] @@ -2685,9 +2952,9 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" @@ -2697,9 +2964,9 @@ checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -2709,9 +2976,9 @@ checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -2721,9 +2988,9 @@ checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -2733,9 +3000,9 @@ checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -2745,9 +3012,9 @@ checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -2757,9 +3024,9 @@ checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" diff --git a/Cargo.toml b/Cargo.toml index 7c009386..ac07897f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,54 +1,14 @@ -[package] -name = "floresta" -version = "0.4.0" -edition = "2021" - [workspace] -members = [".", "cli/"] - -[dependencies] -rustreexo = { git = "https://www.github.com/mit-dci/rustreexo" } -btcd-rpc = { git = "https://github.com/Davidson-Souza/rust-btcd-rpc", features = [ - "utreexod", -], branch = "use-reqwest", optional = true } -clap = { version = "4.0.29", features = ["derive"] } -sha2 = "^0.10.6" -async-std = { version = "1.12.0", features = ["attributes"] } -log = "0.4" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -kv = "0.24.0" -# Using master branch of miniscript because the multipath feature for output descriptor parsing isn't released yet -miniscript = { git = "https://github.com/douglaz/rust-miniscript.git", branch = "master-2023-03-30" } -pretty_env_logger = "0.4.0" -futures = "0.3.4" -rmp-serde = { optional = true, version = "1.1.1" } -toml = "0.5.10" -argfile = "0.1.5" -dirs = "4.0.0" -signal-hook = "0.3.14" -rand = "0.8.5" -bitcoin = { version = "0.29", features = ["serde", "std", "bitcoinconsensus"] } -slip132 = "0.9.0" -dns-lookup = "1.0.8" -ctrlc = "3.2.5" - -jsonrpc-http-server = { version = "18.0.0", optional = true } -jsonrpc-derive = { version = "18.0.0", optional = true } -jsonrpc-core = { version = "18.0.0", optional = true } -jsonrpc-core-client = { version = "18.0.0", features = [ - "http", -], optional = true } -oneshot = "0.1.5" - -[dev-dependencies] -pretty_assertions = "1" -zstd = "0.12.3" +members = [ + "florestad", + "crates/floresta", + "crates/floresta-chain", + "crates/floresta-cli", + "crates/floresta-common", + "crates/floresta-electrum", + "crates/floresta-watch-only", + "crates/floresta-wire", +] -[features] -default = ["experimental-p2p"] -cli-blockchain = ["rmp-serde", "btcd-rpc"] -experimental-p2p = [] -json-rpc = ["jsonrpc-http-server", "jsonrpc-derive", "jsonrpc-core", "jsonrpc-core-client"] [patch."https://github.com/rust-lang/crates.io-index"] bitcoin = { git = "https://github.com/Davidson-Souza/rust-bitcoin", rev = "a320c6535567acd3771da37759a7644eea5c6eb2" } diff --git a/crates/floresta-chain/Cargo.toml b/crates/floresta-chain/Cargo.toml new file mode 100644 index 00000000..dc1b9136 --- /dev/null +++ b/crates/floresta-chain/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "floresta-chain" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["rlib"] + +[dependencies] +rustreexo = { git = "https://www.github.com/mit-dci/rustreexo" } +sha2 = "^0.10.6" +log = "0.4" +kv = "0.24.0" +bitcoin = { version = "0.29", features = [ + "serde", + "no-std", + "bitcoinconsensus", +] } +spin = "0.9.8" +core2 = { version = "0.4.0", optional = true } +hashbrown = { version = "0.14.0", optional = true } +secp256k1 = { version = "*", features = ["alloc"] } +async-std = { version = "1.12.0", default-features = false, features = [ + "std", + "futures-core", +] } +floresta-common = { path = "../floresta-common" } +futures = "0.3.28" +wasm-bindgen = "0.2.87" + +[dev-dependencies] +pretty_assertions = "1" +rand = "0.8.5" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +zstd = "0.12.3" + +[features] +no-std = ["hashbrown", "core2"] diff --git a/crates/floresta-chain/src/lib.rs b/crates/floresta-chain/src/lib.rs new file mode 100644 index 00000000..7c1f4075 --- /dev/null +++ b/crates/floresta-chain/src/lib.rs @@ -0,0 +1,51 @@ +#![cfg_attr(any(feature = "no-std", not(test)), no_std)] + +pub mod pruned_utreexo; +pub(crate) use floresta_common::prelude; +pub use pruned_utreexo::chain_state::*; +pub use pruned_utreexo::chainparams::*; +pub use pruned_utreexo::chainstore::*; +pub use pruned_utreexo::error::*; +pub use pruned_utreexo::udata::*; +pub use pruned_utreexo::Notification; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Network { + Bitcoin, + Testnet, + Regtest, + Signet, +} +impl From for Network { + fn from(network: bitcoin::network::constants::Network) -> Self { + match network { + bitcoin::network::constants::Network::Bitcoin => Network::Bitcoin, + bitcoin::network::constants::Network::Testnet => Network::Testnet, + bitcoin::network::constants::Network::Regtest => Network::Regtest, + bitcoin::network::constants::Network::Signet => Network::Signet, + } + } +} +impl From for bitcoin::network::constants::Network { + fn from(network: Network) -> Self { + match network { + Network::Bitcoin => bitcoin::network::constants::Network::Bitcoin, + Network::Testnet => bitcoin::network::constants::Network::Testnet, + Network::Regtest => bitcoin::network::constants::Network::Regtest, + Network::Signet => bitcoin::network::constants::Network::Signet, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use bitcoin::network::constants::Network as BNetwork; + #[test] + fn test_network() { + assert_eq!(Network::Bitcoin, BNetwork::Bitcoin.into()); + assert_eq!(Network::Testnet, BNetwork::Testnet.into()); + assert_eq!(Network::Regtest, BNetwork::Regtest.into()); + assert_eq!(Network::Signet, BNetwork::Signet.into()); + } +} diff --git a/src/blockchain/chain_state.rs b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs similarity index 91% rename from src/blockchain/chain_state.rs rename to crates/floresta-chain/src/pruned_utreexo/chain_state.rs index 08ab039e..819a13fd 100644 --- a/src/blockchain/chain_state.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs @@ -1,10 +1,14 @@ +extern crate alloc; use super::{ + chain_state_builder::ChainStateBuilder, chainparams::ChainParams, chainstore::{ChainStore, DiskBlockHeader, KvChainStore}, error::{BlockValidationErrors, BlockchainError}, - BlockchainInterface, BlockchainProviderInterface, Notification, + BlockchainInterface, Notification, UpdatableChainstate, }; -use crate::{read_lock, write_lock}; +use crate::prelude::*; +use crate::{read_lock, write_lock, Network}; +use alloc::{borrow::ToOwned, fmt::format, string::ToString, vec::Vec}; use async_std::channel::Sender; use bitcoin::{ bitcoinconsensus, @@ -12,17 +16,16 @@ use bitcoin::{ consensus::{deserialize_partial, Decodable, Encodable}, hashes::{hex::FromHex, sha256, Hash}, util::uint::Uint256, - Block, BlockHash, BlockHeader, Network, OutPoint, Transaction, TxOut, + Block, BlockHash, BlockHeader, OutPoint, Transaction, TxOut, }; + +use core::ffi::c_uint; +use futures::executor::block_on; use log::{info, trace}; -use rustreexo::accumulator::{proof::Proof, stump::Stump}; +use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof, stump::Stump}; use sha2::{Digest, Sha512_256}; -use std::sync::RwLock; -use std::{ - collections::{HashMap, HashSet}, - ffi::c_uint, - io::Write, -}; +use spin::RwLock; + pub struct ChainStateInner { /// The acc we use for validation. acc: Stump, @@ -49,7 +52,6 @@ pub struct ChainStateInner { /// is still validated. assume_valid: (BlockHash, u32), } - pub struct ChainState { inner: RwLock>, } @@ -64,7 +66,7 @@ impl ChainState { ) -> sha256::Hash { let header_code = height << 1; - let mut ser_utxo = vec![]; + let mut ser_utxo = Vec::new(); let utxo = transaction.output.get(vout as usize).unwrap(); utxo.consensus_encode(&mut ser_utxo).unwrap(); let header_code = if transaction.is_coin_base() { @@ -85,12 +87,15 @@ impl ChainState { } fn maybe_reindex(&self, potential_tip: &DiskBlockHeader) { match potential_tip { - DiskBlockHeader::FullyValid(_, height) | DiskBlockHeader::HeadersOnly(_, height) => { + DiskBlockHeader::HeadersOnly(_, height) => { if *height > self.get_best_block().unwrap().0 { let best_chain = self.reindex_chain(); write_lock!(self).best_block = best_chain; } } + DiskBlockHeader::FullyValid(header, _) => { + self.inner.write().best_block.validation_index = header.block_hash(); + } _ => {} } } @@ -173,7 +178,7 @@ impl ChainState { subsidy: u64, verify_script: bool, flags: c_uint, - ) -> Result { + ) -> Result { // Blocks must contain at least one transaction if transactions.is_empty() { return Err(BlockValidationErrors::EmptyBlock.into()); @@ -252,10 +257,10 @@ impl ChainState { while !self.is_genesis(&header) { let _header = self.get_ancestor(&header)?; if let DiskBlockHeader::Orphan(block) = _header { - return Err(BlockchainError::InvalidTip(format!( + return Err(BlockchainError::InvalidTip(format(format_args!( "Block {} doesn't have a known ancestor (i.e an orphan block)", block.block_hash() - ))); + )))); } header = *_header; } @@ -333,22 +338,22 @@ impl ChainState { continue; } Some(DiskBlockHeader::Orphan(header)) => { - return Err(BlockchainError::InvalidTip(format!( + return Err(BlockchainError::InvalidTip(format(format_args!( "Block {} doesn't have a known ancestor (i.e an orphan block)", header.block_hash() - ))); + )))); } Some(DiskBlockHeader::InvalidChain(header)) => { - return Err(BlockchainError::InvalidTip(format!( + return Err(BlockchainError::InvalidTip(format(format_args!( "Block {} is invalid", header.block_hash() - ))); + )))); } None => { - return Err(BlockchainError::InvalidTip(format!( + return Err(BlockchainError::InvalidTip(format(format_args!( "Block {} isn't in our storage", header.block_hash() - ))); + )))); } } } @@ -373,7 +378,7 @@ impl ChainState { } /// Changes the active chain to the new branch during a reorg fn change_active_chain(&self, new_tip: &BlockHeader, last_valid: BlockHash, depth: u32) { - let mut inner = self.inner.write().unwrap(); + let mut inner = self.inner.write(); inner.best_block.best_block = new_tip.block_hash(); inner.best_block.validation_index = last_valid; inner.best_block.depth = depth; @@ -390,17 +395,17 @@ impl ChainState { match _header { DiskBlockHeader::FullyValid(_, _) => return Ok(header.block_hash()), DiskBlockHeader::Orphan(_) => { - return Err(BlockchainError::InvalidTip(format!( + return Err(BlockchainError::InvalidTip(format(format_args!( "Block {} doesn't have a known ancestor (i.e an orphan block)", header.block_hash() - ))) + )))) } DiskBlockHeader::HeadersOnly(_, _) | DiskBlockHeader::InFork(_, _) => {} DiskBlockHeader::InvalidChain(_) => { - return Err(BlockchainError::InvalidTip(format!( + return Err(BlockchainError::InvalidTip(format(format_args!( "Block {} is in an invalid chain", header.block_hash() - ))) + )))) } } @@ -535,7 +540,12 @@ impl ChainState { del_hashes: Vec, ) -> Result { let block_hash = block.block_hash(); - let mut leaf_hashes = vec![]; + let mut leaf_hashes = Vec::new(); + let del_hashes = del_hashes + .iter() + .map(|hash| NodeHash::from(hash.into_inner())) + .collect::>(); + if !proof.verify(&del_hashes, acc)? { return Err(BlockchainError::InvalidProof); } @@ -560,19 +570,23 @@ impl ChainState { } } } - let acc = acc.modify(&leaf_hashes, &del_hashes, &proof)?.0; + let hashes: Vec = leaf_hashes + .iter() + .map(|&hash| NodeHash::from(hash.into_inner())) + .collect(); + let acc = acc.modify(&hashes, &del_hashes, &proof)?.0; Ok(acc) } fn save_acc(&self) -> Result<(), bitcoin::consensus::encode::Error> { let inner = read_lock!(self); - let mut ser_acc: Vec = vec![]; - inner.acc.leafs.consensus_encode(&mut ser_acc)?; + let mut ser_acc: Vec = Vec::new(); + inner.acc.leaves.consensus_encode(&mut ser_acc)?; #[allow(clippy::significant_drop_in_scrutinee)] for root in inner.acc.roots.iter() { ser_acc - .write_all(root) + .write_all(&**root) .expect("String formatting should not err"); } @@ -585,7 +599,7 @@ impl ChainState { #[allow(clippy::await_holding_lock)] async fn notify(&self, what: Notification) { //TODO: Use async-std::RwLock not std::RwLock - let inner = self.inner.read().unwrap(); + let inner = self.inner.read(); let subs = inner.subscribers.iter(); for client in subs { let _ = client.send(what.clone()).await; @@ -597,7 +611,7 @@ impl ChainState { network: Network, assume_valid: Option, ) -> ChainState { - let genesis = genesis_block(network); + let genesis = genesis_block(network.into()); chainstore .save_header(&super::chainstore::DiskBlockHeader::FullyValid( genesis.header, @@ -618,11 +632,11 @@ impl ChainState { depth: 0, validation_index: genesis.block_hash(), rescan_index: None, - alternative_tips: vec![], + alternative_tips: Vec::new(), assume_valid_index: 0, }, - broadcast_queue: vec![], - subscribers: vec![], + broadcast_queue: Vec::new(), + subscribers: Vec::new(), fee_estimation: (1_f64, 1_f64, 1_f64), ibd: true, chain_params: network.into(), @@ -678,10 +692,10 @@ impl ChainState { } BestChain { best_block: hash, - depth: height - 1, + depth: height, validation_index: hash, rescan_index: None, - alternative_tips: vec![], + alternative_tips: Vec::new(), assume_valid_index: 0, } } @@ -730,18 +744,15 @@ impl ChainState { let leaves = acc.drain(0..8).collect::>(); let (leaves, _) = deserialize_partial::(&leaves).expect("load_acc: Invalid num_leaves"); - let mut roots = vec![]; + let mut roots = Vec::new(); while acc.len() >= 32 { // Since we only expect hashes after the num_leaves, it should always align with 32 bytes assert_eq!(acc.len() % 32, 0); let root = acc.drain(0..32).collect::>(); - let root = sha256::Hash::from_slice(&root).expect("Invalid hash"); + let root = NodeHash::try_from(&*root).expect("Invalid hash"); roots.push(root); } - Stump { - leafs: leaves, - roots, - } + Stump { leaves, roots } } fn update_view( &self, @@ -773,7 +784,7 @@ impl ChainState { inner.best_block.depth = height; } fn verify_script(&self, height: u32) -> bool { - let inner = self.inner.read().unwrap(); + let inner = self.inner.read(); inner.assume_valid.1 < height } fn acc(&self) -> Stump { @@ -819,12 +830,13 @@ impl ChainState { } impl BlockchainInterface for ChainState { + type Error = BlockchainError; fn is_in_idb(&self) -> bool { - self.inner.read().unwrap().ibd + self.inner.read().ibd } fn get_block_hash(&self, height: u32) -> super::Result { - let inner = self.inner.read().expect("get_block_hash: Poisoned lock"); + let inner = self.inner.read(); if let Some(hash) = inner.chainstore.get_block_hash(height)? { return Ok(hash); } @@ -883,19 +895,12 @@ impl BlockchainInterface for ChainState) { - let mut inner = self.inner.write().expect("get_block_hash: Poisoned lock"); + let mut inner = self.inner.write(); inner.subscribers.push(tx); } - - fn get_root_hashes(&self) -> super::Result> { - let inner = read_lock!(self); - Ok(inner.acc.roots.clone()) - } -} -impl BlockchainProviderInterface for ChainState { fn get_block_locator(&self) -> Result, BlockchainError> { let top_height = self.get_height()?; - let mut indexes = vec![]; + let mut indexes = Vec::new(); let mut step = 1; let mut index = top_height; while index > 0 { @@ -917,6 +922,31 @@ impl BlockchainProviderInterface for ChainState super::Result { + let inner = self.inner.read(); + let validation = inner.best_block.validation_index; + let header = self.get_disk_block_header(&validation)?; + match header { + DiskBlockHeader::HeadersOnly(_, height) => Ok(height), + DiskBlockHeader::FullyValid(_, height) => Ok(height), + _ => unreachable!( + "Validation index is in an invalid state, you should re-index your node" + ), + } + } + + fn is_coinbase_mature(&self, height: u32, block: BlockHash) -> super::Result { + let chain_params = self.chain_params(); + let current_height = self.get_disk_block_header(&block)?.height().unwrap_or(0); + + Ok(height + chain_params.coinbase_maturity <= current_height) + } + fn get_unbroadcasted(&self) -> Vec { + let mut inner = write_lock!(self); + inner.broadcast_queue.drain(..).collect() + } +} +impl UpdatableChainstate for ChainState { fn invalidate_block(&self, block: BlockHash) -> super::Result<()> { let height = self.get_disk_block_header(&block)?.height(); if height.is_none() { @@ -947,7 +977,7 @@ impl BlockchainProviderInterface for ChainState super::Result<()> { let header = self.get_disk_block_header(&block.block_hash())?; let height = header.height().expect("Recaning in an invalid tip"); - async_std::task::block_on(self.notify(Notification::NewBlock((block.to_owned(), height)))); + block_on(self.notify(Notification::NewBlock((block.to_owned(), height)))); if self.get_height().unwrap() == height { info!("Rescan completed"); write_lock!(self).best_block.rescan_index = None; @@ -1002,7 +1032,7 @@ impl BlockchainProviderInterface for ChainState BlockchainProviderInterface for ChainState Vec { - let mut inner = write_lock!(self); - inner.broadcast_queue.drain(..).collect() - } + fn accept_header(&self, header: BlockHeader) -> super::Result<()> { trace!("Accepting header {header:?}"); let _header = self.get_disk_block_header(&header.block_hash()); @@ -1054,41 +1081,43 @@ impl BlockchainProviderInterface for ChainState super::Result { - let inner = self.inner.read().unwrap(); - let validation = inner.best_block.validation_index; - let header = self.get_disk_block_header(&validation)?; - match header { - DiskBlockHeader::HeadersOnly(_, height) => Ok(height), - DiskBlockHeader::FullyValid(_, height) => Ok(height), - _ => { - self.reindex_chain(); - log::error!("Validation index is in an invalid state, re-indexing chainstate"); - Ok(0) - } - } + fn get_root_hashes(&self) -> Vec { + let inner = read_lock!(self); + inner.acc.roots.clone() } +} - fn is_coinbase_mature(&self, height: u32, block: BlockHash) -> super::Result { - let chain_params = self.chain_params(); - let current_height = self.get_disk_block_header(&block)?.height().unwrap_or(0); +impl From> for ChainState { + fn from(mut builder: ChainStateBuilder) -> Self { + let inner = ChainStateInner { + acc: builder.acc(), + chainstore: builder.chainstore(), + best_block: builder.best_block(), + assume_valid: builder.assume_valid(), + ibd: builder.ibd(), + broadcast_queue: Vec::new(), + subscribers: Vec::new(), + fee_estimation: (1_f64, 1_f64, 1_f64), + chain_params: builder.chain_params(), + }; - Ok(height + chain_params.coinbase_maturity <= current_height) + let inner = RwLock::new(inner); + Self { inner } } } + #[macro_export] /// Grabs a RwLock for reading macro_rules! read_lock { ($obj: ident) => { - $obj.inner.read().expect("get_block_hash: Poisoned lock") + $obj.inner.read() }; } #[macro_export] /// Grabs a RwLock for writing macro_rules! write_lock { ($obj: ident) => { - $obj.inner.write().expect("get_block_hash: Poisoned lock") + $obj.inner.write() }; } @@ -1122,10 +1151,7 @@ impl BestChain { } } impl Encodable for BestChain { - fn consensus_encode( - &self, - writer: &mut W, - ) -> Result { + fn consensus_encode(&self, writer: &mut W) -> Result { let mut len = 0; len += self.best_block.consensus_encode(writer)?; len += self.depth.consensus_encode(writer)?; @@ -1140,8 +1166,20 @@ impl Encodable for BestChain { Ok(len) } } +impl From<(BlockHash, u32)> for BestChain { + fn from((best_block, depth): (BlockHash, u32)) -> Self { + Self { + best_block, + depth, + validation_index: best_block, + rescan_index: None, + assume_valid_index: 0, + alternative_tips: Vec::new(), + } + } +} impl Decodable for BestChain { - fn consensus_decode( + fn consensus_decode( reader: &mut R, ) -> Result { let best_block = BlockHash::consensus_decode(reader)?; @@ -1169,19 +1207,20 @@ impl Decodable for BestChain { #[cfg(test)] mod test { - use std::{collections::HashMap, io::Cursor}; - + extern crate std; + use crate::prelude::HashMap; + use crate::Network; use bitcoin::{ consensus::{deserialize, Decodable}, hashes::hex::FromHex, - Block, BlockHash, BlockHeader, Network, + Block, BlockHash, BlockHeader, }; use rustreexo::accumulator::proof::Proof; + use std::io::Cursor; + use std::{format, vec::Vec}; - use crate::blockchain::{ - chainparams::ChainParams, - chainstore::{DiskBlockHeader, KvChainStore}, - BlockchainInterface, BlockchainProviderInterface, + use super::{ + BlockchainInterface, ChainParams, DiskBlockHeader, KvChainStore, UpdatableChainstate, }; use super::ChainState; @@ -1242,7 +1281,7 @@ mod test { let block: Block = deserialize(&block).unwrap(); chain.accept_header(block.header).unwrap(); chain - .connect_block(&block, Proof::default(), HashMap::new(), vec![]) + .connect_block(&block, Proof::default(), HashMap::new(), Vec::new()) .unwrap(); } assert_eq!( diff --git a/crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs b/crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs new file mode 100644 index 00000000..1eb17cca --- /dev/null +++ b/crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs @@ -0,0 +1,85 @@ +use bitcoin::hashes::Hash; +use bitcoin::BlockHash; +use rustreexo::accumulator::stump::Stump; + +use crate::{BestChain, ChainParams, ChainState, ChainStore}; + +#[derive(Clone, Debug)] +pub enum BlockchainBuilderError { + MissingChainstore, + MissingChainParams, +} +#[derive(Clone, Debug, Default)] +pub struct ChainStateBuilder { + acc: Option, + chainstore: Option, + ibd: bool, + chain_params: Option, + assume_valid: Option<(BlockHash, u32)>, + tip: Option<(BlockHash, u32)>, +} + +impl ChainStateBuilder { + pub fn new() -> Self { + ChainStateBuilder { + acc: None, + chainstore: None, + ibd: true, + chain_params: None, + assume_valid: None, + tip: None, + } + } + pub fn build(self) -> Result, BlockchainBuilderError> { + if self.chainstore.is_none() { + return Err(BlockchainBuilderError::MissingChainstore); + } + if self.chain_params.is_none() { + return Err(BlockchainBuilderError::MissingChainParams); + } + Ok(ChainState::from(self)) + } + pub fn with_chainstore(mut self, chainstore: T) -> Self { + self.chainstore = Some(chainstore); + self + } + pub fn toggle_ibd(mut self, ibd: bool) -> Self { + self.ibd = ibd; + self + } + pub fn with_chain_params(mut self, chain_params: ChainParams) -> Self { + self.chain_params = Some(chain_params); + self + } + pub fn with_assume_valid(mut self, assume_valid: (BlockHash, u32)) -> Self { + self.assume_valid = Some(assume_valid); + self + } + pub fn assume_utreexo(mut self, acc: Stump) -> Self { + self.acc = Some(acc); + self + } + pub fn with_tip(mut self, tip: (BlockHash, u32)) -> Self { + self.tip = Some(tip); + self + } + pub fn acc(&self) -> Stump { + self.acc.clone().unwrap_or_default() + } + pub fn chainstore(&mut self) -> T { + self.chainstore.take().unwrap() + } + pub fn ibd(&self) -> bool { + self.ibd + } + pub fn chain_params(&self) -> ChainParams { + self.chain_params.clone().unwrap() + } + pub fn best_block(&self) -> BestChain { + let block = self.tip.unwrap_or((BlockHash::all_zeros(), 0)); + BestChain::from(block) + } + pub fn assume_valid(&self) -> (BlockHash, u32) { + self.assume_valid.unwrap_or((BlockHash::all_zeros(), 0)) + } +} diff --git a/src/blockchain/chainparams.rs b/crates/floresta-chain/src/pruned_utreexo/chainparams.rs similarity index 94% rename from src/blockchain/chainparams.rs rename to crates/floresta-chain/src/pruned_utreexo/chainparams.rs index be198c68..c1d99a41 100644 --- a/src/blockchain/chainparams.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chainparams.rs @@ -1,13 +1,17 @@ -use std::{collections::HashMap, ffi::c_uint}; +extern crate alloc; +use crate::prelude::*; +use alloc::vec::Vec; +use crate::Network; use bitcoin::{ bitcoinconsensus::{VERIFY_NONE, VERIFY_P2SH, VERIFY_WITNESS}, blockdata::constants::{genesis_block, max_target}, hashes::hex::FromHex, util::uint::Uint256, - Block, BlockHash, Network, + Block, BlockHash, }; -#[derive(Clone)] +use core::ffi::c_uint; +#[derive(Clone, Debug)] pub struct ChainParams { /// The network's first block, also called genesis block. pub genesis: Block, @@ -38,12 +42,14 @@ pub struct ChainParams { pub segwit_activation_height: u32, /// The height at which csv(CHECK_SEQUENCE_VERIFY) is activated pub csv_activation_height: u32, + /// A list of exceptions to the rules, where the key is the block hash and the value is the + /// verification flags pub exceptions: HashMap, } impl ChainParams { fn max_target(net: Network) -> Uint256 { match net { - Network::Bitcoin => max_target(net), + Network::Bitcoin => max_target(net.into()), Network::Testnet => Uint256([ 0x0000000000000000, 0x0000000000000000, @@ -67,7 +73,7 @@ impl ChainParams { } impl From for ChainParams { fn from(net: Network) -> Self { - let genesis = genesis_block(net); + let genesis = genesis_block(net.into()); let max_target = ChainParams::max_target(net); // For some reason, some blocks in the mainnet and testnet have different rules than it should // be, so we need to keep a list of exceptions and treat them differently @@ -158,7 +164,7 @@ impl From for ChainParams { } pub fn get_chain_dns_seeds(network: Network) -> Vec<&'static str> { - let mut seeds = vec![]; + let mut seeds = Vec::new(); // DNS seeds taken from Bitcoin Core at commit 382b692a503355df7347efd9c128aff465b5583e match network { Network::Bitcoin => { diff --git a/src/blockchain/chainstore.rs b/crates/floresta-chain/src/pruned_utreexo/chainstore.rs similarity index 95% rename from src/blockchain/chainstore.rs rename to crates/floresta-chain/src/pruned_utreexo/chainstore.rs index 6d452d03..f8827720 100644 --- a/src/blockchain/chainstore.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chainstore.rs @@ -3,9 +3,8 @@ //! This is a basic kv database that stores all metadata about our blockchain and utreexo //! state. //! Author: Davidson Souza -type Result = std::result::Result; - -use std::ops::Deref; +type Result = core::result::Result; +use crate::prelude::*; use bitcoin::{ consensus::{deserialize, serialize, Decodable, Encodable}, @@ -46,9 +45,9 @@ impl Deref for DiskBlockHeader { } } impl Decodable for DiskBlockHeader { - fn consensus_decode( + fn consensus_decode( reader: &mut R, - ) -> std::result::Result { + ) -> core::result::Result { let tag = u8::consensus_decode(reader)?; let header = BlockHeader::consensus_decode(reader)?; @@ -73,10 +72,10 @@ impl Decodable for DiskBlockHeader { } } impl Encodable for DiskBlockHeader { - fn consensus_encode( + fn consensus_encode( &self, writer: &mut W, - ) -> std::result::Result { + ) -> core::result::Result { let mut len = 80 + 1; // Header + tag + height match self { DiskBlockHeader::FullyValid(header, height) => { @@ -126,7 +125,6 @@ pub trait ChainStore { fn flush(&self) -> Result<()>; fn update_block_index(&self, height: u32, hash: BlockHash) -> Result<()>; } - pub struct KvChainStore(Store); impl KvChainStore { pub fn new(datadir: String) -> Result { diff --git a/crates/floresta-chain/src/pruned_utreexo/error.rs b/crates/floresta-chain/src/pruned_utreexo/error.rs new file mode 100644 index 00000000..f6333527 --- /dev/null +++ b/crates/floresta-chain/src/pruned_utreexo/error.rs @@ -0,0 +1,78 @@ +use crate::prelude::*; +use bitcoin::blockdata::script; +#[cfg(feature = "cli-blockchain")] +use btcd_rpc::error::UtreexodError; +use floresta_common::impl_error_from; + +#[derive(Debug)] +pub enum BlockchainError { + BlockNotPresent, + #[cfg(feature = "cli-blockchain")] + #[error("Json-Rpc error")] + JsonRpcError(#[from] UtreexodError), + ParsingError(bitcoin::hashes::hex::Error), + BlockValidationError(BlockValidationErrors), + InvalidProof, + UtreexoError(String), + DatabaseError(kv::Error), + ConsensusDecodeError(bitcoin::consensus::encode::Error), + ChainNotInitialized, + InvalidTip(String), + ScriptValidationFailed(script::Error), + IoError(ioError), +} + +#[derive(Debug)] +pub enum BlockValidationErrors { + InvalidTx, + NotEnoughPow, + BadMerkleRoot, + BadWitnessCommitment, + NotEnoughMoney, + FirstTxIsnNotCoinbase, + BadCoinbaseOutValue, + EmptyBlock, + BlockExtendsAnOrphanChain, + BadBip34, +} +impl Display for BlockValidationErrors { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + BlockValidationErrors::InvalidTx => { + write!(f, "This block contains an invalid transaction") + } + BlockValidationErrors::NotEnoughPow => { + write!(f, "This block doesn't have enough proof-of-work") + } + BlockValidationErrors::BadMerkleRoot => write!(f, "Wrong merkle root"), + BlockValidationErrors::BadWitnessCommitment => write!(f, "Wrong witness commitment"), + BlockValidationErrors::NotEnoughMoney => { + write!(f, "A transaction spends more than it should") + } + BlockValidationErrors::FirstTxIsnNotCoinbase => { + write!(f, "The first transaction in a block isn't a coinbase") + } + BlockValidationErrors::BadCoinbaseOutValue => { + write!(f, "Coinbase claims more bitcoins than it should") + } + BlockValidationErrors::EmptyBlock => { + write!(f, "This block is empty (doesn't have a coinbase tx)") + } + BlockValidationErrors::BlockExtendsAnOrphanChain => { + write!(f, "This block extends a chain we don't have the ancestors") + } + BlockValidationErrors::BadBip34 => write!(f, "BIP34 commitment mismatch"), + } + } +} +impl_error_from!(BlockchainError, kv::Error, DatabaseError); +impl_error_from!(BlockchainError, ioError, IoError); +impl_error_from!( + BlockchainError, + bitcoin::consensus::encode::Error, + ConsensusDecodeError +); +impl_error_from!(BlockchainError, BlockValidationErrors, BlockValidationError); +impl_error_from!(BlockchainError, bitcoin::hashes::hex::Error, ParsingError); +impl_error_from!(BlockchainError, String, UtreexoError); +impl_error_from!(BlockchainError, script::Error, ScriptValidationFailed); diff --git a/src/blockchain/mod.rs b/crates/floresta-chain/src/pruned_utreexo/mod.rs similarity index 85% rename from src/blockchain/mod.rs rename to crates/floresta-chain/src/pruned_utreexo/mod.rs index d3ed521d..67266793 100644 --- a/src/blockchain/mod.rs +++ b/crates/floresta-chain/src/pruned_utreexo/mod.rs @@ -1,27 +1,23 @@ +extern crate alloc; pub mod chain_state; -mod chainparams; +pub mod chain_state_builder; +pub mod chainparams; pub mod chainstore; -#[cfg(not(feature = "experimental-p2p"))] -pub mod cli_blockchain; pub mod error; -#[cfg(feature = "experimental-p2p")] -pub mod p2p_blockchain; pub mod udata; -use std::collections::HashMap; - +use crate::prelude::*; use async_std::channel::Sender; use bitcoin::{hashes::sha256, Block, BlockHash, BlockHeader, OutPoint, Transaction, TxOut}; -use rustreexo::accumulator::proof::Proof; +use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof}; use self::error::BlockchainError; -type Result = std::result::Result; +type Result = core::result::Result; /// This trait is the main interface between our blockchain backend and other services. /// It'll be useful for transitioning from rpc to a p2p based node pub trait BlockchainInterface { - /// Returns the roots of our utreexo accumulator - fn get_root_hashes(&self) -> Result>; + type Error; /// Returns the block with a given height in our current tip. fn get_block_hash(&self, height: u32) -> Result; /// Returns a bitcoin [Transaction] given it's txid. @@ -43,13 +39,22 @@ pub trait BlockchainInterface { fn subscribe(&self, tx: Sender); /// Tells whether or not we are on ibd fn is_in_idb(&self) -> bool; + /// Returns the list of unbroadcasted transactions. + fn get_unbroadcasted(&self) -> Vec; + /// Checks if a coinbase is mature + fn is_coinbase_mature(&self, height: u32, block: BlockHash) -> Result; + /// Returns a block locator + fn get_block_locator(&self) -> Result>; + /// Returns the last block we validated + fn get_validation_index(&self) -> Result; /// Triggers a rescan, downloading (but not validating) all blocks in [start_height:tip] fn rescan(&self, start_height: u32) -> Result<()>; fn get_rescan_index(&self) -> Option; } -/// A [BlockchainProviderInterface] is the trait that a implementation of blockchain uses to update -/// the chainstate. -pub trait BlockchainProviderInterface { +/// [UpdatableChainstate] is a contract that a is expected from a chainstate +/// implementation, that wishes to be updated. Using those methods, a backend like the p2p-node, +/// can notify new blocks and transactions to a chainstate, allowing it to update it's state. +pub trait UpdatableChainstate { /// This is one of the most important methods for a ChainState, it gets a block and some utreexo data, /// validates this block and connects to our chain of blocks. This function is meant to /// be atomic and prone of running in parallel. @@ -69,20 +74,16 @@ pub trait BlockchainProviderInterface { fn handle_transaction(&self) -> Result<()>; /// Persists our data. Should be invoked periodically. fn flush(&self) -> Result<()>; - /// Returns the list of unbroadcasted transactions. - fn get_unbroadcasted(&self) -> Vec; /// Toggle ibd on/off fn toggle_ibd(&self, is_ibd: bool); - /// Returns a block locator - fn get_block_locator(&self) -> Result>; - /// Returns the last block we validated - fn get_validation_index(&self) -> Result; /// Tells this blockchain to consider this block invalid, and not build on top of it fn invalidate_block(&self, block: BlockHash) -> Result<()>; - /// Checks if a coinbase is mature - fn is_coinbase_mature(&self, height: u32, block: BlockHash) -> Result; + /// Gives a requested block for rescan fn process_rescan_block(&self, block: &Block) -> Result<()>; + /// Returns the root hashes of our utreexo forest + fn get_root_hashes(&self) -> Vec; } + #[derive(Debug, Clone)] /// A notification is a hook that a type implementing [BlockchainInterface] sends each /// time the given event happens. This is use to notify new blocks to the Electrum server. diff --git a/src/blockchain/testdata/headers.zst b/crates/floresta-chain/src/pruned_utreexo/testdata/headers.zst similarity index 100% rename from src/blockchain/testdata/headers.zst rename to crates/floresta-chain/src/pruned_utreexo/testdata/headers.zst diff --git a/src/blockchain/testdata/signet_headers b/crates/floresta-chain/src/pruned_utreexo/testdata/signet_headers similarity index 100% rename from src/blockchain/testdata/signet_headers rename to crates/floresta-chain/src/pruned_utreexo/testdata/signet_headers diff --git a/src/blockchain/testdata/signet_headers.zst b/crates/floresta-chain/src/pruned_utreexo/testdata/signet_headers.zst similarity index 100% rename from src/blockchain/testdata/signet_headers.zst rename to crates/floresta-chain/src/pruned_utreexo/testdata/signet_headers.zst diff --git a/src/blockchain/testdata/test_reorg.json b/crates/floresta-chain/src/pruned_utreexo/testdata/test_reorg.json similarity index 100% rename from src/blockchain/testdata/test_reorg.json rename to crates/floresta-chain/src/pruned_utreexo/testdata/test_reorg.json diff --git a/src/blockchain/udata.rs b/crates/floresta-chain/src/pruned_utreexo/udata.rs similarity index 97% rename from src/blockchain/udata.rs rename to crates/floresta-chain/src/pruned_utreexo/udata.rs index a6501316..d425fce8 100644 --- a/src/blockchain/udata.rs +++ b/crates/floresta-chain/src/pruned_utreexo/udata.rs @@ -1,16 +1,16 @@ //! UData is the serialized data used for proof propagation in utreexo. It contains all //! data needed for validating some piece of information, like a transaction and a block. + +use crate::prelude::*; use bitcoin::{ consensus::{Decodable, Encodable}, hashes::{sha256, Hash}, BlockHash, OutPoint, TxOut, }; -use serde::Deserialize; use sha2::{Digest, Sha512_256}; - /// Leaf data is the data that is hashed when adding to utreexo state. It contains validation /// data and some commitments to make it harder to attack an utreexo-only node. -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Debug, PartialEq)] pub struct LeafData { /// A commitment to the block creating this utxo pub block_hash: BlockHash, @@ -19,7 +19,7 @@ pub struct LeafData { /// Header code is a compact commitment to the block height and whether or not this /// transaction is coinbase. It's defined as /// - /// ``` + /// ```ignore /// header_code: u32 = if transaction.is_coinbase() { /// (block_height << 1 ) | 1 /// } else { @@ -32,7 +32,7 @@ pub struct LeafData { } impl LeafData { pub fn _get_leaf_hashes(&self) -> sha256::Hash { - let mut ser_utxo = vec![]; + let mut ser_utxo = Vec::new(); let _ = self.utxo.consensus_encode(&mut ser_utxo); let leaf_hash = Sha512_256::new() .chain_update(self.block_hash) @@ -46,12 +46,12 @@ impl LeafData { } } impl Decodable for LeafData { - fn consensus_decode( + fn consensus_decode( reader: &mut R, ) -> Result { Self::consensus_decode_from_finite_reader(reader) } - fn consensus_decode_from_finite_reader( + fn consensus_decode_from_finite_reader( reader: &mut R, ) -> Result { let block_hash = BlockHash::consensus_decode(reader)?; @@ -66,7 +66,6 @@ impl Decodable for LeafData { }) } } -#[cfg(feature = "experimental-p2p")] pub mod proof_util { use bitcoin::{ blockdata::script::Instruction, hashes::Hash, network::utreexo::CompactLeafData, @@ -149,9 +148,11 @@ pub mod proof_util { Err(Error::EmptyStack) } } -#[cfg(all(test, feature = "experimental-p2p"))] +#[cfg(test)] mod test { + extern crate std; use std::str::FromStr; + use std::vec::Vec; use bitcoin::{ consensus::deserialize, hashes::hex::FromHex, network::utreexo::CompactLeafData, Amount, diff --git a/cli/Cargo.toml b/crates/floresta-cli/Cargo.toml similarity index 100% rename from cli/Cargo.toml rename to crates/floresta-cli/Cargo.toml diff --git a/cli/README.md b/crates/floresta-cli/README.md similarity index 100% rename from cli/README.md rename to crates/floresta-cli/README.md diff --git a/cli/src/main.rs b/crates/floresta-cli/src/main.rs similarity index 96% rename from cli/src/main.rs rename to crates/floresta-cli/src/main.rs index 6e8e0511..bdb7b38c 100644 --- a/cli/src/main.rs +++ b/crates/floresta-cli/src/main.rs @@ -57,7 +57,7 @@ fn get_req(cmd: &Cli) -> (Vec>, String) { Methods::Stop => "stop", }; let params = match &cmd.methods { - Methods::GetBlockchainInfo => vec![], + Methods::GetBlockchainInfo => Vec::new(), Methods::GetBlockHash { height } => vec![arg(height)], Methods::GetTxOut { txid, vout } => vec![arg(txid), arg(vout)], Methods::GetTxProof { txids, blockhash } => { @@ -80,14 +80,14 @@ fn get_req(cmd: &Cli) -> (Vec>, String) { } Methods::RescanBlockchain { start_height } => vec![arg(start_height)], Methods::SendRawTransaction { tx } => vec![arg(tx)], - Methods::GetRoots => vec![], + Methods::GetRoots => Vec::new(), Methods::GetBlock { hash } => vec![arg(hash)], - Methods::GetPeerInfo => vec![], - Methods::ListTransactions => vec![], + Methods::GetPeerInfo => Vec::new(), + Methods::ListTransactions => Vec::new(), Methods::FindUtxo { height, txid, vout } => { vec![arg(height), arg(txid), arg(vout)] } - Methods::Stop => vec![], + Methods::Stop => Vec::new(), }; (params, method.to_string()) diff --git a/crates/floresta-common/Cargo.toml b/crates/floresta-common/Cargo.toml new file mode 100644 index 00000000..56c50f03 --- /dev/null +++ b/crates/floresta-common/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "floresta-common" +version = "0.1.0" +edition = "2021" +description = "Multiple inernal utilities" +authors = ["Davidson Souza "] +license = "MIT" + +[dependencies] +sha2 = "^0.10.6" +bitcoin = "0.29.2" +miniscript = { git = "https://github.com/douglaz/rust-miniscript.git", branch = "master-2023-03-30" } diff --git a/src/version.rs b/crates/floresta-common/src/constants.rs similarity index 100% rename from src/version.rs rename to crates/floresta-common/src/constants.rs diff --git a/crates/floresta-common/src/lib.rs b/crates/floresta-common/src/lib.rs new file mode 100644 index 00000000..9b52adec --- /dev/null +++ b/crates/floresta-common/src/lib.rs @@ -0,0 +1,83 @@ +#![no_std] +use bitcoin::hashes::{sha256, Hash}; +use bitcoin::Script; +use miniscript::{Descriptor, DescriptorPublicKey}; +use prelude::*; + +use sha2::Digest; +pub mod constants; + +pub fn get_hash_from_u8(data: &[u8]) -> sha256::Hash { + let hash = sha2::Sha256::new().chain_update(data).finalize(); + sha256::Hash::from_slice(hash.as_slice()).expect("Engines shouldn't be Err") +} + +pub fn get_spk_hash(spk: &Script) -> sha256::Hash { + let script_hash = spk.as_bytes(); + let mut hash = sha2::Sha256::new().chain_update(script_hash).finalize(); + hash.reverse(); + sha256::Hash::from_slice(hash.as_slice()).expect("Engines shouldn't be Err") +} +pub fn parse_descriptors( + descriptors: &[String], +) -> Result>, miniscript::Error> { + let descriptors = descriptors + .iter() + .map(|descriptor| { + let descriptor = Descriptor::::from_str(descriptor.as_str())?; + descriptor.sanity_check()?; + descriptor.into_single_descriptors() + }) + .collect::>, _>>()? + .into_iter() + .flatten() + .collect::>(); + Ok(descriptors) +} +#[cfg(feature = "no-std")] +pub mod prelude { + extern crate alloc; + pub use alloc::{borrow::ToOwned, string::String, vec, vec::Vec}; + pub use core::{ + cmp, convert, + core::str::FromStr, + fmt, + fmt::Display, + iter, mem, ops, + ops::{Deref, DerefMut}, + option, result, slice, str, + }; + + pub use core2::{ + error::Error, + io::{Error as ioError, Read, Write}, + }; + pub use hashbrown::{HashMap, HashSet}; +} +#[cfg(not(feature = "no-std"))] +pub mod prelude { + extern crate std; + pub use std::borrow::ToOwned; + pub use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + error::Error, + fmt::{self, Display, Formatter}, + io::{Error as ioError, Read, Write}, + ops::{Deref, DerefMut}, + result::Result, + str::FromStr, + string::String, + sync, + vec::{self, Vec}, + }; +} +#[macro_export] +macro_rules! impl_error_from { + ($thing: ty, $from_thing: ty, $field: ident) => { + impl From<$from_thing> for $thing { + fn from(e: $from_thing) -> Self { + <$thing>::$field(e) + } + } + }; +} diff --git a/crates/floresta-electrum/Cargo.toml b/crates/floresta-electrum/Cargo.toml new file mode 100644 index 00000000..2f72924c --- /dev/null +++ b/crates/floresta-electrum/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "floresta-electrum" +version = "0.1.0" +edition = "2021" + +[dependencies] +floresta-common = { path = "../floresta-common" } +floresta-chain = { path = "../floresta-chain" } +floresta-watch-only = { path = "../floresta-watch-only" } + +rustreexo = { git = "https://www.github.com/mit-dci/rustreexo" } +btcd-rpc = { git = "https://github.com/Davidson-Souza/rust-btcd-rpc", features = [ + "utreexod", +], branch = "use-reqwest", optional = true } +sha2 = "^0.10.6" +async-std = { version = "1.12.0", features = ["attributes"] } +log = "0.4" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +kv = "0.24.0" +miniscript = { git = "https://github.com/douglaz/rust-miniscript.git", branch = "master-2023-03-30" } +futures = "0.3.4" +rmp-serde = { optional = true, version = "1.1.1" } +toml = "0.5.10" +bitcoin = { version = "0.29", features = ["serde", "std", "bitcoinconsensus"] } +thiserror = "1.0" + +[dev-dependencies] +pretty_assertions = "1" +rand = "0.8.5" diff --git a/src/electrum/electrum_protocol.rs b/crates/floresta-electrum/src/electrum_protocol.rs similarity index 95% rename from src/electrum/electrum_protocol.rs rename to crates/floresta-electrum/src/electrum_protocol.rs index f2b9ea34..71c43b9e 100644 --- a/src/electrum/electrum_protocol.rs +++ b/crates/floresta-electrum/src/electrum_protocol.rs @@ -1,9 +1,11 @@ -use crate::address_cache::kv_database::KvDatabase; -use crate::address_cache::{AddressCache, CachedTransaction}; -use crate::blockchain::{BlockchainInterface, Notification}; -use crate::electrum::request::Request; +use crate::request::Request; use crate::{get_arg, json_rpc_res}; use bitcoin::hashes::hex::FromHex; +use floresta_chain::pruned_utreexo::BlockchainInterface; +use floresta_chain::Notification; +use floresta_common::{get_hash_from_u8, get_spk_hash}; +use floresta_watch_only::kv_database::KvDatabase; +use floresta_watch_only::{AddressCache, CachedTransaction}; use futures::{select, FutureExt}; use async_std::sync::RwLock; @@ -14,7 +16,7 @@ use async_std::{ prelude::*, }; -use bitcoin::hashes::{hex::ToHex, sha256, Hash}; +use bitcoin::hashes::{hex::ToHex, sha256}; use bitcoin::{ consensus::{deserialize, serialize}, Script, Txid, @@ -23,7 +25,6 @@ use bitcoin::{Transaction, TxOut}; use log::{info, log, trace, Level}; use serde_json::{json, Value}; -use sha2::Digest; use std::collections::{HashMap, HashSet}; use std::sync::Arc; @@ -73,7 +74,7 @@ impl ElectrumServer { ) -> Result, Box> { let listener = Arc::new(TcpListener::bind(address).await?); let (tx, rx) = unbounded(); - let unconfirmed = address_cache.read().await.find_unconfirmed()?; + let unconfirmed = address_cache.read().await.find_unconfirmed().unwrap(); for tx in unconfirmed { chain.broadcast(&tx).expect("Invalid chain"); } @@ -100,7 +101,10 @@ impl ElectrumServer { .chain .get_block_hash(height as u32) .map_err(|_| super::error::Error::InvalidParams)?; - let header = self.chain.get_block_header(&hash)?; + let header = self.chain.get_block_header(&hash).map_err(|val| { + println!("Error: {:?}", val); + super::error::Error::ChainError(val) + })?; let header = serialize(&header).to_hex(); json_rpc_res!(request, header) } @@ -157,7 +161,7 @@ impl ElectrumServer { .read() .await .get_address_history(&script_hash); - let mut res = vec![]; + let mut res = Vec::new(); for transaction in transactions { let entry = if transaction.height == 0 { json!({ @@ -182,9 +186,9 @@ impl ElectrumServer { let hash = get_arg!(request, sha256::Hash, 0); let utxos = self.address_cache.read().await.get_address_utxos(&hash); if utxos.is_none() { - return Err(crate::electrum::error::Error::InvalidParams); + return Err(crate::error::Error::InvalidParams); } - let mut final_utxos = vec![]; + let mut final_utxos = Vec::new(); for (utxo, prevout) in utxos.unwrap().into_iter() { let height = self .address_cache @@ -470,17 +474,6 @@ pub async fn accept_loop(listener: Arc, notify_channel: Sender sha256::Hash { - let hash = sha2::Sha256::new().chain_update(data).finalize(); - sha256::Hash::from_slice(hash.as_slice()).expect("Engines shouldn't be Err") -} -pub fn get_spk_hash(spk: &Script) -> sha256::Hash { - let script_hash = spk.as_bytes(); - let mut hash = sha2::Sha256::new().chain_update(script_hash).finalize(); - hash.reverse(); - sha256::Hash::from_slice(hash.as_slice()).expect("Engines shouldn't be Err") -} /// As per electrum documentation: /// ### To calculate the status of a script hash (or address): /// @@ -506,6 +499,7 @@ fn get_status(transactions: Vec) -> sha256::Hash { } get_hash_from_u8(status_preimage.as_bytes()) } + #[macro_export] /// Builds the response as defined by jsonrpc v2.0. Request should have type [Request] and the /// response is always a [Json] diff --git a/crates/floresta-electrum/src/error.rs b/crates/floresta-electrum/src/error.rs new file mode 100644 index 00000000..2371deac --- /dev/null +++ b/crates/floresta-electrum/src/error.rs @@ -0,0 +1,23 @@ +#[cfg(feature = "cli-blockchain")] +use btcd_rpc::error::UtreexodError; + +use floresta_chain::BlockchainError; +use floresta_common::impl_error_from; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Utreexod error")] + #[cfg(feature = "cli-blockchain")] + BackendError(#[from] UtreexodError), + #[error("Invalid params passed in")] + InvalidParams, + #[error("Invalid json string {0}")] + ParsingError(#[from] serde_json::Error), + #[error("Blockchain error")] + ChainError(BlockchainError), + #[error("IO error")] + IoError(#[from] std::io::Error), +} + +impl_error_from!(Error, BlockchainError, ChainError); diff --git a/src/electrum/mod.rs b/crates/floresta-electrum/src/lib.rs similarity index 100% rename from src/electrum/mod.rs rename to crates/floresta-electrum/src/lib.rs diff --git a/src/electrum/request.rs b/crates/floresta-electrum/src/request.rs similarity index 100% rename from src/electrum/request.rs rename to crates/floresta-electrum/src/request.rs diff --git a/crates/floresta-watch-only/Cargo.toml b/crates/floresta-watch-only/Cargo.toml new file mode 100644 index 00000000..31e663e0 --- /dev/null +++ b/crates/floresta-watch-only/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "floresta-watch-only" +version = "0.1.0" +edition = "2021" +description = "A simple, lightweight and Electrum-First, watch-only wallet" +authors = ["Davidson Souza "] +keywords = ["bitcoin", "watch-only", "electrum-server"] +license = "MIT" + +[dependencies] +serde = "1.0.0" +serde_json = { version = "1.0.0", features = ["alloc"] } +anyhow = "1.0.70" +bitcoin = { version = "0.29.2", features = ["serde"] } +kv = "0.24.0" +log = "0.4" +thiserror = "1.0" +floresta-common = { path = "../floresta-common" } + +[dev-dependencies] +rand = "0.8.5" + +[features] +default = [] +memory-database = [] +no-std = [] +std = ["serde/std"] \ No newline at end of file diff --git a/src/address_cache/kv_database.rs b/crates/floresta-watch-only/src/kv_database.rs similarity index 52% rename from src/address_cache/kv_database.rs rename to crates/floresta-watch-only/src/kv_database.rs index 886e6842..5d269a40 100644 --- a/src/address_cache/kv_database.rs +++ b/crates/floresta-watch-only/src/kv_database.rs @@ -1,14 +1,16 @@ use super::{AddressCacheDatabase, Stats}; +use bitcoin::consensus::encode::Error; use bitcoin::{ consensus::{deserialize, serialize}, - hashes::Hash, + hashes::{hex::ToHex, Hash}, Txid, }; +use floresta_common::{impl_error_from, prelude::*}; use kv::{Bucket, Config, Store}; pub struct KvDatabase(Store, Bucket<'static, String, Vec>); impl KvDatabase { - pub fn new(datadir: String) -> Result { + pub fn new(datadir: String) -> Result { // Configure the database let cfg = Config::new(datadir); @@ -18,14 +20,38 @@ impl KvDatabase { Ok(KvDatabase(store, bucket)) } } +#[derive(Debug)] +pub enum KvDatabaseError { + KvError(kv::Error), + SerdeJsonError(serde_json::Error), + WalletNotInitialized, + DeserializeError(Error), + TransactionNotFound, +} +impl_error_from!(KvDatabaseError, serde_json::Error, SerdeJsonError); +impl_error_from!(KvDatabaseError, kv::Error, KvError); +impl_error_from!(KvDatabaseError, Error, DeserializeError); + +impl Display for KvDatabaseError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + KvDatabaseError::KvError(e) => write!(f, "KvError: {}", e), + KvDatabaseError::SerdeJsonError(e) => write!(f, "SerdeJsonError: {}", e), + KvDatabaseError::WalletNotInitialized => write!(f, "WalletNotInitialized"), + KvDatabaseError::DeserializeError(e) => write!(f, "DeserializeError: {}", e), + KvDatabaseError::TransactionNotFound => write!(f, "TransactionNotFound"), + } + } +} + +impl floresta_common::prelude::Error for KvDatabaseError {} + +type Result = floresta_common::prelude::Result; + impl AddressCacheDatabase for KvDatabase { - fn load(&self) -> Result, E> - where - E: From - + std::convert::From - + std::convert::From, - { - let mut addresses = vec![]; + type Error = KvDatabaseError; + fn load(&self) -> Result> { + let mut addresses = Vec::new(); for item in self.1.iter() { let item = item?; let key = item.key::()?; @@ -39,7 +65,7 @@ impl AddressCacheDatabase for KvDatabase { Ok(addresses) } fn save(&self, address: &super::CachedAddress) { - let key = address.script_hash.to_string(); + let key = address.script_hash.to_hex(); let value = serde_json::to_vec(&address).expect("Invalid object serialization"); self.1 @@ -50,50 +76,47 @@ impl AddressCacheDatabase for KvDatabase { fn update(&self, address: &super::CachedAddress) { self.save(address); } - fn get_cache_height(&self) -> Result { - let height = self.1.get(&"height".to_string())?; + fn get_cache_height(&self) -> Result { + let height = self.1.get(&String::from("height"))?; if let Some(height) = height { return Ok(deserialize(&height)?); } - Err(crate::error::Error::WalletNotInitialized) + Err(KvDatabaseError::WalletNotInitialized) } - fn set_cache_height(&self, height: u32) -> Result<(), crate::error::Error> { - self.1.set(&"height".to_string(), &serialize(&height))?; + fn set_cache_height(&self, height: u32) -> Result<()> { + self.1.set(&String::from("height"), &serialize(&height))?; self.1.flush()?; Ok(()) } - fn desc_save(&self, descriptor: &str) -> Result<(), crate::error::Error> { + fn desc_save(&self, descriptor: &str) -> Result<()> { let mut descs = self.descs_get()?; - descs.push(descriptor.to_string()); + descs.push(String::from(descriptor)); self.1 - .set(&"desc".to_string(), &serde_json::to_vec(&descs).unwrap())?; + .set(&String::from("desc"), &serde_json::to_vec(&descs).unwrap())?; self.1.flush()?; Ok(()) } - fn descs_get(&self) -> Result, crate::error::Error> { - let res = self.1.get(&"desc".to_string())?; + fn descs_get(&self) -> Result> { + let res = self.1.get(&String::from("desc"))?; if let Some(res) = res { return Ok(serde_json::de::from_slice(&res)?); } - Ok(vec![]) + Ok(Vec::new()) } - fn get_transaction( - &self, - txid: &bitcoin::Txid, - ) -> Result { + fn get_transaction(&self, txid: &bitcoin::Txid) -> Result { let store = self.0.bucket::<&[u8], Vec>(Some("transactions"))?; let res = store.get(&txid.to_vec().as_slice())?; if let Some(res) = res { return Ok(serde_json::de::from_slice(&res)?); } - Err(crate::error::Error::TransactionNotFound) + Err(KvDatabaseError::TransactionNotFound) } - fn save_transaction(&self, tx: &super::CachedTransaction) -> Result<(), crate::error::Error> { + fn save_transaction(&self, tx: &super::CachedTransaction) -> Result<()> { let store = self.0.bucket::<&[u8], Vec>(Some("transactions"))?; let ser_tx = serde_json::to_vec(&tx)?; store.set(&tx.tx.txid().to_vec().as_slice(), &ser_tx)?; @@ -102,26 +125,26 @@ impl AddressCacheDatabase for KvDatabase { Ok(()) } - fn get_stats(&self) -> Result { + fn get_stats(&self) -> Result { let store = self.0.bucket::<&[u8], Vec>(Some("stats"))?; - let res = store.get(&"stats".to_string().as_bytes())?; + let res = store.get(&String::from("stats").as_bytes())?; if let Some(res) = res { return Ok(serde_json::de::from_slice(&res)?); } - Err(crate::error::Error::TransactionNotFound) + Err(KvDatabaseError::TransactionNotFound) } - fn save_stats(&self, stats: &Stats) -> Result<(), crate::error::Error> { + fn save_stats(&self, stats: &Stats) -> Result<()> { let store = self.0.bucket::<&[u8], Vec>(Some("stats"))?; let ser_stats = serde_json::to_vec(&stats)?; - store.set(&"stats".to_string().as_bytes(), &ser_stats)?; + store.set(&String::from("stats").as_bytes(), &ser_stats)?; self.1.flush()?; Ok(()) } - fn list_transactions(&self) -> Result, crate::error::Error> { - let mut transactions = vec![]; + fn list_transactions(&self) -> Result> { + let mut transactions = Vec::new(); let store = self.0.bucket::<&[u8], Vec>(Some("transactions"))?; for item in store.iter() { diff --git a/src/address_cache/mod.rs b/crates/floresta-watch-only/src/lib.rs similarity index 82% rename from src/address_cache/mod.rs rename to crates/floresta-watch-only/src/lib.rs index a031841b..62ad5ebb 100644 --- a/src/address_cache/mod.rs +++ b/crates/floresta-watch-only/src/lib.rs @@ -1,28 +1,57 @@ +#![cfg_attr(feature = "no-std", no_std)] +use core::{cmp::Ordering, fmt::Debug}; + +use floresta_common::{get_spk_hash, parse_descriptors}; + pub mod kv_database; -#[cfg(test)] +#[cfg(any(test, feature = "memory-database"))] pub mod memory_database; pub mod merkle; -use merkle::MerkleProof; -use serde::{Deserialize, Serialize}; -use std::{ - cmp::Ordering, - collections::{HashMap, HashSet}, - vec, -}; - -use crate::{electrum::electrum_protocol::get_spk_hash, wallet_input::parse_descriptors}; use bitcoin::{ consensus::deserialize, consensus::encode::serialize_hex, hash_types::Txid, hashes::{ hex::{FromHex, ToHex}, - sha256::{self, Hash}, + sha256::Hash, Hash as HashTrait, }, Block, OutPoint, Script, Transaction, TxOut, }; +use floresta_common::prelude::*; + +use merkle::MerkleProof; +use serde::{Deserialize, Serialize}; + +#[derive(Debug)] +pub enum WatchOnlyError { + WalletNotInitialized, + TransactionNotFound, + DatabaseError(DatabaseError), +} +impl Display for WatchOnlyError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + WatchOnlyError::WalletNotInitialized => { + write!(f, "Wallet isn't initialized") + } + WatchOnlyError::TransactionNotFound => { + write!(f, "Transaction not found") + } + WatchOnlyError::DatabaseError(e) => { + write!(f, "Database error: {:?}", e) + } + } + } +} +impl From for WatchOnlyError { + fn from(e: DatabaseError) -> Self { + WatchOnlyError::DatabaseError(e) + } +} +impl floresta_common::prelude::Error for WatchOnlyError {} + /// Every address contains zero or more associated transactions, this struct defines what /// data we store for those. #[derive(Debug, Clone, Eq, Serialize, Deserialize)] @@ -89,37 +118,33 @@ pub struct Stats { } /// Public trait defining a common interface for databases to be used with our cache pub trait AddressCacheDatabase { + type Error: fmt::Debug + Send + Sync + 'static; /// Saves a new address to the database. If the address already exists, `update` should /// be used instead fn save(&self, address: &CachedAddress); /// Loads all addresses we have cached so far - fn load(&self) -> Result, E> - where - E: From - + Into - + std::convert::From - + std::convert::From; + fn load(&self) -> Result, Self::Error>; /// Loads the data associated with our watch-only wallet. - fn get_stats(&self) -> Result; + fn get_stats(&self) -> Result; /// Saves the data associated with our watch-only wallet. - fn save_stats(&self, stats: &Stats) -> Result<(), crate::error::Error>; + fn save_stats(&self, stats: &Stats) -> Result<(), Self::Error>; /// Updates an address, probably because a new transaction arrived fn update(&self, address: &CachedAddress); /// TODO: Maybe turn this into another db /// Returns the height of the last block we filtered - fn get_cache_height(&self) -> Result; + fn get_cache_height(&self) -> Result; /// Saves the height of the last block we filtered - fn set_cache_height(&self, height: u32) -> Result<(), crate::error::Error>; + fn set_cache_height(&self, height: u32) -> Result<(), Self::Error>; /// Saves the descriptor of associated cache - fn desc_save(&self, descriptor: &str) -> Result<(), crate::error::Error>; + fn desc_save(&self, descriptor: &str) -> Result<(), Self::Error>; /// Get associated descriptors - fn descs_get(&self) -> Result, crate::error::Error>; + fn descs_get(&self) -> Result, Self::Error>; /// Get a transaction from the database - fn get_transaction(&self, txid: &Txid) -> Result; + fn get_transaction(&self, txid: &Txid) -> Result; /// Saves a transaction to the database - fn save_transaction(&self, tx: &CachedTransaction) -> Result<(), crate::error::Error>; - /// Returns all transactions we have cached - fn list_transactions(&self) -> Result, crate::error::Error>; + fn save_transaction(&self, tx: &CachedTransaction) -> Result<(), Self::Error>; + /// Returns all transaction we have cached so far + fn list_transactions(&self) -> Result, Self::Error>; } /// Holds all addresses and associated transactions. We need a database with some basic /// methods, to store all data @@ -135,11 +160,12 @@ pub struct AddressCache { /// Keeps track of all utxos we own, and the script hash they belong to utxo_index: HashMap, } + impl AddressCache { /// Iterates through a block, finds transactions destined to ourselves. /// Returns all transactions we found. pub fn block_process(&mut self, block: &Block, height: u32) -> Vec<(Transaction, TxOut)> { - let mut my_transactions = vec![]; + let mut my_transactions = Vec::new(); // Check if this transaction spends from one of our utxos for (position, transaction) in block.txdata.iter().enumerate() { for (vin, txin) in transaction.input.iter().enumerate() { @@ -194,6 +220,7 @@ impl AddressCache { } my_transactions } + fn get_stats(&self) -> Stats { self.database .get_stats() @@ -205,9 +232,7 @@ impl AddressCache { .expect("Database is not working"); } pub fn new(database: D) -> AddressCache { - let scripts = database - .load::() - .expect("Could not load database"); + let scripts = database.load().expect("Could not load database"); if database.get_stats().is_err() { database .save_stats(&Stats::default()) @@ -230,10 +255,10 @@ impl AddressCache { utxo_index, } } - pub fn get_address_utxos(&self, script_hash: &sha256::Hash) -> Option> { + pub fn get_address_utxos(&self, script_hash: &Hash) -> Option> { let address = self.address_map.get(script_hash)?; let utxos = &address.utxos; - let mut address_utxos = vec![]; + let mut address_utxos = Vec::new(); for utxo in utxos { let tx = self.get_transaction(&utxo.txid)?; let txout = tx.tx.output.get(utxo.vout as usize)?; @@ -246,7 +271,7 @@ impl AddressCache { self.database.get_transaction(txid).ok() } /// Returns all transactions this address has, both input and outputs - pub fn get_address_history(&self, script_hash: &sha256::Hash) -> Vec { + pub fn get_address_history(&self, script_hash: &Hash) -> Vec { if let Some(cached_script) = self.address_map.get(script_hash) { let mut transactions: Vec<_> = cached_script .transactions @@ -261,10 +286,10 @@ impl AddressCache { transactions.extend(unconfirmed); return transactions; } - vec![] + Vec::new() } /// Returns the balance of this address, debts (spends) are taken in account - pub fn get_address_balance(&self, script_hash: &sha256::Hash) -> u64 { + pub fn get_address_balance(&self, script_hash: &Hash) -> u64 { if let Some(cached_script) = self.address_map.get(script_hash) { return cached_script.balance; } @@ -273,7 +298,7 @@ impl AddressCache { } /// Returns the Merkle Proof for a given address pub fn get_merkle_proof(&self, txid: &Txid) -> Option<(Vec, u32)> { - let mut hashes = vec![]; + let mut hashes = Vec::new(); let tx = self.get_transaction(txid)?; // If a given transaction is cached, but the merkle tree doesn't exist, that means // an unconfirmed transaction. @@ -289,7 +314,6 @@ impl AddressCache { pub fn get_height(&self, txid: &Txid) -> Option { Some(self.get_transaction(txid)?.height) } - pub fn get_cached_transaction(&self, txid: &Txid) -> Option { let tx = self.get_transaction(txid)?; Some(serialize_hex(&tx.tx)) @@ -315,24 +339,24 @@ impl AddressCache { } /// Setup is the first command that should be executed. In a new cache. It sets our wallet's /// state, like the height we should start scanning and the wallet's descriptor. - pub fn setup(&self) -> Result<(), crate::error::Error> { + pub fn setup(&self) -> Result<(), WatchOnlyError> { if self.database.descs_get().is_err() { self.database.set_cache_height(0)?; } Ok(()) } /// Tells whether or not a descriptor is already cached - pub fn is_cached(&self, desc: &String) -> Result { + pub fn is_cached(&self, desc: &String) -> Result> { let known_descs = self.database.descs_get()?; Ok(known_descs.contains(desc)) } - pub fn push_descriptor(&self, descriptor: &str) -> Result<(), crate::error::Error> { - self.database.desc_save(descriptor) + pub fn push_descriptor(&self, descriptor: &str) -> Result<(), WatchOnlyError> { + Ok(self.database.desc_save(descriptor)?) } - fn derive_addresses(&mut self) -> Result<(), crate::error::Error> { + fn derive_addresses(&mut self) -> Result<(), WatchOnlyError> { let mut stats = self.get_stats(); let descriptors = self.database.descs_get()?; - let descriptors = parse_descriptors(&descriptors)?; + let descriptors = parse_descriptors(&descriptors).expect("We validate those descriptors"); for desc in descriptors { let index = stats.derivation_index; for idx in index..(index + 100) { @@ -344,7 +368,7 @@ impl AddressCache { } } stats.derivation_index += 100; - self.database.save_stats(&stats) + Ok(self.database.save_stats(&stats)?) } fn maybe_derive_addresses(&mut self) { let stats = self.get_stats(); @@ -355,9 +379,9 @@ impl AddressCache { } } } - pub fn find_unconfirmed(&self) -> Result, crate::error::Error> { + pub fn find_unconfirmed(&self) -> Result, WatchOnlyError> { let transactions = self.database.list_transactions()?; - let mut unconfirmed = vec![]; + let mut unconfirmed = Vec::new(); for tx in transactions { let tx = self.database.get_transaction(&tx)?; @@ -368,7 +392,7 @@ impl AddressCache { Ok(unconfirmed) } fn find_spend(&self, transaction: &Transaction) -> Vec<(usize, TxOut)> { - let mut spends = vec![]; + let mut spends = Vec::new(); for (idx, input) in transaction.input.iter().enumerate() { if self.utxo_index.contains_key(&input.previous_output) { let prev_tx = self.get_transaction(&input.previous_output.txid).unwrap(); @@ -464,7 +488,6 @@ impl AddressCache { }; address.utxos.push(utxo); self.utxo_index.insert(utxo, hash); - address.balance += value; } @@ -500,7 +523,7 @@ impl AddressCache { .expect("Database not working"); let hash = get_spk_hash(script); - if let std::collections::hash_map::Entry::Vacant(e) = self.address_map.entry(hash) { + if let Entry::Vacant(e) = self.address_map.entry(hash) { // This means `cache_transaction` have been called with an address we don't // follow. This may be useful for caching new addresses without re-scanning. // We can track this address from now onwards, but the past history is only @@ -508,12 +531,12 @@ impl AddressCache { let new_address = CachedAddress { balance: transaction.output[index].value, script_hash: hash, - transactions: vec![transaction_to_cache.hash], + transactions: Vec::from([transaction_to_cache.hash]), script: script.to_owned(), - utxos: vec![OutPoint { + utxos: Vec::from([OutPoint { txid: transaction.txid(), vout: index as u32, - }], + }]), }; self.database.save(&new_address); @@ -539,13 +562,16 @@ impl AddressCache { #[cfg(test)] mod test { - use crate::electrum::electrum_protocol::get_spk_hash; use bitcoin::{ consensus::{deserialize, Decodable}, - hashes::{hex::FromHex, sha256}, + hashes::{ + hex::{FromHex, ToHex}, + sha256, + }, Address, Script, Txid, }; - use std::str::FromStr; + use floresta_common::get_spk_hash; + use floresta_common::prelude::*; const BLOCK_FIRST_UTXO: &str = "00000020b4f594a390823c53557c5a449fa12413cbbae02be529c11c4eb320ff8e000000dd1211eb35ca09dc0ee519b0f79319fae6ed32c66f8bbf353c38513e2132c435474d81633c4b011e195a220002010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0403edce01feffffff028df2052a0100000016001481113cad52683679a83e76f76f84a4cfe36f75010000000000000000776a24aa21a9ed67863b4f356b7b9f3aab7a2037615989ef844a0917fb0a1dcd6c23a383ee346b4c4fecc7daa2490047304402203768ff10a948a2dd1825cc5a3b0d336d819ea68b5711add1390b290bf3b1cba202201d15e73791b2df4c0904fc3f7c7b2f22ab77762958e9bc76c625138ad3a04d290100012000000000000000000000000000000000000000000000000000000000000000000000000002000000000101be07b18750559a418d144f1530be380aa5f28a68a0269d6b2d0e6ff3ff25f3200000000000feffffff0240420f00000000001600142b6a2924aa9b1b115d1ac3098b0ba0e6ed510f2a326f55d94c060000160014c2ed86a626ee74d854a12c9bb6a9b72a80c0ddc50247304402204c47f6783800831bd2c75f44d8430bf4d962175349dc04d690a617de6c1eaed502200ffe70188a6e5ad89871b2acb4d0f732c2256c7ed641d2934c6e84069c792abc012103ba174d9c66078cf813d0ac54f5b19b5fe75104596bdd6c1731d9436ad8776f41ecce0100"; const BLOCK_SPEND: &str = "000000203ea734fa2c8dee7d3194878c9eaf6e83a629f79b3076ec857793995e01010000eb99c679c0305a1ac0f5eb2a07a9f080616105e605b92b8c06129a2451899225ab5481633c4b011e0b26720102020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0403efce01feffffff026ef2052a01000000225120a1a1b1376d5165617a50a6d2f59abc984ead8a92df2b25f94b53dbc2151824730000000000000000776a24aa21a9ed1b4c48a7220572ff3ab3d2d1c9231854cb62542fbb1e0a4b21ebbbcde8d652bc4c4fecc7daa2490047304402204b37c41fce11918df010cea4151737868111575df07f7f2945d372e32a6d11dd02201658873a8228d7982df6bdbfff5d0cad1d6f07ee400e2179e8eaad8d115b7ed001000120000000000000000000000000000000000000000000000000000000000000000000000000020000000001017ca523c5e6df0c014e837279ab49be1676a9fe7571c3989aeba1e5d534f4054a0000000000fdffffff01d2410f00000000001600142b6a2924aa9b1b115d1ac3098b0ba0e6ed510f2a02473044022071b8583ba1f10531b68cb5bd269fb0e75714c20c5a8bce49d8a2307d27a082df022069a978dac00dd9d5761aa48c7acc881617fa4d2573476b11685596b17d437595012103b193d06bd0533d053f959b50e3132861527e5a7a49ad59c5e80a265ff6a77605eece0100"; @@ -553,7 +579,7 @@ mod test { let hex = Vec::from_hex(thing).unwrap(); deserialize(&hex).unwrap() } - use super::{kv_database::KvDatabase, memory_database::MemoryDatabase, AddressCache}; + use super::{memory_database::MemoryDatabase, AddressCache}; fn get_test_cache() -> AddressCache { let database = MemoryDatabase::new(); AddressCache::new(database) @@ -578,7 +604,7 @@ mod test { // Assert we indeed have one cached address assert_eq!(cache.address_map.len(), 1); assert_eq!(cache.get_address_balance(&script_hash), 0); - assert_eq!(cache.get_address_history(&script_hash), vec![]); + assert_eq!(cache.get_address_history(&script_hash), Vec::new()); } #[test] fn test_cache_transaction() { @@ -612,12 +638,12 @@ mod test { let cached_merkle_block = cache.get_merkle_proof(&transaction.txid()).unwrap(); assert_eq!(balance, 999890); assert_eq!( - history[0].hash.to_string(), + history[0].hash.to_hex(), String::from("6bb0665122c7dcecc6e6c45b6384ee2bdce148aea097896e6f3e9e08070353ea") ); - let expected_hashes = vec![String::from( + let expected_hashes = Vec::from([String::from( "e7d6e69230db7dd074cc2610c32be013468f1c224172b347eccdef98f36e0834", - )]; + )]); assert_eq!(cached_merkle_block, (expected_hashes, 1)); } #[test] @@ -638,50 +664,12 @@ mod test { let cached_merkle_block = cache.get_merkle_proof(&transaction_id).unwrap(); assert_eq!(balance, 999890); assert_eq!( - history[0].hash.to_string(), - String::from("6bb0665122c7dcecc6e6c45b6384ee2bdce148aea097896e6f3e9e08070353ea") - ); - let expected_hashes = vec![String::from( - "e7d6e69230db7dd074cc2610c32be013468f1c224172b347eccdef98f36e0834", - )]; - assert_eq!(cached_merkle_block, (expected_hashes, 1)); - } - #[test] - fn test_persistency() { - let random_name = rand::random::(); - let (address, script_hash) = get_test_address(); - // Create a new address cache - { - let database = KvDatabase::new(format!("/tmp/utreexo/{random_name}")) - .expect("Could not open database"); - let mut cache = AddressCache::new(database); - - cache.cache_address(address.script_pubkey()); - - let block = "000000203ea734fa2c8dee7d3194878c9eaf6e83a629f79b3076ec857793995e01010000eb99c679c0305a1ac0f5eb2a07a9f080616105e605b92b8c06129a2451899225ab5481633c4b011e0b26720102020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0403efce01feffffff026ef2052a01000000225120a1a1b1376d5165617a50a6d2f59abc984ead8a92df2b25f94b53dbc2151824730000000000000000776a24aa21a9ed1b4c48a7220572ff3ab3d2d1c9231854cb62542fbb1e0a4b21ebbbcde8d652bc4c4fecc7daa2490047304402204b37c41fce11918df010cea4151737868111575df07f7f2945d372e32a6d11dd02201658873a8228d7982df6bdbfff5d0cad1d6f07ee400e2179e8eaad8d115b7ed001000120000000000000000000000000000000000000000000000000000000000000000000000000020000000001017ca523c5e6df0c014e837279ab49be1676a9fe7571c3989aeba1e5d534f4054a0000000000fdffffff01d2410f00000000001600142b6a2924aa9b1b115d1ac3098b0ba0e6ed510f2a02473044022071b8583ba1f10531b68cb5bd269fb0e75714c20c5a8bce49d8a2307d27a082df022069a978dac00dd9d5761aa48c7acc881617fa4d2573476b11685596b17d437595012103b193d06bd0533d053f959b50e3132861527e5a7a49ad59c5e80a265ff6a77605eece0100"; - let block = deserialize(&Vec::from_hex(block).unwrap()).unwrap(); - cache.block_process(&block, 118511); - cache.bump_height(118511) - } - // Load it from disk after persisting the data - let database = KvDatabase::new(format!("/tmp/utreexo/{random_name}")) - .expect("Could not open database"); - let cache = AddressCache::new(database); - - let balance = cache.get_address_balance(&script_hash); - let history = cache.get_address_history(&script_hash); - let transaction_id = - Txid::from_str("6bb0665122c7dcecc6e6c45b6384ee2bdce148aea097896e6f3e9e08070353ea") - .unwrap(); - let cached_merkle_block = cache.get_merkle_proof(&transaction_id).unwrap(); - assert_eq!(balance, 999890); - assert_eq!( - history[0].hash.to_string(), + history[0].hash.to_hex(), String::from("6bb0665122c7dcecc6e6c45b6384ee2bdce148aea097896e6f3e9e08070353ea") ); - let expected_hashes = vec![String::from( + let expected_hashes = Vec::from([String::from( "e7d6e69230db7dd074cc2610c32be013468f1c224172b347eccdef98f36e0834", - )]; + )]); assert_eq!(cached_merkle_block, (expected_hashes, 1)); } #[test] diff --git a/src/address_cache/memory_database.rs b/crates/floresta-watch-only/src/memory_database.rs similarity index 58% rename from src/address_cache/memory_database.rs rename to crates/floresta-watch-only/src/memory_database.rs index d4bfb9e8..a8f85b9c 100644 --- a/src/address_cache/memory_database.rs +++ b/crates/floresta-watch-only/src/memory_database.rs @@ -4,10 +4,9 @@ //! //! For actual databases that can be used for production code, see [KvDatabase]. use super::{AddressCacheDatabase, CachedAddress, CachedTransaction, Stats}; -use crate::error::Error; - use bitcoin::{hashes::sha256, Txid}; -use std::{collections::HashMap, sync::RwLock}; +use floresta_common::prelude::sync::RwLock; +use floresta_common::prelude::*; #[derive(Debug, Default)] struct Inner { addresses: HashMap, @@ -17,15 +16,27 @@ struct Inner { descriptors: Vec, } +#[derive(Debug)] +pub enum MemoryDatabaseError { + PoisonedLock, +} +#[derive(Debug, Default)] pub struct MemoryDatabase { inner: RwLock, } + +type Result = floresta_common::prelude::Result; + impl MemoryDatabase { - fn get_inner_mut(&self) -> Result, Error> { - self.inner.write().map_err(|_| Error::DatabaseError) + fn get_inner(&self) -> Result> { + self.inner + .read() + .map_err(|_| MemoryDatabaseError::PoisonedLock) } - fn get_inner(&self) -> Result, Error> { - self.inner.read().map_err(|_| Error::DatabaseError) + fn get_inner_mut(&self) -> Result> { + self.inner + .write() + .map_err(|_| MemoryDatabaseError::PoisonedLock) } pub fn new() -> MemoryDatabase { MemoryDatabase { @@ -34,6 +45,7 @@ impl MemoryDatabase { } } impl AddressCacheDatabase for MemoryDatabase { + type Error = MemoryDatabaseError; fn save(&self, address: &CachedAddress) { self.get_inner_mut() .map(|mut inner| { @@ -44,24 +56,19 @@ impl AddressCacheDatabase for MemoryDatabase { .unwrap(); } - fn load(&self) -> Result, E> - where - E: From - + Into - + std::convert::From - + std::convert::From, - { + fn load(&self) -> Result> { Ok(self.get_inner()?.addresses.values().cloned().collect()) } - fn get_stats(&self) -> Result { + fn get_stats(&self) -> Result { Ok(self.get_inner()?.stats.to_owned()) } - fn save_stats(&self, stats: &super::Stats) -> Result<(), crate::error::Error> { + fn save_stats(&self, stats: &super::Stats) -> Result<()> { self.get_inner_mut().map(|mut inner| { inner.stats.clone_from(stats); - }) + })?; + Ok(()) } fn update(&self, address: &super::CachedAddress) { @@ -75,43 +82,40 @@ impl AddressCacheDatabase for MemoryDatabase { .unwrap(); } - fn get_cache_height(&self) -> Result { + fn get_cache_height(&self) -> Result { Ok(self.get_inner()?.height) } - fn set_cache_height(&self, height: u32) -> Result<(), crate::error::Error> { + fn set_cache_height(&self, height: u32) -> Result<()> { self.get_inner_mut()?.height = height; Ok(()) } - fn desc_save(&self, descriptor: &str) -> Result<(), crate::error::Error> { + fn desc_save(&self, descriptor: &str) -> Result<()> { self.get_inner_mut().map(|mut inner| { inner.descriptors.push(descriptor.into()); }) } - fn descs_get(&self) -> Result, crate::error::Error> { + fn descs_get(&self) -> Result> { Ok(self.get_inner()?.descriptors.to_owned()) } - fn get_transaction( - &self, - txid: &bitcoin::Txid, - ) -> Result { - self.get_inner()? - .transactions - .get(txid) - .map_or(Err(Error::TransactionNotFound), |tx| Ok(tx.to_owned())) + fn get_transaction(&self, txid: &bitcoin::Txid) -> Result { + if let Some(tx) = self.get_inner()?.transactions.get(txid) { + return Ok(tx.clone()); + } + Err(MemoryDatabaseError::PoisonedLock) } - fn save_transaction(&self, tx: &super::CachedTransaction) -> Result<(), crate::error::Error> { - self.get_inner_mut() - .map(|mut inner| inner.transactions.insert(tx.hash, tx.to_owned())) - .map_err(|_| Error::DatabaseError)?; + fn save_transaction(&self, tx: &super::CachedTransaction) -> Result<()> { + self.get_inner_mut()? + .transactions + .insert(tx.hash, tx.to_owned()); Ok(()) } - fn list_transactions(&self) -> Result, crate::error::Error> { + fn list_transactions(&self) -> Result> { Ok(self.get_inner()?.transactions.keys().copied().collect()) } } diff --git a/src/address_cache/merkle.rs b/crates/floresta-watch-only/src/merkle.rs similarity index 96% rename from src/address_cache/merkle.rs rename to crates/floresta-watch-only/src/merkle.rs index 96dd1aa7..a60fdb3f 100644 --- a/src/address_cache/merkle.rs +++ b/crates/floresta-watch-only/src/merkle.rs @@ -3,6 +3,7 @@ use bitcoin::{ hashes::{sha256d, Hash, HashEngine}, Block, Txid, }; +use floresta_common::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct MerkleProof { @@ -20,7 +21,7 @@ impl MerkleProof { fn new() -> Self { MerkleProof { target: Txid::all_zeros(), - hashes: vec![], + hashes: Vec::new(), pos: 0, } } @@ -33,7 +34,7 @@ impl MerkleProof { /// this only proves one tx at the time. pub fn from_block_hashes(tx_list: Vec, target: u64) -> Self { let target_hash = tx_list[target as usize]; - let (_, proof) = Self::transverse(tx_list, vec![], target); + let (_, proof) = Self::transverse(tx_list, Vec::new(), target); Self { target: target_hash.into(), pos: target, @@ -92,7 +93,7 @@ impl MerkleProof { return (nodes, proof); } // Here we store all nodes for the next row - let mut new_nodes = vec![]; + let mut new_nodes = Vec::new(); // Grab a node's sibling. In a Merkle Tree, our target nodes are given, and its parent // can be computed using available data. We must only provide a node's sibling, so verifier // can get a parent hash. @@ -126,13 +127,13 @@ impl MerkleProof { } impl Decodable for MerkleProof { - fn consensus_decode( + fn consensus_decode( reader: &mut R, ) -> Result { let pos = u64::consensus_decode(reader)?; let target = Txid::consensus_decode(reader)?; let len = u64::consensus_decode(reader)?; - let mut hashes = vec![]; + let mut hashes = Vec::new(); for _ in 0..len { let hash = sha256d::Hash::consensus_decode(reader)?; hashes.push(hash); @@ -145,10 +146,7 @@ impl Decodable for MerkleProof { } } impl Encodable for MerkleProof { - fn consensus_encode( - &self, - writer: &mut W, - ) -> Result { + fn consensus_encode(&self, writer: &mut W) -> Result { let mut len = 0; len += self.pos.consensus_encode(writer)?; len += self.target.consensus_encode(writer)?; @@ -164,21 +162,21 @@ impl Encodable for MerkleProof { } #[cfg(test)] mod test { - use crate::address_cache::merkle::MerkleProof; + use super::MerkleProof; use bitcoin::{ consensus::deserialize, hashes::{hex::FromHex, sha256d}, }; - use std::str::FromStr; + use floresta_common::prelude::*; #[test] fn test_merkle_root() { - let hashes = vec![ + let hashes = Vec::from([ "9fe0683d05e5a8ce867712f0f744a1e9893365307d433ab3b8f65dfc59d561de", "9e2804f04a9d52ad4b67e10cba631934915a7d6d083126b338dda680522bb602", "01ad659d8d3f17e96d54e4240614fad5813a58cc1ac67a336839b0bf6c56f2d3", "8627dad7e4df3cc60d1349aac61cae36436423429a12f3df9a1e54a5ca8ee008", "5f82784d819f440ee1766d9802d113c54626bd613009cbf699213f49adf2fbbd", - ]; + ]); let root = sha256d::Hash::from_str( "ff8fa20a8da05e334d59d257c8ba6f76b31856fafe92afdb51151daa2fe0a240", ) @@ -193,13 +191,13 @@ mod test { #[test] fn test_serialization() { use bitcoin::consensus::serialize; - let hashes = vec![ + let hashes = Vec::from([ "9fe0683d05e5a8ce867712f0f744a1e9893365307d433ab3b8f65dfc59d561de", "9e2804f04a9d52ad4b67e10cba631934915a7d6d083126b338dda680522bb602", "01ad659d8d3f17e96d54e4240614fad5813a58cc1ac67a336839b0bf6c56f2d3", "8627dad7e4df3cc60d1349aac61cae36436423429a12f3df9a1e54a5ca8ee008", "5f82784d819f440ee1766d9802d113c54626bd613009cbf699213f49adf2fbbd", - ]; + ]); let root = sha256d::Hash::from_str( "ff8fa20a8da05e334d59d257c8ba6f76b31856fafe92afdb51151daa2fe0a240", ) diff --git a/crates/floresta-wire/Cargo.toml b/crates/floresta-wire/Cargo.toml new file mode 100644 index 00000000..e2b5aaed --- /dev/null +++ b/crates/floresta-wire/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "floresta-wire" +version = "0.1.0" +edition = "2021" + +[dependencies] +rustreexo = { git = "https://www.github.com/mit-dci/rustreexo" } +btcd-rpc = { git = "https://github.com/Davidson-Souza/rust-btcd-rpc", features = [ + "utreexod", +], branch = "use-reqwest", optional = true } +sha2 = "^0.10.6" +async-std = { version = "1.12.0", features = ["attributes", "alloc"] } +log = "0.4" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +kv = "0.24.0" +futures = "0.3.4" +rmp-serde = { optional = true, version = "1.1.1" } +toml = "0.5.10" +rand = "0.8.5" +bitcoin = { version = "0.29", features = ["serde", "std", "bitcoinconsensus"] } +dns-lookup = "1.0.8" +ctrlc = "3.2.5" +floresta-chain = { path = "../floresta-chain" } +thiserror = "1.0" +floresta-common = { path = "../floresta-common" } +oneshot = "0.1.5" + +[dev-dependencies] +pretty_assertions = "1" + +[features] +default = [] +pruned_utreexo_chainstate = [] diff --git a/src/blockchain/cli_blockchain/mod.rs b/crates/floresta-wire/src/cli_wire/mod.rs similarity index 99% rename from src/blockchain/cli_blockchain/mod.rs rename to crates/floresta-wire/src/cli_wire/mod.rs index 07ce98db..8d753261 100644 --- a/src/blockchain/cli_blockchain/mod.rs +++ b/crates/floresta-wire/src/cli_wire/mod.rs @@ -24,7 +24,7 @@ use serde::Deserialize; use super::{ chain_state::ChainState, chainstore::KvChainStore, error::BlockchainError, udata::LeafData, - BlockchainInterface, BlockchainProviderInterface, Result, + BlockchainInterface, Result, UpdatableChainstate, }; use crate::try_and_log; diff --git a/crates/floresta-wire/src/lib.rs b/crates/floresta-wire/src/lib.rs new file mode 100644 index 00000000..05388279 --- /dev/null +++ b/crates/floresta-wire/src/lib.rs @@ -0,0 +1,19 @@ +mod p2p_wire; + +use bitcoin::{Block, BlockHeader, Transaction}; +pub use p2p_wire::{address_man, mempool, node, node_context, node_interface}; + +/// NodeHooks is a trait that defines the hooks that a node can use to interact with the network +/// and the blockchain. Every time an event happens, the node will call the corresponding hook. +pub trait NodeHooks { + /// We've received a new block + fn on_block_received(&mut self, block: &Block); + /// We've received a new transaction + fn on_transaction_received(&mut self, transaction: &Transaction); + /// We've received a new peer + fn on_peer_connected(&mut self, peer: &u32); + /// We've lost a peer + fn on_peer_disconnected(&mut self, peer: &u32); + /// We've received a new header + fn on_header_received(&mut self, header: &BlockHeader); +} diff --git a/src/blockchain/p2p_blockchain/address_man.rs b/crates/floresta-wire/src/p2p_wire/address_man.rs similarity index 97% rename from src/blockchain/p2p_blockchain/address_man.rs rename to crates/floresta-wire/src/p2p_wire/address_man.rs index 5d262b6f..35d66549 100644 --- a/src/blockchain/p2p_blockchain/address_man.rs +++ b/crates/floresta-wire/src/p2p_wire/address_man.rs @@ -2,13 +2,11 @@ //! metadata. This module is very important in keeping our node protected against targeted //! attacks, like eclipse attacks. -use bitcoin::{ - network::{ - address::{AddrV2, AddrV2Message}, - constants::ServiceFlags, - }, - Network, +use bitcoin::network::{ + address::{AddrV2, AddrV2Message}, + constants::ServiceFlags, }; +use floresta_chain::Network; use log::info; use serde::{Deserialize, Serialize}; use std::{ @@ -16,8 +14,8 @@ use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, time::{SystemTime, UNIX_EPOCH}, }; +use thiserror::Error; const RETRY_TIME: u64 = 60 * 60; // 1 hour -use crate::blockchain::error::BlockchainError; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum AddressState { @@ -50,7 +48,8 @@ pub struct LocalAddress { /// Random id for this peer pub id: usize, } - +#[derive(Debug, Error)] +pub enum AddrManError {} impl From for LocalAddress { fn from(value: AddrV2) -> Self { LocalAddress { @@ -100,7 +99,7 @@ impl TryFrom<&str> for LocalAddress { )) } - type Error = BlockchainError; + type Error = std::net::AddrParseError; } impl LocalAddress { @@ -212,7 +211,7 @@ impl AddressMan { .as_secs(); match peer.state { AddressState::Banned(_) => None, - AddressState::Connected => None, + AddressState::Connected => self.get_random_good_address(), AddressState::NeverTried => Some((id, peer)), AddressState::Tried(time) => { if now - time > RETRY_TIME { @@ -273,7 +272,7 @@ impl AddressMan { default_port: u16, network: Network, dns_seeds: &[&'static str], - ) -> Result, BlockchainError> { + ) -> Result, std::io::Error> { let local_db = std::fs::read_to_string(format!("{datadir}/peers.json")); let peers = if let Ok(peers) = local_db { info!("Peers database found, using it"); @@ -299,7 +298,7 @@ impl AddressMan { } let anchors = std::fs::read_to_string(format!("{datadir}/anchors.json")); if anchors.is_err() { - return Ok(vec![]); + return Ok(Vec::new()); } if let Ok(anchors) = serde_json::from_str::>(&anchors.unwrap()) { let anchors = anchors @@ -309,12 +308,12 @@ impl AddressMan { .collect::>(); return Ok(anchors); } - Ok(vec![]) + Ok(Vec::new()) } /// This function moves addresses between buckets, like if the ban time of a peer expired, /// or if we tried to connect to a peer and it failed in the past, but now it might be online /// again. - pub fn rearrange_buckets(&mut self) -> Result<(), BlockchainError> { + pub fn rearrange_buckets(&mut self) { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -340,7 +339,6 @@ impl AddressMan { AddressState::Connected => {} } } - Ok(()) } /// Updates the state of an address pub fn update_set_state(&mut self, idx: usize, state: AddressState) -> &mut Self { diff --git a/crates/floresta-wire/src/p2p_wire/error.rs b/crates/floresta-wire/src/p2p_wire/error.rs new file mode 100644 index 00000000..c79096dc --- /dev/null +++ b/crates/floresta-wire/src/p2p_wire/error.rs @@ -0,0 +1,35 @@ +use floresta_chain::BlockchainError; +use floresta_common::impl_error_from; +use thiserror::Error; + +use crate::node::NodeRequest; + +use super::peer::PeerError; + +#[derive(Error, Debug)] +pub enum WireError { + #[error("Blockchain error")] + BlockchainError(BlockchainError), + #[error("Error while writing into a channel")] + ChannelSendError(async_std::channel::SendError), + #[error("Peer error")] + PeerError(PeerError), + #[error("Coinbase didn't mature")] + CoinbaseNotMatured, + #[error("Peer not found in our current connections")] + PeerNotFound, + #[error("We don't have any peers")] + NoPeersAvailable, + #[error("Our peer is misbehaving")] + PeerMisbehaving, + #[error("Error while reading from a channel")] + ChannelRecvError(#[from] async_std::channel::RecvError), + #[error("Generic io error")] + IoError(std::io::Error), + #[error("We don't have any utreexo peers")] + NoUtreexoPeersAvailable, + #[error("We couldn't find a peer to send the request")] + NoPeerToSendRequest, +} +impl_error_from!(WireError, PeerError, PeerError); +impl_error_from!(WireError, BlockchainError, BlockchainError); diff --git a/src/blockchain/p2p_blockchain/mempool.rs b/crates/floresta-wire/src/p2p_wire/mempool.rs similarity index 90% rename from src/blockchain/p2p_blockchain/mempool.rs rename to crates/floresta-wire/src/p2p_wire/mempool.rs index 078bc5a2..75ce6e91 100644 --- a/src/blockchain/p2p_blockchain/mempool.rs +++ b/crates/floresta-wire/src/p2p_wire/mempool.rs @@ -5,7 +5,7 @@ use std::{ use bitcoin::{Block, Transaction, Txid}; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Mempool(HashMap); impl Mempool { pub fn new() -> Mempool { @@ -13,9 +13,9 @@ impl Mempool { } pub fn consume_block(&mut self, block: &Block) -> Vec { if self.0.is_empty() { - return vec![]; + return Vec::new(); } - let mut delta = vec![]; + let mut delta = Vec::new(); for tx in block.txdata.iter() { if self.0.contains_key(&tx.txid()) { delta.push(self.0.remove(&tx.txid())); @@ -34,7 +34,7 @@ impl Mempool { None } pub fn get_stale(&mut self) -> Vec { - let mut stale = vec![]; + let mut stale = Vec::new(); for (txid, transaction) in self.0.iter_mut() { if transaction.1.elapsed() > Duration::from_secs(60 * 60) { transaction.1 = Instant::now(); diff --git a/src/blockchain/p2p_blockchain/mod.rs b/crates/floresta-wire/src/p2p_wire/mod.rs similarity index 94% rename from src/blockchain/p2p_blockchain/mod.rs rename to crates/floresta-wire/src/p2p_wire/mod.rs index c652cc55..57956ac5 100644 --- a/src/blockchain/p2p_blockchain/mod.rs +++ b/crates/floresta-wire/src/p2p_wire/mod.rs @@ -1,6 +1,7 @@ //! Main module for the p2p chain. This is a blockchain provider, just like cli-chain, but it's //! backed by p2p Bitcoin's p2p network. pub mod address_man; +pub mod error; pub mod mempool; pub mod node; pub mod node_context; diff --git a/src/blockchain/p2p_blockchain/node.rs b/crates/floresta-wire/src/p2p_wire/node.rs similarity index 81% rename from src/blockchain/p2p_blockchain/node.rs rename to crates/floresta-wire/src/p2p_wire/node.rs index 791bca09..e5758866 100644 --- a/src/blockchain/p2p_blockchain/node.rs +++ b/crates/floresta-wire/src/p2p_wire/node.rs @@ -2,19 +2,16 @@ //! events, such as new blocks, peer connection/disconnection, new addresses, etc. //! A node should not care about peer-specific messages, peers'll handle things like pings. use super::{ - address_man::{AddressMan, LocalAddress}, + address_man::{AddressMan, AddressState, LocalAddress}, + error::WireError, mempool::Mempool, node_context::{IBDNode, NodeContext, RunningNode}, node_interface::{NodeInterface, NodeResponse, PeerInfo, UserRequest}, peer::{Peer, PeerMessages, Version}, }; -use crate::blockchain::{ - chain_state::ChainState, chainparams::get_chain_dns_seeds, chainstore::KvChainStore, - error::BlockchainError, p2p_blockchain::address_man::AddressState, udata::proof_util, - BlockchainInterface, BlockchainProviderInterface, -}; + use async_std::{ - channel::{self, bounded, Receiver, Sender}, + channel::{self, bounded, Receiver, SendError, Sender}, future::timeout, sync::RwLock, task::spawn, @@ -26,19 +23,29 @@ use bitcoin::{ message_blockdata::Inventory, utreexo::{UData, UtreexoBlock}, }, - BlockHash, BlockHeader, Network, OutPoint, Transaction, TxOut, Txid, + BlockHash, BlockHeader, OutPoint, Transaction, TxOut, Txid, +}; +use floresta_chain::{ + pruned_utreexo::{ + chainparams::get_chain_dns_seeds, udata::proof_util, BlockchainInterface, + UpdatableChainstate, + }, + BlockchainError, Network, }; use log::{error, info, trace, warn}; -use oneshot::SendError; -use rustreexo::accumulator::proof::Proof; +use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof}; use std::{ collections::{HashMap, HashSet}, + fmt::Debug, net::IpAddr, ops::{Deref, DerefMut}, sync::{Arc, Mutex}, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; +/// Max number of simultaneous connections we initiates we are willing to hold +const MAX_OUTGOING_PEERS: usize = 10; + #[derive(Debug)] pub enum NodeNotification { FromPeer(u32, PeerMessages), @@ -97,7 +104,22 @@ pub enum RescanStatus { Completed(Instant), None, } -pub struct NodeCommon { + +impl Default for RunningNode { + fn default() -> Self { + RunningNode { + last_rescan_request: RescanStatus::None, + last_feeler: Instant::now(), + last_address_rearrange: Instant::now(), + user_requests: Arc::new(NodeInterface { + requests: Mutex::new(Vec::new()), + }), + last_block_check: Instant::now(), + } + } +} + +pub struct NodeCommon { peer_id_count: u32, last_headers_request: Instant, last_tip_update: Instant, @@ -110,7 +132,7 @@ pub struct NodeCommon { utreexo_peers: Vec, peer_ids: Vec, peers: HashMap, - chain: Arc>, + chain: Arc, inflight: HashMap, node_rx: Receiver, node_tx: Sender, @@ -119,23 +141,18 @@ pub struct NodeCommon { datadir: String, address_man: AddressMan, } -/// The main node struct. It holds all the important information about the node, such as the -/// blockchain, the peers, the mempool, etc. -/// It also holds the channels to communicate with peers and the block downloader. -/// The node is the central task that runs and handles important events, such as new blocks, -/// peer connection/disconnection, new addresses, etc. -pub struct UtreexoNode(NodeCommon, Context); -// To reduce keystrokes while using node, assuming that we'll predominantly use the inner -// NodeCommon, we implement deref and deref mut as a passthrough of self.0, so self.something -// is an alias to self.0.something -impl Deref for UtreexoNode { +pub struct UtreexoNode( + NodeCommon, + Context, +); +impl Deref for UtreexoNode { fn deref(&self) -> &Self::Target { &self.0 } - type Target = NodeCommon; + type Target = NodeCommon; } -impl DerefMut for UtreexoNode { +impl DerefMut for UtreexoNode { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -146,23 +163,11 @@ enum PeerStatus { Ready, ShutingDown, } - -impl Default for RunningNode { - fn default() -> Self { - RunningNode { - last_rescan_request: RescanStatus::None, - last_feeler: Instant::now(), - last_address_rearrange: Instant::now(), - user_requests: Arc::new(NodeInterface { - requests: Mutex::new(vec![]), - }), - } - } -} - -impl UtreexoNode { +impl + UtreexoNode +{ pub fn new( - chain: Arc>, + chain: Arc, mempool: Arc>, network: Network, datadir: String, @@ -194,7 +199,16 @@ impl UtreexoNode { T::default(), ) } - async fn handle_disconnection(&mut self, peer: u32, idx: usize) -> Result<(), BlockchainError> { + fn get_peer_info(&self, peer: &u32) -> Option { + let peer = self.peers.get(peer)?; + Some(PeerInfo { + address: format!("{}:{}", peer.address, peer.port), + services: peer.services.to_string(), + user_agent: peer.user_agent.clone(), + initial_height: peer.height, + }) + } + async fn handle_disconnection(&mut self, peer: u32, idx: usize) -> Result<(), WireError> { if let Some(p) = self.peers.remove(&peer) { p.channel.close(); if !p.feeler && p.state == PeerStatus::Ready { @@ -218,20 +232,7 @@ impl UtreexoNode { ); Ok(()) } - fn get_peer_info(&self, peer: &u32) -> Option { - let peer = self.peers.get(peer)?; - Some(PeerInfo { - address: format!("{}:{}", peer.address, peer.port), - services: peer.services.to_string(), - user_agent: peer.user_agent.clone(), - initial_height: peer.height, - }) - } - async fn handle_peer_ready( - &mut self, - peer: u32, - version: &Version, - ) -> Result<(), BlockchainError> { + async fn handle_peer_ready(&mut self, peer: u32, version: &Version) -> Result<(), WireError> { if version.feeler { self.send_to_peer(peer, NodeRequest::Shutdown).await?; self.address_man.update_set_state( @@ -290,18 +291,18 @@ impl UtreexoNode { fn process_proof( udata: &UData, transactions: &[Transaction], - chain: &ChainState, + chain: &Arc, block_hash: &BlockHash, - ) -> Result<(Proof, Vec, HashMap), BlockchainError> { + ) -> Result<(Proof, Vec, HashMap), WireError> { let targets = udata.proof.targets.iter().map(|target| target.0).collect(); let hashes = udata .proof .hashes .iter() - .map(|hash| sha256::Hash::from_inner(hash.into_inner())) + .map(|hash| NodeHash::Some(hash.into_inner())) .collect(); let proof = Proof::new(targets, hashes); - let hashes = vec![]; + let hashes = Vec::new(); let mut leaves_iter = udata.leaves.iter().cloned(); let mut tx_iter = transactions.iter(); @@ -331,7 +332,7 @@ impl UtreexoNode { if leaf.header_code & 1 == 1 && !chain.is_coinbase_mature(height, *block_hash)? { - return Err(BlockchainError::CoinbaseNotMatured); + return Err(WireError::CoinbaseNotMatured); } inputs.insert(leaf.prevout, leaf.utxo); } @@ -341,17 +342,43 @@ impl UtreexoNode { Ok((proof, hashes, inputs)) } - - async fn send_to_peer(&self, peer_id: u32, req: NodeRequest) -> Result<(), BlockchainError> { + pub async fn ibd_handle_headers(&mut self, headers: Vec) -> Result<(), WireError> { + if headers.is_empty() { + // Start downloading blocks + self.chain.flush()?; + self.state = NodeState::DownloadBlocks; + return Ok(()); + } + self.last_headers_request = Instant::now(); + trace!( + "Downloading headers at: {} hash: {}", + self.chain.get_best_block()?.0, + headers[0].block_hash() + ); + for header in headers { + self.chain.accept_header(header)?; + } + let locator = self.chain.get_block_locator()?; + let peer = self + .send_to_random_peer(NodeRequest::GetHeaders(locator), ServiceFlags::NONE) + .await?; + self.inflight + .insert(InflightRequests::Headers, (peer, Instant::now())); + Ok(()) + } + async fn send_to_peer(&self, peer_id: u32, req: NodeRequest) -> Result<(), WireError> { if let Some(peer) = &self.peers.get(&peer_id) { if peer.state == PeerStatus::Ready { - peer.channel.send(req).await?; + peer.channel + .send(req) + .await + .map_err(WireError::ChannelSendError)?; } } Ok(()) } - async fn check_for_timeout(&mut self) -> Result<(), BlockchainError> { - let mut timed_out = vec![]; + async fn check_for_timeout(&mut self) -> Result<(), WireError> { + let mut timed_out = Vec::new(); for request in self.inflight.keys() { let (_, time) = self.inflight.get(request).unwrap(); if time.elapsed() > Duration::from_secs(T::REQUEST_TIMEOUT) { @@ -359,15 +386,15 @@ impl UtreexoNode { } } let mut removed_peers = HashSet::new(); - let mut to_request = vec![]; + let mut to_request = Vec::new(); + let mut rescan_blocks = Vec::new(); for request in timed_out { let Some((peer, _)) = self.inflight.remove(&request) else { continue; }; match request { - InflightRequests::Blocks(block) | InflightRequests::RescanBlock(block) => { - to_request.push(block) - } + InflightRequests::Blocks(block) => to_request.push(block), + InflightRequests::RescanBlock(block) => rescan_blocks.push(block), InflightRequests::Headers => { self.send_to_random_peer(NodeRequest::GetAddresses, ServiceFlags::NONE) .await?; @@ -380,14 +407,24 @@ impl UtreexoNode { } if !removed_peers.contains(&peer) { + self.send_to_peer(peer, NodeRequest::Shutdown).await?; + removed_peers.insert(peer); if let Some(peer) = self.peers.get_mut(&peer) { + info!("Peer {} timed out request, shuting down", peer.address); peer.state = PeerStatus::ShutingDown; } - - self.send_to_peer(peer, NodeRequest::Shutdown).await?; - removed_peers.insert(peer); } } + for hash in rescan_blocks { + let peer = self + .send_to_random_peer( + NodeRequest::GetBlock((vec![hash], false)), + ServiceFlags::NONE, + ) + .await?; + self.inflight + .insert(InflightRequests::RescanBlock(hash), (peer, Instant::now())); + } self.request_blocks(to_request).await?; Ok(()) } @@ -396,15 +433,14 @@ impl UtreexoNode { &mut self, req: NodeRequest, required_services: ServiceFlags, - ) -> Result { + ) -> Result { + if self.peers.is_empty() { + return Err(WireError::NoPeersAvailable); + } for _ in 0..10 { - if self.peers.is_empty() { - return Err(BlockchainError::NoPeersAvailable); - } let idx = if required_services.has(ServiceFlags::NODE_UTREEXO) { if self.utreexo_peers.is_empty() { - self.state = NodeState::WaitingPeer; - return Err(BlockchainError::NoPeersAvailable); + return Err(WireError::NoUtreexoPeersAvailable); } let idx = rand::random::() % self.utreexo_peers.len(); *self @@ -413,8 +449,7 @@ impl UtreexoNode { .expect("node is in the interval 0..utreexo_peers.len(), but is not here?") } else { if self.peer_ids.is_empty() { - self.state = NodeState::WaitingPeer; - return Err(BlockchainError::NoPeersAvailable); + return Err(WireError::NoPeersAvailable); } let idx = rand::random::() % self.peer_ids.len(); *self @@ -426,21 +461,28 @@ impl UtreexoNode { if peer.state != PeerStatus::Ready { continue; } - peer.channel.send(req.clone()).await?; + peer.channel + .send(req.clone()) + .await + .map_err(WireError::ChannelSendError)?; return Ok(idx); } } - Err(BlockchainError::NoPeersAvailable) + self.create_connection(false).await; + Err(WireError::NoPeerToSendRequest) } - pub async fn init_peers(&mut self) -> Result<(), BlockchainError> { - let datadir = self.datadir.clone(); - let port = self.get_default_port(); - let net = self.network; - let seeds = get_chain_dns_seeds(self.network); + pub async fn init_peers(&mut self) -> Result<(), WireError> { let anchors = self + .0 .address_man - .start_addr_man(datadir, port, net, &seeds)?; + .start_addr_man( + self.datadir.clone(), + self.get_default_port(), + self.network, + &get_chain_dns_seeds(self.network), + ) + .map_err(WireError::IoError)?; for address in anchors { self.open_connection(false, address.id, address).await; } @@ -455,11 +497,11 @@ impl UtreexoNode { try_and_log!(self.save_peers()); try_and_log!(self.chain.flush()); } - pub async fn ask_block(&mut self) -> Result<(), BlockchainError> { + pub async fn ask_block(&mut self) -> Result<(), WireError> { let blocks = self.get_blocks_to_download()?; self.request_blocks(blocks).await } - pub async fn handle_broadcast(&self) -> Result<(), BlockchainError> { + pub async fn handle_broadcast(&self) -> Result<(), WireError> { for (_, peer) in self.peers.iter() { if peer.services.has(ServiceFlags::NODE_UTREEXO) { continue; @@ -472,28 +514,32 @@ impl UtreexoNode { self.mempool.write().await.accept_to_mempool(transaction); peer.channel .send(NodeRequest::BroadcastTransaction(txid)) - .await?; + .await + .map_err(WireError::ChannelSendError)?; } let stale = self.mempool.write().await.get_stale(); for tx in stale { peer.channel .send(NodeRequest::BroadcastTransaction(tx)) - .await?; + .await + .map_err(WireError::ChannelSendError)?; } } Ok(()) } - pub async fn ask_for_addresses(&mut self) -> Result<(), BlockchainError> { + pub async fn ask_for_addresses(&mut self) -> Result<(), WireError> { let _ = self .send_to_random_peer(NodeRequest::GetAddresses, ServiceFlags::NONE) .await?; Ok(()) } - fn save_peers(&self) -> Result<(), BlockchainError> { - Ok(self.address_man.dump_peers(&self.datadir)?) + fn save_peers(&self) -> Result<(), WireError> { + self.address_man + .dump_peers(&self.datadir) + .map_err(WireError::IoError) } - fn get_blocks_to_download(&mut self) -> Result, BlockchainError> { - let mut blocks = vec![]; + fn get_blocks_to_download(&mut self) -> Result, WireError> { + let mut blocks = Vec::new(); let tip = self.chain.get_height()?; for i in (self.last_block_request + 1)..=(self.last_block_request + 100) { @@ -508,36 +554,34 @@ impl UtreexoNode { Ok(blocks) } - async fn maybe_open_connection(&mut self) -> Result<(), BlockchainError> { - if self.peers.len() < T::MAX_OUTGOING_PEERS { + async fn maybe_open_connection(&mut self) -> Result<(), WireError> { + if self.peers.len() < MAX_OUTGOING_PEERS { self.create_connection(false).await; } Ok(()) } - async fn open_feeler_connection(&mut self) -> Result<(), BlockchainError> { + async fn open_feeler_connection(&mut self) -> Result<(), WireError> { self.create_connection(true).await; Ok(()) } - async fn request_blocks(&mut self, blocks: Vec) -> Result<(), BlockchainError> { - if self.state == NodeState::WaitingPeer { - return Ok(()); - } + async fn request_blocks(&mut self, blocks: Vec) -> Result<(), WireError> { + let blocks: Vec<_> = blocks + .into_iter() + .filter(|block| { + !self + .inflight + .contains_key(&InflightRequests::Blocks(*block)) + }) + .collect(); + let peer = self + .send_to_random_peer( + NodeRequest::GetBlock((blocks.clone(), true)), + ServiceFlags::NODE_UTREEXO, + ) + .await?; for block in blocks.iter() { - // Don't ask for the same block again - if self - .inflight - .contains_key(&InflightRequests::Blocks(*block)) - { - continue; - } - let peer = self - .send_to_random_peer( - NodeRequest::GetBlock((vec![*block], true)), - ServiceFlags::NODE_UTREEXO, - ) - .await?; self.inflight .insert(InflightRequests::Blocks(*block), (peer, Instant::now())); } @@ -545,8 +589,8 @@ impl UtreexoNode { } async fn create_connection(&mut self, feeler: bool) -> Option<()> { - // We should try to keep at least one utreexo connections - let required_services = if self.utreexo_peers.is_empty() { + // We should try to keep at least two utreexo connections + let required_services = if self.utreexo_peers.len() < 2 { ServiceFlags::NETWORK | ServiceFlags::WITNESS | ServiceFlags::NODE_UTREEXO } else { ServiceFlags::NETWORK | ServiceFlags::WITNESS @@ -575,7 +619,7 @@ impl UtreexoNode { self.peer_id_count, (address.get_net_address(), address.get_port()), self.mempool.clone(), - self.network, + self.network.into(), self.node_tx.clone(), requests_rx, peer_id, @@ -607,11 +651,13 @@ impl UtreexoNode { self.peer_id_count += 1; } } -impl UtreexoNode { - async fn handle_block( - chain: &Arc>, - block: UtreexoBlock, - ) -> Result<(), BlockchainError> { +impl UtreexoNode { + /// Processing blocks actually takes a lot of CPU time, and we need to wait until it either + /// succeed or fail before doing something else. This will hang our node up (not peers, only + /// the node) and make it do funky stuff, like timeout blocks we just got but not processed. + /// This task solves it by taking up the actual CPU time for processing blocks, while our + /// node's main loop can continue normally. + async fn handle_block(chain: &Arc, block: UtreexoBlock) -> Result<(), WireError> { let (proof, del_hashes, inputs) = Self::process_proof( &block.udata.unwrap(), &block.block.txdata, @@ -632,10 +678,7 @@ impl UtreexoNode { })); Ok(()) } - pub async fn handle_headers( - &mut self, - headers: Vec, - ) -> Result<(), BlockchainError> { + pub async fn handle_headers(&mut self, headers: Vec) -> Result<(), WireError> { if headers.is_empty() { // Start downloading blocks self.chain.flush()?; @@ -659,7 +702,7 @@ impl UtreexoNode { .insert(InflightRequests::Headers, (peer, Instant::now())); Ok(()) } - async fn maybe_request_headers(&mut self) -> Result<(), BlockchainError> { + async fn maybe_request_headers(&mut self) -> Result<(), WireError> { if self.state != NodeState::DownloadHeaders { return Ok(()); } @@ -673,7 +716,7 @@ impl UtreexoNode { self.last_headers_request = Instant::now(); Ok(()) } - pub async fn run(&mut self, stop_signal: &Arc>) -> Result<(), BlockchainError> { + pub async fn run(&mut self, stop_signal: &Arc>) -> Result<(), WireError> { self.create_connection(false).await; loop { while let Ok(notification) = @@ -703,7 +746,10 @@ impl UtreexoNode { if self.state == NodeState::DownloadBlocks { self.process_queued_blocks().await.or_else(|err| { // This usually means we just processed all blocks, and we are done. - if matches!(err, BlockchainError::BlockNotPresent) { + if matches!( + err, + WireError::BlockchainError(BlockchainError::BlockNotPresent) + ) { info!("Finished downloading blocks"); self.chain.toggle_ibd(false); Ok(()) @@ -741,7 +787,7 @@ impl UtreexoNode { } Ok(()) } - async fn process_queued_blocks(&mut self) -> Result<(), BlockchainError> { + async fn process_queued_blocks(&mut self) -> Result<(), WireError> { let mut hash = self .chain .get_block_hash(self.chain.get_validation_index()? + 1)?; @@ -758,7 +804,7 @@ impl UtreexoNode { pub async fn handle_notification( &mut self, notification: Result, - ) -> Result<(), BlockchainError> { + ) -> Result<(), WireError> { match notification? { NodeNotification::FromPeer(peer, message) => match message { PeerMessages::NewBlock(block) => { @@ -784,7 +830,7 @@ impl UtreexoNode { ); self.send_to_peer(peer, NodeRequest::Shutdown).await?; - return Err(BlockchainError::PeerMisbehaving); + return Err(WireError::PeerMisbehaving); } // We may receive blocks out of order, so we store them in a map until we // receive all the previous ones. @@ -846,8 +892,7 @@ impl UtreexoNode { } } -impl UtreexoNode { - #[cfg(feature = "json-rpc")] +impl UtreexoNode { /// Returns a handle to the node interface that we can use to request data from our /// node. This struct is thread safe, so we can use it from multiple threads and have /// multiple handles. It also doesn't require a mutable reference to the node, or any @@ -855,9 +900,10 @@ impl UtreexoNode { pub fn get_handle(&self) -> Arc { self.1.user_requests.clone() } + #[allow(clippy::result_large_err)] fn check_request_timeout(&mut self) -> Result<(), SendError> { let mutex = self.1.user_requests.requests.lock().unwrap(); - let mut to_remove = vec![]; + let mut to_remove = Vec::new(); for req in mutex.iter() { if req.time.elapsed() > Duration::from_secs(10) { to_remove.push(req.req); @@ -871,7 +917,7 @@ impl UtreexoNode { Ok(()) } async fn handle_user_request(&mut self) { - let mut requests = vec![]; + let mut requests = Vec::new(); for request in self.1.user_requests.requests.lock().unwrap().iter() { if !self @@ -884,7 +930,7 @@ impl UtreexoNode { self.perform_user_request(requests).await; } fn handle_get_peer_info(&self) { - let mut peers = vec![]; + let mut peers = Vec::new(); for peer in self.peer_ids.iter() { peers.push(self.get_peer_info(peer)); } @@ -894,6 +940,21 @@ impl UtreexoNode { Some(NodeResponse::GetPeerInfo(peers)), ); } + /// In some edge cases, we may get a block header, but not the block itself. This + /// function checks if we have the block, and if not, requests it. + async fn ask_missed_block(&mut self) -> Result<(), WireError> { + let best_block = self.chain.get_best_block()?.0; + let validation_index = self.chain.get_validation_index()?; + + if best_block == validation_index { + return Ok(()); + } + for block in validation_index..=best_block { + let block = self.0.chain.get_block_hash(block)?; + self.request_blocks(vec![block]).await?; + } + Ok(()) + } async fn perform_user_request(&mut self, user_req: Vec) { for user_req in user_req { let req = match user_req { @@ -919,7 +980,7 @@ impl UtreexoNode { // Use this node state to Initial Block download let mut ibd = UtreexoNode(self.0, IBDNode::default()); - try_and_log!(UtreexoNode::::run(&mut ibd, kill_signal).await); + try_and_log!(UtreexoNode::::run(&mut ibd, kill_signal).await); // Then take the final state and run the node self = UtreexoNode(ibd.0, self.1); @@ -931,8 +992,7 @@ impl UtreexoNode { .await; loop { - while let Ok(notification) = - timeout(Duration::from_millis(100), self.node_rx.recv()).await + while let Ok(notification) = timeout(Duration::from_secs(1), self.node_rx.recv()).await { try_and_log!(self.handle_notification(notification).await); } @@ -954,6 +1014,14 @@ impl UtreexoNode { self.address_man.rearrange_buckets(), self.1.last_address_rearrange, ADDRESS_REARRANGE_INTERVAL, + RunningNode, + true + ); + // Check if we haven't missed any block + periodic_job!( + self.ask_missed_block().await, + self.1.last_block_check, + BLOCK_CHECK_INTERVAL, RunningNode ); // Perhaps we need more connections @@ -1003,7 +1071,7 @@ impl UtreexoNode { try_and_log!(self.request_rescan_block().await); } } - async fn request_rescan_block(&mut self) -> Result<(), BlockchainError> { + async fn request_rescan_block(&mut self) -> Result<(), WireError> { let tip = self.chain.get_height().unwrap(); if self.inflight.len() + 10 > RunningNode::MAX_INFLIGHT_REQUESTS { return Ok(()); @@ -1043,7 +1111,7 @@ impl UtreexoNode { } /// This function checks how many time has passed since our last tip update, if it's /// been more than 15 minutes, try to update it. - async fn check_for_stale_tip(&mut self) -> Result<(), BlockchainError> { + async fn check_for_stale_tip(&mut self) -> Result<(), WireError> { warn!("Potential stale tip detected, trying extra peers"); self.create_connection(false).await; self.send_to_random_peer( @@ -1053,7 +1121,7 @@ impl UtreexoNode { .await?; Ok(()) } - async fn handle_new_block(&mut self) -> Result<(), BlockchainError> { + async fn handle_new_block(&mut self) -> Result<(), WireError> { if self.inflight.contains_key(&InflightRequests::Headers) { return Ok(()); } @@ -1067,18 +1135,14 @@ impl UtreexoNode { Ok(()) } - async fn handle_block_data( - &mut self, - block: UtreexoBlock, - peer: u32, - ) -> Result<(), BlockchainError> { + async fn handle_block_data(&mut self, block: UtreexoBlock, peer: u32) -> Result<(), WireError> { if self .inflight .remove(&InflightRequests::RescanBlock(block.block.block_hash())) .is_some() { self.request_rescan_block().await?; - return self.chain.process_rescan_block(&block.block); + return Ok(self.chain.process_rescan_block(&block.block)?); } if self .inflight @@ -1118,7 +1182,7 @@ impl UtreexoNode { block.block.block_hash() ); self.send_to_peer(peer, NodeRequest::Shutdown).await?; - return Err(BlockchainError::PeerMisbehaving); + return Err(WireError::PeerMisbehaving); } let mempool_delta = self.mempool.write().await.consume_block(&block.block); @@ -1153,7 +1217,7 @@ impl UtreexoNode { for tx in mempool_delta { self.mempool.write().await.accept_to_mempool(tx); } - return Err(e); + return Err(e.into()); } self.last_tip_update = Instant::now(); Ok(()) @@ -1161,7 +1225,7 @@ impl UtreexoNode { pub async fn handle_notification( &mut self, notification: Result, - ) -> Result<(), BlockchainError> { + ) -> Result<(), WireError> { match notification? { NodeNotification::FromPeer(peer, message) => match message { PeerMessages::NewBlock(block) => { @@ -1180,8 +1244,6 @@ impl UtreexoNode { for header in headers.iter() { self.chain.accept_header(*header)?; } - let hashes = headers.iter().map(|header| header.block_hash()).collect(); - self.request_blocks(hashes).await?; } PeerMessages::Ready(version) => { self.handle_peer_ready(peer, &version).await?; @@ -1234,7 +1296,7 @@ macro_rules! try_and_log { let result = $what; if let Err(error) = result { - log::error!("{:?}", error); + log::error!("{}:{} - {:?}", line!(), file!(), error); } }; } @@ -1245,6 +1307,12 @@ macro_rules! periodic_job { $timer = Instant::now(); } }; + ($what: expr, $timer: expr, $interval: ident, $context: ty, $no_log: literal) => { + if $timer.elapsed() > Duration::from_secs(<$context>::$interval) { + $what; + $timer = Instant::now(); + } + }; } pub(crate) use periodic_job; pub(crate) use try_and_log; diff --git a/src/blockchain/p2p_blockchain/node_context.rs b/crates/floresta-wire/src/p2p_wire/node_context.rs similarity index 90% rename from src/blockchain/p2p_blockchain/node_context.rs rename to crates/floresta-wire/src/p2p_wire/node_context.rs index 7db13669..302a5887 100644 --- a/src/blockchain/p2p_blockchain/node_context.rs +++ b/crates/floresta-wire/src/p2p_wire/node_context.rs @@ -13,7 +13,7 @@ pub trait NodeContext { /// Save our database of peers every PEER_DB_DUMP_INTERVAL seconds const PEER_DB_DUMP_INTERVAL: u64 = 60 * 5; // 5 minutes /// Attempt to open a new connection (if needed) every TRY_NEW_CONNECTION seconds - const TRY_NEW_CONNECTION: u64 = 30; // 30 seconds + const TRY_NEW_CONNECTION: u64 = 10; // 10 seconds /// If ASSUME_STALE seconds passed since our last tip update, treat it as stale const ASSUME_STALE: u64 = 30 * 60; // 30 minutes /// While on IBD, if we've been without blocks for this long, ask for headers again @@ -28,6 +28,8 @@ pub trait NodeContext { const ADDRESS_REARRANGE_INTERVAL: u64 = 60 * 60; // 1 hour /// How long we ban a peer for const BAN_TIME: u64 = 60 * 60 * 24; + /// How often we check if we haven't missed a block + const BLOCK_CHECK_INTERVAL: u64 = 60 * 5; // 5 minutes } #[derive(Debug, Clone)] @@ -35,6 +37,7 @@ pub struct RunningNode { pub last_rescan_request: RescanStatus, pub last_feeler: Instant, pub last_address_rearrange: Instant, + pub last_block_check: Instant, pub user_requests: Arc, } impl NodeContext for RunningNode { diff --git a/src/blockchain/p2p_blockchain/node_interface.rs b/crates/floresta-wire/src/p2p_wire/node_interface.rs similarity index 99% rename from src/blockchain/p2p_blockchain/node_interface.rs rename to crates/floresta-wire/src/p2p_wire/node_interface.rs index 393ffab9..041b733b 100644 --- a/src/blockchain/p2p_blockchain/node_interface.rs +++ b/crates/floresta-wire/src/p2p_wire/node_interface.rs @@ -120,7 +120,7 @@ impl NodeMethods for NodeInterface { let peer_info = rx.recv()?; Ok(match peer_info { Some(NodeResponse::GetPeerInfo(peer_info)) => peer_info, - None => vec![], + None => Vec::new(), _ => unreachable!(), }) } diff --git a/src/blockchain/p2p_blockchain/peer.rs b/crates/floresta-wire/src/p2p_wire/peer.rs similarity index 90% rename from src/blockchain/p2p_blockchain/peer.rs rename to crates/floresta-wire/src/p2p_wire/peer.rs index 11de47ad..8829c832 100644 --- a/src/blockchain/p2p_blockchain/peer.rs +++ b/crates/floresta-wire/src/p2p_wire/peer.rs @@ -4,7 +4,8 @@ use super::{ node::{NodeNotification, NodeRequest}, stream_reader::StreamReader, }; -use crate::blockchain::error::BlockchainError; +use thiserror::Error; + use async_std::{ channel::{unbounded, Receiver, Sender}, io::{BufReader, WriteExt}, @@ -59,6 +60,21 @@ pub struct Peer { feeler: bool, wants_addrv2: bool, } +#[derive(Debug, Error)] +pub enum PeerError { + #[error("Error while sending to peer")] + SendError, + #[error("Error while reading from peer")] + ReadError(#[from] std::io::Error), + #[error("Error while parsing message")] + ParseError(#[from] bitcoin::consensus::encode::Error), + #[error("Peer sent us a message that we aren't expecting")] + UnexpectedMessage, + #[error("Peer sent us a message that is too big")] + MessageTooBig, + #[error("Peer sent us a message with the wrong magic bits")] + MagicBitsMismatch, +} impl Debug for Peer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.id)?; @@ -66,15 +82,17 @@ impl Debug for Peer { Ok(()) } } +type Result = std::result::Result; + impl Peer { - pub async fn read_loop(mut self) -> Result<(), BlockchainError> { + pub async fn read_loop(mut self) -> Result<()> { let err = self.peer_loop_inner().await; debug!("Peer connection loop closed: {err:?}"); self.send_to_node(PeerMessages::Disconnected(self.address_id)) .await; Ok(()) } - async fn peer_loop_inner(&mut self) -> Result<(), BlockchainError> { + async fn peer_loop_inner(&mut self) -> Result<()> { // send a version let version = peer_utils::build_version_message(); self.write(version).await?; @@ -100,17 +118,12 @@ impl Peer { }; if let State::SentVersion(when) = self.state { if Instant::now().duration_since(when) > Duration::from_secs(10) { - return Err(BlockchainError::InvalidMessage( - "Version timeout".to_string(), - )); + return Err(PeerError::UnexpectedMessage); } } } } - pub async fn handle_node_request( - &mut self, - request: NodeRequest, - ) -> Result<(), BlockchainError> { + pub async fn handle_node_request(&mut self, request: NodeRequest) -> Result<()> { match request { NodeRequest::GetBlock((block_hashes, proof)) => { let inv = if proof { @@ -155,14 +168,9 @@ impl Peer { } Ok(()) } - pub async fn handle_peer_message( - &mut self, - message: RawNetworkMessage, - ) -> Result<(), BlockchainError> { + pub async fn handle_peer_message(&mut self, message: RawNetworkMessage) -> Result<()> { if self.state == State::None && message.cmd() != "version" { - return Err(BlockchainError::InvalidMessage( - "Expected version message".to_string(), - )); + return Err(PeerError::UnexpectedMessage); } match message.payload { bitcoin::network::message::NetworkMessage::Version(version) => { @@ -200,7 +208,7 @@ impl Peer { } } bitcoin::network::message::NetworkMessage::GetHeaders(_) => { - self.write(NetworkMessage::Headers(vec![])).await?; + self.write(NetworkMessage::Headers(Vec::new())).await?; } bitcoin::network::message::NetworkMessage::Block(block) => { self.send_to_node(PeerMessages::Block(block)).await; @@ -222,13 +230,13 @@ impl Peer { self.send_to_node(PeerMessages::Addr(addresses)).await; } bitcoin::network::message::NetworkMessage::GetBlocks(_) => { - self.write(NetworkMessage::Inv(vec![])).await?; + self.write(NetworkMessage::Inv(Vec::new())).await?; } bitcoin::network::message::NetworkMessage::SendAddrV2 => { self.wants_addrv2 = true; } bitcoin::network::message::NetworkMessage::GetAddr => { - self.write(NetworkMessage::AddrV2(vec![])).await?; + self.write(NetworkMessage::AddrV2(Vec::new())).await?; } bitcoin::network::message::NetworkMessage::GetData(inv) => { for inv_el in inv { @@ -249,7 +257,7 @@ impl Peer { } } impl Peer { - pub async fn write(&self, msg: NetworkMessage) -> Result<(), BlockchainError> { + pub async fn write(&self, msg: NetworkMessage) -> Result<()> { let data = &mut RawNetworkMessage { magic: self.network.magic(), payload: msg, @@ -258,7 +266,7 @@ impl Peer { (&self.stream).write_all(&data).await?; Ok(()) } - pub async fn handle_get_data(&self, inv: Inventory) -> Result<(), BlockchainError> { + pub async fn handle_get_data(&self, inv: Inventory) -> Result<()> { match inv { Inventory::WitnessTransaction(txid) => { if let Some(tx) = self.mempool.read().await.get_from_mempool(&txid) { @@ -316,12 +324,12 @@ impl Peer { }; spawn(peer.read_loop()); } - async fn handle_ping(&mut self, nonce: u64) -> Result<(), BlockchainError> { + async fn handle_ping(&mut self, nonce: u64) -> Result<()> { self.last_ping = Instant::now(); let pong = make_pong(nonce); self.write(pong).await } - async fn handle_version(&mut self, version: VersionMessage) -> Result<(), BlockchainError> { + async fn handle_version(&mut self, version: VersionMessage) -> Result<()> { self.user_agent = version.user_agent; self.blocks_only = !version.relay; self.current_best_block = version.start_height; @@ -338,7 +346,6 @@ impl Peer { let _ = self.node_tx.send(message).await; } } - pub(super) mod peer_utils { use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, @@ -350,8 +357,8 @@ pub(super) mod peer_utils { message::{self, NetworkMessage}, message_network, }; + use floresta_common::constants::{FLORESTA_VERSION, RUSTREEXO_VERSION, RUST_BITCOIN_VERSION}; - use crate::version::{FLORESTA_VERSION, RUSTREEXO_VERSION, RUST_BITCOIN_VERSION}; /// Protocol version we speak pub const PROTOCOL_VERSION: u32 = 70016; diff --git a/src/blockchain/p2p_blockchain/seeds/mainnet_seeds.json b/crates/floresta-wire/src/p2p_wire/seeds/mainnet_seeds.json similarity index 100% rename from src/blockchain/p2p_blockchain/seeds/mainnet_seeds.json rename to crates/floresta-wire/src/p2p_wire/seeds/mainnet_seeds.json diff --git a/src/blockchain/p2p_blockchain/seeds/regtest_seeds.json b/crates/floresta-wire/src/p2p_wire/seeds/regtest_seeds.json similarity index 100% rename from src/blockchain/p2p_blockchain/seeds/regtest_seeds.json rename to crates/floresta-wire/src/p2p_wire/seeds/regtest_seeds.json diff --git a/src/blockchain/p2p_blockchain/seeds/signet_seeds.json b/crates/floresta-wire/src/p2p_wire/seeds/signet_seeds.json similarity index 100% rename from src/blockchain/p2p_blockchain/seeds/signet_seeds.json rename to crates/floresta-wire/src/p2p_wire/seeds/signet_seeds.json diff --git a/src/blockchain/p2p_blockchain/seeds/testnet_seeds.json b/crates/floresta-wire/src/p2p_wire/seeds/testnet_seeds.json similarity index 100% rename from src/blockchain/p2p_blockchain/seeds/testnet_seeds.json rename to crates/floresta-wire/src/p2p_wire/seeds/testnet_seeds.json diff --git a/src/blockchain/p2p_blockchain/stream_reader.rs b/crates/floresta-wire/src/p2p_wire/stream_reader.rs similarity index 89% rename from src/blockchain/p2p_blockchain/stream_reader.rs rename to crates/floresta-wire/src/p2p_wire/stream_reader.rs index 12d53966..558ad6a5 100644 --- a/src/blockchain/p2p_blockchain/stream_reader.rs +++ b/crates/floresta-wire/src/p2p_wire/stream_reader.rs @@ -6,13 +6,12 @@ //! this header. With payload size we can finally read the entire message and return a parsed //! structure. +use super::peer::PeerError; use async_std::{channel::Sender, io::ReadExt}; use bitcoin::consensus::{deserialize, deserialize_partial, Decodable}; use futures::AsyncRead; use std::marker::PhantomData; -use crate::blockchain::error::BlockchainError; - /// A simple type that wraps a stream and returns T, if T is [Decodable]. pub struct StreamReader { /// Were we read bytes from, usually a TcpStream @@ -23,7 +22,7 @@ pub struct StreamReader>, + sender: Sender>, } impl StreamReader where @@ -31,7 +30,7 @@ where Source: Sync + Send + ReadExt + Unpin + AsyncRead, { /// Creates a new reader from a given stream - pub fn new(stream: Source, magic: u32, sender: Sender>) -> Self { + pub fn new(stream: Source, magic: u32, sender: Sender>) -> Self { StreamReader { source: stream, phantom: PhantomData, @@ -39,7 +38,7 @@ where sender, } } - async fn read_loop_inner(&mut self) -> Result<(), BlockchainError> { + async fn read_loop_inner(&mut self) -> Result<(), PeerError> { loop { let mut data: Vec = Vec::new(); data.resize(24, 0); @@ -48,11 +47,11 @@ where self.source.read_exact(&mut data).await?; let header: P2PMessageHeader = deserialize_partial(&data)?.0; if header.magic != self.magic { - return Err(crate::blockchain::BlockchainError::PeerMessageInvalidMagic); + return Err(PeerError::MagicBitsMismatch); } // Network Message too big if header.length > (1024 * 1024 * 32) as u32 { - return Err(crate::blockchain::BlockchainError::MessageTooBig); + return Err(PeerError::MessageTooBig); } data.resize(24 + header.length as usize, 0); diff --git a/crates/floresta/Cargo.toml b/crates/floresta/Cargo.toml new file mode 100644 index 00000000..947c3f3b --- /dev/null +++ b/crates/floresta/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "floresta" +version = "0.1.0" +authors = ["Davidson Souza "] +edition = "2021" +description = """ + A modular and extensible framework for building Utreexo based Bitcoin nodes. +""" +repository = "https://github.com/Davidson-Souza/Floresta" +license = "MIT" +readme = "README.md" +keywords = ["bitcoin", "utreexo", "node", "blockchain", "rust"] +categories = ["bitcoin", "blockchain", "node"] + +[dependencies] +floresta-common = { path = "../floresta-common" } +floresta-chain = { path = "../floresta-chain" } +floresta-wire = { path = "../floresta-wire" } +floresta-watch-only = { path = "../floresta-watch-only", features = ["memory-database" ] } +hashbrown = "0.14.0" + +[dev-dependencies] +rustreexo = { git = "https://www.github.com/mit-dci/rustreexo" } +miniscript = "10.0.0" +async-std = "1.12.0" +bitcoin = { version = "0.29", features = [ + "serde", + "no-std", + "bitcoinconsensus", +] } + +[lib] +crate-type = ["cdylib", "rlib", "staticlib"] + +[[example]] +name = "node" +path = "examples/node.rs" + +[[example]] +name = "watch-only" +path = "examples/watch-only.rs" + +[[example]] +name = "chainstate-builder" +path = "examples/chainstate-builder.rs" diff --git a/crates/floresta/examples/chainstate-builder.rs b/crates/floresta/examples/chainstate-builder.rs new file mode 100644 index 00000000..8beacf7a --- /dev/null +++ b/crates/floresta/examples/chainstate-builder.rs @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +//! In node.rs we created a node that connects to the Bitcoin network and downloads the blockchain. +//! We use the default chainstate, which starts at genesis and validates all blocks. This is +//! the simplest way to create a node, but you can also create a node that starts at a given +//! block, or that doesn't validate all signatures. All customizations are done through the +//! ChainStateBuilder struct. This example shows how to use it. +use bitcoin::hashes::Hash; +use bitcoin::BlockHash; +use floresta::chain::{ChainState, KvChainStore, Network}; + +use floresta_chain::pruned_utreexo::chain_state_builder::ChainStateBuilder; +use floresta_chain::ChainParams; +use rustreexo::accumulator::stump::Stump; +use std::str::FromStr; + +const DATA_DIR: &str = "./data"; + +#[async_std::main] +async fn main() { + // Create a new chain state, which will store the accumulator and the headers chain. + // It will be stored in the DATA_DIR directory. With this chain state, we don't keep + // the block data after we validated it. This saves a lot of space, but it means that + // we can't serve blocks to other nodes or rescan the blockchain without downloading + // it again. + let chain_store = + KvChainStore::new(DATA_DIR.into()).expect("failed to open the blockchain database"); + + // Create a new chain state builder. We can use it to customize the chain state. + // Assume valid is the same as in node.rs, it's the block that we assume that all + // blocks before it have valid signatures. + // + // Tip is the block that we consider to be the tip of the chain. If you want to + // start the chainstate at a given block, you can use this. If you don't set it, + // it will start at genesis. + // + // We also set the chain params, which are the parameters of the network that we + // are connecting to. We use the Bitcoin network here, but you can also use + // Testnet, Signet or Regtest. + // + // Finally, we set the utreexo accumulator. This is the accumulator that we use + // to validate the blockchain. If you set the chain height, you should update + // the accumulator to the state of the blockchain at that height too. + let _chain: ChainState = ChainStateBuilder::new() + .with_assume_valid((BlockHash::all_zeros(), 0)) + .with_chain_params(ChainParams::from(Network::Bitcoin)) + .with_tip((BlockHash::from_str("").unwrap(), 0)) + .assume_utreexo(Stump::new()) + .with_chainstore(chain_store) + .build() + .unwrap(); + + // ... If you want to drive the chainstate, you can use the BlockchainInterface trait. + // See node.rs for an example on how to do it ... +} diff --git a/crates/floresta/examples/node.rs b/crates/floresta/examples/node.rs new file mode 100644 index 00000000..7078796a --- /dev/null +++ b/crates/floresta/examples/node.rs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT + +//! A simple example of a Bitcoin node using the floresta crates. It connects to the Bitcoin +//! network, downloads the blockchain, and prints the current state of the accumulator. +//! This will validate all blocks from genesis to the current tip, so it will take a while +//! to sync. + +use async_std::sync::RwLock; +use bitcoin::BlockHash; +use floresta::chain::{pruned_utreexo::BlockchainInterface, ChainState, KvChainStore, Network}; +use floresta::wire::mempool::Mempool; +use floresta::wire::node::UtreexoNode; +use floresta::wire::node_context::RunningNode; +use floresta_wire::node_interface::NodeMethods; +use std::str::FromStr; +use std::sync::Arc; + +const DATA_DIR: &str = "./data"; + +#[async_std::main] +async fn main() { + // Create a new chain state, which will store the accumulator and the headers chain. + // It will be stored in the DATA_DIR directory. With this chain state, we don't keep + // the block data after we validated it. This saves a lot of space, but it means that + // we can't serve blocks to other nodes or rescan the blockchain without downloading + // it again. + let chain_store = + KvChainStore::new(DATA_DIR.into()).expect("failed to open the blockchain database"); + + // The actual chainstate. It will keep track of the current state of the accumulator + // and the headers chain. It will also validate new blocks and headers as we receive them. + // The last parameter is the assume valid block. We assume that all blocks before this + // one have valid signatures. This is a performance optimization, as we don't need to validate all + // signatures in the blockchain, just the ones after the assume valid block. We are givin a None + // value, so we will validate all signatures regardless. + // We place the chain state in an Arc, so we can share it with other components. + let chain = Arc::new(ChainState::::new( + chain_store, + Network::Bitcoin, + None, + )); + + // Create a new node. It will connect to the Bitcoin network and start downloading the blockchain. + // It will also start a mempool, which will keep track of the current mempool state, this + // particular mempool doesn't store other's transactions, it just keeps track of our own, to + // perform broadcast. We always rebroadcast our own transactions every hour. + // Note that we are using the RunningNode context, which is a state optimized for a node that + // already has the blockchain synced. You don't need to worry about this, because internally + // the node will automatically switch to the IBD context and back once it's finished. + // If you want a node to IBD only, you can use the IBDNode context. + // Finally, we are using the chain state created above, the node will use it to determine + // what blocks and headers to download, and hand them to it to validate. + let p2p: UtreexoNode> = UtreexoNode::new( + chain.clone(), + Arc::new(RwLock::new(Mempool::new())), + Network::Bitcoin, + DATA_DIR.into(), + ); + // A handle is a simple way to interact with the node. It implements a queue of requests + // that will be processed by the node. + let handle = p2p.get_handle(); + + // Start the node. This will start the IBD process, and will return once the node is synced. + // It will also start the mempool, which will start rebroadcasting our transactions every hour. + // The node will keep running until the process is killed, by setting kill_signal to true. In + // this example, we don't kill the node, so it will keep running forever. + p2p.run(&Arc::new(RwLock::new(false))).await; + + // That's it! The node is now running, and will keep running until the process is killed. + // You can now use the chain state to query the current state of the accumulator, or the + // mempool to query the current state of the mempool. You may also ask the node to grab some + // blocks or headers for you, or to send a transaction to the network, rescan the blockchain, + // etc. Check the documentation of the node for more information. + + // You can't request blocks or headers from the node until it's synced. You can check if it's + // synced by calling the is_in_ibd method. + loop { + // Wait till the node is synced + if !chain.is_in_idb() { + break; + } + // Sleep for 10 seconds, and check again + std::thread::sleep(std::time::Duration::from_secs(10)); + } + + // Here we ask the node to grab the block with the given hash. + let block = handle + .get_block( + BlockHash::from_str("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") + .unwrap(), + ) + .unwrap(); + println!("Block: {:?}", block); +} diff --git a/crates/floresta/examples/watch-only.rs b/crates/floresta/examples/watch-only.rs new file mode 100644 index 00000000..4aaadd77 --- /dev/null +++ b/crates/floresta/examples/watch-only.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT + +//! This example shows how to create a watch-only wallet, and drive it. + +use bitcoin::{consensus::deserialize, hashes::hex::FromHex, Script}; +use floresta_common::get_spk_hash; +use floresta_watch_only::{memory_database::MemoryDatabase, AddressCache}; +use miniscript::{bitcoin::secp256k1::Secp256k1, Descriptor}; + +fn main() { + // First, we need some place to store the wallet data. Here, we use an in-memory database, + // that will be destroyed when the program exits. You can use any database that implements + // the `AddressCacheDatabase` trait. + let wallet_data = MemoryDatabase::new(); + // Then, we create the wallet itself. + let mut wallet = AddressCache::new(wallet_data); + // Now, we need to add the addresses we want to watch. We can add them one by one, or + // we can add a descriptor that will generate the addresses for us. Here, we use a + // descriptor that generates P2WPKH addresses. The descriptor is parsed using the + // `miniscript` library. You can use any descriptor that `miniscript` supports. + + // To parse a descriptor, we need a `Secp256k1` context. This is a wrapper around the + // secp256k1 library, that provides some additional functionality. We need it to parse + // the descriptor, and to derive the addresses. + let secp = Secp256k1::new(); + + // We can now parse the descriptor. The `parse_descriptor` function returns a tuple + // containing the parsed descriptor, and the map of the keys used in the descriptor. + // The keys are indexed by their fingerprint, and the index of the key in the descriptor. + let (descriptor, _) = Descriptor::parse_descriptor(&secp, "wpkh([18940c85/84'/1'/0']tpubDDgpGUUjzTqLqQL9WzPvMDTKyD95AUcwWohMWWoj5kqGU7VLSZ3ju9ZtHRN4ofK6KNaZsTSpB6yGrFuV1V4yVgcwksueuFW3YnKxwoNqb3V/0/*)#0vfhw5fe").unwrap(); + + // We can now add the descriptor to the wallet. This will generate the first 100 addresses + // for us, and add them to the wallet. + for i in 0..100 { + wallet.cache_address( + bitcoin::Script::try_from( + descriptor + .at_derivation_index(i) + .unwrap() + .explicit_script() + .unwrap() + .as_bytes() + .to_vec(), + ) + .unwrap(), + ); + } + // We can now process some blocks. Here, we process the first 11 blocks of a custom + // regtest network. Each coinbase some of the addresses derived above. + for block in BLOCKS.iter() { + let block = Vec::from_hex(block).unwrap(); + let _ = wallet.block_process(&deserialize(&block).unwrap(), 1); + } + // We can now query the wallet for information about the addresses we added. For example, + // we can get the history of the second address, the balance, and the UTXOs. To fetch the + // history, we need to know the hash of the address. We can get it using the `get_spk_hash` + // function from the `floresta_common` crate. This hash is defined by the Electrum protocol. + let hash = + get_spk_hash(&Script::from_hex("00148dae58668d0c15f7ed4f430925634c9a2c666b84").unwrap()); + + // We can now query the wallet for the information we want. + + // Fetch all txids that involve the address. + let history = wallet + .get_address_history(&hash) + .iter() + .map(|tx| tx.hash) + .collect::>(); + // Fetch the balance of the address. + let balance = wallet.get_address_balance(&hash); + // Fetch the UTXOs of the address. + let utxos = wallet.get_address_utxos(&hash); + + // We can now print the information we fetched. + println!("************** Wallet Summary *****************\n"); + println!("Descriptor: {}\n", descriptor); + println!("Address #1 hash: {}\n", hash); + println!("History: {:#?}\n", history); + println!("Balance: {:?}\n", balance); + println!("UTXOs: {:#?}", utxos); +} + +const BLOCKS: [&str; 11] = [ + "0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f05b917cfbd9c40358ff9a0e64f9da95fd676b8a02a42a32c1c192bd76db60eef20249364ffff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0200f2052a01000000160014b4ad0d1d8978f3680fa3fc2dfb81b1143a27d4a30000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000201b0abeb75b806f29ee13248bf9c7892672cf80ded985f27fb2f5eb41108bca061e0d3a1c401b2cb5abdc7d1b45da6fa6eb23c7b2ceb82b3c501e15e33d8f30089b309364ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03520101ffffffff0200f2052a010000001600148dae58668d0c15f7ed4f430925634c9a2c666b840000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000203ed220606ae32337ff45816054bb01a39bfd943ca84daa61c50ad173285f414df345b7d00a902077ef29be75ea0f7b6a4b816d6ad61112865273576fcd3e4b109b309364ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03530101ffffffff0200f2052a010000001600148dae58668d0c15f7ed4f430925634c9a2c666b840000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020ba4b379335f3896b025293e6ceeceb865a22093e8550adcca844adfd2afdb00c7482b91566cb4f5c26f917e121699d61bba1926b9dbf43e0fa85fae80649f6889c309364ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03540101ffffffff0200f2052a010000001600148dae58668d0c15f7ed4f430925634c9a2c666b840000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000208297ce55eed31f7d3fb2cb5b1a5aaeddbc8b4b3133b5623502603a7c1b807477075cf1bb8f9e2f58c63c8ed8b3a085a52a5c982adc6a020994dd59cbe227832d9c309364ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03550101ffffffff0200f2052a010000001600148dae58668d0c15f7ed4f430925634c9a2c666b840000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020936b5320b7b346069715fd6ab491b8a29241b5c990b39f50a793c6329b7005671bf190dc397e976064eccff3eca995df63c3f49948fd2889355b935ff42c91de9c309364ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03560101ffffffff0200f2052a010000001600148dae58668d0c15f7ed4f430925634c9a2c666b840000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0000002058217e2ee8e6074c8b26d67b96c9130f0cb52adcced5ba67a30e53a7eacee37caaa4812183e293e93428161f6f89179066124152ca8b1658d66ace5b76aa61849c309364ffff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03570101ffffffff0200f2052a010000001600148dae58668d0c15f7ed4f430925634c9a2c666b840000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020e48e8d22a54e78018591d386fbe5b09d3588756cc541e514074f82aa2596a76400b239f668cb9c5c5809e206754c96a0215a5d7013405ac1b4ad37536400c3649d309364ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03580101ffffffff0200f2052a010000001600148dae58668d0c15f7ed4f430925634c9a2c666b840000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020c007dbdee745169560c57367725b69be705b2e2728c6da6313c722309d4a456bce0d0e66ddf0ad519cb91384f1e5038ee3765f31b57781f6eda08678e83e1fcc9f309364ffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03590101ffffffff0200f2052a010000001600148dae58668d0c15f7ed4f430925634c9a2c666b840000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000201096d0268755a324b6b9a1df531877b3f8e161c21261063ba1f1d79d5ba22e5bb386dd8071a9d2a62a2763cc64ceebe85535d4665d7a4f02f18408579874852e9f309364ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035a0101ffffffff0200f2052a010000001600148dae58668d0c15f7ed4f430925634c9a2c666b840000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "00000020d0a457ae192831a9dcc1245ed7387c1efc3423bd09d5a306a02772f3ac16827c021a6dbe6981c7e57307533026dc972dd29e5b04d899ba13df355c87cd2797ed9f309364ffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035b0101ffffffff0200f2052a010000001600148dae58668d0c15f7ed4f430925634c9a2c666b840000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", +]; diff --git a/crates/floresta/src/lib.rs b/crates/floresta/src/lib.rs new file mode 100644 index 00000000..051fac46 --- /dev/null +++ b/crates/floresta/src/lib.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +//! Floresta, a collection of crates to build lightweight, fast, and reliable +//! Bitcoin nodes and wallets, powered by Utreexo, a novel accumulator to represent +//! the Bitcoin UTXO set. +//! +//! This project is layed out as a collection of crates, each implementing one functionality. +//! They are all named floresta-*. The main crate is floresta, which is a meta-crate +//! that depends on all the others. It is meant to be used as a dependency in other projects. +//! +//! A Bitcoin node is composed of several components. You need some way to express the current +//! network state, some way to communicate with other nodes, some way to store the blockchain, +//! validate data you receive and interact with the user. +//! The current network state (including the accumulator) is tracked by the floresta-chain crate. +//! It doesn't know where data comes from, it just tracks the state, exposing a simple API to +//! update it. The chain is reactive, meaning that it will only update when new data is received. +//! +//! The floresta-wire crate is responsible for communicating with other nodes. It is a simple +//! node implementation that can connect to other nodes, send and receive messages, and +//! handle the peer-to-peer protocol. It is not a full node, it doesn't store the blockchain +//! or validate data, it just sends and receives messages. +//! +//! Floresta also provides a simple watch-only wallet an an electrum server implementation. +//! They are meant to be used in `florestad`, a full node implementation that uses all the +//! crates in this project. +//! +//! You can find examples of how to use the crates in the examples directory. +//! # Name +//! Floresta is the Portuguese word for forest. It is a reference to the Utreexo accumulator, +//! which is a forest of Merkle trees. It's pronounced /floˈɾɛstɐ/. +pub use floresta_chain as chain; +pub use floresta_common as common; +pub use floresta_watch_only as wallet; +pub use floresta_wire as wire; diff --git a/florestad/Cargo.toml b/florestad/Cargo.toml new file mode 100644 index 00000000..ff557e8d --- /dev/null +++ b/florestad/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "florestad" +version = "0.1.0" +edition = "2021" + +[dependencies] +rustreexo = { git = "https://www.github.com/mit-dci/rustreexo" } +btcd-rpc = { git = "https://github.com/Davidson-Souza/rust-btcd-rpc", features = [ + "utreexod", +], branch = "use-reqwest", optional = true } +clap = { version = "4.0.29", features = ["derive"] } +sha2 = "^0.10.6" +async-std = { version = "1.12.0", features = ["attributes"] } +log = "0.4" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +kv = "0.24.0" +# Using master branch of miniscript because the multipath feature for output descriptor parsing isn't released yet +miniscript = { git = "https://github.com/douglaz/rust-miniscript.git", branch = "master-2023-03-30" } +pretty_env_logger = "0.4.0" +futures = "0.3.4" +rmp-serde = { optional = true, version = "1.1.1" } +toml = "0.5.10" +dirs = "4.0.0" +rand = "0.8.5" +bitcoin = { version = "0.29", features = ["serde", "std", "bitcoinconsensus"] } +slip132 = "0.9.0" +ctrlc = "3.2.5" +floresta-chain = { path = "../crates/floresta-chain" } +floresta-common = { path = "../crates/floresta-common" } +floresta-electrum = { path = "../crates/floresta-electrum" } +floresta-watch-only = { path = "../crates/floresta-watch-only" } +floresta-wire = { path = "../crates/floresta-wire" } +anyhow = "1.0.40" + +jsonrpc-http-server = { version = "18.0.0", optional = true } +jsonrpc-derive = { version = "18.0.0", optional = true } +jsonrpc-core = { version = "18.0.0", optional = true } +jsonrpc-core-client = { version = "18.0.0", features = [ + "http", +], optional = true } + +[dev-dependencies] +pretty_assertions = "1" + +[features] +experimental-p2p = [] +json-rpc = [ + "jsonrpc-http-server", + "jsonrpc-derive", + "jsonrpc-core", + "jsonrpc-core-client", +] +default = ["experimental-p2p"] diff --git a/docs/assets/Screenshot_ibd.jpg b/florestad/docs/assets/Screenshot_ibd.jpg similarity index 100% rename from docs/assets/Screenshot_ibd.jpg rename to florestad/docs/assets/Screenshot_ibd.jpg diff --git a/docs/tutorial(PT-BR).md b/florestad/docs/tutorial(PT-BR).md similarity index 100% rename from docs/tutorial(PT-BR).md rename to florestad/docs/tutorial(PT-BR).md diff --git a/src/cli.rs b/florestad/src/cli.rs similarity index 100% rename from src/cli.rs rename to florestad/src/cli.rs diff --git a/src/config_file.rs b/florestad/src/config_file.rs similarity index 100% rename from src/config_file.rs rename to florestad/src/config_file.rs diff --git a/src/error.rs b/florestad/src/error.rs similarity index 87% rename from src/error.rs rename to florestad/src/error.rs index f5a15c1a..4c782a82 100644 --- a/src/error.rs +++ b/florestad/src/error.rs @@ -1,18 +1,13 @@ -use crate::{ - blockchain::error::{BlockValidationErrors, BlockchainError}, - impl_from_error, -}; use bitcoin::consensus::encode; #[cfg(feature = "cli-blockchain")] use btcd_rpc::error::UtreexodError; +use floresta_chain::{BlockValidationErrors, BlockchainError}; #[derive(Debug)] pub enum Error { #[cfg(feature = "cli-blockchain")] UtreexodError(UtreexodError), - TransactionNotFound, ParsingError(bitcoin::hashes::hex::Error), EncodeError(encode::Error), - WalletNotInitialized, DbError(kv::Error), ParseNumError(std::num::ParseIntError), RustreexoError(String), @@ -25,8 +20,6 @@ pub enum Error { WalletInputError(slip132::Error), AddressParsingError(bitcoin::util::address::Error), MiniscriptError(miniscript::Error), - #[cfg(test)] - DatabaseError, } impl std::fmt::Display for Error { @@ -36,7 +29,6 @@ impl std::fmt::Display for Error { Error::ParsingError(err) => write!(f, "Parsing Error {err}"), #[cfg(feature = "cli-blockchain")] Error::UtreexodError(_) => write!(f, "UtreexodError"), - Error::WalletNotInitialized => write!(f, "WalletNotInitialized"), Error::DbError(err) => write!(f, "Database error {err}"), Error::ParseNumError(err) => write!(f, "int parse error: {err}"), Error::RustreexoError(err) => write!(f, "Rustreexo error: {err}"), @@ -48,14 +40,21 @@ impl std::fmt::Display for Error { Error::TomlParsingError(err) => write!(f, "Error deserializing toml file {err}"), Error::AddressParsingError(err) => write!(f, "Invalid address {err}"), Error::MiniscriptError(err) => write!(f, "Miniscript error: {err}"), - Error::TransactionNotFound => write!(f, "Transaction not found"), Error::BlockValidationError(err) => write!(f, "Error while validating block: {err:?}"), - #[cfg(test)] - Error::DatabaseError => write!(f, "An unknown error happened with a database"), } } } - +/// Implements [From] where [T] is a possible error outcome in this crate, this macro only +/// takes [T] and builds [Error] with the right variant. +macro_rules! impl_from_error { + ($field: ident, $error: ty) => { + impl From<$error> for Error { + fn from(err: $error) -> Self { + Error::$field(err) + } + } + }; +} impl_from_error!(ParsingError, bitcoin::hashes::hex::Error); #[cfg(feature = "cli-blockchain")] impl_from_error!(UtreexodError, UtreexodError); @@ -74,15 +73,3 @@ impl_from_error!(AddressParsingError, bitcoin::util::address::Error); impl_from_error!(MiniscriptError, miniscript::Error); impl std::error::Error for Error {} -#[macro_export] -/// Implements [From] where [T] is a possible error outcome in this crate, this macro only -/// takes [T] and builds [Error] with the right variant. -macro_rules! impl_from_error { - ($field: ident, $error: ty) => { - impl From<$error> for Error { - fn from(err: $error) -> Self { - Error::$field(err) - } - } - }; -} diff --git a/src/json_rpc/mod.rs b/florestad/src/json_rpc/mod.rs similarity index 100% rename from src/json_rpc/mod.rs rename to florestad/src/json_rpc/mod.rs diff --git a/src/json_rpc/res.rs b/florestad/src/json_rpc/res.rs similarity index 100% rename from src/json_rpc/res.rs rename to florestad/src/json_rpc/res.rs diff --git a/src/json_rpc/server.rs b/florestad/src/json_rpc/server.rs similarity index 87% rename from src/json_rpc/server.rs rename to florestad/src/json_rpc/server.rs index edb80c07..ee4fd430 100644 --- a/src/json_rpc/server.rs +++ b/florestad/src/json_rpc/server.rs @@ -1,14 +1,15 @@ -use crate::blockchain::{ - error::BlockchainError, - p2p_blockchain::node_interface::{NodeInterface, NodeMethods, PeerInfo}, - BlockchainProviderInterface, -}; use async_std::sync::RwLock; use bitcoin::{ consensus::{deserialize, serialize}, hashes::hex::{FromHex, ToHex}, BlockHash, BlockHeader, Network, Transaction, TxOut, Txid, }; +use floresta_chain::{ + pruned_utreexo::{BlockchainInterface, UpdatableChainstate}, + ChainState, KvChainStore, +}; +use floresta_watch_only::{kv_database::KvDatabase, AddressCache}; +use floresta_wire::node_interface::{NodeInterface, NodeMethods, PeerInfo}; use futures::executor::block_on; use jsonrpc_core::Result; use jsonrpc_derive::rpc; @@ -16,11 +17,6 @@ use jsonrpc_http_server::ServerBuilder; use serde_json::{json, Value}; use std::sync::Arc; -use crate::{ - address_cache::{kv_database::KvDatabase, AddressCache}, - blockchain::{chain_state::ChainState, chainstore::KvChainStore, BlockchainInterface}, -}; - use super::res::{Error, GetBlockchainInfoRes}; #[rpc] @@ -81,11 +77,8 @@ impl Rpc for RpcImpl { } fn get_roots(&self) -> Result> { - let ret = self.chain.get_root_hashes(); - if let Ok(hashes) = ret { - return Ok(hashes.iter().map(|h| h.to_string()).collect()); - } - Err(Error::ChainError.into()) + let hashes = self.chain.get_root_hashes(); + return Ok(hashes.iter().map(|h| h.to_string()).collect()); } fn get_block_hash(&self, height: u32) -> Result { @@ -115,12 +108,12 @@ impl Rpc for RpcImpl { fn load_descriptor(&self, descriptor: String, rescan: Option) -> Result<()> { let wallet = block_on(self.wallet.write()); - let result = wallet.push_descriptor(&descriptor).and_then(|_| { - if let Some(rescan) = rescan { - self.chain.rescan(rescan)?; - } - Ok(()) - }); + let result = wallet.push_descriptor(&descriptor); + if let Some(rescan) = rescan { + self.chain + .rescan(rescan) + .map_err(|_| jsonrpc_core::Error::internal_error())?; + } if result.is_err() { return Err(Error::InvalidDescriptor.into()); } @@ -179,7 +172,10 @@ impl Rpc for RpcImpl { } fn find_tx_out(&self, block_height: u32, tx_id: Txid, outpoint: usize) -> Result { - let block_hash = self.chain.get_block_hash(block_height)?; + let block_hash = self + .chain + .get_block_hash(block_height) + .map_err(|_| Error::ChainError)?; let block = self .node .get_block(block_hash) @@ -243,13 +239,3 @@ impl RpcImpl { .unwrap() } } - -impl From for jsonrpc_core::Error { - fn from(e: BlockchainError) -> Self { - jsonrpc_core::Error { - code: 1.into(), - message: format!("{:?}", e), - data: None, - } - } -} diff --git a/src/main.rs b/florestad/src/main.rs similarity index 90% rename from src/main.rs rename to florestad/src/main.rs index 0a684fb1..a1386670 100644 --- a/src/main.rs +++ b/florestad/src/main.rs @@ -24,41 +24,35 @@ #![deny(non_upper_case_globals)] #![deny(unused)] -mod address_cache; -mod blockchain; mod cli; mod config_file; -mod electrum; mod error; #[cfg(feature = "json-rpc")] mod json_rpc; -mod version; mod wallet_input; use std::{path::PathBuf, sync::Arc}; -use address_cache::{kv_database::KvDatabase, AddressCache, AddressCacheDatabase}; use async_std::{ sync::RwLock, task::{self, block_on}, }; use bitcoin::Network; -use blockchain::{chain_state::ChainState, chainstore::KvChainStore}; -#[cfg(not(feature = "experimental-p2p"))] -use btcd_rpc::client::{BTCDClient, BTCDConfigs}; use clap::Parser; use cli::{Cli, Commands}; use config_file::ConfigFile; +use floresta_chain::{ + pruned_utreexo::BlockchainInterface, BlockchainError, ChainState, KvChainStore, +}; +use floresta_common::constants::DIR_NAME; +use floresta_electrum::electrum_protocol::{accept_loop, ElectrumServer}; +use floresta_watch_only::{kv_database::KvDatabase, AddressCache, AddressCacheDatabase}; +use floresta_wire::{mempool::Mempool, node::UtreexoNode}; use log::{debug, error, info}; use pretty_env_logger::env_logger::{Env, TimestampPrecision}; -#[cfg(not(feature = "experimental-p2p"))] -use crate::blockchain::cli_blockchain::UtreexodBackend; - -#[cfg(feature = "experimental-p2p")] -use crate::blockchain::p2p_blockchain::{mempool::Mempool, node::UtreexoNode}; -use crate::{blockchain::BlockchainInterface, version::DIR_NAME, wallet_input::InitialWalletSetup}; +use crate::wallet_input::InitialWalletSetup; fn main() { // Setup global logger @@ -236,13 +230,11 @@ fn main() { let chain_provider = UtreexoNode::new( blockchain_state.clone(), Arc::new(async_std::sync::RwLock::new(Mempool::new())), - get_net(¶ms.network), + get_net(¶ms.network).into(), data_dir, ); info!("Starting server"); let wallet = Arc::new(RwLock::new(wallet)); - // Create a new electrum server, we need to block_on because `ElectrumServer::new` is `async` - // but our main isn't, so we can't `.await` on it. #[cfg(feature = "json-rpc")] let _server = json_rpc::server::RpcImpl::create( blockchain_state.clone(), @@ -251,14 +243,16 @@ fn main() { chain_provider.get_handle(), kill_signal.clone(), ); - let electrum_server = block_on(electrum::electrum_protocol::ElectrumServer::new( + // Create a new electrum server, we need to block_on because `ElectrumServer::new` is `async` + // but our main isn't, so we can't `.await` on it. + let electrum_server = block_on(ElectrumServer::new( "0.0.0.0:50001", wallet, blockchain_state, )) .expect("Could not create an Electrum Server"); - task::spawn(electrum::electrum_protocol::accept_loop( + task::spawn(accept_loop( electrum_server .listener .clone() @@ -306,30 +300,30 @@ fn get_config_file(params: &cli::Cli) -> ConfigFile { } } } -fn get_key_from_env() -> Option { - let xpub = std::env::var("WALLET_XPUB"); - match xpub { - Ok(key) => return Some(key), - Err(e) => match e { - std::env::VarError::NotPresent => {} - std::env::VarError::NotUnicode(xpub) => error!("Invalid xpub {xpub:?}"), - }, - } - None -} +// fn get_key_from_env() -> Option { +// let xpub = std::env::var("WALLET_XPUB"); +// match xpub { +// Ok(key) => return Some(key), +// Err(e) => match e { +// std::env::VarError::NotPresent => {} +// std::env::VarError::NotUnicode(xpub) => error!("Invalid xpub {xpub:?}"), +// }, +// } +// None +// } fn load_chain_state( data_dir: &String, network: Network, assume_valid: Option, ) -> ChainState { let db = KvChainStore::new(data_dir.to_string()).expect("Could not read db"); - match ChainState::::load_chain_state(db, network, assume_valid) { + match ChainState::::load_chain_state(db, network.into(), assume_valid) { Ok(chainstate) => chainstate, Err(err) => match err { - blockchain::error::BlockchainError::ChainNotInitialized => { + BlockchainError::ChainNotInitialized => { let db = KvChainStore::new(data_dir.to_string()).expect("Could not read db"); - ChainState::::new(db, network, assume_valid) + ChainState::::new(db, network.into(), assume_valid) } _ => unreachable!(), }, @@ -371,16 +365,12 @@ fn get_net(net: &cli::Network) -> Network { } fn setup_wallet( - mut xpubs: Vec, + xpubs: Vec, descriptors: Vec, addresses: Vec, wallet: &mut AddressCache, network: cli::Network, -) -> Result<(), crate::error::Error> { - let env_key = get_key_from_env(); - if let Some(key) = env_key { - xpubs.push(key); - } +) -> anyhow::Result<()> { let setup = InitialWalletSetup::build(&xpubs, &descriptors, &addresses, get_net(&network), 100)?; for descriptor in setup.descriptors { @@ -393,7 +383,7 @@ fn setup_wallet( wallet.cache_address(addresses.script_pubkey()); } info!("Wallet setup completed!"); - Ok(()) + anyhow::Ok(()) } #[cfg(not(feature = "experimental-p2p"))] @@ -423,7 +413,7 @@ where } fn get_both_vec(a: Option>, b: Option>) -> Vec { - let mut result: Vec = vec![]; + let mut result: Vec = Vec::new(); if let Some(a) = a { result.extend(a.into_iter()); } diff --git a/src/wallet_input.rs b/florestad/src/wallet_input.rs similarity index 100% rename from src/wallet_input.rs rename to florestad/src/wallet_input.rs diff --git a/readme.md b/readme.md index d42ab943..1226c032 100644 --- a/readme.md +++ b/readme.md @@ -1,9 +1,11 @@ ### Floresta -**This is a WIP project, and should not be used with real money!** +Welcome to Floresta, a lightweight Bitcoin full node implementation written in Rust, powered by [Utreexo](https://eprint.iacr.org/2019/611) a novel dynamic accumulator designed for the Bitcoin UTXO set. -This is a WIP for a wallet/node powered by utreexo. This codebase can download and parse blocks ~~(from a Utreexod cli)~~, find transactions we need to our wallet and do some basic consensus validation. The consensus part is incomplete, and should not be trusted yet. -This code also have an out-of-the-box Electrum Server that you can use with any wallet that supports it. +This project is composed of two parts, `libfloresta` and `florestad`. `libfloresta` is +a set of reusable components that can be used to build Bitcoin applications. `florestad` is built on top of `libfloresta` to provide a full node implementation, including a watch-only wallet and an Electrum server. If you just want to run a full node, you can use `florestad` directly, either by building it from source or by downloading a pre-built binary from the [releases](https://github.com/Davidson-Souza/Floresta/releases/tag/v0.4.0). + +If you want to use `libfloresta` to build your own Bitcoin application, you can find the documentation [here](https://docs.rs/floresta/). ### Building @@ -15,7 +17,7 @@ Once you have Cargo, clone the repository with: git clone https://github.com/Davidson-Souza/Floresta.git ``` -Navegue para dentro da pasta com +go to the Floresta directory ```bash cd Floresta/ @@ -24,11 +26,13 @@ cd Floresta/ and build with cargo build ```bash -cargo build --release +cargo build --release --bin florestad +# Optionally, you can install it with +cargo install --path . ``` ### Running -Right now, this project is working on signet only. Mainnet support is still a todo thing. You can get some signet coins [here](signetfaucet.com/) and just play around with it. +Right now, this project is working on signet only. Mainnet support is still a todo thing. You can get some signet coins [here](https://www.signetfaucet.com/) and just play around with it. Copy `config.toml.sample` to `config.toml`, and fill up your xpubs and addresses that you intend to track, and then run with ```bash @@ -46,3 +50,27 @@ or ```bash cargo run --release -- -c config.toml --network signet run ``` + +### Running the tests +There's a set of unit tests that you can run with +```bash +cargo test +``` + +### Contributing +Contributions are welcome, feel free to open an issue or a pull request. There's not really a set of guidelines for contributing other than the code compiling and the tests passing. If you want to contribute but don't know where to start, take a look at the issues, there's a few of them marked as `good first issue`. + +### License +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details + +### Acknowledgments +* [Utreexo](https://eprint.iacr.org/2019/611) +* [Bitcoin Core](https://github.com/bitcoin/bitcoin) +* [Rust Bitcoin](https://github.com/rust-bitcoin/rust-bitcoin) +* [Rust Miniscript](https://github.com/rust-bitcoin/rust-miniscript) + +### Consensus implementation + +One of the most challenging parts of working with Bitcoin is keeping up with the consensus rules. Given it's nature as a consensus protocol, it's very important to make sure that the implementation is correct. Instead of reimplementing a Script interpreter, we use [`rust-bitcoinconsensus`](https://github.com/rust-bitcoin/rust-bitcoinconsensus/) to verify transactions. This is a bind around a shared library that is part of Bitcoin Core. This way, we can be sure that the consensus rules are the same as Bitcoin Core, at least for scripts. + +Although tx validation is arguably the hardest part in this process. This integration can be further improved by using `libbitcoinkernel`, that will increase the scope of `libbitcoinconsensus` to outside scripts, but this is still a work in progress. \ No newline at end of file diff --git a/src/blockchain/error.rs b/src/blockchain/error.rs deleted file mode 100644 index 88699e9d..00000000 --- a/src/blockchain/error.rs +++ /dev/null @@ -1,115 +0,0 @@ -#[cfg(feature = "experimental-p2p")] -use super::p2p_blockchain::node::NodeRequest; - -use bitcoin::blockdata::script; - -#[cfg(feature = "cli-blockchain")] -use btcd_rpc::error::UtreexodError; - -#[derive(Debug)] -pub enum BlockchainError { - BlockNotPresent, - #[cfg(feature = "cli-blockchain")] - JsonRpcError(UtreexodError), - ParsingError(bitcoin::hashes::hex::Error), - BlockValidationError(BlockValidationErrors), - InvalidProof, - UtreexoError(String), - DatabaseError(kv::Error), - ConsensusDecodeError(bitcoin::consensus::encode::Error), - ChainNotInitialized, - InvalidTip(String), - IoError(std::io::Error), - #[cfg(feature = "experimental-p2p")] - MessageTooBig, - #[cfg(feature = "experimental-p2p")] - PeerMessageInvalidMagic, - #[cfg(feature = "experimental-p2p")] - NoPeersAvailable, - #[cfg(feature = "experimental-p2p")] - ChannelError(async_std::channel::SendError), - #[cfg(feature = "experimental-p2p")] - RecvError(async_std::channel::RecvError), - #[cfg(feature = "experimental-p2p")] - CoinbaseNotMatured, - #[cfg(feature = "experimental-p2p")] - AddrParseError(std::net::AddrParseError), - #[cfg(feature = "experimental-p2p")] - PeerMisbehaving, - #[cfg(feature = "experimental-p2p")] - InvalidMessage(String), -} - -#[derive(Debug)] -pub enum BlockValidationErrors { - InvalidTx, - NotEnoughPow, - BadMerkleRoot, - BadWitnessCommitment, - NotEnoughMoney, - FirstTxIsnNotCoinbase, - BadCoinbaseOutValue, - EmptyBlock, - BlockExtendsAnOrphanChain, - BadBip34, -} -impl From for BlockchainError { - fn from(err: bitcoin::consensus::encode::Error) -> Self { - Self::ConsensusDecodeError(err) - } -} - -impl From for BlockchainError { - fn from(err: kv::Error) -> Self { - BlockchainError::DatabaseError(err) - } -} -#[cfg(feature = "experimental-p2p")] -impl From> for BlockchainError { - fn from(err: async_std::channel::SendError) -> Self { - BlockchainError::ChannelError(err) - } -} -#[cfg(feature = "experimental-p2p")] -impl From for BlockchainError { - fn from(err: std::net::AddrParseError) -> Self { - BlockchainError::AddrParseError(err) - } -} -#[cfg(feature = "cli-blockchain")] -impl From for BlockchainError { - fn from(err: UtreexodError) -> Self { - BlockchainError::JsonRpcError(err) - } -} -impl From for BlockchainError { - fn from(err: bitcoin::hashes::hex::Error) -> Self { - BlockchainError::ParsingError(err) - } -} -impl From for BlockchainError { - fn from(_: script::Error) -> Self { - BlockchainError::BlockValidationError(BlockValidationErrors::InvalidTx) - } -} -impl From for BlockchainError { - fn from(err: String) -> Self { - BlockchainError::UtreexoError(err) - } -} -impl From for BlockchainError { - fn from(e: std::io::Error) -> Self { - BlockchainError::IoError(e) - } -} -#[cfg(feature = "experimental-p2p")] -impl From for BlockchainError { - fn from(e: async_std::channel::RecvError) -> Self { - BlockchainError::RecvError(e) - } -} -impl From for BlockchainError { - fn from(e: BlockValidationErrors) -> Self { - BlockchainError::BlockValidationError(e) - } -} diff --git a/src/electrum/error.rs b/src/electrum/error.rs deleted file mode 100644 index 4dc5d66c..00000000 --- a/src/electrum/error.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[cfg(feature = "cli-blockchain")] -use btcd_rpc::error::UtreexodError; - -use crate::{blockchain::error::BlockchainError, impl_from_error}; -#[derive(Debug)] -pub enum Error { - #[cfg(feature = "cli-blockchain")] - BackendError(UtreexodError), - InvalidParams, - ParsingError(serde_json::Error), - BlockchainError(BlockchainError), -} -#[cfg(feature = "cli-blockchain")] -impl_from_error!(BackendError, UtreexodError); -impl_from_error!(ParsingError, serde_json::Error); -impl_from_error!(BlockchainError, BlockchainError); From 9a25113b481832c6daf6195cf3239d69d37fec55 Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:56:07 -0300 Subject: [PATCH 002/328] Create LICENSE (#60) --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..46386800 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Davidson Souza + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 0d710c9191f838c55207b53f9513922534e5b08e Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Sat, 29 Jul 2023 22:44:33 -0300 Subject: [PATCH 003/328] Use env var to pass xpubs (#61) --- florestad/src/main.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/florestad/src/main.rs b/florestad/src/main.rs index a1386670..59f50b96 100644 --- a/florestad/src/main.rs +++ b/florestad/src/main.rs @@ -300,17 +300,17 @@ fn get_config_file(params: &cli::Cli) -> ConfigFile { } } } -// fn get_key_from_env() -> Option { -// let xpub = std::env::var("WALLET_XPUB"); -// match xpub { -// Ok(key) => return Some(key), -// Err(e) => match e { -// std::env::VarError::NotPresent => {} -// std::env::VarError::NotUnicode(xpub) => error!("Invalid xpub {xpub:?}"), -// }, -// } -// None -// } +fn get_key_from_env() -> Option { + let xpub = std::env::var("WALLET_XPUB"); + match xpub { + Ok(key) => return Some(key), + Err(e) => match e { + std::env::VarError::NotPresent => {} + std::env::VarError::NotUnicode(xpub) => error!("Invalid xpub {xpub:?}"), + }, + } + None +} fn load_chain_state( data_dir: &String, network: Network, @@ -365,12 +365,15 @@ fn get_net(net: &cli::Network) -> Network { } fn setup_wallet( - xpubs: Vec, + mut xpubs: Vec, descriptors: Vec, addresses: Vec, wallet: &mut AddressCache, network: cli::Network, ) -> anyhow::Result<()> { + if let Some(key) = get_key_from_env() { + xpubs.push(key); + } let setup = InitialWalletSetup::build(&xpubs, &descriptors, &addresses, get_net(&network), 100)?; for descriptor in setup.descriptors { From f98194f64273e40b0bdad87a344180220c21072a Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Tue, 1 Aug 2023 14:49:17 -0300 Subject: [PATCH 004/328] Refactor: Move stateless code to a new module (#62) Some of the code in chainstate doesn't require a knowledge of the current tip, and will be used in alternative implementations of chainstate, like partial_chainstate used in parallel sync. This commit is move-only. --- .../src/pruned_utreexo/chain_state.rs | 271 ++++-------------- .../src/pruned_utreexo/consensus.rs | 195 +++++++++++++ .../floresta-chain/src/pruned_utreexo/mod.rs | 1 + 3 files changed, 252 insertions(+), 215 deletions(-) create mode 100644 crates/floresta-chain/src/pruned_utreexo/consensus.rs diff --git a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs index 819a13fd..98775ae5 100644 --- a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs @@ -3,6 +3,7 @@ use super::{ chain_state_builder::ChainStateBuilder, chainparams::ChainParams, chainstore::{ChainStore, DiskBlockHeader, KvChainStore}, + consensus::Consensus, error::{BlockValidationErrors, BlockchainError}, BlockchainInterface, Notification, UpdatableChainstate, }; @@ -12,9 +13,9 @@ use alloc::{borrow::ToOwned, fmt::format, string::ToString, vec::Vec}; use async_std::channel::Sender; use bitcoin::{ bitcoinconsensus, - blockdata::constants::{genesis_block, COIN_VALUE}, + blockdata::constants::genesis_block, consensus::{deserialize_partial, Decodable, Encodable}, - hashes::{hex::FromHex, sha256, Hash}, + hashes::{hex::FromHex, sha256}, util::uint::Uint256, Block, BlockHash, BlockHeader, OutPoint, Transaction, TxOut, }; @@ -23,7 +24,6 @@ use core::ffi::c_uint; use futures::executor::block_on; use log::{info, trace}; use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof, stump::Stump}; -use sha2::{Digest, Sha512_256}; use spin::RwLock; pub struct ChainStateInner { @@ -45,8 +45,8 @@ pub struct ChainStateInner { fee_estimation: (f64, f64, f64), /// Are we in Initial Block Download? ibd: bool, - /// Global parameter of the chain we are in - chain_params: ChainParams, + /// Parameters for the chain and functions that verify the chain. + consensus: Consensus, /// Assume valid is a Core-specific config that tells the node to not validate signatures /// in blocks before this one. Note that we only skip signature validation, everything else /// is still validated. @@ -57,34 +57,6 @@ pub struct ChainState { } impl ChainState { - // TODO: Move to LeafData - pub fn get_leaf_hashes( - transaction: &Transaction, - vout: u32, - height: u32, - block_hash: BlockHash, - ) -> sha256::Hash { - let header_code = height << 1; - - let mut ser_utxo = Vec::new(); - let utxo = transaction.output.get(vout as usize).unwrap(); - utxo.consensus_encode(&mut ser_utxo).unwrap(); - let header_code = if transaction.is_coin_base() { - header_code | 1 - } else { - header_code - }; - - let leaf_hash = Sha512_256::new() - .chain_update(block_hash) - .chain_update(transaction.txid()) - .chain_update(vout.to_le_bytes()) - .chain_update(header_code.to_le_bytes()) - .chain_update(ser_utxo) - .finalize(); - sha256::Hash::from_slice(leaf_hash.as_slice()) - .expect("parent_hash: Engines shouldn't be Err") - } fn maybe_reindex(&self, potential_tip: &DiskBlockHeader) { match potential_tip { DiskBlockHeader::HeadersOnly(_, height) => { @@ -101,7 +73,7 @@ impl ChainState { } /// Returns the validation flags, given the current block height fn get_validation_flags(&self, height: u32) -> c_uint { - let chains_params = read_lock!(self).chain_params.clone(); + let chains_params = &read_lock!(self).consensus.parameters; let hash = read_lock!(self) .chainstore .get_block_hash(height) @@ -135,19 +107,7 @@ impl ChainState { } flags } - /// Returns the amount of block subsidy to be paid in a block, given it's height. - /// Bitcoin Core source: https://github.com/bitcoin/bitcoin/blob/2b211b41e36f914b8d0487e698b619039cc3c8e2/src/validation.cpp#L1501-L1512 - fn get_subsidy(&self, height: u32) -> u64 { - let halvings = height / read_lock!(self).chain_params.subsidy_halving_interval as u32; - // Force block reward to zero when right shift is undefined. - if halvings >= 64 { - return 0; - } - let mut subsidy = 50 * COIN_VALUE; - // Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years. - subsidy >>= halvings; - subsidy - } + fn update_header(&self, header: &DiskBlockHeader) -> Result<(), BlockchainError> { Ok(read_lock!(self).chainstore.save_header(header)?) } @@ -172,63 +132,7 @@ impl ChainState { })?; Ok(block_hash) } - fn verify_block_transactions( - mut utxos: HashMap, - transactions: &[Transaction], - subsidy: u64, - verify_script: bool, - flags: c_uint, - ) -> Result { - // Blocks must contain at least one transaction - if transactions.is_empty() { - return Err(BlockValidationErrors::EmptyBlock.into()); - } - let mut fee = 0; - // Skip the coinbase tx - for (n, transaction) in transactions.iter().enumerate() { - // We don't need to verify the coinbase inputs, as it spends newly generated coins - if transaction.is_coin_base() { - if n == 0 { - continue; - } - // A block must contain only one coinbase, and it should be the fist thing inside it - return Err(BlockValidationErrors::FirstTxIsnNotCoinbase.into()); - } - // Amount of all outputs - let output_value = transaction.output.iter().fold(0, |acc, tx| acc + tx.value); - // Amount of all inputs - let in_value = transaction.input.iter().fold(0, |acc, input| { - acc + utxos - .get(&input.previous_output) - .expect("We have all prevouts here") - .value - }); - // Value in should be greater or equal to value out. Otherwise, inflation. - if output_value > in_value { - return Err(BlockValidationErrors::NotEnoughMoney.into()); - } - // Fee is the difference between inputs and outputs - fee += in_value - output_value; - // Verify the tx script - if verify_script { - transaction.verify_with_flags(|outpoint| utxos.remove(outpoint), flags)?; - } - } - // In each block, the first transaction, and only the first, should be coinbase - if !transactions[0].is_coin_base() { - return Err(BlockValidationErrors::FirstTxIsnNotCoinbase.into()); - } - // Checks if the miner isn't trying to create inflation - if fee + subsidy - < transactions[0] - .output - .iter() - .fold(0, |acc, out| acc + out.value) - { - return Err(BlockValidationErrors::BadCoinbaseOutValue.into()); - } - Ok(true) - } + #[inline] /// Whether a node is the genesis block for this net fn is_genesis(&self, header: &BlockHeader) -> bool { @@ -463,66 +367,12 @@ impl ChainState { .push(branch_tip.block_hash()); Ok(()) } - /// Calculates the next target for the proof of work algorithm, given the - /// current target and the time it took to mine the last 2016 blocks. - fn calc_next_work_required( - last_block: &BlockHeader, - first_block: &BlockHeader, - params: ChainParams, - ) -> u32 { - let cur_target = last_block.target(); - - let expected_timespan = Uint256::from_u64(params.pow_target_timespan).unwrap(); - let mut actual_timespan = last_block.time - first_block.time; - // Difficulty adjustments are limited, to prevent large swings in difficulty - // caused by malicious miners. - if actual_timespan < params.pow_target_timespan as u32 / 4 { - actual_timespan = params.pow_target_timespan as u32 / 4; - } - if actual_timespan > params.pow_target_timespan as u32 * 4 { - actual_timespan = params.pow_target_timespan as u32 * 4; - } - let new_target = cur_target.mul_u32(actual_timespan); - let new_target = new_target / expected_timespan; - BlockHeader::compact_target_from_u256(&new_target) - } - /// Returns the next required work for the next block, usually it's just the last block's target - /// but if we are in a retarget period, it's calculated from the last 2016 blocks. - fn get_next_required_work( - &self, - last_block: &BlockHeader, - next_height: u32, - next_header: &BlockHeader, - ) -> Uint256 { - let params: ChainParams = self.chain_params(); - // Special testnet rule, if a block takes more than 20 minutes to mine, we can - // mine a block with diff 1 - if params.pow_allow_min_diff - && last_block.time + params.pow_target_spacing as u32 * 2 < next_header.time - { - return params.max_target; - } - // Regtest don't have retarget - if !params.pow_allow_no_retarget && (next_height) % 2016 == 0 { - // First block in this epoch - let first_block = self.get_block_header_by_height(next_height - 2016); - let last_block = self.get_block_header_by_height(next_height - 1); - - let next_bits = - Self::calc_next_work_required(&last_block, &first_block, self.chain_params()); - let target = BlockHeader::u256_from_compact_target(next_bits); - if target < params.max_target { - return target; - } - return params.max_target; - } - last_block.target() - } /// Returns the chain_params struct for the current network fn chain_params(&self) -> ChainParams { let inner = read_lock!(self); - inner.chain_params.clone() + // We clone the parameters here, because we don't want to hold the lock for too long + inner.consensus.parameters.clone() } // This function should be only called if a block is guaranteed to be on chain fn get_block_header_by_height(&self, height: u32) -> BlockHeader { @@ -532,53 +382,6 @@ impl ChainState { self.get_block_header(&block) .expect("This block should also be present") } - fn update_acc( - acc: &Stump, - block: &Block, - height: u32, - proof: Proof, - del_hashes: Vec, - ) -> Result { - let block_hash = block.block_hash(); - let mut leaf_hashes = Vec::new(); - let del_hashes = del_hashes - .iter() - .map(|hash| NodeHash::from(hash.into_inner())) - .collect::>(); - - if !proof.verify(&del_hashes, acc)? { - return Err(BlockchainError::InvalidProof); - } - let mut block_inputs = HashSet::new(); - for transaction in block.txdata.iter() { - for input in transaction.input.iter() { - block_inputs.insert((input.previous_output.txid, input.previous_output.vout)); - } - } - - for transaction in block.txdata.iter() { - for (i, output) in transaction.output.iter().enumerate() { - if !output.script_pubkey.is_provably_unspendable() - && !block_inputs.contains(&(transaction.txid(), i as u32)) - { - leaf_hashes.push(Self::get_leaf_hashes( - transaction, - i as u32, - height, - block_hash, - )) - } - } - } - let hashes: Vec = leaf_hashes - .iter() - .map(|&hash| NodeHash::from(hash.into_inner())) - .collect(); - let acc = acc.modify(&hashes, &del_hashes, &proof)?.0; - - Ok(acc) - } - fn save_acc(&self) -> Result<(), bitcoin::consensus::encode::Error> { let inner = read_lock!(self); let mut ser_acc: Vec = Vec::new(); @@ -639,7 +442,9 @@ impl ChainState { subscribers: Vec::new(), fee_estimation: (1_f64, 1_f64, 1_f64), ibd: true, - chain_params: network.into(), + consensus: Consensus { + parameters: network.into(), + }, assume_valid: (assume_valid_hash, 0), }), } @@ -718,7 +523,9 @@ impl ChainState { fee_estimation: (1_f64, 1_f64, 1_f64), subscribers: Vec::new(), ibd: true, - chain_params: network.into(), + consensus: Consensus { + parameters: network.into(), + }, assume_valid: (Self::get_assume_valid_value(network, assume_valid_hash), 0), }; info!( @@ -790,6 +597,38 @@ impl ChainState { fn acc(&self) -> Stump { read_lock!(self).acc.to_owned() } + /// Returns the next required work for the next block, usually it's just the last block's target + /// but if we are in a retarget period, it's calculated from the last 2016 blocks. + fn get_next_required_work( + &self, + last_block: &BlockHeader, + next_height: u32, + next_header: &BlockHeader, + ) -> Uint256 { + let params: ChainParams = self.chain_params(); + // Special testnet rule, if a block takes more than 20 minutes to mine, we can + // mine a block with diff 1 + if params.pow_allow_min_diff + && last_block.time + params.pow_target_spacing as u32 * 2 < next_header.time + { + return params.max_target; + } + // Regtest don't have retarget + if !params.pow_allow_no_retarget && (next_height) % 2016 == 0 { + // First block in this epoch + let first_block = self.get_block_header_by_height(next_height - 2016); + let last_block = self.get_block_header_by_height(next_height - 1); + + let next_bits = + Consensus::calc_next_work_required(&last_block, &first_block, self.chain_params()); + let target = BlockHeader::u256_from_compact_target(next_bits); + if target < params.max_target { + return target; + } + return params.max_target; + } + last_block.target() + } fn validate_block( &self, block: &Block, @@ -820,10 +659,10 @@ impl ChainState { )); } // Validate block transactions - let subsidy = self.get_subsidy(height); + let subsidy = read_lock!(self).consensus.get_subsidy(height); let verify_script = self.verify_script(height); let flags = self.get_validation_flags(height); - Self::verify_block_transactions(inputs, &block.txdata, subsidy, verify_script, flags) + Consensus::verify_block_transactions(inputs, &block.txdata, subsidy, verify_script, flags) .map_err(|_| BlockchainError::BlockValidationError(BlockValidationErrors::InvalidTx))?; Ok(()) } @@ -1011,7 +850,7 @@ impl UpdatableChainstate for ChainState height, }; self.validate_block(block, height, inputs)?; - let acc = Self::update_acc(&self.acc(), block, height, proof, del_hashes)?; + let acc = Consensus::update_acc(&self.acc(), block, height, proof, del_hashes)?; let ibd = self.is_in_idb(); // ... If we came this far, we consider this block valid ... if ibd && height % 10_000 == 0 { @@ -1098,7 +937,9 @@ impl From> for ChainState { broadcast_queue: Vec::new(), subscribers: Vec::new(), fee_estimation: (1_f64, 1_f64, 1_f64), - chain_params: builder.chain_params(), + consensus: Consensus { + parameters: builder.chain_params(), + }, }; let inner = RwLock::new(inner); @@ -1208,8 +1049,8 @@ impl Decodable for BestChain { #[cfg(test)] mod test { extern crate std; - use crate::prelude::HashMap; use crate::Network; + use crate::{prelude::HashMap, pruned_utreexo::consensus::Consensus}; use bitcoin::{ consensus::{deserialize, Decodable}, hashes::hex::FromHex, @@ -1260,7 +1101,7 @@ mod test { let last_block = Vec::from_hex("00000020dec6741f7dc5df6661bcb2d3ec2fceb14bd0e6def3db80da904ed1eeb8000000d1f308132e6a72852c04b059e92928ea891ae6d513cd3e67436f908c804ec7be51df535fae77031e4d00f800").unwrap(); let last_block = deserialize(&last_block).unwrap(); - let next_target = super::ChainState::::calc_next_work_required( + let next_target = Consensus::calc_next_work_required( &last_block, &first_block, ChainParams::from(Network::Bitcoin), diff --git a/crates/floresta-chain/src/pruned_utreexo/consensus.rs b/crates/floresta-chain/src/pruned_utreexo/consensus.rs new file mode 100644 index 00000000..76ae7a64 --- /dev/null +++ b/crates/floresta-chain/src/pruned_utreexo/consensus.rs @@ -0,0 +1,195 @@ +//! A collection of functions that implement the consensus rules for the Bitcoin Network. +//! This module contains functions that are used to verify blocks and transactions, and doesn't +//! assume anything about the chainstate, so it can be used in any context. +//! We use this to avoid code reuse among the different implementations of the chainstate. + +use core::ffi::c_uint; + +use floresta_common::prelude::*; + +use bitcoin::{ + blockdata::constants::COIN_VALUE, + consensus::Encodable, + hashes::{sha256, Hash}, + util::uint::Uint256, + Block, BlockHash, BlockHeader, OutPoint, Transaction, TxOut, +}; +use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof, stump::Stump}; +use sha2::{Digest, Sha512_256}; + +use crate::{BlockValidationErrors, BlockchainError, ChainParams}; + +pub struct Consensus { + pub parameters: ChainParams, +} + +impl Consensus { + /// Returns the amount of block subsidy to be paid in a block, given it's height. + /// Bitcoin Core source: https://github.com/bitcoin/bitcoin/blob/2b211b41e36f914b8d0487e698b619039cc3c8e2/src/validation.cpp#L1501-L1512 + pub fn get_subsidy(&self, height: u32) -> u64 { + let halvings = height / self.parameters.subsidy_halving_interval as u32; + // Force block reward to zero when right shift is undefined. + if halvings >= 64 { + return 0; + } + let mut subsidy = 50 * COIN_VALUE; + // Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years. + subsidy >>= halvings; + subsidy + } + fn get_leaf_hashes( + transaction: &Transaction, + vout: u32, + height: u32, + block_hash: BlockHash, + ) -> sha256::Hash { + let header_code = height << 1; + + let mut ser_utxo = Vec::new(); + let utxo = transaction.output.get(vout as usize).unwrap(); + utxo.consensus_encode(&mut ser_utxo).unwrap(); + let header_code = if transaction.is_coin_base() { + header_code | 1 + } else { + header_code + }; + + let leaf_hash = Sha512_256::new() + .chain_update(block_hash) + .chain_update(transaction.txid()) + .chain_update(vout.to_le_bytes()) + .chain_update(header_code.to_le_bytes()) + .chain_update(ser_utxo) + .finalize(); + sha256::Hash::from_slice(leaf_hash.as_slice()) + .expect("parent_hash: Engines shouldn't be Err") + } + pub fn verify_block_transactions( + mut utxos: HashMap, + transactions: &[Transaction], + subsidy: u64, + verify_script: bool, + flags: c_uint, + ) -> Result { + // Blocks must contain at least one transaction + if transactions.is_empty() { + return Err(BlockValidationErrors::EmptyBlock.into()); + } + let mut fee = 0; + // Skip the coinbase tx + for (n, transaction) in transactions.iter().enumerate() { + // We don't need to verify the coinbase inputs, as it spends newly generated coins + if transaction.is_coin_base() { + if n == 0 { + continue; + } + // A block must contain only one coinbase, and it should be the fist thing inside it + return Err(BlockValidationErrors::FirstTxIsnNotCoinbase.into()); + } + // Amount of all outputs + let output_value = transaction.output.iter().fold(0, |acc, tx| acc + tx.value); + // Amount of all inputs + let in_value = transaction.input.iter().fold(0, |acc, input| { + acc + utxos + .get(&input.previous_output) + .expect("We have all prevouts here") + .value + }); + // Value in should be greater or equal to value out. Otherwise, inflation. + if output_value > in_value { + return Err(BlockValidationErrors::NotEnoughMoney.into()); + } + // Fee is the difference between inputs and outputs + fee += in_value - output_value; + // Verify the tx script + if verify_script { + transaction.verify_with_flags(|outpoint| utxos.remove(outpoint), flags)?; + } + } + // In each block, the first transaction, and only the first, should be coinbase + if !transactions[0].is_coin_base() { + return Err(BlockValidationErrors::FirstTxIsnNotCoinbase.into()); + } + // Checks if the miner isn't trying to create inflation + if fee + subsidy + < transactions[0] + .output + .iter() + .fold(0, |acc, out| acc + out.value) + { + return Err(BlockValidationErrors::BadCoinbaseOutValue.into()); + } + Ok(true) + } + /// Calculates the next target for the proof of work algorithm, given the + /// current target and the time it took to mine the last 2016 blocks. + pub fn calc_next_work_required( + last_block: &BlockHeader, + first_block: &BlockHeader, + params: ChainParams, + ) -> u32 { + let cur_target = last_block.target(); + + let expected_timespan = Uint256::from_u64(params.pow_target_timespan).unwrap(); + let mut actual_timespan = last_block.time - first_block.time; + // Difficulty adjustments are limited, to prevent large swings in difficulty + // caused by malicious miners. + if actual_timespan < params.pow_target_timespan as u32 / 4 { + actual_timespan = params.pow_target_timespan as u32 / 4; + } + if actual_timespan > params.pow_target_timespan as u32 * 4 { + actual_timespan = params.pow_target_timespan as u32 * 4; + } + let new_target = cur_target.mul_u32(actual_timespan); + let new_target = new_target / expected_timespan; + + BlockHeader::compact_target_from_u256(&new_target) + } + + pub fn update_acc( + acc: &Stump, + block: &Block, + height: u32, + proof: Proof, + del_hashes: Vec, + ) -> Result { + let block_hash = block.block_hash(); + let mut leaf_hashes = Vec::new(); + let del_hashes = del_hashes + .iter() + .map(|hash| NodeHash::from(hash.into_inner())) + .collect::>(); + + if !proof.verify(&del_hashes, acc)? { + return Err(BlockchainError::InvalidProof); + } + let mut block_inputs = HashSet::new(); + for transaction in block.txdata.iter() { + for input in transaction.input.iter() { + block_inputs.insert((input.previous_output.txid, input.previous_output.vout)); + } + } + + for transaction in block.txdata.iter() { + for (i, output) in transaction.output.iter().enumerate() { + if !output.script_pubkey.is_provably_unspendable() + && !block_inputs.contains(&(transaction.txid(), i as u32)) + { + leaf_hashes.push(Self::get_leaf_hashes( + transaction, + i as u32, + height, + block_hash, + )) + } + } + } + let hashes: Vec = leaf_hashes + .iter() + .map(|&hash| NodeHash::from(hash.into_inner())) + .collect(); + let acc = acc.modify(&hashes, &del_hashes, &proof)?.0; + + Ok(acc) + } +} diff --git a/crates/floresta-chain/src/pruned_utreexo/mod.rs b/crates/floresta-chain/src/pruned_utreexo/mod.rs index 67266793..4c1434a9 100644 --- a/crates/floresta-chain/src/pruned_utreexo/mod.rs +++ b/crates/floresta-chain/src/pruned_utreexo/mod.rs @@ -3,6 +3,7 @@ pub mod chain_state; pub mod chain_state_builder; pub mod chainparams; pub mod chainstore; +pub mod consensus; pub mod error; pub mod udata; From 0f4e2697821eb2ae42c65ed64689b74ca8caedea Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:49:08 -0300 Subject: [PATCH 005/328] Rate limit peers (#66) --- crates/floresta-wire/src/p2p_wire/peer.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/floresta-wire/src/p2p_wire/peer.rs b/crates/floresta-wire/src/p2p_wire/peer.rs index 8829c832..f143bd88 100644 --- a/crates/floresta-wire/src/p2p_wire/peer.rs +++ b/crates/floresta-wire/src/p2p_wire/peer.rs @@ -27,7 +27,7 @@ use bitcoin::{ BlockHash, BlockHeader, Network, Transaction, }; use futures::FutureExt; -use log::debug; +use log::{debug, error}; use std::{ fmt::Debug, sync::Arc, @@ -49,6 +49,8 @@ pub struct Peer { blocks_only: bool, services: ServiceFlags, user_agent: String, + messages: u64, + start_time: Instant, current_best_block: i32, last_ping: Instant, id: u32, @@ -74,6 +76,8 @@ pub enum PeerError { MessageTooBig, #[error("Peer sent us a message with the wrong magic bits")] MagicBitsMismatch, + #[error("Peer sent us too many message in a short period of time")] + TooManyMessages, } impl Debug for Peer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -110,12 +114,24 @@ impl Peer { self.handle_node_request(request).await?; } } - peer_request = async_std::future::timeout(Duration::from_secs(1), rx.recv()).fuse() => { + peer_request = async_std::future::timeout(Duration::from_secs(10), rx.recv()).fuse() => { if let Ok(Ok(peer_request)) = peer_request { self.handle_peer_message(peer_request?).await?; } } }; + // divide the number of messages by the number of seconds we've been connected, + // if it's more than 100 msg/sec, this peer is sending us too many messages, and we should + // disconnect. + if self.messages > 0 + && self.messages / Instant::now().duration_since(self.start_time).as_secs() > 10 + { + error!( + "Peer {} is sending us too many messages, disconnecting", + self.id + ); + return Err(PeerError::TooManyMessages); + } if let State::SentVersion(when) = self.state { if Instant::now().duration_since(when) > Duration::from_secs(10) { return Err(PeerError::UnexpectedMessage); @@ -315,6 +331,8 @@ impl Peer { node_tx, services: ServiceFlags::NONE, stream, + messages: 0, + start_time: Instant::now(), user_agent: "".into(), state: State::None, send_headers: false, From 707b95fbcf6d86777a131f9b84fb09081dea170f Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:56:05 -0300 Subject: [PATCH 006/328] Insert partial chains (#63) Partial chains are simpler chainstates we use to perform IBD in parallel Each partial chain will take a range of blocks and validate it to completion. Afterwards, we check if they all match up and continue with the regular full chainstate --- Cargo.lock | 7 + crates/floresta-chain/Cargo.toml | 1 + .../src/pruned_utreexo/chain_state.rs | 2 +- .../src/pruned_utreexo/consensus.rs | 39 +- .../src/pruned_utreexo/error.rs | 4 +- .../floresta-chain/src/pruned_utreexo/mod.rs | 1 + .../src/pruned_utreexo/partial_chain.rs | 387 ++++++++++++++++++ .../src/pruned_utreexo/testdata/blocks.txt | 151 +++++++ 8 files changed, 582 insertions(+), 10 deletions(-) create mode 100644 crates/floresta-chain/src/pruned_utreexo/partial_chain.rs create mode 100644 crates/floresta-chain/src/pruned_utreexo/testdata/blocks.txt diff --git a/Cargo.lock b/Cargo.lock index d8d986d6..4676180d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -755,6 +755,7 @@ dependencies = [ "floresta-common", "futures 0.3.28", "hashbrown 0.14.0", + "hex", "kv", "log", "pretty_assertions", @@ -1198,6 +1199,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex_lit" version = "0.1.1" diff --git a/crates/floresta-chain/Cargo.toml b/crates/floresta-chain/Cargo.toml index dc1b9136..84f6b081 100644 --- a/crates/floresta-chain/Cargo.toml +++ b/crates/floresta-chain/Cargo.toml @@ -34,6 +34,7 @@ rand = "0.8.5" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" zstd = "0.12.3" +hex = "0.4.3" [features] no-std = ["hashbrown", "core2"] diff --git a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs index 98775ae5..af76475b 100644 --- a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs @@ -594,7 +594,7 @@ impl ChainState { let inner = self.inner.read(); inner.assume_valid.1 < height } - fn acc(&self) -> Stump { + pub fn acc(&self) -> Stump { read_lock!(self).acc.to_owned() } /// Returns the next required work for the next block, usually it's just the last block's target diff --git a/crates/floresta-chain/src/pruned_utreexo/consensus.rs b/crates/floresta-chain/src/pruned_utreexo/consensus.rs index 76ae7a64..74177d57 100644 --- a/crates/floresta-chain/src/pruned_utreexo/consensus.rs +++ b/crates/floresta-chain/src/pruned_utreexo/consensus.rs @@ -3,8 +3,6 @@ //! assume anything about the chainstate, so it can be used in any context. //! We use this to avoid code reuse among the different implementations of the chainstate. -use core::ffi::c_uint; - use floresta_common::prelude::*; use bitcoin::{ @@ -14,12 +12,17 @@ use bitcoin::{ util::uint::Uint256, Block, BlockHash, BlockHeader, OutPoint, Transaction, TxOut, }; +use core::ffi::c_uint; use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof, stump::Stump}; use sha2::{Digest, Sha512_256}; use crate::{BlockValidationErrors, BlockchainError, ChainParams}; - +/// This struct contains all the information and methods needed to validate a block, +/// it is used by the [ChainState] to validate blocks and transactions. +#[derive(Debug, Clone)] pub struct Consensus { + /// The parameters of the chain we are validating, it is usually hardcoded + /// constants. See [ChainParams] for more information. pub parameters: ChainParams, } @@ -37,6 +40,9 @@ impl Consensus { subsidy >>= halvings; subsidy } + + /// Returns the hash of a leaf node in the utreexo accumulator. + #[inline] fn get_leaf_hashes( transaction: &Transaction, vout: u32, @@ -64,6 +70,16 @@ impl Consensus { sha256::Hash::from_slice(leaf_hash.as_slice()) .expect("parent_hash: Engines shouldn't be Err") } + /// Verify if all transactions in a block are valid. Here we check the following: + /// - The block must contain at least one transaction, and this transaction must be coinbase + /// - The first transaction in the block must be coinbase + /// - The coinbase transaction must have the correct value (subsidy + fees) + /// - The block must not create more coins than allowed + /// - All transactions must be valid: + /// - The transaction must not be coinbase (already checked) + /// - The transaction must not have duplicate inputs + /// - The transaction must not spend more coins than it claims in the inputs + /// - The transaction must have valid scripts pub fn verify_block_transactions( mut utxos: HashMap, transactions: &[Transaction], @@ -145,7 +161,11 @@ impl Consensus { BlockHeader::compact_target_from_u256(&new_target) } - + /// Updates our accumulator with the new block. This is done by calculating the new + /// root hash of the accumulator, and then verifying the proof of inclusion of the + /// deleted nodes. If the proof is valid, we return the new accumulator. Otherwise, + /// we return an error. + /// This function is pure, it doesn't modify the accumulator, but returns a new one. pub fn update_acc( acc: &Stump, block: &Block, @@ -159,17 +179,19 @@ impl Consensus { .iter() .map(|hash| NodeHash::from(hash.into_inner())) .collect::>(); - + // Verify the proof of inclusion of the deleted nodes if !proof.verify(&del_hashes, acc)? { - return Err(BlockchainError::InvalidProof); + return Err(BlockValidationErrors::InvalidProof.into()); } + // Get inputs from the block, we'll need this HashSet to check if an output is spent + // in the same block. If it is, we don't need to add it to the accumulator. let mut block_inputs = HashSet::new(); for transaction in block.txdata.iter() { for input in transaction.input.iter() { block_inputs.insert((input.previous_output.txid, input.previous_output.vout)); } } - + // Get all leaf hashes that will be added to the accumulator for transaction in block.txdata.iter() { for (i, output) in transaction.output.iter().enumerate() { if !output.script_pubkey.is_provably_unspendable() @@ -184,12 +206,13 @@ impl Consensus { } } } + // Convert the leaf hashes to NodeHashes used in Rustreexo let hashes: Vec = leaf_hashes .iter() .map(|&hash| NodeHash::from(hash.into_inner())) .collect(); + // Update the accumulator let acc = acc.modify(&hashes, &del_hashes, &proof)?.0; - Ok(acc) } } diff --git a/crates/floresta-chain/src/pruned_utreexo/error.rs b/crates/floresta-chain/src/pruned_utreexo/error.rs index f6333527..ad12a79a 100644 --- a/crates/floresta-chain/src/pruned_utreexo/error.rs +++ b/crates/floresta-chain/src/pruned_utreexo/error.rs @@ -22,7 +22,7 @@ pub enum BlockchainError { IoError(ioError), } -#[derive(Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum BlockValidationErrors { InvalidTx, NotEnoughPow, @@ -34,6 +34,7 @@ pub enum BlockValidationErrors { EmptyBlock, BlockExtendsAnOrphanChain, BadBip34, + InvalidProof, } impl Display for BlockValidationErrors { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -62,6 +63,7 @@ impl Display for BlockValidationErrors { write!(f, "This block extends a chain we don't have the ancestors") } BlockValidationErrors::BadBip34 => write!(f, "BIP34 commitment mismatch"), + BlockValidationErrors::InvalidProof => write!(f, "Invalid proof"), } } } diff --git a/crates/floresta-chain/src/pruned_utreexo/mod.rs b/crates/floresta-chain/src/pruned_utreexo/mod.rs index 4c1434a9..3ea2471d 100644 --- a/crates/floresta-chain/src/pruned_utreexo/mod.rs +++ b/crates/floresta-chain/src/pruned_utreexo/mod.rs @@ -5,6 +5,7 @@ pub mod chainparams; pub mod chainstore; pub mod consensus; pub mod error; +pub mod partial_chain; pub mod udata; use crate::prelude::*; diff --git a/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs b/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs new file mode 100644 index 00000000..81588eaf --- /dev/null +++ b/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs @@ -0,0 +1,387 @@ +//! A partial chain is a chain that only contains a subset of the blocks in the +//! full chain. We use multiple partial chains to sync up with the full chain, +//! and then merge them together to get the full chain. This allows us to conduct +//! the sync in parallel. + +use floresta_common::prelude::*; + +use super::consensus::Consensus; +use bitcoin::{bitcoinconsensus, BlockHeader}; +use core::ffi::c_uint; +use log::info; +use rustreexo::accumulator::stump::Stump; + +use crate::{BlockValidationErrors, BlockchainError, ChainParams}; + +/// A partial chain is a chain that only contains a subset of the blocks in the +/// full chain. We use multiple partial chains to sync up with the full chain, +/// and then merge them together to get the full chain. This allows us to conduct +/// the sync in parallel. To build one, we need to know the initial +/// height, the final height, and the block headers in between. +pub struct PartialChainState { + /// The current accumulator state, it starts with a hardcoded value and + /// gets checked against the result of the previous partial chainstate. + current_acc: Stump, + /// The block headers in this interval, we need this to verify the blocks + /// and to build the accumulator. We assume this is sorted by height, and + /// should contains all blocks in this interval. + blocks: Vec, + /// The height this interval starts at. This [initial_height, final_height), so + /// if we break the interval at height 100, the first interval will be [0, 100) + /// and the second interval will be [100, 200). And the initial height of the + /// second interval will be 99. + initial_height: u32, + /// The height we are on right now, this is used to keep track of the progress + /// of the sync. + current_height: u32, + /// The height we are syncing up to, trying to push more blocks than this will + /// result in an error. + final_height: u32, + /// The error that occurred during validation, if any. It is here so we can + /// pull that afterwords. + error: Option, + /// The consensus parameters, we need this to validate the blocks. + consensus: Consensus, + /// Whether we assume the signatures in this interval as valid, this is used to + /// speed up syncing, by assuming signatures in old blocks are valid. + assume_valid: bool, +} +impl PartialChainState { + /// Returns the height we started syncing from + pub fn initial_height(&self) -> u32 { + self.initial_height + } + /// Is this interval valid? + pub fn is_valid(&self) -> bool { + self.is_sync() && self.error.is_none() + } + /// Returns the validation error, if any + pub fn error(&self) -> Option { + self.error + } + /// Returns the height we have synced up to so far + pub fn current_height(&self) -> u32 { + self.current_height + } + /// Whether or not we have synced up to the final height + pub fn is_sync(&self) -> bool { + self.current_height == self.final_height + } + pub fn get_block(&self, height: u32) -> Option<&BlockHeader> { + let index = height - self.initial_height; + self.blocks.get(index as usize) + } + /// Returns the validation flags, given the current block height + fn get_validation_flags(&self, height: u32) -> c_uint { + let chains_params = &self.consensus.parameters; + let hash = self.get_block(height).unwrap().block_hash(); + if let Some(flag) = chains_params.exceptions.get(&hash) { + return *flag; + } + // From Bitcoin Core: + // BIP16 didn't become active until Apr 1 2012 (on mainnet, and + // retroactively applied to testnet) + // However, only one historical block violated the P2SH rules (on both + // mainnet and testnet). + // Similarly, only one historical block violated the TAPROOT rules on + // mainnet. + // For simplicity, always leave P2SH+WITNESS+TAPROOT on except for the two + // violating blocks. + let mut flags = bitcoinconsensus::VERIFY_P2SH | bitcoinconsensus::VERIFY_WITNESS; + + if height >= chains_params.bip65_activation_height { + flags |= bitcoinconsensus::VERIFY_CHECKLOCKTIMEVERIFY; + } + if height >= chains_params.bip66_activation_height { + flags |= bitcoinconsensus::VERIFY_DERSIG; + } + if height >= chains_params.csv_activation_height { + flags |= bitcoinconsensus::VERIFY_CHECKSEQUENCEVERIFY; + } + if height >= chains_params.segwit_activation_height { + flags |= bitcoinconsensus::VERIFY_NULLDUMMY; + } + flags + } + + #[inline] + /// Update our internal state, given a new height and accumulator + fn update_state(&mut self, height: u32, acc: Stump) { + self.current_height = height; + self.current_acc = acc; + } + #[inline] + /// Returns the parameters for this chain + fn chain_params(&self) -> ChainParams { + self.consensus.parameters.clone() + } + #[inline] + /// Returns the ancestor for a given block header + fn get_ancestor(&self, height: u32) -> super::Result { + let prev = self.get_block(height - 1).unwrap(); + Ok(*prev) + } + /// Process a block, given the proof, inputs, and deleted hashes. If we find an error, + /// we save it. + pub fn process_block( + &mut self, + block: &bitcoin::Block, + proof: rustreexo::accumulator::proof::Proof, + inputs: HashMap, + del_hashes: Vec, + ) -> bool { + let height = self.current_height + 1; + if let Err(BlockchainError::BlockValidationError(e)) = + self.validate_block(block, height, inputs) + { + self.error = Some(e); + return false; + } + let acc = match Consensus::update_acc(&self.current_acc, block, height, proof, del_hashes) { + Ok(acc) => acc, + Err(_) => { + self.error = Some(BlockValidationErrors::InvalidProof); + return false; + } + }; + + // ... If we came this far, we consider this block valid ... + + if height % 10_000 == 0 { + info!( + "Downloading blocks: height={height} hash={}", + block.block_hash() + ); + } + self.update_state(height, acc); + + true + } + /// Is the current accumulator what we expect? + pub fn is_expected(&self, acc: Stump) -> bool { + self.current_acc == acc + } + /// Check whether a block is valid + fn validate_block( + &self, + block: &bitcoin::Block, + height: u32, + inputs: HashMap, + ) -> super::Result<()> { + if !block.check_merkle_root() { + return Err(BlockchainError::BlockValidationError( + BlockValidationErrors::BadMerkleRoot, + )); + } + if height >= self.chain_params().bip34_activation_height + && block.bip34_block_height() != Ok(height as u64) + { + return Err(BlockchainError::BlockValidationError( + BlockValidationErrors::BadBip34, + )); + } + if !block.check_witness_commitment() { + return Err(BlockchainError::BlockValidationError( + BlockValidationErrors::BadWitnessCommitment, + )); + } + let prev_block = self.get_ancestor(height)?; + if block.header.prev_blockhash != prev_block.block_hash() { + return Err(BlockchainError::BlockValidationError( + BlockValidationErrors::BlockExtendsAnOrphanChain, + )); + } + // Validate block transactions + let subsidy = self.consensus.get_subsidy(height); + let verify_script = self.assume_valid; + let flags = self.get_validation_flags(height); + let valid = Consensus::verify_block_transactions( + inputs, + &block.txdata, + subsidy, + verify_script, + flags, + )?; + if !valid { + return Err(BlockchainError::BlockValidationError( + BlockValidationErrors::InvalidTx, + )); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use core::str::FromStr; + use std::collections::HashMap; + + use crate::{BlockValidationErrors, Network}; + use bitcoin::{consensus::deserialize, Block}; + use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof, stump::Stump}; + + use crate::{pruned_utreexo::consensus::Consensus, ChainParams}; + + use super::PartialChainState; + #[test] + fn test_with_invalid_block() { + fn run(block: &str, reason: BlockValidationErrors) { + let genesis = parse_block("0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"); + let block = parse_block(block); + + let mut chainstate = get_empty_pchain(vec![genesis.header, block.header]); + + assert!(!chainstate.process_block(&block, Proof::default(), HashMap::new(), vec![])); + assert!(!chainstate.is_valid()); + assert_eq!(chainstate.error, Some(reason)); + } + run("0000002000226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f39adbcd7823048d34357bdca86cd47172afe2a4af8366b5b34db36df89386d49b23ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165108feddb99c6b8435060b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000",super::BlockValidationErrors::BlockExtendsAnOrphanChain); + run("0000002000226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f40adbcd7823048d34357bdca86cd47172afe2a4af8366b5b34db36df89386d49b23ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165108feddb99c6b8435060b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000", BlockValidationErrors::BadMerkleRoot); + } + fn parse_block(hex: &str) -> Block { + let block = hex::decode(hex).unwrap(); + deserialize(&block).unwrap() + } + fn get_empty_pchain(blocks: Vec) -> PartialChainState { + PartialChainState { + assume_valid: true, + consensus: Consensus { + parameters: ChainParams::from(Network::Regtest), + }, + current_height: 0, + current_acc: Stump::default(), + final_height: 1, + blocks, + error: None, + initial_height: 0, + } + } + #[test] + fn test_updating_single_chain() { + let blocks = include_str!("./testdata/blocks.txt"); + let mut parsed_blocks = vec![]; + for (i, block) in blocks.lines().enumerate() { + if i > 100 { + break; + } + let block: Block = deserialize(&hex::decode(block).unwrap()).unwrap(); + parsed_blocks.push(block); + } + let mut chainstate = PartialChainState { + assume_valid: true, + consensus: Consensus { + parameters: ChainParams::from(Network::Regtest), + }, + current_height: 0, + current_acc: Stump::default(), + final_height: 100, + blocks: parsed_blocks.iter().map(|block| block.header).collect(), + error: None, + initial_height: 0, + }; + parsed_blocks.remove(0); + for block in parsed_blocks { + let proof = Proof::default(); + let inputs = HashMap::new(); + let del_hashes = vec![]; + chainstate.process_block(&block, proof, inputs, del_hashes); + } + assert_eq!(chainstate.current_height, 100); + assert!(chainstate.is_valid()); + } + #[test] + fn test_updating_multiple_chains() { + // We have two chains, one with 100 blocks, one with 50 blocks. We expect the + // accumulator to be what we expect after 100 blocks and after 150 blocks. + let blocks = include_str!("./testdata/blocks.txt"); + let mut parsed_blocks = vec![]; + for block in blocks.lines() { + let block: Block = deserialize(&hex::decode(block).unwrap()).unwrap(); + parsed_blocks.push(block); + } + // The file contains 150 blocks, we split them into two chains. + let (blocks1, blocks2) = parsed_blocks.split_at(101); + let mut chainstate1 = PartialChainState { + assume_valid: true, + consensus: Consensus { + parameters: ChainParams::from(Network::Regtest), + }, + current_height: 0, + current_acc: Stump::default(), + final_height: 100, + blocks: blocks1.iter().map(|block| block.header).collect(), + error: None, + initial_height: 0, + }; + // We need to add the last block of the first chain to the second chain, so that + // the second chain can validate all its blocks. + let mut blocks2_headers = vec![blocks1.last().unwrap()]; + blocks2_headers.extend(blocks2); + + let blocks2_headers = blocks2_headers.iter().map(|block| block.header).collect(); + + let mut blocks1 = blocks1.iter(); + blocks1.next(); + + for block in blocks1 { + let proof = Proof::default(); + let inputs = HashMap::new(); + let del_hashes = vec![]; + chainstate1.process_block(block, proof, inputs, del_hashes); + } + // The state after 100 blocks, computed ahead of time. + let roots = [ + "52a2ca409852acda9e194fbf2d8d9b10159bb4cee56248b3f3affdceaa294865", + "87b871d9c268eca95114929d619cf23399371248d350dc88e91ca33074341faf", + "480563375e4013e5ac1238fe0d85f8d997d93f626a0772ad76be6900f85b66c3", + ] + .iter() + .map(|hash| NodeHash::from_str(hash).unwrap()) + .collect(); + + let acc2 = Stump { roots, leaves: 100 }; + + // acc2 is hard-coded, while chainstate1.current_acc is calculated. + // after catching up in the first half, the accumulator should be the same. + // We can have the speedup of doing it in parallel, without needing to trust + // the hard-coded values. + assert_eq!(chainstate1.current_acc, acc2); + + let mut chainstate2 = PartialChainState { + assume_valid: true, + consensus: Consensus { + parameters: ChainParams::from(Network::Regtest), + }, + current_height: 100, + current_acc: acc2, + final_height: 150, + blocks: blocks2_headers, + error: None, + initial_height: 100, + }; + + for block in blocks2 { + let proof = Proof::default(); + let inputs = HashMap::new(); + let del_hashes = vec![]; + chainstate2.process_block(block, proof, inputs, del_hashes); + } + + let roots = [ + "f5fbbfca687f84f363a790ac26953bbc541e8864987a788083511c5dd5adae87", + "062221add29798889f62519b8b1de3ffb19b1349baea730a07aec8c6eaacb4e9", + "9f5cc264618d85b719a1738cdf3169355aac7033ff24f56361db34dfb9e6bbfd", + "350686755287afe78c33a27b187d83df1cd3c99ea93f4dd66781ecdedcc7d5b2", + ] + .iter() + .map(|x| NodeHash::try_from(hex::decode(x).unwrap().as_slice()).unwrap()) + .collect::>(); + + let expected_acc: Stump = Stump { leaves: 150, roots }; + + assert_eq!(chainstate2.current_height, 150); + assert_eq!(chainstate2.current_acc, expected_acc); + + assert!(chainstate2.is_valid()); + } +} diff --git a/crates/floresta-chain/src/pruned_utreexo/testdata/blocks.txt b/crates/floresta-chain/src/pruned_utreexo/testdata/blocks.txt new file mode 100644 index 00000000..a2bc644a --- /dev/null +++ b/crates/floresta-chain/src/pruned_utreexo/testdata/blocks.txt @@ -0,0 +1,151 @@ +0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000 +0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f39adbcd7823048d34357bdca86cd47172afe2a4af8366b5b34db36df89386d49b23ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165108feddb99c6b8435060b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020fb0842beb993386a5ed95a78e941895308f619bdfdb687a49f7f8dec5450ab43c184ed8e37dee24a5fc2bbd55dad079e155892e65a6aec235299d00e6aa6d621b63ec964ffff7f20040000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165208615899ffea248fdb0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002037489a1a101f632597e3d0f1fb037c040da8acb9de164192f154427d009ba8489e838f7745e964da7ca959b3aa38739d7b6d80caebd302f890102e1bdb6b9541b63ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165308dab66f64f49f10190b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000209b4eeca69d58dfb51789ba36a99219f4572dc322b142ca0f56d7f6a50f68f2271a65ad5c4369ab7cbdeca0b1362e74da6425097c26b8558860af9a5ab933e0a8b73ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165408b4d2ee615d3d9f070b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000209b361b7795b6ca2646b026f8c78416c028a2a221e887ca62ea34d5c9dba4365d1827fe322257e998a8f06cfefeac9b930e8af58e2c09d7e4dfc73362f27f06f0b73ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165508f05a67b3ec0b8e3f0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002009c1ec3db398b6653b70ec4f35447954f2761c1c2da32d4bb8358b72bc420856536f7cd221067c9195a7bec58edd29b0e6bd29418fc22da32d34affed6d8b0d5b73ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165608779f04157ee6b1500b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000204c0a5e2b33bd1cc24830b6e86105fe74b8e2b5e1e283ed546d941f08f013f65e25c8733b3a050ee58fdd72004ba0a83a69004619cf071047668e829111b53992b73ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1657082afba9ebe54680000b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020abd02ec9e91aa454cc45e1b144b93279f86b1b34ab015defe4a6bd305e12002cb3035e572973a0258769c949bde63e6a3f33e733c04fb5731a0af27a4fadd434b83ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165808db324172eba561ca0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020a053ea9f9b1b6c4396c714a31c80ff32722a71f9f2b91c303c22b05aa4605d1f568f2d30d413ee771c85e662b1aeadcd7b417c55fcbc5d386a4e72d203b8ab2cb83ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1659087c3e09f869f19fb60b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020589f05c666986a199a4a8b7ab2827d6f5f97a3c54e3cf85fd5ae8def29a90a63979f0cbd2ba9c296f1b49eb7fdd560f52d9efc318f724a9d2c98f525e1cef245b83ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165a08a193970d601034960b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002011e53792789fb97db53859a5b24c55fc2a4baab4236601a31d895c1e6d0d823fe89a03c39a43ffbfc76b3760616941f2371fd1c0f131ee26cee50b2a9efd7828b83ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165b08932881507a15c9410b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020181206e76c8c4b19e1fd9ad0ffd291c495fcf8376cad9de09ca1b83b1521f9037f1d0845eac1f2af7a2af257de9f122cec4d5a1490eab2a96d54ed80d4ff82e6b83ec964ffff7f20030000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165c08401f5f700d202cd90b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000206a82f4fdf6b0ef31209995a299120afc884aafbe4cdb0a82f3d8b5fbdf912d5deb9651cca05bde72001c8a417c1c18e0287678de3f09b90b5e22701f90d7efaab83ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165d08dc7e88b09dee461c0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020ad9ed0df775edec23307e24d1ad94122841f3a36d77e4ad06102140d475d1d7b1c4ed26b62777d62e3a48c1e4e78c385abac3c06d79f373c4b40dc97b733dba1b93ec964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165e0838a3a1df43b90e450b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002020002b3a1c685789106babf60df0a2008d95fdb808f8b001b8d48e39bdd59d4fd0f9da07712fe60f5037d0cb89554b350b89e973de70890ed2a6aa021825db75b93ec964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165f08dd7d58ff82bea7ca0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020175c57cacbe5f12424d242df493bbb6cef0b00bce3b327f818543992622c067f1c28b5d2fa7d80793c1d8dfee13d9b73478329ea7d76ff3f5d2dc70b9a51b2dfb93ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff166008efc6a0a35e5f01540b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020b435968f287ac57e2a904d210d95ebe86a303216d1922fc995ce4ad13aa73076273aa9c4c6fc6d10ead5a2b646cb4656e1f3eb1c187cabd3af765a8241e63f59b93ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170111087dca044d651116c90b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000202ea056862ad25281eea10e6bc8ae89a8062c348b3b3a0cbfffa1cdf853aa3f0061521f57cef07da174735c79f2952ef2b3d01a9c66e22132e3dbb5f9d3128452b93ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170112080ebf98adaaf1fa0e0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000200b1856b5263eb4938519d5d15b32fa8eca56e4db93c5513bc80ff78c4a3ede2bb54f75e06e0aac5880dfc878cb3de9165e305d1faed8a1ae9b2f682f254db5e8b93ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011308db7fa6c66d9c519d0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002023462813fb845d3a9efb9ddbea96d52bd0655e2674164415f212173c0192cf2968b70109d40d668a7d071e081cf2950e3aad42de4eaa9083944595dc55a82795ba3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1701140880980d125d5573530b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000201289a39496560d73fea3d40806da6312abce62fd4c47cc5acbeca27170f9dc3f51d5a980f482b1232d4b8694a66b54b1b9f25ee45f318baf2d28d80871d4a40fba3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011508033dcefd6e3e562d0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002015eb444617bb0a5cc1c286fcfc7fe19e9e649e080f0d4b5ed1c3987b2770045e2411741b398d29e62ec591ec181c13955f9fc3873d0061c8bf99bc3d7fbbcc31ba3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011608c1d1644d56d4a60a0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020e9b2bfee606c2bc8e8b25767acd423ed8adafeb1a8752534d9e9e8188954c6482f3c356a83fb5fbb9fd5f743181aaa3241608973e0ef4f7a67bc34e20c0d546fba3ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011708bf9e1e1b715cd8840b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020928d9fb2518faec4edf3ee44a51c55064fea3113115919faac30b66085326d50a57783d5afdeb7140a537fbccf831dfd91c47cac9d14f0ce9157b21b2da5b604ba3ec964ffff7f20040000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011808a37fb8a12b55df4c0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000200d011940cdf3e961a28fa75847cf2ddd6538a9bd048985f03adee9d4276e551df5dbc4378251197c195b716999cc84b49e134a2812cc15b8118878666367999fba3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011908209a5a2b4ce197a40b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000205d7a48198ff7dc6062bbdba68de91a68565990fc6831080d44c5053b064bb95a682c2772b31259ba4615b0353f4207ddea2f86f9ad4280a59b2276f9e812c1cabb3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011a0846dd65659e50e2ec0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002009a5f4d3aef6ec72942a67c47ec47c5213ca4b40d5af52f0881bfae2d52b990e4c8bb1f7576747eb0c50e4d7f383a0281191b68b0ad65ffa8e9fab2567feef83bb3ec964ffff7f20030000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011b085519887d9e4997090b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020f9d24d28e3dda4417a6c08dff8f4eb2713cebcbd38cd182016e9171b5e4d3c7609157fb605bd23f2926b074e40d713b5f844d5b23bf29d27ebc24588e3ff420bbb3ec964ffff7f20030000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011c08f6a125517de6eae70b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020aed66849d644187c73fd308b7b554f08eaf6c1ac4efb7a5a63a3ce13c001e13ec8efb9d8ae9e398c895079253fd1b759260b35e746872268c76da7b39d8dbcd5bb3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011d08ab0e5530a9f1f42e0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002039e9fedbdeb83a8aef78607de7f9960c53a6c3756f0940e4fc89cba8cab8072f350efb164e8c4c7ac06bb1a5104a20d896704b953f0983ada7ccdcbb2893d476bb3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011e08608f9c92b6f134ce0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020572a489bede6abc4ebb7e3f592dd591d3ed00df84468c548b14ec34c5addef5dc7b2d07032c0f31a7baf435659e591f6e3cc43ed394f1101ffb74c030c71ab33bb3ec964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17011f0857a4b82fa1441d5b0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020f41cfdeea8e023e7dd98d091f61549d2c8a49cda1c355b5518c2c93daa92dc5f1035ae1c4d6540f5a48b46eb55f27baff1c5594a2fafa5c2407f795446db01f5bc3ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012008f6ff83f9f766f2850b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020ebe3ea794ea022ac790e01a10a704d025aae50229b7641ccf226d6fa4641653eb03b419dbb47a0147e97d3583c0a8f78e7666ba47682f23163b9f4928ca480efbc3ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170121087905ca9581a572600b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000206f759d579d273307d1f0369ba8f88193aeeca8e772f172718cb49db74a4a6178e0e3c85da7948905d6fccae7f8c90994d54576e3553fef107264a8ffaf7e91e8bc3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1701220841d75d72b1afb75a0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002016eb7937ebf3067612851b81fec6c9a7b7e75612d9cfa590dbe1df376385d93ddf453f42137601932fc4e970efe348eb98419331adcd8d46f07de6d7fcfd84adbc3ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012308b744db801e4bc9c40b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020d92100640175235b744ead092ffd630fb59398d5c3dec44b7e37118187b14f22069ceb7edca9eb13e6b5e9506fb1ce4dcedbed98cf873e57fa55c17c45742e7fbc3ec964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012408fee0950d0baae9410b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020c29ccca248c993cb98034d528dd671fba3af3f38b6e6a5cc4ea2133bb1ad44637f43a0d28e7351396cb665764d61176ae81c162c2b2e9eb32a22a526995b5770bc3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012508914422038da5668e0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002064a435ed752f1c4d38fe2639d6f085480f6252535d979bb810d4acaf67e8875f77193ee743c49bff1de76b0befc40f7f10d0809df41f7003b1b0d4e38267e6d2bd3ec964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012608fcbbd9493a6b01660b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020a622b0f2d3dd10cfd6f91652e80b5878c754ed3895caf1a07dda7caa057c096af277431ac5bfecf30d1f30fe9e3550324c203fe469e780baaac156cc4b1bf589bd3ec964ffff7f20030000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170127085b9a243e930f655f0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020c1a0dbf2bc73998f8d52e2b46589db49e9d6be5f50026031905a78ce3fc5cd3bd06dee8dd13f9dfcd0be33ae2a951bda9b2902d0605b27e32fe6fa501325a5d2bd3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012808645c86c40df82aad0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000209078e8198d1df451311fdf9b84278fef9b4f55cd9636c409311c403d7b3d377ca44d46032576d42a5464ef584ac41c17b9def76139badb09b73690705c948952bd3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012908b0276b5cff9492620b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020dd5cc66c260829a7ee471ed2c2b1298693168af8d31541fb2d6d8f32d7f20455e942ddba5ce47ef35da6a7213d303068f8c650b68d7fd99f1a59846c700abb5abd3ec964ffff7f20030000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012a08e3ccb1648bb014c50b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020895f85c1d696fe1ca5b48fa022c2867185af405583dc3ab0b9e3e79846192c2c5f994a2ea99b4f67b8546f494e8621b51684ceb638e22639407b6d048c7676d3bd3ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012b082f3d4fd799e5e4530b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020609c2d348d8f988cbbd22e7bb0b0d7c14f256c244ed0ddda3e0af147a2f09b596779c8b915fef2618b50e5e562ed3efbd3a6360b018de4d7ccb5914c7a1cc66bbe3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012c08c39faec63cddf0bb0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002060ec3e71ee61a9733d53482ee3260ef09559d716f906f0b2c90784788a552e05767ba1ed65d9fdb69d3e066b2da7ae6d8891de834798e3b8c6ecd99819d399eebe3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012d087c7baedc6c41c6430b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002067594b9e770a2bbd2b2bb78761d548e9efed37034a47e84904c106d442cfd3621c8ba8e643f83369e2e06b61a3a46fe7c052bdd36232aadf395e71b2e803c4f8be3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012e08fe0a65456cbd927b0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000202aedfada26061e2cb616cdb031e3439b6b2b09764d321385b5ebc000954eee2cf8c723e2f46fa98297a406370c3f427f23983e28ea410b4e3cef8acdd9258d53be3ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17012f086d593e1336867bee0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002057583e26c72dd4be5db295fc897d8006f72996d36ced288253f055a8b9deda091bdee024cfc7c451c000df88c51fef104819e397236a6b215cbe7b2ace9df0f5be3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013008c41ee8f9a0b723930b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000205ba819a407733cf5081dd8e2bf1d0d2df34cf44c8d13215d20ac0ef5c5dea674093db664a1c26d20c0d6adce5ab501c97a989a0f3138fa261daf62a003f2245dbe3ec964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013108cdf29385ef5a66240b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002076209af53e8b788e1783bad91a36f8f6f790901cb166ff500c59cff79f36132346f79a78130cba06d93e5a7ffbe0e883bb0a38f4b8fe264433c7fa3eec387caebf3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013208db5458d9c81bae790b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020875ddcc93896b837746ddacd99e2faa97ccd6c0d18c288abc74f9838a96e2148717855bb7f00c41a91dcdee677b996f6c9e9a9705061b0d96d3d5b80a933fd9bbf3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013308e5eb8e4be0e8e57e0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020edf1cbf9780c16157693bac961415574970fb9c1091880907300fd7cf8aece3dfdad200af3852d356ae3eec5a9e52b2880bcbc36a1179307fa2343a04670a296bf3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013408bc631f5ea0de2a4b0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000209d9d80f2a14f8498db46df6bcd1ba8587b8990a9a5ac25ae9a8f5b22d16bd351f1cd5cae026822d4c837fcbeb8d20a2cb056c918210a2654348ecfabdae2d9dcbf3ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170135086a68c0851f6546410b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002079022454862f955e82757a734a24eafb780caff4d13b4cca62a4c43a8d30457e62e35f820f9555da6c188344a164202960c7070316395b6b13d6a16e2e9bbc23bf3ec964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013608481669812fad350e0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002017cbc4df5763563334fd51938052501ef1f13648713af4bc181a61eef984ff2a5b0e6960b1fb046c43da37a2df5d4d1d12eb68224b98276da634d70e89f99fffbf3ec964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013708a0ad5af48ac52e0f0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020949358f7c15a1eab742a7f71430f7f43a95f7a562c1d1ebee6358b16f2b09d2442470c5ff71e7fa8970640278553a4ba4b45e6492eeb2aeae84c2c4700ed5373c03ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1701380858e0c45c0973ffda0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020ea04e9071951ff5b96909dd34e60815d009c1c158192a3ce2eb0b882dbb5fb0fc12532e7560bce81c873bd81e339beaf7860a3452d07a2cbb7f726cd31b8538bc03ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1701390882e4b0b6f66a55520b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000209e00d7cc942f53d4666ba5a4039a161fc39067a254ff63243aa91a5dd71f0664cd193bd03c0f060dcb16b2a25be1a4bffb152857edfe7c4d8692e9d853dce576c03ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013a08c465a6176bbc5a2f0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020b903bd6e362067bd52ae5b086f2e9f2fc24e93eb4df278b13ab4b2061c474658841fc0a313ea95dfa4b86525fc4bfb16d7dd146fdc6d2877589394fcdbcc5f75c03ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013b083019b8562379e97d0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020fc1c4da1e259b12a1dc5929a7b9a684ca450caf11c625e652a4825ad782d9d0edcb5437b57e209800916fa6b3db4c18818c6bc544f22a2dba762bfd0f5d88a37c03ec964ffff7f20030000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013c08b7385465e39231f20b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002020d081141694d7054331dbbf5deff808677406a9507ceb321f970046980067519d8bffedfb6f66c1352182e0a2b9f7ebb481cf1eb5e20d77b0933e57377fd1f0c03ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013d08d104ef8a8becd6ee0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000207bda1496c6ef71568ecd29bb22a2da314f688d3511a2ba1878aa4e608e24186b347d2d74b60e11e3f5cdcb05a0cafcc01be558f571a27f6aae7bf2ef1332e560c13ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013e08359d6f7ebf5761620b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020895a66158e1cf0d3b1f5f876252485a7aa36797d608435daef0b8546dcc60952dd2b02264a31ab8bed0512de9237515795e12f01b7ef08ee44eb42f91bcb2b80c13ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17013f087002715c9d8833960b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020fea8a6b17ad073f6d6e4c885608dbf06b3a7b41715d575a56bac655b797a533d868993acaa878f90839b12afeb96b3de27c5db1cfacf979850c5e715e02857dac13ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014008e6efe4a7e5d7585e0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020ca62c7d4a0b94267ca20edfe89034cff95c1420c4f0d0ab45e729fcea438d13e7425875f0c3275c7360a4112f3e0c784acbb2c3722624df1d5b8e527be106321c13ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014108ca94edf9b2c25ea20b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020570f6fc43ed2209665cc8a0bb00970c381915ab716938dcb2a10a4d38e341d1b04ae4a25ff0203dc987f97bf08722999be5bab47eccfde6372f01edf877ffac9c13ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1701420820e08b67f49cde620b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000207cef6819d33c3d175b2f383da61d91776107431fe69e7781ca9186ab9c6d835f9c1f03869d32a7f6edb10f3ce7cbd9183ef242068850fde0adadb94eaacbf929c13ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014308d65524653a4c8c450b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020e74111d664fa3ae884d3c949325806a2b235b76306ae3ba388906e6b417c51655476817c2bf15e344552c1b96b2a11baba58dde982db139d1e4a8890cd5da180c23ec964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170144082363bccedccfa5520b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020d004a74f40fa0b6aaeefbc443f20fac0734ed872c403b09290a45a3d9bef5a3aa6e644d48f997cacc4f6b6a3c8ba9952803cdf788559120d1dc1bd473a1f785ec23ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014508993136b4f4fe6fb70b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020950d8f8d9966ef7f29f9e8353c55d179927465e3218781318c7b1fc5a26bd032e663197bfba53a7f14714f885124116ca838ecc359961929df203f9227c11690c23ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014608cc9951fdc11556350b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020176f2219a27791ff58eae916d220d3bfb3549a51794948a7f58a1c6ae458f008f7d342b0ef5b8df8825b6c1f7cf685671c5ff7c32436ace5a0f25f756ddc1aa6c23ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170147089cd444e4cd82e81d0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020cdbe34a91e2845b46801438a878f52799b0a073d6906dfe84b4081dbf34cdb33f1e1437e798a3190009cf09fd99e5c28d2171fd1d286531fbca1e0f98fdf9a27c23ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014808ddf9ddc465a817790b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020b335175595f833d77470736654678ed94c9c000839eb3e64a200eba8d5b0b456f41ecc4f2fc7c2ae419d3147833f0ea82f5d855d681344e02b6d08c0d51d936cc23ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014908308ad4d4498092950b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020bff946ceca3c8697fbbbadf92f9a0c2ac66e280e6f32de0ddd21ded665c2be36c3a06c6158da0e72a3a765de7b45873c95f00f61be2c1f47e0ef405853975d8bc33ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014a084f08ae7176c08d150b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020ffb401aa318ac965b905eadc75408026145c94c75964fa9f9226e23cc83679050104e1c6c6050834bf7678b780d339812ad4fd54995303b6bd245b5d39007363c33ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014b08767c74a3b923b07c0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000208488f406900213e8feed6656bec33f9c32232dae1c4980adba7ec8f6d89ee809bee1b4761fb6a018ea11b8a50109ae0488ede1fd72b57c96bc1c2b50b84ea3b9c33ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014c0895c6575ccea50b320b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020f0504f0f605040c1185a27b7332ef53bdf339d838ea2f952dc80a8642ceae93f604659e84cb433f6167ba9bbfb7cb16fed752e7024425ab9d75621a05a6d0ca9c33ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014d086c5e01a20c4c3cfd0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020c3ca2bd022d4908171479110475056a20e41603b117740a5a2cac001825ea0683a15c99cdd3f4088ab9f49b578a2ab721bf037a4abf759fa76efed88c4b85922c33ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014e08068f4e1eb3f793a20b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020ce4a47354f86aa6cf95b3dd79acb7d7eb0f7ec5a5c180075acfefddfcb3adf019a55daa41111590565c9d747ea79edf44e1ab9a6d45f150bc495c87748b1d5e3c33ec964ffff7f20030000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17014f0885cf90e8360efaef0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002044c059a8a9cae6bb5227d1d793332e9c1d9b97d454684f9d3f14b89c415db6182fc4ecd821b04297d9f544ee0de59ce23443ce5e0371e2b2f060a257d3c63c0ec43ec964ffff7f20030000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1701500840d0d7499372ba970b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002002368c44832b14f0ec76e8b9fa59a7dc8f31fcc9d9adc24e30f694af2919ad2a548d70a33a76491e1fa52c268eafe264985b67b251d35eade1db37ff50ea934bc43ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17015108ae913210c96bf99e0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020b46760acb497d2a064b128828fa7d9749aacfb359149fc82a25429ab2a2edc16b178fbac6728071f1138bb856c2ff63932ae6e1009687ac12fc823ef63a2799cc43ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1701520889f48dd9e46b21b60b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020a5989f7f996d1af603bef5530857397e20d24ed7c01c84062ffa69739f52172688a0031da06e95ec37822f8198a9e51a60ce19f83145bd361cb81045b32e8270c43ec964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1701530829d51fc62bd806460b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002068baad48f3eb238b98f86444006fc41c362ec59391192e6937d2c3a36fac57781483d2b71b9277c45959c8fb8ee42e5221bd2e036f25f8775b3d04655e59580ec43ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17015408f31c2b201bc0bdd00b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020b2b9e41caa08daad5aa7c0e9dfad7c922dd6794803ffa95886cb53b10554aa2b3cf312ebf36a86edfb7d1daf32733aebbdccc79ff97a0fe840576814ed901f2fc43ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17015508186839b332db52d30b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000203b2686c328a91462190044bc3928e0586768f831cece0f8887ad213e79fa63402d14488d4d54dbc51c124050aa85febd1d09935892d3445556494e066a590a3bc53ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17015608f7e186f298d99ef30b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002013e0126c977493a657839edb64b6007d19898dcfdfb69c1e0a01e750a309e94b4ea7596b16caa6f8dd24aa6ac76f9cbe1f96180cb0f55b091ab14116d51d68d2c53ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170157083a1175bb80ed17a60b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020fe16b517e490554e59ec20064ef6f33ceb6834b4c91c153bcf639c792712de22ac466abd180fb3c7b77051bf13fd3b8b8a95d39df026cba2afc8ec75fcd5baa5c53ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17015808717b3c6a73161e9b0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000207c36f721544ab27dbf558140ba2fd4d40e04ef84ae6a0068f158425e99a779495f84e74d031aec5aeaeba35a933d6dd823e22d922c5bd80c0e490ae7d320acf9c53ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170159081375cdf1811930b90b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000206d4186becf2885d0d7d9f130d9d731004444b01c367f12a26436183c6f8bf45ec2f52f52b1fa650d934a87586a4c24458e8163a4fd57c6fe84476be6d96bbf06c53ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17015a08375b370b4d73733e0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000202c4d733eab735f47cfecc6b5955cac242f471deed9d0e6eb7717b7480a4bd26df37c552615357101acaca47cf6d02f71e4998d8f409c336b8afe2d226361ca4ec53ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17015b0828179d0d9aff6daf0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020dce6ec0dae3bb1c23ab94c41dd01da2753058a35c15343f11965e49c71b6741dd81768d4dc33abd6b2bc80931439b904bc686cd09e0f61207243f587beb1e2f4c63ec964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17015c089ff697fcd9dad3b30b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020b5830ecc7ed8b9648d8d5ea6e00433aa6c00f0351590c10587f6c7dd5f0b5579cc83a5ab7469fbb3480cf1f4e6fcf9ae887741bb6bf3171fabd6314df3dc82a8c63ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17015d081ea0fab64c4a89030b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002047a0756477b9ea080f84c0b9ab0f84682725525577c5c29e6d74f7b822caf32a5ca76a397d3e2fd9af13a851aa98ce8b7685e7febbc1ba17998da62ff21306e5c63ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17015e087b5675d3000447a10b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002093777c4a7485b1657c24ac8c2e0685e31fbae4eadd229dbff1cdf1119cdcf70aa93fc0ba703d3d0245c5eb2e900eef63e44a66e86bfc9a8314e1c3e31b0d4a6ec63ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17015f087b7889b9100763450b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020ec66e5f685add2b6a9aa3fe054f1759201399a01587a4bcc448d0920cdc6d40db90bf38a13d5e2239959184be3c4074d634f2697d5f8fed2594fde6f013284b4c63ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170160089d72db8cc180246c0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020e870164625686a37671e33d57d821b05cee5c1359bf0750512b91dc3ed5b50330cd4569e27b6e4b55040472d9357760b74d8255b8fc306202e881cb64cf5a467c63ec964ffff7f20030000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1701610863b3b01d8840ca460b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000203988cdb9d120967eb4667a801df886c8142d9ac9bba39f0104721563b2517f64f09056a1dd4c551ac4999a5d9dec0d60706fbd80db0cf5016e05650165757d9ac73ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016208b036f03d70c5685d0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020394ca39725a7b769a54da360364a0ab60434fc8c3d2a196290520482c30e7e55a9f1dd4f07a7d5c3a4e992615a76bb44af47eab51224c3d369ccaa57690d1c32c73ec964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170163085f6c402910d841e80b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000200f12ca702a50e4a3d37fd2bc152c783d64897d1190c4e9ac901c813b7636f47398055a780e04fffa8c86d893e6e98912bf34baf66f3a13c59e864b94d48dd924c73ec964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016408301a185cf3d7eda30b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002076f2097747d8f21662108d84bf720e1c83143bae7fca6044c5ab370f4ab912660f3b622473851809106e3292a8a9d757f28fd912ac35aec0fab305161526a911c73ec964ffff7f20040000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016508cff4455f62ab7bf80b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000201ccfc6db30dae1d5f5ecd824603e6e3a53c8fb7dea6939df5ca5ee6428b60330d8103cd9724afab32063217d830f21354e494b57b59b6e7a92d136b853c9c9da8b49c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016608b781dbdcae3c01860b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000206b59dac2859824754ad60df2206b0a0d85c6f91b9d4406f1fed0262287f1863f7a03613af77eea7f3ba39c3e65b6edc46c9d85fc6d1943fa5f5243d6132201e78b49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170167089105574b0c55db2a0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020f5f26e278d06bbe2699ca106fa8df6b0eb96de4ff3a5dfcdf09922497d883978bbf120dfee5a8747210129f4ad5c9d29c916bc2f58e60324bbb292027f1465ee8b49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016808949f096a253a926b0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020979b15590e6595839a71d507c1adccccb066f1f7f72121b493de860aa823856e69fdcced1d6982cbebcec3402160fdd5050424f36b4f54fb0138ae3a00d323f08b49c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016908ab9523705c50124b0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020c0d06e0f6aaff2017bcb426e1590f2213086805997686c3ebd1b0ce6bbcbfb12f6bf66862a342b126df3873b81e67e8e976a690c4c133642c8a3122f08750ad38b49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016a08889031f83770babf0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020540c16f2d567400196af52778a79d0c21392b74a3b30a370945f5ed1a8d76e2ef095588aae2e7e7eaf2a49f448d05061c036ca71004cbebc907a9c8a11746ac38b49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016b08428f25088f38aa5c0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020e5a67f8254b58fd3ae8314f9761066b52c890ab3190ab3cbb35f5555205a9b50f8458b8ac463a8f2bb62afa5d250dd809e4661d09ddd1115eb475276e1ef4ebe8c49c964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016c083b4c45d3e78b295f0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002061f4c7b9ff0dd564ce64a73fc7c6b6f2571862d6213e85154bde84bc00189a67a187124a503db1fde07b0a0628fd8bacf4326e8de85c0bd40fdfb2e57715488b8c49c964ffff7f20040000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016d08f5f21bdc6815bde20b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000202378b3d80cf22cd5e8e637439c1f943755c5dc7843050328b1c0b9848212c44e08a2125fcbe6c28a972013b737d9a7389bf7863facabaeb2ff4eb6cd657e09948c49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016e085a8dfa811371c01c0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020b1a9436575b8c63fe5eadccb6df58a05e5307737256c4506026701d13243b92bbd66343bf83edfea2d222d95073d6e07a80b8ac69971b7b9dec2f7ff42d129c18c49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17016f08491dd123bfa854e10b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000204886a38dd5efec51dfa0fdaf54e236c02e25126994edab967850744002e3242ae2706fd804640e3e922c6ea709a11c4acec3fb3cbcebfdb95cd40bbd85fd28218c49c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017008c7019cf47277a7be0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000206d17f64ee8e9c4a9bc9f05f8fe4e8f7f468b52ec1cc9cbb6e46db904c4c3c02323b970244ad19da00bc4a85e39390e224508b0c6bb4c4985b50f61f7948d7fdb8c49c964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170171083a3857111dc7a0800b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020c932eb94e74243d164d242b70c0b42bbb1044ffba7b4de1d9b27767d1b07d427bdc959df21d088c4182a14c10c68fd23da72131556db0ec9faed0c67218d143a8d49c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170172087cd6c2a737bb06500b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000203202cb7dab369d1379a767298d17d85b95fb00cb08d971db084ca82f4563f623406c9467ff9be818e4db5eec0dbc0df6fc44e68cf0b1ea53a62e3250cff6c4978d49c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017308220a3070731f20f10b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020fa71172b706a9440d66567c1f6370d11d5f06963810e764114eb8bc2748aed77b3973b67323b7252edff9c1b23763c675215df4def7a1e72d75caee7f102072a8d49c964ffff7f20050000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017408c823b033142522710b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020544a3494767c414ed968bf406ebb8d29732c5ab84e8ea15a7304677110c52012559e31bc28c26db1bf1a64ff62e24be0b6dbd12ac0a87ecb349c7f127c27632f8d49c964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170175084cbb4052912451f60b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020a6a4444d81ab988d858a07727a1a23ff984cc3fa143eaefef7c1bae33fb08c60521c95b5d1acfbc9dc96d666923bc0ec3b36582a4c40dd91dfeaf6cde63d84478d49c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017608a291be014c98a2820b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000201d93db19693e7f00a89dd0922ac5b60a23fe9a7df1967d7d18bcbfd0a752de7232263b30256f0722b5b33dc6d2a788b1753bbb7fd3ba2772a7802d323a473e8d8d49c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170177085a3db79991dc3d130b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020d89303680b47ad57e26ab32be5de2cee0dcbf8d79a0adf779fbd20d68e5e6c2325eb335f2049b91d2da86beb22f0c8869bdcc191c3670b6f906595b7261ab2368e49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017808bd965d935a87209d0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020410e3f97b3fa360d33dfff1e7cbd288478d0ceebfb3074fdf3eacf003b30bc54a1f8d8fe716716ae2a887bfb113d706d84ab974713c3082e5b83ce107a139e878e49c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017908194332f0c9de73d90b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002014e02088503240e113da5221c4bf6e085e00076de5aae8e486f117fbbc879f1d21bff8cd1ad5f1b73bb0a249e480f4731eb29807623fe521bb4234b3fed4c5268e49c964ffff7f20030000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017a088559a52d718307410b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002054f343fb2d62857ee2c5f6b9f33ec4681265be2ce4c6da6505c171831246e540efe49839c3f28facc0d37d750320662793ebe1aa313c7af655b82649cd73b6ea8e49c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017b08fa5bbd6f7495c1470b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002067c9eed7dee31c64835300e5643279e3216ea540cc7eb1073a5ee4a66fcfcc3ac02dbf31ff16918f291ec472f2b58df0747f472c1ee67aa6a2892b6d8964e4358e49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017c083a85ddc7b6bc22db0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000201429db5d94446a6b3cac84b9015adabd799ab59dd3f20ad92e8e17f2f03fd633f534aeb93e13713b8615d937b539b076114dcb8b0ff2411cfda9996d65fd77bc8e49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017d08a09f0b79e1b00b5e0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000200409be070012bd8119d45353cb37fc36aa87bfa3cd152502a1df7cad61b4a0760961d5fc0467cd89bec0a4d5208439416b7d78de51c62bba1b9f29e0de781d908f49c964ffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017e084120b4031c37412e0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020d244c113dfb69dc6efb2ee04b80971adf072f90a8a934ee97e255d69cdb0f43554d247e48a9ed776998419825153278a076675f77902e4763d07721dbcc2d9f88f49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17017f086a670b13b1fe53b40b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020625217cd97341555ed8734f2f879e069e20adb4642820a1415515e1ad7d12c1e8cba6a1efa54a3d1b1019686e27cdfb29b5a509174c08348e20a2eeb1a393eea8f49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff180280000814fd5c07b814516b0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000200d07daff7c967a0cf5811c3125a42bb8f46105d6b63f9ab1a564d6dab849ec03f6358481bbeb2ee95638679c989671454bbf306e0d4787f09d6e6b457f1f54d48f49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff180281000894c62c4e0b0cb5410b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002067a74a123676cc9a18f059bc84c1efd3cb6c4214f8328c43fb25fd1d16b94e0198d908deb1a708ea100d1aa6b922965fe8f78bc1aa550df60797e8cdc94837fd8f49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18028200089854b1252e4d4d8b0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020eb842e981aac13c18f5d92c3db3c733010ee4a737cfcf089beee8ee6d750e43208f0ce164f1db3486ee92993d189bb7ece6b6a6cbe596fc26999254243f89a748f49c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1802830008fe820e1e5a99b2cd0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000202d2352b7d80577b7dfbb66d05b6f89fa7bf6934f6a639af52c06f89f6feab2735ca64213a59fdacc49474a0a852bb817435f6466e79788cda9af6404592c2c5d9049c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff180284000823d6a297513352af0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020d6c4cf55faaeb36a2c9fa85c918f68e2c8ac290e8dc3714d65689301cefc1a317186bb00593e96a17f1edb3d4fe05a34ec0a1c7c375e801d996c61ce71bf1e7e9049c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18028500085e50d54f8ec5e2450b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020a19d41c1f0cf3ee1ea696ae1d1074727080fed0da81b0d6ed4b77ee958f5cf05108292e9084156409935efe1ec7751b481634b628f482663b7843f93ac8057519049c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff180286000877f6e6d6c06af9e50b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002076e6374dc8ad62a59aeb72186bb815447fd7493d6dd3f104b0d8b57b2e41de572289df55819ceb7d5d6f8149360f3e61d8ca9c034d1df198d726716d264b17689049c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18028700081b02fe316ca387f60b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020a35a4b046592502d35fe9467b6870e8adecec166885515aae22ff2a244c3d63e74839aa7d2ca4289d34a19a6ccfde0ea3c2d845091cfa2ffdc810d6a75156da79049c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18028800084713c25be3f7b1ac0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000200ea397e529295d98d8eaee19f362109c284af58c445e5f7f0b8bb8ca6587b07b05fa860635e6c94d65bc38e939b69e7dc31163bf2757eeb52b3ff21e8fdbea4a9049c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1802890008078954d56dd307e80b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000203a6b55d076d51ecc533916774e14f1c51b891c521da6d54ffbe79e125c5b397c995a5fdfa2c26091e927e4ab3ff3340953cc8301c316d7ab9c01a43992b394259149c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18028a0008de0c4c620ef381ba0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020aed6a67b0f1fdf3f0db80b902ffc3bfa746d9592d98115b77429ed14ccfb5a6a6b3291b6539c69305313361f90322e42ce8da50389c6a713ab54cab0e0ada8199149c964ffff7f20030000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18028b0008da926ce61c4abf970b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020e6c2aa86ac154e3b9baf06f6fe5918472a55446bee550608de0b4f8a3f32d60c1f789543cd14331ec4c6d4d8e899eaa408a0cff5df02efaf16580796b51381299149c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18028c00084fb88a70b0b61fc10b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0000002070ed3ff0bf2419e2bdeff2b8e1180a69e7d078973ed01b8db18a86b7cce1a919584536c58f50a54e9dc6126c6676989d625c137de3e35fff25a53a8232fdeb5f9149c964ffff7f20070000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18028d0008346106c8b990da0b0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +000000209ee8c2924920acf161e02f1fccc8073da1ba55f5a3ebca4dcdf7945a41950658999eb1f83223fe6762a3d17451d43ab07d4502ac000403fccebec6aa252e7cb49149c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18028e000822ce5fc939e319a90b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +00000020ead2d10b403ff0733d867c26549b707276dc3d7b26535d792ee05136f10b3b15d6c4128ac405b990b5175ccbfc4231989319f7fe33d86f608fa313fa64fe24ad9149c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18028f0008cf8ba44a9ff4c6ab0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +03000030242a04f460bf8b1085871163a9e8a271300780e860b4a603ace84ef404be0578911c171d6c18003d0a31150b52ff253f0913dce463900fc1b32cdaedb034f6cc9249c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18029000084d61551d7ce182990b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0300003068fdea69b1207f71f3f4c83e6f075953e92a96bfb4c9ef3788a3099c0aa4eb13d9fead1ba74116e7981d40a9630aee27961c2feec3ed6b264a014a48c35073529249c964ffff7f20010000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18029100085704b617c2631e380b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +03000030adc3d4234383076af6dbde54c62c63506dd4dfdd3829f3f894ce25e66486ab0ce9536907045794694087481767d97e97b8c2715961b730e26a2642c285ec45529249c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1802920008386e44f1c5a54f320b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +0300003063c6449bf1ac1c15014c5ff9ebd6d11e4c2285bfdebba7570e3c96667275445429291f34b85b4af49e68855b45d6027fc40254648635f8f03ed72c9d8a383a909249c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff180293000887d676d5f256da3c0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +03000030ed29959bdfa90c79464558aa6b503276d4545a80012d3f1fcb34d1ba658813573016aa633bd8f73ce116d1e3827c2c1d0a56cbbf7bbde9c9b673cd4d249926659249c964ffff7f20040000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff180294000899d7318b10c7ccea0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +030000303e63e8c01dcba6c21b0e3148c6b225b8577b5bc656c5c6023cca1fb4bd87564bab2a2c4691ee399ad0b2e737b3ad411511152f07682ca05239b5c26e2e29d7c99249c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18029500085faa80a2c85675de0b2f503253482f627463642fffffffff0100f2052a01000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 +03000030c674849a6e4f27eab1e8a231069b7275652b993f9fe78cf0665a55e79f8b413bb10e1cd8a23831f8897ae8ea79165c419230271ce09e77e3242d27fb1aaf5df59349c964ffff7f20000000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff18029600084d5dc667dadea7cf0b2f503253482f627463642fffffffff0100f9029500000000160014806cef41295922d32ddfca09c26cc4acd36c3ed000000000 From 146f8a240fd38f5930ee9b2260b481f72e99f28d Mon Sep 17 00:00:00 2001 From: Bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Mon, 14 Aug 2023 08:11:44 -0700 Subject: [PATCH 007/328] Update readme.md (#68) the build creates `florestad`, not `floresta` as mentioned in the docs. --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 1226c032..6fe32d0f 100644 --- a/readme.md +++ b/readme.md @@ -36,13 +36,13 @@ Right now, this project is working on signet only. Mainnet support is still a to Copy `config.toml.sample` to `config.toml`, and fill up your xpubs and addresses that you intend to track, and then run with ```bash -floresta -c config.toml --network signet run +florestad -c config.toml --network signet run ``` or ```bash -./target/release/floresta -c config.toml --network signet run +./target/release/florestad -c config.toml --network signet run ``` or @@ -73,4 +73,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file One of the most challenging parts of working with Bitcoin is keeping up with the consensus rules. Given it's nature as a consensus protocol, it's very important to make sure that the implementation is correct. Instead of reimplementing a Script interpreter, we use [`rust-bitcoinconsensus`](https://github.com/rust-bitcoin/rust-bitcoinconsensus/) to verify transactions. This is a bind around a shared library that is part of Bitcoin Core. This way, we can be sure that the consensus rules are the same as Bitcoin Core, at least for scripts. -Although tx validation is arguably the hardest part in this process. This integration can be further improved by using `libbitcoinkernel`, that will increase the scope of `libbitcoinconsensus` to outside scripts, but this is still a work in progress. \ No newline at end of file +Although tx validation is arguably the hardest part in this process. This integration can be further improved by using `libbitcoinkernel`, that will increase the scope of `libbitcoinconsensus` to outside scripts, but this is still a work in progress. From e7f91988a5dc01444d0f1b064d439d0cc0d0cf58 Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:08:26 -0300 Subject: [PATCH 008/328] Send addresses to our peers (#67) Peer discovery in bitcoin works by nodes sending addresses of known peers to each other. This commit makes floresta send addresses of good peers to others. We only send addresses of nodes we already connected to. --- .../floresta-wire/src/p2p_wire/address_man.rs | 25 ++++++++++++++++ crates/floresta-wire/src/p2p_wire/node.rs | 30 +++++++++++++++++++ .../src/p2p_wire/node_context.rs | 2 ++ crates/floresta-wire/src/p2p_wire/peer.rs | 3 ++ 4 files changed, 60 insertions(+) diff --git a/crates/floresta-wire/src/p2p_wire/address_man.rs b/crates/floresta-wire/src/p2p_wire/address_man.rs index 35d66549..d226eb6e 100644 --- a/crates/floresta-wire/src/p2p_wire/address_man.rs +++ b/crates/floresta-wire/src/p2p_wire/address_man.rs @@ -157,6 +157,31 @@ impl AddressMan { } } } + pub fn get_addresses_to_send(&self) -> Result, ()> { + let addresses = self + .addresses + .iter() + .flat_map(|(time, v)| { + if let AddressState::Tried(time) = v.state { + if time + RETRY_TIME + < SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + { + Some((v.address.clone(), time, v.services, v.port)) + } else { + None + } + } else if matches!(v.state, AddressState::Connected) { + Some((v.address.clone(), *time as u64, v.services, v.port)) + } else { + None + } + }) + .collect(); + Ok(addresses) + } pub fn get_seeds_from_dns( &mut self, seed: &str, diff --git a/crates/floresta-wire/src/p2p_wire/node.rs b/crates/floresta-wire/src/p2p_wire/node.rs index e5758866..cbbf0e6d 100644 --- a/crates/floresta-wire/src/p2p_wire/node.rs +++ b/crates/floresta-wire/src/p2p_wire/node.rs @@ -19,6 +19,7 @@ use async_std::{ use bitcoin::{ hashes::{sha256, Hash}, network::{ + address::AddrV2Message, constants::ServiceFlags, message_blockdata::Inventory, utreexo::{UData, UtreexoBlock}, @@ -66,6 +67,8 @@ pub enum NodeRequest { BroadcastTransaction(Txid), /// Ask for an unconfirmed transaction MempoolTransaction(Txid), + /// Sends know addresses to our peers + SendAddresses(Vec), } #[derive(Default, PartialEq)] @@ -126,6 +129,7 @@ pub struct NodeCommon { last_connection: Instant, last_peer_db_dump: Instant, last_broadcast: Instant, + last_send_addresses: Instant, last_block_request: u32, network: Network, last_get_address_request: Instant, @@ -194,6 +198,7 @@ impl UtreexoNode Result<(), WireError> { + let addresses = self + .address_man + .get_addresses_to_send() + .unwrap() + .into_iter() + .map(|(addr, time, services, port)| AddrV2Message { + services, + addr, + port, + time: time as u32, + }) + .collect(); + + self.send_to_random_peer(NodeRequest::SendAddresses(addresses), ServiceFlags::NONE) + .await?; + Ok(()) + } pub async fn run(mut self, kill_signal: &Arc>) { try_and_log!(self.init_peers().await); @@ -1066,6 +1089,13 @@ impl UtreexoNode { + self.write(NetworkMessage::AddrV2(addresses)).await?; + } } Ok(()) } From 6cec8f542693c3c5e93eec13eab0debc6f4a9fc5 Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Mon, 21 Aug 2023 21:17:29 -0300 Subject: [PATCH 009/328] Improve assumeutreexo (#70) * Refactor the ChainStore trait and remove hardcoded KvDatabase * Make libbitcoinconsensus an optional feature * Improve handling for assumeutxo Even if we assumeutxo, we still need the headers. They are used to compute leaf hashes, retarget(at least the block TIP-2015) and in case of reorgs. This commit allows you to push headers to the chain without validating them. --- Cargo.lock | 6 +-- crates/floresta-chain/Cargo.toml | 13 +++-- .../src/pruned_utreexo/chain_state.rs | 37 ++++++++++--- .../src/pruned_utreexo/chain_state_builder.rs | 27 ++++++++-- .../src/pruned_utreexo/chainparams.rs | 54 ++++++++++++------- .../src/pruned_utreexo/chainstore.rs | 43 +++++++-------- .../src/pruned_utreexo/consensus.rs | 10 +++- .../src/pruned_utreexo/error.rs | 24 ++++++--- .../src/pruned_utreexo/partial_chain.rs | 31 +++++++---- crates/floresta-common/Cargo.toml | 6 ++- crates/floresta-common/src/lib.rs | 5 +- crates/floresta-electrum/Cargo.toml | 2 +- .../src/electrum_protocol.rs | 8 +-- crates/floresta-wire/Cargo.toml | 4 +- crates/floresta-wire/src/lib.rs | 4 +- .../floresta-wire/src/p2p_wire/address_man.rs | 5 +- crates/floresta-wire/src/p2p_wire/node.rs | 1 - .../src/p2p_wire/stream_reader.rs | 3 +- crates/floresta/Cargo.toml | 9 +++- .../floresta/examples/chainstate-builder.rs | 7 ++- florestad/Cargo.toml | 2 +- florestad/src/main.rs | 4 +- 22 files changed, 205 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4676180d..5a931bf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -761,7 +761,7 @@ dependencies = [ "pretty_assertions", "rand 0.8.5", "rustreexo", - "secp256k1 0.27.0", + "secp256k1 0.24.3", "serde", "serde_json", "sha2", @@ -2184,10 +2184,10 @@ dependencies = [ [[package]] name = "rustreexo" version = "0.1.0" -source = "git+https://www.github.com/mit-dci/rustreexo#e70cc1a1d6065b46bf15482584c5523a8ef78c2e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4852cab7dec3e1d89bb3ce30ca3461f7d6baee2143d0460c07bf1a718af68997" dependencies = [ "bitcoin_hashes 0.12.0", - "lazy_static", ] [[package]] diff --git a/crates/floresta-chain/Cargo.toml b/crates/floresta-chain/Cargo.toml index 84f6b081..5f958ec7 100644 --- a/crates/floresta-chain/Cargo.toml +++ b/crates/floresta-chain/Cargo.toml @@ -4,27 +4,26 @@ version = "0.1.0" edition = "2021" [lib] -crate-type = ["rlib"] +crate-type = ["cdylib", "rlib"] [dependencies] -rustreexo = { git = "https://www.github.com/mit-dci/rustreexo" } +rustreexo = "0.1.0" sha2 = "^0.10.6" log = "0.4" kv = "0.24.0" bitcoin = { version = "0.29", features = [ "serde", "no-std", - "bitcoinconsensus", -] } +], default-features = false } spin = "0.9.8" core2 = { version = "0.4.0", optional = true } hashbrown = { version = "0.14.0", optional = true } -secp256k1 = { version = "*", features = ["alloc"] } +secp256k1 = { version = "*", features = ["alloc"], optional = true } async-std = { version = "1.12.0", default-features = false, features = [ "std", "futures-core", ] } -floresta-common = { path = "../floresta-common" } +floresta-common = { path = "../floresta-common", default-features = false } futures = "0.3.28" wasm-bindgen = "0.2.87" @@ -37,4 +36,4 @@ zstd = "0.12.3" hex = "0.4.3" [features] -no-std = ["hashbrown", "core2"] +bitcoinconsensus = ["bitcoin/bitcoinconsensus"] \ No newline at end of file diff --git a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs index af76475b..8ec2de7b 100644 --- a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs @@ -11,15 +11,17 @@ use crate::prelude::*; use crate::{read_lock, write_lock, Network}; use alloc::{borrow::ToOwned, fmt::format, string::ToString, vec::Vec}; use async_std::channel::Sender; +#[cfg(feature = "bitcoinconsensus")] +use bitcoin::bitcoinconsensus; + use bitcoin::{ - bitcoinconsensus, blockdata::constants::genesis_block, consensus::{deserialize_partial, Decodable, Encodable}, hashes::{hex::FromHex, sha256}, util::uint::Uint256, Block, BlockHash, BlockHeader, OutPoint, Transaction, TxOut, }; - +#[cfg(feature = "bitcoinconsensus")] use core::ffi::c_uint; use futures::executor::block_on; use log::{info, trace}; @@ -71,6 +73,22 @@ impl ChainState { _ => {} } } + /// Just adds headers to the chainstate, without validating them. + pub fn push_headers( + &self, + headers: Vec, + height: u32, + ) -> Result<(), BlockchainError> { + let chainstore = &read_lock!(self).chainstore; + for (offset, &header) in headers.iter().enumerate() { + let block_hash = header.block_hash(); + let disk_header = DiskBlockHeader::FullyValid(header, height + offset as u32); + chainstore.save_header(&disk_header)?; + chainstore.update_block_index(height + offset as u32, block_hash)?; + } + Ok(()) + } + #[cfg(feature = "bitcoinconsensus")] /// Returns the validation flags, given the current block height fn get_validation_flags(&self, height: u32) -> c_uint { let chains_params = &read_lock!(self).consensus.parameters; @@ -410,10 +428,10 @@ impl ChainState { } pub fn new( - chainstore: KvChainStore, + chainstore: PersistedState, network: Network, assume_valid: Option, - ) -> ChainState { + ) -> ChainState { let genesis = genesis_block(network.into()); chainstore .save_header(&super::chainstore::DiskBlockHeader::FullyValid( @@ -661,9 +679,16 @@ impl ChainState { // Validate block transactions let subsidy = read_lock!(self).consensus.get_subsidy(height); let verify_script = self.verify_script(height); + #[cfg(feature = "bitcoinconsensus")] let flags = self.get_validation_flags(height); + #[cfg(not(feature = "bitcoinconsensus"))] + let flags = 0; Consensus::verify_block_transactions(inputs, &block.txdata, subsidy, verify_script, flags) - .map_err(|_| BlockchainError::BlockValidationError(BlockValidationErrors::InvalidTx))?; + .map_err(|err| { + BlockchainError::BlockValidationError(BlockValidationErrors::InvalidTx( + alloc::format!("{:?}", err), + )) + })?; Ok(()) } } @@ -962,7 +987,7 @@ macro_rules! write_lock { }; } -#[derive(Clone)] +#[derive(Clone, Debug)] /// Internal representation of the chain we are in pub struct BestChain { /// Hash of the last block in the chain we believe has more work on diff --git a/crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs b/crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs index 1eb17cca..b3efac0a 100644 --- a/crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs @@ -1,8 +1,10 @@ use bitcoin::hashes::Hash; -use bitcoin::BlockHash; +use bitcoin::{BlockHash, BlockHeader}; use rustreexo::accumulator::stump::Stump; -use crate::{BestChain, ChainParams, ChainState, ChainStore}; +use super::chain_state::{BestChain, ChainState}; +use super::chainparams::ChainParams; +use super::chainstore::ChainStore; #[derive(Clone, Debug)] pub enum BlockchainBuilderError { @@ -17,6 +19,7 @@ pub struct ChainStateBuilder { chain_params: Option, assume_valid: Option<(BlockHash, u32)>, tip: Option<(BlockHash, u32)>, + first: Option, } impl ChainStateBuilder { @@ -28,6 +31,7 @@ impl ChainStateBuilder { chain_params: None, assume_valid: None, tip: None, + first: None, } } pub fn build(self) -> Result, BlockchainBuilderError> { @@ -37,12 +41,28 @@ impl ChainStateBuilder { if self.chain_params.is_none() { return Err(BlockchainBuilderError::MissingChainParams); } + if let Some(first) = self.first { + self.chainstore + .as_ref() + .unwrap() + .save_header(&crate::DiskBlockHeader::FullyValid( + first, + self.tip.unwrap().1, + )) + .unwrap(); + self.chainstore + .as_ref() + .unwrap() + .update_block_index(self.tip.unwrap().1, self.tip.unwrap().0) + .unwrap(); + } Ok(ChainState::from(self)) } pub fn with_chainstore(mut self, chainstore: T) -> Self { self.chainstore = Some(chainstore); self } + pub fn toggle_ibd(mut self, ibd: bool) -> Self { self.ibd = ibd; self @@ -59,8 +79,9 @@ impl ChainStateBuilder { self.acc = Some(acc); self } - pub fn with_tip(mut self, tip: (BlockHash, u32)) -> Self { + pub fn with_tip(mut self, tip: (BlockHash, u32), header: BlockHeader) -> Self { self.tip = Some(tip); + self.first = Some(header); self } pub fn acc(&self) -> Stump { diff --git a/crates/floresta-chain/src/pruned_utreexo/chainparams.rs b/crates/floresta-chain/src/pruned_utreexo/chainparams.rs index c1d99a41..a24a8920 100644 --- a/crates/floresta-chain/src/pruned_utreexo/chainparams.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chainparams.rs @@ -3,10 +3,15 @@ use crate::prelude::*; use alloc::vec::Vec; use crate::Network; + +#[cfg(feature = "bitcoinconsensus")] use bitcoin::{ bitcoinconsensus::{VERIFY_NONE, VERIFY_P2SH, VERIFY_WITNESS}, - blockdata::constants::{genesis_block, max_target}, hashes::hex::FromHex, +}; + +use bitcoin::{ + blockdata::constants::{genesis_block, max_target}, util::uint::Uint256, Block, BlockHash, }; @@ -71,29 +76,38 @@ impl ChainParams { } } } +#[cfg(feature = "bitcoinconsensus")] +fn get_exceptions() -> HashMap { + // For some reason, some blocks in the mainnet and testnet have different rules than it should + // be, so we need to keep a list of exceptions and treat them differently + let mut exceptions = HashMap::new(); + exceptions.insert( + BlockHash::from_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22") + .unwrap(), + VERIFY_NONE, + ); // BIP16 exception on main net + exceptions.insert( + BlockHash::from_hex("0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad") + .unwrap(), + VERIFY_P2SH | VERIFY_WITNESS, + ); // Taproot exception on main net + exceptions.insert( + BlockHash::from_hex("00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105") + .unwrap(), + VERIFY_NONE, + ); // BIP16 exception on test net + exceptions +} + +#[cfg(not(feature = "bitcoinconsensus"))] +fn get_exceptions() -> HashMap { + HashMap::new() +} impl From for ChainParams { fn from(net: Network) -> Self { let genesis = genesis_block(net.into()); let max_target = ChainParams::max_target(net); - // For some reason, some blocks in the mainnet and testnet have different rules than it should - // be, so we need to keep a list of exceptions and treat them differently - let mut exceptions = HashMap::new(); - exceptions.insert( - BlockHash::from_hex("00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22") - .unwrap(), - VERIFY_NONE, - ); // BIP16 exception on main net - exceptions.insert( - BlockHash::from_hex("0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad") - .unwrap(), - VERIFY_P2SH | VERIFY_WITNESS, - ); // Taproot exception on main net - exceptions.insert( - BlockHash::from_hex("00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105") - .unwrap(), - VERIFY_NONE, - ); // BIP16 exception on test net - + let exceptions = get_exceptions(); match net { Network::Bitcoin => ChainParams { genesis, diff --git a/crates/floresta-chain/src/pruned_utreexo/chainstore.rs b/crates/floresta-chain/src/pruned_utreexo/chainstore.rs index f8827720..9619810e 100644 --- a/crates/floresta-chain/src/pruned_utreexo/chainstore.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chainstore.rs @@ -3,7 +3,6 @@ //! This is a basic kv database that stores all metadata about our blockchain and utreexo //! state. //! Author: Davidson Souza -type Result = core::result::Result; use crate::prelude::*; use bitcoin::{ @@ -110,24 +109,25 @@ impl Encodable for DiskBlockHeader { } use kv::{Config, Integer, Store}; -use super::chain_state::BestChain; +use super::{chain_state::BestChain, error::DatabaseError}; pub trait ChainStore { + type Error: DatabaseError; /// Saves the current state of our accumulator. - fn save_roots(&self, roots: Vec) -> Result<()>; + fn save_roots(&self, roots: Vec) -> Result<(), Self::Error>; /// Loads the state of our accumulator. - fn load_roots(&self) -> Result>>; + fn load_roots(&self) -> Result>, Self::Error>; /// Loads the blockchain height - fn load_height(&self) -> Result>; - fn save_height(&self, height: &BestChain) -> Result<()>; - fn get_header(&self, block_hash: &BlockHash) -> Result>; - fn save_header(&self, header: &DiskBlockHeader) -> Result<()>; - fn get_block_hash(&self, height: u32) -> Result>; - fn flush(&self) -> Result<()>; - fn update_block_index(&self, height: u32, hash: BlockHash) -> Result<()>; + fn load_height(&self) -> Result, Self::Error>; + fn save_height(&self, height: &BestChain) -> Result<(), Self::Error>; + fn get_header(&self, block_hash: &BlockHash) -> Result, Self::Error>; + fn save_header(&self, header: &DiskBlockHeader) -> Result<(), Self::Error>; + fn get_block_hash(&self, height: u32) -> Result, Self::Error>; + fn flush(&self) -> Result<(), Self::Error>; + fn update_block_index(&self, height: u32, hash: BlockHash) -> Result<(), Self::Error>; } pub struct KvChainStore(Store); impl KvChainStore { - pub fn new(datadir: String) -> Result { + pub fn new(datadir: String) -> Result { // Configure the database let cfg = Config::new(datadir + "/chain_data").cache_capacity(100_000_000); @@ -138,18 +138,19 @@ impl KvChainStore { } } impl ChainStore for KvChainStore { - fn load_roots(&self) -> Result>> { + type Error = kv::Error; + fn load_roots(&self) -> Result>, Self::Error> { let bucket = self.0.bucket::<&str, Vec>(None)?; bucket.get(&"roots") } - fn save_roots(&self, roots: Vec) -> Result<()> { + fn save_roots(&self, roots: Vec) -> Result<(), Self::Error> { let bucket = self.0.bucket::<&str, Vec>(None)?; bucket.set(&"roots", &roots)?; Ok(()) } - fn load_height(&self) -> Result> { + fn load_height(&self) -> Result, Self::Error> { let bucket = self.0.bucket::<&str, Vec>(None)?; let height = bucket.get(&"height")?; @@ -159,13 +160,13 @@ impl ChainStore for KvChainStore { Ok(None) } - fn save_height(&self, height: &BestChain) -> Result<()> { + fn save_height(&self, height: &BestChain) -> Result<(), Self::Error> { let bucket = self.0.bucket::<&str, Vec>(None)?; let height = serialize(height); bucket.set(&"height", &height)?; Ok(()) } - fn get_header(&self, block_hash: &BlockHash) -> Result> { + fn get_header(&self, block_hash: &BlockHash) -> Result, Self::Error> { let bucket = self.0.bucket::<&[u8], Vec>(Some("header"))?; let block_hash = serialize(&block_hash); @@ -175,7 +176,7 @@ impl ChainStore for KvChainStore { } Ok(None) } - fn flush(&self) -> Result<()> { + fn flush(&self) -> Result<(), Self::Error> { // Flush the header bucket let bucket = self.0.bucket::<&[u8], Vec>(Some("header"))?; bucket.flush()?; @@ -188,7 +189,7 @@ impl ChainStore for KvChainStore { Ok(()) } - fn save_header(&self, header: &DiskBlockHeader) -> Result<()> { + fn save_header(&self, header: &DiskBlockHeader) -> Result<(), Self::Error> { let ser_header = serialize(header); let block_hash = serialize(&header.block_hash()); let bucket = self.0.bucket::<&[u8], Vec>(Some("header"))?; @@ -196,7 +197,7 @@ impl ChainStore for KvChainStore { Ok(()) } - fn get_block_hash(&self, height: u32) -> Result> { + fn get_block_hash(&self, height: u32) -> Result, Self::Error> { let bucket = self.0.bucket::>(Some("index"))?; let block = bucket.get(&Integer::from(height))?; if let Some(block) = block { @@ -205,7 +206,7 @@ impl ChainStore for KvChainStore { Ok(None) } - fn update_block_index(&self, height: u32, hash: BlockHash) -> Result<()> { + fn update_block_index(&self, height: u32, hash: BlockHash) -> Result<(), Self::Error> { let bucket = self.0.bucket::>(Some("index"))?; let block_hash = serialize(&hash); diff --git a/crates/floresta-chain/src/pruned_utreexo/consensus.rs b/crates/floresta-chain/src/pruned_utreexo/consensus.rs index 74177d57..4ae18209 100644 --- a/crates/floresta-chain/src/pruned_utreexo/consensus.rs +++ b/crates/floresta-chain/src/pruned_utreexo/consensus.rs @@ -16,7 +16,11 @@ use core::ffi::c_uint; use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof, stump::Stump}; use sha2::{Digest, Sha512_256}; -use crate::{BlockValidationErrors, BlockchainError, ChainParams}; +use super::{ + chainparams::ChainParams, + error::{BlockValidationErrors, BlockchainError}, +}; + /// This struct contains all the information and methods needed to validate a block, /// it is used by the [ChainState] to validate blocks and transactions. #[derive(Debug, Clone)] @@ -80,6 +84,7 @@ impl Consensus { /// - The transaction must not have duplicate inputs /// - The transaction must not spend more coins than it claims in the inputs /// - The transaction must have valid scripts + #[allow(unused)] pub fn verify_block_transactions( mut utxos: HashMap, transactions: &[Transaction], @@ -118,6 +123,7 @@ impl Consensus { // Fee is the difference between inputs and outputs fee += in_value - output_value; // Verify the tx script + #[cfg(feature = "bitcoinconsensus")] if verify_script { transaction.verify_with_flags(|outpoint| utxos.remove(outpoint), flags)?; } @@ -180,7 +186,7 @@ impl Consensus { .map(|hash| NodeHash::from(hash.into_inner())) .collect::>(); // Verify the proof of inclusion of the deleted nodes - if !proof.verify(&del_hashes, acc)? { + if !acc.verify(&proof, &del_hashes)? { return Err(BlockValidationErrors::InvalidProof.into()); } // Get inputs from the block, we'll need this HashSet to check if an output is spent diff --git a/crates/floresta-chain/src/pruned_utreexo/error.rs b/crates/floresta-chain/src/pruned_utreexo/error.rs index ad12a79a..fb8fd005 100644 --- a/crates/floresta-chain/src/pruned_utreexo/error.rs +++ b/crates/floresta-chain/src/pruned_utreexo/error.rs @@ -1,9 +1,11 @@ use crate::prelude::*; + use bitcoin::blockdata::script; #[cfg(feature = "cli-blockchain")] use btcd_rpc::error::UtreexodError; +use core::fmt::Debug; use floresta_common::impl_error_from; - +pub trait DatabaseError: Debug + Send + Sync + 'static {} #[derive(Debug)] pub enum BlockchainError { BlockNotPresent, @@ -14,7 +16,7 @@ pub enum BlockchainError { BlockValidationError(BlockValidationErrors), InvalidProof, UtreexoError(String), - DatabaseError(kv::Error), + DatabaseError(Box), ConsensusDecodeError(bitcoin::consensus::encode::Error), ChainNotInitialized, InvalidTip(String), @@ -22,9 +24,9 @@ pub enum BlockchainError { IoError(ioError), } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum BlockValidationErrors { - InvalidTx, + InvalidTx(String), NotEnoughPow, BadMerkleRoot, BadWitnessCommitment, @@ -39,8 +41,8 @@ pub enum BlockValidationErrors { impl Display for BlockValidationErrors { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - BlockValidationErrors::InvalidTx => { - write!(f, "This block contains an invalid transaction") + BlockValidationErrors::InvalidTx(e) => { + write!(f, "This block contains an invalid transaction {}", e) } BlockValidationErrors::NotEnoughPow => { write!(f, "This block doesn't have enough proof-of-work") @@ -67,7 +69,13 @@ impl Display for BlockValidationErrors { } } } -impl_error_from!(BlockchainError, kv::Error, DatabaseError); + +impl From for BlockchainError { + fn from(value: T) -> Self { + BlockchainError::DatabaseError(Box::new(value)) + } +} + impl_error_from!(BlockchainError, ioError, IoError); impl_error_from!( BlockchainError, @@ -78,3 +86,5 @@ impl_error_from!(BlockchainError, BlockValidationErrors, BlockValidationError); impl_error_from!(BlockchainError, bitcoin::hashes::hex::Error, ParsingError); impl_error_from!(BlockchainError, String, UtreexoError); impl_error_from!(BlockchainError, script::Error, ScriptValidationFailed); + +impl DatabaseError for kv::Error {} diff --git a/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs b/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs index 81588eaf..669f32ad 100644 --- a/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs +++ b/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs @@ -4,15 +4,20 @@ //! the sync in parallel. use floresta_common::prelude::*; - -use super::consensus::Consensus; -use bitcoin::{bitcoinconsensus, BlockHeader}; +extern crate alloc; +use super::{ + chainparams::ChainParams, + consensus::Consensus, + error::{BlockValidationErrors, BlockchainError}, +}; +#[cfg(feature = "bitcoinconsensus")] +use bitcoin::bitcoinconsensus; +use bitcoin::BlockHeader; +#[cfg(feature = "bitcoinconsensus")] use core::ffi::c_uint; use log::info; use rustreexo::accumulator::stump::Stump; -use crate::{BlockValidationErrors, BlockchainError, ChainParams}; - /// A partial chain is a chain that only contains a subset of the blocks in the /// full chain. We use multiple partial chains to sync up with the full chain, /// and then merge them together to get the full chain. This allows us to conduct @@ -57,7 +62,7 @@ impl PartialChainState { } /// Returns the validation error, if any pub fn error(&self) -> Option { - self.error + self.error.clone() } /// Returns the height we have synced up to so far pub fn current_height(&self) -> u32 { @@ -71,6 +76,7 @@ impl PartialChainState { let index = height - self.initial_height; self.blocks.get(index as usize) } + #[cfg(feature = "bitcoinconsensus")] /// Returns the validation flags, given the current block height fn get_validation_flags(&self, height: u32) -> c_uint { let chains_params = &self.consensus.parameters; @@ -194,7 +200,10 @@ impl PartialChainState { // Validate block transactions let subsidy = self.consensus.get_subsidy(height); let verify_script = self.assume_valid; + #[cfg(feature = "bitcoinconsensus")] let flags = self.get_validation_flags(height); + #[cfg(not(feature = "bitcoinconsensus"))] + let flags = 0; let valid = Consensus::verify_block_transactions( inputs, &block.txdata, @@ -204,7 +213,7 @@ impl PartialChainState { )?; if !valid { return Err(BlockchainError::BlockValidationError( - BlockValidationErrors::InvalidTx, + BlockValidationErrors::InvalidTx(String::from("invalid block transactions")), )); } Ok(()) @@ -216,11 +225,15 @@ mod tests { use core::str::FromStr; use std::collections::HashMap; - use crate::{BlockValidationErrors, Network}; use bitcoin::{consensus::deserialize, Block}; use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof, stump::Stump}; - use crate::{pruned_utreexo::consensus::Consensus, ChainParams}; + use crate::{ + pruned_utreexo::{ + chainparams::ChainParams, consensus::Consensus, error::BlockValidationErrors, + }, + Network, + }; use super::PartialChainState; #[test] diff --git a/crates/floresta-common/Cargo.toml b/crates/floresta-common/Cargo.toml index 56c50f03..e88f7f1b 100644 --- a/crates/floresta-common/Cargo.toml +++ b/crates/floresta-common/Cargo.toml @@ -9,4 +9,8 @@ license = "MIT" [dependencies] sha2 = "^0.10.6" bitcoin = "0.29.2" -miniscript = { git = "https://github.com/douglaz/rust-miniscript.git", branch = "master-2023-03-30" } +miniscript = { git = "https://github.com/douglaz/rust-miniscript.git", optional = true, branch = "master-2023-03-30" } + +[features] +default = ["descriptors"] +descriptors = ["miniscript"] diff --git a/crates/floresta-common/src/lib.rs b/crates/floresta-common/src/lib.rs index 9b52adec..7ffa11f0 100644 --- a/crates/floresta-common/src/lib.rs +++ b/crates/floresta-common/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] use bitcoin::hashes::{sha256, Hash}; use bitcoin::Script; +#[cfg(feature = "descriptors")] use miniscript::{Descriptor, DescriptorPublicKey}; use prelude::*; @@ -18,6 +19,7 @@ pub fn get_spk_hash(spk: &Script) -> sha256::Hash { hash.reverse(); sha256::Hash::from_slice(hash.as_slice()).expect("Engines shouldn't be Err") } +#[cfg(feature = "descriptors")] pub fn parse_descriptors( descriptors: &[String], ) -> Result>, miniscript::Error> { @@ -37,7 +39,7 @@ pub fn parse_descriptors( #[cfg(feature = "no-std")] pub mod prelude { extern crate alloc; - pub use alloc::{borrow::ToOwned, string::String, vec, vec::Vec}; + pub use alloc::{borrow::ToOwned, boxed::Box, string::String, vec, vec::Vec}; pub use core::{ cmp, convert, core::str::FromStr, @@ -59,6 +61,7 @@ pub mod prelude { extern crate std; pub use std::borrow::ToOwned; pub use std::{ + boxed::Box, collections::{hash_map::Entry, HashMap, HashSet}, error::Error, fmt::{self, Display, Formatter}, diff --git a/crates/floresta-electrum/Cargo.toml b/crates/floresta-electrum/Cargo.toml index 2f72924c..8ad79ffd 100644 --- a/crates/floresta-electrum/Cargo.toml +++ b/crates/floresta-electrum/Cargo.toml @@ -8,7 +8,7 @@ floresta-common = { path = "../floresta-common" } floresta-chain = { path = "../floresta-chain" } floresta-watch-only = { path = "../floresta-watch-only" } -rustreexo = { git = "https://www.github.com/mit-dci/rustreexo" } +rustreexo = "0.1.0" btcd-rpc = { git = "https://github.com/Davidson-Souza/rust-btcd-rpc", features = [ "utreexod", ], branch = "use-reqwest", optional = true } diff --git a/crates/floresta-electrum/src/electrum_protocol.rs b/crates/floresta-electrum/src/electrum_protocol.rs index 71c43b9e..8f11008e 100644 --- a/crates/floresta-electrum/src/electrum_protocol.rs +++ b/crates/floresta-electrum/src/electrum_protocol.rs @@ -101,10 +101,10 @@ impl ElectrumServer { .chain .get_block_hash(height as u32) .map_err(|_| super::error::Error::InvalidParams)?; - let header = self.chain.get_block_header(&hash).map_err(|val| { - println!("Error: {:?}", val); - super::error::Error::ChainError(val) - })?; + let header = self + .chain + .get_block_header(&hash) + .map_err(super::error::Error::ChainError)?; let header = serialize(&header).to_hex(); json_rpc_res!(request, header) } diff --git a/crates/floresta-wire/Cargo.toml b/crates/floresta-wire/Cargo.toml index e2b5aaed..31673121 100644 --- a/crates/floresta-wire/Cargo.toml +++ b/crates/floresta-wire/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -rustreexo = { git = "https://www.github.com/mit-dci/rustreexo" } +rustreexo = "0.1.0" btcd-rpc = { git = "https://github.com/Davidson-Souza/rust-btcd-rpc", features = [ "utreexod", ], branch = "use-reqwest", optional = true } @@ -31,4 +31,4 @@ pretty_assertions = "1" [features] default = [] -pruned_utreexo_chainstate = [] +pruned_utreexo_chainstate = [] \ No newline at end of file diff --git a/crates/floresta-wire/src/lib.rs b/crates/floresta-wire/src/lib.rs index 05388279..80fb4b20 100644 --- a/crates/floresta-wire/src/lib.rs +++ b/crates/floresta-wire/src/lib.rs @@ -1,7 +1,9 @@ +#[cfg(not(target_arch = "wasm32"))] mod p2p_wire; +#[cfg(not(target_arch = "wasm32"))] +pub use p2p_wire::{address_man, mempool, node, node_context, node_interface}; use bitcoin::{Block, BlockHeader, Transaction}; -pub use p2p_wire::{address_man, mempool, node, node_context, node_interface}; /// NodeHooks is a trait that defines the hooks that a node can use to interact with the network /// and the blockchain. Every time an event happens, the node will call the corresponding hook. diff --git a/crates/floresta-wire/src/p2p_wire/address_man.rs b/crates/floresta-wire/src/p2p_wire/address_man.rs index d226eb6e..afa2ef46 100644 --- a/crates/floresta-wire/src/p2p_wire/address_man.rs +++ b/crates/floresta-wire/src/p2p_wire/address_man.rs @@ -16,6 +16,7 @@ use std::{ }; use thiserror::Error; const RETRY_TIME: u64 = 60 * 60; // 1 hour +type AddressToSend = Vec<(AddrV2, u64, ServiceFlags, u16)>; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum AddressState { @@ -157,7 +158,7 @@ impl AddressMan { } } } - pub fn get_addresses_to_send(&self) -> Result, ()> { + pub fn get_addresses_to_send(&self) -> AddressToSend { let addresses = self .addresses .iter() @@ -180,7 +181,7 @@ impl AddressMan { } }) .collect(); - Ok(addresses) + addresses } pub fn get_seeds_from_dns( &mut self, diff --git a/crates/floresta-wire/src/p2p_wire/node.rs b/crates/floresta-wire/src/p2p_wire/node.rs index cbbf0e6d..1ab1f14b 100644 --- a/crates/floresta-wire/src/p2p_wire/node.rs +++ b/crates/floresta-wire/src/p2p_wire/node.rs @@ -984,7 +984,6 @@ impl UtreexoNode Result<(), PeerError> { loop { - let mut data: Vec = Vec::new(); - data.resize(24, 0); + let mut data: Vec = vec![0; 24]; // Read the reader first, so learn the payload size self.source.read_exact(&mut data).await?; diff --git a/crates/floresta/Cargo.toml b/crates/floresta/Cargo.toml index 947c3f3b..2a25162c 100644 --- a/crates/floresta/Cargo.toml +++ b/crates/floresta/Cargo.toml @@ -16,11 +16,13 @@ categories = ["bitcoin", "blockchain", "node"] floresta-common = { path = "../floresta-common" } floresta-chain = { path = "../floresta-chain" } floresta-wire = { path = "../floresta-wire" } -floresta-watch-only = { path = "../floresta-watch-only", features = ["memory-database" ] } +floresta-watch-only = { path = "../floresta-watch-only", features = [ + "memory-database", +] } hashbrown = "0.14.0" [dev-dependencies] -rustreexo = { git = "https://www.github.com/mit-dci/rustreexo" } +rustreexo = "0.1.0" miniscript = "10.0.0" async-std = "1.12.0" bitcoin = { version = "0.29", features = [ @@ -29,6 +31,9 @@ bitcoin = { version = "0.29", features = [ "bitcoinconsensus", ] } +[features] +bitcoinconsensus = ["floresta-chain/bitcoinconsensus"] + [lib] crate-type = ["cdylib", "rlib", "staticlib"] diff --git a/crates/floresta/examples/chainstate-builder.rs b/crates/floresta/examples/chainstate-builder.rs index 8beacf7a..10d794bd 100644 --- a/crates/floresta/examples/chainstate-builder.rs +++ b/crates/floresta/examples/chainstate-builder.rs @@ -5,6 +5,7 @@ //! the simplest way to create a node, but you can also create a node that starts at a given //! block, or that doesn't validate all signatures. All customizations are done through the //! ChainStateBuilder struct. This example shows how to use it. +use bitcoin::blockdata::constants::genesis_block; use bitcoin::hashes::Hash; use bitcoin::BlockHash; use floresta::chain::{ChainState, KvChainStore, Network}; @@ -12,7 +13,6 @@ use floresta::chain::{ChainState, KvChainStore, Network}; use floresta_chain::pruned_utreexo::chain_state_builder::ChainStateBuilder; use floresta_chain::ChainParams; use rustreexo::accumulator::stump::Stump; -use std::str::FromStr; const DATA_DIR: &str = "./data"; @@ -44,7 +44,10 @@ async fn main() { let _chain: ChainState = ChainStateBuilder::new() .with_assume_valid((BlockHash::all_zeros(), 0)) .with_chain_params(ChainParams::from(Network::Bitcoin)) - .with_tip((BlockHash::from_str("").unwrap(), 0)) + .with_tip( + (genesis_block(bitcoin::Network::Bitcoin).block_hash(), 0), + genesis_block(bitcoin::Network::Bitcoin).header, + ) .assume_utreexo(Stump::new()) .with_chainstore(chain_store) .build() diff --git a/florestad/Cargo.toml b/florestad/Cargo.toml index ff557e8d..55707851 100644 --- a/florestad/Cargo.toml +++ b/florestad/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -rustreexo = { git = "https://www.github.com/mit-dci/rustreexo" } +rustreexo = "0.1.0" btcd-rpc = { git = "https://github.com/Davidson-Souza/rust-btcd-rpc", features = [ "utreexod", ], branch = "use-reqwest", optional = true } diff --git a/florestad/src/main.rs b/florestad/src/main.rs index 59f50b96..07f3bbc1 100644 --- a/florestad/src/main.rs +++ b/florestad/src/main.rs @@ -418,10 +418,10 @@ where fn get_both_vec(a: Option>, b: Option>) -> Vec { let mut result: Vec = Vec::new(); if let Some(a) = a { - result.extend(a.into_iter()); + result.extend(a); } if let Some(b) = b { - result.extend(b.into_iter()); + result.extend(b); } result } From a1d02cf285c523127c14f2bc947661137a1204d6 Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Wed, 23 Aug 2023 22:05:42 -0300 Subject: [PATCH 010/328] Improve support to wasm (#71) * Refactor the ChainStore trait and remove hardcoded KvDatabase * Make libbitcoinconsensus an optional feature * Improve handling for assumeutxo Even if we assumeutxo, we still need the headers. They are used to compute leaf hashes, retarget(at least the block TIP-2015) and in case of reorgs. This commit allows you to push headers to the chain without validating them. From b03be380821b1b02cc9d57fd1505e7510a6ede38 Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Thu, 24 Aug 2023 18:28:02 -0300 Subject: [PATCH 011/328] Feature/refactor manifests (#72) * Refactor the ChainStore trait and remove hardcoded KvDatabase * Make libbitcoinconsensus an optional feature * Refactor manifests and features This commit removes a few unused dependeces, expose features in the manifests and fill up some metainfo. * Improve all manifests with useful keys --- Cargo.lock | 526 +----------------- crates/floresta-chain/Cargo.toml | 17 +- .../src/pruned_utreexo/chain_state.rs | 40 +- .../src/pruned_utreexo/chain_state_builder.rs | 2 +- .../src/pruned_utreexo/chainstore.rs | 18 +- .../src/pruned_utreexo/error.rs | 7 + .../floresta-chain/src/pruned_utreexo/mod.rs | 78 ++- .../src/pruned_utreexo/partial_chain.rs | 4 +- crates/floresta-cli/Cargo.toml | 9 + crates/floresta-common/Cargo.toml | 5 +- crates/floresta-electrum/Cargo.toml | 19 +- .../src/electrum_protocol.rs | 21 +- crates/floresta-electrum/src/error.rs | 6 +- crates/floresta-watch-only/Cargo.toml | 7 +- crates/floresta-wire/Cargo.toml | 20 +- crates/floresta-wire/src/p2p_wire/node.rs | 12 +- crates/floresta/Cargo.toml | 15 +- crates/floresta/src/lib.rs | 3 + florestad/Cargo.toml | 4 - 19 files changed, 207 insertions(+), 606 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a931bf9..d51f4f03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,7 +119,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -129,7 +129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -279,12 +279,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "base64" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" - [[package]] name = "bech32" version = "0.9.1" @@ -393,18 +387,6 @@ dependencies = [ "serde", ] -[[package]] -name = "btcd-rpc" -version = "0.1.0" -source = "git+https://github.com/Davidson-Souza/rust-btcd-rpc?branch=use-reqwest#7a0ecf02b17ff83e90d49360406f3012fb9605ca" -dependencies = [ - "json_types", - "reqwest", - "serde", - "serde_json", - "tokio", -] - [[package]] name = "bumpalo" version = "3.13.0" @@ -507,22 +489,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - [[package]] name = "core2" version = "0.3.3" @@ -608,7 +574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" dependencies = [ "nix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -672,15 +638,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "env_logger" version = "0.7.1" @@ -702,7 +659,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -736,11 +693,12 @@ version = "0.1.0" dependencies = [ "async-std", "bitcoin 0.29.2", + "floresta", "floresta-chain", "floresta-common", + "floresta-electrum", "floresta-watch-only", "floresta-wire", - "hashbrown 0.14.0", "miniscript 10.0.0", "rustreexo", ] @@ -758,7 +716,6 @@ dependencies = [ "hex", "kv", "log", - "pretty_assertions", "rand 0.8.5", "rustreexo", "secp256k1 0.24.3", @@ -766,7 +723,6 @@ dependencies = [ "serde_json", "sha2", "spin", - "wasm-bindgen", "zstd", ] @@ -797,7 +753,7 @@ version = "0.1.0" dependencies = [ "async-std", "bitcoin 0.29.2", - "btcd-rpc", + "core2 0.4.0", "floresta-chain", "floresta-common", "floresta-watch-only", @@ -805,9 +761,6 @@ dependencies = [ "kv", "log", "miniscript 9.0.0", - "pretty_assertions", - "rand 0.8.5", - "rmp-serde", "rustreexo", "serde", "serde_json", @@ -820,12 +773,10 @@ dependencies = [ name = "floresta-watch-only" version = "0.1.0" dependencies = [ - "anyhow", "bitcoin 0.29.2", "floresta-common", "kv", "log", - "rand 0.8.5", "serde", "serde_json", "thiserror", @@ -837,8 +788,6 @@ version = "0.1.0" dependencies = [ "async-std", "bitcoin 0.29.2", - "btcd-rpc", - "ctrlc", "dns-lookup", "floresta-chain", "floresta-common", @@ -846,9 +795,7 @@ dependencies = [ "kv", "log", "oneshot", - "pretty_assertions", "rand 0.8.5", - "rmp-serde", "rustreexo", "serde", "serde_json", @@ -864,7 +811,6 @@ dependencies = [ "anyhow", "async-std", "bitcoin 0.29.2", - "btcd-rpc", "clap", "ctrlc", "dirs", @@ -884,7 +830,6 @@ dependencies = [ "pretty_assertions", "pretty_env_logger", "rand 0.8.5", - "rmp-serde", "rustreexo", "serde", "serde_json", @@ -899,30 +844,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" -dependencies = [ - "percent-encoding 2.3.0", -] - [[package]] name = "fs2" version = "0.4.3" @@ -1124,25 +1045,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "h2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util 0.7.8", - "tracing", -] - [[package]] name = "hashbrown" version = "0.8.2" @@ -1153,12 +1055,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.0" @@ -1264,7 +1160,6 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", "http", "http-body", "httparse", @@ -1278,19 +1173,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "idna" version = "0.1.5" @@ -1302,26 +1184,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "instant" version = "0.1.12" @@ -1339,15 +1201,9 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys 0.48.0", + "windows-sys", ] -[[package]] -name = "ipnet" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" - [[package]] name = "is-terminal" version = "0.4.7" @@ -1357,7 +1213,7 @@ dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1384,22 +1240,13 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json_types" -version = "0.1.0" -source = "git+https://github.com/Davidson-Souza/rust-btcd-rpc?branch=use-reqwest#7a0ecf02b17ff83e90d49360406f3012fb9605ca" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "jsonrpc" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8128f36b47411cd3f044be8c1f5cc0c9e24d1d1bfdc45f0a57897b32513053f2" dependencies = [ - "base64 0.13.1", + "base64", "serde", "serde_json", ] @@ -1419,7 +1266,7 @@ dependencies = [ "serde", "serde_json", "tokio", - "url 1.7.2", + "url", ] [[package]] @@ -1504,7 +1351,7 @@ dependencies = [ "log", "tokio", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util", "unicase", ] @@ -1611,12 +1458,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "miniscript" version = "9.0.0" @@ -1643,25 +1484,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "windows-sys", ] [[package]] @@ -1697,15 +1520,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - [[package]] name = "num_cpus" version = "1.15.0" @@ -1731,60 +1545,6 @@ dependencies = [ "loom", ] -[[package]] -name = "openssl" -version = "0.10.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.18", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-src" -version = "111.26.0+1.1.1u" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - [[package]] name = "output_vt100" version = "0.1.3" @@ -1854,24 +1614,12 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "paste" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" - [[package]] name = "percent-encoding" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" -[[package]] -name = "percent-encoding" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1903,7 +1651,7 @@ dependencies = [ "libc", "log", "pin-project-lite", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2099,65 +1847,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" -[[package]] -name = "reqwest" -version = "0.11.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" -dependencies = [ - "base64 0.21.2", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding 2.3.0", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "tower-service", - "url 2.4.0", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - -[[package]] -name = "rmp" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" -dependencies = [ - "byteorder", - "num-traits", - "paste", -] - -[[package]] -name = "rmp-serde" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b13be192e0220b8afb7222aa5813cb62cc269ebb5cac346ca6487681d2913e" -dependencies = [ - "byteorder", - "rmp", - "serde", -] - [[package]] name = "rustc_version" version = "0.4.0" @@ -2178,7 +1867,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2202,15 +1891,6 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -2262,29 +1942,6 @@ dependencies = [ "cc", ] -[[package]] -name = "security-framework" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.17" @@ -2322,18 +1979,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha2" version = "0.10.7" @@ -2457,20 +2102,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tempfile" -version = "3.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" -dependencies = [ - "autocfg", - "cfg-if 1.0.0", - "fastrand", - "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "termcolor" version = "1.2.0" @@ -2541,7 +2172,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2555,16 +2186,6 @@ dependencies = [ "syn 2.0.18", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-stream" version = "0.1.14" @@ -2590,20 +2211,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-util" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - [[package]] name = "toml" version = "0.5.11" @@ -2729,20 +2336,9 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" dependencies = [ - "idna 0.1.5", + "idna", "matches", - "percent-encoding 1.0.1", -] - -[[package]] -name = "url" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" -dependencies = [ - "form_urlencoded", - "idna 0.4.0", - "percent-encoding 2.3.0", + "percent-encoding", ] [[package]] @@ -2763,12 +2359,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -2918,21 +2508,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -2948,108 +2523,57 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - [[package]] name = "yansi" version = "0.5.1" diff --git a/crates/floresta-chain/Cargo.toml b/crates/floresta-chain/Cargo.toml index 5f958ec7..0f71421a 100644 --- a/crates/floresta-chain/Cargo.toml +++ b/crates/floresta-chain/Cargo.toml @@ -2,6 +2,17 @@ name = "floresta-chain" version = "0.1.0" edition = "2021" +authors = ["Davidson Souza "] +description = """ + Reusable components for building consensus-critical Bitcoin applications. + Using floresta-chain, you can create a Bitcoin node that validates blocks + and transactions, acording to the Bitcoin consensus rules. +""" +repository = "https://github.com/Davidson-Souza/Floresta" +license = "MIT" +readme = "README.md" +keywords = ["bitcoin", "utreexo", "node", "consensus"] +categories = ["bitcoin", "blockchain", "node"] [lib] crate-type = ["cdylib", "rlib"] @@ -16,7 +27,7 @@ bitcoin = { version = "0.29", features = [ "no-std", ], default-features = false } spin = "0.9.8" -core2 = { version = "0.4.0", optional = true } +core2 = { version = "0.4.0", default-features = false } hashbrown = { version = "0.14.0", optional = true } secp256k1 = { version = "*", features = ["alloc"], optional = true } async-std = { version = "1.12.0", default-features = false, features = [ @@ -25,10 +36,8 @@ async-std = { version = "1.12.0", default-features = false, features = [ ] } floresta-common = { path = "../floresta-common", default-features = false } futures = "0.3.28" -wasm-bindgen = "0.2.87" [dev-dependencies] -pretty_assertions = "1" rand = "0.8.5" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -36,4 +45,4 @@ zstd = "0.12.3" hex = "0.4.3" [features] -bitcoinconsensus = ["bitcoin/bitcoinconsensus"] \ No newline at end of file +bitcoinconsensus = ["bitcoin/bitcoinconsensus"] diff --git a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs index 8ec2de7b..31894f28 100644 --- a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs @@ -2,10 +2,10 @@ extern crate alloc; use super::{ chain_state_builder::ChainStateBuilder, chainparams::ChainParams, - chainstore::{ChainStore, DiskBlockHeader, KvChainStore}, + chainstore::{DiskBlockHeader, KvChainStore}, consensus::Consensus, error::{BlockValidationErrors, BlockchainError}, - BlockchainInterface, Notification, UpdatableChainstate, + BlockchainInterface, ChainStore, Notification, UpdatableChainstate, }; use crate::prelude::*; use crate::{read_lock, write_lock, Network}; @@ -490,7 +490,7 @@ impl ChainState { } } } - fn get_disk_block_header(&self, hash: &BlockHash) -> super::Result { + fn get_disk_block_header(&self, hash: &BlockHash) -> Result { let inner = read_lock!(self); if let Some(header) = inner.chainstore.get_header(hash)? { return Ok(header); @@ -699,7 +699,7 @@ impl BlockchainInterface for ChainState super::Result { + fn get_block_hash(&self, height: u32) -> Result { let inner = self.inner.read(); if let Some(hash) = inner.chainstore.get_block_hash(height)? { return Ok(hash); @@ -707,22 +707,22 @@ impl BlockchainInterface for ChainState super::Result> { + fn get_tx(&self, _txid: &bitcoin::Txid) -> Result, Self::Error> { unimplemented!("This chainstate doesn't hold any tx") } - fn get_height(&self) -> super::Result { + fn get_height(&self) -> Result { let inner = read_lock!(self); Ok(inner.best_block.depth) } - fn broadcast(&self, tx: &bitcoin::Transaction) -> super::Result<()> { + fn broadcast(&self, tx: &bitcoin::Transaction) -> Result<(), Self::Error> { let mut inner = write_lock!(self); inner.broadcast_queue.push(tx.clone()); Ok(()) } - fn estimate_fee(&self, target: usize) -> super::Result { + fn estimate_fee(&self, target: usize) -> Result { let inner = read_lock!(self); if target == 1 { Ok(inner.fee_estimation.0) @@ -733,16 +733,16 @@ impl BlockchainInterface for ChainState super::Result { + fn get_block(&self, _hash: &BlockHash) -> Result { unimplemented!("This chainstate doesn't hold full blocks") } - fn get_best_block(&self) -> super::Result<(u32, BlockHash)> { + fn get_best_block(&self) -> Result<(u32, BlockHash), Self::Error> { let inner = read_lock!(self); Ok((inner.best_block.depth, inner.best_block.best_block)) } - fn get_block_header(&self, hash: &BlockHash) -> super::Result { + fn get_block_header(&self, hash: &BlockHash) -> Result { let inner = read_lock!(self); if let Some(header) = inner.chainstore.get_header(hash)? { return Ok(*header); @@ -752,7 +752,7 @@ impl BlockchainInterface for ChainState Option { read_lock!(self).best_block.rescan_index } - fn rescan(&self, start_height: u32) -> super::Result<()> { + fn rescan(&self, start_height: u32) -> Result<(), Self::Error> { let mut inner = write_lock!(self); info!("Rescanning from block {start_height}"); inner.best_block.rescan_index = Some(start_height); @@ -786,7 +786,7 @@ impl BlockchainInterface for ChainState super::Result { + fn get_validation_index(&self) -> Result { let inner = self.inner.read(); let validation = inner.best_block.validation_index; let header = self.get_disk_block_header(&validation)?; @@ -799,7 +799,7 @@ impl BlockchainInterface for ChainState super::Result { + fn is_coinbase_mature(&self, height: u32, block: BlockHash) -> Result { let chain_params = self.chain_params(); let current_height = self.get_disk_block_header(&block)?.height().unwrap_or(0); @@ -811,7 +811,7 @@ impl BlockchainInterface for ChainState UpdatableChainstate for ChainState { - fn invalidate_block(&self, block: BlockHash) -> super::Result<()> { + fn invalidate_block(&self, block: BlockHash) -> Result<(), BlockchainError> { let height = self.get_disk_block_header(&block)?.height(); if height.is_none() { return Err(BlockchainError::BlockNotPresent); @@ -838,7 +838,7 @@ impl UpdatableChainstate for ChainState super::Result<()> { + fn process_rescan_block(&self, block: &Block) -> Result<(), BlockchainError> { let header = self.get_disk_block_header(&block.block_hash())?; let height = header.height().expect("Recaning in an invalid tip"); block_on(self.notify(Notification::NewBlock((block.to_owned(), height)))); @@ -861,7 +861,7 @@ impl UpdatableChainstate for ChainState, del_hashes: Vec, - ) -> super::Result { + ) -> Result { let header = self.get_disk_block_header(&block.block_hash())?; let height = match header { DiskBlockHeader::FullyValid(_, height) => { @@ -900,11 +900,11 @@ impl UpdatableChainstate for ChainState super::Result<()> { + fn handle_transaction(&self) -> Result<(), BlockchainError> { unimplemented!("This chain_state has no mempool") } - fn flush(&self) -> super::Result<()> { + fn flush(&self) -> Result<(), BlockchainError> { self.save_acc()?; let inner = read_lock!(self); inner.chainstore.flush()?; @@ -912,7 +912,7 @@ impl UpdatableChainstate for ChainState super::Result<()> { + fn accept_header(&self, header: BlockHeader) -> Result<(), BlockchainError> { trace!("Accepting header {header:?}"); let _header = self.get_disk_block_header(&header.block_hash()); if _header.is_ok() { diff --git a/crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs b/crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs index b3efac0a..b85091ab 100644 --- a/crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chain_state_builder.rs @@ -4,7 +4,7 @@ use rustreexo::accumulator::stump::Stump; use super::chain_state::{BestChain, ChainState}; use super::chainparams::ChainParams; -use super::chainstore::ChainStore; +use super::ChainStore; #[derive(Clone, Debug)] pub enum BlockchainBuilderError { diff --git a/crates/floresta-chain/src/pruned_utreexo/chainstore.rs b/crates/floresta-chain/src/pruned_utreexo/chainstore.rs index 9619810e..eddb6ebf 100644 --- a/crates/floresta-chain/src/pruned_utreexo/chainstore.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chainstore.rs @@ -2,7 +2,6 @@ //! This is a basic kv database that stores all metadata about our blockchain and utreexo //! state. -//! Author: Davidson Souza use crate::prelude::*; use bitcoin::{ @@ -109,22 +108,7 @@ impl Encodable for DiskBlockHeader { } use kv::{Config, Integer, Store}; -use super::{chain_state::BestChain, error::DatabaseError}; -pub trait ChainStore { - type Error: DatabaseError; - /// Saves the current state of our accumulator. - fn save_roots(&self, roots: Vec) -> Result<(), Self::Error>; - /// Loads the state of our accumulator. - fn load_roots(&self) -> Result>, Self::Error>; - /// Loads the blockchain height - fn load_height(&self) -> Result, Self::Error>; - fn save_height(&self, height: &BestChain) -> Result<(), Self::Error>; - fn get_header(&self, block_hash: &BlockHash) -> Result, Self::Error>; - fn save_header(&self, header: &DiskBlockHeader) -> Result<(), Self::Error>; - fn get_block_hash(&self, height: u32) -> Result, Self::Error>; - fn flush(&self) -> Result<(), Self::Error>; - fn update_block_index(&self, height: u32, hash: BlockHash) -> Result<(), Self::Error>; -} +use super::{chain_state::BestChain, ChainStore}; pub struct KvChainStore(Store); impl KvChainStore { pub fn new(datadir: String) -> Result { diff --git a/crates/floresta-chain/src/pruned_utreexo/error.rs b/crates/floresta-chain/src/pruned_utreexo/error.rs index fb8fd005..205c3521 100644 --- a/crates/floresta-chain/src/pruned_utreexo/error.rs +++ b/crates/floresta-chain/src/pruned_utreexo/error.rs @@ -87,4 +87,11 @@ impl_error_from!(BlockchainError, bitcoin::hashes::hex::Error, ParsingError); impl_error_from!(BlockchainError, String, UtreexoError); impl_error_from!(BlockchainError, script::Error, ScriptValidationFailed); +impl Display for BlockchainError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + impl DatabaseError for kv::Error {} +impl core2::error::Error for BlockchainError {} diff --git a/crates/floresta-chain/src/pruned_utreexo/mod.rs b/crates/floresta-chain/src/pruned_utreexo/mod.rs index 3ea2471d..f665f179 100644 --- a/crates/floresta-chain/src/pruned_utreexo/mod.rs +++ b/crates/floresta-chain/src/pruned_utreexo/mod.rs @@ -8,34 +8,31 @@ pub mod error; pub mod partial_chain; pub mod udata; -use crate::prelude::*; +use crate::{prelude::*, BestChain, BlockchainError, DatabaseError, DiskBlockHeader}; use async_std::channel::Sender; use bitcoin::{hashes::sha256, Block, BlockHash, BlockHeader, OutPoint, Transaction, TxOut}; use rustreexo::accumulator::{node_hash::NodeHash, proof::Proof}; -use self::error::BlockchainError; - -type Result = core::result::Result; /// This trait is the main interface between our blockchain backend and other services. /// It'll be useful for transitioning from rpc to a p2p based node pub trait BlockchainInterface { - type Error; + type Error: core2::error::Error + Send + Sync + 'static; /// Returns the block with a given height in our current tip. - fn get_block_hash(&self, height: u32) -> Result; + fn get_block_hash(&self, height: u32) -> Result; /// Returns a bitcoin [Transaction] given it's txid. - fn get_tx(&self, txid: &bitcoin::Txid) -> Result>; + fn get_tx(&self, txid: &bitcoin::Txid) -> Result, Self::Error>; /// Get the height of our best know chain. - fn get_height(&self) -> Result; + fn get_height(&self) -> Result; /// Broadcasts a transaction to the network. - fn broadcast(&self, tx: &bitcoin::Transaction) -> Result<()>; + fn broadcast(&self, tx: &bitcoin::Transaction) -> Result<(), Self::Error>; /// Returns fee estimation for inclusion in `target` blocks. - fn estimate_fee(&self, target: usize) -> Result; + fn estimate_fee(&self, target: usize) -> Result; /// Returns a block with a given `hash` if any. - fn get_block(&self, hash: &BlockHash) -> Result; + fn get_block(&self, hash: &BlockHash) -> Result; /// Returns the best known block - fn get_best_block(&self) -> Result<(u32, BlockHash)>; + fn get_best_block(&self) -> Result<(u32, BlockHash), Self::Error>; /// Returns associated header for block with `hash` - fn get_block_header(&self, hash: &BlockHash) -> Result; + fn get_block_header(&self, hash: &BlockHash) -> Result; /// Register for receiving notifications for some event. Right now it only works for /// new blocks, but may work with transactions in the future too. fn subscribe(&self, tx: Sender); @@ -44,13 +41,13 @@ pub trait BlockchainInterface { /// Returns the list of unbroadcasted transactions. fn get_unbroadcasted(&self) -> Vec; /// Checks if a coinbase is mature - fn is_coinbase_mature(&self, height: u32, block: BlockHash) -> Result; + fn is_coinbase_mature(&self, height: u32, block: BlockHash) -> Result; /// Returns a block locator - fn get_block_locator(&self) -> Result>; + fn get_block_locator(&self) -> Result, Self::Error>; /// Returns the last block we validated - fn get_validation_index(&self) -> Result; + fn get_validation_index(&self) -> Result; /// Triggers a rescan, downloading (but not validating) all blocks in [start_height:tip] - fn rescan(&self, start_height: u32) -> Result<()>; + fn rescan(&self, start_height: u32) -> Result<(), Self::Error>; fn get_rescan_index(&self) -> Option; } /// [UpdatableChainstate] is a contract that a is expected from a chainstate @@ -66,26 +63,61 @@ pub trait UpdatableChainstate { proof: Proof, inputs: HashMap, del_hashes: Vec, - ) -> Result; + ) -> Result; /// Accepts a new header to our chain. This method is called before connect_block, and /// makes some basic checks on a header and saves it on disk. We only accept a block as /// valid after calling connect_block. - fn accept_header(&self, header: BlockHeader) -> Result<()>; + fn accept_header(&self, header: BlockHeader) -> Result<(), BlockchainError>; /// Not used for now, but in a future blockchain with mempool, we can process transactions /// that are not in a block yet. - fn handle_transaction(&self) -> Result<()>; + fn handle_transaction(&self) -> Result<(), BlockchainError>; /// Persists our data. Should be invoked periodically. - fn flush(&self) -> Result<()>; + fn flush(&self) -> Result<(), BlockchainError>; /// Toggle ibd on/off fn toggle_ibd(&self, is_ibd: bool); /// Tells this blockchain to consider this block invalid, and not build on top of it - fn invalidate_block(&self, block: BlockHash) -> Result<()>; + fn invalidate_block(&self, block: BlockHash) -> Result<(), BlockchainError>; /// Gives a requested block for rescan - fn process_rescan_block(&self, block: &Block) -> Result<()>; + fn process_rescan_block(&self, block: &Block) -> Result<(), BlockchainError>; /// Returns the root hashes of our utreexo forest fn get_root_hashes(&self) -> Vec; } +/// [ChainStore] is a trait defining how we interact with our chain database. This definitions +/// will be used by the [ChainState] to save and retrieve data about the blockchain, likely +/// on disk. +/// Right now, you can use the [KvChainStore] in your code, it implements this trait and +/// uses a key-value store to save data. +/// The [DatabaseError] is a simple trait that can be implemented by any error type that +/// implements [std::error::Error] and [std::fmt::Display]. This is useful to abstract +/// the database implementation from the blockchain. +/// See the documentation of [DatabaseError] for more info. +pub trait ChainStore { + type Error: DatabaseError; + /// Saves the current state of our accumulator. + fn save_roots(&self, roots: Vec) -> Result<(), Self::Error>; + /// Loads the state of our accumulator. + fn load_roots(&self) -> Result>, Self::Error>; + /// Loads the blockchain height + fn load_height(&self) -> Result, Self::Error>; + /// Saves the blockchain height. + fn save_height(&self, height: &BestChain) -> Result<(), Self::Error>; + /// Get a block header from our database. See [DiskBlockHeader] for more info about + /// the data we save. + fn get_header(&self, block_hash: &BlockHash) -> Result, Self::Error>; + /// Saves a block header to our database. See [DiskBlockHeader] for more info about + /// the data we save. + fn save_header(&self, header: &DiskBlockHeader) -> Result<(), Self::Error>; + /// Returns the block hash for a given height. + fn get_block_hash(&self, height: u32) -> Result, Self::Error>; + /// Flushes write buffers to disk, this is called periodically by the [ChainState], + /// so in case of a crash, we don't lose too much data. If the database doesn't support + /// write buffers, this method can be a no-op. + fn flush(&self) -> Result<(), Self::Error>; + /// Associates a block hash with a given height, so we can retrieve it later. + fn update_block_index(&self, height: u32, hash: BlockHash) -> Result<(), Self::Error>; +} + #[derive(Debug, Clone)] /// A notification is a hook that a type implementing [BlockchainInterface] sends each /// time the given event happens. This is use to notify new blocks to the Electrum server. diff --git a/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs b/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs index 669f32ad..f6ea6359 100644 --- a/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs +++ b/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs @@ -123,7 +123,7 @@ impl PartialChainState { } #[inline] /// Returns the ancestor for a given block header - fn get_ancestor(&self, height: u32) -> super::Result { + fn get_ancestor(&self, height: u32) -> Result { let prev = self.get_block(height - 1).unwrap(); Ok(*prev) } @@ -173,7 +173,7 @@ impl PartialChainState { block: &bitcoin::Block, height: u32, inputs: HashMap, - ) -> super::Result<()> { + ) -> Result<(), BlockchainError> { if !block.check_merkle_root() { return Err(BlockchainError::BlockValidationError( BlockValidationErrors::BadMerkleRoot, diff --git a/crates/floresta-cli/Cargo.toml b/crates/floresta-cli/Cargo.toml index 6c9cbd6e..b402a77f 100644 --- a/crates/floresta-cli/Cargo.toml +++ b/crates/floresta-cli/Cargo.toml @@ -4,6 +4,15 @@ version = "0.1.0" edition = "2021" authors = ["Davidson Souza "] license = "MIT" +description = """ + A command line interface for Florestad. You can use this client to interact + with a running Florestad node. +""" +repository = "https://github.com/Davidson-Souza/Floresta" +readme = "README.md" +keywords = ["bitcoin", "utreexo", "node", "blockchain", "rust"] +categories = ["bitcoin", "blockchain", "node"] + [dependencies] jsonrpc = "0.14.1" diff --git a/crates/floresta-common/Cargo.toml b/crates/floresta-common/Cargo.toml index e88f7f1b..9c6531bf 100644 --- a/crates/floresta-common/Cargo.toml +++ b/crates/floresta-common/Cargo.toml @@ -2,9 +2,12 @@ name = "floresta-common" version = "0.1.0" edition = "2021" -description = "Multiple inernal utilities" +description = "Common types and functions for Floresta" authors = ["Davidson Souza "] license = "MIT" +repository = "https://github.com/Davidson-Souza/Floresta" +readme = "README.md" + [dependencies] sha2 = "^0.10.6" diff --git a/crates/floresta-electrum/Cargo.toml b/crates/floresta-electrum/Cargo.toml index 8ad79ffd..255d25b4 100644 --- a/crates/floresta-electrum/Cargo.toml +++ b/crates/floresta-electrum/Cargo.toml @@ -2,6 +2,17 @@ name = "floresta-electrum" version = "0.1.0" edition = "2021" +authors = ["Davidson Souza "] +description = """ + A simple Electrum server implementation for Floresta. It is based on the + Electrum protocol specification and works out of the box with any wallet + that supports Electrum servers. +""" +repository = "https://github.com/Davidson-Souza/Floresta" +license = "MIT" +readme = "README.md" +keywords = ["bitcoin", "utreexo", "node", "blockchain", "rust"] +categories = ["bitcoin", "blockchain", "node"] [dependencies] floresta-common = { path = "../floresta-common" } @@ -9,9 +20,6 @@ floresta-chain = { path = "../floresta-chain" } floresta-watch-only = { path = "../floresta-watch-only" } rustreexo = "0.1.0" -btcd-rpc = { git = "https://github.com/Davidson-Souza/rust-btcd-rpc", features = [ - "utreexod", -], branch = "use-reqwest", optional = true } sha2 = "^0.10.6" async-std = { version = "1.12.0", features = ["attributes"] } log = "0.4" @@ -20,11 +28,8 @@ serde_json = "1.0" kv = "0.24.0" miniscript = { git = "https://github.com/douglaz/rust-miniscript.git", branch = "master-2023-03-30" } futures = "0.3.4" -rmp-serde = { optional = true, version = "1.1.1" } toml = "0.5.10" bitcoin = { version = "0.29", features = ["serde", "std", "bitcoinconsensus"] } thiserror = "1.0" +core2 = { version = "0.4.0", default-features = false } -[dev-dependencies] -pretty_assertions = "1" -rand = "0.8.5" diff --git a/crates/floresta-electrum/src/electrum_protocol.rs b/crates/floresta-electrum/src/electrum_protocol.rs index 8f11008e..e5c3e784 100644 --- a/crates/floresta-electrum/src/electrum_protocol.rs +++ b/crates/floresta-electrum/src/electrum_protocol.rs @@ -104,7 +104,7 @@ impl ElectrumServer { let header = self .chain .get_block_header(&hash) - .map_err(super::error::Error::ChainError)?; + .map_err(|e| super::error::Error::ChainError(Box::new(e)))?; let header = serialize(&header).to_hex(); json_rpc_res!(request, header) } @@ -119,7 +119,10 @@ impl ElectrumServer { .get_block_hash(height as u32) .map_err(|_| super::error::Error::InvalidParams)?; - let header = self.chain.get_block_header(&hash).unwrap(); + let header = self + .chain + .get_block_header(&hash) + .map_err(|e| super::error::Error::ChainError(Box::new(e)))?; let header = serialize(&header).to_hex(); headers.push_str(&header); } @@ -131,8 +134,14 @@ impl ElectrumServer { } "blockchain.estimatefee" => json_rpc_res!(request, 0.0001), "blockchain.headers.subscribe" => { - let (height, hash) = self.chain.get_best_block()?; - let header = self.chain.get_block_header(&hash)?; + let (height, hash) = self + .chain + .get_best_block() + .map_err(|e| super::error::Error::ChainError(Box::new(e)))?; + let header = self + .chain + .get_block_header(&hash) + .map_err(|e| super::error::Error::ChainError(Box::new(e)))?; let result = json!({ "height": height, "hex": serialize(&header).to_hex() @@ -237,7 +246,9 @@ impl ElectrumServer { Vec::from_hex(&tx).map_err(|_| super::error::Error::InvalidParams)?; let tx: Transaction = deserialize(&hex).map_err(|_| super::error::Error::InvalidParams)?; - self.chain.broadcast(&tx)?; + self.chain + .broadcast(&tx) + .map_err(|e| super::error::Error::ChainError(Box::new(e)))?; let id = tx.txid(); let updated = self .address_cache diff --git a/crates/floresta-electrum/src/error.rs b/crates/floresta-electrum/src/error.rs index 2371deac..e487ed16 100644 --- a/crates/floresta-electrum/src/error.rs +++ b/crates/floresta-electrum/src/error.rs @@ -1,8 +1,6 @@ #[cfg(feature = "cli-blockchain")] use btcd_rpc::error::UtreexodError; -use floresta_chain::BlockchainError; -use floresta_common::impl_error_from; use thiserror::Error; #[derive(Error, Debug)] @@ -15,9 +13,7 @@ pub enum Error { #[error("Invalid json string {0}")] ParsingError(#[from] serde_json::Error), #[error("Blockchain error")] - ChainError(BlockchainError), + ChainError(Box), #[error("IO error")] IoError(#[from] std::io::Error), } - -impl_error_from!(Error, BlockchainError, ChainError); diff --git a/crates/floresta-watch-only/Cargo.toml b/crates/floresta-watch-only/Cargo.toml index 31e663e0..f4fafc79 100644 --- a/crates/floresta-watch-only/Cargo.toml +++ b/crates/floresta-watch-only/Cargo.toml @@ -5,21 +5,20 @@ edition = "2021" description = "A simple, lightweight and Electrum-First, watch-only wallet" authors = ["Davidson Souza "] keywords = ["bitcoin", "watch-only", "electrum-server"] +categories = ["bitcoin", "blockchain", "node"] license = "MIT" +repository = "https://github.com/Davidson-Souza/Floresta" +readme = "README.md" [dependencies] serde = "1.0.0" serde_json = { version = "1.0.0", features = ["alloc"] } -anyhow = "1.0.70" bitcoin = { version = "0.29.2", features = ["serde"] } kv = "0.24.0" log = "0.4" thiserror = "1.0" floresta-common = { path = "../floresta-common" } -[dev-dependencies] -rand = "0.8.5" - [features] default = [] memory-database = [] diff --git a/crates/floresta-wire/Cargo.toml b/crates/floresta-wire/Cargo.toml index 31673121..d686e62c 100644 --- a/crates/floresta-wire/Cargo.toml +++ b/crates/floresta-wire/Cargo.toml @@ -2,12 +2,21 @@ name = "floresta-wire" version = "0.1.0" edition = "2021" +authors = ["Davidson Souza "] +description = """ + Some utreexo-aware wire protocol for floresta. You can use this crate + to fetch blocks and transactions from the network, and to broadcast + your own transactions. +""" +repository = "https://github.com/Davidson-Souza/Floresta" +license = "MIT" +readme = "README.md" +keywords = ["bitcoin", "utreexo", "p2p", "networking"] +categories = ["bitcoin", "blockchain", "node"] + [dependencies] rustreexo = "0.1.0" -btcd-rpc = { git = "https://github.com/Davidson-Souza/rust-btcd-rpc", features = [ - "utreexod", -], branch = "use-reqwest", optional = true } sha2 = "^0.10.6" async-std = { version = "1.12.0", features = ["attributes", "alloc"] } log = "0.4" @@ -15,20 +24,15 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" kv = "0.24.0" futures = "0.3.4" -rmp-serde = { optional = true, version = "1.1.1" } toml = "0.5.10" rand = "0.8.5" bitcoin = { version = "0.29", features = ["serde", "std", "bitcoinconsensus"] } dns-lookup = "1.0.8" -ctrlc = "3.2.5" floresta-chain = { path = "../floresta-chain" } thiserror = "1.0" floresta-common = { path = "../floresta-common" } oneshot = "0.1.5" -[dev-dependencies] -pretty_assertions = "1" - [features] default = [] pruned_utreexo_chainstate = [] \ No newline at end of file diff --git a/crates/floresta-wire/src/p2p_wire/node.rs b/crates/floresta-wire/src/p2p_wire/node.rs index 1ab1f14b..faab18fe 100644 --- a/crates/floresta-wire/src/p2p_wire/node.rs +++ b/crates/floresta-wire/src/p2p_wire/node.rs @@ -169,6 +169,8 @@ enum PeerStatus { } impl UtreexoNode +where + WireError: From<::Error>, { pub fn new( chain: Arc, @@ -656,7 +658,10 @@ impl UtreexoNode { +impl UtreexoNode +where + WireError: From<::Error>, +{ /// Processing blocks actually takes a lot of CPU time, and we need to wait until it either /// succeed or fail before doing something else. This will hang our node up (not peers, only /// the node) and make it do funky stuff, like timeout blocks we just got but not processed. @@ -897,7 +902,10 @@ impl UtreexoNode UtreexoNode { +impl UtreexoNode +where + WireError: From<::Error>, +{ /// Returns a handle to the node interface that we can use to request data from our /// node. This struct is thread safe, so we can use it from multiple threads and have /// multiple handles. It also doesn't require a mutable reference to the node, or any diff --git a/crates/floresta/Cargo.toml b/crates/floresta/Cargo.toml index 2a25162c..aa766e50 100644 --- a/crates/floresta/Cargo.toml +++ b/crates/floresta/Cargo.toml @@ -18,8 +18,8 @@ floresta-chain = { path = "../floresta-chain" } floresta-wire = { path = "../floresta-wire" } floresta-watch-only = { path = "../floresta-watch-only", features = [ "memory-database", -] } -hashbrown = "0.14.0" +], optional = true } +floresta-electrum = { path = "../floresta-electrum", optional = true } [dev-dependencies] rustreexo = "0.1.0" @@ -31,8 +31,19 @@ bitcoin = { version = "0.29", features = [ "bitcoinconsensus", ] } +floresta = { version = "0.1.0", path = "../floresta", features = [ + "bitcoinconsensus", + "memory-database", + "electrum-server", + "watch-only-wallet", +] } + [features] +default = ["bitcoinconsensus", "electrum-server", "watch-only-wallet"] bitcoinconsensus = ["floresta-chain/bitcoinconsensus"] +memory-database = ["floresta-watch-only/memory-database"] +electrum-server = ["floresta-electrum"] +watch-only-wallet = ["floresta-watch-only"] [lib] crate-type = ["cdylib", "rlib", "staticlib"] diff --git a/crates/floresta/src/lib.rs b/crates/floresta/src/lib.rs index 051fac46..d9100dd6 100644 --- a/crates/floresta/src/lib.rs +++ b/crates/floresta/src/lib.rs @@ -30,5 +30,8 @@ //! which is a forest of Merkle trees. It's pronounced /floˈɾɛstɐ/. pub use floresta_chain as chain; pub use floresta_common as common; +#[cfg(feature = "electrum-server")] +pub use floresta_electrum as electrum; +#[cfg(feature = "watch-only-wallet")] pub use floresta_watch_only as wallet; pub use floresta_wire as wire; diff --git a/florestad/Cargo.toml b/florestad/Cargo.toml index 55707851..71c4ab15 100644 --- a/florestad/Cargo.toml +++ b/florestad/Cargo.toml @@ -5,9 +5,6 @@ edition = "2021" [dependencies] rustreexo = "0.1.0" -btcd-rpc = { git = "https://github.com/Davidson-Souza/rust-btcd-rpc", features = [ - "utreexod", -], branch = "use-reqwest", optional = true } clap = { version = "4.0.29", features = ["derive"] } sha2 = "^0.10.6" async-std = { version = "1.12.0", features = ["attributes"] } @@ -19,7 +16,6 @@ kv = "0.24.0" miniscript = { git = "https://github.com/douglaz/rust-miniscript.git", branch = "master-2023-03-30" } pretty_env_logger = "0.4.0" futures = "0.3.4" -rmp-serde = { optional = true, version = "1.1.1" } toml = "0.5.10" dirs = "4.0.0" rand = "0.8.5" From 672cac33f06e3593cf6fb077d3c3a0edd56ebacd Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Mon, 28 Aug 2023 17:08:37 -0300 Subject: [PATCH 012/328] Fix: return leaf_hashes (#73) In some previous iteration, the leaf_hashes wasn't being returned, and therefore, no proof verification was actually happening. --- crates/floresta-wire/src/p2p_wire/node.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/floresta-wire/src/p2p_wire/node.rs b/crates/floresta-wire/src/p2p_wire/node.rs index faab18fe..cbc21cd4 100644 --- a/crates/floresta-wire/src/p2p_wire/node.rs +++ b/crates/floresta-wire/src/p2p_wire/node.rs @@ -309,7 +309,7 @@ where .map(|hash| NodeHash::Some(hash.into_inner())) .collect(); let proof = Proof::new(targets, hashes); - let hashes = Vec::new(); + let mut hashes = Vec::new(); let mut leaves_iter = udata.leaves.iter().cloned(); let mut tx_iter = transactions.iter(); @@ -341,6 +341,7 @@ where { return Err(WireError::CoinbaseNotMatured); } + hashes.push(leaf._get_leaf_hashes()); inputs.insert(leaf.prevout, leaf.utxo); } } From 578b47f1ea27adb402376a08a7fd80f2fe72b688 Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Mon, 28 Aug 2023 19:07:58 -0300 Subject: [PATCH 013/328] Docs improvements (#74) * Fix: return leaf_hashes In some previous iteration, the leaf_hashes wasn't being returned, and therefore, no proof verification was actually happening. * Add readme to floresta-wire * Add useful comments to electrum_protocol.rs * Remove outdated doc in main.rs * Add documentation to mempool * Add more documentation to floresta * Add more comments to multiple modules --- crates/floresta-chain/src/lib.rs | 11 +++++ .../src/electrum_protocol.rs | 27 ++++++++-- crates/floresta-wire/readme.md | 49 +++++++++++++++++++ crates/floresta-wire/src/lib.rs | 11 +++++ crates/floresta-wire/src/p2p_wire/mempool.rs | 12 ++++- crates/floresta/Cargo.toml | 10 ++++ crates/floresta/src/lib.rs | 5 ++ florestad/src/error.rs | 2 +- florestad/src/main.rs | 4 -- 9 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 crates/floresta-wire/readme.md diff --git a/crates/floresta-chain/src/lib.rs b/crates/floresta-chain/src/lib.rs index 7c1f4075..f36bee7d 100644 --- a/crates/floresta-chain/src/lib.rs +++ b/crates/floresta-chain/src/lib.rs @@ -1,3 +1,14 @@ +// SPDX-License-Identifier: MIT +//! # Floresta Chain +//! This crate provides the core validation logic for a full node using libfloresta. +//! It is maintained as a separate crate to allow other projects to build on it, +//! independent of the libfloresta P2P network or libfloresta wallet. +//! The main entry point is the [ChainState] struct, that keeps track of the current +//! blockchain state, like headers and utreexo accumulator. +//! +//! All data is stored in a `ChainStore` implementation, which is generic over the +//! underlying database. See the ChainStore trait for more information. For a +//! ready-to-use implementation, see the [KvChainStore] struct. #![cfg_attr(any(feature = "no-std", not(test)), no_std)] pub mod pruned_utreexo; diff --git a/crates/floresta-electrum/src/electrum_protocol.rs b/crates/floresta-electrum/src/electrum_protocol.rs index e5c3e784..77035920 100644 --- a/crates/floresta-electrum/src/electrum_protocol.rs +++ b/crates/floresta-electrum/src/electrum_protocol.rs @@ -28,6 +28,7 @@ use serde_json::{json, Value}; use std::collections::{HashMap, HashSet}; use std::sync::Arc; +/// A client connected to the server #[derive(Debug, Clone, Default)] pub struct Peer { _addresses: HashSet