diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 35aaa533..a317d610 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -59,7 +59,7 @@ jobs: needs: [rustfmt] strategy: matrix: - crate: [rosetta-server-astar, rosetta-server-bitcoin, rosetta-server-ethereum, rosetta-server-polkadot, rosetta-client] + crate: [rosetta-server-astar, rosetta-server-ethereum, rosetta-server-polkadot, rosetta-client, rosetta-testing-arbitrum] name: ${{ matrix.crate }} runs-on: self-hosted steps: @@ -94,6 +94,16 @@ jobs: - name: Pull nodes run: ./scripts/pull_nodes.sh + + - name: Checkout nitro-testnode + if: ${{ matrix.crate == 'rosetta-testing-arbitrum' }} + run: git clone -b release --depth=1 --no-tags --recurse-submodules https://github.com/ManojJiSharma/nitro-testnode.git + + - name: Start arbitrum nitro-testnode + if: ${{ matrix.crate == 'rosetta-testing-arbitrum' }} + run: | + cd nitro-testnode + ./test-node.bash --detach - name: test (${{ matrix.crate }}) run: cargo test --locked -p ${{ matrix.crate }} @@ -119,20 +129,12 @@ jobs: components: clippy target: x86_64-unknown-linux-musl override: true - - - name: Checkout code - run: git clone -b release --recurse-submodules https://github.com/ManojJiSharma/nitro-testnode.git - - - name: Run the arbitrum nitro-testnode - run: | - cd nitro-testnode - ./test-node.bash --detach - name: clippy run: | cargo clippy --locked --workspace --examples --tests --all-features \ + --exclude rosetta-testing-arbitrum \ --exclude rosetta-server-astar \ - --exclude rosetta-server-bitcoin \ --exclude rosetta-server-ethereum \ --exclude rosetta-server-polkadot \ --exclude rosetta-client \ @@ -150,11 +152,11 @@ jobs: - name: cargo test run: | cargo test --locked --workspace --all-features \ + --exclude rosetta-testing-arbitrum \ --exclude rosetta-server-astar \ - --exclude rosetta-server-bitcoin \ --exclude rosetta-server-ethereum \ --exclude rosetta-server-polkadot \ --exclude rosetta-client - name: Cleanup Docker - run: ./scripts/reset_docker.sh \ No newline at end of file + run: ./scripts/reset_docker.sh diff --git a/Cargo.lock b/Cargo.lock index 3764d7ff..f5c19dfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -869,27 +869,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "base64-compat" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" -dependencies = [ - "byteorder", -] - [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bech32" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" - [[package]] name = "bech32" version = "0.9.1" @@ -920,7 +905,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" dependencies = [ - "bitcoin_hashes 0.11.0", + "bitcoin_hashes", "rand 0.8.5", "rand_core 0.6.4", "serde", @@ -942,47 +927,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" -[[package]] -name = "bitcoin_hashes" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1" -dependencies = [ - "serde", -] - [[package]] name = "bitcoin_hashes" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" -[[package]] -name = "bitcoincore-rpc-async" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a36d64c8fc538867644983cb3af416595d0e70ab6a06a98cbee5cd60bdc1625d" -dependencies = [ - "async-trait", - "bitcoincore-rpc-json-async", - "jsonrpc-async", - "log", - "serde", - "serde_json", -] - -[[package]] -name = "bitcoincore-rpc-json-async" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2703b13da6811c55e51e3dcb39b55f06081dbc95f063c141120be7564fd09be4" -dependencies = [ - "hex 0.3.2", - "sapio-bitcoin", - "serde", - "serde_json", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -1306,11 +1256,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" dependencies = [ "base64 0.21.7", - "bech32 0.9.1", + "bech32", "bs58 0.5.0", "digest 0.10.7", "generic-array 0.14.7", - "hex 0.4.3", + "hex", "ripemd", "serde", "serde_derive", @@ -1347,7 +1297,7 @@ checksum = "a5104de16b218eddf8e34ffe2f86f74bfa4e61e95a1b89732fccf6325efd0557" dependencies = [ "cfg-if", "cpufeatures", - "hex 0.4.3", + "hex", "proptest", "serde", ] @@ -2042,7 +1992,7 @@ checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ "curve25519-dalek 3.2.0", "hashbrown 0.12.3", - "hex 0.4.3", + "hex", "rand_core 0.6.4", "sha2 0.9.9", "zeroize", @@ -2057,7 +2007,7 @@ dependencies = [ "curve25519-dalek 4.1.1", "ed25519 2.2.3", "hashbrown 0.14.3", - "hex 0.4.3", + "hex", "rand_core 0.6.4", "sha2 0.10.8", "zeroize", @@ -2115,7 +2065,7 @@ checksum = "fe81b5c06ecfdbc71dd845216f225f53b62a10cb8a16c946836a3467f701d05b" dependencies = [ "base64 0.21.7", "bytes", - "hex 0.4.3", + "hex", "k256", "log", "rand 0.8.5", @@ -2165,7 +2115,7 @@ dependencies = [ "aes 0.8.3", "ctr 0.9.2", "digest 0.10.7", - "hex 0.4.3", + "hex", "hmac 0.12.1", "pbkdf2 0.11.0", "rand 0.8.5", @@ -2185,7 +2135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" dependencies = [ "ethereum-types", - "hex 0.4.3", + "hex", "once_cell", "regex", "serde", @@ -2399,6 +2349,7 @@ dependencies = [ "const-hex", "enr", "ethers-core", + "futures-channel", "futures-core", "futures-timer", "futures-util", @@ -2673,9 +2624,9 @@ dependencies = [ [[package]] name = "fraction" -version = "0.13.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3027ae1df8d41b4bed2241c8fdad4acc1e7af60c8e17743534b545e77182d678" +checksum = "864d054a996544d325470be9db9ff3be18319e568ad90b3b59f1b8bea046ab2f" dependencies = [ "lazy_static", "num", @@ -3060,12 +3011,6 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" -[[package]] -name = "hex" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" - [[package]] name = "hex" version = "0.4.3" @@ -3267,7 +3212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" dependencies = [ "futures-util", - "hex 0.4.3", + "hex", "hyper", "pin-project", "tokio", @@ -3497,20 +3442,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonrpc-async" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20e8e4ed08ee58717113cbf277b1ecef5cd9554d3e48c114de338289727d466" -dependencies = [ - "async-trait", - "base64-compat", - "serde", - "serde_derive", - "serde_json", - "tokio", -] - [[package]] name = "jsonrpsee" version = "0.20.3" @@ -4310,9 +4241,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.62" +version = "0.10.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" dependencies = [ "bitflags 2.4.2", "cfg-if", @@ -4342,9 +4273,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.98" +version = "0.9.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" dependencies = [ "cc", "libc", @@ -4807,9 +4738,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -5006,13 +4937,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.4", "regex-syntax 0.8.2", ] @@ -5027,9 +4958,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" dependencies = [ "aho-corasick", "memchr", @@ -5175,20 +5106,18 @@ dependencies = [ "fraction", "futures", "getrandom 0.2.12", - "hex 0.4.3", + "hex", "js-sys", "log", "num-traits", "rosetta-core", "rosetta-server-astar", - "rosetta-server-bitcoin", "rosetta-server-ethereum", "rosetta-server-polkadot", "rosetta-tx-ethereum", "rosetta-tx-polkadot", "serde", "serde_json", - "void", "wasm-bindgen", "web-sys", ] @@ -5202,19 +5131,13 @@ dependencies = [ "subxt", ] -[[package]] -name = "rosetta-config-bitcoin" -version = "0.5.0" -dependencies = [ - "anyhow", - "rosetta-core", -] - [[package]] name = "rosetta-config-ethereum" version = "0.5.0" dependencies = [ "anyhow", + "derivative", + "ethbloom", "ethereum-types", "hex-literal", "impl-serde", @@ -5257,13 +5180,13 @@ name = "rosetta-crypto" version = "0.5.0" dependencies = [ "anyhow", - "bech32 0.9.1", + "bech32", "blake2-rfc", "bs58 0.5.0", "ecdsa", "ed25519-dalek 1.0.1", "ethers", - "hex 0.4.3", + "hex", "hmac 0.12.1", "k256", "p256", @@ -5288,7 +5211,7 @@ dependencies = [ "docker-api", "futures", "getrandom 0.2.12", - "hex 0.4.3", + "hex", "log", "nanoid", "rosetta-client", @@ -5326,7 +5249,7 @@ dependencies = [ "futures", "futures-timer", "futures-util", - "hex 0.4.3", + "hex", "jsonrpsee 0.21.0", "log", "nanoid", @@ -5353,7 +5276,7 @@ dependencies = [ "ethers", "ethers-solc", "futures", - "hex 0.4.3", + "hex", "log", "parity-scale-codec", "rosetta-client", @@ -5371,22 +5294,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "rosetta-server-bitcoin" -version = "0.5.0" -dependencies = [ - "anyhow", - "async-trait", - "bitcoincore-rpc-async", - "hex 0.4.3", - "rosetta-config-bitcoin", - "rosetta-core", - "rosetta-docker", - "serde_json", - "tokio", - "void", -] - [[package]] name = "rosetta-server-ethereum" version = "0.5.0" @@ -5400,7 +5307,7 @@ dependencies = [ "ethers-solc", "futures-timer", "futures-util", - "hex 0.4.3", + "hex", "rosetta-client", "rosetta-config-ethereum", "rosetta-core", @@ -5421,7 +5328,7 @@ version = "0.5.0" dependencies = [ "anyhow", "async-trait", - "hex 0.4.3", + "hex", "parity-scale-codec", "rosetta-config-polkadot", "rosetta-core", @@ -5440,17 +5347,17 @@ dependencies = [ name = "rosetta-testing-arbitrum" version = "0.1.0" dependencies = [ - "alloy-sol-types 0.5.4", + "alloy-sol-types 0.6.0", "anyhow", "ethers", "ethers-solc", + "hex-literal", + "rand_core 0.6.4", "rosetta-client", "rosetta-config-ethereum", "rosetta-core", "rosetta-docker", "rosetta-server-ethereum", - "sequential-test", - "serde_json", "sha3", "tokio", "tracing", @@ -5527,7 +5434,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f868fd508789837c62557c8eb3c2ce6891ed3b4961e96e14f068d35299c270f" dependencies = [ - "bitcoin_hashes 0.11.0", + "bitcoin_hashes", "rand_core 0.6.4", "serde", "unicode-normalization", @@ -5769,28 +5676,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "sapio-bitcoin" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60df4fbc4dc3173998ec341b611c38c15ef5ce93c7d7405673aea3a911016d7a" -dependencies = [ - "bech32 0.7.3", - "bitcoin_hashes 0.9.7", - "sapio-secp256k1", - "serde", -] - -[[package]] -name = "sapio-secp256k1" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7830cd189705b505d3c291fd41a6ab852085d5024feffdc15e59aff64f1024d2" -dependencies = [ - "secp256k1-sys 0.4.2", - "serde", -] - [[package]] name = "scale-bits" version = "0.4.0" @@ -6026,16 +5911,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ - "secp256k1-sys 0.6.1", -] - -[[package]] -name = "secp256k1-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" -dependencies = [ - "cc", + "secp256k1-sys", ] [[package]] @@ -6133,21 +6009,6 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" -[[package]] -name = "sequential-macro" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5facc5f409a55d25bf271c853402a00e1187097d326757043f5dd711944d07" - -[[package]] -name = "sequential-test" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d9c0d773bc7e7733264f460e5dfa00b2510421ddd6284db0749eef8dfb79e9" -dependencies = [ - "sequential-macro", -] - [[package]] name = "serde" version = "1.0.195" @@ -6237,7 +6098,7 @@ checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" dependencies = [ "base64 0.13.1", "chrono", - "hex 0.4.3", + "hex", "indexmap 1.9.3", "serde", "serde_json", @@ -6421,9 +6282,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b187f0231d56fe41bfb12034819dd2bf336422a5866de41bc3fec4b2e3883e8" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "smol" @@ -6465,7 +6326,7 @@ dependencies = [ "futures-lite 2.2.0", "futures-util", "hashbrown 0.14.3", - "hex 0.4.3", + "hex", "hmac 0.12.1", "itertools 0.11.0", "libm", @@ -6515,7 +6376,7 @@ dependencies = [ "futures-lite 2.2.0", "futures-util", "hashbrown 0.14.3", - "hex 0.4.3", + "hex", "itertools 0.11.0", "log", "lru", @@ -7292,7 +7153,7 @@ dependencies = [ "either", "frame-metadata 16.0.0", "futures", - "hex 0.4.3", + "hex", "impl-serde", "jsonrpsee 0.20.3", "parity-scale-codec", @@ -7322,7 +7183,7 @@ checksum = "12800ad6128b4bfc93d2af89b7d368bff7ea2f6604add35f96f6a8c06c7f9abf" dependencies = [ "frame-metadata 16.0.0", "heck", - "hex 0.4.3", + "hex", "jsonrpsee 0.20.3", "parity-scale-codec", "proc-macro2", @@ -7484,7 +7345,7 @@ checksum = "20689c7d03b6461b502d0b95d6c24874c7d24dea2688af80486a130a06af3b07" dependencies = [ "dirs", "fs2", - "hex 0.4.3", + "hex", "once_cell", "reqwest", "semver 1.0.21", @@ -7763,9 +7624,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2 0.5.5", "tokio-macros", "windows-sys 0.48.0", @@ -8106,7 +7965,7 @@ checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy", - "hex 0.4.3", + "hex", "static_assertions", ] @@ -8278,12 +8137,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "w3f-bls" version = "0.1.3" diff --git a/Cargo.toml b/Cargo.toml index fa004aad..308f18f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,6 @@ members = [ "chains/astar/config", "chains/astar/server", - "chains/bitcoin/config", - "chains/bitcoin/server", "chains/ethereum/config", "chains/ethereum/rpc-client", "chains/ethereum/server", @@ -27,8 +25,6 @@ resolver = "2" [workspace.dependencies] rosetta-config-astar = { path = "chains/astar/config", default-features = false } rosetta-server-astar = { path = "chains/astar/server" } -rosetta-config-bitcoin = { path = "chains/bitcoin/config" } -rosetta-server-bitcoin = { path = "chains/bitcoin/server" } rosetta-config-ethereum = { path = "chains/ethereum/config" } rosetta-server-ethereum = { path = "chains/ethereum/server" } rosetta-ethereum-rpc-client = { path = "chains/ethereum/rpc-client" } diff --git a/README.md b/README.md index a45e96b1..c76201ce 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ rosetta-wallet [command] ## Reference wallet implementations -To help you get started with wallets on specific chains, we have developed complete Rosetta API reference implementations for Bitcoin (deprioritized for now), Ethereum, and Substrate-based chains. +To help you get started with wallets on specific chains, we have developed complete Rosetta API reference implementations for Ethereum, and Substrate-based chains. ### Ethereum example @@ -92,18 +92,6 @@ rosetta-wallet --chain dot --keyfile /tmp/alice transfer bob_acc_key 15000000000 rosetta-wallet --chain dot --keyfile /tmp/bob balance ``` -### Bitcoin example - -To use this repository, you need to fork it and start playing with the code. For example, running these commands will help you learn more about Rosetta API implementation for Bitcoin wallets: - -``` -rosetta-wallet --chain btc --keyfile /tmp/alice faucet 1000 -rosetta-wallet --chain btc --keyfile /tmp/bob account -rosetta-wallet --chain btc --keyfile /tmp/alice transfer bob_acc_key 1000 -rosetta-wallet --chain btc --keyfile /tmp/alice faucet 1 -rosetta-wallet --chain btc --keyfile /tmp/bob balance -``` - ## Reference CLI implementation To help you get started with rosetta-cli, we have developed a standard indexer endpoint that you can leverage to integrate external blockchains automatically. The indexer endpoint complements the existing Data and Construction API endpoints in Rosetta API specifications, allowing developers to fully support asset integration. @@ -127,7 +115,6 @@ http://rosetta.analog.one:3000 Running a local testnet with docker compose up initiates a number of containers, including: -- bitcoin: http://127.0.0.1:8080 - ethereum: http://127.0.0.1:8081 - polkadot: http://127.0.0.1:8082 - block explorer: [http://127.0.0.1:3000](http://127.0.0.1:3000) diff --git a/chains/arbitrum/testing/rosetta-testing-arbitrum/Cargo.toml b/chains/arbitrum/testing/rosetta-testing-arbitrum/Cargo.toml index f2139ce0..9ea2e651 100644 --- a/chains/arbitrum/testing/rosetta-testing-arbitrum/Cargo.toml +++ b/chains/arbitrum/testing/rosetta-testing-arbitrum/Cargo.toml @@ -6,21 +6,19 @@ license = "MIT" repository = "https://github.com/analog-labs/chain-connectors" description = "Arbitrum unit test." -[dependencies] +[dev-dependencies] +alloy-sol-types = { version = "0.6" } anyhow = "1.0" -ethers = { version = "2.0", default-features = true, features = ["abigen", "rustls"] } +ethers = { version = "2.0", default-features = true, features = ["abigen", "rustls", "ws"] } +ethers-solc = "2.0" +hex-literal = "0.4" +rand_core = { version = "0.6", features = ["getrandom"] } +rosetta-client.workspace = true rosetta-config-ethereum.workspace = true rosetta-core.workspace = true rosetta-docker = { workspace = true, features = ["tests"] } rosetta-server-ethereum.workspace = true -sequential-test = "0.2.4" -serde_json.workspace = true +sha3 = "0.10" tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } tracing = "0.1.40" - -[dev-dependencies] -alloy-sol-types = { version = "0.5" } -ethers-solc = "2.0" -rosetta-client.workspace = true -sha3 = "0.10" url = "2.4" diff --git a/chains/arbitrum/testing/rosetta-testing-arbitrum/src/lib.rs b/chains/arbitrum/testing/rosetta-testing-arbitrum/src/lib.rs index 360961d9..a105c802 100644 --- a/chains/arbitrum/testing/rosetta-testing-arbitrum/src/lib.rs +++ b/chains/arbitrum/testing/rosetta-testing-arbitrum/src/lib.rs @@ -20,7 +20,6 @@ //! - `rosetta_client`: Client library for Rosetta API interactions. //! - `rosetta_config_ethereum`: Configuration for Ethereum Rosetta server. //! - `rosetta_server_arbitrum`: Custom client implementation for interacting with Arbitrum. -//! - `sequential_test`: Macro for ensuring sequential execution of asynchronous tests. //! - `sha3`: SHA-3 (Keccak) implementation for hashing. //! - `tokio`: Asynchronous runtime for running async functions. //! @@ -43,18 +42,33 @@ mod tests { use ethers::{ providers::Middleware, signers::{LocalWallet, Signer}, - types::{transaction::eip2718::TypedTransaction, TransactionRequest, H160, H256, U256}, - utils::hex, + types::{ + transaction::eip2718::TypedTransaction, BlockId, BlockNumber, TransactionRequest, H160, + H256, U256, U64, + }, }; use ethers_solc::{artifacts::Source, CompilerInput, EvmVersion, Solc}; + use hex_literal::hex; use rosetta_client::Wallet; use rosetta_config_ethereum::{AtBlock, CallResult}; - use rosetta_core::{types::PartialBlockIdentifier, BlockchainClient}; + use rosetta_core::BlockchainClient; use rosetta_server_ethereum::MaybeWsEthereumClient; - use sequential_test::sequential; use sha3::Digest; - use std::{collections::BTreeMap, future::Future, path::Path, str::FromStr}; - use tokio::sync::oneshot::{error::TryRecvError, Receiver}; + use std::{collections::BTreeMap, future::Future, path::Path, time::Duration}; + + /// Account used to fund other testing accounts. + const FUNDING_ACCOUNT_PRIVATE_KEY: [u8; 32] = + hex!("8aab161e2a1e57367b60bd870861e3042c2513f8a856f9fee014e7b96e0a2a36"); + + /// Account used exclusively to continuously sending tx to mine new blocks. + const BLOCK_INCREMENTER_PRIVATE_KEY: [u8; 32] = + hex!("b6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659"); + + /// Arbitrum rpc url + const ARBITRUM_RPC_HTTP_URL: &str = "http://127.0.0.1:8547"; + const ARBITRUM_RPC_WS_URL: &str = "ws://127.0.0.1:8548"; + + type WsProvider = ethers::providers::Provider; sol! { interface TestContract { @@ -65,190 +79,226 @@ mod tests { } } - async fn run_test(_future: Fut, mut stop_rx: Receiver<()>) { - loop { - if matches!(stop_rx.try_recv(), Ok(()) | Err(TryRecvError::Closed)) { - break; + /// Send funds from funding account to the provided account. + /// This function is can be called concurrently. + async fn sync_send_funds(dest: H160, amount: u128) -> Result<()> { + // Guarantee the funding account nonce is incremented atomically + static NONCE: tokio::sync::Mutex = tokio::sync::Mutex::const_new(0); + + // Connect to the provider + let wallet = LocalWallet::from_bytes(&FUNDING_ACCOUNT_PRIVATE_KEY)?; + let provider = + ethers::providers::Provider::::try_from(ARBITRUM_RPC_HTTP_URL) + .context("Failed to create HTTP provider")? + .interval(Duration::from_secs(1)); + + // Retrieve chain id + let chain_id = provider.get_chainid().await?.as_u64(); + + // Acquire nonce lock + let mut nonce_lock = NONCE.lock().await; + + // Initialize nonce if necessary + if *nonce_lock == 0 { + // retrieve the current nonce, used to initialize the nonce if necessary, once + // `OnceLock` doesn't support async functions + let current_nonce = provider + .get_transaction_count(wallet.address(), Some(BlockId::Number(BlockNumber::Latest))) + .await? + .as_u64(); + *nonce_lock = current_nonce; + } + + // Create a transaction request + let transaction_request = TransactionRequest { + from: None, + to: Some(ethers::types::NameOrAddress::Address(dest)), + value: Some(U256::from(amount)), + gas: Some(U256::from(210_000)), + gas_price: Some(U256::from(500_000_000)), + nonce: Some(U256::from(*nonce_lock)), + data: None, + chain_id: Some(chain_id.into()), + }; + + // Sign and send the transaction + let tx: TypedTransaction = transaction_request.into(); + let signature = wallet.sign_transaction(&tx).await?; + let tx = tx.rlp_signed(&signature); + let pending_tx = provider.send_raw_transaction(tx).await?; + + // Increment and release nonce lock + // increment only after successfully send the tx to avoid nonce gaps + *nonce_lock += 1; + drop(nonce_lock); + + // Verify if the tx reverted + let receipt = + pending_tx.confirmations(1).await?.context("failed to retrieve tx receipt")?; + if !matches!(receipt.status, Some(U64([1]))) { + anyhow::bail!("Transaction reverted: {:?}", receipt.transaction_hash); + } + Ok(()) + } + + /// Creates a random account and send funds to it + async fn create_test_account(initial_balance: u128) -> Result<[u8; 32]> { + use ethers::core::k256::ecdsa::SigningKey; + use rand_core::OsRng; + let signing_key = SigningKey::random(&mut OsRng); + let address = ::ethers::utils::secret_key_to_address(&signing_key); + sync_send_funds(address, initial_balance).await?; + Ok(signing_key.to_bytes().into()) + } + + /// Run the test in another thread while sending txs to force arbitrum to mine new blocks + /// # Panic + /// Panics if the future panics + async fn run_test + Send + 'static>(future: Fut) { + // Guarantee that only one test is incrementing blocks at a time + static LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(()); + + // Run the test in another thread + let test_handler = tokio::spawn(future); + + // Acquire Lock + let guard = LOCK.lock().await; + + // Check if the test is finished after acquiring the lock + if test_handler.is_finished() { + // Release lock + drop(guard); + + // Now is safe to panic + if let Err(err) = test_handler.await { + std::panic::resume_unwind(err.into_panic()); } - let hex_string = "0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659"; - let hex_string = &hex_string[2..]; - let mut private_key_result = [0; 32]; - let bytes = hex::decode(hex_string).expect("Failed to decode hex string"); - private_key_result.copy_from_slice(&bytes); - let result = MaybeWsEthereumClient::new( - "arbitrum", - "dev", - "ws://127.0.0.1:8548", - Some(private_key_result), - ) - .await; - assert!(result.is_ok(), "Error creating ArbitrumClient"); - let wallet = LocalWallet::from_bytes(&private_key_result).unwrap(); - let provider = ethers::providers::Provider::::try_from( - "http://localhost:8547", - ) - .expect("Failed to create HTTP provider"); - let address = H160::from_str("0x8Db77D3B019a52788bD3804724f5653d7C9Cf0b6").unwrap(); - let nonce = provider - .get_transaction_count( - H160::from_str("0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E").unwrap(), - None, - ) - .await - .unwrap(); - let chain_id = provider.get_chainid().await.unwrap().as_u64(); - // Create a transaction request - let transaction_request = TransactionRequest { - from: None, - to: Some(ethers::types::NameOrAddress::Address(address)), - value: Some(U256::from(1)), - gas: Some(U256::from(210_000)), - gas_price: Some(U256::from(500_000_000)), - nonce: Some(nonce), - data: None, - chain_id: Some(chain_id.into()), - }; - let tx: TypedTransaction = transaction_request.into(); - let signature = wallet.sign_transaction(&tx).await.unwrap(); - let tx = tx.rlp_signed(&signature); - let _ = provider + return; + } + + // Connect to arbitrum node + let wallet = LocalWallet::from_bytes(&BLOCK_INCREMENTER_PRIVATE_KEY).unwrap(); + let provider = WsProvider::connect(ARBITRUM_RPC_WS_URL) + .await + .map(|provider| provider.interval(Duration::from_millis(500))) + .unwrap(); + + // Retrieve chain id + let chain_id = provider.get_chainid().await.unwrap().as_u64(); + + // Retrieve current nonce + let mut nonce = provider + .get_transaction_count(wallet.address(), None) + .await + .expect("failed to retrieve account nonce") + .as_u64(); + + // Create a transaction request + let transaction_request = TransactionRequest { + from: None, + to: Some(wallet.address().into()), + value: None, + gas: Some(U256::from(210_000)), + gas_price: Some(U256::from(500_000_000)), + nonce: None, + data: None, + chain_id: Some(chain_id.into()), + }; + let mut tx: TypedTransaction = transaction_request.into(); + + // Mine a new block by sending a transaction until the test finishes + while !test_handler.is_finished() { + // Set tx nonce + tx.set_nonce(nonce); + + // Increment nonce + nonce += 1; + + // Sign and send the transaction + let signature = wallet.sign_transaction(&tx).await.expect("failed to sign tx"); + let tx: ethers::types::Bytes = tx.rlp_signed(&signature); + let receipt = provider .send_raw_transaction(tx) .await .unwrap() .confirmations(1) .await .unwrap() - .context("failed to retrieve tx receipt") - .unwrap() - .transaction_hash - .0 - .to_vec(); - tokio::time::sleep(std::time::Duration::from_secs(1)).await; + .expect("tx receipt not found"); + + // Verify if the tx reverted + assert!(receipt.status.unwrap().as_u64() == 1, "Transaction reverted: {receipt:?}"); + + // Wait 500ms for the tx to be mined + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + } + // Release lock + drop(guard); + + // Now is safe to panic + if let Err(err) = test_handler.await { + // Resume the panic on the main task + std::panic::resume_unwind(err.into_panic()); } } #[tokio::test] - #[sequential] async fn network_status() { - let hex_string = "0x8aab161e2a1e57367b60bd870861e3042c2513f8a856f9fee014e7b96e0a2a36"; - // Remove the "0x" prefix - let hex_string = &hex_string[2..]; - let mut result = [0; 32]; - // Parse the hexadecimal string into a Vec - let bytes = hex::decode(hex_string).expect("Failed to decode hex string"); - result.copy_from_slice(&bytes); - - match MaybeWsEthereumClient::new("arbitrum", "dev", "ws://127.0.0.1:8548", Some(result)) + let private_key = create_test_account(20 * u128::pow(10, 18)).await.unwrap(); + run_test(async move { + let client = MaybeWsEthereumClient::new( + "arbitrum", + "dev", + ARBITRUM_RPC_WS_URL, + Some(private_key), + ) .await - { - Ok(client) => { - // The client was successfully created, continue with the rest of the function - // ... - println!("Client created successfully"); - // Check if the genesis is consistent - let expected_genesis = client.genesis_block().clone(); - tracing::info!("expected_genesis=> {expected_genesis:?}"); - let actual_genesis = client - .block(&PartialBlockIdentifier { index: Some(0), hash: None }) - .await - .unwrap() - .block_identifier; - - tracing::info!("actual_genesis=> {actual_genesis:?}"); - assert_eq!(expected_genesis, actual_genesis); - // Check if the current block is consistent - let expected_current = client.current_block().await.unwrap(); - tracing::info!("expected_current=> {expected_current:?}"); - let actual_current = client - .block(&PartialBlockIdentifier { - index: None, - hash: Some(expected_current.hash.clone()), - }) - .await; - match actual_current { - Ok(block) => { - tracing::info!("actual_current=> {:?}", block.block_identifier); - assert_eq!(expected_current, block.block_identifier); - }, - Err(error) => { - tracing::error!("{error:?}"); - }, - } + .expect("Error creating client"); + // Check if the genesis is consistent + let genesis_block = client.genesis_block(); + assert_eq!(genesis_block.index, 0); - // Check if the finalized block is consistent - let expected_finalized = client.finalized_block().await.unwrap(); - tracing::info!("expected_finalized=> {expected_finalized:?}"); - let actual_finalized = client - .block(&PartialBlockIdentifier { - index: None, - hash: Some(expected_finalized.hash.clone()), - }) - .await; - - match actual_finalized { - Ok(block) => { - tracing::info!("actual_finalized=> {:?}", block.block_identifier); - assert_eq!(expected_finalized, block.block_identifier); - }, - Err(error) => { - tracing::error!("ad{error:?}"); - }, - } + // Check if the current block is consistent + let current_block = client.current_block().await.unwrap(); + if current_block.index > 0 { + assert_ne!(current_block.hash, genesis_block.hash); + } else { + assert_eq!(current_block.hash, genesis_block.hash); + } - tracing::info!("Arbitrum network is up and running"); - }, - Err(err) => { - // An error occurred while creating the client, handle the error here - eprintln!("Error creating client: {err:?}"); - }, - } + // Check if the finalized block is consistent + let finalized_block = client.finalized_block().await.unwrap(); + assert!(finalized_block.index >= genesis_block.index); + }) + .await; } #[tokio::test] - #[sequential] async fn test_account() { - let (stop_tx, mut stop_rx) = tokio::sync::oneshot::channel::<()>(); - let handler = tokio::spawn(async move { - run_test(async {}, stop_rx).await; - }); - let hex_string = "0x8aab161e2a1e57367b60bd870861e3042c2513f8a856f9fee014e7b96e0a2a36"; - // Remove the "0x" prefix - let hex_string = &hex_string[2..]; - let mut private_key_result = [0; 32]; - // Parse the hexadecimal string into a Vec - let bytes = hex::decode(hex_string).expect("Failed to decode hex string"); - private_key_result.copy_from_slice(&bytes); - let result = MaybeWsEthereumClient::new( - "arbitrum", - "dev", - "ws://127.0.0.1:8548", - Some(private_key_result), - ) - .await; - assert!(result.is_ok(), "Error creating ArbitrumClient"); - let client = result.unwrap(); - let value = 100 * u128::pow(10, client.config().currency_decimals); - let wallet = Wallet::from_config( - client.config().clone(), - "ws://127.0.0.1:8548", - None, - Some(private_key_result), - ) + let private_key = create_test_account(20 * u128::pow(10, 18)).await.unwrap(); + run_test(async move { + let client = MaybeWsEthereumClient::new( + "arbitrum", + "dev", + ARBITRUM_RPC_WS_URL, + Some(private_key), + ) + .await + .expect("Error creating ArbitrumClient"); + let wallet = Wallet::from_config( + client.config().clone(), + ARBITRUM_RPC_WS_URL, + None, + Some(private_key), + ) + .await + .unwrap(); + let value = 10 * u128::pow(10, client.config().currency_decimals); + let _ = wallet.faucet(value).await; + let amount = wallet.balance().await.unwrap(); + assert_eq!(amount, value); + }) .await; - match wallet { - Ok(w) => { - let _ = w.faucet(value).await; - let amount = w.balance().await.unwrap(); - assert_eq!((amount.value), (value).to_string()); - assert_eq!(amount.currency, client.config().currency()); - assert!(amount.metadata.is_none()); - }, - Err(e) => { - println!("Error : {e:?}"); - }, - } - stop_tx.send(()).expect("Failed to send stop signal"); - handler.await.expect("Failed to join the background task"); } fn compile_snippet(source: &str) -> Result> { @@ -276,132 +326,103 @@ mod tests { Ok(bytecode) } - #[allow(clippy::needless_raw_string_hashes)] #[tokio::test] - #[sequential] async fn test_smart_contract() { - let (stop_tx, mut stop_rx) = tokio::sync::oneshot::channel::<()>(); - let handler = tokio::spawn(async move { - run_test(async {}, stop_rx).await; - }); - let hex_string = "0x8aab161e2a1e57367b60bd870861e3042c2513f8a856f9fee014e7b96e0a2a36"; - // Remove the "0x" prefix - let hex_string = &hex_string[2..]; - let mut private_key_result = [0; 32]; - // Parse the hexadecimal string into a Vec - let bytes = hex::decode(hex_string).expect("Failed to decode hex string"); - private_key_result.copy_from_slice(&bytes); - let result = MaybeWsEthereumClient::new( - "arbitrum", - "dev", - "ws://127.0.0.1:8548", - Some(private_key_result), - ) + let private_key = create_test_account(20 * u128::pow(10, 18)).await.unwrap(); + run_test(async move { + let client = MaybeWsEthereumClient::new( + "arbitrum", + "dev", + ARBITRUM_RPC_WS_URL, + Some(private_key), + ) + .await + .expect("Error creating ArbitrumClient"); + let faucet = 10 * u128::pow(10, client.config().currency_decimals); + let wallet = Wallet::from_config( + client.config().clone(), + ARBITRUM_RPC_WS_URL, + None, + Some(private_key), + ) + .await + .unwrap(); + wallet.faucet(faucet).await.unwrap(); + + let bytes = compile_snippet( + r" + event AnEvent(); + function emitEvent() public { + emit AnEvent(); + } + ", + ) + .unwrap(); + let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap(); + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let contract_address = receipt.contract_address.unwrap(); + let tx_hash = { + let call = TestContract::emitEventCall {}; + wallet.eth_send_call(contract_address.0, call.abi_encode(), 0).await.unwrap() + }; + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + assert_eq!(receipt.logs.len(), 1); + let topic = receipt.logs[0].topics[0]; + let expected = H256(sha3::Keccak256::digest("AnEvent()").into()); + assert_eq!(topic, expected); + }) .await; - assert!(result.is_ok(), "Error creating ArbitrumClient"); - - let client = result.unwrap(); - - let faucet = 100 * u128::pow(10, client.config().currency_decimals); - let wallet = Wallet::from_config( - client.config().clone(), - "ws://127.0.0.1:8548", - None, - Some(private_key_result), - ) - .await - .unwrap(); - wallet.faucet(faucet).await.unwrap(); - - let bytes = compile_snippet( - r" - event AnEvent(); - function emitEvent() public { - emit AnEvent(); - } - ", - ) - .unwrap(); - let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap(); - let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); - let contract_address = receipt.contract_address.unwrap(); - let tx_hash = { - let call = TestContract::emitEventCall {}; - wallet.eth_send_call(contract_address.0, call.abi_encode(), 0).await.unwrap() - }; - let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); - assert_eq!(receipt.logs.len(), 1); - let topic = receipt.logs[0].topics[0]; - let expected = H256(sha3::Keccak256::digest("AnEvent()").into()); - assert_eq!(topic, expected); - stop_tx.send(()).expect("Failed to send stop signal"); - handler.await.expect("Failed to join the background task"); } - #[allow(clippy::needless_raw_string_hashes)] #[tokio::test] - #[sequential] async fn test_smart_contract_view() { - let (stop_tx, mut stop_rx) = tokio::sync::oneshot::channel::<()>(); - let handler = tokio::spawn(async move { - run_test(async {}, stop_rx).await; - }); - let hex_string = "0x8aab161e2a1e57367b60bd870861e3042c2513f8a856f9fee014e7b96e0a2a36"; - // Remove the "0x" prefix - let hex_string = &hex_string[2..]; - let mut private_key_result = [0; 32]; - // Parse the hexadecimal string into a Vec - let bytes = hex::decode(hex_string).expect("Failed to decode hex string"); - private_key_result.copy_from_slice(&bytes); - let result = MaybeWsEthereumClient::new( - "arbitrum", - "dev", - "ws://127.0.0.1:8548", - Some(private_key_result), - ) - .await; - assert!(result.is_ok(), "Error creating ArbitrumClient"); - let client = result.unwrap(); - let faucet = 100 * u128::pow(10, client.config().currency_decimals); - let wallet = Wallet::from_config( - client.config().clone(), - "ws://127.0.0.1:8548", - None, - Some(private_key_result), - ) - .await - .unwrap(); - wallet.faucet(faucet).await.unwrap(); - let bytes = compile_snippet( - r" - function identity(bool a) public view returns (bool) { - return a; - } - ", - ) - .unwrap(); - let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap(); - let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); - let contract_address = receipt.contract_address.unwrap(); - - let response = { - let call = TestContract::identityCall { a: true }; - wallet - .eth_view_call(contract_address.0, call.abi_encode(), AtBlock::Latest) - .await - .unwrap() - }; - assert_eq!( - response, - CallResult::Success( - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1 - ] - .to_vec() + let private_key = create_test_account(20 * u128::pow(10, 18)).await.unwrap(); + run_test(async move { + let client = MaybeWsEthereumClient::new( + "arbitrum", + "dev", + ARBITRUM_RPC_WS_URL, + Some(private_key), ) - ); - stop_tx.send(()).expect("Failed to send stop signal"); - handler.await.expect("Failed to join the background task"); + .await + .expect("Error creating ArbitrumClient"); + let faucet = 10 * u128::pow(10, client.config().currency_decimals); + let wallet = Wallet::from_config( + client.config().clone(), + ARBITRUM_RPC_WS_URL, + None, + Some(private_key), + ) + .await + .unwrap(); + wallet.faucet(faucet).await.unwrap(); + let bytes = compile_snippet( + r" + function identity(bool a) public view returns (bool) { + return a; + } + ", + ) + .unwrap(); + let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap(); + let receipt = wallet.eth_transaction_receipt(tx_hash).await.unwrap().unwrap(); + let contract_address = receipt.contract_address.unwrap(); + + let response = { + let call = TestContract::identityCall { a: true }; + wallet + .eth_view_call(contract_address.0, call.abi_encode(), AtBlock::Latest) + .await + .unwrap() + }; + assert_eq!( + response, + CallResult::Success( + hex!("0000000000000000000000000000000000000000000000000000000000000001") + .to_vec() + ) + ); + }) + .await; } } diff --git a/chains/astar/server/README.md b/chains/astar/server/README.md index 752b93ff..368d8188 100644 --- a/chains/astar/server/README.md +++ b/chains/astar/server/README.md @@ -13,7 +13,6 @@ Methods implemented are: - `metadata` - `submit` - `block` -- `block_transaction` - `call` ### `config`: @@ -42,13 +41,6 @@ Fetches account balance from on chain and returns it. It takes two arguments: This function takes `PartialBlockIdentifier` which contains a block index or hash and returns block transaction and operations happened in that transaction. -### `block_transaction`: - -This function takes: -`block`: Which is a block identifier of block from which we want to fetch transaction from. -`tx`: Transaction identifier of transaction we want to fetch. -And returns a specific transaction and its operations within specified block. - ### `faucet`: This method is used to fund an account with some amount of tokens in testnet. It takes two arguments: diff --git a/chains/astar/server/src/lib.rs b/chains/astar/server/src/lib.rs index 11004dc7..b5a8a4a9 100644 --- a/chains/astar/server/src/lib.rs +++ b/chains/astar/server/src/lib.rs @@ -13,9 +13,7 @@ use rosetta_core::{ address::{Address, AddressFormat}, PublicKey, }, - types::{ - Block, BlockIdentifier, Coin, PartialBlockIdentifier, Transaction, TransactionIdentifier, - }, + types::{BlockIdentifier, PartialBlockIdentifier}, BlockchainClient, BlockchainConfig, }; use rosetta_server::ws::default_client; @@ -26,7 +24,9 @@ use subxt::{ backend::{ legacy::{rpc_methods::BlockNumber, LegacyBackend, LegacyRpcMethods}, rpc::RpcClient, + BlockRef, }, + config::substrate::U256, dynamic::Value as SubtxValue, ext::sp_core::{self, crypto::Ss58AddressFormat}, tx::PairSigner, @@ -75,7 +75,7 @@ impl AstarClient { async fn account_info( &self, address: &Address, - maybe_block: Option<&BlockIdentifier>, + maybe_block: Option<&PartialBlockIdentifier>, ) -> Result>> { let account: AccountId32 = address .address() @@ -87,16 +87,89 @@ impl AstarClient { let storage_query = subxt::dynamic::storage("System", "Account", vec![SubtxValue::from_bytes(account)]); - let block_hash = { - let block_number = maybe_block.map(|block| BlockNumber::from(block.index)); - self.rpc_methods - .chain_get_block_hash(block_number) + // TODO: Change the `PartialBlockIdentifier` for distinguish between ethereum blocks and + // substrate blocks. + let block_hash = match maybe_block { + Some(PartialBlockIdentifier { hash: Some(block_hash), .. }) => { + // If a hash if provided, we don't know if it's a ethereum block hash or substrate + // block hash. We try to fetch the block using ethereum first, and + // if it fails, we try to fetch it using substrate. + let ethereum_block = + self.client.call(&EthQuery::GetBlockByHash(H256(*block_hash))).await.map( + |result| match result { + EthQueryResult::GetBlockByHash(block) => block, + _ => unreachable!(), + }, + ); + + if let Ok(Some(ethereum_block)) = ethereum_block { + // Convert ethereum block to substrate block by fetching the block by number. + let substrate_block_number = BlockNumber::Number(ethereum_block.header.number); + let substrate_block_hash = self + .rpc_methods + .chain_get_block_hash(Some(substrate_block_number)) + .await? + .map(BlockRef::from_hash) + .ok_or_else(|| anyhow::anyhow!("no block hash found"))?; + + // Verify if the ethereum block belongs to this substrate block. + let query_current_eth_block = + astar_metadata::storage().ethereum().current_block(); + + // Fetch ethereum block from `ethereum.current_block` state. + let Some(actual_eth_block) = self + .ws_client + .storage() + .at(substrate_block_hash.clone()) + .fetch(&query_current_eth_block) + .await? + else { + // This error should not happen, once all astar blocks must have one + // ethereum block + anyhow::bail!("[report this bug!] no ethereum block found for astar at block {substrate_block_hash:?}"); + }; + + // Verify if the ethereum block hash matches the provided ethereum block hash. + // TODO: compute the block hash + if U256(actual_eth_block.header.number.0) != + U256::from(ethereum_block.header.number) + { + anyhow::bail!("ethereum block hash mismatch"); + } + if actual_eth_block.header.parent_hash.as_fixed_bytes() != + ðereum_block.header.parent_hash.0 + { + anyhow::bail!("ethereum block hash mismatch"); + } + substrate_block_hash + } else { + self.rpc_methods + .chain_get_block_hash(Some(BlockNumber::Hex(U256::from_big_endian( + block_hash, + )))) + .await? + .map(BlockRef::from_hash) + .ok_or_else(|| anyhow::anyhow!("no block hash found"))? + } + }, + Some(PartialBlockIdentifier { index: Some(block_number), .. }) => { + // If a block number is provided, the value is the same for ethereum blocks and + // substrate blocks. + self.rpc_methods + .chain_get_block_hash(Some(BlockNumber::Number(*block_number))) + .await? + .map(BlockRef::from_hash) + .ok_or_else(|| anyhow::anyhow!("no block hash found"))? + }, + Some(PartialBlockIdentifier { .. }) | None => self + .rpc_methods + .chain_get_block_hash(None) .await? - .ok_or_else(|| anyhow::anyhow!("no block hash found"))? + .map(BlockRef::from_hash) + .ok_or_else(|| anyhow::anyhow!("no block hash found"))?, }; let account_info = self.ws_client.storage().at(block_hash).fetch(&storage_query).await?; - account_info.map_or_else( || { Ok(AccountInfo::> { @@ -128,27 +201,36 @@ impl BlockchainClient for AstarClient { type Call = EthQuery; type CallResult = EthQueryResult; + type AtBlock = PartialBlockIdentifier; + type BlockIdentifier = BlockIdentifier; + + type Query = rosetta_config_ethereum::Query; + type Transaction = rosetta_config_ethereum::SignedTransaction; + + async fn query( + &self, + query: Self::Query, + ) -> Result<::Result> { + self.client.query(query).await + } + fn config(&self) -> &BlockchainConfig { self.client.config() } - fn genesis_block(&self) -> &BlockIdentifier { + fn genesis_block(&self) -> Self::BlockIdentifier { self.client.genesis_block() } - async fn node_version(&self) -> Result { - self.client.node_version().await - } - - async fn current_block(&self) -> Result { + async fn current_block(&self) -> Result { self.client.current_block().await } - async fn finalized_block(&self) -> Result { + async fn finalized_block(&self) -> Result { self.client.finalized_block().await } - async fn balance(&self, address: &Address, block: &BlockIdentifier) -> Result { + async fn balance(&self, address: &Address, block: &Self::AtBlock) -> Result { let balance = match address.format() { AddressFormat::Ss58(_) => { let account_info = self.account_info(address, Some(block)).await?; @@ -169,10 +251,6 @@ impl BlockchainClient for AstarClient { Ok(balance) } - async fn coins(&self, address: &Address, block: &BlockIdentifier) -> Result> { - self.client.coins(address, block).await - } - async fn faucet(&self, address: &Address, value: u128) -> Result> { // convert address let dest = { @@ -212,18 +290,6 @@ impl BlockchainClient for AstarClient { self.client.submit(transaction).await } - async fn block(&self, block_identifier: &PartialBlockIdentifier) -> Result { - self.client.block(block_identifier).await - } - - async fn block_transaction( - &self, - block_identifier: &BlockIdentifier, - tx: &TransactionIdentifier, - ) -> Result { - self.client.block_transaction(block_identifier, tx).await - } - async fn call(&self, req: &EthQuery) -> Result { self.client.call(req).await } @@ -334,7 +400,6 @@ mod tests { let topic = logs[0].topics[0]; let expected = H256::from_slice(sha3::Keccak256::digest("AnEvent()").as_ref()); assert_eq!(topic, expected); - Ok(()) }) .await; Ok(()) @@ -381,7 +446,6 @@ mod tests { .to_vec() ) ); - Ok(()) }) .await; Ok(()) diff --git a/chains/bitcoin/config/Cargo.toml b/chains/bitcoin/config/Cargo.toml deleted file mode 100644 index 982a0d9f..00000000 --- a/chains/bitcoin/config/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "rosetta-config-bitcoin" -version = "0.5.0" -edition = "2021" -license = "MIT" -repository = "https://github.com/analog-labs/chain-connectors" -description = "Bitcoin configuration." - -[dependencies] -anyhow = "1.0" -rosetta-core.workspace = true diff --git a/chains/bitcoin/config/src/lib.rs b/chains/bitcoin/config/src/lib.rs deleted file mode 100644 index c6763705..00000000 --- a/chains/bitcoin/config/src/lib.rs +++ /dev/null @@ -1,48 +0,0 @@ -use anyhow::Result; -use rosetta_core::{ - crypto::{address::AddressFormat, Algorithm}, - BlockchainConfig, NodeUri, -}; -use std::sync::Arc; - -/// Retrieve the [`BlockchainConfig`] from the provided `network` -/// -/// # Errors -/// Returns `Err` if the network is not supported -pub fn config(network: &str) -> Result { - let (network, symbol, bip44_id) = match network { - "regtest" => ("regtest", "tBTC", 1), - "mainnet" => ("mainnet", "BTC", 0), - _ => anyhow::bail!("unsupported network: {}", network), - }; - Ok(BlockchainConfig { - blockchain: "bitcoin", - network, - algorithm: Algorithm::EcdsaSecp256k1, - address_format: AddressFormat::Bech32("bcrt"), - coin: bip44_id, - bip44: true, - utxo: true, - currency_unit: "satoshi", - currency_symbol: symbol, - currency_decimals: 8, - node_uri: NodeUri::parse("http://127.0.0.1:18443")?, - node_image: "ruimarinho/bitcoin-core:23", - node_command: Arc::new(|network, port| { - let mut params: Vec = vec![ - "-rpcbind=0.0.0.0".into(), - format!("-rpcport={port}"), - "-rpcallowip=0.0.0.0/0".into(), - "-rpcuser=rosetta".into(), - "-rpcpassword=rosetta".into(), - ]; - if network == "regtest" { - params.push("-regtest=1".into()); - } - params - }), - node_additional_ports: &[], - connector_port: 8080, - testnet: network == "regtest", - }) -} diff --git a/chains/bitcoin/server/Cargo.toml b/chains/bitcoin/server/Cargo.toml deleted file mode 100644 index ec1ae79d..00000000 --- a/chains/bitcoin/server/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "rosetta-server-bitcoin" -version = "0.5.0" -edition = "2021" -license = "MIT" -repository = "https://github.com/analog-labs/chain-connectors" -description = "Bitcoin rosetta server." - -[dependencies] -anyhow = "1.0" -async-trait = "0.1" -bitcoincore-rpc-async = "3.0" -hex = "0.4" -rosetta-config-bitcoin.workspace = true -rosetta-core.workspace = true -serde_json = "1.0" -tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -void = "1.0" - -[dev-dependencies] -rosetta-docker = { workspace = true, features = ["tests"] } diff --git a/chains/bitcoin/server/src/lib.rs b/chains/bitcoin/server/src/lib.rs deleted file mode 100644 index 72634ed9..00000000 --- a/chains/bitcoin/server/src/lib.rs +++ /dev/null @@ -1,209 +0,0 @@ -use anyhow::{Context, Result}; -use bitcoincore_rpc_async::{bitcoin::BlockHash, Auth, Client, RpcApi}; -use rosetta_core::{ - crypto::{address::Address, PublicKey}, - types::{ - Block, BlockIdentifier, Coin, PartialBlockIdentifier, Transaction, TransactionIdentifier, - }, - BlockchainClient, BlockchainConfig, -}; -use std::str::FromStr; -use void::{unreachable, Void}; - -pub type BitcoinMetadataParams = (); -pub type BitcoinMetadata = (); - -pub struct BitcoinClient { - config: BlockchainConfig, - client: Client, - genesis_block: BlockIdentifier, -} - -impl BitcoinClient { - /// Creates a new bitcoin client from `network`and `addr` - /// - /// # Errors - /// Will return `Err` when the network is invalid, or when the provided `addr` is unreacheable. - pub async fn new(network: &str, addr: &str) -> Result { - let config = rosetta_config_bitcoin::config(network)?; - Self::from_config(config, addr).await - } - - /// Creates a new bitcoin client from `config` and `addr` - /// - /// # Errors - /// Will return `Err` when the network is invalid, or when the provided `addr` is unreacheable. - pub async fn from_config(config: BlockchainConfig, addr: &str) -> Result { - let client = - Client::new(addr.to_string(), Auth::UserPass("rosetta".into(), "rosetta".into())) - .await?; - let genesis = client.get_block_hash(0).await?; - let genesis_block = BlockIdentifier { index: 0, hash: genesis.to_string() }; - - Ok(Self { config, client, genesis_block }) - } -} - -/// Bitcoin community has adopted 6 blocks as a standard confirmation period. -/// That is, once a transaction is included in a block in the blockchain which is followed up by at -/// least 6 additional blocks the transaction is called “confirmed.” While this was chosen somewhat -/// arbitrarily, it is a reasonably safe value in practice as the only time this would have left -/// users vulnerable to double-spending was the atypical March 2013 fork. -const CONFIRMATION_PERIOD: u64 = 6; - -#[async_trait::async_trait] -impl BlockchainClient for BitcoinClient { - type MetadataParams = BitcoinMetadataParams; - type Metadata = BitcoinMetadata; - type EventStream<'a> = rosetta_core::EmptyEventStream; - type Call = Void; - type CallResult = (); - - fn config(&self) -> &BlockchainConfig { - &self.config - } - - fn genesis_block(&self) -> &BlockIdentifier { - &self.genesis_block - } - - async fn node_version(&self) -> Result { - let info = self.client.get_network_info().await?; - let major = info.version / 10000; - let rest = info.version % 10000; - let minor = rest / 100; - let patch = rest % 100; - Ok(format!("{major}.{minor}.{patch}")) - } - - async fn current_block(&self) -> Result { - let hash = self.client.get_best_block_hash().await?; - let info = self.client.get_block_info(&hash).await?; - Ok(BlockIdentifier { index: info.height as u64, hash: hash.to_string() }) - } - - async fn finalized_block(&self) -> Result { - let index = self.client.get_block_count().await?.saturating_sub(CONFIRMATION_PERIOD); - let hash = self.client.get_block_hash(index).await?; - Ok(BlockIdentifier { index, hash: hash.to_string() }) - } - - async fn balance(&self, _address: &Address, _block: &BlockIdentifier) -> Result { - todo!() - } - - async fn coins(&self, _address: &Address, _block: &BlockIdentifier) -> Result> { - todo!() - } - - async fn faucet(&self, _address: &Address, _value: u128) -> Result> { - todo!() - } - - async fn metadata( - &self, - _public_key: &PublicKey, - _options: &Self::MetadataParams, - ) -> Result { - Ok(()) - } - - async fn submit(&self, _transaction: &[u8]) -> Result> { - todo!() - } - - async fn block(&self, block: &PartialBlockIdentifier) -> Result { - let block = match (block.hash.as_ref(), block.index) { - (Some(block_hash), _) => { - let hash = BlockHash::from_str(block_hash).context("Invalid block hash")?; - self.client.get_block(&hash).await? - }, - (None, Some(height)) => { - let block_bash = - self.client.get_block_hash(height).await.context("cannot find by index")?; - self.client.get_block(&block_bash).await? - }, - (None, None) => anyhow::bail!("the block hash or index must be specified"), - }; - - let block_height = if let Ok(height) = block.bip34_block_height() { - height - } else { - let info = self - .client - .get_block_info(&block.block_hash()) - .await - .context("Cannot find block height")?; - info.height as u64 - }; - - let transactions = block - .txdata - .iter() - .map(|tx| Transaction { - transaction_identifier: TransactionIdentifier::new(tx.txid().as_hash().to_string()), - operations: vec![], - related_transactions: None, - metadata: serde_json::to_value(tx.clone()).ok(), - }) - .collect::>(); - - Ok(Block { - block_identifier: BlockIdentifier { - index: block_height, - hash: block.block_hash().to_string(), - }, - parent_block_identifier: BlockIdentifier { - index: block_height.saturating_sub(1), - hash: block.header.prev_blockhash.to_string(), - }, - timestamp: i64::from(block.header.time) * 1000, - transactions, - metadata: None, - }) - } - - async fn block_transaction( - &self, - _block: &BlockIdentifier, - _tx: &TransactionIdentifier, - ) -> Result { - anyhow::bail!("not implemented") - } - - async fn call(&self, req: &Void) -> Result<()> { - unreachable(*req) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub async fn client_from_config(config: BlockchainConfig) -> Result { - let network = config.network.to_string(); - let url = config.node_uri.to_string(); - BitcoinClient::new(network.as_str(), url.as_str()).await - } - - #[tokio::test] - async fn test_network_status() -> Result<()> { - let config = rosetta_config_bitcoin::config("regtest")?; - rosetta_docker::tests::network_status::(client_from_config, config) - .await - } - - #[tokio::test] - #[ignore] - async fn test_account() -> Result<()> { - let config = rosetta_config_bitcoin::config("regtest")?; - rosetta_docker::tests::account::(client_from_config, config).await - } - - #[tokio::test] - #[ignore] - async fn test_construction() -> Result<()> { - let config = rosetta_config_bitcoin::config("regtest")?; - rosetta_docker::tests::construction::(client_from_config, config).await - } -} diff --git a/chains/ethereum/config/Cargo.toml b/chains/ethereum/config/Cargo.toml index 37c4803e..b73b39ec 100644 --- a/chains/ethereum/config/Cargo.toml +++ b/chains/ethereum/config/Cargo.toml @@ -8,7 +8,10 @@ description = "Ethereum configuration." [dependencies] anyhow = "1.0" +derivative = { version = "2.2", default-features = false, features = ["use_core"] } +ethbloom = { version = "0.13", default-features = false } ethereum-types = { version = "0.14", default-features = false, features = ["rlp", "ethbloom", "num-traits"] } +hex-literal = { version = "0.4" } rosetta-config-astar = { workspace = true } rosetta-core.workspace = true diff --git a/chains/ethereum/config/src/lib.rs b/chains/ethereum/config/src/lib.rs index 7b673b61..cd6b2383 100644 --- a/chains/ethereum/config/src/lib.rs +++ b/chains/ethereum/config/src/lib.rs @@ -1,23 +1,144 @@ +#![cfg_attr(not(feature = "std"), no_std)] + #[cfg(feature = "serde")] mod serde_utils; mod types; -use anyhow::Result; pub use ethereum_types; +use ethereum_types::H256; use rosetta_config_astar::config as astar_config; use rosetta_core::{ crypto::{address::AddressFormat, Algorithm}, BlockchainConfig, NodeUri, }; -use std::sync::Arc; pub use types::*; +#[cfg(not(feature = "std"))] +#[cfg_attr(test, macro_use)] +extern crate alloc; + +#[cfg(feature = "std")] +pub(crate) mod rstd { + #[cfg(feature = "serde")] + pub use std::option; + + // borrow, boxed, cmp, default, hash, iter, marker, mem, ops, rc, result, + // time, + pub use std::{convert, fmt, result, str, sync, vec}; + // pub mod error { + // pub use std::error::Error; + // } +} + +#[cfg(not(feature = "std"))] +pub(crate) mod rstd { + #[cfg(feature = "serde")] + pub use core::option; + + pub use alloc::{sync, vec}; + pub use core::{convert, fmt, result, str}; + // pub use alloc::{borrow, boxed, rc, vec}; + // pub use core::{cmp, convert, default, fmt, hash, iter, marker, mem, ops, result, time}; + // pub mod error { + // pub trait Error {} + // impl Error for T {} + // } +} + +impl rosetta_core::traits::Transaction for types::SignedTransaction { + type Call = (); + + type SignaturePayload = (); +} + +#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct BlockHash(pub ethereum_types::H256); + +impl From for BlockHash { + fn from(hash: H256) -> Self { + Self(hash) + } +} + +impl From for H256 { + fn from(block_hash: BlockHash) -> Self { + block_hash.0 + } +} + +impl rstd::convert::AsMut<[u8]> for BlockHash { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_bytes_mut() + } +} + +impl rstd::convert::AsRef<[u8]> for BlockHash { + fn as_ref(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl rstd::str::FromStr for BlockHash { + type Err = ::Err; + + fn from_str(s: &str) -> rstd::result::Result { + let hash = ::from_str(s)?; + Ok(Self(hash)) + } +} + +impl rstd::fmt::Display for BlockHash { + fn fmt(&self, f: &mut rstd::fmt::Formatter<'_>) -> rstd::fmt::Result { + rstd::fmt::Display::fmt(&self.0, f) + } +} + +impl rosetta_core::traits::HashOutput for BlockHash {} + +impl rosetta_core::traits::Header for types::header::Header { + type Hash = BlockHash; + + fn number(&self) -> rosetta_core::traits::BlockNumber { + self.number + } + + fn hash(&self) -> Self::Hash { + // TODO: compute header hash + BlockHash(H256::zero()) + } +} + +impl rosetta_core::traits::Block for types::BlockFull { + type Transaction = types::SignedTransaction; + type Header = types::header::Header; + type Hash = BlockHash; + + fn header(&self) -> &Self::Header { + &self.header + } + + fn transactions(&self) -> &[Self::Transaction] { + self.transactions.as_slice() + } + + fn hash(&self) -> Self::Hash { + BlockHash(self.hash) + } +} + /// Retrieve the [`BlockchainConfig`] from the provided polygon `network` /// /// # Errors /// Returns `Err` if the network is not supported -pub fn polygon_config(network: &str) -> Result { +pub fn polygon_config(network: &str) -> anyhow::Result { let (network, bip44_id, is_dev) = match network { "dev" => ("dev", 1, true), "mumbai" => ("mumbai", 1, true), @@ -31,7 +152,7 @@ pub fn polygon_config(network: &str) -> Result { /// /// # Errors /// Returns `Err` if the network is not supported -pub fn arbitrum_config(network: &str) -> Result { +pub fn arbitrum_config(network: &str) -> anyhow::Result { // All available networks are listed here: let (network, bip44_id, is_dev) = match network { "dev" => ("dev", 1, true), @@ -39,7 +160,6 @@ pub fn arbitrum_config(network: &str) -> Result { "mainnet" => ("mainnet", 42161, false), _ => anyhow::bail!("unsupported network: {}", network), }; - Ok(evm_config("arbitrum", network, "ARB", bip44_id, is_dev)) } @@ -47,7 +167,7 @@ pub fn arbitrum_config(network: &str) -> Result { /// /// # Errors /// Returns `Err` if the network is not supported -pub fn config(network: &str) -> Result { +pub fn config(network: &str) -> anyhow::Result { let (network, symbol, bip44_id, is_dev) = match network { "dev" => ("dev", "ETH", 1, true), "mainnet" => ("mainnet", "ETH", 60, false), @@ -96,7 +216,7 @@ fn evm_config( NodeUri::parse("ws://127.0.0.1:8545/ws").expect("uri is valid; qed") }, node_image: "ethereum/client-go:v1.12.2", - node_command: Arc::new(|network, port| { + node_command: rstd::sync::Arc::new(|network, port| { let mut params = if network == "dev" { vec!["--dev".into(), "--dev.period=1".into(), "--ipcdisable".into()] } else { diff --git a/chains/ethereum/config/src/serde_utils.rs b/chains/ethereum/config/src/serde_utils.rs index e3a2742a..204158e6 100644 --- a/chains/ethereum/config/src/serde_utils.rs +++ b/chains/ethereum/config/src/serde_utils.rs @@ -1,3 +1,4 @@ +use crate::rstd::{option::Option, result::Result, vec::Vec}; use impl_serde::serialize::{deserialize_check_len, serialize_uint, ExpectedLen}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -9,7 +10,7 @@ pub mod uint_to_hex { #[allow(clippy::trivially_copy_pass_by_ref)] pub fn serialize(value: &T, serializer: S) -> Result where - T: SerializableNumber, + T: SerializableNumber + core::fmt::Debug, S: Serializer, { T::serialize_eth_uint(value, serializer) @@ -46,6 +47,20 @@ pub mod bytes_to_hex { } } +/// Deserialize that always returns `Some(T)` or `Some(T::default())` must be used with +/// `#[serde(deserialize_with = "deserialize_null_default")]` attribute +/// +/// # Errors +/// returns an error if fails to deserialize T +pub fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result +where + T: Default + Deserialize<'de>, + D: Deserializer<'de>, +{ + let opt = as Deserialize<'de>>::deserialize(deserializer)?; + Ok(opt.unwrap_or_default()) +} + /// Serialize a primitive uint as hexadecimal string, must be used with `#[serde(serialize_with = /// "serialize_uint")]` attribute pub trait SerializableNumber { @@ -94,7 +109,7 @@ pub trait DeserializableNumber<'de>: Sized { impl<'de, T> DeserializableNumber<'de> for Option where - T: DeserializableNumber<'de>, + T: DeserializableNumber<'de> + core::fmt::Debug, { /// Deserialize a primitive uint from hexadecimal string /// # Errors @@ -112,6 +127,24 @@ where /// Helper for deserializing optional uints from hexadecimal string struct DeserializeWrapper(T); +impl core::fmt::Debug for DeserializeWrapper +where + T: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("DeserializeWrapper").field(&self.0).finish() + } +} + +impl core::fmt::Display for DeserializeWrapper +where + T: core::fmt::Display, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + ::fmt(&self.0, f) + } +} + impl DeserializeWrapper { fn into_inner(self) -> T { self.0 diff --git a/chains/ethereum/config/src/types.rs b/chains/ethereum/config/src/types.rs index 40f16718..e3da9b68 100644 --- a/chains/ethereum/config/src/types.rs +++ b/chains/ethereum/config/src/types.rs @@ -1,9 +1,17 @@ +pub mod block; +pub mod constants; +pub mod header; +pub mod transaction; pub use ethereum_types; use ethereum_types::{Address, Bloom, H256, U256}; #[cfg(feature = "serde")] use crate::serde_utils::{bytes_to_hex, uint_to_hex}; +pub type SignedTransaction = transaction::SignedTransaction; +pub type BlockFull = block::Block; +pub type BlockRef = block::Block; + #[derive(Clone, Debug)] #[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] #[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] @@ -191,12 +199,20 @@ pub enum Query { /// from is not tampered with. #[cfg_attr(feature = "serde", serde(rename = "eth_getProof"))] GetProof(GetProof), + /// Returns information about a block whose hash is in the request, or null when no block was + /// found. + #[cfg_attr(feature = "serde", serde(rename = "eth_getblockbyhash"))] + GetBlockByHash(H256), /// Returns the currently configured chain ID, a value used in replay-protected transaction /// signing as introduced by EIP-155 #[cfg_attr(feature = "serde", serde(rename = "eth_chainId"))] ChainId, } +impl rosetta_core::traits::Query for Query { + type Result = QueryResult; +} + /// The result of contract call execution #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] @@ -246,6 +262,10 @@ pub enum QueryResult { /// from is not tampered with. #[cfg_attr(feature = "serde", serde(rename = "eth_getProof"))] GetProof(EIP1186ProofResponse), + /// Returns information about a block whose hash is in the request, or null when no block was + /// found. + #[cfg_attr(feature = "serde", serde(rename = "eth_getblockbyhash"))] + GetBlockByHash(Option), /// Returns the account and storage values of the specified account including the /// Merkle-proof. This call can be used to verify that the data you are pulling /// from is not tampered with. @@ -417,9 +437,9 @@ pub struct EIP1186ProofResponse { #[cfg(all(test, feature = "serde"))] mod tests { + use crate::rstd::str::FromStr; use hex_literal::hex; use serde_json::json; - use std::str::FromStr; use super::{AtBlock, CallResult, EIP1186ProofResponse, StorageProof}; use ethereum_types::{Address, H256, U256}; diff --git a/chains/ethereum/config/src/types/block.rs b/chains/ethereum/config/src/types/block.rs new file mode 100644 index 00000000..bf5b745b --- /dev/null +++ b/chains/ethereum/config/src/types/block.rs @@ -0,0 +1,60 @@ +use crate::{header::Header, rstd::vec::Vec}; +use ethereum_types::{H256, U256}; + +#[cfg(feature = "serde")] +use crate::serde_utils::{bytes_to_hex, uint_to_hex}; + +/// The block type returned from RPC calls. +/// +/// This is generic over a `TX` type which will be either the hash or the full transaction, +/// i.e. `Block` or `Block`. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct Block { + /// Hash of the block + pub hash: H256, + + /// Block header. + #[cfg_attr(feature = "serde", serde(flatten))] + pub header: Header, + + /// Total difficulty + #[cfg_attr(feature = "serde", serde(default))] + pub total_difficulty: Option, + + /// Seal fields + #[cfg_attr( + feature = "serde", + serde( + default, + rename = "sealFields", + with = "bytes_to_hex", + skip_serializing_if = "Vec::is_empty", + ) + )] + pub seal_fields: Vec, + + /// Transactions + #[cfg_attr( + feature = "serde", + serde(bound = "TX: serde::Serialize + serde::de::DeserializeOwned") + )] + pub transactions: Vec, + + /// Uncles' hashes + #[cfg_attr( + feature = "serde", + serde(bound = "OMMERS: serde::Serialize + serde::de::DeserializeOwned") + )] + pub uncles: Vec, + + /// Size in bytes + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub size: Option, +} diff --git a/chains/ethereum/config/src/types/constants.rs b/chains/ethereum/config/src/types/constants.rs new file mode 100644 index 00000000..d0743183 --- /dev/null +++ b/chains/ethereum/config/src/types/constants.rs @@ -0,0 +1,14 @@ +use ethereum_types::H256; +use hex_literal::hex; + +/// Keccak256 over empty array. +pub const KECCAK_EMPTY: H256 = + H256(hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")); + +/// Ommer root of empty list. +pub const EMPTY_OMMER_ROOT_HASH: H256 = + H256(hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")); + +/// Root hash of an empty trie. +pub const EMPTY_ROOT_HASH: H256 = + H256(hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")); diff --git a/chains/ethereum/config/src/types/header.rs b/chains/ethereum/config/src/types/header.rs new file mode 100644 index 00000000..fc5c42bc --- /dev/null +++ b/chains/ethereum/config/src/types/header.rs @@ -0,0 +1,158 @@ +use crate::{ + constants::{EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH}, + rstd::vec::Vec, +}; +use ethbloom::Bloom; +use ethereum_types::{H160, H256, U256}; + +#[cfg(feature = "serde")] +use crate::serde_utils::{bytes_to_hex, uint_to_hex}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct Header { + /// The Keccak 256-bit hash of the parent + /// block’s header, in its entirety; formally Hp. + pub parent_hash: H256, + + /// The Keccak 256-bit hash of the ommers list portion of this block; formally Ho. + #[cfg_attr(feature = "serde", serde(rename = "sha3Uncles"))] + pub ommers_hash: H256, + + /// The 160-bit address to which all fees collected from the successful mining of this block + /// be transferred; formally Hc. + #[cfg_attr(feature = "serde", serde(rename = "miner", alias = "beneficiary"))] + pub beneficiary: H160, + + /// The Keccak 256-bit hash of the root node of the state trie, after all transactions are + /// executed and finalisations applied; formally Hr. + pub state_root: H256, + + /// The Keccak 256-bit hash of the root node of the trie structure populated with each + /// transaction in the transactions list portion of the block; formally Ht. + pub transactions_root: H256, + + /// The Keccak 256-bit hash of the root node of the trie structure populated with the receipts + /// of each transaction in the transactions list portion of the block; formally He. + pub receipts_root: H256, + + /// The Bloom filter composed from indexable information (logger address and log topics) + /// contained in each log entry from the receipt of each transaction in the transactions list; + /// formally Hb. + pub logs_bloom: Bloom, + + /// A scalar value corresponding to the difficulty level of this block. This can be calculated + /// from the previous block’s difficulty level and the timestamp; formally Hd. + pub difficulty: U256, + + /// A scalar value equal to the number of ancestor blocks. The genesis block has a number of + /// zero; formally Hi. + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub number: u64, + + /// A scalar value equal to the current limit of gas expenditure per block; formally Hl. + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub gas_limit: u64, + + /// A scalar value equal to the total gas used in transactions in this block; formally Hg. + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub gas_used: u64, + + /// A scalar value equal to the reasonable output of Unix’s time() at this block’s inception; + /// formally Hs. + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub timestamp: u64, + + /// An arbitrary byte array containing data relevant to this block. This must be 32 bytes or + /// fewer; formally Hx. + #[cfg_attr(feature = "serde", serde(default, with = "bytes_to_hex"))] + pub extra_data: Vec, + + /// A 256-bit hash which, combined with the + /// nonce, proves that a sufficient amount of computation has been carried out on this block; + /// formally Hm. + #[cfg_attr(feature = "serde", serde(default))] + pub mix_hash: H256, + + /// A 64-bit value which, combined with the mixhash, proves that a sufficient amount of + /// computation has been carried out on this block; formally Hn. + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub nonce: u64, + + /// A scalar representing EIP1559 base fee which can move up or down each block according + /// to a formula which is a function of gas used in parent block and gas target + /// (block gas limit divided by elasticity multiplier) of parent block. + /// The algorithm results in the base fee per gas increasing when blocks are + /// above the gas target, and decreasing when blocks are below the gas target. The base fee per + /// gas is burned. + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none", with = "uint_to_hex") + )] + pub base_fee_per_gas: Option, + + /// The Keccak 256-bit hash of the withdrawals list portion of this block. + /// + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] + pub withdrawals_root: Option, + + /// The total amount of blob gas consumed by the transactions within the block, added in + /// EIP-4844. + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none", with = "uint_to_hex") + )] + pub blob_gas_used: Option, + + /// A running total of blob gas consumed in excess of the target, prior to the block. Blocks + /// with above-target blob gas consumption increase this value, blocks with below-target blob + /// gas consumption decrease it (bounded at 0). This was added in EIP-4844. + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none", with = "uint_to_hex") + )] + pub excess_blob_gas: Option, + + /// The hash of the parent beacon block's root is included in execution blocks, as proposed by + /// EIP-4788. + /// + /// This enables trust-minimized access to consensus state, supporting staking pools, bridges, + /// and more. + /// + /// The beacon roots contract handles root storage, enhancing Ethereum's functionalities. + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] + pub parent_beacon_block_root: Option, +} + +impl Default for Header { + fn default() -> Self { + Self { + parent_hash: H256::zero(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: H160::zero(), + state_root: EMPTY_ROOT_HASH, + transactions_root: EMPTY_ROOT_HASH, + receipts_root: EMPTY_ROOT_HASH, + logs_bloom: Bloom::zero(), + difficulty: U256::zero(), + number: 0, + gas_limit: 0, + gas_used: 0, + timestamp: 0, + extra_data: Vec::new(), + mix_hash: H256::zero(), + nonce: 0, + base_fee_per_gas: None, + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + } + } +} diff --git a/chains/ethereum/config/src/types/transaction.rs b/chains/ethereum/config/src/types/transaction.rs new file mode 100644 index 00000000..328121f9 --- /dev/null +++ b/chains/ethereum/config/src/types/transaction.rs @@ -0,0 +1,17 @@ +mod access_list; +mod eip1559; +mod eip2930; +mod legacy; +mod rpc_transaction; +mod signature; +mod signed_transaction; +mod typed_transaction; + +pub use access_list::{AccessList, AccessListItem, AccessListWithGasUsed}; +pub use eip1559::Eip1559Transaction; +pub use eip2930::Eip2930Transaction; +pub use legacy::LegacyTransaction; +pub use rpc_transaction::RpcTransaction; +pub use signature::Signature; +pub use signed_transaction::SignedTransaction; +pub use typed_transaction::TypedTransaction; diff --git a/chains/ethereum/config/src/types/transaction/access_list.rs b/chains/ethereum/config/src/types/transaction/access_list.rs new file mode 100644 index 00000000..fe9fa089 --- /dev/null +++ b/chains/ethereum/config/src/types/transaction/access_list.rs @@ -0,0 +1,130 @@ +use crate::rstd::{vec, vec::Vec}; +use ethereum_types::{H160, H256, U256}; + +#[derive(Clone, Default, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct AccessList(pub Vec); + +impl AccessList { + #[must_use] + pub const fn new() -> Self { + Self(Vec::new()) + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + #[must_use] + pub fn into_raw(self) -> Vec<(H160, Vec)> { + self.0 + .into_iter() + .map(|item| (item.address, item.storage_keys)) + .collect::>() + } +} + +impl From)>> for AccessList { + fn from(src: Vec<(H160, Vec)>) -> Self { + Self( + src.into_iter() + .map(|(address, storage_keys)| AccessListItem { address, storage_keys }) + .collect(), + ) + } +} + +impl IntoIterator for AccessList { + type Item = AccessListItem; + type IntoIter = vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +#[derive(Clone, Default, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct AccessListWithGasUsed { + pub access_list: AccessList, + pub gas_used: U256, +} + +impl From> for AccessList { + fn from(src: Vec) -> Self { + Self(src) + } +} + +/// Access list item +#[derive(Clone, Default, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct AccessListItem { + /// Accessed address + pub address: H160, + /// Accessed storage keys + pub storage_keys: Vec, +} + +#[cfg(all(test, feature = "serde"))] +mod tests { + use super::{AccessList, AccessListItem, H160, H256}; + + #[test] + fn serde_encode_works() { + let access_list = AccessList(vec![AccessListItem { + address: H160::from(hex_literal::hex!("8e5660b4ab70168b5a6feea0e0315cb49c8cd539")), + storage_keys: vec![ + H256::zero(), + H256::from(hex_literal::hex!( + "a19fd53308a1c44a3ed22d3f20ed4229aa8909e0d0a90510ca482367ad42caa6" + )), + H256::from(hex_literal::hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + ], + }]); + + // can encode as json + let actual = serde_json::to_value(access_list.clone()).unwrap(); + let expected = serde_json::json!([ + { + "address": "0x8e5660b4ab70168b5a6feea0e0315cb49c8cd539", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xa19fd53308a1c44a3ed22d3f20ed4229aa8909e0d0a90510ca482367ad42caa6", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ], + }, + ]); + assert_eq!(expected, actual); + + // can decode json + let json_str = serde_json::to_string(&access_list).unwrap(); + let decoded = serde_json::from_str::(&json_str).unwrap(); + assert_eq!(access_list, decoded); + } +} diff --git a/chains/ethereum/config/src/types/transaction/eip1559.rs b/chains/ethereum/config/src/types/transaction/eip1559.rs new file mode 100644 index 00000000..19f5a77f --- /dev/null +++ b/chains/ethereum/config/src/types/transaction/eip1559.rs @@ -0,0 +1,157 @@ +use super::access_list::AccessList; +use crate::rstd::vec::Vec; +use ethereum_types::{H160, U256}; + +#[cfg(feature = "serde")] +use crate::serde_utils::{bytes_to_hex, uint_to_hex}; + +/// Transactions with type 0x2 are transactions introduced in EIP-1559, included in Ethereum's +/// London fork. EIP-1559 addresses the network congestion and overpricing of transaction fees +/// caused by the historical fee market, in which users send transactions specifying a gas price bid +/// using the gasPrice parameter, and miners choose transactions with the highest bids. +/// +/// EIP-1559 transactions don’t specify gasPrice, and instead use an in-protocol, dynamically +/// changing base fee per gas. At each block, the base fee per gas is adjusted to address network +/// congestion as measured by a gas target. +/// +/// An EIP-1559 transaction always pays the base fee of the block it’s included in, and it pays a +/// priority fee as priced by `max_priority_fee_per_gas` or, if the base fee per gas + +/// `max_priority_fee_per_gas` exceeds `max_fee_per_gas`, it pays a priority fee as priced by +/// `max_fee_per_gas` minus the base fee per gas. The base fee is burned, and the priority fee is +/// paid to the miner that included the transaction. A transaction’s priority fee per gas +/// incentivizes miners to include the transaction over other transactions with lower priority fees +/// per gas. +#[derive(Clone, Default, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct Eip1559Transaction { + /// The chain ID of the transaction. It is mandatory for EIP-1559 transactions. + /// + /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + /// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 + /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub chain_id: u64, + + /// The nonce of the transaction. + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub nonce: u64, + + /// Represents the maximum tx fee that will go to the miner as part of the user's + /// fee payment. It serves 3 purposes: + /// 1. Compensates miners for the uncle/ommer risk + fixed costs of including transaction in a + /// block; + /// 2. Allows users with high opportunity costs to pay a premium to miners; + /// 3. In times where demand exceeds the available block space (i.e. 100% full, 30mm gas), + /// this component allows first price auctions (i.e. the pre-1559 fee model) to happen on the + /// priority fee. + /// + /// Incorporated as part of the London upgrade via [EIP-1559]. + /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + pub max_priority_fee_per_gas: U256, + + /// Represents the maximum amount that a user is willing to pay for their tx (inclusive of + /// baseFeePerGas and maxPriorityFeePerGas). The difference between maxFeePerGas and + /// baseFeePerGas + maxPriorityFeePerGas is “refunded” to the user. + /// + /// Incorporated as part of the London upgrade via [EIP-1559]. + /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + pub max_fee_per_gas: U256, + + /// Supplied gas + #[cfg_attr(feature = "serde", serde(rename = "gas", with = "uint_to_hex",))] + pub gas_limit: u64, + + /// Recipient address (None for contract creation) + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub to: Option, + + /// Transferred value + pub value: U256, + + /// The data of the transaction. + #[cfg_attr( + feature = "serde", + serde(with = "bytes_to_hex", skip_serializing_if = "Vec::is_empty") + )] + pub data: Vec, + + /// Optional access list introduced in EIP-2930. + /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "AccessList::is_empty"))] + pub access_list: AccessList, +} + +#[cfg(all(test, feature = "serde"))] +pub mod tests { + use super::Eip1559Transaction; + use crate::transaction::{AccessList, AccessListItem}; + use ethereum_types::{H160, H256}; + use hex_literal::hex; + + pub fn build_eip1559() -> (Eip1559Transaction, serde_json::Value) { + let tx = Eip1559Transaction { + chain_id: 1, + nonce: 117, + max_priority_fee_per_gas: 100_000_000.into(), + max_fee_per_gas: 28_379_509_371u128.into(), + gas_limit: 187_293, + to: Some(hex!("3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad").into()), + value: 3_650_000_000_000_000_000u128.into(), + data: hex!("3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006547d41700000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000032a767a9562d00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000032a767a9562d000000000000000000000000000000000000000000000021b60af11987fa0670342f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8b55ee890426341fe45ee6dc788d2d93d25b59063000000000000000000000000000000000000000000").to_vec(), + access_list: AccessList(vec![AccessListItem { + address: H160::from(hex!("3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad")), + storage_keys: vec![ + H256::zero(), + H256::from(hex!( + "a19fd53308a1c44a3ed22d3f20ed4229aa8909e0d0a90510ca482367ad42caa6" + )), + H256::from(hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + ], + }]), + }; + let json = serde_json::json!({ + "chainId": "0x1", + "nonce": "0x75", + "maxPriorityFeePerGas": "0x5f5e100", + "maxFeePerGas": "0x69b8cf27b", + "gas": "0x2db9d", + "to": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "value": "0x32a767a9562d0000", + "data": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006547d41700000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000032a767a9562d00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000032a767a9562d000000000000000000000000000000000000000000000021b60af11987fa0670342f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8b55ee890426341fe45ee6dc788d2d93d25b59063000000000000000000000000000000000000000000", + "accessList": [ + { + "address": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xa19fd53308a1c44a3ed22d3f20ed4229aa8909e0d0a90510ca482367ad42caa6", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + ] + } + ], + // "v": "0x1", + // "r": "0xbde8e920a9acce0c9950f112d02d457d517835297b2610b4d0bcd56df114010f", + // "s": "0x66ee7972cde2c5bd85fdb06aa358da04944b3ad5e56fe3e06d8fcb1137a52939" + }); + (tx, json) + } + + #[test] + fn serde_encode_works() { + let (tx, expected) = build_eip1559(); + let actual = serde_json::to_value(&tx).unwrap(); + assert_eq!(expected, actual); + + // can decode json + let json_str = serde_json::to_string(&tx).unwrap(); + let decoded = serde_json::from_str::(&json_str).unwrap(); + assert_eq!(tx, decoded); + } +} diff --git a/chains/ethereum/config/src/types/transaction/eip2930.rs b/chains/ethereum/config/src/types/transaction/eip2930.rs new file mode 100644 index 00000000..5bbb9511 --- /dev/null +++ b/chains/ethereum/config/src/types/transaction/eip2930.rs @@ -0,0 +1,121 @@ +use super::access_list::AccessList; +use crate::rstd::vec::Vec; +use ethereum_types::{H160, U256}; + +#[cfg(feature = "serde")] +use crate::serde_utils::{bytes_to_hex, uint_to_hex}; + +/// Transactions with type 0x1 are transactions introduced in EIP-2930. They contain, along with the +/// legacy parameters, an access list which specifies an array of addresses and storage keys that +/// the transaction plans to access (an access list) +#[derive(Clone, Default, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct Eip2930Transaction { + /// The chain ID of the transaction. It is mandatory for EIP-2930 transactions. + /// + /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + /// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 + /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub chain_id: u64, + + /// The nonce of the transaction. + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub nonce: u64, + + /// Gas price + pub gas_price: U256, + + /// Supplied gas + #[cfg_attr(feature = "serde", serde(rename = "gas", with = "uint_to_hex"))] + pub gas_limit: u64, + + /// Recipient address (None for contract creation) + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub to: Option, + + /// Transferred value + pub value: U256, + + /// The data of the transaction. + #[cfg_attr( + feature = "serde", + serde(with = "bytes_to_hex", skip_serializing_if = "Vec::is_empty") + )] + pub data: Vec, + + /// Optional access list introduced in EIP-2930. + /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "AccessList::is_empty"))] + pub access_list: AccessList, +} + +#[cfg(all(test, feature = "serde"))] +pub mod tests { + use super::Eip2930Transaction; + use crate::transaction::{AccessList, AccessListItem}; + use ethereum_types::{H160, H256}; + use hex_literal::hex; + + pub fn build_eip2930() -> (Eip2930Transaction, serde_json::Value) { + let tx = Eip2930Transaction { + chain_id: 1, + nonce: 117, + gas_price: 28_379_509_371u128.into(), + gas_limit: 187_293, + to: Some(hex!("3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad").into()), + value: 3_650_000_000_000_000_000u128.into(), + data: hex!("3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006547d41700000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000032a767a9562d00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000032a767a9562d000000000000000000000000000000000000000000000021b60af11987fa0670342f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8b55ee890426341fe45ee6dc788d2d93d25b59063000000000000000000000000000000000000000000").to_vec(), + access_list: AccessList(vec![AccessListItem { + address: H160::from(hex!("3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad")), + storage_keys: vec![ + H256::zero(), + H256::from(hex!( + "a19fd53308a1c44a3ed22d3f20ed4229aa8909e0d0a90510ca482367ad42caa6" + )), + H256::from(hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + ], + }]), + }; + let json = serde_json::json!({ + "chainId": "0x1", + "nonce": "0x75", + "gasPrice": "0x69b8cf27b", + "gas": "0x2db9d", + "to": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "value": "0x32a767a9562d0000", + "data": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006547d41700000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000032a767a9562d00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000032a767a9562d000000000000000000000000000000000000000000000021b60af11987fa0670342f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8b55ee890426341fe45ee6dc788d2d93d25b59063000000000000000000000000000000000000000000", + "accessList": [ + { + "address": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xa19fd53308a1c44a3ed22d3f20ed4229aa8909e0d0a90510ca482367ad42caa6", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + ] + } + ], + }); + (tx, json) + } + + #[test] + fn serde_encode_works() { + let (tx, expected) = build_eip2930(); + let actual = serde_json::to_value(&tx).unwrap(); + assert_eq!(expected, actual); + + // can decode json + let json_str = serde_json::to_string(&tx).unwrap(); + let decoded = serde_json::from_str::(&json_str).unwrap(); + assert_eq!(tx, decoded); + } +} diff --git a/chains/ethereum/config/src/types/transaction/legacy.rs b/chains/ethereum/config/src/types/transaction/legacy.rs new file mode 100644 index 00000000..8e931aff --- /dev/null +++ b/chains/ethereum/config/src/types/transaction/legacy.rs @@ -0,0 +1,122 @@ +use crate::rstd::vec::Vec; +use ethereum_types::{H160, U256}; + +#[cfg(feature = "serde")] +use crate::serde_utils::{bytes_to_hex, uint_to_hex}; + +/// Legacy transaction that use the transaction format existing before typed transactions were +/// introduced in EIP-2718. Legacy transactions don’t use access lists or incorporate EIP-1559 fee +/// market changes. +#[derive(Clone, Default, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct LegacyTransaction { + /// The nonce of the transaction. If set to `None`, no checks are performed. + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub nonce: u64, + + /// Gas price + pub gas_price: U256, + + /// Supplied gas + #[cfg_attr(feature = "serde", serde(rename = "gas", with = "uint_to_hex"))] + pub gas_limit: u64, + + /// Recipient address (None for contract creation) + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub to: Option, + + /// Transferred value + pub value: U256, + + /// The data of the transaction. + #[cfg_attr( + feature = "serde", + serde(with = "bytes_to_hex", skip_serializing_if = "Vec::is_empty") + )] + pub data: Vec, + + /// The chain ID of the transaction. If set to `None`, no checks are performed. + /// + /// Incorporated as part of the Spurious Dragon upgrade via [EIP-155]. + /// + /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none", with = "uint_to_hex") + )] + pub chain_id: Option, +} + +#[cfg(all(test, feature = "serde"))] +pub mod tests { + use super::LegacyTransaction; + use hex_literal::hex; + + pub fn build_legacy(eip155: bool) -> (LegacyTransaction, serde_json::Value) { + if eip155 { + let tx = LegacyTransaction { + chain_id: Some(1), + nonce: 137, + gas_price: 20_400_000_000u64.into(), + gas_limit: 1_000_000, + to: Some(hex!("dc6c91b569c98f9f6f74d90f9beff99fdaf4248b").into()), + value: 278_427_500_000_000_000u64.into(), + data: hex!("288b8133920339b815ee42a02099dcca27c01d192418334751613a1eea786a0c3a673cec000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000032464a3bc15000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000003dd2c560933380000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000b14232b0204b2f7bb6ba5aff59ef36030f7fe38b00000000000000000000000041f8d14c9475444f30a80431c68cf24dc9a8369a000000000000000000000000b9e29984fe50602e7a619662ebed4f90d93824c7000000000000000000000000dc6c91b569c98f9f6f74d90f9beff99fdaf4248b0000000000000000000000000000000000000000000000000000000002faf08000000000000000000000000000000000000000000000000003dd2c560933380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005d7625241afaa81faf3c2bd525f64f6e0ec3af39c1053d672b65c2f64992521e6f454e67000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000581b96d32320d6cb174e807b585a41f4faa8ba7da95e117f2abbcadbb257d37a5fcc16c2ba6db86200888ed85dd5eba547bb07fa0f9910950d3133026abafdd5c09e1f3896a7abb3c99e1f38f77be69448ee7770d18c001e0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000561c7dd7d43db98e3b7a6e18b4e97f0e254a5a6bb9b373d49d7e6676ccb1b02d50f131bca36928cb48cd0daec0499e9e93a390253c733607437bbebf0aa13b7080911f3896a7abb3c99e1f38f77be69448ee7770d18c0400000000000000000000").to_vec(), + }; + let json = serde_json::json!({ + "nonce": "0x89", + "gas": "0xf4240", + "gasPrice": "0x4bfef4c00", + "to": "0xdc6c91b569c98f9f6f74d90f9beff99fdaf4248b", + "value": "0x3dd2c5609333800", + "data": "0x288b8133920339b815ee42a02099dcca27c01d192418334751613a1eea786a0c3a673cec000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000032464a3bc15000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000003dd2c560933380000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000b14232b0204b2f7bb6ba5aff59ef36030f7fe38b00000000000000000000000041f8d14c9475444f30a80431c68cf24dc9a8369a000000000000000000000000b9e29984fe50602e7a619662ebed4f90d93824c7000000000000000000000000dc6c91b569c98f9f6f74d90f9beff99fdaf4248b0000000000000000000000000000000000000000000000000000000002faf08000000000000000000000000000000000000000000000000003dd2c560933380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005d7625241afaa81faf3c2bd525f64f6e0ec3af39c1053d672b65c2f64992521e6f454e67000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000581b96d32320d6cb174e807b585a41f4faa8ba7da95e117f2abbcadbb257d37a5fcc16c2ba6db86200888ed85dd5eba547bb07fa0f9910950d3133026abafdd5c09e1f3896a7abb3c99e1f38f77be69448ee7770d18c001e0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000561c7dd7d43db98e3b7a6e18b4e97f0e254a5a6bb9b373d49d7e6676ccb1b02d50f131bca36928cb48cd0daec0499e9e93a390253c733607437bbebf0aa13b7080911f3896a7abb3c99e1f38f77be69448ee7770d18c0400000000000000000000", + "chainId": "0x1", + }); + (tx, json) + } else { + let tx = LegacyTransaction { + chain_id: None, + nonce: 3166, + gas_price: 60_000_000_000u64.into(), + gas_limit: 300_000, + to: Some(hex!("6b92c944c82c694725acbd1c000c277ea1a44f00").into()), + value: 0.into(), + data: hex!("41c0e1b5").into(), + }; + let json = serde_json::json!({ + "gas": "0x493e0", + "gasPrice": "0xdf8475800", + "data": "0x41c0e1b5", + "nonce": "0xc5e", + "to": "0x6b92c944c82c694725acbd1c000c277ea1a44f00", + "value": "0x0", + }); + (tx, json) + } + } + + #[test] + fn serde_encode_works() { + // With EIP155 + let (tx, expected) = build_legacy(true); + let actual = serde_json::to_value(&tx).unwrap(); + assert_eq!(expected, actual); + let json_str = serde_json::to_string(&tx).unwrap(); + let decoded = serde_json::from_str::(&json_str).unwrap(); + assert_eq!(tx, decoded); + + // Without EIP155 + let (tx, expected) = build_legacy(false); + let actual = serde_json::to_value(&tx).unwrap(); + assert_eq!(expected, actual); + let json_str = serde_json::to_string(&tx).unwrap(); + let decoded = serde_json::from_str::(&json_str).unwrap(); + assert_eq!(tx, decoded); + } +} diff --git a/chains/ethereum/config/src/types/transaction/rpc_transaction.rs b/chains/ethereum/config/src/types/transaction/rpc_transaction.rs new file mode 100644 index 00000000..055d8739 --- /dev/null +++ b/chains/ethereum/config/src/types/transaction/rpc_transaction.rs @@ -0,0 +1,450 @@ +use crate::{ + rstd::vec::Vec, + transaction::{ + access_list::AccessList, eip1559::Eip1559Transaction, eip2930::Eip2930Transaction, + legacy::LegacyTransaction, signature::Signature, signed_transaction::SignedTransaction, + typed_transaction::TypedTransaction, + }, +}; +use ethereum_types::{H160, H256, H512, U256}; + +#[cfg(feature = "serde")] +use crate::serde_utils::{bytes_to_hex, deserialize_null_default, uint_to_hex}; + +/// Transaction +#[derive(Clone, Default, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct RpcTransaction { + /// Hash + pub hash: H256, + + /// Nonce + #[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] + pub nonce: u64, + + /// Block hash + #[cfg_attr(feature = "serde", serde(default))] + pub block_hash: Option, + + /// Block number + #[cfg_attr(feature = "serde", serde(default, with = "uint_to_hex"))] + pub block_number: Option, + + /// Transaction Index + #[cfg_attr(feature = "serde", serde(default, with = "uint_to_hex"))] + pub transaction_index: Option, + + /// Sender + pub from: H160, + + /// Recipient + #[cfg_attr(feature = "serde", serde(default))] + pub to: Option, + + /// Transfered value + pub value: U256, + + /// Gas Price + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] + pub gas_price: Option, + + /// Max BaseFeePerGas the user is willing to pay. + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] + pub max_fee_per_gas: Option, + + /// The miner's tip. + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] + pub max_priority_fee_per_gas: Option, + + /// Gas limit + #[cfg_attr(feature = "serde", serde(default, rename = "gas"))] + pub gas_limit: U256, + + /// Data + #[cfg_attr( + feature = "serde", + serde(default, with = "bytes_to_hex", skip_serializing_if = "Vec::is_empty") + )] + pub input: Vec, + + /// Creates contract + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] + pub creates: Option, + + /// Raw transaction data + #[cfg_attr( + feature = "serde", + serde(default, with = "bytes_to_hex", skip_serializing_if = "Vec::is_empty") + )] + pub raw: Vec, + + /// Public key of the signer. + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] + pub public_key: Option, + + /// The network id of the transaction, if any. + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none", with = "uint_to_hex",) + )] + pub chain_id: Option, + + /// The V field of the signature. + #[cfg_attr(feature = "serde", serde(default, flatten))] + pub signature: Signature, + + /// Pre-pay to warm storage access. + #[cfg_attr(feature = "serde", serde(default, deserialize_with = "deserialize_null_default"))] + pub access_list: AccessList, + + /// EIP-2718 type + #[cfg_attr( + feature = "serde", + serde( + default, + rename = "type", + skip_serializing_if = "Option::is_none", + with = "uint_to_hex" + ) + )] + pub transaction_type: Option, +} + +impl TryFrom for LegacyTransaction { + type Error = &'static str; + + fn try_from(tx: RpcTransaction) -> Result { + if let Some(transaction_type) = tx.transaction_type { + if transaction_type != 0 { + return Err("transaction type is not 0"); + } + } + + if !tx.access_list.is_empty() { + return Err("legacy tx doesn't support access list"); + } + if tx.max_fee_per_gas.is_some() { + return Err("legacy tx doesn't support max_fee_per_gas"); + } + if tx.max_priority_fee_per_gas.is_some() { + return Err("legacy tx doesn't support max_priority_fee_per_gas"); + } + let Some(gas_price) = tx.gas_price else { + return Err("legacy tx gas_price is mandatory"); + }; + + let chain_id = if tx.signature.r.is_zero() && tx.signature.s.is_zero() { + tx.chain_id.or_else(|| tx.signature.v.chain_id()) + } else { + tx.signature.v.chain_id() + }; + + Ok(Self { + nonce: tx.nonce, + gas_price, + gas_limit: u64::try_from(tx.gas_limit).unwrap_or(u64::MAX), + to: tx.to, + value: tx.value, + data: tx.input, + chain_id, + }) + } +} + +impl TryFrom for Eip2930Transaction { + type Error = &'static str; + + fn try_from(tx: RpcTransaction) -> Result { + if let Some(transaction_type) = tx.transaction_type { + if transaction_type != 1 { + return Err("transaction type is not 0"); + } + } + + if tx.max_fee_per_gas.is_some() { + return Err("EIP2930 Tx doesn't support max_fee_per_gas"); + } + if tx.max_priority_fee_per_gas.is_some() { + return Err("EIP2930 Tx doesn't support max_priority_fee_per_gas"); + } + let Some(chain_id) = tx.chain_id else { + return Err("chain_id is mandatory for EIP2930 transactions"); + }; + let Some(gas_price) = tx.gas_price else { + return Err("gas_price is mandatory for EIP2930 transactions"); + }; + + Ok(Self { + nonce: tx.nonce, + gas_price, + gas_limit: u64::try_from(tx.gas_limit).unwrap_or(u64::MAX), + to: tx.to, + value: tx.value, + data: tx.input, + chain_id, + access_list: tx.access_list, + }) + } +} + +impl TryFrom for Eip1559Transaction { + type Error = &'static str; + + fn try_from(tx: RpcTransaction) -> Result { + if let Some(transaction_type) = tx.transaction_type { + if transaction_type != 2 { + return Err("transaction type is not 0"); + } + } + + let Some(chain_id) = tx.chain_id else { + return Err("chain_id is mandatory for EIP1559 transactions"); + }; + let Some(max_fee_per_gas) = tx.max_fee_per_gas else { + return Err("max_fee_per_gas is mandatory for EIP1559 transactions"); + }; + let Some(max_priority_fee_per_gas) = tx.max_priority_fee_per_gas else { + return Err("max_priority_fee_per_gas is mandatory for EIP1559 transactions"); + }; + + Ok(Self { + nonce: tx.nonce, + max_fee_per_gas, + max_priority_fee_per_gas, + gas_limit: u64::try_from(tx.gas_limit).unwrap_or(u64::MAX), + to: tx.to, + value: tx.value, + data: tx.input, + chain_id, + access_list: tx.access_list, + }) + } +} + +impl TryFrom for TypedTransaction { + type Error = &'static str; + + fn try_from(tx: RpcTransaction) -> Result { + let typed_tx = match tx.transaction_type { + Some(0) => Self::Legacy(tx.try_into()?), + Some(1) => Self::Eip2930(tx.try_into()?), + Some(2) => Self::Eip1559(tx.try_into()?), + Some(_) => return Err("unknown transaction type"), + None => { + if tx.max_fee_per_gas.is_some() || tx.max_priority_fee_per_gas.is_some() { + Self::Eip1559(tx.try_into()?) + } else { + Self::Legacy(tx.try_into()?) + } + }, + }; + Ok(typed_tx) + } +} + +impl TryFrom for SignedTransaction { + type Error = &'static str; + + fn try_from(tx: RpcTransaction) -> Result { + let tx_hash = tx.hash; + let signature = tx.signature; + let payload = match tx.transaction_type { + Some(0) => TypedTransaction::Legacy(tx.try_into()?), + Some(1) => TypedTransaction::Eip2930(tx.try_into()?), + Some(2) => TypedTransaction::Eip1559(tx.try_into()?), + Some(_) => return Err("unknown transaction type"), + None => { + if tx.max_fee_per_gas.is_some() || tx.max_priority_fee_per_gas.is_some() { + TypedTransaction::Eip1559(tx.try_into()?) + } else if tx.access_list.is_empty() { + TypedTransaction::Legacy(tx.try_into()?) + } else { + TypedTransaction::Eip2930(tx.try_into()?) + } + }, + }; + Ok(Self { tx_hash, payload, signature }) + } +} + +#[cfg(all(test, feature = "serde"))] +mod tests { + use super::RpcTransaction; + use crate::transaction::{access_list::AccessList, signature::Signature}; + use ethereum_types::{H160, H256, U256}; + use hex_literal::hex; + + #[test] + fn decode_legacy_json_works() { + let json = r#" + { + "hash": "0x831a62a594cb62b250a606a63d3a762300815c8d3765c6192d46d6bca440faa6", + "nonce": "0x32a", + "blockHash": "0xdbdb6ab6ef116b498ceab7141a8ab1646960e2550bafbe3e8e22f1daffacc7cf", + "blockNumber": "0x15780", + "transactionIndex": "0x0", + "from": "0x32be343b94f860124dc4fee278fdcbd38c102d88", + "to": "0x78293691c74717191d1d417b531f398350d54e89", + "value": "0x5fc1b97136320000", + "gasPrice": "0xde197ae65", + "gas": "0x5208", + "input": "0x", + "v": "0x1c", + "r": "0xc8fc04e29b0859a7f265b67af7d4c5c6bc9e3d5a8de4950f89fa71a12a3cf8ae", + "s": "0x7dd15a10f9f2c8d1519a6044d880d04756798fc23923ff94f4823df8dc5b987a", + "type": "0x0" + }"#; + let expected = RpcTransaction { + hash: H256(hex!("831a62a594cb62b250a606a63d3a762300815c8d3765c6192d46d6bca440faa6")), + nonce: 810, + block_hash: Some(H256(hex!( + "dbdb6ab6ef116b498ceab7141a8ab1646960e2550bafbe3e8e22f1daffacc7cf" + ))), + block_number: Some(87936), + transaction_index: Some(0), + gas_price: Some(59_619_389_029u128.into()), + gas_limit: 21000.into(), + from: H160(hex!("32be343b94f860124dc4fee278fdcbd38c102d88")), + to: Some(H160(hex!("78293691c74717191d1d417b531f398350d54e89"))), + value: 6_900_000_000_000_000_000u128.into(), + input: Vec::new(), + chain_id: None, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + creates: None, + raw: Vec::new(), + public_key: None, + signature: Signature { + v: 0x1c.into(), + r: U256::from_big_endian(&hex!( + "c8fc04e29b0859a7f265b67af7d4c5c6bc9e3d5a8de4950f89fa71a12a3cf8ae" + )), + s: U256::from_big_endian(&hex!( + "7dd15a10f9f2c8d1519a6044d880d04756798fc23923ff94f4823df8dc5b987a" + )), + }, + access_list: AccessList::default(), + transaction_type: Some(0), + }; + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn decode_eip1559_json_works() { + let json = r#" + { + "blockHash": "0xfdee00b60ddb4fd465426871a247ca905ff2acd5425b2222ab495157038772f3", + "blockNumber": "0x11abc28", + "from": "0x1e8c05fa1e52adcb0b66808fa7b843d106f506d5", + "gas": "0x2335e", + "gasPrice": "0xb9c7097c0", + "maxPriorityFeePerGas": "0x5f5e100", + "maxFeePerGas": "0xbdee918d2", + "hash": "0x24cce1f28e0462c26ece316d6ae808a972d41161a237f14d31ab22c11edfb122", + "input": "0x161ac21f0000000000000000000000001fe1ffffef6b4dca417d321ccd37e081f604d1c70000000000000000000000000000a26b00c1f0df003000390027140000faa71900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002360c6ebe", + "nonce": "0x1cca", + "to": "0x00005ea00ac477b1030ce78506496e8c2de24bf5", + "transactionIndex": "0x5f", + "value": "0x38d7ea4c680000", + "type": "0x2", + "accessList": [], + "chainId": "0x1", + "v": "0x0", + "r": "0x8623bae9c86fb05f96cebd0f07247afc363f0ed3e1cf381ef99277ebf2b6c84a", + "s": "0x766ba586a5aac2769cf5ce9e3c6fccf01ad6c57eeefc3770e4a2f49516837ae2" + } + "#; + let expected = RpcTransaction { + hash: hex!("24cce1f28e0462c26ece316d6ae808a972d41161a237f14d31ab22c11edfb122").into(), + nonce: 7370, + block_hash: Some(hex!("fdee00b60ddb4fd465426871a247ca905ff2acd5425b2222ab495157038772f3").into()), + block_number: Some(18_529_320), + transaction_index: Some(95), + gas_price: Some(49_869_264_832_u64.into()), + gas_limit: 0x2335e.into(), + from: H160(hex!("1e8c05fa1e52adcb0b66808fa7b843d106f506d5")), + to: Some(H160(hex!("00005ea00ac477b1030ce78506496e8c2de24bf5"))), + value: 16_000_000_000_000_000u128.into(), + input: hex!("161ac21f0000000000000000000000001fe1ffffef6b4dca417d321ccd37e081f604d1c70000000000000000000000000000a26b00c1f0df003000390027140000faa71900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002360c6ebe").into(), + chain_id: Some(1), + max_priority_fee_per_gas: Some(100_000_000.into()), + max_fee_per_gas: Some(50_984_458_450_u64.into()), + creates: None, + raw: Vec::new(), + public_key: None, + signature: Signature { + v: 0x0.into(), + r: hex!("8623bae9c86fb05f96cebd0f07247afc363f0ed3e1cf381ef99277ebf2b6c84a").into(), + s: hex!("766ba586a5aac2769cf5ce9e3c6fccf01ad6c57eeefc3770e4a2f49516837ae2").into(), + }, + access_list: AccessList::default(), + transaction_type: Some(2), + }; + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn decode_astar_json_works() { + let json = r#" + { + "hash": "0x543865875066b0c3b7039866deb8666c7740f83cc8a920b6b261cf30db1e6bdb", + "nonce": "0x71f1", + "blockHash": "0x73f9f64e13cf96569683db7eb494d52dcb52a98feae0b0519663d0c92702f3d2", + "blockNumber": "0x4a3b18", + "transactionIndex": "0x0", + "from": "0x530de54355b619bd9b3b46ab5054933b72ca8cc0", + "to": "0xa55d9ef16af921b70fed1421c1d298ca5a3a18f1", + "value": "0x0", + "gasPrice": "0x3b9aca000", + "gas": "0x61a80", + "input": "0x3798c7f200000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000006551475800000000000000000000000000000000000000000000000000000000014a139f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004415641580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054d415449430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000045ff8a5800000000000000000000000000000000000000000000000000000000036e5f480", + "creates": null, + "raw": "0xf9022f8271f18503b9aca00083061a8094a55d9ef16af921b70fed1421c1d298ca5a3a18f180b901c43798c7f200000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000006551475800000000000000000000000000000000000000000000000000000000014a139f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004415641580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054d415449430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000045ff8a5800000000000000000000000000000000000000000000000000000000036e5f4808204c4a04c58b0730a3487da33a44b7b501387fa48d6a6339d32ff520bcefc1da16945c1a062fb6b5c6c631b8d5205d59c0716c973995b47eb1eb329100e790a0957bff72c", + "publicKey": "0x75159f240a12daf62cd20487a6dca0093a6e8a139dacf8f8888fe582a1d08ae423f742a04b82579083e86c1b78104c7137e211be1d396a1c3c14fa840d9e094a", + "chainId": "0x250", + "standardV": "0x1", + "v": "0x4c4", + "r": "0x4c58b0730a3487da33a44b7b501387fa48d6a6339d32ff520bcefc1da16945c1", + "s": "0x62fb6b5c6c631b8d5205d59c0716c973995b47eb1eb329100e790a0957bff72c", + "accessList": null, + "type": "0x0" + } + "#; + let expected = RpcTransaction { + hash: hex!("543865875066b0c3b7039866deb8666c7740f83cc8a920b6b261cf30db1e6bdb").into(), + nonce: 0x71f1, + block_hash: Some(hex!("73f9f64e13cf96569683db7eb494d52dcb52a98feae0b0519663d0c92702f3d2").into()), + block_number: Some(0x004a_3b18), + transaction_index: Some(0x0), + gas_price: Some(0x0003_b9ac_a000_u64.into()), + gas_limit: 0x61a80.into(), + from: H160::from(hex!("530de54355b619bd9b3b46ab5054933b72ca8cc0")), + to: Some(H160::from(hex!("a55d9ef16af921b70fed1421c1d298ca5a3a18f1"))), + value: 0.into(), + input: hex!("3798c7f200000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000006551475800000000000000000000000000000000000000000000000000000000014a139f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004415641580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054d415449430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000045ff8a5800000000000000000000000000000000000000000000000000000000036e5f480").into(), + chain_id: Some(0x250), + max_priority_fee_per_gas: None, + max_fee_per_gas: None, + creates: None, + raw: hex!("f9022f8271f18503b9aca00083061a8094a55d9ef16af921b70fed1421c1d298ca5a3a18f180b901c43798c7f200000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000006551475800000000000000000000000000000000000000000000000000000000014a139f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004415641580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054d415449430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000045ff8a5800000000000000000000000000000000000000000000000000000000036e5f4808204c4a04c58b0730a3487da33a44b7b501387fa48d6a6339d32ff520bcefc1da16945c1a062fb6b5c6c631b8d5205d59c0716c973995b47eb1eb329100e790a0957bff72c").to_vec(), + public_key: Some(hex!("75159f240a12daf62cd20487a6dca0093a6e8a139dacf8f8888fe582a1d08ae423f742a04b82579083e86c1b78104c7137e211be1d396a1c3c14fa840d9e094a").into()), + signature: Signature { + v: 0x4c4.into(), + r: hex!("4c58b0730a3487da33a44b7b501387fa48d6a6339d32ff520bcefc1da16945c1").into(), + s: hex!("62fb6b5c6c631b8d5205d59c0716c973995b47eb1eb329100e790a0957bff72c").into(), + }, + access_list: AccessList::default(), + transaction_type: Some(0), + }; + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + } +} diff --git a/chains/ethereum/config/src/types/transaction/signature.rs b/chains/ethereum/config/src/types/transaction/signature.rs new file mode 100644 index 00000000..917bee96 --- /dev/null +++ b/chains/ethereum/config/src/types/transaction/signature.rs @@ -0,0 +1,131 @@ +use ethereum_types::{H520, U256}; + +#[cfg(feature = "serde")] +use crate::serde_utils::uint_to_hex; + +/// An ECDSA signature +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct Signature { + /// The ECDSA recovery id, this value encodes the parity of the y-coordinate of the secp256k1 + /// signature. May also encode the chain_id for legacy EIP-155 transactions. + pub v: RecoveryId, + /// The ECDSA signature r + pub r: U256, + /// The ECDSA signature s + pub s: U256, +} + +impl Signature { + #[allow(clippy::cast_possible_truncation)] + pub fn to_raw_signature(&self, output: &mut [u8; 65]) { + self.r.to_big_endian(&mut output[0..32]); + self.s.to_big_endian(&mut output[32..64]); + output[64] = self.v.y_parity() as u8; + } +} + +impl From for H520 { + fn from(value: Signature) -> Self { + let mut output = [0u8; 65]; + value.to_raw_signature(&mut output); + Self(output) + } +} + +/// The ECDSA recovery id, encodes the parity of the y-coordinate and for EIP-155 compatible +/// transactions also encodes the chain id +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct RecoveryId(#[cfg_attr(feature = "serde", serde(with = "uint_to_hex"))] u64); + +impl RecoveryId { + #[must_use] + pub fn new(v: u64) -> Self { + debug_assert!(v >= 35 || matches!(v, 0 | 1 | 27 | 28)); + Self(v) + } + + #[must_use] + pub const fn as_u64(self) -> u64 { + self.0 + } + + /// Returns the parity (0 for even, 1 for odd) of the y-value of a secp256k1 signature. + #[must_use] + pub const fn y_parity(self) -> u64 { + let v = self.as_u64(); + + // if v is greather or equal to 35, it is an EIP-155 signature + // [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + if v >= 35 { + return (v - 35) & 1; + } + + // 27 or 28, it is a legacy signature + if v == 27 || v == 28 { + return v - 27; + } + + // otherwise, simply return the parity of the least significant bit + v & 1 + } + + #[must_use] + pub const fn chain_id(self) -> Option { + let v = self.as_u64(); + if v >= 35 { + Some((v - 35) >> 1) + } else { + None + } + } + + #[must_use] + pub const fn is_eip155(self) -> bool { + self.chain_id().is_some() + } + + /// Applies [EIP155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md) + #[must_use] + pub fn as_eip155>(self, chain_id: I) -> u64 { + let chain_id = chain_id.into(); + self.y_parity() + 35 + (chain_id * 2) + } + + /// the recovery id is encoded as 0 or 1 for EIP-2930. + #[must_use] + pub const fn is_eip2930(self) -> bool { + self.as_u64() < 2 + } + + /// Returns a legacy signature, with + #[must_use] + pub const fn as_legacy(self) -> u64 { + self.y_parity() + 27 + } +} + +impl From for u64 { + fn from(v: RecoveryId) -> Self { + v.as_u64() + } +} + +impl From for RecoveryId { + fn from(v: u64) -> Self { + Self::new(v) + } +} diff --git a/chains/ethereum/config/src/types/transaction/signed_transaction.rs b/chains/ethereum/config/src/types/transaction/signed_transaction.rs new file mode 100644 index 00000000..80ccd615 --- /dev/null +++ b/chains/ethereum/config/src/types/transaction/signed_transaction.rs @@ -0,0 +1,209 @@ +use derivative::Derivative; +use ethereum_types::H256; + +use super::signature::Signature; + +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +#[derive(Derivative)] +#[derivative(Clone, PartialEq, Eq, Debug)] +pub struct SignedTransaction { + #[cfg_attr(feature = "serde", serde(rename = "hash"))] + pub tx_hash: H256, + #[cfg_attr( + feature = "serde", + serde( + bound( + serialize = "T: serde::Serialize", + deserialize = "T: serde::de::DeserializeOwned", + ), + flatten + ) + )] + pub payload: T, + #[cfg_attr(feature = "serde", serde(flatten))] + pub signature: Signature, +} + +impl SignedTransaction { + pub const fn new(tx_hash: H256, payload: T, signature: Signature) -> Self { + Self { tx_hash, payload, signature } + } +} + +#[cfg(all(test, feature = "serde"))] +mod tests { + use super::SignedTransaction; + use crate::transaction::{ + access_list::{AccessList, AccessListItem}, + eip2930::Eip2930Transaction, + legacy::LegacyTransaction, + rpc_transaction::RpcTransaction, + signature::{RecoveryId, Signature}, + typed_transaction::TypedTransaction, + }; + use ethereum_types::{H160, H256, U256}; + use hex_literal::hex; + + fn build_eip2930() -> (H256, Eip2930Transaction, Signature) { + let tx_hash = + H256(hex!("a777326ad77731344d00263b06843be6ef05cbe9ab699e2ed0d1448f8b2b50a3")); + let tx = Eip2930Transaction { + chain_id: 1, + nonce: 117, + gas_price: 28_379_509_371u128.into(), + gas_limit: 187_293, + to: Some(H160(hex!("3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad"))), + value: 3_650_000_000_000_000_000u128.into(), + data: hex!("3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006547d41700000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000032a767a9562d00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000032a767a9562d000000000000000000000000000000000000000000000021b60af11987fa0670342f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8b55ee890426341fe45ee6dc788d2d93d25b59063000000000000000000000000000000000000000000").to_vec(), + access_list: AccessList(vec![AccessListItem { + address: H160(hex!("3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad")), + storage_keys: vec![ + H256::zero(), + H256(hex!( + "a19fd53308a1c44a3ed22d3f20ed4229aa8909e0d0a90510ca482367ad42caa6" + )), + H256(hex!( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + )), + ], + }]), + }; + let signature = Signature { + v: RecoveryId::new(0x01), + r: hex!("5fe8eb06ac27f44de3e8d1c7214f750b9fc8291ab63d71ea6a4456cfd328deb9").into(), + s: hex!("41425cc35a5ed1c922c898cb7fda5cf3b165b4792ada812700bf55cbc21a75a1").into(), + }; + (tx_hash, tx, signature) + } + + #[test] + fn serde_encode_works() { + let (tx_hash, tx, sig) = build_eip2930(); + let signed_tx = SignedTransaction::new(tx_hash, tx, sig); + let actual = serde_json::to_value(&signed_tx).unwrap(); + let expected = serde_json::json!({ + "hash": "0xa777326ad77731344d00263b06843be6ef05cbe9ab699e2ed0d1448f8b2b50a3", + "chainId": "0x1", + "nonce": "0x75", + "gasPrice": "0x69b8cf27b", + "gas": "0x2db9d", + "to": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "value": "0x32a767a9562d0000", + "data": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006547d41700000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000032a767a9562d00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000032a767a9562d000000000000000000000000000000000000000000000021b60af11987fa0670342f00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8b55ee890426341fe45ee6dc788d2d93d25b59063000000000000000000000000000000000000000000", + "accessList": [ + { + "address": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xa19fd53308a1c44a3ed22d3f20ed4229aa8909e0d0a90510ca482367ad42caa6", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + ] + } + ], + "v": "0x1", + "r": "0x5fe8eb06ac27f44de3e8d1c7214f750b9fc8291ab63d71ea6a4456cfd328deb9", + "s": "0x41425cc35a5ed1c922c898cb7fda5cf3b165b4792ada812700bf55cbc21a75a1" + }); + assert_eq!(expected, actual); + + // can decode json + let json_str = serde_json::to_string(&signed_tx).unwrap(); + let decoded = + serde_json::from_str::>(&json_str).unwrap(); + assert_eq!(signed_tx, decoded); + } + + #[test] + fn serde_decode_legacy_tx() { + let json_tx = r#" + { + "hash": "0xb3fbbda7862791ec65c07b1162bd6c6aa10efc89196a8727790a9b03b3ca7bab", + "nonce": "0x115", + "blockHash": "0x533ae98e36b11720a6095de0cbae802e80719cede1e3a65e02379436993a2007", + "blockNumber": "0x37cd6", + "transactionIndex": "0x0", + "from": "0xcf684dfb8304729355b58315e8019b1aa2ad1bac", + "to": null, + "value": "0x0", + "gasPrice": "0xba43b7400", + "gas": "0x2f4d60", + "input": "0x60606040526009600060146101000a81548160ff021916908302179055505b6000600033600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff02191690830217905550600091505b600060149054906101000a900460ff1660ff168260ff16101561010457600090505b600060149054906101000a900460ff1660ff168160ff1610156100f6578082600060149054906101000a900460ff1602016001600050826009811015610002579090601202016000508360098110156100025790906002020160005060010160146101000a81548160ff021916908302179055505b8080600101915050610074565b5b8180600101925050610052565b5b5050610160806101166000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900480634166c1fd1461004457806341c0e1b51461007457610042565b005b61005b600480359060200180359060200150610081565b604051808260ff16815260200191505060405180910390f35b61007f6004506100cc565b005b60006001600050836009811015610002579090601202016000508260098110156100025790906002020160005060010160149054906101000a900460ff1690506100c6565b92915050565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561015d57600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b5b56", + "chainId": "0x1", + "v": "0x1b", + "r": "0x834b0e7866457890809cb81a33a59380e890e1cc0d6e17a81382e99132b16bc8", + "s": "0x65dcc7686efc8f7937b3ae0d09d682cd3a7ead281a920ec39d4e2b0c34e972be", + "type": "0x0" + }"#; + let actual = serde_json::from_str::(json_tx).unwrap(); + let actual = SignedTransaction::::try_from(actual).unwrap(); + let expected = SignedTransaction { + tx_hash: H256(hex!("b3fbbda7862791ec65c07b1162bd6c6aa10efc89196a8727790a9b03b3ca7bab")), + payload: TypedTransaction::Legacy(LegacyTransaction { + chain_id: None, + nonce: 0x0115, + gas_price: 0x000b_a43b_7400_u64.into(), + gas_limit: 0x002f_4d60, + to: None, + value: U256::zero(), + data: hex!("60606040526009600060146101000a81548160ff021916908302179055505b6000600033600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff02191690830217905550600091505b600060149054906101000a900460ff1660ff168260ff16101561010457600090505b600060149054906101000a900460ff1660ff168160ff1610156100f6578082600060149054906101000a900460ff1602016001600050826009811015610002579090601202016000508360098110156100025790906002020160005060010160146101000a81548160ff021916908302179055505b8080600101915050610074565b5b8180600101925050610052565b5b5050610160806101166000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900480634166c1fd1461004457806341c0e1b51461007457610042565b005b61005b600480359060200180359060200150610081565b604051808260ff16815260200191505060405180910390f35b61007f6004506100cc565b005b60006001600050836009811015610002579090601202016000508260098110156100025790906002020160005060010160149054906101000a900460ff1690506100c6565b92915050565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561015d57600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b5b56").to_vec(), + }), + signature: Signature { + v: RecoveryId::new(0x1b), + r: hex!("834b0e7866457890809cb81a33a59380e890e1cc0d6e17a81382e99132b16bc8").into(), + s: hex!("65dcc7686efc8f7937b3ae0d09d682cd3a7ead281a920ec39d4e2b0c34e972be").into(), + }, + }; + + assert_eq!(expected, actual); + } + + #[test] + fn serde_decode_legacy_eip155() { + let json_tx = r#" + { + "blockHash": "0x1b05659b54037e74a4f8f5b9c46ee9d53b8eb5a573fb53c4ffb65bc381ff0076", + "blockNumber": "0x81ed64", + "from": "0x1f3896a7abb3c99e1f38f77be69448ee7770d18c", + "gas": "0xf4240", + "gasPrice": "0x4bfef4c00", + "hash": "0xdf99f8176f765d84ed1c00a12bba00206c6da97986c802a532884aca5aaa3809", + "input": "0x288b8133920339b815ee42a02099dcca27c01d192418334751613a1eea786a0c3a673cec000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000032464a3bc15000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000003dd2c560933380000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000b14232b0204b2f7bb6ba5aff59ef36030f7fe38b00000000000000000000000041f8d14c9475444f30a80431c68cf24dc9a8369a000000000000000000000000b9e29984fe50602e7a619662ebed4f90d93824c7000000000000000000000000dc6c91b569c98f9f6f74d90f9beff99fdaf4248b0000000000000000000000000000000000000000000000000000000002faf08000000000000000000000000000000000000000000000000003dd2c560933380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005d7625241afaa81faf3c2bd525f64f6e0ec3af39c1053d672b65c2f64992521e6f454e67000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000581b96d32320d6cb174e807b585a41f4faa8ba7da95e117f2abbcadbb257d37a5fcc16c2ba6db86200888ed85dd5eba547bb07fa0f9910950d3133026abafdd5c09e1f3896a7abb3c99e1f38f77be69448ee7770d18c001e0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000561c7dd7d43db98e3b7a6e18b4e97f0e254a5a6bb9b373d49d7e6676ccb1b02d50f131bca36928cb48cd0daec0499e9e93a390253c733607437bbebf0aa13b7080911f3896a7abb3c99e1f38f77be69448ee7770d18c0400000000000000000000", + "nonce": "0x89", + "to": "0xdc6c91b569c98f9f6f74d90f9beff99fdaf4248b", + "transactionIndex": "0x59", + "value": "0x3dd2c5609333800", + "type": "0x0", + "chainId": "0x1", + "v": "0x25", + "r": "0x20d7064f0b3c956e603c994fd83247499ede5a1209d6c997d2b2ea29b5627a7", + "s": "0x6f6c3ceb0a57952386cbb9ceb3e4d05f1d4bc8d30b67d56281d89775f972a34d" + }"#; + let actual = serde_json::from_str::(json_tx).unwrap(); + let actual = SignedTransaction::::try_from(actual).unwrap(); + let expected = SignedTransaction { + tx_hash: H256(hex!("df99f8176f765d84ed1c00a12bba00206c6da97986c802a532884aca5aaa3809")), + payload: TypedTransaction::Legacy(LegacyTransaction { + chain_id: Some(0x1), + nonce: 0x89, + gas_price: 0x0004_bfef_4c00_u64.into(), + gas_limit: 0xf4240, + to: Some(H160(hex!("dc6c91b569c98f9f6f74d90f9beff99fdaf4248b"))), + value: U256::from(0x03dd_2c56_0933_3800_u128), + data: hex!("288b8133920339b815ee42a02099dcca27c01d192418334751613a1eea786a0c3a673cec000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000032464a3bc15000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000003dd2c560933380000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000b14232b0204b2f7bb6ba5aff59ef36030f7fe38b00000000000000000000000041f8d14c9475444f30a80431c68cf24dc9a8369a000000000000000000000000b9e29984fe50602e7a619662ebed4f90d93824c7000000000000000000000000dc6c91b569c98f9f6f74d90f9beff99fdaf4248b0000000000000000000000000000000000000000000000000000000002faf08000000000000000000000000000000000000000000000000003dd2c560933380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005d7625241afaa81faf3c2bd525f64f6e0ec3af39c1053d672b65c2f64992521e6f454e67000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000581b96d32320d6cb174e807b585a41f4faa8ba7da95e117f2abbcadbb257d37a5fcc16c2ba6db86200888ed85dd5eba547bb07fa0f9910950d3133026abafdd5c09e1f3896a7abb3c99e1f38f77be69448ee7770d18c001e0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000561c7dd7d43db98e3b7a6e18b4e97f0e254a5a6bb9b373d49d7e6676ccb1b02d50f131bca36928cb48cd0daec0499e9e93a390253c733607437bbebf0aa13b7080911f3896a7abb3c99e1f38f77be69448ee7770d18c0400000000000000000000").to_vec(), + }), + signature: Signature { + v: RecoveryId::new(0x25), + r: hex!("020d7064f0b3c956e603c994fd83247499ede5a1209d6c997d2b2ea29b5627a7").into(), + s: hex!("6f6c3ceb0a57952386cbb9ceb3e4d05f1d4bc8d30b67d56281d89775f972a34d").into(), + }, + }; + + assert_eq!(expected, actual); + } +} diff --git a/chains/ethereum/config/src/types/transaction/typed_transaction.rs b/chains/ethereum/config/src/types/transaction/typed_transaction.rs new file mode 100644 index 00000000..cebab98c --- /dev/null +++ b/chains/ethereum/config/src/types/transaction/typed_transaction.rs @@ -0,0 +1,194 @@ +use ethereum_types::{H160, U256}; + +use super::{ + eip1559::Eip1559Transaction, eip2930::Eip2930Transaction, legacy::LegacyTransaction, AccessList, +}; + +/// The [`TypedTransaction`] enum represents all Ethereum transaction types. +/// +/// Its variants correspond to specific allowed transactions: +/// 1. Legacy (pre-EIP2718) [`LegacyTransaction`] +/// 2. EIP2930 (state access lists) [`Eip2930Transaction`] +/// 3. EIP1559 [`Eip1559Transaction`] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +#[cfg_attr(feature = "scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type"))] +pub enum TypedTransaction { + #[cfg_attr(feature = "serde", serde(rename = "0x0"))] + Legacy(LegacyTransaction), + #[cfg_attr(feature = "serde", serde(rename = "0x1"))] + Eip2930(Eip2930Transaction), + #[cfg_attr(feature = "serde", serde(rename = "0x2"))] + Eip1559(Eip1559Transaction), +} + +impl TypedTransaction { + #[must_use] + pub fn data(&self) -> &[u8] { + match self { + Self::Legacy(tx) => tx.data.as_ref(), + Self::Eip2930(tx) => tx.data.as_ref(), + Self::Eip1559(tx) => tx.data.as_ref(), + } + } + + #[must_use] + pub const fn to(&self) -> Option { + match self { + Self::Legacy(tx) => tx.to, + Self::Eip2930(tx) => tx.to, + Self::Eip1559(tx) => tx.to, + } + } + + #[must_use] + pub const fn nonce(&self) -> u64 { + match self { + Self::Legacy(tx) => tx.nonce, + Self::Eip2930(tx) => tx.nonce, + Self::Eip1559(tx) => tx.nonce, + } + } + + #[must_use] + pub const fn gas_limit(&self) -> u64 { + match self { + Self::Legacy(tx) => tx.gas_limit, + Self::Eip2930(tx) => tx.gas_limit, + Self::Eip1559(tx) => tx.gas_limit, + } + } + + #[must_use] + pub const fn value(&self) -> U256 { + match self { + Self::Legacy(tx) => tx.value, + Self::Eip2930(tx) => tx.value, + Self::Eip1559(tx) => tx.value, + } + } + + #[must_use] + pub const fn chain_id(&self) -> Option { + match self { + Self::Legacy(tx) => tx.chain_id, + Self::Eip2930(tx) => Some(tx.chain_id), + Self::Eip1559(tx) => Some(tx.chain_id), + } + } + + #[must_use] + pub const fn access_list(&self) -> Option<&AccessList> { + match self { + Self::Legacy(_) => None, + Self::Eip2930(tx) => Some(&tx.access_list), + Self::Eip1559(tx) => Some(&tx.access_list), + } + } + + #[must_use] + pub const fn tx_type(&self) -> u8 { + match self { + Self::Legacy(_) => 0x0, + Self::Eip2930(_) => 0x1, + Self::Eip1559(_) => 0x2, + } + } +} + +impl From for TypedTransaction { + fn from(tx: LegacyTransaction) -> Self { + Self::Legacy(tx) + } +} + +impl From for TypedTransaction { + fn from(tx: Eip2930Transaction) -> Self { + Self::Eip2930(tx) + } +} + +impl From for TypedTransaction { + fn from(tx: Eip1559Transaction) -> Self { + Self::Eip1559(tx) + } +} + +#[cfg(all(test, feature = "serde"))] +mod tests { + use super::TypedTransaction; + use crate::transaction::{ + eip1559::tests::build_eip1559, eip2930::tests::build_eip2930, legacy::tests::build_legacy, + }; + + #[allow(clippy::unwrap_used)] + fn build_typed_transaction>( + builder: fn() -> (T, serde_json::Value), + ) -> (TypedTransaction, serde_json::Value) { + let (tx, mut expected) = builder(); + let tx: TypedTransaction = tx.into(); + let tx_type = match &tx { + TypedTransaction::Legacy(_) => "0x0", + TypedTransaction::Eip2930(_) => "0x1", + TypedTransaction::Eip1559(_) => "0x2", + }; + // Add the type field to the json + let old_value = expected + .as_object_mut() + .unwrap() + .insert("type".to_string(), serde_json::json!(tx_type)); + + // Guarantee that the type field was not already present + assert_eq!(old_value, None); + (tx, expected) + } + + #[test] + fn can_encode_eip1559() { + let (tx, expected) = build_typed_transaction(build_eip1559); + let actual = serde_json::to_value(&tx).unwrap(); + assert_eq!(expected, actual); + + // can decode json + let json = serde_json::to_value(&tx).unwrap(); + let decoded = serde_json::from_value::(json).unwrap(); + assert_eq!(tx, decoded); + } + + #[test] + fn can_encode_eip2930() { + let (tx, expected) = build_typed_transaction(build_eip2930); + let actual = serde_json::to_value(&tx).unwrap(); + assert_eq!(expected, actual); + + // can decode json + let json_str = serde_json::to_string(&tx).unwrap(); + let decoded = serde_json::from_str::(&json_str).unwrap(); + assert_eq!(tx, decoded); + } + + #[test] + fn can_encode_legacy() { + let (tx, expected) = build_typed_transaction(|| build_legacy(false)); + let actual = serde_json::to_value(&tx).unwrap(); + assert_eq!(expected, actual); + + // can decode json + let json_str = serde_json::to_string(&tx).unwrap(); + let decoded = serde_json::from_str::(&json_str).unwrap(); + assert_eq!(tx, decoded); + } + + #[test] + fn can_encode_legacy_eip155() { + let (tx, expected) = build_typed_transaction(|| build_legacy(true)); + let actual = serde_json::to_value(&tx).unwrap(); + assert_eq!(expected, actual); + + // can decode json + let json_str = serde_json::to_string(&tx).unwrap(); + let decoded = serde_json::from_str::(&json_str).unwrap(); + assert_eq!(tx, decoded); + } +} diff --git a/chains/ethereum/server/src/client.rs b/chains/ethereum/server/src/client.rs index 40fc9817..c0b1f89d 100644 --- a/chains/ethereum/server/src/client.rs +++ b/chains/ethereum/server/src/client.rs @@ -7,27 +7,22 @@ use anyhow::{Context, Result}; use ethers::{ prelude::*, providers::{JsonRpcClient, Middleware, Provider}, - types::{transaction::eip2718::TypedTransaction, Bytes}, + types::{transaction::eip2718::TypedTransaction, Bytes, U64}, utils::{keccak256, rlp::Encodable}, }; use rosetta_config_ethereum::{ - AtBlock, CallContract, CallResult, EIP1186ProofResponse, EthereumMetadata, + header::Header, AtBlock, CallContract, CallResult, EIP1186ProofResponse, EthereumMetadata, EthereumMetadataParams, GetBalance, GetProof, GetStorageAt, GetTransactionReceipt, Log, Query as EthQuery, QueryResult as EthQueryResult, StorageProof, TransactionReceipt, }; use rosetta_core::{ crypto::{address::Address, PublicKey}, - types::{ - Block, BlockIdentifier, Coin, PartialBlockIdentifier, Transaction, TransactionIdentifier, - }, + types::{BlockIdentifier, PartialBlockIdentifier}, BlockchainConfig, }; -use std::{ - str::FromStr, - sync::{ - atomic::{self, Ordering}, - Arc, - }, +use std::sync::{ + atomic::{self, Ordering}, + Arc, }; /// Strategy used to determine the finalized block @@ -109,23 +104,23 @@ where &self.config } - pub const fn genesis_block(&self) -> &BlockIdentifier { - &self.genesis_block.identifier - } - - #[allow(clippy::missing_errors_doc)] - pub async fn node_version(&self) -> Result { - Ok(self.client.client_version().await?) + pub fn genesis_block(&self) -> BlockIdentifier { + self.genesis_block.identifier.clone() } #[allow(clippy::missing_errors_doc)] pub async fn current_block(&self) -> Result { let index = self.client.get_block_number().await?.as_u64(); - let Some(block_hash) = self.client.get_block(index).await?.context("missing block")?.hash + let Some(block_hash) = self + .client + .get_block(BlockId::Number(BlockNumber::Number(U64::from(index)))) + .await? + .context("missing block")? + .hash else { anyhow::bail!("FATAL: block hash is missing"); }; - Ok(BlockIdentifier { index, hash: hex::encode(block_hash) }) + Ok(BlockIdentifier { index, hash: block_hash.0 }) } #[allow(clippy::missing_errors_doc)] @@ -159,21 +154,23 @@ where } #[allow(clippy::missing_errors_doc)] - pub async fn balance(&self, address: &Address, block: &BlockIdentifier) -> Result { - let block = hex::decode(&block.hash)? - .try_into() - .map_err(|_| anyhow::anyhow!("invalid block hash"))?; + pub async fn balance( + &self, + address: &Address, + block_identifier: &PartialBlockIdentifier, + ) -> Result { + // Convert `PartialBlockIdentifier` to `BlockId` + let block_id = block_identifier.hash.as_ref().map_or_else( + || { + let index = block_identifier + .index + .map_or(BlockNumber::Latest, |index| BlockNumber::Number(U64::from(index))); + BlockId::Number(index) + }, + |hash| BlockId::Hash(H256(*hash)), + ); let address: H160 = address.address().parse()?; - Ok(self - .client - .get_balance(address, Some(BlockId::Hash(H256(block)))) - .await? - .as_u128()) - } - - #[allow(clippy::unused_async, clippy::missing_errors_doc)] - pub async fn coins(&self, _address: &Address, _block: &BlockIdentifier) -> Result> { - anyhow::bail!("not a utxo chain"); + Ok(self.client.get_balance(address, Some(block_id)).await?.as_u128()) } #[allow(clippy::single_match_else, clippy::missing_errors_doc)] @@ -278,64 +275,6 @@ where .to_vec()) } - #[allow(clippy::missing_errors_doc)] - pub async fn block(&self, block_identifier: &PartialBlockIdentifier) -> Result { - let block_id = if let Some(hash) = block_identifier.hash.as_ref() { - BlockId::Hash(H256::from_str(hash)?) - } else { - let index = block_identifier - .index - .map_or(BlockNumber::Latest, |index| BlockNumber::Number(U64::from(index))); - BlockId::Number(index) - }; - let block = self - .client - .get_block_with_txs(block_id) - .await - .map_err(|error| { - anyhow::anyhow!("Failed to get block with transactions: {}", error.to_string()) - })? - .context("block not found")?; - let block_number = block.number.context("Unable to fetch block number")?; - let block_hash = block.hash.context("Unable to fetch block hash")?; - let mut transactions = vec![]; - let block_reward_transaction = - crate::utils::block_reward_transaction(&self.client, self.config(), &block).await?; - transactions.push(block_reward_transaction); - Ok(Block { - block_identifier: BlockIdentifier { - index: block_number.as_u64(), - hash: hex::encode(block_hash), - }, - parent_block_identifier: BlockIdentifier { - index: block_number.as_u64().saturating_sub(1), - hash: hex::encode(block.parent_hash), - }, - timestamp: i64::try_from(block.timestamp.as_u64()).context("timestamp overflow")?, - transactions, - metadata: None, - }) - } - - #[allow(clippy::missing_errors_doc)] - pub async fn block_transaction( - &self, - block: &BlockIdentifier, - tx: &TransactionIdentifier, - ) -> Result { - let tx_id = H256::from_str(&tx.hash)?; - let block = self - .client - .get_block(BlockId::Hash(H256::from_str(&block.hash)?)) - .await? - .context("block not found")?; - let transaction = - self.client.get_transaction(tx_id).await?.context("transaction not found")?; - let transaction = - crate::utils::get_transaction(&self.client, self.config(), block, &transaction).await?; - Ok(transaction) - } - #[allow(clippy::too_many_lines, clippy::missing_errors_doc)] pub async fn call(&self, req: &EthQuery) -> Result { let result = match req { @@ -461,6 +400,52 @@ where .collect(), }) }, + EthQuery::GetBlockByHash(block_hash) => { + use rosetta_config_ethereum::BlockFull; + let Some(block) = self.client.get_block(*block_hash).await? else { + return Ok(EthQueryResult::GetBlockByHash(None)); + }; + let block = BlockFull { + hash: *block_hash, + header: Header { + parent_hash: block.parent_hash, + ommers_hash: block.uncles_hash, + beneficiary: block.author.unwrap_or_default(), + state_root: block.state_root, + transactions_root: block.transactions_root, + receipts_root: block.receipts_root, + logs_bloom: block.logs_bloom.unwrap_or_default(), + difficulty: block.difficulty, + number: block.number.map(|n| n.as_u64()).unwrap_or_default(), + gas_limit: block.gas_limit.try_into().unwrap_or(u64::MAX), + gas_used: block.gas_used.try_into().unwrap_or(u64::MAX), + timestamp: block.timestamp.try_into().unwrap_or(u64::MAX), + extra_data: block.extra_data.to_vec(), + mix_hash: block.mix_hash.unwrap_or_default(), + nonce: block + .nonce + .map(|n| u64::from_be_bytes(n.to_fixed_bytes())) + .unwrap_or_default(), + base_fee_per_gas: block + .base_fee_per_gas + .map(|n| u64::try_from(n).unwrap_or(u64::MAX)), + withdrawals_root: block.withdrawals_root, + blob_gas_used: block + .blob_gas_used + .map(|n| u64::try_from(n).unwrap_or(u64::MAX)), + excess_blob_gas: block + .excess_blob_gas + .map(|n| u64::try_from(n).unwrap_or(u64::MAX)), + parent_beacon_block_root: block.parent_beacon_block_root, + }, + total_difficulty: block.total_difficulty, + seal_fields: Vec::new(), + transactions: Vec::new(), + uncles: Vec::new(), + size: block.size.map(|n| u64::try_from(n).unwrap_or(u64::MAX)), + }; + EthQueryResult::GetBlockByHash(Some(block)) + }, EthQuery::ChainId => { let chain_id = self.client.get_chainid().await?.as_u64(); EthQueryResult::ChainId(chain_id) diff --git a/chains/ethereum/server/src/eth_types.rs b/chains/ethereum/server/src/eth_types.rs deleted file mode 100644 index e7954782..00000000 --- a/chains/ethereum/server/src/eth_types.rs +++ /dev/null @@ -1,89 +0,0 @@ -use ethers::types::{Bytes, H160, U256, U64}; -use serde::{Deserialize, Serialize}; - -pub const FEE_OP_TYPE: &str = "FEE"; -pub const CALL_OP_TYPE: &str = "CALL"; -pub const MINING_REWARD_OP_TYPE: &str = "MINER_REWARD"; -pub const UNCLE_REWARD_OP_TYPE: &str = "UNCLE_REWARD"; -pub const _CALL_CODE_OP_TYPE: &str = "CALLCODE"; -pub const _DELEGATE_CALL_OP_TYPE: &str = "DELEGATECALL"; -pub const _STATIC_CALL_OP_TYPE: &str = "STATICCALL"; -pub const SELF_DESTRUCT_OP_TYPE: &str = "SELFDESTRUCT"; -pub const DESTRUCT_OP_TYPE: &str = "DESTRUCT"; - -pub const CREATE_OP_TYPE: &str = "CREATE"; -pub const CREATE2_OP_TYPE: &str = "CREATE2"; - -pub const SUCCESS_STATUS: &str = "SUCCESS"; -pub const FAILURE_STATUS: &str = "FAILURE"; - -pub const UNCLE_REWARD_MULTIPLIER: u64 = 32; -pub const MAX_UNCLE_DEPTH: u64 = 8; - -pub const _TRANSFER_GAS_LIMIT: u64 = 21000; - -pub const FRONTIER_BLOCK_REWARD: u64 = 5_000_000_000_000_000_000; -pub const BYZANTIUM_BLOCK_REWARD: u64 = 3_000_000_000_000_000_000; -pub const CONSTANTINOPLE_BLOCK_REWARD: u64 = 2_000_000_000_000_000_000; - -pub struct ChainConfig { - pub byzantium_block: u64, - pub constantinople_block: u64, -} - -pub const _MAINNET_CHAIN_CONFIG: ChainConfig = - ChainConfig { byzantium_block: 4_370_000, constantinople_block: 7_280_000 }; - -pub const TESTNET_CHAIN_CONFIG: ChainConfig = - ChainConfig { byzantium_block: 0, constantinople_block: 0 }; - -#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] -pub struct Trace { - pub from: H160, - pub gas: U64, - #[serde(rename = "gasUsed")] - pub gas_used: U64, - pub input: Bytes, - pub output: Bytes, - pub to: H160, - #[serde(rename = "type")] - pub ty: String, - pub value: U256, - #[serde(default)] - pub revert: bool, - #[serde(rename = "error", default)] - pub error_message: String, - #[serde(default)] - pub calls: Vec, -} - -#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] -pub struct FlattenTrace { - pub from: H160, - pub gas: U64, - pub gas_used: U64, - pub input: Bytes, - pub output: Bytes, - pub to: H160, - pub trace_type: String, - pub value: U256, - pub revert: bool, - pub error_message: String, -} - -impl From for FlattenTrace { - fn from(trace: Trace) -> Self { - Self { - from: trace.from, - gas: trace.gas, - gas_used: trace.gas_used, - input: trace.input, - output: trace.output, - to: trace.to, - trace_type: trace.ty, - value: trace.value, - revert: trace.revert, - error_message: trace.error_message, - } - } -} diff --git a/chains/ethereum/server/src/lib.rs b/chains/ethereum/server/src/lib.rs index aa9f364d..0ebb7331 100644 --- a/chains/ethereum/server/src/lib.rs +++ b/chains/ethereum/server/src/lib.rs @@ -6,16 +6,13 @@ pub use rosetta_config_ethereum::{ }; use rosetta_core::{ crypto::{address::Address, PublicKey}, - types::{ - Block, BlockIdentifier, Coin, PartialBlockIdentifier, Transaction, TransactionIdentifier, - }, + types::{BlockIdentifier, PartialBlockIdentifier}, BlockchainClient, BlockchainConfig, }; use rosetta_server::ws::{default_client, DefaultClient}; use url::Url; mod client; -mod eth_types; mod event_stream; mod proof; mod utils; @@ -55,7 +52,7 @@ impl MaybeWsEthereumClient { Self::from_config(config, addr, private_key).await } - /// Creates a new bitcoin client from `config` and `addr` + /// Creates a new ethereum client from `config` and `addr` /// /// # Errors /// Will return `Err` when the network is invalid, or when the provided `addr` is unreacheable. @@ -99,6 +96,22 @@ impl BlockchainClient for MaybeWsEthereumClient { type Call = EthQuery; type CallResult = EthQueryResult; + type AtBlock = PartialBlockIdentifier; + type BlockIdentifier = BlockIdentifier; + + type Query = EthQuery; + type Transaction = rosetta_config_ethereum::SignedTransaction; + + async fn query( + &self, + query: Self::Query, + ) -> Result<::Result> { + match self { + Self::Http(http_client) => http_client.call(&query).await, + Self::Ws(ws_client) => ws_client.call(&query).await, + } + } + fn config(&self) -> &BlockchainConfig { match self { Self::Http(http_client) => http_client.config(), @@ -106,49 +119,35 @@ impl BlockchainClient for MaybeWsEthereumClient { } } - fn genesis_block(&self) -> &BlockIdentifier { + fn genesis_block(&self) -> Self::BlockIdentifier { match self { Self::Http(http_client) => http_client.genesis_block(), Self::Ws(ws_client) => ws_client.genesis_block(), } } - async fn node_version(&self) -> Result { - match self { - Self::Http(http_client) => http_client.node_version().await, - Self::Ws(ws_client) => ws_client.node_version().await, - } - } - - async fn current_block(&self) -> Result { + async fn current_block(&self) -> Result { match self { Self::Http(http_client) => http_client.current_block().await, Self::Ws(ws_client) => ws_client.current_block().await, } } - async fn finalized_block(&self) -> Result { + async fn finalized_block(&self) -> Result { let block = match self { Self::Http(http_client) => http_client.finalized_block(None).await?, Self::Ws(ws_client) => ws_client.finalized_block(None).await?, }; - Ok(BlockIdentifier { index: block.number, hash: hex::encode(block.hash) }) + Ok(BlockIdentifier { index: block.number, hash: block.hash.0 }) } - async fn balance(&self, address: &Address, block: &BlockIdentifier) -> Result { + async fn balance(&self, address: &Address, block: &Self::AtBlock) -> Result { match self { Self::Http(http_client) => http_client.balance(address, block).await, Self::Ws(ws_client) => ws_client.balance(address, block).await, } } - async fn coins(&self, address: &Address, block: &BlockIdentifier) -> Result> { - match self { - Self::Http(http_client) => http_client.coins(address, block).await, - Self::Ws(ws_client) => ws_client.coins(address, block).await, - } - } - async fn faucet(&self, address: &Address, param: u128) -> Result> { match self { Self::Http(http_client) => http_client.faucet(address, param).await, @@ -174,24 +173,6 @@ impl BlockchainClient for MaybeWsEthereumClient { } } - async fn block(&self, block_identifier: &PartialBlockIdentifier) -> Result { - match self { - Self::Http(http_client) => http_client.block(block_identifier).await, - Self::Ws(ws_client) => ws_client.block(block_identifier).await, - } - } - - async fn block_transaction( - &self, - block: &BlockIdentifier, - tx: &TransactionIdentifier, - ) -> Result { - match self { - Self::Http(http_client) => http_client.block_transaction(block, tx).await, - Self::Ws(ws_client) => ws_client.block_transaction(block, tx).await, - } - } - async fn call(&self, req: &EthQuery) -> Result { match self { Self::Http(http_client) => http_client.call(req).await, @@ -303,11 +284,11 @@ mod tests { let bytes = compile_snippet( r" - event AnEvent(); - function emitEvent() public { - emit AnEvent(); - } - ", + event AnEvent(); + function emitEvent() public { + emit AnEvent(); + } + ", ) .unwrap(); let tx_hash = wallet.eth_deploy_contract(bytes).await.unwrap(); @@ -322,7 +303,6 @@ mod tests { let topic = receipt.logs[0].topics[0]; let expected = H256(sha3::Keccak256::digest("AnEvent()").into()); assert_eq!(topic, expected); - Ok(()) }) .await; Ok(()) @@ -371,7 +351,6 @@ mod tests { .to_vec() ) ); - Ok(()) }) .await; Ok(()) diff --git a/chains/ethereum/server/src/utils.rs b/chains/ethereum/server/src/utils.rs index 3cf01fe8..5b9c61bc 100644 --- a/chains/ethereum/server/src/utils.rs +++ b/chains/ethereum/server/src/utils.rs @@ -1,29 +1,6 @@ -use crate::eth_types::{ - FlattenTrace, Trace, BYZANTIUM_BLOCK_REWARD, CALL_OP_TYPE, CONSTANTINOPLE_BLOCK_REWARD, - CREATE2_OP_TYPE, CREATE_OP_TYPE, DESTRUCT_OP_TYPE, FAILURE_STATUS, FEE_OP_TYPE, - FRONTIER_BLOCK_REWARD, MAX_UNCLE_DEPTH, MINING_REWARD_OP_TYPE, SELF_DESTRUCT_OP_TYPE, - SUCCESS_STATUS, TESTNET_CHAIN_CONFIG, UNCLE_REWARD_MULTIPLIER, UNCLE_REWARD_OP_TYPE, -}; -use anyhow::{bail, Context, Result}; -use ethers::{ - prelude::*, - providers::Middleware, - types::{Block, Transaction, TransactionReceipt, H160, H256, U256, U64}, - utils::to_checksum, -}; -use rosetta_core::{ - types::{ - self as rosetta_types, AccountIdentifier, Amount, BlockIdentifier, Currency, Operation, - OperationIdentifier, TransactionIdentifier, - }, - BlockchainConfig, -}; -use serde_json::json; -use std::{ - collections::{HashMap, VecDeque}, - str::FromStr, - sync::Arc, -}; +use ethers::{prelude::*, providers::Middleware, types::H256}; +use rosetta_core::types::BlockIdentifier; +use std::sync::Arc; /// A block that is not pending, so it must have a valid hash and number. /// This allow skipping duplicated checks in the code @@ -44,7 +21,7 @@ impl TryFrom> for NonPendingBlock { Ok(Self { hash, number: number.as_u64(), - identifier: BlockIdentifier::new(number.as_u64(), hex::encode(hash)), + identifier: BlockIdentifier::new(number.as_u64(), hash.0), block, }) } @@ -54,7 +31,7 @@ impl TryFrom> for NonPendingBlock { pub async fn get_non_pending_block( client: Arc>, block_id: ID, -) -> Result> +) -> anyhow::Result> where C: JsonRpcClient + 'static, ID: Into + Send + Sync, @@ -74,411 +51,3 @@ where }; Ok(Some(block)) } - -pub async fn get_transaction( - client: &Provider

