From d2f007287ea358711a4116941fc21f0a66e94f78 Mon Sep 17 00:00:00 2001 From: Torkel Rogstad Date: Fri, 4 Oct 2024 13:23:45 +0200 Subject: [PATCH] multi: pull bip300301_wallet into enforcer 1. Rename `Bip300` to `Validator`, to make it clearer what this does. 2. Copy over the wallet implementation from the `bip300301_wallet` repo. 3. Replaced usage of the `bs58` library with `rust-bitcoin`, since this is already in the dependency tree through BDK 4. Same goes for the `bip39` library 5. Note that we're using an outdated version of `cusf_sidechain_proto`. This is to minimize the changes done here. 6. Note that none of this is tested, nor exposed anywhere. This is also to minimize the changes needed in copying over the wallet. --- .../workflows/check_lint_build_release.yaml | 5 + .gitmodules | 3 + Cargo.lock | 751 ++++++++- Cargo.toml | 7 + build.rs | 11 +- cusf_sidechain_proto | 1 + src/gen.rs | 4 + src/main.rs | 9 +- src/server.rs | 4 +- src/{bip300.rs => validator.rs} | 4 +- src/wallet.rs | 1481 +++++++++++++++++ 11 files changed, 2256 insertions(+), 24 deletions(-) create mode 100644 .gitmodules create mode 160000 cusf_sidechain_proto rename src/{bip300.rs => validator.rs} (99%) create mode 100644 src/wallet.rs diff --git a/.github/workflows/check_lint_build_release.yaml b/.github/workflows/check_lint_build_release.yaml index d140ad9..f059bed 100644 --- a/.github/workflows/check_lint_build_release.yaml +++ b/.github/workflows/check_lint_build_release.yaml @@ -11,6 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: "recursive" + - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -79,6 +82,8 @@ jobs: timeout-minutes: 20 steps: - uses: actions/checkout@v4 + with: + submodules: "recursive" - name: Install latest stable toolchain uses: dtolnay/rust-toolchain@master diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..758d2b2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cusf_sidechain_proto"] + path = cusf_sidechain_proto + url = https://github.com/LayerTwo-Labs/cusf_sidechain_proto diff --git a/Cargo.lock b/Cargo.lock index 8972bfa..a74fb72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,29 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy 0.7.35", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -26,6 +49,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "anstream" version = "0.6.15" @@ -81,6 +110,18 @@ version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-stream" version = "0.3.5" @@ -197,6 +238,22 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes 0.14.0", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -209,12 +266,59 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bdk" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc1fc1a92e0943bfbcd6eb7d32c1b2a79f2f1357eb1e2eee9d7f36d6d7ca44a" +dependencies = [ + "ahash 0.7.8", + "async-trait", + "bdk-macros", + "bip39", + "bitcoin 0.30.2", + "electrum-client", + "getrandom", + "js-sys", + "log", + "miniscript", + "rand", + "rusqlite", + "serde", + "serde_json", + "sled", + "tokio", +] + +[[package]] +name = "bdk-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81c1980e50ae23bb6efa9283ae8679d6ea2c6fa6a99fe62533f65f4a25a1a56c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bech32" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "beef" version = "0.5.2" @@ -234,11 +338,16 @@ dependencies = [ name = "bip300301_enforcer" version = "0.1.0" dependencies = [ + "bdk", "bincode", "bip300301_messages", + "bitcoin 0.32.3", + "blake3", "byteorder", "clap", - "fallible-iterator", + "cusf_sidechain_types", + "ed25519-dalek-bip32", + "fallible-iterator 0.3.0", "futures", "heed", "hex", @@ -247,6 +356,8 @@ dependencies = [ "prost", "prost-build", "protox", + "rusqlite", + "rusqlite_migration", "serde", "sha2", "tokio", @@ -261,33 +372,94 @@ name = "bip300301_messages" version = "0.1.0" source = "git+https://github.com/LayerTwo-Labs/bip300301_messages#398b224981c7c236c8354704e655996d33685149" dependencies = [ - "bitcoin", + "bitcoin 0.30.2", "byteorder", "miette 5.10.0", "nom", "sha2", ] +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes 0.11.0", + "serde", + "unicode-normalization", +] + [[package]] name = "bitcoin" version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1945a5048598e4189e239d3f809b19bdad4845c4b2ba400d304d2dcf26d2c462" dependencies = [ - "bech32", + "base64 0.13.1", + "bech32 0.9.1", "bitcoin-private", - "bitcoin_hashes", + "bitcoin_hashes 0.12.0", + "hex_lit", + "secp256k1 0.27.0", + "serde", +] + +[[package]] +name = "bitcoin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0032b0e8ead7074cda7fc4f034409607e3f03a6f71d66ade8a307f79b4d99e73" +dependencies = [ + "base58ck", + "bech32 0.11.0", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes 0.14.0", + "hex-conservative", "hex_lit", - "secp256k1", + "secp256k1 0.29.1", "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" + [[package]] name = "bitcoin-private" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" + [[package]] name = "bitcoin_hashes" version = "0.12.0" @@ -298,12 +470,42 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -313,6 +515,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2", + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -386,6 +604,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "cpufeatures" version = "0.2.14" @@ -404,6 +634,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -429,6 +668,61 @@ dependencies = [ "typenum", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "cusf_sidechain_types" +version = "0.1.0" +source = "git+https://github.com/LayerTwo-Labs/cusf_sidechain_types#b608d2e0d7459ed6be2c4ed605df3770e7e4f70f" +dependencies = [ + "bincode", + "bitcoin 0.32.3", + "blake3", + "bs58", + "serde", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + [[package]] name = "digest" version = "0.10.7" @@ -437,6 +731,44 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "ed25519-dalek-bip32" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b49a684b133c4980d7ee783936af771516011c8cd15f429dbda77245e282f03" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac", + "sha2", ] [[package]] @@ -445,6 +777,25 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "electrum-client" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc133f1c8d829d254f013f946653cbeb2b08674b960146361d1e9b67733ad19" +dependencies = [ + "bitcoin 0.30.2", + "bitcoin-private", + "byteorder", + "libc", + "log", + "rustls 0.21.12", + "serde", + "serde_json", + "webpki", + "webpki-roots 0.22.6", + "winapi", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -461,18 +812,36 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fallible-iterator" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "fixedbitset" version = "0.4.2" @@ -504,6 +873,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures" version = "0.3.30" @@ -593,6 +972,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -650,6 +1038,19 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] [[package]] name = "heck" @@ -704,12 +1105,30 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex_lit" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "1.1.0" @@ -840,6 +1259,15 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "is_ci" version = "1.2.0" @@ -867,6 +1295,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -879,6 +1316,16 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libsqlite3-sys" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -895,6 +1342,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -1012,6 +1469,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniscript" +version = "10.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d371924f9eb7aa860ab395baaaa0bcdfa81a32f330b538c4e2c04617b2722fe3" +dependencies = [ + "bitcoin 0.30.2", + "bitcoin-private", + "serde", +] + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -1080,6 +1548,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1128,6 +1621,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -1294,6 +1797,15 @@ dependencies = [ "getrandom", ] +[[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 = "regex" version = "1.11.0" @@ -1338,25 +1850,70 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rusqlite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +dependencies = [ + "bitflags 1.3.2", + "fallible-iterator 0.2.0", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rusqlite_migration" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef7dd29a4426624704d5966416682fb7ab3682f724986e9e3893eaca44accabc" +dependencies = [ + "log", + "rusqlite", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.13" @@ -1367,7 +1924,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] @@ -1378,6 +1935,16 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.102.8" @@ -1401,14 +1968,42 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "secp256k1" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ - "bitcoin_hashes", - "secp256k1-sys", + "bitcoin_hashes 0.12.0", + "rand", + "secp256k1-sys 0.8.1", + "serde", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes 0.14.0", + "secp256k1-sys 0.10.1", "serde", ] @@ -1421,6 +2016,21 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" version = "1.0.210" @@ -1470,6 +2080,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -1479,6 +2098,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -1507,6 +2142,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1868,9 +2513,9 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] @@ -1903,12 +2548,12 @@ dependencies = [ "flate2", "log", "once_cell", - "rustls", + "rustls 0.23.13", "rustls-pki-types", "serde", "serde_json", "url", - "webpki-roots", + "webpki-roots 0.26.5", ] [[package]] @@ -1940,6 +2585,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -1961,6 +2612,80 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + [[package]] name = "webpki-roots" version = "0.26.5" diff --git a/Cargo.toml b/Cargo.toml index 49204bf..0a02502 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,15 @@ protox = "0.7.1" tonic-build = "0.12.3" [dependencies] +bdk = { version = "0.29.0", features = ["all-keys", "sqlite"] } bincode = "1.3.3" bip300301_messages = { git = "https://github.com/LayerTwo-Labs/bip300301_messages" } +bitcoin = "0.32.3" +blake3 = "1.5.4" byteorder = "1.5.0" clap = { version = "4.5.18", features = ["derive"] } +cusf_sidechain_types = { git = "https://github.com/LayerTwo-Labs/cusf_sidechain_types" } +ed25519-dalek-bip32 = "0.3.0" fallible-iterator = "0.3.0" futures = "0.3.30" heed = { git = "https://github.com/meilisearch/heed", tag = "v0.12.4", version = "0.12.4" } @@ -23,6 +28,8 @@ hex = "0.4.3" miette = { version = "7.1.0", features = ["fancy"] } nom = "7.1.3" prost = "0.13.2" +rusqlite = "0.28.0" +rusqlite_migration = "1.0.2" serde = { version = "1.0.197", features = ["derive"] } sha2 = "0.10.8" tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] } diff --git a/build.rs b/build.rs index 014b3da..054ea4f 100644 --- a/build.rs +++ b/build.rs @@ -3,8 +3,13 @@ use std::{env, fs, path::PathBuf}; use prost::Message; fn main() -> Result<(), Box> { - let protos: &[&str] = &["proto/validator/v1/validator.proto"]; - let includes: &[&str] = &["proto/validator/v1"]; + let protos: &[&str] = &[ + "proto/validator/v1/validator.proto", + "cusf_sidechain_proto/proto/sidechain.proto", + ]; + + let includes: &[&str] = &["proto/validator/v1", "cusf_sidechain_proto/proto"]; + let file_descriptors = protox::compile(protos, includes)?; let file_descriptor_path = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR environment variable not set")) @@ -16,7 +21,7 @@ fn main() -> Result<(), Box> { tonic_build::configure() .skip_protoc_run() .file_descriptor_set_path(file_descriptor_path) - .build_client(false) + .build_client(true) // Needed for sidechain client .compile_protos_with_config(config, protos, includes)?; Ok(()) } diff --git a/cusf_sidechain_proto b/cusf_sidechain_proto new file mode 160000 index 0000000..3d0bfa3 --- /dev/null +++ b/cusf_sidechain_proto @@ -0,0 +1 @@ +Subproject commit 3d0bfa3580c8710c3050d02b1eb9e0c266732a7b diff --git a/src/gen.rs b/src/gen.rs index 18cfdc5..a134cfe 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -1,3 +1,7 @@ pub mod validator { tonic::include_proto!("validator.v1"); } + +pub mod sidechain { + tonic::include_proto!("sidechain"); +} diff --git a/src/main.rs b/src/main.rs index e546098..0106ba2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ use std::{net::SocketAddr, path::Path}; -mod bip300; mod cli; mod gen; mod server; mod types; +mod validator; +mod wallet; use clap::Parser; use futures::{ @@ -13,7 +14,7 @@ use futures::{ }; use gen::validator::validator_service_server::ValidatorServiceServer; use miette::{miette, IntoDiagnostic, Result}; -use server::Bip300; +use server::Validator; use tonic::transport::Server; use ureq_jsonrpc::Client; @@ -37,7 +38,7 @@ fn _create_client(main_datadir: &Path) -> Result { }) } -async fn run_server(bip300: Bip300, addr: SocketAddr) -> Result<()> { +async fn run_server(bip300: Validator, addr: SocketAddr) -> Result<()> { println!("Listening for gRPC on {addr}"); Server::builder() .add_service(ValidatorServiceServer::new(bip300)) @@ -51,7 +52,7 @@ async fn main() -> Result<()> { let cli = cli::Config::parse(); let serve_rpc_addr = cli.serve_rpc_addr; - let bip300 = Bip300::new(Path::new("./"))?; + let bip300 = Validator::new(Path::new("./"))?; let task = bip300 .run(cli) diff --git a/src/server.rs b/src/server.rs index ec1336f..3821cff 100644 --- a/src/server.rs +++ b/src/server.rs @@ -15,8 +15,8 @@ use bip300301_messages::{ use miette::Result; use tonic::{Request, Response, Status}; -pub use crate::bip300::Bip300; use crate::types; +pub use crate::validator::Validator; fn invalid_enum_variant(field_name: &str, variant_name: &str) -> tonic::Status where @@ -32,7 +32,7 @@ where } #[tonic::async_trait] -impl ValidatorService for Bip300 { +impl ValidatorService for Validator { async fn connect_block( &self, _: Request, diff --git a/src/bip300.rs b/src/validator.rs similarity index 99% rename from src/bip300.rs rename to src/validator.rs index 3453e3e..9a249db 100644 --- a/src/bip300.rs +++ b/src/validator.rs @@ -74,7 +74,7 @@ impl Serialize for UnitKey { } #[derive(Clone)] -pub struct Bip300 { +pub struct Validator { env: Env, data_hash_to_sidechain_proposal: @@ -93,7 +93,7 @@ pub struct Bip300 { Database, SerdeBincode>>, } -impl Bip300 { +impl Validator { const NUM_DBS: u32 = 11; pub fn new(datadir: &Path) -> Result { diff --git a/src/wallet.rs b/src/wallet.rs new file mode 100644 index 0000000..4fa0307 --- /dev/null +++ b/src/wallet.rs @@ -0,0 +1,1481 @@ +use crate::gen::sidechain::sidechain_client::SidechainClient; +use crate::gen::sidechain::{ + CollectTransactionsRequest, ConnectMainBlockRequest, GetChainTipRequest, GetUtxoSetRequest, + GetWithdrawalBundleRequest, SubmitBlockRequest, SubmitTransactionRequest, +}; +use crate::types::SidechainProposal; +use crate::validator::Validator; +use bdk::blockchain::ElectrumBlockchain; +use bdk::database::SqliteDatabase; +use bdk::keys::bip39::{Language, Mnemonic}; +use bdk::keys::{DerivableKey, ExtendedKey}; +use bdk::template::Bip84; +use bdk::wallet::AddressIndex; +use bdk::{KeychainKind, SignOptions, SyncOptions}; +use bip300301_messages::bitcoin::opcodes::all::{OP_PUSHBYTES_1, OP_PUSHBYTES_36}; +use bip300301_messages::bitcoin::opcodes::OP_TRUE; +use bip300301_messages::bitcoin::Witness; +use bip300301_messages::OP_DRIVECHAIN; +use bitcoin::opcodes::all::OP_RETURN; +use bitcoin::{Amount, Network, Txid}; +use cusf_sidechain_types::{ + Hashable, OutPoint, Output, ADDRESS_LENGTH, HASH_LENGTH, MAIN_ADDRESS_LENGTH, +}; +use ed25519_dalek_bip32::{ChildIndex, DerivationPath, ExtendedSigningKey}; +use miette::{miette, IntoDiagnostic, Result}; +use rusqlite::{Connection, Row}; +use std::collections::{BTreeMap, HashMap}; +use std::io::Cursor; +use std::path::Path; +use std::str::FromStr; +use tonic::transport::Channel; + +pub struct Wallet { + main_client: Client, + validator: Validator, + sidechain_clients: HashMap>, + bitcoin_wallet: bdk::Wallet, + db_connection: Connection, + bitcoin_blockchain: ElectrumBlockchain, + sidechain_wallet: Connection, + mnemonic: Mnemonic, + // seed + // sidechain number + // index + // address (20 byte hash of public key) + // utxos +} + +impl Wallet { + pub async fn new>(datadir: P, validator: Validator) -> Result { + let network = Network::Regtest; // Or this can be Network::Bitcoin, Network::Signet or Network::Regtest + // Generate fresh mnemonic + + /* + let mnemonic: GeneratedKey<_, miniscript::Segwitv0> = + Mnemonic::generate((WordCount::Words12, Language::English)).unwrap(); + // Convert mnemonic to string + let mnemonic_words = mnemonic.to_string(); + // Parse a mnemonic + let mnemonic = Mnemonic::parse(&mnemonic_words).unwrap(); + */ + + let mnemonic = Mnemonic::parse_in_normalized( + Language::English, + "betray annual dog current tomorrow media ghost dynamic mule length sure salad", + ) + .into_diagnostic()?; + // Generate the extended key + let xkey: ExtendedKey = mnemonic.clone().into_extended_key().into_diagnostic()?; + // Get xprv from the extended key + let xprv = xkey + .into_xprv(network) + .ok_or(miette!("couldn't get xprv"))?; + + std::fs::create_dir_all(&datadir).into_diagnostic()?; + + // Create a BDK wallet structure using BIP 84 descriptor ("m/84h/1h/0h/0" and "m/84h/1h/0h/1") + let bitcoin_wallet = bdk::Wallet::new( + Bip84(xprv, KeychainKind::External), + Some(Bip84(xprv, KeychainKind::Internal)), + network, + SqliteDatabase::new(datadir.as_ref().join("wallet.sqlite")), + ) + .into_diagnostic()?; + + let bitcoin_wallet_client = + bdk::electrum_client::Client::new("127.0.0.1:60401").into_diagnostic()?; + let bitcoin_blockchain = ElectrumBlockchain::from(bitcoin_wallet_client); + + use rusqlite_migration::{Migrations, M}; + + let sidechain_wallet = { + let migrations = Migrations::new(vec![ + M::up( + "CREATE TABLE keys + (sidechain_number INTEGER NOT NULL, + key_index INTEGER NOT NULL, + address BLOB NOT NULL, + PRIMARY KEY(sidechain_number, key_index) + );", + ), + M::up( + "CREATE TABLE utxos + (id INTEGER NOT NULL PRIMARY KEY, + sidechain_number INTEGER NOT NULL, + key_index INTEGER NOT NULL, + value INTEGER NOT NULL, + main_fee INTEGER, + transaction_number INTEGER, + transaction_output_number INTEGER, + block_number INTEGER, + coinbase_output_number INTEGER, + deposit_number INTEGER, + FOREIGN KEY (sidechain_number, key_index) REFERENCES keys(sidechain_number, key_index), + UNIQUE (transaction_number, transaction_output_number), + UNIQUE (block_number, coinbase_output_number), + UNIQUE (deposit_number) + );"), + M::up( + "CREATE TABLE transaction_inputs + (id INTEGER NOT NULL PRIMARY KEY, + sidechain_number INTEGER NOT NULL, + utxo_id INTEGER NOT NULL UNIQUE, + FOREIGN KEY(utxo_id) REFERENCES utxo(id) + );", + ), + M::up( + "CREATE TABLE transaction_outputs + (id INTEGER NOT NULL PRIMARY KEY, + sidechain_number INTEGER NOT NULL, + address BLOB NOT NULL, + value INTEGER NOT NULL, + main_address BLOB, + main_fee INTEGER);", + ), + ]); + + // seed + // sidechain number + // index + // address (20 byte hash of public key) + // utxos + + let mut sidechain_wallet = + Connection::open(datadir.as_ref().join("sidechain_wallet.sqlite")) + .into_diagnostic()?; + + migrations + .to_latest(&mut sidechain_wallet) + .into_diagnostic()?; + sidechain_wallet + }; + + let db_connection = { + // 1️⃣ Define migrations + let migrations = Migrations::new(vec![ + M::up( + "CREATE TABLE sidechain_proposals + (number INTEGER NOT NULL, + data BLOB NOT NULL, + UNIQUE(number, data));", + ), + M::up( + "CREATE TABLE sidechain_acks + (number INTEGER NOT NULl, + data_hash BLOB NOT NULL, + UNIQUE(number, data_hash));", + ), + M::up( + "CREATE TABLE bundle_proposals + (sidechain_number INTEGER NOT NULL, + bundle_hash BLOB NOT NULL, + UNIQUE(sidechain_number, bundle_hash));", + ), + M::up( + "CREATE TABLE bundle_acks + (sidechain_number INTEGER NOT NULL, + bundle_hash BLOB NOT NULL, + UNIQUE(sidechain_number, bundle_hash));", + ), + M::up( + "CREATE TABLE deposits + (sidechain_number INTEGER NOT NULL, + address BLOB NOT NULl, + amount INTEGER NOT NULL, + txid BLOB NOT NULL);", + ), + M::up( + "CREATE TABLE mempool + (txid BLOB UNIQUE NOT NULL, + tx_data BLOB NOT NULL);", + ), + ]); + + let mut db_connection = + Connection::open(datadir.as_ref().join("db.sqlite")).into_diagnostic()?; + + migrations.to_latest(&mut db_connection).into_diagnostic()?; + db_connection + }; + + let main_datadir = Path::new("../../data/bitcoin/"); + let main_client = create_client(main_datadir)?; + + let sidechain_clients = HashMap::new(); + + let mut wallet = Self { + main_client, + validator, + sidechain_clients, + bitcoin_wallet, + db_connection, + sidechain_wallet, + bitcoin_blockchain, + mnemonic, + }; + let sidechains = wallet.get_sidechains().await?; + for sidechain in &sidechains { + let endpoint = format!("http://[::1]:{}", 50052 + sidechain.sidechain_number as u32); + let sidechain_client = SidechainClient::connect(endpoint).await.into_diagnostic()?; + wallet.sidechain_clients.insert(0, sidechain_client); + } + Ok(wallet) + } + + pub fn get_new_sidechain_address( + &mut self, + sidechain_number: u8, + ) -> Result<(u32, [u8; ADDRESS_LENGTH])> { + let tx = self.sidechain_wallet.transaction().into_diagnostic()?; + let mut key_index = tx + .query_row("SELECT MAX(key_index) FROM keys;", [], |row| { + Ok(row.get(0).unwrap_or(0)) + }) + .into_diagnostic()?; + key_index += 1; + let seed = self.mnemonic.to_seed(""); + let xpriv = ExtendedSigningKey::from_seed(&seed).into_diagnostic()?; + let derivation_path = DerivationPath::new([ + ChildIndex::Hardened(1), + ChildIndex::Hardened(0), + ChildIndex::Hardened(0), + ChildIndex::Hardened(sidechain_number as u32 + 1), + ChildIndex::Hardened(key_index), + ]); + let child = xpriv.derive(&derivation_path).into_diagnostic()?; + let verifying_key = child.verifying_key(); + let verifying_key_bytes = verifying_key.to_bytes(); + let mut hasher = blake3::Hasher::new(); + hasher.update(&verifying_key_bytes); + let mut address_reader = hasher.finalize_xof(); + let mut address = [0; ADDRESS_LENGTH]; + address_reader.fill(&mut address); + tx.execute( + "INSERT INTO keys (sidechain_number, key_index, address) VALUES (?1, ?2, ?3)", + (&sidechain_number, &key_index, &address), + ) + .into_diagnostic()?; + tx.commit().into_diagnostic()?; + Ok((key_index, address)) + } + + pub fn get_block_height(&self) -> Result { + let block_height: u32 = self + .main_client + .send_request("getblockcount", &[]) + .into_diagnostic()? + .ok_or(miette!("failed to get block count"))?; + Ok(block_height) + } + + pub async fn generate_block( + &self, + coinbase_outputs: &[TxOut], + transactions: Vec, + ) -> Result { + let addr = self + .bitcoin_wallet + .get_address(AddressIndex::New) + .into_diagnostic()?; + let script_pubkey = addr.script_pubkey(); + let block_height = self.get_block_height()?; + println!("Block height: {block_height}"); + let block_hash: String = self + .main_client + .send_request("getblockhash", &[json!(block_height)]) + .into_diagnostic()? + .ok_or(miette!("failed to get block hash"))?; + let prev_blockhash = BlockHash::from_str(&block_hash).into_diagnostic()?; + + let start = SystemTime::now(); + let time = start + .duration_since(UNIX_EPOCH) + .into_diagnostic()? + .as_secs() as u32; + + let script_sig = bitcoin::blockdata::script::Builder::new() + .push_int((block_height + 1) as i64) + .push_opcode(OP_0) + .into_script(); + let value = get_block_value(block_height + 1, 0, Network::Regtest); + + let output = if value > 0 { + vec![TxOut { + script_pubkey, + value, + }] + } else { + vec![TxOut { + script_pubkey: ScriptBuf::builder().push_opcode(OP_RETURN).into_script(), + value: 0, + }] + }; + + const WITNESS_RESERVED_VALUE: [u8; 32] = [0; 32]; + + let txdata = [ + vec![Transaction { + version: 2, + lock_time: LockTime::Blocks(Height::ZERO), + input: vec![TxIn { + previous_output: bitcoin::OutPoint { + txid: Txid::all_zeros(), + vout: 0xFFFF_FFFF, + }, + sequence: Sequence::MAX, + witness: Witness::from_slice(&[WITNESS_RESERVED_VALUE]), + script_sig, + }], + output: [&output, coinbase_outputs].concat(), + }], + transactions, + ] + .concat(); + + let genesis_block = genesis_block(bitcoin::Network::Regtest); + let bits = genesis_block.header.bits; + let header = bitcoin::block::Header { + version: Version::NO_SOFT_FORK_SIGNALLING, + prev_blockhash, + // merkle root is computed after the witness commitment is added to coinbase + merkle_root: TxMerkleNode::all_zeros(), + time, + bits, + nonce: 0, + }; + let mut block = Block { header, txdata }; + let witness_root = block.witness_root().unwrap(); + let witness_commitment = + Block::compute_witness_commitment(&witness_root, &WITNESS_RESERVED_VALUE); + + let script_pubkey_bytes = [ + vec![OP_RETURN.to_u8(), OP_PUSHBYTES_36.to_u8()], + vec![0xaa, 0x21, 0xa9, 0xed], + witness_commitment.as_byte_array().into(), + ] + .concat(); + let script_pubkey = ScriptBuf::from_bytes(script_pubkey_bytes); + block.txdata[0].output.push(TxOut { + script_pubkey, + value: 0, + }); + let mut tx_hashes: Vec<_> = block.txdata.iter().map(Transaction::txid).collect(); + block.header.merkle_root = merkle_tree::calculate_root_inline(&mut tx_hashes) + .unwrap() + .to_raw_hash() + .into(); + Ok(block) + } + + pub async fn mine( + &mut self, + coinbase_outputs: &[TxOut], + transactions: Vec, + ) -> Result<()> { + let mut block = self.generate_block(coinbase_outputs, transactions).await?; + loop { + block.header.nonce += 1; + if block.header.validate_pow(block.header.target()).is_ok() { + break; + } + } + let mut block_bytes = vec![]; + block.consensus_encode(&mut block_bytes).into_diagnostic()?; + let block_hex = hex::encode(block_bytes); + + let _: Option<()> = self + .main_client + .send_request("submitblock", &[json!(block_hex)]) + .into_diagnostic()?; + + let block_hash = block.header.block_hash().as_byte_array().to_vec(); + let block_height: u32 = self + .main_client + .send_request("getblockcount", &[]) + .into_diagnostic()? + .ok_or(miette!("failed to get block count"))?; + let bmm_hashes: Vec> = self + .get_bmm_requests() + .await? + .into_iter() + .map(|(_sidechain_number, bmm_hash)| bmm_hash.to_vec()) + .collect(); + for (_sidechain_number, sidechain_client) in &mut self.sidechain_clients { + let request = ConnectMainBlockRequest { + block_height, + block_hash: block_hash.clone(), + bmm_hashes: bmm_hashes.clone(), + deposits: vec![], + withdrawal_bundle_event: None, + }; + sidechain_client + .connect_main_block(request) + .await + .into_diagnostic()?; + } + std::thread::sleep(Duration::from_millis(500)); + Ok(()) + } + + pub fn get_balance(&self) -> Result<()> { + self.bitcoin_wallet + .sync(&self.bitcoin_blockchain, SyncOptions::default()) + .into_diagnostic()?; + let balance = self.bitcoin_wallet.get_balance().into_diagnostic()?; + let immature = Amount::from_sat(balance.immature); + let untrusted_pending = Amount::from_sat(balance.untrusted_pending); + let trusted_pending = Amount::from_sat(balance.trusted_pending); + let confirmed = Amount::from_sat(balance.confirmed); + println!("Confirmed: {confirmed}"); + println!("Immature: {immature}"); + println!("Untrusted pending: {untrusted_pending}"); + println!("Trusted pending: {trusted_pending}"); + Ok(()) + } + + pub fn get_utxos(&self) -> Result<()> { + self.bitcoin_wallet + .sync(&self.bitcoin_blockchain, SyncOptions::default()) + .into_diagnostic()?; + let utxos = self.bitcoin_wallet.list_unspent().into_diagnostic()?; + for utxo in &utxos { + println!( + "address: {}, value: {}", + utxo.txout.script_pubkey, utxo.txout.value + ); + } + Ok(()) + } + + pub fn propose_sidechain(&self, sidechain_number: u8, data: &[u8]) -> Result<()> { + self.db_connection + .execute( + "INSERT INTO sidechain_proposals (number, data) VALUES (?1, ?2)", + (sidechain_number, data), + ) + .into_diagnostic()?; + Ok(()) + } + + pub fn ack_sidechain(&self, sidechain_number: u8, data_hash: &[u8; 32]) -> Result<()> { + self.db_connection + .execute( + "INSERT INTO sidechain_acks (number, data_hash) VALUES (?1, ?2)", + (sidechain_number, data_hash), + ) + .into_diagnostic()?; + Ok(()) + } + + pub fn nack_sidechain(&self, sidechain_number: u8, data_hash: &[u8; 32]) -> Result<()> { + self.db_connection + .execute( + "DELETE FROM sidechain_acks WHERE number = ?1 AND data_hash = ?2", + (sidechain_number, data_hash), + ) + .into_diagnostic()?; + Ok(()) + } + + pub fn get_sidechain_acks(&self) -> Result> { + let mut statement = self + .db_connection + .prepare("SELECT number, data_hash FROM sidechain_acks") + .into_diagnostic()?; + let rows = statement + .query_map([], |row| { + let data_hash: [u8; 32] = row.get(1)?; + Ok(SidechainAck { + sidechain_number: row.get(0)?, + data_hash, + }) + }) + .into_diagnostic()?; + let mut acks = vec![]; + for ack in rows { + let ack = ack.into_diagnostic()?; + acks.push(ack); + } + Ok(acks) + } + + pub fn delete_sidechain_ack(&self, ack: &SidechainAck) -> Result<()> { + self.db_connection + .execute( + "DELETE FROM sidechain_acks WHERE number = ?1 AND data_hash = ?2", + (ack.sidechain_number, ack.data_hash), + ) + .into_diagnostic()?; + Ok(()) + } + + pub async fn get_pending_sidechain_proposals( + &mut self, + ) -> Result> { + let pending_proposals = self + .validator + .get_sidechain_proposals() + .map_err(|e| miette::miette!(e.to_string()))? + .into_iter() + .map(|(_, sidechain_proposal)| { + (sidechain_proposal.sidechain_number, sidechain_proposal) + }) + .collect(); + Ok(pending_proposals) + } + + pub fn get_sidechain_proposals(&mut self) -> Result> { + let mut statement = self + .db_connection + .prepare("SELECT number, data FROM sidechain_proposals") + .into_diagnostic()?; + let rows = statement + .query_map([], |row| { + let data: Vec = row.get(1)?; + Ok(Sidechain { + sidechain_number: row.get(0)?, + data, + }) + }) + .into_diagnostic()?; + let mut proposals = vec![]; + for proposal in rows { + let proposal = proposal.into_diagnostic()?; + proposals.push(proposal); + } + + Ok(proposals) + } + + pub async fn get_sidechains(&mut self) -> Result> { + let sidechains = self + .validator + .get_sidechains() + .map_err(|e| miette::miette!(e.to_string()))? + .into_iter() + .map(|sidechain| Sidechain { + sidechain_number: sidechain.sidechain_number, + data: sidechain.data, + }) + .collect(); + Ok(sidechains) + } + + pub async fn get_ctip( + &mut self, + sidechain_number: u8, + ) -> Result> { + let ctip = self + .validator + .get_ctip(sidechain_number) + .map_err(|e| miette::miette!(e.to_string())); + + let sequence_number = self + .validator + .get_ctip_sequence_number(sidechain_number)? + .unwrap(); + + if let Ok(Some(ctip)) = ctip { + let value = ctip.value; + Ok(Some((ctip.outpoint, value, sequence_number))) + } else { + Ok(None) + } + } + + pub fn delete_sidechain_proposals(&self) -> Result<()> { + self.db_connection + .execute("DELETE FROM sidechain_proposals;", ()) + .into_diagnostic()?; + Ok(()) + } + + pub async fn is_sidechain_active(&mut self, sidechain_number: u8) -> Result { + let sidechains = self.get_sidechains().await?; + for sidechain in sidechains { + if sidechain.sidechain_number == sidechain_number { + return Ok(true); + } + } + Ok(false) + } + + pub async fn send( + &mut self, + sidechain_number: u8, + address: &str, + value: u64, + fee: u64, + ) -> Result<()> { + let address = base58::decode(address).into_diagnostic()?; + + let (_, change_address) = self.get_new_sidechain_address(sidechain_number)?; + let tx = self.sidechain_wallet.transaction().into_diagnostic()?; + let transaction = { + let mut statement = tx.prepare("SELECT value, deposit_number FROM utxos WHERE sidechain_number = ?1 ORDER BY value ASC").into_diagnostic()?; + let deposits: Vec<_> = statement + .query_map([sidechain_number], |row| { + let value: u64 = row.get(0)?; + let deposit_number: u64 = row.get(1)?; + Ok((value, deposit_number)) + }) + .into_diagnostic()? + .collect(); + let mut inputs = vec![]; + let mut value_in = 0; + for deposit in deposits { + let (deposit_value, deposit_number) = deposit.unwrap(); + value_in += deposit_value; + let input = cusf_sidechain_types::OutPoint::Deposit { + sequence_number: deposit_number, + }; + inputs.push(input); + if value_in >= value { + break; + } + } + let mut outputs = vec![]; + let output = cusf_sidechain_types::Output::Regular { + address: address.try_into().unwrap(), + value, + }; + outputs.push(output); + let change_value = value_in - value - fee; + if change_value > 0 { + let change = cusf_sidechain_types::Output::Regular { + address: change_address, + value: change_value, + }; + outputs.push(change); + } + + cusf_sidechain_types::Transaction { inputs, outputs } + }; + tx.commit().into_diagnostic()?; + let transaction_bytes = bincode::serialize(&transaction).into_diagnostic()?; + let sidechain_client = self.sidechain_clients.get_mut(&0).unwrap(); + let request = SubmitTransactionRequest { + transaction: transaction_bytes, + }; + sidechain_client + .submit_transaction(request) + .await + .into_diagnostic()?; + Ok(()) + } + + pub async fn deposit( + &mut self, + sidechain_number: u8, + amount: u64, + address: &Option, + ) -> Result<()> { + if !self.is_sidechain_active(sidechain_number).await? { + return Err(miette!("sidechain slot {sidechain_number} is not active")); + } + let message = [ + OP_DRIVECHAIN.to_u8(), + OP_PUSHBYTES_1.to_u8(), + sidechain_number, + OP_TRUE.to_u8(), + ]; + let op_drivechain = ScriptBuf::from_bytes(message.into()); + + let sequence_number = self.validator.get_ctip_sequence_number(sidechain_number)?; + + let deposit_number = match sequence_number { + Some(sequence_number) => sequence_number + 1, + None => 0, + }; + let (key_index, address) = match address { + Some(address) => (None, base58::decode(address).into_diagnostic()?), + None => { + let (key_index, address) = self.get_new_sidechain_address(sidechain_number)?; + (Some(key_index), address.to_vec()) + } + }; + if address.len() != 20 { + return Err(miette!( + "invalid address length, is is {} bytes, when it must be 20 bytes", + address.len() + )); + } + let message = [vec![OP_RETURN.to_u8()], address.clone()].concat(); + let address_op_return = ScriptBuf::from_bytes(message); + + let ctip = self.get_ctip(sidechain_number).await?; + + // FIXME: Make this easier to read. + let ctip_amount = ctip.map(|ctip| ctip.1).unwrap_or(0); + + let mut builder = self.bitcoin_wallet.build_tx(); + builder + .ordering(bdk::wallet::tx_builder::TxOrdering::Untouched) + .add_recipient(op_drivechain.clone(), ctip_amount + amount) + .add_recipient(address_op_return, 0); + + if let Some((ctip_outpoint, _, _)) = ctip { + let transaction_hex: String = self + .main_client + .send_request("getrawtransaction", &[json!(ctip_outpoint.txid)]) + .into_diagnostic()? + .unwrap(); + let transaction_bytes = hex::decode(transaction_hex).unwrap(); + let mut cursor = Cursor::new(transaction_bytes); + let transaction = Transaction::consensus_decode(&mut cursor).into_diagnostic()?; + builder + .add_foreign_utxo( + ctip_outpoint, + bitcoin::psbt::Input { + non_witness_utxo: Some(transaction), + ..bitcoin::psbt::Input::default() + }, + 0, + ) + .into_diagnostic()?; + } + + let (mut psbt, _details) = builder.finish().into_diagnostic()?; + self.bitcoin_wallet + .sign(&mut psbt, SignOptions::default()) + .into_diagnostic()?; + let transaction = psbt.extract_tx(); + + let mut tx_data = vec![]; + let mut cursor = Cursor::new(&mut tx_data); + transaction + .consensus_encode(&mut cursor) + .into_diagnostic()?; + self.db_connection + .execute( + "INSERT INTO deposits (sidechain_number, address, amount, txid) VALUES (?1, ?2, ?3, ?4)", + (sidechain_number, &address, amount, transaction.txid().as_byte_array()), + ) + .into_diagnostic()?; + self.db_connection + .execute( + "INSERT INTO mempool (txid, tx_data) VALUES (?1, ?2)", + (transaction.txid().as_byte_array(), &tx_data), + ) + .into_diagnostic()?; + if let Some(key_index) = key_index { + self.sidechain_wallet + .execute( + "INSERT INTO utxos (sidechain_number, key_index, value, deposit_number) VALUES (?1, ?2, ?3, ?4)", + (&sidechain_number, &key_index, &amount, &deposit_number), + ) + .into_diagnostic()?; + } + Ok(()) + } + + pub fn delete_deposits(&self) -> Result<()> { + self.db_connection + .execute("DELETE FROM deposits;", ()) + .into_diagnostic()?; + Ok(()) + } + + pub async fn get_deposits(&mut self, sidechain_number: u8) -> Result<()> { + let deposits = self + .validator + .get_deposits(sidechain_number) + .map_err(|e| miette::miette!(e.to_string()))?; + dbg!(deposits); + Ok(()) + } + + pub fn get_pending_deposits(&self, sidechain_number: Option) -> Result> { + let mut statement = match sidechain_number { + Some(_sidechain_number) => self + .db_connection + .prepare( + "SELECT sidechain_number, address, amount, tx_data + FROM deposits INNER JOIN mempool ON deposits.txid = mempool.txid + WHERE sidechain_number = ?1;", + ) + .into_diagnostic()?, + None => self + .db_connection + .prepare( + "SELECT sidechain_number, address, amount, tx_data + FROM deposits INNER JOIN mempool ON deposits.txid = mempool.txid;", + ) + .into_diagnostic()?, + }; + // FIXME: Make this code more sane. + let func = |row: &Row| { + let sidechain_number: u8 = row.get(0)?; + let address: Vec = row.get(1)?; + let amount: u64 = row.get(2)?; + let tx_data: Vec = row.get(3)?; + let transaction = + Transaction::consensus_decode_from_finite_reader(&mut tx_data.as_slice()).unwrap(); + let deposit = Deposit { + sidechain_number, + address, + amount, + transaction, + }; + Ok(deposit) + }; + let rows = match sidechain_number { + Some(sidechain_number) => statement + .query_map([sidechain_number], func) + .into_diagnostic()?, + None => statement.query_map([], func).into_diagnostic()?, + }; + let mut deposits = vec![]; + for deposit in rows { + let deposit = deposit.into_diagnostic()?; + deposits.push(deposit); + } + Ok(deposits) + } + + pub async fn get_bmm_requests(&mut self) -> Result> { + let mut bmm_requests = vec![]; + let active_sidechains = self.get_sidechains().await?; + for sidechain in &active_sidechains { + let (header, _coinbase, _transactions) = + self.get_next_block(sidechain.sidechain_number).await?; + let bmm_hash = header.hash(); + bmm_requests.push((sidechain.sidechain_number, bmm_hash)); + } + Ok(bmm_requests) + } + + pub async fn get_next_block( + &mut self, + sidechain_number: u8, + ) -> Result<( + cusf_sidechain_types::Header, + Vec, + Vec, + )> { + let sidechain_client = match self.sidechain_clients.get_mut(&sidechain_number) { + Some(sidechain_client) => sidechain_client, + None => return Err(miette!("sidechain is not active")), + }; + let prev_side_block_hash = sidechain_client + .get_chain_tip(GetChainTipRequest {}) + .await + .into_diagnostic()? + .into_inner() + .block_hash; + let prev_side_block_hash: [u8; HASH_LENGTH] = prev_side_block_hash.try_into().unwrap(); + let transactions_bytes = sidechain_client + .collect_transactions(CollectTransactionsRequest {}) + .await + .into_diagnostic()? + .into_inner() + .transactions; + let transactions: Vec = + bincode::deserialize(&transactions_bytes).into_diagnostic()?; + let coinbase = vec![]; + let merkle_root = + cusf_sidechain_types::Header::compute_merkle_root(&coinbase, &transactions); + let header = cusf_sidechain_types::Header { + prev_side_block_hash, + merkle_root, + }; + println!("header: {}", hex::encode(header.hash())); + println!( + "prev_side_block_hash: {}", + hex::encode(header.prev_side_block_hash) + ); + println!("merkle_root: {}", hex::encode(header.merkle_root)); + let block = (header, coinbase, transactions); + Ok(block) + } + + pub async fn mine_side_block(&mut self, sidechain_number: u8) -> Result<()> { + let block = self.get_next_block(sidechain_number).await?; + let sidechain_client = match self.sidechain_clients.get_mut(&sidechain_number) { + Some(sidechain_client) => sidechain_client, + None => return Err(miette!("sidechain is not active")), + }; + let block = bincode::serialize(&block).into_diagnostic()?; + let request = SubmitBlockRequest { block }; + sidechain_client + .submit_block(request) + .await + .into_diagnostic()?; + Ok(()) + } + + pub fn add_output(&mut self, value: u64, main_fee: Option) -> Result<()> { + let (sidechain_number, ids_outpoints_values, outputs) = self.get_pending_transaction()?; + let (_key_index, address) = self.get_new_sidechain_address(sidechain_number)?; + let value_in: u64 = ids_outpoints_values + .iter() + .map(|(_id, _outpoint, value)| value) + .sum(); + let mut value_out: u64 = outputs.iter().map(|output| output.total_value()).sum(); + + let main_address = { + if main_fee.is_none() { + None + } else { + /* + let main_address = self + .bitcoin_wallet + .get_address(AddressIndex::New) + .into_diagnostic()?; + let main_address = match main_address.address.payload { + bitcoin::address::Payload::PubkeyHash(pubkey_hash) => { + pubkey_hash.to_byte_array() + } + bitcoin::address::Payload::ScriptHash(script_hash) => { + script_hash.to_byte_array() + } + bitcoin::address::Payload::WitnessProgram(witness_hash) => { + witness_hash.to_byte_array() + } + _ => todo!(), + }; + */ + let main_address = [0; MAIN_ADDRESS_LENGTH]; + Some(main_address) + } + }; + let output = match (main_address, main_fee) { + (Some(main_address), Some(main_fee)) => cusf_sidechain_types::Output::Withdrawal { + address, + main_address, + value, + fee: main_fee, + }, + (None, None) => cusf_sidechain_types::Output::Regular { address, value }, + _ => { + return Err(miette!("invalid arguments to add_output")); + } + }; + value_out += output.total_value(); + if value_in < value_out { + return Err(miette!("not enough value in")); + } + self.sidechain_wallet + .execute( + "INSERT INTO transaction_outputs + (sidechain_number, address, value, main_address, main_fee) + VALUES (?1, ?2, ?3, ?4, ?5)", + (sidechain_number, address, value, main_address, main_fee), + ) + .into_diagnostic()?; + Ok(()) + } + + pub fn spend(&self, utxo_id: u64) -> Result<()> { + let utxo_sidechain_number = self + .sidechain_wallet + .query_row( + "SELECT sidechain_number FROM utxos WHERE id = ?1", + [utxo_id], + |row| { + let sidechain_number: u8 = row.get(0)?; + Ok(sidechain_number) + }, + ) + .into_diagnostic()?; + let mut statement = self + .sidechain_wallet + .prepare("SELECT sidechain_number FROM transaction_inputs") + .into_diagnostic()?; + let sidechain_numbers: Vec<_> = statement + .query_map([], |row| { + let sidechain_number: u8 = row.get(0)?; + Ok(sidechain_number) + }) + .into_diagnostic()? + .collect(); + for sidechain_number in sidechain_numbers { + let sidechain_number = sidechain_number.into_diagnostic()?; + if utxo_sidechain_number != sidechain_number { + return Err(miette!("trying to add a utxo from sidechain {utxo_sidechain_number} to a transaction for sidechainn {sidechain_number}")); + } + } + self.sidechain_wallet + .execute( + "INSERT INTO transaction_inputs (sidechain_number, utxo_id) VALUES (?1, ?2)", + (utxo_sidechain_number, utxo_id), + ) + .into_diagnostic()?; + Ok(()) + } + + pub fn clear_pending_transaction(&mut self) -> Result<()> { + let tx = self.sidechain_wallet.transaction().into_diagnostic()?; + tx.execute("DELETE FROM transaction_inputs", ()) + .into_diagnostic()?; + tx.execute("DELETE FROM transaction_outputs", ()) + .into_diagnostic()?; + tx.commit().into_diagnostic()?; + Ok(()) + } + + pub async fn submit_pending_transaction(&mut self) -> Result<()> { + let (sidechain_number, ids_outpoints_values, outputs) = self.get_pending_transaction()?; + let inputs = ids_outpoints_values + .iter() + .map(|(_id, outpoint, _value)| outpoint) + .cloned() + .collect(); + let transaction = cusf_sidechain_types::Transaction { inputs, outputs }; + let transaction_bytes = bincode::serialize(&transaction).into_diagnostic()?; + + let sidechain_client = self.sidechain_clients.get_mut(&sidechain_number).unwrap(); + let request = SubmitTransactionRequest { + transaction: transaction_bytes, + }; + + sidechain_client + .submit_transaction(request) + .await + .into_diagnostic()?; + { + // FIXME: This is a crutch. + // TODO: Implement actual syncing of utxos from sidechain. + let tx = self.sidechain_wallet.transaction().into_diagnostic()?; + for (id, _outpoint, _value) in &ids_outpoints_values { + tx.execute("DELETE FROM utxos WHERE id = ?1", (id,)) + .into_diagnostic()?; + } + tx.commit().into_diagnostic()?; + } + self.clear_pending_transaction()?; + Ok(()) + } + + pub fn get_pending_transaction( + &self, + ) -> Result<( + u8, + Vec<(u64, cusf_sidechain_types::OutPoint, u64)>, + Vec, + )> { + let mut statement = self + .sidechain_wallet + .prepare( + "SELECT id, value, + transaction_number, transaction_output_number, + block_number, coinbase_output_number, + deposit_number + FROM utxos WHERE id IN (SELECT utxo_id FROM transaction_inputs)", + ) + .into_diagnostic()?; + let utxos: Vec<_> = statement + .query_map([], |row| { + let id: u64 = row.get(0)?; + let value: u64 = row.get(1)?; + let transaction_number: Option = row.get(2)?; + let transaction_output_number: Option = row.get(3)?; + let block_number: Option = row.get(4)?; + let coinbases_output_number: Option = row.get(5)?; + let deposit_number: Option = row.get(6)?; + Ok(( + id, + value, + transaction_number, + transaction_output_number, + block_number, + coinbases_output_number, + deposit_number, + )) + }) + .into_diagnostic()? + .collect(); + if utxos.is_empty() { + return Err(miette!("no pending transaction")); + } + let sidechain_number = { + let (utxo_id, _, _, _, _, _, _) = utxos[0].as_ref().unwrap(); + self.sidechain_wallet + .query_row( + "SELECT sidechain_number FROM utxos WHERE id = ?1", + [utxo_id], + |row| { + let sidechain_number: u8 = row.get(0)?; + Ok(sidechain_number) + }, + ) + .into_diagnostic()? + }; + + let mut ids_outpoints_values = vec![]; + for utxo in utxos { + let ( + id, + value, + transaction_number, + transaction_output_number, + block_number, + coinbase_output_number, + deposit_number, + ) = utxo.unwrap(); + let outpoint = match ( + transaction_number, + transaction_output_number, + block_number, + coinbase_output_number, + deposit_number, + ) { + (Some(transaction_number), Some(output_number), None, None, None) => { + cusf_sidechain_types::OutPoint::Regular { + transaction_number, + output_number, + } + } + (None, None, Some(block_number), Some(output_number), None) => { + cusf_sidechain_types::OutPoint::Coinbase { + block_number, + output_number, + } + } + (None, None, None, None, Some(sequence_number)) => { + cusf_sidechain_types::OutPoint::Deposit { sequence_number } + } + _ => { + todo!(); + } + }; + ids_outpoints_values.push((id, outpoint, value)); + } + + let mut statement = self + .sidechain_wallet + .prepare("SELECT id, address, value, main_address, main_fee FROM transaction_outputs") + .into_diagnostic()?; + + let raw_outputs: Vec<_> = statement + .query_map([], |row| { + let id: u64 = row.get(0)?; + let address: Vec = row.get(1)?; + let address: [u8; ADDRESS_LENGTH] = address.try_into().unwrap(); + let value: u64 = row.get(2)?; + let main_address: Option> = row.get(3)?; + let main_address: Option<[u8; MAIN_ADDRESS_LENGTH]> = + main_address.map(|main_address| main_address.try_into().unwrap()); + let main_fee: Option = row.get(4)?; + Ok((id, address, value, main_address, main_fee)) + }) + .into_diagnostic()? + .collect(); + + let mut outputs = vec![]; + + for raw_output in &raw_outputs { + let (_id, address, value, main_address, main_fee) = raw_output.as_ref().unwrap(); + let output = match (main_address, main_fee) { + (Some(main_address), Some(main_fee)) => cusf_sidechain_types::Output::Withdrawal { + address: *address, + main_address: *main_address, + value: *value, + fee: *main_fee, + }, + (None, None) => cusf_sidechain_types::Output::Regular { + address: *address, + value: *value, + }, + _ => return Err(miette!("invalid output in database")), + }; + outputs.push(output); + } + + Ok((sidechain_number, ids_outpoints_values, outputs)) + } + + pub async fn sync_side_utxos(&mut self, sidechain_number: u8) -> Result<()> { + let sidechain_client = self.sidechain_clients.get_mut(&sidechain_number).unwrap(); + let utxos = sidechain_client + .get_utxo_set(GetUtxoSetRequest {}) + .await + .into_diagnostic()? + .into_inner() + .utxos; + let utxos: HashMap = bincode::deserialize(&utxos).into_diagnostic()?; + let wallet_utxos = self.get_side_utxos(sidechain_number)?; + let tx = self.sidechain_wallet.transaction().into_diagnostic()?; + for (id, (outpoint, _key_index, _value, _main_fee)) in &wallet_utxos { + if !utxos.contains_key(outpoint) { + tx.execute("DELETE FROM utxos WHERE id = ?1", (id,)) + .into_diagnostic()?; + } + } + let wallet_addresses = { + let mut statement = tx + .prepare("SELECT address, key_index FROM keys WHERE sidechain_number = ?1") + .into_diagnostic()?; + let addresses: HashMap<[u8; ADDRESS_LENGTH], u32> = statement + .query_map([sidechain_number], |row| { + let address: Vec = row.get(0)?; + let address: [u8; ADDRESS_LENGTH] = address.try_into().unwrap(); + let key_index: u32 = row.get(1)?; + Ok((address, key_index)) + }) + .into_diagnostic()? + .map(|address| address.unwrap()) + .collect(); + addresses + }; + for (outpoint, output) in &utxos { + let address = output.address(); + if let Some(key_index) = wallet_addresses.get(&address) { + let (value, main_fee) = match output { + cusf_sidechain_types::Output::Withdrawal { value, fee, .. } => { + (value, Some(fee)) + } + cusf_sidechain_types::Output::Regular { value, .. } => (value, None), + }; + match outpoint { + cusf_sidechain_types::OutPoint::Regular { + transaction_number, + output_number, + } => { + tx.execute( + "INSERT OR IGNORE INTO utxos + (sidechain_number, + key_index, + value, + main_fee, + transaction_number, + transaction_output_number) + VALUES (?1, ?2, ?3, ?4, ?5, ?6);", + ( + sidechain_number, + key_index, + value, + main_fee, + transaction_number, + output_number, + ), + ) + .into_diagnostic()?; + } + cusf_sidechain_types::OutPoint::Deposit { sequence_number } => { + tx.execute( + "INSERT OR IGNORE INTO utxos + (sidechain_number, + key_index, + value, + main_fee, + deposit_number) + VALUES (?1, ?2, ?3, ?4, ?5)", + ( + sidechain_number, + key_index, + value, + main_fee, + sequence_number, + ), + ) + .into_diagnostic()?; + } + cusf_sidechain_types::OutPoint::Coinbase { + block_number, + output_number, + } => { + tx.execute( + "INSERT OR IGNORE INTO utxos + (sidechain_number, + key_index, + value, + main_fee, + block_number, + coinbase_output_number) + VALUES (?1, ?2, ?3, ?4, ?5, ?6)", + ( + sidechain_number, + key_index, + value, + main_fee, + block_number, + output_number, + ), + ) + .into_diagnostic()?; + } + } + } + let total_value = Amount::from_sat(output.total_value()); + let output_type = match output { + cusf_sidechain_types::Output::Regular { .. } => "regular", + cusf_sidechain_types::Output::Withdrawal { .. } => "withdrawal", + }; + println!("{outpoint} : {output_type} : {total_value}",); + } + tx.commit().into_diagnostic()?; + Ok(()) + } + + pub fn get_side_utxos( + &self, + sidechain_number: u8, + ) -> Result)>> { + let mut statement = self + .sidechain_wallet + .prepare( + "SELECT id, key_index, value, main_fee, + transaction_number, transaction_output_number, + block_number, coinbase_output_number, + deposit_number + FROM utxos WHERE sidechain_number = ?1", + ) + .into_diagnostic()?; + let utxos: Vec<_> = statement + .query_map([sidechain_number], |row| { + let id: u64 = row.get(0)?; + let key_index: u32 = row.get(1)?; + let value: u64 = row.get(2)?; + let main_fee: Option = row.get(3)?; + let transaction_number: Option = row.get(4)?; + let transaction_output_number: Option = row.get(5)?; + let block_number: Option = row.get(6)?; + let coinbases_output_number: Option = row.get(7)?; + let deposit_number: Option = row.get(8)?; + Ok(( + id, + key_index, + value, + main_fee, + transaction_number, + transaction_output_number, + block_number, + coinbases_output_number, + deposit_number, + )) + }) + .into_diagnostic()? + .collect(); + let mut id_to_outpoint_key_index_value_main_fee = BTreeMap::new(); + for utxo in utxos { + let ( + id, + key_index, + value, + main_fee, + transaction_number, + transaction_output_number, + block_number, + coinbase_output_number, + deposit_number, + ) = utxo.unwrap(); + let outpoint = match ( + transaction_number, + transaction_output_number, + block_number, + coinbase_output_number, + deposit_number, + ) { + (Some(transaction_number), Some(output_number), None, None, None) => { + cusf_sidechain_types::OutPoint::Regular { + transaction_number, + output_number, + } + } + (None, None, Some(block_number), Some(output_number), None) => { + cusf_sidechain_types::OutPoint::Coinbase { + block_number, + output_number, + } + } + (None, None, None, None, Some(sequence_number)) => { + cusf_sidechain_types::OutPoint::Deposit { sequence_number } + } + _ => { + todo!(); + } + }; + id_to_outpoint_key_index_value_main_fee + .insert(id, (outpoint, key_index, value, main_fee)); + } + Ok(id_to_outpoint_key_index_value_main_fee) + } + + pub async fn get_withdrawal_bundle( + &mut self, + sidechain_number: u8, + ) -> Result { + let sidechain_client = self.sidechain_clients.get_mut(&sidechain_number).unwrap(); + let bundle = sidechain_client + .get_withdrawal_bundle(GetWithdrawalBundleRequest {}) + .await + .into_diagnostic()? + .into_inner() + .bundle; + let mut cursor = Cursor::new(bundle); + let bundle = bitcoin::Transaction::consensus_decode(&mut cursor).into_diagnostic()?; + Ok(bundle) + } +} + +#[derive(Debug)] +pub struct Deposit { + pub sidechain_number: u8, + pub address: Vec, + pub amount: u64, + pub transaction: Transaction, +} + +#[derive(Debug)] +pub struct Sidechain { + pub sidechain_number: u8, + pub data: Vec, +} + +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use ureq_jsonrpc::{json, Client}; + +pub fn create_client(main_datadir: &Path) -> Result { + let auth = std::fs::read_to_string(main_datadir.join("regtest/.cookie")).into_diagnostic()?; + let mut auth = auth.split(':'); + let user = auth + .next() + .ok_or(miette!("failed to get rpcuser"))? + .to_string(); + let password = auth + .next() + .ok_or(miette!("failed to get rpcpassword"))? + .to_string(); + Ok(Client { + host: "localhost".into(), + port: 18443, + user, + password, + id: "mainchain".into(), + }) +} + +use bdk::bitcoin::constants::SUBSIDY_HALVING_INTERVAL; +use bdk::bitcoin::{self, base58}; +use bitcoin::absolute::{Height, LockTime}; +use bitcoin::block::Version; +use bitcoin::consensus::Encodable; +use bitcoin::constants::genesis_block; +use bitcoin::hash_types::TxMerkleNode; +use bitcoin::hashes::Hash; +use bitcoin::opcodes::OP_0; +use bitcoin::{consensus::Decodable, Block}; +use bitcoin::{merkle_tree, BlockHash, ScriptBuf, Sequence, Transaction, TxIn, TxOut}; + +fn get_block_value(height: u32, fees: u64, network: Network) -> u64 { + let mut subsidy = 50 * Amount::ONE_BTC.to_sat(); + let subsidy_halving_interval = match network { + Network::Regtest => 150, + _ => SUBSIDY_HALVING_INTERVAL, + }; + let halvings = height / subsidy_halving_interval; + if halvings >= 64 { + fees + } else { + subsidy >>= halvings; + subsidy + fees + } +} + +#[derive(Debug)] +pub struct SidechainAck { + pub sidechain_number: u8, + pub data_hash: [u8; 32], +}