From ded12938f068c0e3fac8ad6399524242387d4c62 Mon Sep 17 00:00:00 2001 From: Ash Manning Date: Thu, 2 May 2024 22:49:47 +0800 Subject: [PATCH] Netwoking refactor, change default net addr, Reorgs, bump version number for release --- Cargo.lock | 899 ++++++++++--------- Cargo.toml | 4 +- app/Cargo.toml | 1 + app/app.rs | 97 +- app/cli.rs | 4 +- app/gui/activity/block_explorer.rs | 22 +- app/gui/activity/mod.rs | 2 +- app/gui/miner.rs | 8 +- app/main.rs | 3 + app/rpc_server.rs | 24 +- cli/lib.rs | 6 - lib/Cargo.toml | 8 +- lib/archive.rs | 512 ++++++++--- lib/authorization.rs | 18 +- lib/lib.rs | 2 + lib/mempool.rs | 63 +- lib/miner.rs | 7 +- lib/net.rs | 212 ----- lib/net/mod.rs | 325 +++++++ lib/net/peer.rs | 713 +++++++++++++++ lib/node.rs | 1329 +++++++++++++++++++--------- lib/state.rs | 964 +++++++++++++++----- lib/types/hashes.rs | 4 + lib/types/mod.rs | 227 ++--- lib/types/transaction.rs | 2 + rpc-api/lib.rs | 4 - 26 files changed, 3876 insertions(+), 1584 deletions(-) delete mode 100644 lib/net.rs create mode 100644 lib/net/mod.rs create mode 100644 lib/net/peer.rs diff --git a/Cargo.lock b/Cargo.lock index 86f2502..26eefca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.23" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225" +checksum = "6f90148830dac590fac7ccfe78ec4a8ea404c60f75a24e16407a71f0f40de775" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "accesskit" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb10ed32c63247e4e39a8f42e8e30fb9442fbf7878c8e4a9849e7e381619bea" +checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b" [[package]] name = "accesskit_consumer" @@ -164,18 +164,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-activity" @@ -184,7 +184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" dependencies = [ "android-properties", - "bitflags 2.4.2", + "bitflags 2.5.0", "cc", "cesu8", "jni", @@ -269,26 +269,25 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" dependencies = [ "backtrace", ] [[package]] name = "arboard" -version = "3.3.2" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2041f1943049c7978768d84e6d0fd95de98b76d6c4727b09e78ec253d29fa58" +checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89" dependencies = [ "clipboard-win", "log", - "objc", - "objc-foundation", - "objc_id", + "objc2 0.5.1", + "objc2-app-kit", + "objc2-foundation", "parking_lot", - "thiserror", "x11rb", ] @@ -331,28 +330,27 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" dependencies = [ "concurrent-queue", - "event-listener 5.2.0", - "event-listener-strategy 0.5.0", + "event-listener 5.3.0", + "event-listener-strategy 0.5.2", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.8.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" dependencies = [ - "async-lock 3.3.0", "async-task", "concurrent-queue", - "fastrand 2.0.1", - "futures-lite 2.2.0", + "fastrand 2.1.0", + "futures-lite 2.3.0", "slab", ] @@ -390,18 +388,18 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" dependencies = [ "async-lock 3.3.0", "cfg-if 1.0.0", "concurrent-queue", "futures-io", - "futures-lite 2.2.0", + "futures-lite 2.3.0", "parking", - "polling 3.5.0", - "rustix 0.38.31", + "polling 3.7.0", + "rustix 0.38.34", "slab", "tracing", "windows-sys 0.52.0", @@ -446,54 +444,54 @@ dependencies = [ "cfg-if 1.0.0", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.31", + "rustix 0.38.34", "windows-sys 0.48.0", ] [[package]] name = "async-recursion" -version = "1.0.5" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] name = "async-signal" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" +checksum = "afe66191c335039c7bb78f99dc7520b0cbb166b3a1cb33a03f53d8a1c6f2afda" dependencies = [ - "async-io 2.3.1", - "async-lock 2.8.0", + "async-io 2.3.2", + "async-lock 3.3.0", "atomic-waker", "cfg-if 1.0.0", "futures-core", "futures-io", - "rustix 0.38.31", + "rustix 0.38.34", "signal-hook-registry", "slab", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "async-task" -version = "4.7.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -565,15 +563,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -596,6 +594,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -635,7 +639,7 @@ dependencies = [ [[package]] name = "bip300301" version = "0.1.1" -source = "git+https://github.com/Ash-L2L/bip300301.git?rev=43ba4d7bee075ecd2504f87b5ec2a5ba3b10cd3b#43ba4d7bee075ecd2504f87b5ec2a5ba3b10cd3b" +source = "git+https://github.com/Ash-L2L/bip300301.git?rev=e47a263c8cbf9c520aa80f71788ee28e9f11bb62#e47a263c8cbf9c520aa80f71788ee28e9f11bb62" dependencies = [ "base64 0.21.7", "bitcoin", @@ -667,9 +671,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitcoin" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd00f3c09b5f21fb357abe32d29946eb8bb7a0862bae62c0b5e4a692acbbe73c" +checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae" dependencies = [ "bech32 0.10.0-beta", "bitcoin-internals", @@ -708,15 +712,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", @@ -764,7 +768,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" dependencies = [ - "objc-sys 0.3.2", + "objc-sys 0.3.3", ] [[package]] @@ -787,27 +791,34 @@ dependencies = [ "objc2 0.4.1", ] +[[package]] +name = "block2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ff7d91d3c1d568065b06c899777d1e48dcf76103a672a0adbc238a7f247f1e" +dependencies = [ + "objc2 0.5.1", +] + [[package]] name = "blocking" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" dependencies = [ "async-channel", "async-lock 3.3.0", "async-task", - "fastrand 2.0.1", "futures-io", - "futures-lite 2.2.0", + "futures-lite 2.3.0", "piper", - "tracing", ] [[package]] name = "borsh" -version = "1.3.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" +checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77" dependencies = [ "borsh-derive", "cfg_aliases", @@ -815,23 +826,23 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.3.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" +checksum = "d7a8646f94ab393e43e8b35a2558b1624bed28b97ee09c5d15456e3c9463f46d" dependencies = [ "once_cell", "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", "syn_derive", ] [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "sha2", "tinyvec", @@ -839,28 +850,28 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -871,9 +882,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "calloop" @@ -881,10 +892,10 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "log", - "polling 3.5.0", - "rustix 0.38.31", + "polling 3.7.0", + "rustix 0.38.34", "slab", "thiserror", ] @@ -896,19 +907,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" dependencies = [ "calloop", - "rustix 0.38.31", + "rustix 0.38.34", "wayland-backend", "wayland-client", ] [[package]] name = "cc" -version = "1.0.89" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -919,9 +931,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-expr" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", "target-lexicon", @@ -956,15 +968,15 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -996,7 +1008,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.0", + "strsim 0.11.1", ] [[package]] @@ -1008,7 +1020,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -1019,9 +1031,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clipboard-win" -version = "5.2.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9a0700e0127ba15d1d52dd742097f821cd9c65939303a44d970465040a297" +checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad" dependencies = [ "error-code", ] @@ -1111,9 +1123,9 @@ dependencies = [ [[package]] name = "combine" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", @@ -1121,9 +1133,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -1158,9 +1170,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1276,11 +1288,11 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.2" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ - "nix 0.27.1", + "nix 0.28.0", "windows-sys 0.52.0", ] @@ -1316,7 +1328,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -1340,7 +1352,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -1351,14 +1363,14 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "zeroize", @@ -1455,7 +1467,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.2", + "libloading 0.8.3", ] [[package]] @@ -1469,9 +1481,9 @@ dependencies = [ [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "ecolor" @@ -1556,7 +1568,7 @@ dependencies = [ "parking_lot", "percent-encoding", "raw-window-handle 0.5.2", - "raw-window-handle 0.6.0", + "raw-window-handle 0.6.1", "static_assertions", "thiserror", "wasm-bindgen", @@ -1608,7 +1620,7 @@ dependencies = [ "arboard", "egui", "log", - "raw-window-handle 0.6.0", + "raw-window-handle 0.6.1", "smithay-clipboard", "web-time", "webbrowser", @@ -1625,7 +1637,7 @@ dependencies = [ "egui", "glow", "log", - "memoffset 0.9.0", + "memoffset 0.9.1", "wasm-bindgen", "web-sys", "winit", @@ -1633,9 +1645,9 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "emath" @@ -1656,7 +1668,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -1677,7 +1689,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -1695,6 +1707,8 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ + "anstream", + "anstyle", "env_filter", "log", ] @@ -1767,9 +1781,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" dependencies = [ "concurrent-queue", "parking", @@ -1788,14 +1802,20 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.2.0", + "event-listener 5.3.0", "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "1.9.0" @@ -1807,9 +1827,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdeflate" @@ -1822,15 +1842,15 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" +checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -1860,7 +1880,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -1959,11 +1979,11 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.0.1", + "fastrand 2.1.0", "futures-core", "futures-io", "parking", @@ -1978,7 +1998,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -2043,9 +2063,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if 1.0.0", "libc", @@ -2143,7 +2163,7 @@ version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg_aliases", "cgl", "core-foundation", @@ -2152,7 +2172,7 @@ dependencies = [ "glutin_glx_sys", "glutin_wgl_sys", "icrate", - "libloading 0.8.2", + "libloading 0.8.3", "objc2 0.4.1", "once_cell", "raw-window-handle 0.5.2", @@ -2208,7 +2228,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "gpu-alloc-types", ] @@ -2218,7 +2238,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", ] [[package]] @@ -2240,9 +2260,9 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "gpu-descriptor-types", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -2251,14 +2271,14 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", ] [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -2266,7 +2286,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.5", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -2281,9 +2301,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -2295,10 +2315,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "com", "libc", - "libloading 0.8.2", + "libloading 0.8.3", "thiserror", "widestring", "winapi 0.3.9", @@ -2474,7 +2494,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.6", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -2573,12 +2593,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "serde", ] @@ -2631,9 +2651,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" @@ -2659,9 +2679,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -2872,7 +2892,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.2", + "libloading 0.8.3", "pkg-config", ] @@ -2890,9 +2910,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libes" @@ -2920,34 +2940,33 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2caa5afb8bf9f3a2652760ce7d4f62d21c4d5a423e68466fca30df82f2330164" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if 1.0.0", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] name = "libredox" -version = "0.0.1" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", "redox_syscall 0.4.1", ] [[package]] name = "libredox" -version = "0.0.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", - "redox_syscall 0.4.1", ] [[package]] @@ -2980,9 +2999,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -3014,9 +3033,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memmap2" @@ -3038,9 +3057,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -3072,7 +3091,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "block", "core-graphics-types", "foreign-types", @@ -3140,10 +3159,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" dependencies = [ "bit-set", - "bitflags 2.4.2", + "bitflags 2.5.0", "codespan-reporting", "hexf-parse", - "indexmap 2.2.5", + "indexmap 2.2.6", "log", "num-traits", "rustc-hash", @@ -3159,13 +3178,13 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "jni-sys", "log", "ndk-sys", "num_enum", "raw-window-handle 0.5.2", - "raw-window-handle 0.6.0", + "raw-window-handle 0.6.1", "thiserror", ] @@ -3209,12 +3228,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if 1.0.0", + "cfg_aliases", "libc", ] @@ -3306,7 +3326,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -3319,17 +3339,6 @@ dependencies = [ "objc_exception", ] -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - [[package]] name = "objc-sys" version = "0.2.0-beta.2" @@ -3338,9 +3347,9 @@ checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" [[package]] name = "objc-sys" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459" +checksum = "da284c198fb9b7b0603f8635185e85fbd5b64ee154b1ed406d489077de2d6d60" [[package]] name = "objc2" @@ -3359,10 +3368,43 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ - "objc-sys 0.3.2", + "objc-sys 0.3.3", "objc2-encode 3.0.0", ] +[[package]] +name = "objc2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b25e1034d0e636cd84707ccdaa9f81243d399196b8a773946dcffec0401659" +dependencies = [ + "objc-sys 0.3.3", + "objc2-encode 4.0.1", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb79768a710a9a1798848179edb186d1af7e8a8679f369e4b8d201dd2a034047" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", + "objc2-core-data", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e092bc42eaf30a08844e6a076938c60751225ec81431ab89f5d1ccd9f958d6c" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", + "objc2-foundation", +] + [[package]] name = "objc2-encode" version = "2.0.0-pre.2" @@ -3379,21 +3421,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" [[package]] -name = "objc_exception" -version = "0.1.2" +name = "objc2-encode" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +checksum = "88658da63e4cc2c8adb1262902cd6af51094df0488b760d6fd27194269c0950a" + +[[package]] +name = "objc2-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfaefe14254871ea16c7d88968c0ff14ba554712a20d76421eec52f0a7fb8904" dependencies = [ - "cc", + "block2 0.5.0", + "objc2 0.5.1", ] [[package]] -name = "objc_id" -version = "0.1.1" +name = "objc_exception" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" dependencies = [ - "objc", + "cc", ] [[package]] @@ -3481,9 +3530,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -3491,15 +3540,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -3519,11 +3568,11 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "serde", ] @@ -3535,29 +3584,29 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -3572,7 +3621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" dependencies = [ "atomic-waker", - "fastrand 2.0.1", + "fastrand 2.1.0", "futures-io", ] @@ -3594,7 +3643,7 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plain_bitnames" -version = "0.6.0" +version = "0.7.0" dependencies = [ "addr", "anyhow", @@ -3610,12 +3659,16 @@ dependencies = [ "ed25519-dalek", "ed25519-dalek-bip32", "educe", + "fallible-iterator", + "futures", "heed", "hex", "hex-literal", + "jsonrpsee", "lazy_static", "merkle-cbt", "nonempty", + "parking_lot", "quinn", "rayon", "rcgen", @@ -3627,13 +3680,15 @@ dependencies = [ "thiserror", "tiny-bip39", "tokio", + "tokio-stream", + "tokio-util", "tracing", "x25519-dalek", ] [[package]] name = "plain_bitnames_app" -version = "0.6.0" +version = "0.7.0" dependencies = [ "anyhow", "async_zmq", @@ -3665,13 +3720,14 @@ dependencies = [ "thiserror", "tiny-bip39", "tokio", + "tokio-util", "tracing", "tracing-subscriber", ] [[package]] name = "plain_bitnames_app_cli" -version = "0.6.0" +version = "0.7.0" dependencies = [ "anyhow", "bip300301", @@ -3685,7 +3741,7 @@ dependencies = [ [[package]] name = "plain_bitnames_app_rpc_api" -version = "0.6.0" +version = "0.7.0" dependencies = [ "bip300301", "jsonrpsee", @@ -3694,9 +3750,9 @@ dependencies = [ [[package]] name = "platforms" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "png" @@ -3729,14 +3785,15 @@ dependencies = [ [[package]] name = "polling" -version = "3.5.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" dependencies = [ "cfg-if 1.0.0", "concurrent-queue", + "hermit-abi", "pin-project-lite", - "rustix 0.38.31", + "rustix 0.38.34", "tracing", "windows-sys 0.52.0", ] @@ -3815,9 +3872,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -3830,9 +3887,9 @@ checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" [[package]] name = "psl" -version = "2.1.26" +version = "2.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2add805cd6812c130c1ffbc3c85fe113081a485e80e8022221391383939b14" +checksum = "14138c8a3dd9e14adeb406e5b2c74749c7ae638aa2f8a3a95497bf797f565490" dependencies = [ "psl-types", ] @@ -3895,16 +3952,16 @@ checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", - "socket2 0.5.6", + "socket2 0.5.7", "tracing", "windows-sys 0.48.0", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -3947,15 +4004,15 @@ checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "raw-window-handle" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" +checksum = "8cc3bcbdb1ddfc11e700e62968e6b4cc9c75bb466464ad28fb61c5b2c964418b" [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -4001,27 +4058,36 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", - "libredox 0.0.1", + "libredox 0.1.3", "thiserror", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -4041,7 +4107,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -4052,9 +4118,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "renderdoc-sys" @@ -4135,11 +4201,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys 0.4.13", @@ -4148,9 +4214,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", @@ -4191,9 +4257,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "ryu" @@ -4276,9 +4342,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -4289,9 +4355,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -4311,29 +4377,29 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -4342,13 +4408,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -4362,15 +4428,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.6.1" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_derive", "serde_json", @@ -4380,14 +4446,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.6.1" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -4455,9 +4521,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -4497,9 +4563,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay-client-toolkit" @@ -4507,14 +4573,14 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "calloop", "calloop-wayland-source", "cursor-icon", "libc", "log", "memmap2", - "rustix 0.38.31", + "rustix 0.38.34", "thiserror", "wayland-backend", "wayland-client", @@ -4558,9 +4624,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -4600,7 +4666,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", ] [[package]] @@ -4633,9 +4699,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" @@ -4656,7 +4722,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -4678,9 +4744,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -4696,7 +4762,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -4722,12 +4788,12 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.2.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", - "heck 0.4.1", + "heck 0.5.0", "pkg-config", "toml", "version-compare", @@ -4746,8 +4812,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if 1.0.0", - "fastrand 2.0.1", - "rustix 0.38.31", + "fastrand 2.1.0", + "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -4762,9 +4828,9 @@ dependencies = [ [[package]] name = "test-log" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b319995299c65d522680decf80f2c108d85b861d81dfe340a10d16cee29d9e6" +checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" dependencies = [ "env_logger", "test-log-macros", @@ -4773,33 +4839,33 @@ dependencies = [ [[package]] name = "test-log-macros" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8f546451eaa38373f549093fe9fd05e7d2bade739e2ddf834b9968621d60107" +checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -4814,9 +4880,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -4835,9 +4901,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -4904,9 +4970,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -4915,7 +4981,7 @@ dependencies = [ "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.6", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] @@ -4928,7 +4994,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -4943,9 +5009,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -4962,6 +5028,8 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", + "futures-util", + "hashbrown 0.14.5", "pin-project-lite", "tokio", "tracing", @@ -4969,14 +5037,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.10" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.6", + "toml_edit 0.22.12", ] [[package]] @@ -4994,7 +5062,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.40", ] @@ -5005,22 +5073,22 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.5", + "winnow 0.6.7", ] [[package]] @@ -5070,7 +5138,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -5145,7 +5213,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" dependencies = [ - "memoffset 0.9.0", + "memoffset 0.9.1", "tempfile", "winapi 0.3.9", ] @@ -5179,9 +5247,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode-xid" @@ -5236,9 +5304,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version-compare" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "version_check" @@ -5298,7 +5366,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", "wasm-bindgen-shared", ] @@ -5332,7 +5400,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5351,7 +5419,7 @@ checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.31", + "rustix 0.38.34", "scoped-tls", "smallvec", "wayland-sys", @@ -5363,8 +5431,8 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" dependencies = [ - "bitflags 2.4.2", - "rustix 0.38.31", + "bitflags 2.5.0", + "rustix 0.38.34", "wayland-backend", "wayland-scanner", ] @@ -5375,7 +5443,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cursor-icon", "wayland-backend", ] @@ -5386,7 +5454,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" dependencies = [ - "rustix 0.38.31", + "rustix 0.38.34", "wayland-client", "xcursor", ] @@ -5397,7 +5465,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -5409,7 +5477,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5422,7 +5490,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -5474,9 +5542,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b2391658b02c27719fc5a0a73d6e696285138e8b12fba9d4baa70451023c71" +checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" dependencies = [ "core-foundation", "home", @@ -5497,9 +5565,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "wgpu" -version = "0.19.3" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1213b52478a7631d6e387543ed8f642bc02c578ef4e3b49aca2a29a7df0cb" +checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" dependencies = [ "arrayvec", "cfg-if 1.0.0", @@ -5508,7 +5576,7 @@ dependencies = [ "log", "parking_lot", "profiling", - "raw-window-handle 0.6.0", + "raw-window-handle 0.6.1", "smallvec", "static_assertions", "wasm-bindgen", @@ -5521,22 +5589,22 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.19.3" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f6b033c2f00ae0bc8ea872c5989777c60bc241aac4e58b24774faa8b391f78" +checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg_aliases", "codespan-reporting", - "indexmap 2.2.5", + "indexmap 2.2.6", "log", "naga", "once_cell", "parking_lot", "profiling", - "raw-window-handle 0.6.0", + "raw-window-handle 0.6.1", "rustc-hash", "smallvec", "thiserror", @@ -5547,14 +5615,14 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.19.3" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f972c280505ab52ffe17e94a7413d9d54b58af0114ab226b9fc4999a47082e" +checksum = "fc1a4924366df7ab41a5d8546d6534f1f33231aa5b3f72b9930e300f254e39c3" dependencies = [ "android_system_properties", "arrayvec", "ash", - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg_aliases", "core-graphics-types", "glow", @@ -5566,7 +5634,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.2", + "libloading 0.8.3", "log", "metal", "naga", @@ -5575,7 +5643,7 @@ dependencies = [ "once_cell", "parking_lot", "profiling", - "raw-window-handle 0.6.0", + "raw-window-handle 0.6.1", "renderdoc-sys", "rustc-hash", "smallvec", @@ -5592,16 +5660,16 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "js-sys", "web-sys", ] [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -5633,11 +5701,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi 0.3.9", + "windows-sys 0.52.0", ] [[package]] @@ -5664,7 +5732,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -5673,7 +5741,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -5722,7 +5790,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -5757,17 +5825,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -5784,9 +5853,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -5802,9 +5871,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -5820,9 +5889,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -5838,9 +5913,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -5856,9 +5931,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -5874,9 +5949,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -5892,20 +5967,20 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winit" -version = "0.29.14" +version = "0.29.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a3db69ffbe53a9babec7804da7a90f21020fcce1f2f5e5291e2311245b993d" +checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.4.2", + "bitflags 2.5.0", "bytemuck", "calloop", "cfg_aliases", @@ -5924,9 +5999,9 @@ dependencies = [ "orbclient", "percent-encoding", "raw-window-handle 0.5.2", - "raw-window-handle 0.6.0", + "raw-window-handle 0.6.1", "redox_syscall 0.3.5", - "rustix 0.38.31", + "rustix 0.38.34", "sctk-adwaita", "smithay-client-toolkit", "smol_str", @@ -5956,9 +6031,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" dependencies = [ "memchr", ] @@ -5986,24 +6061,24 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.8.2", + "libloading 0.8.3", "once_cell", - "rustix 0.38.31", + "rustix 0.38.34", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "x25519-dalek" @@ -6039,7 +6114,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "dlib", "log", "once_cell", @@ -6054,9 +6129,9 @@ checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" [[package]] name = "xml-rs" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" [[package]] name = "yasna" @@ -6171,7 +6246,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -6191,7 +6266,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1756cd0..39efc0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,11 @@ members = [ [workspace.package] authors = [ "Ash Manning " ] edition = "2021" -version = "0.6.0" +version = "0.7.0" [workspace.dependencies.bip300301] git = "https://github.com/Ash-L2L/bip300301.git" -rev = "43ba4d7bee075ecd2504f87b5ec2a5ba3b10cd3b" +rev = "e47a263c8cbf9c520aa80f71788ee28e9f11bb62" [profile.release] # lto = "fat" \ No newline at end of file diff --git a/app/Cargo.toml b/app/Cargo.toml index 5a960ab..0409f9d 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -38,6 +38,7 @@ strum = { version = "0.25.0", features = ["derive"] } thiserror = "1.0.44" tiny-bip39 = "1.0.0" tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] } +tokio-util = { version = "0.7.10", features = ["rt"] } tracing = "0.1.40" tracing-subscriber = "0.3.18" diff --git a/app/app.rs b/app/app.rs index fedda37..8db5715 100644 --- a/app/app.rs +++ b/app/app.rs @@ -5,8 +5,6 @@ use std::{ }; use parking_lot::RwLock; -use tokio::sync::RwLock as TokioRwLock; - use plain_bitnames::{ bip300301::{bitcoin, MainClient}, format_deposit_address, @@ -18,18 +16,11 @@ use plain_bitnames::{ }, wallet::{self, Wallet}, }; +use tokio::sync::RwLock as TokioRwLock; +use tokio_util::task::LocalPoolHandle; use crate::cli::Config; -#[derive(Clone)] -pub struct App { - pub node: Arc, - pub wallet: Arc, - pub miner: Arc>, - pub utxos: Arc>>, - pub runtime: Arc, -} - #[derive(Debug, thiserror::Error)] pub enum Error { #[error("drivechain error")] @@ -48,6 +39,16 @@ pub enum Error { Wallet(#[from] wallet::Error), } +#[derive(Clone)] +pub struct App { + pub node: Arc, + pub wallet: Arc, + pub miner: Arc>, + pub utxos: Arc>>, + pub runtime: Arc, + pub local_pool: LocalPoolHandle, +} + impl App { pub fn new(config: &Config) -> Result { // Node launches some tokio tasks for p2p networking, that is why we need a tokio runtime @@ -66,24 +67,18 @@ impl App { &config.main_user, &config.main_password, )?; - let node = runtime.block_on(async { - let node = match Node::new( - config.net_addr, - &config.datadir, - config.main_addr, - &config.main_password, - &config.main_user, - #[cfg(all(not(target_os = "windows"), feature = "zmq"))] - config.zmq_addr, - ) { - Ok(node) => node, - Err(err) => return Err(err), - }; - Ok(node) - })?; - let node = Arc::new(node); let rt_guard = runtime.enter(); - node.clone().run()?; + let local_pool = LocalPoolHandle::new(1); + let node = Node::new( + config.net_addr, + &config.datadir, + config.main_addr, + &config.main_password, + &config.main_user, + local_pool.clone(), + #[cfg(all(not(target_os = "windows"), feature = "zmq"))] + config.zmq_addr, + )?; drop(rt_guard); let utxos = { let mut utxos = wallet.get_utxos()?; @@ -96,18 +91,18 @@ impl App { Arc::new(RwLock::new(utxos)) }; Ok(Self { - node, + node: Arc::new(node), wallet: Arc::new(wallet), miner: Arc::new(TokioRwLock::new(miner)), utxos, runtime: Arc::new(runtime), + local_pool, }) } pub fn sign_and_send(&self, tx: Transaction) -> Result<(), Error> { let authorized_transaction = self.wallet.authorize(tx)?; - self.runtime - .block_on(self.node.submit_transaction(&authorized_transaction))?; + self.node.submit_transaction(authorized_transaction)?; self.update_utxos()?; Ok(()) } @@ -157,6 +152,7 @@ impl App { inbox_whitelist.contains(bitname) }) }; + let tip = self.node.get_tip()?; let outpoints_to_block_heights: HashMap<_, _> = utxos .iter() .map(|(&outpoint, _)| outpoint) @@ -166,8 +162,16 @@ impl App { _ => None, }) .map(|(outpoint, txid)| { - let height = self.node.get_tx_height(txid)?; - Ok((outpoint, height)) + let inclusions = self.node.get_tx_inclusions(txid)?; + let Some(block_hash) = + inclusions.into_iter().try_find(|block_hash| { + self.node.is_descendant(*block_hash, tip) + })? + else { + return Ok((outpoint, None)); + }; + let height = self.node.get_height(block_hash)?; + Ok((outpoint, Some(height))) }) .collect::>()?; let inpoints_to_block_heights: HashMap<_, _> = @@ -179,8 +183,14 @@ impl App { "Impossible: bitname inpoint can only refer to regular tx" ) }; - let height = self.node.get_tx_height(txid)?; - Ok((spent_output.inpoint, height)) + let inclusions = self.node.get_tx_inclusions(txid)?; + let Some(block_hash) = inclusions.into_iter().try_find(|block_hash| { + self.node.is_descendant(*block_hash, tip) + })? else { + return Ok((spent_output.inpoint, None)); + }; + let height = self.node.get_height(block_hash)?; + Ok((spent_output.inpoint, Some(height))) }).collect::>()?; /* associate to each address, a set of pairs of bitname data and ownership period for the bitname. */ @@ -192,7 +202,9 @@ impl App { continue; }; let bitname_data = self.node.get_current_bitname_data(bitname)?; - let height = outpoints_to_block_heights[&outpoint]; + let Some(height) = outpoints_to_block_heights[&outpoint] else { + continue; + }; let owned = Range { start: height, end: u32::MAX, @@ -207,14 +219,17 @@ impl App { let Some(bitname) = output.output.bitname() else { continue; }; - let acquired_height = outpoints_to_block_heights[&outpoint]; + let Some(acquired_height) = outpoints_to_block_heights[&outpoint] + else { + continue; + }; let spent_height = inpoints_to_block_heights[&output.inpoint]; let bitname_data = self .node .get_bitname_data_at_block_height(bitname, acquired_height)?; let owned = Range { start: acquired_height, - end: spent_height, + end: spent_height.unwrap_or(u32::MAX), }; addrs_to_bitnames_ownership .entry(output.output.address) @@ -231,7 +246,9 @@ impl App { else { return false; }; - let height = outpoints_to_block_heights[outpoint]; + let Some(height) = outpoints_to_block_heights[outpoint] else { + return false; + }; let min_fee = bitname_data_ownership .iter() .filter_map(|(bitname_data, ownership)| { @@ -278,7 +295,7 @@ impl App { let txs = txs.into_iter().map(|tx| tx.into()).collect(); Body::new(txs, coinbase) }; - let prev_side_hash = self.node.get_best_hash()?; + let prev_side_hash = self.node.get_tip()?; let prev_main_hash = self .miner .read() diff --git a/app/cli.rs b/app/cli.rs index 56e829c..593dbe5 100644 --- a/app/cli.rs +++ b/app/cli.rs @@ -19,7 +19,7 @@ pub struct Cli { /// Path to a mnemonic seed phrase #[arg(long)] pub mnemonic_seed_phrase_path: Option, - /// address to use for P2P networking, defaults to 127.0.0.1:4000 + /// address to use for P2P networking, defaults to 0.0.0.0:4000 #[arg(short, long)] pub net_addr: Option, /// mainchain node RPC password, defaults to "password" @@ -73,7 +73,7 @@ impl Cli { .clone() .unwrap_or_else(|| "password".into()); let main_user = self.user_main.clone().unwrap_or_else(|| "user".into()); - const DEFAULT_NET_ADDR: &str = "127.0.0.1:4000"; + const DEFAULT_NET_ADDR: &str = "0.0.0.0:4000"; let net_addr: SocketAddr = self .net_addr .clone() diff --git a/app/gui/activity/block_explorer.rs b/app/gui/activity/block_explorer.rs index 7c3250f..835185a 100644 --- a/app/gui/activity/block_explorer.rs +++ b/app/gui/activity/block_explorer.rs @@ -1,7 +1,10 @@ use eframe::egui; use human_size::{Byte, Kibibyte, Mebibyte, SpecificSize}; -use plain_bitnames::{bip300301::bitcoin, types::GetValue}; +use plain_bitnames::{ + bip300301::bitcoin, + types::{Body, GetValue, Header}, +}; use crate::app::App; @@ -15,9 +18,18 @@ impl BlockExplorer { } pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) { - let max_height = app.node.get_height().unwrap_or(0); - let header = app.node.get_header(self.height).ok().flatten(); - let body = app.node.get_body(self.height).ok().flatten(); + let max_height = app.node.get_tip_height().unwrap_or(0); + let block: Option<(Header, Body)> = { + if let Ok(Some(block_hash)) = + app.node.try_get_block_hash(self.height) + && let Ok(header) = app.node.get_header(block_hash) + && let Ok(body) = app.node.get_body(block_hash) + { + Some((header, body)) + } else { + None + } + }; egui::CentralPanel::default().show_inside(ui, |ui| { ui.heading("Block"); ui.horizontal(|ui| { @@ -32,7 +44,7 @@ impl BlockExplorer { self.height = max_height; } }); - if let (Some(header), Some(body)) = (header, body) { + if let Some((header, body)) = block { let hash = &format!("{}", header.hash()); let merkle_root = &format!("{}", header.merkle_root); let prev_side_hash = &format!("{}", header.prev_side_hash); diff --git a/app/gui/activity/mod.rs b/app/gui/activity/mod.rs index b214e70..148247e 100644 --- a/app/gui/activity/mod.rs +++ b/app/gui/activity/mod.rs @@ -27,7 +27,7 @@ pub struct Activity { impl Activity { pub fn new(app: &App) -> Self { - let height = app.node.get_height().unwrap_or(0); + let height = app.node.get_tip_height().unwrap_or(0); Self { tab: Default::default(), block_explorer: BlockExplorer::new(height), diff --git a/app/gui/miner.rs b/app/gui/miner.rs index 3dc4aad..91b8254 100644 --- a/app/gui/miner.rs +++ b/app/gui/miner.rs @@ -22,8 +22,8 @@ impl Default for Miner { impl Miner { pub fn show(&mut self, app: &App, ui: &mut egui::Ui) { - let block_height = app.node.get_height().unwrap_or(0); - let best_hash = app.node.get_best_hash().unwrap_or([0; 32].into()); + let block_height = app.node.get_tip_height().unwrap_or(0); + let best_hash = app.node.get_tip().unwrap_or([0; 32].into()); ui.label("Block height: "); ui.monospace(format!("{block_height}")); ui.label("Best hash: "); @@ -36,10 +36,10 @@ impl Miner { .clicked() { self.running.store(true, atomic::Ordering::SeqCst); - app.runtime.spawn({ + app.local_pool.spawn_pinned({ let app = app.clone(); let running = self.running.clone(); - async move { + || async move { tracing::debug!("Mining..."); let mining_result = app.mine(None).await; running.store(false, atomic::Ordering::SeqCst); diff --git a/app/main.rs b/app/main.rs index 80e11f4..4029a97 100644 --- a/app/main.rs +++ b/app/main.rs @@ -1,3 +1,6 @@ +#![feature(let_chains)] +#![feature(try_find)] + use std::sync::mpsc; use clap::Parser as _; diff --git a/app/rpc_server.rs b/app/rpc_server.rs index 824d99c..24455ce 100644 --- a/app/rpc_server.rs +++ b/app/rpc_server.rs @@ -52,11 +52,7 @@ impl RpcServer for RpcServerImpl { } async fn connect_peer(&self, addr: SocketAddr) -> RpcResult<()> { - self.app - .node - .connect_peer(addr) - .await - .map_err(convert_node_err) + self.app.node.connect_peer(addr).map_err(convert_node_err) } async fn format_deposit_address( @@ -87,17 +83,6 @@ impl RpcServer for RpcServerImpl { Ok(block) } - async fn get_block_hash(&self, height: u32) -> RpcResult { - let block_hash = self - .app - .node - .get_header(height) - .map_err(convert_node_err)? - .ok_or_else(|| custom_err("block not found"))? - .hash(); - Ok(block_hash) - } - async fn get_new_address(&self) -> RpcResult
{ self.app .wallet @@ -110,12 +95,15 @@ impl RpcServer for RpcServerImpl { } async fn getblockcount(&self) -> RpcResult { - self.app.node.get_height().map_err(convert_node_err) + self.app.node.get_tip_height().map_err(convert_node_err) } async fn mine(&self, fee: Option) -> RpcResult<()> { let fee = fee.map(bip300301::bitcoin::Amount::from_sat); - self.app.mine(fee).await.map_err(convert_app_err) + self.app.local_pool.spawn_pinned({ + let app = self.app.clone(); + move || async move { app.mine(fee).await.map_err(convert_app_err) } + }).await.unwrap() } async fn my_utxos(&self) -> RpcResult> { diff --git a/cli/lib.rs b/cli/lib.rs index 57fd142..dbad473 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -25,8 +25,6 @@ pub enum Command { GetNewAddress, /// Get the current block count GetBlockcount, - /// Get the hash of the block at the specified height - GetBlockHash { height: u32 }, /// Get all paymail GetPaymail, /// Attempt to mine a sidechain block @@ -103,10 +101,6 @@ impl Cli { let block = rpc_client.get_block(block_hash).await?; serde_json::to_string_pretty(&block)? } - Command::GetBlockHash { height } => { - let block_hash = rpc_client.get_block_hash(height).await?; - format!("{block_hash}") - } Command::GetBlockcount => { let blockcount = rpc_client.getblockcount().await?; format!("{blockcount}") diff --git a/lib/Cargo.toml b/lib/Cargo.toml index a2f7f74..4bcfcbe 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -18,16 +18,20 @@ bytes = "1.4.0" ed25519-dalek = { version = "2.1.1", features = ["batch", "serde"] } ed25519-dalek-bip32 = "0.3.0" educe = { version = "0.4.23", features = ["Hash"] } +fallible-iterator = "0.3.0" +futures = "0.3.30" heed = { git = "https://github.com/meilisearch/heed", tag = "v0.12.4", version = "0.12.4" } hex = { version = "0.4.3", features = ["serde"] } hex-literal = "0.4.1" +jsonrpsee = { version = "0.20.0" } lazy_static = "1.4.0" merkle-cbt = "0.3.2" nonempty = { version = "0.8.1", features = ["serialize"] } +parking_lot = "0.12.1" quinn = "0.10.1" rayon = "1.7.0" rcgen = "0.11.1" -rustls = { version = "0.21.5", features = ["dangerous_configuration"] } +rustls = { version = "0.21.11", features = ["dangerous_configuration"] } serde = { version = "1.0.179", features = ["derive"] } serde_json = "1.0.113" serde_with = "3.4.0" @@ -35,6 +39,8 @@ sha256 = "1.2.2" thiserror = "1.0.44" tiny-bip39 = "1.0.0" tokio = { version = "1.29.1", features = ["sync"] } +tokio-stream = "0.1.15" +tokio-util = { version = "0.7.10", features = ["rt"] } tracing = "0.1.40" x25519-dalek = { version = "2.0.0", features = ["serde"] } diff --git a/lib/archive.rs b/lib/archive.rs index 0a6dd53..465618b 100644 --- a/lib/archive.rs +++ b/lib/archive.rs @@ -1,199 +1,465 @@ -use heed::{ - byteorder::{BigEndian, ByteOrder}, - types::{OwnedType, SerdeBincode}, - Database, RoTxn, RwTxn, -}; +use std::{cmp::Ordering, collections::BTreeSet}; -use crate::types::{hashes, Block, BlockHash, Body, Header, Txid}; +use bip300301::{ + bitcoin::{self, block::Header as BitcoinHeader, hashes::Hash}, + DepositInfo, +}; +use fallible_iterator::FallibleIterator; +use heed::{types::SerdeBincode, Database, RoTxn, RwTxn}; -#[derive(Clone)] -pub struct Archive { - headers: Database, SerdeBincode
>, - bodies: Database, SerdeBincode>, - hash_to_height: Database, OwnedType<[u8; 4]>>, - txid_to_block_hash: Database, OwnedType<[u8; 32]>>, -} +use crate::types::{Block, BlockHash, Body, Header, Txid}; #[derive(Debug, thiserror::Error)] pub enum Error { + #[error("invalid mainchain block hash for deposit")] + DepositInvalidMainBlockHash, #[error("heed error")] Heed(#[from] heed::Error), #[error("invalid previous side hash")] InvalidPrevSideHash, #[error("invalid merkle root")] InvalidMerkleRoot, + #[error("no block with hash {0}")] + NoBlock(BlockHash), + #[error("no BMM verification result with for block {0}")] + NoBmmVerification(BlockHash), + #[error("no deposits info for block {0}")] + NoDepositsInfo(bitcoin::BlockHash), #[error("no header with hash {0}")] NoHeader(BlockHash), + #[error("no height info hash {0}")] + NoHeight(BlockHash), + #[error("no mainchain header with hash {0}")] + NoMainHeader(bitcoin::BlockHash), #[error("no tx with txid {0}")] NoTx(Txid), } +#[derive(Clone)] +pub struct Archive { + block_hash_to_height: Database, SerdeBincode>, + /// BMM verification status for each header. + /// A status of false indicates that verification failed. + bmm_verifications: Database, SerdeBincode>, + bodies: Database, SerdeBincode>, + /// Deposits by mainchain block, sorted first-to-last in each block + deposits: Database< + SerdeBincode, + SerdeBincode>, + >, + /// Sidechain headers. All ancestors of any header should always be present. + headers: Database, SerdeBincode
>, + /// Mainchain headers. All ancestors of any header should always be present + main_headers: + Database, SerdeBincode>, + /// Total work for mainchain headers. + /// All ancestors of any block should always be present + total_work: + Database, SerdeBincode>, + /// Blocks in which a tx has been included + txid_to_inclusions: + Database, SerdeBincode>>, +} + impl Archive { - pub const NUM_DBS: u32 = 4; + pub const NUM_DBS: u32 = 8; pub fn new(env: &heed::Env) -> Result { - let headers = env.create_database(Some("headers"))?; + let block_hash_to_height = + env.create_database(Some("hash_to_height"))?; + let bmm_verifications = + env.create_database(Some("bmm_verifications"))?; let bodies = env.create_database(Some("bodies"))?; - let hash_to_height = env.create_database(Some("hash_to_height"))?; - let txid_to_block_hash = - env.create_database(Some("txid_to_block_hash"))?; + let deposits = env.create_database(Some("deposits"))?; + let headers = env.create_database(Some("headers"))?; + let main_headers = env.create_database(Some("main_headers"))?; + let total_work = env.create_database(Some("total_work"))?; + let txid_to_inclusions = + env.create_database(Some("txid_to_inclusions"))?; Ok(Self { - headers, + block_hash_to_height, + bmm_verifications, bodies, - hash_to_height, - txid_to_block_hash, + deposits, + headers, + main_headers, + total_work, + txid_to_inclusions, }) } - pub fn get_header( + /** Get the height of a block from it's hash. + * Returns [`None`] if no block with the specified hash exists. */ + pub fn try_get_height( &self, - txn: &RoTxn, - height: u32, - ) -> Result, Error> { - let height = height.to_be_bytes(); - let header = self.headers.get(txn, &height)?; - Ok(header) + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result, Error> { + if block_hash == BlockHash::default() { + Ok(Some(0)) + } else { + self.block_hash_to_height + .get(rotxn, &block_hash) + .map_err(Error::from) + } } - pub fn get_body( + /** Get the height of a block from it's hash. + * Returns an error if no block with the specified hash exists. */ + pub fn get_height( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result { + self.try_get_height(rotxn, block_hash)? + .ok_or(Error::NoHeight(block_hash)) + } + + pub fn try_get_bmm_verification( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result, Error> { + if block_hash == BlockHash::default() { + Ok(Some(true)) + } else { + self.bmm_verifications + .get(rotxn, &block_hash) + .map_err(Error::from) + } + } + + pub fn get_bmm_verification( &self, - txn: &RoTxn, - height: u32, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result { + self.try_get_bmm_verification(rotxn, block_hash)? + .ok_or(Error::NoBmmVerification(block_hash)) + } + + pub fn try_get_body( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, ) -> Result, Error> { - let height = height.to_be_bytes(); - let header = self.bodies.get(txn, &height)?; - Ok(header) + let body = self.bodies.get(rotxn, &block_hash)?; + Ok(body) } - pub fn get_best_hash(&self, txn: &RoTxn) -> Result { - let best_hash = match self.headers.last(txn)? { - Some((_, header)) => hashes::hash(&header).into(), - None => [0; 32].into(), - }; - Ok(best_hash) + pub fn get_body( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result { + self.try_get_body(rotxn, block_hash)? + .ok_or(Error::NoBlock(block_hash)) } - pub fn get_height(&self, txn: &RoTxn) -> Result { - let height = match self.headers.last(txn)? { - Some((height, _)) => BigEndian::read_u32(&height), - None => 0, - }; - Ok(height) + pub fn try_get_deposits( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result>, Error> { + let deposits = self.deposits.get(rotxn, &block_hash)?; + Ok(deposits) } - /** Get the height of a block from it's hash. - * Returns [`None`] if no block with the specified hash exists. */ - pub fn try_get_block_height( + pub fn get_deposits( &self, - txn: &RoTxn, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result, Error> { + self.try_get_deposits(rotxn, block_hash)? + .ok_or(Error::NoDepositsInfo(block_hash)) + } + + pub fn try_get_header( + &self, + rotxn: &RoTxn, block_hash: BlockHash, - ) -> Result, Error> { - let res = self - .hash_to_height - .get(txn, &block_hash.into())? - .map(|height| BigEndian::read_u32(&height)); - Ok(res) + ) -> Result, Error> { + let header = self.headers.get(rotxn, &block_hash)?; + Ok(header) } - /** Get the height of a block from it's hash. - * Returns an error if no block with the specified hash exists. */ - pub fn get_block_height( + pub fn get_header( &self, - txn: &RoTxn, + rotxn: &RoTxn, block_hash: BlockHash, - ) -> Result { - if let Some(block_height) = - self.try_get_block_height(txn, block_hash)? - { - Ok(block_height) - } else { - Err(Error::NoHeader(block_hash)) - } + ) -> Result { + self.try_get_header(rotxn, block_hash)? + .ok_or(Error::NoHeader(block_hash)) } - pub fn get_block( + pub fn try_get_block( &self, - txn: &RoTxn, + rotxn: &RoTxn, block_hash: BlockHash, - ) -> Result { - let height = self.get_block_height(txn, block_hash)?; - let header = self.get_header(txn, height)?.unwrap(); - let body = self.get_body(txn, height)?.unwrap(); + ) -> Result, Error> { + let Some(body) = self.try_get_body(rotxn, block_hash)? else { + return Ok(None); + }; + let header = self.get_header(rotxn, block_hash)?; + let height = self.get_height(rotxn, block_hash)?; let block = Block { header, body, height, }; - Ok(block) + Ok(Some(block)) + } + + pub fn get_block( + &self, + rotxn: &RoTxn, + block_hash: BlockHash, + ) -> Result { + self.try_get_block(rotxn, block_hash)? + .ok_or(Error::NoBlock(block_hash)) + } + + pub fn try_get_main_header( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result, Error> { + let header = self.main_headers.get(rotxn, &block_hash)?; + Ok(header) + } + + fn get_main_header( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result { + self.try_get_main_header(rotxn, block_hash)? + .ok_or(Error::NoMainHeader(block_hash)) + } + + pub fn try_get_total_work( + &self, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result, Error> { + let total_work = self.total_work.get(rotxn, &block_hash)?; + Ok(total_work) } - /** Returns the height of the block in which the tx was included, - * if it was included */ - pub fn try_get_tx_height( + pub fn get_total_work( &self, - txn: &RoTxn, + rotxn: &RoTxn, + block_hash: bitcoin::BlockHash, + ) -> Result { + self.try_get_total_work(rotxn, block_hash)? + .ok_or(Error::NoMainHeader(block_hash)) + } + + /// Get blocks in which a tx was included. + pub fn get_tx_inclusions( + &self, + rotxn: &RoTxn, txid: Txid, - ) -> Result, Error> { - let Some(block_hash) = - self.txid_to_block_hash.get(txn, &txid.into())? - else { - return Ok(None); - }; - let height = self.get_block_height(txn, block_hash.into())?; - Ok(Some(height)) + ) -> Result, Error> { + let inclusions = self + .txid_to_inclusions + .get(rotxn, &txid)? + .unwrap_or_default(); + Ok(inclusions) } - /** Returns the height of the block in which the tx was included. - * Returns an error if the tx does not exist in any known block. */ - pub fn get_tx_height(&self, txn: &RoTxn, txid: Txid) -> Result { - if let Some(height) = self.try_get_tx_height(txn, txid)? { - Ok(height) - } else { - Err(Error::NoTx(txid)) - } + /// Store a BMM verification result + pub fn put_bmm_verification( + &self, + rwtxn: &mut RwTxn, + block_hash: BlockHash, + verification_result: bool, + ) -> Result<(), Error> { + self.bmm_verifications + .put(rwtxn, &block_hash, &verification_result)?; + Ok(()) } + /// Store a block body. The header must already exist. pub fn put_body( &self, - txn: &mut RwTxn, - header: &Header, + rwtxn: &mut RwTxn, + block_hash: BlockHash, body: &Body, ) -> Result<(), Error> { - /* - if header.merkle_root != body.compute_merkle_root() { - return Err(Error::InvalidMerkleRoot); - } - */ - let block_hash = header.hash(); - let height = self - .hash_to_height - .get(txn, &block_hash.into())? - .ok_or(Error::NoHeader(block_hash))?; - self.bodies.put(txn, &height, body)?; + let _header = self.get_header(rwtxn, block_hash)?; + self.bodies.put(rwtxn, &block_hash, body)?; body.transactions.iter().try_for_each(|tx| { - self.txid_to_block_hash.put( - txn, - &tx.txid().into(), - &block_hash.into(), - ) - })?; + let txid = tx.txid(); + let mut inclusions = self.get_tx_inclusions(rwtxn, txid)?; + inclusions.insert(block_hash); + self.txid_to_inclusions.put(rwtxn, &txid, &inclusions)?; + Ok(()) + }) + } + + /// Store deposit info for a block + pub fn put_deposits( + &self, + rwtxn: &mut RwTxn, + block_hash: bitcoin::BlockHash, + mut deposits: Vec, + ) -> Result<(), Error> { + deposits.sort_by_key(|deposit| deposit.tx_index); + if !deposits + .iter() + .all(|deposit| deposit.block_hash == block_hash) + { + return Err(Error::DepositInvalidMainBlockHash); + }; + self.deposits.put(rwtxn, &block_hash, &deposits)?; Ok(()) } - pub fn append_header( + pub fn put_header( &self, - txn: &mut RwTxn, + rwtxn: &mut RwTxn, header: &Header, ) -> Result<(), Error> { - let height = self.get_height(txn)?; - let best_hash = self.get_best_hash(txn)?; - if header.prev_side_hash != best_hash { + let Some(prev_height) = + self.try_get_height(rwtxn, header.prev_side_hash)? + else { return Err(Error::InvalidPrevSideHash); + }; + let height = prev_height + 1; + let block_hash = header.hash(); + self.block_hash_to_height.put(rwtxn, &block_hash, &height)?; + self.headers.put(rwtxn, &block_hash, header)?; + Ok(()) + } + + pub fn put_main_header( + &self, + rwtxn: &mut RwTxn, + header: &BitcoinHeader, + ) -> Result<(), Error> { + if self + .try_get_main_header(rwtxn, header.prev_blockhash)? + .is_none() + && header.prev_blockhash != bitcoin::BlockHash::all_zeros() + { + return Err(Error::NoMainHeader(header.prev_blockhash)); } - let new_height = (height + 1).to_be_bytes(); - self.headers.put(txn, &new_height, header)?; - self.hash_to_height - .put(txn, &header.hash().into(), &new_height)?; + let block_hash = header.block_hash(); + let total_work = + if header.prev_blockhash != bitcoin::BlockHash::all_zeros() { + let prev_work = + self.get_total_work(rwtxn, header.prev_blockhash)?; + prev_work + header.work() + } else { + header.work() + }; + self.main_headers.put(rwtxn, &block_hash, header)?; + self.total_work.put(rwtxn, &block_hash, &total_work)?; Ok(()) } + + /// Return a fallible iterator over ancestors of a block, + /// starting with the specified block's header + pub fn ancestors<'a>( + &'a self, + rotxn: &'a RoTxn, + mut block_hash: BlockHash, + ) -> impl FallibleIterator + 'a { + fallible_iterator::from_fn(move || { + if block_hash == BlockHash::default() { + Ok(None) + } else { + let res = Some(block_hash); + let header = self.get_header(rotxn, block_hash)?; + block_hash = header.prev_side_hash; + Ok(res) + } + }) + } + + /// Returns true if the second specified block is a descendant of the first + /// specified block + /// Returns an error if either of the specified block headers do not exist + /// in the archive. + pub fn is_descendant( + &self, + rotxn: &RoTxn, + ancestor: BlockHash, + descendant: BlockHash, + ) -> Result { + if ancestor == descendant { + return Ok(true); + } + let ancestor_height = self.get_height(rotxn, ancestor)?; + let descendant_height = self.get_height(rotxn, descendant)?; + if ancestor_height > descendant_height { + return Ok(false); + } + self.ancestors(rotxn, descendant) + .skip(1) + .take((descendant_height - ancestor_height) as usize) + .any(|block_hash| Ok(block_hash == ancestor)) + } + + /// Return a fallible iterator over ancestors of a mainchain block, + /// starting with the specified block's header + pub fn main_ancestors<'a>( + &'a self, + rotxn: &'a RoTxn, + mut block_hash: bitcoin::BlockHash, + ) -> impl FallibleIterator + 'a + { + fallible_iterator::from_fn(move || { + if block_hash == bitcoin::BlockHash::all_zeros() { + Ok(None) + } else { + let res = Some(block_hash); + let header = self.get_main_header(rotxn, block_hash)?; + block_hash = header.prev_blockhash; + Ok(res) + } + }) + } + + /// Find the last common ancestor of two blocks, if headers for both exist + pub fn last_common_ancestor( + &self, + rotxn: &RoTxn, + mut block_hash0: BlockHash, + mut block_hash1: BlockHash, + ) -> Result { + let mut height0 = self.get_height(rotxn, block_hash0)?; + let mut height1 = self.get_height(rotxn, block_hash1)?; + let mut header0 = self.try_get_header(rotxn, block_hash0)?; + let mut header1 = self.try_get_header(rotxn, block_hash1)?; + // Find respective ancestors of block_hash0 and block_hash1 with height + // equal to min(height0, height1) + loop { + match height0.cmp(&height1) { + Ordering::Less => { + block_hash1 = header1.unwrap().prev_side_hash; + header1 = self.try_get_header(rotxn, block_hash1)?; + height1 -= 1; + } + Ordering::Greater => { + block_hash0 = header0.unwrap().prev_side_hash; + header0 = self.try_get_header(rotxn, block_hash0)?; + height0 -= 1; + } + Ordering::Equal => { + if block_hash0 == block_hash1 { + return Ok(block_hash0); + } else { + block_hash0 = header0.unwrap().prev_side_hash; + block_hash1 = header1.unwrap().prev_side_hash; + header0 = self.try_get_header(rotxn, block_hash0)?; + header1 = self.try_get_header(rotxn, block_hash1)?; + height0 -= 1; + height1 -= 1; + } + } + }; + } + } } diff --git a/lib/authorization.rs b/lib/authorization.rs index 9ef78fe..e414268 100644 --- a/lib/authorization.rs +++ b/lib/authorization.rs @@ -11,6 +11,8 @@ pub use ed25519_dalek::{ #[derive(Debug, thiserror::Error)] pub enum Error { + #[error("borsh serialization error")] + BorshSerialize(#[from] borsh::io::Error), #[error("ed25519_dalek error")] DalekError(#[from] SignatureError), #[error("bincode error")] @@ -69,8 +71,8 @@ struct Package<'a> { pub fn verify_authorized_transaction( transaction: &AuthorizedTransaction, ) -> Result<(), Error> { - let serialized_transaction = bincode::serialize(&transaction.transaction)?; - let messages: Vec<_> = std::iter::repeat(serialized_transaction.as_slice()) + let tx_bytes_canonical = borsh::to_vec(&transaction.transaction)?; + let messages: Vec<_> = std::iter::repeat(tx_bytes_canonical.as_slice()) .take(transaction.authorizations.len()) .collect(); let (verifying_keys, signatures): (Vec, Vec) = @@ -96,7 +98,7 @@ pub fn verify_authorizations(body: &Body) -> Result<(), Error> { let serialized_transactions: Vec> = body .transactions .par_iter() - .map(bincode::serialize) + .map(borsh::to_vec) .collect::>()?; let serialized_transactions = serialized_transactions.iter().map(Vec::as_slice); @@ -160,11 +162,11 @@ pub fn verify_authorizations(body: &Body) -> Result<(), Error> { } pub fn sign( - keypair: &SigningKey, + signing_key: &SigningKey, transaction: &Transaction, ) -> Result { - let message = bincode::serialize(&transaction)?; - Ok(keypair.sign(&message)) + let tx_bytes_canonical = borsh::to_vec(&transaction)?; + Ok(signing_key.sign(&tx_bytes_canonical)) } pub fn authorize( @@ -173,7 +175,7 @@ pub fn authorize( ) -> Result { let mut authorizations: Vec = Vec::with_capacity(addresses_signing_keys.len()); - let message = bincode::serialize(&transaction)?; + let tx_bytes_canonical = borsh::to_vec(&transaction)?; for (address, signing_key) in addresses_signing_keys { let hash_verifying_key = get_address(&signing_key.verifying_key()); if *address != hash_verifying_key { @@ -184,7 +186,7 @@ pub fn authorize( } let authorization = Authorization { verifying_key: signing_key.verifying_key(), - signature: signing_key.sign(&message), + signature: signing_key.sign(&tx_bytes_canonical), }; authorizations.push(authorization); } diff --git a/lib/lib.rs b/lib/lib.rs index 2d7c5c7..01c9136 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -1,3 +1,5 @@ +#![feature(let_chains)] + pub mod archive; pub mod authorization; pub mod mempool; diff --git a/lib/mempool.rs b/lib/mempool.rs index 39dcc86..6c68ae3 100644 --- a/lib/mempool.rs +++ b/lib/mempool.rs @@ -1,12 +1,22 @@ +use std::collections::VecDeque; + +use heed::{types::SerdeBincode, Database, RoTxn, RwTxn}; + use crate::types::{AuthorizedTransaction, OutPoint, Txid}; -use heed::types::*; -use heed::{Database, RoTxn, RwTxn}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("can't add transaction, utxo double spent")] + UtxoDoubleSpent, +} #[derive(Clone)] pub struct MemPool { pub transactions: - Database, SerdeBincode>, - pub spent_utxos: Database, Unit>, + Database, SerdeBincode>, + pub spent_utxos: Database, SerdeBincode>, } impl MemPool { @@ -26,26 +36,39 @@ impl MemPool { txn: &mut RwTxn, transaction: &AuthorizedTransaction, ) -> Result<(), Error> { - println!( - "adding transaction {} to mempool", - transaction.transaction.txid() - ); + let txid = transaction.transaction.txid(); + tracing::debug!("adding transaction {txid} to mempool"); for input in &transaction.transaction.inputs { if self.spent_utxos.get(txn, input)?.is_some() { return Err(Error::UtxoDoubleSpent); } - self.spent_utxos.put(txn, input, &())?; + self.spent_utxos.put(txn, input, &txid)?; } - self.transactions.put( - txn, - &transaction.transaction.txid().into(), - transaction, - )?; + self.transactions.put(txn, &txid, transaction)?; Ok(()) } - pub fn delete(&self, txn: &mut RwTxn, txid: &Txid) -> Result<(), Error> { - self.transactions.delete(txn, txid.into())?; + pub fn delete(&self, rwtxn: &mut RwTxn, txid: Txid) -> Result<(), Error> { + let mut pending_deletes = VecDeque::from([txid]); + while let Some(txid) = pending_deletes.pop_front() { + if let Some(tx) = self.transactions.get(rwtxn, &txid)? { + for outpoint in &tx.transaction.inputs { + self.spent_utxos.delete(rwtxn, outpoint)?; + } + self.transactions.delete(rwtxn, &txid)?; + for vout in 0..tx.transaction.outputs.len() { + let outpoint = OutPoint::Regular { + txid, + vout: vout as u32, + }; + if let Some(child_txid) = + self.spent_utxos.get(rwtxn, &outpoint)? + { + pending_deletes.push_back(child_txid); + } + } + } + } Ok(()) } @@ -74,11 +97,3 @@ impl MemPool { Ok(transactions) } } - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("heed error")] - Heed(#[from] heed::Error), - #[error("can't add transaction, utxo double spent")] - UtxoDoubleSpent, -} diff --git a/lib/miner.rs b/lib/miner.rs index 0aac9b0..796faa4 100644 --- a/lib/miner.rs +++ b/lib/miner.rs @@ -54,7 +54,7 @@ impl Miner { height: u32, header: Header, body: Body, - ) -> Result<(), Error> { + ) -> Result { let str_hash_prev = header.prev_main_hash.to_string(); let critical_hash: [u8; 32] = header.hash().into(); let critical_hash = bitcoin::BlockHash::from_byte_array(critical_hash); @@ -76,11 +76,12 @@ impl Miner { .as_str() .map(|s| s.to_owned()) .ok_or(Error::InvalidJson { json: value })?; - let _ = + let txid = bitcoin::Txid::from_str(&txid).map_err(bip300301::Error::from)?; + tracing::info!("created BMM tx: {txid}"); //assert_eq!(header.merkle_root, body.compute_merkle_root()); self.block = Some((header, body)); - Ok(()) + Ok(txid) } pub async fn confirm_bmm( diff --git a/lib/net.rs b/lib/net.rs deleted file mode 100644 index 276a95e..0000000 --- a/lib/net.rs +++ /dev/null @@ -1,212 +0,0 @@ -use std::{collections::HashMap, net::SocketAddr, sync::Arc}; - -use quinn::{ClientConfig, Connection, Endpoint, ServerConfig}; -use serde::{Deserialize, Serialize}; -use tokio::sync::RwLock; - -use crate::types::{AuthorizedTransaction, Body, Header}; - -pub const READ_LIMIT: usize = 1024; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("address parse error")] - AddrParse(#[from] std::net::AddrParseError), - #[error("quinn error")] - Io(#[from] std::io::Error), - #[error("connect error")] - Connect(#[from] quinn::ConnectError), - #[error("connection error")] - Connection(#[from] quinn::ConnectionError), - #[error("rcgen")] - RcGen(#[from] rcgen::RcgenError), - #[error("accept error")] - AcceptError, - #[error("read to end error")] - ReadToEnd(#[from] quinn::ReadToEndError), - #[error("write error")] - Write(#[from] quinn::WriteError), - #[error("send datagram error")] - SendDatagram(#[from] quinn::SendDatagramError), - #[error("quinn rustls error")] - QuinnRustls(#[from] quinn::crypto::rustls::Error), - #[error("bincode error")] - Bincode(#[from] bincode::Error), - #[error("already connected to peer at {0}")] - AlreadyConnected(SocketAddr), -} - -#[derive(Clone, Debug, Serialize, Deserialize, Default)] -pub struct PeerState { - pub block_height: u32, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum Request { - GetBlock { height: u32 }, - PushTransaction { transaction: AuthorizedTransaction }, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum Response { - Block { header: Header, body: Body }, - NoBlock, - TransactionAccepted, - TransactionRejected, -} - -#[derive(Clone)] -pub struct Peer { - pub state: Arc>>, - pub connection: Connection, -} - -impl Peer { - pub fn heart_beat(&self, state: &PeerState) -> Result<(), Error> { - let message = bincode::serialize(state)?; - self.connection.send_datagram(bytes::Bytes::from(message))?; - Ok(()) - } - - pub async fn request(&self, message: &Request) -> Result { - let (mut send, mut recv) = self.connection.open_bi().await?; - let message = bincode::serialize(message)?; - send.write_all(&message).await?; - send.finish().await?; - let response = recv.read_to_end(READ_LIMIT).await?; - let response: Response = bincode::deserialize(&response)?; - Ok(response) - } -} - -// State. -// Archive. - -// Keep track of peer state -// Exchange metadata -// Bulk download -// Propagation -// -// Initial block download -// -// 1. Download headers -// 2. Download blocks -// 3. Update the state -#[derive(Clone)] -pub struct Net { - pub client: Endpoint, - pub server: Endpoint, - pub peers: Arc>>, -} - -impl Net { - pub fn new(bind_addr: SocketAddr) -> Result { - let (server, _) = make_server_endpoint(bind_addr)?; - let client = make_client_endpoint("0.0.0.0:0".parse()?)?; - let peers = Arc::new(RwLock::new(HashMap::new())); - Ok(Net { - server, - client, - peers, - }) - } - pub async fn connect_peer(&self, addr: SocketAddr) -> Result { - for peer in self.peers.read().await.values() { - if peer.connection.remote_address() == addr { - return Err(Error::AlreadyConnected(addr)); - } - } - let connection = self.client.connect(addr, "localhost")?.await?; - tracing::debug!("Connected to peer at {addr}"); - let peer = Peer { - state: Arc::new(RwLock::new(None)), - connection, - }; - self.peers - .write() - .await - .insert(peer.connection.stable_id(), peer.clone()); - Ok(peer) - } - - pub async fn disconnect( - &self, - stable_id: usize, - ) -> Result, Error> { - let peer = self.peers.write().await.remove(&stable_id); - Ok(peer) - } -} - -#[allow(unused)] -pub fn make_client_endpoint(bind_addr: SocketAddr) -> Result { - let client_cfg = configure_client(); - let mut endpoint = Endpoint::client(bind_addr)?; - endpoint.set_default_client_config(client_cfg); - Ok(endpoint) -} - -/// Constructs a QUIC endpoint configured to listen for incoming connections on a certain address -/// and port. -/// -/// ## Returns -/// -/// - a stream of incoming QUIC connections -/// - server certificate serialized into DER format -#[allow(unused)] -pub fn make_server_endpoint( - bind_addr: SocketAddr, -) -> Result<(Endpoint, Vec), Error> { - let (server_config, server_cert) = configure_server()?; - let endpoint = Endpoint::server(server_config, bind_addr)?; - Ok((endpoint, server_cert)) -} - -/// Returns default server configuration along with its certificate. -fn configure_server() -> Result<(ServerConfig, Vec), Error> { - let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])?; - let cert_der = cert.serialize_der()?; - let priv_key = cert.serialize_private_key_der(); - let priv_key = rustls::PrivateKey(priv_key); - let cert_chain = vec![rustls::Certificate(cert_der.clone())]; - - let mut server_config = - ServerConfig::with_single_cert(cert_chain, priv_key)?; - let transport_config = Arc::get_mut(&mut server_config.transport).unwrap(); - transport_config.max_concurrent_uni_streams(1_u8.into()); - - Ok((server_config, cert_der)) -} - -/// Dummy certificate verifier that treats any certificate as valid. -/// NOTE, such verification is vulnerable to MITM attacks, but convenient for testing. -struct SkipServerVerification; - -impl SkipServerVerification { - fn new() -> Arc { - Arc::new(Self) - } -} - -impl rustls::client::ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( - &self, - _end_entity: &rustls::Certificate, - _intermediates: &[rustls::Certificate], - _server_name: &rustls::ServerName, - _scts: &mut dyn Iterator, - _ocsp_response: &[u8], - _now: std::time::SystemTime, - ) -> Result { - Ok(rustls::client::ServerCertVerified::assertion()) - } -} - -fn configure_client() -> ClientConfig { - let crypto = rustls::ClientConfig::builder() - .with_safe_defaults() - .with_custom_certificate_verifier(SkipServerVerification::new()) - .with_no_client_auth(); - - ClientConfig::new(Arc::new(crypto)) -} diff --git a/lib/net/mod.rs b/lib/net/mod.rs new file mode 100644 index 0000000..10580f1 --- /dev/null +++ b/lib/net/mod.rs @@ -0,0 +1,325 @@ +use std::{ + collections::{hash_map, HashMap, HashSet}, + net::SocketAddr, + sync::Arc, +}; + +use fallible_iterator::{FallibleIterator, IteratorExt}; +use futures::{channel::mpsc, StreamExt}; +use heed::{ + types::{OwnedType, SerdeBincode}, + Database, +}; +use parking_lot::RwLock; +use quinn::{ClientConfig, Endpoint, ServerConfig}; +use tokio_stream::StreamNotifyClose; + +use crate::{archive::Archive, state::State, types::AuthorizedTransaction}; + +mod peer; + +use peer::{ + Connection, ConnectionContext as PeerConnectionCtxt, + ConnectionHandle as PeerConnectionHandle, +}; +pub use peer::{ + ConnectionError as PeerConnectionError, Info as PeerConnectionInfo, + Request as PeerRequest, Response as PeerResponse, +}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("accept error")] + AcceptError, + #[error("address parse error")] + AddrParse(#[from] std::net::AddrParseError), + #[error("already connected to peer at {0}")] + AlreadyConnected(SocketAddr), + #[error("bincode error")] + Bincode(#[from] bincode::Error), + #[error("connect error")] + Connect(#[from] quinn::ConnectError), + #[error("connection error")] + Connection(#[from] quinn::ConnectionError), + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("quinn error")] + Io(#[from] std::io::Error), + #[error("peer connection")] + PeerConnection(#[from] PeerConnectionError), + #[error("quinn rustls error")] + QuinnRustls(#[from] quinn::crypto::rustls::Error), + #[error("rcgen")] + RcGen(#[from] rcgen::RcgenError), + #[error("read to end error")] + ReadToEnd(#[from] quinn::ReadToEndError), + #[error("send datagram error")] + SendDatagram(#[from] quinn::SendDatagramError), + #[error("server endpoint closed")] + ServerEndpointClosed, + #[error("write error")] + Write(#[from] quinn::WriteError), +} + +pub fn make_client_endpoint(bind_addr: SocketAddr) -> Result { + let client_cfg = configure_client(); + let mut endpoint = Endpoint::client(bind_addr)?; + endpoint.set_default_client_config(client_cfg); + Ok(endpoint) +} + +// None indicates that the stream has ended +pub type PeerInfoRx = + mpsc::UnboundedReceiver<(SocketAddr, Option)>; + +// State. +// Archive. + +// Keep track of peer state +// Exchange metadata +// Bulk download +// Propagation +// +// Initial block download +// +// 1. Download headers +// 2. Download blocks +// 3. Update the state +#[derive(Clone)] +pub struct Net { + pub client: Endpoint, + pub server: Endpoint, + archive: Archive, + state: State, + active_peers: Arc>>, + // None indicates that the stream has ended + peer_info_tx: + mpsc::UnboundedSender<(SocketAddr, Option)>, + known_peers: Database, OwnedType<()>>, +} + +impl Net { + pub const NUM_DBS: u32 = 1; + + fn add_active_peer( + &self, + addr: SocketAddr, + peer_connection_handle: PeerConnectionHandle, + ) -> Result<(), Error> { + let mut active_peers_write = self.active_peers.write(); + match active_peers_write.entry(addr) { + hash_map::Entry::Occupied(_) => Err(Error::AlreadyConnected(addr)), + hash_map::Entry::Vacant(active_peer_entry) => { + active_peer_entry.insert(peer_connection_handle); + Ok(()) + } + } + } + + pub fn remove_active_peer(&self, addr: SocketAddr) { + let mut active_peers_write = self.active_peers.write(); + if let Some(peer_connection) = active_peers_write.remove(&addr) { + drop(peer_connection); + tracing::info!("Disconnected from peer at {addr}") + } + } + + pub fn connect_peer( + &self, + env: heed::Env, + addr: SocketAddr, + ) -> Result<(), Error> { + if self.active_peers.read().contains_key(&addr) { + return Err(Error::AlreadyConnected(addr)); + } + let mut rwtxn = env.write_txn()?; + self.known_peers.put(&mut rwtxn, &addr, &())?; + rwtxn.commit()?; + let connection_ctxt = PeerConnectionCtxt { + env, + archive: self.archive.clone(), + state: self.state.clone(), + }; + let (connection_handle, info_rx) = + peer::connect(self.client.clone(), addr, connection_ctxt); + tokio::spawn({ + let info_rx = StreamNotifyClose::new(info_rx) + .map(move |info| Ok((addr, info))); + let peer_info_tx = self.peer_info_tx.clone(); + async move { + if let Err(_send_err) = info_rx.forward(peer_info_tx).await { + tracing::error!(%addr, "Failed to send peer connection info"); + } + } + }); + self.add_active_peer(addr, connection_handle)?; + Ok(()) + } + + pub fn new( + env: &heed::Env, + archive: Archive, + state: State, + bind_addr: SocketAddr, + ) -> Result<(Self, PeerInfoRx), Error> { + let (server, _) = make_server_endpoint(bind_addr)?; + let client = make_client_endpoint("0.0.0.0:0".parse()?)?; + let active_peers = Arc::new(RwLock::new(HashMap::new())); + let known_peers = env.create_database(Some("known_peers"))?; + let (peer_info_tx, peer_info_rx) = mpsc::unbounded(); + let net = Net { + server, + client, + archive, + state, + active_peers, + peer_info_tx, + known_peers, + }; + #[allow(clippy::let_and_return)] + let known_peers: Vec<_> = { + let rotxn = env.read_txn()?; + let known_peers = net + .known_peers + .iter(&rotxn)? + .transpose_into_fallible() + .collect()?; + known_peers + }; + let () = known_peers.into_iter().try_for_each(|(peer_addr, _)| { + net.connect_peer(env.clone(), peer_addr) + })?; + Ok((net, peer_info_rx)) + } + + /// Accept the next incoming connection + pub async fn accept_incoming(&self, env: heed::Env) -> Result<(), Error> { + let connection = match self.server.accept().await { + Some(conn) => Connection(conn.await?), + None => return Err(Error::ServerEndpointClosed), + }; + let addr = connection.addr(); + if self.active_peers.read().contains_key(&addr) { + tracing::info!( + "already connected to {addr}, refusing duplicate connection", + ); + connection + .0 + .close(quinn::VarInt::from_u32(1), b"already connected"); + } + if connection.0.close_reason().is_some() { + return Ok(()); + } + tracing::info!("connected to peer at {addr}"); + let mut rwtxn = env.write_txn()?; + self.known_peers.put(&mut rwtxn, &addr, &())?; + rwtxn.commit()?; + let connection_ctxt = PeerConnectionCtxt { + env, + archive: self.archive.clone(), + state: self.state.clone(), + }; + let (connection_handle, info_rx) = + peer::handle(connection_ctxt, connection); + tokio::spawn({ + let info_rx = StreamNotifyClose::new(info_rx) + .map(move |info| Ok((addr, info))); + let peer_info_tx = self.peer_info_tx.clone(); + async move { + if let Err(_send_err) = info_rx.forward(peer_info_tx).await { + tracing::error!(%addr, "Failed to send peer connection info"); + } + } + }); + self.add_active_peer(addr, connection_handle)?; + Ok(()) + } + + /// Push a tx to all active peers, except those in the provided set + pub fn push_tx( + &self, + exclude: HashSet, + tx: AuthorizedTransaction, + ) { + self.active_peers + .read() + .iter() + .filter(|(addr, _)| !exclude.contains(addr)) + .for_each(|(addr, peer_connection_handle)| { + if let Err(_send_err) = peer_connection_handle + .forward_request_tx + .unbounded_send(PeerRequest::PushTransaction { + transaction: tx.clone(), + }) + { + let txid = tx.transaction.txid(); + tracing::warn!("Failed to push tx {txid} to peer at {addr}") + } + }) + } +} + +/// Constructs a QUIC endpoint configured to listen for incoming connections on a certain address +/// and port. +/// +/// ## Returns +/// +/// - a stream of incoming QUIC connections +/// - server certificate serialized into DER format +#[allow(unused)] +pub fn make_server_endpoint( + bind_addr: SocketAddr, +) -> Result<(Endpoint, Vec), Error> { + let (server_config, server_cert) = configure_server()?; + let endpoint = Endpoint::server(server_config, bind_addr)?; + Ok((endpoint, server_cert)) +} + +/// Returns default server configuration along with its certificate. +fn configure_server() -> Result<(ServerConfig, Vec), Error> { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])?; + let cert_der = cert.serialize_der()?; + let priv_key = cert.serialize_private_key_der(); + let priv_key = rustls::PrivateKey(priv_key); + let cert_chain = vec![rustls::Certificate(cert_der.clone())]; + + let mut server_config = + ServerConfig::with_single_cert(cert_chain, priv_key)?; + let transport_config = Arc::get_mut(&mut server_config.transport).unwrap(); + transport_config.max_concurrent_uni_streams(1_u8.into()); + + Ok((server_config, cert_der)) +} + +/// Dummy certificate verifier that treats any certificate as valid. +/// NOTE, such verification is vulnerable to MITM attacks, but convenient for testing. +struct SkipServerVerification; + +impl SkipServerVerification { + fn new() -> Arc { + Arc::new(Self) + } +} + +impl rustls::client::ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::Certificate, + _intermediates: &[rustls::Certificate], + _server_name: &rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: std::time::SystemTime, + ) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } +} + +fn configure_client() -> ClientConfig { + let crypto = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_custom_certificate_verifier(SkipServerVerification::new()) + .with_no_client_auth(); + + ClientConfig::new(Arc::new(crypto)) +} diff --git a/lib/net/peer.rs b/lib/net/peer.rs new file mode 100644 index 0000000..f8a0c62 --- /dev/null +++ b/lib/net/peer.rs @@ -0,0 +1,713 @@ +use std::{collections::HashSet, net::SocketAddr}; + +use bip300301::bitcoin::{self, hashes::Hash}; +use fallible_iterator::FallibleIterator; +use futures::{channel::mpsc, stream, StreamExt, TryFutureExt, TryStreamExt}; +use quinn::{Endpoint, SendStream}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use tokio::{ + spawn, + task::{JoinHandle, JoinSet}, + time::{interval, timeout, Duration}, +}; +use tokio_stream::wrappers::IntervalStream; + +use crate::{ + archive::{self, Archive}, + state::{self, State}, + types::{AuthorizedTransaction, BlockHash, Body, Header, Txid}, +}; + +#[derive(Debug, Error)] +pub enum BanReason { + #[error("BMM verification failed for block {0}")] + BmmVerificationFailed(BlockHash), + #[error("Incorrect total work for block {block_hash}: {total_work:?}")] + IncorrectTotalWork { + block_hash: BlockHash, + total_work: Option, + }, +} + +#[must_use] +#[derive(Debug, Error)] +pub enum ConnectionError { + #[error("archive error")] + Archive(#[from] archive::Error), + #[error("bincode error")] + Bincode(#[from] bincode::Error), + #[error("connect error")] + Connect(#[from] quinn::ConnectError), + #[error("connection error")] + Connection(#[from] quinn::ConnectionError), + #[error("Heartbeat timeout")] + HeartbeatTimeout, + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("peer should be banned; {0}")] + PeerBan(#[from] BanReason), + #[error("read to end error")] + ReadToEnd(#[from] quinn::ReadToEndError), + #[error("send datagram error")] + SendDatagram(#[from] quinn::SendDatagramError), + #[error("send forward request error")] + SendForwardRequest, + #[error("send info error")] + SendInfo, + #[error("state error")] + State(#[from] state::Error), + #[error("write error")] + Write(#[from] quinn::WriteError), +} + +impl From> for ConnectionError { + fn from(_: mpsc::TrySendError) -> Self { + Self::SendInfo + } +} + +impl From> for ConnectionError { + fn from(_: mpsc::TrySendError) -> Self { + Self::SendForwardRequest + } +} + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct PeerState { + block_height: u32, + tip: BlockHash, + total_work: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Response { + Block { + header: Header, + body: Body, + }, + /// Headers, from start to end + Headers(Vec
), + NoBlock { + block_hash: BlockHash, + }, + NoHeader { + block_hash: BlockHash, + }, + TransactionAccepted(Txid), + TransactionRejected(Txid), +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum Request { + Heartbeat(PeerState), + GetBlock { + block_hash: BlockHash, + }, + /// Request headers up to [`end`] + GetHeaders { + /// Request headers AFTER (not including) the first ancestor found in + /// the specified list, if such an ancestor exists. + start: HashSet, + end: BlockHash, + /// Height is only relevant for the requester, + /// so serialization is skipped + #[serde(skip)] + height: Option, + }, + PushTransaction { + transaction: AuthorizedTransaction, + }, +} + +impl Request { + fn expect_response(&self) -> bool { + match self { + Self::GetBlock { .. } + | Self::GetHeaders { .. } + | Self::PushTransaction { .. } => true, + Self::Heartbeat(_) => false, + } + } +} + +#[must_use] +#[derive(Debug)] +pub enum Info { + Error(ConnectionError), + /// Need BMM verification for the specified blocks + NeedBmmVerification(Vec), + /// Need Mainchain ancestors for the specified block hash + NeedMainchainAncestors(bitcoin::BlockHash), + /// New tip ready (body and header exist in archive, BMM verified) + NewTipReady(BlockHash), + NewTransaction(AuthorizedTransaction), + Response(Response, Request), +} + +impl From for Info { + fn from(err: ConnectionError) -> Self { + Self::Error(err) + } +} + +impl From> for Info +where + Info: From, +{ + fn from(res: Result) -> Self { + match res { + Ok(value) => value.into(), + Err(err) => Self::Error(err), + } + } +} + +#[derive(Clone)] +pub struct Connection(pub(super) quinn::Connection); + +impl Connection { + pub const READ_LIMIT: usize = 1024; + + pub const HEARTBEAT_SEND_INTERVAL: Duration = Duration::from_secs(1); + + pub const HEARTBEAT_TIMEOUT_INTERVAL: Duration = Duration::from_secs(5); + + pub fn addr(&self) -> SocketAddr { + self.0.remote_address() + } + + pub async fn new( + endpoint: &Endpoint, + addr: SocketAddr, + ) -> Result { + let connection = endpoint.connect(addr, "localhost")?.await?; + tracing::info!("Connected to peer at {addr}"); + Ok(Self(connection)) + } + + async fn receive_request( + &self, + ) -> Result<(Request, SendStream), ConnectionError> { + let (tx, mut rx) = self.0.accept_bi().await?; + let request_bytes = rx.read_to_end(Connection::READ_LIMIT).await?; + let request: Request = bincode::deserialize(&request_bytes)?; + Ok((request, tx)) + } + + pub async fn request( + &self, + message: &Request, + ) -> Result, ConnectionError> { + let expect_response = message.expect_response(); + let (mut send, mut recv) = self.0.open_bi().await?; + let message = bincode::serialize(message)?; + send.write_all(&message).await?; + send.finish().await?; + if expect_response { + let response = recv.read_to_end(Self::READ_LIMIT).await?; + let response: Response = bincode::deserialize(&response)?; + Ok(Some(response)) + } else { + Ok(None) + } + } +} + +pub struct ConnectionContext { + pub env: heed::Env, + pub archive: Archive, + pub state: State, +} + +struct ConnectionTask { + connection: Connection, + ctxt: ConnectionContext, + info_tx: mpsc::UnboundedSender, + peer_state: Option, + /// Push a request to forward to the peer + forward_request_tx: mpsc::UnboundedSender, + /// Receive requests to forward to the peer + forward_request_rx: mpsc::UnboundedReceiver, +} + +impl ConnectionTask { + async fn send_request( + conn: &Connection, + info_tx: &mpsc::UnboundedSender, + request: Request, + ) { + let resp = match conn.request(&request).await { + Ok(Some(resp)) => Ok(resp), + Err(err) => Err(err), + Ok(None) => return, + }; + let info = resp.map(|resp| Info::Response(resp, request)).into(); + if info_tx.unbounded_send(info).is_err() { + let addr = conn.addr(); + tracing::error!(%addr, "Failed to send peer connection info") + }; + } + + async fn send_response( + mut response_tx: SendStream, + response: Response, + ) -> Result<(), ConnectionError> { + let response_bytes = bincode::serialize(&response)?; + response_tx + .write_all(&response_bytes) + .await + .map_err(ConnectionError::from) + } + + /// If a new tip is announced with greater height than the current tip: + /// * If the header does not exist, request it + /// * Verify height of the new tip. + /// * If the previous mainchain header does not exist, request it + /// * Verify PoW + /// * Verify BMM + /// * If ancestor bodies do not exist, request them + /// * Attempt to apply the new tip + async fn handle_heartbeat( + ctxt: &ConnectionContext, + info_tx: &mpsc::UnboundedSender, + forward_request_tx: &mpsc::UnboundedSender, + peer_state: &PeerState, + ) -> Result<(), ConnectionError> { + let (tip, tip_height, total_work) = { + let rotxn = ctxt.env.read_txn()?; + let tip = ctxt.state.get_tip(&rotxn)?; + let tip_height = ctxt.state.get_height(&rotxn)?; + let total_work = match ctxt.archive.try_get_header(&rotxn, tip)? { + None => None, + Some(header) + if header.prev_main_hash + == bitcoin::BlockHash::all_zeros() => + { + None + } + Some(header) => Some( + ctxt.archive + .get_total_work(&rotxn, header.prev_main_hash)?, + ), + }; + (tip, tip_height, total_work) + }; + let peer_height = peer_state.block_height; + if peer_height > tip_height + || (peer_height == tip_height && peer_state.total_work > total_work) + { + let header = { + let rotxn = ctxt.env.read_txn()?; + ctxt.archive.try_get_header(&rotxn, peer_state.tip)? + }; + let Some(header) = header else { + // Request headers + let request = Request::GetHeaders { + // TODO: provide alternative start points + start: HashSet::new(), + end: peer_state.tip, + height: Some(peer_state.block_height), + }; + forward_request_tx.unbounded_send(request)?; + return Ok(()); + }; + // Check mainchain headers + let prev_main_header = { + let rotxn = ctxt.env.read_txn()?; + ctxt.archive + .try_get_main_header(&rotxn, header.prev_main_hash)? + }; + let Some(_prev_main_header) = prev_main_header else { + let info = Info::NeedMainchainAncestors(header.prev_main_hash); + info_tx.unbounded_send(info)?; + return Ok(()); + }; + // Check PoW + let prev_main_total_work = { + let rotxn = ctxt.env.read_txn()?; + ctxt.archive.get_total_work(&rotxn, header.prev_main_hash)? + }; + if Some(prev_main_total_work) != peer_state.total_work { + let ban_reason = BanReason::IncorrectTotalWork { + block_hash: peer_state.tip, + total_work: peer_state.total_work, + }; + return Err(ConnectionError::PeerBan(ban_reason)); + } + let last_common_ancestor = { + let rotxn = ctxt.env.read_txn()?; + ctxt.archive.last_common_ancestor( + &rotxn, + tip, + peer_state.tip, + )? + }; + // Verify BMM + { + let rotxn = ctxt.env.read_txn()?; + let mut missing_bmm = Vec::new(); + let mut ancestors = + ctxt.archive.ancestors(&rotxn, peer_state.tip).take_while( + |block_hash| Ok(*block_hash != last_common_ancestor), + ); + while let Some(block_hash) = ancestors.next()? { + match ctxt + .archive + .try_get_bmm_verification(&rotxn, peer_state.tip)? + { + Some(false) => { + let ban_reason = + BanReason::BmmVerificationFailed(block_hash); + return Err(ConnectionError::PeerBan(ban_reason)); + } + Some(true) => (), + None => missing_bmm.push(block_hash), + } + } + if !missing_bmm.is_empty() { + missing_bmm.reverse(); + let info = Info::NeedBmmVerification(missing_bmm); + info_tx.unbounded_send(info)?; + return Ok(()); + } + }; + let missing_bodies: Vec<_> = { + let rotxn = ctxt.env.read_txn()?; + ctxt.archive + .ancestors(&rotxn, peer_state.tip) + .take_while(|block_hash| { + Ok(*block_hash != last_common_ancestor) + }) + .filter_map(|block_hash| { + match ctxt.archive.try_get_body(&rotxn, block_hash)? { + Some(_) => Ok(None), + None => Ok(Some(block_hash)), + } + }) + .collect()? + }; + if missing_bodies.is_empty() { + let info = Info::NewTipReady(peer_state.tip); + info_tx.unbounded_send(info)?; + } else { + // Request missing bodies + missing_bodies.into_iter().try_for_each(|block_hash| { + let request = Request::GetBlock { block_hash }; + forward_request_tx.unbounded_send(request) + })?; + } + } + Ok(()) + } + + async fn handle_get_block( + ctxt: &ConnectionContext, + response_tx: SendStream, + block_hash: BlockHash, + ) -> Result<(), ConnectionError> { + let (header, body) = { + let rotxn = ctxt.env.read_txn()?; + let header = ctxt.archive.try_get_header(&rotxn, block_hash)?; + let body = ctxt.archive.try_get_body(&rotxn, block_hash)?; + (header, body) + }; + let resp = match (header, body) { + (Some(header), Some(body)) => Response::Block { header, body }, + (_, _) => Response::NoBlock { block_hash }, + }; + Self::send_response(response_tx, resp).await + } + + async fn handle_get_headers( + ctxt: &ConnectionContext, + response_tx: SendStream, + mut start: HashSet, + end: BlockHash, + ) -> Result<(), ConnectionError> { + start.insert(BlockHash::default()); + let response = { + let rotxn = ctxt.env.read_txn()?; + if ctxt.archive.try_get_header(&rotxn, end)?.is_some() { + let mut headers: Vec
= ctxt + .archive + .ancestors(&rotxn, end) + .take_while(|block_hash| Ok(!start.contains(block_hash))) + .map(|block_hash| { + ctxt.archive.get_header(&rotxn, block_hash) + }) + .collect()?; + headers.reverse(); + Response::Headers(headers) + } else { + Response::NoHeader { block_hash: end } + } + }; + Self::send_response(response_tx, response).await + } + + async fn handle_push_tx( + ctxt: &ConnectionContext, + info_tx: &mpsc::UnboundedSender, + response_tx: SendStream, + tx: AuthorizedTransaction, + ) -> Result<(), ConnectionError> { + let txid = tx.transaction.txid(); + let validate_tx_result = { + let rotxn = ctxt.env.read_txn()?; + ctxt.state.validate_transaction(&rotxn, &tx) + }; + match validate_tx_result { + Err(err) => { + Self::send_response( + response_tx, + Response::TransactionRejected(txid), + ) + .await?; + Err(ConnectionError::from(err)) + } + Ok(_) => { + Self::send_response( + response_tx, + Response::TransactionAccepted(txid), + ) + .await?; + info_tx.unbounded_send(Info::NewTransaction(tx))?; + Ok(()) + } + } + } + + async fn handle_request( + ctxt: &ConnectionContext, + info_tx: &mpsc::UnboundedSender, + forward_request_tx: &mpsc::UnboundedSender, + peer_state: &mut Option, + response_tx: SendStream, + request: Request, + ) -> Result<(), ConnectionError> { + match request { + Request::Heartbeat(new_peer_state) => { + let () = Self::handle_heartbeat( + ctxt, + info_tx, + forward_request_tx, + &new_peer_state, + ) + .await?; + *peer_state = Some(new_peer_state); + Ok(()) + } + Request::GetBlock { block_hash } => { + Self::handle_get_block(ctxt, response_tx, block_hash).await + } + Request::GetHeaders { + start, + end, + height: _, + } => Self::handle_get_headers(ctxt, response_tx, start, end).await, + Request::PushTransaction { transaction } => { + Self::handle_push_tx(ctxt, info_tx, response_tx, transaction) + .await + } + } + } + + async fn run(mut self) -> Result<(), ConnectionError> { + enum MailboxItem { + ForwardRequest(Request), + /// Signals that a heartbeat message should be sent to the peer + Heartbeat, + Request((Request, SendStream)), + } + let forward_request_stream = self + .forward_request_rx + .map(|request| Ok(MailboxItem::ForwardRequest(request))); + let heartbeat_stream = + IntervalStream::new(interval(Connection::HEARTBEAT_SEND_INTERVAL)) + .map(|_| Ok(MailboxItem::Heartbeat)); + let request_stream = stream::try_unfold((), { + let conn = self.connection.clone(); + move |()| { + let conn = conn.clone(); + let fut = async move { + let item = timeout( + Connection::HEARTBEAT_TIMEOUT_INTERVAL, + conn.receive_request(), + ) + .map_err(|_| ConnectionError::HeartbeatTimeout) + .await??; + Result::<_, ConnectionError>::Ok(Some((item, ()))) + }; + Box::pin(fut) + } + }) + .map_ok(MailboxItem::Request); + let mut mailbox_stream = stream::select_all([ + forward_request_stream.boxed(), + heartbeat_stream.boxed(), + request_stream.boxed(), + ]); + // spawn child tasks on a JoinSet so that they are dropped alongside this task + let mut task_set: JoinSet<()> = JoinSet::new(); + while let Some(mailbox_item) = mailbox_stream.try_next().await? { + match mailbox_item { + MailboxItem::ForwardRequest(request) => { + task_set.spawn({ + let connection = self.connection.clone(); + let info_tx = self.info_tx.clone(); + async move { + Self::send_request(&connection, &info_tx, request) + .await + } + }); + } + MailboxItem::Heartbeat => { + let (tip, tip_height, total_work) = { + let rotxn = self.ctxt.env.read_txn()?; + let tip = self.ctxt.state.get_tip(&rotxn)?; + let tip_height = self.ctxt.state.get_height(&rotxn)?; + let total_work = match self + .ctxt + .archive + .try_get_header(&rotxn, tip)? + { + None => None, + Some(header) + if header.prev_main_hash + == bitcoin::BlockHash::all_zeros() => + { + None + } + Some(header) => { + Some(self.ctxt.archive.get_total_work( + &rotxn, + header.prev_main_hash, + )?) + } + }; + (tip, tip_height, total_work) + }; + let heartbeat_msg = Request::Heartbeat(PeerState { + block_height: tip_height, + tip, + total_work, + }); + task_set.spawn({ + let connection = self.connection.clone(); + let info_tx = self.info_tx.clone(); + async move { + Self::send_request( + &connection, + &info_tx, + heartbeat_msg, + ) + .await; + } + }); + } + MailboxItem::Request((request, response_tx)) => { + let () = Self::handle_request( + &self.ctxt, + &self.info_tx, + &self.forward_request_tx, + &mut self.peer_state, + response_tx, + request, + ) + .await?; + } + } + } + Ok(()) + } +} + +/// Connection killed on drop +pub struct ConnectionHandle { + task: JoinHandle<()>, + pub forward_request_tx: mpsc::UnboundedSender, +} + +impl Drop for ConnectionHandle { + fn drop(&mut self) { + self.task.abort() + } +} + +/// Handle an existing connection +pub fn handle( + ctxt: ConnectionContext, + connection: Connection, +) -> (ConnectionHandle, mpsc::UnboundedReceiver) { + let (forward_request_tx, forward_request_rx) = mpsc::unbounded(); + let (info_tx, info_rx) = mpsc::unbounded(); + let connection_task = { + let info_tx = info_tx.clone(); + let forward_request_tx = forward_request_tx.clone(); + move || async move { + let connection_task = ConnectionTask { + connection, + ctxt, + info_tx, + peer_state: None, + forward_request_tx, + forward_request_rx, + }; + connection_task.run().await + } + }; + let task = spawn(async move { + if let Err(err) = connection_task().await { + if let Err(send_error) = info_tx.unbounded_send(err.into()) + && let Info::Error(err) = send_error.into_inner() + { + tracing::warn!("Failed to send error to receiver: {err}") + } + } + }); + let connection_handle = ConnectionHandle { + task, + forward_request_tx, + }; + (connection_handle, info_rx) +} + +pub fn connect( + endpoint: Endpoint, + addr: SocketAddr, + ctxt: ConnectionContext, +) -> (ConnectionHandle, mpsc::UnboundedReceiver) { + let (forward_request_tx, forward_request_rx) = mpsc::unbounded(); + let (info_tx, info_rx) = mpsc::unbounded(); + let connection_task = { + let info_tx = info_tx.clone(); + let forward_request_tx = forward_request_tx.clone(); + move || async move { + let connection = Connection::new(&endpoint, addr).await?; + let connection_task = ConnectionTask { + connection, + ctxt, + info_tx, + peer_state: None, + forward_request_tx, + forward_request_rx, + }; + connection_task.run().await + } + }; + let task = spawn(async move { + if let Err(err) = connection_task().await { + if let Err(send_error) = info_tx.unbounded_send(err.into()) + && let Info::Error(err) = send_error.into_inner() + { + tracing::warn!("Failed to send error to receiver: {err}") + } + } + }); + let connection_handle = ConnectionHandle { + task, + forward_request_tx, + }; + (connection_handle, info_rx) +} diff --git a/lib/node.rs b/lib/node.rs index fb5396f..6fc9662 100644 --- a/lib/node.rs +++ b/lib/node.rs @@ -1,5 +1,5 @@ use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeSet, HashMap, HashSet}, fmt::Debug, net::SocketAddr, path::Path, @@ -8,20 +8,34 @@ use std::{ #[cfg(all(not(target_os = "windows"), feature = "zmq"))] use async_zmq::SinkExt; -use bip300301::bitcoin; -use heed::RoTxn; -use tokio::sync::RwLock; +use bip300301::{ + bitcoin::{ + self, + block::{self, Header as BitcoinHeader}, + hashes::Hash, + }, + DepositInfo, +}; +use fallible_iterator::{FallibleIterator, IteratorExt}; +use futures::{stream, StreamExt, TryFutureExt}; +use heed::RwTxn; #[cfg(all(not(target_os = "windows"), feature = "zmq"))] -use tokio::{sync::mpsc, task::JoinHandle}; +use tokio::sync::mpsc; +use tokio::{task::JoinHandle, time::Duration}; +use tokio_stream::StreamNotifyClose; +use tokio_util::task::LocalPoolHandle; use crate::{ - authorization::Authorization, - net::{PeerState, Request, Response}, + archive::{self, Archive}, + mempool::{self, MemPool}, + net::{ + self, Net, PeerConnectionInfo, PeerInfoRx, PeerRequest, PeerResponse, + }, + state::{self, State}, types::{ - hashes::BitName, Address, Authorized, AuthorizedTransaction, - BitNameData, Block, BlockHash, Body, FilledOutput, FilledTransaction, - GetAddress, GetValue, Header, OutPoint, SpentOutput, Txid, Verify, - WithdrawalBundle, + Address, Authorized, AuthorizedTransaction, BitName, BitNameData, + Block, BlockHash, Body, FilledOutput, FilledTransaction, GetValue, + Header, MerkleRoot, OutPoint, SpentOutput, Txid, WithdrawalBundle, }, }; @@ -32,7 +46,7 @@ pub enum Error { #[error("address parse error")] AddrParse(#[from] std::net::AddrParseError), #[error("archive error")] - Archive(#[from] crate::archive::Error), + Archive(#[from] archive::Error), #[error("bincode error")] Bincode(#[from] bincode::Error), #[error("drivechain error")] @@ -42,11 +56,13 @@ pub enum Error { #[error("quinn error")] Io(#[from] std::io::Error), #[error("mempool error")] - MemPool(#[from] crate::mempool::Error), + MemPool(#[from] mempool::Error), #[error("net error")] - Net(#[from] crate::net::Error), + Net(#[from] net::Error), + #[error("peer info stream closed")] + PeerInfoRxClosed, #[error("state error")] - State(#[from] crate::state::Error), + State(#[from] state::Error), } #[cfg(all(not(target_os = "windows"), feature = "zmq"))] @@ -56,18 +72,6 @@ struct ZmqPubHandler { _handle: JoinHandle<()>, } -#[derive(Clone)] -pub struct Node { - archive: crate::archive::Archive, - drivechain: bip300301::Drivechain, - env: heed::Env, - mempool: crate::mempool::MemPool, - net: crate::net::Net, - state: crate::state::State, - #[cfg(all(not(target_os = "windows"), feature = "zmq"))] - zmq_pub_handler: Arc, -} - #[cfg(all(not(target_os = "windows"), feature = "zmq"))] impl ZmqPubHandler { // run the handler, obtaining a sender sink and the handler task @@ -91,6 +95,697 @@ impl ZmqPubHandler { } } +/// Attempt to verify bmm for the provided header, +/// and store the verification result +async fn verify_bmm( + env: &heed::Env, + archive: &Archive, + drivechain: &bip300301::Drivechain, + header: Header, +) -> Result { + use jsonrpsee::types::error::ErrorCode as JsonrpseeErrorCode; + const VERIFY_BMM_POLL_INTERVAL: Duration = Duration::from_secs(15); + let block_hash = header.hash(); + let res = { + let rotxn = env.read_txn()?; + archive.try_get_bmm_verification(&rotxn, block_hash)? + }; + if let Some(res) = res { + return Ok(res); + } + let res = match drivechain + .verify_bmm( + &header.prev_main_hash, + &block_hash.into(), + VERIFY_BMM_POLL_INTERVAL, + ) + .await + { + Ok(()) => true, + Err(bip300301::Error::Jsonrpsee(jsonrpsee::core::Error::Call(err))) + if JsonrpseeErrorCode::from(err.code()) + == JsonrpseeErrorCode::ServerError(-1) => + { + false + } + Err(err) => return Err(Error::from(err)), + }; + let mut rwtxn = env.write_txn()?; + let () = archive.put_bmm_verification(&mut rwtxn, block_hash, res)?; + rwtxn.commit()?; + Ok(res) +} + +/// Request ancestor headers from the mainchain node, +/// including the specified header +async fn request_ancestor_headers( + env: &heed::Env, + archive: &Archive, + drivechain: &bip300301::Drivechain, + mut block_hash: bitcoin::BlockHash, +) -> Result<(), Error> { + let mut headers: Vec = Vec::new(); + loop { + if block_hash == bitcoin::BlockHash::all_zeros() { + break; + } else { + let rotxn = env.read_txn()?; + if archive.try_get_main_header(&rotxn, block_hash)?.is_some() { + break; + } + } + let header = drivechain.get_header(block_hash).await?; + block_hash = header.prev_blockhash; + headers.push(header); + } + if headers.is_empty() { + Ok(()) + } else { + let mut rwtxn = env.write_txn()?; + headers.into_iter().rev().try_for_each(|header| { + archive.put_main_header(&mut rwtxn, &header) + })?; + rwtxn.commit()?; + Ok(()) + } +} + +/// Request any missing two way peg data up to the specified block hash. +/// All ancestor headers must exist in the archive. +// TODO: deposits only for now +#[allow(dead_code)] +async fn request_two_way_peg_data( + env: &heed::Env, + archive: &Archive, + drivechain: &bip300301::Drivechain, + block_hash: bitcoin::BlockHash, +) -> Result<(), Error> { + // last block for which deposit info is known + let last_known_deposit_info = { + let rotxn = env.read_txn()?; + #[allow(clippy::let_and_return)] + let last_known_deposit_info = archive + .main_ancestors(&rotxn, block_hash) + .find(|block_hash| { + let deposits = archive.try_get_deposits(&rotxn, *block_hash)?; + Ok(deposits.is_some()) + })?; + last_known_deposit_info + }; + if last_known_deposit_info == Some(block_hash) { + return Ok(()); + } + let two_way_peg_data = drivechain + .get_two_way_peg_data(block_hash, last_known_deposit_info) + .await?; + let mut rwtxn = env.write_txn()?; + // Deposits by block, first-to-last within each block + let deposits_by_block: HashMap> = { + let mut deposits = HashMap::<_, Vec<_>>::new(); + two_way_peg_data.deposits.into_iter().for_each(|deposit| { + deposits + .entry(deposit.block_hash) + .or_default() + .push(deposit) + }); + let () = archive + .main_ancestors(&rwtxn, block_hash) + .take_while(|block_hash| { + Ok(last_known_deposit_info != Some(*block_hash)) + }) + .for_each(|block_hash| { + let _ = deposits.entry(block_hash).or_default(); + Ok(()) + })?; + deposits + }; + deposits_by_block + .into_iter() + .try_for_each(|(block_hash, deposits)| { + archive.put_deposits(&mut rwtxn, block_hash, deposits) + })?; + rwtxn.commit()?; + Ok(()) +} + +async fn connect_tip_( + rwtxn: &mut RwTxn<'_, '_>, + archive: &Archive, + drivechain: &bip300301::Drivechain, + mempool: &MemPool, + state: &State, + header: &Header, + body: &Body, +) -> Result<(), Error> { + let last_deposit_block_hash = state.get_last_deposit_block_hash(rwtxn)?; + let two_way_peg_data = drivechain + .get_two_way_peg_data(header.prev_main_hash, last_deposit_block_hash) + .await?; + let block_hash = header.hash(); + let (_fees, merkle_root): (u64, MerkleRoot) = + state.validate_block(rwtxn, header, body)?; + if tracing::enabled!(tracing::Level::DEBUG) { + let height = state.get_height(rwtxn)?; + let _: MerkleRoot = state.connect_block(rwtxn, header, body)?; + tracing::debug!(%height, %merkle_root, %block_hash, + "connected body") + } else { + let _: MerkleRoot = state.connect_block(rwtxn, header, body)?; + } + let () = state.connect_two_way_peg_data(rwtxn, &two_way_peg_data)?; + let () = archive.put_header(rwtxn, header)?; + let () = archive.put_body(rwtxn, block_hash, body)?; + for transaction in &body.transactions { + let () = mempool.delete(rwtxn, transaction.txid())?; + } + Ok(()) +} + +async fn disconnect_tip_( + rwtxn: &mut RwTxn<'_, '_>, + archive: &Archive, + drivechain: &bip300301::Drivechain, + mempool: &MemPool, + state: &State, +) -> Result<(), Error> { + let tip_block_hash = state.get_tip(rwtxn)?; + let tip_header = archive.get_header(rwtxn, tip_block_hash)?; + let tip_body = archive.get_body(rwtxn, tip_block_hash)?; + let height = state.get_height(rwtxn)?; + let two_way_peg_data = { + let start_block_hash = state + .deposit_blocks + .rev_iter(rwtxn)? + .transpose_into_fallible() + .find_map(|(_, (block_hash, applied_height))| { + if applied_height < height - 1 { + Ok(Some(block_hash)) + } else { + Ok(None) + } + })?; + drivechain + .get_two_way_peg_data(tip_header.prev_main_hash, start_block_hash) + .await? + }; + let () = state.disconnect_two_way_peg_data(rwtxn, &two_way_peg_data)?; + let () = state.disconnect_tip(rwtxn, &tip_header, &tip_body)?; + for transaction in tip_body.authorized_transactions().iter().rev() { + mempool.put(rwtxn, transaction)?; + } + Ok(()) +} + +#[cfg(all(not(target_os = "windows"), feature = "zmq"))] +#[allow(clippy::too_many_arguments)] +async fn submit_block( + env: &heed::Env, + archive: &Archive, + drivechain: &bip300301::Drivechain, + mempool: &MemPool, + state: &State, + #[cfg(all(not(target_os = "windows"), feature = "zmq"))] + zmq_pub_handler: &ZmqPubHandler, + header: &Header, + body: &Body, +) -> Result<(), Error> { + let mut rwtxn = env.write_txn()?; + // Request mainchain headers if they do not exist + request_ancestor_headers(env, archive, drivechain, header.prev_main_hash) + .await?; + let () = connect_tip_( + &mut rwtxn, archive, drivechain, mempool, state, header, body, + ) + .await?; + let bundle = state.get_pending_withdrawal_bundle(&rwtxn)?; + #[cfg(all(not(target_os = "windows"), feature = "zmq"))] + let height = state.get_height(&rwtxn)?; + rwtxn.commit()?; + #[cfg(all(not(target_os = "windows"), feature = "zmq"))] + { + let block_hash = header.hash(); + let zmq_msgs = vec![ + "hashblock".into(), + block_hash.0[..].into(), + height.to_le_bytes()[..].into(), + ]; + zmq_pub_handler.tx.send(zmq_msgs).unwrap(); + } + if let Some((bundle, _)) = bundle { + let () = drivechain + .broadcast_withdrawal_bundle(bundle.transaction) + .await?; + } + Ok(()) +} + +/// Re-org to the specified tip. The new tip block and all ancestor blocks +/// must exist in the node's archive. +async fn reorg_to_tip( + env: &heed::Env, + archive: &Archive, + drivechain: &bip300301::Drivechain, + mempool: &MemPool, + state: &State, + #[cfg(all(not(target_os = "windows"), feature = "zmq"))] + zmq_pub_handler: &ZmqPubHandler, + new_tip: BlockHash, +) -> Result<(), Error> { + let mut rwtxn = env.write_txn()?; + let tip = state.get_tip(&rwtxn)?; + let tip_height = state.get_height(&rwtxn)?; + let common_ancestor = archive.last_common_ancestor(&rwtxn, tip, new_tip)?; + // Check that all necessary bodies exist before disconnecting tip + let blocks_to_apply: Vec<(Header, Body)> = archive + .ancestors(&rwtxn, new_tip) + .take_while(|block_hash| Ok(*block_hash != common_ancestor)) + .map(|block_hash| { + let header = archive.get_header(&rwtxn, block_hash)?; + let body = archive.get_body(&rwtxn, block_hash)?; + Ok((header, body)) + }) + .collect()?; + // Disconnect tip until common ancestor is reached + let common_ancestor_height = archive.get_height(&rwtxn, common_ancestor)?; + for _ in 0..tip_height - common_ancestor_height { + let () = + disconnect_tip_(&mut rwtxn, archive, drivechain, mempool, state) + .await?; + } + let tip = state.get_tip(&rwtxn)?; + assert_eq!(tip, common_ancestor); + // Apply blocks until new tip is reached + for (header, body) in blocks_to_apply.iter().rev() { + let () = connect_tip_( + &mut rwtxn, archive, drivechain, mempool, state, header, body, + ) + .await?; + } + let tip = state.get_tip(&rwtxn)?; + assert_eq!(tip, new_tip); + rwtxn.commit()?; + tracing::info!("reorged to tip: {new_tip}"); + #[cfg(all(not(target_os = "windows"), feature = "zmq"))] + { + for (idx, (header, _body)) in + blocks_to_apply.into_iter().rev().enumerate() + { + let block_hash = header.hash(); + let height = common_ancestor_height + idx as u32 + 1; + let zmq_msgs = vec![ + "hashblock".into(), + block_hash.0[..].into(), + height.to_le_bytes()[..].into(), + ]; + zmq_pub_handler.tx.send(zmq_msgs).unwrap(); + } + } + Ok(()) +} + +#[derive(Clone)] +struct NetTaskContext { + env: heed::Env, + archive: Archive, + drivechain: bip300301::Drivechain, + mempool: MemPool, + net: Net, + state: State, + #[cfg(all(not(target_os = "windows"), feature = "zmq"))] + zmq_pub_handler: Arc, +} + +struct NetTask { + ctxt: NetTaskContext, + peer_info_rx: PeerInfoRx, +} + +impl NetTask { + const VERIFY_BMM_POLL_INTERVAL: Duration = Duration::from_secs(15); + + async fn handle_response( + ctxt: &NetTaskContext, + addr: SocketAddr, + resp: PeerResponse, + req: PeerRequest, + ) -> Result<(), Error> { + match (req, resp) { + ( + req @ PeerRequest::GetBlock { block_hash }, + ref resp @ PeerResponse::Block { + ref header, + ref body, + }, + ) => { + let tip = { + let rotxn = ctxt.env.read_txn()?; + ctxt.state.get_tip(&rotxn)? + }; + if header.hash() != block_hash { + // Invalid response + tracing::warn!(%addr, ?req, ?resp,"Invalid response from peer; unexpected block hash"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + } + // Verify BMM + // TODO: Spawn a task for this + let () = ctxt + .drivechain + .verify_bmm( + &header.prev_main_hash, + &block_hash.into(), + Self::VERIFY_BMM_POLL_INTERVAL, + ) + .await?; + if header.prev_side_hash == tip { + submit_block( + &ctxt.env, + &ctxt.archive, + &ctxt.drivechain, + &ctxt.mempool, + &ctxt.state, + #[cfg(all( + not(target_os = "windows"), + feature = "zmq" + ))] + &ctxt.zmq_pub_handler, + header, + body, + ) + .await + } else { + let mut rwtxn = ctxt.env.write_txn()?; + let () = ctxt.archive.put_header(&mut rwtxn, header)?; + let () = + ctxt.archive.put_body(&mut rwtxn, block_hash, body)?; + rwtxn.commit()?; + Ok(()) + } + } + ( + PeerRequest::GetBlock { + block_hash: req_block_hash, + }, + PeerResponse::NoBlock { + block_hash: resp_block_hash, + }, + ) if req_block_hash == resp_block_hash => Ok(()), + ( + ref req @ PeerRequest::GetHeaders { + ref start, + end, + height: Some(height), + }, + PeerResponse::Headers(headers), + ) => { + // check that the end header is as requested + let Some(end_header) = headers.last() else { + tracing::warn!(%addr, ?req, "Invalid response from peer; missing end header"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + }; + if end_header.hash() != end { + tracing::warn!(%addr, ?req, ?end_header,"Invalid response from peer; unexpected end header"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + } + // Must be at least one header due to previous check + let start_hash = headers.first().unwrap().prev_side_hash; + // check that the first header is after a start block + if !(start.contains(&start_hash) + || start_hash == BlockHash::default()) + { + tracing::warn!(%addr, ?req, ?start_hash, "Invalid response from peer; invalid start hash"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + } + // check that the end header height is as expected + { + let rotxn = ctxt.env.read_txn()?; + let start_height = + ctxt.archive.get_height(&rotxn, start_hash)?; + if start_height + headers.len() as u32 != height { + tracing::warn!(%addr, ?req, ?start_hash, "Invalid response from peer; invalid end height"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + } + } + // check that headers are sequential based on prev_side_hash + let mut prev_side_hash = start_hash; + for header in &headers { + if header.prev_side_hash != prev_side_hash { + tracing::warn!(%addr, ?req, ?headers,"Invalid response from peer; non-sequential headers"); + let () = ctxt.net.remove_active_peer(addr); + return Ok(()); + } + prev_side_hash = header.hash(); + } + // Request mainchain headers + tokio::spawn({ + let ctxt = ctxt.clone(); + let prev_main_hash = headers.last().unwrap().prev_main_hash; + async move { + if let Err(err) = request_ancestor_headers( + &ctxt.env, + &ctxt.archive, + &ctxt.drivechain, + prev_main_hash, + ) + .await + { + let err = anyhow::anyhow!(err); + tracing::error!(%addr, err = format!("{err:#}"), "Request ancestor headers error"); + } + } + }); + // Verify BMM + tokio::spawn({ + let ctxt = ctxt.clone(); + let headers = headers.clone(); + async move { + for header in headers.clone() { + match verify_bmm( + &ctxt.env, + &ctxt.archive, + &ctxt.drivechain, + header.clone(), + ) + .await + { + Ok(true) => (), + Ok(false) => { + tracing::warn!( + %addr, + ?header, + ?headers, + "Invalid response from peer; BMM verification failed" + ); + let () = ctxt.net.remove_active_peer(addr); + break; + } + Err(err) => { + let err = anyhow::anyhow!(err); + tracing::error!(%addr, err = format!("{err:#}"), "Verify BMM error"); + } + } + } + } + }); + // Store new headers + let mut rwtxn = ctxt.env.write_txn()?; + for header in headers { + let block_hash = header.hash(); + if ctxt + .archive + .try_get_header(&rwtxn, block_hash)? + .is_none() + { + if header.prev_side_hash == BlockHash::default() + || ctxt + .archive + .try_get_header(&rwtxn, header.prev_side_hash)? + .is_some() + { + ctxt.archive.put_header(&mut rwtxn, &header)?; + } else { + break; + } + } + } + rwtxn.commit()?; + Ok(()) + } + ( + PeerRequest::GetHeaders { + start: _, + end, + height: _, + }, + PeerResponse::NoHeader { block_hash }, + ) if end == block_hash => Ok(()), + ( + PeerRequest::PushTransaction { transaction: _ }, + PeerResponse::TransactionAccepted(_), + ) => Ok(()), + ( + PeerRequest::PushTransaction { transaction: _ }, + PeerResponse::TransactionRejected(_), + ) => Ok(()), + ( + req @ (PeerRequest::GetBlock { .. } + | PeerRequest::GetHeaders { .. } + | PeerRequest::Heartbeat(_) + | PeerRequest::PushTransaction { .. }), + resp, + ) => { + // Invalid response + tracing::warn!(%addr, ?req, ?resp,"Invalid response from peer"); + let () = ctxt.net.remove_active_peer(addr); + Ok(()) + } + } + } + + async fn run(self) -> Result<(), Error> { + enum MailboxItem { + AcceptConnection(Result<(), Error>), + PeerInfo(Option<(SocketAddr, Option)>), + } + let accept_connections = stream::try_unfold((), |()| { + let env = self.ctxt.env.clone(); + let net = self.ctxt.net.clone(); + let fut = async move { + let () = net.accept_incoming(env).await?; + Result::<_, Error>::Ok(Some(((), ()))) + }; + Box::pin(fut) + }) + .map(MailboxItem::AcceptConnection); + let peer_info_stream = StreamNotifyClose::new(self.peer_info_rx) + .map(MailboxItem::PeerInfo); + let mut mailbox_stream = + stream::select(accept_connections, peer_info_stream); + while let Some(mailbox_item) = mailbox_stream.next().await { + match mailbox_item { + MailboxItem::AcceptConnection(res) => res?, + MailboxItem::PeerInfo(None) => { + return Err(Error::PeerInfoRxClosed) + } + MailboxItem::PeerInfo(Some((addr, None))) => { + // peer connection is closed, remove it + tracing::warn!(%addr, "Connection to peer closed"); + let () = self.ctxt.net.remove_active_peer(addr); + continue; + } + MailboxItem::PeerInfo(Some((addr, Some(peer_info)))) => { + match peer_info { + PeerConnectionInfo::Error(err) => { + let err = anyhow::anyhow!(err); + tracing::error!(%addr, err = format!("{err:#}"), "Peer connection error"); + let () = self.ctxt.net.remove_active_peer(addr); + } + PeerConnectionInfo::NeedBmmVerification( + block_hashes, + ) => { + let headers: Vec<_> = { + let rotxn = self.ctxt.env.read_txn()?; + block_hashes + .into_iter() + .map(|block_hash| { + self.ctxt + .archive + .get_header(&rotxn, block_hash) + }) + .transpose_into_fallible() + .collect()? + }; + tokio::spawn({ + let ctxt = self.ctxt.clone(); + async move { + for header in headers { + if let Err(err) = verify_bmm( + &ctxt.env, + &ctxt.archive, + &ctxt.drivechain, + header, + ) + .await + { + let err = anyhow::anyhow!(err); + tracing::error!(%addr, err = format!("{err:#}"), "Verify BMM error") + } + } + } + }); + } + PeerConnectionInfo::NeedMainchainAncestors( + block_hash, + ) => { + tokio::spawn({ + let ctxt = self.ctxt.clone(); + async move { + let () = request_ancestor_headers(&ctxt.env, &ctxt.archive, &ctxt.drivechain, block_hash) + .unwrap_or_else(move |err| { + let err = anyhow::anyhow!(err); + tracing::error!(%addr, err = format!("{err:#}"), "Request ancestor headers error"); + }).await; + } + }); + } + PeerConnectionInfo::NewTipReady(new_tip) => { + let () = reorg_to_tip( + &self.ctxt.env, + &self.ctxt.archive, + &self.ctxt.drivechain, + &self.ctxt.mempool, + &self.ctxt.state, + #[cfg(all( + not(target_os = "windows"), + feature = "zmq" + ))] + &self.ctxt.zmq_pub_handler, + new_tip, + ) + .await?; + } + PeerConnectionInfo::NewTransaction(new_tx) => { + let mut rwtxn = self.ctxt.env.write_txn()?; + self.ctxt.mempool.put(&mut rwtxn, &new_tx)?; + rwtxn.commit()?; + // broadcast + let () = self + .ctxt + .net + .push_tx(HashSet::from_iter([addr]), new_tx); + } + PeerConnectionInfo::Response(resp, req) => { + let () = Self::handle_response( + &self.ctxt, addr, resp, req, + ) + .await?; + } + } + } + } + } + Ok(()) + } +} + +#[derive(Clone)] +pub struct Node { + archive: Archive, + drivechain: bip300301::Drivechain, + env: heed::Env, + _local_pool: LocalPoolHandle, + mempool: MemPool, + net: Net, + net_task: Arc>, + state: State, + #[cfg(all(not(target_os = "windows"), feature = "zmq"))] + zmq_pub_handler: Arc, +} + impl Node { pub fn new( bind_addr: SocketAddr, @@ -98,6 +793,7 @@ impl Node { main_addr: SocketAddr, password: &str, user: &str, + local_pool: LocalPoolHandle, #[cfg(all(not(target_os = "windows"), feature = "zmq"))] zmq_addr: SocketAddr, ) -> Result { @@ -107,29 +803,51 @@ impl Node { let env = heed::EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs( - crate::state::State::NUM_DBS - + crate::archive::Archive::NUM_DBS - + crate::mempool::MemPool::NUM_DBS, + State::NUM_DBS + + Archive::NUM_DBS + + MemPool::NUM_DBS + + Net::NUM_DBS, ) .open(env_path)?; - let archive = crate::archive::Archive::new(&env)?; + let archive = Archive::new(&env)?; let drivechain = bip300301::Drivechain::new( THIS_SIDECHAIN, main_addr, user, password, )?; - let mempool = crate::mempool::MemPool::new(&env)?; - let net = crate::net::Net::new(bind_addr)?; + let mempool = MemPool::new(&env)?; let state = crate::state::State::new(&env)?; #[cfg(all(not(target_os = "windows"), feature = "zmq"))] let zmq_pub_handler = Arc::new(ZmqPubHandler::new(zmq_addr)); + let (net, peer_info_rx) = + Net::new(&env, archive.clone(), state.clone(), bind_addr)?; + let net_task = local_pool.spawn_pinned({ + let ctxt = NetTaskContext { + env: env.clone(), + archive: archive.clone(), + drivechain: drivechain.clone(), + mempool: mempool.clone(), + net: net.clone(), + state: state.clone(), + #[cfg(all(not(target_os = "windows"), feature = "zmq"))] + zmq_pub_handler: zmq_pub_handler.clone(), + }; + || { + NetTask { ctxt, peer_info_rx }.run().unwrap_or_else(|err| { + let err = anyhow::anyhow!(err); + tracing::error!(err = format!("{err:#}")) + }) + } + }); Ok(Self { archive, drivechain, env, + _local_pool: local_pool, mempool, net, + net_task: Arc::new(net_task), state, #[cfg(all(not(target_os = "windows"), feature = "zmq"))] zmq_pub_handler, @@ -153,21 +871,49 @@ impl Node { Ok(res) } - pub fn get_height(&self) -> Result { - let txn = self.env.read_txn()?; - Ok(self.archive.get_height(&txn)?) + pub fn get_tip_height(&self) -> Result { + let rotxn = self.env.read_txn()?; + Ok(self.state.get_height(&rotxn)?) } - pub fn get_best_hash(&self) -> Result { - let txn = self.env.read_txn()?; - Ok(self.archive.get_best_hash(&txn)?) + pub fn get_tip(&self) -> Result { + let rotxn = self.env.read_txn()?; + Ok(self.state.get_tip(&rotxn)?) } - /** Returns the height of the block in which the tx was included. - * Returns an error if the tx does not exist in any known block. */ - pub fn get_tx_height(&self, txid: Txid) -> Result { - let txn = self.env.read_txn()?; - Ok(self.archive.get_tx_height(&txn, txid)?) + pub fn try_get_height( + &self, + block_hash: BlockHash, + ) -> Result, Error> { + let rotxn = self.env.read_txn()?; + Ok(self.archive.try_get_height(&rotxn, block_hash)?) + } + + pub fn get_height(&self, block_hash: BlockHash) -> Result { + let rotxn = self.env.read_txn()?; + Ok(self.archive.get_height(&rotxn, block_hash)?) + } + + /// Get blocks in which a tx was included. + pub fn get_tx_inclusions( + &self, + txid: Txid, + ) -> Result, Error> { + let rotxn = self.env.read_txn()?; + Ok(self.archive.get_tx_inclusions(&rotxn, txid)?) + } + + /// Returns true if the second specified block is a descendant of the first + /// specified block + /// Returns an error if either of the specified block headers do not exist + /// in the archive. + pub fn is_descendant( + &self, + ancestor: BlockHash, + descendant: BlockHash, + ) -> Result { + let rotxn = self.env.read_txn()?; + Ok(self.archive.is_descendant(&rotxn, ancestor, descendant)?) } /// List all BitNames and their current data @@ -217,48 +963,17 @@ impl Node { Ok(self.state.get_current_bitname_data(&txn, bitname)?) } - pub fn validate_transaction( + pub fn submit_transaction( &self, - rotxn: &RoTxn, - transaction: &AuthorizedTransaction, - ) -> Result { - let filled_transaction = self - .state - .fill_transaction(rotxn, &transaction.transaction)?; - for (authorization, spent_utxo) in transaction - .authorizations - .iter() - .zip(filled_transaction.spent_utxos.iter()) - { - if authorization.get_address() != spent_utxo.address { - return Err(crate::state::Error::WrongPubKeyForAddress.into()); - } - } - if Authorization::verify_transaction(transaction).is_err() { - return Err(crate::state::Error::AuthorizationError.into()); - } - let fee = self - .state - .validate_filled_transaction(rotxn, &filled_transaction)?; - Ok(fee) - } - - pub async fn submit_transaction( - &self, - transaction: &AuthorizedTransaction, + transaction: AuthorizedTransaction, ) -> Result<(), Error> { { let mut txn = self.env.write_txn()?; - self.validate_transaction(&txn, transaction)?; - self.mempool.put(&mut txn, transaction)?; + self.state.validate_transaction(&txn, &transaction)?; + self.mempool.put(&mut txn, &transaction)?; txn.commit()?; } - for peer in self.net.peers.read().await.values() { - peer.request(&Request::PushTransaction { - transaction: transaction.clone(), - }) - .await?; - } + self.net.push_tx(Default::default(), transaction); Ok(()) } @@ -285,42 +1000,76 @@ impl Node { Ok(utxos) } - pub fn get_header(&self, height: u32) -> Result, Error> { + pub fn try_get_header( + &self, + block_hash: BlockHash, + ) -> Result, Error> { let txn = self.env.read_txn()?; - Ok(self.archive.get_header(&txn, height)?) + Ok(self.archive.try_get_header(&txn, block_hash)?) } - pub fn get_body(&self, height: u32) -> Result, Error> { + pub fn get_header(&self, block_hash: BlockHash) -> Result { let txn = self.env.read_txn()?; - Ok(self.archive.get_body(&txn, height)?) + Ok(self.archive.get_header(&txn, block_hash)?) + } + + /// Get the block hash at the specified height in the current chain, + /// if it exists + pub fn try_get_block_hash( + &self, + height: u32, + ) -> Result, Error> { + let rotxn = self.env.read_txn()?; + let tip = self.state.get_tip(&rotxn)?; + let tip_height = self.state.get_height(&rotxn)?; + if tip_height >= height { + self.archive + .ancestors(&rotxn, tip) + .nth((tip_height - height) as usize) + .map_err(Error::from) + } else { + Ok(None) + } + } + + pub fn try_get_body( + &self, + block_hash: BlockHash, + ) -> Result, Error> { + let rotxn = self.env.read_txn()?; + Ok(self.archive.try_get_body(&rotxn, block_hash)?) + } + + pub fn get_body(&self, block_hash: BlockHash) -> Result { + let rotxn = self.env.read_txn()?; + Ok(self.archive.get_body(&rotxn, block_hash)?) } - /// Get the block corresponding to the provided blockhash, if it exists pub fn get_block(&self, block_hash: BlockHash) -> Result { - let txn = self.env.read_txn()?; - Ok(self.archive.get_block(&txn, block_hash)?) + let rotxn = self.env.read_txn()?; + Ok(self.archive.get_block(&rotxn, block_hash)?) } pub fn get_all_transactions( &self, ) -> Result, Error> { - let txn = self.env.read_txn()?; - let transactions = self.mempool.take_all(&txn)?; + let rotxn = self.env.read_txn()?; + let transactions = self.mempool.take_all(&rotxn)?; Ok(transactions) } /// Get total sidechain wealth in Bitcoin pub fn get_sidechain_wealth(&self) -> Result { - let txn = self.env.read_txn()?; - Ok(self.state.sidechain_wealth(&txn)?) + let rotxn = self.env.read_txn()?; + Ok(self.state.sidechain_wealth(&rotxn)?) } pub fn get_transactions( &self, number: usize, ) -> Result<(Vec>, u64), Error> { - let mut txn = self.env.write_txn()?; - let transactions = self.mempool.take(&txn, number)?; + let mut rwtxn = self.env.write_txn()?; + let transactions = self.mempool.take(&rwtxn, number)?; let mut fee: u64 = 0; let mut returned_transactions = vec![]; let mut spent_utxos = HashSet::new(); @@ -330,16 +1079,21 @@ impl Node { if !spent_utxos.is_disjoint(&inputs) { println!("UTXO double spent"); self.mempool - .delete(&mut txn, &transaction.transaction.txid())?; + .delete(&mut rwtxn, transaction.transaction.txid())?; continue; } - if self.validate_transaction(&txn, &transaction).is_err() { + if self + .state + .validate_transaction(&rwtxn, &transaction) + .is_err() + { self.mempool - .delete(&mut txn, &transaction.transaction.txid())?; + .delete(&mut rwtxn, transaction.transaction.txid())?; continue; } - let filled_transaction = - self.state.fill_authorized_transaction(&txn, transaction)?; + let filled_transaction = self + .state + .fill_authorized_transaction(&rwtxn, transaction)?; let value_in: u64 = filled_transaction .transaction .spent_utxos @@ -356,7 +1110,7 @@ impl Node { spent_utxos.extend(filled_transaction.transaction.inputs()); returned_transactions.push(filled_transaction); } - txn.commit()?; + rwtxn.commit()?; Ok((returned_transactions, fee)) } @@ -364,339 +1118,80 @@ impl Node { &self, ) -> Result, Error> { let txn = self.env.read_txn()?; - Ok(self.state.get_pending_withdrawal_bundle(&txn)?) + let bundle = self + .state + .get_pending_withdrawal_bundle(&txn)? + .map(|(bundle, _)| bundle); + Ok(bundle) } - pub async fn submit_block( + pub async fn connect_tip( &self, header: &Header, body: &Body, ) -> Result<(), Error> { - let last_deposit_block_hash = { - let txn = self.env.read_txn()?; - self.state.get_last_deposit_block_hash(&txn)? - }; - let bundle = { - let two_way_peg_data = self - .drivechain - .get_two_way_peg_data( - header.prev_main_hash, - last_deposit_block_hash, - ) - .await?; - let mut txn = self.env.write_txn()?; - self.state.validate_body(&txn, body)?; - let height = self.archive.get_height(&txn)?; - if tracing::enabled!(tracing::Level::DEBUG) { - let block_hash = header.hash(); - let merkle_root = - self.state.connect_body(&mut txn, body, height)?; - tracing::debug!(%height, %merkle_root, %block_hash, - "connected body") - } else { - self.state.connect_body(&mut txn, body, height)?; - } - self.state.connect_two_way_peg_data( - &mut txn, - &two_way_peg_data, - height, - )?; - let bundle = self.state.get_pending_withdrawal_bundle(&txn)?; - self.archive.append_header(&mut txn, header)?; - self.archive.put_body(&mut txn, header, body)?; - for transaction in &body.transactions { - self.mempool.delete(&mut txn, &transaction.txid())?; - } - txn.commit()?; - #[cfg(all(not(target_os = "windows"), feature = "zmq"))] - { - let block_hash = header.hash(); - let zmq_msgs = vec![ - "hashblock".into(), - block_hash.0[..].into(), - height.to_le_bytes()[..].into(), - ]; - self.zmq_pub_handler.tx.send(zmq_msgs).unwrap(); - } - bundle - }; - if let Some(bundle) = bundle { - let () = self - .drivechain - .broadcast_withdrawal_bundle(bundle.transaction) - .await?; - } + let mut rwtxn = self.env.write_txn()?; + let () = connect_tip_( + &mut rwtxn, + &self.archive, + &self.drivechain, + &self.mempool, + &self.state, + header, + body, + ) + .await?; + rwtxn.commit()?; Ok(()) } - pub async fn heart_beat_listen( - &self, - peer: &crate::net::Peer, - ) -> Result<(), Error> { - let message = match peer.connection.read_datagram().await { - Ok(message) => message, - Err(err) => { - self.net - .peers - .write() - .await - .remove(&peer.connection.stable_id()); - let addr = peer.connection.stable_id(); - println!("connection {addr} closed"); - return Err(crate::net::Error::from(err).into()); - } - }; - let state: PeerState = bincode::deserialize(&message)?; - *peer.state.write().await = Some(state); - Ok(()) + pub fn connect_peer(&self, addr: SocketAddr) -> Result<(), Error> { + self.net + .connect_peer(self.env.clone(), addr) + .map_err(Error::from) } - pub async fn connect_peer(&self, addr: SocketAddr) -> Result<(), Error> { - let peer = self.net.connect_peer(addr).await?; - tokio::spawn({ - let node = self.clone(); - let peer = peer.clone(); - async move { - loop { - match node.peer_listen(&peer).await { - Ok(_) => {} - Err(err) => { - println!("{:?}", err); - break; - } - } - } - } - }); - tokio::spawn({ - let node = self.clone(); - let peer = peer.clone(); - async move { - loop { - match node.heart_beat_listen(&peer).await { - Ok(_) => {} - Err(err) => { - println!("{:?}", err); - break; - } - } - } - } - }); - Ok(()) - } - - pub async fn peer_listen( + pub async fn submit_block( &self, - peer: &crate::net::Peer, + header: &Header, + body: &Body, ) -> Result<(), Error> { - let (mut send, mut recv) = peer - .connection - .accept_bi() - .await - .map_err(crate::net::Error::from)?; - let data = recv - .read_to_end(crate::net::READ_LIMIT) - .await - .map_err(crate::net::Error::from)?; - let message: Request = bincode::deserialize(&data)?; - match message { - Request::GetBlock { height } => { - let (header, body) = { - let txn = self.env.read_txn()?; - ( - self.archive.get_header(&txn, height)?, - self.archive.get_body(&txn, height)?, - ) - }; - let response = match (header, body) { - (Some(header), Some(body)) => { - Response::Block { header, body } - } - (_, _) => Response::NoBlock, - }; - let response = bincode::serialize(&response)?; - send.write_all(&response) - .await - .map_err(crate::net::Error::from)?; - send.finish().await.map_err(crate::net::Error::from)?; - } - Request::PushTransaction { transaction } => { - let valid = { - let txn = self.env.read_txn()?; - self.validate_transaction(&txn, &transaction) - }; - match valid { - Err(err) => { - let response = Response::TransactionRejected; - let response = bincode::serialize(&response)?; - send.write_all(&response) - .await - .map_err(crate::net::Error::from)?; - return Err(err); - } - Ok(_) => { - { - let mut txn = self.env.write_txn()?; - println!( - "adding transaction to mempool: {:?}", - &transaction - ); - self.mempool.put(&mut txn, &transaction)?; - txn.commit()?; - } - for peer0 in self.net.peers.read().await.values() { - if peer0.connection.stable_id() - == peer.connection.stable_id() - { - continue; - } - peer0 - .request(&Request::PushTransaction { - transaction: transaction.clone(), - }) - .await?; - } - let response = Response::TransactionAccepted; - let response = bincode::serialize(&response)?; - send.write_all(&response) - .await - .map_err(crate::net::Error::from)?; - return Ok(()); - } - } - } - }; - Ok(()) + submit_block( + &self.env, + &self.archive, + &self.drivechain, + &self.mempool, + &self.state, + #[cfg(all(not(target_os = "windows"), feature = "zmq"))] + &self.zmq_pub_handler, + header, + body, + ) + .await } - pub fn run(self: Arc) -> Result<(), Error> { - // Listening to connections. - let node = self.clone(); - tokio::spawn(async move { - loop { - let incoming_conn = node.net.server.accept().await.unwrap(); - let connection = incoming_conn.await.unwrap(); - for peer in node.net.peers.read().await.values() { - if peer.connection.remote_address() - == connection.remote_address() - { - println!( - "already connected to {} refusing duplicate connection", - connection.remote_address() - ); - connection.close( - quinn::VarInt::from_u32(1), - b"already connected", - ); - } - } - if connection.close_reason().is_some() { - continue; - } - println!( - "[server] connection accepted: addr={} id={}", - connection.remote_address(), - connection.stable_id(), - ); - let peer = crate::net::Peer { - state: Arc::new(RwLock::new(None)), - connection, - }; - tokio::spawn({ - let node = node.clone(); - let peer = peer.clone(); - async move { - loop { - match node.peer_listen(&peer).await { - Ok(_) => {} - Err(err) => { - println!("{:?}", err); - break; - } - } - } - } - }); - tokio::spawn({ - let node = node.clone(); - let peer = peer.clone(); - async move { - loop { - match node.heart_beat_listen(&peer).await { - Ok(_) => {} - Err(err) => { - println!("{:?}", err); - break; - } - } - } - } - }); - node.net - .peers - .write() - .await - .insert(peer.connection.stable_id(), peer); - } - }); - - // Heart beat. - let node = self.clone(); - tokio::spawn(async move { - loop { - for peer in node.net.peers.read().await.values() { - let block_height = { - let txn = node.env.read_txn().unwrap(); - node.archive.get_height(&txn).unwrap() - }; - let state = PeerState { block_height }; - peer.heart_beat(&state).unwrap(); - } - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - } - }); - - // Request missing headers. - let node = self.clone(); - tokio::spawn({ - let node = node.clone(); - async move { - loop { - for peer in node.net.peers.read().await.values() { - if let Some(state) = &peer.state.read().await.as_ref() { - let height = { - let txn = node.env.read_txn().unwrap(); - node.archive.get_height(&txn).unwrap() - }; - if state.block_height > height { - let response = peer - .request(&Request::GetBlock { - height: height + 1, - }) - .await - .unwrap(); - match response { - Response::Block { header, body } => { - println!( - "got new header {:?}", - &header - ); - node.submit_block(&header, &body) - .await - .unwrap(); - } - Response::NoBlock => {} - Response::TransactionAccepted => {} - Response::TransactionRejected => {} - }; - } - } - } - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - } - } - }); + pub async fn disconnect_tip(&self) -> Result<(), Error> { + let mut rwtxn = self.env.write_txn()?; + let () = disconnect_tip_( + &mut rwtxn, + &self.archive, + &self.drivechain, + &self.mempool, + &self.state, + ) + .await?; + rwtxn.commit()?; Ok(()) } } + +impl Drop for Node { + // If only one reference exists (ie. within self), abort the net task. + fn drop(&mut self) { + // use `Arc::get_mut` since `Arc::into_inner` requires ownership of the + // Arc, and cloning would increase the reference count + if let Some(task) = Arc::get_mut(&mut self.net_task) { + task.abort() + } + } +} diff --git a/lib/state.rs b/lib/state.rs index 5ce165d..78021c7 100644 --- a/lib/state.rs +++ b/lib/state.rs @@ -1,13 +1,9 @@ use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeMap, HashMap, HashSet}, net::{Ipv4Addr, Ipv6Addr}, }; -use hashes::MerkleRoot; -use heed::{ - types::{OwnedType, SerdeBincode}, - Database, RoTxn, RwTxn, -}; +use heed::{types::SerdeBincode, Database, RoTxn, RwTxn}; use nonempty::{nonempty, NonEmpty}; use serde::{Deserialize, Serialize}; @@ -23,10 +19,11 @@ use crate::{ self, constants, hashes::{self, BitName}, Address, AggregatedWithdrawal, Authorized, AuthorizedTransaction, - BatchIcannRegistrationData, BitNameDataUpdates, Body, EncryptionPubKey, - FilledOutput, FilledOutputContent, FilledTransaction, GetAddress as _, - GetValue as _, Hash, InPoint, OutPoint, OutputContent, SpentOutput, - Transaction, TxData, Txid, Update, Verify as _, WithdrawalBundle, + BatchIcannRegistrationData, BitNameDataUpdates, BlockHash, Body, + EncryptionPubKey, FilledOutput, FilledOutputContent, FilledTransaction, + GetAddress as _, GetValue as _, Hash, Header, InPoint, MerkleRoot, + OutPoint, OutputContent, SpentOutput, Transaction, TxData, Txid, + Update, Verify as _, WithdrawalBundle, }, }; @@ -46,103 +43,6 @@ struct TxidStamped { #[serde(transparent)] struct RollBack(NonEmpty>); -/// Representation of BitName data that supports rollbacks. -/// The most recent datum is the element at the back of the vector. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct BitNameData { - /// commitment to arbitrary data - commitment: RollBack>, - /// set if the plain bitname is known to be an ICANN domain - is_icann: bool, - /// optional ipv4 addr - ipv4_addr: RollBack>, - /// optional ipv6 addr - ipv6_addr: RollBack>, - /// optional pubkey used for encryption - encryption_pubkey: RollBack>, - /// optional pubkey used for signing messages - signing_pubkey: RollBack>, - /// optional minimum paymail fee, in sats - paymail_fee: RollBack>, -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("failed to verify authorization")] - AuthorizationError, - #[error("bad coinbase output content")] - BadCoinbaseOutputContent, - #[error("bitname {name_hash} already registered")] - BitNameAlreadyRegistered { name_hash: BitName }, - #[error("bitname {name_hash} already registered as an ICANN name")] - BitNameAlreadyIcann { name_hash: BitName }, - #[error("bundle too heavy {weight} > {max_weight}")] - BundleTooHeavy { weight: u64, max_weight: u64 }, - #[error("failed to fill tx output contents: invalid transaction")] - FillTxOutputContentsFailed, - #[error("heed error")] - Heed(#[from] heed::Error), - #[error("invalid ICANN name: {plain_name}")] - IcannNameInvalid { plain_name: String }, - #[error("failed to compute merkle root")] - MerkleRoot, - #[error("missing BitName {name_hash}")] - MissingBitName { name_hash: BitName }, - #[error( - "Missing BitName data for {name_hash} at block height {block_height}" - )] - MissingBitNameData { - name_hash: BitName, - block_height: u32, - }, - #[error("missing BitName input {name_hash}")] - MissingBitNameInput { name_hash: BitName }, - #[error("missing BitName reservation {txid}")] - MissingReservation { txid: Txid }, - #[error("no BitNames to update")] - NoBitNamesToUpdate, - #[error("total fees less than coinbase value")] - NotEnoughFees, - #[error("value in is less than value out")] - NotEnoughValueIn, - #[error("utxo {outpoint} doesn't exist")] - NoUtxo { outpoint: OutPoint }, - #[error(transparent)] - SignatureError(#[from] ed25519_dalek::SignatureError), - #[error("Too few BitName outputs")] - TooFewBitNameOutputs, - #[error("unbalanced BitNames: {n_bitname_inputs} BitName inputs, {n_bitname_outputs} BitName outputs")] - UnbalancedBitNames { - n_bitname_inputs: usize, - n_bitname_outputs: usize, - }, - #[error("unbalanced reservations: {n_reservation_inputs} reservation inputs, {n_reservation_outputs} reservation outputs")] - UnbalancedReservations { - n_reservation_inputs: usize, - n_reservation_outputs: usize, - }, - #[error("utxo double spent")] - UtxoDoubleSpent, - #[error("wrong public key for address")] - WrongPubKeyForAddress, -} - -#[derive(Clone)] -pub struct State { - /// associates tx hashes with bitname reservation commitments - pub bitname_reservations: Database, SerdeBincode>, - /// associates bitname IDs (name hashes) with bitname data - pub bitnames: Database, SerdeBincode>, - pub utxos: Database, SerdeBincode>, - pub stxos: Database, SerdeBincode>, - pub pending_withdrawal_bundle: - Database, SerdeBincode>, - pub last_withdrawal_bundle_failure_height: - Database, OwnedType>, - pub last_deposit_block: - Database, SerdeBincode>, -} - impl RollBack { fn new(value: T, txid: Txid, height: u32) -> Self { let txid_stamped = TxidStamped { @@ -163,6 +63,11 @@ impl RollBack { self.0.push(txid_stamped) } + /// pop the most recent value + fn pop(&mut self) -> Option> { + self.0.pop() + } + /** Returns the value as it was, at the specified block height. * If a value was updated several times in the block, returns the * last value seen in the block. */ @@ -179,6 +84,26 @@ impl RollBack { } } +/// Representation of BitName data that supports rollbacks. +/// The most recent datum is the element at the back of the vector. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct BitNameData { + /// commitment to arbitrary data + commitment: RollBack>, + /// set if the plain bitname is known to be an ICANN domain + is_icann: bool, + /// optional ipv4 addr + ipv4_addr: RollBack>, + /// optional ipv6 addr + ipv6_addr: RollBack>, + /// optional pubkey used for encryption + encryption_pubkey: RollBack>, + /// optional pubkey used for signing messages + signing_pubkey: RollBack>, + /// optional minimum paymail fee, in sats + paymail_fee: RollBack>, +} + impl BitNameData { // initialize from BitName data provided during a registration fn init(bitname_data: types::BitNameData, txid: Txid, height: u32) -> Self { @@ -251,6 +176,71 @@ impl BitNameData { apply_field_update(paymail_fee, updates.paymail_fee, txid, height); } + // revert bitname data updates + fn revert_updates( + &mut self, + updates: BitNameDataUpdates, + txid: Txid, + height: u32, + ) { + // apply an update to a single data field + fn revert_field_update( + data_field: &mut RollBack>, + update: Update, + txid: Txid, + height: u32, + ) where + T: std::fmt::Debug + Eq, + { + match update { + Update::Delete => { + let popped = data_field.pop(); + assert!(popped.is_some()); + let popped = popped.unwrap(); + assert!(popped.data.is_none()); + assert_eq!(popped.txid, txid); + assert_eq!(popped.height, height) + } + Update::Retain => (), + Update::Set(value) => { + let popped = data_field.pop(); + assert!(popped.is_some()); + let popped = popped.unwrap(); + assert!(popped.data.is_some()); + assert_eq!(popped.data.unwrap(), value); + assert_eq!(popped.txid, txid); + assert_eq!(popped.height, height) + } + } + } + + let Self { + ref mut commitment, + is_icann: _, + ref mut ipv4_addr, + ref mut ipv6_addr, + ref mut encryption_pubkey, + ref mut signing_pubkey, + ref mut paymail_fee, + } = self; + revert_field_update(paymail_fee, updates.paymail_fee, txid, height); + revert_field_update( + signing_pubkey, + updates.signing_pubkey, + txid, + height, + ); + revert_field_update( + encryption_pubkey, + updates.encryption_pubkey, + txid, + height, + ); + revert_field_update(ipv6_addr, updates.ipv6_addr, txid, height); + revert_field_update(ipv4_addr, updates.ipv4_addr, txid, height); + revert_field_update(commitment, updates.commitment, txid, height); + } + /** Returns the Bitname data as it was, at the specified block height. * If a value was updated several times in the block, returns the * last value seen in the block. @@ -283,11 +273,151 @@ impl BitNameData { } } +#[derive(Debug, thiserror::Error)] +pub enum InvalidHeaderError { + #[error("expected block hash {expected}, but computed {computed}")] + BlockHash { + expected: BlockHash, + computed: BlockHash, + }, + #[error("expected previous sidechain block hash {expected}, but received {received}")] + PrevSideHash { + expected: BlockHash, + received: BlockHash, + }, +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("failed to verify authorization")] + AuthorizationError, + #[error("bad coinbase output content")] + BadCoinbaseOutputContent, + #[error("bitname {name_hash} already registered")] + BitNameAlreadyRegistered { name_hash: BitName }, + #[error("bitname {name_hash} already registered as an ICANN name")] + BitNameAlreadyIcann { name_hash: BitName }, + #[error("bundle too heavy {weight} > {max_weight}")] + BundleTooHeavy { weight: u64, max_weight: u64 }, + #[error("failed to fill tx output contents: invalid transaction")] + FillTxOutputContentsFailed, + #[error("heed error")] + Heed(#[from] heed::Error), + #[error("invalid ICANN name: {plain_name}")] + IcannNameInvalid { plain_name: String }, + #[error("invalid body: expected merkle root {expected}, but computed {computed}")] + InvalidBody { + expected: MerkleRoot, + computed: MerkleRoot, + }, + #[error("invalid header: {0}")] + InvalidHeader(InvalidHeaderError), + #[error("failed to compute merkle root")] + MerkleRoot, + #[error("missing BitName {name_hash}")] + MissingBitName { name_hash: BitName }, + #[error( + "Missing BitName data for {name_hash} at block height {block_height}" + )] + MissingBitNameData { + name_hash: BitName, + block_height: u32, + }, + #[error("missing BitName input {name_hash}")] + MissingBitNameInput { name_hash: BitName }, + #[error("missing BitName reservation {txid}")] + MissingReservation { txid: Txid }, + #[error("no BitNames to update")] + NoBitNamesToUpdate, + #[error("deposit block doesn't exist")] + NoDepositBlock, + #[error("total fees less than coinbase value")] + NotEnoughFees, + #[error("value in is less than value out")] + NotEnoughValueIn, + #[error("stxo {outpoint} doesn't exist")] + NoStxo { outpoint: OutPoint }, + #[error("no tip")] + NoTip, + #[error("utxo {outpoint} doesn't exist")] + NoUtxo { outpoint: OutPoint }, + #[error(transparent)] + SignatureError(#[from] ed25519_dalek::SignatureError), + #[error("Too few BitName outputs")] + TooFewBitNameOutputs, + #[error("unbalanced BitNames: {n_bitname_inputs} BitName inputs, {n_bitname_outputs} BitName outputs")] + UnbalancedBitNames { + n_bitname_inputs: usize, + n_bitname_outputs: usize, + }, + #[error("unbalanced reservations: {n_reservation_inputs} reservation inputs, {n_reservation_outputs} reservation outputs")] + UnbalancedReservations { + n_reservation_inputs: usize, + n_reservation_outputs: usize, + }, + #[error("utxo double spent")] + UtxoDoubleSpent, + #[error("wrong public key for address")] + WrongPubKeyForAddress, +} + +/// Unit key. LMDB can't use zero-sized keys, so this encodes to a single byte +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct UnitKey; + +impl<'de> Deserialize<'de> for UnitKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // Deserialize any byte (ignoring it) and return UnitKey + let _ = u8::deserialize(deserializer)?; + Ok(UnitKey) + } +} + +impl Serialize for UnitKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // Always serialize to the same arbitrary byte + serializer.serialize_u8(0x69) + } +} + +#[derive(Clone)] +pub struct State { + /// Current tip + tip: Database, SerdeBincode>, + /// Current height + height: Database, SerdeBincode>, + /// associates tx hashes with bitname reservation commitments + pub bitname_reservations: Database, SerdeBincode>, + /// associates bitname IDs (name hashes) with bitname data + pub bitnames: Database, SerdeBincode>, + pub utxos: Database, SerdeBincode>, + pub stxos: Database, SerdeBincode>, + /// Pending withdrawal bundle and block height + pub pending_withdrawal_bundle: + Database, SerdeBincode<(WithdrawalBundle, u32)>>, + /// Mapping from block height to withdrawal bundle and status + pub withdrawal_bundles: Database< + SerdeBincode, + SerdeBincode<(WithdrawalBundle, WithdrawalBundleStatus)>, + >, + /// deposit blocks and the height at which they were applied, keyed sequentially + pub deposit_blocks: + Database, SerdeBincode<(bitcoin::BlockHash, u32)>>, +} + impl State { - pub const NUM_DBS: u32 = 7; + pub const NUM_DBS: u32 = 9; pub const WITHDRAWAL_BUNDLE_FAILURE_GAP: u32 = 5; pub fn new(env: &heed::Env) -> Result { + let tip = env.create_database(Some("tip"))?; + let height = env.create_database(Some("height"))?; let bitname_reservations = env.create_database(Some("bitname_reservations"))?; let bitnames = env.create_database(Some("bitnames"))?; @@ -295,21 +425,32 @@ impl State { let stxos = env.create_database(Some("stxos"))?; let pending_withdrawal_bundle = env.create_database(Some("pending_withdrawal_bundle"))?; - let last_withdrawal_bundle_failure_height = - env.create_database(Some("last_withdrawal_bundle_failure_height"))?; - let last_deposit_block = - env.create_database(Some("last_deposit_block"))?; + let withdrawal_bundles = + env.create_database(Some("withdrawal_bundles"))?; + let deposit_blocks = env.create_database(Some("deposit_blocks"))?; Ok(Self { + tip, + height, bitname_reservations, bitnames, utxos, stxos, pending_withdrawal_bundle, - last_withdrawal_bundle_failure_height, - last_deposit_block, + withdrawal_bundles, + deposit_blocks, }) } + pub fn get_tip(&self, rotxn: &RoTxn) -> Result { + let tip = self.tip.get(rotxn, &UnitKey)?.unwrap_or_default(); + Ok(tip) + } + + pub fn get_height(&self, rotxn: &RoTxn) -> Result { + let height = self.height.get(rotxn, &UnitKey)?.unwrap_or_default(); + Ok(height) + } + /// Return the Bitname data. Returns an error if it does not exist. fn get_bitname( &self, @@ -406,16 +547,30 @@ impl State { Ok(utxos) } - pub fn fill_transaction( + /// Get the latest failed withdrawal bundle, and the height at which it failed + fn get_latest_failed_withdrawal_bundle( &self, - txn: &RoTxn, + rotxn: &RoTxn, + ) -> Result, Error> { + for item in self.withdrawal_bundles.rev_iter(rotxn)? { + if let (height, (bundle, WithdrawalBundleStatus::Failed)) = item? { + let res = Some((height, bundle)); + return Ok(res); + } + } + Ok(None) + } + + fn fill_transaction( + &self, + rotxn: &RoTxn, transaction: &Transaction, ) -> Result { let mut spent_utxos = vec![]; for input in &transaction.inputs { let utxo = self .utxos - .get(txn, input)? + .get(rotxn, input)? .ok_or(Error::NoUtxo { outpoint: *input })?; spent_utxos.push(utxo); } @@ -425,6 +580,36 @@ impl State { }) } + /// Fill a transaction that has already been applied + fn fill_transaction_from_stxos( + &self, + rotxn: &RoTxn, + tx: Transaction, + ) -> Result { + let txid = tx.txid(); + let mut spent_utxos = vec![]; + // fill inputs last-to-first + for (vin, input) in tx.inputs.iter().enumerate().rev() { + let stxo = self + .stxos + .get(rotxn, input)? + .ok_or(Error::NoStxo { outpoint: *input })?; + assert_eq!( + stxo.inpoint, + InPoint::Regular { + txid, + vin: vin as u32 + } + ); + spent_utxos.push(stxo.output); + } + spent_utxos.reverse(); + Ok(FilledTransaction { + spent_utxos, + transaction: tx, + }) + } + pub fn fill_authorized_transaction( &self, txn: &RoTxn, @@ -491,7 +676,7 @@ impl State { address_to_aggregated_withdrawal.into_values().collect(); aggregated_withdrawals.sort_by_key(|a| std::cmp::Reverse(a.clone())); let mut fee = 0; - let mut spend_utxos = HashMap::::new(); + let mut spend_utxos = BTreeMap::::new(); let mut bundle_outputs = vec![]; for aggregated in &aggregated_withdrawals { if bundle_outputs.len() > MAX_BUNDLE_OUTPUTS { @@ -582,11 +767,12 @@ impl State { })) } + /// Get pending withdrawal bundle and block height pub fn get_pending_withdrawal_bundle( &self, txn: &RoTxn, - ) -> Result, Error> { - Ok(self.pending_withdrawal_bundle.get(txn, &0)?) + ) -> Result, Error> { + Ok(self.pending_withdrawal_bundle.get(txn, &UnitKey)?) } /// Check that @@ -714,36 +900,81 @@ impl State { tx.fee().ok_or(Error::NotEnoughValueIn) } - pub fn validate_body( + pub fn validate_transaction( &self, rotxn: &RoTxn, - body: &Body, + transaction: &AuthorizedTransaction, ) -> Result { + let filled_transaction = + self.fill_transaction(rotxn, &transaction.transaction)?; + for (authorization, spent_utxo) in transaction + .authorizations + .iter() + .zip(filled_transaction.spent_utxos.iter()) + { + if authorization.get_address() != spent_utxo.address { + return Err(Error::WrongPubKeyForAddress); + } + } + if Authorization::verify_transaction(transaction).is_err() { + return Err(Error::AuthorizationError); + } + let fee = + self.validate_filled_transaction(rotxn, &filled_transaction)?; + Ok(fee) + } + + /// Validate a block, returning the merkle root and fees + pub fn validate_block( + &self, + rotxn: &RoTxn, + header: &Header, + body: &Body, + ) -> Result<(u64, MerkleRoot), Error> { + let tip_hash = self.get_tip(rotxn)?; + if header.prev_side_hash != tip_hash { + let err = InvalidHeaderError::PrevSideHash { + expected: tip_hash, + received: header.prev_side_hash, + }; + return Err(Error::InvalidHeader(err)); + }; let mut coinbase_value: u64 = 0; for output in &body.coinbase { coinbase_value += output.get_value(); } let mut total_fees: u64 = 0; let mut spent_utxos = HashSet::new(); - let filled_transactions: Vec<_> = body + let filled_txs: Vec<_> = body .transactions .iter() .map(|t| self.fill_transaction(rotxn, t)) .collect::>()?; - for filled_transaction in &filled_transactions { - for input in &filled_transaction.transaction.inputs { + for filled_tx in &filled_txs { + for input in &filled_tx.transaction.inputs { if spent_utxos.contains(input) { return Err(Error::UtxoDoubleSpent); } spent_utxos.insert(*input); } - total_fees += - self.validate_filled_transaction(rotxn, filled_transaction)?; + total_fees += self.validate_filled_transaction(rotxn, filled_tx)?; } if coinbase_value > total_fees { return Err(Error::NotEnoughFees); } - let spent_utxos = filled_transactions + let merkle_root = Body::compute_merkle_root( + body.coinbase.as_slice(), + filled_txs.as_slice(), + ) + .ok_or(Error::MerkleRoot)?; + if merkle_root != header.merkle_root { + let err = Error::InvalidBody { + expected: header.merkle_root, + computed: merkle_root, + }; + return Err(err); + } + let spent_utxos = filled_txs .iter() .flat_map(|t| t.spent_utxos_requiring_auth().into_iter()); for (authorization, spent_utxo) in @@ -756,85 +987,191 @@ impl State { if Authorization::verify_body(body).is_err() { return Err(Error::AuthorizationError); } - Ok(total_fees) + Ok((total_fees, merkle_root)) } pub fn get_last_deposit_block_hash( &self, - txn: &RoTxn, + rotxn: &RoTxn, ) -> Result, Error> { - Ok(self.last_deposit_block.get(txn, &0)?) + let block_hash = self + .deposit_blocks + .last(rotxn)? + .map(|(_, (block_hash, _))| block_hash); + Ok(block_hash) } pub fn connect_two_way_peg_data( &self, - txn: &mut RwTxn, + rwtxn: &mut RwTxn, two_way_peg_data: &TwoWayPegData, - block_height: u32, ) -> Result<(), Error> { + let block_height = self.get_height(rwtxn)?; // Handle deposits. if let Some(deposit_block_hash) = two_way_peg_data.deposit_block_hash { - self.last_deposit_block.put(txn, &0, &deposit_block_hash)?; + let deposit_block_seq_idx = self + .deposit_blocks + .last(rwtxn)? + .map_or(0, |(seq_idx, _)| seq_idx + 1); + self.deposit_blocks.put( + rwtxn, + &deposit_block_seq_idx, + &(deposit_block_hash, block_height - 1), + )?; } - for (outpoint, deposit) in &two_way_peg_data.deposits { - if let Ok(address) = deposit.address.parse() { - let outpoint = OutPoint::Deposit(*outpoint); + for deposit in &two_way_peg_data.deposits { + if let Ok(address) = deposit.output.address.parse() { + let outpoint = OutPoint::Deposit(deposit.outpoint); let output = FilledOutput::new( address, - FilledOutputContent::Bitcoin(deposit.value), + FilledOutputContent::Bitcoin(deposit.output.value), ); - self.utxos.put(txn, &outpoint, &output)?; + self.utxos.put(rwtxn, &outpoint, &output)?; } } // Handle withdrawals. let last_withdrawal_bundle_failure_height = self - .last_withdrawal_bundle_failure_height - .get(txn, &0)? - .unwrap_or(0); - if (block_height + 1) - last_withdrawal_bundle_failure_height + .get_latest_failed_withdrawal_bundle(rwtxn)? + .map(|(height, _bundle)| height) + .unwrap_or_default(); + if block_height - last_withdrawal_bundle_failure_height > Self::WITHDRAWAL_BUNDLE_FAILURE_GAP - && self.pending_withdrawal_bundle.get(txn, &0)?.is_none() + && self + .pending_withdrawal_bundle + .get(rwtxn, &UnitKey)? + .is_none() { if let Some(bundle) = - self.collect_withdrawal_bundle(txn, block_height + 1)? + self.collect_withdrawal_bundle(rwtxn, block_height)? { for (outpoint, spend_output) in &bundle.spend_utxos { - self.utxos.delete(txn, outpoint)?; + self.utxos.delete(rwtxn, outpoint)?; let txid = bundle.transaction.txid(); let spent_output = SpentOutput { output: spend_output.clone(), inpoint: InPoint::Withdrawal { txid }, }; - self.stxos.put(txn, outpoint, &spent_output)?; + self.stxos.put(rwtxn, outpoint, &spent_output)?; } - self.pending_withdrawal_bundle.put(txn, &0, &bundle)?; + self.pending_withdrawal_bundle.put( + rwtxn, + &UnitKey, + &(bundle, block_height), + )?; } } for (txid, status) in &two_way_peg_data.bundle_statuses { - if let Some(bundle) = self.pending_withdrawal_bundle.get(txn, &0)? { + if let Some((bundle, bundle_block_height)) = + self.pending_withdrawal_bundle.get(rwtxn, &UnitKey)? + { if bundle.transaction.txid() != *txid { continue; } - match status { - WithdrawalBundleStatus::Failed => { - self.last_withdrawal_bundle_failure_height.put( - txn, - &0, - &(block_height + 1), - )?; - self.pending_withdrawal_bundle.delete(txn, &0)?; - for (outpoint, spend_output) in &bundle.spend_utxos { - self.stxos.delete(txn, outpoint)?; - self.utxos.put(txn, outpoint, spend_output)?; - } + assert_eq!(bundle_block_height, block_height); + self.withdrawal_bundles.put( + rwtxn, + &block_height, + &(bundle.clone(), *status), + )?; + self.pending_withdrawal_bundle.delete(rwtxn, &UnitKey)?; + if let WithdrawalBundleStatus::Failed = status { + for (outpoint, output) in &bundle.spend_utxos { + self.stxos.delete(rwtxn, outpoint)?; + self.utxos.put(rwtxn, outpoint, output)?; } - WithdrawalBundleStatus::Confirmed => { - self.pending_withdrawal_bundle.delete(txn, &0)?; + } + } + } + Ok(()) + } + + pub fn disconnect_two_way_peg_data( + &self, + rwtxn: &mut RwTxn, + two_way_peg_data: &TwoWayPegData, + ) -> Result<(), Error> { + let block_height = self.get_height(rwtxn)?; + // Restore pending withdrawal bundle + for (txid, status) in two_way_peg_data.bundle_statuses.iter().rev() { + if let Some(( + latest_bundle_height, + (latest_bundle, latest_bundle_status), + )) = self.withdrawal_bundles.last(rwtxn)? + { + if latest_bundle.transaction.txid() != *txid { + continue; + } + assert_eq!(*status, latest_bundle_status); + assert_eq!(latest_bundle_height, block_height); + self.withdrawal_bundles + .delete(rwtxn, &latest_bundle_height)?; + self.pending_withdrawal_bundle.put( + rwtxn, + &UnitKey, + &(latest_bundle.clone(), latest_bundle_height), + )?; + if *status == WithdrawalBundleStatus::Failed { + for (outpoint, output) in + latest_bundle.spend_utxos.into_iter().rev() + { + let spent_output = SpentOutput { + output: output.clone(), + inpoint: InPoint::Withdrawal { txid: *txid }, + }; + self.stxos.put(rwtxn, &outpoint, &spent_output)?; + if self.utxos.delete(rwtxn, &outpoint)? { + return Err(Error::NoUtxo { outpoint }); + }; } } } } + // Handle withdrawals. + let last_withdrawal_bundle_failure_height = self + .get_latest_failed_withdrawal_bundle(rwtxn)? + .map(|(height, _bundle)| height) + .unwrap_or_default(); + if block_height - last_withdrawal_bundle_failure_height + > Self::WITHDRAWAL_BUNDLE_FAILURE_GAP + && let Some((bundle, bundle_height)) = + self.pending_withdrawal_bundle.get(rwtxn, &UnitKey)? + && bundle_height == block_height + { + self.pending_withdrawal_bundle.delete(rwtxn, &UnitKey)?; + for (outpoint, output) in bundle.spend_utxos.into_iter().rev() { + if !self.stxos.delete(rwtxn, &outpoint)? { + return Err(Error::NoStxo { outpoint }); + }; + self.utxos.put(rwtxn, &outpoint, &output)?; + } + } + // Handle deposits. + if let Some(deposit_block_hash) = two_way_peg_data.deposit_block_hash { + let ( + last_deposit_block_seq_idx, + (last_deposit_block_hash, last_deposit_block_height), + ) = self + .deposit_blocks + .last(rwtxn)? + .ok_or(Error::NoDepositBlock)?; + assert_eq!(deposit_block_hash, last_deposit_block_hash); + assert_eq!(block_height - 1, last_deposit_block_height); + if !self + .deposit_blocks + .delete(rwtxn, &last_deposit_block_seq_idx)? + { + return Err(Error::NoDepositBlock); + }; + } + for deposit in two_way_peg_data.deposits.iter().rev() { + if let Ok(_address) = deposit.output.address.parse::
() { + let outpoint = OutPoint::Deposit(deposit.outpoint); + if !self.utxos.delete(rwtxn, &outpoint)? { + return Err(Error::NoUtxo { outpoint }); + } + } + } Ok(()) } @@ -876,6 +1213,38 @@ impl State { Ok(()) } + fn revert_bitname_registration( + &self, + rwtxn: &mut RwTxn, + filled_tx: &FilledTransaction, + name_hash: BitName, + ) -> Result<(), Error> { + if !self.bitnames.delete(rwtxn, &name_hash)? { + return Err(Error::MissingBitName { name_hash }); + } + // Find the reservation to restore + let implied_commitment = + filled_tx.implied_reservation_commitment().expect( + "A BitName registration tx should have an implied commitment", + ); + let burned_reservation_txid = + filled_tx.spent_reservations().find_map(|(_, filled_output)| { + let (txid, commitment) = filled_output.reservation_data() + .expect("A spent reservation should correspond to a commitment"); + if *commitment == implied_commitment { + Some(txid) + } else { + None + } + }).expect("A BitName registration tx should correspond to a burned reservation"); + self.bitname_reservations.put( + rwtxn, + burned_reservation_txid, + &implied_commitment, + )?; + Ok(()) + } + // apply bitname updates fn apply_bitname_updates( &self, @@ -905,6 +1274,34 @@ impl State { Ok(()) } + fn revert_bitname_updates( + &self, + rwtxn: &mut RwTxn, + filled_tx: &FilledTransaction, + bitname_updates: BitNameDataUpdates, + height: u32, + ) -> Result<(), Error> { + // the updated bitname is the BitName that corresponds to the last + // bitname output, or equivalently, the BitName corresponding to the + // last bitname input + let updated_bitname = filled_tx + .spent_bitnames() + .next_back() + .ok_or(Error::NoBitNamesToUpdate)? + .1 + .bitname() + .expect("should only contain BitName outputs"); + let mut bitname_data = self + .bitnames + .get(rwtxn, updated_bitname)? + .ok_or(Error::MissingBitName { + name_hash: *updated_bitname, + })?; + bitname_data.revert_updates(bitname_updates, filled_tx.txid(), height); + self.bitnames.put(rwtxn, updated_bitname, &bitname_data)?; + Ok(()) + } + // apply batch icann registration fn apply_batch_icann( &self, @@ -942,20 +1339,93 @@ impl State { Ok(()) } - pub fn connect_body( + // revert batch icann registration + fn revert_batch_icann( + &self, + rwtxn: &mut RwTxn, + filled_tx: &FilledTransaction, + batch_icann_data: &BatchIcannRegistrationData, + ) -> Result<(), Error> { + let name_hashes = batch_icann_data.plain_names.iter().map(|name| { + let hash = blake3::hash(name.as_bytes()); + BitName(Hash::from(hash)) + }); + let mut spent_bitnames = filled_tx.spent_bitnames(); + for name_hash in name_hashes.into_iter().rev() { + // search for the bitname to be registered as an ICANN domain + // exists in the inputs + let found_bitname = spent_bitnames.any(|(_, outpoint)| { + let bitname = outpoint.bitname() + .expect("spent bitname input should correspond to a known name hash"); + *bitname == name_hash + }); + if found_bitname { + let mut bitname_data = self + .bitnames + .get(rwtxn, &name_hash)? + .ok_or(Error::MissingBitName { name_hash })?; + assert!(!bitname_data.is_icann); + bitname_data.is_icann = false; + self.bitnames.put(rwtxn, &name_hash, &bitname_data)?; + } else { + return Err(Error::MissingBitNameInput { name_hash }); + } + } + Ok(()) + } + + pub fn connect_block( &self, - txn: &mut RwTxn, + rwtxn: &mut RwTxn, + header: &Header, body: &Body, - height: u32, ) -> Result { + let height = self.get_height(rwtxn)?; + let tip_hash = self.get_tip(rwtxn)?; + if tip_hash != header.prev_side_hash { + let err = InvalidHeaderError::PrevSideHash { + expected: tip_hash, + received: header.prev_side_hash, + }; + return Err(Error::InvalidHeader(err)); + } + for (vout, output) in body.coinbase.iter().enumerate() { + let outpoint = OutPoint::Coinbase { + merkle_root: header.merkle_root, + vout: vout as u32, + }; + let filled_content = match output.content.clone() { + OutputContent::Value(value) => { + FilledOutputContent::Bitcoin(value) + } + OutputContent::Withdrawal { + value, + main_fee, + main_address, + } => FilledOutputContent::BitcoinWithdrawal { + value, + main_fee, + main_address, + }, + OutputContent::BitName | OutputContent::BitNameReservation => { + return Err(Error::BadCoinbaseOutputContent); + } + }; + let filled_output = FilledOutput { + address: output.address, + content: filled_content, + memo: output.memo.clone(), + }; + self.utxos.put(rwtxn, &outpoint, &filled_output)?; + } let mut filled_txs: Vec = Vec::new(); for transaction in &body.transactions { - let filled_tx = self.fill_transaction(txn, transaction)?; + let filled_tx = self.fill_transaction(rwtxn, transaction)?; let txid = filled_tx.txid(); for (vin, input) in filled_tx.inputs().iter().enumerate() { let spent_output = self .utxos - .get(txn, input)? + .get(rwtxn, input)? .ok_or(Error::NoUtxo { outpoint: *input })?; let spent_output = SpentOutput { output: spent_output, @@ -964,8 +1434,8 @@ impl State { vin: vin as u32, }, }; - self.utxos.delete(txn, input)?; - self.stxos.put(txn, input, &spent_output)?; + self.utxos.delete(rwtxn, input)?; + self.stxos.put(rwtxn, input, &spent_output)?; } let filled_outputs = filled_tx .filled_outputs() @@ -975,12 +1445,12 @@ impl State { txid, vout: vout as u32, }; - self.utxos.put(txn, &outpoint, filled_output)?; + self.utxos.put(rwtxn, &outpoint, filled_output)?; } match &transaction.data { None => (), Some(TxData::BitNameReservation { commitment }) => { - self.bitname_reservations.put(txn, &txid, commitment)?; + self.bitname_reservations.put(rwtxn, &txid, commitment)?; } Some(TxData::BitNameRegistration { name_hash, @@ -988,7 +1458,7 @@ impl State { bitname_data, }) => { let () = self.apply_bitname_registration( - txn, + rwtxn, &filled_tx, *name_hash, bitname_data, @@ -997,7 +1467,7 @@ impl State { } Some(TxData::BitNameUpdate(bitname_updates)) => { let () = self.apply_bitname_updates( - txn, + rwtxn, &filled_tx, (**bitname_updates).clone(), height, @@ -1005,7 +1475,7 @@ impl State { } Some(TxData::BatchIcann(batch_icann_data)) => { let () = self.apply_batch_icann( - txn, + rwtxn, &filled_tx, batch_icann_data, )?; @@ -1018,36 +1488,132 @@ impl State { filled_txs.as_slice(), ) .ok_or(Error::MerkleRoot)?; - for (vout, output) in body.coinbase.iter().enumerate() { - let outpoint = OutPoint::Coinbase { - merkle_root, - vout: vout as u32, + if merkle_root != header.merkle_root { + let err = Error::InvalidBody { + expected: header.merkle_root, + computed: merkle_root, }; - let filled_content = match output.content.clone() { - OutputContent::Value(value) => { - FilledOutputContent::Bitcoin(value) + return Err(err); + } + let block_hash = header.hash(); + self.tip.put(rwtxn, &UnitKey, &block_hash)?; + self.height.put(rwtxn, &UnitKey, &(height + 1))?; + Ok(merkle_root) + } + + pub fn disconnect_tip( + &self, + rwtxn: &mut RwTxn, + header: &Header, + body: &Body, + ) -> Result<(), Error> { + let tip_hash = self.get_tip(rwtxn)?; + if tip_hash != header.hash() { + let err = InvalidHeaderError::BlockHash { + expected: tip_hash, + computed: header.hash(), + }; + return Err(Error::InvalidHeader(err)); + } + let height = self.get_height(rwtxn)?; + // revert txs, last-to-first + let mut filled_txs: Vec = Vec::new(); + body.transactions.iter().rev().try_for_each(|tx| { + let txid = tx.txid(); + let filled_tx = + self.fill_transaction_from_stxos(rwtxn, tx.clone())?; + // revert transaction effects + match &tx.data { + None => (), + Some(TxData::BitNameReservation { commitment }) => { + self.bitname_reservations.put(rwtxn, &txid, commitment)?; + if !self.bitname_reservations.delete(rwtxn, &txid)? { + return Err(Error::MissingReservation { txid }); + } } - OutputContent::Withdrawal { - value, - main_fee, - main_address, - } => FilledOutputContent::BitcoinWithdrawal { - value, - main_fee, - main_address, + Some(TxData::BitNameRegistration { + name_hash, + revealed_nonce: _, + bitname_data: _, + }) => { + let () = self.revert_bitname_registration( + rwtxn, &filled_tx, *name_hash, + )?; + } + Some(TxData::BitNameUpdate(bitname_updates)) => { + let () = self.revert_bitname_updates( + rwtxn, + &filled_tx, + (**bitname_updates).clone(), + height - 1, + )?; + } + Some(TxData::BatchIcann(batch_icann_data)) => { + let () = self.revert_batch_icann( + rwtxn, + &filled_tx, + batch_icann_data, + )?; + } + } + filled_txs.push(filled_tx); + // delete UTXOs, last-to-first + tx.outputs.iter().enumerate().rev().try_for_each( + |(vout, _output)| { + let outpoint = OutPoint::Regular { + txid, + vout: vout as u32, + }; + if self.utxos.delete(rwtxn, &outpoint)? { + Ok(()) + } else { + Err(Error::NoUtxo { outpoint }) + } }, - OutputContent::BitName | OutputContent::BitNameReservation => { - return Err(Error::BadCoinbaseOutputContent); + )?; + // unspend STXOs, last-to-first + tx.inputs.iter().rev().try_for_each(|outpoint| { + if let Some(spent_output) = self.stxos.get(rwtxn, outpoint)? { + self.stxos.delete(rwtxn, outpoint)?; + self.utxos.put(rwtxn, outpoint, &spent_output.output)?; + Ok(()) + } else { + Err(Error::NoStxo { + outpoint: *outpoint, + }) } + }) + })?; + filled_txs.reverse(); + // delete coinbase UTXOs, last-to-first + body.coinbase.iter().enumerate().rev().try_for_each( + |(vout, _output)| { + let outpoint = OutPoint::Coinbase { + merkle_root: header.merkle_root, + vout: vout as u32, + }; + if self.utxos.delete(rwtxn, &outpoint)? { + Ok(()) + } else { + Err(Error::NoUtxo { outpoint }) + } + }, + )?; + let merkle_root = Body::compute_merkle_root( + body.coinbase.as_slice(), + filled_txs.as_slice(), + ) + .ok_or(Error::MerkleRoot)?; + if merkle_root != header.merkle_root { + let err = Error::InvalidBody { + expected: header.merkle_root, + computed: merkle_root, }; - let filled_output = FilledOutput { - address: output.address, - content: filled_content, - memo: output.memo.clone(), - }; - self.utxos.put(txn, &outpoint, &filled_output)?; + return Err(err); } - Ok(merkle_root) + self.tip.put(rwtxn, &UnitKey, &header.prev_side_hash)?; + self.height.put(rwtxn, &UnitKey, &(height - 1))?; + Ok(()) } /// Get total sidechain wealth in Bitcoin diff --git a/lib/types/hashes.rs b/lib/types/hashes.rs index e741163..92ad938 100644 --- a/lib/types/hashes.rs +++ b/lib/types/hashes.rs @@ -18,7 +18,9 @@ use super::serde_hexstr_human_readable; Deserialize, Eq, Hash, + Ord, PartialEq, + PartialOrd, Serialize, )] #[repr(transparent)] @@ -85,7 +87,9 @@ impl FromStr for BlockHash { Deserialize, Eq, Hash, + Ord, PartialEq, + PartialOrd, Serialize, )] #[repr(transparent)] diff --git a/lib/types/mod.rs b/lib/types/mod.rs index 6d8b2de..4a2d476 100644 --- a/lib/types/mod.rs +++ b/lib/types/mod.rs @@ -1,4 +1,7 @@ -use std::{cmp::Ordering, collections::HashMap}; +use std::{ + cmp::Ordering, + collections::{BTreeMap, HashMap}, +}; use bech32::{FromBase32, ToBase32}; use borsh::BorshSerialize; @@ -15,7 +18,7 @@ pub mod hashes; mod transaction; pub use address::*; -pub use hashes::{BlockHash, Hash, MerkleRoot, Txid}; +pub use hashes::{BitName, BlockHash, Hash, MerkleRoot, Txid}; pub use transaction::{ Authorized, AuthorizedTransaction, BatchIcannRegistrationData, BitNameData, BitNameDataUpdates, Content as OutputContent, @@ -166,7 +169,7 @@ pub enum WithdrawalBundleStatus { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WithdrawalBundle { - pub spend_utxos: HashMap, + pub spend_utxos: BTreeMap, pub transaction: bitcoin::Transaction, } @@ -177,106 +180,6 @@ pub struct TwoWayPegData { pub bundle_statuses: HashMap, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Body { - pub coinbase: Vec, - pub transactions: Vec, - pub authorizations: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Block { - #[serde(flatten)] - pub header: Header, - #[serde(flatten)] - pub body: Body, - pub height: u32, -} - -/* -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DisconnectData { - pub spent_utxos: HashMap, - pub deposits: Vec, - pub pending_bundles: Vec, - pub spent_bundles: HashMap>, - pub spent_withdrawals: HashMap, - pub failed_withdrawals: Vec, -} -*/ - -#[derive(Eq, PartialEq, Clone, Debug)] -pub struct AggregatedWithdrawal { - pub spend_utxos: HashMap, - pub main_address: bitcoin::Address, - pub value: u64, - pub main_fee: u64, -} - -#[derive(Debug, Error)] -pub enum Bech32mDecodeError { - #[error(transparent)] - Bech32m(#[from] bech32::Error), - #[error("Wrong Bech32 HRP. Perhaps this key is being used somewhere it shouldn't be.")] - WrongHrp, - #[error("Wrong decoded byte length. Must decode to 32 bytes of data.")] - WrongSize, - #[error("Wrong Bech32 variant. Only Bech32m is accepted.")] - WrongVariant, -} - -impl EncryptionPubKey { - /// HRP for Bech32m encoding - const BECH32M_HRP: &'static str = "bn-enc"; - - /// Encode to Bech32m format - pub fn bech32m_encode(&self) -> String { - bech32::encode( - Self::BECH32M_HRP, - self.0.as_bytes().to_base32(), - bech32::Variant::Bech32m, - ) - .expect("Bech32m Encoding should not fail") - } - - /// Decode from Bech32m format - pub fn bech32m_decode(s: &str) -> Result { - let (hrp, data5, variant) = bech32::decode(s)?; - if variant != bech32::Variant::Bech32m { - return Err(Bech32mDecodeError::WrongVariant); - } - if hrp != Self::BECH32M_HRP { - return Err(Bech32mDecodeError::WrongHrp); - } - let data8 = Vec::::from_base32(&data5)?; - let Ok(bytes) = <[u8; 32]>::try_from(data8) else { - return Err(Bech32mDecodeError::WrongSize); - }; - Ok(Self::from(bytes)) - } -} - -impl std::fmt::Display for EncryptionPubKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.bech32m_encode().fmt(f) - } -} - -impl From for EncryptionPubKey -where - x25519_dalek::PublicKey: From, -{ - fn from(value: T) -> Self { - Self(value.into()) - } -} - -impl Header { - pub fn hash(&self) -> BlockHash { - hashes::hash(self).into() - } -} - // Internal node of a CBMT #[derive(Clone, Debug, Default, Eq, PartialEq)] struct CbmtNode { @@ -335,6 +238,13 @@ impl merkle_cbt::merkle_tree::Merge for MergeFeeSizeTotal { // Complete binary merkle tree with annotated fee and canonical size totals type CbmtWithFeeTotal = merkle_cbt::CBMT; +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Body { + pub coinbase: Vec, + pub transactions: Vec, + pub authorizations: Vec, +} + impl Body { pub fn new( authorized_transactions: Vec, @@ -359,6 +269,24 @@ impl Body { } } + pub fn authorized_transactions(&self) -> Vec { + let mut authorizations_iter = self.authorizations.iter(); + self.transactions + .iter() + .map(|tx| { + let mut authorizations = Vec::with_capacity(tx.inputs.len()); + for _ in 0..tx.inputs.len() { + let auth = authorizations_iter.next().unwrap(); + authorizations.push(auth.clone()); + } + AuthorizedTransaction { + transaction: tx.clone(), + authorizations, + } + }) + .collect() + } + pub fn compute_merkle_root( coinbase: &[Output], txs: &[FilledTransaction], @@ -424,6 +352,99 @@ impl Body { } } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Block { + #[serde(flatten)] + pub header: Header, + #[serde(flatten)] + pub body: Body, + pub height: u32, +} + +/* +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DisconnectData { + pub spent_utxos: HashMap, + pub deposits: Vec, + pub pending_bundles: Vec, + pub spent_bundles: HashMap>, + pub spent_withdrawals: HashMap, + pub failed_withdrawals: Vec, +} +*/ + +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct AggregatedWithdrawal { + pub spend_utxos: HashMap, + pub main_address: bitcoin::Address, + pub value: u64, + pub main_fee: u64, +} + +#[derive(Debug, Error)] +pub enum Bech32mDecodeError { + #[error(transparent)] + Bech32m(#[from] bech32::Error), + #[error("Wrong Bech32 HRP. Perhaps this key is being used somewhere it shouldn't be.")] + WrongHrp, + #[error("Wrong decoded byte length. Must decode to 32 bytes of data.")] + WrongSize, + #[error("Wrong Bech32 variant. Only Bech32m is accepted.")] + WrongVariant, +} + +impl EncryptionPubKey { + /// HRP for Bech32m encoding + const BECH32M_HRP: &'static str = "bn-enc"; + + /// Encode to Bech32m format + pub fn bech32m_encode(&self) -> String { + bech32::encode( + Self::BECH32M_HRP, + self.0.as_bytes().to_base32(), + bech32::Variant::Bech32m, + ) + .expect("Bech32m Encoding should not fail") + } + + /// Decode from Bech32m format + pub fn bech32m_decode(s: &str) -> Result { + let (hrp, data5, variant) = bech32::decode(s)?; + if variant != bech32::Variant::Bech32m { + return Err(Bech32mDecodeError::WrongVariant); + } + if hrp != Self::BECH32M_HRP { + return Err(Bech32mDecodeError::WrongHrp); + } + let data8 = Vec::::from_base32(&data5)?; + let Ok(bytes) = <[u8; 32]>::try_from(data8) else { + return Err(Bech32mDecodeError::WrongSize); + }; + Ok(Self::from(bytes)) + } +} + +impl std::fmt::Display for EncryptionPubKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.bech32m_encode().fmt(f) + } +} + +impl From for EncryptionPubKey +where + x25519_dalek::PublicKey: From, +{ + fn from(value: T) -> Self { + Self(value.into()) + } +} + +impl Header { + pub fn hash(&self) -> BlockHash { + hashes::hash(self).into() + } +} + impl Ord for AggregatedWithdrawal { fn cmp(&self, other: &Self) -> Ordering { if self == other { diff --git a/lib/types/transaction.rs b/lib/types/transaction.rs index fbc6a87..516d4bc 100644 --- a/lib/types/transaction.rs +++ b/lib/types/transaction.rs @@ -37,7 +37,9 @@ where Deserialize, Eq, Hash, + Ord, PartialEq, + PartialOrd, Serialize, )] pub enum OutPoint { diff --git a/rpc-api/lib.rs b/rpc-api/lib.rs index 3c627be..4b74b12 100644 --- a/rpc-api/lib.rs +++ b/rpc-api/lib.rs @@ -38,10 +38,6 @@ pub trait Rpc { #[method(name = "get_block")] async fn get_block(&self, block_hash: BlockHash) -> RpcResult; - /// Get the hash of the block at the specified height - #[method(name = "get_block_hash")] - async fn get_block_hash(&self, height: u32) -> RpcResult; - /// Get a new address #[method(name = "get_new_address")] async fn get_new_address(&self) -> RpcResult
;