mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-10 13:09:24 +00:00
Merge branch 'develop' of https://github.com/akildemir/serai into emissions
This commit is contained in:
67
Cargo.lock
generated
67
Cargo.lock
generated
@@ -102,7 +102,7 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||
[[package]]
|
||||
name = "alloy-consensus"
|
||||
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 = [
|
||||
"alloy-eips",
|
||||
"alloy-primitives",
|
||||
@@ -110,7 +110,6 @@ dependencies = [
|
||||
"alloy-serde",
|
||||
"c-kzg",
|
||||
"serde",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -125,7 +124,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-eips"
|
||||
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 = [
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
@@ -133,23 +132,25 @@ dependencies = [
|
||||
"c-kzg",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloy-genesis"
|
||||
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 = [
|
||||
"alloy-primitives",
|
||||
"alloy-serde",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloy-json-abi"
|
||||
version = "0.7.0"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83a35ddfd27576474322a5869e4c123e5f3e7b2177297c18e4e82ea501cb125b"
|
||||
checksum = "786689872ec4e7d354810ab0dffd48bb40b838c047522eb031cbd47d15634849"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-sol-type-parser",
|
||||
@@ -159,7 +160,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-json-rpc"
|
||||
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 = [
|
||||
"alloy-primitives",
|
||||
"serde",
|
||||
@@ -171,7 +172,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-network"
|
||||
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 = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -179,6 +180,7 @@ dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types",
|
||||
"alloy-signer",
|
||||
"alloy-sol-types",
|
||||
"async-trait",
|
||||
"futures-utils-wasm",
|
||||
"thiserror",
|
||||
@@ -187,7 +189,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-node-bindings"
|
||||
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 = [
|
||||
"alloy-genesis",
|
||||
"alloy-primitives",
|
||||
@@ -201,9 +203,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-primitives"
|
||||
version = "0.7.0"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99bbad0a6b588ef4aec1b5ddbbfdacd9ef04e00b979617765b03174318ee1f3a"
|
||||
checksum = "525448f6afc1b70dd0f9d0a8145631bf2f5e434678ab23ab18409ca264cae6b3"
|
||||
dependencies = [
|
||||
"alloy-rlp",
|
||||
"bytes",
|
||||
@@ -224,7 +226,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-provider"
|
||||
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 = [
|
||||
"alloy-eips",
|
||||
"alloy-json-rpc",
|
||||
@@ -271,7 +273,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-client"
|
||||
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 = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-transport",
|
||||
@@ -289,7 +291,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types"
|
||||
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 = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -307,7 +309,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-trace"
|
||||
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 = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types",
|
||||
@@ -319,7 +321,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-serde"
|
||||
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 = [
|
||||
"alloy-primitives",
|
||||
"serde",
|
||||
@@ -329,7 +331,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-signer"
|
||||
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 = [
|
||||
"alloy-primitives",
|
||||
"async-trait",
|
||||
@@ -352,9 +354,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-sol-macro"
|
||||
version = "0.7.0"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "452d929748ac948a10481fff4123affead32c553cf362841c5103dd508bdfc16"
|
||||
checksum = "89c80a2cb97e7aa48611cbb63950336f9824a174cdf670527cc6465078a26ea1"
|
||||
dependencies = [
|
||||
"alloy-json-abi",
|
||||
"alloy-sol-macro-input",
|
||||
@@ -371,9 +373,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-sol-macro-input"
|
||||
version = "0.7.0"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df64e094f6d2099339f9e82b5b38440b159757b6920878f28316243f8166c8d1"
|
||||
checksum = "c58894b58ac50979eeac6249661991ac40b9d541830d9a725f7714cc9ef08c23"
|
||||
dependencies = [
|
||||
"alloy-json-abi",
|
||||
"const-hex",
|
||||
@@ -388,18 +390,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-sol-type-parser"
|
||||
version = "0.7.0"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "715f4d09a330cc181fc7c361b5c5c2766408fa59a0bac60349dcb7baabd404cc"
|
||||
checksum = "7da8e71ea68e780cc203919e03f69f59e7afe92d2696fb1dcb6662f61e4031b6"
|
||||
dependencies = [
|
||||
"winnow 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloy-sol-types"
|
||||
version = "0.7.0"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43bc2d6dfc2a19fd56644494479510f98b1ee929e04cf0d4aa45e98baa3e545b"
|
||||
checksum = "399287f68d1081ed8b1f4903c49687658b95b142207d7cb4ae2f4813915343ef"
|
||||
dependencies = [
|
||||
"alloy-json-abi",
|
||||
"alloy-primitives",
|
||||
@@ -410,7 +412,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-transport"
|
||||
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 = [
|
||||
"alloy-json-rpc",
|
||||
"base64 0.22.0",
|
||||
@@ -428,7 +430,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alloy-transport-http"
|
||||
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 = [
|
||||
"alloy-transport",
|
||||
"url",
|
||||
@@ -2123,7 +2125,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dockertest"
|
||||
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 = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -2331,6 +2333,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-core",
|
||||
"alloy-network",
|
||||
"alloy-node-bindings",
|
||||
"alloy-provider",
|
||||
"alloy-rpc-client",
|
||||
@@ -3790,7 +3793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8099,11 +8102,13 @@ dependencies = [
|
||||
"curve25519-dalek",
|
||||
"dkg",
|
||||
"dockertest",
|
||||
"ethereum-serai",
|
||||
"hex",
|
||||
"monero-serai",
|
||||
"parity-scale-codec",
|
||||
"rand_core",
|
||||
"serai-client",
|
||||
"serai-db",
|
||||
"serai-docker-tests",
|
||||
"serai-message-queue",
|
||||
"serai-message-queue-tests",
|
||||
@@ -9376,9 +9381,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn-solidity"
|
||||
version = "0.7.0"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4497156948bd342b52038035a6fa514a89626e37af9d2c52a5e8d8ebcc7ee479"
|
||||
checksum = "5aa0cefd02f532035d83cfec82647c6eb53140b0485220760e669f4bad489e36"
|
||||
dependencies = [
|
||||
"paste",
|
||||
"proc-macro2",
|
||||
|
||||
@@ -110,7 +110,7 @@ panic = "unwind"
|
||||
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
|
||||
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
|
||||
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-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-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6", default-features = false }
|
||||
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "037dd4b20ec8533d6b6d5cf5e9489bbb182c18c6", default-features = false }
|
||||
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false, features = ["k256"] }
|
||||
alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", 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-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]
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false, features = ["tests"] }
|
||||
|
||||
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]
|
||||
tests = []
|
||||
tests = ["alloy-node-bindings"]
|
||||
|
||||
@@ -21,8 +21,8 @@ tower = "0.4"
|
||||
serde_json = { version = "1", 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-transport = { 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 = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["tls"]
|
||||
|
||||
@@ -31,7 +31,10 @@ pub fn address(point: &ProjectivePoint) -> [u8; 20] {
|
||||
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!(
|
||||
tx.chain_id.is_none(),
|
||||
"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_rpc_types::{BlockNumberOrTag, Filter};
|
||||
use alloy_rpc_types::Filter;
|
||||
use alloy_simple_request_transport::SimpleRequest;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
@@ -25,22 +25,8 @@ pub struct TopLevelErc20Transfer {
|
||||
pub struct Erc20(Arc<RootProvider<SimpleRequest>>, Address);
|
||||
impl Erc20 {
|
||||
/// Construct a new view of the specified ERC20 contract.
|
||||
///
|
||||
/// This checks a contract is deployed at that address yet does not check the contract is
|
||||
/// 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 fn new(provider: Arc<RootProvider<SimpleRequest>>, address: [u8; 20]) -> Self {
|
||||
Self(provider, Address::from(&address))
|
||||
}
|
||||
|
||||
pub async fn top_level_transfers(
|
||||
@@ -65,7 +51,8 @@ impl Erc20 {
|
||||
}
|
||||
|
||||
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 tx.to == Some(self.1) {
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub use alloy_core;
|
||||
pub use alloy_consensus;
|
||||
pub mod alloy {
|
||||
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_simple_request_transport;
|
||||
pub use alloy_rpc_client;
|
||||
pub use alloy_provider;
|
||||
pub use alloy_consensus as consensus;
|
||||
pub use alloy_network as network;
|
||||
pub use alloy_rpc_types as rpc_types;
|
||||
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;
|
||||
|
||||
@@ -18,8 +23,8 @@ pub mod router;
|
||||
|
||||
pub mod machine;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
#[cfg(any(test, feature = "tests"))]
|
||||
pub mod tests;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
|
||||
pub enum Error {
|
||||
|
||||
@@ -159,11 +159,12 @@ impl Router {
|
||||
#[cfg(test)]
|
||||
pub async fn serai_key(&self, at: [u8; 32]) -> Result<PublicKey, Error> {
|
||||
let call = TransactionRequest::default()
|
||||
.to(Some(self.1))
|
||||
.to(self.1)
|
||||
.input(TransactionInput::new(abi::seraiKeyCall::new(()).abi_encode().into()));
|
||||
let bytes = self
|
||||
.0
|
||||
.call(&call, Some(BlockId::Hash(B256::from(at).into())))
|
||||
.call(&call)
|
||||
.block(BlockId::Hash(B256::from(at).into()))
|
||||
.await
|
||||
.map_err(|_| Error::ConnectionError)?;
|
||||
let res =
|
||||
@@ -197,11 +198,12 @@ impl Router {
|
||||
#[cfg(test)]
|
||||
pub async fn nonce(&self, at: [u8; 32]) -> Result<U256, Error> {
|
||||
let call = TransactionRequest::default()
|
||||
.to(Some(self.1))
|
||||
.to(self.1)
|
||||
.input(TransactionInput::new(abi::nonceCall::new(()).abi_encode().into()));
|
||||
let bytes = self
|
||||
.0
|
||||
.call(&call, Some(BlockId::Hash(B256::from(at).into())))
|
||||
.call(&call)
|
||||
.block(BlockId::Hash(B256::from(at).into()))
|
||||
.await
|
||||
.map_err(|_| Error::ConnectionError)?;
|
||||
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.event_signature(SeraiKeyUpdated::SIGNATURE_HASH);
|
||||
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 = last_key_x_coordinate_log
|
||||
@@ -246,7 +251,9 @@ impl Router {
|
||||
compressed_point[0] = u8::from(sec1::Tag::CompressedEvenY);
|
||||
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(
|
||||
@@ -254,7 +261,9 @@ impl Router {
|
||||
block: u64,
|
||||
allowed_tokens: &HashSet<[u8; 20]>,
|
||||
) -> 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.event_signature(InInstructionEvent::SIGNATURE_HASH);
|
||||
@@ -274,7 +283,13 @@ impl Router {
|
||||
);
|
||||
|
||||
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 =
|
||||
log.log_decode::<InInstructionEvent>().map_err(|_| Error::ConnectionError)?.inner.data;
|
||||
|
||||
@@ -11,16 +11,20 @@ use alloy_core::{
|
||||
};
|
||||
use alloy_consensus::{SignableTransaction, TxLegacy};
|
||||
|
||||
use alloy_rpc_types::TransactionReceipt;
|
||||
use alloy_rpc_types::{BlockNumberOrTag, TransactionReceipt};
|
||||
use alloy_simple_request_transport::SimpleRequest;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
use crate::crypto::{address, deterministically_sign, PublicKey};
|
||||
|
||||
#[cfg(test)]
|
||||
mod crypto;
|
||||
|
||||
#[cfg(test)]
|
||||
mod abi;
|
||||
#[cfg(test)]
|
||||
mod schnorr;
|
||||
#[cfg(test)]
|
||||
mod router;
|
||||
|
||||
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();
|
||||
// tx.chain_id = Some(chain_id);
|
||||
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
|
||||
tx.gas_price = 100_000_000_000u128;
|
||||
|
||||
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!(
|
||||
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)
|
||||
);
|
||||
|
||||
|
||||
@@ -56,12 +56,12 @@ pub async fn call_verify(
|
||||
let px: [u8; 32] = public_key.px.to_repr().into();
|
||||
let c_bytes: [u8; 32] = signature.c.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_encode()
|
||||
.into(),
|
||||
));
|
||||
let bytes = provider.call(&call, None).await.map_err(|_| Error::ConnectionError)?;
|
||||
let bytes = provider.call(&call).await.map_err(|_| Error::ConnectionError)?;
|
||||
let res =
|
||||
abi::verifyCall::abi_decode_returns(&bytes, true).map_err(|_| Error::ConnectionError)?;
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ impl Client {
|
||||
fn connector() -> Connector {
|
||||
let mut res = HttpConnector::new();
|
||||
res.set_keepalive(Some(core::time::Duration::from_secs(60)));
|
||||
res.set_nodelay(true);
|
||||
res.set_reuse_address(true);
|
||||
#[cfg(feature = "tls")]
|
||||
let res = HttpsConnectorBuilder::new()
|
||||
.with_native_roots()
|
||||
@@ -68,7 +70,9 @@ impl Client {
|
||||
pub fn with_connection_pool() -> Client {
|
||||
Client {
|
||||
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()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,5 +103,5 @@ allow-git = [
|
||||
"https://github.com/serai-dex/substrate",
|
||||
"https://github.com/alloy-rs/alloy",
|
||||
"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
|
||||
|
||||
~/.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"]
|
||||
bitcoin = ["dep:secp256k1", "secp256k1", "bitcoin-serai", "serai-client/bitcoin"]
|
||||
|
||||
ethereum = ["secp256k1", "ethereum-serai"]
|
||||
ethereum = ["secp256k1", "ethereum-serai/tests"]
|
||||
|
||||
ed25519 = ["dalek-ff-group", "frost/ed25519"]
|
||||
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));
|
||||
}
|
||||
|
||||
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![];
|
||||
for chunk in payments.as_slice().chunks(N::MAX_OUTPUTS) {
|
||||
// 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())
|
||||
.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));
|
||||
Plan {
|
||||
key: current_key,
|
||||
|
||||
@@ -11,11 +11,13 @@ use ciphersuite::{group::GroupEncoding, Ciphersuite, Secp256k1};
|
||||
use frost::ThresholdKeys;
|
||||
|
||||
use ethereum_serai::{
|
||||
alloy_core::primitives::U256,
|
||||
alloy_rpc_types::{BlockNumberOrTag, Transaction},
|
||||
alloy_simple_request_transport::SimpleRequest,
|
||||
alloy_rpc_client::ClientBuilder,
|
||||
alloy_provider::{Provider, RootProvider},
|
||||
alloy::{
|
||||
primitives::U256,
|
||||
rpc_types::{BlockNumberOrTag, Transaction},
|
||||
simple_request_transport::SimpleRequest,
|
||||
rpc_client::ClientBuilder,
|
||||
provider::{Provider, RootProvider},
|
||||
},
|
||||
crypto::{PublicKey, Signature},
|
||||
erc20::Erc20,
|
||||
deployer::Deployer,
|
||||
@@ -23,7 +25,7 @@ use ethereum_serai::{
|
||||
machine::*,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use ethereum_serai::alloy_core::primitives::B256;
|
||||
use ethereum_serai::alloy::primitives::B256;
|
||||
|
||||
use tokio::{
|
||||
time::sleep,
|
||||
@@ -112,7 +114,7 @@ impl TryInto<Vec<u8>> for Address {
|
||||
|
||||
impl fmt::Display for Address {
|
||||
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]
|
||||
impl<D: fmt::Debug + Db> TransactionTrait<Ethereum<D>> for Transaction {
|
||||
impl<D: Db> TransactionTrait<Ethereum<D>> for Transaction {
|
||||
type Id = [u8; 32];
|
||||
fn id(&self) -> Self::Id {
|
||||
self.hash.0
|
||||
@@ -157,7 +159,7 @@ impl Epoch {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<D: fmt::Debug + Db> Block<Ethereum<D>> for Epoch {
|
||||
impl<D: Db> Block<Ethereum<D>> for Epoch {
|
||||
type Id = [u8; 32];
|
||||
fn id(&self) -> [u8; 32] {
|
||||
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];
|
||||
|
||||
fn kind(&self) -> OutputType {
|
||||
@@ -181,7 +183,7 @@ impl<D: fmt::Debug + Db> Output<Ethereum<D>> for EthereumInInstruction {
|
||||
let mut id = [0; 40];
|
||||
id[.. 32].copy_from_slice(&self.id.0);
|
||||
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] {
|
||||
self.id.0
|
||||
@@ -282,8 +284,8 @@ impl EventualityTrait for Eventuality {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Ethereum<D: fmt::Debug + Db> {
|
||||
#[derive(Clone)]
|
||||
pub struct Ethereum<D: Db> {
|
||||
// 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
|
||||
// first key (regardless of local state), and this is safe.
|
||||
@@ -292,20 +294,26 @@ pub struct Ethereum<D: fmt::Debug + Db> {
|
||||
deployer: Deployer,
|
||||
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 {
|
||||
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 {
|
||||
let provider = Arc::new(RootProvider::new(
|
||||
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;
|
||||
while !matches!(deployer, Ok(Some(_))) {
|
||||
log::error!("Deployer wasn't deployed yet or networking error");
|
||||
@@ -362,7 +370,7 @@ impl<D: fmt::Debug + Db> Ethereum<D> {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
||||
impl<D: Db> Network for Ethereum<D> {
|
||||
type Curve = Secp256k1;
|
||||
|
||||
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
|
||||
let key_at_end_of_block = loop {
|
||||
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) => {
|
||||
log::error!("couldn't connect to router for the key at the end of the block: {e:?}");
|
||||
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 top_level_txids = HashSet::new();
|
||||
for erc20_addr in [DAI] {
|
||||
let erc20 = loop {
|
||||
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;
|
||||
};
|
||||
let erc20 = Erc20::new(self.provider.clone(), erc20_addr);
|
||||
|
||||
for block in block.start .. (block.start + 32) {
|
||||
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
|
||||
#[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 = router.as_ref().unwrap();
|
||||
|
||||
@@ -750,17 +733,30 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
||||
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;
|
||||
let sig =
|
||||
k256::ecdsa::SigningKey::from(k256::elliptic_curve::NonZeroScalar::new(key).unwrap())
|
||||
.sign_prehash_recoverable(tx.signature_hash().as_ref())
|
||||
if self.provider.get_transaction_by_hash(*tx.hash()).await.unwrap().is_none() {
|
||||
self
|
||||
.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 mut bytes = vec![];
|
||||
tx.encode_with_signature_fields(&sig.into(), &mut bytes);
|
||||
let _ = self.provider.send_raw_transaction(&bytes).await.ok().unwrap();
|
||||
let (tx, sig, _) = tx.into_parts();
|
||||
let mut bytes = vec![];
|
||||
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(())
|
||||
}
|
||||
@@ -804,46 +800,62 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
||||
block: usize,
|
||||
eventuality: &Self::Eventuality,
|
||||
) -> Self::Transaction {
|
||||
match eventuality.1 {
|
||||
RouterCommand::UpdateSeraiKey { nonce, .. } | RouterCommand::Execute { nonce, .. } => {
|
||||
let router = self.router().await;
|
||||
let router = router.as_ref().unwrap();
|
||||
// We mine 96 blocks to ensure the 32 blocks relevant are finalized
|
||||
// Back-check the prior two epochs in response to this
|
||||
// TODO: Review why this is sub(3) and not sub(2)
|
||||
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 filter = router
|
||||
.key_updated_filter()
|
||||
.from_block(block * 32)
|
||||
.to_block(((block + 1) * 32) - 1)
|
||||
.topic1(nonce);
|
||||
let logs = self.provider.get_logs(&filter).await.unwrap();
|
||||
if let Some(log) = logs.first() {
|
||||
let block = u64::try_from(block).unwrap();
|
||||
let filter = router
|
||||
.key_updated_filter()
|
||||
.from_block(block * 32)
|
||||
.to_block(((block + 1) * 32) - 1)
|
||||
.topic1(nonce);
|
||||
let logs = self.provider.get_logs(&filter).await.unwrap();
|
||||
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
|
||||
.provider
|
||||
.get_transaction_by_hash(log.clone().transaction_hash.unwrap())
|
||||
.get_transaction_by_hash(logs[0].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();
|
||||
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)]
|
||||
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)]
|
||||
async fn test_send(&self, send_to: Self::Address) -> Self::Block {
|
||||
use rand_core::OsRng;
|
||||
use ciphersuite::group::ff::Field;
|
||||
use ethereum_serai::alloy::sol_types::SolCall;
|
||||
|
||||
let key = <Secp256k1 as Ciphersuite>::F::random(&mut OsRng);
|
||||
let address = ethereum_serai::crypto::address(&(Secp256k1::generator() * key));
|
||||
@@ -858,18 +870,25 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
||||
.await
|
||||
.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,
|
||||
nonce: 0,
|
||||
gas_price: 100_000_000_000u128,
|
||||
gas_limit: 21_0000u128,
|
||||
to: ethereum_serai::alloy_core::primitives::TxKind::Call(send_to.0.into()),
|
||||
gas_price: 1_000_000_000u128,
|
||||
gas_limit: 200_000u128,
|
||||
to: ethereum_serai::alloy::primitives::TxKind::Call(send_to.0.into()),
|
||||
// 1 ETH
|
||||
value: U256::from_str_radix("1000000000000000000", 10).unwrap(),
|
||||
input: vec![].into(),
|
||||
value,
|
||||
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())
|
||||
.sign_prehash_recoverable(tx.signature_hash().as_ref())
|
||||
.unwrap();
|
||||
|
||||
@@ -432,9 +432,12 @@ pub trait Network: 'static + Send + Sync + Clone + PartialEq + Debug {
|
||||
|
||||
let plan_id = plan.id();
|
||||
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>() -
|
||||
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 {
|
||||
// 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 rand_core::OsRng;
|
||||
@@ -82,8 +82,9 @@ async fn spend<N: UtxoNetwork, D: Db>(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_addresses<N: UtxoNetwork>(network: N)
|
||||
where
|
||||
pub async fn test_addresses<N: UtxoNetwork>(
|
||||
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||
) where
|
||||
<N::Scheduler as Scheduler<N>>::Addendum: From<()>,
|
||||
{
|
||||
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 mut db = MemDb::new();
|
||||
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 db = MemDb::new();
|
||||
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
||||
assert!(current_keys.is_empty());
|
||||
let mut txn = db.txn();
|
||||
|
||||
@@ -3,6 +3,8 @@ use dockertest::{
|
||||
TestBodySpecification, DockerOperations, DockerTest,
|
||||
};
|
||||
|
||||
use serai_db::MemDb;
|
||||
|
||||
#[cfg(feature = "bitcoin")]
|
||||
mod bitcoin {
|
||||
use std::sync::Arc;
|
||||
@@ -33,8 +35,6 @@ mod bitcoin {
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use serai_db::MemDb;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
networks::{Network, Bitcoin, Output, OutputType, Block},
|
||||
@@ -57,7 +57,7 @@ mod bitcoin {
|
||||
fn test_receive_data_from_input() {
|
||||
let docker = spawn_bitcoin();
|
||||
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
|
||||
let mut keys = frost::tests::key_gen::<_, <Bitcoin as Network>::Curve>(&mut OsRng)
|
||||
@@ -208,23 +208,26 @@ mod bitcoin {
|
||||
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 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
|
||||
move |_db| Box::pin(Bitcoin::new(url.clone()))
|
||||
}
|
||||
|
||||
test_network!(
|
||||
test_utxo_network!(
|
||||
Bitcoin,
|
||||
spawn_bitcoin,
|
||||
bitcoin,
|
||||
bitcoin_key_gen,
|
||||
bitcoin_scanner,
|
||||
bitcoin_no_deadlock_in_multisig_completed,
|
||||
bitcoin_signer,
|
||||
bitcoin_wallet,
|
||||
bitcoin_addresses,
|
||||
bitcoin_no_deadlock_in_multisig_completed,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -252,24 +255,185 @@ mod monero {
|
||||
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 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 {
|
||||
monero.mine_block().await;
|
||||
}
|
||||
monero
|
||||
move |_db| Box::pin(Monero::new(url.clone()))
|
||||
}
|
||||
|
||||
test_network!(
|
||||
test_utxo_network!(
|
||||
Monero,
|
||||
spawn_monero,
|
||||
monero,
|
||||
monero_key_gen,
|
||||
monero_scanner,
|
||||
monero_no_deadlock_in_multisig_completed,
|
||||
monero_signer,
|
||||
monero_wallet,
|
||||
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;
|
||||
|
||||
mod key_gen;
|
||||
pub(crate) use key_gen::test_key_gen;
|
||||
|
||||
mod scanner;
|
||||
pub(crate) use scanner::{test_scanner, test_no_deadlock_in_multisig_completed};
|
||||
|
||||
mod signer;
|
||||
pub(crate) use signer::{sign, test_signer};
|
||||
pub(crate) use signer::sign;
|
||||
|
||||
mod cosigner;
|
||||
mod batch_signer;
|
||||
|
||||
mod wallet;
|
||||
pub(crate) use wallet::test_wallet;
|
||||
|
||||
mod addresses;
|
||||
pub(crate) use addresses::test_addresses;
|
||||
|
||||
// Effective Once
|
||||
static INIT_LOGGER_CELL: OnceLock<()> = OnceLock::new();
|
||||
@@ -27,22 +23,25 @@ fn init_logger() {
|
||||
#[macro_export]
|
||||
macro_rules! test_network {
|
||||
(
|
||||
$N: ident,
|
||||
$N: ty,
|
||||
$docker: ident,
|
||||
$network: ident,
|
||||
$key_gen: ident,
|
||||
$scanner: ident,
|
||||
$no_deadlock_in_multisig_completed: ident,
|
||||
$signer: ident,
|
||||
$wallet: ident,
|
||||
$addresses: ident,
|
||||
$no_deadlock_in_multisig_completed: ident,
|
||||
) => {
|
||||
use core::{pin::Pin, future::Future};
|
||||
use $crate::tests::{
|
||||
init_logger, test_key_gen, test_scanner, test_no_deadlock_in_multisig_completed, test_signer,
|
||||
test_wallet, test_addresses,
|
||||
init_logger,
|
||||
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]
|
||||
async fn $key_gen() {
|
||||
init_logger();
|
||||
@@ -54,34 +53,8 @@ macro_rules! test_network {
|
||||
init_logger();
|
||||
let docker = $docker();
|
||||
docker.run(|ops| async move {
|
||||
test_scanner($network(&ops).await).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;
|
||||
let new_network = $network(&ops).await;
|
||||
test_scanner(new_network).await;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -90,7 +63,66 @@ macro_rules! test_network {
|
||||
init_logger();
|
||||
let docker = $docker();
|
||||
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 ciphersuite::Ciphersuite;
|
||||
use rand_core::OsRng;
|
||||
|
||||
use ciphersuite::{group::GroupEncoding, Ciphersuite};
|
||||
use frost::{Participant, tests::key_gen};
|
||||
|
||||
use tokio::{sync::Mutex, time::timeout};
|
||||
|
||||
use serai_db::{DbTxn, Db, MemDb};
|
||||
use serai_client::validator_sets::primitives::Session;
|
||||
|
||||
use crate::{
|
||||
networks::{OutputType, Output, Block, UtxoNetwork},
|
||||
networks::{OutputType, Output, Block, Network},
|
||||
key_gen::NetworkKeyDb,
|
||||
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,
|
||||
db: &D,
|
||||
group_key: <N::Curve as Ciphersuite>::G,
|
||||
@@ -40,18 +42,27 @@ pub async fn new_scanner<N: UtxoNetwork, D: Db>(
|
||||
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 =
|
||||
frost::tests::key_gen::<_, N::Curve>(&mut OsRng).remove(&Participant::new(1).unwrap()).unwrap();
|
||||
N::tweak_keys(&mut keys);
|
||||
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
|
||||
for _ in 0 .. N::CONFIRMATIONS {
|
||||
network.mine_block().await;
|
||||
}
|
||||
|
||||
let db = MemDb::new();
|
||||
let first = Arc::new(Mutex::new(true));
|
||||
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());
|
||||
}
|
||||
|
||||
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
|
||||
for _ in 0 .. N::CONFIRMATIONS {
|
||||
network.mine_block().await;
|
||||
}
|
||||
|
||||
let mut db = MemDb::new();
|
||||
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
||||
assert!(current_keys.is_empty());
|
||||
|
||||
let mut txn = db.txn();
|
||||
// Register keys to cause Block events at CONFIRMATIONS (dropped since first keys),
|
||||
// CONFIRMATIONS + 1, and CONFIRMATIONS + 2
|
||||
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
|
||||
.register_key(
|
||||
&mut txn,
|
||||
network.get_latest_block_number().await.unwrap() + N::CONFIRMATIONS + i,
|
||||
{
|
||||
let mut keys = key_gen(&mut OsRng);
|
||||
for keys in keys.values_mut() {
|
||||
N::tweak_keys(keys);
|
||||
}
|
||||
keys[&Participant::new(1).unwrap()].group_key()
|
||||
},
|
||||
key,
|
||||
)
|
||||
.await;
|
||||
txn.commit();
|
||||
}
|
||||
txn.commit();
|
||||
|
||||
for _ in 0 .. (3 * N::CONFIRMATIONS) {
|
||||
network.mine_block().await;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use core::{pin::Pin, future::Future};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
use ciphersuite::group::GroupEncoding;
|
||||
use frost::{
|
||||
Participant, ThresholdKeys,
|
||||
dkg::tests::{key_gen, clone_without},
|
||||
@@ -16,14 +18,15 @@ use serai_client::{
|
||||
|
||||
use messages::sign::*;
|
||||
use crate::{
|
||||
Payment, Plan,
|
||||
networks::{Output, Transaction, Eventuality, UtxoNetwork},
|
||||
Payment,
|
||||
networks::{Output, Transaction, Eventuality, Network},
|
||||
key_gen::NetworkKeyDb,
|
||||
multisigs::scheduler::Scheduler,
|
||||
signer::Signer,
|
||||
};
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub async fn sign<N: UtxoNetwork>(
|
||||
pub async fn sign<N: Network>(
|
||||
network: N,
|
||||
session: Session,
|
||||
mut keys_txs: HashMap<
|
||||
@@ -153,53 +156,55 @@ pub async fn sign<N: UtxoNetwork>(
|
||||
typed_claim
|
||||
}
|
||||
|
||||
pub async fn test_signer<N: UtxoNetwork>(network: N)
|
||||
where
|
||||
<N::Scheduler as Scheduler<N>>::Addendum: From<()>,
|
||||
{
|
||||
pub async fn test_signer<N: Network>(
|
||||
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||
) {
|
||||
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();
|
||||
|
||||
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
|
||||
.get_outputs(&network.test_send(N::external_address(&network, key).await).await, key)
|
||||
.await;
|
||||
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 eventualities = vec![];
|
||||
for (i, keys) in keys.drain() {
|
||||
let (signable, eventuality) = network
|
||||
.prepare_send(
|
||||
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();
|
||||
let (signable, eventuality) =
|
||||
network.prepare_send(sync_block, plan.clone(), 0).await.unwrap().tx.unwrap();
|
||||
|
||||
eventualities.push(eventuality.clone());
|
||||
keys_txs.insert(i, (keys, (signable, eventuality)));
|
||||
@@ -217,11 +222,21 @@ where
|
||||
key,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(outputs.len(), 2);
|
||||
// Adjust the amount for the fees
|
||||
let amount = amount - tx.fee(&network).await;
|
||||
// Check either output since Monero will randomize its output order
|
||||
assert!((outputs[0].balance().amount.0 == amount) || (outputs[1].balance().amount.0 == amount));
|
||||
// Don't run if Ethereum as the received output will revert by the contract
|
||||
// (and therefore not actually exist)
|
||||
if N::NETWORK != NetworkId::Ethereum {
|
||||
assert_eq!(outputs.len(), 1 + usize::from(u8::from(plan.change.is_some())));
|
||||
// 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
|
||||
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 ciphersuite::group::GroupEncoding;
|
||||
use frost::{Participant, dkg::tests::key_gen};
|
||||
|
||||
use tokio::time::timeout;
|
||||
@@ -15,21 +17,19 @@ use serai_client::{
|
||||
|
||||
use crate::{
|
||||
Payment, Plan,
|
||||
networks::{Output, Transaction, Eventuality, Block, UtxoNetwork},
|
||||
networks::{Output, Transaction, Eventuality, Block, Network},
|
||||
key_gen::NetworkKeyDb,
|
||||
multisigs::{
|
||||
scanner::{ScannerEvent, Scanner},
|
||||
scheduler::Scheduler,
|
||||
scheduler::{self, Scheduler},
|
||||
},
|
||||
tests::sign,
|
||||
};
|
||||
|
||||
// Tests the Scanner, Scheduler, and Signer together
|
||||
pub async fn test_wallet<N: UtxoNetwork>(network: N) {
|
||||
// Mine blocks so there's a confirmed block
|
||||
for _ in 0 .. N::CONFIRMATIONS {
|
||||
network.mine_block().await;
|
||||
}
|
||||
|
||||
pub async fn test_wallet<N: Network>(
|
||||
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||
) {
|
||||
let mut keys = key_gen(&mut OsRng);
|
||||
for keys in keys.values_mut() {
|
||||
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 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());
|
||||
assert!(current_keys.is_empty());
|
||||
let (block_id, outputs) = {
|
||||
@@ -93,7 +105,13 @@ pub async fn test_wallet<N: UtxoNetwork>(network: N) {
|
||||
txn.commit();
|
||||
assert_eq!(plans.len(), 1);
|
||||
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!(
|
||||
plans[0].payments,
|
||||
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![];
|
||||
@@ -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 block = network.get_block(block_number).await.unwrap();
|
||||
let outputs = network.get_outputs(&block, key).await;
|
||||
assert_eq!(outputs.len(), 2);
|
||||
let amount = amount - tx.fee(&network).await;
|
||||
assert!((outputs[0].balance().amount.0 == amount) || (outputs[1].balance().amount.0 == amount));
|
||||
|
||||
// Don't run if Ethereum as the received output will revert by the contract
|
||||
// (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 {
|
||||
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;
|
||||
}
|
||||
|
||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||
ScannerEvent::Block { is_retirement_block, block: block_id, outputs: these_outputs } => {
|
||||
scanner.multisig_completed.send(false).unwrap();
|
||||
assert!(!is_retirement_block);
|
||||
assert_eq!(block_id, block.id());
|
||||
assert_eq!(these_outputs, outputs);
|
||||
if N::NETWORK != NetworkId::Ethereum {
|
||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||
ScannerEvent::Block { is_retirement_block, block: block_id, outputs: these_outputs } => {
|
||||
scanner.multisig_completed.send(false).unwrap();
|
||||
assert!(!is_retirement_block);
|
||||
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
|
||||
let mut txn = db.txn();
|
||||
assert_eq!(scanner.ack_block(&mut txn, block.id()).await.1, outputs);
|
||||
scanner.release_lock().await;
|
||||
txn.commit();
|
||||
// Check the Scanner DB can reload the outputs
|
||||
let mut txn = db.txn();
|
||||
assert_eq!(scanner.ack_block(&mut txn, block.id()).await.1, outputs);
|
||||
scanner.release_lock().await;
|
||||
txn.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ pub fn build(name: String) {
|
||||
// Check any additionally specified paths
|
||||
let meta = |path: PathBuf| (path.clone(), fs::metadata(path));
|
||||
let mut metadatas = match name.as_str() {
|
||||
"bitcoin" | "monero" => vec![],
|
||||
"bitcoin" | "ethereum" | "monero" => vec![],
|
||||
"message-queue" => vec![
|
||||
meta(repo_path.join("common")),
|
||||
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"] }
|
||||
|
||||
bitcoin-serai = { path = "../../coins/bitcoin" }
|
||||
ethereum-serai = { path = "../../coins/ethereum" }
|
||||
monero-serai = { path = "../../coins/monero" }
|
||||
|
||||
messages = { package = "serai-processor-messages", path = "../../processor/messages" }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3" }
|
||||
serai-client = { path = "../../substrate/client" }
|
||||
serai-db = { path = "../../common/db", default-features = false }
|
||||
serai-message-queue = { path = "../../message-queue" }
|
||||
|
||||
borsh = { version = "1", features = ["de_strict_order"] }
|
||||
|
||||
@@ -181,7 +181,28 @@ impl Coordinator {
|
||||
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 => {
|
||||
use monero_serai::rpc::HttpRpc;
|
||||
|
||||
@@ -271,7 +292,45 @@ impl Coordinator {
|
||||
block.consensus_encode(&mut block_buf).unwrap();
|
||||
(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 => {
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
|
||||
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]) {
|
||||
let rpc_url = network_rpc(self.network, ops, &self.network_handle);
|
||||
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 to = rpc.get_latest_block_number().await.unwrap();
|
||||
for coordinator in others {
|
||||
let from = Rpc::new(network_rpc(self.network, ops, &coordinator.network_handle))
|
||||
.await
|
||||
.expect("couldn't connect to the Bitcoin RPC")
|
||||
.get_latest_block_number()
|
||||
.await
|
||||
.unwrap() +
|
||||
1;
|
||||
let from = rpc.get_latest_block_number().await.unwrap() + 1;
|
||||
|
||||
for b in from ..= to {
|
||||
let mut buf = vec![];
|
||||
rpc
|
||||
@@ -360,11 +381,40 @@ impl Coordinator {
|
||||
.unwrap()
|
||||
.consensus_encode(&mut buf)
|
||||
.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 => {
|
||||
use monero_serai::rpc::HttpRpc;
|
||||
|
||||
@@ -378,12 +428,21 @@ impl Coordinator {
|
||||
.await
|
||||
.unwrap();
|
||||
for b in from .. to {
|
||||
coordinator
|
||||
.broadcast_block(
|
||||
ops,
|
||||
&rpc.get_block(rpc.get_block_hash(b).await.unwrap()).await.unwrap().serialize(),
|
||||
)
|
||||
.await;
|
||||
let block =
|
||||
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
|
||||
.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.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 => {
|
||||
use monero_serai::{transaction::Transaction, rpc::HttpRpc};
|
||||
|
||||
@@ -445,7 +516,26 @@ impl Coordinator {
|
||||
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 => {
|
||||
use monero_serai::rpc::HttpRpc;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ pub const RPC_USER: &str = "serai";
|
||||
pub const RPC_PASS: &str = "seraidex";
|
||||
|
||||
pub const BTC_PORT: u32 = 8332;
|
||||
pub const ETH_PORT: u32 = 8545;
|
||||
pub const XMR_PORT: u32 = 18081;
|
||||
|
||||
pub fn bitcoin_instance() -> (TestBodySpecification, u32) {
|
||||
@@ -31,6 +32,17 @@ pub fn bitcoin_instance() -> (TestBodySpecification, u32) {
|
||||
(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) {
|
||||
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) {
|
||||
match network {
|
||||
NetworkId::Bitcoin => bitcoin_instance(),
|
||||
NetworkId::Ethereum => todo!(),
|
||||
NetworkId::Ethereum => ethereum_instance(),
|
||||
NetworkId::Monero => monero_instance(),
|
||||
NetworkId::Serai => {
|
||||
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)
|
||||
.host_port(match network {
|
||||
NetworkId::Bitcoin => BTC_PORT,
|
||||
NetworkId::Ethereum => todo!(),
|
||||
NetworkId::Ethereum => ETH_PORT,
|
||||
NetworkId::Monero => XMR_PORT,
|
||||
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::*;
|
||||
match network {
|
||||
NetworkId::Bitcoin => Bitcoin::CONFIRMATIONS,
|
||||
NetworkId::Ethereum => todo!(),
|
||||
NetworkId::Ethereum => Ethereum::<serai_db::MemDb>::CONFIRMATIONS,
|
||||
NetworkId::Monero => Monero::CONFIRMATIONS,
|
||||
NetworkId::Serai => panic!("getting confirmations required for Serai"),
|
||||
}
|
||||
@@ -83,6 +95,10 @@ pub enum Wallet {
|
||||
public_key: bitcoin_serai::bitcoin::PublicKey,
|
||||
input_tx: bitcoin_serai::bitcoin::Transaction,
|
||||
},
|
||||
Ethereum {
|
||||
key: <ciphersuite::Secp256k1 as Ciphersuite>::F,
|
||||
nonce: u64,
|
||||
},
|
||||
Monero {
|
||||
handle: String,
|
||||
spend_key: Zeroizing<curve25519_dalek::scalar::Scalar>,
|
||||
@@ -138,7 +154,37 @@ impl Wallet {
|
||||
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 => {
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
|
||||
@@ -282,6 +328,24 @@ impl Wallet {
|
||||
(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 } => {
|
||||
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
|
||||
use monero_serai::{
|
||||
@@ -374,6 +438,13 @@ impl Wallet {
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
Wallet::Ethereum { key, .. } => {
|
||||
use ciphersuite::{Ciphersuite, Secp256k1};
|
||||
ExternalAddress::new(
|
||||
ethereum_serai::crypto::address(&(Secp256k1::generator() * key)).into(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
Wallet::Monero { view_pair, .. } => {
|
||||
use monero_serai::wallet::address::{Network, AddressSpec};
|
||||
ExternalAddress::new(
|
||||
|
||||
Reference in New Issue
Block a user