diff --git a/Cargo.lock b/Cargo.lock index b5adef70..f4d6f661 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,7 +15,7 @@ dependencies = [ [[package]] name = "actor-system-error" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "derive_more 0.99.18", ] @@ -149,9 +149,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfb9e6e5fa5d6981b8b0d4a5e50f9e2d3de3872bdd57832f479389b5e072136" +checksum = "d8cbebb817e6ada1abb27e642592a39eebc963eb0b9e78f66c467549f3903770" dependencies = [ "alloy-consensus", "alloy-contract", @@ -185,9 +185,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42642aed67f938363d9c7543e5ca4163cfb4205d9ec15fe933dc4e865d2932dd" +checksum = "cdf02dfacfc815214f9b54ff50d54900ba527a68fd73e2c5637ced3460005045" dependencies = [ "alloy-eips", "alloy-primitives", @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "694f433e4105c2c5302e4c97f0fa408fa9058bd4bbf19398c0506a46ed3df255" +checksum = "d45354c6946d064827d3b85041876aad9490b634f1761139934f8b1f65686b09" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -263,20 +263,21 @@ dependencies = [ [[package]] name = "alloy-eip7702" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeffd2590ce780ddfaa9d0ae340eb2b4e08627650c4676eef537cef0b4bf535d" +checksum = "64ffc577390ce50234e02d841214b3dc0bea6aaaae8e04bbf3cb82e9a45da9eb" dependencies = [ "alloy-primitives", "alloy-rlp", + "derive_more 1.0.0", "serde", ] [[package]] name = "alloy-eips" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbc52a30df46f9831ed74557dfad0d94b12420393662a8b9ef90e2d6c8cb4b0" +checksum = "769da342b6bcd945013925ef4c40763cc82f11e002c60702dba8b444bb60e5a7" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -292,9 +293,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0787d1688b9806290313cc335d416cc7ee39b11e3245f3d218544c62572d92ba" +checksum = "c698ce0ada980b17f0323e1a28c7da8a2e9abc6dff5be9ee33d1525b28ac46b6" dependencies = [ "alloy-primitives", "alloy-serde", @@ -315,9 +316,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55a16a5f9ca498a217c060414bcd1c43e934235dc8058b31b87dcd69ff4f105" +checksum = "c1050e1d65524c030b17442b6546b564da51fdab7f71bd534b001ba65f2ebb16" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -329,9 +330,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d236a8c3e1d5adc09b1b63c81815fc9b757d9a4ba9482cc899f9679b55dd437" +checksum = "da34a18446a27734473af3d77eb21c5ebbdf97ea8eb65c39c0b50916bc659023" dependencies = [ "alloy-consensus", "alloy-eips", @@ -350,9 +351,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15a0990fa8a56d85a42d6a689719aa4eebf5e2f1a5c5354658c0bfc52cac9a" +checksum = "9a968c063fcfcb937736665c865a71fc2242b68916156f5ffa41fee7b44bb695" dependencies = [ "alloy-consensus", "alloy-eips", @@ -363,9 +364,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2249f3c3ce446cf4063fe3d1aa7530823643c2706a1cc63045e0683ebc497a0a" +checksum = "439fc6a933b9f8e8b272a8cac35dbeabaf2b2eaf9590482bebedb5782153118e" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -408,9 +409,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "316f522bb6f9ac3805132112197957013b570e20cfdad058e8339dae6030c849" +checksum = "c45dbc0e3630becef9e988b69d43339f68d67e32a854e3c855bc28bd5031895b" dependencies = [ "alloy-chains", "alloy-consensus", @@ -450,9 +451,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222cd9b17b1c5ad48de51a88ffbdb17f17145170288f22662f80ac88739125e6" +checksum = "1e3961a56e10f44bfd69dd3f4b0854b90b84c612b0c43708e738933e8b47f93a" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -491,9 +492,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b2ab59712c594c9624aaa69e38e4d38f180cb569f1fa46cdaf8c21fd50793e5" +checksum = "917e5504e4f8f7e39bdc322ff81589ed54c1e462240adaeb58162c2d986a5a2b" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -515,9 +516,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba21284319e12d053baa204d438db6c1577aedd94c1298e4becefdac1f9cec87" +checksum = "07c7eb2dc6db1dd41e5e7bd2b98a38813854efc30e034afd90d1e420e7f3de2b" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -527,9 +528,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba40bea86c3102b9ed9b3be579e32e0b3e54e766248d873de5fc0437238c8df2" +checksum = "2640928d9b1d43bb1cec7a0d615e10c2b407c5bd8ff1fcbe49e6318a2b62d731" dependencies = [ "alloy-primitives", "alloy-serde", @@ -538,9 +539,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35894711990019fafff0012b82b9176cbb744516eb2a9bbe6b8e5cae522163ee" +checksum = "e855b0daccf2320ba415753c3fed422abe9d3ad5d77b2d6cafcc9bcf32fe387f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -557,9 +558,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2843c195675f06b29c09a4315cccdc233ab5bdc7c0a3775909f9f0cab5e9ae0f" +checksum = "35c2661ca6785add8fc37aff8005439c806ffad58254c19939c6f59ac0d6596e" dependencies = [ "alloy-primitives", "serde", @@ -568,9 +569,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b2a00d9803dfef99963303ffe41a7bf2221f3342f0a503d6741a9f4a18e5e5" +checksum = "67eca011160d18a7dc6d8cdc1e8dc13e2e86c908f8e41b02aa76e429d6fe7085" dependencies = [ "alloy-primitives", "async-trait", @@ -582,9 +583,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a2505d4f8c98dcae86152d58d549cb4bcf953f8352fca903410e0a0ef535571" +checksum = "1c54b195a6ee5a83f32e7c697b4e6b565966737ed5a2ef9176bbbb39f720d023" dependencies = [ "alloy-consensus", "alloy-network", @@ -671,9 +672,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dc2c8f6b8c227ef0398f702d954c4ab572c2ead3c1ed4a5157aa1cbaf959747" +checksum = "3e4a136e733f55fef0870b81e1f8f1db28e78973d1b1ae5a5df642ba39538a07" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -691,9 +692,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd328e990d57f4c4e63899fb2c26877597d6503f8e0022a3d71b2d753ecbfc0c" +checksum = "1a6b358a89b6d107b92d09b61a61fbc04243942182709752c796f4b29402cead" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -779,9 +780,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "approx" @@ -4679,7 +4680,7 @@ dependencies = [ [[package]] name = "galloc" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "gear-dlmalloc", ] @@ -4753,7 +4754,7 @@ dependencies = [ [[package]] name = "gclient" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "anyhow", "async-trait", @@ -4796,7 +4797,7 @@ dependencies = [ [[package]] name = "gcore" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "gear-core-errors 1.5.0", "gear-stack-buffer 1.5.0", @@ -4831,7 +4832,7 @@ dependencies = [ [[package]] name = "gear-common" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "derive_more 0.99.18", "enum-iterator 1.5.0", @@ -4898,7 +4899,7 @@ dependencies = [ [[package]] name = "gear-common-codegen" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "quote", "syn 2.0.82", @@ -4926,7 +4927,7 @@ dependencies = [ [[package]] name = "gear-core" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "blake2", "byteorder", @@ -5009,7 +5010,7 @@ dependencies = [ [[package]] name = "gear-core-backend" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "actor-system-error 1.5.0", "blake2", @@ -5067,7 +5068,7 @@ dependencies = [ [[package]] name = "gear-core-errors" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "derive_more 0.99.18", "enum-iterator 1.5.0", @@ -5101,7 +5102,7 @@ dependencies = [ [[package]] name = "gear-core-processor" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "actor-system-error 1.5.0", "derive_more 0.99.18", @@ -5150,7 +5151,7 @@ dependencies = [ [[package]] name = "gear-lazy-pages" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "cfg-if", "derive_more 0.99.18", @@ -5214,7 +5215,7 @@ dependencies = [ [[package]] name = "gear-lazy-pages-common" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "gear-core 1.5.0", "num_enum 0.6.1", @@ -5258,7 +5259,7 @@ dependencies = [ [[package]] name = "gear-lazy-pages-native-interface" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "gear-core 1.5.0", "gear-lazy-pages 1.5.0", @@ -5282,7 +5283,7 @@ dependencies = [ [[package]] name = "gear-node-wrapper" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "anyhow", "rand 0.8.5", @@ -5360,7 +5361,7 @@ dependencies = [ [[package]] name = "gear-sandbox" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "gear-sandbox-env 1.5.0", "gear-sandbox-interface 1.5.0", @@ -5406,7 +5407,7 @@ dependencies = [ [[package]] name = "gear-sandbox-env" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "parity-scale-codec", "sp-debug-derive 8.0.0", @@ -5440,7 +5441,7 @@ dependencies = [ [[package]] name = "gear-sandbox-host" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "defer", "environmental", @@ -5504,7 +5505,7 @@ dependencies = [ [[package]] name = "gear-sandbox-interface" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "gear-sandbox-host 1.5.0", "log", @@ -5541,7 +5542,7 @@ dependencies = [ [[package]] name = "gear-ss58" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "blake2", "bs58 0.5.1", @@ -5572,7 +5573,7 @@ dependencies = [ [[package]] name = "gear-stack-buffer" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" [[package]] name = "gear-stack-buffer" @@ -5588,7 +5589,7 @@ source = "git+https://github.com/gear-tech/gear.git?tag=v1.6.2#93eb5b59abc5b3567 [[package]] name = "gear-utils" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "env_logger 0.10.2", "gear-core 1.5.0", @@ -5625,7 +5626,7 @@ checksum = "bbfbfa701dc65e683fcd2fb24f046bcef22634acbdf47ad14724637dc39ad05b" [[package]] name = "gear-wasm-builder" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "anyhow", "cargo_metadata", @@ -5694,7 +5695,7 @@ dependencies = [ [[package]] name = "gear-wasm-instrument" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "derive_more 0.99.18", "enum-iterator 1.5.0", @@ -5916,7 +5917,7 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "gmeta" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "blake2", "derive_more 0.99.18", @@ -6010,7 +6011,7 @@ dependencies = [ [[package]] name = "gprimitives" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "derive_more 0.99.18", "gear-ss58 1.5.0", @@ -6061,7 +6062,7 @@ dependencies = [ [[package]] name = "gsdk" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "anyhow", "base64 0.21.7", @@ -6124,7 +6125,7 @@ dependencies = [ [[package]] name = "gsdk-codegen" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "proc-macro2", "quote", @@ -6145,7 +6146,7 @@ dependencies = [ [[package]] name = "gstd" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "arrayvec 0.7.6", "const_format", @@ -6202,7 +6203,7 @@ dependencies = [ [[package]] name = "gstd-codegen" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "gprimitives 1.5.0", "proc-macro2", @@ -6242,7 +6243,7 @@ checksum = "54cc8b379c05124bb4b08d89b8c9b694175bb553213748f8c87356e793324e89" [[package]] name = "gtest" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "cargo_toml", "colored", @@ -8962,7 +8963,7 @@ dependencies = [ [[package]] name = "numerated" version = "1.5.0" -source = "git+https://github.com/gear-tech/gear.git?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" +source = "git+https://github.com/gear-tech/gear?tag=v1.5.0#33ff492338671fbd6bf63ee7c15e0ccfcf40fbcb" dependencies = [ "derive_more 0.99.18", "num-traits", @@ -10169,9 +10170,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -10713,6 +10714,7 @@ dependencies = [ "dotenv", "erc20-relay-client", "ethereum-client", + "ethereum-common", "futures", "gclient 1.6.2", "gear-core 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -11975,13 +11977,13 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.3" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +checksum = "22760a375f81a31817aeaf6f5081e9ccb7ffd7f2da1809a6e3fc82b6656f10d5" dependencies = [ "bitvec", "cfg-if", - "derive_more 0.99.18", + "derive_more 1.0.0", "parity-scale-codec", "scale-info-derive", "serde", @@ -11989,9 +11991,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.3" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +checksum = "abc61ebe25a5c410c0e245028fc9934bf8fa4817199ef5a24a68092edfd34614" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", @@ -13227,7 +13229,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#21930ed2019219c2ffd57c08c0bf675db467a91f" +source = "git+https://github.com/paritytech/polkadot-sdk#ed231828fbe662cdb78b24d54a98fc13a9a64fdd" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -13291,7 +13293,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#21930ed2019219c2ffd57c08c0bf675db467a91f" +source = "git+https://github.com/paritytech/polkadot-sdk#ed231828fbe662cdb78b24d54a98fc13a9a64fdd" dependencies = [ "proc-macro2", "quote", @@ -13324,7 +13326,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#21930ed2019219c2ffd57c08c0bf675db467a91f" +source = "git+https://github.com/paritytech/polkadot-sdk#ed231828fbe662cdb78b24d54a98fc13a9a64fdd" dependencies = [ "environmental", "parity-scale-codec", @@ -13598,7 +13600,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#21930ed2019219c2ffd57c08c0bf675db467a91f" +source = "git+https://github.com/paritytech/polkadot-sdk#ed231828fbe662cdb78b24d54a98fc13a9a64fdd" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -13642,7 +13644,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#21930ed2019219c2ffd57c08c0bf675db467a91f" +source = "git+https://github.com/paritytech/polkadot-sdk#ed231828fbe662cdb78b24d54a98fc13a9a64fdd" dependencies = [ "Inflector", "expander", @@ -13783,7 +13785,7 @@ checksum = "2d5bbc9339227d1b6a9b7ccd9b2920c818653d40eef1512f1e2e824d72e7a336" [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#21930ed2019219c2ffd57c08c0bf675db467a91f" +source = "git+https://github.com/paritytech/polkadot-sdk#ed231828fbe662cdb78b24d54a98fc13a9a64fdd" [[package]] name = "sp-storage" @@ -13815,7 +13817,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#21930ed2019219c2ffd57c08c0bf675db467a91f" +source = "git+https://github.com/paritytech/polkadot-sdk#ed231828fbe662cdb78b24d54a98fc13a9a64fdd" dependencies = [ "impl-serde 0.5.0", "parity-scale-codec", @@ -13878,7 +13880,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#21930ed2019219c2ffd57c08c0bf675db467a91f" +source = "git+https://github.com/paritytech/polkadot-sdk#ed231828fbe662cdb78b24d54a98fc13a9a64fdd" dependencies = [ "parity-scale-codec", "tracing", @@ -14024,7 +14026,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#21930ed2019219c2ffd57c08c0bf675db467a91f" +source = "git+https://github.com/paritytech/polkadot-sdk#ed231828fbe662cdb78b24d54a98fc13a9a64fdd" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -14335,9 +14337,9 @@ dependencies = [ [[package]] name = "subxt-core" -version = "0.37.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f41eb2e2eea6ed45649508cc735f92c27f1fcfb15229e75f8270ea73177345" +checksum = "3af3b36405538a36b424d229dc908d1396ceb0994c90825ce928709eac1a159a" dependencies = [ "base58", "blake2", @@ -14592,9 +14594,9 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] @@ -14621,9 +14623,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", diff --git a/ethereum/client/build.rs b/ethereum/client/build.rs index d32febc2..3902f266 100644 --- a/ethereum/client/build.rs +++ b/ethereum/client/build.rs @@ -1,7 +1,8 @@ use std::process::Command; fn main() { - println!("cargo::rerun-if-changed=*"); + println!("cargo::rerun-if-changed=../src"); + println!("cargo::rerun-if-changed=../lib"); Command::new("forge") .arg("build") diff --git a/ethereum/client/src/abi.rs b/ethereum/client/src/abi.rs index 6c887496..d9cf77e4 100644 --- a/ethereum/client/src/abi.rs +++ b/ethereum/client/src/abi.rs @@ -38,6 +38,12 @@ sol! { } } +sol!( + #[sol(rpc)] + IERC20Treasury, + "../out/IERC20Treasury.sol/IERC20Treasury.json" +); + impl ContentMessage { pub fn to_bytes(&self) -> Vec { let mut ret: Vec = Vec::with_capacity(32 + 32 + 20 + self.data.len()); diff --git a/ethereum/client/src/lib.rs b/ethereum/client/src/lib.rs index d55df95e..eb40dd86 100644 --- a/ethereum/client/src/lib.rs +++ b/ethereum/client/src/lib.rs @@ -1,7 +1,5 @@ use std::{marker::PhantomData, str::FromStr}; -use abi::IRelayer::MerkleRoot; - use alloy::{ contract::Event, network::{Ethereum, EthereumWallet}, @@ -21,22 +19,22 @@ use alloy::{ Transport, }, }; - +use primitive_types::{H160, H256}; use reqwest::Url; -pub use error::Error; - -use crate::abi::{ - ContentMessage, IMessageQueue, IMessageQueue::IMessageQueueInstance, IRelayer, - IRelayer::IRelayerInstance, -}; pub use alloy::primitives::TxHash; #[cfg(test)] mod tests; mod abi; +use abi::{ + ContentMessage, IERC20Treasury, IMessageQueue, IMessageQueue::IMessageQueueInstance, IRelayer, + IRelayer::IRelayerInstance, IRelayer::MerkleRoot, +}; + pub mod error; +pub use error::Error; type ProviderType = FillProvider< JoinFill< @@ -64,6 +62,16 @@ pub struct MerkleRootEntry { pub block_number: u64, } +#[derive(Debug, Clone)] +pub struct DepositEventEntry { + pub from: H160, + pub to: H256, + pub token: H160, + pub amount: primitive_types::U256, + + pub tx_hash: TxHash, +} + #[derive(Debug)] pub enum TxStatus { Finalized, @@ -121,6 +129,11 @@ impl EthApi { }) } + // TODO: Don't expose provider here. + pub fn raw_provider(&self) -> &ProviderType { + &self.contracts.provider + } + pub async fn get_approx_balance(&self) -> Result { self.contracts.get_approx_balance(self.public_key).await } @@ -158,10 +171,44 @@ impl EthApi { self.contracts.fetch_merkle_roots_in_range(from, to).await } + pub async fn fetch_deposit_events( + &self, + contract_address: H160, + block: u64, + ) -> Result, Error> { + Ok(self + .contracts + .fetch_deposit_events(Address::from_slice(contract_address.as_bytes()), block) + .await? + .into_iter() + .map( + |( + IERC20Treasury::Deposit { + from, + to, + token, + amount, + }, + tx_hash, + )| DepositEventEntry { + from: H160(*from.0), + to: H256(to.0), + token: H160(*token.0), + amount: primitive_types::U256::from_little_endian(&amount.to_le_bytes_vec()), + tx_hash, + }, + ) + .collect()) + } + pub async fn block_number(&self) -> Result { self.contracts.block_number().await } + pub async fn finalized_block_number(&self) -> Result { + self.contracts.finalized_block_number().await + } + #[allow(clippy::too_many_arguments)] pub async fn provide_content_message( &self, @@ -258,6 +305,17 @@ where self.provider.get_block_number().await.map_err(|e| e.into()) } + pub async fn finalized_block_number(&self) -> Result { + Ok(self + .provider + .get_block_by_number(BlockNumberOrTag::Finalized, false) + .await + .map_err(Error::ErrorInHTTPTransport)? + .ok_or(Error::ErrorFetchingBlock)? + .header + .number) + } + pub async fn fetch_merkle_roots(&self, depth: u64) -> Result, Error> { let current_block: u64 = self.provider.get_block_number().await?; @@ -291,6 +349,33 @@ where .collect()) } + pub async fn fetch_deposit_events( + &self, + contract_address: Address, + block: u64, + ) -> Result, Error> { + let filter = Filter::new() + .address(contract_address) + .event_signature(IERC20Treasury::Deposit::SIGNATURE_HASH) + .from_block(block) + .to_block(block); + + let event: Event = + Event::new(self.provider.clone(), filter); + + let logs = event.query().await.map_err(Error::ErrorQueryingEvent)?; + + logs.into_iter() + .map(|(event, log)| { + Ok(( + event, + log.transaction_hash + .ok_or(Error::ErrorFetchingTransaction)?, + )) + }) + .collect() + } + #[allow(clippy::too_many_arguments)] pub async fn provide_content_message( &self, diff --git a/gear-rpc-client/src/lib.rs b/gear-rpc-client/src/lib.rs index fbe05860..b3b06f22 100644 --- a/gear-rpc-client/src/lib.rs +++ b/gear-rpc-client/src/lib.rs @@ -55,7 +55,7 @@ type GearHeader = sp_runtime::generic::Header AnyResult<()> { - log::info!("Started"); - - let RelayErc20Args { - program_id, - beacon_endpoint, - vara_domain, - vara_port, - vara_suri, - eth_endpoint, - tx_hash, - } = args; - - let program_id: [u8; 32] = - utils::try_from_hex_encoded(&program_id).expect("Expecting correct ProgramId"); - let tx_hash: [u8; 32] = - utils::try_from_hex_encoded(&tx_hash).expect("Expecting correct hash of a transaction"); - - let rpc_url = eth_endpoint.parse()?; - let provider = ProviderBuilder::new().on_http(rpc_url); - - let receipt = provider - .get_transaction_receipt(tx_hash.into()) - .await? - .ok_or(anyhow!("Transaction receipt is missing"))?; - - let block = match receipt.block_hash { - Some(hash) => provider - .get_block_by_hash(hash, BlockTransactionsKind::Hashes) - .await? - .ok_or(anyhow!("Ethereum block (hash) is missing"))?, - None => match receipt.block_number { - Some(number) => provider - .get_block_by_number(BlockNumberOrTag::Number(number), false) - .await? - .ok_or(anyhow!("Ethereum block (number) is missing"))?, - None => return Err(anyhow!("Unable to get Ethereum block")), - }, - }; - - let beacon_root_parent = block - .header - .parent_beacon_block_root - .ok_or(anyhow!("Unable to determine root of parent beacon block"))?; - let block_number = block.header.number; - let client_http = Client::new(); - let proof_block = build_inclusion_proof( - &client_http, - &beacon_endpoint, - &beacon_root_parent, - block_number, - ) - .await?; - - // receipt Merkle-proof - let tx_index = receipt - .transaction_index - .ok_or(anyhow!("Unable to determine transaction index"))?; - let receipts = provider - .get_block_receipts(BlockId::Number(BlockNumberOrTag::Number(block_number))) - .await? - .unwrap_or_default() - .iter() - .map(|tx_receipt| { - let receipt = tx_receipt.as_ref(); - - tx_receipt - .transaction_index - .map(|i| (i, eth_utils::map_receipt_envelope(receipt))) - }) - .collect::>>() - .unwrap_or_default(); - - let MerkleProof { proof, receipt } = eth_utils::generate_merkle_proof(tx_index, &receipts[..])?; - - let mut receipt_rlp = Vec::with_capacity(Encodable::length(&receipt)); - Encodable::encode(&receipt, &mut receipt_rlp); - let message = EthToVaraEvent { - proof_block, - proof, - transaction_index: tx_index, - receipt_rlp, - }; - - let client = GearApi::init_with(WSAddress::new(vara_domain, vara_port), vara_suri).await?; - let gas_limit_block = client.block_gas_limit()?; - // use 95% of block gas limit for all extrinsics - let gas_limit = gas_limit_block / 100 * 95; - - let remoting = GClientRemoting::new(client); - - let mut erc20_service = Erc20Relay::new(remoting.clone()); - let mut listener = erc20_relay_client::erc_20_relay::events::listener(remoting.clone()); - let mut events = listener.listen().await.unwrap(); - - let result = erc20_service - .relay(message) - .with_gas_limit(gas_limit) - .send_recv(program_id.into()) - .await - .unwrap(); - - log::debug!("result = {result:?}"); - if result.is_ok() { - let event = events.next().await.unwrap(); - - log::debug!("event = {event:?}"); - } - - Ok(()) -} - -async fn build_inclusion_proof( - client_http: &Client, - rpc_url: &str, - beacon_root_parent: &[u8; 32], - block_number: u64, -) -> AnyResult { - let beacon_block_parent = - utils::get_block_by_hash(client_http, rpc_url, beacon_root_parent).await?; - - let beacon_block = LightBeaconBlock::from( - find_beacon_block(client_http, rpc_url, block_number, &beacon_block_parent).await?, - ); - - let slot = beacon_block.slot; - if slot % SLOTS_PER_EPOCH == 0 { - return Ok(BlockInclusionProof { - block: beacon_block, - headers: vec![], - }); - } - - let epoch_next = 1 + eth_utils::calculate_epoch(beacon_block.slot); - let slot_checkpoint = epoch_next * SLOTS_PER_EPOCH; - - Ok(BlockInclusionProof { - block: beacon_block, - headers: utils::request_headers(client_http, rpc_url, slot + 1, slot_checkpoint + 1) - .await? - .into_iter() - .collect(), - }) -} - -async fn find_beacon_block( - client_http: &Client, - rpc_url: &str, - block_number: u64, - block_start: &BeaconBlock, -) -> AnyResult { - match block_number.cmp(&block_start.body.execution_payload.block_number) { - Ordering::Less => { - return Err(anyhow!( - "Requested block number is behind the start beacon block" - )) - } - Ordering::Equal => return Ok(block_start.clone()), - Ordering::Greater => (), - } - - let block_finalized = utils::get_block_finalized(client_http, rpc_url).await?; - - let slot_start = block_start.slot + 1; - for slot in slot_start..=block_finalized.slot { - match utils::get_block(client_http, rpc_url, slot).await { - Ok(block) if block.body.execution_payload.block_number == block_number => { - return Ok(block) - } - Ok(_) => (), - Err(e) if e.downcast_ref::().is_some() => (), - Err(e) => return Err(e), - } - } - - Err(anyhow!("Block was not found")) -} diff --git a/relayer/src/ethereum_beacon_client/mod.rs b/relayer/src/ethereum_beacon_client/mod.rs new file mode 100644 index 00000000..5f3346af --- /dev/null +++ b/relayer/src/ethereum_beacon_client/mod.rs @@ -0,0 +1,212 @@ +use std::{ + cmp::{self, Ordering}, + error::Error, + fmt, + time::Duration, +}; + +use anyhow::{anyhow, Error as AnyError, Result as AnyResult}; +use checkpoint_light_client_io::{BeaconBlockHeader, Slot}; +use ethereum_common::{ + beacon::Block as BeaconBlock, + utils::{ + BeaconBlockHeaderResponse, BeaconBlockResponse, FinalityUpdate, FinalityUpdateResponse, + UpdateResponse, + }, + MAX_REQUEST_LIGHT_CLIENT_UPDATES, +}; +use reqwest::{Client, ClientBuilder, RequestBuilder}; +use serde::{de::DeserializeOwned, Deserialize}; + +pub mod slots_batch; +pub mod utils; + +#[derive(Clone, Debug)] +pub struct ErrorNotFound; + +impl fmt::Display for ErrorNotFound { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt("Not found (404)", f) + } +} + +impl Error for ErrorNotFound {} + +#[allow(dead_code)] +#[derive(Deserialize)] +struct CodeResponse { + code: u64, + message: String, +} + +#[derive(Clone)] +pub struct BeaconClient { + client: Client, + rpc_url: String, +} + +impl BeaconClient { + pub async fn new(rpc_url: String, timeout: Option) -> AnyResult { + let client = ClientBuilder::new(); + let client = match timeout { + Some(timeout) => client.timeout(timeout), + None => client, + }; + + let client = client + .build() + .expect("Failed to create reqwest http client"); + + Ok(Self { client, rpc_url }) + } + + pub async fn get_updates(&self, period: u64, count: u8) -> AnyResult { + let count = cmp::min(count, MAX_REQUEST_LIGHT_CLIENT_UPDATES); + let url = format!( + "{}/eth/v1/beacon/light_client/updates?start_period={}&count={}", + self.rpc_url, period, count + ); + + get::(self.client.get(&url)).await + } + + pub async fn get_block_header(&self, slot: u64) -> AnyResult { + let url = format!("{}/eth/v1/beacon/headers/{}", self.rpc_url, slot); + + get::(self.client.get(&url)) + .await + .map(|response| response.data.header.message) + } + + pub async fn get_block_finalized(&self) -> AnyResult { + let url = format!("{}/eth/v2/beacon/blocks/finalized", self.rpc_url); + + get::(self.client.get(&url)) + .await + .map(|response| response.data.message) + } + + pub async fn get_block(&self, slot: u64) -> AnyResult { + let url = format!("{}/eth/v2/beacon/blocks/{}", self.rpc_url, slot); + + get::(self.client.get(&url)) + .await + .map(|response| response.data.message) + } + + pub async fn get_block_by_hash(&self, hash: &[u8; 32]) -> AnyResult { + let mut hex_encoded = [0u8; 66]; + hex_encoded[0] = b'0'; + hex_encoded[1] = b'x'; + + hex::encode_to_slice(hash, &mut hex_encoded[2..]).expect("The buffer has the right size"); + let url = format!( + "{}/eth/v2/beacon/blocks/{}", + self.rpc_url, + String::from_utf8_lossy(&hex_encoded) + ); + + get::(self.client.get(&url)) + .await + .map(|response| response.data.message) + } + + pub async fn get_finality_update(&self) -> AnyResult { + let url = format!( + "{}/eth/v1/beacon/light_client/finality_update", + self.rpc_url + ); + + get::(self.client.get(&url)) + .await + .map(|response| response.data) + } + + pub async fn request_headers( + &self, + slot_start: Slot, + slot_end: Slot, + ) -> AnyResult> { + let batch_size = (slot_end - slot_start) as usize; + let mut requests_headers = Vec::with_capacity(batch_size); + for i in slot_start..slot_end { + requests_headers.push(self.get_block_header(i)); + } + + futures::future::join_all(requests_headers) + .await + .into_iter() + .filter(|maybe_header| !matches!(maybe_header, Err(e) if e.downcast_ref::().is_some())) + .collect::, _>>() + .map_err(|e| { + anyhow!("Failed to fetch block headers ([{slot_start}; {slot_end})): {e:?}") + }) + } + + pub async fn find_beacon_block( + &self, + block_number: u64, + block_start: &BeaconBlock, + ) -> AnyResult { + match block_number.cmp(&block_start.body.execution_payload.block_number) { + Ordering::Less => { + return Err(anyhow!( + "Requested block number is behind the start beacon block" + )) + } + Ordering::Equal => return Ok(block_start.clone()), + Ordering::Greater => (), + } + + let block_finalized = self.get_block_finalized().await?; + + let slot_start = block_start.slot + 1; + for slot in slot_start..=block_finalized.slot { + match self.get_block(slot).await { + Ok(block) if block.body.execution_payload.block_number == block_number => { + return Ok(block) + } + Ok(_) => (), + Err(e) if e.downcast_ref::().is_some() => {} + Err(e) => return Err(e), + } + } + + Err(anyhow!("Block was not found")) + } + + #[cfg(test)] + pub async fn get_bootstrap( + &self, + checkpoint: &str, + ) -> AnyResult { + let checkpoint_no_prefix = match checkpoint.starts_with("0x") { + true => &checkpoint[2..], + false => checkpoint, + }; + + let url = format!( + "{}/eth/v1/beacon/light_client/bootstrap/0x{checkpoint_no_prefix}", + self.rpc_url + ); + + get::(self.client.get(&url)) + .await + .map(|response| response.data) + } +} + +async fn get(request_builder: RequestBuilder) -> AnyResult { + let bytes = request_builder + .send() + .await + .map_err(AnyError::from)? + .bytes() + .await + .map_err(AnyError::from)?; + + match serde_json::from_slice::(&bytes) { + Ok(code_response) if code_response.code == 404 => Err(ErrorNotFound.into()), + _ => Ok(serde_json::from_slice::(&bytes).map_err(AnyError::from)?), + } +} diff --git a/relayer/src/ethereum_checkpoints/utils/slots_batch.rs b/relayer/src/ethereum_beacon_client/slots_batch.rs similarity index 100% rename from relayer/src/ethereum_checkpoints/utils/slots_batch.rs rename to relayer/src/ethereum_beacon_client/slots_batch.rs diff --git a/relayer/src/ethereum_beacon_client/utils.rs b/relayer/src/ethereum_beacon_client/utils.rs new file mode 100644 index 00000000..30ef8408 --- /dev/null +++ b/relayer/src/ethereum_beacon_client/utils.rs @@ -0,0 +1,80 @@ +use ark_serialize::CanonicalDeserialize; +use checkpoint_light_client_io::{ + ArkScale, G1TypeInfo, G2TypeInfo, SyncCommitteeKeys, SyncCommitteeUpdate, G1, G2, + SYNC_COMMITTEE_SIZE, +}; +use ethereum_common::{ + base_types::{BytesFixed, FixedArray}, + beacon::BLSPubKey, + utils::{FinalityUpdate, Update}, +}; + +pub fn sync_update_from_finality( + signature: G2, + finality_update: FinalityUpdate, +) -> SyncCommitteeUpdate { + SyncCommitteeUpdate { + signature_slot: finality_update.signature_slot, + attested_header: finality_update.attested_header, + finalized_header: finality_update.finalized_header, + sync_aggregate: finality_update.sync_aggregate, + sync_committee_next_aggregate_pubkey: None, + sync_committee_signature: G2TypeInfo(signature).into(), + sync_committee_next_pub_keys: None, + sync_committee_next_branch: None, + finality_branch: finality_update + .finality_branch + .into_iter() + .map(|BytesFixed(array)| array.0) + .collect::<_>(), + } +} + +pub fn map_public_keys( + compressed_public_keys: &FixedArray, +) -> Box { + let keys = compressed_public_keys + .0 + .iter() + .map(|BytesFixed(pub_key_compressed)| { + let pub_key = ::deserialize_compressed_unchecked( + &pub_key_compressed.0[..], + ) + .expect("Public keys have the required size"); + + let ark_scale: ArkScale = G1TypeInfo(pub_key).into(); + + ark_scale + }) + .collect::>(); + + Box::new(FixedArray(keys.try_into().expect( + "The size of keys array is guaranteed on the type level", + ))) +} + +pub fn sync_update_from_update(signature: G2, update: Update) -> SyncCommitteeUpdate { + let next_sync_committee_keys = map_public_keys(&update.next_sync_committee.pubkeys); + + SyncCommitteeUpdate { + signature_slot: update.signature_slot, + attested_header: update.attested_header, + finalized_header: update.finalized_header, + sync_aggregate: update.sync_aggregate, + sync_committee_next_aggregate_pubkey: Some(update.next_sync_committee.aggregate_pubkey), + sync_committee_signature: G2TypeInfo(signature).into(), + sync_committee_next_pub_keys: Some(next_sync_committee_keys), + sync_committee_next_branch: Some( + update + .next_sync_committee_branch + .into_iter() + .map(|BytesFixed(array)| array.0) + .collect::<_>(), + ), + finality_branch: update + .finality_branch + .into_iter() + .map(|BytesFixed(array)| array.0) + .collect::<_>(), + } +} diff --git a/relayer/src/ethereum_checkpoints/mod.rs b/relayer/src/ethereum_checkpoints/mod.rs index 7fdd3a62..bd18ccdc 100644 --- a/relayer/src/ethereum_checkpoints/mod.rs +++ b/relayer/src/ethereum_checkpoints/mod.rs @@ -6,19 +6,18 @@ use checkpoint_light_client_io::{ tree_hash::Hash256, Handle, HandleResult, Slot, SyncCommitteeUpdate, G2, }; +use ethereum_beacon_client::{slots_batch::Iter as SlotsBatchIter, BeaconClient}; use futures::{ future::{self, Either}, pin_mut, }; use gclient::{EventProcessor, GearApi, WSAddress}; use parity_scale_codec::Decode; -use reqwest::{Client, ClientBuilder}; use tokio::{ signal::unix::{self, SignalKind}, sync::mpsc::{self, Sender}, time::{self, Duration}, }; -use utils::slots_batch::Iter as SlotsBatchIter; #[cfg(test)] mod tests; @@ -26,7 +25,6 @@ mod tests; mod metrics; mod replay_back; mod sync_update; -pub mod utils; const SIZE_CHANNEL: usize = 100_000; const SIZE_BATCH: u64 = 30 * SLOTS_PER_EPOCH; @@ -50,18 +48,19 @@ pub async fn relay(args: RelayCheckpointsArgs) { }, } = args; - let program_id = utils::try_from_hex_encoded(&program_id).expect("Expecting correct ProgramId"); + let program_id = + crate::hex_utils::decode_byte_array(&program_id).expect("Failed to parse ProgramId"); - let client_http = ClientBuilder::new() - .timeout(Duration::from_secs(beacon_timeout)) - .build() - .expect("Reqwest client should be created"); + let timeout = Some(Duration::from_secs(beacon_timeout)); + let beacon_client = BeaconClient::new(beacon_endpoint.clone(), timeout) + .await + .expect("Failed to connect to beacon node"); let mut signal_interrupt = unix::signal(SignalKind::interrupt()).expect("Set SIGINT handler"); let (sender, mut receiver) = mpsc::channel(SIZE_CHANNEL); - sync_update::spawn_receiver(client_http.clone(), beacon_endpoint.clone(), sender); + sync_update::spawn_receiver(beacon_client.clone(), sender); let client = GearApi::init_with(WSAddress::new(vara_domain, vara_port), vara_suri) .await @@ -92,8 +91,7 @@ pub async fn relay(args: RelayCheckpointsArgs) { checkpoint, })) => { if let Err(e) = replay_back::execute( - &client_http, - &beacon_endpoint, + &beacon_client, &client, program_id, gas_limit, diff --git a/relayer/src/ethereum_checkpoints/replay_back.rs b/relayer/src/ethereum_checkpoints/replay_back.rs index 3062ac83..4f75d7e3 100644 --- a/relayer/src/ethereum_checkpoints/replay_back.rs +++ b/relayer/src/ethereum_checkpoints/replay_back.rs @@ -1,9 +1,9 @@ use super::*; +use crate::ethereum_beacon_client::{self, BeaconClient}; #[allow(clippy::too_many_arguments)] pub async fn execute( - client_http: &Client, - beacon_endpoint: &str, + beacon_client: &BeaconClient, client: &GearApi, program_id: [u8; 32], gas_limit: u64, @@ -23,8 +23,7 @@ pub async fn execute( .ok_or(anyhow!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_end = {slot_end}."))?; replay_back_slots( - client_http, - beacon_endpoint, + beacon_client, client, program_id, gas_limit, @@ -36,14 +35,10 @@ pub async fn execute( } let period_start = 1 + eth_utils::calculate_period(slot_start); - let updates = utils::get_updates( - client_http, - beacon_endpoint, - period_start, - MAX_REQUEST_LIGHT_CLIENT_UPDATES, - ) - .await - .map_err(|e| anyhow!("Failed to get updates for period {period_start}: {e:?}"))?; + let updates = beacon_client + .get_updates(period_start, MAX_REQUEST_LIGHT_CLIENT_UPDATES) + .await + .map_err(|e| anyhow!("Failed to get updates for period {period_start}: {e:?}"))?; let slot_last = sync_update.finalized_header.slot; for update in updates { @@ -58,10 +53,10 @@ pub async fn execute( ) .map_err(|e| anyhow!("Failed to deserialize point on G2 (replay back): {e:?}"))?; - let sync_update = utils::sync_update_from_update(signature, update.data); + let sync_update = + ethereum_beacon_client::utils::sync_update_from_update(signature, update.data); replay_back_slots_start( - client_http, - beacon_endpoint, + beacon_client, client, program_id, gas_limit, @@ -71,8 +66,7 @@ pub async fn execute( .await?; replay_back_slots( - client_http, - beacon_endpoint, + beacon_client, client, program_id, gas_limit, @@ -90,8 +84,7 @@ pub async fn execute( .ok_or(anyhow!("Failed to create slots_batch::Iter with slot_start = {slot_start}, slot_last = {slot_last}."))?; replay_back_slots_start( - client_http, - beacon_endpoint, + beacon_client, client, program_id, gas_limit, @@ -101,8 +94,7 @@ pub async fn execute( .await?; replay_back_slots( - client_http, - beacon_endpoint, + beacon_client, client, program_id, gas_limit, @@ -116,8 +108,7 @@ pub async fn execute( } async fn replay_back_slots( - client_http: &Client, - beacon_endpoint: &str, + beacon_client: &BeaconClient, client: &GearApi, program_id: [u8; 32], gas_limit: u64, @@ -126,8 +117,7 @@ async fn replay_back_slots( for (slot_start, slot_end) in slots_batch_iter { log::debug!("slot_start = {slot_start}, slot_end = {slot_end}"); replay_back_slots_inner( - client_http, - beacon_endpoint, + beacon_client, client, program_id, slot_start, @@ -142,17 +132,14 @@ async fn replay_back_slots( #[allow(clippy::too_many_arguments)] async fn replay_back_slots_inner( - client_http: &Client, - beacon_endpoint: &str, + beacon_client: &BeaconClient, client: &GearApi, program_id: [u8; 32], slot_start: Slot, slot_end: Slot, gas_limit: u64, ) -> AnyResult<()> { - let payload = Handle::ReplayBack( - utils::request_headers(client_http, beacon_endpoint, slot_start, slot_end).await?, - ); + let payload = Handle::ReplayBack(beacon_client.request_headers(slot_start, slot_end).await?); let mut listener = client.subscribe().await?; @@ -181,8 +168,7 @@ async fn replay_back_slots_inner( #[allow(clippy::too_many_arguments)] async fn replay_back_slots_start( - client_http: &Client, - beacon_endpoint: &str, + beacon_client: &BeaconClient, client: &GearApi, program_id: [u8; 32], gas_limit: u64, @@ -195,7 +181,7 @@ async fn replay_back_slots_start( let payload = Handle::ReplayBackStart { sync_update, - headers: utils::request_headers(client_http, beacon_endpoint, slot_start, slot_end).await?, + headers: beacon_client.request_headers(slot_start, slot_end).await?, }; let mut listener = client.subscribe().await?; diff --git a/relayer/src/ethereum_checkpoints/sync_update.rs b/relayer/src/ethereum_checkpoints/sync_update.rs index d211f88b..c5ed2a8b 100644 --- a/relayer/src/ethereum_checkpoints/sync_update.rs +++ b/relayer/src/ethereum_checkpoints/sync_update.rs @@ -1,18 +1,15 @@ use super::*; +use crate::ethereum_beacon_client::{self, BeaconClient}; pub use checkpoint_light_client_io::sync_update::Error; use std::ops::ControlFlow::{self, *}; -pub fn spawn_receiver( - client_http: Client, - beacon_endpoint: String, - sender: Sender, -) { +pub fn spawn_receiver(beacon_client: BeaconClient, sender: Sender) { tokio::spawn(async move { log::info!("Update receiver spawned"); let mut failures = 0; loop { - match receive(&client_http, &beacon_endpoint, &sender).await { + match receive(&beacon_client, &sender).await { Ok(Break(_)) => break, Ok(Continue(_)) => (), Err(e) => { @@ -31,16 +28,17 @@ pub fn spawn_receiver( } async fn receive( - client_http: &Client, - beacon_endpoint: &str, + beacon_client: &BeaconClient, sender: &Sender, ) -> AnyResult> { - let finality_update = utils::get_finality_update(client_http, beacon_endpoint) + let finality_update = beacon_client + .get_finality_update() .await .map_err(|e| anyhow!("Unable to fetch FinalityUpdate: {e:?}"))?; let period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = utils::get_updates(client_http, beacon_endpoint, period, 1) + let mut updates = beacon_client + .get_updates(period, 1) .await .map_err(|e| anyhow!("Unable to fetch Updates: {e:?}"))?; @@ -61,9 +59,9 @@ async fn receive( .map_err(|e| anyhow!("Failed to deserialize point on G2: {e:?}"))?; let sync_update = if update.finalized_header.slot >= finality_update.finalized_header.slot { - utils::sync_update_from_update(signature, update) + ethereum_beacon_client::utils::sync_update_from_update(signature, update) } else { - utils::sync_update_from_finality(signature, finality_update) + ethereum_beacon_client::utils::sync_update_from_finality(signature, finality_update) }; if sender.send(sync_update).await.is_err() { diff --git a/relayer/src/ethereum_checkpoints/tests/mod.rs b/relayer/src/ethereum_checkpoints/tests/mod.rs index b8002695..6d0704f7 100644 --- a/relayer/src/ethereum_checkpoints/tests/mod.rs +++ b/relayer/src/ethereum_checkpoints/tests/mod.rs @@ -1,4 +1,4 @@ -use super::utils::{self, slots_batch}; +use crate::ethereum_beacon_client::{self, slots_batch, BeaconClient}; use checkpoint_light_client::WASM_BINARY; use checkpoint_light_client_io::{ ethereum_common::{ @@ -13,7 +13,6 @@ use checkpoint_light_client_io::{ }; use gclient::{EventListener, EventProcessor, GearApi, Result}; use parity_scale_codec::{Decode, Encode}; -use reqwest::Client; use tokio::time::{self, Duration}; const RPC_URL: &str = "http://127.0.0.1:5052"; @@ -66,13 +65,15 @@ async fn upload_program( } async fn init(network: Network) -> Result<()> { - let client_http = Client::new(); + let beacon_client = BeaconClient::new(RPC_URL.to_string(), None) + .await + .expect("Failed to connect to beacon node"); // use the latest finality header as a checkpoint for bootstrapping - let finality_update = utils::get_finality_update(&client_http, RPC_URL).await?; + let finality_update = beacon_client.get_finality_update().await?; let slot = finality_update.finalized_header.slot; let current_period = eth_utils::calculate_period(slot); - let mut updates = utils::get_updates(&client_http, RPC_URL, current_period, 1).await?; + let mut updates = beacon_client.get_updates(current_period, 1).await?; println!( "finality_update slot = {}, period = {}", @@ -92,17 +93,18 @@ async fn init(network: Network) -> Result<()> { update.finalized_header.slot, checkpoint_hex ); - let bootstrap = utils::get_bootstrap(&client_http, RPC_URL, &checkpoint_hex).await?; + let bootstrap = beacon_client.get_bootstrap(&checkpoint_hex).await?; let signature = ::deserialize_compressed( &update.sync_aggregate.sync_committee_signature.0 .0[..], ) .unwrap(); - let sync_update = utils::sync_update_from_update(signature, update); + let sync_update = ethereum_beacon_client::utils::sync_update_from_update(signature, update); println!("bootstrap slot = {}", bootstrap.header.slot); - let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); + let pub_keys = + ethereum_beacon_client::utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); let init = Init { network, sync_committee_current_pub_keys: pub_keys, @@ -146,12 +148,14 @@ async fn init_mainnet() -> Result<()> { #[ignore] #[tokio::test] async fn init_and_updating() -> Result<()> { - let client_http = Client::new(); + let beacon_client = BeaconClient::new(RPC_URL.to_string(), None) + .await + .expect("Failed to connect to beacon node"); // use the latest finality header as a checkpoint for bootstrapping - let finality_update = utils::get_finality_update(&client_http, RPC_URL).await?; + let finality_update = beacon_client.get_finality_update().await?; let current_period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = utils::get_updates(&client_http, RPC_URL, current_period, 1).await?; + let mut updates = beacon_client.get_updates(current_period, 1).await?; println!( "finality_update slot = {}, period = {}", @@ -171,17 +175,18 @@ async fn init_and_updating() -> Result<()> { update.finalized_header.slot, checkpoint_hex ); - let bootstrap = utils::get_bootstrap(&client_http, RPC_URL, &checkpoint_hex).await?; + let bootstrap = beacon_client.get_bootstrap(&checkpoint_hex).await?; let signature = ::deserialize_compressed( &update.sync_aggregate.sync_committee_signature.0 .0[..], ) .unwrap(); - let sync_update = utils::sync_update_from_update(signature, update); + let sync_update = ethereum_beacon_client::utils::sync_update_from_update(signature, update); println!("bootstrap slot = {}", bootstrap.header.slot); - let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); + let pub_keys = + ethereum_beacon_client::utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); let init = Init { network: Network::Holesky, sync_committee_current_pub_keys: pub_keys, @@ -205,11 +210,11 @@ async fn init_and_updating() -> Result<()> { println!(); for _ in 0..30 { - let update = utils::get_finality_update(&client_http, RPC_URL).await?; + let update = beacon_client.get_finality_update().await?; let slot: u64 = update.finalized_header.slot; let current_period = eth_utils::calculate_period(slot); - let mut updates = utils::get_updates(&client_http, RPC_URL, current_period, 1).await?; + let mut updates = beacon_client.get_updates(current_period, 1).await?; match updates.pop() { Some(update) if updates.is_empty() && update.data.finalized_header.slot >= slot => { println!("update sync committee"); @@ -218,8 +223,9 @@ async fn init_and_updating() -> Result<()> { &update.data.sync_aggregate.sync_committee_signature.0 .0[..], ) .unwrap(); - let payload = - Handle::SyncUpdate(utils::sync_update_from_update(signature, update.data)); + let payload = Handle::SyncUpdate( + ethereum_beacon_client::utils::sync_update_from_update(signature, update.data), + ); let gas_limit = client .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) .await? @@ -251,8 +257,9 @@ async fn init_and_updating() -> Result<()> { continue; }; - let payload = - Handle::SyncUpdate(utils::sync_update_from_finality(signature, update)); + let payload = Handle::SyncUpdate( + ethereum_beacon_client::utils::sync_update_from_finality(signature, update), + ); let gas_limit = client .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) @@ -284,7 +291,9 @@ async fn init_and_updating() -> Result<()> { #[ignore] #[tokio::test] async fn replaying_back() -> Result<()> { - let client_http = Client::new(); + let beacon_client = BeaconClient::new(RPC_URL.to_string(), None) + .await + .expect("Failed to connect to beacon node"); let finality_update: FinalityUpdateResponse = serde_json::from_slice(FINALITY_UPDATE_5_254_112).unwrap(); @@ -296,7 +305,7 @@ async fn replaying_back() -> Result<()> { // This SyncCommittee operated for about 13K slots, so we make adjustments let current_period = eth_utils::calculate_period(finality_update.finalized_header.slot); - let mut updates = utils::get_updates(&client_http, RPC_URL, current_period - 1, 1).await?; + let mut updates = beacon_client.get_updates(current_period - 1, 1).await?; let update = match updates.pop() { Some(update) if updates.is_empty() => update.data, @@ -305,7 +314,7 @@ async fn replaying_back() -> Result<()> { let checkpoint = update.finalized_header.tree_hash_root(); let checkpoint_hex = hex::encode(checkpoint); - let bootstrap = utils::get_bootstrap(&client_http, RPC_URL, &checkpoint_hex).await?; + let bootstrap = beacon_client.get_bootstrap(&checkpoint_hex).await?; println!("bootstrap slot = {}", bootstrap.header.slot); println!("update slot = {}", update.finalized_header.slot); @@ -313,7 +322,7 @@ async fn replaying_back() -> Result<()> { &update.sync_aggregate.sync_committee_signature.0 .0[..], ) .unwrap(); - let sync_update = utils::sync_update_from_update(signature, update); + let sync_update = ethereum_beacon_client::utils::sync_update_from_update(signature, update); let slot_start = sync_update.finalized_header.slot; let slot_end = finality_update.finalized_header.slot; println!( @@ -321,7 +330,8 @@ async fn replaying_back() -> Result<()> { slot_end - slot_start ); - let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); + let pub_keys = + ethereum_beacon_client::utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); let init = Init { network: Network::Sepolia, sync_committee_current_pub_keys: pub_keys, @@ -350,7 +360,7 @@ async fn replaying_back() -> Result<()> { if let Some((slot_start, slot_end)) = slots_batch_iter.next() { let mut requests_headers = Vec::with_capacity(batch_size as usize); for i in slot_start..slot_end { - requests_headers.push(utils::get_block_header(&client_http, RPC_URL, i)); + requests_headers.push(beacon_client.get_block_header(i)); } let headers = futures::future::join_all(requests_headers) @@ -365,7 +375,10 @@ async fn replaying_back() -> Result<()> { .unwrap(); let payload = Handle::ReplayBackStart { - sync_update: utils::sync_update_from_finality(signature, finality_update), + sync_update: ethereum_beacon_client::utils::sync_update_from_finality( + signature, + finality_update, + ), headers, }; @@ -391,7 +404,7 @@ async fn replaying_back() -> Result<()> { for (slot_start, slot_end) in slots_batch_iter { let mut requests_headers = Vec::with_capacity(batch_size as usize); for i in slot_start..slot_end { - requests_headers.push(utils::get_block_header(&client_http, RPC_URL, i)); + requests_headers.push(beacon_client.get_block_header(i)); } let headers = futures::future::join_all(requests_headers) @@ -460,9 +473,10 @@ async fn sync_update_requires_replaying_back() -> Result<()> { &update.sync_aggregate.sync_committee_signature.0 .0[..], ) .unwrap(); - let sync_update = utils::sync_update_from_update(signature, update); + let sync_update = ethereum_beacon_client::utils::sync_update_from_update(signature, update); - let pub_keys = utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); + let pub_keys = + ethereum_beacon_client::utils::map_public_keys(&bootstrap.current_sync_committee.pubkeys); let init = Init { network: Network::Sepolia, sync_committee_current_pub_keys: pub_keys, @@ -494,7 +508,10 @@ async fn sync_update_requires_replaying_back() -> Result<()> { ) .unwrap(); - let payload = Handle::SyncUpdate(utils::sync_update_from_finality(signature, finality_update)); + let payload = Handle::SyncUpdate(ethereum_beacon_client::utils::sync_update_from_finality( + signature, + finality_update, + )); let gas_limit = client .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) diff --git a/relayer/src/ethereum_checkpoints/utils/mod.rs b/relayer/src/ethereum_checkpoints/utils/mod.rs deleted file mode 100644 index b5bd50d5..00000000 --- a/relayer/src/ethereum_checkpoints/utils/mod.rs +++ /dev/null @@ -1,244 +0,0 @@ -use anyhow::{anyhow, Error as AnyError, Result as AnyResult}; -use ark_serialize::CanonicalDeserialize; -use checkpoint_light_client_io::{ - ethereum_common::{ - base_types::{BytesFixed, FixedArray}, - beacon::{BLSPubKey, Block as BeaconBlock}, - utils, MAX_REQUEST_LIGHT_CLIENT_UPDATES, - }, - ArkScale, BeaconBlockHeader, G1TypeInfo, G2TypeInfo, Slot, SyncCommitteeKeys, - SyncCommitteeUpdate, G1, G2, SYNC_COMMITTEE_SIZE, -}; -use reqwest::{Client, RequestBuilder}; -use serde::{de::DeserializeOwned, Deserialize}; -use std::{cmp, error::Error, fmt}; -use utils::{ - BeaconBlockHeaderResponse, BeaconBlockResponse, FinalityUpdate, FinalityUpdateResponse, Update, - UpdateResponse, -}; - -pub mod slots_batch; - -#[derive(Clone, Debug)] -pub struct ErrorNotFound; - -impl fmt::Display for ErrorNotFound { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt("Not found (404)", f) - } -} - -impl Error for ErrorNotFound {} - -#[allow(dead_code)] -#[derive(Deserialize)] -struct CodeResponse { - code: u64, - message: String, -} - -pub async fn get(request_builder: RequestBuilder) -> AnyResult { - let bytes = request_builder - .send() - .await - .map_err(AnyError::from)? - .bytes() - .await - .map_err(AnyError::from)?; - - match serde_json::from_slice::(&bytes) { - Ok(code_response) if code_response.code == 404 => Err(ErrorNotFound.into()), - _ => Ok(serde_json::from_slice::(&bytes).map_err(AnyError::from)?), - } -} - -#[cfg(test)] -pub async fn get_bootstrap( - client: &Client, - rpc_url: &str, - checkpoint: &str, -) -> AnyResult { - let checkpoint_no_prefix = match checkpoint.starts_with("0x") { - true => &checkpoint[2..], - false => checkpoint, - }; - - let url = format!("{rpc_url}/eth/v1/beacon/light_client/bootstrap/0x{checkpoint_no_prefix}",); - - get::(client.get(&url)) - .await - .map(|response| response.data) -} - -pub async fn get_updates( - client: &Client, - rpc_url: &str, - period: u64, - count: u8, -) -> AnyResult { - let count = cmp::min(count, MAX_REQUEST_LIGHT_CLIENT_UPDATES); - let url = format!( - "{rpc_url}/eth/v1/beacon/light_client/updates?start_period={period}&count={count}", - ); - - get::(client.get(&url)).await -} - -pub async fn get_block_header( - client: &Client, - rpc_url: &str, - slot: u64, -) -> AnyResult { - let url = format!("{rpc_url}/eth/v1/beacon/headers/{slot}"); - - get::(client.get(&url)) - .await - .map(|response| response.data.header.message) -} - -pub async fn get_block_finalized(client: &Client, rpc_url: &str) -> AnyResult { - let url = format!("{rpc_url}/eth/v2/beacon/blocks/finalized"); - - get::(client.get(&url)) - .await - .map(|response| response.data.message) -} - -pub async fn get_block(client: &Client, rpc_url: &str, slot: u64) -> AnyResult { - let url = format!("{rpc_url}/eth/v2/beacon/blocks/{slot}"); - - get::(client.get(&url)) - .await - .map(|response| response.data.message) -} - -pub async fn get_block_by_hash( - client: &Client, - rpc_url: &str, - hash: &[u8; 32], -) -> AnyResult { - let mut hex_encoded = [0u8; 66]; - hex_encoded[0] = b'0'; - hex_encoded[1] = b'x'; - - hex::encode_to_slice(hash, &mut hex_encoded[2..]).expect("The buffer has the right size"); - let url = format!( - "{rpc_url}/eth/v2/beacon/blocks/{}", - String::from_utf8_lossy(&hex_encoded) - ); - - get::(client.get(&url)) - .await - .map(|response| response.data.message) -} - -pub async fn get_finality_update(client: &Client, rpc_url: &str) -> AnyResult { - let url = format!("{rpc_url}/eth/v1/beacon/light_client/finality_update"); - - get::(client.get(&url)) - .await - .map(|response| response.data) -} - -pub fn map_public_keys( - compressed_public_keys: &FixedArray, -) -> Box { - let keys = compressed_public_keys - .0 - .iter() - .map(|BytesFixed(pub_key_compressed)| { - let pub_key = ::deserialize_compressed_unchecked( - &pub_key_compressed.0[..], - ) - .expect("Public keys have the required size"); - - let ark_scale: ArkScale = G1TypeInfo(pub_key).into(); - - ark_scale - }) - .collect::>(); - - Box::new(FixedArray(keys.try_into().expect( - "The size of keys array is guaranteed on the type level", - ))) -} - -pub fn sync_update_from_finality( - signature: G2, - finality_update: FinalityUpdate, -) -> SyncCommitteeUpdate { - SyncCommitteeUpdate { - signature_slot: finality_update.signature_slot, - attested_header: finality_update.attested_header, - finalized_header: finality_update.finalized_header, - sync_aggregate: finality_update.sync_aggregate, - sync_committee_next_aggregate_pubkey: None, - sync_committee_signature: G2TypeInfo(signature).into(), - sync_committee_next_pub_keys: None, - sync_committee_next_branch: None, - finality_branch: finality_update - .finality_branch - .into_iter() - .map(|BytesFixed(array)| array.0) - .collect::<_>(), - } -} - -pub fn sync_update_from_update(signature: G2, update: Update) -> SyncCommitteeUpdate { - let next_sync_committee_keys = map_public_keys(&update.next_sync_committee.pubkeys); - - SyncCommitteeUpdate { - signature_slot: update.signature_slot, - attested_header: update.attested_header, - finalized_header: update.finalized_header, - sync_aggregate: update.sync_aggregate, - sync_committee_next_aggregate_pubkey: Some(update.next_sync_committee.aggregate_pubkey), - sync_committee_signature: G2TypeInfo(signature).into(), - sync_committee_next_pub_keys: Some(next_sync_committee_keys), - sync_committee_next_branch: Some( - update - .next_sync_committee_branch - .into_iter() - .map(|BytesFixed(array)| array.0) - .collect::<_>(), - ), - finality_branch: update - .finality_branch - .into_iter() - .map(|BytesFixed(array)| array.0) - .collect::<_>(), - } -} - -pub fn try_from_hex_encoded>>(hex_encoded: &str) -> Option { - let data = match hex_encoded.starts_with("0x") { - true => &hex_encoded[2..], - false => hex_encoded, - }; - - hex::decode(data) - .ok() - .and_then(|bytes| >>::try_from(bytes).ok()) -} - -pub async fn request_headers( - client_http: &Client, - beacon_endpoint: &str, - slot_start: Slot, - slot_end: Slot, -) -> AnyResult> { - let batch_size = (slot_end - slot_start) as usize; - let mut requests_headers = Vec::with_capacity(batch_size); - for i in slot_start..slot_end { - requests_headers.push(get_block_header(client_http, beacon_endpoint, i)); - } - - futures::future::join_all(requests_headers) - .await - .into_iter() - .filter(|maybe_header| !matches!(maybe_header, Err(e) if e.downcast_ref::().is_some())) - .collect::, _>>() - .map_err(|e| { - anyhow!("Failed to fetch block headers ([{slot_start}; {slot_end})): {e:?}") - }) -} diff --git a/relayer/src/hex_utils.rs b/relayer/src/hex_utils.rs new file mode 100644 index 00000000..8c6ffd0b --- /dev/null +++ b/relayer/src/hex_utils.rs @@ -0,0 +1,18 @@ +use primitive_types::{H160, H256}; + +pub fn decode_h256(hex: &str) -> anyhow::Result { + let data: [u8; 32] = decode_byte_array(hex)?; + Ok(data.into()) +} + +pub fn decode_h160(hex: &str) -> anyhow::Result { + let data: [u8; 20] = decode_byte_array(hex)?; + Ok(data.into()) +} + +pub fn decode_byte_array(hex: &str) -> anyhow::Result<[u8; LEN]> { + let address = if &hex[..2] == "0x" { &hex[2..] } else { hex }; + hex::decode(address)? + .try_into() + .map_err(|_| anyhow::anyhow!("Invalid length")) +} diff --git a/relayer/src/main.rs b/relayer/src/main.rs index 43c1059c..cf027740 100644 --- a/relayer/src/main.rs +++ b/relayer/src/main.rs @@ -1,23 +1,27 @@ use std::time::Duration; use clap::{Args, Parser, Subcommand}; -use message_relayer::{all_token_transfers, paid_token_transfers}; +use ethereum_beacon_client::BeaconClient; +use gclient::{GearApi as GClientGearApi, WSAddress}; use ethereum_client::EthApi; use gear_rpc_client::GearApi; +use message_relayer::{eth_to_gear, gear_to_eth}; use proof_storage::{FileSystemProofStorage, GearProofStorage, ProofStorage}; use prover::proving::GenesisConfig; use relay_merkle_roots::MerkleRootRelayer; use utils_prometheus::MetricsBuilder; -mod erc20; +mod ethereum_beacon_client; mod ethereum_checkpoints; +mod hex_utils; mod message_relayer; mod proof_storage; mod prover_interface; mod relay_merkle_roots; const DEFAULT_VARA_RPC: &str = "ws://localhost:8989"; +const DEFAULT_ETH_BEACON_RPC: &str = "http://localhost:50000"; const DEFAULT_ETH_RPC: &str = "http://localhost:8545"; const DEFAULT_PROMETHEUS_ENDPOINT: &str = "0.0.0.0:9090"; @@ -74,6 +78,7 @@ struct RelayMerkleRootsArgs { proof_storage_args: ProofStorageArgs, } +// TODO: Separate domain and port. #[derive(Args)] struct VaraEndpointArg { /// Address of the VARA RPC endpoint @@ -105,6 +110,21 @@ struct EthereumArgs { mq_address: String, } +#[derive(Args)] +struct BeaconRpcArgs { + /// Address of the ethereum beacon RPC endpoint + #[arg( + long = "ethereum-beacon-rpc", + default_value = DEFAULT_ETH_BEACON_RPC, + env = "ETH_BEACON_RPC" + )] + beacon_endpoint: String, + + /// Timeout in seconds for requests to the ethereum beacon RPC + #[arg(long = "ethereum-beacon-rpc-timeout", env = "ETH_BEACON_RPC_TIMEOUT")] + beacon_timeout: Option, +} + #[derive(Args)] struct PrometheusArgs { /// Address of the prometheus endpoint @@ -168,40 +188,42 @@ struct RelayCheckpointsArgs { #[derive(Args)] struct RelayErc20Args { - /// Specify ProgramId of the program - #[arg(long, env = "ADDRESS")] - program_id: String, + /// Address of the ERC20Treasury contract on ethereum + #[arg(long = "erc20-treasury-address", env = "ERC20_TREASURY_ADDRESS")] + erc20_treasury_address: String, - /// Specify an endpoint providing Beacon API - #[arg(long, env = "BEACON_ENDPOINT")] - beacon_endpoint: String, + /// Address of the checkpoint-light-client program on gear + #[arg( + long = "checkpoint-light-client-address", + env = "CHECKPOINT_LIGHT_CLIENT_ADDRESS" + )] + checkpoint_light_client_address: String, - /// Domain of the VARA RPC endpoint - #[arg(long, default_value = "ws://127.0.0.1", env = "VARA_DOMAIN")] - vara_domain: String, + /// Address of the ethereum-event-client program on gear + #[arg( + long = "ethereum-event-client-address", + env = "ETHEREUM_EVENT_CLIENT_ADDRESS" + )] + ethereum_event_client_address: String, - /// Port of the VARA RPC endpoint - #[arg(long, default_value = "9944", env = "VARA_PORT")] - vara_port: u16, + #[clap(flatten)] + vara_endpoint: VaraEndpointArg, /// Substrate URI that identifies a user by a mnemonic phrase or /// provides default users from the keyring (e.g., "//Alice", "//Bob", /// etc.). The password for URI should be specified in the same `suri`, /// separated by the ':' char - #[arg(long, default_value = "//Alice", env = "VARA_SURI")] + #[arg(long, env = "VARA_SURI")] vara_suri: String, - /// Address of the ethereum endpoint - #[arg( - long = "ethereum-endpoint", - default_value = DEFAULT_ETH_RPC, - env = "ETH_RPC" - )] - eth_endpoint: String, + #[clap(flatten)] + ethereum_args: EthereumArgs, + + #[clap(flatten)] + beacon_rpc: BeaconRpcArgs, - /// Specify the hash of the ERC20-transaction to relay - #[arg(long, env = "TX_HASH")] - tx_hash: String, + #[clap(flatten)] + prometheus_args: PrometheusArgs, } #[tokio::main] @@ -273,20 +295,10 @@ async fn main() { let eth_api = create_eth_client(&args.ethereum_args); if let Some(bridging_payment_address) = args.bridging_payment_address { - let bridging_payment_address = if &bridging_payment_address[..2] == "0x" { - &bridging_payment_address[2..] - } else { - &bridging_payment_address - }; - - let bridging_payment_address: [u8; 32] = hex::decode(bridging_payment_address) - .expect("Wrong format of bridging-payment-address") - .try_into() - .expect("Wrong format of bridging-payment-address"); - - let bridging_payment_address = bridging_payment_address.into(); + let bridging_payment_address = hex_utils::decode_h256(&bridging_payment_address) + .expect("Failed to parse address"); - let relayer = paid_token_transfers::Relayer::new( + let relayer = gear_to_eth::paid_token_transfers::Relayer::new( gear_api, eth_api, args.from_block, @@ -303,9 +315,13 @@ async fn main() { relayer.run(); } else { - let relayer = all_token_transfers::Relayer::new(gear_api, eth_api, args.from_block) - .await - .unwrap(); + let relayer = gear_to_eth::all_token_transfers::Relayer::new( + gear_api, + eth_api, + args.from_block, + ) + .await + .unwrap(); MetricsBuilder::new() .register_service(&relayer) @@ -322,10 +338,64 @@ async fn main() { } } CliCommands::RelayCheckpoints(args) => ethereum_checkpoints::relay(args).await, - CliCommands::RelayErc20(args) => erc20::relay(args).await, + CliCommands::RelayErc20(args) => { + let eth_api = create_eth_client(&args.ethereum_args); + let beacon_client = create_beacon_client(&args.beacon_rpc).await; + + let gear_api = create_gear_client(&args.vara_endpoint).await; + let gclient_client = create_gclient_client(&args.vara_endpoint, &args.vara_suri).await; + + let erc20_treasury_address = hex_utils::decode_h160(&args.erc20_treasury_address) + .expect("Failed to parse address"); + let checkpoint_light_client_address = + hex_utils::decode_h256(&args.checkpoint_light_client_address) + .expect("Failed to parse address"); + let ethereum_event_client_address = + hex_utils::decode_h256(&args.ethereum_event_client_address) + .expect("Failed to parse address"); + + let relayer = eth_to_gear::all_token_transfers::Relayer::new( + gear_api, + gclient_client, + eth_api, + beacon_client, + erc20_treasury_address, + checkpoint_light_client_address, + ethereum_event_client_address, + ) + .await + .expect("Failed to create relayer"); + + MetricsBuilder::new() + .register_service(&relayer) + .build() + .run(args.prometheus_args.endpoint) + .await; + + relayer.run(); + + loop { + // relayer.run() spawns thread and exits, so we need to add this loop after calling run. + std::thread::sleep(Duration::from_millis(100)); + } + } }; } +async fn create_gclient_client(args: &VaraEndpointArg, vara_suri: &str) -> GClientGearApi { + let endpoint_parts: Vec<_> = args.vara_endpoint.split(':').collect(); + let [domain_1, domain_2, port] = endpoint_parts + .try_into() + .expect("Invalid gear endpoint provided"); + + let domain = [domain_1, domain_2].join(":"); + let port: u16 = port.parse().expect("Invalid gear endpoint provided"); + + GClientGearApi::init_with(WSAddress::new(domain, Some(port)), vara_suri) + .await + .expect("Failed to create gclient client") +} + async fn create_gear_client(args: &VaraEndpointArg) -> GearApi { GearApi::new(&args.vara_endpoint) .await @@ -348,3 +418,11 @@ fn create_eth_client(args: &EthereumArgs) -> EthApi { ) .unwrap_or_else(|err| panic!("Error while creating ethereum client: {}", err)) } + +async fn create_beacon_client(args: &BeaconRpcArgs) -> BeaconClient { + let timeout = args.beacon_timeout.map(Duration::from_secs); + + BeaconClient::new(args.beacon_endpoint.clone(), timeout) + .await + .expect("Failed to create beacon client") +} diff --git a/relayer/src/message_relayer/common/ethereum_block_listener.rs b/relayer/src/message_relayer/common/ethereum/block_listener.rs similarity index 89% rename from relayer/src/message_relayer/common/ethereum_block_listener.rs rename to relayer/src/message_relayer/common/ethereum/block_listener.rs index cb02f8de..acfc51b4 100644 --- a/relayer/src/message_relayer/common/ethereum_block_listener.rs +++ b/relayer/src/message_relayer/common/ethereum/block_listener.rs @@ -7,18 +7,18 @@ use ethereum_client::EthApi; use prometheus::IntGauge; use utils_prometheus::{impl_metered_service, MeteredService}; -use super::EthereumBlockNumber; +use crate::message_relayer::common::EthereumBlockNumber; const ETHEREUM_BLOCK_TIME_APPROX: Duration = Duration::from_secs(12); -pub struct EthereumBlockListener { +pub struct BlockListener { eth_api: EthApi, from_block: u64, metrics: Metrics, } -impl MeteredService for EthereumBlockListener { +impl MeteredService for BlockListener { fn get_sources(&self) -> impl IntoIterator> { self.metrics.get_sources() } @@ -33,7 +33,7 @@ impl_metered_service! { } } -impl EthereumBlockListener { +impl BlockListener { pub fn new(eth_api: EthApi, from_block: u64) -> Self { Self { eth_api, @@ -64,7 +64,7 @@ impl EthereumBlockListener { self.metrics.latest_block.set(current_block as i64); loop { - let latest = self.eth_api.block_number().await?; + let latest = self.eth_api.finalized_block_number().await?; if latest >= current_block { for block in current_block..=latest { sender.send(EthereumBlockNumber(block))?; diff --git a/relayer/src/message_relayer/common/ethereum/deposit_event_extractor.rs b/relayer/src/message_relayer/common/ethereum/deposit_event_extractor.rs new file mode 100644 index 00000000..4f863f38 --- /dev/null +++ b/relayer/src/message_relayer/common/ethereum/deposit_event_extractor.rs @@ -0,0 +1,174 @@ +use std::{ + sync::mpsc::{channel, Receiver, Sender}, + time::Duration, +}; + +use alloy::providers::Provider; +use alloy_eips::BlockNumberOrTag; +use anyhow::anyhow; +use futures::executor::block_on; +use prometheus::IntCounter; +use sails_rs::H160; + +use ethereum_client::{DepositEventEntry, EthApi}; +use utils_prometheus::{impl_metered_service, MeteredService}; + +use crate::{ + ethereum_beacon_client::BeaconClient, + message_relayer::common::{ERC20DepositTx, EthereumBlockNumber, EthereumSlotNumber}, +}; + +pub struct DepositEventExtractor { + eth_api: EthApi, + beacon_client: BeaconClient, + + erc20_treasury_address: H160, + + metrics: Metrics, +} + +impl MeteredService for DepositEventExtractor { + fn get_sources(&self) -> impl IntoIterator> { + self.metrics.get_sources() + } +} + +impl_metered_service! { + struct Metrics { + total_deposits_found: IntCounter = IntCounter::new( + "deposit_event_extractor_total_deposits_found", + "Total amount of deposit events discovered", + ), + } +} + +impl DepositEventExtractor { + pub fn new(eth_api: EthApi, beacon_client: BeaconClient, erc20_treasury_address: H160) -> Self { + Self { + eth_api, + beacon_client, + + erc20_treasury_address, + + metrics: Metrics::new(), + } + } + + pub fn run(self, blocks: Receiver) -> Receiver { + let (sender, receiver) = channel(); + + tokio::task::spawn_blocking(move || loop { + let res = block_on(self.run_inner(&sender, &blocks)); + if let Err(err) = res { + log::error!("Deposit event extractor failed: {}", err); + } + }); + + receiver + } + + async fn run_inner( + &self, + sender: &Sender, + blocks: &Receiver, + ) -> anyhow::Result<()> { + loop { + for block in blocks.try_iter() { + self.process_block_events(block, sender).await?; + } + } + } + + async fn process_block_events( + &self, + block: EthereumBlockNumber, + sender: &Sender, + ) -> anyhow::Result<()> { + let events = self + .eth_api + .fetch_deposit_events(self.erc20_treasury_address, block.0) + .await?; + + if events.is_empty() { + return Ok(()); + } + + let slot_number = self.find_slot_by_block_number(block).await?; + + self.metrics + .total_deposits_found + .inc_by(events.len() as u64); + + for ev in &events { + log::info!( + "Found deposit event: tx_hash={}, from={}, to={}, token={}, amount={}, slot_number={}", + hex::encode(ev.tx_hash.0), + hex::encode(ev.from.0), + hex::encode(ev.to.0), + hex::encode(ev.token.0), + ev.amount, + slot_number.0, + ); + } + + for DepositEventEntry { tx_hash, .. } in events { + sender.send(ERC20DepositTx { + slot_number, + tx_hash, + })?; + } + + Ok(()) + } + + async fn find_slot_by_block_number( + &self, + block: EthereumBlockNumber, + ) -> anyhow::Result { + let block_body = self + .eth_api + .raw_provider() + .get_block_by_number(BlockNumberOrTag::Number(block.0), false) + .await? + .ok_or(anyhow!("Ethereum block #{} is missing", block.0))?; + + let beacon_root_parent = block_body.header.parent_beacon_block_root.ok_or(anyhow!( + "Unable to determine root of parent beacon block for block #{}", + block.0 + ))?; + + let beacon_block_parent = self + .beacon_client + .get_block_by_hash(&beacon_root_parent.0) + .await?; + + // TODO: It's a temporary solution of a problem that we're connecting to a different + // nodes, so if we're observing finalized block on one node, the finalized slot might still be not + // available on other. + for _ in 0..10 { + let beacon_block_result = self + .beacon_client + .find_beacon_block(block.0, &beacon_block_parent) + .await; + + match beacon_block_result { + Ok(beacon_block) => { + return Ok(EthereumSlotNumber(beacon_block.slot)); + } + Err(err) => { + log::warn!( + "Failed to find beacon block for ethereum block #{}: {}. Waiting for 1 second before next attempt...", + block.0, + err + ); + tokio::time::sleep(Duration::from_secs(1)).await; + } + } + } + + anyhow::bail!( + "Failed to find beacon block for Ethereum block #{} after 5 attempts", + block.0 + ); + } +} diff --git a/relayer/src/message_relayer/common/merkle_root_extractor.rs b/relayer/src/message_relayer/common/ethereum/merkle_root_extractor.rs similarity index 96% rename from relayer/src/message_relayer/common/merkle_root_extractor.rs rename to relayer/src/message_relayer/common/ethereum/merkle_root_extractor.rs index 42b55ac6..235d25df 100644 --- a/relayer/src/message_relayer/common/merkle_root_extractor.rs +++ b/relayer/src/message_relayer/common/ethereum/merkle_root_extractor.rs @@ -6,7 +6,9 @@ use gear_rpc_client::GearApi; use prometheus::IntGauge; use utils_prometheus::{impl_metered_service, MeteredService}; -use super::{AuthoritySetId, EthereumBlockNumber, GearBlockNumber, RelayedMerkleRoot}; +use crate::message_relayer::common::{ + AuthoritySetId, EthereumBlockNumber, GearBlockNumber, RelayedMerkleRoot, +}; pub struct MerkleRootExtractor { eth_api: EthApi, diff --git a/relayer/src/message_relayer/common/ethereum_message_sender/era.rs b/relayer/src/message_relayer/common/ethereum/message_sender/era.rs similarity index 100% rename from relayer/src/message_relayer/common/ethereum_message_sender/era.rs rename to relayer/src/message_relayer/common/ethereum/message_sender/era.rs diff --git a/relayer/src/message_relayer/common/ethereum_message_sender/mod.rs b/relayer/src/message_relayer/common/ethereum/message_sender/mod.rs similarity index 96% rename from relayer/src/message_relayer/common/ethereum_message_sender/mod.rs rename to relayer/src/message_relayer/common/ethereum/message_sender/mod.rs index a0df272f..510b72c8 100644 --- a/relayer/src/message_relayer/common/ethereum_message_sender/mod.rs +++ b/relayer/src/message_relayer/common/ethereum/message_sender/mod.rs @@ -14,9 +14,9 @@ use crate::message_relayer::common::{AuthoritySetId, MessageInBlock}; mod era; use era::{Era, Metrics as EraMetrics}; -use super::RelayedMerkleRoot; +use crate::message_relayer::common::RelayedMerkleRoot; -pub struct EthereumMessageSender { +pub struct MessageSender { eth_api: EthApi, gear_api: GearApi, @@ -24,7 +24,7 @@ pub struct EthereumMessageSender { era_metrics: EraMetrics, } -impl MeteredService for EthereumMessageSender { +impl MeteredService for MessageSender { fn get_sources(&self) -> impl IntoIterator> { self.metrics .get_sources() @@ -46,7 +46,7 @@ impl_metered_service! { } } -impl EthereumMessageSender { +impl MessageSender { pub fn new(eth_api: EthApi, gear_api: GearApi) -> Self { Self { eth_api, diff --git a/relayer/src/message_relayer/common/ethereum/mod.rs b/relayer/src/message_relayer/common/ethereum/mod.rs new file mode 100644 index 00000000..ba28c5a9 --- /dev/null +++ b/relayer/src/message_relayer/common/ethereum/mod.rs @@ -0,0 +1,4 @@ +pub mod block_listener; +pub mod deposit_event_extractor; +pub mod merkle_root_extractor; +pub mod message_sender; diff --git a/relayer/src/message_relayer/common/gear_block_listener.rs b/relayer/src/message_relayer/common/gear/block_listener.rs similarity index 94% rename from relayer/src/message_relayer/common/gear_block_listener.rs rename to relayer/src/message_relayer/common/gear/block_listener.rs index 5e1b932a..ec5ba037 100644 --- a/relayer/src/message_relayer/common/gear_block_listener.rs +++ b/relayer/src/message_relayer/common/gear/block_listener.rs @@ -7,18 +7,18 @@ use gear_rpc_client::GearApi; use prometheus::IntGauge; use utils_prometheus::{impl_metered_service, MeteredService}; -use super::GearBlockNumber; +use crate::message_relayer::common::GearBlockNumber; const GEAR_BLOCK_TIME_APPROX: Duration = Duration::from_secs(3); -pub struct GearBlockListener { +pub struct BlockListener { gear_api: GearApi, from_block: u32, metrics: Metrics, } -impl MeteredService for GearBlockListener { +impl MeteredService for BlockListener { fn get_sources(&self) -> impl IntoIterator> { self.metrics.get_sources() } @@ -33,7 +33,7 @@ impl_metered_service! { } } -impl GearBlockListener { +impl BlockListener { pub fn new(gear_api: GearApi, from_block: u32) -> Self { Self { gear_api, diff --git a/relayer/src/message_relayer/common/gear/checkpoints_extractor.rs b/relayer/src/message_relayer/common/gear/checkpoints_extractor.rs new file mode 100644 index 00000000..50ffefad --- /dev/null +++ b/relayer/src/message_relayer/common/gear/checkpoints_extractor.rs @@ -0,0 +1,139 @@ +use std::sync::mpsc::{channel, Receiver, Sender}; + +use futures::executor::block_on; +use gear_rpc_client::GearApi; +use parity_scale_codec::{Decode, Encode}; +use primitive_types::H256; +use prometheus::IntGauge; +use utils_prometheus::{impl_metered_service, MeteredService}; + +use checkpoint_light_client_io::meta::{Order, State, StateRequest}; + +use crate::message_relayer::common::{EthereumSlotNumber, GearBlockNumber}; + +pub struct CheckpointsExtractor { + checkpoint_light_client_address: H256, + + gear_api: GearApi, + + latest_checkpoint: Option, + + metrics: Metrics, +} + +impl MeteredService for CheckpointsExtractor { + fn get_sources(&self) -> impl IntoIterator> { + self.metrics.get_sources() + } +} + +impl_metered_service! { + struct Metrics { + latest_checkpoint_slot: IntGauge = IntGauge::new( + "checkpoint_extractor_latest_checkpoint_slot", + "Latest slot found in checkpoint light client program state", + ), + } +} + +impl CheckpointsExtractor { + pub fn new(gear_api: GearApi, checkpoint_light_client_address: H256) -> Self { + Self { + checkpoint_light_client_address, + gear_api, + latest_checkpoint: None, + metrics: Metrics::new(), + } + } + + pub fn run(mut self, blocks: Receiver) -> Receiver { + let (sender, receiver) = channel(); + + tokio::task::spawn_blocking(move || loop { + let res = block_on(self.run_inner(&sender, &blocks)); + if let Err(err) = res { + log::error!("Checkpoints extractor failed: {}", err); + } + }); + + receiver + } + + async fn run_inner( + &mut self, + sender: &Sender, + blocks: &Receiver, + ) -> anyhow::Result<()> { + loop { + for block in blocks.try_iter() { + self.process_block_events(block.0, sender).await?; + } + } + } + + async fn process_block_events( + &mut self, + block: u32, + sender: &Sender, + ) -> anyhow::Result<()> { + let block_hash = self.gear_api.block_number_to_hash(block).await?; + + let request = StateRequest { + order: Order::Reverse, + index_start: 0, + count: 1, + } + .encode(); + + let state = self + .gear_api + .api + .read_state( + self.checkpoint_light_client_address, + request, + Some(block_hash), + ) + .await?; + + let state = hex::decode(&state[2..])?; + let state = State::decode(&mut &state[..])?; + + assert!(state.checkpoints.len() <= 1); + + let latest_checkpoint = state.checkpoints.first(); + + match (latest_checkpoint, self.latest_checkpoint) { + (None, None) => {} + (None, Some(_)) => { + panic!( + "Invalid state detected: checkpoint-light-client program contains no checkpoints \ + but there's one in checkpoints extractor state" + ); + } + (Some(checkpoint), None) => { + self.latest_checkpoint = Some(EthereumSlotNumber(checkpoint.0)); + + self.metrics.latest_checkpoint_slot.set(checkpoint.0 as i64); + + log::info!("First checkpoint discovered: {}", checkpoint.0); + + sender.send(EthereumSlotNumber(checkpoint.0))?; + } + (Some(latest), Some(stored)) => { + if latest.0 > stored.0 { + self.metrics.latest_checkpoint_slot.set(latest.0 as i64); + + let latest = EthereumSlotNumber(latest.0); + + self.latest_checkpoint = Some(latest); + + log::info!("New checkpoint discovered: {}", latest.0); + + sender.send(latest)?; + } + } + } + + Ok(()) + } +} diff --git a/relayer/src/message_relayer/common/message_paid_event_extractor.rs b/relayer/src/message_relayer/common/gear/message_paid_event_extractor.rs similarity index 97% rename from relayer/src/message_relayer/common/message_paid_event_extractor.rs rename to relayer/src/message_relayer/common/gear/message_paid_event_extractor.rs index cac2b0e6..f55aabea 100644 --- a/relayer/src/message_relayer/common/message_paid_event_extractor.rs +++ b/relayer/src/message_relayer/common/gear/message_paid_event_extractor.rs @@ -7,9 +7,10 @@ use prometheus::IntCounter; use sails_rs::events::EventIo; use utils_prometheus::{impl_metered_service, MeteredService}; -use super::{GearBlockNumber, PaidMessage}; use bridging_payment_client::bridging_payment::events::BridgingPaymentEvents; +use crate::message_relayer::common::{GearBlockNumber, PaidMessage}; + pub struct MessagePaidEventExtractor { bridging_payment_address: H256, diff --git a/relayer/src/message_relayer/common/message_queued_event_extractor.rs b/relayer/src/message_relayer/common/gear/message_queued_event_extractor.rs similarity index 97% rename from relayer/src/message_relayer/common/message_queued_event_extractor.rs rename to relayer/src/message_relayer/common/gear/message_queued_event_extractor.rs index 140afb4e..cdb91b2c 100644 --- a/relayer/src/message_relayer/common/message_queued_event_extractor.rs +++ b/relayer/src/message_relayer/common/gear/message_queued_event_extractor.rs @@ -5,7 +5,7 @@ use gear_rpc_client::GearApi; use prometheus::IntCounter; use utils_prometheus::{impl_metered_service, MeteredService}; -use super::{GearBlockNumber, MessageInBlock}; +use crate::message_relayer::common::{GearBlockNumber, MessageInBlock}; pub struct MessageQueuedEventExtractor { gear_api: GearApi, diff --git a/relayer/src/message_relayer/common/gear/message_sender/compose_payload.rs b/relayer/src/message_relayer/common/gear/message_sender/compose_payload.rs new file mode 100644 index 00000000..12654135 --- /dev/null +++ b/relayer/src/message_relayer/common/gear/message_sender/compose_payload.rs @@ -0,0 +1,116 @@ +use crate::ethereum_beacon_client::BeaconClient; + +use alloy::{network::primitives::BlockTransactionsKind, primitives::TxHash, providers::Provider}; +use alloy_eips::{BlockId, BlockNumberOrTag}; +use alloy_rlp::Encodable; +use anyhow::{anyhow, Result as AnyResult}; +use sails_rs::prelude::*; + +use checkpoint_light_client_io::ethereum_common::{ + beacon::light::Block as LightBeaconBlock, + utils::{self as eth_utils, MerkleProof}, + SLOTS_PER_EPOCH, +}; +use erc20_relay_client::{BlockInclusionProof, EthToVaraEvent}; +use ethereum_client::EthApi; + +pub async fn compose( + beacon_client: &BeaconClient, + eth_client: &EthApi, + tx_hash: TxHash, +) -> AnyResult { + let provider = eth_client.raw_provider(); + + let receipt = provider + .get_transaction_receipt(tx_hash) + .await? + .ok_or(anyhow!("Transaction receipt is missing"))?; + + let block = match receipt.block_hash { + Some(hash) => provider + .get_block_by_hash(hash, BlockTransactionsKind::Hashes) + .await? + .ok_or(anyhow!("Ethereum block (hash) is missing"))?, + None => match receipt.block_number { + Some(number) => provider + .get_block_by_number(BlockNumberOrTag::Number(number), false) + .await? + .ok_or(anyhow!("Ethereum block (number) is missing"))?, + None => return Err(anyhow!("Unable to get Ethereum block")), + }, + }; + + let beacon_root_parent = block + .header + .parent_beacon_block_root + .ok_or(anyhow!("Unable to determine root of parent beacon block"))?; + let block_number = block.header.number; + + let proof_block = + build_inclusion_proof(beacon_client, &beacon_root_parent, block_number).await?; + + // receipt Merkle-proof + let tx_index = receipt + .transaction_index + .ok_or(anyhow!("Unable to determine transaction index"))?; + let receipts = provider + .get_block_receipts(BlockId::Number(BlockNumberOrTag::Number(block_number))) + .await? + .unwrap_or_default() + .iter() + .map(|tx_receipt| { + let receipt = tx_receipt.as_ref(); + + tx_receipt + .transaction_index + .map(|i| (i, eth_utils::map_receipt_envelope(receipt))) + }) + .collect::>>() + .unwrap_or_default(); + + let MerkleProof { proof, receipt } = eth_utils::generate_merkle_proof(tx_index, &receipts[..])?; + + let mut receipt_rlp = Vec::with_capacity(Encodable::length(&receipt)); + Encodable::encode(&receipt, &mut receipt_rlp); + + Ok(EthToVaraEvent { + proof_block, + proof, + transaction_index: tx_index, + receipt_rlp, + }) +} + +async fn build_inclusion_proof( + beacon_client: &BeaconClient, + beacon_root_parent: &[u8; 32], + block_number: u64, +) -> AnyResult { + let beacon_block_parent = beacon_client.get_block_by_hash(beacon_root_parent).await?; + + let beacon_block = LightBeaconBlock::from( + beacon_client + .find_beacon_block(block_number, &beacon_block_parent) + .await?, + ); + + let slot = beacon_block.slot; + if slot % SLOTS_PER_EPOCH == 0 { + return Ok(BlockInclusionProof { + block: beacon_block, + headers: vec![], + }); + } + + let epoch_next = 1 + eth_utils::calculate_epoch(beacon_block.slot); + let slot_checkpoint = epoch_next * SLOTS_PER_EPOCH; + + Ok(BlockInclusionProof { + block: beacon_block, + headers: beacon_client + .request_headers(slot + 1, slot_checkpoint + 1) + .await? + .into_iter() + .collect(), + }) +} diff --git a/relayer/src/message_relayer/common/gear/message_sender/mod.rs b/relayer/src/message_relayer/common/gear/message_sender/mod.rs new file mode 100644 index 00000000..1971c9ee --- /dev/null +++ b/relayer/src/message_relayer/common/gear/message_sender/mod.rs @@ -0,0 +1,176 @@ +use std::sync::mpsc::Receiver; + +use ethereum_client::EthApi; +use futures::executor::block_on; +use gclient::GearApi; +use primitive_types::H256; +use prometheus::IntGauge; +use sails_rs::{ + calls::{Action, Call}, + gclient::calls::GClientRemoting, +}; + +use erc20_relay_client::{traits::Erc20Relay as _, Erc20Relay}; +use utils_prometheus::{impl_metered_service, MeteredService}; + +use crate::{ + ethereum_beacon_client::BeaconClient, + message_relayer::common::{ERC20DepositTx, EthereumSlotNumber}, +}; + +mod compose_payload; + +pub struct MessageSender { + gear_api: GearApi, + eth_api: EthApi, + beacon_client: BeaconClient, + + ethereum_event_client_address: H256, + + metrics: Metrics, +} + +impl MeteredService for MessageSender { + fn get_sources(&self) -> impl IntoIterator> { + self.metrics.get_sources() + } +} + +impl_metered_service! { + struct Metrics { + messages_waiting_checkpoint: IntGauge = IntGauge::new( + "gear_message_sender_messages_waiting_checkpoint", + "Amount of messages waiting for corresponding checkpoint", + ), + messages_waiting_finality: IntGauge = IntGauge::new( + "gear_message_sender_messages_waiting_finality", + "Amount of messages waiting for finality on gear", + ), + fee_payer_balance: IntGauge = IntGauge::new( + "gear_message_sender_fee_payer_balance", + "Transaction fee payer balance", + ) + } +} + +impl MessageSender { + pub fn new( + gear_api: GearApi, + eth_api: EthApi, + beacon_client: BeaconClient, + ethereum_event_client_address: H256, + ) -> Self { + Self { + gear_api, + eth_api, + beacon_client, + + ethereum_event_client_address, + + metrics: Metrics::new(), + } + } + + pub fn run( + self, + messages: Receiver, + checkpoints: Receiver, + ) { + tokio::task::spawn_blocking(move || loop { + let res = block_on(self.run_inner(&messages, &checkpoints)); + if let Err(err) = res { + log::error!("Gear message sender failed: {}", err); + } + }); + } + + async fn run_inner( + &self, + messages: &Receiver, + checkpoints: &Receiver, + ) -> anyhow::Result<()> { + self.update_balance_metric().await?; + + let mut waiting_checkpoint: Vec = vec![]; + + let mut latest_checkpoint_slot = None; + + loop { + for checkpoint in checkpoints.try_iter() { + if latest_checkpoint_slot.unwrap_or_default() < checkpoint { + latest_checkpoint_slot = Some(checkpoint); + } else { + log::error!( + "Received checkpoints not in sequential order. \ + Previously found checkpoint: {:?} and new checkpoint is {}", + latest_checkpoint_slot, + checkpoint + ); + } + } + + for message in messages.try_iter() { + waiting_checkpoint.push(message); + } + + if waiting_checkpoint.is_empty() { + continue; + } + for i in (0..waiting_checkpoint.len()).rev() { + if waiting_checkpoint[i].slot_number <= latest_checkpoint_slot.unwrap_or_default() { + self.submit_message(&waiting_checkpoint[i]).await?; + let _ = waiting_checkpoint.remove(i); + } + } + + self.update_balance_metric().await?; + + self.metrics + .messages_waiting_checkpoint + .set(waiting_checkpoint.len() as i64); + } + } + + async fn submit_message(&self, message: &ERC20DepositTx) -> anyhow::Result<()> { + let message = + compose_payload::compose(&self.beacon_client, &self.eth_api, message.tx_hash).await?; + + log::info!( + "Sending message in gear_message_sender: tx_index={}, slot={}", + message.transaction_index, + message.proof_block.block.slot + ); + + let gas_limit_block = self.gear_api.block_gas_limit()?; + // Use 95% of block gas limit for all extrinsics. + let gas_limit = gas_limit_block / 100 * 95; + + let remoting = GClientRemoting::new(self.gear_api.clone()); + + let mut erc20_service = Erc20Relay::new(remoting.clone()); + + erc20_service + .relay(message) + .with_gas_limit(gas_limit) + .send_recv(self.ethereum_event_client_address.into()) + .await + .map_err(|_| anyhow::anyhow!("Failed to send message to ethereum event client"))? + .map_err(|_| anyhow::anyhow!("Internal ethereum event clint error"))?; + + Ok(()) + } + + async fn update_balance_metric(&self) -> anyhow::Result<()> { + let balance = self + .gear_api + .total_balance(self.gear_api.account_id()) + .await?; + + let balance = balance / 1_000_000_000_000; + let balance: i64 = balance.try_into().unwrap_or(i64::MAX); + + self.metrics.fee_payer_balance.set(balance); + + Ok(()) + } +} diff --git a/relayer/src/message_relayer/common/gear/mod.rs b/relayer/src/message_relayer/common/gear/mod.rs new file mode 100644 index 00000000..1b2d71ba --- /dev/null +++ b/relayer/src/message_relayer/common/gear/mod.rs @@ -0,0 +1,5 @@ +pub mod block_listener; +pub mod checkpoints_extractor; +pub mod message_paid_event_extractor; +pub mod message_queued_event_extractor; +pub mod message_sender; diff --git a/relayer/src/message_relayer/common/mod.rs b/relayer/src/message_relayer/common/mod.rs index 8924cfdc..5baef64f 100644 --- a/relayer/src/message_relayer/common/mod.rs +++ b/relayer/src/message_relayer/common/mod.rs @@ -1,12 +1,9 @@ +use ethereum_client::TxHash; use gear_rpc_client::dto::Message; use primitive_types::H256; -pub mod ethereum_block_listener; -pub mod ethereum_message_sender; -pub mod gear_block_listener; -pub mod merkle_root_extractor; -pub mod message_paid_event_extractor; -pub mod message_queued_event_extractor; +pub mod ethereum; +pub mod gear; pub mod paid_messages_filter; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, derive_more::Display)] @@ -18,6 +15,11 @@ pub struct GearBlockNumber(pub u32); #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, derive_more::Display)] pub struct EthereumBlockNumber(pub u64); +#[derive( + Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Default, derive_more::Display, +)] +pub struct EthereumSlotNumber(pub u64); + #[derive(Clone, Debug)] pub struct MessageInBlock { pub message: Message, @@ -35,3 +37,9 @@ pub struct RelayedMerkleRoot { pub block: GearBlockNumber, pub authority_set_id: AuthoritySetId, } + +#[derive(Clone, Debug)] +pub struct ERC20DepositTx { + pub slot_number: EthereumSlotNumber, + pub tx_hash: TxHash, +} diff --git a/relayer/src/message_relayer/eth_to_gear/all_token_transfers.rs b/relayer/src/message_relayer/eth_to_gear/all_token_transfers.rs new file mode 100644 index 00000000..60e51a57 --- /dev/null +++ b/relayer/src/message_relayer/eth_to_gear/all_token_transfers.rs @@ -0,0 +1,101 @@ +use std::iter; + +use gclient::GearApi as GclientGearApi; +use primitive_types::{H160, H256}; + +use ethereum_client::EthApi; +use gear_rpc_client::GearApi; +use utils_prometheus::MeteredService; + +use crate::{ + ethereum_beacon_client::BeaconClient, + message_relayer::common::{ + ethereum::{ + block_listener::BlockListener as EthereumBlockListener, + deposit_event_extractor::DepositEventExtractor, + }, + gear::{ + block_listener::BlockListener as GearBlockListener, + checkpoints_extractor::CheckpointsExtractor, message_sender::MessageSender, + }, + }, +}; + +pub struct Relayer { + gear_block_listener: GearBlockListener, + ethereum_block_listener: EthereumBlockListener, + + deposit_event_extractor: DepositEventExtractor, + checkpoints_extractor: CheckpointsExtractor, + + gear_message_sender: MessageSender, +} + +impl MeteredService for Relayer { + fn get_sources(&self) -> impl IntoIterator> { + iter::empty() + .chain(self.gear_block_listener.get_sources()) + .chain(self.ethereum_block_listener.get_sources()) + .chain(self.deposit_event_extractor.get_sources()) + .chain(self.checkpoints_extractor.get_sources()) + .chain(self.gear_message_sender.get_sources()) + } +} + +impl Relayer { + #[allow(clippy::too_many_arguments)] + pub async fn new( + gear_api: GearApi, + gclient_gear_api: GclientGearApi, + eth_api: EthApi, + beacon_client: BeaconClient, + erc20_treasury_address: H160, + checkpoint_light_client_address: H256, + ethereum_event_client_address: H256, + ) -> anyhow::Result { + let from_eth_block = eth_api.finalized_block_number().await?; + + let from_gear_block = gear_api.latest_finalized_block().await?; + let from_gear_block = gear_api.block_hash_to_number(from_gear_block).await?; + + let gear_block_listener = GearBlockListener::new(gear_api.clone(), from_gear_block); + + let ethereum_block_listener = EthereumBlockListener::new(eth_api.clone(), from_eth_block); + + let deposit_event_extractor = DepositEventExtractor::new( + eth_api.clone(), + beacon_client.clone(), + erc20_treasury_address, + ); + + let checkpoints_extractor = + CheckpointsExtractor::new(gear_api.clone(), checkpoint_light_client_address); + + let gear_message_sender = MessageSender::new( + gclient_gear_api, + eth_api, + beacon_client, + ethereum_event_client_address, + ); + + Ok(Self { + gear_block_listener, + ethereum_block_listener, + + deposit_event_extractor, + checkpoints_extractor, + + gear_message_sender, + }) + } + + pub fn run(self) { + let [gear_blocks] = self.gear_block_listener.run(); + let ethereum_blocks = self.ethereum_block_listener.run(); + + let deposit_events = self.deposit_event_extractor.run(ethereum_blocks); + let checkpoints = self.checkpoints_extractor.run(gear_blocks); + + self.gear_message_sender.run(deposit_events, checkpoints); + } +} diff --git a/relayer/src/message_relayer/eth_to_gear/mod.rs b/relayer/src/message_relayer/eth_to_gear/mod.rs new file mode 100644 index 00000000..7fb6b8d1 --- /dev/null +++ b/relayer/src/message_relayer/eth_to_gear/mod.rs @@ -0,0 +1 @@ +pub mod all_token_transfers; diff --git a/relayer/src/message_relayer/all_token_transfers.rs b/relayer/src/message_relayer/gear_to_eth/all_token_transfers.rs similarity index 80% rename from relayer/src/message_relayer/all_token_transfers.rs rename to relayer/src/message_relayer/gear_to_eth/all_token_transfers.rs index 19cb4404..9204e1a4 100644 --- a/relayer/src/message_relayer/all_token_transfers.rs +++ b/relayer/src/message_relayer/gear_to_eth/all_token_transfers.rs @@ -4,10 +4,15 @@ use ethereum_client::EthApi; use gear_rpc_client::GearApi; use utils_prometheus::MeteredService; -use super::common::{ - ethereum_block_listener::EthereumBlockListener, ethereum_message_sender::EthereumMessageSender, - gear_block_listener::GearBlockListener, merkle_root_extractor::MerkleRootExtractor, - message_queued_event_extractor::MessageQueuedEventExtractor, +use crate::message_relayer::common::{ + ethereum::{ + block_listener::BlockListener as EthereumBlockListener, + merkle_root_extractor::MerkleRootExtractor, message_sender::MessageSender, + }, + gear::{ + block_listener::BlockListener as GearBlockListener, + message_queued_event_extractor::MessageQueuedEventExtractor, + }, }; pub struct Relayer { @@ -17,7 +22,7 @@ pub struct Relayer { message_sent_listener: MessageQueuedEventExtractor, merkle_root_extractor: MerkleRootExtractor, - message_sender: EthereumMessageSender, + message_sender: MessageSender, } impl MeteredService for Relayer { @@ -44,7 +49,7 @@ impl Relayer { gear_api.block_hash_to_number(block).await? }; - let from_eth_block = eth_api.block_number().await?; + let from_eth_block = eth_api.finalized_block_number().await?; let gear_block_listener = GearBlockListener::new(gear_api.clone(), from_gear_block); @@ -54,7 +59,7 @@ impl Relayer { let merkle_root_listener = MerkleRootExtractor::new(eth_api.clone(), gear_api.clone()); - let message_sender = EthereumMessageSender::new(eth_api, gear_api); + let message_sender = MessageSender::new(eth_api, gear_api); Ok(Self { gear_block_listener, diff --git a/relayer/src/message_relayer/gear_to_eth/mod.rs b/relayer/src/message_relayer/gear_to_eth/mod.rs new file mode 100644 index 00000000..a85883fb --- /dev/null +++ b/relayer/src/message_relayer/gear_to_eth/mod.rs @@ -0,0 +1,2 @@ +pub mod all_token_transfers; +pub mod paid_token_transfers; diff --git a/relayer/src/message_relayer/paid_token_transfers.rs b/relayer/src/message_relayer/gear_to_eth/paid_token_transfers.rs similarity index 84% rename from relayer/src/message_relayer/paid_token_transfers.rs rename to relayer/src/message_relayer/gear_to_eth/paid_token_transfers.rs index 09dc3968..e9b4c424 100644 --- a/relayer/src/message_relayer/paid_token_transfers.rs +++ b/relayer/src/message_relayer/gear_to_eth/paid_token_transfers.rs @@ -5,11 +5,16 @@ use gear_rpc_client::GearApi; use primitive_types::H256; use utils_prometheus::MeteredService; -use super::common::{ - ethereum_block_listener::EthereumBlockListener, ethereum_message_sender::EthereumMessageSender, - gear_block_listener::GearBlockListener, merkle_root_extractor::MerkleRootExtractor, - message_paid_event_extractor::MessagePaidEventExtractor, - message_queued_event_extractor::MessageQueuedEventExtractor, +use crate::message_relayer::common::{ + ethereum::{ + block_listener::BlockListener as EthereumBlockListener, + merkle_root_extractor::MerkleRootExtractor, message_sender::MessageSender, + }, + gear::{ + block_listener::BlockListener as GearBlockListener, + message_paid_event_extractor::MessagePaidEventExtractor, + message_queued_event_extractor::MessageQueuedEventExtractor, + }, paid_messages_filter::PaidMessagesFilter, }; @@ -23,7 +28,7 @@ pub struct Relayer { paid_messages_filter: PaidMessagesFilter, merkle_root_extractor: MerkleRootExtractor, - message_sender: EthereumMessageSender, + message_sender: MessageSender, } impl MeteredService for Relayer { @@ -53,7 +58,7 @@ impl Relayer { gear_api.block_hash_to_number(block).await? }; - let from_eth_block = eth_api.block_number().await?; + let from_eth_block = eth_api.finalized_block_number().await?; log::info!( "Starting gear event processing from block #{}", @@ -74,7 +79,7 @@ impl Relayer { let merkle_root_listener = MerkleRootExtractor::new(eth_api.clone(), gear_api.clone()); - let message_sender = EthereumMessageSender::new(eth_api, gear_api); + let message_sender = MessageSender::new(eth_api, gear_api); Ok(Self { gear_block_listener, diff --git a/relayer/src/message_relayer/mod.rs b/relayer/src/message_relayer/mod.rs index d6dca88f..ffd77c61 100644 --- a/relayer/src/message_relayer/mod.rs +++ b/relayer/src/message_relayer/mod.rs @@ -1,4 +1,4 @@ mod common; -pub mod all_token_transfers; -pub mod paid_token_transfers; +pub mod eth_to_gear; +pub mod gear_to_eth;