From 73a58f7ba751048c10f92d6dde707dcdb1f85ba6 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Wed, 28 Aug 2024 06:28:43 +0200 Subject: [PATCH 1/3] trie-geyser: RPC --- .github/workflows/master.yml | 12 +- Cargo.lock | 200 +++++++++++++++--- Cargo.toml | 9 +- common/cf-solana/src/proof.rs | 1 + common/trie-geyser/Cargo.toml | 19 ++ common/trie-geyser/src/api.rs | 85 ++++++++ common/trie-geyser/src/lib.rs | 1 + ..._geyser__api__slot_data_serialisation.snap | 27 +++ .../.rustfmt.toml | 0 .../Cargo.lock | 0 .../Cargo.toml | 5 +- .../README.md | 11 +- solana/trie-geyser-plugin/config.json | 6 + .../src/config.rs | 17 +- .../src/lib.rs | 4 + .../src/plugin.rs | 64 ++++-- solana/trie-geyser-plugin/src/rpc.rs | 166 +++++++++++++++ .../src/types.rs | 0 .../src/utils.rs | 0 .../src/worker.rs | 69 ++++-- solana/trie-geyser/config.json | 5 - 21 files changed, 610 insertions(+), 91 deletions(-) create mode 100644 common/trie-geyser/Cargo.toml create mode 100644 common/trie-geyser/src/api.rs create mode 100644 common/trie-geyser/src/lib.rs create mode 100644 common/trie-geyser/src/snapshots/witnessed_trie_geyser__api__slot_data_serialisation.snap rename solana/{trie-geyser => trie-geyser-plugin}/.rustfmt.toml (100%) rename solana/{trie-geyser => trie-geyser-plugin}/Cargo.lock (100%) rename solana/{trie-geyser => trie-geyser-plugin}/Cargo.toml (98%) rename solana/{trie-geyser => trie-geyser-plugin}/README.md (74%) create mode 100644 solana/trie-geyser-plugin/config.json rename solana/{trie-geyser => trie-geyser-plugin}/src/config.rs (89%) rename solana/{trie-geyser => trie-geyser-plugin}/src/lib.rs (88%) rename solana/{trie-geyser => trie-geyser-plugin}/src/plugin.rs (81%) create mode 100644 solana/trie-geyser-plugin/src/rpc.rs rename solana/{trie-geyser => trie-geyser-plugin}/src/types.rs (100%) rename solana/{trie-geyser => trie-geyser-plugin}/src/utils.rs (100%) rename solana/{trie-geyser => trie-geyser-plugin}/src/worker.rs (73%) delete mode 100644 solana/trie-geyser/config.json diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 258822df..b81dca08 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -44,7 +44,7 @@ jobs: - name: Check formatting run: | cargo fmt --all --check - ( cd solana/trie-geyser && cargo fmt --all --check ) + ( cd solana/trie-geyser-plugin && cargo fmt --all --check ) - name: Check Clippy (all features) uses: actions-rs/clippy-check@v1 @@ -56,12 +56,12 @@ jobs: uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features --manifest-path solana/trie-geyser/Cargo.toml -- -D warnings + args: --all-features --manifest-path solana/trie-geyser-plugin/Cargo.toml -- -D warnings - name: Miri tests run: | cargo miri test -- -Z unstable-options --report-time --skip ::anchor - ( cd solana/trie-geyser && cargo miri test -- -Z unstable-options --report-time ) + ( cd solana/trie-geyser-plugin && cargo miri test -- -Z unstable-options --report-time ) anchor-build: name: Anchor Test @@ -158,10 +158,10 @@ jobs: run: cargo test --all-features - name: Run trie-geyser tests (default features) - run: cd solana/trie-geyser && cargo test + run: cd solana/trie-geyser-plugin && cargo test - name: Run trie-geyser tests (no default features) - run: cd solana/trie-geyser && cargo test --no-default-features + run: cd solana/trie-geyser-plugin && cargo test --no-default-features - name: Run trie-geyser tests (all features) - run: cd solana/trie-geyser && cargo test --all-features + run: cd solana/trie-geyser-plugin && cargo test --all-features diff --git a/Cargo.lock b/Cargo.lock index d4f02c6f..65d341bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,7 +285,7 @@ checksum = "cb48c4a7911038da546dc752655a29fa49f6bd50ebc1edca218bac8da1012acd" dependencies = [ "anchor-lang 0.29.0", "anyhow", - "futures", + "futures 0.3.30", "regex", "serde", "solana-account-decoder", @@ -293,7 +293,7 @@ dependencies = [ "solana-sdk 1.17.31", "thiserror", "tokio", - "url", + "url 2.5.0", ] [[package]] @@ -1627,7 +1627,7 @@ dependencies = [ "hashbrown 0.14.3", "lock_api", "once_cell", - "parking_lot_core", + "parking_lot_core 0.9.9", "rayon", ] @@ -2070,7 +2070,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "percent-encoding", + "percent-encoding 2.3.1", ] [[package]] @@ -2079,6 +2079,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" + [[package]] name = "futures" version = "0.3.30" @@ -2119,6 +2125,7 @@ dependencies = [ "futures-core", "futures-task", "futures-util", + "num_cpus", ] [[package]] @@ -2156,6 +2163,7 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures 0.1.31", "futures-channel", "futures-core", "futures-io", @@ -3126,7 +3134,7 @@ dependencies = [ "ibc", "ibc-client-tendermint-types", "ibc-proto", - "parking_lot", + "parking_lot 0.12.1", "subtle-encoding", "tendermint", "tendermint-testgen", @@ -3159,6 +3167,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.5.0" @@ -3272,6 +3291,7 @@ dependencies = [ "console", "lazy_static", "linked-hash-map", + "serde", "similar", "yaml-rust", ] @@ -3324,13 +3344,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonrpc-client-transports" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b99d4207e2a04fb4581746903c2bb7eb376f88de9c699d0f3e10feeac0cd3a" +dependencies = [ + "derive_more", + "futures 0.3.30", + "jsonrpc-core", + "jsonrpc-pubsub", + "log", + "serde", + "serde_json", + "url 1.7.2", +] + [[package]] name = "jsonrpc-core" version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" dependencies = [ - "futures", + "futures 0.3.30", "futures-executor", "futures-util", "log", @@ -3339,6 +3375,43 @@ dependencies = [ "serde_json", ] +[[package]] +name = "jsonrpc-core-client" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b51da17abecbdab3e3d4f26b01c5ec075e88d3abe3ab3b05dc9aa69392764ec0" +dependencies = [ + "futures 0.3.30", + "jsonrpc-client-transports", +] + +[[package]] +name = "jsonrpc-derive" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b939a78fa820cdfcb7ee7484466746a7377760970f6f9c6fe19f9edcc8a38d2" +dependencies = [ + "proc-macro-crate 0.1.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "jsonrpc-pubsub" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240f87695e6c6f62fb37f05c02c04953cf68d6408b8c1c89de85c7a0125b1011" +dependencies = [ + "futures 0.3.30", + "jsonrpc-core", + "lazy_static", + "log", + "parking_lot 0.11.2", + "rand 0.7.3", + "serde", +] + [[package]] name = "keccak" version = "0.1.4" @@ -3513,6 +3586,12 @@ dependencies = [ "libc", ] +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "memchr" version = "2.7.1" @@ -3963,6 +4042,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -3970,7 +4060,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -4019,6 +4123,12 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -4313,7 +4423,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" dependencies = [ - "percent-encoding", + "percent-encoding 2.3.1", ] [[package]] @@ -4508,6 +4618,15 @@ dependencies = [ "yasna", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -4588,7 +4707,7 @@ dependencies = [ "log", "mime", "once_cell", - "percent-encoding", + "percent-encoding 2.3.1", "pin-project-lite", "rustls", "rustls-pemfile 1.0.4", @@ -4600,7 +4719,7 @@ dependencies = [ "tokio-rustls", "tokio-util", "tower-service", - "url", + "url 2.5.0", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -4633,7 +4752,7 @@ dependencies = [ "mime", "native-tls", "once_cell", - "percent-encoding", + "percent-encoding 2.3.1", "pin-project-lite", "rustls-pemfile 2.1.2", "serde", @@ -4644,7 +4763,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tower-service", - "url", + "url 2.5.0", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -4988,7 +5107,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5c67b6f14ecc5b86c66fa63d76b5092352678545a8a3cdae80aef5128371910" dependencies = [ - "parking_lot", + "parking_lot 0.12.1", ] [[package]] @@ -5399,7 +5518,7 @@ dependencies = [ "thiserror", "tiny-bip39", "uriparse", - "url", + "url 2.5.0", ] [[package]] @@ -5415,7 +5534,7 @@ dependencies = [ "serde_yaml", "solana-clap-utils", "solana-sdk 1.17.31", - "url", + "url 2.5.0", ] [[package]] @@ -5427,7 +5546,7 @@ dependencies = [ "async-trait", "bincode", "dashmap 4.0.2", - "futures", + "futures 0.3.30", "futures-util", "indexmap 2.4.0", "indicatif", @@ -5718,7 +5837,7 @@ dependencies = [ "solana-sdk 1.17.31", "solana-version", "tokio", - "url", + "url 2.5.0", ] [[package]] @@ -5791,7 +5910,7 @@ dependencies = [ "num-bigint 0.4.6", "num-derive 0.3.3", "num-traits", - "parking_lot", + "parking_lot 0.12.1", "rand 0.8.5", "rustc_version", "rustversion", @@ -5845,7 +5964,7 @@ dependencies = [ "num-bigint 0.4.6", "num-derive 0.4.1", "num-traits", - "parking_lot", + "parking_lot 0.12.1", "rand 0.8.5", "rustc_version", "rustversion", @@ -5896,7 +6015,7 @@ dependencies = [ "num-bigint 0.4.6", "num-derive 0.4.1", "num-traits", - "parking_lot", + "parking_lot 0.12.1", "rand 0.8.5", "rustc_version", "rustversion", @@ -5987,7 +6106,7 @@ dependencies = [ "tokio-stream", "tokio-tungstenite", "tungstenite", - "url", + "url 2.5.0", ] [[package]] @@ -5998,7 +6117,7 @@ checksum = "6201869768fe133ce9b8088e4f718f53ff164b8e5df3d0d46a6563a22545924f" dependencies = [ "async-mutex", "async-trait", - "futures", + "futures 0.3.30", "itertools", "lazy_static", "log", @@ -6047,7 +6166,7 @@ dependencies = [ "log", "num-derive 0.3.3", "num-traits", - "parking_lot", + "parking_lot 0.12.1", "qstring", "semver", "solana-sdk 1.17.31", @@ -7131,7 +7250,7 @@ dependencies = [ "ed25519 2.2.3", "ed25519-consensus", "flex-error", - "futures", + "futures 0.3.30", "num-traits", "once_cell", "prost", @@ -7312,7 +7431,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2", @@ -7557,7 +7676,7 @@ dependencies = [ "rustls", "sha1", "thiserror", - "url", + "url 2.5.0", "utf-8", "webpki-roots 0.24.0", ] @@ -7686,6 +7805,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna 0.1.5", + "matches", + "percent-encoding 1.0.1", +] + [[package]] name = "url" version = "2.5.0" @@ -7693,8 +7823,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna", - "percent-encoding", + "idna 0.5.0", + "percent-encoding 2.3.1", ] [[package]] @@ -8186,6 +8316,20 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "witnessed-trie-geyser" +version = "0.0.0" +dependencies = [ + "cf-solana", + "insta", + "jsonrpc-core", + "jsonrpc-core-client", + "jsonrpc-derive", + "lib", + "serde", + "serde_json", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index dea7c5d9..ed24d54a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,9 @@ members = [ "validator", ] exclude = [ - # See solana/trie-geyser/Cargo.toml for description why this is - # not part of the workspace. - "solana/trie-geyser", + # See solana/trie-geyser-plugin/Cargo.toml for description why + # this is not part of the workspace. + "solana/trie-geyser-plugin", ] resolver = "2" @@ -70,6 +70,9 @@ ibc-testkit = { git = "https://github.com/mina86/ibc-rs", rev = ibc-proto = { version = "0.41.0", default-features = false } insta = { version = "1.34.0" } +jsonrpc-core = "18.0" +jsonrpc-core-client = "18.0" +jsonrpc-derive = "18.0" # https://github.com/contain-rs/linear-map/pull/38 adds no_std support linear-map = { git = "https://github.com/contain-rs/linear-map", rev = "57f1432e26ff902bc883b250a85e0b5716bd241c", default-features = false } log = "0.4.20" diff --git a/common/cf-solana/src/proof.rs b/common/cf-solana/src/proof.rs index 10fa9862..78186f9d 100644 --- a/common/cf-solana/src/proof.rs +++ b/common/cf-solana/src/proof.rs @@ -4,6 +4,7 @@ pub use cf_guest::proof::{ generate_for_trie, verify_for_trie, GenerateError, IbcProof, VerifyError, }; use lib::hash::CryptoHash; +#[allow(unused_imports)] use lib::par::prelude::*; #[cfg(test)] diff --git a/common/trie-geyser/Cargo.toml b/common/trie-geyser/Cargo.toml new file mode 100644 index 00000000..e3544f10 --- /dev/null +++ b/common/trie-geyser/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "witnessed-trie-geyser" +authors = ["Michal Nazarewicz "] +version = "0.0.0" +edition = "2021" + +[dependencies] +serde = { workspace = true, features = ["rc", "std"] } +serde_json.workspace = true +jsonrpc-core-client.workspace = true +jsonrpc-core.workspace = true +jsonrpc-derive.workspace = true + +cf-solana = { workspace = true, features = ["serde"] } + +[dev-dependencies] +insta = { workspace = true, features = ["json"] } + +lib = { workspace = true, features = ["test_utils"] } diff --git a/common/trie-geyser/src/api.rs b/common/trie-geyser/src/api.rs new file mode 100644 index 00000000..6ee540f7 --- /dev/null +++ b/common/trie-geyser/src/api.rs @@ -0,0 +1,85 @@ +use std::sync::Arc; + +use cf_solana::proof; + +type Result = ::core::result::Result; + +/// Data provided by the Witnessed Trie Geyser plugin. +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct SlotData { + /// Proof of the accounts delta hash. + pub delta_hash_proof: proof::DeltaHashProof, + /// Proof of the witness trie. + pub witness_proof: proof::AccountProof, + /// Trie root account. + pub root_account: proof::AccountHashData, +} + +#[jsonrpc_derive::rpc] +pub trait Methods { + type Metadata; + + #[rpc(meta, name = "listSlots")] + fn list_slots(&self, meta: Self::Metadata) -> Result>; + + #[rpc(meta, name = "getLatestSlotData")] + fn get_latest_slot_data( + &self, + meta: Self::Metadata, + ) -> Result)>>; + + #[rpc(meta, name = "getSlotData")] + fn get_slot_data( + &self, + meta: Self::Metadata, + slot: u64, + ) -> Result>>; +} + +#[test] +fn test_slot_data_serialisation() { + use cf_solana::types::PubKey; + use lib::hash::CryptoHash; + + let account_hash_data = cf_solana::proof::AccountHashData::new( + 42, + &PubKey(CryptoHash::test(1).into()), + false, + u64::MAX, + b"foo", + &PubKey(CryptoHash::test(2).into()), + ); + + let mut proof = proof::MerkleProof::default(); + let level = [ + CryptoHash::test(10).into(), + CryptoHash::test(11).into(), + CryptoHash::test(12).into(), + ]; + proof.push_level(&level, 1); + + let data = SlotData { + delta_hash_proof: proof::DeltaHashProof { + parent_blockhash: CryptoHash::test(101).into(), + accounts_delta_hash: CryptoHash::test(102).into(), + num_sigs: 103, + blockhash: CryptoHash::test(104).into(), + epoch_accounts_hash: None, + }, + witness_proof: proof::AccountProof { account_hash_data, proof }, + root_account: cf_solana::proof::AccountHashData::new( + 42, + &PubKey(CryptoHash::test(50).into()), + false, + u64::MAX, + b"trie", + &PubKey(CryptoHash::test(51).into()), + ), + }; + + insta::assert_json_snapshot!(data); + let serialised = serde_json::to_string(&data).unwrap(); + + let deserialised = serde_json::from_str(&serialised).unwrap(); + assert_eq!(data, deserialised); +} diff --git a/common/trie-geyser/src/lib.rs b/common/trie-geyser/src/lib.rs new file mode 100644 index 00000000..e5fdf85e --- /dev/null +++ b/common/trie-geyser/src/lib.rs @@ -0,0 +1 @@ +pub mod api; diff --git a/common/trie-geyser/src/snapshots/witnessed_trie_geyser__api__slot_data_serialisation.snap b/common/trie-geyser/src/snapshots/witnessed_trie_geyser__api__slot_data_serialisation.snap new file mode 100644 index 00000000..0cfca0c9 --- /dev/null +++ b/common/trie-geyser/src/snapshots/witnessed_trie_geyser__api__slot_data_serialisation.snap @@ -0,0 +1,27 @@ +--- +source: common/trie-geyser/src/api.rs +expression: data +--- +{ + "delta_hash_proof": { + "parent_blockhash": "AAAAZQAAAGUAAABlAAAAZQAAAGUAAABlAAAAZQAAAGU=", + "accounts_delta_hash": "AAAAZgAAAGYAAABmAAAAZgAAAGYAAABmAAAAZgAAAGY=", + "num_sigs": 103, + "blockhash": "AAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGg=" + }, + "witness_proof": { + "account_hash_data": { + "lamports": 42, + "owner": "1113diW7qC4UejYmrNwaGTZbE5bsokUdUaunfztqD2", + "data": "Zm9v", + "key": "1116GS1EfP7xJU6Yhkt9Xv8BTACkcVxFxApaLznfR3" + }, + "proof": "ARIAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAM" + }, + "root_account": { + "lamports": 42, + "owner": "1113Gen6tYXzqXgPfZv3gKu6WGy4oJUpSnEWNcugZMs", + "data": "dHJpZQ==", + "key": "1113KHVc1Nj4KBQwSRHzFbMf6W3fg7EJ5FpRAHuaPZt" + } +} diff --git a/solana/trie-geyser/.rustfmt.toml b/solana/trie-geyser-plugin/.rustfmt.toml similarity index 100% rename from solana/trie-geyser/.rustfmt.toml rename to solana/trie-geyser-plugin/.rustfmt.toml diff --git a/solana/trie-geyser/Cargo.lock b/solana/trie-geyser-plugin/Cargo.lock similarity index 100% rename from solana/trie-geyser/Cargo.lock rename to solana/trie-geyser-plugin/Cargo.lock diff --git a/solana/trie-geyser/Cargo.toml b/solana/trie-geyser-plugin/Cargo.toml similarity index 98% rename from solana/trie-geyser/Cargo.toml rename to solana/trie-geyser-plugin/Cargo.toml index 254715d6..114e03dc 100644 --- a/solana/trie-geyser/Cargo.toml +++ b/solana/trie-geyser-plugin/Cargo.toml @@ -5,7 +5,7 @@ # being let’s just go with it. [package] -name = "witnessed-trie-geyser" +name = "witnessed-trie-geyser-plugin" version = "0.0.0" edition = "2021" @@ -18,14 +18,17 @@ borsh = "1.5.1" crossbeam-channel = "0.5.13" derive_more = "0.99.18" hex = { git = "https://github.com/mina86/rust-hex.git", branch = "main", default-features = false } +jsonrpc-http-server = "18.0" log = "0.4.17" # TODO(mina86): Change to "1" once we update the toolchain. Building # with serde 1.0.204 breaks due to the use of ‘diagnostic’ attribute. serde = { version = "=1.0.203", features = ["derive"] } serde_json = "1" +#tokio = { version = "1.39", features = ["net", "rt", "sync"] } cf-solana = { path = "../../common/cf-solana", default-features = false, features = ["solana-program-2", "rayon", "serde"] } solana-witnessed-trie = { path = "../witnessed-trie", default-features = false, features = ["api2"] } +witnessed-trie-geyser = { path = "../../common/trie-geyser" } solana-accounts-db = { git = "https://github.com/ComposableFi/mantis-solana.git", branch = "mantis/dev" } solana-geyser-plugin-interface = { git = "https://github.com/ComposableFi/mantis-solana.git", branch = "mantis/dev" } diff --git a/solana/trie-geyser/README.md b/solana/trie-geyser-plugin/README.md similarity index 74% rename from solana/trie-geyser/README.md rename to solana/trie-geyser-plugin/README.md index 5fa19f0b..a36a4ebb 100644 --- a/solana/trie-geyser/README.md +++ b/solana/trie-geyser-plugin/README.md @@ -20,14 +20,14 @@ necessary binaries: cd path/to/emulated-light-client cargo build-sbf - cargo build -r --manifest-path=solana/trie-geyser/Cargo.toml + cargo build -r --manifest-path=solana/trie-geyser-plugin/Cargo.toml To start the Solana validator with the plugin enabled, use the `--geyser-plugin-config` flag to point at the `config.json` file. cd mantis-solana ./target/release/solana-test-validator --geyser-plugin-config \ - path/to/emulated-light-client/solana/trie-geyser/config.json + path/to/emulated-light-client/solana/trie-geyser-plugin/config.json In another terminal, deploy the witnessed-trie contract and test it with provided command line tool: @@ -39,6 +39,13 @@ provided command line tool: You may need to adjust `trie_program` and `root_account` in `config.json` and restart the validator. +The plugin provides an RPC server for getting the proofs and trie data. The +simplest way to test this server is by using httpie utility, for example: + + http 127.0.0.1:42069 jsonrpc:='"2.0"' id=_ method=listSlots + http 127.0.0.1:42069 jsonrpc:='"2.0"' id=_ method=getLatestSlotData + http 127.0.0.1:42069 jsonrpc:='"2.0"' id=_ method=getSlotData params:='[66522]' + ## Using the proof At the moment, the proof is only logged. Mechanism for getting the proof to be diff --git a/solana/trie-geyser-plugin/config.json b/solana/trie-geyser-plugin/config.json new file mode 100644 index 00000000..f3525af0 --- /dev/null +++ b/solana/trie-geyser-plugin/config.json @@ -0,0 +1,6 @@ +{ + "libpath": "target/release/libwitnessed_trie_geyser_plugin.so", + "trie_program": "8Czzh5DFpFAN69Qow3gvpqS4APJyTFpqZR7cJhwphqPE", + "root_account": "4r4XhdAitwVUXmurwF6ywkVjUYnUqxe23NjzBo6MdNsj", + "bind_address": "127.0.0.1:42069" +} diff --git a/solana/trie-geyser/src/config.rs b/solana/trie-geyser-plugin/src/config.rs similarity index 89% rename from solana/trie-geyser/src/config.rs rename to solana/trie-geyser-plugin/src/config.rs index 31b0a447..3d81efb8 100644 --- a/solana/trie-geyser/src/config.rs +++ b/solana/trie-geyser-plugin/src/config.rs @@ -2,12 +2,6 @@ use cf_solana::types::PubKey; use solana_geyser_plugin_interface::geyser_plugin_interface; use solana_sdk::pubkey::Pubkey; -#[derive(Debug, Clone)] -pub struct Config { - pub root_account: Pubkey, - pub witness_account: Pubkey, -} - #[derive(Debug, Clone, serde::Deserialize)] #[serde(deny_unknown_fields)] struct FileConfig { @@ -15,6 +9,14 @@ struct FileConfig { _libpath: String, trie_program: PubKey, root_account: PubKey, + bind_address: std::net::SocketAddr, +} + +#[derive(Debug, Clone)] +pub struct Config { + pub root_account: Pubkey, + pub witness_account: Pubkey, + pub bind_address: std::net::SocketAddr, } #[derive(Debug, derive_more::From, derive_more::Display)] @@ -39,13 +41,14 @@ impl Config { serde_json::from_reader(std::fs::File::open(file)?)?; let trie_program = Pubkey::from(cfg.trie_program); let root_account = Pubkey::from(cfg.root_account); + let bind_address = cfg.bind_address; let (witness_account, _) = wittrie::api::find_witness_account(&trie_program, &root_account) .ok_or_else(|| Error::UnableToFindWitnessAccount { trie_program, root_account, })?; - Ok(Self { root_account, witness_account }) + Ok(Self { root_account, witness_account, bind_address }) } } diff --git a/solana/trie-geyser/src/lib.rs b/solana/trie-geyser-plugin/src/lib.rs similarity index 88% rename from solana/trie-geyser/src/lib.rs rename to solana/trie-geyser-plugin/src/lib.rs index e799a76d..45621565 100644 --- a/solana/trie-geyser/src/lib.rs +++ b/solana/trie-geyser-plugin/src/lib.rs @@ -1,7 +1,11 @@ +extern crate alloc; +extern crate core; + use solana_geyser_plugin_interface::geyser_plugin_interface::GeyserPlugin; mod config; mod plugin; +mod rpc; mod types; mod utils; mod worker; diff --git a/solana/trie-geyser/src/plugin.rs b/solana/trie-geyser-plugin/src/plugin.rs similarity index 81% rename from solana/trie-geyser/src/plugin.rs rename to solana/trie-geyser-plugin/src/plugin.rs index 585e3552..f12e3a56 100644 --- a/solana/trie-geyser/src/plugin.rs +++ b/solana/trie-geyser-plugin/src/plugin.rs @@ -3,17 +3,21 @@ use solana_geyser_plugin_interface::geyser_plugin_interface::{ ReplicaBlockInfoVersions, ReplicaTransactionInfoVersions, }; -use crate::{config, types, utils, worker}; +use crate::{config, rpc, types, utils, worker}; type Result = ::core::result::Result; #[derive(Debug, Default)] pub(crate) struct Plugin(Option); -#[derive(Debug)] struct Inner { + /// The RPC server. + server: jsonrpc_http_server::Server, + + /// The worker thread. + worker: std::thread::JoinHandle<()>, + /// Channel for sending messages to the worker. sender: crossbeam_channel::Sender, - thread: std::thread::JoinHandle<()>, } impl Plugin { @@ -58,30 +62,31 @@ impl GeyserPlugin for Plugin { return Err(utils::custom_err(msg)); } - let cfg = + let config = config::Config::load(config_file.as_ref()).map_err(|err| { log::error!("{config_file}: {err}"); err })?; - let (sender, receiver) = crossbeam_channel::unbounded(); - let thread = std::thread::Builder::new() - .name("witnessed-trie-worker".into()) - .spawn(move || worker::worker(cfg, receiver)) - .map_err(|err| { - log::error!("{err}"); - utils::custom_err(err) - })?; - self.0 = Some(Inner { sender, thread }); + + let (server, db) = rpc::spawn_server(&config.bind_address)?; + let (worker, sender) = worker::spawn_worker(config, db)?; + + self.0 = Some(Inner { worker, sender, server }); Ok(()) } /// Resets the object and terminates the worker thread. fn on_unload(&mut self) { - let handler = self.0.take().map(Inner::into_join_handler); - let err = match handler.and_then(|h| h.join().err()) { - Some(err) => err, + let inner = match self.0.take() { + Some(inner) => inner, None => return, }; + inner.server.close(); + core::mem::drop(inner.sender); + let err = match inner.worker.join() { + Ok(()) => return, + Err(err) => err, + }; if let Some(msg) = err.downcast_ref::<&str>() { log::error!("worker thread panicked: {msg}") } else if let Some(msg) = err.downcast_ref::() { @@ -184,10 +189,27 @@ impl Inner { fn send_message(&self, message: worker::Message) -> Result { self.sender.send(message).map_err(utils::custom_err) } +} - /// Consumes self and returns the worker join handler. - /// - /// The send channel is disconnected which signals the worker to terminate. - /// The returned handler can be used to wait for the thread. - fn into_join_handler(self) -> std::thread::JoinHandle<()> { self.thread } +impl core::fmt::Debug for Inner { + fn fmt(&self, fmtr: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + struct Thread<'a>(&'a std::thread::Thread); + + impl core::fmt::Debug for Thread<'_> { + fn fmt( + &self, + fmtr: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + match self.0.name() { + Some(name) => fmtr.write_str(name), + None => self.0.id().fmt(fmtr), + } + } + } + + fmtr.debug_struct("Inner") + .field("server", self.server.address()) + .field("worker", &Thread(self.worker.thread())) + .finish_non_exhaustive() + } } diff --git a/solana/trie-geyser-plugin/src/rpc.rs b/solana/trie-geyser-plugin/src/rpc.rs new file mode 100644 index 00000000..17a919ce --- /dev/null +++ b/solana/trie-geyser-plugin/src/rpc.rs @@ -0,0 +1,166 @@ +use alloc::collections::VecDeque; +use std::sync::Arc; +// use tokio::sync::{RwLock, Notify}; +// use tokio::net::{TcpListener, TcpStream}; +use std::sync::RwLock; + +use jsonrpc_http_server::jsonrpc_core; +use solana_geyser_plugin_interface::geyser_plugin_interface::GeyserPluginError; +use witnessed_trie_geyser::api::Methods as _; + +use crate::utils; + +pub type Result = core::result::Result; +pub(crate) use witnessed_trie_geyser::api::SlotData; + +/// Mximum number of past slot for which data is stored. +/// +/// Since we only store information for slots in which the trie has changed, +/// the oldest slot the database will keep information for will usually be much +/// older than this number. +const MAX_SLOTS: usize = 128; + +/// Name of the RPC server thread. +const THREAD_NAME: &str = "witnessed-trie-rpc"; + +pub(crate) struct Database { + /// Slot numbers for each corresponding entry in `slot_data` list. + /// + /// Together with `slot_data` this forms a slot number → slot data mapping. + /// The two vectors are separated so that lookup can be performed quickly + /// with lower pressure on cache. + /// + /// The mapping has a limited size (see `MAX_SLOTS`) and whenever a new + /// entry is inserted the oldest one is removed. We’re not using a hash map + /// because it doesn’t offer easy access to the element with lowest key. + slot_nums: VecDeque, + + /// Data for slot whose number is stored in corresponding entry in + /// `slot_nums` list. See `slot_nums` for more context. + slot_data: VecDeque>, +} + +pub(crate) type DBHandle = Arc>; + +impl Default for Database { + fn default() -> Self { + Self { + slot_nums: VecDeque::with_capacity(MAX_SLOTS), + slot_data: VecDeque::with_capacity(MAX_SLOTS), + } + } +} + +impl Database { + /// Creates new empty database. + pub fn new() -> DBHandle { Arc::new(Default::default()) } + + /// Adds a new entry to the database. + /// + /// `slot` must be greater than slot number for any existing entry. Data is + /// not inserted if this is not the case. + pub fn add(&mut self, slot: u64, data: SlotData) { + if let Some(&last) = self.slot_nums.back() { + if last >= slot { + log::error!( + "{THREAD_NAME}: trying to insert rooted slot {slot} out \ + of order; latest is {last}" + ); + return; + } + } + if self.slot_nums.len() == MAX_SLOTS { + self.slot_nums.pop_front(); + self.slot_data.pop_front(); + } + self.slot_nums.push_back(slot); + self.slot_data.push_back(Arc::new(data)); + } + + /// Returns list of all slots for which data exists. + pub fn list_slots(&self) -> Vec { + self.slot_nums.iter().copied().collect() + } + + /// Returns data for the latest slot. + pub fn get_latest(&self) -> Option<(u64, Arc)> { + let slot = *self.slot_nums.back()?; + let data = self.slot_data.back()?.clone(); + Some((slot, data)) + } + + /// Returns data for given slot. + pub fn get(&self, slot: u64) -> Option> { + self.slot_nums + .binary_search(&slot) + .ok() + .map(|index| self.slot_data[index].clone()) + } +} + +pub(crate) fn spawn_server( + bind_address: &std::net::SocketAddr, +) -> Result<(jsonrpc_http_server::Server, DBHandle), GeyserPluginError> { + let mut io = jsonrpc_core::MetaIoHandler::default(); + io.extend_with(Server.to_delegate()); + + let db = Database::new(); + let server = { + let db = db.clone(); + jsonrpc_http_server::ServerBuilder::with_meta_extractor( + io, + move |_: &_| db.clone(), + ) + } + .cors(jsonrpc_http_server::DomainsValidation::AllowOnly(vec![ + jsonrpc_http_server::AccessControlAllowOrigin::Any, + ])) + .cors_max_age(86400) + .start_http(bind_address) + .map_err(|err| { + log::error!("{bind_address}: {err}"); + utils::custom_err(err) + })?; + Ok((server, db)) +} + + +struct Server; + +impl Server { + fn read( + meta: &DBHandle, + func: impl FnOnce(&Database) -> T, + ) -> Result { + match meta.try_read() { + Ok(db) => Ok(func(&db)), + Err(err) => { + log::error!("{err}"); + Err(jsonrpc_core::Error::internal_error()) + } + } + } +} + +impl witnessed_trie_geyser::api::Methods for Server { + type Metadata = DBHandle; + + fn list_slots(&self, meta: Self::Metadata) -> Result> { + Self::read(&meta, Database::list_slots) + } + + fn get_latest_slot_data( + &self, + meta: Self::Metadata, + ) -> Result)>> { + Self::read(&meta, Database::get_latest) + } + + fn get_slot_data( + &self, + meta: Self::Metadata, + slot: u64, + ) -> Result>> { + Self::read(&meta, |meta| meta.get(slot)) + } +} diff --git a/solana/trie-geyser/src/types.rs b/solana/trie-geyser-plugin/src/types.rs similarity index 100% rename from solana/trie-geyser/src/types.rs rename to solana/trie-geyser-plugin/src/types.rs diff --git a/solana/trie-geyser/src/utils.rs b/solana/trie-geyser-plugin/src/utils.rs similarity index 100% rename from solana/trie-geyser/src/utils.rs rename to solana/trie-geyser-plugin/src/utils.rs diff --git a/solana/trie-geyser/src/worker.rs b/solana/trie-geyser-plugin/src/worker.rs similarity index 73% rename from solana/trie-geyser/src/worker.rs rename to solana/trie-geyser-plugin/src/worker.rs index dda44b2c..36d99047 100644 --- a/solana/trie-geyser/src/worker.rs +++ b/solana/trie-geyser-plugin/src/worker.rs @@ -1,9 +1,13 @@ use std::collections::{BTreeMap, HashMap}; use cf_solana::proof::AccountHashData; +use solana_geyser_plugin_interface::geyser_plugin_interface::GeyserPluginError; use solana_sdk::pubkey::Pubkey; -use crate::{config, types, utils}; +use crate::{config, rpc, types, utils}; + +/// Name of the RPC server thread. +const THREAD_NAME: &str = "witnessed-trie-worker"; /// Message sent from the plugin to the worker thread. #[derive(derive_more::From)] @@ -42,6 +46,8 @@ struct Worker { config: config::Config, /// Data accumulated about individual slots. slots: BTreeMap, + /// Database handle + db: rpc::DBHandle, } /// State of a slot which hasn’t been rooted yet. @@ -56,11 +62,30 @@ struct SlotAccumulator { num_sigs: u64, } -pub(crate) fn worker( +pub(crate) fn spawn_worker( + config: config::Config, + db: rpc::DBHandle, +) -> Result< + (std::thread::JoinHandle<()>, crossbeam_channel::Sender), + GeyserPluginError, +> { + let (sender, receiver) = crossbeam_channel::unbounded(); + std::thread::Builder::new() + .name(THREAD_NAME.into()) + .spawn(move || worker(config, receiver, db)) + .map(|handle| (handle, sender)) + .map_err(|err| { + log::error!("{err}"); + utils::custom_err(err) + }) +} + +fn worker( config: config::Config, receiver: crossbeam_channel::Receiver, + db: rpc::DBHandle, ) { - let mut worker = Worker { config, slots: Default::default() }; + let mut worker = Worker { config, slots: Default::default(), db }; for msg in receiver { match msg { Message::Account(msg) => worker.handle_account(msg), @@ -71,6 +96,7 @@ pub(crate) fn worker( } } } + log::info!("{THREAD_NAME}: terminating"); } impl Worker { @@ -87,8 +113,8 @@ impl Worker { } } - log::info!( - "[{}-{}] account update: {}", + log::debug!( + "#{}-{}: account update: {}", info.slot, info.write_version, info.account.key(), @@ -128,19 +154,23 @@ impl Worker { if key == slot { break value; } - log::info!("[{key}] dropping accumulator"); + log::debug!("#{key}: dropping accumulator"); } _ => { - log::error!("[{slot}] accumulator not found"); + log::error!("#{slot}: accumulator not found"); return; } } }; - // If the witness account is not in collection of changed accounts, - // don’t do anything. + // If the trie or witness accounts are not in collection of changed + // accounts, don’t do anything. if !entry.accounts.contains_key(&self.config.witness_account) { - log::info!("[{slot}] witness account not modified"); + log::debug!("#{slot}: witness account not modified"); + return; + } + if !entry.accounts.contains_key(&self.config.root_account) { + log::debug!("#{slot}: trie account not modified"); return; } @@ -148,7 +178,7 @@ impl Worker { let block = if let Some(block) = entry.block { block } else { - log::error!("[{slot}] missing block info"); + log::error!("#{slot}: missing block info"); return; }; @@ -162,7 +192,7 @@ impl Worker { .collect(); // Create account proof for the witness account. - let (accounts_delta_hash, account_proof) = entry + let (accounts_delta_hash, witness_proof) = entry .accounts .remove(&self.config.witness_account) .unwrap() @@ -172,7 +202,7 @@ impl Worker { // Calculate bankhash based on accounts_delta_hash and information // extracted from the block. - let hash_proof = cf_solana::proof::DeltaHashProof { + let delta_hash_proof = cf_solana::proof::DeltaHashProof { parent_blockhash: block.parent_blockhash.into(), accounts_delta_hash, num_sigs: entry.num_sigs, @@ -185,10 +215,13 @@ impl Worker { epoch_accounts_hash: None, }; - // TODO(mina86): Figure out how we communicate the proofs to relayer. - let bank_hash = hash_proof.calculate_bank_hash(); - log::info!("[{slot}] bank_hash: {bank_hash}"); - log::info!("[{slot}] hash_proof: {hash_proof:?}"); - log::info!("[{slot}] account_proof: {account_proof:?}"); + // Add the trie account and witness account proof to the database. + let root_account = + entry.accounts.remove(&self.config.root_account).unwrap().1; + let data = + rpc::SlotData { delta_hash_proof, witness_proof, root_account }; + + log::info!("#{slot}: adding to database"); + self.db.write().unwrap().add(slot, data); } } diff --git a/solana/trie-geyser/config.json b/solana/trie-geyser/config.json deleted file mode 100644 index 9a0d52a5..00000000 --- a/solana/trie-geyser/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libpath": "target/release/libwitnessed_trie_geyser.so", - "trie_program": "8Czzh5DFpFAN69Qow3gvpqS4APJyTFpqZR7cJhwphqPE", - "root_account": "4r4XhdAitwVUXmurwF6ywkVjUYnUqxe23NjzBo6MdNsj" -} From 206476602b1dcfb3da30b69d6b125598540a98bd Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Thu, 29 Aug 2024 19:53:25 +0200 Subject: [PATCH 2/3] fix miri --- common/trie-geyser/src/api.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/trie-geyser/src/api.rs b/common/trie-geyser/src/api.rs index 6ee540f7..f722d8d0 100644 --- a/common/trie-geyser/src/api.rs +++ b/common/trie-geyser/src/api.rs @@ -77,7 +77,9 @@ fn test_slot_data_serialisation() { ), }; - insta::assert_json_snapshot!(data); + if !cfg!(miri) { + insta::assert_json_snapshot!(data); + } let serialised = serde_json::to_string(&data).unwrap(); let deserialised = serde_json::from_str(&serialised).unwrap(); From 8bd7f6728aeba54243adb70b8ea087e8d6630f2d Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Wed, 4 Sep 2024 16:45:07 +0200 Subject: [PATCH 3/3] add MacOS instructions --- solana/trie-geyser-plugin/README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/solana/trie-geyser-plugin/README.md b/solana/trie-geyser-plugin/README.md index a36a4ebb..8e38b477 100644 --- a/solana/trie-geyser-plugin/README.md +++ b/solana/trie-geyser-plugin/README.md @@ -23,11 +23,16 @@ necessary binaries: cargo build -r --manifest-path=solana/trie-geyser-plugin/Cargo.toml To start the Solana validator with the plugin enabled, use the -`--geyser-plugin-config` flag to point at the `config.json` file. +`--geyser-plugin-config` flag to point at the `config.json` file. Note that +when running on MacOS, path in `config.json` needs to be updated to point to +a `.dylib` file. cd mantis-solana - ./target/release/solana-test-validator --geyser-plugin-config \ - path/to/emulated-light-client/solana/trie-geyser-plugin/config.json + config=path/to/emulated-light-client/solana/trie-geyser-plugin/config.json + # On MacOS execute: + # sed -i s/\\.so/.dylib/ -- "${config:?}" + ./target/release/solana-test-validator \ + --geyser-plugin-config "${config:?}" In another terminal, deploy the witnessed-trie contract and test it with provided command line tool: