diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 873901ab0..000000000 --- a/.cirrus.yml +++ /dev/null @@ -1,43 +0,0 @@ -task: - name: test [ARM] - arm_container: - image: rust:1.63.0 - cpu: 4 - memory: 8G - registry_cache: - folder: $CARGO_HOME/registry - fingerprint_script: cat Cargo.lock - target_cache: - folder: target - fingerprint_script: - - rustc --version - - cat Cargo.lock - build_script: - - apt-get update -qqy - - apt-get install -qqy clang - - rustup component add clippy rustfmt - - cargo check --all --locked - - cargo build --all --locked - test_script: - - cargo test --all --locked - - cargo clippy -- -D warnings - - cargo fmt --all -- --check - before_cache_script: rm -rf $CARGO_HOME/registry/index - - -task: - compute_engine_instance: - image_project: cirrus-images - image: family/docker-builder-arm64 - architecture: arm64 - platform: linux - cpu: 2 - memory: 4G - - name: integration [ARM] - env: - CIRRUS_ARCH: arm64 - build_script: - - docker build --build-arg=ARCH=aarch64 -f Dockerfile.ci . --rm -t electrs:tests - test_script: - - docker run -v $PWD/contrib/:/contrib -v $PWD/tests/:/tests --rm electrs:tests bash /tests/run.sh diff --git a/Cargo.lock b/Cargo.lock index cdcc541f6..77139cb70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "aho-corasick" -version = "1.0.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "ascii" @@ -36,10 +36,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "bech32" -version = "0.9.1" +name = "bech32grs" +version = "0.10.0-beta" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +checksum = "4e190aad8fbe17cf8d2d68ba93844c33a2ef9544e05945da83df0bf81078f044" [[package]] name = "bindgen" @@ -61,6 +61,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bitcoin-test-data" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c188654f9dce3bc6ce1bfa9c49777ad514bcad37e421b5f53e9d0ee10603f34" + [[package]] name = "bitflags" version = "1.3.2" @@ -69,24 +75,24 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bzip2-sys" @@ -111,9 +117,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", "libc", @@ -136,15 +142,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chunked_transfer" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" [[package]] name = "clang-sys" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" dependencies = [ "glob", "libc", @@ -178,54 +184,54 @@ dependencies = [ "void", ] +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -233,9 +239,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -264,15 +270,16 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "electrs" -version = "0.10.0" +version = "0.10.2" dependencies = [ "anyhow", + "bitcoin-test-data", "configure_me", "configure_me_codegen", "crossbeam-channel", @@ -280,6 +287,7 @@ dependencies = [ "electrs-rocksdb", "env_logger", "groestlcoin", + "groestlcoin_slices", "groestlcoincore-rpc", "hex_lit", "log", @@ -296,9 +304,9 @@ dependencies = [ [[package]] name = "electrs-librocksdb-sys" -version = "0.9.0-e2+7.8.3" +version = "0.9.0-e3+7.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f816b3e8df09b69e307ca4ac8b0cdde5abeb7155a01c0dacdf95c06556c09304" +checksum = "b34c24ce2a4d41fe320cea3a9b703b50efea6c2dd4a6d5b0cdbb6ee81636932f" dependencies = [ "bindgen", "bzip2-sys", @@ -311,9 +319,9 @@ dependencies = [ [[package]] name = "electrs-rocksdb" -version = "0.19.0-e2" +version = "0.19.0-e3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1de04efd613a54f60fcd0ac12639b1ed374c2efa9371f3cfdc97ab736db319" +checksum = "1f33e74754aaacc3ff9ace8d3278a7f1be1173104d1960ea18f36386f0feec1e" dependencies = [ "electrs-librocksdb-sys", "libc", @@ -321,9 +329,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", @@ -334,30 +342,19 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fmt2io" @@ -373,9 +370,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -383,9 +380,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -409,41 +406,56 @@ dependencies = [ [[package]] name = "groestlcoin" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96751e4654343e6f1368c5704cd0eb62cea297c23315863de9347cc1c8c5b868" +checksum = "3a617420e955e9758a7b47079ba9ba79a3ecdf87eaf385fccd05b768aeab60e5" dependencies = [ - "bech32", - "groestlcoin-private", + "bech32grs", + "groestlcoin-internals", "groestlcoin_hashes", + "hex-conservative", "hex_lit", "secp256k1", "serde", ] [[package]] -name = "groestlcoin-private" -version = "0.1.0" +name = "groestlcoin-internals" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ff3dd863854fa31ecd3d532241e001e9237b9d2577a54fe19c3479976fe22aa" +checksum = "7ab8f6476dc5e65dab47ac48163668359f423a093a1189204a3b86e672761ded" +dependencies = [ + "serde", +] [[package]] name = "groestlcoin_hashes" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ef56eac1b579cd63485a5cb01c0dfb3a6d399d36a1dcad36626138c7bac888" +checksum = "b0b8709b770874bba4832de0315d6bdb1e9c440ff102dc6b1034c2a643d6b3b1" dependencies = [ "groestl", - "groestlcoin-private", + "hex-conservative", "serde", ] +[[package]] +name = "groestlcoin_slices" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508c9325e9e6bba272b766b8b3ab5d40a72fc0d4e3f8c44ce772ec18f18094a" +dependencies = [ + "groestlcoin", + "groestlcoin_hashes", + "sha2", +] + [[package]] name = "groestlcoincore-rpc" -version = "0.17.0" -source = "git+https://github.com/Groestlcoin/rust-groestlcoincore-rpc?branch=0.17.0#45c9ea6039991e6096533da44560f3515517a192" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4f249291f05167e2be4e6a298e733fc44f1dfa123fc6613094c6fc213cd8596" dependencies = [ - "groestlcoin-private", "groestlcoincore-rpc-json", "jsonrpc", "log", @@ -453,20 +465,20 @@ dependencies = [ [[package]] name = "groestlcoincore-rpc-json" -version = "0.17.0" -source = "git+https://github.com/Groestlcoin/rust-groestlcoincore-rpc?branch=0.17.0#45c9ea6039991e6096533da44560f3515517a192" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a9978ba693184a53872ae9a31fc63e7749c4af63bdb36961e15ed9fb009f6f" dependencies = [ "groestlcoin", - "groestlcoin-private", "serde", "serde_json", ] [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" [[package]] name = "hex" @@ -474,6 +486,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" + [[package]] name = "hex_lit" version = "0.1.1" @@ -482,9 +500,9 @@ checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -505,26 +523,26 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", - "rustix 0.38.8", - "windows-sys 0.48.0", + "libc", + "windows-sys 0.52.0", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" dependencies = [ "libc", ] @@ -554,25 +572,36 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", - "winapi", + "windows-sys 0.48.0", +] + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", ] [[package]] name = "libz-sys" -version = "1.1.12" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" dependencies = [ "cc", "pkg-config", @@ -587,15 +616,15 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -609,18 +638,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.9.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "minimal-lexical" @@ -638,16 +658,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -660,15 +670,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -685,9 +695,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "ppv-lite86" @@ -697,9 +707,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -714,7 +724,7 @@ dependencies = [ "byteorder", "hex", "lazy_static", - "rustix 0.36.15", + "rustix 0.36.17", ] [[package]] @@ -742,9 +752,9 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "quote" -version = "1.0.32" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -781,9 +791,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" dependencies = [ "either", "rayon-core", @@ -791,50 +801,39 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.9.3" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -844,9 +843,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -855,9 +854,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc-hash" @@ -867,9 +866,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.36.15" +version = "0.36.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941" +checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" dependencies = [ "bitflags 1.3.2", "errno", @@ -881,22 +880,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.2", "errno", "libc", - "linux-raw-sys 0.4.5", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.13", + "windows-sys 0.52.0", ] [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "scopeguard" @@ -906,9 +905,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "secp256k1" -version = "0.27.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ "rand", "secp256k1-sys", @@ -917,9 +916,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.8.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" dependencies = [ "cc", ] @@ -941,25 +940,36 @@ checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" @@ -982,9 +992,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "syn" @@ -999,9 +1009,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -1010,44 +1020,43 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.7.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", - "rustix 0.38.8", - "windows-sys 0.48.0", + "rustix 0.38.31", + "windows-sys 0.52.0", ] [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.48", ] [[package]] @@ -1073,21 +1082,21 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "vcpkg" @@ -1131,9 +1140,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1159,7 +1168,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -1179,17 +1197,32 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -1200,9 +1233,15 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" @@ -1212,9 +1251,15 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" @@ -1224,9 +1269,15 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" @@ -1236,9 +1287,15 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" @@ -1248,9 +1305,15 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" @@ -1260,9 +1323,15 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" @@ -1272,17 +1341,22 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +version = "2.0.9+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 7fe70a8c1..b0ef9edc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "electrs" -version = "0.10.0" +version = "0.10.2" authors = ["Groestlcoin Developers "] description = "An efficient re-implementation of Electrum Server in Rust" license = "MIT" @@ -22,8 +22,9 @@ spec = "internal/config_specification.toml" [dependencies] anyhow = "1.0" -groestlcoin = { version = "0.30.0", features = ["serde", "rand-std"] } -groestlcoincore-rpc = { git = "https://github.com/Groestlcoin/rust-groestlcoincore-rpc", branch = "0.17.0" } +groestlcoin = { version = "0.31.0", features = ["serde", "rand-std"] } +groestlcoin_slices = { version = "0.7.0", features = ["groestlcoin", "sha2"] } +groestlcoincore-rpc = "0.18.0" configure_me = "0.4" crossbeam-channel = "0.5" dirs-next = "2.0" @@ -31,7 +32,7 @@ env_logger = "0.10" log = "0.4" parking_lot = "0.12" prometheus = { version = "0.13", optional = true } -rayon = "1.7" +rayon = "1.8" serde = "1.0" serde_derive = "1.0, <=1.0.171" # avoid precompiled binaries (https://github.com/serde-rs/serde/issues/2538) serde_json = "1.0" @@ -39,7 +40,7 @@ signal-hook = "0.3" tiny_http = { version = "0.12", optional = true } [dependencies.electrs-rocksdb] -version = "0.19.0-e2" +version = "0.19.0-e3" default-features = false # ZSTD is used for data compression @@ -50,5 +51,6 @@ features = ["zstd", "snappy"] configure_me_codegen = { version = "0.4.4", default-features = false } [dev-dependencies] +bitcoin-test-data = "0.2.0" hex_lit = "0.1.1" -tempfile = "3.5" +tempfile = "3.9" diff --git a/Dockerfile.ci b/Dockerfile.ci index 9664ad471..145cf7861 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -22,7 +22,7 @@ FROM base as groestlcoin-build # Download WORKDIR /build/groestlcoin ARG ARCH=x86_64 -ARG GROESTLCOIND_VERSION=25.0 +ARG GROESTLCOIND_VERSION=26.0 RUN wget -q https://github.com/Groestlcoin/groestlcoin/releases/download/v$GROESTLCOIND_VERSION/groestlcoin-$GROESTLCOIND_VERSION-x86_64-linux-gnu.tar.gz RUN tar xvf groestlcoin-$GROESTLCOIND_VERSION-x86_64-linux-gnu.tar.gz RUN mv -v groestlcoin-$GROESTLCOIND_VERSION/bin/groestlcoind . diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 000dfdb30..410756e21 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,32 @@ +# 0.10.2 (Dec 31 2023) + +* Use batched RPC to fetch mempool entries & transactions (#979) +* Avoid redundant recomputation of fee histogram bins (#971) +* Explain behavior of config file loading (#964) +* Don't flush when nothing is written to DB (#958) +* Allow setting RocksDB log directory (#959) +* Upgrade `bitcoin` to 0.31.0 (#941) +* Update dependencies (#953, #951, #980) + +# 0.10.1 (Nov 01 2023) + +* Fix build failure on Raspberry Pi 4 (32bit) (#940) +* Return first txid-matching transaction (#933) +* Add txid collision scanner (#928) +* Optimize indexing via `bitcoin_slices` (#927) +* Optimize index querying via `bitcoin_slices` (#913) +* Avoid precompiled 'serde_derive' >=1.0.172 (#924) +* Allow exiting mempool sync on SIGINT (#917) +* Allow skipping merkle proof downloads in `history.py` (#915) +* Remove IndexResult and index into db::WriteBatch (#914) +* Dockerfile: re-add curl for the second time, so it can be used for docker health checks (#912) +* Reuse buffer in p2p handling (#910) +* Preallocate serialized vector of HashPrefixRow (#909) +* Less verbose logging when bitcoind is warming up (#908) +* Drop Cirrus CI due to flakiness (#948) +* Update dependencies (`anyhow`, `bitcoin`, `electrs-rocksdb`, `rayon`, `rustix`, `serde`, `serde_derive`, `serde_json`, `signal-hook`, `syn`, `tempfile`) + + # 0.10.0 (Jul 22 2023) * Add ARM build and integration tests in Cirrus CI diff --git a/doc/config.md b/doc/config.md index edb537997..a4c842d6d 100644 --- a/doc/config.md +++ b/doc/config.md @@ -25,6 +25,8 @@ $ groestlcoind -server=1 -txindex=0 -prune=0 ``` ### Electrs configuration +**Note:** this documentation may occasionally become stale. We recommend running `electrs --help` to get an up-to-date list of options. + Electrs can be configured using command line, environment variables and configuration files (or their combination). It is highly recommended to use configuration files for any non-trivial setups since it's easier to manage. If you're setting password manually instead of cookie files, configuration file is the only way to set it due to security reasons. @@ -36,10 +38,12 @@ can lead to serious problems! Currently the *only* permitted operation is *delet #### Configuration files and priorities The Toml-formatted config files ([an example here](config_example.toml)) are (from lowest priority to highest): `/etc/electrs/config.toml`, `~/.electrs/config.toml`, `./electrs.toml`. +They are loaded if they *exist* and ignored if not however, to aid debugging, any other error when opening them such as permission error will make electrs exit with error. The options in highest-priority config files override options set in lowest-priority config files. +If loading these files is undesirable (common in case of protected systemd services), use the `--skip-default-conf-files` argument to prevent it. -**Environment variables** override options in config files and finally **arguments** override everythig else. +**Environment variables** override options in config files and finally **arguments** override everything else. There are two special arguments `--conf` which reads the specified file and `--conf-dir`, which read all the files in the specified directory. @@ -70,6 +74,14 @@ allowing this server to use groestlcoind JSONRPC interface. Note: there was a `cookie` option in the version 0.8.7 and below, it's now deprecated - do **not** use, it will be removed. Please read upgrade notes if you're upgrading to a newer version. +## Connecting an Electrum client ## + +To connect to your Electrs server, you will need to point Electrum to your server using the `ip_address:port` syntax. You will notice that most default servers in Electrum use the `50002` port (which is for SSL connections), while Electrs serves port `50001` and does not provide SSL out of the box. + +You would need to either use a webserver to provide SSL (see _SSL connection_ below), or connect without SSL. To tell Electrum to connect to your server without SSL, you need to add `:t` after the port (ie: `localhost:50001:t`). Please note that this is not secure and therefore recommended only for local connections. + +Electrs will listen by default on `127.0.0.1:50001`, which means it will only serve clients in the local machine. This is configured via the `electrum_rpc_addr` setting and if you wish to connect from another machine, you need to change it to `0.0.0.0:50001`. This is less secure though, and the recommended way to access Electrs remotely is to keep listening on `127.0.0.1` and tunnel to your server. + ## Extra configuration suggestions ### SSL connection diff --git a/doc/install.md b/doc/install.md index 3cc4fc639..4c9dd5cce 100644 --- a/doc/install.md +++ b/doc/install.md @@ -136,7 +136,7 @@ sudo mount --rbind /sys debootstrap-buster/sys sudo mount --rbind /dev debootstrap-buster/dev ``` -If you have checked out the electrs git reposity somewhere already and don't want to have a duplicate copy inside the debootstrap working directory, just mount bind the exiting directory into the chroot: +If you have checked out the electrs git repository somewhere already and don't want to have a duplicate copy inside the debootstrap working directory, just mount bind the exiting directory into the chroot: ``` sudo mkdir -p debootstrap-buster/mnt/electrs sudo mount --rbind ./electrs debootstrap-buster/mnt/electrs diff --git a/examples/tx_collisions.rs b/examples/tx_collisions.rs new file mode 100644 index 000000000..54aee0064 --- /dev/null +++ b/examples/tx_collisions.rs @@ -0,0 +1,31 @@ +use anyhow::{Context, Result}; +use electrs_rocksdb::{ColumnFamilyDescriptor, IteratorMode, Options, DB}; + +fn main() -> Result<()> { + let path = std::env::args().skip(1).next().context("missing DB path")?; + let cf_names = DB::list_cf(&Options::default(), &path)?; + let cfs: Vec<_> = cf_names + .iter() + .map(|name| ColumnFamilyDescriptor::new(name, Options::default())) + .collect(); + let db = DB::open_cf_descriptors(&Options::default(), &path, cfs)?; + let cf = db.cf_handle("txid").context("missing column family")?; + + let mut state: Option<(u64, u32)> = None; + for row in db.iterator_cf(cf, IteratorMode::Start) { + let (curr, _value) = row?; + let curr_prefix = u64::from_le_bytes(curr[..8].try_into()?); + let curr_height = u32::from_le_bytes(curr[8..].try_into()?); + + if let Some((prev_prefix, prev_height)) = state { + if prev_prefix == curr_prefix { + eprintln!( + "prefix={:x} heights: {} {}", + curr_prefix, prev_height, curr_height + ); + }; + } + state = Some((curr_prefix, curr_height)); + } + Ok(()) +} diff --git a/internal/config_specification.toml b/internal/config_specification.toml index a76999675..957c9773f 100644 --- a/internal/config_specification.toml +++ b/internal/config_specification.toml @@ -29,6 +29,11 @@ type = "std::path::PathBuf" doc = "Directory to store index database (default: ./db/)" default = "\"./db\".into()" +[[param]] +name = "db_log_dir" +type = "std::path::PathBuf" +doc = "Directory to store index database internal log (default: same as specified by `db_dir`)" + [[param]] name = "daemon_dir" type = "std::path::PathBuf" @@ -52,7 +57,7 @@ doc = "JSONRPC authentication cookie file (default: ~/.groestlcoin/.cookie)" [[param]] name = "network" type = "crate::config::GroestlcoinNetwork" -convert_into = "::groestlcoin::network::constants::Network" +convert_into = "::groestlcoin::Network" doc = "Select Groestlcoin network type ('groestlcoin', 'testnet', 'regtest' or 'signet')" default = "Default::default()" diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index af92bdd9f..000000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -1.63.0 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 000000000..8f24f9679 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.65.0" +components = [ "rustfmt" ] diff --git a/src/bin/electrs.rs b/src/bin/electrs.rs index fb7297362..f41c88a70 100644 --- a/src/bin/electrs.rs +++ b/src/bin/electrs.rs @@ -1,5 +1,3 @@ -#![recursion_limit = "256"] - use anyhow::Result; fn main() -> Result<()> { diff --git a/src/cache.rs b/src/cache.rs index 63d36e6b5..af01c3ca0 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -29,7 +29,7 @@ impl Cache { pub fn add_tx(&self, txid: Txid, f: impl FnOnce() -> Transaction) { self.txs.write().entry(txid).or_insert_with(|| { let tx = f(); - self.txs_size.observe("serialized", tx.size() as f64); + self.txs_size.observe("serialized", tx.total_size() as f64); tx }); } diff --git a/src/chain.rs b/src/chain.rs index 39bfac1a9..bd87e4729 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,8 +1,7 @@ use std::collections::HashMap; use groestlcoin::blockdata::block::Header as BlockHeader; -use groestlcoin::network::constants; -use groestlcoin::BlockHash; +use groestlcoin::{BlockHash, Network}; /// A new header found, to be added to the chain at specific height pub(crate) struct NewHeader { @@ -37,7 +36,7 @@ pub struct Chain { impl Chain { // create an empty chain - pub fn new(network: constants::Network) -> Self { + pub fn new(network: Network) -> Self { let genesis = groestlcoin::blockdata::constants::genesis_block(network); let genesis_hash = genesis.block_hash(); Self { @@ -148,7 +147,7 @@ mod tests { use super::{Chain, NewHeader}; use groestlcoin::blockdata::block::Header as BlockHeader; use groestlcoin::consensus::deserialize; - use groestlcoin::network::constants::Network::Regtest; + use groestlcoin::Network::Regtest; use hex_lit::hex; #[test] diff --git a/src/config.rs b/src/config.rs index 9991804f8..9ff25c11a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,6 @@ use dirs_next::home_dir; -use groestlcoin::network::constants::{Magic, Network}; +use groestlcoin::p2p::Magic; +use groestlcoin::Network; use groestlcoincore_rpc::Auth; use std::ffi::{OsStr, OsString}; @@ -129,6 +130,7 @@ pub struct Config { // See below for the documentation of each field: pub network: Network, pub db_path: PathBuf, + pub db_log_dir: Option, pub daemon_dir: PathBuf, pub daemon_auth: SensitiveAuth, pub daemon_rpc_addr: SocketAddr, @@ -184,7 +186,7 @@ fn default_daemon_dir() -> PathBuf { fn default_config_files() -> Vec { let mut files = vec![OsString::from("electrs.toml")]; // cwd if let Some(mut path) = home_dir() { - path.extend(&[".electrs", "config.toml"]); + path.extend([".electrs", "config.toml"]); files.push(OsString::from(path)) // home directory } files.push(OsString::from("/etc/electrs/config.toml")); // system-wide @@ -336,6 +338,7 @@ impl Config { let config = Config { network: config.network, db_path: config.db_dir, + db_log_dir: config.db_log_dir, daemon_dir: config.daemon_dir, daemon_auth, daemon_rpc_addr, diff --git a/src/daemon.rs b/src/daemon.rs index 54b7474d2..693cc1833 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,7 +1,8 @@ use anyhow::{Context, Result}; use crossbeam_channel::Receiver; -use groestlcoin::{Amount, Block, BlockHash, Transaction, Txid}; +use groestlcoin::{consensus::deserialize, hashes::hex::FromHex}; +use groestlcoin::{Amount, BlockHash, Transaction, Txid}; use groestlcoincore_rpc::{json, jsonrpc, Auth, Client, RpcApi}; use parking_lot::Mutex; use serde_json::{json, Value}; @@ -16,6 +17,7 @@ use crate::{ metrics::Metrics, p2p::Connection, signals::ExitFlag, + types::SerBlock, }; enum PollResult { @@ -222,10 +224,59 @@ impl Daemon { .context("failed to get mempool txids") } - pub(crate) fn get_mempool_entry(&self, txid: &Txid) -> Result { - self.rpc - .get_mempool_entry(txid) - .context("failed to get mempool entry") + pub(crate) fn get_mempool_entries( + &self, + txids: &[Txid], + ) -> Result>> { + let client = self.rpc.get_jsonrpc_client(); + debug!("getting {} mempool entries", txids.len()); + let args: Vec<_> = txids + .iter() + .map(|txid| vec![serde_json::value::to_raw_value(txid).unwrap()]) + .collect(); + let reqs: Vec<_> = args + .iter() + .map(|a| client.build_request("getmempoolentry", a)) + .collect(); + let res = client.send_batch(&reqs).context("batch request failed")?; + debug!("got {} mempool entries", res.len()); + Ok(res + .into_iter() + .map(|r| { + r.context("missing response")? + .result::() + .context("invalid response") + }) + .collect()) + } + + pub(crate) fn get_mempool_transactions( + &self, + txids: &[Txid], + ) -> Result>> { + let client = self.rpc.get_jsonrpc_client(); + debug!("getting {} transactions", txids.len()); + let args: Vec<_> = txids + .iter() + .map(|txid| vec![serde_json::value::to_raw_value(txid).unwrap()]) + .collect(); + let reqs: Vec<_> = args + .iter() + .map(|a| client.build_request("getrawtransaction", a)) + .collect(); + let res = client.send_batch(&reqs).context("batch request failed")?; + debug!("got {} mempool transactions", res.len()); + Ok(res + .into_iter() + .map(|r| -> Result { + let tx_hex = r + .context("missing response")? + .result::() + .context("invalid response")?; + let tx_bytes = Vec::from_hex(&tx_hex).context("non-hex transaction")?; + deserialize(&tx_bytes).context("invalid transaction") + }) + .collect()) } pub(crate) fn get_new_headers(&self, chain: &Chain) -> Result> { @@ -235,7 +286,7 @@ impl Daemon { pub(crate) fn for_blocks(&self, blockhashes: B, func: F) -> Result<()> where B: IntoIterator, - F: FnMut(BlockHash, Block), + F: FnMut(BlockHash, SerBlock), { self.p2p.lock().for_blocks(blockhashes, func) } diff --git a/src/db.rs b/src/db.rs index dbb0381dd..8d6c7583e 100644 --- a/src/db.rs +++ b/src/db.rs @@ -121,10 +121,13 @@ impl DBStore { .collect() } - fn open_internal(path: &Path) -> Result { + fn open_internal(path: &Path, log_dir: Option<&Path>) -> Result { let mut db_opts = default_opts(); db_opts.create_if_missing(true); db_opts.create_missing_column_families(true); + if let Some(d) = log_dir { + db_opts.set_db_log_dir(d); + } let db = rocksdb::DB::open_cf_descriptors(&db_opts, path, Self::create_cf_descriptors()) .with_context(|| format!("failed to open DB: {}", path.display()))?; @@ -152,8 +155,8 @@ impl DBStore { } /// Opens a new RocksDB at the specified location. - pub fn open(path: &Path, auto_reindex: bool) -> Result { - let mut store = Self::open_internal(path)?; + pub fn open(path: &Path, log_dir: Option<&Path>, auto_reindex: bool) -> Result { + let mut store = Self::open_internal(path, log_dir)?; let config = store.get_config(); debug!("DB {:?}", config); let mut config = config.unwrap_or_default(); // use default config when DB is empty @@ -185,7 +188,7 @@ impl DBStore { path.display() ) })?; - store = Self::open_internal(path)?; + store = Self::open_internal(path, log_dir)?; config = Config::default(); // re-init config after dropping DB } if config.compacted { @@ -280,6 +283,7 @@ impl DBStore { } pub(crate) fn flush(&self) { + debug!("flushing DB column families"); let mut config = self.get_config().unwrap_or_default(); for name in COLUMN_FAMILIES { let cf = self.db.cf_handle(name).expect("missing CF"); @@ -359,18 +363,23 @@ impl Drop for DBStore { #[cfg(test)] mod tests { use super::{rocksdb, DBStore, WriteBatch, CURRENT_FORMAT}; + use std::ffi::{OsStr, OsString}; + use std::path::Path; #[test] fn test_reindex_new_format() { let dir = tempfile::tempdir().unwrap(); { - let store = DBStore::open(dir.path(), false).unwrap(); + let store = DBStore::open(dir.path(), None, false).unwrap(); let mut config = store.get_config().unwrap(); config.format += 1; store.set_config(config); }; assert_eq!( - DBStore::open(dir.path(), false).err().unwrap().to_string(), + DBStore::open(dir.path(), None, false) + .err() + .unwrap() + .to_string(), format!( "re-index required due to unsupported format {} != {}", CURRENT_FORMAT + 1, @@ -378,7 +387,7 @@ mod tests { ) ); { - let store = DBStore::open(dir.path(), true).unwrap(); + let store = DBStore::open(dir.path(), None, true).unwrap(); store.flush(); let config = store.get_config().unwrap(); assert_eq!(config.format, CURRENT_FORMAT); @@ -396,11 +405,14 @@ mod tests { db.put(b"F", b"").unwrap(); // insert legacy DB compaction marker (in 'default' column family) }; assert_eq!( - DBStore::open(dir.path(), false).err().unwrap().to_string(), + DBStore::open(dir.path(), None, false) + .err() + .unwrap() + .to_string(), format!("re-index required due to legacy format",) ); { - let store = DBStore::open(dir.path(), true).unwrap(); + let store = DBStore::open(dir.path(), None, true).unwrap(); store.flush(); let config = store.get_config().unwrap(); assert_eq!(config.format, CURRENT_FORMAT); @@ -410,7 +422,7 @@ mod tests { #[test] fn test_db_prefix_scan() { let dir = tempfile::tempdir().unwrap(); - let store = DBStore::open(dir.path(), true).unwrap(); + let store = DBStore::open(dir.path(), None, true).unwrap(); let items: &[&[u8]] = &[ b"ab", @@ -438,4 +450,34 @@ mod tests { .map(|v| v.to_vec().into_boxed_slice()) .collect() } + + #[test] + fn test_db_log_in_same_dir() { + let dir1 = tempfile::tempdir().unwrap(); + let _store = DBStore::open(dir1.path(), None, true).unwrap(); + + // LOG file is created in dir1 + let dir_files = list_log_files(dir1.path()); + assert_eq!(dir_files, vec![OsStr::new("LOG")]); + + let dir2 = tempfile::tempdir().unwrap(); + let dir3 = tempfile::tempdir().unwrap(); + let _store = DBStore::open(dir2.path(), Some(dir3.path()), true).unwrap(); + + // *_LOG file is not created in dir2, but in dir3 + let dir_files = list_log_files(dir2.path()); + assert_eq!(dir_files, Vec::::new()); + + let dir_files = list_log_files(dir3.path()); + assert_eq!(dir_files.len(), 1); + assert!(dir_files[0].to_str().unwrap().ends_with("_LOG")); + } + + fn list_log_files(path: &Path) -> Vec { + path.read_dir() + .unwrap() + .map(|e| e.unwrap().file_name()) + .filter(|e| e.to_str().unwrap().contains("LOG")) + .collect() + } } diff --git a/src/electrum.rs b/src/electrum.rs index 77867f9c7..2e377020c 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -373,7 +373,7 @@ impl Rpc { .map(|(blockhash, _tx)| blockhash); return self.daemon.get_transaction_info(&txid, blockhash); } - if let Some(tx) = self.cache.get_tx(&txid, |tx| serialize_hex(tx)) { + if let Some(tx) = self.cache.get_tx(&txid, serialize_hex) { return Ok(json!(tx)); } debug!("tx cache miss: txid={}", txid); diff --git a/src/index.rs b/src/index.rs index 64a601fb9..bdb7d7c17 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,6 +1,8 @@ use anyhow::{Context, Result}; -use groestlcoin::consensus::{deserialize, serialize}; -use groestlcoin::{Block, BlockHash, OutPoint, Txid}; +use groestlcoin::consensus::{deserialize, serialize, Decodable}; +use groestlcoin::{BlockHash, OutPoint, Txid}; +use groestlcoin_slices::{bsl, Visit, Visitor}; +use std::ops::ControlFlow; use crate::{ chain::{Chain, NewHeader}, @@ -8,7 +10,10 @@ use crate::{ db::{DBStore, Row, WriteBatch}, metrics::{self, Gauge, Histogram, Metrics}, signals::ExitFlag, - types::{HashPrefixRow, HeaderRow, ScriptHash, ScriptHashRow, SpendingPrefixRow, TxidRow}, + types::{ + bsl_txid, HashPrefixRow, HeaderRow, ScriptHash, ScriptHashRow, SerBlock, SpendingPrefixRow, + TxidRow, + }, }; #[derive(Clone)] @@ -81,6 +86,7 @@ pub struct Index { chain: Chain, stats: Stats, is_ready: bool, + flush_needed: bool, } impl Index { @@ -112,6 +118,7 @@ impl Index { chain, stats, is_ready: false, + flush_needed: false, }) } @@ -174,7 +181,10 @@ impl Index { ); } _ => { - self.store.flush(); // full compaction is performed on the first flush call + if self.flush_needed { + self.store.flush(); // full compaction is performed on the first flush call + self.flush_needed = false; + } self.is_ready = true; return Ok(true); // no more blocks to index (done for now) } @@ -190,6 +200,7 @@ impl Index { } self.chain.update(new_headers); self.stats.observe_chain(&self.chain); + self.flush_needed = true; Ok(false) // sync is not done } @@ -229,33 +240,57 @@ fn db_rows_size(rows: &[Row]) -> usize { rows.iter().map(|key| key.len()).sum() } -fn index_single_block(block_hash: BlockHash, block: Block, height: usize, batch: &mut WriteBatch) { - for tx in &block.txdata { - batch - .txid_rows - .push(TxidRow::row(tx.txid(), height).to_db_row()); +fn index_single_block( + block_hash: BlockHash, + block: SerBlock, + height: usize, + batch: &mut WriteBatch, +) { + struct IndexBlockVisitor<'a> { + batch: &'a mut WriteBatch, + height: usize, + } - batch.funding_rows.extend( - tx.output - .iter() - .filter(|txo| !txo.script_pubkey.is_provably_unspendable()) - .map(|txo| { - let scripthash = ScriptHash::new(&txo.script_pubkey); - ScriptHashRow::row(scripthash, height).to_db_row() - }), - ); + impl<'a> Visitor for IndexBlockVisitor<'a> { + fn visit_transaction(&mut self, tx: &bsl::Transaction) -> ControlFlow<()> { + let txid = bsl_txid(tx); + self.batch + .txid_rows + .push(TxidRow::row(txid, self.height).to_db_row()); + ControlFlow::Continue(()) + } - if tx.is_coin_base() { - continue; // coinbase doesn't have inputs + fn visit_tx_out(&mut self, _vout: usize, tx_out: &bsl::TxOut) -> ControlFlow<()> { + let script = groestlcoin::Script::from_bytes(tx_out.script_pubkey()); + // skip indexing unspendable outputs + if !script.is_provably_unspendable() { + let row = ScriptHashRow::row(ScriptHash::new(script), self.height); + self.batch.funding_rows.push(row.to_db_row()); + } + ControlFlow::Continue(()) + } + + fn visit_tx_in(&mut self, _vin: usize, tx_in: &bsl::TxIn) -> ControlFlow<()> { + let prevout: OutPoint = tx_in.prevout().into(); + // skip indexing coinbase transactions' input + if !prevout.is_null() { + let row = SpendingPrefixRow::row(prevout, self.height); + self.batch.spending_rows.push(row.to_db_row()); + } + ControlFlow::Continue(()) + } + + fn visit_block_header(&mut self, header: &bsl::BlockHeader) -> ControlFlow<()> { + let header = groestlcoin::block::Header::consensus_decode(&mut header.as_ref()) + .expect("block header was already validated"); + self.batch + .header_rows + .push(HeaderRow::new(header).to_db_row()); + ControlFlow::Continue(()) } - batch.spending_rows.extend( - tx.input - .iter() - .map(|txin| SpendingPrefixRow::row(txin.previous_output, height).to_db_row()), - ); } - batch - .header_rows - .push(HeaderRow::new(block.header).to_db_row()); + + let mut index_block = IndexBlockVisitor { batch, height }; + bsl::Block::visit(&block, &mut index_block).expect("core returned invalid block"); batch.tip_row = serialize(&block_hash).into_boxed_slice(); } diff --git a/src/mempool.rs b/src/mempool.rs index b9481b903..b3b244e6e 100644 --- a/src/mempool.rs +++ b/src/mempool.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use std::collections::{BTreeSet, HashMap, HashSet}; use std::convert::TryFrom; @@ -7,8 +7,6 @@ use std::ops::Bound; use groestlcoin::hashes::Hash; use groestlcoin::{Amount, OutPoint, Transaction, Txid}; -use groestlcoincore_rpc::json; -use rayon::prelude::*; use serde::ser::{Serialize, SerializeSeq, Serializer}; use crate::{ @@ -37,6 +35,76 @@ pub(crate) struct Mempool { count: Gauge, } +/// An update to [`Mempool`]'s internal state. This can be fetched +/// asynchronously using [`MempoolSyncUpdate::poll`], and applied +/// using [`Mempool::apply_sync_update`]. +pub(crate) struct MempoolSyncUpdate { + new_entries: Vec, + removed_entries: HashSet, +} + +impl MempoolSyncUpdate { + /// Poll the bitcoin node and compute a [`MempoolSyncUpdate`] based on the given set of + /// `old_txids` which are already cached. + pub fn poll( + daemon: &Daemon, + old_txids: HashSet, + exit_flag: &ExitFlag, + ) -> Result { + let txids = daemon.get_mempool_txids()?; + debug!("loading {} mempool transactions", txids.len()); + + let new_txids = HashSet::::from_iter(txids); + + let to_add = &new_txids - &old_txids; + let to_remove = &old_txids - &new_txids; + + let to_add: Vec = to_add.into_iter().collect(); + let mut new_entries = Vec::with_capacity(to_add.len()); + + for txids_chunk in to_add.chunks(1000) { + exit_flag.poll().context("mempool update interrupted")?; + let entries = daemon.get_mempool_entries(txids_chunk)?; + ensure!( + txids_chunk.len() == entries.len(), + "got {} mempools entries, expected {}", + entries.len(), + txids_chunk.len() + ); + let txs = daemon.get_mempool_transactions(txids_chunk)?; + ensure!( + txids_chunk.len() == txs.len(), + "got {} mempools transactions, expected {}", + txs.len(), + txids_chunk.len() + ); + let chunk_entries: Vec = txids_chunk + .iter() + .zip(entries.into_iter().zip(txs.into_iter())) + .filter_map(|(txid, (entry, tx))| { + let tx = tx.ok()?; + let entry = entry.ok()?; + Some(Entry { + txid: *txid, + tx, + vsize: entry.vsize, + fee: entry.fees.base, + has_unconfirmed_inputs: !entry.depends.is_empty(), + }) + }) + .collect(); + + new_entries.extend(chunk_entries); + } + + let update = MempoolSyncUpdate { + new_entries, + removed_entries: to_remove, + }; + Ok(update) + } +} + // Smallest possible txid fn txid_min() -> Txid { Txid::all_zeros() @@ -53,7 +121,7 @@ impl Mempool { entries: Default::default(), by_funding: Default::default(), by_spending: Default::default(), - fees: FeeHistogram::empty(), + fees: FeeHistogram::default(), vsize: metrics.gauge( "mempool_txs_vsize", "Total vsize of mempool transactions (in bytes)", @@ -97,57 +165,21 @@ impl Mempool { .collect() } - pub fn sync(&mut self, daemon: &Daemon, exit_flag: &ExitFlag) { - let txids = match daemon.get_mempool_txids() { - Ok(txids) => txids, - Err(e) => { - warn!("mempool sync failed: {}", e); - return; - } - }; - debug!("loading {} mempool transactions", txids.len()); + /// Apply a [`MempoolSyncUpdate`] to the mempool state. + pub fn apply_sync_update(&mut self, update: MempoolSyncUpdate) { + let removed = update.removed_entries.len(); + let added = update.new_entries.len(); - let new_txids = HashSet::::from_iter(txids); - let old_txids = HashSet::::from_iter(self.entries.keys().copied()); - - let to_add = &new_txids - &old_txids; - let to_remove = &old_txids - &new_txids; - - let removed = to_remove.len(); - for txid in to_remove { - self.remove_entry(txid); - } - let to_add: Vec = to_add.into_iter().collect(); - let mut added = 0; - for chunk in to_add.chunks(100) { - if exit_flag.poll().is_err() { - warn!("interrupted while syncing mempool"); - return; - } - let entries: Vec<_> = chunk - .par_iter() - .filter_map(|txid| { - let tx = daemon.get_transaction(txid, None); - let entry = daemon.get_mempool_entry(txid); - match (tx, entry) { - (Ok(tx), Ok(entry)) => Some((txid, tx, entry)), - _ => None, // skip missing mempool entries - } - }) - .collect(); - added += entries.len(); - for (txid, tx, entry) in entries { - self.add_entry(*txid, tx, entry); - } + for txid_to_remove in update.removed_entries { + self.remove_entry(txid_to_remove); } - self.fees = FeeHistogram::new(self.entries.values().map(|e| (e.fee, e.vsize))); - for i in 0..FeeHistogram::BINS { - let bin_index = FeeHistogram::BINS - i - 1; // from 63 to 0 - let limit = 1u128 << i; - let label = format!("[{:20.0}, {:20.0})", limit / 2, limit); - self.vsize.set(&label, self.fees.vsize[bin_index] as f64); - self.count.set(&label, self.fees.count[bin_index] as f64); + + for entry in update.new_entries { + self.add_entry(entry); } + + self.update_metrics(); + debug!( "{} mempool txs: {} added, {} removed", self.entries.len(), @@ -156,27 +188,51 @@ impl Mempool { ); } - fn add_entry(&mut self, txid: Txid, tx: Transaction, entry: json::GetMempoolEntryResult) { - for txi in &tx.input { - self.by_spending.insert((txi.previous_output, txid)); + fn update_metrics(&mut self) { + for i in 0..FeeHistogram::BINS { + let bin_index = FeeHistogram::BINS - i - 1; // from 63 to 0 + let (lower, upper) = FeeHistogram::bin_range(bin_index); + let label = format!("[{:20.0}, {:20.0})", lower, upper); + self.vsize.set(&label, self.fees.vsize[bin_index] as f64); + self.count.set(&label, self.fees.count[bin_index] as f64); + } + } + + pub fn sync(&mut self, daemon: &Daemon, exit_flag: &ExitFlag) { + let old_txids = HashSet::::from_iter(self.entries.keys().copied()); + + let poll_result = MempoolSyncUpdate::poll(daemon, old_txids, exit_flag); + + let sync_update = match poll_result { + Ok(sync_update) => sync_update, + Err(e) => { + warn!("mempool sync failed: {}", e); + return; + } + }; + + self.apply_sync_update(sync_update); + } + + /// Add a transaction entry to the mempool and update the fee histogram. + fn add_entry(&mut self, entry: Entry) { + for txi in &entry.tx.input { + self.by_spending.insert((txi.previous_output, entry.txid)); } - for txo in &tx.output { + for txo in &entry.tx.output { let scripthash = ScriptHash::new(&txo.script_pubkey); - self.by_funding.insert((scripthash, txid)); // may have duplicates + self.by_funding.insert((scripthash, entry.txid)); // may have duplicates } - let entry = Entry { - txid, - tx, - vsize: entry.vsize, - fee: entry.fees.base, - has_unconfirmed_inputs: !entry.depends.is_empty(), - }; + + self.modify_fee_histogram(entry.fee, entry.vsize as i64); + assert!( - self.entries.insert(txid, entry).is_none(), + self.entries.insert(entry.txid, entry).is_none(), "duplicate mempool txid" ); } + /// Remove a transaction entry from the mempool and update the fee histogram. fn remove_entry(&mut self, txid: Txid) { let entry = self.entries.remove(&txid).expect("missing tx from mempool"); for txi in entry.tx.input { @@ -186,6 +242,22 @@ impl Mempool { let scripthash = ScriptHash::new(&txo.script_pubkey); self.by_funding.remove(&(scripthash, txid)); // may have misses } + + self.modify_fee_histogram(entry.fee, -(entry.vsize as i64)); + } + + /// Apply a change to the fee histogram. Used when transactions are added or + /// removed from the mempool. If `vsize_change` is positive, we increase + /// the histogram vsize and TX count in the appropriate bin. If negative, + /// we decrease them. + fn modify_fee_histogram(&mut self, fee: Amount, vsize_change: i64) { + let vsize = vsize_change.unsigned_abs(); + let bin_index = FeeHistogram::bin_index(fee, vsize); + if vsize_change >= 0 { + self.fees.insert(bin_index, vsize); + } else { + self.fees.remove(bin_index, vsize); + } } } @@ -215,24 +287,40 @@ impl Default for FeeHistogram { impl FeeHistogram { const BINS: usize = 65; // 0..=64 - fn empty() -> Self { - Self::new(std::iter::empty()) + fn bin_index(fee: Amount, vsize: u64) -> usize { + let fee_rate = fee.to_sat() / vsize; + usize::try_from(fee_rate.leading_zeros()).unwrap() } - fn new(items: impl Iterator) -> Self { - let mut result = FeeHistogram::default(); - for (fee, vsize) in items { - let fee_rate = fee.to_sat() / vsize; - let index = usize::try_from(fee_rate.leading_zeros()).unwrap(); - // skip transactions with too low fee rate (<1 sat/vB) - if let Some(bin) = result.vsize.get_mut(index) { - *bin += vsize - } - if let Some(bin) = result.count.get_mut(index) { - *bin += 1 - } + fn bin_range(bin_index: usize) -> (u128, u128) { + let limit = 1u128 << (FeeHistogram::BINS - bin_index - 1); + (limit / 2, limit) + } + + fn insert(&mut self, bin_index: usize, vsize: u64) { + // skip transactions with too low fee rate (<1 sat/vB) + if let Some(bin) = self.vsize.get_mut(bin_index) { + *bin += vsize + } + if let Some(bin) = self.count.get_mut(bin_index) { + *bin += 1 + } + } + + fn remove(&mut self, bin_index: usize, vsize: u64) { + // skip transactions with too low fee rate (<1 sat/vB) + if let Some(bin) = self.vsize.get_mut(bin_index) { + *bin = bin.checked_sub(vsize).unwrap_or_else(|| { + warn!("removing TX from mempool caused bin count to unexpectedly drop below zero"); + 0 + }); + } + if let Some(bin) = self.count.get_mut(bin_index) { + *bin = bin.checked_sub(1).unwrap_or_else(|| { + warn!("removing TX from mempool caused bin vsize to unexpectedly drop below zero"); + 0 + }); } - result } } @@ -272,10 +360,50 @@ mod tests { (Amount::from_sat(80), 10), (Amount::from_sat(1), 100), ]; - let hist = FeeHistogram::new(items.into_iter()); + let mut hist = FeeHistogram::default(); + for (amount, vsize) in items { + let bin_index = FeeHistogram::bin_index(amount, vsize); + hist.insert(bin_index, vsize); + } assert_eq!( json!(hist), json!([[15, 10], [7, 40], [3, 20], [1, 10], [0, 100]]) ); + + { + let bin_index = FeeHistogram::bin_index(Amount::from_sat(5), 1); // 5 sat/byte + hist.remove(bin_index, 11); + assert_eq!( + json!(hist), + json!([[15, 10], [7, 29], [3, 20], [1, 10], [0, 100]]) + ); + } + + { + let bin_index = FeeHistogram::bin_index(Amount::from_sat(13), 1); // 13 sat/byte + hist.insert(bin_index, 80); + assert_eq!( + json!(hist), + json!([[15, 90], [7, 29], [3, 20], [1, 10], [0, 100]]) + ); + } + + { + let bin_index = FeeHistogram::bin_index(Amount::from_sat(99), 1); // 99 sat/byte + hist.insert(bin_index, 15); + assert_eq!( + json!(hist), + json!([ + [127, 15], + [63, 0], + [31, 0], + [15, 90], + [7, 29], + [3, 20], + [1, 10], + [0, 100] + ]) + ); + } } } diff --git a/src/p2p.rs b/src/p2p.rs index 9021cbc92..b3423660c 100644 --- a/src/p2p.rs +++ b/src/p2p.rs @@ -8,22 +8,23 @@ use groestlcoin::{ Decodable, }, hashes::Hash, - network::{ - address, - constants::{self, Magic}, + p2p::{ + self, address, message::{self, CommandString, NetworkMessage}, message_blockdata::{GetHeadersMessage, Inventory}, - message_network, + message_network, Magic, }, secp256k1::{self, rand::Rng}, Block, BlockHash, Network, }; +use groestlcoin_slices::{bsl, Parse}; use std::io::{self, ErrorKind, Write}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; use std::sync::Arc; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use crate::types::SerBlock; use crate::{ chain::{Chain, NewHeader}, config::ELECTRS_VERSION, @@ -55,7 +56,7 @@ impl Request { pub(crate) struct Connection { req_send: Sender, - blocks_recv: Receiver, + blocks_recv: Receiver, headers_recv: Receiver>, new_block_recv: Receiver<()>, @@ -95,7 +96,7 @@ impl Connection { pub(crate) fn for_blocks(&mut self, blockhashes: B, mut func: F) -> Result<()> where B: IntoIterator, - F: FnMut(BlockHash, Block), + F: FnMut(BlockHash, SerBlock), { self.blocks_duration.observe_duration("total", || { let blockhashes: Vec = blockhashes.into_iter().collect(); @@ -113,7 +114,13 @@ impl Connection { .blocks_recv .recv() .with_context(|| format!("failed to get block {}", hash))?; - ensure!(block.block_hash() == hash, "got unexpected block"); + let header = bsl::BlockHeader::parse(&block[..]) + .expect("core returned invalid blockheader") + .parsed_owned(); + ensure!( + &header.block_hash()[..] == hash.as_byte_array(), // must use groestl, not sha2() + "got unexpected block" + ); Ok(block) })?; self.blocks_duration @@ -191,10 +198,7 @@ impl Connection { }; send_duration.observe_duration("send", || { trace!("send: {:?}", msg); - let raw_msg = message::RawNetworkMessage { - magic, - payload: msg, - }; + let raw_msg = message::RawNetworkMessage::new(magic, msg); buffer.clear(); raw_msg .consensus_encode(&mut buffer) @@ -239,7 +243,7 @@ impl Connection { }); let (req_send, req_recv) = bounded::(1); - let (blocks_send, blocks_recv) = bounded::(10); + let (blocks_send, blocks_recv) = bounded::(10); let (headers_send, headers_recv) = bounded::>(1); let (new_block_send, new_block_recv) = bounded::<()>(0); let (init_send, init_recv) = bounded::<()>(0); @@ -260,36 +264,31 @@ impl Connection { let label = format!("parse_{}", raw_msg.cmd.as_ref()); let msg = match parse_duration.observe_duration(&label, || raw_msg.parse()) { Ok(msg) => msg, - Err(err) => bail!("failed to parse '{}({:?})': {}", raw_msg.cmd, raw_msg.raw, err), + Err(err) => bail!("failed to parse {err}"), }; trace!("recv: {:?}", msg); match msg { - NetworkMessage::GetHeaders(_) => { - tx_send.send(NetworkMessage::Headers(vec![]))?; - } - NetworkMessage::Version(version) => { + ParsedNetworkMessage::Version(version) => { debug!("peer version: {:?}", version); tx_send.send(NetworkMessage::Verack)?; } - NetworkMessage::Inv(inventory) => { + ParsedNetworkMessage::Inv(inventory) => { debug!("peer inventory: {:?}", inventory); if inventory.iter().any(|inv| matches!(inv, Inventory::Block(_))) { let _ = new_block_send.try_send(()); // best-effort notification } }, - NetworkMessage::Ping(nonce) => { + ParsedNetworkMessage::Ping(nonce) => { tx_send.send(NetworkMessage::Pong(nonce))?; // connection keep-alive } - NetworkMessage::Verack => { + ParsedNetworkMessage::Verack => { init_send.send(())?; // peer acknowledged our version } - NetworkMessage::Block(block) => blocks_send.send(block)?, - NetworkMessage::Headers(headers) => headers_send.send(headers)?, - NetworkMessage::Alert(_) => (), // https://bitcoin.org/en/alert/2016-11-01-alert-retirement - NetworkMessage::Addr(_) => (), // unused - msg => warn!("unexpected message: {:?}", msg), + ParsedNetworkMessage::Block(block) => blocks_send.send(block)?, + ParsedNetworkMessage::Headers(headers) => headers_send.send(headers)?, + ParsedNetworkMessage::Ignored => (), } } recv(req_recv) -> result => { @@ -328,10 +327,10 @@ fn build_version_message() -> NetworkMessage { .expect("Time error") .as_secs() as i64; - let services = constants::ServiceFlags::NONE; + let services = p2p::ServiceFlags::NONE; NetworkMessage::Version(message_network::VersionMessage { - version: constants::PROTOCOL_VERSION, + version: p2p::PROTOCOL_VERSION, services, timestamp, receiver: address::Address::new(&addr, services), @@ -350,27 +349,25 @@ struct RawNetworkMessage { } impl RawNetworkMessage { - fn parse(&self) -> Result { + fn parse(self) -> Result { let mut raw: &[u8] = &self.raw; let payload = match self.cmd.as_ref() { - "version" => NetworkMessage::Version(Decodable::consensus_decode(&mut raw)?), - "verack" => NetworkMessage::Verack, - "inv" => NetworkMessage::Inv(Decodable::consensus_decode(&mut raw)?), - "notfound" => NetworkMessage::NotFound(Decodable::consensus_decode(&mut raw)?), - "block" => NetworkMessage::Block(Decodable::consensus_decode(&mut raw)?), + "version" => ParsedNetworkMessage::Version(Decodable::consensus_decode(&mut raw)?), + "verack" => ParsedNetworkMessage::Verack, + "inv" => ParsedNetworkMessage::Inv(Decodable::consensus_decode(&mut raw)?), + "block" => ParsedNetworkMessage::Block(self.raw), "headers" => { let len = VarInt::consensus_decode(&mut raw)?.0; let mut headers = Vec::with_capacity(len as usize); for _ in 0..len { headers.push(Block::consensus_decode(&mut raw)?.header); } - NetworkMessage::Headers(headers) + ParsedNetworkMessage::Headers(headers) } - "ping" => NetworkMessage::Ping(Decodable::consensus_decode(&mut raw)?), - "pong" => NetworkMessage::Pong(Decodable::consensus_decode(&mut raw)?), - "reject" => NetworkMessage::Reject(Decodable::consensus_decode(&mut raw)?), - "alert" => NetworkMessage::Alert(Decodable::consensus_decode(&mut raw)?), - "addr" => NetworkMessage::Addr(Decodable::consensus_decode(&mut raw)?), + "ping" => ParsedNetworkMessage::Ping(Decodable::consensus_decode(&mut raw)?), + "pong" => ParsedNetworkMessage::Ignored, // unused + "addr" => ParsedNetworkMessage::Ignored, // unused + "alert" => ParsedNetworkMessage::Ignored, // https://bitcoin.org/en/alert/2016-11-01-alert-retirement _ => bail!( "unsupported message: command={}, payload={:?}", self.cmd, @@ -381,6 +378,17 @@ impl RawNetworkMessage { } } +#[derive(Debug)] +enum ParsedNetworkMessage { + Version(message_network::VersionMessage), + Verack, + Inv(Vec), + Ping(u64), + Headers(Vec), + Block(SerBlock), + Ignored, +} + impl Decodable for RawNetworkMessage { fn consensus_decode(d: &mut D) -> Result { let magic = Decodable::consensus_decode(d)?; diff --git a/src/status.rs b/src/status.rs index 2f3a7d4c6..159e10921 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,13 +1,18 @@ use anyhow::Result; use groestlcoin::{ + consensus::Decodable, hashes::{sha256, Hash, HashEngine}, - Amount, Block, BlockHash, OutPoint, SignedAmount, Transaction, Txid, + Amount, BlockHash, OutPoint, SignedAmount, Transaction, Txid, }; +use groestlcoin_slices::{bsl, Visit, Visitor}; use rayon::prelude::*; use serde::ser::{Serialize, Serializer}; -use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryFrom; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + ops::ControlFlow, +}; use crate::{ cache::Cache, @@ -15,7 +20,7 @@ use crate::{ daemon::Daemon, index::Index, mempool::Mempool, - types::{ScriptHash, StatusHash}, + types::{bsl_txid, ScriptHash, SerBlock, StatusHash}, }; /// Given a scripthash, store relevant inputs and outputs of a specific transaction @@ -306,7 +311,7 @@ impl ScriptHashStatus { fn for_new_blocks(&self, blockhashes: B, daemon: &Daemon, func: F) -> Result<()> where B: IntoIterator, - F: FnMut(BlockHash, Block), + F: FnMut(BlockHash, SerBlock), { daemon.for_blocks( blockhashes @@ -331,21 +336,17 @@ impl ScriptHashStatus { let funding_blockhashes = index.limit_result(index.filter_by_funding(scripthash))?; self.for_new_blocks(funding_blockhashes, daemon, |blockhash, block| { let block_entries = result.entry(blockhash).or_default(); - filter_block_txs(block, |tx| filter_outputs(tx, scripthash)).for_each( - |FilteredTx { - pos, - tx, - txid, - result: funding_outputs, - }| { - cache.add_tx(txid, move || tx); - outpoints.extend(make_outpoints(txid, &funding_outputs)); - block_entries - .entry(pos) - .or_insert_with(|| TxEntry::new(txid)) - .outputs = funding_outputs; - }, - ); + for filtered_outputs in filter_block_txs_outputs(block, scripthash) { + cache.add_tx(filtered_outputs.txid, move || filtered_outputs.tx); + outpoints.extend(make_outpoints( + filtered_outputs.txid, + &filtered_outputs.result, + )); + block_entries + .entry(filtered_outputs.pos) + .or_insert_with(|| TxEntry::new(filtered_outputs.txid)) + .outputs = filtered_outputs.result; + } })?; let spending_blockhashes: HashSet = outpoints .par_iter() @@ -353,20 +354,13 @@ impl ScriptHashStatus { .collect(); self.for_new_blocks(spending_blockhashes, daemon, |blockhash, block| { let block_entries = result.entry(blockhash).or_default(); - filter_block_txs(block, |tx| filter_inputs(tx, outpoints)).for_each( - |FilteredTx { - pos, - tx, - txid, - result: spent_outpoints, - }| { - cache.add_tx(txid, move || tx); - block_entries - .entry(pos) - .or_insert_with(|| TxEntry::new(txid)) - .spent = spent_outpoints; - }, - ); + for filtered_inputs in filter_block_txs_inputs(&block, outpoints) { + cache.add_tx(filtered_inputs.txid, move || filtered_inputs.tx); + block_entries + .entry(filtered_inputs.pos) + .or_insert_with(|| TxEntry::new(filtered_inputs.txid)) + .spent = filtered_inputs.result; + } })?; Ok(result @@ -473,7 +467,7 @@ fn filter_outputs(tx: &Transaction, scripthash: ScriptHash) -> Vec { if ScriptHash::new(&txo.script_pubkey) == scripthash { Some(TxOutput { index: vout, - value: Amount::from_sat(txo.value), + value: txo.value, }) } else { None @@ -513,35 +507,111 @@ struct FilteredTx { result: Vec, } -fn filter_block_txs( - block: Block, - map_fn: impl Fn(&Transaction) -> Vec + Sync, -) -> impl Iterator> { - block - .txdata - .into_par_iter() - .enumerate() - .filter_map(|(pos, tx)| { - let result = map_fn(&tx); - if result.is_empty() { - return None; // skip irrelevant transaction +fn filter_block_txs_outputs(block: SerBlock, scripthash: ScriptHash) -> Vec> { + struct FindOutputs { + scripthash: ScriptHash, + result: Vec>, + buffer: Vec, + pos: usize, + } + impl Visitor for FindOutputs { + fn visit_transaction(&mut self, tx: &bsl::Transaction) -> ControlFlow<()> { + if !self.buffer.is_empty() { + let result = std::mem::take(&mut self.buffer); + let txid = bsl_txid(tx); + let tx = groestlcoin::Transaction::consensus_decode(&mut tx.as_ref()) + .expect("transaction was already validated"); + self.result.push(FilteredTx:: { + tx, + txid, + pos: self.pos, + result, + }); } - let txid = tx.txid(); - Some(FilteredTx { - tx, - txid, - pos, - result, - }) - }) - .collect::>() - .into_iter() + self.pos += 1; + ControlFlow::Continue(()) + } + fn visit_tx_out(&mut self, vout: usize, tx_out: &bsl::TxOut) -> ControlFlow<()> { + let current = ScriptHash::hash(tx_out.script_pubkey()); + if current == self.scripthash { + self.buffer.push(TxOutput { + index: vout as u32, + value: Amount::from_sat(tx_out.value()), + }) + } + ControlFlow::Continue(()) + } + } + let mut find_outputs = FindOutputs { + scripthash, + result: vec![], + buffer: vec![], + pos: 0, + }; + + bsl::Block::visit(&block, &mut find_outputs).expect("core returned invalid block"); + + find_outputs.result +} + +fn filter_block_txs_inputs( + block: &SerBlock, + outpoints: &HashSet, +) -> Vec> { + struct FindInputs<'a> { + outpoints: &'a HashSet, + result: Vec>, + buffer: Vec, + pos: usize, + } + + impl<'a> Visitor for FindInputs<'a> { + fn visit_transaction(&mut self, tx: &bsl::Transaction) -> ControlFlow<()> { + if !self.buffer.is_empty() { + let result = std::mem::take(&mut self.buffer); + let txid = bsl_txid(tx); + let tx = groestlcoin::Transaction::consensus_decode(&mut tx.as_ref()) + .expect("transaction was already validated"); + self.result.push(FilteredTx:: { + tx, + txid, + pos: self.pos, + result, + }); + } + self.pos += 1; + ControlFlow::Continue(()) + } + fn visit_tx_in(&mut self, _vin: usize, tx_in: &bsl::TxIn) -> ControlFlow<()> { + let current: OutPoint = tx_in.prevout().into(); + if self.outpoints.contains(¤t) { + self.buffer.push(current); + } + ControlFlow::Continue(()) + } + } + + let mut find_inputs = FindInputs { + outpoints, + result: vec![], + buffer: vec![], + pos: 0, + }; + + bsl::Block::visit(block, &mut find_inputs).expect("core returned invalid block"); + + find_inputs.result } #[cfg(test)] mod tests { + use std::{collections::HashSet, str::FromStr}; + + use crate::types::ScriptHash; + use super::HistoryEntry; - use groestlcoin::Amount; + use bitcoin_test_data::blocks::mainnet_702861; + use groestlcoin::{Address, Amount}; use serde_json::json; #[test] @@ -566,4 +636,42 @@ mod tests { json!({"tx_hash": "5b75086dafeede555fc8f9a810d8b10df57c46f9f176ccc3dd8d2fa20edd685b", "height": 0, "fee": 123}) ); } + + #[test] + fn test_find_outputs() { + let block = mainnet_702861().to_vec(); + + let addr = Address::from_str("FeK4yRzPfRF3R3sVG5ssmXATFgKzA7wxGZ") + .unwrap() + .assume_checked(); + let scripthash = ScriptHash::new(&addr.script_pubkey()); + + let result = &super::filter_block_txs_outputs(block, scripthash)[0]; + assert_eq!( + result.txid.to_string(), + "01112f7b0a50e0259afe3637f9cbbf0aaf0ca2c7291737b3ea8e204f38ed29ff" + ); + assert_eq!(result.pos, 8); + assert_eq!(result.result[0].index, 0); + assert_eq!(result.result[0].value.to_sat(), 709503); + } + + #[test] + fn test_find_inputs() { + let block = mainnet_702861().to_vec(); + let outpoint = groestlcoin::OutPoint::from_str( + "cc135e792b37a9c4ffd784f696b1e38bd1197f8e67ae1f96c9f13e4618b91866:3", + ) + .unwrap(); + let mut outpoints = HashSet::new(); + outpoints.insert(outpoint); + + let result = &super::filter_block_txs_inputs(&block, &outpoints)[0]; + assert_eq!( + result.txid.to_string(), + "01112f7b0a50e0259afe3637f9cbbf0aaf0ca2c7291737b3ea8e204f38ed29ff" + ); + assert_eq!(result.pos, 8); + assert_eq!(result.result[0], outpoint); + } } diff --git a/src/tracker.rs b/src/tracker.rs index d500b6432..a5de23ed3 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -1,5 +1,10 @@ use anyhow::{Context, Result}; use groestlcoin::{BlockHash, Transaction, Txid}; +use groestlcoin_slices::{ + bsl::{self, FindTransaction}, + Error::VisitBreak, + Visit, +}; use crate::{ cache::Cache, @@ -28,7 +33,11 @@ pub(crate) enum Error { impl Tracker { pub fn new(config: &Config, metrics: Metrics) -> Result { - let store = DBStore::open(&config.db_path, config.auto_reindex)?; + let store = DBStore::open( + &config.db_path, + config.db_log_dir.as_deref(), + config.auto_reindex, + )?; let chain = Chain::new(config.network); Ok(Self { index: Index::load( @@ -102,15 +111,14 @@ impl Tracker { let blockhashes = self.index.filter_by_txid(txid); let mut result = None; daemon.for_blocks(blockhashes, |blockhash, block| { - for tx in block.txdata { - if result.is_some() { - return; - } - if tx.txid() == txid { - result = Some((blockhash, tx)); - return; - } + if result.is_some() { + return; // keep first matching transaction } + let mut visitor = FindTransaction::new(txid); + result = match bsl::Block::visit(&block, &mut visitor) { + Ok(_) | Err(VisitBreak) => visitor.tx_found().map(|tx| (blockhash, tx)), + Err(e) => panic!("core returned invalid block: {:?}", e), + }; })?; Ok(result) } diff --git a/src/types.rs b/src/types.rs index a9403b60f..7347112d9 100644 --- a/src/types.rs +++ b/src/types.rs @@ -8,6 +8,7 @@ use groestlcoin::{ hashes::{hash_newtype, sha256, Hash}, OutPoint, Script, Txid, }; +use groestlcoin_slices::bsl; use crate::db; @@ -43,6 +44,7 @@ const HEIGHT_SIZE: usize = 4; type HashPrefix = [u8; HASH_PREFIX_LEN]; type Height = u32; +pub(crate) type SerBlock = Vec; #[derive(Debug, Serialize, Deserialize, PartialEq)] pub(crate) struct HashPrefixRow { @@ -190,6 +192,10 @@ impl HeaderRow { } } +pub(crate) fn bsl_txid(tx: &bsl::Transaction) -> Txid { + groestlcoin::Txid::from_slice(tx.txid_sha2().as_slice()).expect("invalid txid") +} + #[cfg(test)] mod tests { use crate::types::{spending_prefix, HashPrefixRow, ScriptHash, ScriptHashRow, TxidRow}; diff --git a/tests/run.sh b/tests/run.sh index f0852496f..6be87f166 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -55,7 +55,7 @@ tail_log data/electrum-grs/regtest-debug.log | grep -m1 "connection established" $EL getinfo | jq . echo "Loading Electrum wallet..." -test `$EL load_wallet` == "true" +$EL load_wallet echo "Running integration tests:"