From 169eba3f9715424eea6b0e1065fb1f4fc0471a26 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Mon, 11 Nov 2024 17:58:45 +0800 Subject: [PATCH 1/3] wip eth indexer --- chain-signatures/Cargo.lock | 357 ++++++++++++++++-- chain-signatures/node/Cargo.toml | 1 + chain-signatures/node/src/cli.rs | 15 + chain-signatures/node/src/indexer_eth.rs | 250 ++++++++++++ chain-signatures/node/src/types.rs | 99 +++++ integration-tests/chain-signatures/Cargo.lock | 1 + 6 files changed, 702 insertions(+), 21 deletions(-) create mode 100644 chain-signatures/node/src/indexer_eth.rs diff --git a/chain-signatures/Cargo.lock b/chain-signatures/Cargo.lock index aa0151ec0..220ef414c 100644 --- a/chain-signatures/Cargo.lock +++ b/chain-signatures/Cargo.lock @@ -865,6 +865,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -941,7 +947,16 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", ] [[package]] @@ -1032,6 +1047,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "byteorder" version = "1.5.0" @@ -1091,7 +1112,7 @@ source = "git+https://github.com/LIT-Protocol/cait-sith.git?rev=8ad2316#8ad2316f dependencies = [ "auto_ops", "ck-meow", - "digest", + "digest 0.10.7", "ecdsa 0.16.9", "elliptic-curve 0.13.8", "event-listener 2.5.3", @@ -1508,7 +1529,7 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "curve25519-dalek-derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -1787,13 +1808,22 @@ dependencies = [ "syn 2.0.82", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -1863,7 +1893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der 0.7.9", - "digest", + "digest 0.10.7", "elliptic-curve 0.13.8", "rfc6979 0.4.0", "serdect", @@ -1918,7 +1948,7 @@ dependencies = [ "base16ct 0.1.1", "crypto-bigint 0.4.9", "der 0.6.1", - "digest", + "digest 0.10.7", "ff 0.12.1", "generic-array", "group 0.12.1", @@ -1937,7 +1967,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct 0.2.0", "crypto-bigint 0.5.5", - "digest", + "digest 0.10.7", "ff 0.13.0", "generic-array", "group 0.13.0", @@ -1995,6 +2025,50 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash 0.8.0", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash 0.8.0", + "impl-rlp", + "impl-serde", + "primitive-types 0.12.2", + "uint", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -2111,6 +2185,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "flate2" version = "1.0.34" @@ -2281,6 +2367,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -2504,6 +2596,30 @@ dependencies = [ "foldhash", ] +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + [[package]] name = "heck" version = "0.4.1" @@ -2558,7 +2674,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -2580,7 +2696,7 @@ dependencies = [ "aes-gcm", "byteorder", "chacha20poly1305", - "digest", + "digest 0.10.7", "generic-array", "hkdf", "hmac", @@ -2850,6 +2966,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.5.0" @@ -2860,6 +2986,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "indent_write" version = "2.2.0" @@ -3030,6 +3194,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "jsonrpc-core" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" +dependencies = [ + "futures", + "futures-executor", + "futures-util", + "log", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "k256" version = "0.13.4" @@ -3192,7 +3371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if 1.0.0", - "digest", + "digest 0.10.7", ] [[package]] @@ -3256,7 +3435,7 @@ dependencies = [ "anyhow", "borsh", "crypto-shared", - "digest", + "digest 0.10.7", "ecdsa 0.16.9", "k256", "near-crypto", @@ -3338,6 +3517,7 @@ dependencies = [ "tracing-stackdriver", "tracing-subscriber", "url 2.5.2", + "web3", ] [[package]] @@ -3473,7 +3653,7 @@ dependencies = [ "near-config-utils", "near-stdx", "once_cell", - "primitive-types", + "primitive-types 0.10.1", "rand", "secp256k1", "serde", @@ -3676,7 +3856,7 @@ dependencies = [ "num-rational", "once_cell", "ordered-float", - "primitive-types", + "primitive-types 0.10.1", "rand", "rand_chacha", "serde", @@ -4198,6 +4378,32 @@ dependencies = [ "primeorder", ] +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "parking" version = "2.2.1" @@ -4250,7 +4456,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest", + "digest 0.10.7", "hmac", "password-hash", "sha2", @@ -4463,7 +4669,20 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" dependencies = [ - "fixed-hash", + "fixed-hash 0.7.0", + "uint", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash 0.8.0", + "impl-codec", + "impl-rlp", + "impl-serde", "uint", ] @@ -4867,7 +5086,17 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", ] [[package]] @@ -5365,6 +5594,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha1" version = "0.10.6" @@ -5373,7 +5615,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -5390,7 +5632,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -5399,7 +5641,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] @@ -5433,7 +5675,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest", + "digest 0.10.7", "rand_core", ] @@ -5443,7 +5685,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core", ] @@ -5516,6 +5758,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "soketto" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +dependencies = [ + "base64 0.13.1", + "bytes", + "futures", + "httparse", + "log", + "rand", + "sha-1", +] + [[package]] name = "spin" version = "0.9.8" @@ -5885,6 +6142,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -6001,6 +6267,7 @@ checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -6455,6 +6722,54 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web3" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5388522c899d1e1c96a4c307e3797e0f697ba7c77dd8e0e625ecba9dd0342937" +dependencies = [ + "arrayvec", + "base64 0.21.7", + "bytes", + "derive_more", + "ethabi", + "ethereum-types", + "futures", + "futures-timer", + "headers", + "hex", + "idna 0.4.0", + "jsonrpc-core", + "log", + "once_cell", + "parking_lot", + "pin-project", + "reqwest 0.11.27", + "rlp", + "secp256k1", + "serde", + "serde_json", + "soketto", + "tiny-keccak", + "tokio", + "tokio-stream", + "tokio-util", + "url 2.5.2", + "web3-async-native-tls", +] + +[[package]] +name = "web3-async-native-tls" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb" +dependencies = [ + "native-tls", + "thiserror", + "tokio", + "url 2.5.2", +] + [[package]] name = "webpki-roots" version = "0.26.6" diff --git a/chain-signatures/node/Cargo.toml b/chain-signatures/node/Cargo.toml index f8f089d61..a4de5528c 100644 --- a/chain-signatures/node/Cargo.toml +++ b/chain-signatures/node/Cargo.toml @@ -43,6 +43,7 @@ tokio-retry = "0.3" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-stackdriver = "0.10.0" +web3 = "0.19.0" url = { version = "2.4.0", features = ["serde"] } near-account-id = "1.0.0" diff --git a/chain-signatures/node/src/cli.rs b/chain-signatures/node/src/cli.rs index 83131e2df..8b5090e57 100644 --- a/chain-signatures/node/src/cli.rs +++ b/chain-signatures/node/src/cli.rs @@ -51,6 +51,9 @@ pub enum Cli { /// NEAR Lake Indexer options #[clap(flatten)] indexer_options: indexer::Options, + /// Ethereum Indexer options + #[clap(flatten)] + eth_indexer_options: eth_indexer::Options, /// Local address that other peers can use to message this node. #[arg(long, env("MPC_LOCAL_ADDRESS"))] my_address: Option, @@ -182,6 +185,7 @@ pub fn run(cmd: Cli) -> anyhow::Result<()> { cipher_sk, sign_sk, indexer_options, + eth_indexer_options, my_address, storage_options, override_config, @@ -203,6 +207,15 @@ pub fn run(cmd: Cli) -> anyhow::Result<()> { &gcp_service, &rt, )?; + let eth_indexer_handle = indexer_eth::run( + &indexer_options, + ð_indexer_options, + &mpc_contract_id, + &account_id, + &sign_queue, + &gcp_service, + &rt, + )?; let key_storage = storage::secret_storage::init(Some(&gcp_service), &storage_options, &account_id); @@ -274,6 +287,8 @@ pub fn run(cmd: Cli) -> anyhow::Result<()> { tracing::info!("spinning down"); indexer_handle.join().unwrap()?; + eth_indexer_handle.join().unwrap()?; + anyhow::Ok(()) })?; } diff --git a/chain-signatures/node/src/indexer_eth.rs b/chain-signatures/node/src/indexer_eth.rs new file mode 100644 index 000000000..dc162bbc6 --- /dev/null +++ b/chain-signatures/node/src/indexer_eth.rs @@ -0,0 +1,250 @@ +use crate::gcp::error::DatastoreStorageError; +use crate::gcp::GcpService; +use crate::protocol::{SignQueue, SignRequest}; +use crate::types::EthLatestBlockHeight; +use crypto_shared::{derive_epsilon, ScalarExt}; +use ethers::prelude::*; +use k256::Scalar; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use std::thread::JoinHandle; +use std::time::{Duration, Instant}; +use tokio::sync::RwLock; +use web3::{ + contract::{Contract, Options}, + types::{BlockNumber, FilterBuilder, Log, H160, H256}, + Web3, +}; + +/// Configures Ethereum indexer. +#[derive(Debug, Clone, clap::Parser)] +#[group(id = "indexer_eth_options")] +pub struct Options { + /// Ethereum RPC URL + #[clap(long, env("MPC_INDEXER_ETH_RPC_URL"))] + pub eth_rpc_url: String, + + /// The contract address to watch + #[clap(long, env("MPC_INDEXER_ETH_CONTRACT_ADDRESS"))] + pub eth_contract_address: String, + + /// The block height to start indexing from + #[clap(long, env("MPC_INDEXER_ETH_START_BLOCK"), default_value = "0")] + pub eth_start_block_height: u64, + + /// The amount of time before we consider the indexer behind + #[clap(long, env("MPC_INDEXER_ETH_BEHIND_THRESHOLD"), default_value = "180")] + pub eth_behind_threshold: u64, + + /// The threshold to check if indexer needs restart + #[clap(long, env("MPC_INDEXER_ETH_RUNNING_THRESHOLD"), default_value = "300")] + pub eth_running_threshold: u64, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct EthSignRequest { + pub payload: [u8; 32], + pub path: String, + pub key_version: u32, +} + +#[derive(Debug, Clone)] +pub struct EthIndexer { + latest_block_height: Arc>, + last_updated_timestamp: Arc>, + running_threshold: Duration, + behind_threshold: Duration, +} + +impl EthIndexer { + fn new(latest_block_height: LatestBlockHeight, options: &Options) -> Self { + tracing::info!( + "creating new ethereum indexer, latest block height: {}", + latest_block_height.block_height + ); + Self { + latest_block_height: Arc::new(RwLock::new(latest_block_height)), + last_updated_timestamp: Arc::new(RwLock::new(Instant::now())), + running_threshold: Duration::from_secs(options.running_threshold), + behind_threshold: Duration::from_secs(options.behind_threshold), + } + } + + pub async fn latest_block_height(&self) -> u64 { + self.latest_block_height.read().await.block_height + } + + pub async fn is_on_track(&self) -> bool { + self.last_updated_timestamp.read().await.elapsed() <= self.behind_threshold + } + + pub async fn is_running(&self) -> bool { + self.last_updated_timestamp.read().await.elapsed() <= self.running_threshold + } + + pub async fn is_behind(&self) -> bool { + self.last_updated_timestamp.read().await.elapsed() > self.behind_threshold + } + + async fn update_block_height( + &self, + block_height: u64, + gcp: &GcpService, + ) -> Result<(), DatastoreStorageError> { + tracing::debug!(block_height, "eth indexer update_block_height"); + *self.last_updated_timestamp.write().await = Instant::now(); + self.latest_block_height + .write() + .await + .set(block_height) + .store(gcp) + .await + } +} + +#[derive(Clone)] +struct Context { + contract_address: H160, + web3: Web3, + contract: Contract, + gcp_service: GcpService, + queue: Arc>, + indexer: EthIndexer, +} + +async fn handle_block( + block_number: u64, + ctx: &Context, +) -> anyhow::Result<()> { + tracing::debug!(block_height = block_number, "handle eth block"); + + // Create filter for the specific block and SignatureRequested event + let signature_requested_topic = H256::from_slice(&web3::signing::keccak256(b"SignatureRequested(bytes32,address,uint256,uint256,string)"))?; + + let filter = FilterBuilder::default() + .from_block(BlockNumber::Number(block_number.into())) + .to_block(BlockNumber::Number(block_number.into())) + .address(vec![ctx.contract_address]) + .topic0(vec![signature_requested_topic]) // Filter for SignatureRequested event only + .build(); + + // Get logs using filter + let logs = ctx.web3.eth().logs(filter).await?; + + for log in logs { + let event = parse_event(&log)?; + tracing::info!("Found eth event: {:?}", event); + // Create sign request from event + let sign_request = EthSignRequest { + payload: event.message_hash, + path: format!("eth/{}", event.request_id), + key_version: 0, + }; + + // Add to queue + ctx.queue.write().await.push(SignRequest::Eth(sign_request)); + } + + ctx.indexer + .update_block_height(block_number, &ctx.gcp_service) + .await?; + + Ok(()) +} + +// Helper function to parse event logs +fn parse_event(log: &Log) -> anyhow::Result { + // Ensure we have enough topics + if log.topics.len() < 2 { + anyhow::bail!("Invalid number of topics"); + } + + // Parse requester address from topics[1] + let requester = H160::from_slice(&log.topics[1].as_fixed_bytes()[12..]); + + // Parse request_id and message_hash from data + let data = log.data.0.as_slice(); + let request_id = web3::types::U256::from_big_endian(&data[0..32]); + let mut message_hash = [0u8; 32]; + message_hash.copy_from_slice(&data[32..64]); + + Ok(SignatureRequestedEvent { + requester, + request_id, + message_hash, + }) +} + +pub fn run( + options: &Options, + queue: &Arc>, + gcp_service: &GcpService, + rt: &tokio::runtime::Runtime, +) -> anyhow::Result<(JoinHandle>, EthIndexer)> { + let transport = web3::transports::Http::new(&options.eth_rpc_url)?; + let web3 = Web3::new(transport); + + let contract_address = H160::from_str(&options.eth_contract_address)?; + + // Load contract ABI + let contract = Contract::from_json( + web3.eth(), + contract_address, + include_bytes!("../abi/YourContract.json") + )?; + + let context = Context { + contract_address, + web3, + contract, + gcp_service: gcp_service.clone(), + queue: queue.clone(), + indexer: indexer.clone(), + }; + + let latest_block_height = rt.block_on(async { + match EthLatestBlockHeight::fetch(gcp_service).await { + Ok(latest) => latest, + Err(err) => { + tracing::warn!(%err, "failed to fetch eth latest block height; using start_block_height={} instead", options.start_block_height); + EthLatestBlockHeight { + account_id: node_account_id.clone(), + block_height: options.start_block_height, + } + } + } + }); + + let indexer = EthIndexer::new(latest_block_height, options); + + let options = options.clone(); + let join_handle = std::thread::spawn(move || { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build()?; + + rt.block_on(async { + let provider = Provider::::try_from(options.eth_rpc_url.as_str())?; + + loop { + let latest_block = provider.get_block_number().await?; + let current_block = context.indexer.latest_block_height().await; + + if current_block < latest_block.as_u64() { + handle_block(current_block, &context).await?; + } else { + tokio::time::sleep(Duration::from_secs(1)).await; + } + } + }) + }); + + Ok((join_handle, indexer)) +} + +#[derive(Debug)] +struct SignatureRequestedEvent { + requester: H160, + request_id: web3::types::U256, + message_hash: [u8; 32], +} \ No newline at end of file diff --git a/chain-signatures/node/src/types.rs b/chain-signatures/node/src/types.rs index 704bba090..c4804457d 100644 --- a/chain-signatures/node/src/types.rs +++ b/chain-signatures/node/src/types.rs @@ -236,3 +236,102 @@ impl KeyKind for &LatestBlockHeight { "LatestBlockHeight".to_string() } } + +pub struct EthLatestBlockHeight { + pub account_id: AccountId, + pub block_height: u64, +} + +impl EthLatestBlockHeight { + pub async fn fetch(gcp: &GcpService) -> DatastoreResult { + gcp.datastore + .get(format!("{}/eth-latest-block-height", gcp.account_id)) + .await + } + + pub fn set(&mut self, block_height: u64) -> &mut Self { + self.block_height = block_height; + self + } + + pub async fn store(&self, gcp: &GcpService) -> DatastoreResult<()> { + gcp.datastore.upsert(self).await + } +} + +impl IntoValue for LatestBlockHeight { + fn into_value(self) -> Value { + (&self).into_value() + } +} + +impl IntoValue for &EthLatestBlockHeight { + fn into_value(self) -> Value { + let properties = { + let mut properties = std::collections::HashMap::new(); + properties.insert( + "account_id".to_string(), + Value::StringValue(self.account_id.to_string()), + ); + properties.insert( + "block_height".to_string(), + Value::IntegerValue(self.block_height as i64), + ); + properties + }; + Value::EntityValue { + key: google_datastore1::api::Key { + path: Some(vec![google_datastore1::api::PathElement { + kind: Some(EthLatestBlockHeight::kind()), + name: Some(format!("{}/eth-latest-block-height", self.account_id)), + id: None, + }]), + partition_id: None, + }, + properties, + } + } +} + +impl FromValue for EthLatestBlockHeight { + fn from_value(value: Value) -> Result { + match value { + Value::EntityValue { + key: _, + mut properties, + } => { + let account_id = properties + .remove("account_id") + .ok_or_else(|| ConvertError::MissingProperty("account_id".to_string()))?; + let account_id = String::from_value(account_id)?.parse().map_err(|err| { + ConvertError::MalformedProperty(format!( + "EthLatestBlockHeight failed to parse account_id: {err:?}", + )) + })?; + + let block_height = properties + .remove("block_height") + .ok_or_else(|| ConvertError::MissingProperty("block_height".to_string()))?; + let block_height = i64::from_value(block_height)? as u64; + + Ok(EthLatestBlockHeight { account_id, block_height }) + } + _ => Err(ConvertError::UnexpectedPropertyType { + expected: String::from("integer"), + got: String::from(value.type_name()), + }), + } + } +} + +impl KeyKind for EthLatestBlockHeight { + fn kind() -> String { + "EthLatestBlockHeight".to_string() + } +} + +impl KeyKind for &EthLatestBlockHeight { + fn kind() -> String { + "EthLatestBlockHeight".to_string() + } +} diff --git a/integration-tests/chain-signatures/Cargo.lock b/integration-tests/chain-signatures/Cargo.lock index df1299628..ed5441970 100644 --- a/integration-tests/chain-signatures/Cargo.lock +++ b/integration-tests/chain-signatures/Cargo.lock @@ -3791,6 +3791,7 @@ dependencies = [ "tracing-stackdriver", "tracing-subscriber", "url 2.5.1", + "web3", ] [[package]] From 07a0616d9b7c2e4c790dea9444d5d6d8f60cba8a Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Mon, 18 Nov 2024 12:00:55 +0800 Subject: [PATCH 2/3] basically compiles --- chain-signatures/crypto-shared/src/kdf.rs | 11 +- chain-signatures/node/src/cli.rs | 14 +- chain-signatures/node/src/indexer.rs | 3 +- chain-signatures/node/src/indexer_eth.rs | 162 ++++++++++++------ chain-signatures/node/src/lib.rs | 1 + chain-signatures/node/src/protocol/mod.rs | 1 + .../node/src/protocol/signature.rs | 6 + chain-signatures/node/src/types.rs | 8 +- 8 files changed, 138 insertions(+), 68 deletions(-) diff --git a/chain-signatures/crypto-shared/src/kdf.rs b/chain-signatures/crypto-shared/src/kdf.rs index e18b1a1e1..5089e3201 100644 --- a/chain-signatures/crypto-shared/src/kdf.rs +++ b/chain-signatures/crypto-shared/src/kdf.rs @@ -6,7 +6,7 @@ use k256::{ Scalar, Secp256k1, SecretKey, }; use near_account_id::AccountId; -use sha3::{Digest, Sha3_256}; +use sha3::{Digest, Keccak256, Sha3_256}; // Constant prefix that ensures epsilon derivation values are used specifically for // near-mpc-recovery with key derivation protocol vX.Y.Z. @@ -28,6 +28,15 @@ pub fn derive_epsilon(predecessor_id: &AccountId, path: &str) -> Scalar { Scalar::from_non_biased(hash) } +const EPSILON_DERIVATION_PREFIX_ETH: &str = "near-mpc-recovery v0.2.0 epsilon derivation:"; +pub fn derive_epsilon_eth(requester: String, path: &str) -> Scalar { + let derivation_path = format!("{EPSILON_DERIVATION_PREFIX_ETH}{},{}", requester, path); + let mut hasher = Keccak256::new(); + hasher.update(derivation_path); + let hash: [u8; 32] = hasher.finalize().into(); + Scalar::from_non_biased(hash) +} + pub fn derive_key(public_key: PublicKey, epsilon: Scalar) -> PublicKey { (::ProjectivePoint::GENERATOR * epsilon + public_key).to_affine() } diff --git a/chain-signatures/node/src/cli.rs b/chain-signatures/node/src/cli.rs index 8b5090e57..452df42d1 100644 --- a/chain-signatures/node/src/cli.rs +++ b/chain-signatures/node/src/cli.rs @@ -1,7 +1,7 @@ use crate::config::{Config, LocalConfig, NetworkConfig, OverrideConfig}; use crate::gcp::GcpService; use crate::protocol::{MpcSignProtocol, SignQueue}; -use crate::{http_client, indexer, mesh, storage, web}; +use crate::{http_client, indexer, indexer_eth, mesh, storage, web}; use clap::Parser; use deadpool_redis::Runtime; use local_ip_address::local_ip; @@ -53,7 +53,7 @@ pub enum Cli { indexer_options: indexer::Options, /// Ethereum Indexer options #[clap(flatten)] - eth_indexer_options: eth_indexer::Options, + indexer_eth_options: indexer_eth::Options, /// Local address that other peers can use to message this node. #[arg(long, env("MPC_LOCAL_ADDRESS"))] my_address: Option, @@ -86,6 +86,7 @@ impl Cli { cipher_sk, sign_sk, indexer_options, + indexer_eth_options, my_address, storage_options, override_config, @@ -130,6 +131,7 @@ impl Cli { } args.extend(indexer_options.into_str_args()); + args.extend(indexer_eth_options.into_str_args()); args.extend(storage_options.into_str_args()); args.extend(mesh_options.into_str_args()); args.extend(message_options.into_str_args()); @@ -185,7 +187,7 @@ pub fn run(cmd: Cli) -> anyhow::Result<()> { cipher_sk, sign_sk, indexer_options, - eth_indexer_options, + indexer_eth_options, my_address, storage_options, override_config, @@ -207,10 +209,8 @@ pub fn run(cmd: Cli) -> anyhow::Result<()> { &gcp_service, &rt, )?; - let eth_indexer_handle = indexer_eth::run( - &indexer_options, - ð_indexer_options, - &mpc_contract_id, + let (eth_indexer_handle, eth_indexer) = indexer_eth::run( + &indexer_eth_options, &account_id, &sign_queue, &gcp_service, diff --git a/chain-signatures/node/src/indexer.rs b/chain-signatures/node/src/indexer.rs index a744b69d5..6a7878361 100644 --- a/chain-signatures/node/src/indexer.rs +++ b/chain-signatures/node/src/indexer.rs @@ -1,6 +1,6 @@ use crate::gcp::error::DatastoreStorageError; use crate::gcp::GcpService; -use crate::protocol::{SignQueue, SignRequest}; +use crate::protocol::{Chain, SignQueue, SignRequest}; use crate::types::LatestBlockHeight; use crypto_shared::{derive_epsilon, ScalarExt}; use k256::Scalar; @@ -257,6 +257,7 @@ async fn handle_block( entropy, // TODO: use indexer timestamp instead. time_added: Instant::now(), + chain: Chain::NEAR, }); } } diff --git a/chain-signatures/node/src/indexer_eth.rs b/chain-signatures/node/src/indexer_eth.rs index dc162bbc6..c18d8d7b5 100644 --- a/chain-signatures/node/src/indexer_eth.rs +++ b/chain-signatures/node/src/indexer_eth.rs @@ -1,18 +1,21 @@ use crate::gcp::error::DatastoreStorageError; use crate::gcp::GcpService; -use crate::protocol::{SignQueue, SignRequest}; +use crate::indexer::ContractSignRequest; +use crate::protocol::{Chain, SignQueue, SignRequest}; use crate::types::EthLatestBlockHeight; +use crypto_shared::kdf::derive_epsilon_eth; use crypto_shared::{derive_epsilon, ScalarExt}; -use ethers::prelude::*; use k256::Scalar; +use near_account_id::AccountId; use serde::{Deserialize, Serialize}; +use std::str::FromStr; use std::sync::Arc; use std::thread::JoinHandle; use std::time::{Duration, Instant}; use tokio::sync::RwLock; use web3::{ - contract::{Contract, Options}, - types::{BlockNumber, FilterBuilder, Log, H160, H256}, + contract::Contract, + types::{BlockNumber, FilterBuilder, Log, H160, H256, U256}, Web3, }; @@ -41,6 +44,25 @@ pub struct Options { pub eth_running_threshold: u64, } +impl Options { + pub fn into_str_args(self) -> Vec { + let mut args = Vec::new(); + args.extend([ + "--eth-rpc-url".to_string(), + self.eth_rpc_url, + "--eth-contract-address".to_string(), + self.eth_contract_address, + "--eth-start-block".to_string(), + self.eth_start_block_height.to_string(), + "--eth-behind-threshold".to_string(), + self.eth_behind_threshold.to_string(), + "--eth-running-threshold".to_string(), + self.eth_running_threshold.to_string(), + ]); + args + } +} + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct EthSignRequest { pub payload: [u8; 32], @@ -57,7 +79,7 @@ pub struct EthIndexer { } impl EthIndexer { - fn new(latest_block_height: LatestBlockHeight, options: &Options) -> Self { + fn new(latest_block_height: EthLatestBlockHeight, options: &Options) -> Self { tracing::info!( "creating new ethereum indexer, latest block height: {}", latest_block_height.block_height @@ -65,8 +87,8 @@ impl EthIndexer { Self { latest_block_height: Arc::new(RwLock::new(latest_block_height)), last_updated_timestamp: Arc::new(RwLock::new(Instant::now())), - running_threshold: Duration::from_secs(options.running_threshold), - behind_threshold: Duration::from_secs(options.behind_threshold), + running_threshold: Duration::from_secs(options.eth_running_threshold), + behind_threshold: Duration::from_secs(options.eth_behind_threshold), } } @@ -106,44 +128,64 @@ impl EthIndexer { struct Context { contract_address: H160, web3: Web3, - contract: Contract, gcp_service: GcpService, queue: Arc>, indexer: EthIndexer, } -async fn handle_block( - block_number: u64, - ctx: &Context, -) -> anyhow::Result<()> { +async fn handle_block(block_number: u64, ctx: &Context) -> anyhow::Result<()> { tracing::debug!(block_height = block_number, "handle eth block"); // Create filter for the specific block and SignatureRequested event - let signature_requested_topic = H256::from_slice(&web3::signing::keccak256(b"SignatureRequested(bytes32,address,uint256,uint256,string)"))?; - + let signature_requested_topic = H256::from_slice(&web3::signing::keccak256( + b"SignatureRequested(bytes32,address,uint256,uint256,string)", + )); + let filter = FilterBuilder::default() .from_block(BlockNumber::Number(block_number.into())) .to_block(BlockNumber::Number(block_number.into())) .address(vec![ctx.contract_address]) - .topic0(vec![signature_requested_topic]) // Filter for SignatureRequested event only + .topics(Some(vec![signature_requested_topic]), None, None, None) .build(); + let mut pending_requests = Vec::new(); // Get logs using filter let logs = ctx.web3.eth().logs(filter).await?; - for log in logs { let event = parse_event(&log)?; tracing::info!("Found eth event: {:?}", event); // Create sign request from event - let sign_request = EthSignRequest { - payload: event.message_hash, - path: format!("eth/{}", event.request_id), + let Some(payload) = Scalar::from_bytes(event.payload_hash) else { + tracing::warn!( + "eth `sign` did not produce payload hash correctly: {:?}", + event.payload_hash, + ); + continue; + }; + let request = ContractSignRequest { + payload, + path: event.path, key_version: 0, }; + let epsilon = derive_epsilon_eth(event.requester.to_string(), &request.path); + let entropy = [0u8; 32]; // TODO + let sign_request = SignRequest { + request_id: event.request_id, + request, + epsilon, + entropy, + // TODO: use indexer timestamp instead. + time_added: Instant::now(), + chain: Chain::Ethereum, + }; - // Add to queue - ctx.queue.write().await.push(SignRequest::Eth(sign_request)); + pending_requests.push(sign_request); } + let mut queue = ctx.queue.write().await; + for sign_request in pending_requests { + queue.add(sign_request); + } + drop(queue); ctx.indexer .update_block_height(block_number, &ctx.gcp_service) @@ -151,7 +193,6 @@ async fn handle_block( Ok(()) } - // Helper function to parse event logs fn parse_event(log: &Log) -> anyhow::Result { // Ensure we have enough topics @@ -159,75 +200,80 @@ fn parse_event(log: &Log) -> anyhow::Result { anyhow::bail!("Invalid number of topics"); } - // Parse requester address from topics[1] - let requester = H160::from_slice(&log.topics[1].as_fixed_bytes()[12..]); - - // Parse request_id and message_hash from data + // Parse request_id from topics[1] + let mut request_id = [0u8; 32]; + request_id.copy_from_slice(&log.topics[1].as_bytes()); + + // Parse data fields let data = log.data.0.as_slice(); - let request_id = web3::types::U256::from_big_endian(&data[0..32]); - let mut message_hash = [0u8; 32]; - message_hash.copy_from_slice(&data[32..64]); + + // Parse requester address (20 bytes) + let requester = H160::from_slice(&data[12..32]); + + // Parse epsilon (32 bytes) + let epsilon = U256::from_big_endian(&data[32..64]); + + // Parse payload hash (32 bytes) + let mut payload_hash = [0u8; 32]; + payload_hash.copy_from_slice(&data[64..96]); + + // Parse path string + let path_offset = U256::from_big_endian(&data[96..128]).as_usize(); + let path_length = U256::from_big_endian(&data[path_offset..path_offset + 32]).as_usize(); + let path_bytes = &data[path_offset + 32..path_offset + 32 + path_length]; + let path = String::from_utf8(path_bytes.to_vec())?; Ok(SignatureRequestedEvent { - requester, request_id, - message_hash, + requester, + epsilon, + payload_hash, + path, }) } pub fn run( options: &Options, + node_account_id: &AccountId, queue: &Arc>, gcp_service: &GcpService, rt: &tokio::runtime::Runtime, ) -> anyhow::Result<(JoinHandle>, EthIndexer)> { let transport = web3::transports::Http::new(&options.eth_rpc_url)?; let web3 = Web3::new(transport); - + let contract_address = H160::from_str(&options.eth_contract_address)?; - - // Load contract ABI - let contract = Contract::from_json( - web3.eth(), - contract_address, - include_bytes!("../abi/YourContract.json") - )?; - let context = Context { - contract_address, - web3, - contract, - gcp_service: gcp_service.clone(), - queue: queue.clone(), - indexer: indexer.clone(), - }; - let latest_block_height = rt.block_on(async { match EthLatestBlockHeight::fetch(gcp_service).await { Ok(latest) => latest, Err(err) => { - tracing::warn!(%err, "failed to fetch eth latest block height; using start_block_height={} instead", options.start_block_height); + tracing::warn!(%err, "failed to fetch eth latest block height; using start_block_height={} instead", options.eth_start_block_height); EthLatestBlockHeight { account_id: node_account_id.clone(), - block_height: options.start_block_height, + block_height: options.eth_start_block_height, } } } }); let indexer = EthIndexer::new(latest_block_height, options); + let context = Context { + contract_address, + web3, + gcp_service: gcp_service.clone(), + queue: queue.clone(), + indexer: indexer.clone(), + }; - let options = options.clone(); let join_handle = std::thread::spawn(move || { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build()?; rt.block_on(async { - let provider = Provider::::try_from(options.eth_rpc_url.as_str())?; - loop { - let latest_block = provider.get_block_number().await?; + let latest_block = context.web3.eth().block_number().await?; let current_block = context.indexer.latest_block_height().await; if current_block < latest_block.as_u64() { @@ -244,7 +290,9 @@ pub fn run( #[derive(Debug)] struct SignatureRequestedEvent { + request_id: [u8; 32], requester: H160, - request_id: web3::types::U256, - message_hash: [u8; 32], -} \ No newline at end of file + epsilon: U256, + payload_hash: [u8; 32], + path: String, +} diff --git a/chain-signatures/node/src/lib.rs b/chain-signatures/node/src/lib.rs index 5d1d7d058..a04c0b6c2 100644 --- a/chain-signatures/node/src/lib.rs +++ b/chain-signatures/node/src/lib.rs @@ -3,6 +3,7 @@ pub mod config; pub mod gcp; pub mod http_client; pub mod indexer; +mod indexer_eth; pub mod kdf; pub mod mesh; pub mod metrics; diff --git a/chain-signatures/node/src/protocol/mod.rs b/chain-signatures/node/src/protocol/mod.rs index 7109e7c4c..13a649aa0 100644 --- a/chain-signatures/node/src/protocol/mod.rs +++ b/chain-signatures/node/src/protocol/mod.rs @@ -13,6 +13,7 @@ pub use contract::primitives::ParticipantInfo; pub use contract::ProtocolState; pub use cryptography::CryptographicError; pub use message::MpcMessage; +pub use signature::Chain; pub use signature::SignQueue; pub use signature::SignRequest; pub use state::NodeState; diff --git a/chain-signatures/node/src/protocol/signature.rs b/chain-signatures/node/src/protocol/signature.rs index 4bdb898b4..b8bf7ab34 100644 --- a/chain-signatures/node/src/protocol/signature.rs +++ b/chain-signatures/node/src/protocol/signature.rs @@ -33,6 +33,12 @@ pub struct SignRequest { pub epsilon: Scalar, pub entropy: [u8; 32], pub time_added: Instant, + pub chain: Chain, +} + +pub enum Chain { + NEAR, + Ethereum, } /// Type that preserves the insertion order of requests. diff --git a/chain-signatures/node/src/types.rs b/chain-signatures/node/src/types.rs index c4804457d..3e1d23f3b 100644 --- a/chain-signatures/node/src/types.rs +++ b/chain-signatures/node/src/types.rs @@ -237,6 +237,7 @@ impl KeyKind for &LatestBlockHeight { } } +#[derive(Clone, Debug)] pub struct EthLatestBlockHeight { pub account_id: AccountId, pub block_height: u64, @@ -259,7 +260,7 @@ impl EthLatestBlockHeight { } } -impl IntoValue for LatestBlockHeight { +impl IntoValue for EthLatestBlockHeight { fn into_value(self) -> Value { (&self).into_value() } @@ -314,7 +315,10 @@ impl FromValue for EthLatestBlockHeight { .ok_or_else(|| ConvertError::MissingProperty("block_height".to_string()))?; let block_height = i64::from_value(block_height)? as u64; - Ok(EthLatestBlockHeight { account_id, block_height }) + Ok(EthLatestBlockHeight { + account_id, + block_height, + }) } _ => Err(ConvertError::UnexpectedPropertyType { expected: String::from("integer"), From acf0bba7c91ff7f8a00a6fbffce5bd3fe25af0fd Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Tue, 19 Nov 2024 10:48:07 +0800 Subject: [PATCH 3/3] fixes --- chain-signatures/node/src/indexer_eth.rs | 2 +- chain-signatures/node/src/lib.rs | 2 +- .../chain-signatures/src/containers.rs | 9 ++++++++- integration-tests/chain-signatures/src/local.rs | 16 ++++++++++++++++ .../chain-signatures/tests/cases/mod.rs | 12 ++++++++++++ 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/chain-signatures/node/src/indexer_eth.rs b/chain-signatures/node/src/indexer_eth.rs index c18d8d7b5..d0e4bec30 100644 --- a/chain-signatures/node/src/indexer_eth.rs +++ b/chain-signatures/node/src/indexer_eth.rs @@ -52,7 +52,7 @@ impl Options { self.eth_rpc_url, "--eth-contract-address".to_string(), self.eth_contract_address, - "--eth-start-block".to_string(), + "--eth-start-block-height".to_string(), self.eth_start_block_height.to_string(), "--eth-behind-threshold".to_string(), self.eth_behind_threshold.to_string(), diff --git a/chain-signatures/node/src/lib.rs b/chain-signatures/node/src/lib.rs index a04c0b6c2..edb0cc088 100644 --- a/chain-signatures/node/src/lib.rs +++ b/chain-signatures/node/src/lib.rs @@ -3,7 +3,7 @@ pub mod config; pub mod gcp; pub mod http_client; pub mod indexer; -mod indexer_eth; +pub mod indexer_eth; pub mod kdf; pub mod mesh; pub mod metrics; diff --git a/integration-tests/chain-signatures/src/containers.rs b/integration-tests/chain-signatures/src/containers.rs index 460d51d23..86bab6619 100644 --- a/integration-tests/chain-signatures/src/containers.rs +++ b/integration-tests/chain-signatures/src/containers.rs @@ -100,7 +100,13 @@ impl<'a> Node<'a> { running_threshold: 120, behind_threshold: 120, }; - + let indexer_eth_options = mpc_node::indexer_eth::Options { + eth_rpc_url: "http://localhost:8545".to_string(), + eth_contract_address: "0x5FbDB2315678afecb367f032d93F642f64180aa3".to_string(), + eth_start_block_height: 0, + eth_behind_threshold: 120, + eth_running_threshold: 120, + }; let args = mpc_node::cli::Cli::Start { near_rpc: config.near_rpc.clone(), mpc_contract_id: ctx.mpc_contract.id().clone(), @@ -110,6 +116,7 @@ impl<'a> Node<'a> { cipher_pk: hex::encode(config.cipher_pk.to_bytes()), cipher_sk: hex::encode(config.cipher_sk.to_bytes()), indexer_options: indexer_options.clone(), + indexer_eth_options, my_address: None, storage_options: ctx.storage_options.clone(), sign_sk: Some(config.sign_sk.clone()), diff --git a/integration-tests/chain-signatures/src/local.rs b/integration-tests/chain-signatures/src/local.rs index 923ccb155..a9a8f3ebd 100644 --- a/integration-tests/chain-signatures/src/local.rs +++ b/integration-tests/chain-signatures/src/local.rs @@ -56,6 +56,13 @@ impl Node { running_threshold: 120, behind_threshold: 120, }; + let indexer_eth_options = mpc_node::indexer_eth::Options { + eth_rpc_url: "http://localhost:8545".to_string(), + eth_contract_address: "0x5FbDB2315678afecb367f032d93F642f64180aa3".to_string(), + eth_start_block_height: 0, + eth_behind_threshold: 120, + eth_running_threshold: 120, + }; let near_rpc = ctx.lake_indexer.rpc_host_address.clone(); let mpc_contract_id = ctx.mpc_contract.id().clone(); let cli = mpc_node::cli::Cli::Start { @@ -68,6 +75,7 @@ impl Node { cipher_sk: hex::encode(cipher_sk.to_bytes()), sign_sk: Some(sign_sk.clone()), indexer_options, + indexer_eth_options, my_address: None, storage_options: ctx.storage_options.clone(), override_config: Some(OverrideConfig::new(serde_json::to_value( @@ -151,6 +159,13 @@ impl Node { running_threshold: 120, behind_threshold: 120, }; + let indexer_eth_options = mpc_node::indexer_eth::Options { + eth_rpc_url: "http://localhost:8545".to_string(), + eth_contract_address: "0x5FbDB2315678afecb367f032d93F642f64180aa3".to_string(), + eth_start_block_height: 0, + eth_behind_threshold: 120, + eth_running_threshold: 120, + }; let cli = mpc_node::cli::Cli::Start { near_rpc: config.near_rpc.clone(), mpc_contract_id: ctx.mpc_contract.id().clone(), @@ -161,6 +176,7 @@ impl Node { cipher_sk: hex::encode(config.cipher_sk.to_bytes()), sign_sk: Some(config.sign_sk.clone()), indexer_options, + indexer_eth_options, my_address: None, storage_options: ctx.storage_options.clone(), override_config: Some(OverrideConfig::new(serde_json::to_value( diff --git a/integration-tests/chain-signatures/tests/cases/mod.rs b/integration-tests/chain-signatures/tests/cases/mod.rs index 2f696b0b1..2663b44a5 100644 --- a/integration-tests/chain-signatures/tests/cases/mod.rs +++ b/integration-tests/chain-signatures/tests/cases/mod.rs @@ -111,6 +111,18 @@ async fn test_signature_basic() -> anyhow::Result<()> { .await } +#[test(tokio::test)] +async fn test_signature_from_eth() -> anyhow::Result<()> { + with_multichain_nodes(MultichainConfig::default(), |ctx| { + Box::pin(async move { + let state_0 = wait_for::running_mpc(&ctx, Some(0)).await?; + assert_eq!(state_0.participants.len(), 3); + Ok(()) + }) + }) + .await +} + #[test(tokio::test)] async fn test_signature_offline_node() -> anyhow::Result<()> { with_multichain_nodes(MultichainConfig::default(), |mut ctx| {