mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 13:39:25 +00:00
Merge branch 'emissions' of https://github.com/akildemir/serai into block-emissions
This commit is contained in:
67
Cargo.lock
generated
67
Cargo.lock
generated
@@ -102,7 +102,7 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-consensus"
|
name = "alloy-consensus"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-eips",
|
"alloy-eips",
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
@@ -110,7 +110,6 @@ dependencies = [
|
|||||||
"alloy-serde",
|
"alloy-serde",
|
||||||
"c-kzg",
|
"c-kzg",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -125,7 +124,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-eips"
|
name = "alloy-eips"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"alloy-rlp",
|
"alloy-rlp",
|
||||||
@@ -133,23 +132,25 @@ dependencies = [
|
|||||||
"c-kzg",
|
"c-kzg",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sha2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-genesis"
|
name = "alloy-genesis"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"alloy-serde",
|
"alloy-serde",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-json-abi"
|
name = "alloy-json-abi"
|
||||||
version = "0.7.0"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83a35ddfd27576474322a5869e4c123e5f3e7b2177297c18e4e82ea501cb125b"
|
checksum = "786689872ec4e7d354810ab0dffd48bb40b838c047522eb031cbd47d15634849"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"alloy-sol-type-parser",
|
"alloy-sol-type-parser",
|
||||||
@@ -159,7 +160,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-json-rpc"
|
name = "alloy-json-rpc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -171,7 +172,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-network"
|
name = "alloy-network"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-consensus",
|
"alloy-consensus",
|
||||||
"alloy-eips",
|
"alloy-eips",
|
||||||
@@ -179,6 +180,7 @@ dependencies = [
|
|||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"alloy-rpc-types",
|
"alloy-rpc-types",
|
||||||
"alloy-signer",
|
"alloy-signer",
|
||||||
|
"alloy-sol-types",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"futures-utils-wasm",
|
"futures-utils-wasm",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@@ -187,7 +189,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-node-bindings"
|
name = "alloy-node-bindings"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-genesis",
|
"alloy-genesis",
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
@@ -201,9 +203,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-primitives"
|
name = "alloy-primitives"
|
||||||
version = "0.7.0"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "99bbad0a6b588ef4aec1b5ddbbfdacd9ef04e00b979617765b03174318ee1f3a"
|
checksum = "525448f6afc1b70dd0f9d0a8145631bf2f5e434678ab23ab18409ca264cae6b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-rlp",
|
"alloy-rlp",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -224,7 +226,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-provider"
|
name = "alloy-provider"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-eips",
|
"alloy-eips",
|
||||||
"alloy-json-rpc",
|
"alloy-json-rpc",
|
||||||
@@ -271,7 +273,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-rpc-client"
|
name = "alloy-rpc-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-json-rpc",
|
"alloy-json-rpc",
|
||||||
"alloy-transport",
|
"alloy-transport",
|
||||||
@@ -289,7 +291,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-rpc-types"
|
name = "alloy-rpc-types"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-consensus",
|
"alloy-consensus",
|
||||||
"alloy-eips",
|
"alloy-eips",
|
||||||
@@ -307,7 +309,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-rpc-types-trace"
|
name = "alloy-rpc-types-trace"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"alloy-rpc-types",
|
"alloy-rpc-types",
|
||||||
@@ -319,7 +321,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-serde"
|
name = "alloy-serde"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -329,7 +331,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-signer"
|
name = "alloy-signer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -352,9 +354,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-sol-macro"
|
name = "alloy-sol-macro"
|
||||||
version = "0.7.0"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "452d929748ac948a10481fff4123affead32c553cf362841c5103dd508bdfc16"
|
checksum = "89c80a2cb97e7aa48611cbb63950336f9824a174cdf670527cc6465078a26ea1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-json-abi",
|
"alloy-json-abi",
|
||||||
"alloy-sol-macro-input",
|
"alloy-sol-macro-input",
|
||||||
@@ -371,9 +373,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-sol-macro-input"
|
name = "alloy-sol-macro-input"
|
||||||
version = "0.7.0"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df64e094f6d2099339f9e82b5b38440b159757b6920878f28316243f8166c8d1"
|
checksum = "c58894b58ac50979eeac6249661991ac40b9d541830d9a725f7714cc9ef08c23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-json-abi",
|
"alloy-json-abi",
|
||||||
"const-hex",
|
"const-hex",
|
||||||
@@ -388,18 +390,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-sol-type-parser"
|
name = "alloy-sol-type-parser"
|
||||||
version = "0.7.0"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "715f4d09a330cc181fc7c361b5c5c2766408fa59a0bac60349dcb7baabd404cc"
|
checksum = "7da8e71ea68e780cc203919e03f69f59e7afe92d2696fb1dcb6662f61e4031b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winnow 0.6.6",
|
"winnow 0.6.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-sol-types"
|
name = "alloy-sol-types"
|
||||||
version = "0.7.0"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43bc2d6dfc2a19fd56644494479510f98b1ee929e04cf0d4aa45e98baa3e545b"
|
checksum = "399287f68d1081ed8b1f4903c49687658b95b142207d7cb4ae2f4813915343ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-json-abi",
|
"alloy-json-abi",
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
@@ -410,7 +412,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-transport"
|
name = "alloy-transport"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-json-rpc",
|
"alloy-json-rpc",
|
||||||
"base64 0.22.0",
|
"base64 0.22.0",
|
||||||
@@ -428,7 +430,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-transport-http"
|
name = "alloy-transport-http"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/alloy-rs/alloy?rev=037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6#037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6"
|
source = "git+https://github.com/alloy-rs/alloy?rev=b79db21734cffddc11753fe62ba571565c896f42#b79db21734cffddc11753fe62ba571565c896f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-transport",
|
"alloy-transport",
|
||||||
"url",
|
"url",
|
||||||
@@ -2123,7 +2125,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dockertest"
|
name = "dockertest"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/kayabaNerve/dockertest-rs?branch=arc#c0ea77997048f9edc9987984bbe20e43fac74e06"
|
source = "git+https://github.com/orcalabs/dockertest-rs?rev=4dd6ae24738aa6dc5c89444cc822ea4745517493#4dd6ae24738aa6dc5c89444cc822ea4745517493"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -2331,6 +2333,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy-consensus",
|
"alloy-consensus",
|
||||||
"alloy-core",
|
"alloy-core",
|
||||||
|
"alloy-network",
|
||||||
"alloy-node-bindings",
|
"alloy-node-bindings",
|
||||||
"alloy-provider",
|
"alloy-provider",
|
||||||
"alloy-rpc-client",
|
"alloy-rpc-client",
|
||||||
@@ -3790,7 +3793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-targets 0.48.5",
|
"windows-targets 0.52.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8123,11 +8126,13 @@ dependencies = [
|
|||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
"dkg",
|
"dkg",
|
||||||
"dockertest",
|
"dockertest",
|
||||||
|
"ethereum-serai",
|
||||||
"hex",
|
"hex",
|
||||||
"monero-serai",
|
"monero-serai",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"serai-client",
|
"serai-client",
|
||||||
|
"serai-db",
|
||||||
"serai-docker-tests",
|
"serai-docker-tests",
|
||||||
"serai-message-queue",
|
"serai-message-queue",
|
||||||
"serai-message-queue-tests",
|
"serai-message-queue-tests",
|
||||||
@@ -9401,9 +9406,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn-solidity"
|
name = "syn-solidity"
|
||||||
version = "0.7.0"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4497156948bd342b52038035a6fa514a89626e37af9d2c52a5e8d8ebcc7ee479"
|
checksum = "5aa0cefd02f532035d83cfec82647c6eb53140b0485220760e669f4bad489e36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"paste",
|
"paste",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ panic = "unwind"
|
|||||||
lazy_static = { git = "https://github.com/rust-lang-nursery/lazy-static.rs", rev = "5735630d46572f1e5377c8f2ba0f79d18f53b10c" }
|
lazy_static = { git = "https://github.com/rust-lang-nursery/lazy-static.rs", rev = "5735630d46572f1e5377c8f2ba0f79d18f53b10c" }
|
||||||
|
|
||||||
# Needed due to dockertest's usage of `Rc`s when we need `Arc`s
|
# Needed due to dockertest's usage of `Rc`s when we need `Arc`s
|
||||||
dockertest = { git = "https://github.com/kayabaNerve/dockertest-rs", branch = "arc" }
|
dockertest = { git = "https://github.com/orcalabs/dockertest-rs", rev = "4dd6ae24738aa6dc5c89444cc822ea4745517493" }
|
||||||
|
|
||||||
# wasmtime pulls in an old version for this
|
# wasmtime pulls in an old version for this
|
||||||
zstd = { path = "patches/zstd" }
|
zstd = { path = "patches/zstd" }
|
||||||
|
|||||||
@@ -29,18 +29,21 @@ frost = { package = "modular-frost", path = "../../crypto/frost", default-featur
|
|||||||
|
|
||||||
alloy-core = { version = "0.7", default-features = false }
|
alloy-core = { version = "0.7", default-features = false }
|
||||||
alloy-sol-types = { version = "0.7", default-features = false, features = ["json"] }
|
alloy-sol-types = { version = "0.7", default-features = false, features = ["json"] }
|
||||||
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6", default-features = false, features = ["k256"] }
|
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false, features = ["k256"] }
|
||||||
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6", default-features = false }
|
alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||||
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6", default-features = false }
|
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||||
|
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||||
alloy-simple-request-transport = { path = "./alloy-simple-request-transport", default-features = false }
|
alloy-simple-request-transport = { path = "./alloy-simple-request-transport", default-features = false }
|
||||||
alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6", default-features = false }
|
alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||||
|
|
||||||
|
alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false, features = ["tests"] }
|
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false, features = ["tests"] }
|
||||||
|
|
||||||
tokio = { version = "1", features = ["macros"] }
|
tokio = { version = "1", features = ["macros"] }
|
||||||
|
|
||||||
alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6", default-features = false }
|
alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tests = []
|
tests = ["alloy-node-bindings"]
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ tower = "0.4"
|
|||||||
serde_json = { version = "1", default-features = false }
|
serde_json = { version = "1", default-features = false }
|
||||||
simple-request = { path = "../../../common/request", default-features = false }
|
simple-request = { path = "../../../common/request", default-features = false }
|
||||||
|
|
||||||
alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6", default-features = false }
|
alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||||
alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6", default-features = false }
|
alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["tls"]
|
default = ["tls"]
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ pub fn address(point: &ProjectivePoint) -> [u8; 20] {
|
|||||||
keccak256(&encoded_point.as_ref()[1 .. 65])[12 ..].try_into().unwrap()
|
keccak256(&encoded_point.as_ref()[1 .. 65])[12 ..].try_into().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn deterministically_sign(tx: &TxLegacy) -> Signed<TxLegacy> {
|
/// Deterministically sign a transaction.
|
||||||
|
///
|
||||||
|
/// This function panics if passed a transaction with a non-None chain ID.
|
||||||
|
pub fn deterministically_sign(tx: &TxLegacy) -> Signed<TxLegacy> {
|
||||||
assert!(
|
assert!(
|
||||||
tx.chain_id.is_none(),
|
tx.chain_id.is_none(),
|
||||||
"chain ID was Some when deterministically signing a TX (causing a non-deterministic signer)"
|
"chain ID was Some when deterministically signing a TX (causing a non-deterministic signer)"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use alloy_core::primitives::{Address, B256, U256};
|
|||||||
|
|
||||||
use alloy_sol_types::{SolInterface, SolEvent};
|
use alloy_sol_types::{SolInterface, SolEvent};
|
||||||
|
|
||||||
use alloy_rpc_types::{BlockNumberOrTag, Filter};
|
use alloy_rpc_types::Filter;
|
||||||
use alloy_simple_request_transport::SimpleRequest;
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
use alloy_provider::{Provider, RootProvider};
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
@@ -25,22 +25,8 @@ pub struct TopLevelErc20Transfer {
|
|||||||
pub struct Erc20(Arc<RootProvider<SimpleRequest>>, Address);
|
pub struct Erc20(Arc<RootProvider<SimpleRequest>>, Address);
|
||||||
impl Erc20 {
|
impl Erc20 {
|
||||||
/// Construct a new view of the specified ERC20 contract.
|
/// Construct a new view of the specified ERC20 contract.
|
||||||
///
|
pub fn new(provider: Arc<RootProvider<SimpleRequest>>, address: [u8; 20]) -> Self {
|
||||||
/// This checks a contract is deployed at that address yet does not check the contract is
|
Self(provider, Address::from(&address))
|
||||||
/// actually an ERC20.
|
|
||||||
pub async fn new(
|
|
||||||
provider: Arc<RootProvider<SimpleRequest>>,
|
|
||||||
address: [u8; 20],
|
|
||||||
) -> Result<Option<Self>, Error> {
|
|
||||||
let code = provider
|
|
||||||
.get_code_at(address.into(), BlockNumberOrTag::Finalized.into())
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::ConnectionError)?;
|
|
||||||
// Contract has yet to be deployed
|
|
||||||
if code.is_empty() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Ok(Some(Self(provider.clone(), Address::from(&address))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn top_level_transfers(
|
pub async fn top_level_transfers(
|
||||||
@@ -65,7 +51,8 @@ impl Erc20 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tx_id = log.transaction_hash.ok_or(Error::ConnectionError)?;
|
let tx_id = log.transaction_hash.ok_or(Error::ConnectionError)?;
|
||||||
let tx = self.0.get_transaction_by_hash(tx_id).await.map_err(|_| Error::ConnectionError)?;
|
let tx =
|
||||||
|
self.0.get_transaction_by_hash(tx_id).await.ok().flatten().ok_or(Error::ConnectionError)?;
|
||||||
|
|
||||||
// If this is a top-level call...
|
// If this is a top-level call...
|
||||||
if tx.to == Some(self.1) {
|
if tx.to == Some(self.1) {
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub use alloy_core;
|
pub mod alloy {
|
||||||
pub use alloy_consensus;
|
pub use alloy_core::primitives;
|
||||||
|
pub use alloy_core as core;
|
||||||
|
pub use alloy_sol_types as sol_types;
|
||||||
|
|
||||||
pub use alloy_rpc_types;
|
pub use alloy_consensus as consensus;
|
||||||
pub use alloy_simple_request_transport;
|
pub use alloy_network as network;
|
||||||
pub use alloy_rpc_client;
|
pub use alloy_rpc_types as rpc_types;
|
||||||
pub use alloy_provider;
|
pub use alloy_simple_request_transport as simple_request_transport;
|
||||||
|
pub use alloy_rpc_client as rpc_client;
|
||||||
|
pub use alloy_provider as provider;
|
||||||
|
}
|
||||||
|
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
|
|
||||||
@@ -18,8 +23,8 @@ pub mod router;
|
|||||||
|
|
||||||
pub mod machine;
|
pub mod machine;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(any(test, feature = "tests"))]
|
||||||
mod tests;
|
pub mod tests;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
|||||||
@@ -159,11 +159,12 @@ impl Router {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub async fn serai_key(&self, at: [u8; 32]) -> Result<PublicKey, Error> {
|
pub async fn serai_key(&self, at: [u8; 32]) -> Result<PublicKey, Error> {
|
||||||
let call = TransactionRequest::default()
|
let call = TransactionRequest::default()
|
||||||
.to(Some(self.1))
|
.to(self.1)
|
||||||
.input(TransactionInput::new(abi::seraiKeyCall::new(()).abi_encode().into()));
|
.input(TransactionInput::new(abi::seraiKeyCall::new(()).abi_encode().into()));
|
||||||
let bytes = self
|
let bytes = self
|
||||||
.0
|
.0
|
||||||
.call(&call, Some(BlockId::Hash(B256::from(at).into())))
|
.call(&call)
|
||||||
|
.block(BlockId::Hash(B256::from(at).into()))
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::ConnectionError)?;
|
.map_err(|_| Error::ConnectionError)?;
|
||||||
let res =
|
let res =
|
||||||
@@ -197,11 +198,12 @@ impl Router {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub async fn nonce(&self, at: [u8; 32]) -> Result<U256, Error> {
|
pub async fn nonce(&self, at: [u8; 32]) -> Result<U256, Error> {
|
||||||
let call = TransactionRequest::default()
|
let call = TransactionRequest::default()
|
||||||
.to(Some(self.1))
|
.to(self.1)
|
||||||
.input(TransactionInput::new(abi::nonceCall::new(()).abi_encode().into()));
|
.input(TransactionInput::new(abi::nonceCall::new(()).abi_encode().into()));
|
||||||
let bytes = self
|
let bytes = self
|
||||||
.0
|
.0
|
||||||
.call(&call, Some(BlockId::Hash(B256::from(at).into())))
|
.call(&call)
|
||||||
|
.block(BlockId::Hash(B256::from(at).into()))
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::ConnectionError)?;
|
.map_err(|_| Error::ConnectionError)?;
|
||||||
let res =
|
let res =
|
||||||
@@ -229,10 +231,13 @@ impl Router {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn key_at_end_of_block(&self, block: u64) -> Result<ProjectivePoint, Error> {
|
pub async fn key_at_end_of_block(&self, block: u64) -> Result<Option<ProjectivePoint>, Error> {
|
||||||
let filter = Filter::new().from_block(0).to_block(block).address(self.1);
|
let filter = Filter::new().from_block(0).to_block(block).address(self.1);
|
||||||
let filter = filter.event_signature(SeraiKeyUpdated::SIGNATURE_HASH);
|
let filter = filter.event_signature(SeraiKeyUpdated::SIGNATURE_HASH);
|
||||||
let all_keys = self.0.get_logs(&filter).await.map_err(|_| Error::ConnectionError)?;
|
let all_keys = self.0.get_logs(&filter).await.map_err(|_| Error::ConnectionError)?;
|
||||||
|
if all_keys.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
let last_key_x_coordinate_log = all_keys.last().ok_or(Error::ConnectionError)?;
|
let last_key_x_coordinate_log = all_keys.last().ok_or(Error::ConnectionError)?;
|
||||||
let last_key_x_coordinate = last_key_x_coordinate_log
|
let last_key_x_coordinate = last_key_x_coordinate_log
|
||||||
@@ -246,7 +251,9 @@ impl Router {
|
|||||||
compressed_point[0] = u8::from(sec1::Tag::CompressedEvenY);
|
compressed_point[0] = u8::from(sec1::Tag::CompressedEvenY);
|
||||||
compressed_point[1 ..].copy_from_slice(last_key_x_coordinate.as_slice());
|
compressed_point[1 ..].copy_from_slice(last_key_x_coordinate.as_slice());
|
||||||
|
|
||||||
Option::from(ProjectivePoint::from_bytes(&compressed_point)).ok_or(Error::ConnectionError)
|
let key =
|
||||||
|
Option::from(ProjectivePoint::from_bytes(&compressed_point)).ok_or(Error::ConnectionError)?;
|
||||||
|
Ok(Some(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn in_instructions(
|
pub async fn in_instructions(
|
||||||
@@ -254,7 +261,9 @@ impl Router {
|
|||||||
block: u64,
|
block: u64,
|
||||||
allowed_tokens: &HashSet<[u8; 20]>,
|
allowed_tokens: &HashSet<[u8; 20]>,
|
||||||
) -> Result<Vec<InInstruction>, Error> {
|
) -> Result<Vec<InInstruction>, Error> {
|
||||||
let key_at_end_of_block = self.key_at_end_of_block(block).await?;
|
let Some(key_at_end_of_block) = self.key_at_end_of_block(block).await? else {
|
||||||
|
return Ok(vec![]);
|
||||||
|
};
|
||||||
|
|
||||||
let filter = Filter::new().from_block(block).to_block(block).address(self.1);
|
let filter = Filter::new().from_block(block).to_block(block).address(self.1);
|
||||||
let filter = filter.event_signature(InInstructionEvent::SIGNATURE_HASH);
|
let filter = filter.event_signature(InInstructionEvent::SIGNATURE_HASH);
|
||||||
@@ -274,7 +283,13 @@ impl Router {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx_hash = log.transaction_hash.ok_or(Error::ConnectionError)?;
|
let tx_hash = log.transaction_hash.ok_or(Error::ConnectionError)?;
|
||||||
let tx = self.0.get_transaction_by_hash(tx_hash).await.map_err(|_| Error::ConnectionError)?;
|
let tx = self
|
||||||
|
.0
|
||||||
|
.get_transaction_by_hash(tx_hash)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.ok_or(Error::ConnectionError)?;
|
||||||
|
|
||||||
let log =
|
let log =
|
||||||
log.log_decode::<InInstructionEvent>().map_err(|_| Error::ConnectionError)?.inner.data;
|
log.log_decode::<InInstructionEvent>().map_err(|_| Error::ConnectionError)?.inner.data;
|
||||||
|
|||||||
@@ -11,16 +11,20 @@ use alloy_core::{
|
|||||||
};
|
};
|
||||||
use alloy_consensus::{SignableTransaction, TxLegacy};
|
use alloy_consensus::{SignableTransaction, TxLegacy};
|
||||||
|
|
||||||
use alloy_rpc_types::TransactionReceipt;
|
use alloy_rpc_types::{BlockNumberOrTag, TransactionReceipt};
|
||||||
use alloy_simple_request_transport::SimpleRequest;
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
use alloy_provider::{Provider, RootProvider};
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
use crate::crypto::{address, deterministically_sign, PublicKey};
|
use crate::crypto::{address, deterministically_sign, PublicKey};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
mod crypto;
|
mod crypto;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
mod abi;
|
mod abi;
|
||||||
|
#[cfg(test)]
|
||||||
mod schnorr;
|
mod schnorr;
|
||||||
|
#[cfg(test)]
|
||||||
mod router;
|
mod router;
|
||||||
|
|
||||||
pub fn key_gen() -> (HashMap<Participant, ThresholdKeys<Secp256k1>>, PublicKey) {
|
pub fn key_gen() -> (HashMap<Participant, ThresholdKeys<Secp256k1>>, PublicKey) {
|
||||||
@@ -53,14 +57,15 @@ pub async fn send(
|
|||||||
// let chain_id = provider.get_chain_id().await.unwrap();
|
// let chain_id = provider.get_chain_id().await.unwrap();
|
||||||
// tx.chain_id = Some(chain_id);
|
// tx.chain_id = Some(chain_id);
|
||||||
tx.chain_id = None;
|
tx.chain_id = None;
|
||||||
tx.nonce = provider.get_transaction_count(address, None).await.unwrap();
|
tx.nonce =
|
||||||
|
provider.get_transaction_count(address, BlockNumberOrTag::Latest.into()).await.unwrap();
|
||||||
// 100 gwei
|
// 100 gwei
|
||||||
tx.gas_price = 100_000_000_000u128;
|
tx.gas_price = 100_000_000_000u128;
|
||||||
|
|
||||||
let sig = wallet.sign_prehash_recoverable(tx.signature_hash().as_ref()).unwrap();
|
let sig = wallet.sign_prehash_recoverable(tx.signature_hash().as_ref()).unwrap();
|
||||||
assert_eq!(address, tx.clone().into_signed(sig.into()).recover_signer().unwrap());
|
assert_eq!(address, tx.clone().into_signed(sig.into()).recover_signer().unwrap());
|
||||||
assert!(
|
assert!(
|
||||||
provider.get_balance(address, None).await.unwrap() >
|
provider.get_balance(address, BlockNumberOrTag::Latest.into()).await.unwrap() >
|
||||||
((U256::from(tx.gas_price) * U256::from(tx.gas_limit)) + tx.value)
|
((U256::from(tx.gas_price) * U256::from(tx.gas_limit)) + tx.value)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -56,12 +56,12 @@ pub async fn call_verify(
|
|||||||
let px: [u8; 32] = public_key.px.to_repr().into();
|
let px: [u8; 32] = public_key.px.to_repr().into();
|
||||||
let c_bytes: [u8; 32] = signature.c.to_repr().into();
|
let c_bytes: [u8; 32] = signature.c.to_repr().into();
|
||||||
let s_bytes: [u8; 32] = signature.s.to_repr().into();
|
let s_bytes: [u8; 32] = signature.s.to_repr().into();
|
||||||
let call = TransactionRequest::default().to(Some(contract)).input(TransactionInput::new(
|
let call = TransactionRequest::default().to(contract).input(TransactionInput::new(
|
||||||
abi::verifyCall::new((px.into(), message.to_vec().into(), c_bytes.into(), s_bytes.into()))
|
abi::verifyCall::new((px.into(), message.to_vec().into(), c_bytes.into(), s_bytes.into()))
|
||||||
.abi_encode()
|
.abi_encode()
|
||||||
.into(),
|
.into(),
|
||||||
));
|
));
|
||||||
let bytes = provider.call(&call, None).await.map_err(|_| Error::ConnectionError)?;
|
let bytes = provider.call(&call).await.map_err(|_| Error::ConnectionError)?;
|
||||||
let res =
|
let res =
|
||||||
abi::verifyCall::abi_decode_returns(&bytes, true).map_err(|_| Error::ConnectionError)?;
|
abi::verifyCall::abi_decode_returns(&bytes, true).map_err(|_| Error::ConnectionError)?;
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ impl Client {
|
|||||||
fn connector() -> Connector {
|
fn connector() -> Connector {
|
||||||
let mut res = HttpConnector::new();
|
let mut res = HttpConnector::new();
|
||||||
res.set_keepalive(Some(core::time::Duration::from_secs(60)));
|
res.set_keepalive(Some(core::time::Duration::from_secs(60)));
|
||||||
|
res.set_nodelay(true);
|
||||||
|
res.set_reuse_address(true);
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
let res = HttpsConnectorBuilder::new()
|
let res = HttpsConnectorBuilder::new()
|
||||||
.with_native_roots()
|
.with_native_roots()
|
||||||
@@ -68,7 +70,9 @@ impl Client {
|
|||||||
pub fn with_connection_pool() -> Client {
|
pub fn with_connection_pool() -> Client {
|
||||||
Client {
|
Client {
|
||||||
connection: Connection::ConnectionPool(
|
connection: Connection::ConnectionPool(
|
||||||
HyperClient::builder(TokioExecutor::new()).build(Self::connector()),
|
HyperClient::builder(TokioExecutor::new())
|
||||||
|
.pool_idle_timeout(core::time::Duration::from_secs(60))
|
||||||
|
.build(Self::connector()),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,5 +104,5 @@ allow-git = [
|
|||||||
"https://github.com/serai-dex/substrate",
|
"https://github.com/serai-dex/substrate",
|
||||||
"https://github.com/alloy-rs/alloy",
|
"https://github.com/alloy-rs/alloy",
|
||||||
"https://github.com/monero-rs/base58-monero",
|
"https://github.com/monero-rs/base58-monero",
|
||||||
"https://github.com/kayabaNerve/dockertest-rs",
|
"https://github.com/orcalabs/dockertest-rs",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
~/.foundry/bin/anvil --no-mining --slots-in-an-epoch 32
|
~/.foundry/bin/anvil --host 0.0.0.0 --no-cors --no-mining --slots-in-an-epoch 32 --silent
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ serai-docker-tests = { path = "../tests/docker" }
|
|||||||
secp256k1 = ["k256", "frost/secp256k1"]
|
secp256k1 = ["k256", "frost/secp256k1"]
|
||||||
bitcoin = ["dep:secp256k1", "secp256k1", "bitcoin-serai", "serai-client/bitcoin"]
|
bitcoin = ["dep:secp256k1", "secp256k1", "bitcoin-serai", "serai-client/bitcoin"]
|
||||||
|
|
||||||
ethereum = ["secp256k1", "ethereum-serai"]
|
ethereum = ["secp256k1", "ethereum-serai/tests"]
|
||||||
|
|
||||||
ed25519 = ["dalek-ff-group", "frost/ed25519"]
|
ed25519 = ["dalek-ff-group", "frost/ed25519"]
|
||||||
monero = ["ed25519", "monero-serai", "serai-client/monero"]
|
monero = ["ed25519", "monero-serai", "serai-client/monero"]
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ impl<N: Network<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
|
|||||||
assert!(self.coins.contains(&utxo.balance().coin));
|
assert!(self.coins.contains(&utxo.balance().coin));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut nonce = LastNonce::get(txn).map_or(0, |nonce| nonce + 1);
|
let mut nonce = LastNonce::get(txn).map_or(1, |nonce| nonce + 1);
|
||||||
let mut plans = vec![];
|
let mut plans = vec![];
|
||||||
for chunk in payments.as_slice().chunks(N::MAX_OUTPUTS) {
|
for chunk in payments.as_slice().chunks(N::MAX_OUTPUTS) {
|
||||||
// Once we rotate, all further payments should be scheduled via the new multisig
|
// Once we rotate, all further payments should be scheduled via the new multisig
|
||||||
@@ -179,7 +179,7 @@ impl<N: Network<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
|
|||||||
.and_then(|key_bytes| <N::Curve as Ciphersuite>::read_G(&mut key_bytes.as_slice()).ok())
|
.and_then(|key_bytes| <N::Curve as Ciphersuite>::read_G(&mut key_bytes.as_slice()).ok())
|
||||||
.unwrap_or(self.key);
|
.unwrap_or(self.key);
|
||||||
|
|
||||||
let nonce = LastNonce::get(txn).map_or(0, |nonce| nonce + 1);
|
let nonce = LastNonce::get(txn).map_or(1, |nonce| nonce + 1);
|
||||||
LastNonce::set(txn, &(nonce + 1));
|
LastNonce::set(txn, &(nonce + 1));
|
||||||
Plan {
|
Plan {
|
||||||
key: current_key,
|
key: current_key,
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ use ciphersuite::{group::GroupEncoding, Ciphersuite, Secp256k1};
|
|||||||
use frost::ThresholdKeys;
|
use frost::ThresholdKeys;
|
||||||
|
|
||||||
use ethereum_serai::{
|
use ethereum_serai::{
|
||||||
alloy_core::primitives::U256,
|
alloy::{
|
||||||
alloy_rpc_types::{BlockNumberOrTag, Transaction},
|
primitives::U256,
|
||||||
alloy_simple_request_transport::SimpleRequest,
|
rpc_types::{BlockNumberOrTag, Transaction},
|
||||||
alloy_rpc_client::ClientBuilder,
|
simple_request_transport::SimpleRequest,
|
||||||
alloy_provider::{Provider, RootProvider},
|
rpc_client::ClientBuilder,
|
||||||
|
provider::{Provider, RootProvider},
|
||||||
|
},
|
||||||
crypto::{PublicKey, Signature},
|
crypto::{PublicKey, Signature},
|
||||||
erc20::Erc20,
|
erc20::Erc20,
|
||||||
deployer::Deployer,
|
deployer::Deployer,
|
||||||
@@ -23,7 +25,7 @@ use ethereum_serai::{
|
|||||||
machine::*,
|
machine::*,
|
||||||
};
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use ethereum_serai::alloy_core::primitives::B256;
|
use ethereum_serai::alloy::primitives::B256;
|
||||||
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
time::sleep,
|
time::sleep,
|
||||||
@@ -112,7 +114,7 @@ impl TryInto<Vec<u8>> for Address {
|
|||||||
|
|
||||||
impl fmt::Display for Address {
|
impl fmt::Display for Address {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
ethereum_serai::alloy_core::primitives::Address::from(self.0).fmt(f)
|
ethereum_serai::alloy::primitives::Address::from(self.0).fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +126,7 @@ impl SignableTransaction for RouterCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<D: fmt::Debug + Db> TransactionTrait<Ethereum<D>> for Transaction {
|
impl<D: Db> TransactionTrait<Ethereum<D>> for Transaction {
|
||||||
type Id = [u8; 32];
|
type Id = [u8; 32];
|
||||||
fn id(&self) -> Self::Id {
|
fn id(&self) -> Self::Id {
|
||||||
self.hash.0
|
self.hash.0
|
||||||
@@ -157,7 +159,7 @@ impl Epoch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<D: fmt::Debug + Db> Block<Ethereum<D>> for Epoch {
|
impl<D: Db> Block<Ethereum<D>> for Epoch {
|
||||||
type Id = [u8; 32];
|
type Id = [u8; 32];
|
||||||
fn id(&self) -> [u8; 32] {
|
fn id(&self) -> [u8; 32] {
|
||||||
self.end_hash
|
self.end_hash
|
||||||
@@ -170,7 +172,7 @@ impl<D: fmt::Debug + Db> Block<Ethereum<D>> for Epoch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: fmt::Debug + Db> Output<Ethereum<D>> for EthereumInInstruction {
|
impl<D: Db> Output<Ethereum<D>> for EthereumInInstruction {
|
||||||
type Id = [u8; 32];
|
type Id = [u8; 32];
|
||||||
|
|
||||||
fn kind(&self) -> OutputType {
|
fn kind(&self) -> OutputType {
|
||||||
@@ -181,7 +183,7 @@ impl<D: fmt::Debug + Db> Output<Ethereum<D>> for EthereumInInstruction {
|
|||||||
let mut id = [0; 40];
|
let mut id = [0; 40];
|
||||||
id[.. 32].copy_from_slice(&self.id.0);
|
id[.. 32].copy_from_slice(&self.id.0);
|
||||||
id[32 ..].copy_from_slice(&self.id.1.to_le_bytes());
|
id[32 ..].copy_from_slice(&self.id.1.to_le_bytes());
|
||||||
*ethereum_serai::alloy_core::primitives::keccak256(id)
|
*ethereum_serai::alloy::primitives::keccak256(id)
|
||||||
}
|
}
|
||||||
fn tx_id(&self) -> [u8; 32] {
|
fn tx_id(&self) -> [u8; 32] {
|
||||||
self.id.0
|
self.id.0
|
||||||
@@ -282,8 +284,8 @@ impl EventualityTrait for Eventuality {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct Ethereum<D: fmt::Debug + Db> {
|
pub struct Ethereum<D: Db> {
|
||||||
// This DB is solely used to access the first key generated, as needed to determine the Router's
|
// This DB is solely used to access the first key generated, as needed to determine the Router's
|
||||||
// address. Accordingly, all methods present are consistent to a Serai chain with a finalized
|
// address. Accordingly, all methods present are consistent to a Serai chain with a finalized
|
||||||
// first key (regardless of local state), and this is safe.
|
// first key (regardless of local state), and this is safe.
|
||||||
@@ -292,20 +294,26 @@ pub struct Ethereum<D: fmt::Debug + Db> {
|
|||||||
deployer: Deployer,
|
deployer: Deployer,
|
||||||
router: Arc<RwLock<Option<Router>>>,
|
router: Arc<RwLock<Option<Router>>>,
|
||||||
}
|
}
|
||||||
impl<D: fmt::Debug + Db> PartialEq for Ethereum<D> {
|
impl<D: Db> PartialEq for Ethereum<D> {
|
||||||
fn eq(&self, _other: &Ethereum<D>) -> bool {
|
fn eq(&self, _other: &Ethereum<D>) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<D: fmt::Debug + Db> Ethereum<D> {
|
impl<D: Db> fmt::Debug for Ethereum<D> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt
|
||||||
|
.debug_struct("Ethereum")
|
||||||
|
.field("deployer", &self.deployer)
|
||||||
|
.field("router", &self.router)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<D: Db> Ethereum<D> {
|
||||||
pub async fn new(db: D, url: String) -> Self {
|
pub async fn new(db: D, url: String) -> Self {
|
||||||
let provider = Arc::new(RootProvider::new(
|
let provider = Arc::new(RootProvider::new(
|
||||||
ClientBuilder::default().transport(SimpleRequest::new(url), true),
|
ClientBuilder::default().transport(SimpleRequest::new(url), true),
|
||||||
));
|
));
|
||||||
|
|
||||||
#[cfg(test)] // TODO: Move to test code
|
|
||||||
provider.raw_request::<_, ()>("evm_setAutomine".into(), false).await.unwrap();
|
|
||||||
|
|
||||||
let mut deployer = Deployer::new(provider.clone()).await;
|
let mut deployer = Deployer::new(provider.clone()).await;
|
||||||
while !matches!(deployer, Ok(Some(_))) {
|
while !matches!(deployer, Ok(Some(_))) {
|
||||||
log::error!("Deployer wasn't deployed yet or networking error");
|
log::error!("Deployer wasn't deployed yet or networking error");
|
||||||
@@ -362,7 +370,7 @@ impl<D: fmt::Debug + Db> Ethereum<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
impl<D: Db> Network for Ethereum<D> {
|
||||||
type Curve = Secp256k1;
|
type Curve = Secp256k1;
|
||||||
|
|
||||||
type Transaction = Transaction;
|
type Transaction = Transaction;
|
||||||
@@ -479,7 +487,8 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
|||||||
// Grab the key at the end of the epoch
|
// Grab the key at the end of the epoch
|
||||||
let key_at_end_of_block = loop {
|
let key_at_end_of_block = loop {
|
||||||
match router.key_at_end_of_block(block.start + 31).await {
|
match router.key_at_end_of_block(block.start + 31).await {
|
||||||
Ok(key) => break key,
|
Ok(Some(key)) => break key,
|
||||||
|
Ok(None) => return vec![],
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("couldn't connect to router for the key at the end of the block: {e:?}");
|
log::error!("couldn't connect to router for the key at the end of the block: {e:?}");
|
||||||
sleep(Duration::from_secs(5)).await;
|
sleep(Duration::from_secs(5)).await;
|
||||||
@@ -491,17 +500,7 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
|||||||
let mut all_events = vec![];
|
let mut all_events = vec![];
|
||||||
let mut top_level_txids = HashSet::new();
|
let mut top_level_txids = HashSet::new();
|
||||||
for erc20_addr in [DAI] {
|
for erc20_addr in [DAI] {
|
||||||
let erc20 = loop {
|
let erc20 = Erc20::new(self.provider.clone(), erc20_addr);
|
||||||
let Ok(Some(erc20)) = Erc20::new(self.provider.clone(), erc20_addr).await else {
|
|
||||||
log::error!(
|
|
||||||
"couldn't connect to Ethereum node for an ERC20: {}",
|
|
||||||
hex::encode(erc20_addr)
|
|
||||||
);
|
|
||||||
sleep(Duration::from_secs(5)).await;
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
break erc20;
|
|
||||||
};
|
|
||||||
|
|
||||||
for block in block.start .. (block.start + 32) {
|
for block in block.start .. (block.start + 32) {
|
||||||
let transfers = loop {
|
let transfers = loop {
|
||||||
@@ -722,22 +721,6 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
|||||||
// Publish this using a dummy account we fund with magic RPC commands
|
// Publish this using a dummy account we fund with magic RPC commands
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
{
|
{
|
||||||
use rand_core::OsRng;
|
|
||||||
use ciphersuite::group::ff::Field;
|
|
||||||
|
|
||||||
let key = <Secp256k1 as Ciphersuite>::F::random(&mut OsRng);
|
|
||||||
let address = ethereum_serai::crypto::address(&(Secp256k1::generator() * key));
|
|
||||||
|
|
||||||
// Set a 1.1 ETH balance
|
|
||||||
self
|
|
||||||
.provider
|
|
||||||
.raw_request::<_, ()>(
|
|
||||||
"anvil_setBalance".into(),
|
|
||||||
[Address(address).to_string(), "1100000000000000000".into()],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let router = self.router().await;
|
let router = self.router().await;
|
||||||
let router = router.as_ref().unwrap();
|
let router = router.as_ref().unwrap();
|
||||||
|
|
||||||
@@ -750,17 +733,30 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
|||||||
completion.signature(),
|
completion.signature(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
tx.gas_price = 100_000_000_000u128;
|
tx.gas_limit = 1_000_000u64.into();
|
||||||
|
tx.gas_price = 1_000_000_000u64.into();
|
||||||
|
let tx = ethereum_serai::crypto::deterministically_sign(&tx);
|
||||||
|
|
||||||
use ethereum_serai::alloy_consensus::SignableTransaction;
|
if self.provider.get_transaction_by_hash(*tx.hash()).await.unwrap().is_none() {
|
||||||
let sig =
|
self
|
||||||
k256::ecdsa::SigningKey::from(k256::elliptic_curve::NonZeroScalar::new(key).unwrap())
|
.provider
|
||||||
.sign_prehash_recoverable(tx.signature_hash().as_ref())
|
.raw_request::<_, ()>(
|
||||||
|
"anvil_setBalance".into(),
|
||||||
|
[
|
||||||
|
tx.recover_signer().unwrap().to_string(),
|
||||||
|
(U256::from(tx.tx().gas_limit) * U256::from(tx.tx().gas_price)).to_string(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut bytes = vec![];
|
let (tx, sig, _) = tx.into_parts();
|
||||||
tx.encode_with_signature_fields(&sig.into(), &mut bytes);
|
let mut bytes = vec![];
|
||||||
let _ = self.provider.send_raw_transaction(&bytes).await.ok().unwrap();
|
tx.encode_with_signature_fields(&sig, &mut bytes);
|
||||||
|
let pending_tx = self.provider.send_raw_transaction(&bytes).await.unwrap();
|
||||||
|
self.mine_block().await;
|
||||||
|
assert!(pending_tx.get_receipt().await.unwrap().status());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -804,46 +800,62 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
|||||||
block: usize,
|
block: usize,
|
||||||
eventuality: &Self::Eventuality,
|
eventuality: &Self::Eventuality,
|
||||||
) -> Self::Transaction {
|
) -> Self::Transaction {
|
||||||
match eventuality.1 {
|
// We mine 96 blocks to ensure the 32 blocks relevant are finalized
|
||||||
RouterCommand::UpdateSeraiKey { nonce, .. } | RouterCommand::Execute { nonce, .. } => {
|
// Back-check the prior two epochs in response to this
|
||||||
let router = self.router().await;
|
// TODO: Review why this is sub(3) and not sub(2)
|
||||||
let router = router.as_ref().unwrap();
|
for block in block.saturating_sub(3) ..= block {
|
||||||
|
match eventuality.1 {
|
||||||
|
RouterCommand::UpdateSeraiKey { nonce, .. } | RouterCommand::Execute { nonce, .. } => {
|
||||||
|
let router = self.router().await;
|
||||||
|
let router = router.as_ref().unwrap();
|
||||||
|
|
||||||
let block = u64::try_from(block).unwrap();
|
let block = u64::try_from(block).unwrap();
|
||||||
let filter = router
|
let filter = router
|
||||||
.key_updated_filter()
|
.key_updated_filter()
|
||||||
.from_block(block * 32)
|
.from_block(block * 32)
|
||||||
.to_block(((block + 1) * 32) - 1)
|
.to_block(((block + 1) * 32) - 1)
|
||||||
.topic1(nonce);
|
.topic1(nonce);
|
||||||
let logs = self.provider.get_logs(&filter).await.unwrap();
|
let logs = self.provider.get_logs(&filter).await.unwrap();
|
||||||
if let Some(log) = logs.first() {
|
if let Some(log) = logs.first() {
|
||||||
|
return self
|
||||||
|
.provider
|
||||||
|
.get_transaction_by_hash(log.clone().transaction_hash.unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
let filter = router
|
||||||
|
.executed_filter()
|
||||||
|
.from_block(block * 32)
|
||||||
|
.to_block(((block + 1) * 32) - 1)
|
||||||
|
.topic1(nonce);
|
||||||
|
let logs = self.provider.get_logs(&filter).await.unwrap();
|
||||||
|
if logs.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
return self
|
return self
|
||||||
.provider
|
.provider
|
||||||
.get_transaction_by_hash(log.clone().transaction_hash.unwrap())
|
.get_transaction_by_hash(logs[0].transaction_hash.unwrap())
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
};
|
}
|
||||||
|
|
||||||
let filter = router
|
|
||||||
.executed_filter()
|
|
||||||
.from_block(block * 32)
|
|
||||||
.to_block(((block + 1) * 32) - 1)
|
|
||||||
.topic1(nonce);
|
|
||||||
let logs = self.provider.get_logs(&filter).await.unwrap();
|
|
||||||
self.provider.get_transaction_by_hash(logs[0].transaction_hash.unwrap()).await.unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
panic!("couldn't find completion in any three of checked blocks");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
async fn mine_block(&self) {
|
async fn mine_block(&self) {
|
||||||
self.provider.raw_request::<_, ()>("anvil_mine".into(), [32]).await.unwrap();
|
self.provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
async fn test_send(&self, send_to: Self::Address) -> Self::Block {
|
async fn test_send(&self, send_to: Self::Address) -> Self::Block {
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
use ciphersuite::group::ff::Field;
|
use ciphersuite::group::ff::Field;
|
||||||
|
use ethereum_serai::alloy::sol_types::SolCall;
|
||||||
|
|
||||||
let key = <Secp256k1 as Ciphersuite>::F::random(&mut OsRng);
|
let key = <Secp256k1 as Ciphersuite>::F::random(&mut OsRng);
|
||||||
let address = ethereum_serai::crypto::address(&(Secp256k1::generator() * key));
|
let address = ethereum_serai::crypto::address(&(Secp256k1::generator() * key));
|
||||||
@@ -858,18 +870,25 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let tx = ethereum_serai::alloy_consensus::TxLegacy {
|
let value = U256::from_str_radix("1000000000000000000", 10).unwrap();
|
||||||
|
let tx = ethereum_serai::alloy::consensus::TxLegacy {
|
||||||
chain_id: None,
|
chain_id: None,
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
gas_price: 100_000_000_000u128,
|
gas_price: 1_000_000_000u128,
|
||||||
gas_limit: 21_0000u128,
|
gas_limit: 200_000u128,
|
||||||
to: ethereum_serai::alloy_core::primitives::TxKind::Call(send_to.0.into()),
|
to: ethereum_serai::alloy::primitives::TxKind::Call(send_to.0.into()),
|
||||||
// 1 ETH
|
// 1 ETH
|
||||||
value: U256::from_str_radix("1000000000000000000", 10).unwrap(),
|
value,
|
||||||
input: vec![].into(),
|
input: ethereum_serai::router::abi::inInstructionCall::new((
|
||||||
|
[0; 20].into(),
|
||||||
|
value,
|
||||||
|
vec![].into(),
|
||||||
|
))
|
||||||
|
.abi_encode()
|
||||||
|
.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
use ethereum_serai::alloy_consensus::SignableTransaction;
|
use ethereum_serai::alloy::consensus::SignableTransaction;
|
||||||
let sig = k256::ecdsa::SigningKey::from(k256::elliptic_curve::NonZeroScalar::new(key).unwrap())
|
let sig = k256::ecdsa::SigningKey::from(k256::elliptic_curve::NonZeroScalar::new(key).unwrap())
|
||||||
.sign_prehash_recoverable(tx.signature_hash().as_ref())
|
.sign_prehash_recoverable(tx.signature_hash().as_ref())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -432,9 +432,12 @@ pub trait Network: 'static + Send + Sync + Clone + PartialEq + Debug {
|
|||||||
|
|
||||||
let plan_id = plan.id();
|
let plan_id = plan.id();
|
||||||
let Plan { key, inputs, mut payments, change, scheduler_addendum } = plan;
|
let Plan { key, inputs, mut payments, change, scheduler_addendum } = plan;
|
||||||
let theoretical_change_amount =
|
let theoretical_change_amount = if change.is_some() {
|
||||||
inputs.iter().map(|input| input.balance().amount.0).sum::<u64>() -
|
inputs.iter().map(|input| input.balance().amount.0).sum::<u64>() -
|
||||||
payments.iter().map(|payment| payment.balance.amount.0).sum::<u64>();
|
payments.iter().map(|payment| payment.balance.amount.0).sum::<u64>()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
let Some(tx_fee) = self.needed_fee(block_number, &inputs, &payments, &change).await? else {
|
let Some(tx_fee) = self.needed_fee(block_number, &inputs, &payments, &change).await? else {
|
||||||
// This Plan is not fulfillable
|
// This Plan is not fulfillable
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use core::time::Duration;
|
use core::{time::Duration, pin::Pin, future::Future};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
@@ -82,8 +82,9 @@ async fn spend<N: UtxoNetwork, D: Db>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn test_addresses<N: UtxoNetwork>(network: N)
|
pub async fn test_addresses<N: UtxoNetwork>(
|
||||||
where
|
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||||
|
) where
|
||||||
<N::Scheduler as Scheduler<N>>::Addendum: From<()>,
|
<N::Scheduler as Scheduler<N>>::Addendum: From<()>,
|
||||||
{
|
{
|
||||||
let mut keys = frost::tests::key_gen::<_, N::Curve>(&mut OsRng);
|
let mut keys = frost::tests::key_gen::<_, N::Curve>(&mut OsRng);
|
||||||
@@ -92,12 +93,14 @@ where
|
|||||||
}
|
}
|
||||||
let key = keys[&Participant::new(1).unwrap()].group_key();
|
let key = keys[&Participant::new(1).unwrap()].group_key();
|
||||||
|
|
||||||
|
let mut db = MemDb::new();
|
||||||
|
let network = new_network(db.clone()).await;
|
||||||
|
|
||||||
// Mine blocks so there's a confirmed block
|
// Mine blocks so there's a confirmed block
|
||||||
for _ in 0 .. N::CONFIRMATIONS {
|
for _ in 0 .. N::CONFIRMATIONS {
|
||||||
network.mine_block().await;
|
network.mine_block().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut db = MemDb::new();
|
|
||||||
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
||||||
assert!(current_keys.is_empty());
|
assert!(current_keys.is_empty());
|
||||||
let mut txn = db.txn();
|
let mut txn = db.txn();
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ use dockertest::{
|
|||||||
TestBodySpecification, DockerOperations, DockerTest,
|
TestBodySpecification, DockerOperations, DockerTest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use serai_db::MemDb;
|
||||||
|
|
||||||
#[cfg(feature = "bitcoin")]
|
#[cfg(feature = "bitcoin")]
|
||||||
mod bitcoin {
|
mod bitcoin {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -33,8 +35,6 @@ mod bitcoin {
|
|||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serai_db::MemDb;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
networks::{Network, Bitcoin, Output, OutputType, Block},
|
networks::{Network, Bitcoin, Output, OutputType, Block},
|
||||||
@@ -57,7 +57,7 @@ mod bitcoin {
|
|||||||
fn test_receive_data_from_input() {
|
fn test_receive_data_from_input() {
|
||||||
let docker = spawn_bitcoin();
|
let docker = spawn_bitcoin();
|
||||||
docker.run(|ops| async move {
|
docker.run(|ops| async move {
|
||||||
let btc = bitcoin(&ops).await;
|
let btc = bitcoin(&ops).await(MemDb::new()).await;
|
||||||
|
|
||||||
// generate a multisig address to receive the coins
|
// generate a multisig address to receive the coins
|
||||||
let mut keys = frost::tests::key_gen::<_, <Bitcoin as Network>::Curve>(&mut OsRng)
|
let mut keys = frost::tests::key_gen::<_, <Bitcoin as Network>::Curve>(&mut OsRng)
|
||||||
@@ -208,23 +208,26 @@ mod bitcoin {
|
|||||||
test
|
test
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn bitcoin(ops: &DockerOperations) -> Bitcoin {
|
async fn bitcoin(
|
||||||
|
ops: &DockerOperations,
|
||||||
|
) -> impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = Bitcoin>>> {
|
||||||
let handle = ops.handle("serai-dev-bitcoin").host_port(8332).unwrap();
|
let handle = ops.handle("serai-dev-bitcoin").host_port(8332).unwrap();
|
||||||
let bitcoin = Bitcoin::new(format!("http://serai:seraidex@{}:{}", handle.0, handle.1)).await;
|
let url = format!("http://serai:seraidex@{}:{}", handle.0, handle.1);
|
||||||
|
let bitcoin = Bitcoin::new(url.clone()).await;
|
||||||
bitcoin.fresh_chain().await;
|
bitcoin.fresh_chain().await;
|
||||||
bitcoin
|
move |_db| Box::pin(Bitcoin::new(url.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
test_network!(
|
test_utxo_network!(
|
||||||
Bitcoin,
|
Bitcoin,
|
||||||
spawn_bitcoin,
|
spawn_bitcoin,
|
||||||
bitcoin,
|
bitcoin,
|
||||||
bitcoin_key_gen,
|
bitcoin_key_gen,
|
||||||
bitcoin_scanner,
|
bitcoin_scanner,
|
||||||
|
bitcoin_no_deadlock_in_multisig_completed,
|
||||||
bitcoin_signer,
|
bitcoin_signer,
|
||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
bitcoin_addresses,
|
bitcoin_addresses,
|
||||||
bitcoin_no_deadlock_in_multisig_completed,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,24 +255,185 @@ mod monero {
|
|||||||
test
|
test
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn monero(ops: &DockerOperations) -> Monero {
|
async fn monero(
|
||||||
|
ops: &DockerOperations,
|
||||||
|
) -> impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = Monero>>> {
|
||||||
let handle = ops.handle("serai-dev-monero").host_port(18081).unwrap();
|
let handle = ops.handle("serai-dev-monero").host_port(18081).unwrap();
|
||||||
let monero = Monero::new(format!("http://serai:seraidex@{}:{}", handle.0, handle.1)).await;
|
let url = format!("http://serai:seraidex@{}:{}", handle.0, handle.1);
|
||||||
|
let monero = Monero::new(url.clone()).await;
|
||||||
while monero.get_latest_block_number().await.unwrap() < 150 {
|
while monero.get_latest_block_number().await.unwrap() < 150 {
|
||||||
monero.mine_block().await;
|
monero.mine_block().await;
|
||||||
}
|
}
|
||||||
monero
|
move |_db| Box::pin(Monero::new(url.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
test_network!(
|
test_utxo_network!(
|
||||||
Monero,
|
Monero,
|
||||||
spawn_monero,
|
spawn_monero,
|
||||||
monero,
|
monero,
|
||||||
monero_key_gen,
|
monero_key_gen,
|
||||||
monero_scanner,
|
monero_scanner,
|
||||||
|
monero_no_deadlock_in_multisig_completed,
|
||||||
monero_signer,
|
monero_signer,
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
monero_addresses,
|
monero_addresses,
|
||||||
monero_no_deadlock_in_multisig_completed,
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ethereum")]
|
||||||
|
mod ethereum {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use ciphersuite::{Ciphersuite, Secp256k1};
|
||||||
|
|
||||||
|
use serai_client::validator_sets::primitives::Session;
|
||||||
|
|
||||||
|
use crate::networks::Ethereum;
|
||||||
|
|
||||||
|
fn spawn_ethereum() -> DockerTest {
|
||||||
|
serai_docker_tests::build("ethereum".to_string());
|
||||||
|
|
||||||
|
let composition = TestBodySpecification::with_image(
|
||||||
|
Image::with_repository("serai-dev-ethereum").pull_policy(PullPolicy::Never),
|
||||||
|
)
|
||||||
|
.set_start_policy(StartPolicy::Strict)
|
||||||
|
.set_log_options(Some(LogOptions {
|
||||||
|
action: LogAction::Forward,
|
||||||
|
policy: LogPolicy::OnError,
|
||||||
|
source: LogSource::Both,
|
||||||
|
}))
|
||||||
|
.set_publish_all_ports(true);
|
||||||
|
|
||||||
|
let mut test = DockerTest::new();
|
||||||
|
test.provide_container(composition);
|
||||||
|
test
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ethereum(
|
||||||
|
ops: &DockerOperations,
|
||||||
|
) -> impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = Ethereum<MemDb>>>> {
|
||||||
|
use std::sync::Arc;
|
||||||
|
use ethereum_serai::{
|
||||||
|
alloy::{
|
||||||
|
primitives::U256,
|
||||||
|
simple_request_transport::SimpleRequest,
|
||||||
|
rpc_client::ClientBuilder,
|
||||||
|
provider::{Provider, RootProvider},
|
||||||
|
},
|
||||||
|
deployer::Deployer,
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle = ops.handle("serai-dev-ethereum").host_port(8545).unwrap();
|
||||||
|
let url = format!("http://{}:{}", handle.0, handle.1);
|
||||||
|
tokio::time::sleep(core::time::Duration::from_secs(15)).await;
|
||||||
|
|
||||||
|
{
|
||||||
|
let provider = Arc::new(RootProvider::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(url.clone()), true),
|
||||||
|
));
|
||||||
|
provider.raw_request::<_, ()>("evm_setAutomine".into(), [false]).await.unwrap();
|
||||||
|
provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
|
||||||
|
|
||||||
|
// Perform deployment
|
||||||
|
{
|
||||||
|
// Make sure the Deployer constructor returns None, as it doesn't exist yet
|
||||||
|
assert!(Deployer::new(provider.clone()).await.unwrap().is_none());
|
||||||
|
|
||||||
|
// Deploy the Deployer
|
||||||
|
let tx = Deployer::deployment_tx();
|
||||||
|
|
||||||
|
provider
|
||||||
|
.raw_request::<_, ()>(
|
||||||
|
"anvil_setBalance".into(),
|
||||||
|
[
|
||||||
|
tx.recover_signer().unwrap().to_string(),
|
||||||
|
(U256::from(tx.tx().gas_limit) * U256::from(tx.tx().gas_price)).to_string(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (tx, sig, _) = tx.into_parts();
|
||||||
|
let mut bytes = vec![];
|
||||||
|
tx.encode_with_signature_fields(&sig, &mut bytes);
|
||||||
|
|
||||||
|
let pending_tx = provider.send_raw_transaction(&bytes).await.unwrap();
|
||||||
|
provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
|
||||||
|
//tokio::time::sleep(core::time::Duration::from_secs(15)).await;
|
||||||
|
let receipt = pending_tx.get_receipt().await.unwrap();
|
||||||
|
assert!(receipt.status());
|
||||||
|
|
||||||
|
let _ = Deployer::new(provider.clone())
|
||||||
|
.await
|
||||||
|
.expect("network error")
|
||||||
|
.expect("deployer wasn't deployed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
move |db| {
|
||||||
|
let url = url.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
{
|
||||||
|
let db = db.clone();
|
||||||
|
let url = url.clone();
|
||||||
|
// Spawn a task to deploy the proper Router when the time comes
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let key = loop {
|
||||||
|
let Some(key) = crate::key_gen::NetworkKeyDb::get(&db, Session(0)) else {
|
||||||
|
tokio::time::sleep(core::time::Duration::from_secs(1)).await;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
break ethereum_serai::crypto::PublicKey::new(
|
||||||
|
Secp256k1::read_G(&mut key.as_slice()).unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
};
|
||||||
|
let provider = Arc::new(RootProvider::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(url.clone()), true),
|
||||||
|
));
|
||||||
|
let deployer = Deployer::new(provider.clone()).await.unwrap().unwrap();
|
||||||
|
|
||||||
|
let mut tx = deployer.deploy_router(&key);
|
||||||
|
tx.gas_limit = 1_000_000u64.into();
|
||||||
|
tx.gas_price = 1_000_000_000u64.into();
|
||||||
|
let tx = ethereum_serai::crypto::deterministically_sign(&tx);
|
||||||
|
|
||||||
|
provider
|
||||||
|
.raw_request::<_, ()>(
|
||||||
|
"anvil_setBalance".into(),
|
||||||
|
[
|
||||||
|
tx.recover_signer().unwrap().to_string(),
|
||||||
|
(U256::from(tx.tx().gas_limit) * U256::from(tx.tx().gas_price)).to_string(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (tx, sig, _) = tx.into_parts();
|
||||||
|
let mut bytes = vec![];
|
||||||
|
tx.encode_with_signature_fields(&sig, &mut bytes);
|
||||||
|
let pending_tx = provider.send_raw_transaction(&bytes).await.unwrap();
|
||||||
|
provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
|
||||||
|
let receipt = pending_tx.get_receipt().await.unwrap();
|
||||||
|
assert!(receipt.status());
|
||||||
|
|
||||||
|
let _router = deployer.find_router(provider.clone(), &key).await.unwrap().unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ethereum::new(db, url.clone()).await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_network!(
|
||||||
|
Ethereum<MemDb>,
|
||||||
|
spawn_ethereum,
|
||||||
|
ethereum,
|
||||||
|
ethereum_key_gen,
|
||||||
|
ethereum_scanner,
|
||||||
|
ethereum_no_deadlock_in_multisig_completed,
|
||||||
|
ethereum_signer,
|
||||||
|
ethereum_wallet,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
mod key_gen;
|
mod key_gen;
|
||||||
pub(crate) use key_gen::test_key_gen;
|
|
||||||
|
|
||||||
mod scanner;
|
mod scanner;
|
||||||
pub(crate) use scanner::{test_scanner, test_no_deadlock_in_multisig_completed};
|
|
||||||
|
|
||||||
mod signer;
|
mod signer;
|
||||||
pub(crate) use signer::{sign, test_signer};
|
pub(crate) use signer::sign;
|
||||||
|
|
||||||
mod cosigner;
|
mod cosigner;
|
||||||
mod batch_signer;
|
mod batch_signer;
|
||||||
|
|
||||||
mod wallet;
|
mod wallet;
|
||||||
pub(crate) use wallet::test_wallet;
|
|
||||||
|
|
||||||
mod addresses;
|
mod addresses;
|
||||||
pub(crate) use addresses::test_addresses;
|
|
||||||
|
|
||||||
// Effective Once
|
// Effective Once
|
||||||
static INIT_LOGGER_CELL: OnceLock<()> = OnceLock::new();
|
static INIT_LOGGER_CELL: OnceLock<()> = OnceLock::new();
|
||||||
@@ -27,22 +23,25 @@ fn init_logger() {
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! test_network {
|
macro_rules! test_network {
|
||||||
(
|
(
|
||||||
$N: ident,
|
$N: ty,
|
||||||
$docker: ident,
|
$docker: ident,
|
||||||
$network: ident,
|
$network: ident,
|
||||||
$key_gen: ident,
|
$key_gen: ident,
|
||||||
$scanner: ident,
|
$scanner: ident,
|
||||||
|
$no_deadlock_in_multisig_completed: ident,
|
||||||
$signer: ident,
|
$signer: ident,
|
||||||
$wallet: ident,
|
$wallet: ident,
|
||||||
$addresses: ident,
|
|
||||||
$no_deadlock_in_multisig_completed: ident,
|
|
||||||
) => {
|
) => {
|
||||||
|
use core::{pin::Pin, future::Future};
|
||||||
use $crate::tests::{
|
use $crate::tests::{
|
||||||
init_logger, test_key_gen, test_scanner, test_no_deadlock_in_multisig_completed, test_signer,
|
init_logger,
|
||||||
test_wallet, test_addresses,
|
key_gen::test_key_gen,
|
||||||
|
scanner::{test_scanner, test_no_deadlock_in_multisig_completed},
|
||||||
|
signer::test_signer,
|
||||||
|
wallet::test_wallet,
|
||||||
};
|
};
|
||||||
|
|
||||||
// This doesn't interact with a node and accordingly doesn't need to be run
|
// This doesn't interact with a node and accordingly doesn't need to be spawn one
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn $key_gen() {
|
async fn $key_gen() {
|
||||||
init_logger();
|
init_logger();
|
||||||
@@ -54,34 +53,8 @@ macro_rules! test_network {
|
|||||||
init_logger();
|
init_logger();
|
||||||
let docker = $docker();
|
let docker = $docker();
|
||||||
docker.run(|ops| async move {
|
docker.run(|ops| async move {
|
||||||
test_scanner($network(&ops).await).await;
|
let new_network = $network(&ops).await;
|
||||||
});
|
test_scanner(new_network).await;
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn $signer() {
|
|
||||||
init_logger();
|
|
||||||
let docker = $docker();
|
|
||||||
docker.run(|ops| async move {
|
|
||||||
test_signer($network(&ops).await).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn $wallet() {
|
|
||||||
init_logger();
|
|
||||||
let docker = $docker();
|
|
||||||
docker.run(|ops| async move {
|
|
||||||
test_wallet($network(&ops).await).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn $addresses() {
|
|
||||||
init_logger();
|
|
||||||
let docker = $docker();
|
|
||||||
docker.run(|ops| async move {
|
|
||||||
test_addresses($network(&ops).await).await;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +63,66 @@ macro_rules! test_network {
|
|||||||
init_logger();
|
init_logger();
|
||||||
let docker = $docker();
|
let docker = $docker();
|
||||||
docker.run(|ops| async move {
|
docker.run(|ops| async move {
|
||||||
test_no_deadlock_in_multisig_completed($network(&ops).await).await;
|
let new_network = $network(&ops).await;
|
||||||
|
test_no_deadlock_in_multisig_completed(new_network).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn $signer() {
|
||||||
|
init_logger();
|
||||||
|
let docker = $docker();
|
||||||
|
docker.run(|ops| async move {
|
||||||
|
let new_network = $network(&ops).await;
|
||||||
|
test_signer(new_network).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn $wallet() {
|
||||||
|
init_logger();
|
||||||
|
let docker = $docker();
|
||||||
|
docker.run(|ops| async move {
|
||||||
|
let new_network = $network(&ops).await;
|
||||||
|
test_wallet(new_network).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! test_utxo_network {
|
||||||
|
(
|
||||||
|
$N: ty,
|
||||||
|
$docker: ident,
|
||||||
|
$network: ident,
|
||||||
|
$key_gen: ident,
|
||||||
|
$scanner: ident,
|
||||||
|
$no_deadlock_in_multisig_completed: ident,
|
||||||
|
$signer: ident,
|
||||||
|
$wallet: ident,
|
||||||
|
$addresses: ident,
|
||||||
|
) => {
|
||||||
|
use $crate::tests::addresses::test_addresses;
|
||||||
|
|
||||||
|
test_network!(
|
||||||
|
$N,
|
||||||
|
$docker,
|
||||||
|
$network,
|
||||||
|
$key_gen,
|
||||||
|
$scanner,
|
||||||
|
$no_deadlock_in_multisig_completed,
|
||||||
|
$signer,
|
||||||
|
$wallet,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn $addresses() {
|
||||||
|
init_logger();
|
||||||
|
let docker = $docker();
|
||||||
|
docker.run(|ops| async move {
|
||||||
|
let new_network = $network(&ops).await;
|
||||||
|
test_addresses(new_network).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
use core::time::Duration;
|
use core::{pin::Pin, time::Duration, future::Future};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ciphersuite::Ciphersuite;
|
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
|
|
||||||
|
use ciphersuite::{group::GroupEncoding, Ciphersuite};
|
||||||
use frost::{Participant, tests::key_gen};
|
use frost::{Participant, tests::key_gen};
|
||||||
|
|
||||||
use tokio::{sync::Mutex, time::timeout};
|
use tokio::{sync::Mutex, time::timeout};
|
||||||
|
|
||||||
use serai_db::{DbTxn, Db, MemDb};
|
use serai_db::{DbTxn, Db, MemDb};
|
||||||
|
use serai_client::validator_sets::primitives::Session;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
networks::{OutputType, Output, Block, UtxoNetwork},
|
networks::{OutputType, Output, Block, Network},
|
||||||
|
key_gen::NetworkKeyDb,
|
||||||
multisigs::scanner::{ScannerEvent, Scanner, ScannerHandle},
|
multisigs::scanner::{ScannerEvent, Scanner, ScannerHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn new_scanner<N: UtxoNetwork, D: Db>(
|
pub async fn new_scanner<N: Network, D: Db>(
|
||||||
network: &N,
|
network: &N,
|
||||||
db: &D,
|
db: &D,
|
||||||
group_key: <N::Curve as Ciphersuite>::G,
|
group_key: <N::Curve as Ciphersuite>::G,
|
||||||
@@ -40,18 +42,27 @@ pub async fn new_scanner<N: UtxoNetwork, D: Db>(
|
|||||||
scanner
|
scanner
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn test_scanner<N: UtxoNetwork>(network: N) {
|
pub async fn test_scanner<N: Network>(
|
||||||
|
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||||
|
) {
|
||||||
let mut keys =
|
let mut keys =
|
||||||
frost::tests::key_gen::<_, N::Curve>(&mut OsRng).remove(&Participant::new(1).unwrap()).unwrap();
|
frost::tests::key_gen::<_, N::Curve>(&mut OsRng).remove(&Participant::new(1).unwrap()).unwrap();
|
||||||
N::tweak_keys(&mut keys);
|
N::tweak_keys(&mut keys);
|
||||||
let group_key = keys.group_key();
|
let group_key = keys.group_key();
|
||||||
|
|
||||||
|
let mut db = MemDb::new();
|
||||||
|
{
|
||||||
|
let mut txn = db.txn();
|
||||||
|
NetworkKeyDb::set(&mut txn, Session(0), &group_key.to_bytes().as_ref().to_vec());
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
let network = new_network(db.clone()).await;
|
||||||
|
|
||||||
// Mine blocks so there's a confirmed block
|
// Mine blocks so there's a confirmed block
|
||||||
for _ in 0 .. N::CONFIRMATIONS {
|
for _ in 0 .. N::CONFIRMATIONS {
|
||||||
network.mine_block().await;
|
network.mine_block().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = MemDb::new();
|
|
||||||
let first = Arc::new(Mutex::new(true));
|
let first = Arc::new(Mutex::new(true));
|
||||||
let scanner = new_scanner(&network, &db, group_key, &first).await;
|
let scanner = new_scanner(&network, &db, group_key, &first).await;
|
||||||
|
|
||||||
@@ -101,35 +112,47 @@ pub async fn test_scanner<N: UtxoNetwork>(network: N) {
|
|||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn test_no_deadlock_in_multisig_completed<N: UtxoNetwork>(network: N) {
|
pub async fn test_no_deadlock_in_multisig_completed<N: Network>(
|
||||||
|
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||||
|
) {
|
||||||
|
let mut db = MemDb::new();
|
||||||
|
let network = new_network(db.clone()).await;
|
||||||
|
|
||||||
// Mine blocks so there's a confirmed block
|
// Mine blocks so there's a confirmed block
|
||||||
for _ in 0 .. N::CONFIRMATIONS {
|
for _ in 0 .. N::CONFIRMATIONS {
|
||||||
network.mine_block().await;
|
network.mine_block().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut db = MemDb::new();
|
|
||||||
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
||||||
assert!(current_keys.is_empty());
|
assert!(current_keys.is_empty());
|
||||||
|
|
||||||
let mut txn = db.txn();
|
|
||||||
// Register keys to cause Block events at CONFIRMATIONS (dropped since first keys),
|
// Register keys to cause Block events at CONFIRMATIONS (dropped since first keys),
|
||||||
// CONFIRMATIONS + 1, and CONFIRMATIONS + 2
|
// CONFIRMATIONS + 1, and CONFIRMATIONS + 2
|
||||||
for i in 0 .. 3 {
|
for i in 0 .. 3 {
|
||||||
|
let key = {
|
||||||
|
let mut keys = key_gen(&mut OsRng);
|
||||||
|
for keys in keys.values_mut() {
|
||||||
|
N::tweak_keys(keys);
|
||||||
|
}
|
||||||
|
let key = keys[&Participant::new(1).unwrap()].group_key();
|
||||||
|
if i == 0 {
|
||||||
|
let mut txn = db.txn();
|
||||||
|
NetworkKeyDb::set(&mut txn, Session(0), &key.to_bytes().as_ref().to_vec());
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
key
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut txn = db.txn();
|
||||||
scanner
|
scanner
|
||||||
.register_key(
|
.register_key(
|
||||||
&mut txn,
|
&mut txn,
|
||||||
network.get_latest_block_number().await.unwrap() + N::CONFIRMATIONS + i,
|
network.get_latest_block_number().await.unwrap() + N::CONFIRMATIONS + i,
|
||||||
{
|
key,
|
||||||
let mut keys = key_gen(&mut OsRng);
|
|
||||||
for keys in keys.values_mut() {
|
|
||||||
N::tweak_keys(keys);
|
|
||||||
}
|
|
||||||
keys[&Participant::new(1).unwrap()].group_key()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
txn.commit();
|
||||||
}
|
}
|
||||||
txn.commit();
|
|
||||||
|
|
||||||
for _ in 0 .. (3 * N::CONFIRMATIONS) {
|
for _ in 0 .. (3 * N::CONFIRMATIONS) {
|
||||||
network.mine_block().await;
|
network.mine_block().await;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
use core::{pin::Pin, future::Future};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use rand_core::{RngCore, OsRng};
|
use rand_core::{RngCore, OsRng};
|
||||||
|
|
||||||
|
use ciphersuite::group::GroupEncoding;
|
||||||
use frost::{
|
use frost::{
|
||||||
Participant, ThresholdKeys,
|
Participant, ThresholdKeys,
|
||||||
dkg::tests::{key_gen, clone_without},
|
dkg::tests::{key_gen, clone_without},
|
||||||
@@ -16,14 +18,15 @@ use serai_client::{
|
|||||||
|
|
||||||
use messages::sign::*;
|
use messages::sign::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
Payment, Plan,
|
Payment,
|
||||||
networks::{Output, Transaction, Eventuality, UtxoNetwork},
|
networks::{Output, Transaction, Eventuality, Network},
|
||||||
|
key_gen::NetworkKeyDb,
|
||||||
multisigs::scheduler::Scheduler,
|
multisigs::scheduler::Scheduler,
|
||||||
signer::Signer,
|
signer::Signer,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub async fn sign<N: UtxoNetwork>(
|
pub async fn sign<N: Network>(
|
||||||
network: N,
|
network: N,
|
||||||
session: Session,
|
session: Session,
|
||||||
mut keys_txs: HashMap<
|
mut keys_txs: HashMap<
|
||||||
@@ -153,53 +156,55 @@ pub async fn sign<N: UtxoNetwork>(
|
|||||||
typed_claim
|
typed_claim
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn test_signer<N: UtxoNetwork>(network: N)
|
pub async fn test_signer<N: Network>(
|
||||||
where
|
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||||
<N::Scheduler as Scheduler<N>>::Addendum: From<()>,
|
) {
|
||||||
{
|
|
||||||
let mut keys = key_gen(&mut OsRng);
|
let mut keys = key_gen(&mut OsRng);
|
||||||
for keys in keys.values_mut() {
|
for keys in keys.values_mut() {
|
||||||
N::tweak_keys(keys);
|
N::tweak_keys(keys);
|
||||||
}
|
}
|
||||||
let key = keys[&Participant::new(1).unwrap()].group_key();
|
let key = keys[&Participant::new(1).unwrap()].group_key();
|
||||||
|
|
||||||
|
let mut db = MemDb::new();
|
||||||
|
{
|
||||||
|
let mut txn = db.txn();
|
||||||
|
NetworkKeyDb::set(&mut txn, Session(0), &key.to_bytes().as_ref().to_vec());
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
let network = new_network(db.clone()).await;
|
||||||
|
|
||||||
let outputs = network
|
let outputs = network
|
||||||
.get_outputs(&network.test_send(N::external_address(&network, key).await).await, key)
|
.get_outputs(&network.test_send(N::external_address(&network, key).await).await, key)
|
||||||
.await;
|
.await;
|
||||||
let sync_block = network.get_latest_block_number().await.unwrap() - N::CONFIRMATIONS;
|
let sync_block = network.get_latest_block_number().await.unwrap() - N::CONFIRMATIONS;
|
||||||
|
|
||||||
let amount = 2 * N::DUST;
|
let amount = (2 * N::DUST) + 1000;
|
||||||
|
let plan = {
|
||||||
|
let mut txn = db.txn();
|
||||||
|
let mut scheduler = N::Scheduler::new::<MemDb>(&mut txn, key, N::NETWORK);
|
||||||
|
let payments = vec![Payment {
|
||||||
|
address: N::external_address(&network, key).await,
|
||||||
|
data: None,
|
||||||
|
balance: Balance {
|
||||||
|
coin: match N::NETWORK {
|
||||||
|
NetworkId::Serai => panic!("test_signer called with Serai"),
|
||||||
|
NetworkId::Bitcoin => Coin::Bitcoin,
|
||||||
|
NetworkId::Ethereum => Coin::Ether,
|
||||||
|
NetworkId::Monero => Coin::Monero,
|
||||||
|
},
|
||||||
|
amount: Amount(amount),
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let mut plans = scheduler.schedule::<MemDb>(&mut txn, outputs.clone(), payments, key, false);
|
||||||
|
assert_eq!(plans.len(), 1);
|
||||||
|
plans.swap_remove(0)
|
||||||
|
};
|
||||||
|
|
||||||
let mut keys_txs = HashMap::new();
|
let mut keys_txs = HashMap::new();
|
||||||
let mut eventualities = vec![];
|
let mut eventualities = vec![];
|
||||||
for (i, keys) in keys.drain() {
|
for (i, keys) in keys.drain() {
|
||||||
let (signable, eventuality) = network
|
let (signable, eventuality) =
|
||||||
.prepare_send(
|
network.prepare_send(sync_block, plan.clone(), 0).await.unwrap().tx.unwrap();
|
||||||
sync_block,
|
|
||||||
Plan {
|
|
||||||
key,
|
|
||||||
inputs: outputs.clone(),
|
|
||||||
payments: vec![Payment {
|
|
||||||
address: N::external_address(&network, key).await,
|
|
||||||
data: None,
|
|
||||||
balance: Balance {
|
|
||||||
coin: match N::NETWORK {
|
|
||||||
NetworkId::Serai => panic!("test_signer called with Serai"),
|
|
||||||
NetworkId::Bitcoin => Coin::Bitcoin,
|
|
||||||
NetworkId::Ethereum => Coin::Ether,
|
|
||||||
NetworkId::Monero => Coin::Monero,
|
|
||||||
},
|
|
||||||
amount: Amount(amount),
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
change: Some(N::change_address(key).unwrap()),
|
|
||||||
scheduler_addendum: ().into(),
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.tx
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
eventualities.push(eventuality.clone());
|
eventualities.push(eventuality.clone());
|
||||||
keys_txs.insert(i, (keys, (signable, eventuality)));
|
keys_txs.insert(i, (keys, (signable, eventuality)));
|
||||||
@@ -217,11 +222,21 @@ where
|
|||||||
key,
|
key,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(outputs.len(), 2);
|
// Don't run if Ethereum as the received output will revert by the contract
|
||||||
// Adjust the amount for the fees
|
// (and therefore not actually exist)
|
||||||
let amount = amount - tx.fee(&network).await;
|
if N::NETWORK != NetworkId::Ethereum {
|
||||||
// Check either output since Monero will randomize its output order
|
assert_eq!(outputs.len(), 1 + usize::from(u8::from(plan.change.is_some())));
|
||||||
assert!((outputs[0].balance().amount.0 == amount) || (outputs[1].balance().amount.0 == amount));
|
// Adjust the amount for the fees
|
||||||
|
let amount = amount - tx.fee(&network).await;
|
||||||
|
if plan.change.is_some() {
|
||||||
|
// Check either output since Monero will randomize its output order
|
||||||
|
assert!(
|
||||||
|
(outputs[0].balance().amount.0 == amount) || (outputs[1].balance().amount.0 == amount)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert!(outputs[0].balance().amount.0 == amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check the eventualities pass
|
// Check the eventualities pass
|
||||||
for eventuality in eventualities {
|
for eventuality in eventualities {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use std::{time::Duration, collections::HashMap};
|
use core::{time::Duration, pin::Pin, future::Future};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
|
|
||||||
|
use ciphersuite::group::GroupEncoding;
|
||||||
use frost::{Participant, dkg::tests::key_gen};
|
use frost::{Participant, dkg::tests::key_gen};
|
||||||
|
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
@@ -15,21 +17,19 @@ use serai_client::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Payment, Plan,
|
Payment, Plan,
|
||||||
networks::{Output, Transaction, Eventuality, Block, UtxoNetwork},
|
networks::{Output, Transaction, Eventuality, Block, Network},
|
||||||
|
key_gen::NetworkKeyDb,
|
||||||
multisigs::{
|
multisigs::{
|
||||||
scanner::{ScannerEvent, Scanner},
|
scanner::{ScannerEvent, Scanner},
|
||||||
scheduler::Scheduler,
|
scheduler::{self, Scheduler},
|
||||||
},
|
},
|
||||||
tests::sign,
|
tests::sign,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests the Scanner, Scheduler, and Signer together
|
// Tests the Scanner, Scheduler, and Signer together
|
||||||
pub async fn test_wallet<N: UtxoNetwork>(network: N) {
|
pub async fn test_wallet<N: Network>(
|
||||||
// Mine blocks so there's a confirmed block
|
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||||
for _ in 0 .. N::CONFIRMATIONS {
|
) {
|
||||||
network.mine_block().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut keys = key_gen(&mut OsRng);
|
let mut keys = key_gen(&mut OsRng);
|
||||||
for keys in keys.values_mut() {
|
for keys in keys.values_mut() {
|
||||||
N::tweak_keys(keys);
|
N::tweak_keys(keys);
|
||||||
@@ -37,6 +37,18 @@ pub async fn test_wallet<N: UtxoNetwork>(network: N) {
|
|||||||
let key = keys[&Participant::new(1).unwrap()].group_key();
|
let key = keys[&Participant::new(1).unwrap()].group_key();
|
||||||
|
|
||||||
let mut db = MemDb::new();
|
let mut db = MemDb::new();
|
||||||
|
{
|
||||||
|
let mut txn = db.txn();
|
||||||
|
NetworkKeyDb::set(&mut txn, Session(0), &key.to_bytes().as_ref().to_vec());
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
let network = new_network(db.clone()).await;
|
||||||
|
|
||||||
|
// Mine blocks so there's a confirmed block
|
||||||
|
for _ in 0 .. N::CONFIRMATIONS {
|
||||||
|
network.mine_block().await;
|
||||||
|
}
|
||||||
|
|
||||||
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
||||||
assert!(current_keys.is_empty());
|
assert!(current_keys.is_empty());
|
||||||
let (block_id, outputs) = {
|
let (block_id, outputs) = {
|
||||||
@@ -93,7 +105,13 @@ pub async fn test_wallet<N: UtxoNetwork>(network: N) {
|
|||||||
txn.commit();
|
txn.commit();
|
||||||
assert_eq!(plans.len(), 1);
|
assert_eq!(plans.len(), 1);
|
||||||
assert_eq!(plans[0].key, key);
|
assert_eq!(plans[0].key, key);
|
||||||
assert_eq!(plans[0].inputs, outputs);
|
if std::any::TypeId::of::<N::Scheduler>() ==
|
||||||
|
std::any::TypeId::of::<scheduler::smart_contract::Scheduler<N>>()
|
||||||
|
{
|
||||||
|
assert_eq!(plans[0].inputs, vec![]);
|
||||||
|
} else {
|
||||||
|
assert_eq!(plans[0].inputs, outputs);
|
||||||
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
plans[0].payments,
|
plans[0].payments,
|
||||||
vec![Payment {
|
vec![Payment {
|
||||||
@@ -110,7 +128,7 @@ pub async fn test_wallet<N: UtxoNetwork>(network: N) {
|
|||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
assert_eq!(plans[0].change, Some(N::change_address(key).unwrap()));
|
assert_eq!(plans[0].change, N::change_address(key));
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
@@ -139,9 +157,22 @@ pub async fn test_wallet<N: UtxoNetwork>(network: N) {
|
|||||||
let tx = network.get_transaction_by_eventuality(block_number, &eventualities[0]).await;
|
let tx = network.get_transaction_by_eventuality(block_number, &eventualities[0]).await;
|
||||||
let block = network.get_block(block_number).await.unwrap();
|
let block = network.get_block(block_number).await.unwrap();
|
||||||
let outputs = network.get_outputs(&block, key).await;
|
let outputs = network.get_outputs(&block, key).await;
|
||||||
assert_eq!(outputs.len(), 2);
|
|
||||||
let amount = amount - tx.fee(&network).await;
|
// Don't run if Ethereum as the received output will revert by the contract
|
||||||
assert!((outputs[0].balance().amount.0 == amount) || (outputs[1].balance().amount.0 == amount));
|
// (and therefore not actually exist)
|
||||||
|
if N::NETWORK != NetworkId::Ethereum {
|
||||||
|
assert_eq!(outputs.len(), 1 + usize::from(u8::from(plans[0].change.is_some())));
|
||||||
|
// Adjust the amount for the fees
|
||||||
|
let amount = amount - tx.fee(&network).await;
|
||||||
|
if plans[0].change.is_some() {
|
||||||
|
// Check either output since Monero will randomize its output order
|
||||||
|
assert!(
|
||||||
|
(outputs[0].balance().amount.0 == amount) || (outputs[1].balance().amount.0 == amount)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert!(outputs[0].balance().amount.0 == amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for eventuality in eventualities {
|
for eventuality in eventualities {
|
||||||
let completion = network.confirm_completion(&eventuality, &claim).await.unwrap().unwrap();
|
let completion = network.confirm_completion(&eventuality, &claim).await.unwrap().unwrap();
|
||||||
@@ -152,21 +183,23 @@ pub async fn test_wallet<N: UtxoNetwork>(network: N) {
|
|||||||
network.mine_block().await;
|
network.mine_block().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
if N::NETWORK != NetworkId::Ethereum {
|
||||||
ScannerEvent::Block { is_retirement_block, block: block_id, outputs: these_outputs } => {
|
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||||
scanner.multisig_completed.send(false).unwrap();
|
ScannerEvent::Block { is_retirement_block, block: block_id, outputs: these_outputs } => {
|
||||||
assert!(!is_retirement_block);
|
scanner.multisig_completed.send(false).unwrap();
|
||||||
assert_eq!(block_id, block.id());
|
assert!(!is_retirement_block);
|
||||||
assert_eq!(these_outputs, outputs);
|
assert_eq!(block_id, block.id());
|
||||||
|
assert_eq!(these_outputs, outputs);
|
||||||
|
}
|
||||||
|
ScannerEvent::Completed(_, _, _, _, _) => {
|
||||||
|
panic!("unexpectedly got eventuality completion");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ScannerEvent::Completed(_, _, _, _, _) => {
|
|
||||||
panic!("unexpectedly got eventuality completion");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the Scanner DB can reload the outputs
|
// Check the Scanner DB can reload the outputs
|
||||||
let mut txn = db.txn();
|
let mut txn = db.txn();
|
||||||
assert_eq!(scanner.ack_block(&mut txn, block.id()).await.1, outputs);
|
assert_eq!(scanner.ack_block(&mut txn, block.id()).await.1, outputs);
|
||||||
scanner.release_lock().await;
|
scanner.release_lock().await;
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ pub fn build(name: String) {
|
|||||||
// Check any additionally specified paths
|
// Check any additionally specified paths
|
||||||
let meta = |path: PathBuf| (path.clone(), fs::metadata(path));
|
let meta = |path: PathBuf| (path.clone(), fs::metadata(path));
|
||||||
let mut metadatas = match name.as_str() {
|
let mut metadatas = match name.as_str() {
|
||||||
"bitcoin" | "monero" => vec![],
|
"bitcoin" | "ethereum" | "monero" => vec![],
|
||||||
"message-queue" => vec![
|
"message-queue" => vec![
|
||||||
meta(repo_path.join("common")),
|
meta(repo_path.join("common")),
|
||||||
meta(repo_path.join("crypto")),
|
meta(repo_path.join("crypto")),
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, fea
|
|||||||
dkg = { path = "../../crypto/dkg", default-features = false, features = ["tests"] }
|
dkg = { path = "../../crypto/dkg", default-features = false, features = ["tests"] }
|
||||||
|
|
||||||
bitcoin-serai = { path = "../../coins/bitcoin" }
|
bitcoin-serai = { path = "../../coins/bitcoin" }
|
||||||
|
ethereum-serai = { path = "../../coins/ethereum" }
|
||||||
monero-serai = { path = "../../coins/monero" }
|
monero-serai = { path = "../../coins/monero" }
|
||||||
|
|
||||||
messages = { package = "serai-processor-messages", path = "../../processor/messages" }
|
messages = { package = "serai-processor-messages", path = "../../processor/messages" }
|
||||||
|
|
||||||
scale = { package = "parity-scale-codec", version = "3" }
|
scale = { package = "parity-scale-codec", version = "3" }
|
||||||
serai-client = { path = "../../substrate/client" }
|
serai-client = { path = "../../substrate/client" }
|
||||||
|
serai-db = { path = "../../common/db", default-features = false }
|
||||||
serai-message-queue = { path = "../../message-queue" }
|
serai-message-queue = { path = "../../message-queue" }
|
||||||
|
|
||||||
borsh = { version = "1", features = ["de_strict_order"] }
|
borsh = { version = "1", features = ["de_strict_order"] }
|
||||||
|
|||||||
@@ -181,7 +181,28 @@ impl Coordinator {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NetworkId::Ethereum => todo!(),
|
NetworkId::Ethereum => {
|
||||||
|
use ethereum_serai::alloy::{
|
||||||
|
simple_request_transport::SimpleRequest,
|
||||||
|
rpc_client::ClientBuilder,
|
||||||
|
provider::{Provider, RootProvider},
|
||||||
|
network::Ethereum,
|
||||||
|
};
|
||||||
|
|
||||||
|
let provider = RootProvider::<_, Ethereum>::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true),
|
||||||
|
);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if handle
|
||||||
|
.block_on(provider.raw_request::<_, ()>("evm_setAutomine".into(), [false]))
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
handle.block_on(tokio::time::sleep(core::time::Duration::from_secs(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
NetworkId::Monero => {
|
NetworkId::Monero => {
|
||||||
use monero_serai::rpc::HttpRpc;
|
use monero_serai::rpc::HttpRpc;
|
||||||
|
|
||||||
@@ -271,7 +292,45 @@ impl Coordinator {
|
|||||||
block.consensus_encode(&mut block_buf).unwrap();
|
block.consensus_encode(&mut block_buf).unwrap();
|
||||||
(hash, block_buf)
|
(hash, block_buf)
|
||||||
}
|
}
|
||||||
NetworkId::Ethereum => todo!(),
|
NetworkId::Ethereum => {
|
||||||
|
use ethereum_serai::alloy::{
|
||||||
|
simple_request_transport::SimpleRequest,
|
||||||
|
rpc_types::BlockNumberOrTag,
|
||||||
|
rpc_client::ClientBuilder,
|
||||||
|
provider::{Provider, RootProvider},
|
||||||
|
network::Ethereum,
|
||||||
|
};
|
||||||
|
|
||||||
|
let provider = RootProvider::<_, Ethereum>::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true),
|
||||||
|
);
|
||||||
|
let start = provider
|
||||||
|
.get_block(BlockNumberOrTag::Latest.into(), false)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.header
|
||||||
|
.number
|
||||||
|
.unwrap();
|
||||||
|
// We mine 96 blocks to mine one epoch, then cause its finalization
|
||||||
|
provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
|
||||||
|
let end_of_epoch = start + 31;
|
||||||
|
let hash = provider
|
||||||
|
.get_block(BlockNumberOrTag::Number(end_of_epoch).into(), false)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.header
|
||||||
|
.hash
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let state = provider
|
||||||
|
.raw_request::<_, String>("anvil_dumpState".into(), ())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_bytes();
|
||||||
|
(hash.into(), state)
|
||||||
|
}
|
||||||
NetworkId::Monero => {
|
NetworkId::Monero => {
|
||||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
|
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
|
||||||
use monero_serai::{
|
use monero_serai::{
|
||||||
@@ -303,39 +362,6 @@ impl Coordinator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn broadcast_block(&self, ops: &DockerOperations, block: &[u8]) {
|
|
||||||
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
|
|
||||||
match self.network {
|
|
||||||
NetworkId::Bitcoin => {
|
|
||||||
use bitcoin_serai::rpc::Rpc;
|
|
||||||
|
|
||||||
let rpc =
|
|
||||||
Rpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Bitcoin RPC");
|
|
||||||
let res: Option<String> =
|
|
||||||
rpc.rpc_call("submitblock", serde_json::json!([hex::encode(block)])).await.unwrap();
|
|
||||||
if let Some(err) = res {
|
|
||||||
panic!("submitblock failed: {err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NetworkId::Ethereum => todo!(),
|
|
||||||
NetworkId::Monero => {
|
|
||||||
use monero_serai::rpc::HttpRpc;
|
|
||||||
|
|
||||||
let rpc =
|
|
||||||
HttpRpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Monero RPC");
|
|
||||||
let res: serde_json::Value = rpc
|
|
||||||
.json_rpc_call("submit_block", Some(serde_json::json!([hex::encode(block)])))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let err = res.get("error");
|
|
||||||
if err.is_some() && (err.unwrap() != &serde_json::Value::Null) {
|
|
||||||
panic!("failed to submit Monero block: {res}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NetworkId::Serai => panic!("processor tests broadcasting block to Serai"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn sync(&self, ops: &DockerOperations, others: &[Coordinator]) {
|
pub async fn sync(&self, ops: &DockerOperations, others: &[Coordinator]) {
|
||||||
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
|
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
|
||||||
match self.network {
|
match self.network {
|
||||||
@@ -345,13 +371,8 @@ impl Coordinator {
|
|||||||
let rpc = Rpc::new(rpc_url).await.expect("couldn't connect to the Bitcoin RPC");
|
let rpc = Rpc::new(rpc_url).await.expect("couldn't connect to the Bitcoin RPC");
|
||||||
let to = rpc.get_latest_block_number().await.unwrap();
|
let to = rpc.get_latest_block_number().await.unwrap();
|
||||||
for coordinator in others {
|
for coordinator in others {
|
||||||
let from = Rpc::new(network_rpc(self.network, ops, &coordinator.network_handle))
|
let from = rpc.get_latest_block_number().await.unwrap() + 1;
|
||||||
.await
|
|
||||||
.expect("couldn't connect to the Bitcoin RPC")
|
|
||||||
.get_latest_block_number()
|
|
||||||
.await
|
|
||||||
.unwrap() +
|
|
||||||
1;
|
|
||||||
for b in from ..= to {
|
for b in from ..= to {
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
rpc
|
rpc
|
||||||
@@ -360,11 +381,40 @@ impl Coordinator {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.consensus_encode(&mut buf)
|
.consensus_encode(&mut buf)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
coordinator.broadcast_block(ops, &buf).await;
|
|
||||||
|
let rpc_url = network_rpc(coordinator.network, ops, &coordinator.network_handle);
|
||||||
|
let rpc =
|
||||||
|
Rpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Bitcoin RPC");
|
||||||
|
|
||||||
|
let res: Option<String> =
|
||||||
|
rpc.rpc_call("submitblock", serde_json::json!([hex::encode(buf)])).await.unwrap();
|
||||||
|
if let Some(err) = res {
|
||||||
|
panic!("submitblock failed: {err}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NetworkId::Ethereum => todo!(),
|
NetworkId::Ethereum => {
|
||||||
|
use ethereum_serai::alloy::{
|
||||||
|
simple_request_transport::SimpleRequest,
|
||||||
|
rpc_client::ClientBuilder,
|
||||||
|
provider::{Provider, RootProvider},
|
||||||
|
network::Ethereum,
|
||||||
|
};
|
||||||
|
|
||||||
|
let provider = RootProvider::<_, Ethereum>::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true),
|
||||||
|
);
|
||||||
|
let state = provider.raw_request::<_, String>("anvil_dumpState".into(), ()).await.unwrap();
|
||||||
|
|
||||||
|
for coordinator in others {
|
||||||
|
let rpc_url = network_rpc(coordinator.network, ops, &coordinator.network_handle);
|
||||||
|
let provider = RootProvider::<_, Ethereum>::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true),
|
||||||
|
);
|
||||||
|
provider.raw_request::<_, ()>("anvil_loadState".into(), &state).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
NetworkId::Monero => {
|
NetworkId::Monero => {
|
||||||
use monero_serai::rpc::HttpRpc;
|
use monero_serai::rpc::HttpRpc;
|
||||||
|
|
||||||
@@ -378,12 +428,21 @@ impl Coordinator {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
for b in from .. to {
|
for b in from .. to {
|
||||||
coordinator
|
let block =
|
||||||
.broadcast_block(
|
rpc.get_block(rpc.get_block_hash(b).await.unwrap()).await.unwrap().serialize();
|
||||||
ops,
|
|
||||||
&rpc.get_block(rpc.get_block_hash(b).await.unwrap()).await.unwrap().serialize(),
|
let rpc_url = network_rpc(coordinator.network, ops, &coordinator.network_handle);
|
||||||
)
|
let rpc = HttpRpc::new(rpc_url)
|
||||||
.await;
|
.await
|
||||||
|
.expect("couldn't connect to the coordinator's Monero RPC");
|
||||||
|
let res: serde_json::Value = rpc
|
||||||
|
.json_rpc_call("submit_block", Some(serde_json::json!([hex::encode(block)])))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let err = res.get("error");
|
||||||
|
if err.is_some() && (err.unwrap() != &serde_json::Value::Null) {
|
||||||
|
panic!("failed to submit Monero block: {res}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,7 +463,19 @@ impl Coordinator {
|
|||||||
Rpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Bitcoin RPC");
|
Rpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Bitcoin RPC");
|
||||||
rpc.send_raw_transaction(&Transaction::consensus_decode(&mut &*tx).unwrap()).await.unwrap();
|
rpc.send_raw_transaction(&Transaction::consensus_decode(&mut &*tx).unwrap()).await.unwrap();
|
||||||
}
|
}
|
||||||
NetworkId::Ethereum => todo!(),
|
NetworkId::Ethereum => {
|
||||||
|
use ethereum_serai::alloy::{
|
||||||
|
simple_request_transport::SimpleRequest,
|
||||||
|
rpc_client::ClientBuilder,
|
||||||
|
provider::{Provider, RootProvider},
|
||||||
|
network::Ethereum,
|
||||||
|
};
|
||||||
|
|
||||||
|
let provider = RootProvider::<_, Ethereum>::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true),
|
||||||
|
);
|
||||||
|
let _ = provider.send_raw_transaction(tx).await.unwrap();
|
||||||
|
}
|
||||||
NetworkId::Monero => {
|
NetworkId::Monero => {
|
||||||
use monero_serai::{transaction::Transaction, rpc::HttpRpc};
|
use monero_serai::{transaction::Transaction, rpc::HttpRpc};
|
||||||
|
|
||||||
@@ -445,7 +516,26 @@ impl Coordinator {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NetworkId::Ethereum => todo!(),
|
NetworkId::Ethereum => {
|
||||||
|
use ethereum_serai::alloy::{
|
||||||
|
consensus::{TxLegacy, Signed},
|
||||||
|
simple_request_transport::SimpleRequest,
|
||||||
|
rpc_client::ClientBuilder,
|
||||||
|
provider::{Provider, RootProvider},
|
||||||
|
network::Ethereum,
|
||||||
|
};
|
||||||
|
|
||||||
|
let provider = RootProvider::<_, Ethereum>::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true),
|
||||||
|
);
|
||||||
|
let mut hash = [0; 32];
|
||||||
|
hash.copy_from_slice(tx);
|
||||||
|
let tx = provider.get_transaction_by_hash(hash.into()).await.unwrap()?;
|
||||||
|
let (tx, sig, _) = Signed::<TxLegacy>::try_from(tx).unwrap().into_parts();
|
||||||
|
let mut bytes = vec![];
|
||||||
|
tx.encode_with_signature_fields(&sig, &mut bytes);
|
||||||
|
Some(bytes)
|
||||||
|
}
|
||||||
NetworkId::Monero => {
|
NetworkId::Monero => {
|
||||||
use monero_serai::rpc::HttpRpc;
|
use monero_serai::rpc::HttpRpc;
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ pub const RPC_USER: &str = "serai";
|
|||||||
pub const RPC_PASS: &str = "seraidex";
|
pub const RPC_PASS: &str = "seraidex";
|
||||||
|
|
||||||
pub const BTC_PORT: u32 = 8332;
|
pub const BTC_PORT: u32 = 8332;
|
||||||
|
pub const ETH_PORT: u32 = 8545;
|
||||||
pub const XMR_PORT: u32 = 18081;
|
pub const XMR_PORT: u32 = 18081;
|
||||||
|
|
||||||
pub fn bitcoin_instance() -> (TestBodySpecification, u32) {
|
pub fn bitcoin_instance() -> (TestBodySpecification, u32) {
|
||||||
@@ -31,6 +32,17 @@ pub fn bitcoin_instance() -> (TestBodySpecification, u32) {
|
|||||||
(composition, BTC_PORT)
|
(composition, BTC_PORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ethereum_instance() -> (TestBodySpecification, u32) {
|
||||||
|
serai_docker_tests::build("ethereum".to_string());
|
||||||
|
|
||||||
|
let composition = TestBodySpecification::with_image(
|
||||||
|
Image::with_repository("serai-dev-ethereum").pull_policy(PullPolicy::Never),
|
||||||
|
)
|
||||||
|
.set_start_policy(StartPolicy::Strict)
|
||||||
|
.set_publish_all_ports(true);
|
||||||
|
(composition, ETH_PORT)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn monero_instance() -> (TestBodySpecification, u32) {
|
pub fn monero_instance() -> (TestBodySpecification, u32) {
|
||||||
serai_docker_tests::build("monero".to_string());
|
serai_docker_tests::build("monero".to_string());
|
||||||
|
|
||||||
@@ -45,7 +57,7 @@ pub fn monero_instance() -> (TestBodySpecification, u32) {
|
|||||||
pub fn network_instance(network: NetworkId) -> (TestBodySpecification, u32) {
|
pub fn network_instance(network: NetworkId) -> (TestBodySpecification, u32) {
|
||||||
match network {
|
match network {
|
||||||
NetworkId::Bitcoin => bitcoin_instance(),
|
NetworkId::Bitcoin => bitcoin_instance(),
|
||||||
NetworkId::Ethereum => todo!(),
|
NetworkId::Ethereum => ethereum_instance(),
|
||||||
NetworkId::Monero => monero_instance(),
|
NetworkId::Monero => monero_instance(),
|
||||||
NetworkId::Serai => {
|
NetworkId::Serai => {
|
||||||
panic!("Serai is not a valid network to spawn an instance of for a processor")
|
panic!("Serai is not a valid network to spawn an instance of for a processor")
|
||||||
@@ -58,7 +70,7 @@ pub fn network_rpc(network: NetworkId, ops: &DockerOperations, handle: &str) ->
|
|||||||
.handle(handle)
|
.handle(handle)
|
||||||
.host_port(match network {
|
.host_port(match network {
|
||||||
NetworkId::Bitcoin => BTC_PORT,
|
NetworkId::Bitcoin => BTC_PORT,
|
||||||
NetworkId::Ethereum => todo!(),
|
NetworkId::Ethereum => ETH_PORT,
|
||||||
NetworkId::Monero => XMR_PORT,
|
NetworkId::Monero => XMR_PORT,
|
||||||
NetworkId::Serai => panic!("getting port for external network yet it was Serai"),
|
NetworkId::Serai => panic!("getting port for external network yet it was Serai"),
|
||||||
})
|
})
|
||||||
@@ -70,7 +82,7 @@ pub fn confirmations(network: NetworkId) -> usize {
|
|||||||
use processor::networks::*;
|
use processor::networks::*;
|
||||||
match network {
|
match network {
|
||||||
NetworkId::Bitcoin => Bitcoin::CONFIRMATIONS,
|
NetworkId::Bitcoin => Bitcoin::CONFIRMATIONS,
|
||||||
NetworkId::Ethereum => todo!(),
|
NetworkId::Ethereum => Ethereum::<serai_db::MemDb>::CONFIRMATIONS,
|
||||||
NetworkId::Monero => Monero::CONFIRMATIONS,
|
NetworkId::Monero => Monero::CONFIRMATIONS,
|
||||||
NetworkId::Serai => panic!("getting confirmations required for Serai"),
|
NetworkId::Serai => panic!("getting confirmations required for Serai"),
|
||||||
}
|
}
|
||||||
@@ -83,6 +95,10 @@ pub enum Wallet {
|
|||||||
public_key: bitcoin_serai::bitcoin::PublicKey,
|
public_key: bitcoin_serai::bitcoin::PublicKey,
|
||||||
input_tx: bitcoin_serai::bitcoin::Transaction,
|
input_tx: bitcoin_serai::bitcoin::Transaction,
|
||||||
},
|
},
|
||||||
|
Ethereum {
|
||||||
|
key: <ciphersuite::Secp256k1 as Ciphersuite>::F,
|
||||||
|
nonce: u64,
|
||||||
|
},
|
||||||
Monero {
|
Monero {
|
||||||
handle: String,
|
handle: String,
|
||||||
spend_key: Zeroizing<curve25519_dalek::scalar::Scalar>,
|
spend_key: Zeroizing<curve25519_dalek::scalar::Scalar>,
|
||||||
@@ -138,7 +154,37 @@ impl Wallet {
|
|||||||
Wallet::Bitcoin { private_key, public_key, input_tx: funds }
|
Wallet::Bitcoin { private_key, public_key, input_tx: funds }
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkId::Ethereum => todo!(),
|
NetworkId::Ethereum => {
|
||||||
|
use ciphersuite::{group::ff::Field, Ciphersuite, Secp256k1};
|
||||||
|
use ethereum_serai::alloy::{
|
||||||
|
primitives::{U256, Address},
|
||||||
|
simple_request_transport::SimpleRequest,
|
||||||
|
rpc_client::ClientBuilder,
|
||||||
|
provider::{Provider, RootProvider},
|
||||||
|
network::Ethereum,
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = <Secp256k1 as Ciphersuite>::F::random(&mut OsRng);
|
||||||
|
let address =
|
||||||
|
ethereum_serai::crypto::address(&(<Secp256k1 as Ciphersuite>::generator() * key));
|
||||||
|
|
||||||
|
let provider = RootProvider::<_, Ethereum>::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true),
|
||||||
|
);
|
||||||
|
|
||||||
|
provider
|
||||||
|
.raw_request::<_, ()>(
|
||||||
|
"anvil_setBalance".into(),
|
||||||
|
[Address(address.into()).to_string(), {
|
||||||
|
let nine_decimals = U256::from(1_000_000_000u64);
|
||||||
|
(U256::from(100u64) * nine_decimals * nine_decimals).to_string()
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Wallet::Ethereum { key, nonce: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
NetworkId::Monero => {
|
NetworkId::Monero => {
|
||||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
|
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
|
||||||
@@ -282,6 +328,24 @@ impl Wallet {
|
|||||||
(buf, Balance { coin: Coin::Bitcoin, amount: Amount(AMOUNT) })
|
(buf, Balance { coin: Coin::Bitcoin, amount: Amount(AMOUNT) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Wallet::Ethereum { key, ref mut nonce } => {
|
||||||
|
/*
|
||||||
|
use ethereum_serai::alloy::primitives::U256;
|
||||||
|
|
||||||
|
let eight_decimals = U256::from(100_000_000u64);
|
||||||
|
let nine_decimals = eight_decimals * U256::from(10u64);
|
||||||
|
let eighteen_decimals = nine_decimals * nine_decimals;
|
||||||
|
|
||||||
|
let tx = todo!("send to router");
|
||||||
|
|
||||||
|
*nonce += 1;
|
||||||
|
(tx, Balance { coin: Coin::Ether, amount: Amount(u64::try_from(eight_decimals).unwrap()) })
|
||||||
|
*/
|
||||||
|
let _ = key;
|
||||||
|
let _ = nonce;
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
Wallet::Monero { handle, ref spend_key, ref view_pair, ref mut inputs } => {
|
Wallet::Monero { handle, ref spend_key, ref view_pair, ref mut inputs } => {
|
||||||
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
|
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
|
||||||
use monero_serai::{
|
use monero_serai::{
|
||||||
@@ -374,6 +438,13 @@ impl Wallet {
|
|||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
Wallet::Ethereum { key, .. } => {
|
||||||
|
use ciphersuite::{Ciphersuite, Secp256k1};
|
||||||
|
ExternalAddress::new(
|
||||||
|
ethereum_serai::crypto::address(&(Secp256k1::generator() * key)).into(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
Wallet::Monero { view_pair, .. } => {
|
Wallet::Monero { view_pair, .. } => {
|
||||||
use monero_serai::wallet::address::{Network, AddressSpec};
|
use monero_serai::wallet::address::{Network, AddressSpec};
|
||||||
ExternalAddress::new(
|
ExternalAddress::new(
|
||||||
|
|||||||
Reference in New Issue
Block a user