From 44be0c16620c0cbd550f50726605c04207b1e63c Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Thu, 11 Sep 2025 10:40:04 +0800 Subject: [PATCH 01/25] conf: add ws endpoint in chain config --- Cargo.lock | 9 --------- justfile | 3 +++ test-configs/README.md | 8 ++++---- test-configs/c0/node_0.toml | 1 + test-configs/c0/node_1.toml | 1 + test-configs/c0/node_2.toml | 1 + test-configs/c0/node_3.toml | 1 + test-configs/c0/node_4.toml | 1 + test-configs/docker/committee.toml | 2 +- test-configs/docker/node_0.toml | 1 + test-configs/docker/node_1.toml | 1 + test-configs/docker/node_2.toml | 1 + test-configs/docker/node_3.toml | 1 + test-configs/docker/node_4.toml | 1 + test-configs/nitro-ci-committee/node_0.toml | 1 + test-configs/nitro-ci-committee/node_1.toml | 1 + tests/src/tests/timeboost.rs | 5 +++++ tests/src/tests/timeboost/handover.rs | 5 +++++ tests/src/tests/timeboost/timeboost_handover.rs | 5 +++++ timeboost-config/src/binaries/mkconfig.rs | 5 +++++ timeboost-config/src/chain.rs | 1 + 21 files changed, 41 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4bbcc384..6850b4b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1594,15 +1594,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "anvil" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e29d3ca6c44fd6bc23219f6441303e329ee44580d3cee160df9c5d205e8fc83" -dependencies = [ - "thiserror 2.0.16", -] - [[package]] name = "anyhow" version = "1.0.99" diff --git a/justfile b/justfile index 5de77dce..c35ce5a6 100644 --- a/justfile +++ b/justfile @@ -99,6 +99,7 @@ mkconfig NUM_NODES DATETIME *ARGS: --http-api "127.0.0.1:8004" \ --chain-namespace 10101 \ --parent-rpc-url "http://127.0.0.1:8545" \ + --parent-ws-url "ws://127.0.0.1:8545" \ --parent-chain-id 31337 \ --parent-ibox-contract "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" \ --key-manager-contract "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" \ @@ -114,6 +115,7 @@ mkconfig_docker DATETIME *ARGS: --mode "increment-address" \ --chain-namespace 10101 \ --parent-rpc-url "http://127.0.0.1:8545" \ + --parent-ws-url "ws://127.0.0.1:8545" \ --parent-chain-id 31337 \ --parent-ibox-contract "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" \ --key-manager-contract "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" \ @@ -129,6 +131,7 @@ mkconfig_nitro DATETIME *ARGS: --nitro-addr "localhost:55000" \ --chain-namespace 412346 \ --parent-rpc-url "http://127.0.0.1:8545" \ + --parent-ws-url "ws://127.0.0.1:8545" \ --parent-chain-id 1337 \ --parent-ibox-contract "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" \ --key-manager-contract "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" \ diff --git a/test-configs/README.md b/test-configs/README.md index da9614f6..f74ae2c9 100644 --- a/test-configs/README.md +++ b/test-configs/README.md @@ -26,14 +26,14 @@ To generate configs for all nodes in a new committee: ``` sh # see mkconfig.rs Args or `mkconfig --help` for more options -just mkconfig 5 2025-01-09T02:00:00Z --seed 42 -just mkconfig 13 2025-01-09T02:00:00Z --nitro-addr "localhost:55000" +just mkconfig 5 2025-09-01T02:00:00Z --seed 42 +just mkconfig 13 2025-09-01T02:00:00Z --nitro-addr "localhost:55000" # recipe for docker env is fixed at 5 nodes -just mkconfig_docker 2025-01-09T02:00:00Z --seed 42 +just mkconfig_docker 2025-09-01T02:00:00Z --seed 42 # recipe for nitro CI test, fixed at 2 nodes with nitro chain config -just mkconfig_nitro 2025-01-09T02:00:00Z --seed 42 +just mkconfig_nitro 2025-09-01T02:00:00Z --seed 42 ``` ### On test wallet mnemonic diff --git a/test-configs/c0/node_0.toml b/test-configs/c0/node_0.toml index 0ee8bdd1..b9a70777 100644 --- a/test-configs/c0/node_0.toml +++ b/test-configs/c0/node_0.toml @@ -25,6 +25,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/c0/node_1.toml b/test-configs/c0/node_1.toml index 4bb87941..2bab51df 100644 --- a/test-configs/c0/node_1.toml +++ b/test-configs/c0/node_1.toml @@ -25,6 +25,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/c0/node_2.toml b/test-configs/c0/node_2.toml index 0566c638..5d36ec58 100644 --- a/test-configs/c0/node_2.toml +++ b/test-configs/c0/node_2.toml @@ -25,6 +25,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/c0/node_3.toml b/test-configs/c0/node_3.toml index 9a86fa3f..1b8c4f9b 100644 --- a/test-configs/c0/node_3.toml +++ b/test-configs/c0/node_3.toml @@ -25,6 +25,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/c0/node_4.toml b/test-configs/c0/node_4.toml index 73032792..ca8255b1 100644 --- a/test-configs/c0/node_4.toml +++ b/test-configs/c0/node_4.toml @@ -25,6 +25,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/docker/committee.toml b/test-configs/docker/committee.toml index bc523331..d0479888 100644 --- a/test-configs/docker/committee.toml +++ b/test-configs/docker/committee.toml @@ -1,4 +1,4 @@ -effective_timestamp = "2025-09-01T20:00:00Z" +effective_timestamp = "2025-09-01T02:00:00Z" [[members]] signing_key = "eiwaGN1NNaQdbnR9FsjKzUeLghQZsTLPjiL4RcQgfLoX" diff --git a/test-configs/docker/node_0.toml b/test-configs/docker/node_0.toml index f60633a1..79d5984b 100644 --- a/test-configs/docker/node_0.toml +++ b/test-configs/docker/node_0.toml @@ -25,6 +25,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/docker/node_1.toml b/test-configs/docker/node_1.toml index 722dc5be..07c940c4 100644 --- a/test-configs/docker/node_1.toml +++ b/test-configs/docker/node_1.toml @@ -25,6 +25,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/docker/node_2.toml b/test-configs/docker/node_2.toml index 01d19863..8f9379c2 100644 --- a/test-configs/docker/node_2.toml +++ b/test-configs/docker/node_2.toml @@ -25,6 +25,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/docker/node_3.toml b/test-configs/docker/node_3.toml index e9709c95..8ba06cd0 100644 --- a/test-configs/docker/node_3.toml +++ b/test-configs/docker/node_3.toml @@ -25,6 +25,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/docker/node_4.toml b/test-configs/docker/node_4.toml index b2322ad0..e3abee80 100644 --- a/test-configs/docker/node_4.toml +++ b/test-configs/docker/node_4.toml @@ -25,6 +25,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/nitro-ci-committee/node_0.toml b/test-configs/nitro-ci-committee/node_0.toml index ec9a37ef..9d21be5c 100644 --- a/test-configs/nitro-ci-committee/node_0.toml +++ b/test-configs/nitro-ci-committee/node_0.toml @@ -26,6 +26,7 @@ namespace = 412346 [chain.parent] id = 1337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/nitro-ci-committee/node_1.toml b/test-configs/nitro-ci-committee/node_1.toml index d187e182..00f3b4fa 100644 --- a/test-configs/nitro-ci-committee/node_1.toml +++ b/test-configs/nitro-ci-committee/node_1.toml @@ -26,6 +26,7 @@ namespace = 412346 [chain.parent] id = 1337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/tests/src/tests/timeboost.rs b/tests/src/tests/timeboost.rs index 9c7bf9c9..94617781 100644 --- a/tests/src/tests/timeboost.rs +++ b/tests/src/tests/timeboost.rs @@ -120,6 +120,11 @@ where .parse::() .expect("valid url"), ) + .ws_url( + "wss://theserversroom.com/ethereumws/54cmzzhcj1o/" + .parse::() + .expect("valid url"), + ) .ibox_contract(alloy::primitives::Address::default()) .key_manager_contract(alloy::primitives::Address::default()) .block_tag(BlockNumberOrTag::Finalized) diff --git a/tests/src/tests/timeboost/handover.rs b/tests/src/tests/timeboost/handover.rs index 432b5d1a..d7d4e152 100644 --- a/tests/src/tests/timeboost/handover.rs +++ b/tests/src/tests/timeboost/handover.rs @@ -168,6 +168,11 @@ where .parse::() .expect("valid url"), ) + .ws_url( + "wss://theserversroom.com/ethereumws/54cmzzhcj1o/" + .parse::() + .expect("valid url"), + ) .ibox_contract(alloy::primitives::Address::default()) .key_manager_contract(alloy::primitives::Address::default()) .block_tag(BlockNumberOrTag::Finalized) diff --git a/tests/src/tests/timeboost/timeboost_handover.rs b/tests/src/tests/timeboost/timeboost_handover.rs index 08f49334..ffbe88fb 100644 --- a/tests/src/tests/timeboost/timeboost_handover.rs +++ b/tests/src/tests/timeboost/timeboost_handover.rs @@ -420,6 +420,11 @@ async fn mk_configs( .parse::() .expect("valid url"), ) + .ws_url( + "wss://theserversroom.com/ethereumws/54cmzzhcj1o/" + .parse::() + .expect("valid url"), + ) .ibox_contract(alloy::primitives::Address::default()) .key_manager_contract(alloy::primitives::Address::default()) .block_tag(BlockNumberOrTag::Finalized) diff --git a/timeboost-config/src/binaries/mkconfig.rs b/timeboost-config/src/binaries/mkconfig.rs index 51359935..5134bcdc 100644 --- a/timeboost-config/src/binaries/mkconfig.rs +++ b/timeboost-config/src/binaries/mkconfig.rs @@ -68,6 +68,10 @@ struct Args { #[clap(long)] parent_rpc_url: Url, + /// Parent chain websocket url + #[clap(long)] + parent_ws_url: Url, + /// Parent chain id #[clap(long)] parent_chain_id: u64, @@ -182,6 +186,7 @@ impl Args { parent: ParentChain { id: self.parent_chain_id, rpc_url: self.parent_rpc_url.clone(), + ws_url: self.parent_ws_url.clone(), ibox_contract: self.parent_ibox_contract, block_tag: self.parent_block_tag, key_manager_contract: self.key_manager_contract, diff --git a/timeboost-config/src/chain.rs b/timeboost-config/src/chain.rs index c2e90aa3..61732e7a 100644 --- a/timeboost-config/src/chain.rs +++ b/timeboost-config/src/chain.rs @@ -14,6 +14,7 @@ pub struct ChainConfig { pub struct ParentChain { pub id: u64, pub rpc_url: Url, + pub ws_url: Url, pub ibox_contract: Address, pub block_tag: BlockNumberOrTag, pub key_manager_contract: Address, From 574b134a716fd66078eaafa991073091064285a7 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Fri, 12 Sep 2025 07:22:17 +0800 Subject: [PATCH 02/25] trigger set_next_committee by contract event --- Cargo.lock | 82 +++++++++++++++++++++++++++-------- Cargo.toml | 2 +- timeboost/Cargo.toml | 1 + timeboost/src/lib.rs | 101 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 164 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6850b4b4..789ab7ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,7 +167,7 @@ dependencies = [ "alloy-network 0.13.0", "alloy-node-bindings 0.13.0", "alloy-provider 0.13.0", - "alloy-pubsub", + "alloy-pubsub 0.13.0", "alloy-rpc-client 0.13.0", "alloy-rpc-types 0.13.0", "alloy-serde 0.13.0", @@ -176,7 +176,7 @@ dependencies = [ "alloy-signer-local 0.13.0", "alloy-transport 0.13.0", "alloy-transport-http 0.13.0", - "alloy-transport-ws", + "alloy-transport-ws 0.13.0", ] [[package]] @@ -193,6 +193,7 @@ dependencies = [ "alloy-network 1.0.30", "alloy-node-bindings 1.0.30", "alloy-provider 1.0.30", + "alloy-pubsub 1.0.30", "alloy-rpc-client 1.0.30", "alloy-rpc-types 1.0.30", "alloy-serde 1.0.30", @@ -200,6 +201,7 @@ dependencies = [ "alloy-signer-local 1.0.30", "alloy-transport 1.0.30", "alloy-transport-http 1.0.30", + "alloy-transport-ws 1.0.30", "alloy-trie 0.9.1", ] @@ -327,7 +329,7 @@ dependencies = [ "alloy-network-primitives 0.13.0", "alloy-primitives 0.8.25", "alloy-provider 0.13.0", - "alloy-pubsub", + "alloy-pubsub 0.13.0", "alloy-rpc-types-eth 0.13.0", "alloy-sol-types 0.8.25", "alloy-transport 0.13.0", @@ -349,6 +351,7 @@ dependencies = [ "alloy-network-primitives 1.0.30", "alloy-primitives 1.3.1", "alloy-provider 1.0.30", + "alloy-pubsub 1.0.30", "alloy-rpc-types-eth 1.0.30", "alloy-sol-types 1.3.1", "alloy-transport 1.0.30", @@ -838,7 +841,7 @@ dependencies = [ "alloy-network-primitives 0.13.0", "alloy-node-bindings 0.13.0", "alloy-primitives 0.8.25", - "alloy-pubsub", + "alloy-pubsub 0.13.0", "alloy-rpc-client 0.13.0", "alloy-rpc-types-anvil 0.13.0", "alloy-rpc-types-eth 0.13.0", @@ -846,7 +849,7 @@ dependencies = [ "alloy-sol-types 0.8.25", "alloy-transport 0.13.0", "alloy-transport-http 0.13.0", - "alloy-transport-ws", + "alloy-transport-ws 0.13.0", "async-stream", "async-trait", "auto_impl", @@ -881,6 +884,7 @@ dependencies = [ "alloy-network-primitives 1.0.30", "alloy-node-bindings 1.0.30", "alloy-primitives 1.3.1", + "alloy-pubsub 1.0.30", "alloy-rpc-client 1.0.30", "alloy-rpc-types-anvil 1.0.30", "alloy-rpc-types-eth 1.0.30", @@ -928,6 +932,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "alloy-pubsub" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97c18795ce1ce8151c5539ce1e4200940389674173f677c7455f79bfb00e5df" +dependencies = [ + "alloy-json-rpc 1.0.30", + "alloy-primitives 1.3.1", + "alloy-transport 1.0.30", + "auto_impl", + "bimap", + "futures", + "parking_lot", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.5.2", + "tracing", + "wasmtimer", +] + [[package]] name = "alloy-rlp" version = "0.3.12" @@ -958,10 +984,10 @@ checksum = "cec6dc89c4c3ef166f9fa436d1831f8142c16cf2e637647c936a6aaaabd8d898" dependencies = [ "alloy-json-rpc 0.13.0", "alloy-primitives 0.8.25", - "alloy-pubsub", + "alloy-pubsub 0.13.0", "alloy-transport 0.13.0", "alloy-transport-http 0.13.0", - "alloy-transport-ws", + "alloy-transport-ws 0.13.0", "async-stream", "futures", "pin-project", @@ -985,6 +1011,7 @@ checksum = "25289674cd8c58fcca2568b5350423cb0dd7bca8c596c5e2869bfe4c5c57ed14" dependencies = [ "alloy-json-rpc 1.0.30", "alloy-primitives 1.3.1", + "alloy-pubsub 1.0.30", "alloy-transport 1.0.30", "alloy-transport-http 1.0.30", "futures", @@ -1103,7 +1130,7 @@ dependencies = [ "alloy-rlp", "alloy-serde 0.13.0", "alloy-sol-types 0.8.25", - "itertools 0.14.0", + "itertools 0.13.0", "serde", "serde_json", "thiserror 2.0.16", @@ -1124,7 +1151,7 @@ dependencies = [ "alloy-serde 1.0.30", "alloy-sol-types 1.3.1", "arbitrary", - "itertools 0.14.0", + "itertools 0.13.0", "serde", "serde_json", "serde_with", @@ -1468,7 +1495,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef7a4301e8967c1998f193755fd9429e0ca81730e2e134e30c288c43dbf96f0" dependencies = [ - "alloy-pubsub", + "alloy-pubsub 0.13.0", "alloy-transport 0.13.0", "futures", "http 1.3.1", @@ -1480,6 +1507,24 @@ dependencies = [ "ws_stream_wasm", ] +[[package]] +name = "alloy-transport-ws" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6d44395e6793566e9c89bd82297cc4b0566655c1e78a1d69362640814784cc6" +dependencies = [ + "alloy-pubsub 1.0.30", + "alloy-transport 1.0.30", + "futures", + "http 1.3.1", + "rustls 0.23.31", + "serde_json", + "tokio", + "tokio-tungstenite 0.26.2", + "tracing", + "ws_stream_wasm", +] + [[package]] name = "alloy-trie" version = "0.7.9" @@ -6187,7 +6232,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite 0.2.16", - "socket2 0.6.0", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -9120,8 +9165,8 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" dependencies = [ - "heck 0.5.0", - "itertools 0.14.0", + "heck 0.4.1", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -9143,7 +9188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.106", @@ -9156,7 +9201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.106", @@ -9293,7 +9338,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.31", - "socket2 0.6.0", + "socket2 0.5.10", "thiserror 2.0.16", "tokio", "tracing", @@ -9330,7 +9375,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.0", + "socket2 0.5.10", "tracing", "windows-sys 0.60.2", ] @@ -10839,7 +10884,7 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.106", @@ -11809,6 +11854,7 @@ dependencies = [ "clap", "cliquenet", "committable", + "futures", "http 1.3.1", "metrics", "multisig", diff --git a/Cargo.toml b/Cargo.toml index c0043d1d..786ce11d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ rust-version = "1.85.0" [workspace.dependencies] aes-gcm = { version = "0.10.3" } -alloy = { version = "1.0", features = ["default", "arbitrary", "k256", "serde", "rlp", "node-bindings", "getrandom", "signer-mnemonic"] } +alloy = { version = "1.0", features = ["default", "arbitrary", "k256", "serde", "rlp", "node-bindings", "getrandom", "signer-mnemonic", "transport-ws"] } alloy-chains = "0.2" # derive feature is not exposed via `alloy`, thus has to explicitly declare here alloy-rlp = { version = "0.3.12", features = ["derive"] } diff --git a/timeboost/Cargo.toml b/timeboost/Cargo.toml index 08b11e79..a7f273c3 100644 --- a/timeboost/Cargo.toml +++ b/timeboost/Cargo.toml @@ -26,6 +26,7 @@ bon = { workspace = true } clap = { workspace = true } cliquenet = { path = "../cliquenet" } committable = { workspace = true } +futures = { workspace = true } http = { workspace = true } metrics = { path = "../metrics", features = ["prometheus"]} multisig = { path = "../multisig" } diff --git a/timeboost/src/lib.rs b/timeboost/src/lib.rs index 9f627fff..9c59fe12 100644 --- a/timeboost/src/lib.rs +++ b/timeboost/src/lib.rs @@ -4,12 +4,23 @@ use std::iter::once; use std::sync::Arc; use ::metrics::prometheus::PrometheusMetrics; -use anyhow::Result; +use alloy::eips::BlockNumberOrTag; +use alloy::providers::{Provider, ProviderBuilder}; +use alloy::rpc::types::Filter; +use alloy::sol_types::SolEvent; +use alloy::transports::ws::WsConnect; +use anyhow::{Result, anyhow}; +use cliquenet::AddressableCommittee; +use futures::StreamExt; use metrics::TimeboostMetrics; -use multisig::PublicKey; +use multisig::{Committee, PublicKey, x25519}; +use sailfish::types::Timestamp; use timeboost_builder::{Certifier, CertifierDown, Submitter}; +use timeboost_contract::CommitteeMemberSol; +use timeboost_contract::{KeyManager, KeyManager::CommitteeCreated}; +use timeboost_crypto::prelude::DkgEncKey; use timeboost_sequencer::{Output, Sequencer}; -use timeboost_types::BundleVariant; +use timeboost_types::{BundleVariant, ConsensusTime, KeyStore}; use tokio::select; use tokio::sync::mpsc::{self, Receiver, Sender}; use tracing::{info, warn}; @@ -87,6 +98,17 @@ impl Timeboost { } pub async fn go(mut self) -> Result<()> { + // setup the websocket for contract event stream + let ws = WsConnect::new(self.config.chain_config.parent.ws_url.clone()); + // spawn the pubsub service (and backend) and the frontend is registered at the provider + let provider = ProviderBuilder::new().connect_pubsub_with(ws).await?; + + let filter = Filter::new() + .address(self.config.chain_config.parent.key_manager_contract) + .event(KeyManager::CommitteeCreated::SIGNATURE) + .from_block(BlockNumberOrTag::Finalized); + let mut events = provider.subscribe_logs(&filter).await?.into_stream(); + loop { select! { trx = self.receiver.recv() => { @@ -127,8 +149,81 @@ impl Timeboost { let e: CertifierDown = e; return Err(e.into()) } + }, + res = events.next() => match res { + Some(log) => { + let typed_log = log.log_decode_validate::()?; + let id = typed_log.data().id; + let cur: u64 = self.config.key_store.committee().id().into(); + + if id == cur + 1 { + info!(node = %self.label, committee_id = %id, current = %cur, "setting next committee"); + let (t, a, k) = self.fetch_next_committee(&provider, id).await?; + self.sequencer.set_next_committee(t, a, k).await?; + } else { + warn!(node = %self.label, committee_id = %id, current = %cur, "ignored new CommitteeCreated event"); + continue; + } + }, + None => { + warn!(node = %self.label, "event subscription stream ended"); + return Err(anyhow!("contract event pubsub service prematurely shutdown")); + } } } } } + + /// Given the next committee is available on chain, fetch it and prepare it for `NextCommittee` + async fn fetch_next_committee( + &self, + provider: impl Provider, + next_committee_id: u64, + ) -> Result<(ConsensusTime, AddressableCommittee, KeyStore)> { + let contract = KeyManager::new( + self.config.chain_config.parent.key_manager_contract, + &provider, + ); + let c = contract.getCommitteeById(next_committee_id).call().await?; + let members: Vec = c.members; + let timestamp: Timestamp = c.effectiveTimestamp.into(); + + let sailfish_peer_hosts_and_keys = members + .iter() + .map(|peer| { + let sig_key = multisig::PublicKey::try_from(peer.sigKey.as_ref())?; + let dh_key = x25519::PublicKey::try_from(peer.dhKey.as_ref())?; + let sailfish_address = cliquenet::Address::try_from(peer.networkAddress.as_ref())?; + Ok((sig_key, dh_key, sailfish_address)) + }) + .collect::>>()?; + let dkg_enc_keys = members + .iter() + .map(|peer| { + let dkg_enc_key = DkgEncKey::from_bytes(peer.dkgKey.as_ref())?; + Ok(dkg_enc_key) + }) + .collect::>>()?; + + let sailfish_committee = { + let c = Committee::new( + next_committee_id, + sailfish_peer_hosts_and_keys + .iter() + .enumerate() + .map(|(i, (k, ..))| (i as u8, *k)), + ); + AddressableCommittee::new(c, sailfish_peer_hosts_and_keys.iter().cloned()) + }; + + let key_store = KeyStore::new( + sailfish_committee.committee().clone(), + dkg_enc_keys + .into_iter() + .enumerate() + .map(|(i, k)| (i as u8, k)), + ); + + Ok((ConsensusTime(timestamp), sailfish_committee, key_store)) + } } From 39436013dd2929fa1971db57e8956383d9b20925 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Fri, 12 Sep 2025 11:53:52 +0800 Subject: [PATCH 03/25] use 8546 for nitro ws port --- justfile | 2 +- test-configs/nitro-ci-committee/node_0.toml | 2 +- test-configs/nitro-ci-committee/node_1.toml | 2 +- timeboost-config/src/node.rs | 1 + timeboost/src/lib.rs | 19 ++++++++++++++++--- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/justfile b/justfile index c35ce5a6..ba8ac85b 100644 --- a/justfile +++ b/justfile @@ -131,7 +131,7 @@ mkconfig_nitro DATETIME *ARGS: --nitro-addr "localhost:55000" \ --chain-namespace 412346 \ --parent-rpc-url "http://127.0.0.1:8545" \ - --parent-ws-url "ws://127.0.0.1:8545" \ + --parent-ws-url "ws://127.0.0.1:8546" \ --parent-chain-id 1337 \ --parent-ibox-contract "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" \ --key-manager-contract "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" \ diff --git a/test-configs/nitro-ci-committee/node_0.toml b/test-configs/nitro-ci-committee/node_0.toml index 9d21be5c..403206c7 100644 --- a/test-configs/nitro-ci-committee/node_0.toml +++ b/test-configs/nitro-ci-committee/node_0.toml @@ -26,7 +26,7 @@ namespace = 412346 [chain.parent] id = 1337 rpc_url = "http://127.0.0.1:8545/" -ws_url = "ws://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8546/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/nitro-ci-committee/node_1.toml b/test-configs/nitro-ci-committee/node_1.toml index 00f3b4fa..20ca20d0 100644 --- a/test-configs/nitro-ci-committee/node_1.toml +++ b/test-configs/nitro-ci-committee/node_1.toml @@ -26,7 +26,7 @@ namespace = 412346 [chain.parent] id = 1337 rpc_url = "http://127.0.0.1:8545/" -ws_url = "ws://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8546/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/timeboost-config/src/node.rs b/timeboost-config/src/node.rs index 942cd8f0..5f0f8eb8 100644 --- a/timeboost-config/src/node.rs +++ b/timeboost-config/src/node.rs @@ -116,6 +116,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0x4dbd4fc535ac27206064b68ffcf827b0a60bab3f" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/timeboost/src/lib.rs b/timeboost/src/lib.rs index 9c59fe12..52600a88 100644 --- a/timeboost/src/lib.rs +++ b/timeboost/src/lib.rs @@ -23,7 +23,7 @@ use timeboost_sequencer::{Output, Sequencer}; use timeboost_types::{BundleVariant, ConsensusTime, KeyStore}; use tokio::select; use tokio::sync::mpsc::{self, Receiver, Sender}; -use tracing::{info, warn}; +use tracing::{error, info, warn}; pub use conf::{TimeboostConfig, TimeboostConfigBuilder}; pub use timeboost_builder as builder; @@ -101,13 +101,26 @@ impl Timeboost { // setup the websocket for contract event stream let ws = WsConnect::new(self.config.chain_config.parent.ws_url.clone()); // spawn the pubsub service (and backend) and the frontend is registered at the provider - let provider = ProviderBuilder::new().connect_pubsub_with(ws).await?; + let provider = ProviderBuilder::new() + .connect_pubsub_with(ws) + .await + .map_err(|err| { + error!(?err, "event pubsub failed to start"); + err + })?; let filter = Filter::new() .address(self.config.chain_config.parent.key_manager_contract) .event(KeyManager::CommitteeCreated::SIGNATURE) .from_block(BlockNumberOrTag::Finalized); - let mut events = provider.subscribe_logs(&filter).await?.into_stream(); + let mut events = provider + .subscribe_logs(&filter) + .await + .map_err(|err| { + error!(?err, "pubsub subscription failed"); + err + })? + .into_stream(); loop { select! { From 42249861fe76379291175891ed86b701dc6d876a Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Fri, 12 Sep 2025 12:36:43 +0800 Subject: [PATCH 04/25] test: unit test for log event stream --- Cargo.lock | 1 + timeboost-contract/Cargo.toml | 1 + timeboost-contract/src/deployer.rs | 71 +++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 789ab7ee..bfa38e36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11942,6 +11942,7 @@ dependencies = [ "anyhow", "bincode 2.0.1", "clap", + "futures", "rand 0.9.2", "serde", "timeboost-config", diff --git a/timeboost-contract/Cargo.toml b/timeboost-contract/Cargo.toml index 0981b914..f3303e19 100644 --- a/timeboost-contract/Cargo.toml +++ b/timeboost-contract/Cargo.toml @@ -18,6 +18,7 @@ alloy = { workspace = true } anyhow = { workspace = true } bincode = { workspace = true } clap = { workspace = true } +futures = { workspace = true } rand = { workspace = true } serde = { workspace = true } timeboost-config = { path = "../timeboost-config" } diff --git a/timeboost-contract/src/deployer.rs b/timeboost-contract/src/deployer.rs index 760536d6..d7ebdc74 100644 --- a/timeboost-contract/src/deployer.rs +++ b/timeboost-contract/src/deployer.rs @@ -48,8 +48,17 @@ where #[cfg(test)] mod tests { - use crate::{CommitteeMemberSol, CommitteeSol, KeyManager}; - use alloy::{providers::WalletProvider, sol_types::SolValue}; + use super::deploy_key_manager_contract; + use crate::{CommitteeMemberSol, CommitteeSol, KeyManager, KeyManager::CommitteeCreated}; + use alloy::{ + eips::BlockNumberOrTag, + node_bindings::Anvil, + providers::{Provider, ProviderBuilder, WalletProvider}, + rpc::types::Filter, + sol_types::{SolEvent, SolValue}, + transports::ws::WsConnect, + }; + use futures::StreamExt; use rand::prelude::*; #[tokio::test] @@ -93,4 +102,62 @@ mod tests { .abi_encode_sequence() ); } + + #[tokio::test] + async fn test_event_stream() { + let anvil = Anvil::new().spawn(); + let wallet = anvil.wallet().unwrap(); + let provider = ProviderBuilder::new() + .wallet(wallet) + .connect_http(anvil.endpoint_url()); + let pubsub_provider = ProviderBuilder::new() + .connect_pubsub_with(WsConnect::new(anvil.ws_endpoint_url())) + .await + .unwrap(); + assert_eq!( + pubsub_provider.get_chain_id().await.unwrap(), + provider.get_chain_id().await.unwrap() + ); + + let manager = provider.default_signer_address(); + let km_addr = deploy_key_manager_contract(&provider, manager) + .await + .unwrap(); + let contract = KeyManager::new(km_addr, &provider); + + // setup event stream + let filter = Filter::new() + .address(km_addr) + .event(KeyManager::CommitteeCreated::SIGNATURE) + .from_block(BlockNumberOrTag::Latest); + let mut events = pubsub_provider + .subscribe_logs(&filter) + .await + .unwrap() + .into_stream(); + + // register some committees on the contract, which emit events + let rng = &mut rand::rng(); + let c0_timestamp = rng.random::(); + for i in 0..5 { + let members = (0..5) + .map(|_| CommitteeMemberSol::random()) + .collect::>(); + let timestamp = c0_timestamp + 1000 * i; + + let _tx_receipt = contract + .setNextCommittee(timestamp, members.clone()) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + // Read the corresponding event + let log = events.next().await.unwrap(); + let typed_log = log.log_decode_validate::().unwrap(); + assert_eq!(typed_log.data().id, i); + } + } } From 0b70ae7b378a1128f4db0b72a610b4d47c5ae8a8 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Fri, 12 Sep 2025 13:40:29 +0800 Subject: [PATCH 05/25] use Latest tag for test chain --- timeboost/src/lib.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/timeboost/src/lib.rs b/timeboost/src/lib.rs index 52600a88..673e7ce8 100644 --- a/timeboost/src/lib.rs +++ b/timeboost/src/lib.rs @@ -109,10 +109,20 @@ impl Timeboost { err })?; + let chain_id = provider.get_chain_id().await.map_err(|err| { + error!(?err, "fail to get chainid"); + err + })?; + // local test chain don't have finality gadget, thus don't support `Finalized` tag + let tag = if chain_id == 31337 || chain_id == 1337 { + BlockNumberOrTag::Latest + } else { + BlockNumberOrTag::Finalized + }; let filter = Filter::new() .address(self.config.chain_config.parent.key_manager_contract) .event(KeyManager::CommitteeCreated::SIGNATURE) - .from_block(BlockNumberOrTag::Finalized); + .from_block(tag); let mut events = provider .subscribe_logs(&filter) .await From 21ce864bac73a4b358e560a94073f0a308176dfe Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Fri, 12 Sep 2025 21:28:42 +0800 Subject: [PATCH 06/25] add ws endpoint to test-configs/local --- test-configs/local/node_0.toml | 1 + test-configs/local/node_1.toml | 1 + test-configs/local/node_2.toml | 1 + test-configs/local/node_3.toml | 1 + test-configs/local/node_4.toml | 1 + 5 files changed, 5 insertions(+) diff --git a/test-configs/local/node_0.toml b/test-configs/local/node_0.toml index a550e6f1..32afb213 100644 --- a/test-configs/local/node_0.toml +++ b/test-configs/local/node_0.toml @@ -26,6 +26,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/local/node_1.toml b/test-configs/local/node_1.toml index d8837e71..3611c909 100644 --- a/test-configs/local/node_1.toml +++ b/test-configs/local/node_1.toml @@ -26,6 +26,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/local/node_2.toml b/test-configs/local/node_2.toml index e988bf96..a3655fa9 100644 --- a/test-configs/local/node_2.toml +++ b/test-configs/local/node_2.toml @@ -26,6 +26,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/local/node_3.toml b/test-configs/local/node_3.toml index c6f081d5..65e5835e 100644 --- a/test-configs/local/node_3.toml +++ b/test-configs/local/node_3.toml @@ -26,6 +26,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" diff --git a/test-configs/local/node_4.toml b/test-configs/local/node_4.toml index 1c9d41c3..05686932 100644 --- a/test-configs/local/node_4.toml +++ b/test-configs/local/node_4.toml @@ -26,6 +26,7 @@ namespace = 10101 [chain.parent] id = 31337 rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" block_tag = "finalized" key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" From 3d05908a4dc99c2573c450f747b7515ee57be01f Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Fri, 12 Sep 2025 23:37:30 +0800 Subject: [PATCH 07/25] add dynamic committee integration test --- .github/workflows/build-and-test.yml | 2 ++ justfile | 39 ++++++++++++++++++++++++ test-utils/src/binaries/run-committee.rs | 14 ++++++++- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 046200c3..f769c9fb 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -141,6 +141,8 @@ jobs: run: just run_sailfish_demo - name: Test with block-maker run: just test-all + - name: Test dynamic committee change + run: just test-dyn-comm contracts: runs-on: ubuntu-latest diff --git a/justfile b/justfile index 7d0d6df2..29e914d0 100644 --- a/justfile +++ b/justfile @@ -188,3 +188,42 @@ test-all: build_release build-test-utils --committee test-configs/local/committee.toml \ --committee-id 0 \ --blocks 1000 + +test-dyn-comm: build_release build-test-utils + env RUST_LOG=info target/release/run \ + --verbose \ + --timeout 120 \ + --spawn "1:anvil --port 8545" \ + --run "2:sleep 2" \ + --run "3:scripts/deploy-test-contract" \ + --spawn "4:target/release/yapper --keyset-file test-configs/c0/committee.toml" \ + --spawn "5:target/release/run-committee --configs test-configs/c0/ --committee 0" \ + --run "6:target/release/mkconfig -n 6 \ + --public-addr '127.0.0.1:9000' \ + --internal-addr '127.0.0.1:9003' \ + --http-api '127.0.0.1:9004' \ + --chain-namespace 10101 \ + --parent-rpc-url 'http://127.0.0.1:8545' \ + --parent-ws-url 'ws://127.0.0.1:8545' \ + --parent-chain-id 31337 \ + --parent-ibox-contract "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" \ + --key-manager-contract "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" \ + --timestamp '`just now-plus-20s`' \ + --stamp-dir '/tmp' \ + --output 'test-configs/c1'" \ + --run "7:target/release/register -m 'attend year erase basket blind adapt stove broccoli isolate unveil acquire category' \ + -i 0 \ + -u 'http://localhost:8545' \ + -k '0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35' \ + -c 'test-configs/c1/committee.toml'" \ + --run "8:pkill -9 yapper && target/release/yapper --keyset-file test-configs/c1/committee.toml" \ + target/release/run-committee --configs test-configs/c1 \ + --committee 1 \ + --until 800 \ + --required-decrypt-rounds 3 && rm -rf test-configs/c1 + + +# portable calculation of now() + 20s in "%Y-%m-%dT%H:%M:%SZ" format +[private] +now-plus-20s: + @python3 -c 'from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc)+timedelta(seconds=20)).strftime("%Y-%m-%dT%H:%M:%SZ"))' diff --git a/test-utils/src/binaries/run-committee.rs b/test-utils/src/binaries/run-committee.rs index f9accd24..d67d9cf6 100644 --- a/test-utils/src/binaries/run-committee.rs +++ b/test-utils/src/binaries/run-committee.rs @@ -13,11 +13,17 @@ struct Args { #[clap(long, short)] committee: u64, - #[clap(long, short)] + #[clap(long, short, default_value = "target/release/timeboost")] timeboost: PathBuf, #[clap(long, short, default_value = "/tmp")] tmp: PathBuf, + + #[clap(long)] + until: Option, + + #[clap(long)] + required_decrypt_rounds: Option, } #[tokio::main] @@ -52,6 +58,12 @@ async fn main() -> Result<()> { .arg("--config") .arg(entry.path()) .arg("--ignore-stamp"); + if let Some(until) = args.until { + cmd.arg("--until").arg(until.to_string()); + } + if let Some(r) = args.required_decrypt_rounds { + cmd.arg("--required-decrypt-rounds").arg(r.to_string()); + } commands.push(cmd); } From f52988da21617971de4b350cb4e35d301cb65fcd Mon Sep 17 00:00:00 2001 From: Luke Iannucci Date: Fri, 12 Sep 2025 16:06:51 -0400 Subject: [PATCH 08/25] maybe work --- justfile | 43 ++++++++++++--------- test-configs/c1/committee.toml | 25 ++++++++++++ test-configs/c1/node_0.toml | 35 +++++++++++++++++ test-configs/c1/node_1.toml | 35 +++++++++++++++++ test-configs/c1/node_2.toml | 35 +++++++++++++++++ test-utils/src/binaries/run-committee.rs | 2 + timeboost-contract/src/binaries/register.rs | 6 ++- 7 files changed, 161 insertions(+), 20 deletions(-) create mode 100755 test-configs/c1/committee.toml create mode 100755 test-configs/c1/node_0.toml create mode 100755 test-configs/c1/node_1.toml create mode 100755 test-configs/c1/node_2.toml diff --git a/justfile b/justfile index 29e914d0..effec85a 100644 --- a/justfile +++ b/justfile @@ -19,6 +19,9 @@ update-submodules: build_release *ARGS: cargo build --release --workspace --all-targets {{ARGS}} +build_release_until: + cargo build --release --workspace --all-targets --features "until" + build_docker: docker build . -f ./docker/timeboost.Dockerfile -t timeboost:latest docker build . -f ./docker/yapper.Dockerfile -t yapper:latest @@ -189,38 +192,40 @@ test-all: build_release build-test-utils --committee-id 0 \ --blocks 1000 -test-dyn-comm: build_release build-test-utils - env RUST_LOG=info target/release/run \ +test-dyn-comm: build_release_until build-test-utils + env RUST_LOG=sailfish=warn,yapper=error,timeboost=info,info target/release/run \ --verbose \ --timeout 120 \ --spawn "1:anvil --port 8545" \ --run "2:sleep 2" \ --run "3:scripts/deploy-test-contract" \ --spawn "4:target/release/yapper --keyset-file test-configs/c0/committee.toml" \ - --spawn "5:target/release/run-committee --configs test-configs/c0/ --committee 0" \ - --run "6:target/release/mkconfig -n 6 \ - --public-addr '127.0.0.1:9000' \ - --internal-addr '127.0.0.1:9003' \ - --http-api '127.0.0.1:9004' \ + --spawn "5:target/release/run-committee --configs test-configs/c0/ --committee 0 --timeboost target/release/timeboost --until 1000" \ + --run "6:target/release/mkconfig -n 3 \ + --public-addr 127.0.0.1:9000 \ + --internal-addr 127.0.0.1:9003 \ + --http-api 127.0.0.1:9004 \ --chain-namespace 10101 \ - --parent-rpc-url 'http://127.0.0.1:8545' \ - --parent-ws-url 'ws://127.0.0.1:8545' \ + --parent-rpc-url http://127.0.0.1:8545 \ + --parent-ws-url ws://127.0.0.1:8545 \ --parent-chain-id 31337 \ --parent-ibox-contract "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" \ --key-manager-contract "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" \ - --timestamp '`just now-plus-20s`' \ - --stamp-dir '/tmp' \ - --output 'test-configs/c1'" \ - --run "7:target/release/register -m 'attend year erase basket blind adapt stove broccoli isolate unveil acquire category' \ + --timestamp `just now-plus-20s` \ + --stamp-dir /tmp \ + --output test-configs/c1" \ + --run "7:target/release/register \ -i 0 \ - -u 'http://localhost:8545' \ - -k '0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35' \ - -c 'test-configs/c1/committee.toml'" \ - --run "8:pkill -9 yapper && target/release/yapper --keyset-file test-configs/c1/committee.toml" \ - target/release/run-committee --configs test-configs/c1 \ + -u http://localhost:8545 \ + -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ + -c test-configs/c1/committee.toml" \ + --run "8:sleep 5" \ + --spawn "9:target/release/yapper --keyset-file test-configs/c1/committee.toml" \ + target/release/run-committee -- --configs test-configs/c1/ \ --committee 1 \ + --timeboost target/release/timeboost \ --until 800 \ - --required-decrypt-rounds 3 && rm -rf test-configs/c1 + --required-decrypt-rounds 3 # portable calculation of now() + 20s in "%Y-%m-%dT%H:%M:%SZ" format diff --git a/test-configs/c1/committee.toml b/test-configs/c1/committee.toml new file mode 100755 index 00000000..ae8c0444 --- /dev/null +++ b/test-configs/c1/committee.toml @@ -0,0 +1,25 @@ +effective_timestamp = "2025-09-12T20:05:52Z" + +[[members]] +signing_key = "dQST7kJpV3RXqNMyKUA6eMW5HiVLAVK4AFwX5nQJbaka" +dh_key = "JBm1FZhcGcZE4bUVDrRCsHZYcz78BcjgdKejknytQvnH" +dkg_enc_key = "6ZH8nCCCmX5UDMjcR18oNz5YTUixDQeVqXVoYRqMxGHLkoNo95Gw27PWD323Kf7Fqs" +public_address = "127.0.0.1:9000" +http_api = "127.0.0.1:9004" +internal_api = "127.0.0.1:9003" + +[[members]] +signing_key = "gWpDFTTBYN8u5JDz83PYVDakQTMzPPNNonnkt4FRSJD3" +dh_key = "6fBHLbP8fKjk2tYgqaMs3GQTiHQSPh1uZ9d3J4ZYM9gC" +dkg_enc_key = "73sU1acMiGsiJu9pVY8yQag7DvRcvtYRkATjPCv485Mwr6FYimFVLRUsz9Papy745f" +public_address = "127.0.0.1:9010" +http_api = "127.0.0.1:9014" +internal_api = "127.0.0.1:9013" + +[[members]] +signing_key = "c24KQW7jK7d8NdKYatGn6cZDSoMro5XsxKoBkkLmSUwN" +dh_key = "Fer6ptfw1MgBDxCuLn54Xo7V62iEhZZyQ3JTfyyGMgM9" +dkg_enc_key = "5vacdPGGmQSfS474Fy5dizxoE1GHLW4W48FBx7UfVTtERjtPhFN7CEBT23y6uBHiew" +public_address = "127.0.0.1:9020" +http_api = "127.0.0.1:9024" +internal_api = "127.0.0.1:9023" diff --git a/test-configs/c1/node_0.toml b/test-configs/c1/node_0.toml new file mode 100755 index 00000000..46802d3e --- /dev/null +++ b/test-configs/c1/node_0.toml @@ -0,0 +1,35 @@ +stamp = "/tmp/timeboost.0.stamp" + +[net.public] +address = "127.0.0.1:9000" +http_api = "127.0.0.1:9004" + +[net.internal] +address = "127.0.0.1:9003" + +[keys.signing] +secret = "HwhEANTEwUJxYwtGXWqHp7YSubxceEc1kXLBPyVkBryv" +public = "dQST7kJpV3RXqNMyKUA6eMW5HiVLAVK4AFwX5nQJbaka" + +[keys.dh] +secret = "3pz6B8owd4eFZoCmtt8FvJkUKHURTSF4QWw7VVmmeBzW" +public = "JBm1FZhcGcZE4bUVDrRCsHZYcz78BcjgdKejknytQvnH" + +[keys.dkg] +secret = "6rACFLkLskjcCArUeEdUz4UBuU39BnzJAGHuRLJG6dg3" +public = "6ZH8nCCCmX5UDMjcR18oNz5YTUixDQeVqXVoYRqMxGHLkoNo95Gw27PWD323Kf7Fqs" + +[chain] +namespace = 10101 + +[chain.parent] +id = 31337 +rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" +ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" +block_tag = "finalized" +key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" + +[espresso] +base_url = "https://query.decaf.testnet.espresso.network/v1/" +websockets_base_url = "wss://query.decaf.testnet.espresso.network/v1/" diff --git a/test-configs/c1/node_1.toml b/test-configs/c1/node_1.toml new file mode 100755 index 00000000..b3bf2956 --- /dev/null +++ b/test-configs/c1/node_1.toml @@ -0,0 +1,35 @@ +stamp = "/tmp/timeboost.1.stamp" + +[net.public] +address = "127.0.0.1:9010" +http_api = "127.0.0.1:9014" + +[net.internal] +address = "127.0.0.1:9013" + +[keys.signing] +secret = "4JiTfGM5cb7RLDVfNb89nZH7ZeYWEy6UfgukCJe1uJ2s" +public = "gWpDFTTBYN8u5JDz83PYVDakQTMzPPNNonnkt4FRSJD3" + +[keys.dh] +secret = "FZGiSAFQaMvVJ7zCEFmQu5cgRYKwQJdCQqSrw8SYRhHJ" +public = "6fBHLbP8fKjk2tYgqaMs3GQTiHQSPh1uZ9d3J4ZYM9gC" + +[keys.dkg] +secret = "DegFS9EvP9pz7rYX4JsSJ4aLRSq3fBkdy8U6LqD3eQqt" +public = "73sU1acMiGsiJu9pVY8yQag7DvRcvtYRkATjPCv485Mwr6FYimFVLRUsz9Papy745f" + +[chain] +namespace = 10101 + +[chain.parent] +id = 31337 +rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" +ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" +block_tag = "finalized" +key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" + +[espresso] +base_url = "https://query.decaf.testnet.espresso.network/v1/" +websockets_base_url = "wss://query.decaf.testnet.espresso.network/v1/" diff --git a/test-configs/c1/node_2.toml b/test-configs/c1/node_2.toml new file mode 100755 index 00000000..8c96eebd --- /dev/null +++ b/test-configs/c1/node_2.toml @@ -0,0 +1,35 @@ +stamp = "/tmp/timeboost.2.stamp" + +[net.public] +address = "127.0.0.1:9020" +http_api = "127.0.0.1:9024" + +[net.internal] +address = "127.0.0.1:9023" + +[keys.signing] +secret = "9j28e6HiGxdShGwNLH2emYXVaE7mNdBmhikyknSdtTqS" +public = "c24KQW7jK7d8NdKYatGn6cZDSoMro5XsxKoBkkLmSUwN" + +[keys.dh] +secret = "7ecZnC9dPzs9pZ6uqCSkEiLr44GqdFgoWCqG5jgD6mJ8" +public = "Fer6ptfw1MgBDxCuLn54Xo7V62iEhZZyQ3JTfyyGMgM9" + +[keys.dkg] +secret = "FwzsYVwZRnFMNs11bbMKoVdeQPmMM24dSobxFrscmWnu" +public = "5vacdPGGmQSfS474Fy5dizxoE1GHLW4W48FBx7UfVTtERjtPhFN7CEBT23y6uBHiew" + +[chain] +namespace = 10101 + +[chain.parent] +id = 31337 +rpc_url = "http://127.0.0.1:8545/" +ws_url = "ws://127.0.0.1:8545/" +ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" +block_tag = "finalized" +key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" + +[espresso] +base_url = "https://query.decaf.testnet.espresso.network/v1/" +websockets_base_url = "wss://query.decaf.testnet.espresso.network/v1/" diff --git a/test-utils/src/binaries/run-committee.rs b/test-utils/src/binaries/run-committee.rs index d67d9cf6..ba57360a 100644 --- a/test-utils/src/binaries/run-committee.rs +++ b/test-utils/src/binaries/run-committee.rs @@ -60,6 +60,8 @@ async fn main() -> Result<()> { .arg("--ignore-stamp"); if let Some(until) = args.until { cmd.arg("--until").arg(until.to_string()); + cmd.arg("--committee") + .arg(format!("{}/committee.toml", args.configs.to_str().unwrap())); } if let Some(r) = args.required_decrypt_rounds { cmd.arg("--required-decrypt-rounds").arg(r.to_string()); diff --git a/timeboost-contract/src/binaries/register.rs b/timeboost-contract/src/binaries/register.rs index 86a93295..78866d70 100644 --- a/timeboost-contract/src/binaries/register.rs +++ b/timeboost-contract/src/binaries/register.rs @@ -15,7 +15,11 @@ use url::Url; #[derive(Clone, Debug, Parser)] struct Args { - #[clap(short, long)] + #[clap( + short, + long, + default_value = "attend year erase basket blind adapt stove broccoli isolate unveil acquire category" + )] mnemonic: String, #[clap(short, long)] From 8b8e8cf133d06a34aae5af36c9df93a67b080c8b Mon Sep 17 00:00:00 2001 From: Luke Iannucci Date: Fri, 12 Sep 2025 16:29:44 -0400 Subject: [PATCH 09/25] 500 --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index effec85a..fe702cbd 100644 --- a/justfile +++ b/justfile @@ -224,7 +224,7 @@ test-dyn-comm: build_release_until build-test-utils target/release/run-committee -- --configs test-configs/c1/ \ --committee 1 \ --timeboost target/release/timeboost \ - --until 800 \ + --until 500 \ --required-decrypt-rounds 3 From 2488c609d707263c36f7008c30df081f0aa95d65 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Mon, 15 Sep 2025 16:18:43 +0800 Subject: [PATCH 10/25] add register threshold-enc-key functionality --- Cargo.lock | 2 + justfile | 1 - timeboost-contract/Cargo.toml | 4 +- timeboost-contract/src/binaries/register.rs | 137 ++++++++++++++------ timeboost-crypto/src/sg_encryption.rs | 14 ++ {yapper => timeboost-utils}/src/enc_key.rs | 8 +- timeboost-utils/src/lib.rs | 1 + yapper/src/main.rs | 1 - yapper/src/yapper.rs | 3 +- 9 files changed, 122 insertions(+), 49 deletions(-) rename {yapper => timeboost-utils}/src/enc_key.rs (91%) diff --git a/Cargo.lock b/Cargo.lock index 9d9e33a2..f8aba211 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12034,8 +12034,10 @@ dependencies = [ "clap", "futures", "rand 0.9.2", + "reqwest", "serde", "timeboost-config", + "timeboost-crypto", "timeboost-types", "timeboost-utils", "tokio", diff --git a/justfile b/justfile index fe702cbd..0860d0e6 100644 --- a/justfile +++ b/justfile @@ -215,7 +215,6 @@ test-dyn-comm: build_release_until build-test-utils --stamp-dir /tmp \ --output test-configs/c1" \ --run "7:target/release/register \ - -i 0 \ -u http://localhost:8545 \ -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ -c test-configs/c1/committee.toml" \ diff --git a/timeboost-contract/Cargo.toml b/timeboost-contract/Cargo.toml index f3303e19..b34bd0c8 100644 --- a/timeboost-contract/Cargo.toml +++ b/timeboost-contract/Cargo.toml @@ -20,8 +20,10 @@ bincode = { workspace = true } clap = { workspace = true } futures = { workspace = true } rand = { workspace = true } +reqwest = { workspace = true } serde = { workspace = true } timeboost-config = { path = "../timeboost-config" } +timeboost-crypto = { path = "../timeboost-crypto" } timeboost-types = { path = "../timeboost-types" } timeboost-utils = { path = "../timeboost-utils" } tokio = { workspace = true } @@ -30,4 +32,4 @@ tracing = { workspace = true } url = { workspace = true } [build-dependencies] -alloy = { workspace = true } \ No newline at end of file +alloy = { workspace = true } diff --git a/timeboost-contract/src/binaries/register.rs b/timeboost-contract/src/binaries/register.rs index 78866d70..567b5ff8 100644 --- a/timeboost-contract/src/binaries/register.rs +++ b/timeboost-contract/src/binaries/register.rs @@ -4,13 +4,17 @@ use alloy::{ primitives::Address, providers::{Provider, WalletProvider}, }; -use anyhow::{Context, Result, bail}; -use clap::Parser; +use anyhow::{Context, Result, anyhow, bail}; +use clap::{Parser, ValueEnum}; +use reqwest::Client; use std::path::PathBuf; +use std::time::Duration; use timeboost_config::CommitteeConfig; use timeboost_contract::{CommitteeMemberSol, KeyManager, provider::build_provider}; +use timeboost_crypto::prelude::ThresholdEncKey; +use timeboost_utils::enc_key::ThresholdEncKeyCellAccumulator; use timeboost_utils::types::logging; -use tracing::info; +use tracing::{info, warn}; use url::Url; #[derive(Clone, Debug, Parser)] @@ -22,7 +26,7 @@ struct Args { )] mnemonic: String, - #[clap(short, long)] + #[clap(short, long, default_value_t = 0)] index: u32, #[clap(short, long)] @@ -35,6 +39,20 @@ struct Args { /// Path to the committee.toml config for the next committee #[clap(short, long)] config: PathBuf, + + /// What to register (new committee or threshold enc key?) + #[clap(long, short, default_value = "new-committee")] + action: Action, +} + +/// Specific register action +#[derive(Clone, Copy, Debug, Default, ValueEnum)] +enum Action { + /// register the next committee + #[default] + NewCommittee, + /// register the threshold encryption key (when ready) + ThresholdEncKey, } #[tokio::main] @@ -46,8 +64,6 @@ async fn main() -> Result<()> { .await .context(format!("Failed to read config file: {:?}", &args.config))?; - info!("Start committee registration"); - let provider = build_provider(args.mnemonic.clone(), args.index, args.url.clone())?; let addr = args.key_manager_addr; if provider @@ -63,40 +79,79 @@ async fn main() -> Result<()> { let contract = KeyManager::new(addr, provider); - // prepare input argument from config file - let members = config - .members - .iter() - .map(|m| { - Ok::<_, anyhow::Error>(CommitteeMemberSol { - sigKey: m.signing_key.to_bytes().into(), - dhKey: m.dh_key.as_bytes().into(), - dkgKey: m.dkg_enc_key.to_bytes()?.into(), - networkAddress: m.public_address.to_string(), - }) - }) - .collect::, _>>()?; - - let timestamp: u64 = config - .effective_timestamp - .as_second() - .try_into() - .with_context(|| { - format!( - "failed to convert timestamp {} to u64", - config.effective_timestamp - ) - })?; - - // send tx and invoke the contract - let _tx_receipt = contract - .setNextCommittee(timestamp, members) - .send() - .await? - .get_receipt() - .await?; - - let registered_cid = contract.nextCommitteeId().call().await? - 1; - info!("Registered new committee with id: {registered_cid}"); + match args.action { + Action::NewCommittee => { + info!("Start committee registration"); + // prepare input argument from config file + let members = config + .members + .iter() + .map(|m| { + Ok::<_, anyhow::Error>(CommitteeMemberSol { + sigKey: m.signing_key.to_bytes().into(), + dhKey: m.dh_key.as_bytes().into(), + dkgKey: m.dkg_enc_key.to_bytes()?.into(), + networkAddress: m.public_address.to_string(), + }) + }) + .collect::, _>>()?; + + let timestamp: u64 = config + .effective_timestamp + .as_second() + .try_into() + .with_context(|| { + format!( + "failed to convert timestamp {} to u64", + config.effective_timestamp + ) + })?; + + // send tx and invoke the contract + let _tx_receipt = contract + .setNextCommittee(timestamp, members) + .send() + .await? + .get_receipt() + .await?; + + let registered_cid = contract.nextCommitteeId().call().await? - 1; + info!("Registered new committee with id: {registered_cid}"); + } + Action::ThresholdEncKey => { + info!("Start threshold encryption key registration"); + let client = Client::builder().timeout(Duration::from_secs(1)).build()?; + let urls = config + .members + .iter() + .map(|m| { + let addr = m.http_api.clone(); + Url::parse(&format!("http://{addr}/v1/encryption-key")) + .with_context(|| format!("parsing {addr} into a url")) + }) + .collect::, _>>()?; + + let mut acc = ThresholdEncKeyCellAccumulator::new(client, urls.into_iter()); + let Some(key) = acc.enc_key().await else { + warn!("encryption key not available yet"); + return Err(anyhow!( + "threshold enc key not available on enough nodes, try later" + )); + }; + + let _tx_receipt = contract + .setThresholdEncryptionKey(key.to_owned().to_bytes()?.into()) + .send() + .await? + .get_receipt() + .await?; + assert_eq!( + &ThresholdEncKey::from_bytes(&contract.thresholdEncryptionKey().call().await?.0)?, + key + ); + + info!("Registered threshold encryption key"); + } + } Ok(()) } diff --git a/timeboost-crypto/src/sg_encryption.rs b/timeboost-crypto/src/sg_encryption.rs index 3e3b5902..f2c81ee3 100644 --- a/timeboost-crypto/src/sg_encryption.rs +++ b/timeboost-crypto/src/sg_encryption.rs @@ -5,6 +5,7 @@ use anyhow::anyhow; use ark_ec::{AffineRepr, CurveGroup, hashing::HashToCurve}; use ark_ff::{PrimeField, UniformRand}; use ark_poly::{DenseUVPolynomial, Polynomial, polynomial::univariate::DensePolynomial}; +use ark_serialize::SerializationError; use ark_std::rand::Rng; use ark_std::rand::rngs::OsRng; use derive_more::From; @@ -342,6 +343,19 @@ pub struct PublicKey { key: C, } +impl PublicKey { + pub fn to_bytes(&self) -> Result, SerializationError> { + let mut v = Vec::new(); + self.key.serialize_compressed(&mut v)?; + Ok(v) + } + + pub fn from_bytes(value: &[u8]) -> Result { + let key = C::deserialize_compressed(value)?; + Ok(Self { key }) + } +} + #[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop, From)] pub struct KeyShare { diff --git a/yapper/src/enc_key.rs b/timeboost-utils/src/enc_key.rs similarity index 91% rename from yapper/src/enc_key.rs rename to timeboost-utils/src/enc_key.rs index 994563a9..af164164 100644 --- a/yapper/src/enc_key.rs +++ b/timeboost-utils/src/enc_key.rs @@ -1,6 +1,6 @@ use reqwest::{Client, Url}; use std::collections::HashMap; -use timeboost::crypto::prelude::ThresholdEncKey; +use timeboost_crypto::prelude::ThresholdEncKey; use tracing::warn; @@ -28,7 +28,7 @@ async fn fetch_encryption_key(client: &Client, enckey_url: &Url) -> Option>, @@ -40,7 +40,7 @@ pub(crate) struct ThresholdEncKeyCellAccumulator { impl ThresholdEncKeyCellAccumulator { /// give a list of TimeboostApi's endpoint to query `/enckey` status - pub(crate) fn new(client: Client, urls: impl Iterator) -> Self { + pub fn new(client: Client, urls: impl Iterator) -> Self { let results: HashMap> = urls.map(|url| (url, None)).collect(); let threshold = results.len().div_ceil(3); Self { @@ -53,7 +53,7 @@ impl ThresholdEncKeyCellAccumulator { /// try to get the threshold encryption key, only available after a threshold of nodes /// finish their DKG processes. - pub(crate) async fn enc_key(&mut self) -> Option<&ThresholdEncKey> { + pub async fn enc_key(&mut self) -> Option<&ThresholdEncKey> { // if result is already available, directly return if self.output.is_some() { self.output.as_ref() diff --git a/timeboost-utils/src/lib.rs b/timeboost-utils/src/lib.rs index 92b37afa..a7ad98a5 100644 --- a/timeboost-utils/src/lib.rs +++ b/timeboost-utils/src/lib.rs @@ -1,3 +1,4 @@ +pub mod enc_key; pub mod load_generation; pub mod types; pub mod until; diff --git a/yapper/src/main.rs b/yapper/src/main.rs index 08f93243..e27ba1fd 100644 --- a/yapper/src/main.rs +++ b/yapper/src/main.rs @@ -23,7 +23,6 @@ use crate::config::YapperConfig; use crate::yapper::Yapper; mod config; -mod enc_key; mod yapper; #[derive(Parser, Debug)] diff --git a/yapper/src/yapper.rs b/yapper/src/yapper.rs index c31fbf4c..4e04e67a 100644 --- a/yapper/src/yapper.rs +++ b/yapper/src/yapper.rs @@ -11,11 +11,12 @@ use anyhow::{Context, Result}; use futures::future::join_all; use reqwest::{Client, Url}; use timeboost::types::BundleVariant; +use timeboost_utils::enc_key::ThresholdEncKeyCellAccumulator; use timeboost_utils::load_generation::{TxInfo, make_bundle, make_dev_acct_bundle, tps_to_millis}; use tokio::time::interval; use tracing::warn; -use crate::{config::YapperConfig, enc_key::ThresholdEncKeyCellAccumulator}; +use crate::config::YapperConfig; /// This is the address of the prefunded dev account for nitro chain /// https://docs.arbitrum.io/run-arbitrum-node/run-local-full-chain-simulation#default-endpoints-and-addresses From 921807b75941edf498aaf74c7c23e444252681f5 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Mon, 15 Sep 2025 17:50:23 +0800 Subject: [PATCH 11/25] yapper optionally read enc_key from contract --- Cargo.lock | 1 + justfile | 12 ++++++++++-- yapper/Cargo.toml | 1 + yapper/src/config.rs | 3 +++ yapper/src/main.rs | 27 ++++++++++++++++++++++++++- yapper/src/yapper.rs | 31 ++++++++++++++++++++----------- 6 files changed, 61 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8aba211..c70198f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14035,6 +14035,7 @@ dependencies = [ "futures", "reqwest", "timeboost", + "timeboost-contract", "timeboost-utils", "tokio", "tracing", diff --git a/justfile b/justfile index 0860d0e6..50465e45 100644 --- a/justfile +++ b/justfile @@ -218,8 +218,16 @@ test-dyn-comm: build_release_until build-test-utils -u http://localhost:8545 \ -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ -c test-configs/c1/committee.toml" \ - --run "8:sleep 5" \ - --spawn "9:target/release/yapper --keyset-file test-configs/c1/committee.toml" \ + --run "8:sleep 8" \ + --run "9:target/release/register \ + -u http://localhost:8545 \ + -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ + -c test-configs/c0/committee.toml \ + -a threshold-enc-key" \ + --spawn "10:target/release/yapper \ + --keyset-file test-configs/c1/committee.toml \ + --parent-url http://localhost:8545 \ + --key-manager-contract 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" \ target/release/run-committee -- --configs test-configs/c1/ \ --committee 1 \ --timeboost target/release/timeboost \ diff --git a/yapper/Cargo.toml b/yapper/Cargo.toml index 486da357..1b11fe8b 100644 --- a/yapper/Cargo.toml +++ b/yapper/Cargo.toml @@ -14,6 +14,7 @@ cliquenet = { path = "../cliquenet" } futures = { workspace = true } reqwest = { workspace = true } timeboost = { path = "../timeboost" } +timeboost-contract = { path = "../timeboost-contract" } timeboost-utils = { path = "../timeboost-utils" } tokio = { workspace = true } tracing = { workspace = true } diff --git a/yapper/src/config.rs b/yapper/src/config.rs index 6213a930..e7fee873 100644 --- a/yapper/src/config.rs +++ b/yapper/src/config.rs @@ -1,6 +1,7 @@ use bon::Builder; use cliquenet::Address; use reqwest::Url; +use timeboost::crypto::prelude::ThresholdEncKey; #[derive(Debug, Clone, Builder)] pub(crate) struct YapperConfig { @@ -12,4 +13,6 @@ pub(crate) struct YapperConfig { pub(crate) nitro_url: Option, /// Chain id for l2 chain pub(crate) chain_id: u64, + /// Use a given threshold encryption key, instead of fetching from nodes + pub(crate) threshold_enc_key: Option, } diff --git a/yapper/src/main.rs b/yapper/src/main.rs index e27ba1fd..44d4560f 100644 --- a/yapper/src/main.rs +++ b/yapper/src/main.rs @@ -6,11 +6,13 @@ use std::path::PathBuf; +use alloy::{primitives::Address, providers::ProviderBuilder}; use anyhow::{Context, Result}; use clap::Parser; use reqwest::Url; -use timeboost::config::CommitteeConfig; +use timeboost::{config::CommitteeConfig, crypto::prelude::ThresholdEncKey}; +use timeboost_contract::KeyManager; use timeboost_utils::types::logging::init_logging; use timeboost_utils::wait_for_live_peer; use tokio::signal::{ @@ -45,6 +47,14 @@ struct Cli { /// Nitro node url. #[clap(long)] nitro_url: Option, + + /// Parent chain where KeyManager is deployed + #[clap(long)] + parent_url: Option, + + /// KeyManager contract address on the parent chain + #[clap(long)] + key_manager_contract: Option
, } #[tokio::main] @@ -66,10 +76,25 @@ async fn main() -> Result<()> { addresses.push(node.http_api); } + let threshold_enc_key = if let Some(rpc) = cli.parent_url { + let km_addr = cli + .key_manager_contract + .expect("provide both parent chain and key manager contract"); + let provider = ProviderBuilder::new().connect_http(rpc); + let contract = KeyManager::new(km_addr, provider); + + Some(ThresholdEncKey::from_bytes( + &contract.thresholdEncryptionKey().call().await?.0, + )?) + } else { + None + }; + let config = YapperConfig::builder() .addresses(addresses) .tps(cli.tps) .maybe_nitro_url(cli.nitro_url) + .maybe_threshold_enc_key(threshold_enc_key) .chain_id(cli.chain_id) .build(); let yapper = Yapper::new(config).await?; diff --git a/yapper/src/yapper.rs b/yapper/src/yapper.rs index 4e04e67a..918da629 100644 --- a/yapper/src/yapper.rs +++ b/yapper/src/yapper.rs @@ -10,7 +10,7 @@ use alloy::{ use anyhow::{Context, Result}; use futures::future::join_all; use reqwest::{Client, Url}; -use timeboost::types::BundleVariant; +use timeboost::{crypto::prelude::ThresholdEncKey, types::BundleVariant}; use timeboost_utils::enc_key::ThresholdEncKeyCellAccumulator; use timeboost_utils::load_generation::{TxInfo, make_bundle, make_dev_acct_bundle, tps_to_millis}; use tokio::time::interval; @@ -42,6 +42,7 @@ pub(crate) struct Yapper { interval: Duration, chain_id: u64, provider: Option, + enc_key: Option, } impl Yapper { @@ -68,12 +69,14 @@ impl Yapper { } else { None }; + Ok(Self { urls, interval: Duration::from_millis(tps_to_millis(cfg.tps)), client, provider, chain_id: cfg.chain_id, + enc_key: cfg.threshold_enc_key, }) } @@ -93,12 +96,15 @@ impl Yapper { warn!("failed to prepare txn"); continue; }; - let enc_key = match acc.enc_key().await { + let enc_key = match self.enc_key.as_ref() { Some(key) => key, - None => { - warn!("encryption key not available yet"); - continue; - } + None => match acc.enc_key().await { + Some(key) => key, + None => { + warn!("encryption key not available yet"); + continue; + } + }, }; let Ok(b) = make_dev_acct_bundle(enc_key, txn) else { warn!("failed to generate dev account bundle"); @@ -106,12 +112,15 @@ impl Yapper { }; b } else { - let enc_key = match acc.enc_key().await { + let enc_key = match self.enc_key.as_ref() { Some(key) => key, - None => { - warn!("encryption key not available yet"); - continue; - } + None => match acc.enc_key().await { + Some(key) => key, + None => { + warn!("encryption key not available yet"); + continue; + } + }, }; let Ok(b) = make_bundle(enc_key) else { warn!("failed to generate bundle"); From 7d95f46ff9bd0288ef201f5249d2e5e48c4977ef Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Mon, 15 Sep 2025 18:32:39 +0800 Subject: [PATCH 12/25] scan from genesis for test, not missing any events --- timeboost/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/timeboost/src/lib.rs b/timeboost/src/lib.rs index 673e7ce8..ceabfe66 100644 --- a/timeboost/src/lib.rs +++ b/timeboost/src/lib.rs @@ -113,9 +113,9 @@ impl Timeboost { error!(?err, "fail to get chainid"); err })?; - // local test chain don't have finality gadget, thus don't support `Finalized` tag let tag = if chain_id == 31337 || chain_id == 1337 { - BlockNumberOrTag::Latest + // local test chain, we start scanning from the genesis + BlockNumberOrTag::Number(0) } else { BlockNumberOrTag::Finalized }; From d07f98e9897e563380202e664cc73e6102246e3a Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Mon, 15 Sep 2025 23:50:20 +0800 Subject: [PATCH 13/25] fix: sync prev committee during sequencerconfig derivation --- justfile | 22 +++-- test-utils/src/binaries/run-committee.rs | 5 +- timeboost/src/binaries/timeboost.rs | 10 +-- timeboost/src/conf.rs | 104 +++++++++++++++++++++-- timeboost/src/lib.rs | 2 +- 5 files changed, 117 insertions(+), 26 deletions(-) diff --git a/justfile b/justfile index 50465e45..f8af437f 100644 --- a/justfile +++ b/justfile @@ -199,9 +199,8 @@ test-dyn-comm: build_release_until build-test-utils --spawn "1:anvil --port 8545" \ --run "2:sleep 2" \ --run "3:scripts/deploy-test-contract" \ - --spawn "4:target/release/yapper --keyset-file test-configs/c0/committee.toml" \ - --spawn "5:target/release/run-committee --configs test-configs/c0/ --committee 0 --timeboost target/release/timeboost --until 1000" \ - --run "6:target/release/mkconfig -n 3 \ + --spawn "4:target/release/run-committee --configs test-configs/c0/ --committee 0" \ + --run "5:target/release/mkconfig -n 3 \ --public-addr 127.0.0.1:9000 \ --internal-addr 127.0.0.1:9003 \ --http-api 127.0.0.1:9004 \ @@ -209,32 +208,31 @@ test-dyn-comm: build_release_until build-test-utils --parent-rpc-url http://127.0.0.1:8545 \ --parent-ws-url ws://127.0.0.1:8545 \ --parent-chain-id 31337 \ - --parent-ibox-contract "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" \ - --key-manager-contract "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" \ + --parent-ibox-contract 0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1 \ + --key-manager-contract 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ --timestamp `just now-plus-20s` \ --stamp-dir /tmp \ --output test-configs/c1" \ - --run "7:target/release/register \ + --run "6:target/release/register \ -u http://localhost:8545 \ -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ -c test-configs/c1/committee.toml" \ - --run "8:sleep 8" \ - --run "9:target/release/register \ + --run "7:sleep 8" \ + --run "8:target/release/register \ -u http://localhost:8545 \ -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ -c test-configs/c0/committee.toml \ -a threshold-enc-key" \ - --spawn "10:target/release/yapper \ + --spawn "9:target/release/yapper \ --keyset-file test-configs/c1/committee.toml \ --parent-url http://localhost:8545 \ --key-manager-contract 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" \ - target/release/run-committee -- --configs test-configs/c1/ \ + target/release/run-committee -- \ + --configs test-configs/c1/ \ --committee 1 \ - --timeboost target/release/timeboost \ --until 500 \ --required-decrypt-rounds 3 - # portable calculation of now() + 20s in "%Y-%m-%dT%H:%M:%SZ" format [private] now-plus-20s: diff --git a/test-utils/src/binaries/run-committee.rs b/test-utils/src/binaries/run-committee.rs index ba57360a..25e47901 100644 --- a/test-utils/src/binaries/run-committee.rs +++ b/test-utils/src/binaries/run-committee.rs @@ -55,13 +55,14 @@ async fn main() -> Result<()> { let mut cmd = Command::new(args.timeboost.as_os_str()); cmd.arg("--committee-id") .arg(args.committee.to_string()) + .arg("--committee") + .arg(format!("{}/committee.toml", args.configs.to_str().unwrap())) .arg("--config") .arg(entry.path()) .arg("--ignore-stamp"); + if let Some(until) = args.until { cmd.arg("--until").arg(until.to_string()); - cmd.arg("--committee") - .arg(format!("{}/committee.toml", args.configs.to_str().unwrap())); } if let Some(r) = args.required_decrypt_rounds { cmd.arg("--required-decrypt-rounds").arg(r.to_string()); diff --git a/timeboost/src/binaries/timeboost.rs b/timeboost/src/binaries/timeboost.rs index ca4cfbed..59b90342 100644 --- a/timeboost/src/binaries/timeboost.rs +++ b/timeboost/src/binaries/timeboost.rs @@ -100,13 +100,13 @@ async fn main() -> Result<()> { .iter() .map(|peer| { let sig_key = multisig::PublicKey::try_from(peer.sigKey.as_ref()) - .expect("Failed to parse sigKey"); + .expect("Should parse sigKey bytes"); let dh_key = - x25519::PublicKey::try_from(peer.dhKey.as_ref()).expect("Failed to parse dhKey"); - let dkg_enc_key = DkgEncKey::from_bytes(peer.dkgKey.as_ref()) - .expect("Blackbox from_bytes should work"); + x25519::PublicKey::try_from(peer.dhKey.as_ref()).expect("Should parse dhKey bytes"); + let dkg_enc_key = + DkgEncKey::from_bytes(peer.dkgKey.as_ref()).expect("Should parse dkgKey bytes"); let sailfish_address = cliquenet::Address::try_from(peer.networkAddress.as_ref()) - .expect("Failed to parse networkAddress"); + .expect("Should parse networkAddress string"); (sig_key, dh_key, dkg_enc_key, sailfish_address) }) .collect::>(); diff --git a/timeboost/src/conf.rs b/timeboost/src/conf.rs index 229cd564..c0a231c2 100644 --- a/timeboost/src/conf.rs +++ b/timeboost/src/conf.rs @@ -1,9 +1,12 @@ +use alloy::providers::ProviderBuilder; +use anyhow::Result; use bon::Builder; use cliquenet::{Address, AddressableCommittee}; -use multisig::{Keypair, x25519}; +use multisig::{Committee, Keypair, x25519}; use timeboost_builder::{CertifierConfig, SubmitterConfig, robusta}; -use timeboost_config::ChainConfig; -use timeboost_crypto::prelude::DkgDecKey; +use timeboost_config::{ChainConfig, DECRYPTER_PORT_OFFSET}; +use timeboost_contract::{CommitteeMemberSol, KeyManager}; +use timeboost_crypto::prelude::{DkgDecKey, DkgEncKey}; use timeboost_sequencer::SequencerConfig; use timeboost_types::{KeyStore, ThresholdKeyCell}; @@ -61,8 +64,95 @@ pub struct TimeboostConfig { } impl TimeboostConfig { - pub fn sequencer_config(&self) -> SequencerConfig { - SequencerConfig::builder() + pub async fn sequencer_config(&self) -> Result { + let cur_cid: u64 = self.sailfish_committee.committee().id().into(); + + let (prev_sailfish, prev_decrypt) = if cur_cid == 0u64 { + (None, None) + } else { + // syncing with contract to get peer info about the previous committee + // largely adapted from binaries/timeboost.rs + // TODO: (alex) extrapolate the remaining logic into a common helper + let prev_cid = cur_cid - 1; + + let provider = + ProviderBuilder::new().connect_http(self.chain_config.parent.rpc_url.clone()); + let contract = + KeyManager::new(self.chain_config.parent.key_manager_contract, &provider); + let members: Vec = + contract.getCommitteeById(prev_cid).call().await?.members; + + tracing::info!(label = %self.sign_keypair.public_key(), committee_id = %prev_cid, "prev committee info synced"); + + let peer_hosts_and_keys = members + .iter() + .map(|peer| { + let sig_key = multisig::PublicKey::try_from(peer.sigKey.as_ref()) + .expect("Should parse sigKey bytes"); + let dh_key = x25519::PublicKey::try_from(peer.dhKey.as_ref()) + .expect("Should parse dhKey bytes"); + let dkg_enc_key = DkgEncKey::from_bytes(peer.dkgKey.as_ref()) + .expect("Should parse dkgKey bytes"); + let sailfish_address = + cliquenet::Address::try_from(peer.networkAddress.as_ref()) + .expect("Should parse networkAddress string"); + (sig_key, dh_key, dkg_enc_key, sailfish_address) + }) + .collect::>(); + + let mut sailfish_peer_hosts_and_keys = Vec::new(); + let mut decrypt_peer_hosts_and_keys = Vec::new(); + let mut dkg_enc_keys = Vec::new(); + + for (signing_key, dh_key, dkg_enc_key, sailfish_addr) in + peer_hosts_and_keys.iter().cloned() + { + sailfish_peer_hosts_and_keys.push((signing_key, dh_key, sailfish_addr.clone())); + decrypt_peer_hosts_and_keys.push(( + signing_key, + dh_key, + sailfish_addr.clone().with_offset(DECRYPTER_PORT_OFFSET), + )); + dkg_enc_keys.push(dkg_enc_key.clone()); + } + + let sailfish_committee = { + let c = Committee::new( + prev_cid, + sailfish_peer_hosts_and_keys + .iter() + .enumerate() + .map(|(i, (k, ..))| (i as u8, *k)), + ); + AddressableCommittee::new(c, sailfish_peer_hosts_and_keys.iter().cloned()) + }; + + let decrypt_committee = { + let c = Committee::new( + prev_cid, + decrypt_peer_hosts_and_keys + .iter() + .enumerate() + .map(|(i, (k, ..))| (i as u8, *k)), + ); + AddressableCommittee::new(c, decrypt_peer_hosts_and_keys.iter().cloned()) + }; + + let key_store = KeyStore::new( + sailfish_committee.committee().clone(), + dkg_enc_keys + .into_iter() + .enumerate() + .map(|(i, k)| (i as u8, k)), + ); + + ( + Some(sailfish_committee), + Some((decrypt_committee, key_store)), + ) + }; + + Ok(SequencerConfig::builder() .sign_keypair(self.sign_keypair.clone()) .dh_keypair(self.dh_keypair.clone()) .dkg_key(self.dkg_key.clone()) @@ -71,10 +161,12 @@ impl TimeboostConfig { .sailfish_committee(self.sailfish_committee.clone()) .decrypt_committee((self.decrypt_committee.clone(), self.key_store.clone())) .recover(self.recover) + .maybe_previous_sailfish_committee(prev_sailfish) + .maybe_previous_decrypt_committee(prev_decrypt) .leash_len(self.leash_len) .threshold_dec_key(self.threshold_dec_key.clone()) .chain_config(self.chain_config.clone()) - .build() + .build()) } pub fn certifier_config(&self) -> CertifierConfig { diff --git a/timeboost/src/lib.rs b/timeboost/src/lib.rs index ceabfe66..58924495 100644 --- a/timeboost/src/lib.rs +++ b/timeboost/src/lib.rs @@ -58,7 +58,7 @@ impl Timeboost { pub async fn new(cfg: TimeboostConfig) -> Result { let pro = Arc::new(PrometheusMetrics::default()); let met = Arc::new(TimeboostMetrics::new(&*pro)); - let seq = Sequencer::new(cfg.sequencer_config(), &*pro).await?; + let seq = Sequencer::new(cfg.sequencer_config().await?, &*pro).await?; let blk = Certifier::new(cfg.certifier_config(), &*pro).await?; let sub = Submitter::new(cfg.submitter_config(), &*pro); From f9651fa18f0064f2bd77fe73b710a57018399ca7 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Tue, 16 Sep 2025 11:47:29 +0800 Subject: [PATCH 14/25] fix: enough time for resharing --- justfile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/justfile b/justfile index f8af437f..4a3262e3 100644 --- a/justfile +++ b/justfile @@ -199,8 +199,8 @@ test-dyn-comm: build_release_until build-test-utils --spawn "1:anvil --port 8545" \ --run "2:sleep 2" \ --run "3:scripts/deploy-test-contract" \ - --spawn "4:target/release/run-committee --configs test-configs/c0/ --committee 0" \ - --run "5:target/release/mkconfig -n 3 \ + --spawn "4:target/release/run-committee --configs test-configs/c0/ --committee 0 --until 1600" \ + --run "5:target/release/mkconfig -n 4 \ --public-addr 127.0.0.1:9000 \ --internal-addr 127.0.0.1:9003 \ --http-api 127.0.0.1:9004 \ @@ -213,16 +213,16 @@ test-dyn-comm: build_release_until build-test-utils --timestamp `just now-plus-20s` \ --stamp-dir /tmp \ --output test-configs/c1" \ - --run "6:target/release/register \ + --run "6:sleep 8" \ + --run "7:target/release/register \ -u http://localhost:8545 \ -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ - -c test-configs/c1/committee.toml" \ - --run "7:sleep 8" \ + -c test-configs/c0/committee.toml \ + -a threshold-enc-key" \ --run "8:target/release/register \ -u http://localhost:8545 \ -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ - -c test-configs/c0/committee.toml \ - -a threshold-enc-key" \ + -c test-configs/c1/committee.toml" \ --spawn "9:target/release/yapper \ --keyset-file test-configs/c1/committee.toml \ --parent-url http://localhost:8545 \ @@ -230,10 +230,10 @@ test-dyn-comm: build_release_until build-test-utils target/release/run-committee -- \ --configs test-configs/c1/ \ --committee 1 \ - --until 500 \ + --until 2000 \ --required-decrypt-rounds 3 -# portable calculation of now() + 20s in "%Y-%m-%dT%H:%M:%SZ" format +# portable calculation of now() + 12s in "%Y-%m-%dT%H:%M:%SZ" format [private] now-plus-20s: - @python3 -c 'from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc)+timedelta(seconds=20)).strftime("%Y-%m-%dT%H:%M:%SZ"))' + @python3 -c 'from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc)+timedelta(seconds=20)).strftime("%Y-%m-%dT%H:%M:%SZ"))' From 9e56d2f017104657deee7e9aca19feede510dff0 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Tue, 16 Sep 2025 12:52:34 +0800 Subject: [PATCH 15/25] remove tmp c1 configs, disambiguate --committee-id flag --- justfile | 12 ++++---- test-configs/c1/committee.toml | 25 ----------------- test-configs/c1/node_0.toml | 35 ------------------------ test-configs/c1/node_1.toml | 35 ------------------------ test-configs/c1/node_2.toml | 35 ------------------------ test-utils/src/binaries/run-committee.rs | 11 ++++---- 6 files changed, 12 insertions(+), 141 deletions(-) delete mode 100755 test-configs/c1/committee.toml delete mode 100755 test-configs/c1/node_0.toml delete mode 100755 test-configs/c1/node_1.toml delete mode 100755 test-configs/c1/node_2.toml diff --git a/justfile b/justfile index ef815091..2f00215c 100644 --- a/justfile +++ b/justfile @@ -185,7 +185,7 @@ test-all: build_release build-test-utils --run "3:scripts/deploy-test-contract" \ --spawn "4:target/release/block-maker --port 55000 --committee test-configs/local/committee.toml" \ --spawn "4:target/release/yapper --keyset-file test-configs/local/committee.toml" \ - --spawn "5:target/release/run-committee --configs test-configs/local/ --committee 0 --timeboost target/release/timeboost" \ + --spawn "5:target/release/run-committee --configs test-configs/local/ --committee-id 0 --timeboost target/release/timeboost" \ target/release/block-checker -- \ --config test-configs/local/node_0.toml \ --committee test-configs/local/committee.toml \ @@ -199,7 +199,7 @@ test-dyn-comm: build_release_until build-test-utils --spawn "1:anvil --port 8545" \ --run "2:sleep 2" \ --run "3:scripts/deploy-test-contract" \ - --spawn "4:target/release/run-committee --configs test-configs/c0/ --committee 0 --until 1600" \ + --spawn "4:target/release/run-committee --configs test-configs/c0/ --committee-id 0 --until 1600" \ --run "5:target/release/mkconfig -n 4 \ --public-addr 127.0.0.1:9000 \ --internal-addr 127.0.0.1:9003 \ @@ -229,11 +229,11 @@ test-dyn-comm: build_release_until build-test-utils --key-manager-contract 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" \ target/release/run-committee -- \ --configs test-configs/c1/ \ - --committee 1 \ - --until 2000 \ - --required-decrypt-rounds 3 + --committee-id 1 \ + --until 1800 \ + --required-decrypt-rounds 3 && rm -rf test-configs/c1 -# portable calculation of now() + 12s in "%Y-%m-%dT%H:%M:%SZ" format +# portable calculation of now() + 20s in "%Y-%m-%dT%H:%M:%SZ" format [private] now-plus-20s: @python3 -c 'from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc)+timedelta(seconds=20)).strftime("%Y-%m-%dT%H:%M:%SZ"))' diff --git a/test-configs/c1/committee.toml b/test-configs/c1/committee.toml deleted file mode 100755 index ae8c0444..00000000 --- a/test-configs/c1/committee.toml +++ /dev/null @@ -1,25 +0,0 @@ -effective_timestamp = "2025-09-12T20:05:52Z" - -[[members]] -signing_key = "dQST7kJpV3RXqNMyKUA6eMW5HiVLAVK4AFwX5nQJbaka" -dh_key = "JBm1FZhcGcZE4bUVDrRCsHZYcz78BcjgdKejknytQvnH" -dkg_enc_key = "6ZH8nCCCmX5UDMjcR18oNz5YTUixDQeVqXVoYRqMxGHLkoNo95Gw27PWD323Kf7Fqs" -public_address = "127.0.0.1:9000" -http_api = "127.0.0.1:9004" -internal_api = "127.0.0.1:9003" - -[[members]] -signing_key = "gWpDFTTBYN8u5JDz83PYVDakQTMzPPNNonnkt4FRSJD3" -dh_key = "6fBHLbP8fKjk2tYgqaMs3GQTiHQSPh1uZ9d3J4ZYM9gC" -dkg_enc_key = "73sU1acMiGsiJu9pVY8yQag7DvRcvtYRkATjPCv485Mwr6FYimFVLRUsz9Papy745f" -public_address = "127.0.0.1:9010" -http_api = "127.0.0.1:9014" -internal_api = "127.0.0.1:9013" - -[[members]] -signing_key = "c24KQW7jK7d8NdKYatGn6cZDSoMro5XsxKoBkkLmSUwN" -dh_key = "Fer6ptfw1MgBDxCuLn54Xo7V62iEhZZyQ3JTfyyGMgM9" -dkg_enc_key = "5vacdPGGmQSfS474Fy5dizxoE1GHLW4W48FBx7UfVTtERjtPhFN7CEBT23y6uBHiew" -public_address = "127.0.0.1:9020" -http_api = "127.0.0.1:9024" -internal_api = "127.0.0.1:9023" diff --git a/test-configs/c1/node_0.toml b/test-configs/c1/node_0.toml deleted file mode 100755 index 46802d3e..00000000 --- a/test-configs/c1/node_0.toml +++ /dev/null @@ -1,35 +0,0 @@ -stamp = "/tmp/timeboost.0.stamp" - -[net.public] -address = "127.0.0.1:9000" -http_api = "127.0.0.1:9004" - -[net.internal] -address = "127.0.0.1:9003" - -[keys.signing] -secret = "HwhEANTEwUJxYwtGXWqHp7YSubxceEc1kXLBPyVkBryv" -public = "dQST7kJpV3RXqNMyKUA6eMW5HiVLAVK4AFwX5nQJbaka" - -[keys.dh] -secret = "3pz6B8owd4eFZoCmtt8FvJkUKHURTSF4QWw7VVmmeBzW" -public = "JBm1FZhcGcZE4bUVDrRCsHZYcz78BcjgdKejknytQvnH" - -[keys.dkg] -secret = "6rACFLkLskjcCArUeEdUz4UBuU39BnzJAGHuRLJG6dg3" -public = "6ZH8nCCCmX5UDMjcR18oNz5YTUixDQeVqXVoYRqMxGHLkoNo95Gw27PWD323Kf7Fqs" - -[chain] -namespace = 10101 - -[chain.parent] -id = 31337 -rpc_url = "http://127.0.0.1:8545/" -ws_url = "ws://127.0.0.1:8545/" -ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" -block_tag = "finalized" -key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" - -[espresso] -base_url = "https://query.decaf.testnet.espresso.network/v1/" -websockets_base_url = "wss://query.decaf.testnet.espresso.network/v1/" diff --git a/test-configs/c1/node_1.toml b/test-configs/c1/node_1.toml deleted file mode 100755 index b3bf2956..00000000 --- a/test-configs/c1/node_1.toml +++ /dev/null @@ -1,35 +0,0 @@ -stamp = "/tmp/timeboost.1.stamp" - -[net.public] -address = "127.0.0.1:9010" -http_api = "127.0.0.1:9014" - -[net.internal] -address = "127.0.0.1:9013" - -[keys.signing] -secret = "4JiTfGM5cb7RLDVfNb89nZH7ZeYWEy6UfgukCJe1uJ2s" -public = "gWpDFTTBYN8u5JDz83PYVDakQTMzPPNNonnkt4FRSJD3" - -[keys.dh] -secret = "FZGiSAFQaMvVJ7zCEFmQu5cgRYKwQJdCQqSrw8SYRhHJ" -public = "6fBHLbP8fKjk2tYgqaMs3GQTiHQSPh1uZ9d3J4ZYM9gC" - -[keys.dkg] -secret = "DegFS9EvP9pz7rYX4JsSJ4aLRSq3fBkdy8U6LqD3eQqt" -public = "73sU1acMiGsiJu9pVY8yQag7DvRcvtYRkATjPCv485Mwr6FYimFVLRUsz9Papy745f" - -[chain] -namespace = 10101 - -[chain.parent] -id = 31337 -rpc_url = "http://127.0.0.1:8545/" -ws_url = "ws://127.0.0.1:8545/" -ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" -block_tag = "finalized" -key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" - -[espresso] -base_url = "https://query.decaf.testnet.espresso.network/v1/" -websockets_base_url = "wss://query.decaf.testnet.espresso.network/v1/" diff --git a/test-configs/c1/node_2.toml b/test-configs/c1/node_2.toml deleted file mode 100755 index 8c96eebd..00000000 --- a/test-configs/c1/node_2.toml +++ /dev/null @@ -1,35 +0,0 @@ -stamp = "/tmp/timeboost.2.stamp" - -[net.public] -address = "127.0.0.1:9020" -http_api = "127.0.0.1:9024" - -[net.internal] -address = "127.0.0.1:9023" - -[keys.signing] -secret = "9j28e6HiGxdShGwNLH2emYXVaE7mNdBmhikyknSdtTqS" -public = "c24KQW7jK7d8NdKYatGn6cZDSoMro5XsxKoBkkLmSUwN" - -[keys.dh] -secret = "7ecZnC9dPzs9pZ6uqCSkEiLr44GqdFgoWCqG5jgD6mJ8" -public = "Fer6ptfw1MgBDxCuLn54Xo7V62iEhZZyQ3JTfyyGMgM9" - -[keys.dkg] -secret = "FwzsYVwZRnFMNs11bbMKoVdeQPmMM24dSobxFrscmWnu" -public = "5vacdPGGmQSfS474Fy5dizxoE1GHLW4W48FBx7UfVTtERjtPhFN7CEBT23y6uBHiew" - -[chain] -namespace = 10101 - -[chain.parent] -id = 31337 -rpc_url = "http://127.0.0.1:8545/" -ws_url = "ws://127.0.0.1:8545/" -ibox_contract = "0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1" -block_tag = "finalized" -key_manager_contract = "0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35" - -[espresso] -base_url = "https://query.decaf.testnet.espresso.network/v1/" -websockets_base_url = "wss://query.decaf.testnet.espresso.network/v1/" diff --git a/test-utils/src/binaries/run-committee.rs b/test-utils/src/binaries/run-committee.rs index 25e47901..8964e2a7 100644 --- a/test-utils/src/binaries/run-committee.rs +++ b/test-utils/src/binaries/run-committee.rs @@ -11,7 +11,7 @@ struct Args { configs: PathBuf, #[clap(long, short)] - committee: u64, + committee_id: u64, #[clap(long, short, default_value = "target/release/timeboost")] timeboost: PathBuf, @@ -54,15 +54,16 @@ async fn main() -> Result<()> { } let mut cmd = Command::new(args.timeboost.as_os_str()); cmd.arg("--committee-id") - .arg(args.committee.to_string()) - .arg("--committee") - .arg(format!("{}/committee.toml", args.configs.to_str().unwrap())) + .arg(args.committee_id.to_string()) .arg("--config") .arg(entry.path()) .arg("--ignore-stamp"); if let Some(until) = args.until { - cmd.arg("--until").arg(until.to_string()); + cmd.arg("--committee") + .arg(format!("{}/committee.toml", args.configs.to_str().unwrap())) + .arg("--until") + .arg(until.to_string()); } if let Some(r) = args.required_decrypt_rounds { cmd.arg("--required-decrypt-rounds").arg(r.to_string()); From 8c10bdae2f03a49c8ada060c6b0619690d7e43bd Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Tue, 16 Sep 2025 15:27:40 +0800 Subject: [PATCH 16/25] minor fix --- justfile | 2 +- scripts/run-timeboost-demo | 1 - test-utils/src/binaries/run-committee.rs | 5 +---- timeboost/src/binaries/timeboost.rs | 10 +++------- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/justfile b/justfile index 2f00215c..106f0f69 100644 --- a/justfile +++ b/justfile @@ -230,7 +230,7 @@ test-dyn-comm: build_release_until build-test-utils target/release/run-committee -- \ --configs test-configs/c1/ \ --committee-id 1 \ - --until 1800 \ + --until 800 \ --required-decrypt-rounds 3 && rm -rf test-configs/c1 # portable calculation of now() + 20s in "%Y-%m-%dT%H:%M:%SZ" format diff --git a/scripts/run-timeboost-demo b/scripts/run-timeboost-demo index e2d8102e..64394812 100755 --- a/scripts/run-timeboost-demo +++ b/scripts/run-timeboost-demo @@ -116,7 +116,6 @@ i=0 for f in "$config_dir"/node_*.toml; do cmd=(target/release/timeboost --committee-id 0 - --committee "$config_dir/committee.toml" --config "$f" --until $rounds --watchdog-timeout 120) diff --git a/test-utils/src/binaries/run-committee.rs b/test-utils/src/binaries/run-committee.rs index 8964e2a7..bc20322a 100644 --- a/test-utils/src/binaries/run-committee.rs +++ b/test-utils/src/binaries/run-committee.rs @@ -60,10 +60,7 @@ async fn main() -> Result<()> { .arg("--ignore-stamp"); if let Some(until) = args.until { - cmd.arg("--committee") - .arg(format!("{}/committee.toml", args.configs.to_str().unwrap())) - .arg("--until") - .arg(until.to_string()); + cmd.arg("--until").arg(until.to_string()); } if let Some(r) = args.required_decrypt_rounds { cmd.arg("--required-decrypt-rounds").arg(r.to_string()); diff --git a/timeboost/src/binaries/timeboost.rs b/timeboost/src/binaries/timeboost.rs index 177ff0ed..1706879a 100644 --- a/timeboost/src/binaries/timeboost.rs +++ b/timeboost/src/binaries/timeboost.rs @@ -40,11 +40,6 @@ struct Cli { #[clap(long, default_value_t = true, action = clap::ArgAction::Set)] https_only: bool, - /// Path to committee config toml. - #[cfg(feature = "until")] - #[clap(long)] - committee: PathBuf, - /// The until value to use for the committee config. #[cfg(feature = "until")] #[clap(long, default_value_t = 1000)] @@ -246,9 +241,10 @@ async fn main() -> Result<()> { use tokio::time::sleep; use url::Url; - let committee = CommitteeConfig::read(&cli.committee) + let committee_conf = cli.config.with_file_name("committee.toml"); + let committee = CommitteeConfig::read(&committee_conf.to_str().unwrap()) .await - .with_context(|| format!("failed to read committee config {:?}", cli.committee))?; + .with_context(|| format!("failed to read committee config {:?}", committee_conf))?; let handle = { let Some(member) = committee From 4fb6b12fcfb27f37137b38bae22ea44fb027711e Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Tue, 16 Sep 2025 23:39:18 +0800 Subject: [PATCH 17/25] update register, allow __SPACE__ for multi-word argument in run --- Cargo.lock | 27 +++++++++++++-------- justfile | 7 ++++-- scripts/deploy-test-contract | 1 + scripts/test-contract-deploy | 2 +- test-utils/Cargo.toml | 1 + test-utils/src/binaries/run.rs | 23 +++++++++++------- timeboost-contract/src/binaries/register.rs | 8 ++---- 7 files changed, 41 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0f40ac1..70631bcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1146,7 +1146,7 @@ dependencies = [ "alloy-rlp", "alloy-serde 0.13.0", "alloy-sol-types 0.8.25", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "serde_json", "thiserror 2.0.16", @@ -1167,7 +1167,7 @@ dependencies = [ "alloy-serde 1.0.30", "alloy-sol-types 1.3.1", "arbitrary", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "serde_json", "serde_with", @@ -6288,7 +6288,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite 0.2.16", - "socket2 0.5.10", + "socket2 0.6.0", "system-configuration", "tokio", "tower-service", @@ -9231,8 +9231,8 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" dependencies = [ - "heck 0.4.1", - "itertools 0.12.1", + "heck 0.5.0", + "itertools 0.14.0", "log", "multimap", "once_cell", @@ -9254,7 +9254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.106", @@ -9267,7 +9267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.106", @@ -9416,7 +9416,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.31", - "socket2 0.5.10", + "socket2 0.6.0", "thiserror 2.0.16", "tokio", "tracing", @@ -9453,7 +9453,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.0", "tracing", "windows-sys 0.60.2", ] @@ -10812,6 +10812,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shellexpand" version = "3.1.1" @@ -10965,7 +10971,7 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.106", @@ -11674,6 +11680,7 @@ dependencies = [ "robusta", "rustix 1.1.2", "sailfish", + "shell-words", "timeboost", "timeboost-utils", "tokio", diff --git a/justfile b/justfile index 106f0f69..985a1f7d 100644 --- a/justfile +++ b/justfile @@ -215,11 +215,14 @@ test-dyn-comm: build_release_until build-test-utils --output test-configs/c1" \ --run "6:sleep 8" \ --run "7:target/release/register \ + -a threshold-enc-key \ + -m attend__SPACE__year__SPACE__erase__SPACE__basket__SPACE__blind__SPACE__adapt__SPACE__stove__SPACE__broccoli__SPACE__isolate__SPACE__unveil__SPACE__acquire__SPACE__category \ -u http://localhost:8545 \ -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ - -c test-configs/c0/committee.toml \ - -a threshold-enc-key" \ + -c test-configs/c0/committee.toml" \ --run "8:target/release/register \ + -a new-committee \ + -m attend__SPACE__year__SPACE__erase__SPACE__basket__SPACE__blind__SPACE__adapt__SPACE__stove__SPACE__broccoli__SPACE__isolate__SPACE__unveil__SPACE__acquire__SPACE__category \ -u http://localhost:8545 \ -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ -c test-configs/c1/committee.toml" \ diff --git a/scripts/deploy-test-contract b/scripts/deploy-test-contract index aa40c732..f52583e7 100755 --- a/scripts/deploy-test-contract +++ b/scripts/deploy-test-contract @@ -22,6 +22,7 @@ km_addr=$(sed -nr 's/^key_manager.*=.*"(.+)"/\1/p' "$DEPLOYMENT_FILE") # Update the contract env RUST_LOG=info cargo run --release --bin register -- \ + -a new-committee \ -m "$MANAGER_MNEMONIC" \ -i "$MANAGER_ACCOUNT_INDEX" \ -u "$URL" \ diff --git a/scripts/test-contract-deploy b/scripts/test-contract-deploy index afd5ea74..ce5919ea 100755 --- a/scripts/test-contract-deploy +++ b/scripts/test-contract-deploy @@ -84,7 +84,7 @@ km_addr=$(sed -nr 's/^key_manager.*=.*"(.+)"/\1/p' "$DEPLOYMENT_FILE") # Update the contract committee_config="test-configs/$COMMITTEE_PATH/committee.toml" -RUST_LOG=info cargo run --release --bin register -- -m "$MANAGER_MNEMONIC" -i "$MANAGER_ACCOUNT_INDEX" -u "$URL" -k "$km_addr" -c "$committee_config" +RUST_LOG=info cargo run --release --bin register -- -a new-committee -m "$MANAGER_MNEMONIC" -i "$MANAGER_ACCOUNT_INDEX" -u "$URL" -k "$km_addr" -c "$committee_config" # Finally, clean up the temporary deployment file rm -f "$DEPLOYMENT_FILE" diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index b76236d0..dc3b0e9d 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -28,6 +28,7 @@ anyhow = { workspace = true } clap = { workspace = true } futures = { workspace = true } rustix = { workspace = true } +shell-words = "1.1" tokio = { workspace = true } tracing = { workspace = true } # optional diff --git a/test-utils/src/binaries/run.rs b/test-utils/src/binaries/run.rs index 078096dd..82b4b405 100644 --- a/test-utils/src/binaries/run.rs +++ b/test-utils/src/binaries/run.rs @@ -51,13 +51,15 @@ async fn main() -> Result<()> { for commandline in commands { if args.verbose { + let joined = commandline.args.join(" "); if commandline.sync { - eprintln!("running command: {}", commandline.args) + eprintln!("running command: {joined}"); } else { - eprintln!("spawning command: {}", commandline.args) + eprintln!("spawning command: {joined}"); } } - let mut pg = ProcessGroup::spawn(commandline.args.split_whitespace())?; + + let mut pg = ProcessGroup::spawn(&commandline.args)?; if commandline.sync { let status = select! { s = pg.wait() => s?, @@ -69,7 +71,7 @@ async fn main() -> Result<()> { } } else { helpers.spawn(async move { - let mut pg = ProcessGroup::spawn(commandline.args.split_whitespace())?; + let mut pg = ProcessGroup::spawn(&commandline.args)?; let status = pg.wait().await?; Ok(status) }); @@ -110,15 +112,20 @@ async fn main() -> Result<()> { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] struct Commandline { prio: u8, - args: String, + args: Vec, sync: bool, } fn parse_command_line(s: &str) -> Result { let (p, a) = s.split_once(':').unwrap_or(("0", s)); + // Replace __SPACE__ with actual spaces, then split like shell would + let parts = shell_words::split(a)? + .into_iter() + .map(|arg| arg.replace("__SPACE__", " ")) + .collect(); Ok(Commandline { prio: p.parse()?, - args: a.to_string(), + args: parts, sync: true, }) } @@ -137,9 +144,7 @@ impl ProcessGroup { .next() .ok_or_else(|| anyhow!("invalid command-line args"))?; let mut cmd = Command::new(exe); - for a in args { - cmd.arg(a); - } + cmd.args(args); cmd.process_group(0); let child = cmd.spawn()?; let id = child.id().ok_or_else(|| anyhow!("child already exited"))?; diff --git a/timeboost-contract/src/binaries/register.rs b/timeboost-contract/src/binaries/register.rs index 567b5ff8..b56e07b9 100644 --- a/timeboost-contract/src/binaries/register.rs +++ b/timeboost-contract/src/binaries/register.rs @@ -19,11 +19,7 @@ use url::Url; #[derive(Clone, Debug, Parser)] struct Args { - #[clap( - short, - long, - default_value = "attend year erase basket blind adapt stove broccoli isolate unveil acquire category" - )] + #[clap(short, long)] mnemonic: String, #[clap(short, long, default_value_t = 0)] @@ -41,7 +37,7 @@ struct Args { config: PathBuf, /// What to register (new committee or threshold enc key?) - #[clap(long, short, default_value = "new-committee")] + #[clap(short, long)] action: Action, } From 04e216a711bf4e695e0efb3330ba138a90626625 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Tue, 16 Sep 2025 23:42:39 +0800 Subject: [PATCH 18/25] context err when parsing peer info --- timeboost/src/binaries/timeboost.rs | 18 +++++++++--------- timeboost/src/conf.rs | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/timeboost/src/binaries/timeboost.rs b/timeboost/src/binaries/timeboost.rs index 1706879a..53117ca3 100644 --- a/timeboost/src/binaries/timeboost.rs +++ b/timeboost/src/binaries/timeboost.rs @@ -93,18 +93,18 @@ async fn main() -> Result<()> { let peer_hosts_and_keys = members .iter() - .map(|peer| { + .map(|peer| -> Result<_> { let sig_key = multisig::PublicKey::try_from(peer.sigKey.as_ref()) - .expect("Should parse sigKey bytes"); - let dh_key = - x25519::PublicKey::try_from(peer.dhKey.as_ref()).expect("Should parse dhKey bytes"); - let dkg_enc_key = - DkgEncKey::from_bytes(peer.dkgKey.as_ref()).expect("Should parse dkgKey bytes"); + .with_context(|| "Failed to parse sigKey bytes")?; + let dh_key = x25519::PublicKey::try_from(peer.dhKey.as_ref()) + .with_context(|| "Failed to parse dhKey bytes")?; + let dkg_enc_key = DkgEncKey::from_bytes(peer.dkgKey.as_ref()) + .with_context(|| "Failed to parse dkgKey bytes")?; let sailfish_address = cliquenet::Address::try_from(peer.networkAddress.as_ref()) - .expect("Should parse networkAddress string"); - (sig_key, dh_key, dkg_enc_key, sailfish_address) + .with_context(|| "Failed to parse networkAddress string")?; + Ok((sig_key, dh_key, dkg_enc_key, sailfish_address)) }) - .collect::>(); + .collect::>>()?; let mut sailfish_peer_hosts_and_keys = Vec::new(); let mut decrypt_peer_hosts_and_keys = Vec::new(); diff --git a/timeboost/src/conf.rs b/timeboost/src/conf.rs index a0d8e689..7f6144d9 100644 --- a/timeboost/src/conf.rs +++ b/timeboost/src/conf.rs @@ -1,5 +1,5 @@ use alloy::providers::ProviderBuilder; -use anyhow::Result; +use anyhow::{Context, Result}; use bon::Builder; use cliquenet::{Address, AddressableCommittee}; use multisig::{Committee, Keypair, x25519}; @@ -89,19 +89,19 @@ impl TimeboostConfig { let peer_hosts_and_keys = members .iter() - .map(|peer| { + .map(|peer| -> Result<_> { let sig_key = multisig::PublicKey::try_from(peer.sigKey.as_ref()) - .expect("Should parse sigKey bytes"); + .with_context(|| "Failed to parse sigKey bytes")?; let dh_key = x25519::PublicKey::try_from(peer.dhKey.as_ref()) - .expect("Should parse dhKey bytes"); + .with_context(|| "Failed to parse dhKey bytes")?; let dkg_enc_key = DkgEncKey::from_bytes(peer.dkgKey.as_ref()) - .expect("Should parse dkgKey bytes"); + .with_context(|| "Failed to parse dkgKey bytes")?; let sailfish_address = cliquenet::Address::try_from(peer.networkAddress.as_ref()) - .expect("Should parse networkAddress string"); - (sig_key, dh_key, dkg_enc_key, sailfish_address) + .with_context(|| "Failed to parse networkAddress string")?; + Ok((sig_key, dh_key, dkg_enc_key, sailfish_address)) }) - .collect::>(); + .collect::>>()?; let mut sailfish_peer_hosts_and_keys = Vec::new(); let mut decrypt_peer_hosts_and_keys = Vec::new(); From b75a0b3c95575b79c3f0c27637a49aa448294928 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Wed, 17 Sep 2025 16:04:02 +0800 Subject: [PATCH 19/25] modularize committee sync and event stream logic --- Cargo.lock | 1 + Cargo.toml | 1 + timeboost/Cargo.toml | 1 + timeboost/src/binaries/sailfish.rs | 53 ++------ timeboost/src/binaries/timeboost.rs | 110 ++------------- timeboost/src/committee.rs | 201 ++++++++++++++++++++++++++++ timeboost/src/conf.rs | 112 ++-------------- timeboost/src/lib.rs | 140 +++++-------------- 8 files changed, 277 insertions(+), 342 deletions(-) create mode 100644 timeboost/src/committee.rs diff --git a/Cargo.lock b/Cargo.lock index 70631bcd..7460731c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11954,6 +11954,7 @@ dependencies = [ "committable", "futures", "http 1.3.1", + "itertools 0.14.0", "metrics", "multisig", "prost 0.14.1", diff --git a/Cargo.toml b/Cargo.toml index 410e249e..f03dc22d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,7 @@ ethereum_ssz = "0.9.0" futures = { version = "0.3", default-features = false, features = ["alloc"] } generic-array = { version = "0.14.7", features = ["serde", "zeroize"] } http = "1.3.1" +itertools = "0.14.0" jiff = { version = "0.2", default-features = false, features = ["serde", "std"] } minicbor = { version = "2.1.1", features = ["full"] } nohash-hasher = "0.2" diff --git a/timeboost/Cargo.toml b/timeboost/Cargo.toml index a7f273c3..53067324 100644 --- a/timeboost/Cargo.toml +++ b/timeboost/Cargo.toml @@ -28,6 +28,7 @@ cliquenet = { path = "../cliquenet" } committable = { workspace = true } futures = { workspace = true } http = { workspace = true } +itertools = { workspace = true } metrics = { path = "../metrics", features = ["prometheus"]} multisig = { path = "../multisig" } prost = { workspace = true } diff --git a/timeboost/src/binaries/sailfish.rs b/timeboost/src/binaries/sailfish.rs index 281cd2b4..4d7fe78f 100644 --- a/timeboost/src/binaries/sailfish.rs +++ b/timeboost/src/binaries/sailfish.rs @@ -1,20 +1,18 @@ use std::{iter::repeat_with, path::PathBuf, sync::Arc, time::Duration}; use ::metrics::prometheus::PrometheusMetrics; -use alloy::providers::{Provider, ProviderBuilder}; use anyhow::{Context, Result}; use cliquenet::{Network, NetworkMetrics, Overlay}; use committable::{Commitment, Committable, RawCommitmentBuilder}; -use multisig::{Committee, CommitteeId, Keypair, x25519}; +use multisig::{CommitteeId, Keypair, x25519}; use sailfish::{ Coordinator, consensus::{Consensus, ConsensusMetrics}, rbc::{Rbc, RbcConfig, RbcMetrics}, - types::{Action, HasTime, Timestamp, UNKNOWN_COMMITTEE_ID}, + types::{Action, HasTime, Timestamp}, }; use serde::{Deserialize, Serialize}; -use timeboost::config::NodeConfig; -use timeboost_contract::{CommitteeMemberSol, KeyManager}; +use timeboost::{committee::CommitteeInfo, config::NodeConfig}; use timeboost_utils::types::logging; use tokio::{select, signal, time::sleep}; use tracing::{error, info}; @@ -91,40 +89,20 @@ async fn main() -> Result<()> { let dh_keypair = x25519::Keypair::from(config.keys.dh.secret.clone()); // syncing with contract to get peers keys and network addresses - let provider = ProviderBuilder::new().connect_http(config.chain.parent.rpc_url); - assert_eq!( - provider.get_chain_id().await?, - config.chain.parent.id, - "Parent chain RPC has mismatched chain_id" - ); - - let contract = KeyManager::new(config.chain.parent.key_manager_contract, &provider); - let members: Vec = contract - .getCommitteeById(cli.committee_id.into()) - .call() - .await? - .members; + let comm_info = CommitteeInfo::fetch( + config.chain.parent.rpc_url, + config.chain.parent.key_manager_contract, + cli.committee_id.into(), + ) + .await?; info!(label = %config.keys.signing.public, committee_id = %cli.committee_id, "committee info synced"); - let peer_hosts_and_keys = members - .iter() - .map(|peer| { - let sig_key = multisig::PublicKey::try_from(peer.sigKey.as_ref()) - .expect("Failed to parse sigKey"); - let dh_key = - x25519::PublicKey::try_from(peer.dhKey.as_ref()).expect("Failed to parse dhKey"); - let sailfish_address = cliquenet::Address::try_from(peer.networkAddress.as_ref()) - .expect("Failed to parse networkAddress"); - (sig_key, dh_key, sailfish_address) - }) - .collect::>(); - let prom = Arc::new(PrometheusMetrics::default()); let sf_metrics = ConsensusMetrics::new(prom.as_ref()); let net_metrics = NetworkMetrics::new( "sailfish", prom.as_ref(), - peer_hosts_and_keys.iter().map(|(k, ..)| *k), + comm_info.signing_keys().iter().cloned(), ); let rbc_metrics = RbcMetrics::new(prom.as_ref()); let network = Network::create( @@ -132,19 +110,12 @@ async fn main() -> Result<()> { config.net.public.address.clone(), signing_keypair.public_key(), dh_keypair.clone(), - peer_hosts_and_keys.clone(), + comm_info.group(), net_metrics, ) .await?; - let committee = Committee::new( - UNKNOWN_COMMITTEE_ID, - peer_hosts_and_keys - .iter() - .map(|b| b.0) - .enumerate() - .map(|(i, key)| (i as u8, key)), - ); + let committee = comm_info.committee(); // If the stamp file exists we need to recover from a previous run. let recover = if cli.ignore_stamp { diff --git a/timeboost/src/binaries/timeboost.rs b/timeboost/src/binaries/timeboost.rs index 53117ca3..0b0109e6 100644 --- a/timeboost/src/binaries/timeboost.rs +++ b/timeboost/src/binaries/timeboost.rs @@ -1,15 +1,12 @@ use std::path::PathBuf; -use alloy::providers::{Provider, ProviderBuilder}; use anyhow::{Context, Result, bail}; -use cliquenet::AddressableCommittee; use multisig::CommitteeId; -use multisig::{Committee, Keypair, x25519}; +use multisig::{Keypair, x25519}; +use timeboost::committee::CommitteeInfo; use timeboost::{Timeboost, TimeboostConfig}; use timeboost_builder::robusta; -use timeboost_contract::{CommitteeMemberSol, KeyManager}; -use timeboost_crypto::prelude::DkgEncKey; -use timeboost_types::{KeyStore, ThresholdKeyCell}; +use timeboost_types::ThresholdKeyCell; use tokio::select; use tokio::signal; use tokio::task::spawn; @@ -73,99 +70,18 @@ async fn main() -> Result<()> { let dh_keypair = x25519::Keypair::from(node_config.keys.dh.secret.clone()); // syncing with contract to get peers keys and network addresses - let provider = ProviderBuilder::new().connect_http(node_config.chain.parent.rpc_url.clone()); - let chain_id = provider.get_chain_id().await?; - - assert_eq!( - chain_id, node_config.chain.parent.id, - "parent chain rpc has mismatched chain_id" - ); - - let contract = KeyManager::new(node_config.chain.parent.key_manager_contract, &provider); - - let members: Vec = contract - .getCommitteeById(cli.committee_id.into()) - .call() - .await? - .members; - + let comm_info = CommitteeInfo::fetch( + node_config.chain.parent.rpc_url.clone(), + node_config.chain.parent.key_manager_contract, + cli.committee_id.into(), + ) + .await?; info!(label = %sign_keypair.public_key(), committee_id = %cli.committee_id, "committee info synced"); - let peer_hosts_and_keys = members - .iter() - .map(|peer| -> Result<_> { - let sig_key = multisig::PublicKey::try_from(peer.sigKey.as_ref()) - .with_context(|| "Failed to parse sigKey bytes")?; - let dh_key = x25519::PublicKey::try_from(peer.dhKey.as_ref()) - .with_context(|| "Failed to parse dhKey bytes")?; - let dkg_enc_key = DkgEncKey::from_bytes(peer.dkgKey.as_ref()) - .with_context(|| "Failed to parse dkgKey bytes")?; - let sailfish_address = cliquenet::Address::try_from(peer.networkAddress.as_ref()) - .with_context(|| "Failed to parse networkAddress string")?; - Ok((sig_key, dh_key, dkg_enc_key, sailfish_address)) - }) - .collect::>>()?; - - let mut sailfish_peer_hosts_and_keys = Vec::new(); - let mut decrypt_peer_hosts_and_keys = Vec::new(); - let mut certifier_peer_hosts_and_keys = Vec::new(); - let mut dkg_enc_keys = Vec::new(); - - for (signing_key, dh_key, dkg_enc_key, sailfish_addr) in peer_hosts_and_keys.iter().cloned() { - sailfish_peer_hosts_and_keys.push((signing_key, dh_key, sailfish_addr.clone())); - decrypt_peer_hosts_and_keys.push(( - signing_key, - dh_key, - sailfish_addr.clone().with_offset(DECRYPTER_PORT_OFFSET), - )); - certifier_peer_hosts_and_keys.push(( - signing_key, - dh_key, - sailfish_addr.clone().with_offset(CERTIFIER_PORT_OFFSET), - )); - dkg_enc_keys.push(dkg_enc_key.clone()); - } - - let sailfish_committee = { - let c = Committee::new( - cli.committee_id, - sailfish_peer_hosts_and_keys - .iter() - .enumerate() - .map(|(i, (k, ..))| (i as u8, *k)), - ); - AddressableCommittee::new(c, sailfish_peer_hosts_and_keys.iter().cloned()) - }; - - let decrypt_committee = { - let c = Committee::new( - cli.committee_id, - decrypt_peer_hosts_and_keys - .iter() - .enumerate() - .map(|(i, (k, ..))| (i as u8, *k)), - ); - AddressableCommittee::new(c, decrypt_peer_hosts_and_keys.iter().cloned()) - }; - - let certifier_committee = { - let c = Committee::new( - cli.committee_id, - certifier_peer_hosts_and_keys - .iter() - .enumerate() - .map(|(i, (k, ..))| (i as u8, *k)), - ); - AddressableCommittee::new(c, certifier_peer_hosts_and_keys.iter().cloned()) - }; - - let key_store = KeyStore::new( - sailfish_committee.committee().clone(), - dkg_enc_keys - .into_iter() - .enumerate() - .map(|(i, k)| (i as u8, k)), - ); + let sailfish_committee = comm_info.sailfish_committee(); + let decrypt_committee = comm_info.decrypt_committee(); + let certifier_committee = comm_info.certifier_committee(); + let key_store = comm_info.dkg_key_store(); let is_recover = !cli.ignore_stamp && node_config.stamp.is_file(); diff --git a/timeboost/src/committee.rs b/timeboost/src/committee.rs new file mode 100644 index 00000000..927881e9 --- /dev/null +++ b/timeboost/src/committee.rs @@ -0,0 +1,201 @@ +//! Syncing committee info from the KeyManager contract + +use std::pin::Pin; +use std::task::{Context, Poll}; + +use alloy::{ + eips::BlockNumberOrTag, + primitives::Address, + providers::{Provider, ProviderBuilder}, + rpc::types::Filter, + sol_types::SolEvent, + transports::ws::WsConnect, +}; +use anyhow::{Context as AnyhowContext, Result}; +use cliquenet::AddressableCommittee; +use futures::{Stream, StreamExt}; +use itertools::{Itertools, izip}; +use multisig::{Committee, CommitteeId, x25519}; +use timeboost_config::{CERTIFIER_PORT_OFFSET, DECRYPTER_PORT_OFFSET, ParentChain}; +use timeboost_contract::KeyManager::{self, CommitteeCreated}; +use timeboost_crypto::prelude::DkgEncKey; +use timeboost_types::{KeyStore, Timestamp}; +use tracing::error; +use url::Url; + +/// The committee info stored on the KeyManager contract, a subset of [`CommitteeConfig`] +/// Keys and hosts are ordered in the same as they were registered (with KeyId from 0..n) +#[derive(Debug, Clone)] +pub struct CommitteeInfo { + id: CommitteeId, + timestamp: Timestamp, + signing_keys: Vec, + dh_keys: Vec, + dkg_keys: Vec, + public_addresses: Vec, +} + +impl CommitteeInfo { + /// Fetch the committee info for `committee_id` from `key_manager_addr` on chain + pub async fn fetch(rpc: Url, key_manager_addr: Address, committee_id: u64) -> Result { + let provider = ProviderBuilder::new().connect_http(rpc); + + let contract = KeyManager::new(key_manager_addr, &provider); + let c = contract.getCommitteeById(committee_id).call().await?; + + let (signing_keys, dh_keys, dkg_keys, public_addresses) = c + .members + .iter() + .map(|m| { + let sig_key = multisig::PublicKey::try_from(m.sigKey.as_ref()) + .with_context(|| "Failed to parse sigKey bytes")?; + let dh_key = x25519::PublicKey::try_from(m.dhKey.as_ref()) + .with_context(|| "Failed to parse dhKey bytes")?; + let dkg_key = DkgEncKey::from_bytes(m.dkgKey.as_ref()) + .with_context(|| "Failed to parse dkgKey bytes")?; + let addr = cliquenet::Address::try_from(m.networkAddress.as_ref()) + .with_context(|| "Failed to parse networkAddress string")?; + Ok((sig_key, dh_key, dkg_key, addr)) + }) + .collect::>>()? + .into_iter() + .multiunzip(); + + Ok(Self { + id: committee_id.into(), + timestamp: c.effectiveTimestamp.into(), + signing_keys, + dh_keys, + dkg_keys, + public_addresses, + }) + } + + pub fn effective_timestamp(&self) -> Timestamp { + self.timestamp + } + + pub fn signing_keys(&self) -> &[multisig::PublicKey] { + &self.signing_keys + } + + pub fn committee(&self) -> Committee { + Committee::new( + self.id, + self.signing_keys + .iter() + .enumerate() + .map(|(i, k)| (i as u8, *k)), + ) + } + + pub fn group( + &self, + ) -> impl Iterator { + izip!( + self.signing_keys.iter().cloned(), + self.dh_keys.iter().cloned(), + self.public_addresses.iter().cloned() + ) + } + + pub fn sailfish_committee(&self) -> AddressableCommittee { + AddressableCommittee::new(self.committee(), self.group()) + } + + pub fn decrypt_committee(&self) -> AddressableCommittee { + AddressableCommittee::new( + self.committee(), + izip!( + self.signing_keys.iter().cloned(), + self.dh_keys.iter().cloned(), + self.public_addresses + .iter() + .map(|a| a.clone().with_offset(DECRYPTER_PORT_OFFSET)), + ), + ) + } + + pub fn certifier_committee(&self) -> AddressableCommittee { + AddressableCommittee::new( + self.committee(), + izip!( + self.signing_keys.iter().cloned(), + self.dh_keys.iter().cloned(), + self.public_addresses + .iter() + .map(|a| a.clone().with_offset(CERTIFIER_PORT_OFFSET)), + ), + ) + } + + pub fn dkg_key_store(&self) -> KeyStore { + KeyStore::new( + self.committee(), + self.dkg_keys + .iter() + .enumerate() + .map(|(i, k)| (i as u8, k.clone())), + ) + } +} + +/// An pubsub-provider-holding event stream. (the pubsub will close on drop) +pub struct NewCommitteeStream { + _provider: Box, + inner: Pin + Send>>, +} + +impl Stream for NewCommitteeStream { + type Item = u64; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.inner.as_mut().poll_next(cx) + } +} + +impl NewCommitteeStream { + pub async fn create(config: &ParentChain) -> Result { + // setup the websocket for contract event stream + let ws = WsConnect::new(config.ws_url.clone()); + // spawn the pubsub service (and backend) and the frontend is registered at the provider + let provider = ProviderBuilder::new() + .connect_pubsub_with(ws) + .await + .map_err(|err| { + error!(?err, "event pubsub failed to start"); + err + })?; + + let chain_id = config.id; + let tag = if chain_id == 31337 || chain_id == 1337 { + // local test chain, we start scanning from the genesis + BlockNumberOrTag::Number(0) + } else { + config.block_tag + }; + + let filter = Filter::new() + .address(config.key_manager_contract) + .event(KeyManager::CommitteeCreated::SIGNATURE) + .from_block(tag); + let events = provider + .subscribe_logs(&filter) + .await + .map_err(|err| { + error!(?err, "pubsub subscription failed"); + err + })? + .into_stream(); + + let validated = events.filter_map(|log| async move { + log.log_decode_validate::() + .ok() + .map(|v| v.data().id) + }); + Ok(Self { + _provider: Box::new(provider), + inner: Box::pin(validated), + }) + } +} diff --git a/timeboost/src/conf.rs b/timeboost/src/conf.rs index 7f6144d9..80fb3629 100644 --- a/timeboost/src/conf.rs +++ b/timeboost/src/conf.rs @@ -1,12 +1,9 @@ -use alloy::providers::ProviderBuilder; -use anyhow::{Context, Result}; use bon::Builder; use cliquenet::{Address, AddressableCommittee}; -use multisig::{Committee, Keypair, x25519}; +use multisig::{Keypair, x25519}; use timeboost_builder::{CertifierConfig, SubmitterConfig, robusta}; -use timeboost_config::{ChainConfig, DECRYPTER_PORT_OFFSET}; -use timeboost_contract::{CommitteeMemberSol, KeyManager}; -use timeboost_crypto::prelude::{DkgDecKey, DkgEncKey}; +use timeboost_config::ChainConfig; +use timeboost_crypto::prelude::DkgDecKey; use timeboost_sequencer::SequencerConfig; use timeboost_types::{KeyStore, ThresholdKeyCell}; @@ -15,9 +12,15 @@ pub struct TimeboostConfig { /// The sailfish peers that this node will connect to. pub(crate) sailfish_committee: AddressableCommittee, + /// Previous sailfish peers + pub(crate) prev_sailfish_committee: Option, + /// The decrypt peers that this node will connect to. pub(crate) decrypt_committee: AddressableCommittee, + /// Previous decrypt peers + pub(crate) prev_decrypt_committee: Option<(AddressableCommittee, KeyStore)>, + /// The block certifier peers that this node will connect to. pub(crate) certifier_committee: AddressableCommittee, @@ -67,95 +70,8 @@ pub struct TimeboostConfig { } impl TimeboostConfig { - pub async fn sequencer_config(&self) -> Result { - let cur_cid: u64 = self.sailfish_committee.committee().id().into(); - - let (prev_sailfish, prev_decrypt) = if cur_cid == 0u64 { - (None, None) - } else { - // syncing with contract to get peer info about the previous committee - // largely adapted from binaries/timeboost.rs - // TODO: (alex) extrapolate the remaining logic into a common helper - let prev_cid = cur_cid - 1; - - let provider = - ProviderBuilder::new().connect_http(self.chain_config.parent.rpc_url.clone()); - let contract = - KeyManager::new(self.chain_config.parent.key_manager_contract, &provider); - let members: Vec = - contract.getCommitteeById(prev_cid).call().await?.members; - - tracing::info!(label = %self.sign_keypair.public_key(), committee_id = %prev_cid, "prev committee info synced"); - - let peer_hosts_and_keys = members - .iter() - .map(|peer| -> Result<_> { - let sig_key = multisig::PublicKey::try_from(peer.sigKey.as_ref()) - .with_context(|| "Failed to parse sigKey bytes")?; - let dh_key = x25519::PublicKey::try_from(peer.dhKey.as_ref()) - .with_context(|| "Failed to parse dhKey bytes")?; - let dkg_enc_key = DkgEncKey::from_bytes(peer.dkgKey.as_ref()) - .with_context(|| "Failed to parse dkgKey bytes")?; - let sailfish_address = - cliquenet::Address::try_from(peer.networkAddress.as_ref()) - .with_context(|| "Failed to parse networkAddress string")?; - Ok((sig_key, dh_key, dkg_enc_key, sailfish_address)) - }) - .collect::>>()?; - - let mut sailfish_peer_hosts_and_keys = Vec::new(); - let mut decrypt_peer_hosts_and_keys = Vec::new(); - let mut dkg_enc_keys = Vec::new(); - - for (signing_key, dh_key, dkg_enc_key, sailfish_addr) in - peer_hosts_and_keys.iter().cloned() - { - sailfish_peer_hosts_and_keys.push((signing_key, dh_key, sailfish_addr.clone())); - decrypt_peer_hosts_and_keys.push(( - signing_key, - dh_key, - sailfish_addr.clone().with_offset(DECRYPTER_PORT_OFFSET), - )); - dkg_enc_keys.push(dkg_enc_key.clone()); - } - - let sailfish_committee = { - let c = Committee::new( - prev_cid, - sailfish_peer_hosts_and_keys - .iter() - .enumerate() - .map(|(i, (k, ..))| (i as u8, *k)), - ); - AddressableCommittee::new(c, sailfish_peer_hosts_and_keys.iter().cloned()) - }; - - let decrypt_committee = { - let c = Committee::new( - prev_cid, - decrypt_peer_hosts_and_keys - .iter() - .enumerate() - .map(|(i, (k, ..))| (i as u8, *k)), - ); - AddressableCommittee::new(c, decrypt_peer_hosts_and_keys.iter().cloned()) - }; - - let key_store = KeyStore::new( - sailfish_committee.committee().clone(), - dkg_enc_keys - .into_iter() - .enumerate() - .map(|(i, k)| (i as u8, k)), - ); - - ( - Some(sailfish_committee), - Some((decrypt_committee, key_store)), - ) - }; - - Ok(SequencerConfig::builder() + pub fn sequencer_config(&self) -> SequencerConfig { + SequencerConfig::builder() .sign_keypair(self.sign_keypair.clone()) .dh_keypair(self.dh_keypair.clone()) .dkg_key(self.dkg_key.clone()) @@ -164,12 +80,12 @@ impl TimeboostConfig { .sailfish_committee(self.sailfish_committee.clone()) .decrypt_committee((self.decrypt_committee.clone(), self.key_store.clone())) .recover(self.recover) - .maybe_previous_sailfish_committee(prev_sailfish) - .maybe_previous_decrypt_committee(prev_decrypt) + .maybe_previous_sailfish_committee(self.prev_sailfish_committee.clone()) + .maybe_previous_decrypt_committee(self.prev_decrypt_committee.clone()) .leash_len(self.leash_len) .threshold_dec_key(self.threshold_dec_key.clone()) .chain_config(self.chain_config.clone()) - .build()) + .build() } pub fn certifier_config(&self) -> CertifierConfig { diff --git a/timeboost/src/lib.rs b/timeboost/src/lib.rs index 0ca102eb..1b64ebfb 100644 --- a/timeboost/src/lib.rs +++ b/timeboost/src/lib.rs @@ -4,26 +4,17 @@ use std::iter::once; use std::sync::Arc; use ::metrics::prometheus::PrometheusMetrics; -use alloy::eips::BlockNumberOrTag; -use alloy::providers::{Provider, ProviderBuilder}; -use alloy::rpc::types::Filter; -use alloy::sol_types::SolEvent; -use alloy::transports::ws::WsConnect; use anyhow::{Result, anyhow}; -use cliquenet::AddressableCommittee; +use committee::NewCommitteeStream; use futures::StreamExt; use metrics::TimeboostMetrics; -use multisig::{Committee, PublicKey, x25519}; -use sailfish::types::Timestamp; +use multisig::PublicKey; use timeboost_builder::{Certifier, CertifierDown, SenderTaskDown, Submitter}; -use timeboost_contract::CommitteeMemberSol; -use timeboost_contract::{KeyManager, KeyManager::CommitteeCreated}; -use timeboost_crypto::prelude::DkgEncKey; use timeboost_sequencer::{Output, Sequencer}; -use timeboost_types::{BundleVariant, ConsensusTime, KeyStore}; +use timeboost_types::{BundleVariant, ConsensusTime}; use tokio::select; use tokio::sync::mpsc::{self, Receiver, Sender}; -use tracing::{error, info, warn}; +use tracing::{info, warn}; pub use conf::{TimeboostConfig, TimeboostConfigBuilder}; pub use timeboost_builder as builder; @@ -35,9 +26,11 @@ pub use timeboost_types as types; use crate::api::ApiServer; use crate::api::internal::GrpcServer; +use crate::committee::CommitteeInfo; use crate::forwarder::nitro_forwarder::NitroForwarder; pub mod api; +pub mod committee; pub mod forwarder; pub mod metrics; @@ -55,10 +48,22 @@ pub struct Timeboost { } impl Timeboost { - pub async fn new(cfg: TimeboostConfig) -> Result { + pub async fn new(mut cfg: TimeboostConfig) -> Result { let pro = Arc::new(PrometheusMetrics::default()); let met = Arc::new(TimeboostMetrics::new(&*pro)); - let seq = Sequencer::new(cfg.sequencer_config().await?, &*pro).await?; + + // optionally fetch previous committee info + let cid: u64 = cfg.sailfish_committee.committee().id().into(); + if cid > 0u64 { + let c = &cfg.chain_config.parent; + let prev_comm = + CommitteeInfo::fetch(c.rpc_url.clone(), c.key_manager_contract, cid - 1).await?; + + cfg.prev_sailfish_committee = Some(prev_comm.sailfish_committee()); + cfg.prev_decrypt_committee = + Some((prev_comm.decrypt_committee(), prev_comm.dkg_key_store())); + } + let seq = Sequencer::new(cfg.sequencer_config(), &*pro).await?; let blk = Certifier::new(cfg.certifier_config(), &*pro).await?; let sub = Submitter::new(cfg.submitter_config(), &*pro); @@ -98,39 +103,7 @@ impl Timeboost { } pub async fn go(mut self) -> Result<()> { - // setup the websocket for contract event stream - let ws = WsConnect::new(self.config.chain_config.parent.ws_url.clone()); - // spawn the pubsub service (and backend) and the frontend is registered at the provider - let provider = ProviderBuilder::new() - .connect_pubsub_with(ws) - .await - .map_err(|err| { - error!(?err, "event pubsub failed to start"); - err - })?; - - let chain_id = provider.get_chain_id().await.map_err(|err| { - error!(?err, "fail to get chainid"); - err - })?; - let tag = if chain_id == 31337 || chain_id == 1337 { - // local test chain, we start scanning from the genesis - BlockNumberOrTag::Number(0) - } else { - BlockNumberOrTag::Finalized - }; - let filter = Filter::new() - .address(self.config.chain_config.parent.key_manager_contract) - .event(KeyManager::CommitteeCreated::SIGNATURE) - .from_block(tag); - let mut events = provider - .subscribe_logs(&filter) - .await - .map_err(|err| { - error!(?err, "pubsub subscription failed"); - err - })? - .into_stream(); + let mut events = NewCommitteeStream::create(&self.config.chain_config.parent).await?; loop { select! { @@ -177,15 +150,23 @@ impl Timeboost { } }, res = events.next() => match res { - Some(log) => { - let typed_log = log.log_decode_validate::()?; - let id = typed_log.data().id; + Some(id) => { let cur: u64 = self.config.key_store.committee().id().into(); if id == cur + 1 { + info!(node = %self.label, committee_id = %id, current = %cur, "fetching next committee"); + let comm_info = CommitteeInfo::fetch( + self.config.chain_config.parent.rpc_url.clone(), + self.config.chain_config.parent.key_manager_contract, + id, + ).await?; + info!(node = %self.label, committee_id = %id, current = %cur, "setting next committee"); - let (t, a, k) = self.fetch_next_committee(&provider, id).await?; - self.sequencer.set_next_committee(t, a, k).await?; + self.sequencer.set_next_committee( + ConsensusTime(comm_info.effective_timestamp()), + comm_info.sailfish_committee(), + comm_info.dkg_key_store() + ).await?; } else { warn!(node = %self.label, committee_id = %id, current = %cur, "ignored new CommitteeCreated event"); continue; @@ -199,57 +180,4 @@ impl Timeboost { } } } - - /// Given the next committee is available on chain, fetch it and prepare it for `NextCommittee` - async fn fetch_next_committee( - &self, - provider: impl Provider, - next_committee_id: u64, - ) -> Result<(ConsensusTime, AddressableCommittee, KeyStore)> { - let contract = KeyManager::new( - self.config.chain_config.parent.key_manager_contract, - &provider, - ); - let c = contract.getCommitteeById(next_committee_id).call().await?; - let members: Vec = c.members; - let timestamp: Timestamp = c.effectiveTimestamp.into(); - - let sailfish_peer_hosts_and_keys = members - .iter() - .map(|peer| { - let sig_key = multisig::PublicKey::try_from(peer.sigKey.as_ref())?; - let dh_key = x25519::PublicKey::try_from(peer.dhKey.as_ref())?; - let sailfish_address = cliquenet::Address::try_from(peer.networkAddress.as_ref())?; - Ok((sig_key, dh_key, sailfish_address)) - }) - .collect::>>()?; - let dkg_enc_keys = members - .iter() - .map(|peer| { - let dkg_enc_key = DkgEncKey::from_bytes(peer.dkgKey.as_ref())?; - Ok(dkg_enc_key) - }) - .collect::>>()?; - - let sailfish_committee = { - let c = Committee::new( - next_committee_id, - sailfish_peer_hosts_and_keys - .iter() - .enumerate() - .map(|(i, (k, ..))| (i as u8, *k)), - ); - AddressableCommittee::new(c, sailfish_peer_hosts_and_keys.iter().cloned()) - }; - - let key_store = KeyStore::new( - sailfish_committee.committee().clone(), - dkg_enc_keys - .into_iter() - .enumerate() - .map(|(i, k)| (i as u8, k)), - ); - - Ok((ConsensusTime(timestamp), sailfish_committee, key_store)) - } } From 76c22774ecca121fba9e9da3d54b87c8238920b5 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Thu, 18 Sep 2025 22:36:33 +0800 Subject: [PATCH 20/25] mkconfig accept +20s, run.rs accepts single quoted multi-words --- Cargo.lock | 7 ---- justfile | 11 ++--- test-utils/Cargo.toml | 1 - test-utils/src/binaries/run.rs | 45 +++++++++++++-------- timeboost-config/src/binaries/mkconfig.rs | 23 ++++++++++- timeboost-contract/src/binaries/register.rs | 3 +- 6 files changed, 54 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7460731c..522d946b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10812,12 +10812,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-words" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" - [[package]] name = "shellexpand" version = "3.1.1" @@ -11680,7 +11674,6 @@ dependencies = [ "robusta", "rustix 1.1.2", "sailfish", - "shell-words", "timeboost", "timeboost-utils", "tokio", diff --git a/justfile b/justfile index 985a1f7d..fed06842 100644 --- a/justfile +++ b/justfile @@ -210,19 +210,19 @@ test-dyn-comm: build_release_until build-test-utils --parent-chain-id 31337 \ --parent-ibox-contract 0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1 \ --key-manager-contract 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ - --timestamp `just now-plus-20s` \ + --timestamp +18s \ --stamp-dir /tmp \ --output test-configs/c1" \ --run "6:sleep 8" \ --run "7:target/release/register \ -a threshold-enc-key \ - -m attend__SPACE__year__SPACE__erase__SPACE__basket__SPACE__blind__SPACE__adapt__SPACE__stove__SPACE__broccoli__SPACE__isolate__SPACE__unveil__SPACE__acquire__SPACE__category \ + -m 'attend year erase basket blind adapt stove broccoli isolate unveil acquire category' \ -u http://localhost:8545 \ -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ -c test-configs/c0/committee.toml" \ --run "8:target/release/register \ -a new-committee \ - -m attend__SPACE__year__SPACE__erase__SPACE__basket__SPACE__blind__SPACE__adapt__SPACE__stove__SPACE__broccoli__SPACE__isolate__SPACE__unveil__SPACE__acquire__SPACE__category \ + -m 'attend year erase basket blind adapt stove broccoli isolate unveil acquire category' \ -u http://localhost:8545 \ -k 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ -c test-configs/c1/committee.toml" \ @@ -235,8 +235,3 @@ test-dyn-comm: build_release_until build-test-utils --committee-id 1 \ --until 800 \ --required-decrypt-rounds 3 && rm -rf test-configs/c1 - -# portable calculation of now() + 20s in "%Y-%m-%dT%H:%M:%SZ" format -[private] -now-plus-20s: - @python3 -c 'from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc)+timedelta(seconds=20)).strftime("%Y-%m-%dT%H:%M:%SZ"))' diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index dc3b0e9d..b76236d0 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -28,7 +28,6 @@ anyhow = { workspace = true } clap = { workspace = true } futures = { workspace = true } rustix = { workspace = true } -shell-words = "1.1" tokio = { workspace = true } tracing = { workspace = true } # optional diff --git a/test-utils/src/binaries/run.rs b/test-utils/src/binaries/run.rs index 82b4b405..035f022b 100644 --- a/test-utils/src/binaries/run.rs +++ b/test-utils/src/binaries/run.rs @@ -1,7 +1,7 @@ use std::future::pending; use std::ops::{Deref, DerefMut}; +use std::process::ExitStatus; use std::time::Duration; -use std::{ffi::OsStr, process::ExitStatus}; use anyhow::{Result, anyhow, bail}; use clap::Parser; @@ -51,16 +51,15 @@ async fn main() -> Result<()> { for commandline in commands { if args.verbose { - let joined = commandline.args.join(" "); if commandline.sync { - eprintln!("running command: {joined}"); + eprintln!("running command: {}", commandline.args) } else { - eprintln!("spawning command: {joined}"); + eprintln!("spawning command: {}", commandline.args) } } - let mut pg = ProcessGroup::spawn(&commandline.args)?; if commandline.sync { + let mut pg = ProcessGroup::spawn(commandline.args.split_whitespace())?; let status = select! { s = pg.wait() => s?, _ = term.recv() => return Ok(()), @@ -70,8 +69,8 @@ async fn main() -> Result<()> { bail!("{:?} failed with {:?}", commandline.args, status.code()); } } else { + let mut pg = ProcessGroup::spawn(commandline.args.split_whitespace())?; helpers.spawn(async move { - let mut pg = ProcessGroup::spawn(&commandline.args)?; let status = pg.wait().await?; Ok(status) }); @@ -112,20 +111,15 @@ async fn main() -> Result<()> { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] struct Commandline { prio: u8, - args: Vec, + args: String, sync: bool, } fn parse_command_line(s: &str) -> Result { let (p, a) = s.split_once(':').unwrap_or(("0", s)); - // Replace __SPACE__ with actual spaces, then split like shell would - let parts = shell_words::split(a)? - .into_iter() - .map(|arg| arg.replace("__SPACE__", " ")) - .collect(); Ok(Commandline { prio: p.parse()?, - args: parts, + args: a.to_string(), sync: true, }) } @@ -137,14 +131,33 @@ impl ProcessGroup { fn spawn(it: I) -> Result where I: IntoIterator, - S: AsRef, + S: Into + AsRef, { let mut args = it.into_iter(); let exe = args .next() .ok_or_else(|| anyhow!("invalid command-line args"))?; - let mut cmd = Command::new(exe); - cmd.args(args); + let mut cmd = Command::new(exe.as_ref()); + let mut buf: Option> = None; + for a in args { + if let Some(b) = &mut buf { + let mut a = a.into(); + if a.ends_with("'") { + a.pop(); + b.push(a); + cmd.arg(b.join(" ")); + buf = None + } else { + b.push(a); + } + } else if a.as_ref().starts_with("'") { + let mut a = a.into(); + a.remove(0); + buf = Some(vec![a]); + } else { + cmd.arg(a.as_ref()); + } + } cmd.process_group(0); let child = cmd.spawn()?; let id = child.id().ok_or_else(|| anyhow!("child already exited"))?; diff --git a/timeboost-config/src/binaries/mkconfig.rs b/timeboost-config/src/binaries/mkconfig.rs index 56143e14..78f5a3de 100644 --- a/timeboost-config/src/binaries/mkconfig.rs +++ b/timeboost-config/src/binaries/mkconfig.rs @@ -3,12 +3,14 @@ use std::io::Write; use std::net::IpAddr; use std::num::NonZeroU8; use std::path::PathBuf; +use std::str::FromStr; use alloy::eips::BlockNumberOrTag; use anyhow::{Result, bail}; use ark_std::rand::SeedableRng as _; use clap::{Parser, ValueEnum}; use cliquenet::Address; +use jiff::{SignedDuration, Timestamp}; use multisig::x25519; use secp256k1::rand::SeedableRng as _; use timeboost_config::{ChainConfig, ParentChain}; @@ -38,7 +40,7 @@ struct Args { /// The timestamp format corresponds to RFC 3339, section 5.6, for example: /// 1970-01-01T18:00:00Z. #[clap(long)] - timestamp: jiff::Timestamp, + timestamp: TimestampOrOffset, /// The sailfish network address. Decrypter, certifier, and internal address are derived: /// sharing the same IP as the sailfish IP, and a different (but fixed) port number. @@ -108,6 +110,23 @@ struct Args { output: PathBuf, } +#[derive(Debug, Clone)] +struct TimestampOrOffset(Timestamp); + +impl FromStr for TimestampOrOffset { + type Err = String; + + fn from_str(s: &str) -> Result { + if let Ok(ts) = Timestamp::from_str(s) { + Ok(Self(ts)) + } else if let Ok(dur) = s.parse::() { + Ok(Self(Timestamp::now() + dur)) + } else { + Err("Expected RFC3339 timestamp or [+/-]duration".into()) + } + } +} + /// How should addresses be updated? #[derive(Clone, Copy, Debug, Default, ValueEnum)] enum Mode { @@ -216,7 +235,7 @@ impl Args { } let committee_config = CommitteeConfig { - effective_timestamp: self.timestamp, + effective_timestamp: self.timestamp.0, members, }; committee_config_file.write_all(toml::to_string_pretty(&committee_config)?.as_bytes())?; diff --git a/timeboost-contract/src/binaries/register.rs b/timeboost-contract/src/binaries/register.rs index b56e07b9..a52649c2 100644 --- a/timeboost-contract/src/binaries/register.rs +++ b/timeboost-contract/src/binaries/register.rs @@ -42,10 +42,9 @@ struct Args { } /// Specific register action -#[derive(Clone, Copy, Debug, Default, ValueEnum)] +#[derive(Clone, Copy, Debug, ValueEnum)] enum Action { /// register the next committee - #[default] NewCommittee, /// register the threshold encryption key (when ready) ThresholdEncKey, From 4547bcb4ccff4b96f19c8d09c52bce3148fac194 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Fri, 19 Sep 2025 14:35:54 +0800 Subject: [PATCH 21/25] Stream and separating pubsub backend handle --- timeboost-contract/src/provider.rs | 113 +++++++++++++++++++++++++++- timeboost/src/binaries/timeboost.rs | 12 +++ timeboost/src/committee.rs | 107 ++++++++++++-------------- timeboost/src/conf.rs | 19 +++-- timeboost/src/lib.rs | 50 ++++++------ 5 files changed, 207 insertions(+), 94 deletions(-) diff --git a/timeboost-contract/src/provider.rs b/timeboost-contract/src/provider.rs index efb4ac4c..ba4e301f 100644 --- a/timeboost-contract/src/provider.rs +++ b/timeboost-contract/src/provider.rs @@ -1,13 +1,21 @@ //! Helper functions to build Ethereum [providers](https://docs.rs/alloy/latest/alloy/providers/trait.Provider.html) //! Partial Credit: +use std::{ops::Deref, pin::Pin}; + use alloy::{ + eips::BlockNumberOrTag, network::EthereumWallet, - providers::ProviderBuilder, + primitives::Address, + providers::{Provider, ProviderBuilder}, + rpc::types::{Filter, Log}, signers::local::{LocalSignerError, MnemonicBuilder, PrivateKeySigner, coins_bip39::English}, - transports::http::reqwest::Url, + sol_types::SolEvent, + transports::{http::reqwest::Url, ws::WsConnect}, }; -use timeboost_types::HttpProviderWithWallet; +use futures::{Stream, StreamExt}; +use timeboost_types::{HttpProvider, HttpProviderWithWallet}; +use tracing::error; /// Build a local signer from wallet mnemonic and account index pub fn build_signer( @@ -31,3 +39,102 @@ pub fn build_provider( let wallet = EthereumWallet::from(signer); Ok(ProviderBuilder::new().wallet(wallet).connect_http(url)) } + +/// A PubSub service (with backend handle), disconnect on drop. +#[derive(Clone)] +pub struct PubSubProvider(HttpProvider); + +impl Deref for PubSubProvider { + type Target = HttpProvider; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PubSubProvider { + pub async fn new(ws_url: Url) -> anyhow::Result { + let ws = WsConnect::new(ws_url); + let provider = ProviderBuilder::new() + .connect_pubsub_with(ws) + .await + .map_err(|err| { + error!(?err, "event pubsub failed to start"); + err + })?; + Ok(Self(provider)) + } + + pub fn inner(&self) -> &HttpProvider { + &self.0 + } + + /// create an event stream of event type `E`, subscribing since `from_block` on `contract` + pub async fn event_stream( + &self, + contract: Address, + from_block: BlockNumberOrTag, + ) -> anyhow::Result>>>> { + let filter = Filter::new() + .address(contract) + .event(E::SIGNATURE) + .from_block(from_block); + + let events = self + .subscribe_logs(&filter) + .await + .map_err(|err| { + error!(?err, "pubsub subscription failed"); + err + })? + .into_stream(); + + let validated = events.filter_map(|log| async move { + let Ok(event) = log.log_decode_validate::() else { + error!("fail to parse CommitteeCreated event log"); + return None; + }; + Some(event) + }); + + Ok(Box::pin(validated)) + } + + /// Returns the smallest block number whose timestamp is >= `target_ts` through binary search. + /// Useful to determine `from_block` input of `Self::event_strea()` subscription. + pub async fn get_block_number_by_timestamp( + &self, + target_ts: u64, + ) -> anyhow::Result> { + let latest = self.get_block_number().await?; + let mut lo: u64 = 0; + let mut hi: u64 = latest; + + while lo <= hi { + let mid = lo + (hi - lo) / 2; + + let block = match self + .0 + .get_block_by_number(BlockNumberOrTag::Number(mid)) + .await? + { + Some(b) => b, + None => { + lo = mid + 1; + continue; + } + }; + + if block.header.timestamp >= target_ts { + if mid == 0 { + return Ok(Some(0)); + } + hi = mid - 1; + } else { + lo = mid + 1; + } + } + + // At this point, `lo` is the smallest index with ts >= target_ts (if any) + if lo > latest { Ok(None) } else { Ok(Some(lo)) } + } +} diff --git a/timeboost/src/binaries/timeboost.rs b/timeboost/src/binaries/timeboost.rs index 0b0109e6..6dd23368 100644 --- a/timeboost/src/binaries/timeboost.rs +++ b/timeboost/src/binaries/timeboost.rs @@ -94,8 +94,20 @@ async fn main() -> Result<()> { let pubkey = sign_keypair.public_key(); + // optionally fetch previous committee info + let cid: u64 = cli.committee_id.into(); + let prev_comm = if cid > 0u64 { + let c = &node_config.chain.parent; + let prev_comm = + CommitteeInfo::fetch(c.rpc_url.clone(), c.key_manager_contract, cid - 1).await?; + Some(prev_comm) + } else { + None + }; + let config = TimeboostConfig::builder() .sailfish_committee(sailfish_committee) + .maybe_prev_committee(prev_comm) .decrypt_committee(decrypt_committee) .certifier_committee(certifier_committee) .sign_keypair(sign_keypair) diff --git a/timeboost/src/committee.rs b/timeboost/src/committee.rs index 927881e9..0a06008d 100644 --- a/timeboost/src/committee.rs +++ b/timeboost/src/committee.rs @@ -1,15 +1,11 @@ //! Syncing committee info from the KeyManager contract use std::pin::Pin; -use std::task::{Context, Poll}; use alloy::{ eips::BlockNumberOrTag, primitives::Address, providers::{Provider, ProviderBuilder}, - rpc::types::Filter, - sol_types::SolEvent, - transports::ws::WsConnect, }; use anyhow::{Context as AnyhowContext, Result}; use cliquenet::AddressableCommittee; @@ -18,11 +14,15 @@ use itertools::{Itertools, izip}; use multisig::{Committee, CommitteeId, x25519}; use timeboost_config::{CERTIFIER_PORT_OFFSET, DECRYPTER_PORT_OFFSET, ParentChain}; use timeboost_contract::KeyManager::{self, CommitteeCreated}; +use timeboost_contract::provider::PubSubProvider; use timeboost_crypto::prelude::DkgEncKey; use timeboost_types::{KeyStore, Timestamp}; use tracing::error; use url::Url; +/// Type alias for the committee stream +pub type NewCommitteeStream = Pin>>; + /// The committee info stored on the KeyManager contract, a subset of [`CommitteeConfig`] /// Keys and hosts are ordered in the same as they were registered (with KeyId from 0..n) #[derive(Debug, Clone)] @@ -39,7 +39,14 @@ impl CommitteeInfo { /// Fetch the committee info for `committee_id` from `key_manager_addr` on chain pub async fn fetch(rpc: Url, key_manager_addr: Address, committee_id: u64) -> Result { let provider = ProviderBuilder::new().connect_http(rpc); + Self::fetch_with(provider, key_manager_addr, committee_id).await + } + pub(crate) async fn fetch_with( + provider: impl Provider, + key_manager_addr: Address, + committee_id: u64, + ) -> Result { let contract = KeyManager::new(key_manager_addr, &provider); let c = contract.getCommitteeById(committee_id).call().await?; @@ -71,6 +78,10 @@ impl CommitteeInfo { }) } + pub fn id(&self) -> CommitteeId { + self.id + } + pub fn effective_timestamp(&self) -> Timestamp { self.timestamp } @@ -138,64 +149,44 @@ impl CommitteeInfo { .map(|(i, k)| (i as u8, k.clone())), ) } -} -/// An pubsub-provider-holding event stream. (the pubsub will close on drop) -pub struct NewCommitteeStream { - _provider: Box, - inner: Pin + Send>>, -} - -impl Stream for NewCommitteeStream { - type Item = u64; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.inner.as_mut().poll_next(cx) - } -} - -impl NewCommitteeStream { - pub async fn create(config: &ParentChain) -> Result { - // setup the websocket for contract event stream - let ws = WsConnect::new(config.ws_url.clone()); - // spawn the pubsub service (and backend) and the frontend is registered at the provider - let provider = ProviderBuilder::new() - .connect_pubsub_with(ws) + /// subscribe an event stream + pub async fn new_committee_stream( + provider: &PubSubProvider, + start_ts: Timestamp, + config: &ParentChain, + ) -> Result { + let from_block = provider + .get_block_number_by_timestamp(start_ts.into()) + .await? + .unwrap_or_default(); + let events = provider + .event_stream::( + config.key_manager_contract, + BlockNumberOrTag::Number(from_block), + ) .await - .map_err(|err| { - error!(?err, "event pubsub failed to start"); - err + .map_err(|e| { + error!("Failed to create CommitteeCreated stream: {:?}", e); + e })?; - let chain_id = config.id; - let tag = if chain_id == 31337 || chain_id == 1337 { - // local test chain, we start scanning from the genesis - BlockNumberOrTag::Number(0) - } else { - config.block_tag - }; - - let filter = Filter::new() - .address(config.key_manager_contract) - .event(KeyManager::CommitteeCreated::SIGNATURE) - .from_block(tag); - let events = provider - .subscribe_logs(&filter) - .await - .map_err(|err| { - error!(?err, "pubsub subscription failed"); - err - })? - .into_stream(); - - let validated = events.filter_map(|log| async move { - log.log_decode_validate::() - .ok() - .map(|v| v.data().id) + let provider = provider.clone(); + let key_manager_contract = config.key_manager_contract; + let s = events.filter_map(move |log| { + let provider = provider.clone(); + async move { + let id = log.data().id; + match CommitteeInfo::fetch_with(provider.inner(), key_manager_contract, id).await { + Ok(comm_info) => Some(comm_info), + Err(_) => { + error!(committee_id = %id, "fail to fetch new CommitteeInfo"); + None + } + } + } }); - Ok(Self { - _provider: Box::new(provider), - inner: Box::pin(validated), - }) + + Ok(Box::pin(s)) } } diff --git a/timeboost/src/conf.rs b/timeboost/src/conf.rs index 80fb3629..2f48d8cb 100644 --- a/timeboost/src/conf.rs +++ b/timeboost/src/conf.rs @@ -7,20 +7,19 @@ use timeboost_crypto::prelude::DkgDecKey; use timeboost_sequencer::SequencerConfig; use timeboost_types::{KeyStore, ThresholdKeyCell}; +use crate::committee::CommitteeInfo; + #[derive(Debug, Clone, Builder)] pub struct TimeboostConfig { /// The sailfish peers that this node will connect to. pub(crate) sailfish_committee: AddressableCommittee, - /// Previous sailfish peers - pub(crate) prev_sailfish_committee: Option, + /// Previous committee info stored on chain + pub(crate) prev_committee: Option, /// The decrypt peers that this node will connect to. pub(crate) decrypt_committee: AddressableCommittee, - /// Previous decrypt peers - pub(crate) prev_decrypt_committee: Option<(AddressableCommittee, KeyStore)>, - /// The block certifier peers that this node will connect to. pub(crate) certifier_committee: AddressableCommittee, @@ -80,8 +79,14 @@ impl TimeboostConfig { .sailfish_committee(self.sailfish_committee.clone()) .decrypt_committee((self.decrypt_committee.clone(), self.key_store.clone())) .recover(self.recover) - .maybe_previous_sailfish_committee(self.prev_sailfish_committee.clone()) - .maybe_previous_decrypt_committee(self.prev_decrypt_committee.clone()) + .maybe_previous_sailfish_committee( + self.prev_committee.as_ref().map(|c| c.sailfish_committee()), + ) + .maybe_previous_decrypt_committee( + self.prev_committee + .as_ref() + .map(|c| (c.decrypt_committee(), c.dkg_key_store())), + ) .leash_len(self.leash_len) .threshold_dec_key(self.threshold_dec_key.clone()) .chain_config(self.chain_config.clone()) diff --git a/timeboost/src/lib.rs b/timeboost/src/lib.rs index 1b64ebfb..348d77c4 100644 --- a/timeboost/src/lib.rs +++ b/timeboost/src/lib.rs @@ -10,6 +10,7 @@ use futures::StreamExt; use metrics::TimeboostMetrics; use multisig::PublicKey; use timeboost_builder::{Certifier, CertifierDown, SenderTaskDown, Submitter}; +use timeboost_contract::provider::PubSubProvider; use timeboost_sequencer::{Output, Sequencer}; use timeboost_types::{BundleVariant, ConsensusTime}; use tokio::select; @@ -45,24 +46,16 @@ pub struct Timeboost { prometheus: Arc, nitro_forwarder: Option, submitter: Submitter, + // pubsub service (+backend) handle, disconnect on drop + _pubsub_provider: PubSubProvider, + events: NewCommitteeStream, } impl Timeboost { - pub async fn new(mut cfg: TimeboostConfig) -> Result { + pub async fn new(cfg: TimeboostConfig) -> Result { let pro = Arc::new(PrometheusMetrics::default()); let met = Arc::new(TimeboostMetrics::new(&*pro)); - // optionally fetch previous committee info - let cid: u64 = cfg.sailfish_committee.committee().id().into(); - if cid > 0u64 { - let c = &cfg.chain_config.parent; - let prev_comm = - CommitteeInfo::fetch(c.rpc_url.clone(), c.key_manager_contract, cid - 1).await?; - - cfg.prev_sailfish_committee = Some(prev_comm.sailfish_committee()); - cfg.prev_decrypt_committee = - Some((prev_comm.decrypt_committee(), prev_comm.dkg_key_store())); - } let seq = Sequencer::new(cfg.sequencer_config(), &*pro).await?; let blk = Certifier::new(cfg.certifier_config(), &*pro).await?; let sub = Submitter::new(cfg.submitter_config(), &*pro); @@ -74,6 +67,16 @@ impl Timeboost { None }; + let provider = PubSubProvider::new(cfg.chain_config.parent.ws_url.clone()).await?; + let start_ts = cfg + .prev_committee + .as_ref() + .map(|c| c.effective_timestamp()) + .unwrap_or_default(); + let events = + CommitteeInfo::new_committee_stream(&provider, start_ts, &cfg.chain_config.parent) + .await?; + let (tx, rx) = mpsc::channel(100); Ok(Self { @@ -87,6 +90,8 @@ impl Timeboost { _metrics: met, nitro_forwarder, submitter: sub, + _pubsub_provider: provider, + events, }) } @@ -103,8 +108,6 @@ impl Timeboost { } pub async fn go(mut self) -> Result<()> { - let mut events = NewCommitteeStream::create(&self.config.chain_config.parent).await?; - loop { select! { trx = self.receiver.recv() => { @@ -149,26 +152,21 @@ impl Timeboost { return Err(e.into()) } }, - res = events.next() => match res { - Some(id) => { + res = self.events.next() => match res { + Some(comm_info) => { let cur: u64 = self.config.key_store.committee().id().into(); + let new_id: u64 = comm_info.id().into(); - if id == cur + 1 { - info!(node = %self.label, committee_id = %id, current = %cur, "fetching next committee"); - let comm_info = CommitteeInfo::fetch( - self.config.chain_config.parent.rpc_url.clone(), - self.config.chain_config.parent.key_manager_contract, - id, - ).await?; - - info!(node = %self.label, committee_id = %id, current = %cur, "setting next committee"); + // contract ensures consecutive CommitteeId assignment + if new_id == cur + 1 { + info!(node = %self.label, committee_id = %new_id, current = %cur, "setting next committee"); self.sequencer.set_next_committee( ConsensusTime(comm_info.effective_timestamp()), comm_info.sailfish_committee(), comm_info.dkg_key_store() ).await?; } else { - warn!(node = %self.label, committee_id = %id, current = %cur, "ignored new CommitteeCreated event"); + warn!(node = %self.label, committee_id = %new_id, current = %cur, "ignored new CommitteeCreated event"); continue; } }, From d4d1e3c5d9716ba50b9a0715fab10c3b96a6efb1 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Fri, 19 Sep 2025 15:04:51 +0800 Subject: [PATCH 22/25] account for faster local run --- justfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index fed06842..adfa04e9 100644 --- a/justfile +++ b/justfile @@ -199,7 +199,7 @@ test-dyn-comm: build_release_until build-test-utils --spawn "1:anvil --port 8545" \ --run "2:sleep 2" \ --run "3:scripts/deploy-test-contract" \ - --spawn "4:target/release/run-committee --configs test-configs/c0/ --committee-id 0 --until 1600" \ + --spawn "4:target/release/run-committee --configs test-configs/c0/ --committee-id 0 --until 2000" \ --run "5:target/release/mkconfig -n 4 \ --public-addr 127.0.0.1:9000 \ --internal-addr 127.0.0.1:9003 \ @@ -210,10 +210,10 @@ test-dyn-comm: build_release_until build-test-utils --parent-chain-id 31337 \ --parent-ibox-contract 0xa0f3a1a4e2b2bcb7b48c8527c28098f207572ec1 \ --key-manager-contract 0x2bbf15bc655c4cc157b769cfcb1ea9924b9e1a35 \ - --timestamp +18s \ + --timestamp +16s \ --stamp-dir /tmp \ --output test-configs/c1" \ - --run "6:sleep 8" \ + --run "6:sleep 6" \ --run "7:target/release/register \ -a threshold-enc-key \ -m 'attend year erase basket blind adapt stove broccoli isolate unveil acquire category' \ From fbc48a55065d5eafd9b7b53a02983f4136f866af Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Fri, 19 Sep 2025 20:19:27 +0800 Subject: [PATCH 23/25] improve code quality Co-authored-by: Toralf Wittner --- multisig/src/committee.rs | 18 +++++++++++++++++ timeboost-contract/src/provider.rs | 29 +++++++++++++-------------- timeboost/src/binaries/sailfish.rs | 4 ++-- timeboost/src/binaries/timeboost.rs | 6 +++--- timeboost/src/committee.rs | 31 +++++++++++++++++++---------- timeboost/src/lib.rs | 4 ++-- 6 files changed, 59 insertions(+), 33 deletions(-) diff --git a/multisig/src/committee.rs b/multisig/src/committee.rs index 313d4905..29e4af74 100644 --- a/multisig/src/committee.rs +++ b/multisig/src/committee.rs @@ -1,4 +1,5 @@ use std::num::{NonZeroUsize, ParseIntError}; +use std::ops::{Add, Sub}; use std::{fmt, str::FromStr}; use std::sync::Arc; @@ -107,6 +108,7 @@ impl Committee { #[derive( Debug, Copy, + Default, Clone, PartialEq, Eq, @@ -163,3 +165,19 @@ impl Committable for CommitteeId { .finalize() } } + +impl Add for CommitteeId { + type Output = Self; + + fn add(self, rhs: u64) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl Sub for CommitteeId { + type Output = Self; + + fn sub(self, rhs: u64) -> Self::Output { + Self(self.0 - rhs) + } +} diff --git a/timeboost-contract/src/provider.rs b/timeboost-contract/src/provider.rs index ba4e301f..48d25c66 100644 --- a/timeboost-contract/src/provider.rs +++ b/timeboost-contract/src/provider.rs @@ -6,7 +6,7 @@ use std::{ops::Deref, pin::Pin}; use alloy::{ eips::BlockNumberOrTag, network::EthereumWallet, - primitives::Address, + primitives::{Address, BlockNumber}, providers::{Provider, ProviderBuilder}, rpc::types::{Filter, Log}, signers::local::{LocalSignerError, MnemonicBuilder, PrivateKeySigner, coins_bip39::English}, @@ -14,7 +14,7 @@ use alloy::{ transports::{http::reqwest::Url, ws::WsConnect}, }; use futures::{Stream, StreamExt}; -use timeboost_types::{HttpProvider, HttpProviderWithWallet}; +use timeboost_types::{HttpProvider, HttpProviderWithWallet, Timestamp}; use tracing::error; /// Build a local signer from wallet mnemonic and account index @@ -46,6 +46,7 @@ pub struct PubSubProvider(HttpProvider); impl Deref for PubSubProvider { type Target = HttpProvider; + fn deref(&self) -> &Self::Target { &self.0 } @@ -64,10 +65,6 @@ impl PubSubProvider { Ok(Self(provider)) } - pub fn inner(&self) -> &HttpProvider { - &self.0 - } - /// create an event stream of event type `E`, subscribing since `from_block` on `contract` pub async fn event_stream( &self, @@ -89,22 +86,24 @@ impl PubSubProvider { .into_stream(); let validated = events.filter_map(|log| async move { - let Ok(event) = log.log_decode_validate::() else { - error!("fail to parse CommitteeCreated event log"); - return None; - }; - Some(event) + match log.log_decode_validate::() { + Ok(event) => Some(event), + Err(err) => { + error!(%err, "fail to parse `CommitteeCreated` event log"); + None + } + } }); Ok(Box::pin(validated)) } /// Returns the smallest block number whose timestamp is >= `target_ts` through binary search. - /// Useful to determine `from_block` input of `Self::event_strea()` subscription. + /// Useful to determine `from_block` input of `Self::event_stream()` subscription. pub async fn get_block_number_by_timestamp( &self, - target_ts: u64, - ) -> anyhow::Result> { + target_ts: Timestamp, + ) -> anyhow::Result> { let latest = self.get_block_number().await?; let mut lo: u64 = 0; let mut hi: u64 = latest; @@ -124,7 +123,7 @@ impl PubSubProvider { } }; - if block.header.timestamp >= target_ts { + if block.header.timestamp >= target_ts.into() { if mid == 0 { return Ok(Some(0)); } diff --git a/timeboost/src/binaries/sailfish.rs b/timeboost/src/binaries/sailfish.rs index 4d7fe78f..1e5d4d6a 100644 --- a/timeboost/src/binaries/sailfish.rs +++ b/timeboost/src/binaries/sailfish.rs @@ -92,7 +92,7 @@ async fn main() -> Result<()> { let comm_info = CommitteeInfo::fetch( config.chain.parent.rpc_url, config.chain.parent.key_manager_contract, - cli.committee_id.into(), + cli.committee_id, ) .await?; info!(label = %config.keys.signing.public, committee_id = %cli.committee_id, "committee info synced"); @@ -110,7 +110,7 @@ async fn main() -> Result<()> { config.net.public.address.clone(), signing_keypair.public_key(), dh_keypair.clone(), - comm_info.group(), + comm_info.address_info(), net_metrics, ) .await?; diff --git a/timeboost/src/binaries/timeboost.rs b/timeboost/src/binaries/timeboost.rs index 6dd23368..dc1892d7 100644 --- a/timeboost/src/binaries/timeboost.rs +++ b/timeboost/src/binaries/timeboost.rs @@ -73,7 +73,7 @@ async fn main() -> Result<()> { let comm_info = CommitteeInfo::fetch( node_config.chain.parent.rpc_url.clone(), node_config.chain.parent.key_manager_contract, - cli.committee_id.into(), + cli.committee_id, ) .await?; info!(label = %sign_keypair.public_key(), committee_id = %cli.committee_id, "committee info synced"); @@ -95,8 +95,8 @@ async fn main() -> Result<()> { let pubkey = sign_keypair.public_key(); // optionally fetch previous committee info - let cid: u64 = cli.committee_id.into(); - let prev_comm = if cid > 0u64 { + let cid = cli.committee_id; + let prev_comm = if cid > CommitteeId::default() { let c = &node_config.chain.parent; let prev_comm = CommitteeInfo::fetch(c.rpc_url.clone(), c.key_manager_contract, cid - 1).await?; diff --git a/timeboost/src/committee.rs b/timeboost/src/committee.rs index 0a06008d..dd63f25e 100644 --- a/timeboost/src/committee.rs +++ b/timeboost/src/committee.rs @@ -37,7 +37,11 @@ pub struct CommitteeInfo { impl CommitteeInfo { /// Fetch the committee info for `committee_id` from `key_manager_addr` on chain - pub async fn fetch(rpc: Url, key_manager_addr: Address, committee_id: u64) -> Result { + pub async fn fetch( + rpc: Url, + key_manager_addr: Address, + committee_id: CommitteeId, + ) -> Result { let provider = ProviderBuilder::new().connect_http(rpc); Self::fetch_with(provider, key_manager_addr, committee_id).await } @@ -45,10 +49,13 @@ impl CommitteeInfo { pub(crate) async fn fetch_with( provider: impl Provider, key_manager_addr: Address, - committee_id: u64, + committee_id: CommitteeId, ) -> Result { let contract = KeyManager::new(key_manager_addr, &provider); - let c = contract.getCommitteeById(committee_id).call().await?; + let c = contract + .getCommitteeById(committee_id.into()) + .call() + .await?; let (signing_keys, dh_keys, dkg_keys, public_addresses) = c .members @@ -69,7 +76,7 @@ impl CommitteeInfo { .multiunzip(); Ok(Self { - id: committee_id.into(), + id: committee_id, timestamp: c.effectiveTimestamp.into(), signing_keys, dh_keys, @@ -100,7 +107,7 @@ impl CommitteeInfo { ) } - pub fn group( + pub fn address_info( &self, ) -> impl Iterator { izip!( @@ -111,7 +118,7 @@ impl CommitteeInfo { } pub fn sailfish_committee(&self) -> AddressableCommittee { - AddressableCommittee::new(self.committee(), self.group()) + AddressableCommittee::new(self.committee(), self.address_info()) } pub fn decrypt_committee(&self) -> AddressableCommittee { @@ -157,7 +164,7 @@ impl CommitteeInfo { config: &ParentChain, ) -> Result { let from_block = provider - .get_block_number_by_timestamp(start_ts.into()) + .get_block_number_by_timestamp(start_ts) .await? .unwrap_or_default(); let events = provider @@ -176,11 +183,13 @@ impl CommitteeInfo { let s = events.filter_map(move |log| { let provider = provider.clone(); async move { - let id = log.data().id; - match CommitteeInfo::fetch_with(provider.inner(), key_manager_contract, id).await { + let committee_id: CommitteeId = log.data().id.into(); + match CommitteeInfo::fetch_with(&*provider, key_manager_contract, committee_id) + .await + { Ok(comm_info) => Some(comm_info), - Err(_) => { - error!(committee_id = %id, "fail to fetch new CommitteeInfo"); + Err(err) => { + error!(%committee_id, %err, "fail to fetch new `CommitteeInfo`"); None } } diff --git a/timeboost/src/lib.rs b/timeboost/src/lib.rs index 348d77c4..054c59d8 100644 --- a/timeboost/src/lib.rs +++ b/timeboost/src/lib.rs @@ -154,8 +154,8 @@ impl Timeboost { }, res = self.events.next() => match res { Some(comm_info) => { - let cur: u64 = self.config.key_store.committee().id().into(); - let new_id: u64 = comm_info.id().into(); + let cur = self.config.key_store.committee().id(); + let new_id = comm_info.id(); // contract ensures consecutive CommitteeId assignment if new_id == cur + 1 { From c153532eb5339aae99df58b1abd70941e99c5acf Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Fri, 19 Sep 2025 21:34:58 +0800 Subject: [PATCH 24/25] use registeredBlkNum from contract for from_block --- contracts | 2 +- timeboost-contract/src/deployer.rs | 4 +++ timeboost-contract/src/provider.rs | 43 ++--------------------------- timeboost/src/binaries/timeboost.rs | 1 + timeboost/src/committee.rs | 16 ++++++----- timeboost/src/conf.rs | 4 +++ timeboost/src/lib.rs | 15 ++++++---- 7 files changed, 30 insertions(+), 55 deletions(-) diff --git a/contracts b/contracts index d367ab38..9a48cd17 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit d367ab389a735182f245abfc217121e3bb2a9a85 +Subproject commit 9a48cd17444e23a0bc5a30c07fabe37fcee80f7d diff --git a/timeboost-contract/src/deployer.rs b/timeboost-contract/src/deployer.rs index d7ebdc74..3e7eec2d 100644 --- a/timeboost-contract/src/deployer.rs +++ b/timeboost-contract/src/deployer.rs @@ -53,6 +53,7 @@ mod tests { use alloy::{ eips::BlockNumberOrTag, node_bindings::Anvil, + primitives::U256, providers::{Provider, ProviderBuilder, WalletProvider}, rpc::types::Filter, sol_types::{SolEvent, SolValue}, @@ -94,8 +95,11 @@ mod tests { .await .unwrap() .abi_encode_sequence(), + // deploy takes first 2 blocks: deploying implementation contract and proxy contract + // setNextCommittee is the 3rd tx, thus in 3rd block CommitteeSol { id: 0, + registeredBlockNumber: U256::from(3), effectiveTimestamp: timestamp, members, } diff --git a/timeboost-contract/src/provider.rs b/timeboost-contract/src/provider.rs index 48d25c66..4812f19c 100644 --- a/timeboost-contract/src/provider.rs +++ b/timeboost-contract/src/provider.rs @@ -6,7 +6,7 @@ use std::{ops::Deref, pin::Pin}; use alloy::{ eips::BlockNumberOrTag, network::EthereumWallet, - primitives::{Address, BlockNumber}, + primitives::Address, providers::{Provider, ProviderBuilder}, rpc::types::{Filter, Log}, signers::local::{LocalSignerError, MnemonicBuilder, PrivateKeySigner, coins_bip39::English}, @@ -14,7 +14,7 @@ use alloy::{ transports::{http::reqwest::Url, ws::WsConnect}, }; use futures::{Stream, StreamExt}; -use timeboost_types::{HttpProvider, HttpProviderWithWallet, Timestamp}; +use timeboost_types::{HttpProvider, HttpProviderWithWallet}; use tracing::error; /// Build a local signer from wallet mnemonic and account index @@ -97,43 +97,4 @@ impl PubSubProvider { Ok(Box::pin(validated)) } - - /// Returns the smallest block number whose timestamp is >= `target_ts` through binary search. - /// Useful to determine `from_block` input of `Self::event_stream()` subscription. - pub async fn get_block_number_by_timestamp( - &self, - target_ts: Timestamp, - ) -> anyhow::Result> { - let latest = self.get_block_number().await?; - let mut lo: u64 = 0; - let mut hi: u64 = latest; - - while lo <= hi { - let mid = lo + (hi - lo) / 2; - - let block = match self - .0 - .get_block_by_number(BlockNumberOrTag::Number(mid)) - .await? - { - Some(b) => b, - None => { - lo = mid + 1; - continue; - } - }; - - if block.header.timestamp >= target_ts.into() { - if mid == 0 { - return Ok(Some(0)); - } - hi = mid - 1; - } else { - lo = mid + 1; - } - } - - // At this point, `lo` is the smallest index with ts >= target_ts (if any) - if lo > latest { Ok(None) } else { Ok(Some(lo)) } - } } diff --git a/timeboost/src/binaries/timeboost.rs b/timeboost/src/binaries/timeboost.rs index dc1892d7..d35a08fa 100644 --- a/timeboost/src/binaries/timeboost.rs +++ b/timeboost/src/binaries/timeboost.rs @@ -107,6 +107,7 @@ async fn main() -> Result<()> { let config = TimeboostConfig::builder() .sailfish_committee(sailfish_committee) + .registered_blk(*comm_info.registered_block()) .maybe_prev_committee(prev_comm) .decrypt_committee(decrypt_committee) .certifier_committee(certifier_committee) diff --git a/timeboost/src/committee.rs b/timeboost/src/committee.rs index dd63f25e..ee5189c9 100644 --- a/timeboost/src/committee.rs +++ b/timeboost/src/committee.rs @@ -16,7 +16,7 @@ use timeboost_config::{CERTIFIER_PORT_OFFSET, DECRYPTER_PORT_OFFSET, ParentChain use timeboost_contract::KeyManager::{self, CommitteeCreated}; use timeboost_contract::provider::PubSubProvider; use timeboost_crypto::prelude::DkgEncKey; -use timeboost_types::{KeyStore, Timestamp}; +use timeboost_types::{BlockNumber, KeyStore, Timestamp}; use tracing::error; use url::Url; @@ -29,6 +29,7 @@ pub type NewCommitteeStream = Pin>>; pub struct CommitteeInfo { id: CommitteeId, timestamp: Timestamp, + registered_blk: BlockNumber, signing_keys: Vec, dh_keys: Vec, dkg_keys: Vec, @@ -78,6 +79,7 @@ impl CommitteeInfo { Ok(Self { id: committee_id, timestamp: c.effectiveTimestamp.into(), + registered_blk: c.registeredBlockNumber.to::().into(), signing_keys, dh_keys, dkg_keys, @@ -93,6 +95,10 @@ impl CommitteeInfo { self.timestamp } + pub fn registered_block(&self) -> BlockNumber { + self.registered_blk + } + pub fn signing_keys(&self) -> &[multisig::PublicKey] { &self.signing_keys } @@ -160,17 +166,13 @@ impl CommitteeInfo { /// subscribe an event stream pub async fn new_committee_stream( provider: &PubSubProvider, - start_ts: Timestamp, + from_block: BlockNumber, config: &ParentChain, ) -> Result { - let from_block = provider - .get_block_number_by_timestamp(start_ts) - .await? - .unwrap_or_default(); let events = provider .event_stream::( config.key_manager_contract, - BlockNumberOrTag::Number(from_block), + BlockNumberOrTag::Number(*from_block), ) .await .map_err(|e| { diff --git a/timeboost/src/conf.rs b/timeboost/src/conf.rs index 2f48d8cb..56eba434 100644 --- a/timeboost/src/conf.rs +++ b/timeboost/src/conf.rs @@ -1,3 +1,4 @@ +use alloy::primitives::BlockNumber; use bon::Builder; use cliquenet::{Address, AddressableCommittee}; use multisig::{Keypair, x25519}; @@ -14,6 +15,9 @@ pub struct TimeboostConfig { /// The sailfish peers that this node will connect to. pub(crate) sailfish_committee: AddressableCommittee, + /// The block in which the current committee is registered + pub(crate) registered_blk: BlockNumber, + /// Previous committee info stored on chain pub(crate) prev_committee: Option, diff --git a/timeboost/src/lib.rs b/timeboost/src/lib.rs index 054c59d8..e3255eb9 100644 --- a/timeboost/src/lib.rs +++ b/timeboost/src/lib.rs @@ -68,14 +68,17 @@ impl Timeboost { }; let provider = PubSubProvider::new(cfg.chain_config.parent.ws_url.clone()).await?; - let start_ts = cfg + let from_blk = cfg .prev_committee .as_ref() - .map(|c| c.effective_timestamp()) - .unwrap_or_default(); - let events = - CommitteeInfo::new_committee_stream(&provider, start_ts, &cfg.chain_config.parent) - .await?; + .map_or_else(|| cfg.registered_blk, |prev| *prev.registered_block()); + + let events = CommitteeInfo::new_committee_stream( + &provider, + from_blk.into(), + &cfg.chain_config.parent, + ) + .await?; let (tx, rx) = mpsc::channel(100); From d48f068fe6a962a37e9147fa414209428d376cc4 Mon Sep 17 00:00:00 2001 From: Alex Xiong Date: Fri, 19 Sep 2025 22:08:48 +0800 Subject: [PATCH 25/25] minor fix --- timeboost-contract/src/provider.rs | 2 +- timeboost/src/lib.rs | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/timeboost-contract/src/provider.rs b/timeboost-contract/src/provider.rs index 4812f19c..974f865c 100644 --- a/timeboost-contract/src/provider.rs +++ b/timeboost-contract/src/provider.rs @@ -89,7 +89,7 @@ impl PubSubProvider { match log.log_decode_validate::() { Ok(event) => Some(event), Err(err) => { - error!(%err, "fail to parse `CommitteeCreated` event log"); + error!(%err, "failed to parse `CommitteeCreated` event log"); None } } diff --git a/timeboost/src/lib.rs b/timeboost/src/lib.rs index e3255eb9..d116872f 100644 --- a/timeboost/src/lib.rs +++ b/timeboost/src/lib.rs @@ -68,14 +68,9 @@ impl Timeboost { }; let provider = PubSubProvider::new(cfg.chain_config.parent.ws_url.clone()).await?; - let from_blk = cfg - .prev_committee - .as_ref() - .map_or_else(|| cfg.registered_blk, |prev| *prev.registered_block()); - let events = CommitteeInfo::new_committee_stream( &provider, - from_blk.into(), + cfg.registered_blk.into(), &cfg.chain_config.parent, ) .await?;