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], +}