diff --git a/CHANGELOG.md b/CHANGELOG.md index ca9b5016b..8b3e1ecc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Next release +- feat: gas price provider added for block production +- feat: l1 sync service +- feat: gas price worker for l1 +- test: tests added for eth client and event subscription - feat: Added l1->l2 messaging - test: add unitests primitives - tests: add tests for the rpcs endpoints diff --git a/Cargo.lock b/Cargo.lock index 9a080858a..6c9640879 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -833,12 +833,214 @@ dependencies = [ "term", ] +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "assert_matches" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.1.0", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io 2.3.3", + "async-lock 3.4.0", + "blocking", + "futures-lite 2.3.0", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +dependencies = [ + "async-lock 3.4.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.7.2", + "rustix 0.38.34", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-object-pool" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb901c30ebc2fc4ab46395bbfbdba9542c16559d853645d75190c3056caf3bc" +dependencies = [ + "async-std", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.34", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-signal" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" +dependencies = [ + "async-io 2.3.3", + "async-lock 3.4.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.34", + "signal-hook-registry", + "slab", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 1.13.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -861,6 +1063,12 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.80" @@ -964,6 +1172,17 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-cookies" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" +dependencies = [ + "lalrpop 0.20.2", + "lalrpop-util 0.20.2", + "regex", +] + [[package]] name = "beef" version = "0.5.2" @@ -1108,6 +1327,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite 2.3.0", + "piper", +] + [[package]] name = "blst" version = "0.3.13" @@ -3087,6 +3319,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-fnv1a-hash" version = "1.1.0" @@ -3411,20 +3652,28 @@ version = "0.1.0" dependencies = [ "alloy", "anyhow", + "async-trait", "bitvec", "blockifier", "bytes", "dc-db", + "dc-mempool", "dc-metrics", + "dc-sync", + "dc-telemetry", "dotenv", "dp-block", "dp-convert", "dp-transactions", "dp-utils", "futures", + "httpmock", + "lazy_static", "log", "once_cell", + "primitive-types", "prometheus", + "regex", "rstest 0.18.2", "serde", "serde_json", @@ -3432,6 +3681,7 @@ dependencies = [ "starknet_api", "tempfile", "thiserror", + "time", "tokio", "tracing", "tracing-test", @@ -3460,6 +3710,7 @@ name = "dc-mempool" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "bitvec", "blockifier", "dc-db", @@ -3473,6 +3724,7 @@ dependencies = [ "dp-transactions", "dp-utils", "env_logger 0.11.3", + "hyper 0.14.29", "log", "proptest", "proptest-derive", @@ -3532,7 +3784,6 @@ dependencies = [ "bitvec", "bonsai-trie", "dc-db", - "dc-eth", "dc-metrics", "dc-telemetry", "dp-block", @@ -4010,6 +4261,53 @@ dependencies = [ "uint", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.1.0" @@ -4170,6 +4468,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.1.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -4294,6 +4620,18 @@ dependencies = [ "regex-syntax 0.8.4", ] +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "good_lp" version = "1.8.1" @@ -4436,6 +4774,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -4540,6 +4884,34 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "httpmock" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ec9586ee0910472dec1a1f0f8acf52f0fdde93aea74d70d4a3107b4be0fd5b" +dependencies = [ + "assert-json-diff", + "async-object-pool", + "async-std", + "async-trait", + "base64 0.21.7", + "basic-cookies", + "crossbeam-utils", + "form_urlencoded", + "futures-util", + "hyper 0.14.29", + "lazy_static", + "levenshtein", + "log", + "regex", + "serde", + "serde_json", + "serde_regex", + "similar", + "tokio", + "url", +] + [[package]] name = "humantime" version = "2.1.0" @@ -4563,7 +4935,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -4650,7 +5022,7 @@ dependencies = [ "http-body 1.0.0", "hyper 1.3.1", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower", "tower-service", @@ -4934,6 +5306,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ip_network" version = "0.4.1" @@ -5163,6 +5546,15 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lalrpop" version = "0.19.12" @@ -5262,6 +5654,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + [[package]] name = "lewton" version = "0.10.2" @@ -5332,6 +5730,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -5850,6 +6254,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.11.2" @@ -6055,6 +6465,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +dependencies = [ + "atomic-waker", + "fastrand 2.1.0", + "futures-io", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -6071,6 +6492,37 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix 0.38.34", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "portable-atomic" version = "1.6.0" @@ -6697,6 +7149,20 @@ dependencies = [ "semver 1.0.23", ] +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" version = "0.38.34" @@ -6706,7 +7172,7 @@ dependencies = [ "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] @@ -7016,6 +7482,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.6" @@ -7154,6 +7630,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + [[package]] name = "siphasher" version = "0.3.11" @@ -7196,6 +7678,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.7" @@ -7776,8 +8268,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand", - "rustix", + "fastrand 2.1.0", + "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -7941,7 +8433,7 @@ dependencies = [ "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] @@ -8389,6 +8881,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 479311925..785bac701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -153,6 +153,7 @@ fdlimit = "0.3.0" proptest = "1.5.0" proptest-derive = "0.5.0" dotenv = "0.15.0" +httpmock = "0.7.0" tempfile = "3.10.1" env_logger = "0.11.3" diff --git a/crates/client/eth/Cargo.toml b/crates/client/eth/Cargo.toml index 06922aebe..a5c6fc49b 100644 --- a/crates/client/eth/Cargo.toml +++ b/crates/client/eth/Cargo.toml @@ -15,7 +15,10 @@ targets = ["x86_64-unknown-linux-gnu"] # Deoxys dc-db = { workspace = true } +dc-mempool = { workspace = true } dc-metrics = { workspace = true } +dc-sync = { workspace = true } +dc-telemetry = { workspace = true } dp-block = { workspace = true } dp-convert = { workspace = true } dp-transactions = { workspace = true } @@ -29,14 +32,19 @@ starknet_api = { workspace = true } # Other alloy = { workspace = true, features = ["node-bindings"] } anyhow = "1.0.75" +async-trait = "0.1.80" bitvec = { workspace = true } blockifier = { workspace = true } bytes = "1.6.0" futures = { workspace = true, default-features = true } +lazy_static = { workspace = true } log = { workspace = true } +primitive-types = { workspace = true } +regex = "1.10.5" serde = { workspace = true, default-features = true } serde_json = "1" thiserror.workspace = true +time = "0.3.36" tokio = { workspace = true, features = [ "macros", "parking_lot", @@ -52,4 +60,5 @@ once_cell = { workspace = true } tempfile = { workspace = true } dotenv = { workspace = true } prometheus = { workspace = true } +httpmock = { workspace = true } tracing-test = "0.2.5" diff --git a/crates/client/eth/src/client.rs b/crates/client/eth/src/client.rs index 345b2e9bc..42d1c235d 100644 --- a/crates/client/eth/src/client.rs +++ b/crates/client/eth/src/client.rs @@ -135,18 +135,27 @@ pub mod eth_client_getter_test { const L2_BLOCK_HASH: &str = "563216050958639290223177746678863910249919294431961492885921903486585884664"; const L2_STATE_ROOT: &str = "1456190284387746219409791261254265303744585499659352223397867295223408682130"; - #[fixture] - #[once] - pub fn eth_client() -> EthereumClient { - let rpc_url: Url = "http://localhost:8545".parse().expect("issue while parsing"); + pub fn create_ethereum_client(url: Option<&str>) -> EthereumClient { + let rpc_url_string = url.unwrap_or("http://localhost:8545").to_string(); + let rpc_url: Url = rpc_url_string.parse().expect("issue while parsing URL"); + let provider = ProviderBuilder::new().on_http(rpc_url.clone()); let address = Address::parse_checksummed(CORE_CONTRACT_ADDRESS, None).unwrap(); let contract = StarknetCoreContract::new(address, provider.clone()); + let prometheus_service = MetricsService::new(true, false, 9615).unwrap(); let l1_block_metrics = L1BlockMetrics::register(&prometheus_service.registry()).unwrap(); + EthereumClient { provider: Arc::new(provider), l1_core_contract: contract.clone(), l1_block_metrics } } + // Then, you can use this utility function in your fixture like this: + #[fixture] + #[once] + pub fn eth_client() -> EthereumClient { + create_ethereum_client(None) + } + #[rstest] #[tokio::test] async fn get_latest_block_number_works(eth_client: &EthereumClient) { diff --git a/crates/client/eth/src/l1_gas_price.rs b/crates/client/eth/src/l1_gas_price.rs new file mode 100644 index 000000000..54e62b525 --- /dev/null +++ b/crates/client/eth/src/l1_gas_price.rs @@ -0,0 +1,268 @@ +use crate::client::EthereumClient; +use alloy::eips::BlockNumberOrTag; +use alloy::providers::Provider; +use anyhow::Context; +use dc_mempool::{GasPriceProvider, L1DataProvider}; +use std::time::{Duration, UNIX_EPOCH}; + +use dp_utils::wait_or_graceful_shutdown; +use std::time::SystemTime; + +pub async fn gas_price_worker_once( + eth_client: &EthereumClient, + l1_gas_provider: GasPriceProvider, + gas_price_poll_ms: Duration, +) -> anyhow::Result<()> { + match update_gas_price(eth_client, l1_gas_provider.clone()).await { + Ok(_) => log::trace!("Updated gas prices"), + Err(e) => log::error!("Failed to update gas prices: {:?}", e), + } + + let last_update_timestamp = l1_gas_provider.get_gas_prices_last_update(); + let duration_since_last_update = SystemTime::now().duration_since(last_update_timestamp)?; + let last_update_timestemp = + last_update_timestamp.duration_since(UNIX_EPOCH).expect("SystemTime before UNIX EPOCH!").as_micros(); + if duration_since_last_update > 10 * gas_price_poll_ms { + anyhow::bail!( + "Gas prices have not been updated for {} ms. Last update was at {}", + duration_since_last_update.as_micros(), + last_update_timestemp + ); + } + + Ok(()) +} +pub async fn gas_price_worker( + eth_client: &EthereumClient, + l1_gas_provider: GasPriceProvider, + gas_price_poll_ms: Duration, +) -> anyhow::Result<()> { + l1_gas_provider.update_last_update_timestamp(); + let mut interval = tokio::time::interval(gas_price_poll_ms); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + while wait_or_graceful_shutdown(interval.tick()).await.is_some() { + gas_price_worker_once(eth_client, l1_gas_provider.clone(), gas_price_poll_ms).await?; + } + Ok(()) +} + +async fn update_gas_price(eth_client: &EthereumClient, l1_gas_provider: GasPriceProvider) -> anyhow::Result<()> { + let block_number = eth_client.get_latest_block_number().await?; + let fee_history = eth_client.provider.get_fee_history(300, BlockNumberOrTag::Number(block_number), &[]).await?; + + // The RPC responds with 301 elements for some reason. It's also just safer to manually + // take the last 300. We choose 300 to get average gas caprice for last one hour (300 * 12 sec block + // time). + let (_, blob_fee_history_one_hour) = + fee_history.base_fee_per_blob_gas.split_at(fee_history.base_fee_per_blob_gas.len().max(300) - 300); + + let avg_blob_base_fee = blob_fee_history_one_hour.iter().sum::() / blob_fee_history_one_hour.len() as u128; + + let eth_gas_price = fee_history.base_fee_per_gas.last().context("Getting eth gas price")?; + + l1_gas_provider.update_eth_l1_gas_price(*eth_gas_price); + l1_gas_provider.update_eth_l1_data_gas_price(avg_blob_base_fee); + + l1_gas_provider.update_last_update_timestamp(); + + // Update block number separately to avoid holding the lock for too long + update_l1_block_metrics(eth_client, l1_gas_provider).await?; + + Ok(()) +} + +async fn update_l1_block_metrics(eth_client: &EthereumClient, l1_gas_provider: GasPriceProvider) -> anyhow::Result<()> { + // Get the latest block number + let latest_block_number = eth_client.get_latest_block_number().await?; + + // Get the current gas price + let current_gas_price = l1_gas_provider.get_gas_prices(); + let eth_gas_price = current_gas_price.eth_l1_gas_price; + + // Update the metrics + eth_client.l1_block_metrics.l1_block_number.set(latest_block_number as f64); + eth_client.l1_block_metrics.l1_gas_price_wei.set(eth_gas_price as f64); + + // We're ignoring l1_gas_price_strk + + Ok(()) +} + +#[cfg(test)] +mod eth_client_gas_price_worker_test { + use super::*; + use crate::client::eth_client_getter_test::create_ethereum_client; + use alloy::node_bindings::Anvil; + use dc_mempool::GasPriceProvider; + use httpmock::{MockServer, Regex}; + use rstest::*; + use std::time::SystemTime; + use tokio::task::JoinHandle; + use tokio::time::{timeout, Duration}; + + #[fixture] + #[once] + pub fn eth_client_with_mock() -> (MockServer, EthereumClient) { + let server = MockServer::start(); + let addr = format!("http://{}", server.address()); + let eth_client = create_ethereum_client(Some(&addr)); + (server, eth_client) + } + + #[fixture] + #[once] + pub fn eth_client() -> EthereumClient { + create_ethereum_client(None) + } + + #[tokio::test] + async fn gas_price_worker_when_infinite_loop_true_works() { + let anvil = Anvil::new() + .fork("https://eth.merkle.io") + .fork_block_number(20395662) + .try_spawn() + .expect("issue while forking for the anvil"); + let eth_client = create_ethereum_client(Some(anvil.endpoint().as_str())); + let l1_gas_provider = GasPriceProvider::new(); + + // Spawn the gas_price_worker in a separate task + let worker_handle: JoinHandle> = tokio::spawn({ + let eth_client = eth_client.clone(); + let l1_gas_provider = l1_gas_provider.clone(); + async move { gas_price_worker(ð_client, l1_gas_provider, Duration::from_millis(200)).await } + }); + + // Wait for a short duration to allow the worker to run + tokio::time::sleep(Duration::from_secs(3)).await; + + // Abort the worker task + worker_handle.abort(); + + // Wait for the worker to finish (it should be aborted quickly) + let timeout_duration = Duration::from_secs(2); + let result = timeout(timeout_duration, worker_handle).await; + + match result { + Ok(Ok(_)) => println!("Gas price worker completed successfully"), + Ok(Err(e)) => println!("Gas price worker encountered an error: {:?}", e), + Err(_) => println!("Gas price worker timed out"), + } + + // Check if the gas price was updated + let updated_price = l1_gas_provider.get_gas_prices(); + assert_eq!(updated_price.eth_l1_gas_price, 948082986); + assert_eq!(updated_price.eth_l1_data_gas_price, 1); + } + + #[tokio::test] + async fn gas_price_worker_when_infinite_loop_false_works() { + let anvil = Anvil::new() + .fork("https://eth.merkle.io") + .fork_block_number(20395662) + .try_spawn() + .expect("issue while forking for the anvil"); + let eth_client = create_ethereum_client(Some(anvil.endpoint().as_str())); + let l1_gas_provider = GasPriceProvider::new(); + + // Run the worker for a short time + let worker_handle = gas_price_worker_once(ð_client, l1_gas_provider.clone(), Duration::from_millis(200)); + + // Wait for the worker to complete + worker_handle.await.expect("issue with the gas worker"); + + // Check if the gas price was updated + let updated_price = l1_gas_provider.get_gas_prices(); + assert_eq!(updated_price.eth_l1_gas_price, 948082986); + assert_eq!(updated_price.eth_l1_data_gas_price, 1); + } + + #[tokio::test] + async fn gas_price_worker_when_eth_fee_history_fails_should_fails() { + let mock_server = MockServer::start(); + let addr = format!("http://{}", mock_server.address()); + let eth_client = create_ethereum_client(Some(&addr)); + + println!("add is: {:?} ", addr.as_str()); + + let mock = mock_server.mock(|when, then| { + when.method("POST").path("/").json_body_obj(&serde_json::json!({ + "jsonrpc": "2.0", + "method": "eth_feeHistory", + "params": ["0x12c", "0x137368e", []], + "id": 1 + })); + then.status(500).json_body_obj(&serde_json::json!({ + "jsonrpc": "2.0", + "error": { + "code": -32000, + "message": "Internal Server Error" + }, + "id": 1 + })); + }); + + mock_server.mock(|when, then| { + when.method("POST").path("/").json_body_obj(&serde_json::json!({"id":0,"jsonrpc":"2.0","method":"eth_blockNumber"})); + then.status(200).json_body_obj(&serde_json::json!({"jsonrpc":"2.0","id":1,"result":"0x0137368e"} )); + }); + + let l1_gas_provider = GasPriceProvider::new(); + + l1_gas_provider.update_last_update_timestamp(); + + let timeout_duration = Duration::from_secs(10); + + let result = timeout( + timeout_duration, + gas_price_worker(ð_client, l1_gas_provider.clone(), Duration::from_millis(200)), + ) + .await; + + match result { + Ok(Ok(_)) => panic!("Expected gas_price_worker to panic, but it didn't"), + Ok(Err(panic_msg)) => { + if let Some(panic_msg) = panic_msg.downcast_ref::() { + let re = + Regex::new(r"Gas prices have not been updated for \d+ ms\. Last update was at \d+").unwrap(); + assert!(re.is_match(panic_msg), "Panic message did not match expected format. Got: {}", panic_msg); + } else { + panic!("Panic occurred, but message was not a string"); + } + } + Err(err) => panic!("gas_price_worker timed out: {err}"), + } + + // Verify that the mock was called + mock.assert(); + } + + #[rstest] + #[tokio::test] + async fn update_gas_price_works(eth_client: &'static EthereumClient) { + let l1_gas_provider = GasPriceProvider::new(); + + l1_gas_provider.update_last_update_timestamp(); + + // Update gas prices + update_gas_price(eth_client, l1_gas_provider.clone()).await.expect("Failed to update gas prices"); + + // Access the updated gas prices + let updated_prices = l1_gas_provider.get_gas_prices(); + + assert_eq!( + updated_prices.eth_l1_gas_price, 948082986, + "ETH L1 gas price should be 948082986 in test environment" + ); + + assert_eq!(updated_prices.eth_l1_data_gas_price, 1, "ETH L1 data gas price should be 1 in test environment"); + + // Verify that the last update timestamp is recent + + let last_update_timestamp = l1_gas_provider.get_gas_prices_last_update(); + + let time_since_last_update = + SystemTime::now().duration_since(last_update_timestamp).expect("issue while getting the time"); + + assert!(time_since_last_update.as_secs() < 60, "Last update timestamp should be within the last minute"); + } +} diff --git a/crates/client/eth/src/lib.rs b/crates/client/eth/src/lib.rs index a1cba6954..4e8675d38 100644 --- a/crates/client/eth/src/lib.rs +++ b/crates/client/eth/src/lib.rs @@ -1,5 +1,7 @@ pub mod client; pub mod error; +pub mod l1_gas_price; pub mod l1_messaging; pub mod state_update; +pub mod sync; pub mod utils; diff --git a/crates/client/eth/src/state_update.rs b/crates/client/eth/src/state_update.rs index 9d282c8b7..eeacb373f 100644 --- a/crates/client/eth/src/state_update.rs +++ b/crates/client/eth/src/state_update.rs @@ -9,14 +9,13 @@ use dp_transactions::MAIN_CHAIN_ID; use dp_utils::channel_wait_or_graceful_shutdown; use futures::StreamExt; use serde::Deserialize; -use starknet_api::hash::StarkHash; use starknet_types_core::felt::Felt; #[derive(Debug, Clone, Deserialize, PartialEq)] pub struct L1StateUpdate { pub block_number: u64, - pub global_root: StarkHash, - pub block_hash: StarkHash, + pub global_root: Felt, + pub block_hash: Felt, } /// Get the last Starknet state update verified on the L1 @@ -78,13 +77,17 @@ pub fn update_l1( Ok(()) } -pub async fn sync(backend: &DeoxysBackend, eth_client: &EthereumClient, chain_id: Felt) -> anyhow::Result<()> { +pub async fn state_update_worker( + backend: &DeoxysBackend, + eth_client: &EthereumClient, + chain_id: Felt, +) -> anyhow::Result<()> { // Clear L1 confirmed block at startup backend.clear_last_confirmed_block().context("Clearing l1 last confirmed block number")?; log::debug!("update_l1: cleared confirmed block number"); log::info!("🚀 Subscribed to L1 state verification"); - + // ideally here there would be one service which will update the l1 gas prices and another one for messages and one that's already present is state update // Get and store the latest verified state let initial_state = get_initial_state(eth_client).await.context("Getting initial ethereum state")?; update_l1(backend, initial_state, ð_client.l1_block_metrics, chain_id)?; diff --git a/crates/client/eth/src/sync.rs b/crates/client/eth/src/sync.rs new file mode 100644 index 000000000..ebb0a277f --- /dev/null +++ b/crates/client/eth/src/sync.rs @@ -0,0 +1,26 @@ +use crate::client::EthereumClient; +use crate::l1_gas_price::gas_price_worker; +use crate::state_update::state_update_worker; +use dc_mempool::GasPriceProvider; +use starknet_types_core::felt::Felt; +use std::time::Duration; + +use dc_db::DeoxysBackend; + +pub async fn l1_sync_worker( + backend: &DeoxysBackend, + eth_client: &EthereumClient, + chain_id: Felt, + l1_gas_provider: GasPriceProvider, + gas_price_sync_disabled: bool, + gas_price_poll_ms: Duration, +) -> anyhow::Result<()> { + tokio::try_join!(state_update_worker(backend, eth_client, chain_id), async { + if !gas_price_sync_disabled { + gas_price_worker(eth_client, l1_gas_provider, gas_price_poll_ms).await?; + } + Ok(()) + })?; + + Ok(()) +} diff --git a/crates/client/mempool/Cargo.toml b/crates/client/mempool/Cargo.toml index 6bed3c439..726bbbbf4 100644 --- a/crates/client/mempool/Cargo.toml +++ b/crates/client/mempool/Cargo.toml @@ -41,6 +41,8 @@ starknet_api.workspace = true # Other anyhow.workspace = true +async-trait.workspace = true +hyper.workspace = true log.workspace = true rayon.workspace = true thiserror.workspace = true diff --git a/crates/client/mempool/src/l1.rs b/crates/client/mempool/src/l1.rs index 69b729184..2f89ba2d7 100644 --- a/crates/client/mempool/src/l1.rs +++ b/crates/client/mempool/src/l1.rs @@ -1,12 +1,77 @@ use dp_block::header::{GasPrices, L1DataAvailabilityMode}; +use std::sync::{Arc, Mutex}; +use std::time::SystemTime; + +#[derive(Clone)] +pub struct GasPriceProvider { + gas_prices: Arc>, + last_update: Arc>, +} + +impl GasPriceProvider { + pub fn new() -> Self { + GasPriceProvider { + gas_prices: Arc::new(Mutex::new(GasPrices::default())), + last_update: Arc::new(Mutex::new(SystemTime::now())), + } + } + + pub fn set_gas_prices(&self, new_prices: GasPrices) { + let mut prices = self.gas_prices.lock().unwrap(); + *prices = new_prices; + } + + pub fn update_last_update_timestamp(&self) { + let now = SystemTime::now(); + let mut timestamp = self.last_update.lock().unwrap(); + *timestamp = now; + } + + pub fn update_eth_l1_gas_price(&self, new_price: u128) { + let mut prices = self.gas_prices.lock().unwrap(); + prices.eth_l1_gas_price = new_price; + } + + pub fn update_eth_l1_data_gas_price(&self, new_price: u128) { + let mut prices = self.gas_prices.lock().unwrap(); + prices.eth_l1_data_gas_price = new_price; + } + + pub fn update_strk_l1_gas_price(&self, new_price: u128) { + let mut prices = self.gas_prices.lock().unwrap(); + prices.strk_l1_gas_price = new_price; + } + + pub fn update_strk_l1_data_gas_price(&self, new_price: u128) { + let mut prices = self.gas_prices.lock().unwrap(); + prices.strk_l1_data_gas_price = new_price; + } +} + +impl Default for GasPriceProvider { + fn default() -> Self { + Self::new() + } +} -/// This trait enables the block production task to fill in the L1 info. -/// Gas prices and DA mode pub trait L1DataProvider: Send + Sync { - /// Get L1 data gas prices. This needs an oracle for STRK prices. fn get_gas_prices(&self) -> GasPrices; - /// Get the DA mode for L1 Ethereum. This can be either Blob or Calldata, whichever is cheaper at the moment. + fn get_gas_prices_last_update(&self) -> SystemTime; fn get_da_mode(&self) -> L1DataAvailabilityMode; +} + +/// This trait enables the block production task to fill in the L1 info. +/// Gas prices and DA mode +impl L1DataProvider for GasPriceProvider { + fn get_gas_prices(&self) -> GasPrices { + self.gas_prices.lock().unwrap().clone() + } + + fn get_gas_prices_last_update(&self) -> SystemTime { + *self.last_update.lock().expect("Failed to acquire lock") + } - // fn get_pending_l1_handler_txs + fn get_da_mode(&self) -> L1DataAvailabilityMode { + L1DataAvailabilityMode::Blob + } } diff --git a/crates/client/mempool/src/lib.rs b/crates/client/mempool/src/lib.rs index c6ad47308..9994d0ad4 100644 --- a/crates/client/mempool/src/lib.rs +++ b/crates/client/mempool/src/lib.rs @@ -14,7 +14,7 @@ use dp_class::ConvertedClass; use header::make_pending_header; use inner::MempoolInner; pub use inner::{ArrivedAtTimestamp, MempoolTransaction}; -pub use l1::L1DataProvider; +pub use l1::{GasPriceProvider, L1DataProvider}; use starknet_api::core::{ContractAddress, Nonce}; use starknet_api::transaction::TransactionHash; use starknet_types_core::felt::Felt; diff --git a/crates/client/rpc/src/mempool_provider.rs b/crates/client/rpc/src/mempool_provider.rs index e14039389..9ef4a5a43 100644 --- a/crates/client/rpc/src/mempool_provider.rs +++ b/crates/client/rpc/src/mempool_provider.rs @@ -30,19 +30,19 @@ impl AddTransactionProvider for MempoolProvider { &self, declare_transaction: BroadcastedDeclareTransaction, ) -> RpcResult { - Ok(add_declare_transaction(&self.mempool, declare_transaction)?) + Ok(add_declare_transaction(&self.mempool, declare_transaction).await?) } async fn add_deploy_account_transaction( &self, deploy_account_transaction: BroadcastedDeployAccountTransaction, ) -> RpcResult { - Ok(add_deploy_account_transaction(&self.mempool, deploy_account_transaction)?) + Ok(add_deploy_account_transaction(&self.mempool, deploy_account_transaction).await?) } async fn add_invoke_transaction( &self, invoke_transaction: BroadcastedInvokeTransaction, ) -> RpcResult { - Ok(add_invoke_transaction(&self.mempool, invoke_transaction)?) + Ok(add_invoke_transaction(&self.mempool, invoke_transaction).await?) } } @@ -71,7 +71,7 @@ fn deployed_contract_address(tx: &Transaction) -> Option { } } -fn add_tx_to_mempool( +async fn add_tx_to_mempool( mempool: &Arc, tx: Transaction, converted_class: Option, @@ -86,7 +86,7 @@ fn add_tx_to_mempool( Ok(()) } -fn add_declare_transaction( +async fn add_declare_transaction( mempool: &Arc, declare_transaction: BroadcastedDeclareTransaction, ) -> RpcResult { @@ -98,10 +98,10 @@ fn add_declare_transaction( transaction_hash: transaction_hash(&tx), class_hash: declare_class_hash(&tx).expect("Created transaction should be declare"), }; - add_tx_to_mempool(mempool, tx, classes)?; + add_tx_to_mempool(mempool, tx, classes).await?; Ok(res) } -fn add_deploy_account_transaction( +async fn add_deploy_account_transaction( mempool: &Arc, deploy_account_transaction: BroadcastedDeployAccountTransaction, ) -> RpcResult { @@ -116,10 +116,10 @@ fn add_deploy_account_transaction( transaction_hash: transaction_hash(&tx), contract_address: deployed_contract_address(&tx).expect("Created transaction should be deploy account"), }; - add_tx_to_mempool(mempool, tx, classes)?; + add_tx_to_mempool(mempool, tx, classes).await?; Ok(res) } -fn add_invoke_transaction( +async fn add_invoke_transaction( mempool: &Arc, invoke_transaction: BroadcastedInvokeTransaction, ) -> RpcResult { @@ -128,6 +128,6 @@ fn add_invoke_transaction( .map_err(|err| StarknetRpcApiError::TxnExecutionError { tx_index: 0, error: format!("{err:#}") })?; let res = InvokeTransactionResult { transaction_hash: transaction_hash(&tx) }; - add_tx_to_mempool(mempool, tx, classes)?; + add_tx_to_mempool(mempool, tx, classes).await?; Ok(res) } diff --git a/crates/client/sync/Cargo.toml b/crates/client/sync/Cargo.toml index 803b38a37..e80352c3a 100644 --- a/crates/client/sync/Cargo.toml +++ b/crates/client/sync/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] # Deoxys dc-db = { workspace = true } -dc-eth = { workspace = true } +#dc-eth = { workspace = true } dc-metrics = { workspace = true } dc-telemetry = { workspace = true } dp-block = { workspace = true } diff --git a/crates/client/sync/src/fetch/fetchers.rs b/crates/client/sync/src/fetch/fetchers.rs index e7d5880db..68d5e32fc 100644 --- a/crates/client/sync/src/fetch/fetchers.rs +++ b/crates/client/sync/src/fetch/fetchers.rs @@ -35,8 +35,6 @@ pub struct FetchConfig { pub sync_polling_interval: Option, /// Number of blocks to sync (for testing purposes) pub n_blocks_to_sync: Option, - /// Disable l1 sync - pub sync_l1_disabled: bool, } #[derive(Clone, Copy, PartialEq, Eq)] diff --git a/crates/client/sync/src/lib.rs b/crates/client/sync/src/lib.rs index 543e42e97..96561ac2a 100644 --- a/crates/client/sync/src/lib.rs +++ b/crates/client/sync/src/lib.rs @@ -16,7 +16,6 @@ pub mod starknet_sync_worker { use crate::metrics::block_metrics::BlockMetrics; use anyhow::Context; use dc_db::{db_metrics::DbMetrics, DeoxysBackend}; - use dc_eth::client::EthereumClient; use dc_telemetry::TelemetryHandle; use dp_convert::ToFelt; use fetch::fetchers::FetchConfig; @@ -28,7 +27,6 @@ pub mod starknet_sync_worker { pub async fn sync( backend: &Arc, fetch_config: FetchConfig, - eth_client: Option, starting_block: Option, backup_every_n_blocks: Option, block_metrics: BlockMetrics, @@ -56,34 +54,24 @@ pub mod starknet_sync_worker { None => provider, }; - let l1_fut = async { - if let Some(eth_client) = eth_client { - dc_eth::state_update::sync(backend, ð_client, chain_id).await - } else { - Ok(()) - } - }; - - tokio::try_join!( - l1_fut, - l2::sync( - backend, - provider, - L2SyncConfig { - first_block: starting_block, - n_blocks_to_sync: fetch_config.n_blocks_to_sync, - verify: fetch_config.verify, - sync_polling_interval: fetch_config.sync_polling_interval, - backup_every_n_blocks, - pending_block_poll_interval, - }, - block_metrics, - db_metrics, - starting_block, - chain_id, - telemetry, - ), - )?; + // TODO: remove try join from here since there is only one service here + tokio::try_join!(l2::sync( + backend, + provider, + L2SyncConfig { + first_block: starting_block, + n_blocks_to_sync: fetch_config.n_blocks_to_sync, + verify: fetch_config.verify, + sync_polling_interval: fetch_config.sync_polling_interval, + backup_every_n_blocks, + pending_block_poll_interval, + }, + block_metrics, + db_metrics, + starting_block, + chain_id, + telemetry, + ),)?; Ok(()) } diff --git a/crates/node/src/cli/l1.rs b/crates/node/src/cli/l1.rs new file mode 100644 index 000000000..164af2ab9 --- /dev/null +++ b/crates/node/src/cli/l1.rs @@ -0,0 +1,25 @@ +use url::Url; +const DEFAULT_GAS_PRICE_POLL_MS: u64 = 10_000; + +fn parse_url(s: &str) -> Result { + s.parse() +} + +#[derive(Clone, Debug, clap::Args)] +pub struct L1SyncParams { + /// Disable L1 sync. + #[clap(long, alias = "no-l1-sync", conflicts_with = "l1_endpoint")] + pub sync_l1_disabled: bool, + + /// The L1 rpc endpoint url for state verification. + #[clap(long, value_parser = parse_url, value_name = "ETHEREUM RPC URL", required_unless_present = "sync_l1_disabled")] + pub l1_endpoint: Option, + + /// Disable the gas price sync service. The sync service is responsible to fetch the fee history from the ethereum. + #[clap(long, alias = "no-gas-price-sync")] + pub gas_price_sync_disabled: bool, + + /// Time in milliseconds in which the gas price worker will fetch the gas price. + #[clap(long, default_value_t = DEFAULT_GAS_PRICE_POLL_MS, alias = "gas-price-poll")] + pub gas_price_poll_ms: u64, +} diff --git a/crates/node/src/cli/mod.rs b/crates/node/src/cli/mod.rs index b8a52f0be..1f26b7a86 100644 --- a/crates/node/src/cli/mod.rs +++ b/crates/node/src/cli/mod.rs @@ -1,10 +1,12 @@ pub mod block_production; pub mod db; +pub mod l1; pub mod prometheus; pub mod rpc; pub mod sync; pub mod telemetry; +use crate::cli::l1::L1SyncParams; pub use block_production::*; pub use db::*; pub use prometheus::*; @@ -31,6 +33,10 @@ pub struct RunCmd { #[clap(flatten)] pub sync_params: SyncParams, + #[allow(missing_docs)] + #[clap(flatten)] + pub l1_sync_params: L1SyncParams, + #[allow(missing_docs)] #[clap(flatten)] pub telemetry_params: TelemetryParams, diff --git a/crates/node/src/cli/sync.rs b/crates/node/src/cli/sync.rs index b950af1d0..c687df2ac 100644 --- a/crates/node/src/cli/sync.rs +++ b/crates/node/src/cli/sync.rs @@ -2,11 +2,6 @@ use crate::cli::NetworkType; use dc_sync::fetch::fetchers::FetchConfig; use starknet_api::core::ChainId; use std::time::Duration; -use url::Url; - -fn parse_url(s: &str) -> Result { - s.parse() -} #[derive(Clone, Debug, clap::Args)] pub struct SyncParams { @@ -14,14 +9,6 @@ pub struct SyncParams { #[clap(long, alias = "no-sync")] pub sync_disabled: bool, - /// Disable L1 sync. - #[clap(long, alias = "no-l1-sync")] - pub sync_l1_disabled: bool, - - /// The L1 rpc endpoint url for state verification. - #[clap(long, value_parser = parse_url, value_name = "ETHEREUM RPC URL")] - pub l1_endpoint: Option, - /// The block you want to start syncing from. #[clap(long, value_name = "BLOCK NUMBER")] pub starting_block: Option, @@ -84,7 +71,6 @@ impl SyncParams { api_key: self.gateway_key.clone(), sync_polling_interval: polling, n_blocks_to_sync: self.n_blocks_to_sync, - sync_l1_disabled: self.sync_l1_disabled, } } } diff --git a/crates/node/src/main.rs b/crates/node/src/main.rs index 9ccd5d822..59fefd823 100644 --- a/crates/node/src/main.rs +++ b/crates/node/src/main.rs @@ -5,23 +5,21 @@ use std::sync::Arc; use anyhow::Context; use clap::Parser; - mod cli; mod service; mod util; +use crate::service::L1SyncService; use cli::RunCmd; use dc_db::DatabaseService; -use dc_mempool::{L1DataProvider, Mempool}; +use dc_mempool::{GasPriceProvider, L1DataProvider, Mempool}; use dc_metrics::MetricsService; use dc_rpc::providers::AddTransactionProvider; use dc_telemetry::{SysInfo, TelemetryService}; -use dp_block::header::{GasPrices, L1DataAvailabilityMode}; use dp_convert::ToFelt; use dp_utils::service::{Service, ServiceGroup}; use service::{BlockProductionService, RpcService, SyncService}; use starknet_providers::SequencerGatewayProvider; - const GREET_IMPL_NAME: &str = "Deoxys"; const GREET_SUPPORT_URL: &str = "https://github.com/KasarLabs/deoxys/issues"; const GREET_AUTHORS: &[&str] = &["KasarLabs "]; @@ -76,29 +74,26 @@ async fn main() -> anyhow::Result<()> { .await .context("Initializing db service")?; + let l1_gas_setter = GasPriceProvider::new(); + let l1_data_provider: Arc = Arc::new(l1_gas_setter.clone()); + + let l1_service = L1SyncService::new( + &run_cmd.l1_sync_params, + &db_service, + prometheus_service.registry(), + l1_gas_setter, + chain_config.chain_id.clone(), + chain_config.eth_core_contract_address, + ) + .await + .context("Initializing the l1 sync service")?; + // Block provider startup. // `rpc_add_txs_method_provider` is a trait object that tells the RPC task where to put the transactions when using the Write endpoints. let (block_provider_service, rpc_add_txs_method_provider): (_, Arc) = match run_cmd.authority { // Block production service. (authority) true => { - struct DummyProvider; - impl L1DataProvider for DummyProvider { - fn get_gas_prices(&self) -> GasPrices { - GasPrices { - eth_l1_gas_price: 100, - strk_l1_gas_price: 90, - eth_l1_data_gas_price: 10, - strk_l1_data_gas_price: 9, - } - } - fn get_da_mode(&self) -> L1DataAvailabilityMode { - L1DataAvailabilityMode::Blob - } - } - - let l1_data_provider: Arc = Arc::new(DummyProvider); - let mempool = Arc::new(Mempool::new(Arc::clone(db_service.backend()), Arc::clone(&l1_data_provider))); let block_production_service = BlockProductionService::new( @@ -154,6 +149,7 @@ async fn main() -> anyhow::Result<()> { let app = ServiceGroup::default() .with(db_service) + .with(l1_service) .with(block_provider_service) .with(rpc_service) .with(telemetry_service) diff --git a/crates/node/src/service/l1.rs b/crates/node/src/service/l1.rs new file mode 100644 index 000000000..728b46d00 --- /dev/null +++ b/crates/node/src/service/l1.rs @@ -0,0 +1,101 @@ +use crate::cli::l1::L1SyncParams; +use alloy::primitives::Address; +use anyhow::Context; +use dc_db::{DatabaseService, DeoxysBackend}; +use dc_eth::client::{EthereumClient, L1BlockMetrics}; +use dc_mempool::GasPriceProvider; +use dc_metrics::MetricsRegistry; +use dp_convert::ToFelt; +use dp_utils::service::Service; +use primitive_types::H160; +use starknet_api::core::ChainId; +use std::sync::Arc; +use std::time::Duration; +use tokio::task::JoinSet; + +#[derive(Clone)] +pub struct L1SyncService { + db_backend: Arc, + eth_client: Option, + l1_gas_provider: GasPriceProvider, + chain_id: ChainId, + gas_price_sync_disabled: bool, + gas_price_poll_ms: Duration, +} + +impl L1SyncService { + pub async fn new( + config: &L1SyncParams, + db: &DatabaseService, + metrics_handle: MetricsRegistry, + l1_gas_provider: GasPriceProvider, + chain_id: ChainId, + l1_core_address: H160, + ) -> anyhow::Result { + let eth_client = if !config.sync_l1_disabled { + if let Some(l1_rpc_url) = &config.l1_endpoint { + let core_address = Address::from_slice(l1_core_address.as_bytes()); + let l1_block_metrics = L1BlockMetrics::register(&metrics_handle).unwrap(); + Some( + EthereumClient::new(l1_rpc_url.clone(), core_address, l1_block_metrics) + .await + .context("Creating ethereum client")?, + ) + } else { + anyhow::bail!( + "No Ethereum endpoint provided. You need to provide one using --l1-endpoint in order to verify the synced state or disable the l1 watcher using --no-l1-sync." + ); + } + } else { + None + }; + + let gas_price_sync_disabled = config.gas_price_sync_disabled; + let gas_price_poll_ms = Duration::from_secs(config.gas_price_poll_ms); + + if !gas_price_sync_disabled { + let eth_client = eth_client.clone().ok_or_else(|| { + anyhow::anyhow!("EthereumClient is required to start the l1 sync service but not provided.") + })?; + // running at-least once before the block production service + dc_eth::l1_gas_price::gas_price_worker(ð_client, l1_gas_provider.clone(), gas_price_poll_ms).await?; + } + + Ok(Self { + db_backend: Arc::clone(db.backend()), + eth_client, + l1_gas_provider, + chain_id, + gas_price_sync_disabled, + gas_price_poll_ms, + }) + } +} + +#[async_trait::async_trait] +impl Service for L1SyncService { + async fn start(&mut self, join_set: &mut JoinSet>) -> anyhow::Result<()> { + let L1SyncService { eth_client, l1_gas_provider, chain_id, gas_price_sync_disabled, gas_price_poll_ms, .. } = + self.clone(); + + let db_backend = Arc::clone(&self.db_backend); + + let eth_client = eth_client.ok_or_else(|| { + anyhow::anyhow!("EthereumClient is required to start the l1 sync service but not provided.") + })?; + + join_set.spawn(async move { + dc_eth::sync::l1_sync_worker( + &db_backend, + ð_client, + chain_id.to_felt(), + l1_gas_provider, + gas_price_sync_disabled, + gas_price_poll_ms, + ) + .await + }); + + Ok(()) + } +} diff --git a/crates/node/src/service/mod.rs b/crates/node/src/service/mod.rs index fa2744d11..58aeddd33 100644 --- a/crates/node/src/service/mod.rs +++ b/crates/node/src/service/mod.rs @@ -1,7 +1,9 @@ mod block_production; +mod l1; mod rpc; mod sync; pub use block_production::BlockProductionService; +pub use l1::L1SyncService; pub use rpc::RpcService; pub use sync::SyncService; diff --git a/crates/node/src/service/sync.rs b/crates/node/src/service/sync.rs index b8371a308..afa3ed8ff 100644 --- a/crates/node/src/service/sync.rs +++ b/crates/node/src/service/sync.rs @@ -1,9 +1,7 @@ use crate::cli::{NetworkType, SyncParams}; -use alloy::primitives::Address; use anyhow::Context; use dc_db::db_metrics::DbMetrics; use dc_db::{DatabaseService, DeoxysBackend}; -use dc_eth::client::{EthereumClient, L1BlockMetrics}; use dc_metrics::MetricsRegistry; use dc_sync::fetch::fetchers::FetchConfig; use dc_sync::metrics::block_metrics::BlockMetrics; @@ -19,7 +17,6 @@ pub struct SyncService { db_backend: Arc, fetch_config: FetchConfig, backup_every_n_blocks: Option, - eth_client: Option, starting_block: Option, block_metrics: BlockMetrics, db_metrics: DbMetrics, @@ -37,32 +34,13 @@ impl SyncService { metrics_handle: MetricsRegistry, telemetry: TelemetryHandle, ) -> anyhow::Result { - let block_metrics = BlockMetrics::register(&metrics_handle).context("Registering block metrics")?; - let db_metrics = DbMetrics::register(&metrics_handle).context("Registering db metrics")?; + let block_metrics = BlockMetrics::register(&metrics_handle)?; + let db_metrics = DbMetrics::register(&metrics_handle)?; let fetch_config = config.block_fetch_config(chain_config.chain_id.clone(), network); - let eth_client = if !config.sync_l1_disabled { - if let Some(l1_endpoint) = &config.l1_endpoint { - let core_address = Address::from_slice(chain_config.eth_core_contract_address.as_bytes()); - let l1_metrics = L1BlockMetrics::register(&metrics_handle).context("Registering L1 metrics")?; - Some( - EthereumClient::new(l1_endpoint.clone(), core_address, l1_metrics) - .await - .context("Creating ethereum client")?, - ) - } else { - return Err(anyhow::anyhow!( - "❗ No L1 endpoint provided. You must provide one in order to verify the synced state." - )); - } - } else { - None - }; - Ok(Self { db_backend: Arc::clone(db.backend()), fetch_config, - eth_client, starting_block: config.starting_block, backup_every_n_blocks: config.backup_every_n_blocks, block_metrics, @@ -83,7 +61,6 @@ impl Service for SyncService { let SyncService { fetch_config, backup_every_n_blocks, - eth_client, starting_block, block_metrics, db_metrics, @@ -98,7 +75,6 @@ impl Service for SyncService { dc_sync::starknet_sync_worker::sync( &db_backend, fetch_config, - eth_client, starting_block, backup_every_n_blocks, block_metrics,