, - config: &BlockchainConfig, - block: Block, - tx: &Transaction, -) -> Result { - let Some(block_hash) = block.hash else { - anyhow::bail!("Block must have a hash"); - }; - let Some(block_number) = block.number else { - anyhow::bail!("Block must have a number"); - }; - - let tx_receipt = client - .get_transaction_receipt(tx.hash) - .await? - .context("Transaction receipt not found")?; - - if tx_receipt.block_hash.context("Block hash not found in tx receipt")? != block_hash { - bail!("Transaction receipt block hash does not match block hash"); - } - let currency = config.currency(); - - let mut operations = vec![]; - let fee_ops = get_fee_operations(&block, tx, &tx_receipt, ¤cy)?; - operations.extend(fee_ops); - - let tx_trace = if block_number.is_zero() { - None - } else { - let trace = get_transaction_trace(&tx.hash, client).await?; - let trace_ops = get_trace_operations( - trace.clone(), - i64::try_from(operations.len()).context("operations overflow")?, - ¤cy, - )?; - operations.extend(trace_ops); - Some(trace) - }; - - Ok(rosetta_types::Transaction { - transaction_identifier: TransactionIdentifier { hash: hex::encode(tx.hash) }, - operations, - related_transactions: None, - metadata: Some(json!({ - "gas_limit" : tx.gas, - "gas_price": tx.gas_price, - "receipt": tx_receipt, - "trace": tx_trace, - })), - }) -} - -fn get_fee_operations( - block: &Block, - tx: &Transaction, - receipt: &TransactionReceipt, - currency: &Currency, -) -> Result> { - let miner = block.author.context("block has no author")?; - let base_fee = block.base_fee_per_gas.context("block has no base fee")?; - let tx_type = tx.transaction_type.context("transaction type unavailable")?; - let tx_gas_price = tx.gas_price.context("gas price is not available")?; - let tx_max_priority_fee_per_gas = tx.max_priority_fee_per_gas.unwrap_or_default(); - let gas_used = receipt.gas_used.context("gas used is not available")?; - let gas_price = - if tx_type.as_u64() == 2 { base_fee + tx_max_priority_fee_per_gas } else { tx_gas_price }; - let fee_amount = gas_used * gas_price; - let fee_burned = gas_used * base_fee; - let miner_earned_reward = fee_amount - fee_burned; - - let mut operations = vec![]; - - let first_op = Operation { - operation_identifier: OperationIdentifier { index: 0, network_index: None }, - related_operations: None, - r#type: FEE_OP_TYPE.into(), - status: Some(SUCCESS_STATUS.into()), - account: Some(AccountIdentifier { - address: to_checksum(&tx.from, None), - sub_account: None, - metadata: None, - }), - amount: Some(Amount { - value: format!("-{miner_earned_reward}"), - currency: currency.clone(), - metadata: None, - }), - coin_change: None, - metadata: None, - }; - - let second_op = Operation { - operation_identifier: OperationIdentifier { index: 1, network_index: None }, - related_operations: Some(vec![OperationIdentifier { index: 0, network_index: None }]), - r#type: FEE_OP_TYPE.into(), - status: Some(SUCCESS_STATUS.into()), - account: Some(AccountIdentifier { - address: to_checksum(&miner, None), - sub_account: None, - metadata: None, - }), - amount: Some(Amount { - value: format!("{miner_earned_reward}"), - currency: currency.clone(), - metadata: None, - }), - coin_change: None, - metadata: None, - }; - - operations.push(first_op); - operations.push(second_op); - - if fee_burned != U256::from(0) { - let burned_operation = Operation { - operation_identifier: OperationIdentifier { index: 2, network_index: None }, - related_operations: None, - r#type: FEE_OP_TYPE.into(), - status: Some(SUCCESS_STATUS.into()), - account: Some(AccountIdentifier { - address: to_checksum(&tx.from, None), - sub_account: None, - metadata: None, - }), - amount: Some(Amount { - value: format!("-{fee_burned}"), - currency: currency.clone(), - metadata: None, - }), - coin_change: None, - metadata: None, - }; - - operations.push(burned_operation); - } - Ok(operations) -} - -async fn get_transaction_trace( - hash: &H256, - client: &Provider

, -) -> Result { - let params = json!([ - hash, - { - "tracer": "callTracer" - } - ]); - Ok(client.request("debug_traceTransaction", params).await?) -} - -#[allow(clippy::too_many_lines)] -fn get_trace_operations(trace: Trace, op_len: i64, currency: &Currency) -> Result> { - let mut traces = VecDeque::new(); - traces.push_back(trace); - let mut flatten_traces = vec![]; - while let Some(mut trace) = traces.pop_front() { - for mut child in std::mem::take(&mut trace.calls) { - if trace.revert { - child.revert = true; - if child.error_message.is_empty() { - child.error_message = trace.error_message.clone(); - } - } - traces.push_back(child); - } - flatten_traces.push(FlattenTrace::from(trace)); - } - let traces = flatten_traces; - - let mut operations: Vec = vec![]; - let mut destroyed_accs: HashMap = HashMap::new(); - - if traces.is_empty() { - return Ok(operations); - } - - for trace in traces { - let operation_status = if trace.revert { FAILURE_STATUS } else { SUCCESS_STATUS }; - - let zero_value = trace.value.is_zero(); - - let should_add = !(zero_value && trace.trace_type == CALL_OP_TYPE); - - let from = to_checksum(&trace.from, None); - let to = to_checksum(&trace.to, None); - - if should_add { - let mut from_operation = Operation { - operation_identifier: OperationIdentifier { - index: op_len + - i64::try_from(operations.len()).context("operation.index overflow")?, - network_index: None, - }, - related_operations: None, - r#type: trace.trace_type.clone(), - status: Some(operation_status.into()), - account: Some(AccountIdentifier { - address: from.clone(), - sub_account: None, - metadata: None, - }), - amount: Some(Amount { - value: format!("-{}", trace.value), - currency: currency.clone(), - metadata: None, - }), - coin_change: None, - metadata: None, - }; - - if zero_value { - from_operation.amount = None; - } else if let Some(d_from) = destroyed_accs.get(&from) { - if operation_status == SUCCESS_STATUS { - let amount = d_from - trace.value.as_u64(); - destroyed_accs.insert(from.clone(), amount); - } - } - - operations.push(from_operation); - } - - if trace.trace_type == SELF_DESTRUCT_OP_TYPE { - //assigning destroyed from to an empty number - if from == to { - continue; - } - } - - if to.is_empty() { - continue; - } - - // If the account is resurrected, we remove it from - // the destroyed accounts map. - if trace.trace_type == CREATE_OP_TYPE || trace.trace_type == CREATE2_OP_TYPE { - destroyed_accs.remove(&to); - } - - if should_add { - let last_op_index = operations[operations.len() - 1].operation_identifier.index; - let mut to_op = Operation { - operation_identifier: OperationIdentifier { - index: last_op_index + 1, - network_index: None, - }, - related_operations: Some(vec![OperationIdentifier { - index: last_op_index, - network_index: None, - }]), - r#type: trace.trace_type, - status: Some(operation_status.into()), - account: Some(AccountIdentifier { - address: to.clone(), - sub_account: None, - metadata: None, - }), - amount: Some(Amount { - value: format!("{}", trace.value), - currency: currency.clone(), - metadata: None, - }), - coin_change: None, - metadata: None, - }; - - if zero_value { - to_op.amount = None; - } else if let Some(d_to) = destroyed_accs.get(&to) { - if operation_status == SUCCESS_STATUS { - let amount = d_to + trace.value.as_u64(); - destroyed_accs.insert(to.clone(), amount); - } - } - - operations.push(to_op); - } - - for (k, v) in &destroyed_accs { - if v == &0 { - continue; - } - - if v < &0 { - //throw some error - } - - let operation = Operation { - operation_identifier: OperationIdentifier { - index: operations[operations.len() - 1].operation_identifier.index + 1, - network_index: None, - }, - related_operations: None, - r#type: DESTRUCT_OP_TYPE.into(), - status: Some(SUCCESS_STATUS.into()), - account: Some(AccountIdentifier { - address: to_checksum(&H160::from_str(k)?, None), - sub_account: None, - metadata: None, - }), - amount: Some(Amount { - value: format!("-{v}"), - currency: currency.clone(), - metadata: None, - }), - coin_change: None, - metadata: None, - }; - - operations.push(operation); - } - } - - Ok(operations) -} - -pub async fn block_reward_transaction( - client: &Provider

, - config: &BlockchainConfig, - block: &Block, -) -> Result { - let block_number = block.number.context("missing block number")?.as_u64(); - let block_hash = block.hash.context("missing block hash")?; - let block_id = BlockId::Hash(block_hash); - let miner = block.author.context("missing block author")?; - - let mut uncles = vec![]; - for (i, _) in block.uncles.iter().enumerate() { - let uncle = client - .get_uncle(block_id, U64::from(i)) - .await? - .context("Uncle block now found")?; - uncles.push(uncle); - } - - let chain_config = TESTNET_CHAIN_CONFIG; - let mut mining_reward = if chain_config.constantinople_block <= block_number { - CONSTANTINOPLE_BLOCK_REWARD - } else if chain_config.byzantium_block <= block_number { - BYZANTIUM_BLOCK_REWARD - } else { - FRONTIER_BLOCK_REWARD - }; - if !uncles.is_empty() { - mining_reward += (mining_reward / UNCLE_REWARD_MULTIPLIER) * mining_reward; - } - - let mut operations = vec![]; - let mining_reward_operation = Operation { - operation_identifier: OperationIdentifier { index: 0, network_index: None }, - related_operations: None, - r#type: MINING_REWARD_OP_TYPE.into(), - status: Some(SUCCESS_STATUS.into()), - account: Some(AccountIdentifier { - address: to_checksum(&miner, None), - sub_account: None, - metadata: None, - }), - amount: Some(Amount { - value: mining_reward.to_string(), - currency: config.currency(), - metadata: None, - }), - coin_change: None, - metadata: None, - }; - operations.push(mining_reward_operation); - - for block in uncles { - let uncle_miner = block.author.context("Uncle block has no author")?; - let uncle_number = block.number.context("Uncle block has no number")?; - let uncle_block_reward = - (uncle_number + MAX_UNCLE_DEPTH - block_number) * (mining_reward / MAX_UNCLE_DEPTH); - - let operation = Operation { - operation_identifier: OperationIdentifier { - index: i64::try_from(operations.len()).context("operation.index overflow")?, - network_index: None, - }, - related_operations: None, - r#type: UNCLE_REWARD_OP_TYPE.into(), - status: Some(SUCCESS_STATUS.into()), - account: Some(AccountIdentifier { - address: to_checksum(&uncle_miner, None), - sub_account: None, - metadata: None, - }), - amount: Some(Amount { - value: uncle_block_reward.to_string(), - currency: config.currency(), - metadata: None, - }), - coin_change: None, - metadata: None, - }; - operations.push(operation); - } - - Ok(rosetta_types::Transaction { - transaction_identifier: TransactionIdentifier { hash: hex::encode(block_hash) }, - related_transactions: None, - operations, - metadata: None, - }) -} diff --git a/chains/polkadot/config/src/lib.rs b/chains/polkadot/config/src/lib.rs index b93e6d0a..337763e7 100644 --- a/chains/polkadot/config/src/lib.rs +++ b/chains/polkadot/config/src/lib.rs @@ -15,13 +15,19 @@ use subxt::ext::sp_core::crypto::Ss58AddressFormat; pub mod metadata { #[cfg(feature = "polkadot-metadata")] pub mod polkadot { - #[subxt::subxt(runtime_metadata_path = "res/polkadot-v1000001.scale")] + #[subxt::subxt( + runtime_metadata_path = "res/polkadot-v1000001.scale", + derive_for_all_types = "Clone, Eq, PartialEq" + )] pub mod dev {} } #[cfg(feature = "westend-metadata")] pub mod westend { - #[subxt::subxt(runtime_metadata_path = "res/westend-dev-v1.5.0.scale")] + #[subxt::subxt( + runtime_metadata_path = "res/westend-dev-v1.5.0.scale", + derive_for_all_types = "Clone, Eq, PartialEq" + )] pub mod dev {} } } diff --git a/chains/polkadot/server/Cargo.toml b/chains/polkadot/server/Cargo.toml index 541d89e2..e1dbc52e 100644 --- a/chains/polkadot/server/Cargo.toml +++ b/chains/polkadot/server/Cargo.toml @@ -11,7 +11,7 @@ anyhow = "1.0" async-trait = "0.1" hex = "0.4" parity-scale-codec = { workspace = true, features = ["derive"] } -rosetta-config-polkadot.workspace = true +rosetta-config-polkadot = { workspace = true, features = ["polkadot-metadata", "westend-metadata"] } rosetta-core.workspace = true rosetta-server = { workspace = true, default-features = false, features = ["ws", "webpki-tls"] } scale-info.workspace = true diff --git a/chains/polkadot/server/README.md b/chains/polkadot/server/README.md index 752b93ff..368d8188 100644 --- a/chains/polkadot/server/README.md +++ b/chains/polkadot/server/README.md @@ -13,7 +13,6 @@ Methods implemented are: - `metadata` - `submit` - `block` -- `block_transaction` - `call` ### `config`: @@ -42,13 +41,6 @@ Fetches account balance from on chain and returns it. It takes two arguments: This function takes `PartialBlockIdentifier` which contains a block index or hash and returns block transaction and operations happened in that transaction. -### `block_transaction`: - -This function takes: -`block`: Which is a block identifier of block from which we want to fetch transaction from. -`tx`: Transaction identifier of transaction we want to fetch. -And returns a specific transaction and its operations within specified block. - ### `faucet`: This method is used to fund an account with some amount of tokens in testnet. It takes two arguments: diff --git a/chains/polkadot/server/src/block.rs b/chains/polkadot/server/src/block.rs deleted file mode 100644 index fc45b6f2..00000000 --- a/chains/polkadot/server/src/block.rs +++ /dev/null @@ -1,187 +0,0 @@ -use anyhow::{Context, Result}; -use rosetta_core::{ - crypto::address::Address, - types::{ - AccountIdentifier, Amount, Operation, OperationIdentifier, Transaction, - TransactionIdentifier, - }, - BlockchainConfig, -}; -use serde_json::{json, Value}; -use subxt::{ - blocks::{ExtrinsicDetails, ExtrinsicEvents}, - config::Hasher, - events::EventDetails, - ext::scale_value::{scale::TypeId, Composite, Primitive, ValueDef}, - utils::H256, - Config, OnlineClient, -}; - -pub fn get_transaction_identifier>( - extrinsic: &ExtrinsicDetails>, -) -> TransactionIdentifier { - TransactionIdentifier { hash: hex::encode(T::Hasher::hash_of(&extrinsic.bytes())) } -} - -pub fn get_transaction + Send>( - config: &BlockchainConfig, - transaction_identifier: TransactionIdentifier, - events: &ExtrinsicEvents, -) -> Result { - // let transaction_identifier = TransactionIdentifier { - // hash: hex::encode(T::Hasher::hash_of(&extrinsic.bytes())), - // }; - // let events = extrinsic.events().await?; - let mut operations = vec![]; - for (event_index, event_data) in events.iter().enumerate() { - let event = event_data?; - let event_parsed_data = get_operation_data(config, &event)?; - - let mut fields = vec![]; - for field in &event.event_metadata().variant.fields { - fields.push(json!({"name": field.name, "type": field.type_name})); - } - let op_metadata = Value::Array(fields); - - let op_from: Option = event_parsed_data - .from - .map(|address| AccountIdentifier { address, sub_account: None, metadata: None }); - - let op_neg_amount: Option = event_parsed_data.amount.as_ref().map(|amount| { - Amount { value: format!("-{amount}"), currency: config.currency(), metadata: None } - }); - - let operation = Operation { - operation_identifier: OperationIdentifier { - index: i64::try_from(event_index).context("event_index overflow")?, - network_index: None, - }, - related_operations: None, - r#type: event_parsed_data.event_type.clone(), - status: None, - account: op_from, - amount: op_neg_amount, - coin_change: None, - metadata: Some(op_metadata.clone()), - }; - operations.push(operation); - - if let (Some(to), Some(amount)) = (event_parsed_data.to, event_parsed_data.amount) { - operations.push(Operation { - operation_identifier: OperationIdentifier { - index: i64::try_from(event_index).context("event_index overflow")?, - network_index: None, - }, - related_operations: None, - r#type: event_parsed_data.event_type, - status: None, - account: Some(AccountIdentifier { address: to, sub_account: None, metadata: None }), - amount: Some(Amount { value: amount, currency: config.currency(), metadata: None }), - coin_change: None, - metadata: Some(op_metadata), - }); - } - } - Ok(Transaction { - transaction_identifier, - operations, - related_transactions: None, - metadata: None, - }) -} - -fn get_operation_data>( - config: &BlockchainConfig, - event: &EventDetails, -) -> Result { - let pallet_name = event.pallet_name(); - let event_name = event.variant_name(); - - let call_type = format!("{pallet_name}.{event_name}"); - - let event_fields = event.field_values()?; - let parsed_data = match event_fields { - Composite::Named(value) => { - let mut from_data = - value.iter().filter(|(k, _)| k == "from" || k == "who" || k == "account"); - - let sender_address: Option = if let Some(data) = from_data.next() { - let address = generate_address(config, &data.1.value)?; - Some(address) - } else { - None - }; - - let amount: Option = if let Some(value) = - value.iter().find(|(k, _)| k == "amount" || k == "actual_fee") - { - match &value.1.value { - ValueDef::Primitive(Primitive::U128(amount)) => Some(amount.to_string()), - _ => { - anyhow::bail!("invalid operation"); - }, - } - } else { - None - }; - - let to_address: Option = - if let Some(data) = value.iter().find(|(k, _)| k == "to") { - let address = generate_address(config, &data.1.value)?; - Some(address) - } else { - None - }; - - (sender_address, amount, to_address) - }, - Composite::Unnamed(_) => { - anyhow::bail!("invalid operation"); - }, - }; - - Ok(TransactionOperationStatus { - event_type: call_type, - from: parsed_data.0, - amount: parsed_data.1, - to: parsed_data.2, - }) -} - -struct TransactionOperationStatus { - event_type: String, - from: Option, - to: Option, - amount: Option, -} - -fn generate_address(config: &BlockchainConfig, val: &ValueDef) -> Result { - let mut addr_array = vec![]; - match val { - ValueDef::Composite(Composite::Unnamed(unamed_data)) => { - for value_data in unamed_data { - match &value_data.value { - ValueDef::Composite(data) => { - for data in data.values() { - match data.value { - ValueDef::Primitive(Primitive::U128(val)) => { - let Ok(val) = u8::try_from(val) else { - tracing::error!("overflow: {val} > 255"); - anyhow::bail!("overflow: {val} > 255"); - }; - addr_array.push(val); - }, - _ => anyhow::bail!("invalid operation"), - } - } - }, - _ => anyhow::bail!("invalid operation"), - } - } - }, - _ => anyhow::bail!("invalid operation"), - } - - let address = Address::from_public_key_bytes(config.address_format, &addr_array); - Ok(address.address().to_string()) -} diff --git a/chains/polkadot/server/src/call.rs b/chains/polkadot/server/src/call.rs index d7eef74e..51d8453a 100644 --- a/chains/polkadot/server/src/call.rs +++ b/chains/polkadot/server/src/call.rs @@ -9,11 +9,11 @@ use subxt::{ dynamic::Value as SubxtValue, ext::scale_value::{self, scale::TypeId, BitSequence, ValueDef}, metadata::types::StorageEntryType, - OnlineClient, PolkadotConfig as GenericConfig, + OnlineClient, }; -pub fn dynamic_constant_req( - subxt: &OnlineClient, +pub fn dynamic_constant_req( + subxt: &OnlineClient, pallet_name: &str, constant_name: &str, ) -> Result { @@ -24,8 +24,8 @@ pub fn dynamic_constant_req( Ok(serde_val) } -pub async fn dynamic_storage_req( - subxt: &OnlineClient, +pub async fn dynamic_storage_req( + subxt: &OnlineClient, pallet_name: &str, storage_name: &str, params: Value, diff --git a/chains/polkadot/server/src/chains.rs b/chains/polkadot/server/src/chains.rs new file mode 100644 index 00000000..7925a590 --- /dev/null +++ b/chains/polkadot/server/src/chains.rs @@ -0,0 +1,6 @@ +mod polkadot; +mod westend; + +#[allow(unused_imports)] +pub use polkadot::PolkadotConfig; +pub use westend::WestendDevConfig; diff --git a/chains/polkadot/server/src/chains/polkadot.rs b/chains/polkadot/server/src/chains/polkadot.rs new file mode 100644 index 00000000..863a8c2f --- /dev/null +++ b/chains/polkadot/server/src/chains/polkadot.rs @@ -0,0 +1,60 @@ +use crate::types::{ClientConfig, SubxtConfigAdapter}; +use rosetta_config_polkadot::metadata::polkadot::dev; +use std::borrow::Borrow; +use subxt::{ + config::polkadot, + storage::address, + utils::{AccountId32, MultiAddress}, +}; + +pub type Config = SubxtConfigAdapter; +pub type ExtrinsicParams = polkadot::PolkadotExtrinsicParams; +pub type OtherParams = >::OtherParams; +pub type PairSigner = subxt::tx::PairSigner; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PolkadotConfig; + +impl ClientConfig for PolkadotConfig { + type Hash = ::Hash; + type AccountId = ::AccountId; + type Address = ::Address; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type OtherParams = OtherParams; + type ExtrinsicParams = ExtrinsicParams; + type AssetId = ::AssetId; + + type AccountInfo = dev::runtime_types::frame_system::AccountInfo< + u32, + dev::runtime_types::pallet_balances::types::AccountData, + >; + + type TransferKeepAlive = dev::balances::calls::types::TransferKeepAlive; + + type Pair = PairSigner; + + fn account_info( + account: impl Borrow, + ) -> address::Address< + address::StaticStorageMapKey, + Self::AccountInfo, + address::Yes, + address::Yes, + (), + > { + dev::storage().system().account(account) + } + + fn transfer_keep_alive( + dest: MultiAddress, + value: u128, + ) -> ::subxt::tx::Payload { + dev::tx().balances().transfer_keep_alive(dest, value) + } + + fn other_params() -> OtherParams { + OtherParams::default() + } +} diff --git a/chains/polkadot/server/src/chains/westend.rs b/chains/polkadot/server/src/chains/westend.rs new file mode 100644 index 00000000..26503e94 --- /dev/null +++ b/chains/polkadot/server/src/chains/westend.rs @@ -0,0 +1,60 @@ +use crate::types::{ClientConfig, SubxtConfigAdapter}; +use rosetta_config_polkadot::metadata::westend::dev; +use std::borrow::Borrow; +use subxt::{ + config::{polkadot::PolkadotExtrinsicParams, PolkadotConfig}, + storage::address, + utils::{AccountId32, MultiAddress}, +}; + +pub type Config = SubxtConfigAdapter; +pub type ExtrinsicParams = PolkadotExtrinsicParams; +pub type OtherParams = >::OtherParams; +pub type PairSigner = subxt::tx::PairSigner; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WestendDevConfig; + +impl ClientConfig for WestendDevConfig { + type Hash = ::Hash; + type AccountId = ::AccountId; + type Address = ::Address; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type OtherParams = OtherParams; + type ExtrinsicParams = ExtrinsicParams; + type AssetId = ::AssetId; + + type AccountInfo = dev::runtime_types::frame_system::AccountInfo< + u32, + dev::runtime_types::pallet_balances::types::AccountData, + >; + + type TransferKeepAlive = dev::balances::calls::types::TransferKeepAlive; + + type Pair = PairSigner; + + fn account_info( + account: impl Borrow, + ) -> address::Address< + address::StaticStorageMapKey, + Self::AccountInfo, + address::Yes, + address::Yes, + (), + > { + dev::storage().system().account(account) + } + + fn transfer_keep_alive( + dest: MultiAddress, + value: u128, + ) -> ::subxt::tx::Payload { + dev::tx().balances().transfer_keep_alive(dest, value) + } + + fn other_params() -> OtherParams { + OtherParams::default() + } +} diff --git a/chains/polkadot/server/src/client.rs b/chains/polkadot/server/src/client.rs new file mode 100644 index 00000000..bc751046 --- /dev/null +++ b/chains/polkadot/server/src/client.rs @@ -0,0 +1,146 @@ +use crate::types::{BlockIdentifier, ClientConfig, SubxtConfigAdapter}; +use anyhow::Context; +use std::{borrow::Borrow, future::Future, sync::Arc}; +use subxt::{ + backend::{ + rpc::{RpcClient, RpcClientT}, + RuntimeVersion, + }, + blocks::BlockRef, + metadata::Metadata, + utils::AccountId32, +}; + +type Config = SubxtConfigAdapter; +type OnlineClient = subxt::OnlineClient>; +type LegacyRpcMethods = subxt::backend::legacy::LegacyRpcMethods>; +type LegacyBackend = subxt::backend::legacy::LegacyBackend>; +// type PairSigner = subxt::tx::PairSigner, ::Pair>; +// type Block = subxt::blocks::Block, OnlineClient>; +type BlockDetails = subxt::backend::legacy::rpc_methods::BlockDetails>; + +pub struct SubstrateClient { + client: OnlineClient, + rpc_methods: LegacyRpcMethods, +} + +impl SubstrateClient { + /// Creates a new polkadot client using the provided `config` and connects to `addr` + /// + /// # Errors + /// Will return `Err` when the network is invalid, or when the provided `addr` is unreacheable. + pub async fn from_client(client: C) -> anyhow::Result { + let rpc_client = RpcClient::new(client); + let rpc_methods = LegacyRpcMethods::::new(rpc_client.clone()); + let backend = LegacyBackend::::new(rpc_client); + let client = OnlineClient::::from_backend(Arc::new(backend)).await?; + Ok(Self { client, rpc_methods }) + } + + pub const fn client(&self) -> &OnlineClient { + &self.client + } + + async fn block_identifier_to_hash( + &self, + block_identifier: BlockIdentifier, + ) -> anyhow::Result { + use subxt::backend::legacy::rpc_methods::BlockNumber; + let block_hash = match block_identifier { + BlockIdentifier::Hash(block_hash) => block_hash, + BlockIdentifier::Number(block_number) => { + let Some(block_hash) = self + .rpc_methods + .chain_get_block_hash(Some(BlockNumber::Number(block_number))) + .await? + else { + anyhow::bail!("block not found: {block_identifier:?}"); + }; + block_hash + }, + BlockIdentifier::Latest => self + .rpc_methods + .chain_get_block_hash(None) + .await? + .context("latest block not found")?, + BlockIdentifier::Finalized => self.rpc_methods.chain_get_finalized_head().await?, + }; + Ok(block_hash) + } + + pub fn account_info( + &self, + account: impl Borrow, + block_identifier: impl Into>, + ) -> impl Future> + Sized + Send + '_ { + let account = account.borrow(); + let tx = T::account_info(account); + let block_identifier = block_identifier.into(); + async move { + let block_hash = self.block_identifier_to_hash(block_identifier).await?; + let account = self + .client + .storage() + .at(BlockRef::from_hash(block_hash)) + .fetch_or_default(&tx) + .await?; + Ok(account) + } + } + + // pub fn block( + // &self, + // block_identifier: impl Into> + Send, + // ) -> impl Future>> + Sized + Send + '_ { + // let block_identifier = block_identifier.into(); + // async move { + // let block_hash = self.block_identifier_to_hash(block_identifier).await?; + // let block = self.client.blocks().at(BlockRef::from_hash(block_hash)).await?; + // Ok(block) + // } + // } + + pub fn block_details( + &self, + block_identifier: impl Into> + Send, + ) -> impl Future>>> + Sized + Send + '_ { + let block_identifier = block_identifier.into(); + async move { + let block_hash = self.block_identifier_to_hash(block_identifier).await?; + self.rpc_methods + .chain_get_block(Some(block_hash)) + .await + .map_err(anyhow::Error::from) + } + } + + pub async fn faucet( + &self, + signer: T::Pair, + dest: subxt::utils::MultiAddress, + value: u128, + ) -> anyhow::Result { + let tx = T::transfer_keep_alive(dest, value); + let hash = self + .client + .tx() + .sign_and_submit_then_watch(&tx, &signer, T::other_params()) + .await? + .wait_for_finalized_success() + .await? + .extrinsic_hash(); + Ok(hash) + } + + pub fn runtime_version(&self) -> RuntimeVersion { + self.client.runtime_version() + } + + pub fn metadata(&self) -> Metadata { + self.client.metadata() + } + + pub fn genesis_hash(&self) -> T::Hash { + self.client.genesis_hash() + } +} diff --git a/chains/polkadot/server/src/lib.rs b/chains/polkadot/server/src/lib.rs index 32031011..88b9d1c8 100644 --- a/chains/polkadot/server/src/lib.rs +++ b/chains/polkadot/server/src/lib.rs @@ -1,41 +1,29 @@ use anyhow::{Context, Result}; +use chains::WestendDevConfig; use parity_scale_codec::{Decode, Encode}; -use rosetta_config_polkadot::metadata::westend::dev as westend_dev_metadata; pub use rosetta_config_polkadot::{PolkadotMetadata, PolkadotMetadataParams}; use rosetta_core::{ crypto::{address::Address, PublicKey}, - types::{ - Block, BlockIdentifier, CallRequest, Coin, PartialBlockIdentifier, Transaction, - TransactionIdentifier, - }, + types::{BlockIdentifier, CallRequest, PartialBlockIdentifier}, BlockchainClient, BlockchainConfig, EmptyEventStream, }; use rosetta_server::ws::default_client; use serde_json::Value; use sp_keyring::AccountKeyring; -use std::time::Duration; use subxt::{ - backend::{ - legacy::{rpc_methods::BlockNumber, LegacyRpcMethods}, - rpc::RpcClient, - }, - blocks::BlockRef, - config::{Hasher, Header}, - // dynamic::Value as SubtxValue, + config::Header, tx::{PairSigner, SubmittableExtrinsic}, utils::{AccountId32, MultiAddress}, - Config, - OnlineClient, - PolkadotConfig, }; -mod block; mod call; +mod chains; +mod client; +mod types; pub struct PolkadotClient { config: BlockchainConfig, - client: OnlineClient, - rpc_methods: LegacyRpcMethods, + client: client::SubstrateClient, genesis_block: BlockIdentifier, } @@ -49,79 +37,16 @@ impl PolkadotClient { Self::from_config(config, addr).await } - /// Creates a new bitcoin client using the provided `config` and connets to `addr` + /// Creates a new substrate client using the provided `config` and connets to `addr` /// /// # Errors /// Will return `Err` when the network is invalid, or when the provided `addr` is unreacheable. pub async fn from_config(config: BlockchainConfig, addr: &str) -> Result { - let (client, rpc_methods) = { - let ws_client = default_client(addr, None).await?; - let rpc_client = RpcClient::new(ws_client); - let rpc_methods = LegacyRpcMethods::::new(rpc_client.clone()); - let backend = subxt::backend::legacy::LegacyBackend::new(rpc_client); - let client = - OnlineClient::::from_backend(std::sync::Arc::new(backend)).await?; - (client, rpc_methods) - }; + let ws_client = default_client(addr, None).await?; + let client = client::SubstrateClient::::from_client(ws_client).await?; let genesis = client.genesis_hash(); - let genesis_block = BlockIdentifier { index: 0, hash: hex::encode(genesis.as_ref()) }; - Ok(Self { config, client, rpc_methods, genesis_block }) - } - - async fn account_info( - &self, - address: &Address, - maybe_block: Option<&BlockIdentifier>, - ) -> Result> { - let account: AccountId32 = address - .address() - .parse() - .map_err(|err| anyhow::anyhow!("{}", err)) - .context("invalid address")?; - - // Build a dynamic storage query to iterate account information. - // let storage_query = - // subxt::dynamic::storage("System", "Account", vec![SubtxValue::from_bytes(account)]); - - let storage_query = westend_dev_metadata::storage().system().account(account); - - let block_hash = { - let block_number = maybe_block.map(|block| BlockNumber::from(block.index)); - self.rpc_methods - .chain_get_block_hash(block_number) - .await? - .map(BlockRef::from_hash) - .ok_or_else(|| anyhow::anyhow!("no block hash found"))? - }; - - let account_info = self.client.storage().at(block_hash).fetch(&storage_query).await?; - - // let account_info = self.client.storage().at(block_hash).fetch(&storage_query).await?; - - account_info.map_or_else( - || { - Ok(AccountInfo { - nonce: 0, - consumers: 0, - providers: 0, - sufficients: 0, - data: AccountData { free: 0, reserved: 0, frozen: 0 }, - }) - }, - |account_info| { - Ok(AccountInfo { - nonce: account_info.nonce, - consumers: account_info.consumers, - providers: account_info.providers, - sufficients: account_info.sufficients, - data: AccountData { - free: account_info.data.free, - reserved: account_info.data.reserved, - frozen: account_info.data.frozen, - }, - }) - }, - ) + let genesis_block = BlockIdentifier { index: 0, hash: genesis.0 }; + Ok(Self { config, client, genesis_block }) } } @@ -133,63 +58,71 @@ impl BlockchainClient for PolkadotClient { type Call = CallRequest; type CallResult = Value; - fn config(&self) -> &BlockchainConfig { - &self.config + type AtBlock = PartialBlockIdentifier; + type BlockIdentifier = BlockIdentifier; + + type Query = types::Query; + type Transaction = Vec; + + async fn query( + &self, + _query: Self::Query, + ) -> Result<::Result> { + anyhow::bail!("unsupported query"); } - fn genesis_block(&self) -> &BlockIdentifier { - &self.genesis_block + fn config(&self) -> &BlockchainConfig { + &self.config } - async fn node_version(&self) -> Result { - self.rpc_methods.system_version().await.map_err(anyhow::Error::from) + fn genesis_block(&self) -> BlockIdentifier { + self.genesis_block.clone() } async fn current_block(&self) -> Result { - let block = self.rpc_methods.chain_get_block(None).await?.context("no current block")?; + let block = self + .client + .block_details(types::BlockIdentifier::<_>::Latest) + .await? + .context("no current block")?; + // let block = self.rpc_methods.chain_get_block(None).await?.context("no current block")?; let index = u64::from(block.block.header.number); let hash = block.block.header.hash(); - Ok(BlockIdentifier { index, hash: hex::encode(hash.as_ref()) }) + Ok(BlockIdentifier { index, hash: hash.0 }) } async fn finalized_block(&self) -> Result { - let finalized_head = self.rpc_methods.chain_get_finalized_head().await?; - let block = self - .rpc_methods - .chain_get_block(Some(finalized_head)) - .await? - .context("no finalized block")?; + let Some(block) = self.client.block_details(types::BlockIdentifier::<_>::Finalized).await? + else { + return Ok(self.genesis_block.clone()); + }; let index = u64::from(block.block.header.number); let hash = block.block.header.hash(); - Ok(BlockIdentifier { index, hash: hex::encode(hash.as_ref()) }) + Ok(BlockIdentifier { index, hash: hash.0 }) } - async fn balance(&self, address: &Address, block: &BlockIdentifier) -> Result { - let account_info = self.account_info(address, Some(block)).await?; + async fn balance( + &self, + address: &Address, + block_identifier: &PartialBlockIdentifier, + ) -> Result { + let account: AccountId32 = address + .address() + .parse() + .map_err(|err| anyhow::anyhow!("{}", err)) + .context("invalid address")?; + let account_info = self.client.account_info(account, block_identifier).await?; Ok(account_info.data.free) } - async fn coins(&self, _address: &Address, _block: &BlockIdentifier) -> Result> { - anyhow::bail!("not a utxo chain") - } - async fn faucet(&self, address: &Address, value: u128) -> Result> { let address: AccountId32 = address .address() .parse() - .map_err(|err| anyhow::anyhow!("{}", err)) + .map_err(|err| anyhow::anyhow!("{err}")) .context("invalid address")?; - let signer = PairSigner::::new(AccountKeyring::Alice.pair()); - - let tx = westend_dev_metadata::tx().balances().transfer_keep_alive(address.into(), value); - let hash = self - .client - .tx() - .sign_and_submit_then_watch_default(&tx, &signer) - .await? - .wait_for_finalized_success() - .await? - .extrinsic_hash(); + let signer = PairSigner::<_, _>::new(AccountKeyring::Alice.pair()); + let hash = self.client.faucet(signer, address.into(), value).await?; Ok(hash.0.to_vec()) } @@ -199,7 +132,15 @@ impl BlockchainClient for PolkadotClient { params: &Self::MetadataParams, ) -> Result { let address = public_key.to_address(self.config().address_format); - let account_info = self.account_info(&address, None).await?; + + let account: AccountId32 = address + .address() + .parse() + .map_err(|err| anyhow::anyhow!("{err}")) + .context("invalid address")?; + + let account_info = + self.client.account_info(&account, types::BlockIdentifier::<_>::Latest).await?; let runtime = self.client.runtime_version(); let metadata = self.client.metadata(); let pallet = metadata @@ -226,77 +167,14 @@ impl BlockchainClient for PolkadotClient { } async fn submit(&self, transaction: &[u8]) -> Result> { - let hash = SubmittableExtrinsic::from_bytes(self.client.clone(), transaction.to_vec()) - .submit_and_watch() - .await? - .wait_for_finalized_success() - .await? - .extrinsic_hash(); - Ok(hash.0.to_vec()) - } - - async fn block(&self, block_identifier: &PartialBlockIdentifier) -> Result { - let block_hash = if let Some(hash) = block_identifier.hash.as_ref() { - hash.parse()? - } else { - self.rpc_methods - .chain_get_block_hash(block_identifier.index.map(BlockNumber::from)) + let hash = + SubmittableExtrinsic::from_bytes(self.client.client().clone(), transaction.to_vec()) + .submit_and_watch() .await? - .context("block not found")? - }; - - let block = self.client.blocks().at(block_hash).await?; - let extrinsics = block.extrinsics().await?; - - // Build timestamp query - let timestamp_now_query = westend_dev_metadata::storage().timestamp().now(); - let timestamp = block.storage().fetch_or_default(×tamp_now_query).await?; - - let mut transactions = vec![]; - for extrinsic in extrinsics.iter().filter_map(Result::ok) { - let transaction_identifier = crate::block::get_transaction_identifier(&extrinsic); - let events = extrinsic.events().await?; - let transaction = - crate::block::get_transaction(self.config(), transaction_identifier, &events)?; - transactions.push(transaction); - } - Ok(Block { - block_identifier: BlockIdentifier { - index: u64::from(block.number()), - hash: hex::encode(block.hash()), - }, - parent_block_identifier: BlockIdentifier { - index: u64::from(block.number().saturating_sub(1)), - hash: hex::encode(block.header().parent_hash), - }, - timestamp: i64::try_from(Duration::from_millis(timestamp).as_nanos()) - .context("timestamp overflow")?, - transactions, - metadata: None, - }) - } - - async fn block_transaction( - &self, - block_identifier: &BlockIdentifier, - transaction_identifier: &TransactionIdentifier, - ) -> Result { - let block_hash = block_identifier.hash.parse::<::Hash>()?; - let transaction_hash = transaction_identifier.hash.parse()?; - let block = self.client.blocks().at(block_hash).await?; - let extrinsic = block - .extrinsics() - .await? - .iter() - .filter_map(Result::ok) - .find(|extrinsic| { - ::Hasher::hash_of(&extrinsic.bytes()) == transaction_hash - }) - .context("transaction not found")?; - - let identifier = crate::block::get_transaction_identifier(&extrinsic); - let events = extrinsic.events().await?; - crate::block::get_transaction(self.config(), identifier, &events) + .wait_for_finalized_success() + .await? + .extrinsic_hash(); + Ok(hash.0.to_vec()) } async fn call(&self, request: &CallRequest) -> Result { @@ -308,10 +186,12 @@ impl BlockchainClient for PolkadotClient { let call_name = call_details[1]; let query_type = call_details[2]; match query_type.to_lowercase().as_str() { - "constant" => crate::call::dynamic_constant_req(&self.client, pallet_name, call_name), + "constant" => { + crate::call::dynamic_constant_req(self.client.client(), pallet_name, call_name) + }, "storage" => { crate::call::dynamic_storage_req( - &self.client, + self.client.client(), pallet_name, call_name, request.parameters.clone(), diff --git a/chains/polkadot/server/src/types.rs b/chains/polkadot/server/src/types.rs new file mode 100644 index 00000000..d90beaf2 --- /dev/null +++ b/chains/polkadot/server/src/types.rs @@ -0,0 +1,154 @@ +use rosetta_core::{traits::Member, types::PartialBlockIdentifier}; +use std::{borrow::Borrow, fmt::Debug, marker::PhantomData}; +use subxt::{ + config::{ExtrinsicParams, Hasher, Header}, + ext::{codec::Encode, scale_decode::DecodeAsType, scale_encode::EncodeAsType}, + utils::{AccountId32, MultiAddress}, + Config as SubxtConfig, +}; + +pub trait ClientConfig: Debug + Clone + PartialEq + Eq + Sized + Send + Sync + 'static { + /// The output of the `Hasher` function. + type Hash: subxt::config::BlockHash; + + /// The account ID type. + type AccountId: Member + Encode; + + /// The address type. + type Address: Member + Encode + From; + + /// The signature type. + type Signature: Member + Encode; + + /// The hashing system (algorithm) being used in the runtime (e.g. Blake2). + type Hasher: Debug + Hasher; + + /// The block header. + type Header: Member + Header + Send + serde::de::DeserializeOwned; + + /// These parameters can be provided to the constructor along with + /// some default parameters that `subxt` understands, in order to + /// help construct your [`Self::ExtrinsicParams`] object. + type OtherParams: Default + Send + Sync + 'static; + + /// This type defines the extrinsic extra and additional parameters. + type ExtrinsicParams: ExtrinsicParams, OtherParams = Self::OtherParams>; + + /// This is used to identify an asset in the `ChargeAssetTxPayment` signed extension. + type AssetId: Debug + Clone + Encode + DecodeAsType + EncodeAsType; + + type AccountInfo: Member + subxt::metadata::DecodeWithMetadata; + + type TransferKeepAlive: Member + + subxt::blocks::StaticExtrinsic + + subxt::ext::scale_encode::EncodeAsFields; + + type Pair: subxt::tx::Signer> + Send + Sync + 'static; + + fn account_info( + account: impl Borrow, + ) -> ::subxt::storage::address::Address< + ::subxt::storage::address::StaticStorageMapKey, + Self::AccountInfo, + ::subxt::storage::address::Yes, + ::subxt::storage::address::Yes, + (), + >; + + fn transfer_keep_alive( + dest: MultiAddress, + value: u128, + ) -> ::subxt::tx::Payload; + + fn other_params( + ) -> >>::OtherParams; +} + +pub struct SubxtConfigAdapter(PhantomData); + +impl SubxtConfig for SubxtConfigAdapter +where + T: ClientConfig, +{ + type Hash = ::Hash; + type AccountId = ::AccountId; + type Address = ::Address; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type ExtrinsicParams = ::ExtrinsicParams; + type AssetId = ::AssetId; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StorageQuery { + /// Version of the runtime specification. + spec_version: Option, + // Raw storage-key + address: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BlockIdentifier { + Number(u64), + Hash(BlockHash), + Latest, + Finalized, +} + +impl From for BlockIdentifier +where + T: From<[u8; 32]>, +{ + fn from(block_identifier: rosetta_core::types::BlockIdentifier) -> Self { + Self::Hash(T::from(block_identifier.hash)) + } +} + +impl From for BlockIdentifier +where + T: From<[u8; 32]>, +{ + fn from(block_identifier: PartialBlockIdentifier) -> Self { + match block_identifier { + PartialBlockIdentifier { hash: Some(block_hash), .. } => { + Self::Hash((block_hash).into()) + }, + PartialBlockIdentifier { index: Some(block_number), .. } => Self::Number(block_number), + PartialBlockIdentifier { hash: None, index: None } => Self::Latest, + } + } +} + +impl From<&PartialBlockIdentifier> for BlockIdentifier +where + T: From<[u8; 32]>, +{ + fn from(block_identifier: &PartialBlockIdentifier) -> Self { + match block_identifier { + PartialBlockIdentifier { hash: Some(block_hash), .. } => { + Self::Hash((*block_hash).into()) + }, + PartialBlockIdentifier { index: Some(block_number), .. } => Self::Number(*block_number), + PartialBlockIdentifier { hash: None, index: None } => Self::Latest, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Query { + AccountInfo(T::AccountId), + GetBlock(BlockIdentifier), + Storage(StorageQuery), +} + +impl rosetta_core::traits::Query for Query { + type Result = QueryResult; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum QueryResult { + AccountInfo(T::AccountInfo), + GetBlock(u32), + Storage(Vec), +} diff --git a/deny.toml b/deny.toml index 2f83caca..151291ef 100644 --- a/deny.toml +++ b/deny.toml @@ -20,7 +20,6 @@ allow = [ "CC0-1.0", "ISC", "LicenseRef-ring", - "LicenseRef-webpki", "MIT", "MPL-2.0", "Unicode-DFS-2016", diff --git a/docker-compose.yml b/docker-compose.yml index ebe1f15d..830c7334 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,28 +1,11 @@ version: "3.9" volumes: - bitcoin-volume: ethereum-volume: polkadot-volume: astar-volume: services: - bitcoin: - image: "ruimarinho/bitcoin-core:23" - command: "-regtest=1 -rpcbind=0.0.0.0 -rpcport=18443 -rpcallowip=0.0.0.0/0 -rpcuser=rosetta -rpcpassword=rosetta" - expose: - - "18443" - ulimits: - nofile: - soft: 10000 - hard: 10000 - deploy: - resources: - reservations: - memory: 1g - volumes: - - "bitcoin-volume:/home/bitcoin/.bitcoin" - ethereum: image: "ethereum/client-go:v1.12.2" command: "--dev --ipcdisable --http --http.addr 0.0.0.0 --http.vhosts * --http.api eth,debug,admin,txpool,web3" @@ -41,7 +24,7 @@ services: polkadot: image: "parity/polkadot:v1.5.0" - command: "--dev --rpc-external --rpc-port=9944 --alice --blocks-pruning archive --state-pruning archive --base-path /polkadot" + command: "--chain=westend-dev --dev --rpc-external --rpc-port=9944 --alice --blocks-pruning archive --state-pruning archive --base-path /polkadot" expose: - "9944" user: root @@ -73,15 +56,6 @@ services: volumes: - "astar-volume:/astar" - - connector-bitcoin: - image: "analoglabs/connector-bitcoin" - command: "--network regtest --addr 0.0.0.0:8080 --node-addr http://bitcoin:18443" - ports: - - "8080:8080" - depends_on: - - bitcoin - connector-ethereum: image: "analoglabs/connector-ethereum" command: "--network dev --addr 0.0.0.0:8081 --node-addr http://ethereum:8545" diff --git a/rosetta-client/Cargo.toml b/rosetta-client/Cargo.toml index 17ec72e4..56dfc7b8 100644 --- a/rosetta-client/Cargo.toml +++ b/rosetta-client/Cargo.toml @@ -11,7 +11,7 @@ anyhow = "1.0" async-trait = "0.1" derive_more = "0.99" dirs-next = "2.0" -fraction = { version = "0.13", default-features = false, features = ["with-bigint", "with-decimal"] } +fraction = { version = "0.15", default-features = false, features = ["with-bigint", "with-decimal"] } futures = "0.3" getrandom = "0.2" hex = "0.4" @@ -19,14 +19,12 @@ log = "0.4" num-traits = "0.2" rosetta-core.workspace = true rosetta-server-astar.workspace = true -rosetta-server-bitcoin.workspace = true rosetta-server-ethereum.workspace = true rosetta-server-polkadot.workspace = true rosetta-tx-ethereum.workspace = true rosetta-tx-polkadot.workspace = true serde.workspace = true serde_json.workspace = true -void = "1.0" [target.'cfg(target_family = "wasm")'.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/rosetta-client/src/client.rs b/rosetta-client/src/client.rs index 337fa7f6..36576c2c 100644 --- a/rosetta-client/src/client.rs +++ b/rosetta-client/src/client.rs @@ -1,10 +1,7 @@ #![allow(missing_docs)] use crate::{ crypto::{address::Address, PublicKey}, - types::{ - Block, BlockIdentifier, CallRequest, Coin, PartialBlockIdentifier, Transaction, - TransactionIdentifier, - }, + types::CallRequest, Blockchain, BlockchainConfig, }; use anyhow::Result; @@ -12,7 +9,6 @@ use derive_more::From; use futures::Stream; use rosetta_core::{BlockchainClient, ClientEvent}; use rosetta_server_astar::{AstarClient, AstarMetadata, AstarMetadataParams}; -use rosetta_server_bitcoin::{BitcoinClient, BitcoinMetadata, BitcoinMetadataParams}; use rosetta_server_ethereum::{ config::{Query as EthQuery, QueryResult as EthQueryResult}, EthereumMetadata, EthereumMetadataParams, MaybeWsEthereumClient as EthereumClient, @@ -21,13 +17,10 @@ use rosetta_server_polkadot::{PolkadotClient, PolkadotMetadata, PolkadotMetadata use serde::{Deserialize, Serialize}; use serde_json::Value; use std::{pin::Pin, str::FromStr}; -use void::Void; -// TODO: Use -#[allow(clippy::large_enum_variant)] /// Generic Client +#[allow(clippy::large_enum_variant)] pub enum GenericClient { - Bitcoin(BitcoinClient), Ethereum(EthereumClient), Astar(AstarClient), Polkadot(PolkadotClient), @@ -42,10 +35,6 @@ impl GenericClient { private_key: Option<[u8; 32]>, ) -> Result { Ok(match blockchain { - Blockchain::Bitcoin => { - let client = BitcoinClient::new(network, url).await?; - Self::Bitcoin(client) - }, Blockchain::Ethereum => { let client = EthereumClient::new("ethereum", network, url, private_key).await?; Self::Ethereum(client) @@ -79,10 +68,6 @@ impl GenericClient { ) -> Result { let blockchain = Blockchain::from_str(config.blockchain)?; Ok(match blockchain { - Blockchain::Bitcoin => { - let client = BitcoinClient::from_config(config, url).await?; - Self::Bitcoin(client) - }, Blockchain::Ethereum | Blockchain::Polygon | Blockchain::Arbitrum => { let client = EthereumClient::from_config(config, url, private_key).await?; Self::Ethereum(client) @@ -105,7 +90,6 @@ impl GenericClient { /// Generic Blockchain Params #[derive(Deserialize, Serialize, From)] pub enum GenericMetadataParams { - Bitcoin(BitcoinMetadataParams), Ethereum(EthereumMetadataParams), Astar(AstarMetadataParams), Polkadot(PolkadotMetadataParams), @@ -114,29 +98,52 @@ pub enum GenericMetadataParams { /// Generic Blockchain Metadata #[derive(Deserialize, Serialize, From)] pub enum GenericMetadata { - Bitcoin(BitcoinMetadata), Ethereum(EthereumMetadata), Astar(AstarMetadata), Polkadot(PolkadotMetadata), } pub enum GenericCall { - Bitcoin(Void), Ethereum(EthQuery), Polkadot(CallRequest), } #[allow(clippy::large_enum_variant)] pub enum GenericCallResult { - Bitcoin(()), Ethereum(EthQueryResult), Polkadot(Value), } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum GenericAtBlock { + Ethereum(::AtBlock), + Polkadot(::AtBlock), +} + +impl From for GenericAtBlock { + fn from(block: GenericBlockIdentifier) -> Self { + match block { + GenericBlockIdentifier::Ethereum(block) => Self::Ethereum(block.into()), + GenericBlockIdentifier::Polkadot(block) => Self::Polkadot(block.into()), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum GenericBlockIdentifier { + Ethereum(::BlockIdentifier), + Polkadot(::BlockIdentifier), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum GenericTransaction { + Ethereum(::Transaction), + Polkadot(::Transaction), +} + macro_rules! dispatch { ($self:tt$($method:tt)+) => { match $self { - Self::Bitcoin(client) => client$($method)*, Self::Ethereum(client) => client$($method)*, Self::Astar(client) => client$($method)*, Self::Polkadot(client) => client$($method)*, @@ -152,32 +159,77 @@ impl BlockchainClient for GenericClient { type Call = GenericCall; type CallResult = GenericCallResult; - fn config(&self) -> &BlockchainConfig { - dispatch!(self.config()) - } + type AtBlock = GenericAtBlock; + type BlockIdentifier = GenericBlockIdentifier; + + type Query = (); + type Transaction = GenericTransaction; - fn genesis_block(&self) -> &BlockIdentifier { - dispatch!(self.genesis_block()) + async fn query( + &self, + _query: Self::Query, + ) -> Result<::Result> { + anyhow::bail!("unsupported query"); } - async fn node_version(&self) -> Result { - dispatch!(self.node_version().await) + fn config(&self) -> &BlockchainConfig { + dispatch!(self.config()) } - async fn current_block(&self) -> Result { - dispatch!(self.current_block().await) + fn genesis_block(&self) -> Self::BlockIdentifier { + // dispatch!(self.genesis_block()) + match self { + Self::Ethereum(client) => GenericBlockIdentifier::Ethereum(client.genesis_block()), + Self::Astar(client) => GenericBlockIdentifier::Ethereum(client.genesis_block()), + Self::Polkadot(client) => GenericBlockIdentifier::Polkadot(client.genesis_block()), + } } - async fn finalized_block(&self) -> Result { - dispatch!(self.finalized_block().await) + async fn current_block(&self) -> Result { + // dispatch!(self.current_block().await) + match self { + Self::Ethereum(client) => { + client.current_block().await.map(GenericBlockIdentifier::Ethereum) + }, + Self::Astar(client) => { + client.current_block().await.map(GenericBlockIdentifier::Ethereum) + }, + Self::Polkadot(client) => { + client.current_block().await.map(GenericBlockIdentifier::Polkadot) + }, + } } - async fn balance(&self, address: &Address, block: &BlockIdentifier) -> Result { - dispatch!(self.balance(address, block).await) + async fn finalized_block(&self) -> Result { + // dispatch!(self.finalized_block().await) + match self { + Self::Ethereum(client) => { + client.finalized_block().await.map(GenericBlockIdentifier::Ethereum) + }, + Self::Astar(client) => { + client.finalized_block().await.map(GenericBlockIdentifier::Ethereum) + }, + Self::Polkadot(client) => { + client.finalized_block().await.map(GenericBlockIdentifier::Polkadot) + }, + } } - async fn coins(&self, address: &Address, block: &BlockIdentifier) -> Result> { - dispatch!(self.coins(address, block).await) + async fn balance(&self, address: &Address, block: &Self::AtBlock) -> Result { + match self { + Self::Ethereum(client) => match block { + GenericAtBlock::Ethereum(at_block) => client.balance(address, at_block).await, + GenericAtBlock::Polkadot(_) => anyhow::bail!("invalid block identifier"), + }, + Self::Astar(client) => match block { + GenericAtBlock::Ethereum(at_block) => client.balance(address, at_block).await, + GenericAtBlock::Polkadot(_) => anyhow::bail!("invalid block identifier"), + }, + Self::Polkadot(client) => match block { + GenericAtBlock::Polkadot(at_block) => client.balance(address, at_block).await, + GenericAtBlock::Ethereum(_) => anyhow::bail!("invalid block identifier"), + }, + } } async fn faucet(&self, address: &Address, param: u128) -> Result> { @@ -190,9 +242,6 @@ impl BlockchainClient for GenericClient { params: &Self::MetadataParams, ) -> Result { Ok(match (self, params) { - (Self::Bitcoin(client), GenericMetadataParams::Bitcoin(params)) => { - client.metadata(public_key, params).await?.into() - }, (Self::Ethereum(client), GenericMetadataParams::Ethereum(params)) => { client.metadata(public_key, params).await?.into() }, @@ -210,41 +259,25 @@ impl BlockchainClient for GenericClient { dispatch!(self.submit(transaction).await) } - async fn block(&self, block: &PartialBlockIdentifier) -> Result { - dispatch!(self.block(block).await) - } - - async fn block_transaction( - &self, - block: &BlockIdentifier, - tx: &TransactionIdentifier, - ) -> Result { - dispatch!(self.block_transaction(block, tx).await) - } - async fn call(&self, req: &GenericCall) -> Result { let result = match self { - Self::Bitcoin(client) => match req { - GenericCall::Bitcoin(args) => GenericCallResult::Bitcoin(client.call(args).await?), - _ => anyhow::bail!("invalid call"), - }, Self::Ethereum(client) => match req { GenericCall::Ethereum(args) => { GenericCallResult::Ethereum(client.call(args).await?) }, - _ => anyhow::bail!("invalid call"), + GenericCall::Polkadot(_) => anyhow::bail!("invalid call"), }, Self::Astar(client) => match req { GenericCall::Ethereum(args) => { GenericCallResult::Ethereum(client.call(args).await?) }, - _ => anyhow::bail!("invalid call"), + GenericCall::Polkadot(_) => anyhow::bail!("invalid call"), }, Self::Polkadot(client) => match req { GenericCall::Polkadot(args) => { GenericCallResult::Polkadot(client.call(args).await?) }, - _ => anyhow::bail!("invalid call"), + GenericCall::Ethereum(_) => anyhow::bail!("invalid call"), }, }; Ok(result) diff --git a/rosetta-client/src/lib.rs b/rosetta-client/src/lib.rs index 1207bc72..1610041d 100644 --- a/rosetta-client/src/lib.rs +++ b/rosetta-client/src/lib.rs @@ -17,8 +17,6 @@ pub use signer::Signer; /// Supported chains. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Blockchain { - /// Bitcoin - Bitcoin, /// Ethereum Ethereum, /// Astar @@ -44,7 +42,6 @@ impl std::str::FromStr for Blockchain { fn from_str(blockchain: &str) -> Result { Ok(match blockchain { - "bitcoin" => Self::Bitcoin, "ethereum" => Self::Ethereum, "astar" => Self::Astar, "polkadot" => Self::Polkadot, diff --git a/rosetta-client/src/traits.rs b/rosetta-client/src/traits.rs deleted file mode 100644 index 4effe4c8..00000000 --- a/rosetta-client/src/traits.rs +++ /dev/null @@ -1,75 +0,0 @@ -#![allow(dead_code)] - -use serde::de::DeserializeOwned; -use serde::Serialize; -use std::future::Future; - -pub trait Query { - type Params: Serialize + Clone; - type Response: DeserializeOwned + Sized + Send + Sync + 'static; - type Error: DeserializeOwned + Sized + Send + Sync + 'static; - - fn query(&self, request: &Self::Params) -> Result; -} - -pub trait DefaultQuery { - type Balance: Query; -} - -/// Base Ledger Config -pub trait BaseConfig { - type Address; - type Transaction; - - /// The native currency of the blockchain - type MainCurrency: FungibleAssetConfig; -} - -pub enum AtBlock { - /// The latest block with at least 1 confirmation - Latest, - /// The earliest block - Earliest, - /// The pending block, may not yet included in the blockchain - Pending, - /// The block with the given height - Number(T::BlockNumber), - /// The block with the given unique identifier - At(T::BlockIdentifier), -} - -/// A blockchain have the concept of blocks -pub trait BlockchainConfig: BaseConfig { - type BlockIdentifier; - type BlockNumber: num_traits::Unsigned + num_traits::Bounded; - - /// The genesis block identifier - const GENESIS_BLOCK_IDENTIFIER: Self::BlockIdentifier; - - /// The forks of the blockchain, empty if there is no fork - const FORKED_BLOCKS: [(Self::BlockNumber, Self::BlockIdentifier)]; -} - -/// The blockchain have a native currency -pub trait FungibleAssetConfig { - const SYMBOL: &'static str; - const DECIMALS: u8; - type Balance: num_traits::Unsigned + num_traits::Bounded; -} - -pub trait BlockchainClient { - type Error; - - type InspectBalance: InspectBalance; -} - -/// Trait for providing balance-inspection access to a fungible asset. -pub trait InspectBalance: Sized { - type Error: Into; - - type Future: Future::Balance, Self::Error>> - + Unpin; - - /// Returns the balance of the given account. - fn balance_of(&self, account: T::Address, at: AtBlock) -> Self::Future; -} diff --git a/rosetta-client/src/tx_builder.rs b/rosetta-client/src/tx_builder.rs index e722e0d9..bed506a2 100644 --- a/rosetta-client/src/tx_builder.rs +++ b/rosetta-client/src/tx_builder.rs @@ -23,7 +23,7 @@ impl GenericTransactionBuilder { "polkadot" | "westend" | "rococo" => { Self::Polkadot(rosetta_tx_polkadot::PolkadotTransactionBuilder) }, - _ => anyhow::bail!("unsupported blockchain"), + _ => anyhow::bail!("unsupported blockchain: {}", config.blockchain), }) } diff --git a/rosetta-client/src/wallet.rs b/rosetta-client/src/wallet.rs index 03b81a9f..f153b59a 100644 --- a/rosetta-client/src/wallet.rs +++ b/rosetta-client/src/wallet.rs @@ -1,17 +1,14 @@ use crate::{ - client::{GenericClient, GenericMetadata, GenericMetadataParams}, + client::{GenericBlockIdentifier, GenericClient, GenericMetadata, GenericMetadataParams}, crypto::{address::Address, bip32::DerivedSecretKey, bip44::ChildNumber}, mnemonic::MnemonicStore, signer::{RosettaAccount, RosettaPublicKey, Signer}, tx_builder::GenericTransactionBuilder, - types::{AccountIdentifier, Amount, BlockIdentifier, Coin, PublicKey, TransactionIdentifier}, + types::{AccountIdentifier, BlockIdentifier, PublicKey}, Blockchain, BlockchainConfig, }; use anyhow::Result; -use rosetta_core::{ - types::{Block, PartialBlockIdentifier, Transaction}, - BlockchainClient, RosettaAlgorithm, -}; +use rosetta_core::{types::PartialBlockIdentifier, BlockchainClient, RosettaAlgorithm}; use rosetta_server_ethereum::config::{ ethereum_types::{self, Address as EthAddress, H256, U256}, AtBlock, CallContract, CallResult, EIP1186ProofResponse, GetProof, GetStorageAt, @@ -101,21 +98,47 @@ impl Wallet { /// Returns the latest finalized block identifier. #[allow(clippy::missing_errors_doc)] pub async fn status(&self) -> Result { - self.client.finalized_block().await + // self.client.finalized_block().await + match &self.client { + GenericClient::Astar(client) => client.finalized_block().await, + GenericClient::Ethereum(client) => client.finalized_block().await, + GenericClient::Polkadot(client) => client.finalized_block().await, + } } /// Returns the balance of the wallet. #[allow(clippy::missing_errors_doc)] - pub async fn balance(&self) -> Result { + pub async fn balance(&self) -> Result { let block = self.client.current_block().await?; let address = Address::new(self.client.config().address_format, self.account.address.clone()); - let balance = self.client.balance(&address, &block).await?; - Ok(Amount { - value: format!("{balance}"), - currency: self.client.config().currency(), - metadata: None, - }) + let balance = match &self.client { + GenericClient::Astar(client) => match block { + GenericBlockIdentifier::Ethereum(block) => { + client.balance(&address, &PartialBlockIdentifier::from(block)).await? + }, + GenericBlockIdentifier::Polkadot(_) => { + anyhow::bail!("[this is bug] client returned an invalid block identifier") + }, + }, + GenericClient::Ethereum(client) => match block { + GenericBlockIdentifier::Ethereum(block) => { + client.balance(&address, &PartialBlockIdentifier::from(block)).await? + }, + GenericBlockIdentifier::Polkadot(_) => { + anyhow::bail!("[this is bug] client returned an invalid block identifier") + }, + }, + GenericClient::Polkadot(client) => match block { + GenericBlockIdentifier::Polkadot(block) => { + client.balance(&address, &PartialBlockIdentifier::from(block)).await? + }, + GenericBlockIdentifier::Ethereum(_) => { + anyhow::bail!("[this is bug] client returned an invalid block identifier") + }, + }, + }; + Ok(balance) } /// Return a stream of events, return None if the blockchain doesn't support events. @@ -126,35 +149,6 @@ impl Wallet { self.client.listen().await } - /// Returns block data - /// Takes `PartialBlockIdentifier` - #[allow(clippy::missing_errors_doc)] - pub async fn block(&self, data: PartialBlockIdentifier) -> Result { - self.client.block(&data).await - } - - /// Returns transactions included in a block - /// Parameters: - /// 1. `block_identifier`: `BlockIdentifier` containing block number and hash - /// 2. `tx_identifier`: `TransactionIdentifier` containing hash of transaction - #[allow(clippy::missing_errors_doc)] - pub async fn block_transaction( - &self, - block_identifer: BlockIdentifier, - tx_identifier: TransactionIdentifier, - ) -> Result { - self.client.block_transaction(&block_identifer, &tx_identifier).await - } - - /// Returns the coins of the wallet. - #[allow(clippy::missing_errors_doc)] - pub async fn coins(&self) -> Result> { - let block = self.client.current_block().await?; - let address = - Address::new(self.client.config().address_format, self.account.address.clone()); - self.client.coins(&address, &block).await - } - /// Returns the on chain metadata. /// Parameters: /// - `metadata_params`: the metadata parameters which we got from transaction builder. @@ -251,7 +245,7 @@ impl Wallet { match self.metadata(&metadata_params).await? { GenericMetadata::Ethereum(metadata) => metadata, GenericMetadata::Astar(metadata) => metadata.0, - _ => anyhow::bail!("unsupported op"), + GenericMetadata::Polkadot(_) => anyhow::bail!("unsupported op"), }; Ok(rosetta_tx_ethereum::U256(metadata.gas_limit).as_u128()) } @@ -276,7 +270,6 @@ impl Wallet { GenericClient::Ethereum(client) => client.call(&EthQuery::CallContract(call)).await?, GenericClient::Astar(client) => client.call(&EthQuery::CallContract(call)).await?, GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_view_call"), - GenericClient::Bitcoin(_) => anyhow::bail!("bitcoin doesn't support eth_view_call"), }; let EthQueryResult::CallContract(exit_reason) = result else { anyhow::bail!("[this is a bug] invalid result type"); @@ -304,7 +297,6 @@ impl Wallet { client.call(&EthQuery::GetStorageAt(get_storage)).await? }, GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_storage"), - GenericClient::Bitcoin(_) => anyhow::bail!("bitcoin doesn't support eth_storage"), }; let EthQueryResult::GetStorageAt(value) = result else { anyhow::bail!("[this is a bug] invalid result type"); @@ -331,7 +323,6 @@ impl Wallet { GenericClient::Ethereum(client) => client.call(&EthQuery::GetProof(get_proof)).await?, GenericClient::Astar(client) => client.call(&EthQuery::GetProof(get_proof)).await?, GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_storage"), - GenericClient::Bitcoin(_) => anyhow::bail!("bitcoin doesn't support eth_storage"), }; let EthQueryResult::GetProof(proof) = result else { anyhow::bail!("[this is a bug] invalid result type"); @@ -355,7 +346,6 @@ impl Wallet { client.call(&EthQuery::GetTransactionReceipt(get_tx_receipt)).await? }, GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_storage"), - GenericClient::Bitcoin(_) => anyhow::bail!("bitcoin doesn't support eth_storage"), }; let EthQueryResult::GetTransactionReceipt(maybe_receipt) = result else { anyhow::bail!("[this is a bug] invalid result type"); @@ -373,7 +363,6 @@ impl Wallet { GenericClient::Ethereum(client) => client.call(&EthQuery::ChainId).await?, GenericClient::Astar(client) => client.call(&EthQuery::ChainId).await?, GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_chainId"), - GenericClient::Bitcoin(_) => anyhow::bail!("bitcoin doesn't support eth_chainId"), }; let EthQueryResult::ChainId(value) = result else { anyhow::bail!("[this is a bug] invalid result type"); diff --git a/rosetta-core/Cargo.toml b/rosetta-core/Cargo.toml index 3fd1ef6b..9a3efbb8 100644 --- a/rosetta-core/Cargo.toml +++ b/rosetta-core/Cargo.toml @@ -9,7 +9,7 @@ description = "Provides traits and definitions shared by the server and client c [dependencies] anyhow = "1.0" async-trait = "0.1" -fluent-uri = "0.1.4" +fluent-uri = "0.1" futures-util = "0.3" rosetta-crypto.workspace = true rosetta-types.workspace = true diff --git a/rosetta-core/src/lib.rs b/rosetta-core/src/lib.rs index e9e69b00..6ee421e0 100644 --- a/rosetta-core/src/lib.rs +++ b/rosetta-core/src/lib.rs @@ -1,14 +1,13 @@ mod node_uri; +pub mod traits; +pub mod types; use crate::{ crypto::{ address::{Address, AddressFormat}, Algorithm, PublicKey, SecretKey, }, - types::{ - Block, BlockIdentifier, Coin, Currency, CurveType, NetworkIdentifier, - PartialBlockIdentifier, SignatureType, Transaction, TransactionIdentifier, - }, + types::{Block, BlockIdentifier, CurveType, SignatureType}, }; use anyhow::Result; use async_trait::async_trait; @@ -20,7 +19,7 @@ use futures_util::stream::Empty; pub use node_uri::{NodeUri, NodeUriError}; pub use rosetta_crypto as crypto; -pub use rosetta_types as types; +// pub use rosetta_types as types; type NodeCommand = Arc Vec + Send + Sync + 'static>; @@ -44,36 +43,6 @@ pub struct BlockchainConfig { pub testnet: bool, } -impl BlockchainConfig { - #[must_use] - pub fn network(&self) -> NetworkIdentifier { - NetworkIdentifier { - blockchain: self.blockchain.into(), - network: self.network.into(), - sub_network_identifier: None, - } - } - - #[must_use] - pub fn currency(&self) -> Currency { - Currency { - symbol: self.currency_symbol.into(), - decimals: self.currency_decimals, - metadata: None, - } - } - - #[must_use] - pub fn node_url(&self) -> String { - self.node_uri.with_host("rosetta.analog.one").to_string() - } - - #[must_use] - pub fn connector_url(&self) -> String { - format!("http://rosetta.analog.one:{}", self.connector_port) - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub enum BlockOrIdentifier { Identifier(BlockIdentifier), @@ -116,13 +85,19 @@ pub trait BlockchainClient: Sized + Send + Sync + 'static { type Call: Send + Sync + Sized + 'static; type CallResult: Send + Sync + Sized + 'static; + type AtBlock: Clone + Send + Sync + Sized + Eq + From + 'static; + type BlockIdentifier: Clone + Send + Sync + Sized + Eq + 'static; + + type Query: traits::Query; + type Transaction: Clone + Send + Sync + Sized + Eq + 'static; + + async fn query(&self, query: Self::Query) -> Result<::Result>; + fn config(&self) -> &BlockchainConfig; - fn genesis_block(&self) -> &BlockIdentifier; - async fn node_version(&self) -> Result; - async fn current_block(&self) -> Result; - async fn finalized_block(&self) -> Result; - async fn balance(&self, address: &Address, block: &BlockIdentifier) -> Result; - async fn coins(&self, address: &Address, block: &BlockIdentifier) -> Result>; + fn genesis_block(&self) -> Self::BlockIdentifier; + async fn current_block(&self) -> Result; + async fn finalized_block(&self) -> Result; + async fn balance(&self, address: &Address, block: &Self::AtBlock) -> Result; async fn faucet(&self, address: &Address, param: u128) -> Result>; async fn metadata( &self, @@ -130,12 +105,6 @@ pub trait BlockchainClient: Sized + Send + Sync + 'static { params: &Self::MetadataParams, ) -> Result; async fn submit(&self, transaction: &[u8]) -> Result>; - async fn block(&self, block: &PartialBlockIdentifier) -> Result; - async fn block_transaction( - &self, - block: &BlockIdentifier, - tx: &TransactionIdentifier, - ) -> Result; async fn call(&self, req: &Self::Call) -> Result; /// Return a stream of events, return None if the blockchain doesn't support events. @@ -155,27 +124,31 @@ where type Call = ::Call; type CallResult = ::CallResult; + type AtBlock = ::AtBlock; + type BlockIdentifier = ::BlockIdentifier; + + type Query = ::Query; + type Transaction = ::Transaction; + + async fn query(&self, query: Self::Query) -> Result<::Result> { + BlockchainClient::query(Self::as_ref(self), query).await + } + fn config(&self) -> &BlockchainConfig { BlockchainClient::config(Self::as_ref(self)) } - fn genesis_block(&self) -> &BlockIdentifier { + fn genesis_block(&self) -> Self::BlockIdentifier { BlockchainClient::genesis_block(Self::as_ref(self)) } - async fn node_version(&self) -> Result { - BlockchainClient::node_version(Self::as_ref(self)).await - } - async fn current_block(&self) -> Result { + async fn current_block(&self) -> Result { BlockchainClient::current_block(Self::as_ref(self)).await } - async fn finalized_block(&self) -> Result { + async fn finalized_block(&self) -> Result { BlockchainClient::finalized_block(Self::as_ref(self)).await } - async fn balance(&self, address: &Address, block: &BlockIdentifier) -> Result { + async fn balance(&self, address: &Address, block: &Self::AtBlock) -> Result { BlockchainClient::balance(Self::as_ref(self), address, block).await } - async fn coins(&self, address: &Address, block: &BlockIdentifier) -> Result> { - BlockchainClient::coins(Self::as_ref(self), address, block).await - } async fn faucet(&self, address: &Address, param: u128) -> Result> { BlockchainClient::faucet(Self::as_ref(self), address, param).await } @@ -189,16 +162,6 @@ where async fn submit(&self, transaction: &[u8]) -> Result> { BlockchainClient::submit(Self::as_ref(self), transaction).await } - async fn block(&self, block: &PartialBlockIdentifier) -> Result { - BlockchainClient::block(Self::as_ref(self), block).await - } - async fn block_transaction( - &self, - block: &BlockIdentifier, - tx: &TransactionIdentifier, - ) -> Result { - BlockchainClient::block_transaction(Self::as_ref(self), block, tx).await - } async fn call(&self, req: &Self::Call) -> Result { BlockchainClient::call(Self::as_ref(self), req).await } diff --git a/rosetta-core/src/traits.rs b/rosetta-core/src/traits.rs index f018e3fd..23ef8354 100644 --- a/rosetta-core/src/traits.rs +++ b/rosetta-core/src/traits.rs @@ -9,17 +9,6 @@ use core::{ /// Such a maybe-marker trait requires the given bound when `feature = std` and doesn't require /// the bound on `no_std`. This is useful for situations where you require that a type implements /// a certain trait with `feature = std`, but not on `no_std`. -/// -/// # Example -/// -/// ``` -/// sp_core::impl_maybe_marker! { -/// /// A marker for a type that implements `Debug` when `feature = std`. -/// trait MaybeDebug: std::fmt::Debug; -/// /// A marker for a type that implements `Debug + Display` when `feature = std`. -/// trait MaybeDebugDisplay: std::fmt::Debug, std::fmt::Display; -/// } -/// ``` macro_rules! impl_maybe_marker { ( $( @@ -55,8 +44,8 @@ impl_maybe_marker!( ); /// A type that can be used in runtime structures. -pub trait Member: Send + Sync + Sized + Debug + Eq + PartialEq + Clone + 'static {} -impl Member for T {} +pub trait Member: Debug + Send + Sync + Sized + PartialEq + Eq + Clone + 'static {} +impl Member for T {} /// Super trait with all the attributes for a hashing output. pub trait HashOutput: @@ -64,30 +53,108 @@ pub trait HashOutput: { } -pub trait Header: Clone + Send + Sync + Eq + Debug + 'static { - /// Header number. - type Number; +pub type BlockNumber = u64; +pub trait Header: Clone + Send + Sync + Eq + Debug + 'static { /// Header hash type type Hash: HashOutput; + + /// Returns the header block number. + fn number(&self) -> BlockNumber; + + /// Returns the hash of the header. + fn hash(&self) -> Self::Hash; +} + +/// Something that acts like a [`SignaturePayload`](Extrinsic::SignaturePayload) of an +/// [`Transaction`]. +pub trait SignaturePayload { + /// The type of the address that signed the extrinsic. + /// + /// Particular to a signed extrinsic. + type SignatureAddress; + + /// The signature type of the extrinsic. + /// + /// Particular to a signed extrinsic. + type Signature; + + /// The additional data that is specific to the signed extrinsic. + /// + /// Particular to a signed extrinsic. + type SignatureExtra; +} + +impl SignaturePayload for () { + type SignatureAddress = (); + type Signature = (); + type SignatureExtra = (); } /// Something that acts like an `Extrinsic`. pub trait Transaction: Sized { /// The function call. type Call; + + /// The payload we carry for signed transactions. + /// + /// Usually it will contain a `Signature` and + /// may include some additional data that are specific to signed + /// transaction. + type SignaturePayload: SignaturePayload; + + /// Is this `Extrinsic` signed? + /// If no information are available about signed/unsigned, `None` should be returned. + fn is_signed(&self) -> Option { + None + } + + /// Create new instance of the extrinsic. + /// + /// Extrinsics can be split into: + /// 1. Inherents (no signature; created by validators during block production) + /// 2. Unsigned Transactions (no signature; represent "system calls" or other special kinds of + /// calls) 3. Signed Transactions (with signature; a regular transactions with known origin) + fn new(_call: Self::Call, _signed_data: Option) -> Option { + None + } } -pub trait Block { +pub trait Block: Clone + Send + Sync + Eq + Debug + 'static { /// Type for extrinsics. type Transaction: Member + Transaction; /// Header type. type Header: Header; /// Block hash type. type Hash: HashOutput; + + /// Returns a reference to the header. + fn header(&self) -> &Self::Header; + + /// Returns a reference to the list of transactions. + fn transactions(&self) -> &[Self::Transaction]; + + /// Returns the hash of the block. + fn hash(&self) -> Self::Hash; } -pub trait BlockchainPrimitives { - type Block: Clone + Send + Sync + 'static; +pub trait BlockchainConfig { + const NAME: &'static str; + const SYMBOL: &'static str; + const BIP44: u32; + const DEV: bool; + + const CHECKPOINT: Option<(BlockNumber, <::Header as Header>::Hash)>; + + type Block: Block; type Transaction: Clone + Send + Sync + 'static; + type UnsignedTransaction: Clone + Send + Sync + 'static; +} + +pub trait Query: Member { + type Result: Member; +} + +impl Query for () { + type Result = (); } diff --git a/rosetta-core/src/types.rs b/rosetta-core/src/types.rs index 377169c2..7453d7ca 100644 --- a/rosetta-core/src/types.rs +++ b/rosetta-core/src/types.rs @@ -7,15 +7,11 @@ use core::{ use serde::{Deserialize, Serialize}; pub use rosetta_types::{ - AccountIdentifier, Amount, BlockIdentifier, NetworkIdentifier, Operation, OperationIdentifier, - PartialBlockIdentifier, SignatureType, TransactionIdentifier, + AccountIdentifier, CallRequest, CurveType, Operation, OperationIdentifier, PublicKey, + SignatureType, TransactionIdentifier, }; -#[cfg(feature = "std")] -use std::{string::ToString, vec::Vec}; - -#[cfg(not(feature = "std"))] -use alloc::{string::ToString, vec::Vec}; +use std::vec::Vec; /// Block : Blocks contain an array of Transactions that occurred at a particular `BlockIdentifier`. /// A hard requirement for blocks returned by Rosetta implementations is that they MUST be @@ -38,56 +34,50 @@ pub struct Block { pub metadata: Option, } -/// `Currency` is composed of a canonical Symbol and Decimals. This Decimals value is used to -/// convert an Amount.Value from atomic units (Satoshis) to standard units (Bitcoins). +/// `BlockIdentifier` : The `block_identifier` uniquely identifies a block in a particular network. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct Currency { - /// Canonical symbol associated with a currency. - pub symbol: &'static str, - /// Number of decimal places in the standard unit representation of the amount. For example, - /// BTC has 8 decimals. Note that it is not possible to represent the value of some currency in - /// atomic units that is not base 10. - pub decimals: u32, +pub struct BlockIdentifier { + /// This is also known as the block height. + #[serde(rename = "index")] + pub index: u64, + /// This should be normalized according to the case specified in the block_hash_case network + /// options. + #[serde(skip_serializing)] + pub hash: [u8; 32], } -/// `CurveType` is the type of cryptographic curve associated with a `PublicKey`. -/// * [secp256k1: SEC compressed - `33 bytes`](https://secg.org/sec1-v2.pdf#subsubsection.2.3.3) -/// * [secp256r1: SEC compressed - `33 bytes`](https://secg.org/sec1-v2.pdf#subsubsection.2.3.3) -/// * [edwards25519: `y (255-bits) || x-sign-bit (1-bit)` - `32 bytes`](https://ed25519.cr.yp.to/ed25519-20110926.pdf) -/// * [tweedle: 1st pk : Fq.t (32 bytes) || 2nd pk : Fq.t (32 bytes)](https://github.com/CodaProtocol/coda/blob/develop/rfcs/0038-rosetta-construction-api.md#marshal-keys) -/// * [pallas: `x (255 bits) || y-parity-bit (1-bit) - 32 bytes`](https://github.com/zcash/pasta) -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] -pub enum CurveType { - #[serde(rename = "secp256k1")] - Secp256k1, - #[serde(rename = "secp256r1")] - Secp256r1, - #[serde(rename = "edwards25519")] - Edwards25519, - #[serde(rename = "tweedle")] - Tweedle, - #[serde(rename = "pallas")] - Pallas, - #[serde(rename = "schnorrkel")] - Schnorrkel, +impl BlockIdentifier { + /// The `block_identifier` uniquely identifies a block in a particular network. + #[must_use] + pub const fn new(index: u64, hash: [u8; 32]) -> Self { + Self { index, hash } + } +} + +/// `PartialBlockIdentifier` : When fetching data by `BlockIdentifier`, it may be possible to only +/// specify the index or hash. If neither property is specified, it is assumed that the client is +/// making a request at the current block. +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct PartialBlockIdentifier { + #[serde(rename = "index", skip_serializing_if = "Option::is_none")] + pub index: Option, + #[serde(skip_serializing)] + pub hash: Option<[u8; 32]>, } -impl ToString for CurveType { - fn to_string(&self) -> String { - match self { - Self::Secp256k1 => String::from("secp256k1"), - Self::Secp256r1 => String::from("secp256r1"), - Self::Edwards25519 => String::from("edwards25519"), - Self::Tweedle => String::from("tweedle"), - Self::Pallas => String::from("pallas"), - Self::Schnorrkel => String::from("schnorrkel"), - } +impl From for PartialBlockIdentifier { + fn from(block_identifier: BlockIdentifier) -> Self { + Self { index: Some(block_identifier.index), hash: Some(block_identifier.hash) } } } -impl Default for CurveType { - fn default() -> Self { - Self::Secp256k1 +impl PartialBlockIdentifier { + /// When fetching data by `BlockIdentifier`, it may be possible to only specify the index or + /// hash. If neither property is specified, it is assumed that the client is making a request at + /// the current block. + #[must_use] + pub const fn new() -> Self { + Self { index: None, hash: None } } } diff --git a/rosetta-docker/src/lib.rs b/rosetta-docker/src/lib.rs index a6473f15..7885627b 100644 --- a/rosetta-docker/src/lib.rs +++ b/rosetta-docker/src/lib.rs @@ -310,11 +310,13 @@ async fn wait_for_http + Send>(url: S, container: &Container) -> R }) } +/// Helper function to run a test and shutdown docker containers regardless if the test panics or +/// not #[allow(clippy::future_not_send)] pub async fn run_test(env: Env, cb: F) where T: Sync + Send + 'static + rosetta_core::BlockchainClient, - Fut: Future> + Send + 'static, + Fut: Future + Send + 'static, F: FnOnce(&'static mut Env) -> Fut + Sync + Send, { // Convert the context into a raw pointer @@ -343,7 +345,10 @@ pub mod tests { use super::Env; use anyhow::{Ok, Result}; use nanoid::nanoid; - use rosetta_core::{types::PartialBlockIdentifier, BlockchainClient, BlockchainConfig}; + use rosetta_core::{ + types::{BlockIdentifier, PartialBlockIdentifier}, + BlockchainClient, BlockchainConfig, + }; use std::future::Future; fn env_id() -> String { @@ -353,13 +358,18 @@ pub mod tests { ) } - #[allow(clippy::missing_panics_doc, clippy::missing_errors_doc, clippy::future_not_send)] + #[allow( + clippy::missing_panics_doc, + clippy::unwrap_used, + clippy::missing_errors_doc, + clippy::future_not_send + )] pub async fn network_status( start_connector: F, config: BlockchainConfig, ) -> Result<()> where - T: BlockchainClient, + T: BlockchainClient, Fut: Future> + Send, F: FnMut(BlockchainConfig) -> Fut + Send, { @@ -371,43 +381,34 @@ pub mod tests { let client = env.node(); // Check if the genesis is consistent - let expected_genesis = client.genesis_block().clone(); - let actual_genesis = client - .block(&PartialBlockIdentifier { index: Some(0), hash: None }) - .await? - .block_identifier; - assert_eq!(expected_genesis, actual_genesis); + let genesis_block = client.genesis_block(); + assert_eq!(genesis_block.index, 0); + // Check if the current block is consistent - let expected_current = client.current_block().await?; - let actual_current = client - .block(&PartialBlockIdentifier { - index: None, - hash: Some(expected_current.hash.clone()), - }) - .await? - .block_identifier; - assert_eq!(expected_current, actual_current); + let current_block = client.current_block().await.unwrap(); + if current_block.index > 0 { + assert_ne!(current_block.hash, genesis_block.hash); + } else { + assert_eq!(current_block.hash, genesis_block.hash); + } // Check if the finalized block is consistent - let expected_finalized = client.finalized_block().await?; - let actual_finalized = client - .block(&PartialBlockIdentifier { - index: None, - hash: Some(expected_finalized.hash.clone()), - }) - .await? - .block_identifier; - assert_eq!(expected_finalized, actual_finalized); - Ok(()) + let finalized_block = client.finalized_block().await.unwrap(); + assert!(finalized_block.index >= genesis_block.index); }) .await; Ok(()) } - #[allow(clippy::missing_panics_doc, clippy::missing_errors_doc, clippy::future_not_send)] + #[allow( + clippy::missing_panics_doc, + clippy::unwrap_used, + clippy::missing_errors_doc, + clippy::future_not_send + )] pub async fn account(start_connector: F, config: BlockchainConfig) -> Result<()> where - T: BlockchainClient, + T: BlockchainClient, Fut: Future> + Send, F: FnMut(BlockchainConfig) -> Fut + Send, { @@ -415,22 +416,24 @@ pub mod tests { let env = Env::new(&format!("{env_id}-account"), config.clone(), start_connector).await?; crate::run_test(env, |env| async move { let value = 100 * u128::pow(10, config.currency_decimals); - let wallet = env.ephemeral_wallet().await?; - wallet.faucet(value).await?; - let amount = wallet.balance().await?; - assert_eq!(amount.value, value.to_string()); - assert_eq!(amount.currency, config.currency()); - assert!(amount.metadata.is_none()); - Ok(()) + let wallet = env.ephemeral_wallet().await.unwrap(); + wallet.faucet(value).await.unwrap(); + let balance = wallet.balance().await.unwrap(); + assert_eq!(balance, value); }) .await; Ok(()) } - #[allow(clippy::missing_panics_doc, clippy::missing_errors_doc, clippy::future_not_send)] + #[allow( + clippy::missing_panics_doc, + clippy::unwrap_used, + clippy::missing_errors_doc, + clippy::future_not_send + )] pub async fn construction(start_connector: F, config: BlockchainConfig) -> Result<()> where - T: BlockchainClient, + T: BlockchainClient, Fut: Future> + Send, F: FnMut(BlockchainConfig) -> Fut + Send, { @@ -441,26 +444,25 @@ pub mod tests { crate::run_test(env, |env| async move { let faucet = 100 * u128::pow(10, config.currency_decimals); let value = u128::pow(10, config.currency_decimals); - let alice = env.ephemeral_wallet().await?; - let bob = env.ephemeral_wallet().await?; + let alice = env.ephemeral_wallet().await.unwrap(); + let bob = env.ephemeral_wallet().await.unwrap(); assert_ne!(alice.public_key(), bob.public_key()); // Alice and bob have no balance - let balance = alice.balance().await?; - assert_eq!(balance.value, "0"); - let balance = bob.balance().await?; - assert_eq!(balance.value, "0"); + let balance = alice.balance().await.unwrap(); + assert_eq!(balance, 0); + let balance = bob.balance().await.unwrap(); + assert_eq!(balance, 0); // Transfer faucets to alice - alice.faucet(faucet).await?; - let balance = alice.balance().await?; - assert_eq!(balance.value, faucet.to_string()); + alice.faucet(faucet).await.unwrap(); + let balance = alice.balance().await.unwrap(); + assert_eq!(balance, faucet); // Alice transfers to bob - alice.transfer(bob.account(), value).await?; - let amount = bob.balance().await?; - assert_eq!(amount.value, value.to_string()); - Ok(()) + alice.transfer(bob.account(), value).await.unwrap(); + let amount = bob.balance().await.unwrap(); + assert_eq!(amount, value); }) .await; Ok(()) diff --git a/rosetta-server/build.rs b/rosetta-server/build.rs deleted file mode 100644 index 88e78920..00000000 --- a/rosetta-server/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -use anyhow::Result; -use vergen::EmitBuilder; - -fn main() -> Result<()> { - EmitBuilder::builder().all_git().emit()?; - Ok(()) -} diff --git a/rosetta-server/src/ws/reconnect_impl.rs b/rosetta-server/src/ws/reconnect_impl.rs index 773f30a0..e1286f88 100644 --- a/rosetta-server/src/ws/reconnect_impl.rs +++ b/rosetta-server/src/ws/reconnect_impl.rs @@ -1,4 +1,3 @@ -#![allow(dead_code)] use super::reconnect::Reconnect; use futures_timer::Delay; use futures_util::{ diff --git a/rosetta-types/src/account_balance_request.rs b/rosetta-types/src/account_balance_request.rs deleted file mode 100644 index 317c2135..00000000 --- a/rosetta-types/src/account_balance_request.rs +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `AccountBalanceRequest` : An `AccountBalanceRequest` is utilized to make a balance request on -/// the /account/balance endpoint. If the `block_identifier` is populated, a historical balance -/// query should be performed. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct AccountBalanceRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, - #[serde(rename = "account_identifier")] - pub account_identifier: crate::AccountIdentifier, - #[serde(rename = "block_identifier", skip_serializing_if = "Option::is_none")] - pub block_identifier: Option, - /// In some cases, the caller may not want to retrieve all available balances for an - /// AccountIdentifier. If the currencies field is populated, only balances for the specified - /// currencies will be returned. If not populated, all available balances will be returned. - #[serde(rename = "currencies", skip_serializing_if = "Option::is_none")] - pub currencies: Option>, -} - -impl AccountBalanceRequest { - /// An `AccountBalanceRequest` is utilized to make a balance request on the /account/balance - /// endpoint. If the `block_identifier` is populated, a historical balance query should be - /// performed. - #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - account_identifier: crate::AccountIdentifier, - ) -> Self { - Self { network_identifier, account_identifier, block_identifier: None, currencies: None } - } -} diff --git a/rosetta-types/src/account_balance_response.rs b/rosetta-types/src/account_balance_response.rs deleted file mode 100644 index b4f5599d..00000000 --- a/rosetta-types/src/account_balance_response.rs +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `AccountBalanceResponse` : An `AccountBalanceResponse` is returned on the /account/balance -/// endpoint. If an account has a balance for each `AccountIdentifier` describing it (ex: an ERC-20 -/// token balance on a few smart contracts), an account balance request must be made with each -/// `AccountIdentifier`. The `coins` field was removed and replaced by by `/account/coins` in -/// `v1.4.7`. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct AccountBalanceResponse { - #[serde(rename = "block_identifier")] - pub block_identifier: crate::BlockIdentifier, - /// A single account may have a balance in multiple currencies. - #[serde(rename = "balances")] - pub balances: Vec, - /// Account-based blockchains that utilize a nonce or sequence number should include that - /// number in the metadata. This number could be unique to the identifier or global across the - /// account address. - #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl AccountBalanceResponse { - /// An `AccountBalanceResponse` is returned on the /account/balance endpoint. If an account has - /// a balance for each `AccountIdentifier` describing it (ex: an ERC-20 token balance on a few - /// smart contracts), an account balance request must be made with each `AccountIdentifier`. - /// The `coins` field was removed and replaced by by `/account/coins` in `v1.4.7`. - #[must_use] - pub const fn new( - block_identifier: crate::BlockIdentifier, - balances: Vec, - ) -> Self { - Self { block_identifier, balances, metadata: None } - } -} diff --git a/rosetta-types/src/account_coins_request.rs b/rosetta-types/src/account_coins_request.rs deleted file mode 100644 index cdf908e4..00000000 --- a/rosetta-types/src/account_coins_request.rs +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `AccountCoinsRequest` : `AccountCoinsRequest` is utilized to make a request on the -/// /account/coins endpoint. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct AccountCoinsRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, - #[serde(rename = "account_identifier")] - pub account_identifier: crate::AccountIdentifier, - /// Include state from the mempool when looking up an account's unspent coins. Note, using this - /// functionality breaks any guarantee of idempotency. - #[serde(rename = "include_mempool")] - pub include_mempool: bool, - /// In some cases, the caller may not want to retrieve coins for all currencies for an - /// AccountIdentifier. If the currencies field is populated, only coins for the specified - /// currencies will be returned. If not populated, all unspent coins will be returned. - #[serde(rename = "currencies", skip_serializing_if = "Option::is_none")] - pub currencies: Option>, -} - -impl AccountCoinsRequest { - /// `AccountCoinsRequest` is utilized to make a request on the /account/coins endpoint. - #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - account_identifier: crate::AccountIdentifier, - include_mempool: bool, - ) -> Self { - Self { network_identifier, account_identifier, include_mempool, currencies: None } - } -} diff --git a/rosetta-types/src/account_coins_response.rs b/rosetta-types/src/account_coins_response.rs deleted file mode 100644 index a89ac5af..00000000 --- a/rosetta-types/src/account_coins_response.rs +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `AccountCoinsResponse` : `AccountCoinsResponse` is returned on the /account/coins endpoint and -/// includes all unspent Coins owned by an `AccountIdentifier`. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct AccountCoinsResponse { - #[serde(rename = "block_identifier")] - pub block_identifier: crate::BlockIdentifier, - /// If a blockchain is UTXO-based, all unspent Coins owned by an account_identifier should be - /// returned alongside the balance. It is highly recommended to populate this field so that - /// users of the Rosetta API implementation don't need to maintain their own indexer to track - /// their UTXOs. - #[serde(rename = "coins")] - pub coins: Vec, - /// Account-based blockchains that utilize a nonce or sequence number should include that - /// number in the metadata. This number could be unique to the identifier or global across the - /// account address. - #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl AccountCoinsResponse { - /// `AccountCoinsResponse` is returned on the /account/coins endpoint and includes all unspent - /// Coins owned by an `AccountIdentifier`. - #[must_use] - pub const fn new(block_identifier: crate::BlockIdentifier, coins: Vec) -> Self { - Self { block_identifier, coins, metadata: None } - } -} diff --git a/rosetta-types/src/account_faucet_request.rs b/rosetta-types/src/account_faucet_request.rs deleted file mode 100644 index b91c9528..00000000 --- a/rosetta-types/src/account_faucet_request.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::{AccountIdentifier, NetworkIdentifier}; - -/// `AccountFaucetRequest` : `AccountFaucetRequest` is sent for faucet on an account. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct AccountFaucetRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: NetworkIdentifier, - #[serde(rename = "account_identifier")] - pub account_identifier: AccountIdentifier, - #[serde(rename = "faucet_parameter")] - pub faucet_parameter: u128, -} - -impl AccountFaucetRequest { - /// `AccountCoinsRequest` is utilized to make a request on the /account/coins endpoint. - #[must_use] - pub const fn new( - network_identifier: NetworkIdentifier, - account_identifier: AccountIdentifier, - faucet_parameter: u128, - ) -> Self { - Self { network_identifier, account_identifier, faucet_parameter } - } -} diff --git a/rosetta-types/src/allow.rs b/rosetta-types/src/allow.rs deleted file mode 100644 index e638658c..00000000 --- a/rosetta-types/src/allow.rs +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// Allow : Allow specifies supported Operation status, Operation types, and all possible error -/// statuses. This Allow object is used by clients to validate the correctness of a Rosetta Server -/// implementation. It is expected that these clients will error if they receive some response that -/// contains any of the above information that is not specified here. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct Allow { - /// All Operation.Status this implementation supports. Any status that is returned during - /// parsing that is not listed here will cause client validation to error. - #[serde(rename = "operation_statuses")] - pub operation_statuses: Vec, - /// All Operation.Type this implementation supports. Any type that is returned during parsing - /// that is not listed here will cause client validation to error. - #[serde(rename = "operation_types")] - pub operation_types: Vec, - /// All Errors that this implementation could return. Any error that is returned during parsing - /// that is not listed here will cause client validation to error. - #[serde(rename = "errors")] - pub errors: Vec, - /// Any Rosetta implementation that supports querying the balance of an account at any height - /// in the past should set this to true. - #[serde(rename = "historical_balance_lookup")] - pub historical_balance_lookup: bool, - /// If populated, `timestamp_start_index` indicates the first block index where block - /// timestamps are considered valid (i.e. all blocks less than `timestamp_start_index` could - /// have invalid timestamps). This is useful when the genesis block (or blocks) of a network - /// have timestamp 0. If not populated, block timestamps are assumed to be valid for all - /// available blocks. - #[serde(rename = "timestamp_start_index", skip_serializing_if = "Option::is_none")] - pub timestamp_start_index: Option, - /// All methods that are supported by the /call endpoint. Communicating which parameters should - /// be provided to /call is the responsibility of the implementer (this is en lieu of defining - /// an entire type system and requiring the implementer to define that in Allow). - #[serde(rename = "call_methods")] - pub call_methods: Option>, - /// BalanceExemptions is an array of BalanceExemption indicating which account balances could - /// change without a corresponding Operation. BalanceExemptions should be used sparingly as - /// they may introduce significant complexity for integrators that attempt to reconcile all - /// account balance changes. If your implementation relies on any BalanceExemptions, you MUST - /// implement historical balance lookup (the ability to query an account balance at any - /// BlockIdentifier). - #[serde(rename = "balance_exemptions")] - pub balance_exemptions: Option>, - /// Any Rosetta implementation that can update an AccountIdentifier's unspent coins based on - /// the contents of the mempool should populate this field as true. If false, requests to - /// `/account/coins` that set `include_mempool` as true will be automatically rejected. - #[serde(rename = "mempool_coins")] - pub mempool_coins: bool, - #[serde(rename = "block_hash_case", skip_serializing_if = "Option::is_none")] - pub block_hash_case: Option, - #[serde(rename = "transaction_hash_case", skip_serializing_if = "Option::is_none")] - pub transaction_hash_case: Option, -} - -impl Allow { - /// Allow specifies supported Operation status, Operation types, and all possible error - /// statuses. This Allow object is used by clients to validate the correctness of a Rosetta - /// Server implementation. It is expected that these clients will error if they receive some - /// response that contains any of the above information that is not specified here. - #[must_use] - pub const fn new( - operation_statuses: Vec, - operation_types: Vec, - errors: Vec, - historical_balance_lookup: bool, - call_methods: Option>, - balance_exemptions: Option>, - mempool_coins: bool, - ) -> Self { - Self { - operation_statuses, - operation_types, - errors, - historical_balance_lookup, - timestamp_start_index: None, - call_methods, - balance_exemptions, - mempool_coins, - block_hash_case: None, - transaction_hash_case: None, - } - } -} diff --git a/rosetta-types/src/balance_exemption.rs b/rosetta-types/src/balance_exemption.rs deleted file mode 100644 index cbaf9f9f..00000000 --- a/rosetta-types/src/balance_exemption.rs +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `BalanceExemption` : `BalanceExemption` indicates that the balance for an exempt account could -/// change without a corresponding Operation. This typically occurs with staking rewards, vesting -/// balances, and Currencies with a dynamic supply. Currently, it is possible to exempt an account -/// from strict reconciliation by SubAccountIdentifier.Address or by Currency. This means that any -/// account with SubAccountIdentifier.Address would be exempt or any balance of a particular -/// Currency would be exempt, respectively. `BalanceExemptions` should be used sparingly as they -/// may introduce significant complexity for integrators that attempt to reconcile all account -/// balance changes. If your implementation relies on any `BalanceExemptions`, you MUST implement -/// historical balance lookup (the ability to query an account balance at any `BlockIdentifier`). -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct BalanceExemption { - /// SubAccountAddress is the SubAccountIdentifier.Address that the BalanceExemption applies to - /// (regardless of the value of SubAccountIdentifier.Metadata). - #[serde(rename = "sub_account_address", skip_serializing_if = "Option::is_none")] - pub sub_account_address: Option, - #[serde(rename = "currency", skip_serializing_if = "Option::is_none")] - pub currency: Option, - #[serde(rename = "exemption_type", skip_serializing_if = "Option::is_none")] - pub exemption_type: Option, -} - -impl BalanceExemption { - /// `BalanceExemption` indicates that the balance for an exempt account could change without a - /// corresponding Operation. This typically occurs with staking rewards, vesting balances, and - /// Currencies with a dynamic supply. Currently, it is possible to exempt an account from - /// strict reconciliation by SubAccountIdentifier.Address or by Currency. This means that any - /// account with SubAccountIdentifier.Address would be exempt or any balance of a particular - /// Currency would be exempt, respectively. `BalanceExemptions` should be used sparingly as - /// they may introduce significant complexity for integrators that attempt to reconcile all - /// account balance changes. If your implementation relies on any `BalanceExemptions`, you MUST - /// implement historical balance lookup (the ability to query an account balance at any - /// `BlockIdentifier`). - #[must_use] - pub const fn new() -> Self { - Self { sub_account_address: None, currency: None, exemption_type: None } - } -} diff --git a/rosetta-types/src/block_event.rs b/rosetta-types/src/block_event.rs deleted file mode 100644 index 0ded5762..00000000 --- a/rosetta-types/src/block_event.rs +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `BlockEvent` : `BlockEvent` represents the addition or removal of a `BlockIdentifier` from -/// storage. Streaming `BlockEvents` allows lightweight clients to update their own state without -/// needing to implement their own syncing logic. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct BlockEvent { - /// sequence is the unique identifier of a BlockEvent within the context of a - /// NetworkIdentifier. - #[serde(rename = "sequence")] - pub sequence: i64, - #[serde(rename = "block_identifier")] - pub block_identifier: crate::BlockIdentifier, - #[serde(rename = "type")] - pub r#type: crate::BlockEventType, -} - -impl BlockEvent { - /// `BlockEvent` represents the addition or removal of a `BlockIdentifier` from storage. - /// Streaming `BlockEvents` allows lightweight clients to update their own state without needing - /// to implement their own syncing logic. - #[must_use] - pub const fn new( - sequence: i64, - block_identifier: crate::BlockIdentifier, - r#type: crate::BlockEventType, - ) -> Self { - Self { sequence, block_identifier, r#type } - } -} diff --git a/rosetta-types/src/block_event_type.rs b/rosetta-types/src/block_event_type.rs deleted file mode 100644 index 19c6c5bf..00000000 --- a/rosetta-types/src/block_event_type.rs +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `BlockEventType` : `BlockEventType` determines if a `BlockEvent` represents the addition or -/// removal of a block. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] -pub enum BlockEventType { - #[serde(rename = "block_added")] - Added, - #[serde(rename = "block_removed")] - Removed, -} - -impl ToString for BlockEventType { - fn to_string(&self) -> String { - match self { - Self::Added => String::from("block_added"), - Self::Removed => String::from("block_removed"), - } - } -} - -impl Default for BlockEventType { - fn default() -> Self { - Self::Added - } -} diff --git a/rosetta-types/src/block_identifier.rs b/rosetta-types/src/block_identifier.rs index 56600282..42937756 100644 --- a/rosetta-types/src/block_identifier.rs +++ b/rosetta-types/src/block_identifier.rs @@ -16,14 +16,14 @@ pub struct BlockIdentifier { pub index: u64, /// This should be normalized according to the case specified in the block_hash_case network /// options. - #[serde(rename = "hash")] - pub hash: String, + #[serde(skip_serializing)] + pub hash: [u8; 32], } impl BlockIdentifier { /// The `block_identifier` uniquely identifies a block in a particular network. #[must_use] - pub const fn new(index: u64, hash: String) -> Self { + pub const fn new(index: u64, hash: [u8; 32]) -> Self { Self { index, hash } } } diff --git a/rosetta-types/src/block_request.rs b/rosetta-types/src/block_request.rs deleted file mode 100644 index 2dc41918..00000000 --- a/rosetta-types/src/block_request.rs +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `BlockRequest` : A `BlockRequest` is utilized to make a block request on the /block endpoint. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct BlockRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, - #[serde(rename = "block_identifier")] - pub block_identifier: crate::PartialBlockIdentifier, -} - -impl BlockRequest { - /// A `BlockRequest` is utilized to make a block request on the /block endpoint. - #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - block_identifier: crate::PartialBlockIdentifier, - ) -> Self { - Self { network_identifier, block_identifier } - } -} diff --git a/rosetta-types/src/block_response.rs b/rosetta-types/src/block_response.rs deleted file mode 100644 index 10fb0a4e..00000000 --- a/rosetta-types/src/block_response.rs +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `BlockResponse` : A `BlockResponse` includes a fully-populated block or a partially-populated -/// block with a list of other transactions to fetch (`other_transactions`). As a result of the -/// consensus algorithm of some blockchains, blocks can be omitted (i.e. certain block indices can -/// be skipped). If a query for one of these omitted indices is made, the response should not -/// include a `Block` object. It is VERY important to note that blocks MUST still form a canonical, -/// connected chain of blocks where each block has a unique index. In other words, the -/// `PartialBlockIdentifier` of a block after an omitted block should reference the last non-omitted -/// block. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct BlockResponse { - #[serde(rename = "block", skip_serializing_if = "Option::is_none")] - pub block: Option, - /// Some blockchains may require additional transactions to be fetched that weren't returned in - /// the block response (ex: block only returns transaction hashes). For blockchains with a lot - /// of transactions in each block, this can be very useful as consumers can concurrently fetch - /// all transactions returned. - #[serde(rename = "other_transactions", skip_serializing_if = "Option::is_none")] - pub other_transactions: Option>, -} - -impl BlockResponse { - /// A `BlockResponse` includes a fully-populated block or a partially-populated block with a - /// list of other transactions to fetch (`other_transactions`). As a result of the consensus - /// algorithm of some blockchains, blocks can be omitted (i.e. certain block indices can be - /// skipped). If a query for one of these omitted indices is made, the response should not - /// include a `Block` object. It is VERY important to note that blocks MUST still form a - /// canonical, connected chain of blocks where each block has a unique index. In other words, - /// the `PartialBlockIdentifier` of a block after an omitted block should reference the last - /// non-omitted block. - #[must_use] - pub const fn new() -> Self { - Self { block: None, other_transactions: None } - } -} diff --git a/rosetta-types/src/block_transaction.rs b/rosetta-types/src/block_transaction.rs deleted file mode 100644 index 905ca87a..00000000 --- a/rosetta-types/src/block_transaction.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `BlockTransaction` : `BlockTransaction` contains a populated Transaction and the -/// `BlockIdentifier` that contains it. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct BlockTransaction { - #[serde(rename = "block_identifier")] - pub block_identifier: crate::BlockIdentifier, - #[serde(rename = "transaction")] - pub transaction: crate::Transaction, -} - -impl BlockTransaction { - /// `BlockTransaction` contains a populated Transaction and the `BlockIdentifier` that contains - /// it. - #[must_use] - pub const fn new( - block_identifier: crate::BlockIdentifier, - transaction: crate::Transaction, - ) -> Self { - Self { block_identifier, transaction } - } -} diff --git a/rosetta-types/src/block_transaction_request.rs b/rosetta-types/src/block_transaction_request.rs deleted file mode 100644 index f78ace76..00000000 --- a/rosetta-types/src/block_transaction_request.rs +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `BlockTransactionRequest` : A `BlockTransactionRequest` is used to fetch a Transaction included -/// in a block that is not returned in a `BlockResponse`. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct BlockTransactionRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, - #[serde(rename = "block_identifier")] - pub block_identifier: crate::BlockIdentifier, - #[serde(rename = "transaction_identifier")] - pub transaction_identifier: crate::TransactionIdentifier, -} - -impl BlockTransactionRequest { - /// A `BlockTransactionRequest` is used to fetch a Transaction included in a block that is not - /// returned in a `BlockResponse`. - #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - block_identifier: crate::BlockIdentifier, - transaction_identifier: crate::TransactionIdentifier, - ) -> Self { - Self { network_identifier, block_identifier, transaction_identifier } - } -} diff --git a/rosetta-types/src/block_transaction_response.rs b/rosetta-types/src/block_transaction_response.rs deleted file mode 100644 index a382c968..00000000 --- a/rosetta-types/src/block_transaction_response.rs +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `BlockTransactionResponse` : A `BlockTransactionResponse` contains information about a block -/// transaction. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct BlockTransactionResponse { - #[serde(rename = "transaction")] - pub transaction: crate::Transaction, -} - -impl BlockTransactionResponse { - /// A `BlockTransactionResponse` contains information about a block transaction. - #[must_use] - pub const fn new(transaction: crate::Transaction) -> Self { - Self { transaction } - } -} diff --git a/rosetta-types/src/call_request.rs b/rosetta-types/src/call_request.rs index c20eb126..e8555afe 100644 --- a/rosetta-types/src/call_request.rs +++ b/rosetta-types/src/call_request.rs @@ -11,8 +11,6 @@ /// `CallRequest` : `CallRequest` is the input to the `/call` endpoint. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct CallRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, /// Method is some network-specific procedure call. This method could map to a network-specific /// RPC endpoint, a method in an SDK generated from a smart contract, or some hybrid of the /// two. The implementation must define all available methods in the Allow object. However, it @@ -31,11 +29,10 @@ impl CallRequest { /// `CallRequest` is the input to the `/call` endpoint. #[must_use] pub const fn new( - network_identifier: crate::NetworkIdentifier, method: String, parameters: serde_json::Value, block_identifier: Option, ) -> Self { - Self { network_identifier, method, parameters, block_identifier } + Self { method, parameters, block_identifier } } } diff --git a/rosetta-types/src/case.rs b/rosetta-types/src/case.rs deleted file mode 100644 index 4695afc4..00000000 --- a/rosetta-types/src/case.rs +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `Case` specifies the expected case for strings and hashes. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] -pub enum Case { - #[serde(rename = "upper_case")] - UpperCase, - #[serde(rename = "lower_case")] - LowerCase, - #[serde(rename = "case_sensitive")] - CaseSensitive, - #[serde(rename = "null")] - Null, -} - -impl ToString for Case { - fn to_string(&self) -> String { - match self { - Self::UpperCase => String::from("upper_case"), - Self::LowerCase => String::from("lower_case"), - Self::CaseSensitive => String::from("case_sensitive"), - Self::Null => String::from("null"), - } - } -} - -impl Default for Case { - fn default() -> Self { - Self::UpperCase - } -} diff --git a/rosetta-types/src/construction_combine_request.rs b/rosetta-types/src/construction_combine_request.rs index 69d2f8d9..4d8380d4 100644 --- a/rosetta-types/src/construction_combine_request.rs +++ b/rosetta-types/src/construction_combine_request.rs @@ -13,8 +13,6 @@ /// `/construction/payloads` and all required signatures to create a network transaction. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct ConstructionCombineRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, #[serde(rename = "unsigned_transaction")] pub unsigned_transaction: String, #[serde(rename = "signatures")] @@ -26,11 +24,7 @@ impl ConstructionCombineRequest { /// contains the unsigned transaction blob returned by `/construction/payloads` and all required /// signatures to create a network transaction. #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - unsigned_transaction: String, - signatures: Vec, - ) -> Self { - Self { network_identifier, unsigned_transaction, signatures } + pub const fn new(unsigned_transaction: String, signatures: Vec) -> Self { + Self { unsigned_transaction, signatures } } } diff --git a/rosetta-types/src/construction_derive_request.rs b/rosetta-types/src/construction_derive_request.rs index e5040cda..c60451b0 100644 --- a/rosetta-types/src/construction_derive_request.rs +++ b/rosetta-types/src/construction_derive_request.rs @@ -15,8 +15,6 @@ /// vs normal accounts). #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct ConstructionDeriveRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, #[serde(rename = "public_key")] pub public_key: crate::PublicKey, #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")] @@ -29,10 +27,7 @@ impl ConstructionDeriveRequest { /// different networks. Metadata is provided in the request because some blockchains allow for /// multiple address types (i.e. different address for validators vs normal accounts). #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - public_key: crate::PublicKey, - ) -> Self { - Self { network_identifier, public_key, metadata: None } + pub const fn new(public_key: crate::PublicKey) -> Self { + Self { public_key, metadata: None } } } diff --git a/rosetta-types/src/construction_hash_request.rs b/rosetta-types/src/construction_hash_request.rs index 5905315b..7965286a 100644 --- a/rosetta-types/src/construction_hash_request.rs +++ b/rosetta-types/src/construction_hash_request.rs @@ -12,8 +12,6 @@ /// endpoint. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct ConstructionHashRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, #[serde(rename = "signed_transaction")] pub signed_transaction: String, } @@ -21,10 +19,7 @@ pub struct ConstructionHashRequest { impl ConstructionHashRequest { /// `ConstructionHashRequest` is the input to the `/construction/hash` endpoint. #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - signed_transaction: String, - ) -> Self { - Self { network_identifier, signed_transaction } + pub const fn new(signed_transaction: String) -> Self { + Self { signed_transaction } } } diff --git a/rosetta-types/src/construction_metadata_request.rs b/rosetta-types/src/construction_metadata_request.rs deleted file mode 100644 index f57bddd5..00000000 --- a/rosetta-types/src/construction_metadata_request.rs +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `ConstructionMetadataRequest` : A `ConstructionMetadataRequest` is utilized to get information -/// required to construct a transaction. The Options object used to specify which metadata to -/// return is left purposely unstructured to allow flexibility for implementers. Options is not -/// required in the case that there is network-wide metadata of interest. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct ConstructionMetadataRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, - /// Some blockchains require different metadata for different types of transaction construction - /// (ex: delegation versus a transfer). Instead of requiring a blockchain node to return all - /// possible types of metadata for construction (which may require multiple node fetches), the - /// client can populate an options object to limit the metadata returned to only the subset - /// required. - #[serde(rename = "options", skip_serializing_if = "Option::is_none")] - pub options: Option, - - #[serde(rename = "public_keys", skip_serializing_if = "Vec::is_empty")] - pub public_keys: Vec, -} - -impl ConstructionMetadataRequest { - /// A `ConstructionMetadataRequest` is utilized to get information required to construct a - /// transaction. The Options object used to specify which metadata to return is left purposely - /// unstructured to allow flexibility for implementers. Options is not required in the case that - /// there is network-wide metadata of interest. - #[must_use] - pub const fn new(network_identifier: crate::NetworkIdentifier) -> Self { - Self { network_identifier, options: None, public_keys: vec![] } - } -} diff --git a/rosetta-types/src/construction_metadata_response.rs b/rosetta-types/src/construction_metadata_response.rs deleted file mode 100644 index 5622adb9..00000000 --- a/rosetta-types/src/construction_metadata_response.rs +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `ConstructionMetadataResponse` : The `ConstructionMetadataResponse` returns network-specific -/// metadata used for transaction construction. Optionally, the implementer can return the -/// suggested fee associated with the transaction being constructed. The caller may use this info to -/// adjust the intent of the transaction or to create a transaction with a different account that -/// can pay the suggested fee. Suggested fee is an array in case fee payment must occur in multiple -/// currencies. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct ConstructionMetadataResponse { - #[serde(rename = "metadata")] - pub metadata: serde_json::Value, - #[serde(rename = "suggested_fee", skip_serializing_if = "Option::is_none")] - pub suggested_fee: Option>, -} - -impl ConstructionMetadataResponse { - /// The `ConstructionMetadataResponse` returns network-specific metadata used for transaction - /// construction. Optionally, the implementer can return the suggested fee associated with the - /// transaction being constructed. The caller may use this info to adjust the intent of the - /// transaction or to create a transaction with a different account that can pay the suggested - /// fee. Suggested fee is an array in case fee payment must occur in multiple currencies. - #[must_use] - pub const fn new(metadata: serde_json::Value) -> Self { - Self { metadata, suggested_fee: None } - } -} diff --git a/rosetta-types/src/construction_parse_request.rs b/rosetta-types/src/construction_parse_request.rs index 60d38252..e77b38ef 100644 --- a/rosetta-types/src/construction_parse_request.rs +++ b/rosetta-types/src/construction_parse_request.rs @@ -13,8 +13,6 @@ /// transaction. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct ConstructionParseRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, /// Signed is a boolean indicating whether the transaction is signed. #[serde(rename = "signed")] pub signed: bool, @@ -28,11 +26,7 @@ impl ConstructionParseRequest { /// `ConstructionParseRequest` is the input to the `/construction/parse` endpoint. It allows the /// caller to parse either an unsigned or signed transaction. #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - signed: bool, - transaction: String, - ) -> Self { - Self { network_identifier, signed, transaction } + pub const fn new(signed: bool, transaction: String) -> Self { + Self { signed, transaction } } } diff --git a/rosetta-types/src/construction_payloads_request.rs b/rosetta-types/src/construction_payloads_request.rs index ff40a481..53c1b789 100644 --- a/rosetta-types/src/construction_payloads_request.rs +++ b/rosetta-types/src/construction_payloads_request.rs @@ -14,8 +14,6 @@ /// associated with the `AccountIdentifiers` returned in `ConstructionPreprocessResponse`. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct ConstructionPayloadsRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, #[serde(rename = "operations")] pub operations: Vec, #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")] @@ -30,10 +28,7 @@ impl ConstructionPayloadsRequest { /// `/construction/metadata`. Optionally, the request can also include an array of `PublicKeys` /// associated with the `AccountIdentifiers` returned in `ConstructionPreprocessResponse`. #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - operations: Vec, - ) -> Self { - Self { network_identifier, operations, metadata: None, public_keys: None } + pub const fn new(operations: Vec) -> Self { + Self { operations, metadata: None, public_keys: None } } } diff --git a/rosetta-types/src/construction_preprocess_request.rs b/rosetta-types/src/construction_preprocess_request.rs deleted file mode 100644 index 1f41281e..00000000 --- a/rosetta-types/src/construction_preprocess_request.rs +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `ConstructionPreprocessRequest` is passed to the `/construction/preprocess` endpoint so that a -/// Rosetta implementation can determine which metadata it needs to request for construction. -/// Metadata provided in this object should NEVER be a product of live data (i.e. the caller must -/// follow some network-specific data fetching strategy outside of the Construction API to populate -/// required Metadata). If live data is required for construction, it MUST be fetched in the call to -/// `/construction/metadata`. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct ConstructionPreprocessRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, - #[serde(rename = "operations")] - pub operations: Vec, - #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl ConstructionPreprocessRequest { - /// `ConstructionPreprocessRequest` is passed to the `/construction/preprocess` endpoint so that - /// a Rosetta implementation can determine which metadata it needs to request for construction. - /// Metadata provided in this object should NEVER be a product of live data (i.e. the caller - /// must follow some network-specific data fetching strategy outside of the Construction API to - /// populate required Metadata). If live data is required for construction, it MUST be fetched - /// in the call to `/construction/metadata`. - #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - operations: Vec, - ) -> Self { - Self { network_identifier, operations, metadata: None } - } -} diff --git a/rosetta-types/src/construction_preprocess_response.rs b/rosetta-types/src/construction_preprocess_response.rs deleted file mode 100644 index d1006da2..00000000 --- a/rosetta-types/src/construction_preprocess_response.rs +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `ConstructionPreprocessResponse` contains `options` that will be sent unmodified to -/// `/construction/metadata`. If it is not necessary to make a request to `/construction/metadata`, -/// `options` should be omitted. Some blockchains require the `PublicKey` of particular -/// `AccountIdentifiers` to construct a valid transaction. To fetch these `PublicKeys`, populate -/// `required_public_keys` with the `AccountIdentifiers` associated with the desired `PublicKeys`. -/// If it is not necessary to retrieve any `PublicKeys` for construction, `required_public_keys` -/// should be omitted. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct ConstructionPreprocessResponse { - /// The options that will be sent directly to `/construction/metadata` by the caller. - #[serde(rename = "options", skip_serializing_if = "Option::is_none")] - pub options: Option, - #[serde(rename = "required_public_keys", skip_serializing_if = "Option::is_none")] - pub required_public_keys: Option>, -} - -impl ConstructionPreprocessResponse { - /// `ConstructionPreprocessResponse` contains `options` that will be sent unmodified to - /// `/construction/metadata`. If it is not necessary to make a request to - /// `/construction/metadata`, `options` should be omitted. Some blockchains require the - /// `PublicKey` of particular `AccountIdentifiers` to construct a valid transaction. To fetch - /// these `PublicKeys`, populate `required_public_keys` with the `AccountIdentifiers` associated - /// with the desired `PublicKeys`. If it is not necessary to retrieve any `PublicKeys` for - /// construction, `required_public_keys` should be omitted. - #[must_use] - pub const fn new() -> Self { - Self { options: None, required_public_keys: None } - } -} diff --git a/rosetta-types/src/construction_submit_request.rs b/rosetta-types/src/construction_submit_request.rs deleted file mode 100644 index f4164f92..00000000 --- a/rosetta-types/src/construction_submit_request.rs +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `ConstructionSubmitRequest` : The transaction submission request includes a signed transaction. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct ConstructionSubmitRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, - #[serde(rename = "signed_transaction")] - pub signed_transaction: String, -} - -impl ConstructionSubmitRequest { - /// The transaction submission request includes a signed transaction. - #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - signed_transaction: String, - ) -> Self { - Self { network_identifier, signed_transaction } - } -} diff --git a/rosetta-types/src/events_blocks_request.rs b/rosetta-types/src/events_blocks_request.rs deleted file mode 100644 index 8b333d80..00000000 --- a/rosetta-types/src/events_blocks_request.rs +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `EventsBlocksRequest` : `EventsBlocksRequest` is utilized to fetch a sequence of `BlockEvents` -/// indicating which blocks were added and removed from storage to reach the current state. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct EventsBlocksRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, - /// offset is the offset into the event stream to sync events from. If this field is not - /// populated, we return the limit events backwards from tip. If this is set to 0, we start - /// from the beginning. - #[serde(rename = "offset", skip_serializing_if = "Option::is_none")] - pub offset: Option, - /// limit is the maximum number of events to fetch in one call. The implementation may return - /// <= limit events. - #[serde(rename = "limit", skip_serializing_if = "Option::is_none")] - pub limit: Option, -} - -impl EventsBlocksRequest { - /// `EventsBlocksRequest` is utilized to fetch a sequence of `BlockEvents` indicating which - /// blocks were added and removed from storage to reach the current state. - #[must_use] - pub const fn new(network_identifier: crate::NetworkIdentifier) -> Self { - Self { network_identifier, offset: None, limit: None } - } -} diff --git a/rosetta-types/src/events_blocks_response.rs b/rosetta-types/src/events_blocks_response.rs deleted file mode 100644 index fab246c8..00000000 --- a/rosetta-types/src/events_blocks_response.rs +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `EventsBlocksResponse` contains an ordered collection of `BlockEvents` and the max retrievable -/// sequence. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct EventsBlocksResponse { - /// max_sequence is the maximum available sequence number to fetch. - #[serde(rename = "max_sequence")] - pub max_sequence: i64, - /// events is an array of BlockEvents indicating the order to add and remove blocks to maintain - /// a canonical view of blockchain state. Lightweight clients can use this event stream to - /// update state without implementing their own block syncing logic. - #[serde(rename = "events")] - pub events: Vec, -} - -impl EventsBlocksResponse { - /// `EventsBlocksResponse` contains an ordered collection of `BlockEvents` and the max - /// retrievable sequence. - #[must_use] - pub const fn new(max_sequence: i64, events: Vec) -> Self { - Self { max_sequence, events } - } -} diff --git a/rosetta-types/src/lib.rs b/rosetta-types/src/lib.rs index 10634a96..da98957b 100644 --- a/rosetta-types/src/lib.rs +++ b/rosetta-types/src/lib.rs @@ -3,48 +3,18 @@ #[macro_use] extern crate serde; -pub mod account_balance_request; -pub use self::account_balance_request::AccountBalanceRequest; -pub mod account_balance_response; -pub use self::account_balance_response::AccountBalanceResponse; -pub mod account_coins_request; -pub use self::account_coins_request::AccountCoinsRequest; -pub mod account_coins_response; -pub use self::account_coins_response::AccountCoinsResponse; -pub mod account_faucet_request; -pub use self::account_faucet_request::AccountFaucetRequest; pub mod account_identifier; pub use self::account_identifier::AccountIdentifier; -pub mod allow; -pub use self::allow::Allow; pub mod amount; pub use self::amount::Amount; -pub mod balance_exemption; -pub use self::balance_exemption::BalanceExemption; pub mod block; pub use self::block::Block; -pub mod block_event; -pub use self::block_event::BlockEvent; -pub mod block_event_type; -pub use self::block_event_type::BlockEventType; pub mod block_identifier; pub use self::block_identifier::BlockIdentifier; -pub mod block_request; -pub use self::block_request::BlockRequest; -pub mod block_response; -pub use self::block_response::BlockResponse; -pub mod block_transaction; -pub use self::block_transaction::BlockTransaction; -pub mod block_transaction_request; -pub use self::block_transaction_request::BlockTransactionRequest; -pub mod block_transaction_response; -pub use self::block_transaction_response::BlockTransactionResponse; pub mod call_request; pub use self::call_request::CallRequest; pub mod call_response; pub use self::call_response::CallResponse; -pub mod case; -pub use self::case::Case; pub mod coin; pub use self::coin::Coin; pub mod coin_action; @@ -63,10 +33,6 @@ pub mod construction_derive_response; pub use self::construction_derive_response::ConstructionDeriveResponse; pub mod construction_hash_request; pub use self::construction_hash_request::ConstructionHashRequest; -pub mod construction_metadata_request; -pub use self::construction_metadata_request::ConstructionMetadataRequest; -pub mod construction_metadata_response; -pub use self::construction_metadata_response::ConstructionMetadataResponse; pub mod construction_parse_request; pub use self::construction_parse_request::ConstructionParseRequest; pub mod construction_parse_response; @@ -75,12 +41,6 @@ pub mod construction_payloads_request; pub use self::construction_payloads_request::ConstructionPayloadsRequest; pub mod construction_payloads_response; pub use self::construction_payloads_response::ConstructionPayloadsResponse; -pub mod construction_preprocess_request; -pub use self::construction_preprocess_request::ConstructionPreprocessRequest; -pub mod construction_preprocess_response; -pub use self::construction_preprocess_response::ConstructionPreprocessResponse; -pub mod construction_submit_request; -pub use self::construction_submit_request::ConstructionSubmitRequest; pub mod currency; pub use self::currency::Currency; pub mod curve_type; @@ -89,38 +49,16 @@ pub mod direction; pub use self::direction::Direction; pub mod error; pub use self::error::Error; -pub mod events_blocks_request; -pub use self::events_blocks_request::EventsBlocksRequest; -pub mod events_blocks_response; -pub use self::events_blocks_response::EventsBlocksResponse; pub mod exemption_type; pub use self::exemption_type::ExemptionType; -pub mod mempool_response; -pub use self::mempool_response::MempoolResponse; -pub mod mempool_transaction_request; -pub use self::mempool_transaction_request::MempoolTransactionRequest; -pub mod mempool_transaction_response; -pub use self::mempool_transaction_response::MempoolTransactionResponse; pub mod metadata_request; pub use self::metadata_request::MetadataRequest; -pub mod network_identifier; -pub use self::network_identifier::NetworkIdentifier; -pub mod network_list_response; -pub use self::network_list_response::NetworkListResponse; -pub mod network_options_response; -pub use self::network_options_response::NetworkOptionsResponse; -pub mod network_request; -pub use self::network_request::NetworkRequest; -pub mod network_status_response; -pub use self::network_status_response::NetworkStatusResponse; pub mod operation; pub use self::operation::Operation; pub mod operation_identifier; pub use self::operation_identifier::OperationIdentifier; pub mod operation_status; pub use self::operation_status::OperationStatus; -pub mod operator; -pub use self::operator::Operator; pub mod partial_block_identifier; pub use self::partial_block_identifier::PartialBlockIdentifier; pub mod peer; @@ -129,10 +67,6 @@ pub mod public_key; pub use self::public_key::PublicKey; pub mod related_transaction; pub use self::related_transaction::RelatedTransaction; -pub mod search_transactions_request; -pub use self::search_transactions_request::SearchTransactionsRequest; -pub mod search_transactions_response; -pub use self::search_transactions_response::SearchTransactionsResponse; pub mod signature; pub use self::signature::Signature; pub mod signature_type; @@ -141,15 +75,9 @@ pub mod signing_payload; pub use self::signing_payload::SigningPayload; pub mod sub_account_identifier; pub use self::sub_account_identifier::SubAccountIdentifier; -pub mod sub_network_identifier; -pub use self::sub_network_identifier::SubNetworkIdentifier; -pub mod sync_status; -pub use self::sync_status::SyncStatus; pub mod transaction; pub use self::transaction::Transaction; pub mod transaction_identifier; pub use self::transaction_identifier::TransactionIdentifier; -pub mod transaction_identifier_response; -pub use self::transaction_identifier_response::TransactionIdentifierResponse; pub mod version; pub use self::version::Version; diff --git a/rosetta-types/src/mempool_response.rs b/rosetta-types/src/mempool_response.rs deleted file mode 100644 index 64d0a1d8..00000000 --- a/rosetta-types/src/mempool_response.rs +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// A `MempoolResponse` contains all transaction identifiers in the mempool for a particular -/// `network_identifier`. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct MempoolResponse { - #[serde(rename = "transaction_identifiers")] - pub transaction_identifiers: Vec, -} - -impl MempoolResponse { - /// A `MempoolResponse` contains all transaction identifiers in the mempool for a particular - /// `network_identifier`. - #[must_use] - pub const fn new(transaction_identifiers: Vec) -> Self { - Self { transaction_identifiers } - } -} diff --git a/rosetta-types/src/mempool_transaction_request.rs b/rosetta-types/src/mempool_transaction_request.rs deleted file mode 100644 index 4846a103..00000000 --- a/rosetta-types/src/mempool_transaction_request.rs +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// A `MempoolTransactionRequest` is utilized to retrieve a transaction from the mempool. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct MempoolTransactionRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, - #[serde(rename = "transaction_identifier")] - pub transaction_identifier: crate::TransactionIdentifier, -} - -impl MempoolTransactionRequest { - /// A `MempoolTransactionRequest` is utilized to retrieve a transaction from the mempool. - #[must_use] - pub const fn new( - network_identifier: crate::NetworkIdentifier, - transaction_identifier: crate::TransactionIdentifier, - ) -> Self { - Self { network_identifier, transaction_identifier } - } -} diff --git a/rosetta-types/src/mempool_transaction_response.rs b/rosetta-types/src/mempool_transaction_response.rs deleted file mode 100644 index e3940063..00000000 --- a/rosetta-types/src/mempool_transaction_response.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// A `MempoolTransactionResponse` contains an estimate of a mempool transaction. It may not be -/// possible to know the full impact of a transaction in the mempool (ex: fee paid). -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct MempoolTransactionResponse { - #[serde(rename = "transaction")] - pub transaction: crate::Transaction, - #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl MempoolTransactionResponse { - /// A `MempoolTransactionResponse` contains an estimate of a mempool transaction. It may not be - /// possible to know the full impact of a transaction in the mempool (ex: fee paid). - #[must_use] - pub const fn new(transaction: crate::Transaction) -> Self { - Self { transaction, metadata: None } - } -} diff --git a/rosetta-types/src/network_identifier.rs b/rosetta-types/src/network_identifier.rs deleted file mode 100644 index d3da20c0..00000000 --- a/rosetta-types/src/network_identifier.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// The `network_identifier` specifies which network a particular object is associated with. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct NetworkIdentifier { - #[serde(rename = "blockchain")] - pub blockchain: String, - /// If a blockchain has a specific chain-id or network identifier, it should go in this field. - /// It is up to the client to determine which network-specific identifier is mainnet or - /// testnet. - #[serde(rename = "network")] - pub network: String, - #[serde(rename = "sub_network_identifier", skip_serializing_if = "Option::is_none")] - pub sub_network_identifier: Option, -} - -impl NetworkIdentifier { - /// The `network_identifier` specifies which network a particular object is associated with. - #[must_use] - pub const fn new(blockchain: String, network: String) -> Self { - Self { blockchain, network, sub_network_identifier: None } - } -} diff --git a/rosetta-types/src/network_list_response.rs b/rosetta-types/src/network_list_response.rs deleted file mode 100644 index 48470d39..00000000 --- a/rosetta-types/src/network_list_response.rs +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// A `NetworkListResponse` contains all `NetworkIdentifiers` that the node can serve information -/// for. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct NetworkListResponse { - #[serde(rename = "network_identifiers")] - pub network_identifiers: Vec, -} - -impl NetworkListResponse { - /// A `NetworkListResponse` contains all `NetworkIdentifiers` that the node can serve - /// information for. - #[must_use] - pub const fn new(network_identifiers: Vec) -> Self { - Self { network_identifiers } - } -} diff --git a/rosetta-types/src/network_options_response.rs b/rosetta-types/src/network_options_response.rs deleted file mode 100644 index af45801b..00000000 --- a/rosetta-types/src/network_options_response.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `NetworkOptionsResponse` contains information about the versioning of the node and the allowed -/// operation statuses, operation types, and errors. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct NetworkOptionsResponse { - #[serde(rename = "version")] - pub version: crate::Version, - #[serde(rename = "allow")] - pub allow: Option, -} - -impl NetworkOptionsResponse { - /// `NetworkOptionsResponse` contains information about the versioning of the node and the - /// allowed operation statuses, operation types, and errors. - #[must_use] - pub const fn new(version: crate::Version) -> Self { - Self { version, allow: None } - } -} diff --git a/rosetta-types/src/network_request.rs b/rosetta-types/src/network_request.rs deleted file mode 100644 index d4adf22a..00000000 --- a/rosetta-types/src/network_request.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// A `NetworkRequest` is utilized to retrieve some data specific exclusively to a -/// `NetworkIdentifier`. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct NetworkRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, - #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl NetworkRequest { - /// A `NetworkRequest` is utilized to retrieve some data specific exclusively to a - /// `NetworkIdentifier`. - #[must_use] - pub const fn new(network_identifier: crate::NetworkIdentifier) -> Self { - Self { network_identifier, metadata: None } - } -} diff --git a/rosetta-types/src/network_status_response.rs b/rosetta-types/src/network_status_response.rs deleted file mode 100644 index 1947901a..00000000 --- a/rosetta-types/src/network_status_response.rs +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `NetworkStatusResponse` contains basic information about the node's view of a blockchain -/// network. It is assumed that any BlockIdentifier.Index less than or equal to -/// CurrentBlockIdentifier.Index can be queried. If a Rosetta implementation prunes historical -/// state, it should populate the optional `oldest_block_identifier` field with the oldest block -/// available to query. If this is not populated, it is assumed that the `genesis_block_identifier` -/// is the oldest queryable block. If a Rosetta implementation performs some pre-sync before it is -/// possible to query blocks, `sync_status` should be populated so that clients can still monitor -/// healthiness. Without this field, it may appear that the implementation is stuck syncing and -/// needs to be terminated. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct NetworkStatusResponse { - #[serde(rename = "current_block_identifier")] - pub current_block_identifier: crate::BlockIdentifier, - /// The timestamp of the block in milliseconds since the Unix Epoch. The timestamp is stored in - /// milliseconds because some blockchains produce blocks more often than once a second. - #[serde(rename = "current_block_timestamp")] - pub current_block_timestamp: i64, - // TODO: genesis_block_identifier is not nullable. rosetta-ethereum can't determine - // it on dev net. - #[serde(rename = "genesis_block_identifier")] - pub genesis_block_identifier: Option, - #[serde(rename = "oldest_block_identifier", skip_serializing_if = "Option::is_none")] - pub oldest_block_identifier: Option, - #[serde(rename = "latest_finalized_block_identifier")] - pub latest_finalized_block_identifier: crate::BlockIdentifier, - #[serde(rename = "sync_status", skip_serializing_if = "Option::is_none")] - pub sync_status: Option, - #[serde(rename = "peers", skip_serializing_if = "Option::is_none")] - pub peers: Option>, -} - -impl NetworkStatusResponse { - /// `NetworkStatusResponse` contains basic information about the node's view of a blockchain - /// network. It is assumed that any BlockIdentifier.Index less than or equal to - /// CurrentBlockIdentifier.Index can be queried. If a Rosetta implementation prunes historical - /// state, it should populate the optional `oldest_block_identifier` field with the oldest block - /// available to query. If this is not populated, it is assumed that the - /// `genesis_block_identifier` is the oldest queryable block. If a Rosetta implementation - /// performs some pre-sync before it is possible to query blocks, `sync_status` should be - /// populated so that clients can still monitor healthiness. Without this field, it may appear - /// that the implementation is stuck syncing and needs to be terminated. - #[must_use] - pub const fn new( - current_block_identifier: crate::BlockIdentifier, - current_block_timestamp: i64, - genesis_block_identifier: crate::BlockIdentifier, - latest_finalized_block_identifier: crate::BlockIdentifier, - ) -> Self { - Self { - current_block_identifier, - current_block_timestamp, - genesis_block_identifier: Some(genesis_block_identifier), - oldest_block_identifier: None, - latest_finalized_block_identifier, - sync_status: None, - peers: None, - } - } -} diff --git a/rosetta-types/src/operator.rs b/rosetta-types/src/operator.rs deleted file mode 100644 index ae40e0d8..00000000 --- a/rosetta-types/src/operator.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// Operator : Operator is used by query-related endpoints to determine how to apply conditions. If -/// this field is not populated, the default `and` value will be used. Operator is used by -/// query-related endpoints to determine how to apply conditions. If this field is not populated, -/// the default `and` value will be used. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] -pub enum Operator { - #[serde(rename = "or")] - Or, - #[serde(rename = "and")] - And, -} - -impl ToString for Operator { - fn to_string(&self) -> String { - match self { - Self::Or => String::from("or"), - Self::And => String::from("and"), - } - } -} - -impl Default for Operator { - fn default() -> Self { - Self::Or - } -} diff --git a/rosetta-types/src/partial_block_identifier.rs b/rosetta-types/src/partial_block_identifier.rs index fa397012..0b50bb2d 100644 --- a/rosetta-types/src/partial_block_identifier.rs +++ b/rosetta-types/src/partial_block_identifier.rs @@ -8,6 +8,8 @@ * Generated by: https://openapi-generator.tech */ +use crate::BlockIdentifier; + /// `PartialBlockIdentifier` : When fetching data by `BlockIdentifier`, it may be possible to only /// specify the index or hash. If neither property is specified, it is assumed that the client is /// making a request at the current block. @@ -15,8 +17,14 @@ pub struct PartialBlockIdentifier { #[serde(rename = "index", skip_serializing_if = "Option::is_none")] pub index: Option, - #[serde(rename = "hash", skip_serializing_if = "Option::is_none")] - pub hash: Option, + #[serde(skip_serializing)] + pub hash: Option<[u8; 32]>, +} + +impl From for PartialBlockIdentifier { + fn from(block_identifier: BlockIdentifier) -> Self { + Self { index: Some(block_identifier.index), hash: Some(block_identifier.hash) } + } } impl PartialBlockIdentifier { diff --git a/rosetta-types/src/related_transaction.rs b/rosetta-types/src/related_transaction.rs index 97a520a2..9d688a51 100644 --- a/rosetta-types/src/related_transaction.rs +++ b/rosetta-types/src/related_transaction.rs @@ -13,8 +13,6 @@ /// is on the same network. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct RelatedTransaction { - #[serde(rename = "network_identifier", skip_serializing_if = "Option::is_none")] - pub network_identifier: Option, #[serde(rename = "transaction_identifier")] pub transaction_identifier: crate::TransactionIdentifier, #[serde(rename = "direction")] @@ -30,6 +28,6 @@ impl RelatedTransaction { transaction_identifier: crate::TransactionIdentifier, direction: crate::Direction, ) -> Self { - Self { network_identifier: None, transaction_identifier, direction } + Self { transaction_identifier, direction } } } diff --git a/rosetta-types/src/search_transactions_request.rs b/rosetta-types/src/search_transactions_request.rs deleted file mode 100644 index c8ea27fe..00000000 --- a/rosetta-types/src/search_transactions_request.rs +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `SearchTransactionsRequest` : `SearchTransactionsRequest` is used to search for transactions -/// matching a set of provided conditions in canonical blocks. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct SearchTransactionsRequest { - #[serde(rename = "network_identifier")] - pub network_identifier: crate::NetworkIdentifier, - #[serde(rename = "operator", skip_serializing_if = "Option::is_none")] - pub operator: Option, - /// max_block is the largest block index to consider when searching for transactions. If this - /// field is not populated, the current block is considered the max_block. If you do not - /// specify a max_block, it is possible a newly synced block will interfere with paginated - /// transaction queries (as the offset could become invalid with newly added rows). - #[serde(rename = "max_block", skip_serializing_if = "Option::is_none")] - pub max_block: Option, - /// offset is the offset into the query result to start returning transactions. If any search - /// conditions are changed, the query offset will change and you must restart your search - /// iteration. - #[serde(rename = "offset", skip_serializing_if = "Option::is_none")] - pub offset: Option, - /// limit is the maximum number of transactions to return in one call. The implementation may - /// return <= limit transactions. - #[serde(rename = "limit", skip_serializing_if = "Option::is_none")] - pub limit: Option, - #[serde(rename = "transaction_identifier", skip_serializing_if = "Option::is_none")] - pub transaction_identifier: Option, - #[serde(rename = "account_identifier", skip_serializing_if = "Option::is_none")] - pub account_identifier: Option, - #[serde(rename = "coin_identifier", skip_serializing_if = "Option::is_none")] - pub coin_identifier: Option, - #[serde(rename = "currency", skip_serializing_if = "Option::is_none")] - pub currency: Option, - /// status is the network-specific operation type. - #[serde(rename = "status", skip_serializing_if = "Option::is_none")] - pub status: Option, - /// type is the network-specific operation type. - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub r#type: Option, - /// address is AccountIdentifier.Address. This is used to get all transactions related to an - /// AccountIdentifier.Address, regardless of SubAccountIdentifier. - #[serde(rename = "address", skip_serializing_if = "Option::is_none")] - pub address: Option, - /// success is a synthetic condition populated by parsing network-specific operation statuses - /// (using the mapping provided in `/network/options`). - #[serde(rename = "success", skip_serializing_if = "Option::is_none")] - pub success: Option, -} - -impl SearchTransactionsRequest { - /// `SearchTransactionsRequest` is used to search for transactions matching a set of provided - /// conditions in canonical blocks. - #[must_use] - pub const fn new(network_identifier: crate::NetworkIdentifier) -> Self { - Self { - network_identifier, - operator: None, - max_block: None, - offset: None, - limit: None, - transaction_identifier: None, - account_identifier: None, - coin_identifier: None, - currency: None, - status: None, - r#type: None, - address: None, - success: None, - } - } -} diff --git a/rosetta-types/src/search_transactions_response.rs b/rosetta-types/src/search_transactions_response.rs deleted file mode 100644 index 5db368cc..00000000 --- a/rosetta-types/src/search_transactions_response.rs +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `SearchTransactionsResponse` : `SearchTransactionsResponse` contains an ordered collection of -/// `BlockTransactions` that match the query in `SearchTransactionsRequest`. These -/// `BlockTransactions` are sorted from most recent block to oldest block. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct SearchTransactionsResponse { - /// transactions is an array of BlockTransactions sorted by most recent BlockIdentifier - /// (meaning that transactions in recent blocks appear first). If there are many transactions - /// for a particular search, transactions may not contain all matching transactions. It is up - /// to the caller to paginate these transactions using the max_block field. - #[serde(rename = "transactions")] - pub transactions: Vec, - /// total_count is the number of results for a given search. Callers typically use this value - /// to concurrently fetch results by offset or to display a virtual page number associated with - /// results. - #[serde(rename = "total_count")] - pub total_count: i64, - /// next_offset is the next offset to use when paginating through transaction results. If this - /// field is not populated, there are no more transactions to query. - #[serde(rename = "next_offset", skip_serializing_if = "Option::is_none")] - pub next_offset: Option, -} - -impl SearchTransactionsResponse { - /// `SearchTransactionsResponse` contains an ordered collection of `BlockTransactions` that - /// match the query in `SearchTransactionsRequest`. These `BlockTransactions` are sorted from - /// most recent block to oldest block. - #[must_use] - pub const fn new(transactions: Vec, total_count: i64) -> Self { - Self { transactions, total_count, next_offset: None } - } -} diff --git a/rosetta-types/src/sub_network_identifier.rs b/rosetta-types/src/sub_network_identifier.rs deleted file mode 100644 index 1ca9de7f..00000000 --- a/rosetta-types/src/sub_network_identifier.rs +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `SubNetworkIdentifier` : In blockchains with sharded state, the `SubNetworkIdentifier` is -/// required to query some object on a specific shard. This identifier is optional for all -/// non-sharded blockchains. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct SubNetworkIdentifier { - #[serde(rename = "network")] - pub network: String, - #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl SubNetworkIdentifier { - /// In blockchains with sharded state, the `SubNetworkIdentifier` is required to query some - /// object on a specific shard. This identifier is optional for all non-sharded blockchains. - #[must_use] - pub const fn new(network: String) -> Self { - Self { network, metadata: None } - } -} diff --git a/rosetta-types/src/sync_status.rs b/rosetta-types/src/sync_status.rs deleted file mode 100644 index ce2ba0af..00000000 --- a/rosetta-types/src/sync_status.rs +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `SyncStatus` : `SyncStatus` is used to provide additional context about an implementation's sync -/// status. This object is often used by implementations to indicate healthiness when block data -/// cannot be queried until some sync phase completes or cannot be determined by comparing the -/// timestamp of the most recent block with the current time. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct SyncStatus { - /// CurrentIndex is the index of the last synced block in the current stage. This is a - /// separate field from current_block_identifier in NetworkStatusResponse because blocks with - /// indices up to and including the current_index may not yet be queryable by the caller. To - /// reiterate, all indices up to and including current_block_identifier in - /// NetworkStatusResponse must be queryable via the /block endpoint (excluding indices less - /// than oldest_block_identifier). - #[serde(rename = "current_index", skip_serializing_if = "Option::is_none")] - pub current_index: Option, - /// TargetIndex is the index of the block that the implementation is attempting to sync to in - /// the current stage. - #[serde(rename = "target_index", skip_serializing_if = "Option::is_none")] - pub target_index: Option, - /// Stage is the phase of the sync process. - #[serde(rename = "stage", skip_serializing_if = "Option::is_none")] - pub stage: Option, - /// synced is a boolean that indicates if an implementation has synced up to the most recent - /// block. If this field is not populated, the caller should rely on a traditional tip - /// timestamp comparison to determine if an implementation is synced. This field is - /// particularly useful for quiescent blockchains (blocks only produced when there are pending - /// transactions). In these blockchains, the most recent block could have a timestamp far - /// behind the current time but the node could be healthy and at tip. - #[serde(rename = "synced", skip_serializing_if = "Option::is_none")] - pub synced: Option, -} - -impl SyncStatus { - /// `SyncStatus` is used to provide additional context about an implementation's sync status. - /// This object is often used by implementations to indicate healthiness when block data cannot - /// be queried until some sync phase completes or cannot be determined by comparing the - /// timestamp of the most recent block with the current time. - #[must_use] - pub const fn new() -> Self { - Self { current_index: None, target_index: None, stage: None, synced: None } - } -} diff --git a/rosetta-types/src/transaction_identifier_response.rs b/rosetta-types/src/transaction_identifier_response.rs deleted file mode 100644 index 6e08c987..00000000 --- a/rosetta-types/src/transaction_identifier_response.rs +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Rosetta - * - * Build Once. Integrate Your Blockchain Everywhere. - * - * The version of the OpenAPI document: 1.4.13 - * - * Generated by: https://openapi-generator.tech - */ - -/// `TransactionIdentifierResponse` : `TransactionIdentifierResponse` contains the -/// `transaction_identifier` of a transaction that was submitted to either `/construction/hash` or -/// `/construction/submit`. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct TransactionIdentifierResponse { - #[serde(rename = "transaction_identifier")] - pub transaction_identifier: crate::TransactionIdentifier, - #[serde(rename = "metadata", skip_serializing_if = "Option::is_none")] - pub metadata: Option, -} - -impl TransactionIdentifierResponse { - /// `TransactionIdentifierResponse` contains the `transaction_identifier` of a transaction that - /// was submitted to either `/construction/hash` or `/construction/submit`. - #[must_use] - pub const fn new(transaction_identifier: crate::TransactionIdentifier) -> Self { - Self { transaction_identifier, metadata: None } - } -} diff --git a/scripts/check.sh b/scripts/check.sh index 88b8a2b8..52f47737 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -72,8 +72,21 @@ exec_cmd 'cargo fmt' 'cargo +nightly fmt --all -- --check' exec_cmd 'dprint check' 'dprint check' exec_cmd 'cargo deny' 'cargo deny check' +# Run clippy on all packages with different feature flags +# LINT_FLAGS='-- -Dwarnings -Dclippy::unwrap_used -Dclippy::expect_used -Dclippy::nursery -Dclippy::pedantic -Aclippy::module_name_repetitions' + +# exec_cmd 'ethereum build all-features' 'cargo build -p rosetta-config-ethereum --all-features' +# exec_cmd 'ethereum test all-features' 'cargo test -p rosetta-config-ethereum --all-features' +# exec_cmd 'ethereum clippy all-features' "cargo clippy -p rosetta-config-ethereum --all-features ${LINT_FLAGS}" +# ethereumFeatures=('std' 'std,serde' 'std,scale-info' 'std,scale-codec') +# for features in "${ethereumFeatures[@]}"; +# do +# exec_cmd "ethereum build ${features}" "cargo build -p rosetta-config-ethereum --no-default-features --features=${features}" +# exec_cmd "ethereum test ${features}" "cargo test -p rosetta-config-ethereum --no-default-features --features=${features}" +# exec_cmd "ethereum clippy ${features}" "cargo clippy -p rosetta-config-ethereum --no-default-features --features=${features} ${LINT_FLAGS}" +# done + # exec_cmd 'clippy rosetta-server-astar' 'cargo clippy --locked -p rosetta-server-astar --examples --tests -- -Dwarnings -Dclippy::unwrap_used -Dclippy::expect_used -Dclippy::nursery -Dclippy::pedantic -Aclippy::module_name_repetitions' -# exec_cmd 'clippy rosetta-server-bitcoin' 'cargo clippy --locked -p rosetta-server-bitcoin --examples --tests -- -Dwarnings -Dclippy::unwrap_used -Dclippy::expect_used -Dclippy::nursery -Dclippy::pedantic -Aclippy::module_name_repetitions' # exec_cmd 'clippy rosetta-server-ethereum' 'cargo clippy --locked -p rosetta-server-ethereum --examples --tests -- -Dwarnings -Dclippy::unwrap_used -Dclippy::expect_used -Dclippy::nursery -Dclippy::pedantic -Aclippy::module_name_repetitions' # exec_cmd 'clippy rosetta-server-polkadot' 'cargo clippy --locked -p rosetta-server-polkadot --examples --tests -- -Dwarnings -Dclippy::unwrap_used -Dclippy::expect_used -Dclippy::nursery -Dclippy::pedantic -Aclippy::module_name_repetitions' # exec_cmd 'clippy rosetta-client' 'cargo clippy --locked -p rosetta-client --examples --tests -- -Dwarnings -Dclippy::unwrap_used -Dclippy::expect_used -Dclippy::nursery -Dclippy::pedantic -Aclippy::module_name_repetitions' @@ -85,14 +98,13 @@ if [[ "${RUN_TESTS}" == "1" ]]; then cargo test --locked -p rosetta-server-ethereum cargo test --locked -p rosetta-server-astar cargo test --locked -p rosetta-server-polkadot - cargo test --locked -p rosetta-server-bitcoin cargo test --locked -p rosetta-client cargo test --locked --workspace --all-features \ --exclude rosetta-server-astar \ - --exclude rosetta-server-bitcoin \ --exclude rosetta-server-ethereum \ --exclude rosetta-server-polkadot \ - --exclude rosetta-client + --exclude rosetta-client \ + --exclude rosetta-testing-arbitrum # cargo test --locked --all-features --workspace exec_cmd 'cleanup docker' "${SCRIPT_DIR}/reset_docker.sh" fi diff --git a/scripts/pull_nodes.sh b/scripts/pull_nodes.sh index 2f8b8a25..5498c419 100755 --- a/scripts/pull_nodes.sh +++ b/scripts/pull_nodes.sh @@ -1,7 +1,6 @@ #!/bin/bash set -e -docker image pull ruimarinho/bitcoin-core:23 docker image pull ethereum/client-go:v1.12.2 docker image pull parity/polkadot:v1.5.0 docker image pull staketechnologies/astar-collator:v5.28.0-rerun diff --git a/scripts/push_connectors.sh b/scripts/push_connectors.sh deleted file mode 100755 index 149bd0d6..00000000 --- a/scripts/push_connectors.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -set -e - -# First arg is the Docker image tag on all images -tag=$1 - -docker push analoglabs/connector-bitcoin:latest -docker push analoglabs/connector-ethereum:latest -docker push analoglabs/connector-polkadot:latest -docker push analoglabs/connector-astar:latest - -if [[ -n "${tag}" ]]; then - echo "Tagging all images: ${tag}"; - docker tag analoglabs/connector-bitcoin:latest "analoglabs/connector-bitcoin:${tag}" - docker push "analoglabs/connector-bitcoin:${tag}" - - docker tag analoglabs/connector-ethereum:latest "analoglabs/connector-ethereum:${tag}" - docker push "analoglabs/connector-ethereum:${tag}" - - docker tag analoglabs/connector-polkadot:latest "analoglabs/connector-polkadot:${tag}" - docker push "analoglabs/connector-polkadot:${tag}" - - docker tag analoglabs/connector-astar:latest "analoglabs/connector-astar:${tag}" - docker push "analoglabs/connector-astar:${tag}" -fi