diff --git a/Cargo.lock b/Cargo.lock index 57e186d6f5..5bfad3d09d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,9 +176,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -287,15 +287,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ar_archive_writer" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" -dependencies = [ - "object 0.32.2", -] - [[package]] name = "arbitrary" version = "1.4.2" @@ -314,6 +305,15 @@ dependencies = [ "arbitrary", ] +[[package]] +name = "arc-swap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] + [[package]] name = "argon2" version = "0.5.3" @@ -1240,7 +1240,7 @@ dependencies = [ "serde_urlencoded", "thiserror 1.0.69", "tokio", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "url", "winapi", ] @@ -1268,9 +1268,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "serde", @@ -1421,9 +1421,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.44" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "jobserver", @@ -1572,9 +1572,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" dependencies = [ "clap_builder", "clap_derive", @@ -1582,9 +1582,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" dependencies = [ "anstream", "anstyle", @@ -1594,11 +1594,11 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.60" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e602857739c5a4291dfa33b5a298aeac9006185229a700e5810a3ef7272d971" +checksum = "2348487adcd4631696ced64ccdb40d38ac4d31cae7f2eec8817fcea1b9d1c43c" dependencies = [ - "clap 4.5.51", + "clap 4.5.50", ] [[package]] @@ -2346,9 +2346,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", "serde_core", @@ -3137,7 +3137,7 @@ checksum = "3a82608ee96ce76aeab659e9b8d3c2b787bffd223199af88c674923d861ada10" dependencies = [ "execute-command-macro", "execute-command-tokens", - "generic-array 1.3.5", + "generic-array 1.3.4", ] [[package]] @@ -3380,6 +3380,7 @@ dependencies = [ "fendermint_vm_snapshot", "fendermint_vm_topdown", "fendermint_vm_topdown_proof_service", + "filecoin-f3-gpbft", "fs-err", "fvm", "fvm_ipld_blockstore 0.3.1", @@ -3417,7 +3418,7 @@ dependencies = [ "tendermint-proto 0.31.1", "tendermint-rpc", "tokio", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "toml 0.8.23", "tower 0.4.13", "tower-abci", @@ -3434,7 +3435,7 @@ dependencies = [ "anyhow", "bytes", "cid 0.11.1", - "clap 4.5.51", + "clap 4.5.50", "ethers", "fendermint_materializer", "fendermint_vm_actor_interface", @@ -3462,6 +3463,7 @@ dependencies = [ "dirs", "fendermint_vm_encoding", "fendermint_vm_topdown", + "fendermint_vm_topdown_proof_service", "fvm_ipld_encoding 0.5.3", "fvm_shared", "ipc-api", @@ -3537,7 +3539,7 @@ dependencies = [ "async-trait", "axum", "cid 0.11.1", - "clap 4.5.51", + "clap 4.5.50", "erased-serde", "ethers", "ethers-contract", @@ -3658,7 +3660,7 @@ dependencies = [ "tendermint-rpc", "text-tables", "tokio", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "toml 0.8.23", "tracing", "url", @@ -3690,7 +3692,7 @@ dependencies = [ "base64 0.21.7", "bytes", "cid 0.11.1", - "clap 4.5.51", + "clap 4.5.50", "ethers", "fendermint_crypto", "fendermint_vm_actor_interface", @@ -3872,6 +3874,7 @@ dependencies = [ "actors-custom-car", "anyhow", "arbitrary", + "arc-swap", "async-stm", "async-trait", "base64 0.21.7", @@ -3897,6 +3900,7 @@ dependencies = [ "fendermint_vm_message", "fendermint_vm_resolver", "fendermint_vm_topdown", + "fendermint_vm_topdown_proof_service", "fil_actor_eam", "fil_actor_evm", "futures-core", @@ -3917,6 +3921,7 @@ dependencies = [ "num-traits", "pin-project", "prometheus", + "proofs", "quickcheck", "quickcheck_macros", "rand 0.8.5", @@ -3931,7 +3936,7 @@ dependencies = [ "thiserror 1.0.69", "tokio", "tokio-stream", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "tracing", ] @@ -3950,6 +3955,7 @@ dependencies = [ "fendermint_vm_actor_interface", "fendermint_vm_encoding", "fendermint_vm_message", + "fendermint_vm_topdown_proof_service", "fvm_ipld_encoding 0.5.3", "fvm_shared", "hex", @@ -3957,6 +3963,7 @@ dependencies = [ "lazy_static", "multihash-codetable", "num-traits", + "proofs", "quickcheck", "quickcheck_macros", "rand 0.8.5", @@ -4017,7 +4024,7 @@ dependencies = [ "thiserror 1.0.69", "tokio", "tokio-stream", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "tracing", "unsigned-varint 0.7.2", ] @@ -4032,7 +4039,7 @@ dependencies = [ "async-trait", "bytes", "cid 0.11.1", - "clap 4.5.51", + "clap 4.5.50", "ethers", "fendermint_crypto", "fendermint_testing", @@ -4070,7 +4077,7 @@ dependencies = [ "base64 0.21.7", "chrono", "cid 0.11.1", - "clap 4.5.51", + "clap 4.5.50", "fendermint_actor_f3_light_client", "fendermint_vm_genesis", "filecoin-f3-certs", @@ -4084,7 +4091,6 @@ dependencies = [ "humantime-serde", "ipc-api", "ipc-observability", - "ipc-provider", "keccak-hash", "multihash 0.18.1", "multihash-codetable", @@ -4139,7 +4145,7 @@ dependencies = [ "anyhow", "async-std", "cid 0.10.1", - "clap 4.5.51", + "clap 4.5.50", "futures", "fvm_ipld_blockstore 0.2.1", "fvm_ipld_car 0.7.1", @@ -4161,7 +4167,7 @@ dependencies = [ "fvm_ipld_blockstore 0.3.1", "fvm_ipld_encoding 0.5.3", "fvm_shared", - "hex-literal 1.1.0", + "hex-literal 1.0.0", "log", "multihash 0.19.3", "num-derive 0.4.2", @@ -4186,7 +4192,7 @@ dependencies = [ "fvm_ipld_kamt", "fvm_shared", "hex", - "hex-literal 1.1.0", + "hex-literal 1.0.0", "log", "multihash-codetable", "num-derive 0.4.2", @@ -4227,7 +4233,7 @@ dependencies = [ "fvm_sdk", "fvm_shared", "hex", - "integer-encoding 4.1.0", + "integer-encoding 4.0.2", "itertools 0.14.0", "k256 0.13.4", "lazy_static", @@ -4251,7 +4257,7 @@ dependencies = [ [[package]] name = "filecoin-f3-blssig" version = "0.1.0" -source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#f838fcd973e6e7f32298363ceb03a8010a1dc1fe" +source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#40af605984045a9f2b9ba5dcc9c04c984deb8d1f" dependencies = [ "blake2 0.11.0-rc.2", "bls-signatures", @@ -4267,7 +4273,7 @@ dependencies = [ [[package]] name = "filecoin-f3-certs" version = "0.1.0" -source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#f838fcd973e6e7f32298363ceb03a8010a1dc1fe" +source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#40af605984045a9f2b9ba5dcc9c04c984deb8d1f" dependencies = [ "ahash 0.8.12", "filecoin-f3-gpbft", @@ -4278,7 +4284,7 @@ dependencies = [ [[package]] name = "filecoin-f3-gpbft" version = "0.1.0" -source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#f838fcd973e6e7f32298363ceb03a8010a1dc1fe" +source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#40af605984045a9f2b9ba5dcc9c04c984deb8d1f" dependencies = [ "ahash 0.8.12", "anyhow", @@ -4301,7 +4307,7 @@ dependencies = [ [[package]] name = "filecoin-f3-lightclient" version = "0.1.0" -source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#f838fcd973e6e7f32298363ceb03a8010a1dc1fe" +source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#40af605984045a9f2b9ba5dcc9c04c984deb8d1f" dependencies = [ "anyhow", "base64 0.22.1", @@ -4317,7 +4323,7 @@ dependencies = [ [[package]] name = "filecoin-f3-merkle" version = "0.1.0" -source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#f838fcd973e6e7f32298363ceb03a8010a1dc1fe" +source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#40af605984045a9f2b9ba5dcc9c04c984deb8d1f" dependencies = [ "anyhow", "sha3", @@ -4326,7 +4332,7 @@ dependencies = [ [[package]] name = "filecoin-f3-rpc" version = "0.1.0" -source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#f838fcd973e6e7f32298363ceb03a8010a1dc1fe" +source = "git+https://github.com/moshababo/rust-f3?branch=cargo-git-compat#40af605984045a9f2b9ba5dcc9c04c984deb8d1f" dependencies = [ "anyhow", "filecoin-f3-gpbft", @@ -4443,9 +4449,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide 0.8.9", @@ -4951,7 +4957,7 @@ dependencies = [ "serde", "serde_ipld_dagcbor 0.6.4", "serde_repr", - "serde_tuple 1.1.3", + "serde_tuple 1.1.2", "thiserror 2.0.17", ] @@ -5064,9 +5070,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "1.3.5" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542" +checksum = "985a5578ebdb02351d484a77fb27e7cb79272f1ba9bc24692d8243c3cfe40660" dependencies = [ "rustversion", "typenum", @@ -5208,7 +5214,7 @@ dependencies = [ "indexmap 2.12.0", "slab", "tokio", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "tracing", ] @@ -5227,7 +5233,7 @@ dependencies = [ "indexmap 2.12.0", "slab", "tokio", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "tracing", ] @@ -5375,9 +5381,9 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hex-literal" -version = "1.1.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "hex_fmt" @@ -5472,11 +5478,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.12" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5690,7 +5696,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.4", "tower-service", - "webpki-roots 1.0.4", + "webpki-roots 1.0.3", ] [[package]] @@ -5787,9 +5793,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", @@ -5800,9 +5806,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -5813,10 +5819,11 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ + "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -5827,38 +5834,42 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", + "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", + "stable_deref_trait", + "tinystr", "writeable", "yoke", "zerofrom", @@ -5948,9 +5959,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.25" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +checksum = "81776e6f9464432afcc28d03e52eb101c93b6f0566f52aef2427663e700f0403" dependencies = [ "crossbeam-deque", "globset", @@ -6098,9 +6109,9 @@ checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] name = "integer-encoding" -version = "4.1.0" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c00403deb17c3221a1fe4fb571b9ed0370b3dcd116553c77fa294a3d918699" +checksum = "0d762194228a2f1c11063e46e32e5acb96e66e906382b9eb5441f2e0504bbd5a" [[package]] name = "io-lifetimes" @@ -6164,7 +6175,7 @@ dependencies = [ "bytes", "chrono", "cid 0.11.1", - "clap 4.5.51", + "clap 4.5.50", "clap_complete", "contracts-artifacts", "env_logger 0.10.2", @@ -6219,7 +6230,7 @@ dependencies = [ "thiserror 1.0.69", "tokio", "tokio-tungstenite 0.18.0", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "toml 0.7.8", "tracing", "tracing-subscriber 0.3.20", @@ -6343,7 +6354,7 @@ dependencies = [ "ethers", "fs-err", "fvm_shared", - "generic-array 1.3.5", + "generic-array 1.3.4", "hex", "ipc-types", "libc", @@ -6460,13 +6471,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.17" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi 0.5.2", "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6578,9 +6589,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -6642,7 +6653,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tokio-rustls 0.26.4", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "tracing", "url", ] @@ -7546,9 +7557,9 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "literally" @@ -8853,9 +8864,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -8980,9 +8991,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" dependencies = [ "unicode-ident", ] @@ -9099,11 +9110,12 @@ dependencies = [ [[package]] name = "proptest" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" dependencies = [ "bitflags 2.10.0", + "lazy_static", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", @@ -9205,11 +9217,10 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "psm" -version = "0.1.28" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" +checksum = "e66fcd288453b748497d8fb18bccc83a16b0518e3906d4b8df0a8d42d93dbb1c" dependencies = [ - "ar_archive_writer", "cc", ] @@ -9683,7 +9694,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls 0.26.4", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "tower 0.5.2", "tower-http 0.6.6", "tower-service", @@ -9692,7 +9703,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.4", + "webpki-roots 1.0.3", ] [[package]] @@ -10005,7 +10016,7 @@ dependencies = [ "once_cell", "ring 0.17.14", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.7", "subtle", "zeroize", ] @@ -10057,9 +10068,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", "zeroize", @@ -10079,7 +10090,7 @@ dependencies = [ "rustls 0.23.34", "rustls-native-certs 0.8.2", "rustls-platform-verifier-android", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.7", "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs 0.26.11", @@ -10104,9 +10115,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring 0.17.14", "rustls-pki-types", @@ -10219,9 +10230,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.5" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "dyn-clone", "ref-cast", @@ -10513,12 +10524,12 @@ dependencies = [ [[package]] name = "serde_tuple" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af196b9c06f0aa5555ab980c01a2527b0f67517da8d68b1731b9d4764846a6f" +checksum = "52569c5296679bd28e2457f067f97d270077df67da0340647da5412c8eac8d9e" dependencies = [ "serde", - "serde_tuple_macros 1.1.3", + "serde_tuple_macros 1.1.2", ] [[package]] @@ -10534,9 +10545,9 @@ dependencies = [ [[package]] name = "serde_tuple_macros" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3a1e7d2eadec84deabd46ae061bf480a91a6bce74d25dad375bd656f2e19d8" +checksum = "2f46c707781471741d5f2670edb36476479b26e94cf43efe21ca3c220b97ef2e" dependencies = [ "proc-macro2", "quote", @@ -10583,7 +10594,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.12.0", "schemars 0.9.0", - "schemars 1.0.5", + "schemars 1.0.4", "serde_core", "serde_json", "time", @@ -11384,9 +11395,9 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "target-triple" -version = "1.0.0" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" [[package]] name = "tempfile" @@ -11708,9 +11719,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -11879,9 +11890,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -12032,7 +12043,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "tower-layer", "tower-service", "tracing", @@ -12267,9 +12278,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.113" +version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559b6a626c0815c942ac98d434746138b4f89ddd6a1b8cbb168c6845fb3376c5" +checksum = "4d66678374d835fe847e0dc8348fde2ceb5be4a7ec204437d8367f0d8df266a5" dependencies = [ "glob", "serde", @@ -12391,9 +12402,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "unicode-segmentation" @@ -12620,7 +12631,7 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-tungstenite 0.21.0", - "tokio-util 0.7.17", + "tokio-util 0.7.16", "tower-service", "tracing", ] @@ -12642,9 +12653,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -12653,11 +12664,25 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.108", + "wasm-bindgen-shared", +] + [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -12668,9 +12693,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -12678,22 +12703,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ - "bumpalo", "proc-macro2", "quote", "syn 2.0.108", + "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] @@ -12941,9 +12966,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -12985,14 +13010,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.4", + "webpki-root-certs 1.0.3", ] [[package]] name = "webpki-root-certs" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b" +checksum = "05d651ec480de84b762e7be71e6efa7461699c19d9e2c272c8d93455f567786e" dependencies = [ "rustls-pki-types", ] @@ -13014,9 +13039,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" dependencies = [ "rustls-pki-types", ] @@ -13554,9 +13579,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "ws_stream_wasm" @@ -13627,9 +13652,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.28" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" [[package]] name = "xmltree" @@ -13725,10 +13750,11 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ + "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -13736,9 +13762,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -13809,9 +13835,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", @@ -13820,9 +13846,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -13831,9 +13857,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index c5156b3619..12a93b364e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,6 @@ members = [ "fendermint/testing/*-test", "fendermint/tracing", "fendermint/vm/*", - "fendermint/vm/topdown/proof-service", "fendermint/actors", "fendermint/actors-custom-car", "fendermint/actors-builtin-car", @@ -95,6 +94,7 @@ gcra = "0.6.0" hex = "0.4" hex-literal = "0.4.1" http = "0.2.12" +humantime-serde = "1.1" im = "15.1.0" integer-encoding = { version = "3.0.3", default-features = false } jsonrpc-v2 = { version = "0.11", default-features = false, features = [ @@ -137,6 +137,7 @@ num-bigint = "0.4" num-derive = "0.4" num-traits = "0.2" num_enum = "0.7.2" +parking_lot = "0.12" paste = "1" pin-project = "1.1.2" prometheus = { version = "0.13", features = ["process"] } @@ -185,8 +186,6 @@ tracing-appender = "0.2.3" text-tables = "0.3.1" url = { version = "2.4.1", features = ["serde"] } zeroize = "1.6" -parking_lot = "0.12" -humantime-serde = "1.1" # Vendored for cross-compilation, see https://github.com/cross-rs/cross/wiki/Recipes#openssl # Make sure every top level build target actually imports this dependency, and don't end up diff --git a/fendermint/actors/f3-light-client/src/lib.rs b/fendermint/actors/f3-light-client/src/lib.rs index 0898c8243c..23aaaeb166 100644 --- a/fendermint/actors/f3-light-client/src/lib.rs +++ b/fendermint/actors/f3-light-client/src/lib.rs @@ -40,9 +40,9 @@ impl F3LightClientActor { rt.validate_immediate_caller_is(std::iter::once(&SYSTEM_ACTOR_ADDR))?; let state = State::new( - params.instance_id, + params.latest_instance_id, + params.latest_finalized_height, params.power_table, - params.finalized_epochs, )?; rt.create(&state)?; @@ -70,8 +70,8 @@ impl F3LightClient for F3LightClientActor { let lc = &state.light_client_state; Ok(GetStateResponse { - instance_id: lc.instance_id, - finalized_epochs: lc.finalized_epochs.clone(), + latest_instance_id: lc.latest_instance_id, + latest_finalized_height: lc.latest_finalized_height, power_table: lc.power_table.clone(), }) } @@ -99,17 +99,18 @@ mod tests { use fil_actors_runtime::SYSTEM_ACTOR_ADDR; use fvm_ipld_encoding::ipld_block::IpldBlock; use fvm_shared::address::Address; + use fvm_shared::clock::ChainEpoch; use fvm_shared::error::ExitCode; /// Helper function to create test light client state fn create_test_state( - instance_id: u64, - finalized_epochs: Vec, + current_instance_id: u64, + latest_finalized_epoch: Option, power_table: Vec, ) -> LightClientState { LightClientState { - instance_id, - finalized_epochs, + latest_instance_id: current_instance_id, + latest_finalized_height: latest_finalized_epoch, power_table, } } @@ -130,9 +131,9 @@ mod tests { /// Construct the actor and verify initialization pub fn construct_and_verify( - instance_id: u64, + current_instance_id: u64, power_table: Vec, - finalized_epochs: Vec, + latest_finalized_epoch: Option, ) -> MockRuntime { let rt = MockRuntime { receiver: Address::new_id(10), @@ -144,9 +145,9 @@ mod tests { rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); let constructor_params = ConstructorParams { - instance_id, + latest_instance_id: current_instance_id, + latest_finalized_height: latest_finalized_epoch, power_table, - finalized_epochs, }; let result = rt @@ -165,33 +166,26 @@ mod tests { #[test] fn test_constructor_empty_power_table() { - let _rt = construct_and_verify(0, vec![], vec![]); + let _rt = construct_and_verify(0, vec![], Some(10)); // Constructor test passed if we get here without panicking } #[test] fn test_constructor_with_power_table() { let power_entries = create_test_power_entries(); - let _rt = construct_and_verify(1, power_entries, vec![]); - // Constructor test passed if we get here without panicking - } - - #[test] - fn test_constructor_with_finalized_epochs() { - let power_entries = create_test_power_entries(); - let _rt = construct_and_verify(1, power_entries, vec![100, 101, 102]); + let _rt = construct_and_verify(1, power_entries, Some(10)); // Constructor test passed if we get here without panicking } #[test] fn test_update_state_success() { - let rt = construct_and_verify(1, create_test_power_entries(), vec![]); + let rt = construct_and_verify(1, create_test_power_entries(), Some(10)); // Set caller to system actor rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); - let new_state = create_test_state(1, vec![100, 101, 102], create_test_power_entries()); + let new_state = create_test_state(1, Some(10), create_test_power_entries()); let update_params = UpdateStateParams { state: new_state.clone(), }; @@ -207,16 +201,53 @@ mod tests { rt.verify(); } + #[test] + fn test_update_state_non_advancing_height() { + let rt = construct_and_verify(1, create_test_power_entries(), Some(10)); + + // First update to set the finalized height to 102 + rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); + rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); + let initial_state = create_test_state(1, Some(10), create_test_power_entries()); + let initial_params = UpdateStateParams { + state: initial_state, + }; + rt.call::( + Method::UpdateState as u64, + IpldBlock::serialize_cbor(&initial_params).unwrap(), + ) + .unwrap(); + rt.reset(); + + // Try to update with same height + rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); + rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); + let same_height_state = create_test_state(1, Some(10), create_test_power_entries()); + let update_params = UpdateStateParams { + state: same_height_state, + }; + + let result = rt.call::( + Method::UpdateState as u64, + IpldBlock::serialize_cbor(&update_params).unwrap(), + ); + + // Should fail with illegal argument + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(err.exit_code(), ExitCode::USR_ILLEGAL_ARGUMENT); + } + #[test] fn test_update_state_unauthorized_caller() { - let rt = construct_and_verify(1, create_test_power_entries(), vec![]); + let rt = construct_and_verify(1, create_test_power_entries(), Some(10)); // Set caller to non-system actor let unauthorized_caller = Address::new_id(999); rt.set_caller(*SYSTEM_ACTOR_CODE_ID, unauthorized_caller); rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); - let new_state = create_test_state(1, vec![100, 101, 102], create_test_power_entries()); + let new_state = create_test_state(1, Some(11), create_test_power_entries()); let update_params = UpdateStateParams { state: new_state }; let result = rt.call::( @@ -233,12 +264,12 @@ mod tests { #[test] fn test_get_state() { let power_entries = create_test_power_entries(); - let rt = construct_and_verify(42, power_entries.clone(), vec![]); + let rt = construct_and_verify(42, power_entries.clone(), Some(10)); // Update state first rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); - let new_state = create_test_state(42, vec![100, 101, 102], power_entries.clone()); + let new_state = create_test_state(42, Some(11), power_entries.clone()); let update_params = UpdateStateParams { state: new_state }; rt.call::( Method::UpdateState as u64, @@ -255,19 +286,19 @@ mod tests { .unwrap(); let response = result.deserialize::().unwrap(); - assert_eq!(response.instance_id, 42); - assert_eq!(response.finalized_epochs, vec![100, 101, 102]); + assert_eq!(response.latest_instance_id, 42); + assert_eq!(response.latest_finalized_height, Some(11)); assert_eq!(response.power_table, power_entries); } #[test] fn test_state_progression() { - let rt = construct_and_verify(1, create_test_power_entries(), vec![]); + let rt = construct_and_verify(1, create_test_power_entries(), Some(10)); // Update with first state rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); - let state1 = create_test_state(1, vec![100, 101, 102], create_test_power_entries()); + let state1 = create_test_state(1, Some(100), create_test_power_entries()); let params1 = UpdateStateParams { state: state1 }; rt.call::( Method::UpdateState as u64, @@ -279,7 +310,7 @@ mod tests { // Update with second state (higher height) rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); - let state2 = create_test_state(1, vec![200, 201, 202], create_test_power_entries()); + let state2 = create_test_state(1, Some(200), create_test_power_entries()); let params2 = UpdateStateParams { state: state2 }; let result = rt.call::( Method::UpdateState as u64, @@ -287,4 +318,94 @@ mod tests { ); assert!(result.is_ok()); } + + #[test] + fn test_instance_id_progression_next_instance() { + let rt = construct_and_verify(100, create_test_power_entries(), Some(10)); + + // First state at instance 100 + rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); + rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); + let initial_state = create_test_state(100, Some(10), create_test_power_entries()); + let initial_params = UpdateStateParams { + state: initial_state, + }; + rt.call::( + Method::UpdateState as u64, + IpldBlock::serialize_cbor(&initial_params).unwrap(), + ) + .unwrap(); + rt.reset(); + + // Update to next instance (100 -> 101) should succeed + rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); + rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); + let next_instance_state = create_test_state(101, Some(10), create_test_power_entries()); + let update_params = UpdateStateParams { + state: next_instance_state, + }; + + let result = rt.call::( + Method::UpdateState as u64, + IpldBlock::serialize_cbor(&update_params).unwrap(), + ); + assert!(result.is_ok()); + } + + #[test] + fn test_instance_id_skip_rejected() { + let rt = construct_and_verify(100, create_test_power_entries(), Some(10)); + + // First state at instance 100 + rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); + rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); + let initial_state = create_test_state(100, Some(10), create_test_power_entries()); + let initial_params = UpdateStateParams { + state: initial_state, + }; + rt.call::( + Method::UpdateState as u64, + IpldBlock::serialize_cbor(&initial_params).unwrap(), + ) + .unwrap(); + rt.reset(); + + // Try to skip instance (100 -> 102) should fail + rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); + rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); + let skipped_state = create_test_state(102, Some(10), create_test_power_entries()); + let update_params = UpdateStateParams { + state: skipped_state, + }; + + let result = rt.call::( + Method::UpdateState as u64, + IpldBlock::serialize_cbor(&update_params).unwrap(), + ); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(err.exit_code(), ExitCode::USR_ILLEGAL_ARGUMENT); + } + + #[test] + fn test_empty_epochs_rejected() { + let rt = construct_and_verify(1, create_test_power_entries(), Some(10)); + + rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); + rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); + + // Try to update with empty finalized_epochs + let invalid_state = create_test_state(1, Some(10), create_test_power_entries()); + let update_params = UpdateStateParams { + state: invalid_state, + }; + + let result = rt.call::( + Method::UpdateState as u64, + IpldBlock::serialize_cbor(&update_params).unwrap(), + ); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(err.exit_code(), ExitCode::USR_ILLEGAL_ARGUMENT); + } } diff --git a/fendermint/actors/f3-light-client/src/state.rs b/fendermint/actors/f3-light-client/src/state.rs index 64497e9d5c..1142626f5c 100644 --- a/fendermint/actors/f3-light-client/src/state.rs +++ b/fendermint/actors/f3-light-client/src/state.rs @@ -9,6 +9,7 @@ use crate::types::{LightClientState, PowerEntry}; use fil_actors_runtime::runtime::Runtime; use fil_actors_runtime::ActorError; +use fvm_shared::clock::ChainEpoch; use serde::{Deserialize, Serialize}; /// State of the F3 light client actor. @@ -25,14 +26,14 @@ pub struct State { impl State { /// Create a new F3 light client state pub fn new( - instance_id: u64, + latest_instance_id: u64, + latest_finalized_height: Option, power_table: Vec, - finalized_epochs: Vec, ) -> Result { let state = State { light_client_state: LightClientState { - instance_id, - finalized_epochs, + latest_instance_id, + latest_finalized_height, power_table, }, }; @@ -40,10 +41,6 @@ impl State { } /// Update light client state - /// - /// This method should only be called from consensus code path which - /// contains the lightclient verifier. No additional validation is - /// performed here as it's expected to be done by the verifier. pub fn update_state( &mut self, _rt: &impl Runtime, diff --git a/fendermint/actors/f3-light-client/src/types.rs b/fendermint/actors/f3-light-client/src/types.rs index 7065f700d4..3187aa0992 100644 --- a/fendermint/actors/f3-light-client/src/types.rs +++ b/fendermint/actors/f3-light-client/src/types.rs @@ -12,22 +12,19 @@ use fvm_shared::clock::ChainEpoch; /// F3 Light Client State - maintains verifiable parent finality from the parent chain. /// /// This structure represents the essential state needed to track F3 finality: -/// - Instance ID: The current F3 instance (can increment during protocol upgrades) -/// - Finalized Epochs: Complete chain of finalized epochs (not just the latest) +/// - Latest Instance ID: The latest F3 instance that has been committed +/// - Latest Finalized Height: The highest epoch that has been finalized (None if nothing finalized yet) /// - Power Table: Current validator power table (can change between instances) /// /// This state is extracted from F3 certificates received from the parent chain /// and stored by the actor for use in finality proofs. #[derive(Deserialize_tuple, Serialize_tuple, Debug, Clone, PartialEq, Eq)] pub struct LightClientState { - /// Current F3 instance ID - pub instance_id: u64, - /// Finalized chain - full list of finalized epochs - /// Matches ECChain from F3 certificates - /// Empty initially at genesis until first update - pub finalized_epochs: Vec, + /// Latest F3 instance ID that has been committed + pub latest_instance_id: u64, + /// The latest finalized height (None if nothing has been finalized yet) + pub latest_finalized_height: Option, /// Current power table for this instance - /// Power table can change between instances pub power_table: Vec, } @@ -44,11 +41,11 @@ pub struct PowerEntry { #[derive(Deserialize_tuple, Serialize_tuple, Debug, Clone, PartialEq, Eq)] pub struct ConstructorParams { /// Initial F3 instance ID (from genesis) - pub instance_id: u64, + pub latest_instance_id: u64, /// Initial power table (from genesis) pub power_table: Vec, - /// Initial finalized epochs (from genesis certificate) - pub finalized_epochs: Vec, + /// Initial finalized height (None if nothing finalized yet, Some(height) from genesis certificate if available) + pub latest_finalized_height: Option, } /// Parameters for updating the light client state @@ -61,10 +58,10 @@ pub struct UpdateStateParams { /// Response containing the current light client state #[derive(Deserialize_tuple, Serialize_tuple, Debug, Clone, PartialEq, Eq)] pub struct GetStateResponse { - /// Current F3 instance ID - pub instance_id: u64, - /// Finalized chain - full list of finalized epochs (ordered) - pub finalized_epochs: Vec, + /// Latest F3 instance ID that has been committed + pub latest_instance_id: u64, + /// The latest finalized height (None if nothing finalized yet) + pub latest_finalized_height: Option, /// Current power table pub power_table: Vec, } diff --git a/fendermint/app/Cargo.toml b/fendermint/app/Cargo.toml index 01c8a95803..7f43973dad 100644 --- a/fendermint/app/Cargo.toml +++ b/fendermint/app/Cargo.toml @@ -45,8 +45,10 @@ tracing-appender = { workspace = true } tracing-subscriber = { workspace = true } literally = { workspace = true } url = { workspace = true } +ethers = { workspace = true } fendermint_abci = { path = "../abci" } +ipc_actors_abis = { path = "../../contract-bindings" } actors-custom-api = { path = "../actors/api" } fendermint_actor_f3_light_client = { path = "../actors/f3-light-client" } fendermint_app_options = { path = "./options" } @@ -73,8 +75,8 @@ fendermint_vm_snapshot = { path = "../vm/snapshot" } fendermint_vm_topdown = { path = "../vm/topdown" } fendermint_vm_topdown_proof_service = { path = "../vm/topdown/proof-service" } -ipc_actors_abis = { path = "../../contract-bindings" } -ethers = {workspace = true} +# F3 certificate handling +filecoin-f3-gpbft = { git = "https://github.com/moshababo/rust-f3", branch = "cargo-git-compat" } # .car file wrapped in a crate actors-builtin-car = { path = "../actors-builtin-car" } diff --git a/fendermint/app/config/default.toml b/fendermint/app/config/default.toml index 1aa0174248..b965e93ac7 100644 --- a/fendermint/app/config/default.toml +++ b/fendermint/app/config/default.toml @@ -273,6 +273,51 @@ vote_interval = 1 # pausing the syncer, preventing new events to trigger votes. vote_timeout = 60 +# # Top-down checkpoint configuration (uncomment to enable parent syncing) +# [ipc.topdown] +# # Number of blocks to delay before considering a parent block final +# chain_head_delay = 10 +# # Additional delay on top of chain_head_delay before proposing finality +# proposal_delay = 5 +# # Maximum number of blocks to propose in a single checkpoint +# max_proposal_range = 100 +# # Maximum number of blocks to cache (optional) +# # max_cache_blocks = 1000 +# # Parent syncing cron period, in seconds +# polling_interval = 30 +# # Exponential backoff retry base, in seconds +# exponential_back_off = 5 +# # Maximum number of retries before giving up +# exponential_retry_limit = 5 +# # Parent HTTP RPC endpoint +# parent_http_endpoint = "http://api.calibration.node.glif.io/rpc/v1" +# # Parent HTTP timeout (optional), in seconds +# # parent_http_timeout = 60 +# # Bearer token for Authorization header (optional) +# # parent_http_auth_token = "your-token-here" +# # Parent registry address +# parent_registry = "0x74539671a1d2f1c8f200826baba665179f53a1b7" +# # Parent gateway address +# parent_gateway = "0x77aa40b105843728088c0132e43fc44348881da8" +# +# # F3 proof service configuration (optional - for proof-based parent finality) +# # Requires genesis to have F3 parameters configured +# [ipc.topdown.proof_service] +# # Enable F3 proof-based parent finality (default: false) +# enabled = false +# # F3 network name - must match parent chain ("calibrationnet", "mainnet") +# f3_network_name = "calibrationnet" +# # How often to poll parent chain for new F3 certificates, in seconds +# polling_interval = 30 +# # How many F3 instances ahead to pre-generate proofs (lookahead window) +# lookahead_instances = 5 +# # How many old instances to keep after commitment (retention window) +# retention_instances = 2 +# # Gateway actor ID on parent chain (optional - derived from genesis if not set) +# # gateway_actor_id = 176609 +# # Or use Ethereum address (will be resolved to actor ID) +# # gateway_eth_address = "0xE4c61299c16323C4B58376b60A77F68Aa59afC8b" + # # Setting which are only allowed if the `--network` CLI parameter is `testnet`. # [testing] diff --git a/fendermint/app/settings/Cargo.toml b/fendermint/app/settings/Cargo.toml index 20aaeee513..db90508c24 100644 --- a/fendermint/app/settings/Cargo.toml +++ b/fendermint/app/settings/Cargo.toml @@ -32,3 +32,4 @@ ipc-observability = { path = "../../../ipc/observability" } fendermint_vm_encoding = { path = "../../vm/encoding" } fendermint_vm_topdown = { path = "../../vm/topdown" } +fendermint_vm_topdown_proof_service = { path = "../../vm/topdown/proof-service" } diff --git a/fendermint/app/settings/src/lib.rs b/fendermint/app/settings/src/lib.rs index ab738dfa75..9a456e54f4 100644 --- a/fendermint/app/settings/src/lib.rs +++ b/fendermint/app/settings/src/lib.rs @@ -27,6 +27,8 @@ use self::resolver::ResolverSettings; use ipc_observability::config::TracingSettings; use ipc_provider::config::deserialize::deserialize_eth_address_from_str; +use fendermint_vm_topdown_proof_service::ProofServiceConfig; + pub mod eth; pub mod fvm; pub mod resolver; @@ -226,6 +228,18 @@ pub struct TopDownSettings { /// The parent gateway address #[serde(deserialize_with = "deserialize_eth_address_from_str")] pub parent_gateway: Address, + /// F3 configuration (optional - for proof-based finality) + /// If Some, F3 proof-based finality is enabled; if None, use legacy voting-based finality + #[serde(default)] + pub f3: Option, +} + +/// F3 proof-based finality configuration +/// When present, F3 proof-based finality is enabled +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct F3 { + /// F3 proof service configuration (mandatory when F3 is configured) + pub proof_service: ProofServiceConfig, } #[serde_as] diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs index 747f79b130..6253c946e1 100644 --- a/fendermint/app/src/app.rs +++ b/fendermint/app/src/app.rs @@ -23,8 +23,8 @@ use fendermint_storage::{ }; use fendermint_vm_core::Timestamp; use fendermint_vm_interpreter::fvm::state::{ - empty_state_tree, CheckStateRef, FvmExecState, FvmQueryState, FvmStateParams, - FvmUpdatableParams, + empty_state_tree, ipc::F3LightClientCaller, CheckStateRef, FvmExecState, FvmQueryState, + FvmStateParams, FvmUpdatableParams, }; use fendermint_vm_interpreter::fvm::store::ReadOnlyBlockstore; use fendermint_vm_interpreter::genesis::{read_genesis_car, GenesisAppState}; @@ -218,6 +218,80 @@ where } } +/// Create a read-only execution state for querying actors before app initialization. +/// +/// This allows querying state (e.g., F3 Light Client) before the full App is created. +/// Returns `None` if the state hasn't been initialized by genesis yet. +pub fn create_read_only_exec_state( + db: &DB, + state_store: &BS, + namespace: KV::Namespace, +) -> Result>>>> +where + KV: KVStore + Codec + Encode, + DB: KVReadable + 'static, + BS: Blockstore + Clone + 'static + Send + Sync, +{ + // Read committed state from database (same pattern as get_committed_state) + let tx = db.read(); + let state: Option = tx + .get(&namespace, &AppStoreKey::State) + .context("get failed")?; + + let state = match state { + Some(s) => s, + None => return Ok(None), + }; + + let block_height = state.app_state.block_height; + let state_params = state.app_state.state_params; + + // Check if state is queryable (genesis has been initialized) + // It's really the empty state tree that would be the best indicator. + if block_height == 0 + && state_params.timestamp.0 == 0 + && state_params.network_version == NetworkVersion::V0 + { + return Ok(None); + } + + // Create MultiEngine (same as in App::new) + let multi_engine = Arc::new(MultiEngine::new(1)); + + // Create read-only execution state + let exec_state = FvmExecState::new( + ReadOnlyBlockstore::new(Arc::new(state_store.clone())), + multi_engine.as_ref(), + block_height as ChainEpoch, + state_params, + ) + .context("error creating execution state")?; + + Ok(Some(exec_state)) +} + +/// Query the F3 Light Client Actor state from a read-only execution state. +/// Returns (instance_id, latest_finalized_height) if F3 is initialized, None otherwise. +pub fn query_f3_state( + exec_state: &mut FvmExecState>, +) -> Result)>> +where + BS: Blockstore + Clone + 'static + Send + Sync, +{ + let f3_caller = F3LightClientCaller::new(); + match f3_caller.get_state(exec_state) { + Ok(state) => Ok(Some(( + state.latest_instance_id, + state.latest_finalized_height, + ))), + Err(e) => { + // F3 actor might not be deployed (non-Filecoin parent) + tracing::debug!("F3 Light Client Actor not found or not accessible: {}", e); + Ok(None) + } + } +} + impl App where KV: KVStore @@ -481,6 +555,12 @@ where .context("Validator cache is not available")? .get_validator(id) } + + /// Get access to the messages interpreter + /// Used to access the TopDownManager for updating the proof cache + pub fn interpreter(&self) -> &Arc { + &self.messages_interpreter + } } // NOTE: The `Application` interface doesn't allow failures at the moment. The protobuf diff --git a/fendermint/app/src/cmd/genesis.rs b/fendermint/app/src/cmd/genesis.rs index d0365eeb85..7a1d44ca80 100644 --- a/fendermint/app/src/cmd/genesis.rs +++ b/fendermint/app/src/cmd/genesis.rs @@ -350,7 +350,7 @@ pub async fn seal_genesis(genesis_file: &PathBuf, args: &SealGenesisArgs) -> any builder.write_to(args.output_path.clone()).await } -/// Fetches F3 parameters for a specific instance ID from the parent Filecoin chain +/// Fetches F3 parameters from the parent Filecoin chain async fn fetch_f3_params_from_parent( parent_endpoint: &url::Url, parent_auth_token: Option<&String>, @@ -359,7 +359,7 @@ async fn fetch_f3_params_from_parent( tracing::info!( "Fetching F3 parameters for instance {} from parent chain at {}", instance_id, - parent_endpoint + parent_endpoint, ); let jsonrpc_client = JsonRpcClientImpl::new( @@ -440,6 +440,7 @@ pub async fn new_genesis_from_parent( ) })?; + tracing::info!("Fetching F3 data from parent Filecoin chain"); fetch_f3_params_from_parent( parent_rpc, args.parent_filecoin_auth_token.as_ref(), diff --git a/fendermint/app/src/cmd/proof_cache.rs b/fendermint/app/src/cmd/proof_cache.rs index 38aa40e6a0..b64aaf88fe 100644 --- a/fendermint/app/src/cmd/proof_cache.rs +++ b/fendermint/app/src/cmd/proof_cache.rs @@ -24,7 +24,7 @@ fn handle_proof_cache_command(args: &ProofCacheArgs) -> anyhow::Result<()> { } } -fn inspect_cache(db_path: &Path) -> anyhow::Result<()> { +fn inspect_cache(db_path: &PathBuf) -> anyhow::Result<()> { println!("=== Proof Cache Inspection ==="); println!("Database: {}", db_path.display()); println!(); @@ -69,7 +69,7 @@ fn inspect_cache(db_path: &Path) -> anyhow::Result<()> { Ok(()) } -fn show_stats(db_path: &Path) -> anyhow::Result<()> { +fn show_stats(db_path: &PathBuf) -> anyhow::Result<()> { println!("=== Proof Cache Statistics ==="); println!("Database: {}", db_path.display()); println!(); @@ -125,7 +125,7 @@ fn show_stats(db_path: &Path) -> anyhow::Result<()> { Ok(()) } -fn get_proof(db_path: &Path, instance_id: u64) -> anyhow::Result<()> { +fn get_proof(db_path: &PathBuf, instance_id: u64) -> anyhow::Result<()> { println!("=== Get Proof for Instance {} ===", instance_id); println!("Database: {}", db_path.display()); println!(); diff --git a/fendermint/app/src/service/node.rs b/fendermint/app/src/service/node.rs index d2baffacd8..fbfc81dab7 100644 --- a/fendermint/app/src/service/node.rs +++ b/fendermint/app/src/service/node.rs @@ -11,6 +11,7 @@ use fendermint_vm_interpreter::fvm::interpreter::FvmMessagesInterpreter; use fendermint_vm_interpreter::fvm::observe::register_metrics as register_interpreter_metrics; use fendermint_vm_interpreter::fvm::topdown::TopDownManager; use fendermint_vm_interpreter::fvm::upgrades::UpgradeScheduler; +use fendermint_vm_interpreter::MessagesInterpreter; use fendermint_vm_snapshot::{SnapshotManager, SnapshotParams}; use fendermint_vm_topdown::observe::register_metrics as register_topdown_metrics; use fendermint_vm_topdown::proxy::{IPCProviderProxy, IPCProviderProxyWithLatency}; @@ -38,6 +39,10 @@ use crate::{App, AppConfig, AppStore, BitswapBlockstore}; use fendermint_app_settings::{AccountKind, Settings}; use fendermint_vm_interpreter::fvm::end_block_hook::EndBlockManager; +use fendermint_vm_interpreter::fvm::state::ipc::F3LightClientCaller; +use filecoin_f3_gpbft::PowerEntries; +use fvm_shared::clock::ChainEpoch; +use std::path::PathBuf; // Database collection names. namespaces! { @@ -245,9 +250,135 @@ pub async fn run( }; let end_block_manager = EndBlockManager::new(); + + // Validate F3 configuration consistency: config and genesis must match + // Query F3 state from genesis to check if F3 is enabled there + let app_namespace = ns.app.clone(); + let exec_state = crate::app::create_read_only_exec_state::<_, _, AppStore>( + &db, + &state_store, + app_namespace.clone(), + ) + .context("failed to create read-only exec state")?; + + let f3_in_genesis = match exec_state { + Some(mut state) => { + crate::app::query_f3_state(&mut state) + .context("failed to query F3 state from genesis")? + .is_some() + } + None => false, // Genesis not initialized yet - will be checked below + }; + + // Check F3 configuration in settings + let f3_in_config = topdown_enabled + && settings + .ipc + .topdown_config() + .ok() + .and_then(|tc| tc.f3.as_ref()) + .is_some(); + + // Validate: F3 must be enabled in BOTH config and genesis, or NEITHER + match (f3_in_config, f3_in_genesis) { + (true, false) => { + anyhow::bail!( + "F3 is enabled in config but F3 Light Client Actor state not found in genesis. \ + F3 must be configured in both config and genesis, or neither." + ); + } + (false, true) => { + anyhow::bail!( + "F3 is enabled in genesis but not in config. \ + F3 must be configured in both config and genesis, or neither." + ); + } + (true, true) => { + // Both enabled - proceed with F3 initialization + } + (false, false) => { + // Both disabled - F3 not used + } + } + + // Create F3 cache and handler if F3 is configured + let (f3_handler, f3_service_config) = if f3_in_config && f3_in_genesis { + let topdown_config = settings + .ipc + .topdown_config() + .ok() + .context("topdown config required when topdown is enabled")?; + + let f3_config = topdown_config + .f3 + .as_ref() + .expect("F3 config should exist if f3_in_config is true"); + + // Query F3 state - we know it exists from validation above + let exec_state = crate::app::create_read_only_exec_state::<_, _, AppStore>( + &db, + &state_store, + app_namespace, + ) + .context("failed to create read-only exec state")?; + + let f3_initial_state = exec_state + .and_then(|mut state| { + crate::app::query_f3_state(&mut state) + .context("failed to query F3 state") + .transpose() + }) + .transpose()? + .ok_or_else(|| { + anyhow::anyhow!( + "F3 is enabled but F3 Light Client Actor state not found in genesis" + ) + })?; + + // Use queried F3 state to initialize cache correctly + let (initial_instance, initial_epoch) = match f3_initial_state { + (inst, Some(epoch)) => (inst, epoch), + (inst, None) => (inst, 0), + }; + + let db_path = Some(settings.data_dir().join("proof-cache")); + let cache = if let Some(path) = &db_path { + Arc::new( + fendermint_vm_topdown_proof_service::ProofCache::new_with_persistence( + initial_epoch, + initial_instance, + f3_config.proof_service.cache_config.clone(), + path, + )?, + ) + } else { + Arc::new(fendermint_vm_topdown_proof_service::ProofCache::new( + initial_epoch, + initial_instance, + f3_config.proof_service.cache_config.clone(), + )) + }; + + // Create F3 handler with cache + use fendermint_vm_interpreter::fvm::F3FinalityHandler; + let handler = F3FinalityHandler::new(cache, settings.ipc.subnet_id.to_string()); + + // Store config for service creation later + let mut proof_config = f3_config.proof_service.clone(); + proof_config.parent_rpc_url = topdown_config.parent_http_endpoint.to_string(); + + (Some(handler), Some((proof_config, db_path))) + } else { + (None, None) + }; + } else { + (None, None) + }; + let top_down_manager = TopDownManager::new( parent_finality_provider.clone(), parent_finality_votes.clone(), + f3_handler, // F3 handler created upfront ); let interpreter = FvmMessagesInterpreter::new( @@ -274,6 +405,44 @@ pub async fn run( snapshots, )?; + // Launch F3 proof service if configured + if let Some((proof_config, _db_path)) = f3_service_config { + tracing::info!("F3 proof service enabled"); + + // Get cache from F3 handler + let cache = app + .interpreter() + .top_down_manager + .f3 + .as_ref() + .map(|f3| f3.proof_cache().clone()) + .expect("F3 handler should exist if f3_service_config is Some"); + + // Create service - it will fetch initial state from cache + use fendermint_vm_topdown_proof_service::ProofGeneratorService; + let service = ProofGeneratorService::new( + proof_config.clone(), + cache.clone(), + &settings.ipc.subnet_id, + 0, // Service will fetch actual instance ID from cache + PowerEntries(vec![]), // Service will fetch actual power table from parent + ) + .await + .context("Failed to create F3 proof service")?; + + tracing::info!( + f3_network = proof_config.f3_network_name(&settings.ipc.subnet_id), + lookahead = proof_config.cache_config.lookahead_instances, + "F3 proof service initialized successfully" + ); + + // Spawn service in background + tokio::spawn(async move { + service.run().await; + }); + } + } + if let Some((agent_proxy, config)) = ipc_tuple { let app_parent_finality_query = AppParentFinalityQuery::new(app.clone()); tokio::spawn(async move { diff --git a/fendermint/testing/contract-test/tests/gas_market.rs b/fendermint/testing/contract-test/tests/gas_market.rs index 8b57b8a16d..f4fc128531 100644 --- a/fendermint/testing/contract-test/tests/gas_market.rs +++ b/fendermint/testing/contract-test/tests/gas_market.rs @@ -66,7 +66,13 @@ async fn tester_with_upgrader( let end_block_manager = EndBlockManager::default(); let finality_provider = Arc::new(Toggle::disabled()); let vote_tally = VoteTally::empty(); - let top_down_manager = TopDownManager::new(finality_provider, vote_tally); + let top_down_manager = TopDownManager::new( + finality_provider, + vote_tally, + None, + "test-subnet".to_string(), + false, + ); let interpreter: FvmMessagesInterpreter = FvmMessagesInterpreter::new( end_block_manager, diff --git a/fendermint/testing/contract-test/tests/run_upgrades.rs b/fendermint/testing/contract-test/tests/run_upgrades.rs index 0a734b426d..163acfff46 100644 --- a/fendermint/testing/contract-test/tests/run_upgrades.rs +++ b/fendermint/testing/contract-test/tests/run_upgrades.rs @@ -208,7 +208,13 @@ async fn test_applying_upgrades() { let end_block_manager = EndBlockManager::default(); let finality_provider = Arc::new(Toggle::disabled()); let vote_tally = VoteTally::empty(); - let top_down_manager = TopDownManager::new(finality_provider, vote_tally); + let top_down_manager = TopDownManager::new( + finality_provider, + vote_tally, + None, + "test-subnet".to_string(), + false, + ); let interpreter: FvmMessagesInterpreter = FvmMessagesInterpreter::new( end_block_manager, diff --git a/fendermint/vm/genesis/golden/genesis/cbor/genesis.cbor b/fendermint/vm/genesis/golden/genesis/cbor/genesis.cbor index 3a434547aa..9d73ac11e0 100644 --- a/fendermint/vm/genesis/golden/genesis/cbor/genesis.cbor +++ b/fendermint/vm/genesis/golden/genesis/cbor/genesis.cbor @@ -1 +1 @@ -ab63697063a16767617465776179a4697375626e65745f6964821b7eca9e45193a5edf834b008ff4db9f8db9fc98d0014b008c87f3a18bc3dad6a10156040a14b3436b1ce515269d0e01716511eb1e14a8f5b2736d616a6f726974795f70657263656e74616765184076626f74746f6d5f75705f636865636b5f706572696f641bedb1ca6774bbca0b776163746976655f76616c696461746f72735f6c696d69740f686163636f756e747382a2646d657461a1684d756c7469736967a4677369676e657273855501300fa088e63bc7284c7886986f0c0b32a4b8a43155017129d666aefc41888fa7848bc44d87e28e2627e45501394ae215967cb88cdff3028d154bb52de3dec828550109d2f2a1068fec62e2e1c03901b9d09e0fb6a1fb55011ffff67c4ddbe407cade15c5455c016c169a806d697468726573686f6c64026d76657374696e675f73746172741b7e691439f5a3af537076657374696e675f6475726174696f6e1b24ca3cfd0d53d7566762616c616e636540a2646d657461a1684d756c7469736967a4677369676e6572738555016591322f2dd8242769cbb1f25c7ea721a9a7af1655013434f6a3adaa76a3cc8eeb51e6ecf817422549d955017ccedef0ff205eb9de81a5195c611224864fe2af5501738bfd8efcf20518c5d1896a2c4dbf5a4532f23355018edcf2b088860ace2d2451fcda9a8cccf5155e6f697468726573686f6c64036d76657374696e675f73746172741b77bbd6777d7f09ff7076657374696e675f6475726174696f6e1b3cd419a08d6f82af6762616c616e636551009aceabcd73ffacc17e7a2589b0b410cb68626173655f6665654900664a46027308d50568636861696e5f696418656974696d657374616d701bb7e5d1db6ca339f66a636861696e5f6e616d65606a76616c696461746f727389a265706f7765725100f2e3e900debb4b13c01ef1ffbe73af006a7075626c69635f6b657958410430949f5cd9edf3887a6d162477f4adb9162c0aa02126099e4af4a6f18621449bbe65e7ea6383eb76f570ff3c9c7a4a4d939dc140efd778e2d5029c4a52f70ef7a265706f776572406a7075626c69635f6b65795841046f9029b17c045cd92f136b781ec69080903bf1963cc8dd61b464d0b523dce15d453bf839c66ccc7778a6a69e7dfab0d66ec05f28ae4a0f80d6abf9dae473ac0aa265706f7765725100571c2a44f074149d342219ca1c8b31566a7075626c69635f6b6579584104e2b47a1897aed91d422c992607879595875e6134dacab1a34c8037553f9c92e24eb009cfd2fb9f17fca7bf94e83df742c21ac47315d30a15e9c8a79eb2d39738a265706f7765725100ffffffffffffffff95e98069516d1c4d6a7075626c69635f6b65795841041baac9a539f651ce7653b132b3ce6f6d37252a271e67273c6c9622589f024506875c9b9c8ad30d47e87361a6564a9d24dffa4973c85b15299b9e91633c00c926a265706f7765725100e2ec5feec408b63f8f765b56f36f2a986a7075626c69635f6b65795841041691865a9fc99f8fd3bbc782337ffbddfd698fdf90347e8b7e17369baac381a0bf79510bea98c313af91778477d3c26d1fb21a9388d46f9c422b013b5a145a02a265706f7765725100ad363ad866db295a0e261113392461176a7075626c69635f6b657958410443ea92679151790e0864ec6349ad5d02e1b0540c04807de8c987da4d678089a456882ddfc884122288413a519b991954700d78432f8e830ea85747d6b313b785a265706f7765725100bf5c2b3032d672331f48c06fa88ea52d6a7075626c69635f6b6579584104837bc4a25a552f28beb82287674433a33354bb6c75f1b39ddbd3b553ce6fdcf4e270f933dc532d6b2c0ff34797cf9bb0d1f0a75b44017521132caf3bde7f2ed1a265706f776572510090288e0b0b66da00393724c1552d9acf6a7075626c69635f6b65795841047c4ab7ccc6f0b545a9be47a001de9e4f6bc3ef106d52ad97b1e242805009d89299e0534cd38040f0b38650219012aa96e0ba6686deaa1df7a71b07a2b38f6879a265706f776572510074551b9664b0fc66f17b34f9b90c6e926a7075626c69635f6b6579584104f05352e3e6ac2ab0c1298706964b05e0f1f81c81ed23d0ac6da795c45d67e28f322bbc85ad3cb5bc283b6b05a86d8b0898764f2d81a1a88210f99692334004e96b706f7765725f7363616c65006f6e6574776f726b5f76657273696f6e157365616d5f7065726d697373696f6e5f6d6f6465a1646d6f64656c756e72657374726963746564736970635f636f6e7472616374735f6f776e6572782a307830303030303030303030303030303030303030303030303030303030303030303030303030303030 \ No newline at end of file +ab63697063a16767617465776179a4697375626e65745f6964821b816f47d1e720d46080736d616a6f726974795f70657263656e74616765184776626f74746f6d5f75705f636865636b5f706572696f641b373b241b60288031776163746976655f76616c696461746f72735f6c696d697402686163636f756e747383a2646d657461a1674163636f756e74a1656f776e65725501ab4b11a63da573a968cefdcf54f67f59656838836762616c616e636540a2646d657461a1674163636f756e74a1656f776e657256040a2ac9b75d23b88f87379b2c873096cb0a5a5916376762616c616e636551007730724fc9960791c3cc42f0622dddc4a2646d657461a1674163636f756e74a1656f776e65725501de795fb38244abf232203bf0d631faaf93d044d86762616c616e63655100be4bc447f38b3cf7bfbeb8318a60ef7868626173655f6665654900f01f6c5469219bc668636861696e5f6964f66974696d657374616d701bd5d4c237928cb4466a636861696e5f6e616d65606a76616c696461746f727386a265706f77657251005c0d76e3f92f61b5d7b41f7cadbbb3fe6a7075626c69635f6b65795841046657731cfa7598b71db73c30d421397348b11855ef609858fb3fb6015f6ea21c760d378820bfbd06d3e9d96ba7081cf96ec960d44d1d1566b5852f13d4d1fe80a265706f776572510046af9b969cb119ea349cfeb7bcc43d6b6a7075626c69635f6b657958410436da1337304f41ceaf09c3cf908b6b5be7c97f22cd190b6043f8b4d2261eb22b938bf958aa9c22fb9ff02e44d58171caf64a039855a363e78bed5283ec55ed8aa265706f7765725100f582ba6fcffcf75920a71eee4fbe167d6a7075626c69635f6b65795841047429875cee5d219f38eebae8d65d5097dc53eb44312923692900bff1d11bee9327164a72119f6d2983ee4e337a94b4e07464705f1142014f194f411194bb8e81a265706f7765724900a95fb01ae9fb2bf56a7075626c69635f6b6579584104fcf23c5e3ab5918de6f7d4cf67c5984fe66033561205bda6b9c989c237bdbe3deb0911b9e387bb117cd295dd2be5f1ae75ebe5d6cacbdf18450d3161a4b38680a265706f7765725100ffffffffffffffff669c0cfc416a900e6a7075626c69635f6b6579584104c81d106d2bafa195ac7361f51f08660b3f08f19da481d383ccef5e66a4198c722971d18d73ca2cb1dadfd418b9aafa7e318b8de8cb3981ebaeb64d6b58951d1fa265706f7765725100d0fa9f65b0c384b5a78da5f1b384d4d56a7075626c69635f6b6579584104f7919a7af919638900477da71da2223722157852a40023da1887b6458c21f2c7012493e2f1ec92b214eff21ac44c2713f41472d2192cccf3f8377d00a210bf536b706f7765725f7363616c65006f6e6574776f726b5f76657273696f6e157365616d5f7065726d697373696f6e5f6d6f6465a1646d6f64656c756e72657374726963746564736970635f636f6e7472616374735f6f776e6572782a307834333433343334333433343334333433343334333433343334333433343334333433343334333433 \ No newline at end of file diff --git a/fendermint/vm/genesis/golden/genesis/cbor/genesis.txt b/fendermint/vm/genesis/golden/genesis/cbor/genesis.txt index 61f2dbfb46..2969c8b3f2 100644 --- a/fendermint/vm/genesis/golden/genesis/cbor/genesis.txt +++ b/fendermint/vm/genesis/golden/genesis/cbor/genesis.txt @@ -1 +1 @@ -Genesis { chain_name: "", chain_id: 101, timestamp: Timestamp(13251228218958232054), network_version: NetworkVersion(21), base_fee: TokenAmount(7.370780716479075589), power_scale: 0, validators: [Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [35734683, 19512417, 10085551, 41976984, 18230282, 33368942, 47276615, 63840745, 56416749, 795943], magnitude: 1, normalized: true }, y: Field { n: [49745655, 10949268, 59649360, 17022813, 26451393, 52335251, 51377097, 61725653, 65692547, 3119481], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(322856552237206793804.031172885867638528)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [64807261, 20196680, 30808902, 39383843, 9452529, 61973536, 20363137, 24339644, 28408836, 1827850], magnitude: 1, normalized: true }, y: Field { n: [7580682, 50230969, 16256362, 10664232, 40812639, 58633269, 40528359, 53599714, 3786348, 1134334], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(0.0)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [60592866, 906575, 52049096, 13855530, 25648737, 31581541, 46764640, 56915208, 35166126, 3714334], magnitude: 1, normalized: true }, y: Field { n: [47421240, 36300716, 10575516, 30168908, 46275268, 34569680, 41679182, 41705458, 30397179, 1289218], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(115789077268461795555.786014125701411158)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [50480390, 25728551, 41141961, 10254748, 20391210, 15965147, 20648747, 21445081, 27605494, 453298], magnitude: 1, normalized: true }, y: Field { n: [51494, 61102287, 22190521, 30351724, 14678601, 26388297, 53877349, 3481505, 60590803, 2217766], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(340282366920938463455.730169729109531725)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [46367136, 30254826, 65583073, 58605777, 33384847, 14679799, 62683171, 41828174, 39493577, 369761], magnitude: 1, normalized: true }, y: Field { n: [34888194, 46157526, 49923106, 38675281, 18854426, 32829595, 18315335, 51138238, 17558168, 3137108], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(301632854851889825874.9190748556565654)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [58755492, 32936793, 64916632, 3150337, 48345172, 40589120, 38716980, 31733793, 40341841, 1112740], magnitude: 1, normalized: true }, y: Field { n: [51623813, 30537132, 3205765, 17612346, 7343480, 48645717, 1287449, 4753953, 31443076, 1417739], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(230238020826023862616.805863604753817879)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [40885492, 15553779, 54123965, 28432326, 53695675, 30477544, 58861686, 12362490, 10639957, 2154225], magnitude: 1, normalized: true }, y: Field { n: [41889489, 53202679, 55710002, 23924741, 13758631, 32761580, 16725113, 11906224, 20175955, 3710014], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(254361114468202393482.250430575417402669)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [645266, 9478164, 47807262, 4306250, 57394159, 7841683, 65305088, 47519398, 63751920, 2036397], magnitude: 1, normalized: true }, y: Field { n: [59730041, 46262444, 31423089, 35355304, 48282214, 305829, 6619673, 17023694, 55366528, 2521108], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(191619404244571460019.048908483311016655)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [23585423, 31813911, 17483482, 34059407, 15857692, 26395000, 43544681, 11191044, 48490156, 3937492], magnitude: 1, normalized: true }, y: Field { n: [54527209, 40215692, 42475791, 11929222, 9991759, 35349186, 62304346, 47640736, 8760636, 821999], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(154632352284471841201.727616896507604626)) }], accounts: [Actor { meta: Multisig(Multisig { signers: [SignerAddr(Address("f1gah2bchghpdsqtdyq2mg6dalgkslrjbrtgaesia")), SignerAddr(Address("f1oeu5mzvo7rayrd5hqsf4itmh4khcmj7eji5xhya")), SignerAddr(Address("f1hffoefmwps4izx7takgrks5vfxr55sbif4ba23a")), SignerAddr(Address("f1bhjpfiigr7wgfyxbya4qdooqtyh3nip3elwgtmq")), SignerAddr(Address("f1d777m7cn3psapsw6cxcukxabnqljvadnlox6opq"))], threshold: 2, vesting_duration: 2650998388208949078, vesting_start: 9108833960500375379 }), balance: TokenAmount(0.0) }, Actor { meta: Multisig(Multisig { signers: [SignerAddr(Address("f1mwitelzn3asco2olwhzfy7vhegu2plyw5jmez7a")), SignerAddr(Address("f1gq2pni5nvj3khteo5ni6n3hyc5bcksoziofrtza")), SignerAddr(Address("f1pthn54h7ebpltxubuumvyyisesde7yvpvgg7qwi")), SignerAddr(Address("f1oof73dx46icrrrorrfvcytn7ljctf4rtv3lemxa")), SignerAddr(Address("f1r3opfmeiqyfm4ljekh6nvgumzt2rkxtpsugo5by"))], threshold: 3, vesting_duration: 4383156514696692399, vesting_start: 8627725319853246975 }), balance: TokenAmount(205774209073450626808.321982400535204043) }], eam_permission_mode: Unrestricted, ipc: Some(IpcParams { gateway: GatewayParams { subnet_id: SubnetID { root: 9136288813687660255, children: [Address("f015002037678599764495"), Address("f011650084465412981644"), Address("f410fcszug2y44uksnhioafywkepldykkr5nsgp42gai")] }, bottom_up_check_period: 17127693403555613195, majority_percentage: 64, active_validators_limit: 15 } }), ipc_contracts_owner: 0x0000000000000000000000000000000000000000, f3: None } \ No newline at end of file +Genesis { chain_name: "", chain_id: None, timestamp: Timestamp(15408153769078993990), network_version: NetworkVersion(21), base_fee: TokenAmount(17.302667403177597894), power_scale: 0, validators: [Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [57582108, 65896535, 25530291, 22527362, 55095576, 17321564, 57918221, 40033398, 52230773, 1676764], magnitude: 1, normalized: true }, y: Field { n: [13762176, 21742837, 22440792, 55653492, 24037728, 29493054, 43882170, 49552207, 59252927, 1934157], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(122358886857618383084.425213723209872382)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [35566123, 36517001, 11928639, 9122916, 65522047, 2284246, 10239225, 17251004, 53948495, 898692], magnitude: 1, normalized: true }, y: Field { n: [5631370, 55877883, 37648574, 39933581, 49695235, 23092338, 50521165, 9170559, 22588060, 2417406], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(93957767361394412868.518672058372341099)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [18607763, 3144820, 37130896, 17876132, 64771051, 26694693, 49000077, 8813795, 56421981, 1903201], magnitude: 1, normalized: true }, y: Field { n: [12291713, 63980645, 1372564, 24921352, 7627888, 44379448, 48554807, 28616207, 41030047, 640402], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(326339638945795449155.377711843387512445)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [62766653, 40005773, 64646044, 22562838, 65429555, 32597523, 58543350, 38156187, 6175413, 4144271], magnitude: 1, normalized: true }, y: Field { n: [11765376, 55335017, 32605264, 56306479, 41282533, 49904747, 19488210, 49038835, 28959623, 3850820], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(12.204667144838523893)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [1674354, 64461225, 20462798, 41325063, 54462705, 63052162, 53878609, 42358449, 7154607, 3278660], magnitude: 1, normalized: true }, y: Field { n: [9772319, 26434262, 2013931, 61025510, 36801421, 40550047, 33374603, 11716459, 26047434, 679028], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(340282366920938463452.321662323862966286)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [2224839, 32346467, 37593480, 21663744, 52565368, 57182349, 7854705, 26092545, 41613593, 4056166], magnitude: 1, normalized: true }, y: Field { n: [34651987, 31408168, 13582211, 55076019, 66327666, 18024900, 50274732, 38455379, 65204716, 18724], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(277780730297773331994.111486734304072917)) }], accounts: [Actor { meta: Account(Account { owner: SignerAddr(Address("f1vnfrdjr5uvz2s2go7xhvj5t7lfswqoedua5rbqa")) }), balance: TokenAmount(0.0) }, Actor { meta: Account(Account { owner: SignerAddr(Address("f410ffle3oxjdxchyon43fsdtbfwlbjnfsfrxblezpty")) }), balance: TokenAmount(158429680263722177111.518053171922329028) }, Actor { meta: Account(Account { owner: SignerAddr(Address("f13z4v7m4cisv7emrahpynmmp2v6j5argy2mzh7va")) }), balance: TokenAmount(252946722516379168151.698918250941312888) }], eam_permission_mode: Unrestricted, ipc: Some(IpcParams { gateway: GatewayParams { subnet_id: SubnetID { root: 9326752320159011936, children: [] }, bottom_up_check_period: 3979814395707949105, majority_percentage: 71, active_validators_limit: 2 } }), ipc_contracts_owner: 0x4343434343434343434343434343434343434343, f3: None } \ No newline at end of file diff --git a/fendermint/vm/genesis/golden/genesis/json/genesis.json b/fendermint/vm/genesis/golden/genesis/json/genesis.json index 4d642a5102..9ac34e8862 100644 --- a/fendermint/vm/genesis/golden/genesis/json/genesis.json +++ b/fendermint/vm/genesis/golden/genesis/json/genesis.json @@ -1,40 +1,68 @@ { - "chain_name": "U", - "chain_id": 101, - "timestamp": 12982167733895342124, + "chain_name": "⮧\t§LN", + "chain_id": null, + "timestamp": 9166950190161876321, "network_version": 21, - "base_fee": "20131727983098262839895554089832955311", - "power_scale": 3, + "base_fee": "92132748893613804737712518769207785210", + "power_scale": 0, "validators": [ { - "public_key": "BH4dgpBqtkh6sKhMXelu7LKYN2nb1UJSX6ZCYGPmtmsCM9y80iAUMj5wu4Yiu952Dv3Oq2Rrbt9h45EgRmHkFSc=", - "power": "40223257309125237738285695934834340379" + "public_key": "BJ2ICxPgqIpgPxTkWHeENqdDxgMukcg/4a1l8CH51rIabdszVkESNJQ61+NUNq2BqKn8jLA+pRRR+Lng3IODoq0=", + "power": "119680730293037942857664149531788261571" }, { - "public_key": "BBPbyO1PiFQ7AoLdOYNViHBJ+EF6FuhOYGAOTygqwuvdOt960J5TvCIjwX2UIm2vPm+9ILZarBWcTFXz8rQG9bQ=", - "power": "19706769749739782860581433033120308803" + "public_key": "BFChHN77vnsbUNg4/l06mVTfF6qvbV5kaqV2btTLpPUL9mBsgEKwGPlQJuWBAYBv6G9VPhwX7lrztjpayy7jhEk=", + "power": "35254282891067121059299756507316764434" + } + ], + "accounts": [ + { + "meta": { + "Account": { + "owner": "f410flavvsjwlqczzuapnpzwuo7ftq32nvr6kyrzvnna" + } + }, + "balance": "197011208717819580134214681012943466690" }, { - "public_key": "BGB9f7R3VPpLiWszpGgbG+BLbtllYDg1YGq0RMqmv9xhBz7RG6WVOSCmUem+TAVQkIX2tPT7ZcZF+FLkCoNHhOc=", - "power": "260894447470586868017041531430614024483" + "meta": { + "Account": { + "owner": "f410f4pd4avs4gwsdwh5cqgvwg7bhzk4gkyotlez27aa" + } + }, + "balance": "102137686532253315554542634423215460983" }, { - "public_key": "BA0y6qtkjqTG5IdQDRNT3yHZx4WI+6Ua+LFYuXdfczM/ocBhjNJQmQVpCjDvuwHoBPD0hzK4KxtdJe+lPa7a3hU=", - "power": "178349749897518691979494799247727256537" + "meta": { + "Account": { + "owner": "f410fk6imdbvngrp4vzmvb4g7k3vt4q3ivds72dm4yvq" + } + }, + "balance": "153374264530312302270000858553190959779" }, { - "public_key": "BPhrBPJqcNmmxgGlHirO5Tt03wYXcUKlHJVYuqdWzNew6RQby1tcXpqwyFxSazNt6cOQ0UNjXBa2RAnw9CWJieY=", - "power": "135063245844052113159013998821298664415" - } - ], - "accounts": [ + "meta": { + "Multisig": { + "signers": [ + "f1caopsapwuhdielvfbshr3tk3zhrtn7h4de6ubty", + "f1fi2ofihi7ynbrkshlnqnmg4fw5v7ftbjmq6xtsq", + "f1ozfuvcwlf2e3dznoeq23mag3xjdfmd3vw2sn2ri", + "f1u453i6v24omn6yffy75z4c6fs6p2fiwczt5rdui" + ], + "threshold": 2, + "vesting_duration": 3306299543311472807, + "vesting_start": 14249729871702503296 + } + }, + "balance": "309092999959441721106188818455929111123" + }, { "meta": { "Account": { - "owner": "f410fkutwqjirqgadmxm43p2myoi5m3gurnulc4uvrti" + "owner": "f1brtfivrytkaf6jnkj2bejpwdeausws6jnenpn6i" } }, - "balance": "68216112094381133411177903949584186607" + "balance": "268168639178429380690207903639563038123" } ], "eam_permission_mode": { @@ -42,11 +70,11 @@ }, "ipc": { "gateway": { - "subnet_id": "/r16315642738389104412", - "bottom_up_check_period": 11339269183869879227, - "majority_percentage": 73, - "active_validators_limit": 63 + "subnet_id": "/r7209731025411039793/f00", + "bottom_up_check_period": 1, + "majority_percentage": 59, + "active_validators_limit": 67 } }, - "ipc_contracts_owner": "0x8989898989898989898989898989898989898989" + "ipc_contracts_owner": "0x9191919191919191919191919191919191919191" } \ No newline at end of file diff --git a/fendermint/vm/genesis/golden/genesis/json/genesis.txt b/fendermint/vm/genesis/golden/genesis/json/genesis.txt index f37522ab4d..d43116ff43 100644 --- a/fendermint/vm/genesis/golden/genesis/json/genesis.txt +++ b/fendermint/vm/genesis/golden/genesis/json/genesis.txt @@ -1 +1 @@ -Genesis { chain_name: "U", chain_id: 101, timestamp: Timestamp(12982167733895342124), network_version: NetworkVersion(21), base_fee: TokenAmount(20131727983098262839.895554089832955311), power_scale: 3, validators: [Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [45509378, 9967865, 19266148, 57627913, 43530089, 39566124, 42255838, 19000002, 43018934, 2066272], magnitude: 1, normalized: true }, y: Field { n: [31724839, 4723096, 32906809, 26324411, 50187947, 49782147, 62415403, 13171138, 13770772, 849711], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(40223257309125237738.285695934834340379)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [46328797, 60017162, 15074816, 32005025, 4847681, 13984284, 3003288, 22080522, 15552392, 325362], magnitude: 1, normalized: true }, y: Field { n: [456116, 24968365, 22660293, 47803056, 40877344, 10185679, 1562946, 49318031, 47226451, 964574], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(19706769749739782860.581433033120308803)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [46128225, 17904297, 55969451, 26575072, 4943577, 33998584, 45300294, 65613349, 62158676, 1580895], magnitude: 1, normalized: true }, y: Field { n: [55018727, 12124832, 6578053, 64220567, 8779444, 50418724, 18783204, 14975641, 18589077, 118708], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(260894447470586868017.041531430614024483)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [57881407, 36593111, 28281621, 35909268, 31049605, 13957064, 7667921, 43195282, 44786830, 216250], magnitude: 1, normalized: true }, y: Field { n: [47898133, 65621867, 28693086, 13295788, 15791239, 46168577, 10686203, 40113572, 26006096, 2650136], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(178349749897518691979.494799247727256537)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [46978992, 36612565, 38914389, 6145290, 57990918, 45332814, 1724898, 57056024, 15886960, 4070081], magnitude: 1, normalized: true }, y: Field { n: [25790950, 41696521, 23815232, 17665392, 29593809, 46979962, 8766758, 24799939, 63658844, 3818758], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(135063245844052113159.013998821298664415)) }], accounts: [Actor { meta: Account(Account { owner: SignerAddr(Address("f410fkutwqjirqgadmxm43p2myoi5m3gurnulc4uvrti")) }), balance: TokenAmount(68216112094381133411.177903949584186607) }], eam_permission_mode: Unrestricted, ipc: Some(IpcParams { gateway: GatewayParams { subnet_id: SubnetID { root: 16315642738389104412, children: [] }, bottom_up_check_period: 11339269183869879227, majority_percentage: 73, active_validators_limit: 63 } }), ipc_contracts_owner: 0x8989898989898989898989898989898989898989, f3: None } \ No newline at end of file +Genesis { chain_name: "⮧\t§LN", chain_id: None, timestamp: Timestamp(9166950190161876321), network_version: NetworkVersion(21), base_fee: TokenAmount(92132748893613804737.71251876920778521), power_scale: 0, validators: [Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [30847514, 24905854, 66984662, 12207904, 54773251, 31526313, 21906823, 36274428, 51634344, 2580994], magnitude: 1, normalized: true }, y: Field { n: [58958509, 41432864, 21307275, 46201492, 11140236, 28008554, 25048387, 13783275, 55984402, 1799884], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(119680730293037942857.664149531788261571)) }, Validator { public_key: ValidatorKey(PublicKey(Affine { x: Field { n: [61142283, 26981682, 38185559, 45987193, 14620586, 55486037, 25399269, 32271683, 14613438, 1321031], magnitude: 1, normalized: true }, y: Field { n: [48464969, 43430603, 28261219, 7364537, 7296318, 6298618, 40785936, 6546752, 8405680, 4036635], magnitude: 1, normalized: true }, infinity: false })), power: Collateral(TokenAmount(35254282891067121059.299756507316764434)) }], accounts: [Actor { meta: Account(Account { owner: SignerAddr(Address("f410flavvsjwlqczzuapnpzwuo7ftq32nvr6kyrzvnna")) }), balance: TokenAmount(197011208717819580134.21468101294346669) }, Actor { meta: Account(Account { owner: SignerAddr(Address("f410f4pd4avs4gwsdwh5cqgvwg7bhzk4gkyotlez27aa")) }), balance: TokenAmount(102137686532253315554.542634423215460983) }, Actor { meta: Account(Account { owner: SignerAddr(Address("f410fk6imdbvngrp4vzmvb4g7k3vt4q3ivds72dm4yvq")) }), balance: TokenAmount(153374264530312302270.000858553190959779) }, Actor { meta: Multisig(Multisig { signers: [SignerAddr(Address("f1caopsapwuhdielvfbshr3tk3zhrtn7h4de6ubty")), SignerAddr(Address("f1fi2ofihi7ynbrkshlnqnmg4fw5v7ftbjmq6xtsq")), SignerAddr(Address("f1ozfuvcwlf2e3dznoeq23mag3xjdfmd3vw2sn2ri")), SignerAddr(Address("f1u453i6v24omn6yffy75z4c6fs6p2fiwczt5rdui"))], threshold: 2, vesting_duration: 3306299543311472807, vesting_start: 14249729871702503296 }), balance: TokenAmount(309092999959441721106.188818455929111123) }, Actor { meta: Account(Account { owner: SignerAddr(Address("f1brtfivrytkaf6jnkj2bejpwdeausws6jnenpn6i")) }), balance: TokenAmount(268168639178429380690.207903639563038123) }], eam_permission_mode: Unrestricted, ipc: Some(IpcParams { gateway: GatewayParams { subnet_id: SubnetID { root: 7209731025411039793, children: [Address("f00")] }, bottom_up_check_period: 1, majority_percentage: 59, active_validators_limit: 67 } }), ipc_contracts_owner: 0x9191919191919191919191919191919191919191, f3: None } \ No newline at end of file diff --git a/fendermint/vm/interpreter/Cargo.toml b/fendermint/vm/interpreter/Cargo.toml index b364e3c5f0..3ecfcbb9ea 100644 --- a/fendermint/vm/interpreter/Cargo.toml +++ b/fendermint/vm/interpreter/Cargo.toml @@ -18,6 +18,7 @@ fendermint_vm_genesis = { path = "../genesis" } fendermint_vm_message = { path = "../message" } fendermint_vm_resolver = { path = "../resolver" } fendermint_vm_topdown = { path = "../topdown" } +fendermint_vm_topdown_proof_service = { path = "../topdown/proof-service" } fendermint_crypto = { path = "../../crypto" } fendermint_eth_hardhat = { path = "../../eth/hardhat" } fendermint_eth_deployer = { path = "../../eth/deployer" } @@ -39,6 +40,7 @@ ipc-observability = { path = "../../../ipc/observability" } async-trait = { workspace = true } async-stm = { workspace = true } anyhow = { workspace = true } +arc-swap = "1.6" base64 = { workspace = true } ethers = { workspace = true } hex = { workspace = true } @@ -75,6 +77,7 @@ quickcheck = { workspace = true, optional = true } rand = { workspace = true, optional = true } merkle-tree-rs = { path = "../../../ext/merkle-tree-rs" } +proofs = { git = "https://github.com/consensus-shipyard/ipc-filecoin-proofs", branch = "proofs" } [dev-dependencies] quickcheck = { workspace = true } diff --git a/fendermint/vm/interpreter/src/fvm/event_extraction.rs b/fendermint/vm/interpreter/src/fvm/event_extraction.rs new file mode 100644 index 0000000000..f2f3422936 --- /dev/null +++ b/fendermint/vm/interpreter/src/fvm/event_extraction.rs @@ -0,0 +1,183 @@ +// Copyright 2022-2024 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT +//! Event extraction from F3 proof bundles +//! +//! This module provides functionality to extract and decode events from proof bundles, +//! including topdown messages and validator change events. + +use anyhow::{anyhow, Context, Result}; +use ethers::abi::{RawLog}; +use ethers::contract::EthLogDecode; +use ethers::types as et; +use ipc_actors_abis::{lib_gateway, lib_power_change_log}; +use ipc_api::cross::IpcEnvelope; +use ipc_api::staking::PowerChangeRequest; +use proofs::proofs::common::bundle::UnifiedProofBundle; +use tracing::{debug, trace}; + +/// Extract topdown messages from a proof bundle +/// +/// This function iterates through event proofs in the bundle and extracts +/// NewTopDownMessage events by: +/// 1. Finding events matching the signature +/// 2. Decoding the IpcEnvelope from the event data using contract bindings +/// 3. Returning all extracted messages +pub fn extract_topdown_messages( + proof_bundle: &UnifiedProofBundle, +) -> Result> { + let mut messages = Vec::new(); + + for event_proof in &proof_bundle.event_proofs { + let event_log = extract_event_from_proof(event_proof)?; + + // Try to decode as NewTopDownMessage event + if let Ok(event) = decode_topdown_message_event(&event_log) { + trace!( + emitter = event_log.emitter, + subnet = ?event.subnet, + "Found NewTopDownMessage event" + ); + + // Convert from contract binding type to IPC type + let envelope = IpcEnvelope::try_from(event.message) + .context("Failed to convert gateway IpcEnvelope to IPC IpcEnvelope")?; + messages.push(envelope); + } + } + + debug!( + message_count = messages.len(), + "Extracted topdown messages from proof bundle" + ); + + Ok(messages) +} + +/// Extract validator changes from a proof bundle +/// +/// This function iterates through event proofs and extracts +/// NewPowerChangeRequest events by: +/// 1. Finding events matching the signature +/// 2. Decoding the PowerChangeRequest from the event data using contract bindings +/// 3. Returning all extracted changes +pub fn extract_validator_changes( + proof_bundle: &UnifiedProofBundle, +) -> Result> { + let mut changes = Vec::new(); + + for event_proof in &proof_bundle.event_proofs { + let event_log = extract_event_from_proof(event_proof)?; + + // Try to decode as NewPowerChangeRequest event + if let Ok(event) = decode_power_change_event(&event_log) { + trace!( + emitter = event_log.emitter, + validator = ?event.validator, + op = event.op, + "Found NewPowerChangeRequest event" + ); + + // Convert to PowerChangeRequest + let change_request = PowerChangeRequest::try_from(event) + .context("Failed to convert power change event to PowerChangeRequest")?; + changes.push(change_request); + } + } + + debug!( + change_count = changes.len(), + "Extracted validator changes from proof bundle" + ); + + Ok(changes) +} + +/// Extract events from a single event proof +/// +/// The EventProof contains EventData which includes: +/// - emitter: actor ID that emitted the event +/// - topics: hex-encoded topics (event signature, indexed params) +/// - data: hex-encoded event data (often ABI encoded for cross-chain) +fn extract_event_from_proof(event_proof: &proofs::proofs::events::bundle::EventProof) -> Result { + // Convert hex-encoded topics to H256 + let topics: Result> = event_proof + .event_data + .topics + .iter() + .map(|topic| { + // Remove 0x prefix if present and parse hex + let topic_str = topic.trim_start_matches("0x"); + let bytes = hex::decode(topic_str) + .context(format!("Failed to decode topic hex: {}", topic))?; + + if bytes.len() != 32 { + return Err(anyhow!("Topic must be 32 bytes, got {} bytes", bytes.len())); + } + + Ok(et::H256::from_slice(&bytes)) + }) + .collect(); + + let topics = topics?; + + // Convert hex-encoded data + let data_str = event_proof.event_data.data.trim_start_matches("0x"); + let data = hex::decode(data_str) + .context(format!("Failed to decode event data hex: {}", event_proof.event_data.data))?; + + Ok(EventLog { + emitter: event_proof.event_data.emitter, + topics, + data, + }) +} + +/// Helper struct to represent an event log +#[derive(Debug, Clone)] +struct EventLog { + emitter: u64, + topics: Vec, + data: Vec, +} + +/// Decode a NewTopDownMessage event using the contract bindings +fn decode_topdown_message_event(event_log: &EventLog) -> Result { + // Create RawLog from our EventLog + let raw_log = RawLog { + topics: event_log.topics.clone(), + data: event_log.data.clone(), + }; + + // Use the contract binding's decoding + lib_gateway::NewTopDownMessageFilter::decode_log(&raw_log) + .map_err(|e| anyhow!("Failed to decode NewTopDownMessage event: {}", e)) +} + +/// Decode a NewPowerChangeRequest event using the contract bindings +fn decode_power_change_event(event_log: &EventLog) -> Result { + // Create RawLog from our EventLog + let raw_log = RawLog { + topics: event_log.topics.clone(), + data: event_log.data.clone(), + }; + + // Use the contract binding's decoding + lib_power_change_log::NewPowerChangeRequestFilter::decode_log(&raw_log) + .map_err(|e| anyhow!("Failed to decode NewPowerChangeRequest event: {}", e)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_event_signature_generation() { + // Test that we can create event logs and decode them properly + // This would require mock data, so we just verify compilation + let _log = EventLog { + emitter: 0, + topics: vec![], + data: vec![], + }; + } +} \ No newline at end of file diff --git a/fendermint/vm/interpreter/src/fvm/f3.rs b/fendermint/vm/interpreter/src/fvm/f3.rs new file mode 100644 index 0000000000..f0f76117b0 --- /dev/null +++ b/fendermint/vm/interpreter/src/fvm/f3.rs @@ -0,0 +1,382 @@ +// Copyright 2022-2024 Protocol Labs +// SPDX-License-Identifier: Apache-2.0, MIT + +use anyhow::{bail, Context}; +use fendermint_vm_message::chain::ChainMessage; +use fendermint_vm_message::ipc::GeneralisedTopDown; +use fvm_ipld_blockstore::Blockstore; +use ipc_api::cross::IpcEnvelope; +use ipc_api::staking::PowerChangeRequest; +use std::sync::Arc; + +use crate::fvm::state::ipc::F3LightClientCaller; +use crate::fvm::state::FvmExecState; + +/// F3 finality handler - handles all F3 proof-based finality logic +/// This module encapsulates all F3-specific concerns, keeping TopDownManager clean +#[derive(Clone)] +pub struct F3FinalityHandler { + /// Proof cache for F3-based parent finality + proof_cache: Arc, + /// Subnet ID for ProofVerifier (needed for event filtering) + subnet_id: String, + /// F3 Light Client caller for querying F3 state + f3_light_client_caller: F3LightClientCaller, +} + +impl F3FinalityHandler { + pub fn new( + proof_cache: Arc, + subnet_id: String, + ) -> Self { + Self { + proof_cache, + subnet_id, + f3_light_client_caller: F3LightClientCaller::new(), + } + } + + /// Get reference to the proof cache + pub fn proof_cache(&self) -> &Arc { + &self.proof_cache + } + + /// Check if we have a certificate in our local cache + /// Used during attestation to avoid redundant F3 validation + pub fn has_certificate_in_cache(&self, instance_id: u64) -> bool { + self.proof_cache.contains_certificate(instance_id) + } + + /// Query proof cache for next uncommitted proof and create a chain message with proof bundle. + /// + /// This is the v2 proof-based approach that replaces voting with cryptographic verification. + /// + /// Returns `None` if: + /// - No proof available for next height + /// - Cache is temporarily empty (graceful degradation) + pub fn chain_message_from_proof_cache(&self) -> Option { + // Get next uncommitted proof (epoch after last_committed) + let entry = self.proof_cache.get_next_uncommitted()?; + + // Convert FinalityCertificate to get finalized epochs + let finalized_epochs: Vec = entry + .certificate + .ec_chain + .iter() + .map(|ts| ts.epoch) + .collect(); + + tracing::debug!( + instance_id = entry.certificate.gpbft_instance, + epoch = entry.epoch, + epochs = ?finalized_epochs, + "found proof in cache for proposal" + ); + + // Convert FinalityCertificate to SerializableF3Certificate for message + let serializable_cert = + fendermint_vm_topdown_proof_service::types::SerializableF3Certificate::from( + &entry.certificate, + ); + + Some(ChainMessage::Ipc( + fendermint_vm_message::ipc::IpcMessage::GeneralisedTopDown(GeneralisedTopDown { + height: entry.epoch, + certificate: fendermint_vm_message::ipc::Certificate::FilecoinF3(serializable_cert), + }), + )) + } + + /// Attest a generalised top-down message during the attestation phase. + /// + /// This checks the certificate validity: + /// 1. Get proof bundle from local cache using certificate instance ID + /// 2. Check if certificate is in local cache (if yes, we're done - already validated) + /// 3. If not in cache, verify certificate with F3 client and verify proof bundle + /// + /// All correct validators will reach the same decision (deterministic). + /// Attestation must complete here, not defer to execution phase. + pub async fn attest(&self, msg: &GeneralisedTopDown) -> anyhow::Result<()> { + // Extract certificate from message + let certificate = match &msg.certificate { + fendermint_vm_message::ipc::Certificate::FilecoinF3(cert) => cert, + }; + + // Get proof bundle from local cache using certificate instance ID + let proof_bundle = { + // Get the epoch proof entry from cache + let entry = self + .proof_cache + .get_epoch_proof_with_certificate(msg.height) + .ok_or_else(|| { + anyhow::anyhow!( + "proof bundle not found in local cache for height {}", + msg.height + ) + })?; + + // Verify the certificate instance matches + if entry.certificate.gpbft_instance != certificate.gpbft_instance { + bail!( + "Certificate instance mismatch: message has {}, cache has {}", + certificate.gpbft_instance, + entry.certificate.gpbft_instance + ); + } + + entry.proof_bundle.clone() + }; + + // STEP 1: Check if certificate is in local cache + let instance_id = certificate.gpbft_instance; + if self.proof_cache.contains_certificate(instance_id) { + tracing::debug!( + instance = instance_id, + "Certificate found in local cache - already validated by our F3 client" + ); + return Ok(()); + } + + // STEP 2: Certificate not in cache - need to verify + // Convert SerializableF3Certificate to FinalityCertificate for verification + let finality_cert = certificate + .clone() + .try_into_certificate() + .context("failed to convert SerializableF3Certificate to FinalityCertificate")?; + + // Create EpochProofWithCertificate for verification + use fendermint_vm_topdown_proof_service::types::FinalizedTipsets; + let finalized_tipsets = FinalizedTipsets::from(&finality_cert.ec_chain); + + let epoch_proof = fendermint_vm_topdown_proof_service::types::EpochProofWithCertificate { + epoch: msg.height, + proof_bundle: proof_bundle.clone(), + certificate: finality_cert.clone(), + finalized_tipsets, + }; + + // STEP 3: Verify proof bundle using ProofVerifier + use fendermint_vm_topdown_proof_service::verifier::ProofVerifier; + let verifier = ProofVerifier::new(self.subnet_id.clone()); + verifier + .verify_epoch_proof(&epoch_proof) + .context("proof bundle verification failed")?; + + tracing::debug!( + instance = certificate.gpbft_instance, + height = msg.height, + "Proof bundle verified successfully (certificate not in cache)" + ); + + // Note: Full F3 certificate chain continuity validation happens during execution + // when we have state access to query F3LightClientActor + + Ok(()) + } + + /// Get proof bundle for a given height from cache + pub fn get_proof_bundle( + &self, + height: fvm_shared::clock::ChainEpoch, + expected_instance_id: u64, + ) -> anyhow::Result { + let entry = self + .proof_cache + .get_epoch_proof_with_certificate(height) + .ok_or_else(|| { + anyhow::anyhow!( + "proof bundle not found in local cache for height {}", + height + ) + })?; + + // Verify the certificate instance matches + if entry.certificate.gpbft_instance != expected_instance_id { + bail!( + "Certificate instance mismatch: expected {}, cache has {}", + expected_instance_id, + entry.certificate.gpbft_instance + ); + } + + Ok(entry.proof_bundle) + } + + /// Extract topdown messages from proof bundle + pub fn extract_topdown_messages( + &self, + proof_bundle: &proofs::proofs::common::bundle::UnifiedProofBundle, + ) -> anyhow::Result> { + crate::fvm::event_extraction::extract_topdown_messages(proof_bundle) + } + + /// Extract validator changes from proof bundle + pub fn extract_validator_changes( + &self, + proof_bundle: &proofs::proofs::common::bundle::UnifiedProofBundle, + ) -> anyhow::Result> { + crate::fvm::event_extraction::extract_validator_changes(proof_bundle) + } + + /// Get power table for a certificate instance from cache + pub fn get_power_table( + &self, + instance_id: u64, + ) -> Vec { + if let Some(cert_entry) = self.proof_cache.get_certificate(instance_id) { + cert_entry + .power_table + .iter() + .map(|pe| { + // Convert BigInt power to u64 (saturating if too large) + let (_sign, digits) = pe.power.to_u64_digits(); + let power_u64 = if digits.is_empty() { + 0 + } else if digits.len() == 1 { + digits[0] + } else { + u64::MAX // Too large, saturate + }; + fendermint_vm_actor_interface::f3_light_client::PowerEntry { + public_key: pe.pub_key.0.clone(), + power: power_u64, + } + }) + .collect() + } else { + Vec::new() + } + } + + /// Mark epoch as committed in cache + pub fn mark_committed( + &self, + epoch: fvm_shared::clock::ChainEpoch, + instance_id: u64, + ) -> anyhow::Result<()> { + self.proof_cache + .mark_committed(epoch, instance_id) + .map_err(|e| { + anyhow::anyhow!( + "failed to mark epoch {} as committed in cache: {}", + epoch, + e + ) + }) + } + + /// Get F3 light client state + pub fn get_f3_state( + &self, + state: &mut FvmExecState, + ) -> anyhow::Result + where + DB: Blockstore + Clone + 'static + Send + Sync, + { + self.f3_light_client_caller + .get_state(state) + .context("failed to get F3 light client state") + } + + /// Update F3 light client state + pub fn update_f3_state( + &self, + state: &mut FvmExecState, + new_state: fendermint_vm_actor_interface::f3_light_client::LightClientState, + ) -> anyhow::Result<()> + where + DB: Blockstore + Clone + 'static + Send + Sync, + { + self.f3_light_client_caller + .update_state(state, new_state) + .context("failed to update F3LightClientActor state") + } + + /// Execute F3-specific logic for a generalised top-down message. + /// Returns the topdown messages and validator changes to be processed by TopDownManager. + pub fn execute( + &self, + state: &mut FvmExecState, + msg: &GeneralisedTopDown, + ) -> anyhow::Result<(Vec, Vec)> + where + DB: Blockstore + Clone + 'static + Send + Sync, + { + // Extract certificate from message + let certificate = match &msg.certificate { + fendermint_vm_message::ipc::Certificate::FilecoinF3(cert) => cert, + }; + + tracing::debug!( + instance = certificate.gpbft_instance, + height = msg.height, + "executing F3 generalised top-down" + ); + + // Step 1: Verify certificate chain continuity (check against F3LightClientActor state) + let f3_state = self.get_f3_state(state)?; + + // Ensure certificate instance is sequential + if certificate.gpbft_instance != f3_state.latest_instance_id + 1 { + bail!( + "Certificate instance ID {} is not sequential (expected {})", + certificate.gpbft_instance, + f3_state.latest_instance_id + 1 + ); + } + + // Step 2: Get proof bundle and extract topdown effects + let proof_bundle = self.get_proof_bundle(msg.height, certificate.gpbft_instance)?; + let msgs = self.extract_topdown_messages(&proof_bundle)?; + let validator_changes = self.extract_validator_changes(&proof_bundle)?; + + tracing::debug!( + message_count = msgs.len(), + validator_changes_count = validator_changes.len(), + "extracted topdown effects from proof bundle" + ); + + // Step 3: Update F3LightClientActor with new certificate state + let power_table = self.get_power_table(certificate.gpbft_instance); + let latest_finalized_height = Some( + certificate + .finalized_epochs() + .iter() + .max() + .copied() + .context("certificate has no finalized epochs")?, + ); + + let new_light_client_state = + fendermint_vm_actor_interface::f3_light_client::LightClientState { + latest_instance_id: certificate.gpbft_instance, + latest_finalized_height, + power_table, + }; + + self.update_f3_state(state, new_light_client_state)?; + + tracing::debug!( + instance = certificate.gpbft_instance, + "updated F3LightClientActor state" + ); + + // Step 4: Mark epoch as committed in cache + if let Err(e) = self.mark_committed(msg.height, certificate.gpbft_instance) { + tracing::warn!( + error = %e, + epoch = msg.height, + instance = certificate.gpbft_instance, + "failed to mark epoch as committed in cache" + ); + } else { + tracing::debug!( + epoch = msg.height, + instance = certificate.gpbft_instance, + "marked epoch as committed in cache" + ); + } + + Ok((msgs, validator_changes)) + } +} diff --git a/fendermint/vm/interpreter/src/fvm/interpreter.rs b/fendermint/vm/interpreter/src/fvm/interpreter.rs index 5a3cb5bc52..cda9c8fa76 100644 --- a/fendermint/vm/interpreter/src/fvm/interpreter.rs +++ b/fendermint/vm/interpreter/src/fvm/interpreter.rs @@ -51,7 +51,7 @@ where { end_block_manager: EndBlockManager, - top_down_manager: TopDownManager, + pub top_down_manager: TopDownManager, upgrade_scheduler: UpgradeScheduler, push_block_data_to_chainmeta_actor: bool, @@ -275,10 +275,13 @@ where .into_iter() .map(Into::into); + // Get parent finality message - TopDownManager decides internally whether to use F3 or legacy let top_down_iter = self .top_down_manager - .chain_message_from_finality_or_quorum() + .chain_message_for_proposal() .await + .into_iter() + .collect::>() .into_iter(); let mut all_msgs = top_down_iter @@ -333,8 +336,28 @@ where for msg in msgs { match fvm_ipld_encoding::from_slice::(&msg) { Ok(chain_msg) => match chain_msg { + ChainMessage::Ipc(IpcMessage::GeneralisedTopDown(ref msg)) => { + // Attest generalised top-down message (checks local cache, verifies if needed) + match self.top_down_manager.attest_generalised(msg).await { + Ok(()) => { + tracing::debug!( + height = msg.height, + "generalised top-down message attested successfully" + ); + } + Err(e) => { + tracing::warn!( + error = %e, + height = msg.height, + "proof bundle verification failed - rejecting block" + ); + return Ok(AttestMessagesResponse::Reject); + } + } + } ChainMessage::Ipc(IpcMessage::TopDownExec(finality)) => { - if !self.top_down_manager.is_finality_valid(finality).await { + // v1 voting-based finality (kept for backward compatibility) + if !self.top_down_manager.attest_legacy(finality).await { return Ok(AttestMessagesResponse::Reject); } } @@ -459,9 +482,19 @@ where }) } ChainMessage::Ipc(ipc_msg) => match ipc_msg { + IpcMessage::GeneralisedTopDown(msg) => { + let applied_message = self + .top_down_manager + .execute_generalised(state, msg) + .await?; + Ok(ApplyMessageResponse { + applied_message, + domain_hash: None, + }) + } IpcMessage::TopDownExec(p) => { - let applied_message = - self.top_down_manager.execute_topdown_msg(state, p).await?; + // OLD: v1 voting-based execution (kept for backward compatibility) + let applied_message = self.top_down_manager.execute_legacy(state, p).await?; Ok(ApplyMessageResponse { applied_message, domain_hash: None, diff --git a/fendermint/vm/interpreter/src/fvm/mod.rs b/fendermint/vm/interpreter/src/fvm/mod.rs index 762c8b696a..f3b211bbd6 100644 --- a/fendermint/vm/interpreter/src/fvm/mod.rs +++ b/fendermint/vm/interpreter/src/fvm/mod.rs @@ -4,11 +4,13 @@ pub mod constants; mod executions; mod externs; +pub mod f3; pub mod interpreter; pub mod observe; pub mod state; pub mod store; pub mod topdown; +pub use f3::F3FinalityHandler; pub mod upgrades; pub use interpreter::FvmMessagesInterpreter; @@ -17,6 +19,7 @@ pub mod bundle; pub mod activity; pub mod end_block_hook; +pub mod event_extraction; pub(crate) mod gas; pub(crate) mod gas_estimation; diff --git a/fendermint/vm/interpreter/src/fvm/state/ipc.rs b/fendermint/vm/interpreter/src/fvm/state/ipc.rs index 52f55dde81..a138497e9c 100644 --- a/fendermint/vm/interpreter/src/fvm/state/ipc.rs +++ b/fendermint/vm/interpreter/src/fvm/state/ipc.rs @@ -1,16 +1,21 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT -use anyhow::Context; +use anyhow::{bail, Context}; use fvm_ipld_blockstore::Blockstore; use fvm_shared::econ::TokenAmount; use fvm_shared::ActorID; +use num_traits::Zero; use fendermint_crypto::PublicKey; use fendermint_vm_actor_interface::ipc; use fendermint_vm_actor_interface::{ - eam::EthAddress, init::builtin_actor_eth_addr, ipc::GATEWAY_ACTOR_ID, + eam::EthAddress, + f3_light_client, + init::builtin_actor_eth_addr, + ipc::GATEWAY_ACTOR_ID, + system, }; use fendermint_vm_genesis::{Collateral, Power, PowerScale, Validator, ValidatorKey}; use fendermint_vm_message::conv::from_eth; @@ -297,3 +302,106 @@ fn membership_to_power_table( pt } + +/// Caller for the F3 Light Client actor +/// +/// This actor is responsible for: +/// - Storing finalized F3 instance state (instance ID, finalized epochs, validator power table) +/// - Validator power table +#[derive(Clone)] +pub struct F3LightClientCaller { + actor_id: ActorID, +} + +impl F3LightClientCaller { + pub fn new() -> Self { + Self { + actor_id: f3_light_client::F3_LIGHT_CLIENT_ACTOR_ID, + } + } + + /// Update the F3 light client state after verifying a proof bundle. + /// + /// This should be called after successfully executing a proof-based topdown finality message. + pub fn update_state( + &self, + state: &mut FvmExecState, + light_client_state: f3_light_client::LightClientState, + ) -> anyhow::Result<()> { + let method_num = f3_light_client::Method::UpdateState as u64; + + let params = f3_light_client::UpdateStateParams { + state: light_client_state, + }; + + let params_bytes = + fvm_ipld_encoding::to_vec(¶ms).context("failed to serialize update params")?; + + let msg = fvm_shared::message::Message { + version: Default::default(), + from: fvm_shared::address::Address::new_id(system::SYSTEM_ACTOR_ID), + to: fvm_shared::address::Address::new_id(self.actor_id), + sequence: 0, + value: TokenAmount::zero(), + method_num, + params: fvm_ipld_encoding::RawBytes::new(params_bytes), + gas_limit: 10_000_000_000, + gas_fee_cap: TokenAmount::zero(), + gas_premium: TokenAmount::zero(), + }; + + let (ret, _) = state + .execute_implicit(msg) + .context("failed to execute F3 light client update")?; + + if let Some(err) = &ret.failure_info { + bail!( + "F3 light client update failed (exit code {}): {}", + ret.msg_receipt.exit_code.value(), + err + ); + } + + Ok(()) + } + + /// Get the current F3 instance state from the light client actor. + pub fn get_state( + &self, + state: &mut FvmExecState, + ) -> anyhow::Result { + let method_num = f3_light_client::Method::GetState as u64; + + let msg = fvm_shared::message::Message { + version: Default::default(), + from: fvm_shared::address::Address::new_id(system::SYSTEM_ACTOR_ID), + to: fvm_shared::address::Address::new_id(self.actor_id), + sequence: 0, + value: TokenAmount::zero(), + method_num, + params: fvm_ipld_encoding::RawBytes::default(), + gas_limit: 10_000_000_000, + gas_fee_cap: TokenAmount::zero(), + gas_premium: TokenAmount::zero(), + }; + + let (ret, _) = state + .execute_implicit(msg) + .context("failed to execute F3 light client get_state")?; + + if let Some(err) = &ret.failure_info { + bail!( + "F3 light client get_state failed (exit code {}): {}", + ret.msg_receipt.exit_code.value(), + err + ); + } + + let state_response: f3_light_client::GetStateResponse = fvm_ipld_encoding::from_slice( + &ret.msg_receipt.return_data.bytes(), + ) + .context("failed to deserialize F3 light client state")?; + + Ok(state_response) + } +} diff --git a/fendermint/vm/interpreter/src/fvm/state/query.rs b/fendermint/vm/interpreter/src/fvm/state/query.rs index e555bcdd91..547dd5f2ce 100644 --- a/fendermint/vm/interpreter/src/fvm/state/query.rs +++ b/fendermint/vm/interpreter/src/fvm/state/query.rs @@ -108,7 +108,7 @@ where } /// If we know the query is over the state, cache the state tree. - async fn with_exec_state(self, f: F) -> anyhow::Result<(Self, T)> + pub async fn with_exec_state(self, f: F) -> anyhow::Result<(Self, T)> where F: FnOnce(&mut FvmExecState>) -> anyhow::Result, { diff --git a/fendermint/vm/interpreter/src/fvm/topdown.rs b/fendermint/vm/interpreter/src/fvm/topdown.rs index 4fb6c9a6c9..a8e7f3db82 100644 --- a/fendermint/vm/interpreter/src/fvm/topdown.rs +++ b/fendermint/vm/interpreter/src/fvm/topdown.rs @@ -23,6 +23,7 @@ use anyhow::{bail, Context}; use fvm_ipld_blockstore::Blockstore; use crate::fvm::end_block_hook::PowerUpdates; +use crate::fvm::f3::F3FinalityHandler; use crate::fvm::state::ipc::tokens_to_mint; use crate::types::AppliedMessage; use ipc_api::cross::IpcEnvelope; @@ -38,21 +39,29 @@ where votes: VoteTally, // Gateway caller for IPC gateway interactions gateway_caller: GatewayCaller, + // F3 finality handler (None means use legacy voting-based finality) + // Set once during startup before any concurrent access + pub f3: Option, } impl TopDownManager where DB: Blockstore + Clone + 'static + Send + Sync, { - pub fn new(provider: TopDownFinalityProvider, votes: VoteTally) -> Self { + pub fn new( + provider: TopDownFinalityProvider, + votes: VoteTally, + f3: Option, + ) -> Self { Self { provider, votes, gateway_caller: GatewayCaller::default(), + f3, } } - pub async fn is_finality_valid(&self, finality: ParentFinality) -> bool { + pub async fn attest_legacy(&self, finality: ParentFinality) -> bool { let prop = IPCParentFinality { height: finality.height as u64, block_hash: finality.block_hash, @@ -66,7 +75,7 @@ where /// both the next parent's proposal and the quorum of votes. If either the parent's proposal or the quorum is missing, /// the function returns `None`. When both are available, it selects the finality with the lower block height and wraps /// it into a `ChainMessage` for top-down execution. - pub async fn chain_message_from_finality_or_quorum(&self) -> Option { + async fn chain_message_from_finality_or_quorum(&self) -> Option { // Prepare top down proposals. // Before we try to find a quorum, pause incoming votes. This is optional but if there are lots of votes coming in it might hold up proposals. atomically(|| self.votes.pause_votes_until_find_quorum()).await; @@ -116,6 +125,43 @@ where }))) } + /// Get the chain message for parent finality proposal. + /// + /// This method encapsulates the decision of which finality mechanism to use: + /// - If F3 is configured and has a proof available, use F3 proof-based finality + /// - Otherwise, use legacy voting-based finality + /// + /// The caller doesn't need to know which mechanism is being used. + pub async fn chain_message_for_proposal(&self) -> Option { + // Use F3 finality (if configured) + if let Some(f3) = &self.f3 { + if let Some(proof_msg) = f3.chain_message_from_proof_cache() { + tracing::info!("using F3 proof-based parent finality in proposal"); + return Some(proof_msg); + } + } + + // Fall back to legacy voting-based finality + tracing::debug!("using legacy voting-based finality"); + self.chain_message_from_finality_or_quorum().await + } + + /// Attest a generalised top-down message during the attestation phase. + /// + /// Delegates to F3 handler if F3 is configured, otherwise returns error. + pub async fn attest_generalised( + &self, + msg: &fendermint_vm_message::ipc::GeneralisedTopDown, + ) -> anyhow::Result<()> { + if let Some(f3) = &self.f3 { + f3.attest(msg).await + } else { + Err(anyhow::anyhow!( + "F3 not configured - cannot attest generalised top-down message" + )) + } + } + pub async fn update_voting_power_table(&self, power_updates: &PowerUpdates) { let power_updates_mapped: Vec<_> = power_updates .0 @@ -126,8 +172,55 @@ where atomically(|| self.votes.update_power_table(power_updates_mapped.clone())).await } + /// Execute generalised top-down message. + /// Delegates F3-specific logic to F3 module, handles common top-down execution. + pub async fn execute_generalised( + &self, + state: &mut FvmExecState, + msg: fendermint_vm_message::ipc::GeneralisedTopDown, + ) -> anyhow::Result { + // Get F3 handler - all F3-specific logic is delegated to it + let f3 = self.f3.as_ref().ok_or_else(|| { + anyhow::anyhow!("F3 not configured - cannot execute without F3 handler") + })?; + + // Execute F3-specific logic (certificate validation, proof extraction, state updates) + let (msgs, validator_changes) = f3.execute(state, &msg)?; + + // Commit parent finality to gateway + let finality = IPCParentFinality::new(msg.height as i64, vec![]); + let (prev_height, _prev_finality) = self + .commit_finality(state, finality.clone()) + .await + .context("failed to commit finality")?; + + tracing::debug!( + previous_height = prev_height, + current_height = finality.height, + "committed parent finality" + ); + + // Store validator changes in gateway + self.gateway_caller + .store_validator_changes(state, validator_changes) + .context("failed to store validator changes")?; + + // Execute topdown messages + let ret = self + .execute_topdown_msgs(state, msgs) + .await + .context("failed to execute top down messages")?; + + tracing::info!( + height = msg.height, + "generalised top-down executed successfully" + ); + + Ok(ret) + } + // TODO Karel - separate this huge function and clean up - pub async fn execute_topdown_msg( + pub async fn execute_legacy( &self, state: &mut FvmExecState, finality: ParentFinality, diff --git a/fendermint/vm/interpreter/src/genesis.rs b/fendermint/vm/interpreter/src/genesis.rs index 581c75d492..e2ba676578 100644 --- a/fendermint/vm/interpreter/src/genesis.rs +++ b/fendermint/vm/interpreter/src/genesis.rs @@ -444,17 +444,11 @@ impl<'a> GenesisBuilder<'a> { // F3 Light Client actor - manages F3 light client state for proof-based parent finality if let Some(f3_params) = &genesis.f3 { - // For subnets with F3 parameters, initialize with the provided F3 data - // Note: finalized_epochs always starts empty at genesis - let constructor_params = fendermint_actor_f3_light_client::types::ConstructorParams { - instance_id: f3_params.instance_id, - power_table: f3_params.power_table.clone(), - finalized_epochs: Vec::new(), - }; + // No finalized height at genesis - will be set when first certificate is processed let f3_state = fendermint_actor_f3_light_client::state::State::new( - constructor_params.instance_id, - constructor_params.power_table, - constructor_params.finalized_epochs, + f3_params.instance_id, + None, + f3_params.power_table.clone(), )?; state diff --git a/fendermint/vm/interpreter/src/lib.rs b/fendermint/vm/interpreter/src/lib.rs index b3f28e02ec..138c71bab0 100644 --- a/fendermint/vm/interpreter/src/lib.rs +++ b/fendermint/vm/interpreter/src/lib.rs @@ -30,6 +30,15 @@ where msg: Vec, is_recheck: bool, ) -> Result; + + /// Set the proof cache for F3 proof-based parent finality (if supported) + /// Default implementation does nothing for interpreters that don't support F3 + async fn set_proof_cache( + &self, + _cache: std::sync::Arc, + ) { + // Default: no-op for interpreters without F3 support + } async fn prepare_messages_for_block( &self, diff --git a/fendermint/vm/message/Cargo.toml b/fendermint/vm/message/Cargo.toml index 34459becbb..93dd32e033 100644 --- a/fendermint/vm/message/Cargo.toml +++ b/fendermint/vm/message/Cargo.toml @@ -30,7 +30,9 @@ ipc-api = { path = "../../../ipc/api" } fendermint_crypto = { path = "../../crypto" } fendermint_vm_encoding = { path = "../encoding" } fendermint_vm_actor_interface = { path = "../actor_interface" } +fendermint_vm_topdown_proof_service = { path = "../topdown/proof-service" } fendermint_testing = { path = "../../testing", optional = true } +proofs = { git = "https://github.com/consensus-shipyard/ipc-filecoin-proofs.git", branch = "proofs" } [dev-dependencies] ethers = { workspace = true } diff --git a/fendermint/vm/message/src/chain.rs b/fendermint/vm/message/src/chain.rs index 264834d749..d58b380a59 100644 --- a/fendermint/vm/message/src/chain.rs +++ b/fendermint/vm/message/src/chain.rs @@ -12,7 +12,7 @@ use crate::{ipc::IpcMessage, signed::SignedMessage}; /// signatures are stripped from the messages, to save space. Tendermint Core will /// not do this for us (perhaps with ABCI++ Vote Extensions we could do it), though. #[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum ChainMessage { /// A message that can be passed on to the FVM as-is. Signed(SignedMessage), diff --git a/fendermint/vm/message/src/ipc.rs b/fendermint/vm/message/src/ipc.rs index 8f275a1c24..49da55a7b5 100644 --- a/fendermint/vm/message/src/ipc.rs +++ b/fendermint/vm/message/src/ipc.rs @@ -5,12 +5,13 @@ use fvm_shared::clock::ChainEpoch; use serde::{Deserialize, Serialize}; /// Messages involved in InterPlanetary Consensus. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[allow(clippy::large_enum_variant)] pub enum IpcMessage { - /// A top-down checkpoint parent finality proposal. This proposal should contain the latest parent - /// state that to be checked and voted by validators. + /// A top-down checkpoint parent finality proposal (legacy voting-based) TopDownExec(ParentFinality), + /// Generalized top-down finality with extensible certificate types + GeneralisedTopDown(GeneralisedTopDown), } /// A proposal of the parent view that validators will be voting on. @@ -22,6 +23,23 @@ pub struct ParentFinality { pub block_hash: Vec, } +/// Generalized top-down finality structure +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GeneralisedTopDown { + /// The chain epoch this finality is for (height) + pub height: ChainEpoch, + /// The certificate that certifies finality (type-specific, proof is fetched from local cache) + pub certificate: Certificate, +} + +/// Certificate types (extensible for future certificate types) +/// Each variant contains the certificate data. Proofs are fetched from local cache when needed. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Certificate { + /// Filecoin F3 certificate (proof bundle is fetched from local cache using instance ID) + FilecoinF3(fendermint_vm_topdown_proof_service::types::SerializableF3Certificate), +} + #[cfg(feature = "arb")] mod arb { diff --git a/fendermint/vm/topdown/proof-service/Cargo.toml b/fendermint/vm/topdown/proof-service/Cargo.toml index b5ba35a98d..0e891d7301 100644 --- a/fendermint/vm/topdown/proof-service/Cargo.toml +++ b/fendermint/vm/topdown/proof-service/Cargo.toml @@ -31,7 +31,6 @@ fendermint_actor_f3_light_client = { path = "../../../actors/f3-light-client" } fendermint_vm_genesis = { path = "../../genesis" } # IPC -ipc-provider = { path = "../../../../ipc/provider" } ipc-api = { path = "../../../../ipc/api" } ipc-observability = { path = "../../../../ipc/observability" } diff --git a/fendermint/vm/topdown/proof-service/README.md b/fendermint/vm/topdown/proof-service/README.md index 79750d1d39..6cc8ff77df 100644 --- a/fendermint/vm/topdown/proof-service/README.md +++ b/fendermint/vm/topdown/proof-service/README.md @@ -196,7 +196,8 @@ let config = ProofServiceConfig { retention_epochs: 100, }, polling_interval: Duration::from_secs(30), - ..Default::default() + max_cache_size_bytes: 100 * 1024 * 1024, // 100 MB + fallback_rpc_urls: vec![], }; // Launch service with optional persistence @@ -411,9 +412,9 @@ Older issue with reqwest library on macOS (now fixed in upstream). ### Unit Tests -```bash -cargo test --package fendermint_vm_topdown_proof_service --lib -``` +````bash +# Unit tests +cargo test --package fendermint_vm_topdown_proof_service **Test Coverage:** @@ -424,10 +425,41 @@ cargo test --package fendermint_vm_topdown_proof_service --lib - Metrics registration ### Integration Tests - ```bash # Requires live Calibration network cargo test --package fendermint_vm_topdown_proof_service --test integration -- --ignored +```` + +### End-to-End Testing + +1. **Deploy Test Contract** (optional - for testing with TopdownMessenger): + +```bash +cd /path/to/proofs/topdown-messenger +forge create --rpc-url http://api.calibration.node.glif.io/rpc/v1 \ + --private-key $PRIVATE_KEY \ + src/TopdownMessenger.sol:TopdownMessenger +``` + +2. **Run Proof Service**: + +```bash +./target/debug/proof-cache-test run \ + --rpc-url "http://api.calibration.node.glif.io/rpc/v1" \ + --initial-instance \ + --gateway-actor-id \ + --subnet-id "your-subnet-id" \ + --poll-interval 10 \ + --lookahead 3 \ + --db-path /tmp/proof-cache-test.db +``` + +3. **Inspect Results**: + +```bash +# After stopping the service +./target/debug/proof-cache-test inspect --db-path /tmp/proof-cache-test.db +./target/debug/proof-cache-test get --db-path /tmp/proof-cache-test.db --instance-id ``` ### End-to-End Testing diff --git a/fendermint/vm/topdown/proof-service/src/cache.rs b/fendermint/vm/topdown/proof-service/src/cache.rs index f088baf294..a3951eb057 100644 --- a/fendermint/vm/topdown/proof-service/src/cache.rs +++ b/fendermint/vm/topdown/proof-service/src/cache.rs @@ -260,6 +260,24 @@ impl ProofCache { self.last_committed_instance.load(Ordering::Acquire) } + /// Get the next uncommitted epoch (last_committed_epoch + 1) + /// Returns None if no proof is available for that epoch + pub fn get_next_uncommitted_epoch(&self) -> Option { + let next_epoch = self.last_committed_epoch() + 1; + if self.contains_epoch_proof(next_epoch) { + Some(next_epoch) + } else { + None + } + } + + /// Get the next uncommitted proof entry (epoch + certificate) + /// Returns None if no proof is available for next epoch + pub fn get_next_uncommitted(&self) -> Option { + let next_epoch = self.get_next_uncommitted_epoch()?; + self.get_epoch_proof_with_certificate(next_epoch) + } + /// Get the number of cached epoch proofs pub fn epoch_proof_count(&self) -> usize { self.epoch_proofs.read().len() diff --git a/fendermint/vm/topdown/proof-service/src/persistence.rs b/fendermint/vm/topdown/proof-service/src/persistence.rs index b61c086f4f..e348000113 100644 --- a/fendermint/vm/topdown/proof-service/src/persistence.rs +++ b/fendermint/vm/topdown/proof-service/src/persistence.rs @@ -181,14 +181,6 @@ impl ProofCachePersistence { self.clear_all() } - /// Load the last committed instance ID - /// - /// Note: This information is not persisted to disk, so this always returns None. - /// The last committed state is only stored in memory in the ProofCache. - pub fn load_last_committed(&self) -> Result> { - Ok(None) - } - /// Load all entries as combined cache entries /// /// This combines certificates with their associated epoch proofs for inspection. diff --git a/ipc/cli/src/commands/mod.rs b/ipc/cli/src/commands/mod.rs index 1fd0128a27..72b1a3aa18 100644 --- a/ipc/cli/src/commands/mod.rs +++ b/ipc/cli/src/commands/mod.rs @@ -8,6 +8,7 @@ mod crossmsg; // mod daemon; mod deploy; mod node; +mod proof_cache; mod subnet; mod ui; mod util; @@ -16,6 +17,7 @@ mod wallet; use crate::commands::checkpoint::CheckpointCommandsArgs; use crate::commands::crossmsg::CrossMsgsCommandsArgs; +use crate::commands::proof_cache::ProofCacheArgs; use crate::commands::ui::{run_ui_command, UICommandArgs}; use crate::commands::util::UtilCommandsArgs; use crate::GlobalArguments; @@ -62,6 +64,7 @@ enum Commands { Deploy(DeployCommandArgs), Ui(UICommandArgs), Node(NodeCommandsArgs), + ProofCache(ProofCacheArgs), } #[derive(Debug, Parser)] diff --git a/ipc/provider/Cargo.toml b/ipc/provider/Cargo.toml index f2c19911bd..50eef07548 100644 --- a/ipc/provider/Cargo.toml +++ b/ipc/provider/Cargo.toml @@ -61,7 +61,6 @@ fendermint_rpc = { path = "../../fendermint/rpc" } fendermint_actor_f3_light_client = { path = "../../fendermint/actors/f3-light-client" } fendermint_vm_genesis = { path = "../../fendermint/vm/genesis" } - [dev-dependencies] tempfile = { workspace = true } hex = { workspace = true }