diff --git a/.github/workflows/ci-test-integration.yml b/.github/workflows/ci-test-integration.yml index 00b9dfbc1..7dc1a4a57 100644 --- a/.github/workflows/ci-test-integration.yml +++ b/.github/workflows/ci-test-integration.yml @@ -71,6 +71,25 @@ jobs: - uses: ./magicblock-validator/.github/actions/setup-solana + - name: Install Photon indexer + if: matrix.batch_tests == 'table_mania' || matrix.batch_tests == 'committor' || matrix.batch_tests == 'chainlink' + shell: bash + env: + RUSTFLAGS: "-A dead-code" + run: cargo install --git https://github.com/lightprotocol/photon.git --rev ac7df6c388db847b7693a7a1cb766a7c9d7809b5 --locked --force + + - name: Setup Node.js + if: matrix.batch_tests == 'table_mania' || matrix.batch_tests == 'committor' || matrix.batch_tests == 'chainlink' + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install zk-compression CLI + if: matrix.batch_tests == 'table_mania' || matrix.batch_tests == 'committor' || matrix.batch_tests == 'chainlink' + run: | + npm i -g @lightprotocol/zk-compression-cli@0.27.1-alpha.10 + shell: bash + - name: Run integration tests - ${{ matrix.batch_tests }} run: | sudo prlimit --pid $$ --nofile=1048576:1048576 diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index 7371799c3..bb284b7ee 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -244,4 +244,4 @@ jobs: cargo publish $DRY_RUN_FLAG --manifest-path=./Cargo.toml --token $CRATES_TOKEN $NO_VERIFY_FLAG env: CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} - DRY_RUN: ${{ env.DRY_RUN }} + DRY_RUN: ${{ env.DRY_RUN }} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b58ffc68d..6fb1b2a11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -198,9 +198,12 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] [[package]] name = "ark-bn254" @@ -208,9 +211,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" dependencies = [ - "ark-ec", - "ark-ff", - "ark-std", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", ] [[package]] @@ -219,10 +233,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", "itertools 0.10.5", @@ -230,16 +244,37 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash 0.8.12", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.2", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "digest 0.10.7", "itertools 0.10.5", @@ -250,6 +285,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "zeroize", +] + [[package]] name = "ark-ff-asm" version = "0.4.2" @@ -260,6 +315,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.111", +] + [[package]] name = "ark-ff-macros" version = "0.4.2" @@ -273,27 +338,68 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "ark-poly" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", ] +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash 0.8.12", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.2", +] + [[package]] name = "ark-serialize" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ - "ark-serialize-derive", - "ark-std", + "ark-serialize-derive 0.4.2", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", + "arrayvec", "digest 0.10.7", "num-bigint 0.4.6", ] @@ -309,6 +415,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "ark-std" version = "0.4.0" @@ -319,6 +436,17 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", + "rayon", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -459,11 +587,11 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "axum-core 0.5.5", + "axum-core 0.5.6", "bytes", "futures-util", "http 1.4.0", @@ -504,9 +632,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -526,6 +654,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -576,7 +710,7 @@ dependencies = [ "bitflags 2.10.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.10.5", "proc-macro2", "quote", "regex", @@ -782,18 +916,18 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", @@ -824,9 +958,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "jobserver", @@ -975,6 +1109,35 @@ dependencies = [ "unreachable", ] +[[package]] +name = "compressed-delegation-api" +version = "0.1.0" +dependencies = [ + "borsh 0.10.4", + "light-compressed-account", + "light-hasher", + "light-sdk", + "solana-pubkey 2.2.1", +] + +[[package]] +name = "compressed-delegation-client" +version = "0.6.1" +dependencies = [ + "borsh 0.10.4", + "compressed-delegation-api", + "light-compressed-account", + "light-hasher", + "light-sdk", + "light-sdk-types", + "serde", + "solana-account-info", + "solana-cpi", + "solana-instruction 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", +] + [[package]] name = "compression-codecs" version = "0.4.35" @@ -1134,6 +1297,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -1288,18 +1470,18 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", @@ -1387,6 +1569,18 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "either" version = "1.15.0" @@ -1428,6 +1622,26 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "env_logger" version = "0.9.3" @@ -1514,7 +1728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.59.0", ] @@ -1546,9 +1760,18 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + +[[package]] +name = "five8" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "23f76610e969fa1784327ded240f1e28a3fd9520c9cec93b636fcf62dd37f772" +dependencies = [ + "five8_core 1.0.0", +] [[package]] name = "five8_const" @@ -1556,7 +1779,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26dec3da8bc3ef08f2c04f61eab298c3ab334523e55f076354d6d6f613799a7b" dependencies = [ - "five8_core", + "five8_core 0.1.2", +] + +[[package]] +name = "five8_const" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0f1728185f277989ca573a402716ae0beaaea3f76a8ff87ef9dd8fb19436c5" +dependencies = [ + "five8_core 1.0.0", ] [[package]] @@ -1565,12 +1797,24 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5" +[[package]] +name = "five8_core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059c31d7d36c43fe39d89e55711858b4da8be7eb6dabac23c7289b1a19489406" + [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.5" @@ -1754,7 +1998,7 @@ dependencies = [ "clap 4.5.53", "magicblock-accounts-db", "solana-commitment-config", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "sonic-rs", "tempfile", @@ -1988,7 +2232,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.6", "rand 0.8.5", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "sha2 0.10.9", @@ -2209,6 +2453,22 @@ dependencies = [ "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls 0.23.35", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", +] + [[package]] name = "hyper-timeout" version = "0.5.2" @@ -2235,12 +2495,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -2248,12 +2525,16 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "hyper 1.8.1", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2 0.6.1", + "system-configuration 0.6.1", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2445,6 +2726,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2479,6 +2770,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -2490,9 +2790,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" @@ -2570,7 +2870,7 @@ dependencies = [ "solana-account", "solana-clock", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-transaction-status", "structopt", @@ -2668,26 +2968,345 @@ dependencies = [ ] [[package]] -name = "libz-sys" -version = "1.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +name = "libz-sys" +version = "1.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "light-account-checks" +version = "0.3.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "solana-account-info", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "solana-sysvar", + "thiserror 2.0.17", +] + +[[package]] +name = "light-bounded-vec" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58cfa375d028164719e3ffef93d2e5c27855cc8a5bb5bf257b868d17c12a3e66" +dependencies = [ + "bytemuck", + "memoffset", + "solana-program-error 2.2.2", + "thiserror 1.0.69", +] + +[[package]] +name = "light-client" +version = "0.13.1" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "async-trait", + "base64 0.13.1", + "borsh 0.10.4", + "bs58", + "bytemuck", + "lazy_static", + "light-compressed-account", + "light-concurrent-merkle-tree", + "light-hasher", + "light-indexed-merkle-tree", + "light-merkle-tree-metadata", + "light-prover-client", + "light-sdk", + "num-bigint 0.4.6", + "num-traits", + "photon-api", + "rand 0.8.5", + "solana-account", + "solana-account-decoder-client-types", + "solana-address-lookup-table-interface", + "solana-clock", + "solana-commitment-config", + "solana-compute-budget-interface", + "solana-epoch-info", + "solana-hash", + "solana-instruction 2.2.1", + "solana-keypair", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-signature", + "solana-transaction", + "solana-transaction-error", + "solana-transaction-status-client-types", + "thiserror 2.0.17", + "tokio", + "tracing", +] + +[[package]] +name = "light-compressed-account" +version = "0.3.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "borsh 0.10.4", + "bytemuck", + "light-hasher", + "light-macros", + "light-program-profiler", + "light-zero-copy", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "thiserror 2.0.17", + "zerocopy", +] + +[[package]] +name = "light-concurrent-merkle-tree" +version = "2.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "borsh 0.10.4", + "light-bounded-vec", + "light-hasher", + "memoffset", + "solana-program-error 2.2.2", + "thiserror 2.0.17", +] + +[[package]] +name = "light-hasher" +version = "3.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "arrayvec", + "borsh 0.10.4", + "light-poseidon 0.3.0", + "num-bigint 0.4.6", + "sha2 0.10.9", + "sha3", + "solana-nostd-keccak", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "thiserror 2.0.17", +] + +[[package]] +name = "light-indexed-array" +version = "0.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "light-hasher", + "num-bigint 0.4.6", + "num-traits", + "thiserror 2.0.17", +] + +[[package]] +name = "light-indexed-merkle-tree" +version = "2.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "light-bounded-vec", + "light-concurrent-merkle-tree", + "light-hasher", + "light-merkle-tree-reference", + "num-bigint 0.4.6", + "num-traits", + "solana-program-error 2.2.2", + "thiserror 2.0.17", +] + +[[package]] +name = "light-macros" +version = "2.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "light-merkle-tree-metadata" +version = "0.3.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "borsh 0.10.4", + "bytemuck", + "light-compressed-account", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-sysvar", + "thiserror 2.0.17", + "zerocopy", +] + +[[package]] +name = "light-merkle-tree-reference" +version = "2.0.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "light-hasher", + "light-indexed-array", + "num-bigint 0.4.6", + "num-traits", + "thiserror 2.0.17", +] + +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254 0.4.0", + "ark-ff 0.4.2", + "num-bigint 0.4.6", + "thiserror 1.0.69", +] + +[[package]] +name = "light-poseidon" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3d87542063daaccbfecd78b60f988079b6ec4e089249658b9455075c78d42" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "num-bigint 0.4.6", + "thiserror 1.0.69", +] + +[[package]] +name = "light-profiler-macro" +version = "0.1.0" +source = "git+https://github.com/Lightprotocol/light-program-profiler?rev=36a75e14f54dd862bf2f338c97435ffc7e3e8de9#36a75e14f54dd862bf2f338c97435ffc7e3e8de9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "light-program-profiler" +version = "0.1.0" +source = "git+https://github.com/Lightprotocol/light-program-profiler?rev=36a75e14f54dd862bf2f338c97435ffc7e3e8de9#36a75e14f54dd862bf2f338c97435ffc7e3e8de9" +dependencies = [ + "light-profiler-macro", +] + +[[package]] +name = "light-prover-client" +version = "2.0.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "ark-bn254 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "light-hasher", + "light-indexed-array", + "light-sparse-merkle-tree", + "num-bigint 0.4.6", + "num-traits", + "reqwest 0.11.27", + "serde", + "serde_json", + "solana-bn254", + "thiserror 2.0.17", + "tokio", + "tracing", +] + +[[package]] +name = "light-sdk" +version = "0.13.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "borsh 0.10.4", + "light-account-checks", + "light-compressed-account", + "light-hasher", + "light-macros", + "light-sdk-macros", + "light-sdk-types", + "light-zero-copy", + "num-bigint 0.4.6", + "solana-account-info", + "solana-cpi", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "thiserror 2.0.17", +] + +[[package]] +name = "light-sdk-macros" +version = "0.13.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "light-hasher", + "light-poseidon 0.3.0", + "proc-macro2", + "quote", + "solana-pubkey 2.2.1", + "syn 2.0.111", +] + +[[package]] +name = "light-sdk-types" +version = "0.13.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "borsh 0.10.4", + "light-account-checks", + "light-compressed-account", + "light-hasher", + "light-macros", + "light-zero-copy", + "solana-msg 2.2.1", + "thiserror 2.0.17", +] + +[[package]] +name = "light-sparse-merkle-tree" +version = "0.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" dependencies = [ - "cc", - "pkg-config", - "vcpkg", + "light-hasher", + "light-indexed-array", + "num-bigint 0.4.6", + "num-traits", + "thiserror 2.0.17", ] [[package]] -name = "light-poseidon" +name = "light-zero-copy" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" dependencies = [ - "ark-bn254", - "ark-ff", - "num-bigint 0.4.6", - "thiserror 1.0.69", + "light-zero-copy-derive", + "zerocopy", +] + +[[package]] +name = "light-zero-copy-derive" +version = "0.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -2773,7 +3392,7 @@ dependencies = [ "borsh 1.6.0", "bytemuck_derive", "solana-program", - "solana-system-interface", + "solana-system-interface 3.0.0", ] [[package]] @@ -2794,10 +3413,10 @@ dependencies = [ "rand 0.9.2", "solana-account", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-loader-v3-interface", "solana-loader-v4-interface", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-signature", "solana-signer", @@ -2820,7 +3439,7 @@ dependencies = [ "magicblock-core", "magicblock-program", "solana-hash", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-transaction", "solana-transaction-error", "thiserror 1.0.69", @@ -2841,7 +3460,7 @@ dependencies = [ "parking_lot", "reflink-copy", "solana-account", - "solana-pubkey", + "solana-pubkey 2.2.1", "tempfile", "test-kit", "thiserror 1.0.69", @@ -2873,7 +3492,7 @@ dependencies = [ "magicblock-version", "parking_lot", "rand 0.9.2", - "reqwest", + "reqwest 0.11.27", "scc", "serde", "solana-account", @@ -2884,7 +3503,7 @@ dependencies = [ "solana-fee-structure", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-pubsub-client", "solana-rpc-client", "solana-rpc-client-api", @@ -2908,6 +3527,7 @@ dependencies = [ "anyhow", "borsh 1.6.0", "fd-lock", + "light-client", "magic-domain-program", "magicblock-account-cloner", "magicblock-accounts", @@ -2935,12 +3555,12 @@ dependencies = [ "solana-genesis-config", "solana-hash", "solana-inline-spl", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", "solana-native-token", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-rpc-client", "solana-sha256-hasher", @@ -2964,8 +3584,11 @@ dependencies = [ "assert_matches", "async-trait", "bincode", + "borsh 0.10.4", + "compressed-delegation-client", "futures-util", "helius-laserstream", + "light-client", "lru", "magicblock-chainlink", "magicblock-config", @@ -2983,20 +3606,20 @@ dependencies = [ "solana-commitment-config", "solana-feature-set", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-loader-v3-interface", "solana-loader-v4-interface", "solana-message", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-pubsub-client", "solana-rpc-client", "solana-rpc-client-api", "solana-sdk-ids", "solana-signature", "solana-signer", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-sysvar", "solana-transaction", "solana-transaction-error", @@ -3014,11 +3637,11 @@ dependencies = [ name = "magicblock-committor-program" version = "0.6.1" dependencies = [ - "borsh 1.6.0", + "borsh 0.10.4", "paste", "solana-account", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "thiserror 1.0.69", ] @@ -3029,12 +3652,17 @@ dependencies = [ "async-trait", "base64 0.21.7", "bincode", - "borsh 1.6.0", + "borsh 0.10.4", + "compressed-delegation-client", "dyn-clone", "futures-util", "lazy_static", + "light-client", + "light-sdk", "lru", "magicblock-committor-program", + "magicblock-config", + "magicblock-core", "magicblock-delegation-program", "magicblock-metrics", "magicblock-program", @@ -3048,11 +3676,11 @@ dependencies = [ "solana-commitment-config", "solana-compute-budget-interface", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-rpc-client-api", "solana-signature", @@ -3085,7 +3713,7 @@ dependencies = [ "serde_with", "serial_test", "solana-keypair", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signer", "tempfile", "toml 0.8.23", @@ -3096,13 +3724,16 @@ dependencies = [ name = "magicblock-core" version = "0.6.1" dependencies = [ + "compressed-delegation-client", "flume", + "light-compressed-account", + "light-sdk", "magicblock-magic-program-api", "solana-account", "solana-account-decoder", "solana-hash", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-transaction", "solana-transaction-context", @@ -3130,7 +3761,7 @@ dependencies = [ "pinocchio-pubkey", "pinocchio-system", "rkyv 0.7.45", - "solana-curve25519", + "solana-curve25519 3.1.5", "solana-program", "solana-security-txt", "static_assertions", @@ -3159,12 +3790,12 @@ dependencies = [ "solana-account-decoder", "solana-clock", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-measure", "solana-message", "solana-metrics", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-signer", "solana-storage-proto", @@ -3228,7 +3859,7 @@ dependencies = [ "solana-loader-v4-program", "solana-program", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent-collector", "solana-sdk-ids", "solana-signature", @@ -3265,11 +3896,11 @@ dependencies = [ "solana-clock", "solana-fee-calculator", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-log-collector", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-seed-derivable", "solana-signature", @@ -3291,8 +3922,8 @@ dependencies = [ "solana-clock", "solana-commitment-config", "solana-hash", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-rpc-client-api", "solana-signature", @@ -3316,10 +3947,10 @@ dependencies = [ "solana-clock", "solana-commitment-config", "solana-compute-budget-interface", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-signer", "solana-slot-hashes", @@ -3341,9 +3972,9 @@ dependencies = [ "magicblock-ledger", "magicblock-program", "rusqlite", - "solana-instruction", + "solana-instruction 2.2.1", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-rpc-client-api", "solana-signature", @@ -3398,7 +4029,7 @@ dependencies = [ "solana-feature-set", "solana-frozen-abi-macro", "solana-rpc-client-api", - "solana-sanitize", + "solana-sanitize 2.2.1", ] [[package]] @@ -3516,6 +4147,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "munge" version = "0.4.7" @@ -3554,7 +4191,7 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", "security-framework 2.11.1", @@ -3614,6 +4251,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "serde", ] [[package]] @@ -3782,6 +4420,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "openssl-sys" version = "0.9.111" @@ -3876,10 +4520,34 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset", + "fixedbitset 0.4.2", + "indexmap 2.12.1", +] + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset 0.5.7", "indexmap 2.12.1", ] +[[package]] +name = "photon-api" +version = "0.51.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "reqwest 0.12.28", + "serde", + "serde_derive", + "serde_json", + "serde_with", + "url", + "uuid", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -3944,7 +4612,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0225638cadcbebae8932cb7f49cb5da7c15c21beb19f048f05a5ca7d93f065" dependencies = [ - "five8_const", + "five8_const 0.1.4", "pinocchio", "sha2-const-stable", ] @@ -3979,9 +4647,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "potential_utf" @@ -4025,9 +4693,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.36" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn 2.0.111", @@ -4048,7 +4716,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.9", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -4077,9 +4745,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -4163,8 +4831,8 @@ dependencies = [ "itertools 0.10.5", "lazy_static", "log", - "multimap", - "petgraph", + "multimap 0.8.3", + "petgraph 0.6.5", "prettyplease 0.1.25", "prost 0.11.9", "prost-types 0.11.9", @@ -4184,10 +4852,10 @@ dependencies = [ "heck 0.5.0", "itertools 0.12.1", "log", - "multimap", + "multimap 0.10.1", "once_cell", - "petgraph", - "prettyplease 0.2.36", + "petgraph 0.6.5", + "prettyplease 0.2.37", "prost 0.12.6", "prost-types 0.12.6", "regex", @@ -4204,10 +4872,10 @@ dependencies = [ "heck 0.5.0", "itertools 0.14.0", "log", - "multimap", + "multimap 0.10.1", "once_cell", - "petgraph", - "prettyplease 0.2.36", + "petgraph 0.7.1", + "prettyplease 0.2.37", "prost 0.13.5", "prost-types 0.13.5", "regex", @@ -4508,6 +5176,26 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -4545,7 +5233,7 @@ checksum = "23bbed272e39c47a095a5242218a67412a220006842558b03fe2935e8f3d7b92" dependencies = [ "cfg-if", "libc", - "rustix 1.1.2", + "rustix 1.1.3", "windows", ] @@ -4609,8 +5297,8 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", - "hyper-rustls", - "hyper-tls", + "hyper-rustls 0.24.2", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -4626,7 +5314,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", "tokio-rustls 0.24.1", @@ -4640,6 +5328,48 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.12", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-tls 0.6.0", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-native-tls", + "tower 0.5.2", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "reqwest-middleware" version = "0.2.5" @@ -4649,7 +5379,7 @@ dependencies = [ "anyhow", "async-trait", "http 0.2.12", - "reqwest", + "reqwest 0.11.27", "serde", "task-local-extensions", "thiserror 1.0.69", @@ -4792,9 +5522,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", @@ -4832,14 +5562,14 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe", + "openssl-probe 0.2.0", "rustls-pki-types", "schannel", - "security-framework 3.3.0", + "security-framework 3.5.1", ] [[package]] @@ -4862,9 +5592,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "zeroize", ] @@ -4898,9 +5628,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "scc" @@ -4934,9 +5664,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -4987,9 +5717,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.3.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags 2.10.0", "core-foundation 0.10.1", @@ -5065,15 +5795,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -5109,7 +5839,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.12.1", "schemars 0.9.0", - "schemars 1.1.0", + "schemars 1.2.0", "serde_core", "serde_json", "serde_with_macros", @@ -5231,10 +5961,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -5306,8 +6037,8 @@ dependencies = [ "serde_derive", "solana-account-info", "solana-clock", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-sysvar", ] @@ -5333,11 +6064,11 @@ dependencies = [ "solana-config-program", "solana-epoch-schedule", "solana-fee-calculator", - "solana-instruction", + "solana-instruction 2.2.1", "solana-nonce", "solana-program", "solana-program-pack", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-slot-hashes", @@ -5363,7 +6094,7 @@ dependencies = [ "serde_derive", "serde_json", "solana-account", - "solana-pubkey", + "solana-pubkey 2.2.1", "zstd", ] @@ -5375,9 +6106,24 @@ checksum = "e0c17d606a298a205fae325489fbed88ee6dc4463c111672172327e741c8905d" dependencies = [ "bincode", "serde", - "solana-program-error", + "solana-program-error 2.2.2", "solana-program-memory", - "solana-pubkey", + "solana-pubkey 2.2.1", +] + +[[package]] +name = "solana-address" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37320fd2945c5d654b2c6210624a52d66c3f1f73b653ed211ab91a703b35bdd" +dependencies = [ + "five8", + "five8_const 1.0.0", + "serde", + "serde_derive", + "solana-define-syscall 4.0.1", + "solana-program-error 3.0.0", + "solana-sanitize 3.0.1", ] [[package]] @@ -5391,8 +6137,8 @@ dependencies = [ "serde", "serde_derive", "solana-clock", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-slot-hashes", ] @@ -5412,12 +6158,12 @@ dependencies = [ "solana-bincode", "solana-clock", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-packet", "solana-program-runtime", - "solana-pubkey", - "solana-system-interface", + "solana-pubkey 2.2.1", + "solana-system-interface 1.0.0", "solana-transaction-context", "thiserror 2.0.17", ] @@ -5439,7 +6185,7 @@ checksum = "75db7f2bbac3e62cfd139065d15bcda9e2428883ba61fc8d27ccb251081e7567" dependencies = [ "num-bigint 0.4.6", "num-traits", - "solana-define-syscall", + "solana-define-syscall 2.2.1", ] [[package]] @@ -5450,7 +6196,7 @@ checksum = "19a3787b8cf9c9fe3dd360800e8b70982b9e5a8af9e11c354b6665dd4a003adc" dependencies = [ "bincode", "serde", - "solana-instruction", + "solana-instruction 2.2.1", ] [[package]] @@ -5460,9 +6206,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a0801e25a1b31a14494fc80882a036be0ffd290efc4c2d640bfcca120a4672" dependencies = [ "blake3", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "solana-hash", - "solana-sanitize", + "solana-sanitize 2.2.1", ] [[package]] @@ -5471,12 +6217,12 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9abc69625158faaab02347370b91c0d8e0fe347bf9287239f0fbe8f5864d91da" dependencies = [ - "ark-bn254", - "ark-ec", - "ark-ff", - "ark-serialize", + "ark-bn254 0.4.0", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", "bytemuck", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "thiserror 2.0.17", ] @@ -5509,10 +6255,10 @@ dependencies = [ "solana-clock", "solana-compute-budget", "solana-cpi", - "solana-curve25519", + "solana-curve25519 2.2.1", "solana-feature-set", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keccak-hasher", "solana-loader-v3-interface", "solana-loader-v4-interface", @@ -5524,13 +6270,13 @@ dependencies = [ "solana-program-entrypoint", "solana-program-memory", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sbpf", "solana-sdk-ids", "solana-secp256k1-recover", "solana-sha256-hasher", "solana-stable-layout", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-sysvar", "solana-sysvar-id", "solana-timings", @@ -5555,7 +6301,7 @@ dependencies = [ "solana-config-program", "solana-feature-set", "solana-loader-v4-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-stake-program", "solana-system-program", @@ -5572,13 +6318,13 @@ dependencies = [ "solana-commitment-config", "solana-epoch-info", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-signer", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction", "solana-transaction-error", ] @@ -5639,9 +6385,9 @@ dependencies = [ "solana-compute-budget", "solana-compute-budget-interface", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-packet", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-svm-transaction", "solana-transaction-error", @@ -5657,7 +6403,7 @@ dependencies = [ "borsh 1.6.0", "serde", "serde_derive", - "solana-instruction", + "solana-instruction 2.2.1", "solana-sdk-ids", ] @@ -5683,15 +6429,15 @@ dependencies = [ "serde_derive", "solana-account", "solana-bincode", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-short-vec", "solana-stake-interface", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction-context", ] @@ -5702,10 +6448,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dc71126edddc2ba014622fc32d0f5e2e78ec6c5a1e0eb511b85618c09e9ea11" dependencies = [ "solana-account-info", - "solana-define-syscall", - "solana-instruction", - "solana-program-error", - "solana-pubkey", + "solana-define-syscall 2.2.1", + "solana-instruction 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "solana-stable-layout", ] @@ -5718,7 +6464,21 @@ dependencies = [ "bytemuck", "bytemuck_derive", "curve25519-dalek 4.1.3", - "solana-define-syscall", + "solana-define-syscall 2.2.1", + "subtle", + "thiserror 2.0.17", +] + +[[package]] +name = "solana-curve25519" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebca352e7716ff1a0877272f87c772c958489c1d568a92d318dc0c75939d2884" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "solana-define-syscall 3.0.0", "subtle", "thiserror 2.0.17", ] @@ -5738,6 +6498,18 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf784bb2cb3e02cac9801813c30187344228d2ae952534902108f6150573a33d" +[[package]] +name = "solana-define-syscall" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9697086a4e102d28a156b8d6b521730335d6951bd39a5e766512bbe09007cee" + +[[package]] +name = "solana-define-syscall" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e5b1c0bc1d4a4d10c88a4100499d954c09d3fecfae4912c1a074dff68b1738" + [[package]] name = "solana-derivation-path" version = "2.2.1" @@ -5759,7 +6531,7 @@ dependencies = [ "bytemuck_derive", "ed25519-dalek", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-precompile-error", "solana-sdk-ids", ] @@ -5796,7 +6568,7 @@ checksum = "96c5fd2662ae7574810904585fd443545ed2b568dbd304b25a31e79ccc76e81b" dependencies = [ "siphasher", "solana-hash", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -5823,13 +6595,13 @@ dependencies = [ "solana-address-lookup-table-interface", "solana-clock", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keccak-hasher", "solana-message", "solana-nonce", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", "thiserror 2.0.17", ] @@ -5844,12 +6616,12 @@ dependencies = [ "serde_derive", "solana-account", "solana-account-info", - "solana-instruction", - "solana-program-error", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", ] [[package]] @@ -5862,7 +6634,7 @@ dependencies = [ "lazy_static", "solana-epoch-schedule", "solana-hash", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sha256-hasher", ] @@ -5933,7 +6705,7 @@ dependencies = [ "solana-logger", "solana-native-token", "solana-poh-config", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-sha256-hasher", @@ -5966,7 +6738,7 @@ dependencies = [ "serde", "serde_derive", "solana-atomic-u64", - "solana-sanitize", + "solana-sanitize 2.2.1", "wasm-bindgen", ] @@ -5987,7 +6759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "951545bd7d0ab4a878cfc7375ac9f1a475cb6936626677b2ba1d25e7b9f3910b" dependencies = [ "bytemuck", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -6003,11 +6775,35 @@ dependencies = [ "num-traits", "serde", "serde_derive", - "solana-define-syscall", - "solana-pubkey", + "solana-define-syscall 2.2.1", + "solana-pubkey 2.2.1", "wasm-bindgen", ] +[[package]] +name = "solana-instruction" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1b699a2c1518028a9982e255e0eca10c44d90006542d9d7f9f40dbce3f7c78" +dependencies = [ + "bincode", + "borsh 1.6.0", + "serde", + "solana-define-syscall 4.0.1", + "solana-instruction-error", + "solana-pubkey 4.0.0", +] + +[[package]] +name = "solana-instruction-error" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b04259e03c05faf38a8c24217b5cfe4c90572ae6184ab49cddb1584fdd756d3f" +dependencies = [ + "num-traits", + "solana-program-error 3.0.0", +] + [[package]] name = "solana-instructions-sysvar" version = "2.2.1" @@ -6016,10 +6812,10 @@ checksum = "427f2d0d6dc0bb49f16cef5e7f975180d2e80aab9bdd3b2af68e2d029ec63f43" dependencies = [ "bitflags 2.10.0", "solana-account-info", - "solana-instruction", - "solana-program-error", - "solana-pubkey", - "solana-sanitize", + "solana-instruction 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-serialize-utils", "solana-sysvar-id", @@ -6032,9 +6828,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7aeb957fbd42a451b99235df4942d96db7ef678e8d5061ef34c9b34cae12f79" dependencies = [ "sha3", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "solana-hash", - "solana-sanitize", + "solana-sanitize 2.2.1", ] [[package]] @@ -6048,7 +6844,7 @@ dependencies = [ "ed25519-dalek-bip32", "rand 0.7.3", "solana-derivation-path", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-seed-derivable", "solana-seed-phrase", "solana-signature", @@ -6078,8 +6874,8 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", ] @@ -6092,10 +6888,10 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", ] [[package]] @@ -6107,10 +6903,10 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", ] [[package]] @@ -6125,14 +6921,14 @@ dependencies = [ "solana-bincode", "solana-bpf-loader-program", "solana-compute-budget", - "solana-instruction", + "solana-instruction 2.2.1", "solana-loader-v3-interface", "solana-loader-v4-interface", "solana-log-collector", "solana-measure", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sbpf", "solana-sdk-ids", "solana-transaction-context", @@ -6180,12 +6976,12 @@ dependencies = [ "serde_derive", "solana-bincode", "solana-hash", - "solana-instruction", - "solana-pubkey", - "solana-sanitize", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-short-vec", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction-error", "wasm-bindgen", ] @@ -6200,7 +6996,7 @@ dependencies = [ "gethostname", "lazy_static", "log", - "reqwest", + "reqwest 0.11.27", "solana-clock", "solana-cluster-type", "solana-sha256-hasher", @@ -6214,7 +7010,16 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36a1a14399afaabc2781a1db09cb14ee4cc4ee5c7a5a3cfcc601811379a8092" dependencies = [ - "solana-define-syscall", + "solana-define-syscall 2.2.1", +] + +[[package]] +name = "solana-msg" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "264275c556ea7e22b9d3f87d56305546a38d4eee8ec884f3b126236cb7dcbbb4" +dependencies = [ + "solana-define-syscall 3.0.0", ] [[package]] @@ -6233,7 +7038,7 @@ dependencies = [ "serde_derive", "solana-fee-calculator", "solana-hash", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sha256-hasher", ] @@ -6249,6 +7054,15 @@ dependencies = [ "solana-sdk-ids", ] +[[package]] +name = "solana-nostd-keccak" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8ced70920435b1baa58f76e6f84bbc1110ddd1d6161ec76b6d731ae8431e9c4" +dependencies = [ + "sha3", +] + [[package]] name = "solana-offchain-message" version = "2.2.1" @@ -6258,8 +7072,8 @@ dependencies = [ "num_enum", "solana-hash", "solana-packet", - "solana-pubkey", - "solana-sanitize", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", "solana-sha256-hasher", "solana-signature", "solana-signer", @@ -6295,9 +7109,9 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ad1ea160d08dc423c35021fa3e437a5783eb256f5ab8bc3024e27db913acf42" dependencies = [ - "ark-bn254", - "light-poseidon", - "solana-define-syscall", + "ark-bn254 0.4.0", + "light-poseidon 0.2.0", + "solana-define-syscall 2.2.1", "thiserror 2.0.17", ] @@ -6322,7 +7136,7 @@ dependencies = [ "solana-feature-set", "solana-message", "solana-precompile-error", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-secp256k1-program", "solana-secp256r1-program", @@ -6334,7 +7148,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81a57a24e6a4125fc69510b6774cd93402b943191b6cddad05de7281491c90fe" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-signer", ] @@ -6374,14 +7188,14 @@ dependencies = [ "solana-clock", "solana-cpi", "solana-decode-error", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "solana-epoch-rewards", "solana-epoch-schedule", "solana-example-mocks", "solana-feature-gate-interface", "solana-fee-calculator", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-instructions-sysvar", "solana-keccak-hasher", "solana-last-restart-slot", @@ -6389,17 +7203,17 @@ dependencies = [ "solana-loader-v3-interface", "solana-loader-v4-interface", "solana-message", - "solana-msg", + "solana-msg 2.2.1", "solana-native-token", "solana-nonce", "solana-program-entrypoint", - "solana-program-error", + "solana-program-error 2.2.2", "solana-program-memory", "solana-program-option", "solana-program-pack", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-sdk-macro", "solana-secp256k1-recover", @@ -6411,7 +7225,7 @@ dependencies = [ "solana-slot-history", "solana-stable-layout", "solana-stake-interface", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-sysvar", "solana-sysvar-id", "solana-vote-interface", @@ -6426,9 +7240,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "473ffe73c68d93e9f2aa726ad2985fe52760052709aaab188100a42c618060ec" dependencies = [ "solana-account-info", - "solana-msg", - "solana-program-error", - "solana-pubkey", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", ] [[package]] @@ -6442,11 +7256,17 @@ dependencies = [ "serde", "serde_derive", "solana-decode-error", - "solana-instruction", - "solana-msg", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-pubkey 2.2.1", ] +[[package]] +name = "solana-program-error" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1af32c995a7b692a915bb7414d5f8e838450cf7c70414e763d8abcae7b51f28" + [[package]] name = "solana-program-memory" version = "2.2.1" @@ -6454,7 +7274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b0268f6c89825fb634a34bd0c3b8fdaeaecfc3728be1d622a8ee6dd577b60d4" dependencies = [ "num-traits", - "solana-define-syscall", + "solana-define-syscall 2.2.1", ] [[package]] @@ -6469,7 +7289,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "319f0ef15e6e12dc37c597faccb7d62525a509fec5f6975ecb9419efddeb277b" dependencies = [ - "solana-program-error", + "solana-program-error 2.2.2", ] [[package]] @@ -6493,13 +7313,13 @@ dependencies = [ "solana-epoch-schedule", "solana-feature-set", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-last-restart-slot", "solana-log-collector", "solana-measure", "solana-metrics", "solana-precompiles", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sbpf", "solana-sdk-ids", @@ -6525,7 +7345,7 @@ dependencies = [ "bytemuck", "bytemuck_derive", "curve25519-dalek 4.1.3", - "five8_const", + "five8_const 0.1.4", "getrandom 0.2.16", "js-sys", "num-traits", @@ -6534,12 +7354,21 @@ dependencies = [ "serde_derive", "solana-atomic-u64", "solana-decode-error", - "solana-define-syscall", - "solana-sanitize", + "solana-define-syscall 2.2.1", + "solana-sanitize 2.2.1", "solana-sha256-hasher", "wasm-bindgen", ] +[[package]] +name = "solana-pubkey" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f7104d456b58e1418c21a8581e89810278d1190f70f27ece7fc0b2c9282a57" +dependencies = [ + "solana-address", +] + [[package]] name = "solana-pubsub-client" version = "2.2.1" @@ -6549,14 +7378,14 @@ dependencies = [ "crossbeam-channel", "futures-util", "log", - "reqwest", + "reqwest 0.11.27", "semver", "serde", "serde_derive", "serde_json", "solana-account-decoder-client-types", "solana-clock", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client-api", "solana-signature", "thiserror 2.0.17", @@ -6601,7 +7430,7 @@ dependencies = [ "solana-clock", "solana-epoch-schedule", "solana-genesis-config", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", ] @@ -6612,7 +7441,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f6f9113c6003492e74438d1288e30cffa8ccfdc2ef7b49b9e816d8034da18cd" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-reward-info", ] @@ -6624,7 +7453,7 @@ checksum = "2b293f4246626c0e0a991531f08848a713ada965612e99dc510963f04d12cae7" dependencies = [ "lazy_static", "solana-feature-set", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", ] @@ -6650,7 +7479,7 @@ dependencies = [ "bs58", "indicatif", "log", - "reqwest", + "reqwest 0.11.27", "reqwest-middleware", "semver", "serde", @@ -6664,9 +7493,9 @@ dependencies = [ "solana-epoch-schedule", "solana-feature-gate-interface", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client-api", "solana-signature", "solana-transaction", @@ -6686,7 +7515,7 @@ dependencies = [ "base64 0.22.1", "bs58", "jsonrpc-core", - "reqwest", + "reqwest 0.11.27", "reqwest-middleware", "semver", "serde", @@ -6699,7 +7528,7 @@ dependencies = [ "solana-fee-calculator", "solana-inflation", "solana-inline-spl", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signer", "solana-transaction-error", "solana-transaction-status-client-types", @@ -6713,6 +7542,12 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" +[[package]] +name = "solana-sanitize" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09694a0fc14e5ffb18f9b7b7c0f15ecb6eac5b5610bf76a1853459d19daf9" + [[package]] name = "solana-sbpf" version = "0.10.0" @@ -6758,7 +7593,7 @@ dependencies = [ "solana-genesis-config", "solana-hard-forks", "solana-inflation", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", "solana-native-token", @@ -6771,13 +7606,13 @@ dependencies = [ "solana-presigner", "solana-program", "solana-program-memory", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-quic-definitions", "solana-rent-collector", "solana-rent-debits", "solana-reserved-account-keys", "solana-reward-info", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-sdk-macro", "solana-secp256k1-program", @@ -6807,7 +7642,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5d8b9cc68d5c88b062a33e23a6466722467dde0035152d8fb1afbcdf350a5f" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -6835,7 +7670,7 @@ dependencies = [ "serde_derive", "sha3", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-precompile-error", "solana-sdk-ids", ] @@ -6848,7 +7683,7 @@ checksum = "baa3120b6cdaa270f39444f5093a90a7b03d296d362878f7a6991d6de3bbe496" dependencies = [ "borsh 1.6.0", "libsecp256k1", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "thiserror 2.0.17", ] @@ -6861,7 +7696,7 @@ dependencies = [ "bytemuck", "openssl", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-precompile-error", "solana-sdk-ids", ] @@ -6919,9 +7754,9 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "817a284b63197d2b27afdba829c5ab34231da4a9b4e763466a003c40ca4f535e" dependencies = [ - "solana-instruction", - "solana-pubkey", - "solana-sanitize", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", ] [[package]] @@ -6931,7 +7766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0037386961c0d633421f53560ad7c80675c0447cba4d1bb66d60974dd486c7ea" dependencies = [ "sha2 0.10.9", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "solana-hash", ] @@ -6967,7 +7802,7 @@ dependencies = [ "serde", "serde-big-array", "serde_derive", - "solana-sanitize", + "solana-sanitize 2.2.1", ] [[package]] @@ -6976,7 +7811,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-transaction-error", ] @@ -7013,8 +7848,8 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f14f7d02af8f2bc1b5efeeae71bc1c2b7f0f65cd75bcc7d8180f2c762a57f54" dependencies = [ - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", ] [[package]] @@ -7031,10 +7866,10 @@ dependencies = [ "solana-clock", "solana-cpi", "solana-decode-error", - "solana-instruction", - "solana-program-error", - "solana-pubkey", - "solana-system-interface", + "solana-instruction 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "solana-system-interface 1.0.0", "solana-sysvar-id", ] @@ -7052,12 +7887,12 @@ dependencies = [ "solana-config-program", "solana-feature-set", "solana-genesis-config", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-native-token", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-stake-interface", @@ -7079,9 +7914,9 @@ dependencies = [ "serde", "solana-account-decoder", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-transaction", "solana-transaction-context", @@ -7109,7 +7944,7 @@ dependencies = [ "solana-feature-set", "solana-fee-structure", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-instructions-sysvar", "solana-loader-v4-program", "solana-log-collector", @@ -7120,7 +7955,7 @@ dependencies = [ "solana-precompiles", "solana-program", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-rent-debits", "solana-reserved-account-keys", @@ -7152,7 +7987,7 @@ checksum = "4fc4392f0eed412141a376e99dfb052069b96f13697a9abb335504babe29387a" dependencies = [ "solana-hash", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-signature", "solana-transaction", @@ -7169,11 +8004,26 @@ dependencies = [ "serde", "serde_derive", "solana-decode-error", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "wasm-bindgen", ] +[[package]] +name = "solana-system-interface" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14591d6508042ebefb110305d3ba761615927146a26917ade45dc332d8e1ecde" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-address", + "solana-instruction 3.1.0", + "solana-msg 3.0.0", + "solana-program-error 3.0.0", +] + [[package]] name = "solana-system-program" version = "2.2.1" @@ -7186,15 +8036,15 @@ dependencies = [ "serde_derive", "solana-account", "solana-bincode", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-nonce", "solana-nonce-account", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-sysvar", "solana-transaction-context", "solana-type-overrides", @@ -7209,9 +8059,9 @@ dependencies = [ "solana-hash", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signer", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction", ] @@ -7230,20 +8080,20 @@ dependencies = [ "serde_derive", "solana-account-info", "solana-clock", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "solana-epoch-rewards", "solana-epoch-schedule", "solana-fee-calculator", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-instructions-sysvar", "solana-last-restart-slot", "solana-program-entrypoint", - "solana-program-error", + "solana-program-error 2.2.2", "solana-program-memory", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-sdk-macro", "solana-slot-hashes", @@ -7258,7 +8108,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5762b273d3325b047cfda250787f8d796d781746860d5d0a746ee29f3e8812c1" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", ] @@ -7276,7 +8126,7 @@ checksum = "49d9eabdce318cb07c60a23f1cc367b43e177c79225b5c2a081869ad182172ad" dependencies = [ "eager", "enum-iterator", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -7291,18 +8141,18 @@ dependencies = [ "solana-bincode", "solana-feature-set", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", "solana-precompiles", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-reserved-account-keys", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-short-vec", "solana-signature", "solana-signer", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction-error", "wasm-bindgen", ] @@ -7317,8 +8167,8 @@ dependencies = [ "serde", "serde_derive", "solana-account", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-rent", "solana-signature", ] @@ -7331,8 +8181,8 @@ checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1" dependencies = [ "serde", "serde_derive", - "solana-instruction", - "solana-sanitize", + "solana-instruction 2.2.1", + "solana-sanitize 2.2.1", ] [[package]] @@ -7354,16 +8204,16 @@ dependencies = [ "solana-account-decoder", "solana-clock", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-loader-v2-interface", "solana-message", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-reserved-account-keys", "solana-reward-info", "solana-sdk-ids", "solana-signature", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction", "solana-transaction-error", "solana-transaction-status-client-types", @@ -7425,7 +8275,7 @@ dependencies = [ "serde", "serde_derive", "solana-feature-set", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-serde-varint", ] @@ -7443,14 +8293,14 @@ dependencies = [ "solana-clock", "solana-decode-error", "solana-hash", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-serde-varint", "solana-serialize-utils", "solana-short-vec", - "solana-system-interface", + "solana-system-interface 1.0.0", ] [[package]] @@ -7471,11 +8321,11 @@ dependencies = [ "solana-epoch-schedule", "solana-feature-set", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-signer", @@ -7510,8 +8360,8 @@ dependencies = [ "serde_json", "sha3", "solana-derivation-path", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-seed-derivable", "solana-seed-phrase", @@ -7593,8 +8443,8 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f8349dbcbe575f354f9a533a21f272f3eb3808a49e2fdc1c34393b88ba76cb" dependencies = [ - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", ] [[package]] @@ -7604,7 +8454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7398da23554a31660f17718164e31d31900956054f54f52d5ec1be51cb4f4b3" dependencies = [ "bytemuck", - "solana-program-error", + "solana-program-error 2.2.2", "solana-sha256-hasher", "spl-discriminator-derive", ] @@ -7653,11 +8503,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f09647c0974e33366efeb83b8e2daebb329f0420149e74d3a4bd2c08cf9f7cb" dependencies = [ "solana-account-info", - "solana-instruction", - "solana-msg", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", "solana-program-entrypoint", - "solana-program-error", - "solana-pubkey", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", ] [[package]] @@ -7672,10 +8522,10 @@ dependencies = [ "num-derive", "num-traits", "solana-decode-error", - "solana-msg", - "solana-program-error", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", "solana-program-option", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-zk-sdk", "thiserror 2.0.17", ] @@ -7716,10 +8566,10 @@ dependencies = [ "num-traits", "solana-account-info", "solana-decode-error", - "solana-instruction", - "solana-msg", - "solana-program-error", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "spl-discriminator", "spl-pod", "spl-program-error", @@ -7806,7 +8656,7 @@ checksum = "170378693c5516090f6d37ae9bad2b9b6125069be68d9acd4865bbe9fc8499fd" dependencies = [ "base64 0.22.1", "bytemuck", - "solana-curve25519", + "solana-curve25519 2.2.1", "solana-zk-sdk", ] @@ -7817,7 +8667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eff2d6a445a147c9d6dd77b8301b1e116c8299601794b558eafa409b342faf96" dependencies = [ "bytemuck", - "solana-curve25519", + "solana-curve25519 2.2.1", "solana-program", "solana-zk-sdk", "spl-pod", @@ -7856,10 +8706,10 @@ dependencies = [ "num-derive", "num-traits", "solana-decode-error", - "solana-instruction", - "solana-msg", - "solana-program-error", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "spl-discriminator", "spl-pod", "thiserror 1.0.69", @@ -7876,10 +8726,10 @@ dependencies = [ "num-traits", "solana-borsh", "solana-decode-error", - "solana-instruction", - "solana-msg", - "solana-program-error", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "spl-discriminator", "spl-pod", "spl-type-length-value", @@ -7899,10 +8749,10 @@ dependencies = [ "solana-account-info", "solana-cpi", "solana-decode-error", - "solana-instruction", - "solana-msg", - "solana-program-error", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "spl-discriminator", "spl-pod", "spl-program-error", @@ -7922,8 +8772,8 @@ dependencies = [ "num-traits", "solana-account-info", "solana-decode-error", - "solana-msg", - "solana-program-error", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", "spl-discriminator", "spl-pod", "thiserror 1.0.69", @@ -8037,6 +8887,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -8057,7 +8910,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", ] [[package]] @@ -8070,6 +8934,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tabular" version = "0.2.0" @@ -8096,14 +8970,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -8126,7 +9000,7 @@ dependencies = [ "magicblock-ledger", "magicblock-processor", "solana-account", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-program", "solana-rpc-client", @@ -8388,9 +9262,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] @@ -8411,21 +9285,21 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.9" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap 2.12.1", - "toml_datetime 0.7.3", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] @@ -8478,7 +9352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" dependencies = [ "async-trait", - "axum 0.8.7", + "axum 0.8.8", "base64 0.22.1", "bytes", "h2 0.4.12", @@ -8519,7 +9393,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" dependencies = [ - "prettyplease 0.2.36", + "prettyplease 0.2.37", "proc-macro2", "prost-build 0.12.6", "quote", @@ -8532,7 +9406,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" dependencies = [ - "prettyplease 0.2.36", + "prettyplease 0.2.37", "proc-macro2", "prost-build 0.13.5", "prost-types 0.13.5", @@ -8603,6 +9477,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -8617,9 +9509,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -8639,9 +9531,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -8828,6 +9720,7 @@ checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "getrandom 0.3.4", "js-sys", + "serde_core", "wasm-bindgen", ] @@ -9110,6 +10003,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -9444,7 +10348,7 @@ dependencies = [ "solana-clock", "solana-hash", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-transaction", "solana-transaction-context", @@ -9529,9 +10433,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", @@ -9571,6 +10475,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "zmij" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9747e91771f56fd7893e1164abd78febd14a670ceec257caad15e051de35f06" + [[package]] name = "zstd" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index 96a9a6c04..6dc1109f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ split-debuginfo = "packed" [workspace] members = [ + "compressed-delegation-api", + "compressed-delegation-client", "magicblock-account-cloner", "magicblock-accounts", "magicblock-accounts-db", @@ -53,11 +55,13 @@ assert_matches = "1.5.0" async-trait = "0.1.77" base64 = "0.21.7" bincode = "1.3.3" -borsh = { version = "1.5.1", features = ["derive", "unstable__schema"] } +borsh = "0.10.4" bs58 = "0.5.1" byteorder = "1.5.0" chrono = "0.4" clap = "4.5.40" +compressed-delegation-api = { path = "./compressed-delegation-api" } +compressed-delegation-client = { path = "./compressed-delegation-client" } console-subscriber = "0.5.0" derive_more = "2.0" dyn-clone = "1.0.20" @@ -83,6 +87,11 @@ json = { package = "sonic-rs", version = "0.5.3" } lazy_static = "1.4.0" libloading = "0.8" libc = "0.2.153" +light-client = { git = "https://github.com/magicblock-labs/light-protocol", rev = "849e6aa" } +light-compressed-account = { git = "https://github.com/magicblock-labs/light-protocol", rev = "849e6aa" } +light-hasher = { git = "https://github.com/magicblock-labs/light-protocol", rev = "849e6aa" } +light-sdk = { git = "https://github.com/magicblock-labs/light-protocol", rev = "849e6aa" } +light-sdk-types = { git = "https://github.com/magicblock-labs/light-protocol", rev = "849e6aa" } # Only used in magicblock-ledger for solana_metrics::datapoint_info! log = { version = "0.4.20" } lru = "0.16.0" @@ -148,6 +157,7 @@ solana-bpf-loader-program = { version = "2.2" } solana-clock = { version = "2.2" } solana-commitment-config = { version = "2.2" } solana-compute-budget-instruction = { version = "2.2" } +solana-cpi = { version = "2.2" } solana-compute-budget-interface = { version = "2.2" } solana-compute-budget-program = { version = "2.2" } solana-feature-gate-interface = { version = "2.2" } @@ -170,6 +180,7 @@ solana-message = { version = "2.2" } solana-metrics = { version = "2.2" } solana-native-token = { version = "2.2" } solana-program = "2.2" +solana-program-error = { version = "2.2" } solana-program-runtime = { version = "2.2" } solana-pubkey = { version = "2.2" } solana-pubsub-client = { version = "2.2" } diff --git a/compressed-delegation-api/Cargo.toml b/compressed-delegation-api/Cargo.toml new file mode 100644 index 000000000..699d0fa58 --- /dev/null +++ b/compressed-delegation-api/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "compressed-delegation-api" +version = "0.1.0" +edition = "2021" +license-file = "../../LICENSE" +publish = false + +[dependencies] +borsh = { workspace = true } +light-compressed-account = { workspace = true } +light-hasher = { workspace = true, features = ["solana"] } +light-sdk = { workspace = true, features = ["v2"] } +solana-pubkey = { workspace = true } diff --git a/compressed-delegation-api/src/instruction.rs b/compressed-delegation-api/src/instruction.rs new file mode 100644 index 000000000..ba4922f6f --- /dev/null +++ b/compressed-delegation-api/src/instruction.rs @@ -0,0 +1,183 @@ +use std::io::{Read, Write}; + +use borsh::{BorshDeserialize, BorshSerialize}; +use light_sdk::instruction::{ + account_meta::CompressedAccountMeta, PackedAddressTreeInfo, ValidityProof, +}; +use solana_pubkey::Pubkey; + +use crate::state::CompressedDelegationRecord; + +/// Instruction discriminators for the compressed delegation program (`u64`). +#[repr(u64)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CompressedDelegationInstructionDiscriminator { + InitDelegationRecord = 0, + Delegate = 1, + CommitAndFinalize = 2, + Undelegate = 3, +} + +impl CompressedDelegationInstructionDiscriminator { + pub fn from_u64(value: u64) -> Option { + match value { + 0 => Some(Self::InitDelegationRecord), + 1 => Some(Self::Delegate), + 2 => Some(Self::CommitAndFinalize), + 3 => Some(Self::Undelegate), + _ => None, + } + } +} + +#[derive(Clone, Debug)] +#[repr(u64)] +pub enum CompressedDelegationProgramInstruction { + /// Delegate the compressed account to a validator. + InitDelegationRecord { args: InitDelegationRecordArgs }, + + /// Delegate the compressed account to a validator. + Delegate { args: DelegateArgs }, + + /// Commit the compressed account. + CommitAndFinalize { args: CommitAndFinalizeArgs }, + + /// Undelegate the compressed account. + Undelegate { args: UndelegateArgs }, +} + +impl CompressedDelegationProgramInstruction { + /// Discriminator for this instruction (first `u64` in serialized form). + pub fn discriminator( + &self, + ) -> CompressedDelegationInstructionDiscriminator { + match self { + Self::InitDelegationRecord { .. } => { + CompressedDelegationInstructionDiscriminator::InitDelegationRecord + } + Self::Delegate { .. } => CompressedDelegationInstructionDiscriminator::Delegate, + Self::CommitAndFinalize { .. } => CompressedDelegationInstructionDiscriminator::CommitAndFinalize, + Self::Undelegate { .. } => CompressedDelegationInstructionDiscriminator::Undelegate, + } + } +} + +impl BorshSerialize for CompressedDelegationProgramInstruction { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + (self.discriminator() as u64).serialize(writer)?; + match self { + Self::InitDelegationRecord { args } => args.serialize(writer), + Self::Delegate { args } => args.serialize(writer), + Self::CommitAndFinalize { args } => args.serialize(writer), + Self::Undelegate { args } => args.serialize(writer), + } + } +} + +impl BorshDeserialize for CompressedDelegationProgramInstruction { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + let raw = u64::deserialize_reader(reader)?; + let disc = CompressedDelegationInstructionDiscriminator::from_u64(raw) + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid CompressedDelegationProgramInstruction discriminant", + ) + })?; + Ok(match disc { + CompressedDelegationInstructionDiscriminator::InitDelegationRecord => { + Self::InitDelegationRecord { + args: InitDelegationRecordArgs::deserialize_reader(reader)?, + } + } + CompressedDelegationInstructionDiscriminator::Delegate => Self::Delegate { + args: DelegateArgs::deserialize_reader(reader)?, + }, + CompressedDelegationInstructionDiscriminator::CommitAndFinalize => Self::CommitAndFinalize { + args: CommitAndFinalizeArgs::deserialize_reader(reader)?, + }, + CompressedDelegationInstructionDiscriminator::Undelegate => Self::Undelegate { + args: UndelegateArgs::deserialize_reader(reader)?, + }, + }) + } +} + +#[repr(C)] +#[derive( + BorshSerialize, BorshDeserialize, Clone, Default, Debug, PartialEq, +)] +pub struct InitDelegationRecordArgs { + /// The proof of the account data + pub validity_proof: ValidityProof, + /// Address tree info + pub address_tree_info: PackedAddressTreeInfo, + /// Output state tree index + pub output_state_tree_index: u8, + /// Owner program id + pub owner_program_id: Pubkey, + /// PDA seeds + pub pda_seeds: Vec>, + /// Bump + pub bump: u8, +} + +#[repr(C)] +#[derive( + BorshSerialize, BorshDeserialize, Clone, Default, Debug, PartialEq, +)] +pub struct DelegateArgs { + /// The proof of the account data + pub validity_proof: ValidityProof, + /// Account meta + pub account_meta: CompressedAccountMeta, + /// Owner program id + pub owner_program_id: Pubkey, + /// Validator + pub validator: Pubkey, + /// Original lamports of the account + pub lamports: u64, + /// Account data before delegation + pub account_data: Vec, + /// PDA seeds + pub pda_seeds: Vec>, + /// Bump + pub bump: u8, +} + +#[repr(C)] +#[derive( + BorshSerialize, BorshDeserialize, Clone, Default, Debug, PartialEq, +)] +pub struct CommitAndFinalizeArgs { + /// The current data of the compressed delegated account + pub current_compressed_delegated_account_data: Vec, + /// The new state of the compressed delegated account data + pub new_data: Vec, + /// Compressed account meta + pub account_meta: CompressedAccountMeta, + /// Validity proof + pub validity_proof: ValidityProof, + /// Update nonce + pub update_nonce: u64, + /// Allow undelegation + pub allow_undelegation: bool, +} + +#[derive( + BorshSerialize, BorshDeserialize, Clone, Default, Debug, PartialEq, +)] +pub struct UndelegateArgs { + /// The proof of the account data + pub validity_proof: ValidityProof, + /// Delegation record account meta + pub delegation_record_account_meta: CompressedAccountMeta, + /// Compressed delegated record + pub compressed_delegated_record: CompressedDelegationRecord, +} + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq)] +pub struct ExternalUndelegateArgs { + /// The delegation record + pub delegation_record: CompressedDelegationRecord, +} diff --git a/compressed-delegation-api/src/lib.rs b/compressed-delegation-api/src/lib.rs new file mode 100644 index 000000000..1f98f1935 --- /dev/null +++ b/compressed-delegation-api/src/lib.rs @@ -0,0 +1,19 @@ +//! Shared instruction layouts, discriminators, and account types for the +//! compressed delegation program. Used by the on-chain program and off-chain clients. + +pub mod instruction; +pub mod state; + +pub use instruction::{ + CommitAndFinalizeArgs, CompressedDelegationInstructionDiscriminator, + CompressedDelegationProgramInstruction, DelegateArgs, + ExternalUndelegateArgs, InitDelegationRecordArgs, UndelegateArgs, +}; +pub use state::{CompressedDelegationRecord, DCP_DISCRIMINATOR}; + +/// Discriminator for CPI into owner programs for external undelegate. +pub const EXTERNAL_UNDELEGATE_DISCRIMINATOR: [u8; 8] = + [0xD, 0x23, 0xB0, 0x7C, 0x70, 0x68, 0xFE, 0x73]; + +pub const EXTERNAL_UNDELEGATE_DISCRIMINATOR_U64: u64 = + u64::from_le_bytes(EXTERNAL_UNDELEGATE_DISCRIMINATOR); diff --git a/compressed-delegation-api/src/state.rs b/compressed-delegation-api/src/state.rs new file mode 100644 index 000000000..897b2c5f8 --- /dev/null +++ b/compressed-delegation-api/src/state.rs @@ -0,0 +1,56 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use light_hasher::{DataHasher, Hasher, HasherError, Sha256}; +use light_sdk::LightDiscriminator; +use solana_pubkey::Pubkey; + +pub const DCP_DISCRIMINATOR: [u8; 8] = + [0x4d, 0x41, 0x47, 0x49, 0x43, 0x42, 0x4c, 0x4b]; + +/// The Delegated Metadata includes Account Seeds, max delegation time, seeds +/// and other meta information about the delegated account. +/// * Everything necessary at cloning time is instead stored in the delegation record. +#[repr(C)] +#[derive( + BorshSerialize, + BorshDeserialize, + Clone, + Debug, + Default, + PartialEq, +)] +pub struct CompressedDelegationRecord { + /// The PDA of the delegated account + pub pda: Pubkey, + /// The validator the account is delegated to + pub authority: Pubkey, + /// Last update nonce + pub last_update_nonce: u64, + /// Whether the account can be undelegated + pub is_undelegatable: bool, + /// The program that owns the delegated account + pub owner: Pubkey, + /// The slot at which the delegation was created + pub delegation_slot: u64, + /// Original lamports of the account + pub lamports: u64, + /// The original data of the delegated account + pub data: Vec, +} + +impl DataHasher for CompressedDelegationRecord { + fn hash(&self) -> Result<[u8; 32], HasherError> { + let bytes = borsh::to_vec(self).map_err(|_| HasherError::BorshError)?; + let mut hash = Sha256::hash(&bytes)?; + hash[0] = 0; + Ok(hash) + } +} + +impl LightDiscriminator for CompressedDelegationRecord { + const LIGHT_DISCRIMINATOR: [u8; 8] = DCP_DISCRIMINATOR; + const LIGHT_DISCRIMINATOR_SLICE: &'static [u8] = &Self::LIGHT_DISCRIMINATOR; + + fn discriminator() -> [u8; 8] { + Self::LIGHT_DISCRIMINATOR + } +} diff --git a/compressed-delegation-client/Cargo.toml b/compressed-delegation-client/Cargo.toml new file mode 100644 index 000000000..d45647237 --- /dev/null +++ b/compressed-delegation-client/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "compressed-delegation-client" +version.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +license.workspace = true +edition.workspace = true + +[features] +default = [] +serde = ["dep:serde"] +anchor = [] +anchor-idl-build = [] +fetch = [] + +[dependencies] +borsh = { workspace = true } +compressed-delegation-api = { workspace = true } +light-compressed-account = { workspace = true } +light-hasher = { workspace = true } +light-sdk = { workspace = true } +light-sdk-types = { workspace = true } +serde = { workspace = true, optional = true } +solana-account-info = { workspace = true } +solana-cpi = { workspace = true } +solana-instruction = { workspace = true } +solana-program-error = { workspace = true } +solana-pubkey = { workspace = true } diff --git a/compressed-delegation-client/src/builders/commit_finalize.rs b/compressed-delegation-client/src/builders/commit_finalize.rs new file mode 100644 index 000000000..6367b87b6 --- /dev/null +++ b/compressed-delegation-client/src/builders/commit_finalize.rs @@ -0,0 +1,40 @@ +use solana_instruction::{AccountMeta, Instruction}; +use solana_pubkey::Pubkey; + +use crate::{CommitAndFinalizeArgs, DelegationProgramDiscriminator}; + +/// Instruction builder for `Commit`. +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` validator +/// 1. `[]` delegated_account +#[derive(Clone, Debug, Default)] +pub struct CommitAndFinalizeBuilder { + pub validator: Pubkey, + pub delegated_account: Pubkey, + pub remaining_accounts: Vec, + pub args: CommitAndFinalizeArgs, +} + +impl CommitAndFinalizeBuilder { + pub fn instruction(&self) -> Instruction { + Instruction { + program_id: crate::COMPRESSED_DELEGATION_ID, + accounts: [ + &[ + AccountMeta::new(self.validator, true), + AccountMeta::new_readonly(self.delegated_account, false), + ], + self.remaining_accounts.as_slice(), + ] + .concat(), + data: [ + &(DelegationProgramDiscriminator::CommitAndFinalize as u64) + .to_le_bytes(), + borsh::to_vec(&self.args).unwrap().as_slice(), + ] + .concat(), + } + } +} diff --git a/compressed-delegation-client/src/builders/delegate.rs b/compressed-delegation-client/src/builders/delegate.rs new file mode 100644 index 000000000..efa1a28c0 --- /dev/null +++ b/compressed-delegation-client/src/builders/delegate.rs @@ -0,0 +1,40 @@ +use solana_instruction::{AccountMeta, Instruction}; +use solana_pubkey::Pubkey; + +use crate::{DelegateArgs, DelegationProgramDiscriminator}; + +/// Instruction builder for `Delegate`. +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` payer +/// 1. `[signer]` delegated_account +#[derive(Clone, Debug, Default)] +pub struct DelegateBuilder { + pub payer: Pubkey, + pub delegated_account: Pubkey, + pub remaining_accounts: Vec, + pub args: DelegateArgs, +} + +impl DelegateBuilder { + pub fn instruction(&self) -> Instruction { + Instruction { + program_id: crate::COMPRESSED_DELEGATION_ID, + accounts: [ + &[ + AccountMeta::new(self.payer, true), + AccountMeta::new_readonly(self.delegated_account, true), + ], + self.remaining_accounts.as_slice(), + ] + .concat(), + data: [ + &(DelegationProgramDiscriminator::Delegate as u64) + .to_le_bytes(), + borsh::to_vec(&self.args).unwrap().as_slice(), + ] + .concat(), + } + } +} diff --git a/compressed-delegation-client/src/builders/init_delegation_record.rs b/compressed-delegation-client/src/builders/init_delegation_record.rs new file mode 100644 index 000000000..a5c7a6607 --- /dev/null +++ b/compressed-delegation-client/src/builders/init_delegation_record.rs @@ -0,0 +1,40 @@ +use solana_instruction::{AccountMeta, Instruction}; +use solana_pubkey::Pubkey; + +use crate::{DelegationProgramDiscriminator, InitDelegationRecordArgs}; + +/// Instruction builder for `InitDelegationRecord`. +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` payer +/// 1. `[signer]` delegated_account +#[derive(Clone, Debug)] +pub struct InitDelegationRecordBuilder { + pub payer: Pubkey, + pub delegated_account: Pubkey, + pub remaining_accounts: Vec, + pub args: InitDelegationRecordArgs, +} + +impl InitDelegationRecordBuilder { + pub fn instruction(&self) -> Instruction { + Instruction { + program_id: crate::COMPRESSED_DELEGATION_ID, + accounts: [ + &[ + AccountMeta::new(self.payer, true), + AccountMeta::new_readonly(self.delegated_account, true), + ], + self.remaining_accounts.as_slice(), + ] + .concat(), + data: [ + &(DelegationProgramDiscriminator::InitDelegationRecord as u64) + .to_le_bytes(), + borsh::to_vec(&self.args).unwrap().as_slice(), + ] + .concat(), + } + } +} diff --git a/compressed-delegation-client/src/builders/mod.rs b/compressed-delegation-client/src/builders/mod.rs new file mode 100644 index 000000000..7c133a389 --- /dev/null +++ b/compressed-delegation-client/src/builders/mod.rs @@ -0,0 +1,9 @@ +pub(crate) mod commit_finalize; +pub(crate) mod delegate; +pub(crate) mod init_delegation_record; +pub(crate) mod undelegate; + +pub use self::commit_finalize::*; +pub use self::delegate::*; +pub use self::init_delegation_record::*; +pub use self::undelegate::*; diff --git a/compressed-delegation-client/src/builders/undelegate.rs b/compressed-delegation-client/src/builders/undelegate.rs new file mode 100644 index 000000000..54c39ce4f --- /dev/null +++ b/compressed-delegation-client/src/builders/undelegate.rs @@ -0,0 +1,46 @@ +use solana_instruction::{AccountMeta, Instruction}; +use solana_pubkey::Pubkey; + +use crate::{DelegationProgramDiscriminator, UndelegateArgs}; + +/// Instruction builder for `Undelegate`. +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` payer +/// 1. `[writable]` delegated_account +/// 2. `[]` owner_program +/// 3. `[]` system_program +#[derive(Clone, Debug, Default)] +pub struct UndelegateBuilder { + pub payer: Pubkey, + pub delegated_account: Pubkey, + pub owner_program: Pubkey, + pub system_program: Pubkey, + pub remaining_accounts: Vec, + pub args: UndelegateArgs, +} + +impl UndelegateBuilder { + pub fn instruction(&self) -> Instruction { + Instruction { + program_id: crate::COMPRESSED_DELEGATION_ID, + accounts: [ + &[ + AccountMeta::new(self.payer, true), + AccountMeta::new(self.delegated_account, false), + AccountMeta::new_readonly(self.owner_program, false), + AccountMeta::new_readonly(self.system_program, false), + ], + self.remaining_accounts.as_slice(), + ] + .concat(), + data: [ + &(DelegationProgramDiscriminator::Undelegate as u64) + .to_le_bytes(), + borsh::to_vec(&self.args).unwrap().as_slice(), + ] + .concat(), + } + } +} diff --git a/compressed-delegation-client/src/cpi/commit_finalize.rs b/compressed-delegation-client/src/cpi/commit_finalize.rs new file mode 100644 index 000000000..a3dd96a9a --- /dev/null +++ b/compressed-delegation-client/src/cpi/commit_finalize.rs @@ -0,0 +1,41 @@ +use solana_account_info::AccountInfo; +use solana_cpi::invoke_signed; +use solana_program_error::ProgramResult; + +use crate::cpi::helpers::{collect_account_infos, remaining_to_metas}; +use crate::CommitAndFinalizeArgs; + +/// CPI helper for `CommitAndFinalize` (on-chain callers). +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` validator +/// 1. `[]` delegated_account +#[derive(Clone, Debug)] +pub struct CommitAndFinalizeCpi<'a> { + pub validator: AccountInfo<'a>, + pub delegated_account: AccountInfo<'a>, + pub remaining_accounts: Vec<(AccountInfo<'a>, bool, bool)>, + pub args: CommitAndFinalizeArgs, +} + +impl<'a> CommitAndFinalizeCpi<'a> { + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[&[&[u8]]]) -> ProgramResult { + let ix = crate::builders::CommitAndFinalizeBuilder { + validator: *self.validator.key, + delegated_account: *self.delegated_account.key, + remaining_accounts: remaining_to_metas(&self.remaining_accounts), + args: self.args.clone(), + } + .instruction(); + let infos = collect_account_infos( + &[self.validator.clone(), self.delegated_account.clone()], + &self.remaining_accounts, + ); + invoke_signed(&ix, &infos, signers) + } +} diff --git a/compressed-delegation-client/src/cpi/delegate.rs b/compressed-delegation-client/src/cpi/delegate.rs new file mode 100644 index 000000000..ca1f1bb9e --- /dev/null +++ b/compressed-delegation-client/src/cpi/delegate.rs @@ -0,0 +1,41 @@ +use solana_account_info::AccountInfo; +use solana_cpi::invoke_signed; +use solana_program_error::ProgramResult; + +use crate::cpi::helpers::{collect_account_infos, remaining_to_metas}; +use crate::DelegateArgs; + +/// CPI helper for `Delegate` (on-chain callers). +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` payer +/// 1. `[signer]` delegated_account +#[derive(Clone, Debug)] +pub struct DelegateCpi<'a> { + pub payer: AccountInfo<'a>, + pub delegated_account: AccountInfo<'a>, + pub remaining_accounts: Vec<(AccountInfo<'a>, bool, bool)>, + pub args: DelegateArgs, +} + +impl<'a> DelegateCpi<'a> { + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[&[&[u8]]]) -> ProgramResult { + let ix = crate::builders::DelegateBuilder { + payer: *self.payer.key, + delegated_account: *self.delegated_account.key, + remaining_accounts: remaining_to_metas(&self.remaining_accounts), + args: self.args.clone(), + } + .instruction(); + let infos = collect_account_infos( + &[self.payer.clone(), self.delegated_account.clone()], + &self.remaining_accounts, + ); + invoke_signed(&ix, &infos, signers) + } +} diff --git a/compressed-delegation-client/src/cpi/helpers.rs b/compressed-delegation-client/src/cpi/helpers.rs new file mode 100644 index 000000000..88545e74b --- /dev/null +++ b/compressed-delegation-client/src/cpi/helpers.rs @@ -0,0 +1,24 @@ +use solana_account_info::AccountInfo; +use solana_instruction::AccountMeta; + +pub(crate) fn remaining_to_metas<'a>( + remaining: &[(AccountInfo<'a>, bool, bool)], +) -> Vec { + remaining + .iter() + .map(|(ai, is_signer, is_writable)| AccountMeta { + pubkey: *ai.key, + is_signer: *is_signer, + is_writable: *is_writable, + }) + .collect() +} + +pub(crate) fn collect_account_infos<'a>( + prefix: &[AccountInfo<'a>], + remaining: &[(AccountInfo<'a>, bool, bool)], +) -> Vec> { + let mut out = prefix.to_vec(); + out.extend(remaining.iter().map(|(ai, _, _)| ai.clone())); + out +} diff --git a/compressed-delegation-client/src/cpi/init_delegation_record.rs b/compressed-delegation-client/src/cpi/init_delegation_record.rs new file mode 100644 index 000000000..b7f279e13 --- /dev/null +++ b/compressed-delegation-client/src/cpi/init_delegation_record.rs @@ -0,0 +1,41 @@ +use solana_account_info::AccountInfo; +use solana_cpi::invoke_signed; +use solana_program_error::ProgramResult; + +use crate::cpi::helpers::{collect_account_infos, remaining_to_metas}; +use crate::InitDelegationRecordArgs; + +/// CPI helper for `InitDelegationRecord` (on-chain callers). +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` payer +/// 1. `[signer]` delegated_account +#[derive(Clone, Debug)] +pub struct InitDelegationRecordCpi<'a> { + pub payer: AccountInfo<'a>, + pub delegated_account: AccountInfo<'a>, + pub remaining_accounts: Vec<(AccountInfo<'a>, bool, bool)>, + pub args: InitDelegationRecordArgs, +} + +impl<'a> InitDelegationRecordCpi<'a> { + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[&[&[u8]]]) -> ProgramResult { + let ix = crate::builders::InitDelegationRecordBuilder { + payer: *self.payer.key, + delegated_account: *self.delegated_account.key, + remaining_accounts: remaining_to_metas(&self.remaining_accounts), + args: self.args.clone(), + } + .instruction(); + let infos = collect_account_infos( + &[self.payer.clone(), self.delegated_account.clone()], + &self.remaining_accounts, + ); + invoke_signed(&ix, &infos, signers) + } +} diff --git a/compressed-delegation-client/src/cpi/mod.rs b/compressed-delegation-client/src/cpi/mod.rs new file mode 100644 index 000000000..6e9b352ad --- /dev/null +++ b/compressed-delegation-client/src/cpi/mod.rs @@ -0,0 +1,11 @@ +mod helpers; + +pub(crate) mod commit_finalize; +pub(crate) mod delegate; +pub(crate) mod init_delegation_record; +pub(crate) mod undelegate; + +pub use self::commit_finalize::*; +pub use self::delegate::*; +pub use self::init_delegation_record::*; +pub use self::undelegate::*; diff --git a/compressed-delegation-client/src/cpi/undelegate.rs b/compressed-delegation-client/src/cpi/undelegate.rs new file mode 100644 index 000000000..7ca661cb1 --- /dev/null +++ b/compressed-delegation-client/src/cpi/undelegate.rs @@ -0,0 +1,52 @@ +use solana_account_info::AccountInfo; +use solana_cpi::invoke_signed; +use solana_program_error::ProgramResult; + +use crate::cpi::helpers::{collect_account_infos, remaining_to_metas}; +use crate::UndelegateArgs; + +/// CPI helper for `Undelegate` (on-chain callers). +/// +/// ### Accounts: +/// +/// 0. `[writable, signer]` payer +/// 1. `[writable]` delegated_account +/// 2. `[]` owner_program +/// 3. `[]` system_program +#[derive(Clone, Debug)] +pub struct UndelegateCpi<'a> { + pub payer: AccountInfo<'a>, + pub delegated_account: AccountInfo<'a>, + pub owner_program: AccountInfo<'a>, + pub system_program: AccountInfo<'a>, + pub remaining_accounts: Vec<(AccountInfo<'a>, bool, bool)>, + pub args: UndelegateArgs, +} + +impl<'a> UndelegateCpi<'a> { + pub fn invoke(&self) -> ProgramResult { + self.invoke_signed(&[]) + } + + pub fn invoke_signed(&self, signers: &[&[&[u8]]]) -> ProgramResult { + let ix = crate::builders::UndelegateBuilder { + payer: *self.payer.key, + delegated_account: *self.delegated_account.key, + owner_program: *self.owner_program.key, + system_program: *self.system_program.key, + remaining_accounts: remaining_to_metas(&self.remaining_accounts), + args: self.args.clone(), + } + .instruction(); + let infos = collect_account_infos( + &[ + self.payer.clone(), + self.delegated_account.clone(), + self.owner_program.clone(), + self.system_program.clone(), + ], + &self.remaining_accounts, + ); + invoke_signed(&ix, &infos, signers) + } +} diff --git a/compressed-delegation-client/src/lib.rs b/compressed-delegation-client/src/lib.rs new file mode 100644 index 000000000..8f10cbe9b --- /dev/null +++ b/compressed-delegation-client/src/lib.rs @@ -0,0 +1,28 @@ +pub mod builders; +pub mod cpi; +mod programs; + +use light_sdk::derive_light_cpi_signer; +use light_sdk_types::CpiSigner; +use solana_pubkey::Pubkey; +pub use { + compressed_delegation_api::{ + CommitAndFinalizeArgs, CompressedDelegationInstructionDiscriminator, + CompressedDelegationProgramInstruction, CompressedDelegationRecord, + DelegateArgs, ExternalUndelegateArgs, InitDelegationRecordArgs, + UndelegateArgs, DCP_DISCRIMINATOR, EXTERNAL_UNDELEGATE_DISCRIMINATOR, + EXTERNAL_UNDELEGATE_DISCRIMINATOR_U64, + }, + programs::{COMPRESSED_DELEGATION_ID, COMPRESSED_DELEGATION_ID as ID}, +}; + +/// Backwards-compatible name for [`CompressedDelegationInstructionDiscriminator`]. +pub type DelegationProgramDiscriminator = + CompressedDelegationInstructionDiscriminator; + +pub const COMPRESSED_DELEGATION_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("DEL2rPzhFaS5qzo8XY9ZNxSzuunWueySq3p2dxJfwPbT"); + +pub const fn id() -> Pubkey { + ID +} diff --git a/compressed-delegation-client/src/mod.rs b/compressed-delegation-client/src/mod.rs new file mode 100644 index 000000000..500c4f924 --- /dev/null +++ b/compressed-delegation-client/src/mod.rs @@ -0,0 +1,9 @@ +//! Legacy client module layout (`accounts`, `instructions`, `types`). + +pub mod accounts; +pub mod instructions; +pub mod programs; +pub mod shared; +pub mod types; + +pub(crate) use programs::*; diff --git a/compressed-delegation-client/src/programs.rs b/compressed-delegation-client/src/programs.rs new file mode 100644 index 000000000..f68e7d375 --- /dev/null +++ b/compressed-delegation-client/src/programs.rs @@ -0,0 +1,7 @@ +//! Program IDs for on-chain programs in this workspace. + +use solana_pubkey::{pubkey, Pubkey}; + +/// `compressed_delegation` program ID. +pub const COMPRESSED_DELEGATION_ID: Pubkey = + pubkey!("DEL2rPzhFaS5qzo8XY9ZNxSzuunWueySq3p2dxJfwPbT"); diff --git a/compressed-delegation-client/src/shared.rs b/compressed-delegation-client/src/shared.rs new file mode 100644 index 000000000..69c049e3d --- /dev/null +++ b/compressed-delegation-client/src/shared.rs @@ -0,0 +1,16 @@ +//! Shared account decoding helpers for optional `fetch` integration. + +#[cfg(feature = "fetch")] +#[derive(Debug, Clone)] +pub struct DecodedAccount { + pub address: solana_pubkey::Pubkey, + pub account: solana_account::Account, + pub data: T, +} + +#[cfg(feature = "fetch")] +#[derive(Debug, Clone)] +pub enum MaybeAccount { + Exists(DecodedAccount), + NotFound(solana_pubkey::Pubkey), +} diff --git a/config.example.toml b/config.example.toml index a410d84f4..9d02b1ca6 100644 --- a/config.example.toml +++ b/config.example.toml @@ -59,9 +59,9 @@ lifecycle = "ephemeral" # Example format: grpcs://.helius-rpc.com?api-key= # See Helius gRPC docs for available network endpoints. remotes = [ - "https://api.devnet.solana.com", - "wss://api.devnet.solana.com", - "grpcs://laserstream-devnet-ewr.helius-rpc.com?api-key=YOUR_API_KEY", + "https://api.devnet.solana.com", + "wss://api.devnet.solana.com", + "grpcs://laserstream-devnet-ewr.helius-rpc.com?api-key=YOUR_API_KEY", ] # Root directory for application storage (ledger, accountsdb, snapshots). @@ -253,6 +253,20 @@ reset = false # Env: MBV_TASK_SCHEDULER__MIN_INTERVAL min-interval = "10ms" +# ============================================================================== +# Compression Service +# ============================================================================== +[compression] +# The URL of the Photon indexer. +# Default: None +# Env: MBV_COMPRESSION__PHOTON_URL +# photon-url = "http://localhost:8784" + +# The API key for the Photon indexer. +# Default: None +# Env: MBV_COMPRESSION__API_KEY +# api-key = "your-api-key" + # ============================================================================== # Pre-loaded Programs # ============================================================================== diff --git a/docs/compression-documentation.md b/docs/compression-documentation.md new file mode 100644 index 000000000..180795d91 --- /dev/null +++ b/docs/compression-documentation.md @@ -0,0 +1,353 @@ +# MagicBlock Validator Compression Feature + +## Overview + +The MagicBlock Validator's compression feature enables efficient state management by leveraging Light Protocol's compressed account technology (zkCompression). This allows the validator to handle accounts that exist on the base chain in compressed form while using them in uncompressed form locally, providing significant rent savings and improved performance. + +### What is Account Compression? + +Account compression in Solana refers to storing account data off-chain in a Merkle tree structure while maintaining on-chain proofs of validity. The Light Protocol provides the infrastructure for compressed accounts, allowing: + +- **Reduced storage costs**: Account data is stored off-chain +- **Scalability**: Lower on-chain footprint for applications + +### Why Compression in MagicBlock Validator? + +The compression feature serves several key purposes in the MagicBlock Validator ecosystem: + +1. **Cost Efficiency**: Reduces the cost of delegating accounts to the validator +2. **Scalability**: Enables handling more accounts with less on-chain state +3. **State Synchronization**: Allows seamless synchronization between compressed on-chain state and uncompressed local state +4. **Cross-Program Compatibility**: Works with existing Solana programs without modification + +## Architecture Overview + +The compression feature consists of several interconnected components: + +```mermaid +graph TB + subgraph "User Layer" + U[User Applications] + end + + subgraph "Core Components" + CDP[Compressed Delegation Program] + + subgraph MBV ["MagicBlock Validator"] + FCL[Fetch & Clone Logic] + CS[Committor Service] + end + end + + subgraph "Infrastructure Layer" + LP[Light Protocol
Infrastructure] + CT[Compression Tasks] + end + + U --> CDP + U --> MBV + + CDP <--> CS + CDP <--> FCL + + CDP --> LP + CS --> CT + + style MBV fill:#212121 +``` + +## Core Components + +### 1. Compressed Delegation Program + +**Program ID**: `DEL2rPzhFaS5qzo8XY9ZNxSzuunWueySq3p2dxJfwPbT` + +This external program manages the delegation of compressed accounts to MagicBlock validators. It provides the interface between compressed accounts and the validator infrastructure. + +#### Key Data Structures + +**CompressedDelegationRecord**: + +```rust +pub struct CompressedDelegationRecord { + pub pda: Pubkey, // The uncompressed account PDA + pub authority: Pubkey, // Validator authority (who can modify) + pub last_update_nonce: u64, // Prevents replay attacks + pub is_undelegatable: bool, // Whether undelegation is allowed + pub owner: Pubkey, // Original account owner + pub delegation_slot: u64, // Slot when delegation occurred + pub lamports: u64, // Account lamports + pub data: Vec, // Account data +} +``` + +### 2. MagicBlock Validator (Chainlink Component) + +The validator's chainlink component handles the synchronization and local state management of compressed accounts. + +#### FetchCloner Integration + +When a compressed account is encountered, the `FetchCloner` performs special handling: + +1. **Detection**: Identifies accounts owned by the compressed delegation program +2. **Record Parsing**: Extracts the `CompressedDelegationRecord` from the account data +3. **State Transformation**: Converts compressed state to uncompressed form +4. **Local Storage**: Stores the account in uncompressed form locally +5. **Subscription Management**: Manages subscriptions for state updates + +#### Key Methods + +- `is_owned_by_compressed_delegation_program()`: Detects compressed accounts +- `apply_delegation_record_to_account()`: Transforms account state +- `fetch_and_parse_delegation_record()`: Retrieves delegation metadata + +### 3. Committor Service + +Handles the commitment of local state changes back to the compressed on-chain state. + +#### Compression Tasks + +- **CompressedCommitTask**: Commits local changes to compressed accounts +- **CompressedFinalizeTask**: Finalizes compressed account operations +- **CompressedUndelegateTask**: Handles undelegation of compressed accounts + +## Complete Compression Flow + +### 1. Account Delegation (Compression) + +```mermaid +sequenceDiagram + participant User + participant Program + participant CDP as Compressed Delegation Program + participant Light as Light Protocol + participant Validator + + User->>Program: Call delegate_compressed() + Program->>CDP: Invoke delegate_compressed instruction + CDP->>Light: Create compressed account + Light->>CDP: Return compressed account proof + CDP->>Validator: Store delegation record + Validator->>User: Account delegated successfully +``` + +#### Steps: + +1. User calls a program's `delegate_compressed` instruction +2. Program invokes the Compressed Delegation Program +3. CDP creates a compressed account via Light Protocol +4. CDP stores the delegation record with validator authority +5. Account is now accessible in uncompressed form locally + +### 2. Account Usage (Local Operations) + +```mermaid +sequenceDiagram + participant User + participant Validator + participant Chainlink + participant FetchCloner + + User->>Validator: Transaction with compressed account + Validator->>Chainlink: ensure_transaction_accounts() + Chainlink->>FetchCloner: fetch_and_clone_accounts() + FetchCloner->>FetchCloner: Detect compressed account + FetchCloner->>FetchCloner: Parse delegation record + FetchCloner->>FetchCloner: Transform to uncompressed + FetchCloner->>Validator: Return uncompressed account + Validator->>User: Execute transaction +``` + +#### Steps: + +1. Transaction references a compressed account +2. Chainlink ensures account availability +3. FetchCloner detects compressed ownership +4. Parses the CompressedDelegationRecord +5. Transforms account to uncompressed form +6. Stores locally for transaction execution + +### 3. State Commitment (Decompression) + +```mermaid +sequenceDiagram + participant Validator + participant Committor + participant CDP as Compressed Delegation Program + participant Light as Light Protocol + participant Chain as Base Chain + + Validator->>Committor: Schedule compressed commit + Committor->>Committor: Create CompressedCommitTask + Committor->>CDP: Execute commit instruction + CDP->>Light: Update compressed state + Light->>Chain: Verify and store proof + Chain->>Committor: Commit confirmed +``` + +#### Steps: + +1. Validator schedules compressed commit via Committor Service +2. Committor creates appropriate compression task +3. Task executes commit instruction on Compressed Delegation Program +4. CDP updates compressed state via Light Protocol +5. State changes are committed to base chain + +## Data Structures and Types + +### Address Derivation + +Compressed accounts use a deterministic address derivation: + +```rust +pub fn derive_cda_from_pda(pda: &Pubkey) -> Pubkey { + let seed = hashv_to_bn254_field_size_be_const_array::<3>(&[&pda.to_bytes()]); + let address = derive_address(&seed, &ADDRESS_TREE, &COMPRESSED_DELEGATION_ID); + Pubkey::new_from_array(address) +} +``` + +## Integration with Chainlink + +### Account Detection and Handling + +The `FetchCloner` automatically detects compressed accounts by checking ownership: + +```rust +let owned_by_compressed_delegation_program = + account.is_owned_by_compressed_delegation_program(); +``` + +### State Transformation + +When a compressed account is detected: + +1. Parse the `CompressedDelegationRecord` from account data +2. Set account as compressed: `account.set_compressed(true)` +3. Set proper owner: `account.set_owner(delegation_record.owner)` +4. Set account data: `account.set_data(delegation_record.data)` +5. Set lamports: `account.set_lamports(delegation_record.lamports)` +6. Mark as delegated: `account.set_delegated(is_delegated_to_us)` + +### Undelegation Details + +**Ownership Reassignment Flow**: + +During undelegation, ownership reassignment follows a two-phase process: + +1. **Primary Undelegation**: The Compressed Delegation Program itself handles the core ownership reassignment, transferring ownership back to the original account owner and restoring the account to its fully compressed state on-chain. + +2. **Cleanup Callback**: The external undelegate handler (referenced by the `EXTERNAL_UNDELEGATE_DISCRIMINATOR`) is a program-specific callback invoked for cleanup operations after ownership has already been reassigned. This typically involves: + - Restoring lamports and data to the uncompressed account + - Performing any program-specific state transitions + - Ensuring the account is ready for local validator operations + +This separation ensures that critical ownership reassignment is handled by the trusted delegation program, while allowing programs to perform their own cleanup logic through the external handler. + +### Subscription Management + +Compressed accounts require special subscription handling: + +- **Delegation records are never subscribed to by design** - no subscription occurs for compressed delegation records +- **Accounts are unsubscribed when delegated** to maintain synchronization with compressed state +- **Updates for delegated accounts must be retrieved via the Photon indexer** rather than subscriptions +- Undelegating accounts are kept until undelegation completes + +## Committor Service Integration + +### Task Types + +**CompressedCommitTask**: +- Commits local account changes to compressed state +- Updates the CompressedDelegationRecord +- Generates ZK proofs for state transitions + +**CompressedFinalizeTask**: +- Finalizes compressed account operations +- Handles cleanup after successful commits + +**CompressedUndelegateTask**: +- Removes delegation from compressed accounts +- Transitions accounts back to fully compressed state + +### Task Execution Flow + +1. **Preparation**: Gather compressed account data and current state +2. **Proof Generation**: Create ZK proofs for state transitions +3. **Instruction Building**: Construct compressed delegation program instructions +4. **Execution**: Submit transactions to base chain +5. **Confirmation**: Wait for confirmation and update local state + +## Configuration and Setup + +### Required Dependencies + +- `compressed-delegation-client`: Client library for compressed delegation +- `light-client`: Light Protocol client for compressed accounts +- `light-sdk`: Light Protocol SDK for ZK operations +- `solana-program`: Solana program dependencies + +### Environment Constants + +```rust +pub const COMPRESSED_DELEGATION_ID: Pubkey = + pubkey!("DEL2rPzhFaS5qzo8XY9ZNxSzuunWueySq3p2dxJfwPbT"); + +pub const ADDRESS_TREE: Pubkey = + pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"); + +pub const OUTPUT_QUEUE: Pubkey = + pubkey!("oq1na8gojfdUhsfCpyjNt6h4JaDWtHf1yQj4koBWfto"); +``` + +## Error Handling + +### Common Error Scenarios + +1. **Invalid Compressed Account**: Account data doesn't contain valid delegation record +2. **Proof Verification Failure**: ZK proofs fail verification +3. **Insufficient Balance**: Account lacks lamports for operations +4. **Unauthorized Access**: Non-validator attempts to modify delegated accounts + +### Recovery Mechanisms + +- **Fallback Fetching**: Retry account fetching with different methods +- **State Validation**: Verify account state integrity before operations +- **Graceful Degradation**: Fall back to uncompressed operations when possible + +## Testing and Validation + +### Integration Tests + +The compression feature includes comprehensive integration tests covering: + +- Compressed account delegation and undelegation +- State synchronization between compressed and uncompressed forms +- Commit operations and state settlement +- Error scenarios and edge cases + +### Test Infrastructure + +- **IxtestContext**: Test context providing compressed account utilities +- **Photon Indexer**: Mock indexer for compressed account proofs +- **Chainlink Integration**: Full chainlink testing with compression + +## Performance Considerations + +### Benefits + +- **Storage Efficiency**: ~10x reduction in on-chain storage costs +- **Network Efficiency**: Reduced data transfer for account synchronization + +### Trade-offs + +- **Computational Overhead**: ZK proof generation and verification +- **Latency**: Additional round trips for proof fetching +- **Complexity**: Increased system complexity for state management + +## Future Enhancements + +### Planned Features + +1. **Use BufferTasks**: Using `BufferTask` instead of `ArgTask` allows managing larger accounts. +2. **Work directly with compressed accounts**: The current implementation requires that an empty PDA exists to ensure the corresponding compressed account can be created only once. Using compressed account created directly by the user program would allow onboarding compressed accounts users simply. diff --git a/magicblock-account-cloner/src/bpf_loader_v1.rs b/magicblock-account-cloner/src/bpf_loader_v1.rs index b71b5c501..5908925ff 100644 --- a/magicblock-account-cloner/src/bpf_loader_v1.rs +++ b/magicblock-account-cloner/src/bpf_loader_v1.rs @@ -47,6 +47,7 @@ impl BpfUpgradableProgramModifications { owner: Some(bpf_loader_upgradeable::id()), executable: Some(false), delegated: Some(false), + compressed: Some(false), confined: Some(false), remote_slot: Some(loaded_program.remote_slot), } @@ -67,6 +68,7 @@ impl BpfUpgradableProgramModifications { owner: Some(bpf_loader_upgradeable::id()), executable: Some(true), delegated: Some(false), + compressed: Some(false), confined: Some(false), remote_slot: Some(loaded_program.remote_slot), } diff --git a/magicblock-account-cloner/src/lib.rs b/magicblock-account-cloner/src/lib.rs index 456ad08db..f74955e53 100644 --- a/magicblock-account-cloner/src/lib.rs +++ b/magicblock-account-cloner/src/lib.rs @@ -111,6 +111,7 @@ impl ChainlinkCloner { data: Some(request.account.data().to_owned()), executable: Some(request.account.executable()), delegated: Some(request.account.delegated()), + compressed: Some(request.account.compressed()), confined: Some(request.account.confined()), remote_slot: Some(request.account.remote_slot()), }; diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index c67093d03..fb6cd3e6c 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -10,6 +10,7 @@ use magicblock_chainlink::{ remote_account_provider::{ chain_rpc_client::ChainRpcClientImpl, chain_updates_client::ChainUpdatesClient, + photon_client::PhotonClientImpl, }, submux::SubMuxClient, Chainlink, @@ -49,6 +50,7 @@ pub type ChainlinkImpl = Chainlink< SubMuxClient, AccountsDb, ChainlinkCloner, + PhotonClientImpl, >; pub struct ScheduledCommitsProcessorImpl { diff --git a/magicblock-aperture/src/state/mod.rs b/magicblock-aperture/src/state/mod.rs index 8e1263562..4d77ad7cb 100644 --- a/magicblock-aperture/src/state/mod.rs +++ b/magicblock-aperture/src/state/mod.rs @@ -8,6 +8,7 @@ use magicblock_chainlink::{ remote_account_provider::{ chain_rpc_client::ChainRpcClientImpl, chain_updates_client::ChainUpdatesClient, + photon_client::PhotonClientImpl, }, submux::SubMuxClient, Chainlink, @@ -24,6 +25,7 @@ pub type ChainlinkImpl = Chainlink< SubMuxClient, AccountsDb, ChainlinkCloner, + PhotonClientImpl, >; /// A container for the shared, global state of the RPC service. diff --git a/magicblock-api/Cargo.toml b/magicblock-api/Cargo.toml index 1a1c8dab8..b1770c158 100644 --- a/magicblock-api/Cargo.toml +++ b/magicblock-api/Cargo.toml @@ -11,6 +11,7 @@ edition.workspace = true anyhow = { workspace = true } borsh = "1.5.3" fd-lock = { workspace = true } +light-client = { workspace = true, features = ["v2"] } tracing = { workspace = true } magic-domain-program = { workspace = true } diff --git a/magicblock-api/src/magic_validator.rs b/magicblock-api/src/magic_validator.rs index 8911de571..6ead2472b 100644 --- a/magicblock-api/src/magic_validator.rs +++ b/magicblock-api/src/magic_validator.rs @@ -7,6 +7,7 @@ use std::{ thread, }; +use light_client::indexer::photon_indexer::PhotonIndexer; use magicblock_account_cloner::{ map_committor_request_result, ChainlinkCloner, }; @@ -23,7 +24,8 @@ use magicblock_chainlink::{ config::ChainlinkConfig, remote_account_provider::{ chain_rpc_client::ChainRpcClientImpl, - chain_updates_client::ChainUpdatesClient, Endpoints, + chain_updates_client::ChainUpdatesClient, + photon_client::PhotonClientImpl, Endpoint, Endpoints, }, submux::SubMuxClient, Chainlink, @@ -98,6 +100,7 @@ type ChainlinkImpl = Chainlink< SubMuxClient, AccountsDb, ChainlinkCloner, + PhotonClientImpl, >; // ----------------- @@ -338,6 +341,11 @@ impl MagicValidator { async fn init_committor_service( config: &ValidatorParams, ) -> ApiResult>> { + let photon_client = Arc::new(PhotonIndexer::new( + config.compression.photon_url.clone(), + config.compression.api_key.clone(), + )); + let committor_persist_path = config.storage.join("committor_service.sqlite"); debug!(path = %committor_persist_path.display(), "Initializing committor service"); @@ -352,6 +360,7 @@ impl MagicValidator { config.commit.compute_unit_price, ), }, + photon_client, )?)); if let Some(committor_service) = &committor_service { @@ -383,13 +392,18 @@ impl MagicValidator { accountsdb: &Arc, faucet_pubkey: Pubkey, ) -> ApiResult { - let endpoints = Endpoints::try_from(config.remotes.as_slice()) + let mut endpoints = Endpoints::try_from(config.remotes.as_slice()) .map_err(|e| { ApiError::from( magicblock_chainlink::errors::ChainlinkError::from(e), ) })?; + endpoints.push(Endpoint::Compression { + url: config.compression.photon_url.clone(), + api_key: config.compression.api_key.clone(), + }); + let cloner = ChainlinkCloner::new( committor_service, config.chainlink.clone(), diff --git a/magicblock-chainlink/Cargo.toml b/magicblock-chainlink/Cargo.toml index c89f88c14..e951f37e6 100644 --- a/magicblock-chainlink/Cargo.toml +++ b/magicblock-chainlink/Cargo.toml @@ -4,10 +4,13 @@ version.workspace = true edition.workspace = true [dependencies] -arc-swap = "1.7" +arc-swap = { workspace = true } async-trait = { workspace = true } bincode = { workspace = true } +borsh = { workspace = true } +compressed-delegation-client = { workspace = true } futures-util = { workspace = true } +light-client = { workspace = true } helius-laserstream = { workspace = true } tracing = { workspace = true } lru = { workspace = true } diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/ata_projection.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/ata_projection.rs index ce1b4cbfd..3aa838c25 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/ata_projection.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/ata_projection.rs @@ -11,7 +11,9 @@ use tracing::*; use super::{delegation, types::AccountWithCompanion, FetchCloner}; use crate::{ cloner::{AccountCloneRequest, Cloner}, - remote_account_provider::{ChainPubsubClient, ChainRpcClient}, + remote_account_provider::{ + photon_client::PhotonClient, ChainPubsubClient, ChainRpcClient, + }, }; /// Resolves ATAs with eATA projection. @@ -19,8 +21,8 @@ use crate::{ /// and, if the ATA is delegated to us and the eATA exists, we clone the eATA data /// into the ATA in the bank. #[instrument(skip(this, atas))] -pub(crate) async fn resolve_ata_with_eata_projection( - this: &FetchCloner, +pub(crate) async fn resolve_ata_with_eata_projection( + this: &FetchCloner, atas: Vec<( Pubkey, AccountSharedData, @@ -35,6 +37,7 @@ where U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, { if atas.is_empty() { return vec![]; diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/compression.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/compression.rs new file mode 100644 index 000000000..fa2f570a5 --- /dev/null +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/compression.rs @@ -0,0 +1,215 @@ +use std::sync::Arc; + +use borsh::BorshDeserialize; +use compressed_delegation_client::CompressedDelegationRecord; +use dlp::state::DelegationRecord; +use magicblock_core::traits::AccountsBank; +use magicblock_metrics::metrics::AccountFetchOrigin; +use solana_account::{AccountSharedData, ReadableAccount, WritableAccount}; +use solana_pubkey::Pubkey; +use tracing::*; + +use crate::{ + chainlink::account_still_undelegating_on_chain::account_still_undelegating_on_chain, + cloner::{AccountCloneRequest, Cloner}, + fetch_cloner::{types::RefreshDecision, FetchCloner}, + remote_account_provider::{ + photon_client::PhotonClient, ChainPubsubClient, ChainRpcClient, + RemoteAccountProvider, + }, +}; + +pub(crate) async fn resolve_compressed_delegated_accounts( + this: &FetchCloner, + owned_by_deleg_compressed: Vec<(Pubkey, AccountSharedData, u64)>, +) -> Vec +where + T: ChainRpcClient, + U: ChainPubsubClient, + V: AccountsBank, + C: Cloner, + P: PhotonClient, +{ + owned_by_deleg_compressed + .into_iter() + .filter_map(|(pubkey, mut account, _)| { + match CompressedDelegationRecord::try_from_slice(account.data()) { + Ok(delegation_record) => { + account.set_compressed(true); + account.set_lamports(delegation_record.lamports); + account.set_owner(delegation_record.owner); + account.set_data(delegation_record.data); + account.set_delegated( + delegation_record.authority.eq(&this.validator_pubkey), + ); + account.set_confined( + delegation_record.authority.eq(&Pubkey::default()), + ); + + let delegated_to_other = + this.get_delegated_to_other(&DelegationRecord { + authority: delegation_record.authority, + owner: delegation_record.owner, + delegation_slot: delegation_record.delegation_slot, + lamports: delegation_record.lamports, + commit_frequency_ms: 0, + }); + + Some(AccountCloneRequest { + pubkey, + account, + commit_frequency_ms: None, + delegated_to_other, + }) + } + Err(err) => { + error!( + pubkey = %pubkey, + error = %err, + account = ?account, + "Failed to deserialize compressed delegation record", + ); + None + } + } + }) + .collect::>() +} + +pub(crate) async fn resolve_compressed_accounts_to_clone< + T: ChainRpcClient, + U: ChainPubsubClient, + P: PhotonClient, +>( + pubkey: Pubkey, + mut account: AccountSharedData, + remote_account_provider: Arc>, + validator_pubkey: Pubkey, +) -> (Option, Option) { + // If the account is compressed, it can contain either: + // 1. The delegation record + // 2. No data in case the last update was the notif where the account was emptied + // If we fail to get the record, we need to fetch again so that we obtain the data + // from the compressed account. + let delegation_record = match CompressedDelegationRecord::try_from_slice( + account.data(), + ) { + Ok(delegation_record) => Some(delegation_record), + Err(parse_err) => { + debug!( + pubkey = %pubkey, + error = %parse_err, + "The account's data did not contain a valid compressed delegation record", + ); + match remote_account_provider + .try_get(pubkey, AccountFetchOrigin::GetAccount) + .await + { + Ok(remote_acc) => { + if let Some(acc) = remote_acc.fresh_account().cloned() { + match CompressedDelegationRecord::try_from_slice( + acc.data(), + ) { + Ok(delegation_record) => Some(delegation_record), + Err(parse_err) => { + error!( + pubkey = %pubkey, + error = %parse_err, + "Fetched account parse failed", + ); + None + } + } + } else { + error!( + pubkey = %pubkey, + "Remote fetch failed, no fresh account returned", + ); + None + } + } + Err(fetch_err) => { + error!( + pubkey = %pubkey, + error = %fetch_err, + "Remote fetch failed", + ); + None + } + } + } + }; + + if let Some(delegation_record) = delegation_record { + account.set_compressed(true); + account.set_owner(delegation_record.owner); + account.set_data(delegation_record.data); + account.set_lamports(delegation_record.lamports); + account + .set_confined(delegation_record.authority.eq(&Pubkey::default())); + + let is_delegated_to_us = + delegation_record.authority.eq(&validator_pubkey); + account.set_delegated(is_delegated_to_us); + + // TODO(dode): commit frequency ms is not supported for compressed delegation records + ( + Some(account), + Some(DelegationRecord { + authority: delegation_record.authority, + owner: delegation_record.owner, + delegation_slot: delegation_record.delegation_slot, + lamports: delegation_record.lamports, + commit_frequency_ms: 0, + }), + ) + } else { + (None, None) + } +} + +pub(crate) async fn should_refresh_undelegating_in_bank_compressed_account( + pubkey: &Pubkey, + in_bank: &AccountSharedData, + validator_pubkey: Pubkey, +) -> RefreshDecision { + let Some(record) = + CompressedDelegationRecord::try_from_slice(in_bank.data()).ok() + else { + // The delegation record is not present in the account data because the actual account data + // has already been extracted from it. Refreshing would reset the account, losing local changes. + debug!( + pubkey = %pubkey, + data = %format!("{:?}", in_bank.data()), + "Skip refresh for already processed compressed account" + ); + return RefreshDecision::No; + }; + + if !account_still_undelegating_on_chain( + pubkey, + record.authority.eq(&validator_pubkey) + || record.authority.eq(&Pubkey::default()), + in_bank.remote_slot(), + Some(DelegationRecord { + authority: record.authority, + owner: record.owner, + delegation_slot: record.delegation_slot, + lamports: record.lamports, + commit_frequency_ms: 0, // TODO(dode): use the actual commit frequency once implemented + }), + &validator_pubkey, + ) { + debug!( + pubkey = %pubkey, + "Refresh compressed account since the compressed delegation record is not undelegating" + ); + return RefreshDecision::Yes; + }; + debug!( + pubkey = %pubkey, + "Skip refresh for compressed account since the compressed delegation record is undelegating" + ); + + RefreshDecision::No +} diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/delegation.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/delegation.rs index cf77ea51d..c51187829 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/delegation.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/delegation.rs @@ -12,8 +12,8 @@ use crate::{ chainlink::errors::{ChainlinkError, ChainlinkResult}, cloner::Cloner, remote_account_provider::{ - ChainPubsubClient, ChainRpcClient, MatchSlotsConfig, - ResolvedAccountSharedData, + photon_client::PhotonClient, ChainPubsubClient, ChainRpcClient, + MatchSlotsConfig, ResolvedAccountSharedData, }, }; @@ -31,8 +31,8 @@ pub(crate) fn parse_delegation_record( }) } -pub(crate) fn apply_delegation_record_to_account( - this: &FetchCloner, +pub(crate) fn apply_delegation_record_to_account( + this: &FetchCloner, account: &mut ResolvedAccountSharedData, delegation_record: &DelegationRecord, ) -> Option @@ -41,6 +41,7 @@ where U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, { let is_confined = delegation_record.authority.eq(&Pubkey::default()); let is_delegated_to_us = @@ -63,8 +64,8 @@ where } } -pub(crate) fn get_delegated_to_other( - this: &FetchCloner, +pub(crate) fn get_delegated_to_other( + this: &FetchCloner, delegation_record: &DelegationRecord, ) -> Option where @@ -72,6 +73,7 @@ where U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, { let is_delegated_to_us = delegation_record.authority.eq(&this.validator_pubkey) @@ -81,8 +83,8 @@ where } #[instrument(skip(this))] -pub(crate) async fn fetch_and_parse_delegation_record( - this: &FetchCloner, +pub(crate) async fn fetch_and_parse_delegation_record( + this: &FetchCloner, account_pubkey: Pubkey, min_context_slot: u64, fetch_origin: metrics::AccountFetchOrigin, @@ -92,6 +94,7 @@ where U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, { let delegation_record_pubkey = delegation_record_pda_from_delegated_account(&account_pubkey); @@ -115,7 +118,7 @@ where if let Some(delegation_record_remote) = delegation_records.pop() { match delegation_record_remote.fresh_account() { Some(delegation_record_account) => { - FetchCloner::::parse_delegation_record( + FetchCloner::::parse_delegation_record( delegation_record_account.data(), delegation_record_pubkey, ) diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs index bee76a0f4..75dfdb3d0 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs @@ -25,6 +25,7 @@ use tokio::{ use tracing::*; mod ata_projection; +mod compression; mod delegation; mod pipeline; mod program_loader; @@ -48,6 +49,7 @@ use crate::{ }, cloner::{errors::ClonerResult, AccountCloneRequest, Cloner}, remote_account_provider::{ + photon_client::PhotonClient, program_account::get_loaderv3_get_program_data_address, ChainPubsubClient, ChainRpcClient, ForwardedSubscriptionUpdate, MatchSlotsConfig, RemoteAccount, RemoteAccountProvider, @@ -58,15 +60,16 @@ use crate::{ type RemoteAccountRequests = Vec>; #[derive(Clone)] -pub struct FetchCloner +pub struct FetchCloner where T: ChainRpcClient, U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, { /// The RemoteAccountProvider to fetch accounts from - remote_account_provider: Arc>, + remote_account_provider: Arc>, /// Tracks pending account fetch requests to avoid duplicate fetches in parallel /// Once an account is fetched and cloned into the bank, it's removed from here pending_requests: Arc>, @@ -86,16 +89,17 @@ where allowed_programs: Option>, } -impl FetchCloner +impl FetchCloner where T: ChainRpcClient, U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, { /// Create FetchCloner with subscription updates properly connected pub fn new( - remote_account_provider: &Arc>, + remote_account_provider: &Arc>, accounts_bank: &Arc, cloner: &Arc, validator_pubkey: Pubkey, @@ -303,8 +307,10 @@ where let ForwardedSubscriptionUpdate { pubkey, account } = update; let owned_by_delegation_program = account.is_owned_by_delegation_program(); + let owned_by_compressed_delegation_program = + account.is_owned_by_compressed_delegation_program(); - if let Some(account) = account.fresh_account() { + if let Some(account) = account.fresh_account().cloned() { // If the account is owned by the delegation program we need to resolve // its true owner and determine if it is delegated to us if owned_by_delegation_program { @@ -427,6 +433,14 @@ where (None, None) } } + } else if owned_by_compressed_delegation_program { + compression::resolve_compressed_accounts_to_clone( + pubkey, + account, + self.remote_account_provider.clone(), + self.validator_pubkey, + ) + .await } else { // Accounts not owned by the delegation program can be cloned as is // No unsubscription needed for undelegated accounts @@ -550,6 +564,7 @@ where not_found, plain, owned_by_deleg, + owned_by_deleg_compressed, programs, atas, } = pipeline::classify_remote_accounts(accs, pubkeys); @@ -567,6 +582,10 @@ where .iter() .map(|(pubkey, _, slot)| (pubkey.to_string(), *slot)) .collect::>(); + let owned_by_deleg_compressed = owned_by_deleg_compressed + .iter() + .map(|(pubkey, _, slot)| (pubkey.to_string(), *slot)) + .collect::>(); let programs = programs .iter() .map(|(p, _, _)| p.to_string()) @@ -576,7 +595,7 @@ where .map(|(a, _, _, _)| a.to_string()) .collect::>(); trace!( - "Fetched accounts: \nnot_found: {not_found:?} \nplain: {plain:?} \nowned_by_deleg: {owned_by_deleg:?}\nprograms: {programs:?} \natas: {atas:?}", + "Fetched accounts: \nnot_found: {not_found:?} \nplain: {plain:?} \nowned_by_deleg: {owned_by_deleg:?} \nowned_by_deleg_compressed: {owned_by_deleg_compressed:?} \nprograms: {programs:?} \natas: {atas:?}", ); } @@ -666,6 +685,15 @@ where .await; accounts_to_clone.extend(ata_accounts); + // Handle compressed delegated accounts: for each compressed delegated account, we clone the account data into the bank. + let compressed_delegated_accounts = + compression::resolve_compressed_delegated_accounts( + self, + owned_by_deleg_compressed, + ) + .await; + accounts_to_clone.extend(compressed_delegated_accounts); + // Compute sub cancellations now since we may potentially fail during a cloning step let cancel_strategy = pipeline::compute_cancel_strategy( pubkeys, @@ -704,7 +732,22 @@ where in_bank: &AccountSharedData, fetch_origin: AccountFetchOrigin, ) -> RefreshDecision { - if in_bank.undelegating() { + info!( + pubkey = %pubkey, + delegated = in_bank.delegated(), + undelegating = in_bank.undelegating(), + compressed = in_bank.compressed(), + owner = %in_bank.owner(), + "should_refresh_undelegating_in_bank_account" + ); + if in_bank.compressed() { + return compression::should_refresh_undelegating_in_bank_compressed_account( + pubkey, + in_bank, + self.validator_pubkey, + ) + .await; + } else if in_bank.undelegating() { debug!( pubkey = %pubkey, delegated = in_bank.delegated(), @@ -828,11 +871,13 @@ where if tracing::enabled!(tracing::Level::TRACE) { let undelegating = account_in_bank.undelegating(); let delegated = account_in_bank.delegated(); + let compressed = account_in_bank.compressed(); let owner = account_in_bank.owner(); trace!( pubkey = %pubkey, undelegating, delegated, + compressed, owner = %owner, "Account found in bank in valid state, no fetch needed" ); diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/pipeline.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/pipeline.rs index 45418b62f..b6ea0d404 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/pipeline.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/pipeline.rs @@ -20,6 +20,7 @@ use crate::{ chainlink::errors::{ChainlinkError, ChainlinkResult}, cloner::{errors::ClonerResult, AccountCloneRequest, Cloner}, remote_account_provider::{ + photon_client::PhotonClient, program_account::{ get_loaderv3_get_program_data_address, ProgramAccountResolver, LOADER_V3, @@ -29,8 +30,8 @@ use crate::{ }, }; -pub(crate) fn build_existing_subs( - this: &FetchCloner, +pub(crate) fn build_existing_subs( + this: &FetchCloner, pubkeys: &[Pubkey], ) -> ExistingSubs where @@ -38,6 +39,7 @@ where U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, { let delegation_records = pubkeys .iter() @@ -58,35 +60,40 @@ where ExistingSubs { existing_subs } } +/// Helper struct to hold classification buckets +#[derive(Default)] +struct ClassificationBuckets { + not_found: Vec<(Pubkey, u64)>, + plain: Vec, + owned_by_deleg: Vec<(Pubkey, AccountSharedData, u64)>, + owned_by_deleg_compressed: Vec<(Pubkey, AccountSharedData, u64)>, + programs: Vec<(Pubkey, AccountSharedData, u64)>, + atas: Vec<( + Pubkey, + AccountSharedData, + magicblock_core::token_programs::AtaInfo, + u64, + )>, +} + /// Classifies fetched remote accounts into categories pub(crate) fn classify_remote_accounts( accs: Vec, pubkeys: &[Pubkey], ) -> ClassifiedAccounts { - let mut not_found = Vec::new(); - let mut plain = Vec::new(); - let mut owned_by_deleg = Vec::new(); - let mut programs = Vec::new(); - let mut atas = Vec::new(); + let mut buckets = ClassificationBuckets::default(); for (acc, &pubkey) in accs.into_iter().zip(pubkeys) { - classify_single_account( - acc, - pubkey, - &mut not_found, - &mut plain, - &mut owned_by_deleg, - &mut programs, - &mut atas, - ); + classify_single_account(acc, pubkey, &mut buckets); } ClassifiedAccounts { - not_found, - plain, - owned_by_deleg, - programs, - atas, + not_found: buckets.not_found, + plain: buckets.plain, + owned_by_deleg: buckets.owned_by_deleg, + owned_by_deleg_compressed: buckets.owned_by_deleg_compressed, + programs: buckets.programs, + atas: buckets.atas, } } @@ -95,21 +102,12 @@ pub(crate) fn classify_remote_accounts( fn classify_single_account( acc: RemoteAccount, pubkey: Pubkey, - not_found: &mut Vec<(Pubkey, u64)>, - plain: &mut Vec, - owned_by_deleg: &mut Vec<(Pubkey, AccountSharedData, u64)>, - programs: &mut Vec<(Pubkey, AccountSharedData, u64)>, - atas: &mut Vec<( - Pubkey, - AccountSharedData, - magicblock_core::token_programs::AtaInfo, - u64, - )>, + buckets: &mut ClassificationBuckets, ) { use RemoteAccount::*; match acc { NotFound(slot) => { - not_found.push((pubkey, slot)); + buckets.not_found.push((pubkey, slot)); } Found(remote_account_state) => { match remote_account_state.account { @@ -118,7 +116,16 @@ fn classify_single_account( if account_shared_data.owner().eq(&dlp::id()) { // Account owned by delegation program - owned_by_deleg.push(( + buckets.owned_by_deleg.push(( + pubkey, + account_shared_data, + slot, + )); + } else if account_shared_data + .owner() + .eq(&compressed_delegation_client::id()) + { + buckets.owned_by_deleg_compressed.push(( pubkey, account_shared_data, slot, @@ -129,16 +136,21 @@ fn classify_single_account( pubkey, account_shared_data, slot, - programs, + &mut buckets.programs, ); } else if let Some(ata) = is_ata(&pubkey, &account_shared_data) { // Associated Token Account - atas.push((pubkey, account_shared_data, ata, slot)); + buckets.atas.push(( + pubkey, + account_shared_data, + ata, + slot, + )); } else { // Plain account - plain.push(AccountCloneRequest { + buckets.plain.push(AccountCloneRequest { pubkey, account: account_shared_data, commit_frequency_ms: None, @@ -200,8 +212,8 @@ pub(crate) fn partition_not_found( /// Resolves delegated accounts by fetching their delegation records #[instrument(skip(this, owned_by_deleg, plain, pubkeys, existing_subs), fields(pubkey_count = pubkeys.len()))] -pub(crate) async fn resolve_delegated_accounts( - this: &FetchCloner, +pub(crate) async fn resolve_delegated_accounts( + this: &FetchCloner, owned_by_deleg: Vec<(Pubkey, AccountSharedData, u64)>, plain: Vec, min_context_slot: Option, @@ -214,6 +226,7 @@ where U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, { // For potentially delegated accounts we update the owner and delegation state first let mut fetch_with_delegation_record_join_set = JoinSet::new(); @@ -288,17 +301,14 @@ where record_subs.push(delegation_record_pubkey); // If the account is delegated we set the owner and delegation state - let (commit_frequency_ms, delegated_to_other) = if let Some( - delegation_record_data, - ) = - delegation_record - { - // NOTE: failing here is fine when resolving all accounts for a transaction - // since if something is off we better not run it anyways - // However we may consider a different behavior when user is getting - // multiple accounts. - let delegation_record = - match FetchCloner::::parse_delegation_record( + let (commit_frequency_ms, delegated_to_other) = + if let Some(delegation_record_data) = delegation_record { + // NOTE: failing here is fine when resolving all accounts for a transaction + // since if something is off we better not run it anyways + // However we may consider a different behavior when user is getting + // multiple accounts. + let delegation_record = + match FetchCloner::::parse_delegation_record( delegation_record_data.data(), delegation_record_pubkey, ) { @@ -321,20 +331,21 @@ where } }; - trace!(pubkey = %pubkey, "Delegation record found"); + trace!(pubkey = %pubkey, "Delegation record found"); - let delegated_to_other = - this.get_delegated_to_other(&delegation_record); + let delegated_to_other = + this.get_delegated_to_other(&delegation_record); - let commit_freq = this.apply_delegation_record_to_account( - &mut account, - &delegation_record, - ); - (commit_freq, delegated_to_other) - } else { - missing_delegation_record.push((pubkey, account.remote_slot())); - (None, None) - }; + let commit_freq = this.apply_delegation_record_to_account( + &mut account, + &delegation_record, + ); + (commit_freq, delegated_to_other) + } else { + missing_delegation_record + .push((pubkey, account.remote_slot())); + (None, None) + }; accounts_to_clone.push(AccountCloneRequest { pubkey, account: account.into_account_shared_data(), @@ -355,8 +366,8 @@ where /// Resolves program accounts, fetching program data accounts for LoaderV3 programs #[instrument(skip(this, programs, pubkeys, existing_subs), fields(pubkey_count = pubkeys.len()))] -pub(crate) async fn resolve_programs_with_program_data( - this: &FetchCloner, +pub(crate) async fn resolve_programs_with_program_data( + this: &FetchCloner, programs: Vec<(Pubkey, AccountSharedData, u64)>, min_context_slot: Option, fetch_origin: AccountFetchOrigin, @@ -368,6 +379,7 @@ where U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, { // For LoaderV3 accounts we fetch the program data account let (loaderv3_programs, single_account_programs): (Vec<_>, Vec<_>) = @@ -446,7 +458,7 @@ where let account_program = account_pair[0].clone(); let account_data = account_pair[1].clone(); - let result = FetchCloner::::resolve_account_with_companion( + let result = FetchCloner::::resolve_account_with_companion( &this.accounts_bank, pubkey, program_data_pubkey, @@ -607,8 +619,8 @@ pub(crate) fn compute_cancel_strategy( /// Clones accounts and programs into the bank #[instrument(skip(this, accounts_to_clone, loaded_programs))] -pub(crate) async fn clone_accounts_and_programs( - this: &FetchCloner, +pub(crate) async fn clone_accounts_and_programs( + this: &FetchCloner, accounts_to_clone: Vec, loaded_programs: Vec< crate::remote_account_provider::program_account::LoadedProgram, @@ -619,6 +631,7 @@ where U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, { let mut join_set = JoinSet::new(); for request in accounts_to_clone { diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/program_loader.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/program_loader.rs index 78d1fe61a..efdc07f30 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/program_loader.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/program_loader.rs @@ -8,13 +8,14 @@ use super::FetchCloner; use crate::{ cloner::Cloner, remote_account_provider::{ + photon_client::PhotonClient, program_account::{ProgramAccountResolver, LOADER_V1, LOADER_V3}, ChainPubsubClient, ChainRpcClient, }, }; -pub(crate) async fn handle_executable_sub_update( - this: &FetchCloner, +pub(crate) async fn handle_executable_sub_update( + this: &FetchCloner, pubkey: Pubkey, account: AccountSharedData, ) where @@ -22,6 +23,7 @@ pub(crate) async fn handle_executable_sub_update( U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, { if !this.is_program_allowed(&pubkey) { debug!(pubkey = %pubkey, "Skipping clone of program, not in allowed_programs"); diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/subscription.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/subscription.rs index a6037e13d..6aba01ee6 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/subscription.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/subscription.rs @@ -5,7 +5,8 @@ use tokio::task::JoinSet; use tracing::*; use crate::remote_account_provider::{ - ChainPubsubClient, ChainRpcClient, RemoteAccountProvider, + photon_client::PhotonClient, ChainPubsubClient, ChainRpcClient, + RemoteAccountProvider, }; pub(crate) enum CancelStrategy { @@ -102,8 +103,12 @@ impl fmt::Display for CancelStrategy { } #[instrument(skip(provider, strategy))] -pub(crate) async fn cancel_subs( - provider: &Arc>, +pub(crate) async fn cancel_subs< + T: ChainRpcClient, + U: ChainPubsubClient, + P: PhotonClient, +>( + provider: &Arc>, strategy: CancelStrategy, ) { if strategy.is_empty() { diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs index 766f1dfaf..3c93dd0d9 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs @@ -20,6 +20,7 @@ use crate::{ cloner_stub::ClonerStub, deleg::{add_delegation_record_for, add_invalid_delegation_record_for}, init_logger, + photon_client_mock::PhotonClientMock, rpc_client_mock::{ChainRpcClientMock, ChainRpcClientMockBuilder}, utils::{create_test_lru_cache, random_pubkey}, }, @@ -32,6 +33,7 @@ type TestFetchClonerResult = ( ChainPubsubClientMock, AccountsBankStub, ClonerStub, + PhotonClientMock, >, >, mpsc::Sender, @@ -85,8 +87,13 @@ macro_rules! assert_cloned_undelegated_account { } struct FetcherTestCtx { - remote_account_provider: - Arc>, + remote_account_provider: Arc< + RemoteAccountProvider< + ChainRpcClientMock, + ChainPubsubClientMock, + PhotonClientMock, + >, + >, accounts_bank: Arc, rpc_client: crate::testing::rpc_client_mock::ChainRpcClientMock, #[allow(unused)] @@ -97,6 +104,7 @@ struct FetcherTestCtx { ChainPubsubClientMock, AccountsBankStub, ClonerStub, + PhotonClientMock, >, >, #[allow(unused)] @@ -138,6 +146,7 @@ where RemoteAccountProvider::new( rpc_client, pubsub_client, + PhotonClientMock::new(), forward_tx, &config, subscribed_accounts, @@ -167,7 +176,11 @@ where /// Returns (FetchCloner, subscription_sender) for simulating subscription updates in tests fn init_fetch_cloner( remote_account_provider: Arc< - RemoteAccountProvider, + RemoteAccountProvider< + ChainRpcClientMock, + ChainPubsubClientMock, + PhotonClientMock, + >, >, bank: &Arc, validator_pubkey: Pubkey, @@ -911,7 +924,7 @@ async fn test_delegation_record_unsub_race_condition_prevention() { // Use a shared FetchCloner to test deduplication // Helper function to spawn a fetch_and_clone task with shared FetchCloner - let spawn_fetch_task = |fetch_cloner: &Arc>| { + let spawn_fetch_task = |fetch_cloner: &Arc>| { let fetch_cloner = fetch_cloner.clone(); tokio::spawn(async move { fetch_cloner diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/types.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/types.rs index e7ddbeb2b..984aa761a 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/types.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/types.rs @@ -32,6 +32,7 @@ pub(crate) struct ClassifiedAccounts { pub(crate) not_found: Vec<(Pubkey, u64)>, pub(crate) plain: Vec, pub(crate) owned_by_deleg: Vec<(Pubkey, AccountSharedData, u64)>, + pub(crate) owned_by_deleg_compressed: Vec<(Pubkey, AccountSharedData, u64)>, pub(crate) programs: Vec<(Pubkey, AccountSharedData, u64)>, pub(crate) atas: Vec<( Pubkey, diff --git a/magicblock-chainlink/src/chainlink/mod.rs b/magicblock-chainlink/src/chainlink/mod.rs index 1217c75de..116318f67 100644 --- a/magicblock-chainlink/src/chainlink/mod.rs +++ b/magicblock-chainlink/src/chainlink/mod.rs @@ -26,8 +26,10 @@ use crate::{ fetch_cloner::FetchAndCloneResult, filters::is_noop_system_transfer, remote_account_provider::{ - chain_updates_client::ChainUpdatesClient, ChainPubsubClient, - ChainRpcClient, ChainRpcClientImpl, Endpoints, RemoteAccountProvider, + chain_updates_client::ChainUpdatesClient, + photon_client::{PhotonClient, PhotonClientImpl}, + ChainPubsubClient, ChainRpcClient, ChainRpcClientImpl, Endpoints, + RemoteAccountProvider, }, submux::SubMuxClient, }; @@ -40,6 +42,8 @@ pub mod fetch_cloner; pub use blacklisted_accounts::*; +type ArcFetchCloner = Arc>; + // ----------------- // Chainlink // ----------------- @@ -48,9 +52,10 @@ pub struct Chainlink< U: ChainPubsubClient, V: AccountsBank, C: Cloner, + P: PhotonClient, > { accounts_bank: Arc, - fetch_cloner: Option>>, + fetch_cloner: Option>, /// The subscription to events for each account that is removed from /// the accounts tracked by the provider. /// In that case we also remove it from the bank since it is no longer @@ -68,12 +73,17 @@ pub struct Chainlink< remove_confined_accounts: bool, } -impl - Chainlink +impl< + T: ChainRpcClient, + U: ChainPubsubClient, + V: AccountsBank, + C: Cloner, + P: PhotonClient, + > Chainlink { pub fn try_new( accounts_bank: &Arc, - fetch_cloner: Option>>, + fetch_cloner: Option>, validator_pubkey: Pubkey, faucet_pubkey: Pubkey, config: &ChainLinkConfig, @@ -117,7 +127,13 @@ impl config: ChainlinkConfig, chainlink_config: &ChainLinkConfig, ) -> ChainlinkResult< - Chainlink, V, C>, + Chainlink< + ChainRpcClientImpl, + SubMuxClient, + V, + C, + PhotonClientImpl, + >, > { // Extract accounts provider and create fetch cloner while connecting // the subscription channel @@ -281,7 +297,7 @@ impl /// does nothing as only existing accounts are affected. /// See [lru::LruCache::promote] fn promote_accounts( - fetch_cloner: &FetchCloner, + fetch_cloner: &FetchCloner, pubkeys: &[&Pubkey], ) { fetch_cloner.promote_accounts(pubkeys); @@ -448,7 +464,7 @@ impl ))] async fn fetch_accounts_common( &self, - fetch_cloner: &FetchCloner, + fetch_cloner: &FetchCloner, pubkeys: &[Pubkey], mark_empty_if_not_found: Option<&[Pubkey]>, fetch_origin: AccountFetchOrigin, @@ -504,7 +520,7 @@ impl Ok(()) } - pub fn fetch_cloner(&self) -> Option<&Arc>> { + pub fn fetch_cloner(&self) -> Option<&ArcFetchCloner> { self.fetch_cloner.as_ref() } diff --git a/magicblock-chainlink/src/remote_account_provider/chain_laser_actor.rs b/magicblock-chainlink/src/remote_account_provider/chain_laser_actor.rs index 3a1364ed0..0ba303de1 100644 --- a/magicblock-chainlink/src/remote_account_provider/chain_laser_actor.rs +++ b/magicblock-chainlink/src/remote_account_provider/chain_laser_actor.rs @@ -120,7 +120,7 @@ pub struct ChainLaserActor { commitment: CommitmentLevel, /// Channel used to signal connection issues to the submux abort_sender: mpsc::Sender<()>, - /// Slot tracking for activation lookback + /// The current slot on chain, shared with RemoteAccountProvider /// This is only set when the gRPC provider supports backfilling subscription updates slots: Option, /// Unique client ID including the gRPC provider name for this actor instance used in logs diff --git a/magicblock-chainlink/src/remote_account_provider/chain_updates_client.rs b/magicblock-chainlink/src/remote_account_provider/chain_updates_client.rs index c40af8d9b..00460bd89 100644 --- a/magicblock-chainlink/src/remote_account_provider/chain_updates_client.rs +++ b/magicblock-chainlink/src/remote_account_provider/chain_updates_client.rs @@ -87,6 +87,11 @@ impl ChainUpdatesClient { "{endpoint:?}" ))) } + Compression { .. } => { + Err(RemoteAccountProviderError::InvalidPubsubEndpoint(format!( + "{endpoint:?}" + ))) + } } } } diff --git a/magicblock-chainlink/src/remote_account_provider/config.rs b/magicblock-chainlink/src/remote_account_provider/config.rs index 904f02b1d..588e94a8f 100644 --- a/magicblock-chainlink/src/remote_account_provider/config.rs +++ b/magicblock-chainlink/src/remote_account_provider/config.rs @@ -48,7 +48,7 @@ impl RemoteAccountProviderConfig { subscribed_accounts_lru_capacity, lifecycle_mode, enable_subscription_metrics, - resubscription_delay: std::time::Duration::from_millis( + resubscription_delay: Duration::from_millis( DEFAULT_RESUBSCRIPTION_DELAY_MS, ), ..Default::default() @@ -112,7 +112,7 @@ impl Default for RemoteAccountProviderConfig { lifecycle_mode: LifecycleMode::default(), enable_subscription_metrics: true, program_subs: vec![dlp::id()].into_iter().collect(), - resubscription_delay: std::time::Duration::from_millis( + resubscription_delay: Duration::from_millis( DEFAULT_RESUBSCRIPTION_DELAY_MS, ), } diff --git a/magicblock-chainlink/src/remote_account_provider/endpoint.rs b/magicblock-chainlink/src/remote_account_provider/endpoint.rs index e301310aa..59817c2e0 100644 --- a/magicblock-chainlink/src/remote_account_provider/endpoint.rs +++ b/magicblock-chainlink/src/remote_account_provider/endpoint.rs @@ -1,9 +1,10 @@ -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use magicblock_config::types::network::Remote; use url::Url; use super::errors::RemoteAccountProviderError; +use crate::remote_account_provider::RemoteAccountProviderResult; #[derive(Debug, Clone)] pub enum Endpoint { @@ -21,6 +22,10 @@ pub enum Endpoint { supports_backfill: bool, api_key: String, }, + Compression { + url: String, + api_key: Option, + }, } impl Endpoints { @@ -43,6 +48,19 @@ impl Endpoints { }) .collect() } + + pub fn photon(&self) -> RemoteAccountProviderResult> { + let photons = self + .iter() + .filter(|ep| matches!(ep, Endpoint::Compression { .. })) + .collect::>(); + if photons.len() > 1 { + return Err( + RemoteAccountProviderError::MultiplePhotonEndpointsProvided, + ); + } + Ok(photons.first().copied()) + } } impl TryFrom<&[Remote]> for Endpoints { @@ -121,6 +139,12 @@ impl Deref for Endpoints { } } +impl DerefMut for Endpoints { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + impl<'a> IntoIterator for &'a Endpoints { type Item = &'a Endpoint; type IntoIter = std::slice::Iter<'a, Endpoint>; diff --git a/magicblock-chainlink/src/remote_account_provider/errors.rs b/magicblock-chainlink/src/remote_account_provider/errors.rs index 12e9c88fa..50b8ebe28 100644 --- a/magicblock-chainlink/src/remote_account_provider/errors.rs +++ b/magicblock-chainlink/src/remote_account_provider/errors.rs @@ -14,6 +14,9 @@ pub enum RemoteAccountProviderError { #[error(transparent)] JoinError(#[from] tokio::task::JoinError), + #[error(transparent)] + IndexerError(#[from] light_client::indexer::IndexerError), + #[error("Receiver error: {0}")] RecvrError(#[from] tokio::sync::oneshot::error::RecvError), @@ -59,6 +62,9 @@ pub enum RemoteAccountProviderError { #[error("Accounts matched same slot ({0}), but it's less than min required context slot {2} ")] MatchingSlotsNotSatisfyingMinContextSlot(String, Vec, u64), + #[error("Failed to fetch accounts ({0})")] + FailedFetchingAccounts(String), + #[error("LRU capacity must be greater than 0")] InvalidLruCapacity, @@ -105,6 +111,9 @@ pub enum RemoteAccountProviderError { "The LoaderV4 program {0} account state deserialization failed: {1}" )] LoaderV4StateDeserializationFailed(Pubkey, String), + + #[error("Multiple photon endpoints provided")] + MultiplePhotonEndpointsProvided, } impl From diff --git a/magicblock-chainlink/src/remote_account_provider/mod.rs b/magicblock-chainlink/src/remote_account_provider/mod.rs index 584686d68..6da22c98d 100644 --- a/magicblock-chainlink/src/remote_account_provider/mod.rs +++ b/magicblock-chainlink/src/remote_account_provider/mod.rs @@ -48,6 +48,7 @@ pub mod config; pub mod endpoint; pub mod errors; mod lru_cache; +pub mod photon_client; pub mod program_account; pub mod pubsub_common; mod remote_account; @@ -59,6 +60,10 @@ use magicblock_metrics::{ metrics::{ inc_account_fetches_failed, inc_account_fetches_found, inc_account_fetches_not_found, inc_account_fetches_success, + inc_compressed_account_fetches_failed, + inc_compressed_account_fetches_found, + inc_compressed_account_fetches_not_found, + inc_compressed_account_fetches_success, inc_per_program_account_fetch_stats, set_monitored_accounts_count, AccountFetchOrigin, ProgramFetchResult, }, @@ -69,7 +74,9 @@ use crate::{ errors::ChainlinkResult, remote_account_provider::{ chain_updates_client::ChainUpdatesClient, + photon_client::{PhotonClient, PhotonClientImpl}, pubsub_common::SubscriptionUpdate, + remote_account::FetchedRemoteAccounts, }, submux::SubMuxClient, }; @@ -91,13 +98,20 @@ unsafe impl Sync for ForwardedSubscriptionUpdate {} // Not sure why helius uses a different code for this error const HELIUS_CONTEXT_SLOT_NOT_REACHED: i64 = -32603; -pub struct RemoteAccountProvider { +pub struct RemoteAccountProvider< + T: ChainRpcClient, + U: ChainPubsubClient, + P: PhotonClient, +> { /// The RPC client to fetch accounts from chain the first time we receive /// a request for them rpc_client: T, /// The pubsub client to listen for updates on chain and keep the account /// states up to date pubsub_client: U, + /// The client to fetch compressed accounts from photon the first time we receive + /// a request for them + photon_client: P, /// Minimal tracking of accounts currently being fetched to handle race conditions /// between fetch and subscription updates. Only used during active fetch operations. fetching_accounts: Arc, @@ -147,7 +161,11 @@ impl Default for MatchSlotsConfig { } impl - RemoteAccountProvider> + RemoteAccountProvider< + ChainRpcClientImpl, + SubMuxClient, + PhotonClientImpl, + > { pub async fn try_from_urls_and_config( endpoints: &Endpoints, @@ -159,6 +177,7 @@ impl RemoteAccountProvider< ChainRpcClientImpl, SubMuxClient, + PhotonClientImpl, >, >, > { @@ -169,6 +188,7 @@ impl RemoteAccountProvider::< ChainRpcClientImpl, SubMuxClient, + PhotonClientImpl, >::try_new_from_endpoints( endpoints, commitment, @@ -183,20 +203,24 @@ impl } } -impl RemoteAccountProvider { +impl + RemoteAccountProvider +{ pub async fn try_from_clients_and_mode( rpc_client: T, pubsub_client: U, + photon_client: P, subscription_forwarder: mpsc::Sender, config: &RemoteAccountProviderConfig, lrucache_subscribed_accounts: Arc, chain_slot: Arc, - ) -> ChainlinkResult>> { + ) -> ChainlinkResult>> { if config.lifecycle_mode().needs_remote_account_provider() { Ok(Some( Self::new( rpc_client, pubsub_client, + photon_client, subscription_forwarder, config, lrucache_subscribed_accounts, @@ -310,6 +334,7 @@ impl RemoteAccountProvider { pub(crate) async fn new( rpc_client: T, pubsub_client: U, + photon_client: P, subscription_forwarder: mpsc::Sender, config: &RemoteAccountProviderConfig, lrucache_subscribed_accounts: Arc, @@ -333,6 +358,7 @@ impl RemoteAccountProvider { fetching_accounts: Arc::::default(), rpc_client, pubsub_client, + photon_client, chain_slot, last_update_slot: Arc::::default(), received_updates_count: Arc::::default(), @@ -371,6 +397,7 @@ impl RemoteAccountProvider { RemoteAccountProvider< ChainRpcClientImpl, SubMuxClient, + PhotonClientImpl, >, > { if endpoints.is_empty() { @@ -420,6 +447,13 @@ impl RemoteAccountProvider { let submux = SubMuxClient::new(pubsubs, subscribed_accounts.clone(), None); + let photon_client = endpoints + .photon()? + .ok_or_else(|| { + RemoteAccountProviderError::MultiplePhotonEndpointsProvided + }) + .map(PhotonClientImpl::new_from_endpoint)??; + if !config.program_subs().is_empty() { let count = config.program_subs().len(); debug!(count, "Subscribing to program accounts"); @@ -433,9 +467,11 @@ impl RemoteAccountProvider { RemoteAccountProvider::< ChainRpcClientImpl, SubMuxClient, + PhotonClientImpl, >::new( rpc_client, submux, + photon_client, subscription_forwarder, config, subscribed_accounts, @@ -844,20 +880,16 @@ impl RemoteAccountProvider { } // 2. Inform upstream so it can remove it from the store - self.send_removal_update(evicted).await?; + self.send_removal_update(evicted).await; } Ok(()) } - async fn send_removal_update( - &self, - evicted: Pubkey, - ) -> RemoteAccountProviderResult<()> { - self.removed_account_tx.send(evicted).await.map_err( - RemoteAccountProviderError::FailedToSendAccountRemovalUpdate, - )?; - Ok(()) + async fn send_removal_update(&self, evicted: Pubkey) { + if let Err(err) = self.removed_account_tx.send(evicted).await { + warn!("Failed to send removal update for {evicted}: {err:?}"); + } } /// Check if an account is currently being watched (subscribed to) @@ -947,16 +979,167 @@ impl RemoteAccountProvider { fetch_origin: AccountFetchOrigin, program_ids: Option<&[Pubkey]>, ) { - const MAX_RETRIES: u64 = 10; - const RPC_CALL_TIMEOUT: Duration = Duration::from_secs(2); - let rpc_client = self.rpc_client.clone(); + let photon_client = self.photon_client.clone(); let fetching_accounts = self.fetching_accounts.clone(); - let commitment = self.rpc_client.commitment(); + let pubkeys = Arc::new(pubkeys); let mark_empty_if_not_found = mark_empty_if_not_found.unwrap_or(&[]).to_vec(); let program_ids = program_ids.map(|ids| ids.to_vec()); tokio::spawn(async move { + // Fetch accounts from RPC + // If any are owned by the compressed delegation program then we also fetch from Photon with retries + let (rpc_accounts, photon_accounts) = tokio::join!( + Self::fetch_from_rpc( + rpc_client, + pubkeys.clone(), + fetching_accounts.clone(), + mark_empty_if_not_found, + min_context_slot, + program_ids.clone(), + ), + Self::fetch_from_photon( + photon_client, + pubkeys.clone(), + min_context_slot, + ) + ); + debug!(rpc_accounts = ?rpc_accounts, photon_accounts = ?photon_accounts, "Fetched accounts from RPC and Photon"); + + let mut remote_accounts_results = Vec::with_capacity(2); + let mut found_cnt = 0; + let mut not_found_cnt = 0; + let mut compressed_found_count = 0; + let mut compressed_not_found_count = 0; + + let results = vec![rpc_accounts, photon_accounts]; + + for result in results { + match result { + Ok((FetchedRemoteAccounts::Rpc(accs), fc, nfc)) => { + remote_accounts_results + .push(FetchedRemoteAccounts::Rpc(accs)); + found_cnt += fc; + not_found_cnt += nfc; + } + Ok((FetchedRemoteAccounts::Compressed(accs), fc, nfc)) => { + remote_accounts_results + .push(FetchedRemoteAccounts::Compressed(accs)); + compressed_found_count += fc; + compressed_not_found_count += nfc; + } + Err(err) => { + error!("Failed to fetch accounts: {err:?}"); + } + } + } + let remote_accounts = Self::consolidate_fetched_remote_accounts( + &pubkeys, + remote_accounts_results, + ); + + // Update metrics for successful RPC fetch + inc_account_fetches_success(pubkeys.len() as u64); + inc_account_fetches_found(fetch_origin, found_cnt); + inc_account_fetches_not_found(fetch_origin, not_found_cnt); + + let compressed_total = + compressed_found_count + compressed_not_found_count; + if compressed_total > 0 { + // Update metrics for successful compressed fetch + inc_compressed_account_fetches_success(pubkeys.len() as u64); + inc_compressed_account_fetches_found( + fetch_origin, + compressed_found_count, + ); + inc_compressed_account_fetches_not_found( + fetch_origin, + compressed_not_found_count, + ); + } + + // Record per-program metrics if programs were provided + if let Some(program_ids) = &program_ids { + for program_id in program_ids { + if found_cnt > 0 { + inc_per_program_account_fetch_stats( + &program_id.to_string(), + ProgramFetchResult::Found, + found_cnt, + ); + } + if not_found_cnt > 0 { + inc_per_program_account_fetch_stats( + &program_id.to_string(), + ProgramFetchResult::NotFound, + not_found_cnt, + ); + } + } + } + + if tracing::enabled!(tracing::Level::TRACE) { + trace!( + pubkeys = pubkeys_str(&pubkeys), + remote_accounts = %format!("{:?}", remote_accounts), + "Fetched accounts, notifying pending requests", + ); + } + + // Notify all pending requests with fetch results (unless subscription override occurred) + for (pubkey, remote_account) in + pubkeys.iter().zip(remote_accounts.iter()) + { + let requests = { + let mut fetching = match fetching_accounts.lock() { + Ok(guard) => guard, + Err(poisoned) => { + error!( + "fetching_accounts lock poisoned; continuing with inner state: {poisoned:?}" + ); + poisoned.into_inner() + } + }; + // Remove from fetching and get pending requests + // Note: the account might have been resolved by subscription update already + if let Some((_, requests)) = fetching.remove(pubkey) { + requests + } else { + // Account was resolved by subscription update, skip + if tracing::enabled!(tracing::Level::TRACE) { + trace!( + pubkey = %pubkey, + "Account was already resolved by subscription update" + ); + } + continue; + } + }; + + // Send the fetch result to all waiting requests + for request in requests { + let _ = request.send(Ok(remote_account.clone())); + } + } + Ok::<(), RemoteAccountProviderError>(()) + }); + } + + async fn fetch_from_rpc( + rpc_client: T, + pubkeys: Arc>, + fetching_accounts: Arc, + mark_empty_if_not_found: Vec, + min_context_slot: u64, + program_ids: Option>, + ) -> RemoteAccountProviderResult<(FetchedRemoteAccounts, u64, u64)> { + const MAX_RETRIES: u64 = 10; + const RPC_CALL_TIMEOUT: Duration = Duration::from_secs(2); + + let rpc_client = rpc_client.clone(); + let commitment = rpc_client.commitment(); + let pubkeys = pubkeys.clone(); + let (remote_accounts, found_count, not_found_count) = tokio::spawn(async move { use RemoteAccount::*; // Helper to notify all pending requests of fetch failure @@ -974,7 +1157,7 @@ impl RemoteAccountProvider { } } - for pubkey in &pubkeys { + for pubkey in &*pubkeys { // Update metrics // Remove pending requests and send error if let Some((_, requests)) = fetching.remove(pubkey) { @@ -1001,7 +1184,7 @@ impl RemoteAccountProvider { if remaining_retries <= 0 { let err_msg = format!("Max retries {MAX_RETRIES} reached, giving up on fetching accounts: {pubkeys:?}"); notify_error(&err_msg); - return; + return Err(RemoteAccountProviderError::FailedFetchingAccounts(err_msg)); } tokio::time::sleep(Duration::from_millis(400)).await; continue; @@ -1072,7 +1255,7 @@ impl RemoteAccountProvider { "RpcError fetching accounts {}: {err:?}", pubkeys_str(&pubkeys) ); notify_error(&err_msg); - return; + return Err(RemoteAccountProviderError::FailedFetchingAccounts(err_msg)); } } err => { @@ -1080,7 +1263,7 @@ impl RemoteAccountProvider { "RpcError fetching accounts {}: {err:?}", pubkeys_str(&pubkeys) ); notify_error(&err_msg); - return; + return Err(RemoteAccountProviderError::FailedFetchingAccounts(err_msg)); } } } @@ -1090,7 +1273,7 @@ impl RemoteAccountProvider { pubkeys_str(&pubkeys) ); notify_error(&err_msg); - return; + return Err(RemoteAccountProviderError::FailedFetchingAccounts(err_msg)); } }, Err(_) => { @@ -1099,7 +1282,7 @@ impl RemoteAccountProvider { if remaining_retries == 0 { let err_msg = format!("Max retries {MAX_RETRIES} reached, giving up on fetching accounts: {pubkeys:?}"); notify_error(&err_msg); - return; + return Err(RemoteAccountProviderError::FailedFetchingAccounts(err_msg)); } tokio::time::sleep(Duration::from_millis(400)).await; continue; @@ -1108,12 +1291,18 @@ impl RemoteAccountProvider { }; // TODO: should we retry if not or respond with an error? - assert!(response.context.slot >= min_context_slot); + if response.context.slot < min_context_slot { + let err_msg = format!( + "slot {} < min_context_slot {min_context_slot}", response.context.slot + ); + notify_error(&err_msg); + return Err(RemoteAccountProviderError::FailedFetchingAccounts(err_msg)); + } let mut found_count = 0u64; let mut not_found_count = 0u64; - let remote_accounts: Vec = pubkeys + Ok((pubkeys .iter() .zip(response.value) .map(|(pubkey, acc)| match acc { @@ -1144,75 +1333,159 @@ impl RemoteAccountProvider { NotFound(response.context.slot) } }) - .collect(); + .collect(), found_count, not_found_count)) + }).await??; + + Ok(( + FetchedRemoteAccounts::Rpc(remote_accounts), + found_count, + not_found_count, + )) + } - // Update metrics for successful RPC fetch - inc_account_fetches_success(pubkeys.len() as u64); - inc_account_fetches_found(fetch_origin, found_count); - inc_account_fetches_not_found(fetch_origin, not_found_count); + async fn fetch_from_photon( + photon_client: P, + pubkeys: Arc>, + min_context_slot: u64, + ) -> RemoteAccountProviderResult<(FetchedRemoteAccounts, u64, u64)> { + let (compressed_accounts, slot) = photon_client + .get_multiple_accounts(&pubkeys, Some(min_context_slot)) + .await + .inspect_err(|err| { + error!("Error fetching compressed accounts: {err:?}"); + inc_compressed_account_fetches_failed(pubkeys.len() as u64); + })?; - // Record per-program metrics if programs were provided - if let Some(program_ids) = &program_ids { - for program_id in program_ids { - if found_count > 0 { - inc_per_program_account_fetch_stats( - &program_id.to_string(), - ProgramFetchResult::Found, - found_count, - ); + if tracing::enabled!(tracing::Level::TRACE) { + let pks_accs = compressed_accounts + .iter() + .zip(&*pubkeys) + .map(|(acc, pk)| format!("{}: {:?}", pk, acc)) + .collect::>() + .join(", "); + trace!(accs = %pks_accs, "Fetched compressed accounts"); + } + + let mut found_count = 0u64; + let mut not_found_count = 0u64; + let remote_accounts = compressed_accounts + .into_iter() + .map(|acc_opt| match acc_opt { + Some(acc) => { + found_count += 1; + RemoteAccount::from_fresh_account( + acc, + slot, + RemoteAccountUpdateSource::Compressed, + ) + } + None => { + not_found_count += 1; + RemoteAccount::NotFound(slot) + } + }) + .collect::>(); + Ok(( + FetchedRemoteAccounts::Compressed(remote_accounts), + found_count, + not_found_count, + )) + } + + fn consolidate_fetched_remote_accounts( + pubkeys: &[Pubkey], + remote_accounts_results: Vec, + ) -> Vec { + const STALE_SLOT_THRESHOLD: u64 = 100; + let (rpc_accounts, compressed_accounts) = { + if remote_accounts_results.is_empty() { + return vec![]; + } + if remote_accounts_results.len() == 1 { + match &remote_accounts_results[0] { + FetchedRemoteAccounts::Rpc(rpc_accounts) => { + return rpc_accounts.clone(); } - if not_found_count > 0 { - inc_per_program_account_fetch_stats( - &program_id.to_string(), - ProgramFetchResult::NotFound, - not_found_count, - ); + FetchedRemoteAccounts::Compressed(compressed_accounts) => { + return compressed_accounts.clone(); } } } - - if tracing::enabled!(tracing::Level::TRACE) { - let pubkeys = pubkeys - .iter() - .map(|pk| pk.to_string()) - .collect::>() - .join(", "); - trace!( - pubkeys = %pubkeys, remote_accounts = ?remote_accounts, "Fetched, notifying pending requests" - ); - } - - // Notify all pending requests with fetch results (unless subscription override occurred) - for (pubkey, remote_account) in - pubkeys.iter().zip(remote_accounts.iter()) - { - let requests = { - let mut fetching = fetching_accounts.lock().unwrap(); - // Remove from fetching and get pending requests - // Note: the account might have been resolved by subscription update already - if let Some((_, requests)) = fetching.remove(pubkey) { - requests - } else { - // Account was resolved by subscription update, skip - if tracing::enabled!(tracing::Level::TRACE) { - trace!( - "Account {pubkey} was already resolved by subscription update" - ); + if remote_accounts_results.len() == 2 { + let mut rpc_accounts = None; + let mut compressed_accounts = None; + for res in remote_accounts_results { + match res { + FetchedRemoteAccounts::Rpc(rpc_accs) => { + rpc_accounts.replace(rpc_accs); + } + FetchedRemoteAccounts::Compressed(comp_accs) => { + compressed_accounts.replace(comp_accs); } - continue; } - }; - - // Send the fetch result to all waiting requests - for request in requests { - let _ = request.send(Ok(remote_account.clone())); } + (rpc_accounts.unwrap_or_default(), compressed_accounts) + } else { + error!("BUG: More than 2 fetch results found"); + return vec![]; } - }); + }; + + debug_assert_eq!(rpc_accounts.len(), pubkeys.len()); + debug_assert!(compressed_accounts + .as_ref() + .is_none_or(|comp_accs| comp_accs.len() == pubkeys.len())); + + let all_lens_match = pubkeys.len() == rpc_accounts.len() + && pubkeys.len() + == compressed_accounts + .as_ref() + .map_or(rpc_accounts.len(), |comp_accs| comp_accs.len()); + if !all_lens_match { + error!("BUG: Fetched accounts length mismatch: pubkeys {}, rpc {}, compressed {:?}", + pubkeys.len(), rpc_accounts.len(), + compressed_accounts.as_ref().map(|c| c.len())); + return rpc_accounts; + } + + use RemoteAccount::*; + match compressed_accounts { + Some(compressed_accounts) => + pubkeys.iter().zip( + rpc_accounts + .into_iter() + .zip(compressed_accounts)) + .map(|(pubkey, (rpc_acc, comp_acc))| match (rpc_acc, comp_acc) { + (Found(rpc_state), Found(comp_state)) => { + info!("Both RPC and Compressed account found for pubkey {}. Using Compressed account.", pubkey); + if rpc_state.account.slot() > comp_state.account.slot() + STALE_SLOT_THRESHOLD { + warn!("Compressed account is stale. rpc_slot={}, comp_slot={}", rpc_state.account.slot(), comp_state.account.slot()); + } + Found(comp_state) + } + (Found(rpc_state), NotFound(_)) => Found(rpc_state), + (NotFound(_), Found(comp_state)) => Found(comp_state), + (NotFound(rpc_slot), NotFound(comp_slot)) => { + if rpc_slot >= comp_slot { + NotFound(rpc_slot) + } else { + NotFound(comp_slot) + } + } + }) + .collect(), + None => rpc_accounts, + } } } -impl RemoteAccountProvider { +impl + RemoteAccountProvider< + ChainRpcClientImpl, + ChainPubsubClientImpl, + PhotonClientImpl, + > +{ #[cfg(any(test, feature = "dev-context"))] pub fn rpc_client(&self) -> &RpcClient { &self.rpc_client.rpc_client @@ -1223,6 +1496,7 @@ impl RemoteAccountProvider< ChainRpcClientImpl, SubMuxClient, + PhotonClientImpl, > { #[cfg(any(test, feature = "dev-context"))] @@ -1232,7 +1506,11 @@ impl } impl - RemoteAccountProvider> + RemoteAccountProvider< + ChainRpcClientImpl, + SubMuxClient, + PhotonClientImpl, + > { #[cfg(any(test, feature = "dev-context"))] pub fn rpc_client(&self) -> &RpcClient { @@ -1300,6 +1578,7 @@ mod test { }; use crate::testing::{ init_logger, + photon_client_mock::PhotonClientMock, rpc_client_mock::{ AccountAtSlot, ChainRpcClientMock, ChainRpcClientMockBuilder, }, @@ -1324,6 +1603,7 @@ mod test { RemoteAccountProvider::new( rpc_client, pubsub_client, + PhotonClientMock::new(), fwd_tx, &config, subscribed_accounts, @@ -1376,6 +1656,7 @@ mod test { RemoteAccountProvider::new( rpc_client.clone(), pubsub_client, + PhotonClientMock::new(), fwd_tx, &config, subscribed_accounts, @@ -1415,7 +1696,11 @@ mod test { pubkey1: Pubkey, pubkey2: Pubkey, ) -> ( - RemoteAccountProvider, + RemoteAccountProvider< + ChainRpcClientMock, + ChainPubsubClientMock, + PhotonClientMock, + >, mpsc::Receiver, ) { init_logger(); @@ -1456,6 +1741,7 @@ mod test { RemoteAccountProvider::new( rpc_client, pubsub_client, + PhotonClientMock::new(), forward_tx, &config, subscribed_accounts, @@ -1628,7 +1914,11 @@ mod test { pubkeys: &[Pubkey], accounts_capacity: usize, ) -> ( - RemoteAccountProvider, + RemoteAccountProvider< + ChainRpcClientMock, + ChainPubsubClientMock, + PhotonClientMock, + >, mpsc::Receiver, mpsc::Receiver, ) { @@ -1661,6 +1951,7 @@ mod test { let provider = RemoteAccountProvider::new( rpc_client, pubsub_client, + PhotonClientMock::new(), forward_tx, &config, subscribed_accounts, @@ -1957,4 +2248,232 @@ mod test { assert_eq!(removed.len(), 1); assert!(removed.contains(&stale_pubkey)); } + + // ----------------- + // Compressed Accounts + // ----------------- + async fn setup_with_compressed_accounts( + pubkeys: &[Pubkey], + compressed_pubkeys: &[Pubkey], + accounts_capacity: usize, + ) -> ( + RemoteAccountProvider< + ChainRpcClientMock, + ChainPubsubClientMock, + PhotonClientMock, + >, + mpsc::Receiver, + mpsc::Receiver, + ) { + let rpc_client = { + let mut rpc_client_builder = + ChainRpcClientMockBuilder::new().slot(1); + for (idx, pubkey) in pubkeys.iter().enumerate() { + rpc_client_builder = rpc_client_builder.account( + *pubkey, + Account { + lamports: 555, + data: vec![5; idx + 1], + owner: if compressed_pubkeys.get(idx).is_some() { + compressed_delegation_client::id() + } else { + system_program::id() + }, + executable: false, + rent_epoch: 0, + }, + ); + } + rpc_client_builder.build() + }; + + let photon_client = PhotonClientMock::default(); + for (idx, pubkey) in compressed_pubkeys.iter().enumerate() { + photon_client + .add_account( + *pubkey, + Account { + lamports: 777, + data: vec![7; idx + 1], + owner: system_program::id(), + executable: false, + rent_epoch: 0, + }, + 1, + ) + .await; + } + + let (tx, rx) = mpsc::channel(1); + let pubsub_client = ChainPubsubClientMock::new(tx, rx); + + let (forward_tx, forward_rx) = mpsc::channel(100); + let (subscribed_accounts, config) = + create_test_lru_cache(accounts_capacity); + let chain_slot = Arc::new(AtomicU64::default()); + + let provider = RemoteAccountProvider::new( + rpc_client, + pubsub_client, + photon_client, + forward_tx, + &config, + subscribed_accounts, + chain_slot, + ) + .await + .unwrap(); + + let removed_account_tx = provider.try_get_removed_account_rx().unwrap(); + (provider, forward_rx, removed_account_tx) + } + + macro_rules! assert_compressed_account { + ($acc:expr, $expected_lamports:expr, $expected_data_len:expr) => { + assert!($acc.is_found()); + assert_eq!( + $acc.source(), + Some(RemoteAccountUpdateSource::Compressed) + ); + assert_eq!($acc.fresh_lamports(), Some($expected_lamports)); + assert_eq!($acc.fresh_data_len(), Some($expected_data_len)); + }; + } + + macro_rules! assert_regular_account { + ($acc:expr, $expected_lamports:expr, $expected_data_len:expr) => { + assert!($acc.is_found()); + assert_eq!($acc.source(), Some(RemoteAccountUpdateSource::Fetch)); + assert_eq!($acc.fresh_lamports(), Some($expected_lamports)); + assert_eq!($acc.fresh_data_len(), Some($expected_data_len)); + }; + } + + // TODO(dode): Compressed accounts currently cannot exists with a corresponding RPC account. + #[ignore] + #[tokio::test] + async fn test_multiple_photon_accounts() { + init_logger(); + + let [cpk1, cpk2, cpk3] = [ + Pubkey::new_unique(), + Pubkey::new_unique(), + Pubkey::new_unique(), + ]; + let compressed_pubkeys = &[cpk1, cpk2, cpk3]; + + let (remote_account_provider, _, _) = + setup_with_compressed_accounts(&[], compressed_pubkeys, 3).await; + let accs = remote_account_provider + .try_get_multi_until_slots_match( + compressed_pubkeys, + Some(MatchSlotsConfig { + max_retries: 10, + retry_interval_ms: 50, + min_context_slot: Some(1), + }), + AccountFetchOrigin::GetAccount, + ) + .await + .unwrap(); + let [acc1, acc2, acc3] = accs.as_slice() else { + panic!("Expected 3 accounts"); + }; + assert_compressed_account!(acc1, 777, 1); + assert_compressed_account!(acc2, 777, 2); + assert_compressed_account!(acc3, 777, 3); + + let acc2 = remote_account_provider + .try_get(cpk2, AccountFetchOrigin::GetAccount) + .await + .unwrap(); + assert_compressed_account!(acc2, 777, 2); + } + + #[tokio::test] + async fn test_multiple_compressed_accounts() { + init_logger(); + let [pk1, pk2, pk3] = [ + Pubkey::new_unique(), + Pubkey::new_unique(), + Pubkey::new_unique(), + ]; + let pubkeys = &[pk1, pk2, pk3]; + let compressed_pubkeys = &pubkeys.clone(); + + let (remote_account_provider, _, _) = + setup_with_compressed_accounts(pubkeys, compressed_pubkeys, 3) + .await; + + let accs = remote_account_provider + .try_get_multi_until_slots_match( + pubkeys, + Some(MatchSlotsConfig { + max_retries: 10, + retry_interval_ms: 50, + min_context_slot: Some(1), + }), + AccountFetchOrigin::GetAccount, + ) + .await + .unwrap(); + let [acc1, acc2, acc3] = accs.as_slice() else { + panic!("Expected 3 accounts"); + }; + assert_compressed_account!(acc1, 777, 1); + assert_compressed_account!(acc2, 777, 2); + assert_compressed_account!(acc3, 777, 3); + + let cacc2 = remote_account_provider + .try_get(pk2, AccountFetchOrigin::GetAccount) + .await + .unwrap(); + assert_compressed_account!(cacc2, 777, 2); + } + + #[tokio::test] + async fn test_multiple_compressed_accounts_some_missing() { + init_logger(); + let [pk1, pk2, pk3] = [ + Pubkey::new_unique(), + Pubkey::new_unique(), + Pubkey::new_unique(), + ]; + let pubkeys = &[pk1, pk2, pk3]; + let compressed_pubkeys = &pubkeys[..2]; + + let (remote_account_provider, _, _) = + setup_with_compressed_accounts(pubkeys, compressed_pubkeys, 3) + .await; + + let accs = remote_account_provider + .try_get_multi_until_slots_match( + pubkeys, + Some(MatchSlotsConfig { + max_retries: 10, + retry_interval_ms: 50, + min_context_slot: Some(1), + }), + AccountFetchOrigin::GetAccount, + ) + .await + .unwrap(); + let [acc1, acc2, acc3] = accs.as_slice() else { + panic!("Expected 3 accounts"); + }; + assert_compressed_account!(acc1, 777, 1); + assert_compressed_account!(acc2, 777, 2); + assert_regular_account!(acc3, 555, 3); + + let cacc2 = remote_account_provider + .try_get(pk2, AccountFetchOrigin::GetAccount) + .await + .unwrap(); + assert_compressed_account!(cacc2, 777, 2); + let cacc3 = remote_account_provider + .try_get(pk3, AccountFetchOrigin::GetAccount) + .await + .unwrap(); + assert_regular_account!(cacc3, 555, 3); + } } diff --git a/magicblock-chainlink/src/remote_account_provider/photon_client.rs b/magicblock-chainlink/src/remote_account_provider/photon_client.rs new file mode 100644 index 000000000..fd1222302 --- /dev/null +++ b/magicblock-chainlink/src/remote_account_provider/photon_client.rs @@ -0,0 +1,161 @@ +use std::{ops::Deref, sync::Arc}; + +use async_trait::async_trait; +use light_client::indexer::{ + photon_indexer::PhotonIndexer, CompressedAccount, Context, Indexer, + IndexerError, IndexerRpcConfig, Response, +}; +use magicblock_core::compression::derive_cda_from_pda; +use solana_account::Account; +use solana_clock::Slot; +use solana_pubkey::Pubkey; +use tracing::*; + +use crate::remote_account_provider::{ + Endpoint, RemoteAccountProviderError, RemoteAccountProviderResult, +}; + +#[derive(Clone)] +pub struct PhotonClientImpl(Arc); + +impl Deref for PhotonClientImpl { + type Target = Arc; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PhotonClientImpl { + pub(crate) fn new(photon_indexer: Arc) -> Self { + Self(photon_indexer) + } + pub(crate) fn new_from_endpoint( + endpoint: &Endpoint, + ) -> RemoteAccountProviderResult { + let Endpoint::Compression { url, api_key } = endpoint else { + return Err( + RemoteAccountProviderError::AccountSubscriptionsTaskFailed( + format!( + "Endpoint is not a compression endpoint: {:?}", + endpoint + ), + ), + ); + }; + debug!(url = %url, "Creating PhotonClient"); + Ok(Self::new(Arc::new(PhotonIndexer::new( + url.to_string(), + api_key.clone(), + )))) + } +} + +#[async_trait] +pub trait PhotonClient: Send + Sync + Clone + 'static { + async fn get_account( + &self, + pubkey: &Pubkey, + min_context_slot: Option, + ) -> RemoteAccountProviderResult>; + + async fn get_multiple_accounts( + &self, + pubkeys: &[Pubkey], + min_context_slot: Option, + ) -> RemoteAccountProviderResult<(Vec>, Slot)>; +} + +#[async_trait] +impl PhotonClient for PhotonClientImpl { + async fn get_account( + &self, + pubkey: &Pubkey, + min_context_slot: Option, + ) -> RemoteAccountProviderResult> { + let config = min_context_slot.map(|slot| IndexerRpcConfig { + slot, + ..Default::default() + }); + let cda = derive_cda_from_pda(pubkey); + let Response { + value: compressed_acc, + context: Context { slot, .. }, + } = match self.get_compressed_account(cda.to_bytes(), config).await { + Ok(res) => res, + // NOTE: @@@ this is broken, we actually are getting a `None` value + // when the account is not found + // Light released a fix for this but we can't integrate it yet. + // https://github.com/magicblock-labs/magicblock-validator/issues/869 + Err(IndexerError::AccountNotFound) => { + return Ok(None); + } + Err(err) => { + return Err(err.into()); + } + }; + let account = account_from_compressed_account(Some(compressed_acc)); + Ok(account.map(|acc| (acc, slot))) + } + + async fn get_multiple_accounts( + &self, + pubkeys: &[Pubkey], + min_context_slot: Option, + ) -> RemoteAccountProviderResult<(Vec>, Slot)> { + let config = min_context_slot.map(|slot| IndexerRpcConfig { + slot, + ..Default::default() + }); + let cdas: Vec<_> = pubkeys + .iter() + .map(|pk| derive_cda_from_pda(pk).to_bytes()) + .collect(); + + if tracing::enabled!(tracing::Level::DEBUG) { + let pks_cdas = pubkeys + .iter() + .zip(cdas.iter()) + .map(|(pk, cda)| { + format!("({}: {})", pk, Pubkey::new_from_array(*cda)) + }) + .collect::>() + .join(", "); + debug!(cdas = %pks_cdas, "Fetching multiple accounts"); + } + + let Response { + value: compressed_accs, + context: Context { slot, .. }, + } = self + .get_multiple_compressed_accounts(Some(cdas), None, config) + .await?; + + let accounts = compressed_accs + .items + .into_iter() + .map(account_from_compressed_account) + // NOTE: the light-client API is incorrect currently. + // The server will return `None` for missing accounts, + .collect(); + Ok((accounts, slot)) + } +} + +// ----------------- +// Helpers +// ----------------- + +fn account_from_compressed_account( + compressed_acc: Option, +) -> Option { + let compressed_acc = compressed_acc?; + // NOTE: delegated compressed accounts are set to zero lamports when cloned + // Actual lamports have to be paid back when undelegating + Some(Account { + lamports: 0, + data: compressed_acc.data.unwrap_or_default().data, + owner: compressed_acc.owner, + executable: false, + rent_epoch: 0, + }) +} diff --git a/magicblock-chainlink/src/remote_account_provider/remote_account.rs b/magicblock-chainlink/src/remote_account_provider/remote_account.rs index 20f130868..62f06c8ec 100644 --- a/magicblock-chainlink/src/remote_account_provider/remote_account.rs +++ b/magicblock-chainlink/src/remote_account_provider/remote_account.rs @@ -8,6 +8,7 @@ use solana_pubkey::Pubkey; #[derive(Debug, Clone, PartialEq, Eq)] pub enum RemoteAccountUpdateSource { Fetch, + Compressed, Subscription, } @@ -41,6 +42,13 @@ impl ResolvedAccount { .map(ResolvedAccountSharedData::Bank), } } + + pub fn slot(&self) -> Slot { + match self { + ResolvedAccount::Fresh(account) => account.remote_slot(), + ResolvedAccount::Bank((_, slot)) => *slot, + } + } } /// Same as [ResolvedAccount], but with the account data fetched from the bank. @@ -166,6 +174,10 @@ impl ResolvedAccountSharedData { Bank(account) => account.remote_slot(), } } + + pub fn compressed(&self) -> bool { + self.account_shared_data().compressed() + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -188,6 +200,10 @@ impl RemoteAccount { ) -> Self { let mut account_shared_data = AccountSharedData::from(account); account_shared_data.set_remote_slot(slot); + account_shared_data.set_compressed(matches!( + source, + RemoteAccountUpdateSource::Compressed + )); RemoteAccount::Found(RemoteAccountState { account: ResolvedAccount::Fresh(account_shared_data), source, @@ -243,12 +259,12 @@ impl RemoteAccount { !matches!(self, RemoteAccount::NotFound(_)) } - pub fn fresh_account(&self) -> Option { + pub fn fresh_account(&self) -> Option<&AccountSharedData> { match self { RemoteAccount::Found(RemoteAccountState { account: ResolvedAccount::Fresh(account), .. - }) => Some(account.clone()), + }) => Some(account), _ => None, } } @@ -257,6 +273,10 @@ impl RemoteAccount { self.fresh_account().map(|acc| acc.lamports()) } + pub fn fresh_data_len(&self) -> Option { + self.fresh_account().map(|acc| acc.data().len()) + } + pub fn owner(&self) -> Option { self.fresh_account().map(|acc| *acc.owner()) } @@ -264,4 +284,15 @@ impl RemoteAccount { pub fn is_owned_by_delegation_program(&self) -> bool { self.owner().is_some_and(|owner| owner.eq(&dlp::id())) } + + pub fn is_owned_by_compressed_delegation_program(&self) -> bool { + self.owner() + .is_some_and(|owner| owner.eq(&compressed_delegation_client::id())) + } +} + +#[derive(Clone, Debug)] +pub enum FetchedRemoteAccounts { + Rpc(Vec), + Compressed(Vec), } diff --git a/magicblock-chainlink/src/testing/mod.rs b/magicblock-chainlink/src/testing/mod.rs index 29ecc776c..cc957da31 100644 --- a/magicblock-chainlink/src/testing/mod.rs +++ b/magicblock-chainlink/src/testing/mod.rs @@ -9,6 +9,8 @@ pub mod deleg; #[cfg(any(test, feature = "dev-context"))] pub mod eatas; #[cfg(any(test, feature = "dev-context"))] +pub mod photon_client_mock; +#[cfg(any(test, feature = "dev-context"))] pub mod rpc_client_mock; #[cfg(any(test, feature = "dev-context"))] pub mod utils; diff --git a/magicblock-chainlink/src/testing/photon_client_mock.rs b/magicblock-chainlink/src/testing/photon_client_mock.rs new file mode 100644 index 000000000..bdfeb01fe --- /dev/null +++ b/magicblock-chainlink/src/testing/photon_client_mock.rs @@ -0,0 +1,83 @@ +use std::{collections::HashMap, sync::Arc}; + +use async_trait::async_trait; +use magicblock_core::compression::derive_cda_from_pda; +use solana_account::Account; +use solana_clock::Slot; +use solana_pubkey::Pubkey; +use tokio::sync::RwLock; + +use crate::{ + remote_account_provider::{ + photon_client::PhotonClient, RemoteAccountProviderResult, + }, + testing::rpc_client_mock::AccountAtSlot, +}; + +#[derive(Clone, Default)] +pub struct PhotonClientMock { + accounts: Arc>>, +} + +impl PhotonClientMock { + pub fn new() -> Self { + Self { + accounts: Arc::new(RwLock::new(HashMap::new())), + } + } + + pub async fn add_account( + &self, + pubkey: Pubkey, + account: Account, + slot: Slot, + ) { + let cda = derive_cda_from_pda(&pubkey); + let mut accounts = self.accounts.write().await; + accounts.insert(cda, AccountAtSlot { account, slot }); + } +} + +#[async_trait] +impl PhotonClient for PhotonClientMock { + async fn get_account( + &self, + pubkey: &Pubkey, + min_context_slot: Option, + ) -> RemoteAccountProviderResult> { + let cda = derive_cda_from_pda(pubkey); + if let Some(account_at_slot) = self.accounts.read().await.get(&cda) { + if let Some(min_slot) = min_context_slot { + if account_at_slot.slot < min_slot { + return Ok(None); + } + } + return Ok(Some(( + account_at_slot.account.clone(), + account_at_slot.slot, + ))); + } + Ok(None) + } + + async fn get_multiple_accounts( + &self, + pubkeys: &[Pubkey], + min_context_slot: Option, + ) -> RemoteAccountProviderResult<(Vec>, Slot)> { + let mut accs = Vec::with_capacity(pubkeys.len()); + // Seed with min_context_slot if present to better approximate the + // "context slot" semantics of the real Photon client. + let mut slot = min_context_slot.unwrap_or(0); + for pubkey in pubkeys { + let account = self.get_account(pubkey, min_context_slot).await?; + if let Some((ref _acc, acc_slot)) = account { + if acc_slot > slot { + slot = acc_slot; + } + } + accs.push(account.map(|(acc, _)| acc)); + } + Ok((accs, slot)) + } +} diff --git a/magicblock-chainlink/src/testing/utils.rs b/magicblock-chainlink/src/testing/utils.rs index 4f1bc4503..5efb26083 100644 --- a/magicblock-chainlink/src/testing/utils.rs +++ b/magicblock-chainlink/src/testing/utils.rs @@ -18,6 +18,7 @@ use crate::{ pub const PUBSUB_URL: &str = "ws://localhost:7800"; pub const RPC_URL: &str = "http://localhost:7799"; +pub const PHOTON_URL: &str = "http://localhost:8784"; pub fn random_pubkey() -> Pubkey { Keypair::new().pubkey() diff --git a/magicblock-chainlink/tests/01_ensure-accounts.rs b/magicblock-chainlink/tests/01_ensure-accounts.rs index bb072408c..4728c4628 100644 --- a/magicblock-chainlink/tests/01_ensure-accounts.rs +++ b/magicblock-chainlink/tests/01_ensure-accounts.rs @@ -16,6 +16,8 @@ use utils::test_context::TestContext; mod utils; use magicblock_chainlink::testing::init_logger; + +use crate::utils::accounts::compressed_account_shared_with_owner_and_slot; const CURRENT_SLOT: u64 = 11; async fn setup(slot: Slot) -> TestContext { @@ -53,7 +55,7 @@ async fn test_write_non_existing_account() { } // ----------------- -// BasicScenarios:Case 1 Account is initialized and never delegated +// BasicScenarios: Case 1 Account is initialized and never delegated // ----------------- #[tokio::test] async fn test_existing_account_undelegated() { @@ -357,3 +359,136 @@ async fn test_write_existing_account_invalid_delegation_record() { assert_not_subscribed!(chainlink, &[&deleg_record_pubkey, &pubkey]); } + +// ----------------- +// Compressed delegation record is initialized and delegated to us +// ----------------- +#[tokio::test] +async fn test_compressed_delegation_record_delegated() { + let TestContext { + chainlink, + photon_client, + rpc_client, + cloner, + validator_pubkey, + .. + } = setup(CURRENT_SLOT).await; + + let pubkey = Pubkey::new_unique(); + let owner = Pubkey::new_unique(); + let compressed_account = compressed_account_shared_with_owner_and_slot( + pubkey, + validator_pubkey, + owner, + CURRENT_SLOT, + ); + photon_client + .add_account(pubkey, compressed_account.clone().into(), CURRENT_SLOT) + .await; + rpc_client.add_account( + pubkey, + Account { + owner: compressed_delegation_client::id(), + ..Account::default() + }, + ); + + let pubkeys = [pubkey]; + let res = chainlink + .ensure_accounts( + &pubkeys, + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + debug!("res: {res:?}"); + + assert_cloned_as_delegated!(cloner, &pubkeys, CURRENT_SLOT, owner); + assert_not_subscribed!(chainlink, &[&pubkey]); +} + +// ----------------- +// Compressed delegation record is initialized and delegated to another authority +// ----------------- +#[tokio::test] +async fn test_compressed_delegation_record_delegated_to_other() { + let TestContext { + chainlink, + photon_client, + rpc_client, + cloner, + .. + } = setup(CURRENT_SLOT).await; + + let pubkey = Pubkey::new_unique(); + let authority = Pubkey::new_unique(); + let owner = Pubkey::new_unique(); + let compressed_account = compressed_account_shared_with_owner_and_slot( + pubkey, + authority, + owner, + CURRENT_SLOT, + ); + photon_client + .add_account(pubkey, compressed_account.clone().into(), CURRENT_SLOT) + .await; + rpc_client.add_account( + pubkey, + Account { + owner: compressed_delegation_client::id(), + ..Account::default() + }, + ); + + let pubkeys = [pubkey]; + let res = chainlink + .ensure_accounts( + &pubkeys, + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + debug!("res: {res:?}"); + + assert_cloned_as_undelegated!(cloner, &pubkeys, CURRENT_SLOT, owner); + assert_subscribed_without_delegation_record!(chainlink, &[&pubkey]); +} + +// ----------------- +// Compressed delegation record is initialized and empty (undelegated) +// ----------------- +#[tokio::test] +async fn test_compressed_account_undelegated() { + let TestContext { + chainlink, + photon_client, + rpc_client, + cloner, + .. + } = setup(CURRENT_SLOT).await; + + let pubkey = Pubkey::new_unique(); + rpc_client.add_account(pubkey, Account::default()); + photon_client + .add_account(pubkey, Account::default(), CURRENT_SLOT) + .await; + + let pubkeys = [pubkey]; + let res = chainlink + .ensure_accounts( + &pubkeys, + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + debug!("res: {res:?}"); + + assert_cloned_as_undelegated!(cloner, &pubkeys, CURRENT_SLOT); + assert_subscribed_without_delegation_record!(chainlink, &[&pubkey]); +} diff --git a/magicblock-chainlink/tests/03_deleg_after_sub.rs b/magicblock-chainlink/tests/03_deleg_after_sub.rs index 6312e6c29..4c9a595c4 100644 --- a/magicblock-chainlink/tests/03_deleg_after_sub.rs +++ b/magicblock-chainlink/tests/03_deleg_after_sub.rs @@ -13,6 +13,8 @@ use utils::{ accounts::account_shared_with_owner_and_slot, test_context::TestContext, }; +use crate::utils::accounts::compressed_account_shared_with_owner_and_slot; + mod utils; // Implements the following flow: @@ -119,3 +121,118 @@ async fn test_deleg_after_subscribe_case2() { assert_not_subscribed!(&chainlink, &[&pubkey, &delegation_record]); } } + +// NOTE: Flow "Account created then fetched, then delegated" +#[tokio::test] +async fn test_deleg_after_subscribe_case2_compressed() { + let mut slot: u64 = 11; + + let ctx = setup(slot).await; + let TestContext { + chainlink, + cloner, + rpc_client, + photon_client, + validator_pubkey, + .. + } = ctx.clone(); + + let pubkey = Pubkey::new_unique(); + let program_pubkey = Pubkey::new_unique(); + let acc = Account { + lamports: 1_000, + owner: program_pubkey, + ..Default::default() + }; + + // 1. Initially the account does not exist + // - readable: OK (non existing account) + // - writable: NO + { + info!("1. Initially the account does not exist"); + assert_not_cloned!(cloner, &[pubkey]); + + chainlink + .ensure_accounts( + &[pubkey], + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + assert_not_cloned!(cloner, &[pubkey]); + } + + // 2. Account created with original owner + // + // Now we can ensure it as readonly and it will be cloned + // - readable: OK + // - writable: NO + { + info!("2. Create account owned by program {program_pubkey}"); + + slot = rpc_client.set_slot(slot + 11); + let acc = + account_shared_with_owner_and_slot(&acc, program_pubkey, slot); + + // When the account is created we do not receive any update since we do not sub to a non-existing account + let updated = ctx + .send_and_receive_account_update(pubkey, acc.clone(), Some(400)) + .await; + assert!(!updated); + + chainlink + .ensure_accounts( + &[pubkey], + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + assert_cloned_as_undelegated!(cloner, &[pubkey], slot, program_pubkey); + assert_subscribed_without_delegation_record!(&chainlink, &[&pubkey]); + } + + // 3. Account delegated to us + // + // Delegate account to us and the sub update should be received + // even before the ensure_writable request + { + info!("3. Delegate account {pubkey} to us"); + + slot = rpc_client.set_slot(slot + 11); + let compressed_account = compressed_account_shared_with_owner_and_slot( + pubkey, + validator_pubkey, + program_pubkey, + slot, + ); + photon_client + .add_account(pubkey, compressed_account.clone().into(), slot) + .await; + let updated = ctx + .send_and_receive_account_update( + pubkey, + compressed_account.clone(), + Some(400), + ) + .await; + + // Needs to ensure accounts for compressed accounts + chainlink + .ensure_accounts( + &[pubkey], + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + + assert!(updated); + assert_cloned_as_delegated!(cloner, &[pubkey], slot, program_pubkey); + assert_not_subscribed!(&chainlink, &[&pubkey]); + } +} diff --git a/magicblock-chainlink/tests/04_redeleg_other_separate_slots.rs b/magicblock-chainlink/tests/04_redeleg_other_separate_slots.rs index 25f8a5333..06c0c7885 100644 --- a/magicblock-chainlink/tests/04_redeleg_other_separate_slots.rs +++ b/magicblock-chainlink/tests/04_redeleg_other_separate_slots.rs @@ -18,6 +18,8 @@ use utils::{ test_context::{DelegateResult, TestContext}, }; +use crate::utils::accounts::compressed_account_shared_with_owner_and_slot; + mod utils; async fn setup(slot: Slot) -> TestContext { @@ -127,3 +129,124 @@ async fn test_undelegate_redelegate_to_other_in_separate_slot() { assert_subscribed_without_delegation_record!(&chainlink, &[&pubkey]); } } + +#[tokio::test] +async fn test_undelegate_redelegate_to_other_in_separate_slot_compressed() { + let mut slot: u64 = 11; + + let ctx = setup(slot).await; + let TestContext { + chainlink, + cloner, + rpc_client, + photon_client, + validator_pubkey, + .. + } = ctx.clone(); + + let pubkey = Pubkey::new_unique(); + let program_pubkey = Pubkey::new_unique(); + let other_authority = Pubkey::new_unique(); + let acc = Account::default(); + + // 1. Account delegated to us + // Initial state: Account is delegated to us and we can read/write to it + { + info!("1. Account delegated to us"); + + slot = rpc_client.set_slot(slot + 11); + let mut compressed_account = + compressed_account_shared_with_owner_and_slot( + pubkey, + validator_pubkey, + program_pubkey, + slot, + ); + compressed_account.set_remote_slot(slot); + rpc_client.add_account( + pubkey, + Account { + owner: compressed_delegation_client::id(), + ..acc + }, + ); + photon_client + .add_account(pubkey, compressed_account.into(), slot) + .await; + + // Transaction to read + // Fetch account - see it's owned by DP, fetch compressed account, clone account as delegated + ctx.ensure_account(&pubkey).await.unwrap(); + assert_cloned_as_delegated!(cloner, &[pubkey], slot, program_pubkey); + assert_not_subscribed!(&chainlink, &[&pubkey]); + }; + + // 2. Account is undelegated + // Undelegation requested, setup subscription, writes refused + { + info!("2.1. Account is undelegated - Undelegation requested (account owner set to DP in Ephem)"); + + ctx.force_undelegation(&pubkey); + + info!("2.2. Would refuse write (account still owned by DP in Ephem)"); + ctx.ensure_account(&pubkey).await.unwrap(); + assert_remain_undelegating!(cloner, &[pubkey], slot); + + slot = rpc_client.set_slot(slot + 11); + + info!("2.3. Account is undelegated on chain"); + let undelegated_acc = ctx + .commit_and_undelegate(&pubkey, &program_pubkey) + .await + .unwrap(); + + // Account should be cloned as undelegated + assert_eq!(cloner.get_account(&pubkey).unwrap(), undelegated_acc); + + info!("2.4. Would refuse write (undelegated on chain)"); + ctx.ensure_account(&pubkey).await.unwrap(); + assert_cloned_as_undelegated!(cloner, &[pubkey], slot, program_pubkey); + assert_subscribed_without_delegation_record!(&chainlink, &[&pubkey]); + } + + // 4. Account redelegated to another authority + // Delegate to other, subscription update, writes refused + { + info!("4.1. Account redelegated to another authority - Delegate account to other"); + slot = rpc_client.set_slot(slot + 2); + + // Create compressed delegation record + let compressed_account = compressed_account_shared_with_owner_and_slot( + pubkey, + other_authority, + program_pubkey, + slot, + ); + photon_client + .add_account(pubkey, compressed_account.clone().into(), slot) + .await; + + let acc = rpc_client.get_account_at_slot(&pubkey).unwrap(); + let updated = ctx + .send_and_receive_account_update( + pubkey, + acc.account.clone(), + Some(400), + ) + .await; + assert!(updated, "Failed to receive delegation update"); + + // Account should remain owned by DP but delegated to other authority + let acc_redeleg_expected = account_shared_with_owner_and_slot( + &acc.account, + program_pubkey, + slot, + ); + assert_eq!(cloner.get_account(&pubkey).unwrap(), acc_redeleg_expected); + + info!("4.2. Would refuse write (delegated to other)"); + ctx.ensure_account(&pubkey).await.unwrap(); + assert_cloned_as_undelegated!(cloner, &[pubkey], slot, program_pubkey); + assert_subscribed_without_delegation_record!(&chainlink, &[&pubkey]); + } +} diff --git a/magicblock-chainlink/tests/05_redeleg_other_same_slot.rs b/magicblock-chainlink/tests/05_redeleg_other_same_slot.rs index db16b318b..d00564571 100644 --- a/magicblock-chainlink/tests/05_redeleg_other_same_slot.rs +++ b/magicblock-chainlink/tests/05_redeleg_other_same_slot.rs @@ -14,7 +14,11 @@ use solana_program::clock::Slot; use solana_pubkey::Pubkey; use tracing::*; use utils::{ - accounts::account_shared_with_owner_and_slot, test_context::TestContext, + accounts::{ + account_shared_with_owner_and_slot, + compressed_account_shared_with_owner_and_slot, + }, + test_context::TestContext, }; mod utils; @@ -99,3 +103,101 @@ async fn test_undelegate_redelegate_to_other_in_same_slot() { assert_subscribed_without_delegation_record!(&chainlink, &[&pubkey]); } } + +#[tokio::test] +async fn test_undelegate_redelegate_to_other_in_same_slot_compressed() { + let mut slot: u64 = 11; + + let ctx = setup(slot).await; + let TestContext { + chainlink, + cloner, + rpc_client, + photon_client, + .. + } = ctx.clone(); + + let pubkey = Pubkey::new_unique(); + let program_pubkey = Pubkey::new_unique(); + let other_authority = Pubkey::new_unique(); + let acc = Account::default(); // Compressed accounts don't need lamports in the RPC mock + + // 1. Account delegated to us + // Initial state: Account is delegated to us and we can read/write to it + { + info!("1. Account delegated to us"); + + slot = rpc_client.set_slot(slot + 11); + let compressed_account = compressed_account_shared_with_owner_and_slot( + pubkey, + ctx.validator_pubkey, + program_pubkey, + slot, + ); + rpc_client.add_account( + pubkey, + Account { + owner: compressed_delegation_client::id(), + ..acc + }, + ); + photon_client + .add_account(pubkey, compressed_account.clone().into(), slot) + .await; + + // Transaction to read/write would be ok + // Fetch account - see it's owned by DP, fetch delegation record, clone account as delegated + ctx.ensure_account(&pubkey).await.unwrap(); + assert_cloned_as_delegated!(cloner, &[pubkey], slot, program_pubkey); + assert_not_subscribed!(&chainlink, &[&pubkey]); + }; + + // 2. Account is undelegated and redelegated to another authority (same slot) + // Undelegation requested, setup subscription, writes refused + { + info!("2.1. Account is undelegated - Undelegation requested (account owner set to DP in Ephem)"); + + ctx.force_undelegation(&pubkey); + + info!("2.2. Would refuse write (account still owned by DP in Ephem)"); + assert_remain_undelegating!(cloner, &[pubkey], slot); + + slot = rpc_client.set_slot(slot + 1); + + info!("2.3. Account is undelegated and redelegated to other authority in same slot"); + + // First trigger undelegation subscription + ctx.chainlink.undelegation_requested(pubkey).await.unwrap(); + + // Then immediately delegate to other authority (simulating same slot operation) + let compressed_account = compressed_account_shared_with_owner_and_slot( + pubkey, + other_authority, + program_pubkey, + slot, + ); + photon_client + .add_account(pubkey, compressed_account.clone().into(), slot) + .await; + // Update account to be delegated on chain and send a sub update + let acc = rpc_client.get_account_at_slot(&pubkey).unwrap(); + let delegated_acc = account_shared_with_owner_and_slot( + &acc.account, + compressed_delegation_client::id(), + slot, + ); + let updated = ctx + .send_and_receive_account_update( + pubkey, + delegated_acc.clone(), + Some(400), + ) + .await; + assert!(updated, "Failed to receive delegation update"); + + // Account should be cloned as delegated to other (flagged as undelegated) + info!("2.4. Would refuse write (delegated to other)"); + assert_cloned_as_undelegated!(cloner, &[pubkey], slot, program_pubkey); + assert_subscribed_without_delegation_record!(&chainlink, &[&pubkey]); + } +} diff --git a/magicblock-chainlink/tests/06_redeleg_us_separate_slots.rs b/magicblock-chainlink/tests/06_redeleg_us_separate_slots.rs index 0397f1059..a7ff88e61 100644 --- a/magicblock-chainlink/tests/06_redeleg_us_separate_slots.rs +++ b/magicblock-chainlink/tests/06_redeleg_us_separate_slots.rs @@ -8,6 +8,7 @@ use magicblock_chainlink::{ assert_not_subscribed, assert_remain_undelegating, assert_subscribed_without_delegation_record, testing::{deleg::add_delegation_record_for, init_logger}, + AccountFetchOrigin, }; use solana_account::Account; use solana_program::clock::Slot; @@ -17,6 +18,8 @@ use utils::{ accounts::account_shared_with_owner_and_slot, test_context::TestContext, }; +use crate::utils::accounts::compressed_account_shared_with_owner_and_slot; + mod utils; async fn setup(slot: Slot) -> TestContext { @@ -113,3 +116,141 @@ async fn test_undelegate_redelegate_to_us_in_separate_slots() { assert_not_subscribed!(chainlink, &[&pubkey, &deleg_record_pubkey]); } } + +#[tokio::test] +async fn test_undelegate_redelegate_to_us_in_separate_slots_compressed() { + let mut slot: u64 = 11; + + let ctx = setup(slot).await; + let TestContext { + chainlink, + cloner, + rpc_client, + photon_client, + .. + } = ctx.clone(); + + let pubkey = Pubkey::new_unique(); + let program_pubkey = Pubkey::new_unique(); + let acc = Account { + lamports: 1_000, + ..Default::default() + }; + + // 1. Account delegated to us + // Initial state: Account is delegated to us and we can read/write to it + { + info!("1. Account delegated to us"); + + slot = rpc_client.set_slot(slot + 11); + let compressed_account = compressed_account_shared_with_owner_and_slot( + pubkey, + ctx.validator_pubkey, + program_pubkey, + slot, + ); + photon_client + .add_account(pubkey, compressed_account.clone().into(), slot) + .await; + + let delegated_acc = account_shared_with_owner_and_slot( + &acc, + compressed_delegation_client::id(), + slot, + ); + rpc_client.add_account(pubkey, delegated_acc.clone().into()); + + // Fetch account - see it's owned by DP, fetch delegation record, clone account as delegated + ctx.ensure_account(&pubkey).await.unwrap(); + assert_cloned_as_delegated!(cloner, &[pubkey], slot, program_pubkey); + assert_not_subscribed!(&chainlink, &[&pubkey]); + }; + + // 2. Account is undelegated + // Undelegation requested, setup subscription, writes would be refused + { + info!("2.1. Account is undelegated - Undelegation requested (account owner set to DP in Ephem)"); + + ctx.force_undelegation(&pubkey); + + info!("2.2. Would refuse write (account still owned by DP in Ephem)"); + assert_remain_undelegating!(cloner, &[pubkey], slot); + + slot = rpc_client.set_slot(slot + 11); + + info!("2.3. Account is undelegated on chain"); + // Committor service calls this to trigger subscription + chainlink.undelegation_requested(pubkey).await.unwrap(); + + // Committor service then requests undelegation on chain + let acc = rpc_client.get_account_at_slot(&pubkey).unwrap(); + let undelegated_acc = account_shared_with_owner_and_slot( + &acc.account, + program_pubkey, + slot, + ); + photon_client + .add_account(pubkey, Account::default(), slot) + .await; + let updated = ctx + .send_and_receive_account_update( + pubkey, + undelegated_acc.clone(), + Some(400), + ) + .await; + assert!(updated, "Failed to receive undelegation update"); + + // Account should be cloned as undelegated + info!("2.4. Write would be refused (undelegated on chain)"); + assert_cloned_as_undelegated!(cloner, &[pubkey], slot, program_pubkey); + assert_subscribed_without_delegation_record!(&chainlink, &[&pubkey]); + } + + // 3. Account redelegated to us (separate slot) + // Delegate back to us, subscription update, writes allowed + { + info!("3.1. Account redelegated to us - Delegate account back to us"); + slot = rpc_client.set_slot(slot + 11); + + let compressed_account = compressed_account_shared_with_owner_and_slot( + pubkey, + ctx.validator_pubkey, + program_pubkey, + slot, + ); + photon_client + .add_account(pubkey, compressed_account.clone().into(), slot) + .await; + let delegated_acc = account_shared_with_owner_and_slot( + &Account::default(), + compressed_delegation_client::id(), + slot, + ); + let updated = ctx + .send_and_receive_account_update( + pubkey, + delegated_acc.clone(), + Some(400), + ) + .await; + assert!(updated, "Failed to receive delegation update"); + + chainlink + .ensure_accounts( + &[pubkey], + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + + // Account should be cloned as delegated back to us + info!("3.2. Would allow write (delegated to us again)"); + assert_cloned_as_delegated!(cloner, &[pubkey], slot, program_pubkey); + + // Account is delegated to us, so we don't subscribe to it nor its delegation record + assert_not_subscribed!(chainlink, &[&pubkey]); + } +} diff --git a/magicblock-chainlink/tests/07_redeleg_us_same_slot.rs b/magicblock-chainlink/tests/07_redeleg_us_same_slot.rs index 7c91ec534..11e0d5f77 100644 --- a/magicblock-chainlink/tests/07_redeleg_us_same_slot.rs +++ b/magicblock-chainlink/tests/07_redeleg_us_same_slot.rs @@ -7,13 +7,19 @@ use magicblock_chainlink::{ assert_cloned_as_delegated, assert_not_subscribed, assert_remain_undelegating, testing::{deleg::add_delegation_record_for, init_logger}, + AccountFetchOrigin, }; use solana_account::Account; use solana_program::clock::Slot; use solana_pubkey::Pubkey; use tracing::*; -use utils::{ - accounts::account_shared_with_owner_and_slot, test_context::TestContext, + +use crate::utils::{ + accounts::{ + account_shared_with_owner_and_slot, + compressed_account_shared_with_owner_and_slot, + }, + test_context::TestContext, }; mod utils; @@ -108,3 +114,116 @@ async fn test_undelegate_redelegate_to_us_in_same_slot() { assert_not_subscribed!(chainlink, &[&pubkey, &deleg_record_pubkey]); } } + +#[tokio::test] +async fn test_undelegate_redelegate_to_us_in_same_slot_compressed() { + let mut slot: u64 = 11; + + let ctx = setup(slot).await; + let TestContext { + chainlink, + cloner, + rpc_client, + photon_client, + .. + } = ctx.clone(); + + let pubkey = Pubkey::new_unique(); + let program_pubkey = Pubkey::new_unique(); + let acc = Account { + lamports: 1_000, + ..Default::default() + }; + + // 1. Account delegated to us + // Initial state: Account is delegated to us and we can read/write to it + { + info!("1. Account delegated to us"); + + slot = rpc_client.set_slot(slot + 11); + let delegated_acc = account_shared_with_owner_and_slot( + &acc, + compressed_delegation_client::id(), + slot, + ); + rpc_client.add_account(pubkey, delegated_acc.clone().into()); + + let compressed_account = compressed_account_shared_with_owner_and_slot( + pubkey, + ctx.validator_pubkey, + program_pubkey, + slot, + ); + photon_client + .add_account(pubkey, compressed_account.clone().into(), slot) + .await; + + // Transaction to read + // Fetch account - see it's owned by DP, fetch delegation record, clone account as delegated + ctx.ensure_account(&pubkey).await.unwrap(); + assert_cloned_as_delegated!(cloner, &[pubkey], slot, program_pubkey); + assert_not_subscribed!(&chainlink, &[&pubkey]); + } + + // 2. Account is undelegated and redelegated to us (same slot) + // Undelegation requested, setup subscription, writes refused until redelegation + { + info!("2.1. Account is undelegated - Undelegation requested (account owner set to DP in Ephem)"); + + ctx.force_undelegation(&pubkey); + + info!("2.2. Would refuse write (account still owned by DP in Ephem)"); + assert_remain_undelegating!(cloner, &[pubkey], slot); + + slot = rpc_client.set_slot(slot + 1); + + info!("2.3. Account is undelegated and redelegated to us in same slot"); + + // First trigger undelegation subscription + ctx.chainlink.undelegation_requested(pubkey).await.unwrap(); + + // Update account to be delegated on chain and send a sub update + let compressed_account = compressed_account_shared_with_owner_and_slot( + pubkey, + ctx.validator_pubkey, + program_pubkey, + slot, + ); + photon_client + .add_account(pubkey, compressed_account.clone().into(), slot) + .await; + + let acc = rpc_client.get_account_at_slot(&pubkey).unwrap(); + let delegated_acc = account_shared_with_owner_and_slot( + &acc.account, + compressed_delegation_client::id(), + slot, + ); + let updated = ctx + .send_and_receive_account_update( + pubkey, + delegated_acc.clone(), + Some(400), + ) + .await; + assert!(updated, "Failed to receive delegation update"); + + // Then immediately delegate back to us (simulating same slot operation) + chainlink + .ensure_accounts( + &[pubkey], + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + + // Account should be cloned as delegated back to us + info!("2.4. Would allow write (delegated to us again)"); + assert_cloned_as_delegated!(cloner, &[pubkey], slot, program_pubkey); + + // Account is delegated to us, so we don't subscribe to it nor its delegation record + assert_not_subscribed!(chainlink, &[&pubkey]); + } +} diff --git a/magicblock-chainlink/tests/utils/accounts.rs b/magicblock-chainlink/tests/utils/accounts.rs index 5b167626c..cf5b0e3ed 100644 --- a/magicblock-chainlink/tests/utils/accounts.rs +++ b/magicblock-chainlink/tests/utils/accounts.rs @@ -1,4 +1,6 @@ #![allow(dead_code)] +use borsh::BorshSerialize; +use compressed_delegation_client::CompressedDelegationRecord; use magicblock_chainlink::testing::accounts::account_shared_with_owner; use solana_account::{Account, AccountSharedData}; use solana_instruction::{AccountMeta, Instruction}; @@ -16,6 +18,35 @@ pub fn account_shared_with_owner_and_slot( acc } +pub fn compressed_account_shared_with_owner_and_slot( + pda: Pubkey, + authority: Pubkey, + owner: Pubkey, + slot: u64, +) -> AccountSharedData { + let delegation_record_bytes = CompressedDelegationRecord { + pda, + authority, + last_update_nonce: 0, + is_undelegatable: false, + owner, + delegation_slot: slot, + lamports: 1000, + data: vec![], + } + .try_to_vec() + .unwrap(); + let mut acc = Account::new( + 0, + delegation_record_bytes.len(), + &compressed_delegation_client::ID, + ); + acc.data = delegation_record_bytes; + let mut acc = AccountSharedData::from(acc); + acc.set_remote_slot(slot); + acc +} + #[derive(Debug, Clone)] pub struct TransactionAccounts { pub readonly_accounts: Vec, diff --git a/magicblock-chainlink/tests/utils/test_context.rs b/magicblock-chainlink/tests/utils/test_context.rs index 206d9efe9..35c1d8793 100644 --- a/magicblock-chainlink/tests/utils/test_context.rs +++ b/magicblock-chainlink/tests/utils/test_context.rs @@ -17,6 +17,7 @@ use magicblock_chainlink::{ accounts::account_shared_with_owner, cloner_stub::ClonerStub, deleg::add_delegation_record_for, + photon_client_mock::PhotonClientMock, rpc_client_mock::{ChainRpcClientMock, ChainRpcClientMockBuilder}, utils::{create_test_lru_cache, create_test_lru_cache_with_config}, }, @@ -35,16 +36,24 @@ pub type TestChainlink = Chainlink< ChainPubsubClientMock, AccountsBankStub, ClonerStub, + PhotonClientMock, >; #[derive(Clone)] pub struct TestContext { pub rpc_client: ChainRpcClientMock, pub pubsub_client: ChainPubsubClientMock, + pub photon_client: PhotonClientMock, pub chainlink: Arc, pub bank: Arc, pub remote_account_provider: Option< - Arc>, + Arc< + RemoteAccountProvider< + ChainRpcClientMock, + ChainPubsubClientMock, + PhotonClientMock, + >, + >, >, pub cloner: Arc, pub validator_pubkey: Pubkey, @@ -52,13 +61,14 @@ pub struct TestContext { impl TestContext { pub async fn init(slot: Slot) -> Self { - let (rpc_client, pubsub_client) = { + let (rpc_client, pubsub_client, photon_client) = { let rpc_client = ChainRpcClientMockBuilder::new().slot(slot).build(); let (updates_sndr, updates_rcvr) = mpsc::channel(100); let pubsub_client = ChainPubsubClientMock::new(updates_sndr, updates_rcvr); - (rpc_client, pubsub_client) + let photon_client = PhotonClientMock::new(); + (rpc_client, pubsub_client, photon_client) }; let lifecycle_mode = LifecycleMode::Ephemeral; @@ -79,6 +89,7 @@ impl TestContext { RemoteAccountProvider::try_from_clients_and_mode( rpc_client.clone(), pubsub_client.clone(), + photon_client.clone(), tx, &config, subscribed_accounts, @@ -120,6 +131,7 @@ impl TestContext { Self { rpc_client, pubsub_client, + photon_client, chainlink: Arc::new(chainlink), bank, cloner, diff --git a/magicblock-committor-program/bin/magicblock_committor_program.so b/magicblock-committor-program/bin/magicblock_committor_program.so index 8e6eece38..0f98c9cd1 100755 Binary files a/magicblock-committor-program/bin/magicblock_committor_program.so and b/magicblock-committor-program/bin/magicblock_committor_program.so differ diff --git a/magicblock-committor-program/src/instruction_builder/close_buffer.rs b/magicblock-committor-program/src/instruction_builder/close_buffer.rs index 840bfb9fc..3e858ab8d 100644 --- a/magicblock-committor-program/src/instruction_builder/close_buffer.rs +++ b/magicblock-committor-program/src/instruction_builder/close_buffer.rs @@ -1,7 +1,10 @@ use solana_program::instruction::{AccountMeta, Instruction}; use solana_pubkey::Pubkey; -use crate::{instruction::CommittorInstruction, pdas}; +use crate::{ + instruction::CommittorInstruction, instruction_builder::build_instruction, + pdas, +}; // ----------------- // create_close_ix @@ -29,7 +32,6 @@ pub fn create_close_ix(args: CreateCloseIxArgs) -> Instruction { commit_id.to_le_bytes().as_slice(), ); - let program_id = crate::id(); let ix = CommittorInstruction::Close { pubkey, commit_id, @@ -41,5 +43,5 @@ pub fn create_close_ix(args: CreateCloseIxArgs) -> Instruction { AccountMeta::new(chunks_pda, false), AccountMeta::new(buffer_pda, false), ]; - Instruction::new_with_borsh(program_id, &ix, accounts) + build_instruction(ix, accounts) } diff --git a/magicblock-committor-program/src/instruction_builder/init_buffer.rs b/magicblock-committor-program/src/instruction_builder/init_buffer.rs index 04c5a1c08..4fe28abf7 100644 --- a/magicblock-committor-program/src/instruction_builder/init_buffer.rs +++ b/magicblock-committor-program/src/instruction_builder/init_buffer.rs @@ -4,7 +4,10 @@ use solana_program::{ }; use solana_pubkey::Pubkey; -use crate::{instruction::CommittorInstruction, pdas}; +use crate::{ + instruction::CommittorInstruction, instruction_builder::build_instruction, + pdas, +}; // ----------------- // create_init_ix @@ -48,7 +51,6 @@ pub fn create_init_ix(args: CreateInitIxArgs) -> (Instruction, Pubkey, Pubkey) { &pubkey, commit_id.to_le_bytes().as_slice(), ); - let program_id = crate::id(); let ix = CommittorInstruction::Init { pubkey, commit_id, @@ -65,9 +67,5 @@ pub fn create_init_ix(args: CreateInitIxArgs) -> (Instruction, Pubkey, Pubkey) { AccountMeta::new(buffer_pda, false), AccountMeta::new_readonly(system_program::id(), false), ]; - ( - Instruction::new_with_borsh(program_id, &ix, accounts), - chunks_pda, - buffer_pda, - ) + (build_instruction(ix, accounts), chunks_pda, buffer_pda) } diff --git a/magicblock-committor-program/src/instruction_builder/mod.rs b/magicblock-committor-program/src/instruction_builder/mod.rs index ec76233ec..29814df86 100644 --- a/magicblock-committor-program/src/instruction_builder/mod.rs +++ b/magicblock-committor-program/src/instruction_builder/mod.rs @@ -1,4 +1,21 @@ +use borsh::BorshSerialize; +use solana_program::instruction::{AccountMeta, Instruction}; + +use crate::instruction::CommittorInstruction; + pub mod close_buffer; pub mod init_buffer; pub mod realloc_buffer; pub mod write_buffer; + +fn build_instruction( + ix: CommittorInstruction, + accounts: Vec, +) -> Instruction { + Instruction::new_with_bytes( + crate::id(), + &ix.try_to_vec() + .expect("Serialization of instruction should never fail"), + accounts, + ) +} diff --git a/magicblock-committor-program/src/instruction_builder/realloc_buffer.rs b/magicblock-committor-program/src/instruction_builder/realloc_buffer.rs index d5529b7ea..f38286765 100644 --- a/magicblock-committor-program/src/instruction_builder/realloc_buffer.rs +++ b/magicblock-committor-program/src/instruction_builder/realloc_buffer.rs @@ -3,7 +3,8 @@ use solana_pubkey::Pubkey; use crate::{ consts::MAX_ACCOUNT_ALLOC_PER_INSTRUCTION_SIZE, - instruction::CommittorInstruction, pdas, + instruction::CommittorInstruction, instruction_builder::build_instruction, + pdas, }; // ----------------- @@ -70,7 +71,6 @@ fn create_realloc_buffer_ix( commit_id.to_le_bytes().as_slice(), ); - let program_id = crate::id(); let ix = CommittorInstruction::ReallocBuffer { pubkey, buffer_account_size, @@ -82,5 +82,5 @@ fn create_realloc_buffer_ix( AccountMeta::new(authority, true), AccountMeta::new(buffer_pda, false), ]; - Instruction::new_with_borsh(program_id, &ix, accounts) + build_instruction(ix, accounts) } diff --git a/magicblock-committor-program/src/instruction_builder/write_buffer.rs b/magicblock-committor-program/src/instruction_builder/write_buffer.rs index 9cbba078c..b6d3ad6e8 100644 --- a/magicblock-committor-program/src/instruction_builder/write_buffer.rs +++ b/magicblock-committor-program/src/instruction_builder/write_buffer.rs @@ -1,7 +1,10 @@ use solana_program::instruction::{AccountMeta, Instruction}; use solana_pubkey::Pubkey; -use crate::{instruction::CommittorInstruction, pdas}; +use crate::{ + instruction::CommittorInstruction, instruction_builder::build_instruction, + pdas, +}; // ----------------- // create_write_ix @@ -33,7 +36,6 @@ pub fn create_write_ix(args: CreateWriteIxArgs) -> Instruction { commit_id.to_le_bytes().as_slice(), ); - let program_id = crate::id(); let ix = CommittorInstruction::Write { pubkey, commit_id, @@ -47,5 +49,5 @@ pub fn create_write_ix(args: CreateWriteIxArgs) -> Instruction { AccountMeta::new(chunks_pda, false), AccountMeta::new(buffer_pda, false), ]; - Instruction::new_with_borsh(program_id, &ix, accounts) + build_instruction(ix, accounts) } diff --git a/magicblock-committor-program/src/processor.rs b/magicblock-committor-program/src/processor.rs index bc3e6637b..bbb97968b 100644 --- a/magicblock-committor-program/src/processor.rs +++ b/magicblock-committor-program/src/processor.rs @@ -1,4 +1,4 @@ -use borsh::{to_vec, BorshDeserialize}; +use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, log::sol_log_64, msg, program::invoke_signed, program_error::ProgramError, system_instruction, @@ -191,7 +191,7 @@ fn process_init( chunks_account_info .data .borrow_mut() - .copy_from_slice(&to_vec(&chunks)?); + .copy_from_slice(&chunks.try_to_vec()?); Ok(()) } @@ -337,7 +337,7 @@ fn process_write( chunks .set_offset_delivered(offset as usize) .map_err(CommittorError::from)?; - chunks_data.copy_from_slice(&to_vec(&chunks)?); + chunks_data.copy_from_slice(&chunks.try_to_vec()?); Ok(()) } diff --git a/magicblock-committor-program/src/state/changeset_chunks.rs b/magicblock-committor-program/src/state/changeset_chunks.rs index b03c6427f..000dc44a8 100644 --- a/magicblock-committor-program/src/state/changeset_chunks.rs +++ b/magicblock-committor-program/src/state/changeset_chunks.rs @@ -15,7 +15,7 @@ pub struct ChangesetChunk { pub offset: u32, pub data_chunk: Vec, // chunk size can never exceed the ix max size which is well below u16::MAX (65_535) - #[borsh(skip)] + #[borsh_skip] chunk_size: u16, } diff --git a/magicblock-committor-program/src/state/chunks.rs b/magicblock-committor-program/src/state/chunks.rs index 0fa245d85..9f6f11c8e 100644 --- a/magicblock-committor-program/src/state/chunks.rs +++ b/magicblock-committor-program/src/state/chunks.rs @@ -61,7 +61,7 @@ impl Chunks { /// Returns how many bytes [`Chunks`] will occupy certain count pub fn struct_size(count: usize) -> usize { // bits: Vec, - Self::count_to_bitfield_bytes(count) + 4 + Self::count_to_bitfield_bytes(count) // count: usize, + std::mem::size_of::() // chunk_size: u16, diff --git a/magicblock-committor-service/Cargo.toml b/magicblock-committor-service/Cargo.toml index 4aa583f8d..3ba686055 100644 --- a/magicblock-committor-service/Cargo.toml +++ b/magicblock-committor-service/Cargo.toml @@ -15,10 +15,15 @@ async-trait = { workspace = true } base64 = { workspace = true } bincode = { workspace = true } borsh = { workspace = true } +compressed-delegation-client = { workspace = true } dyn-clone = { workspace = true } futures-util = { workspace = true } +light-client = { workspace = true, features = ["v2"] } +light-sdk = { workspace = true, features = ["v2"] } tracing = { workspace = true } lru = { workspace = true } +magicblock-core = { workspace = true } +magicblock-config = { workspace = true } magicblock-committor-program = { workspace = true, features = [ "no-entrypoint", ] } diff --git a/magicblock-committor-service/src/committor_processor.rs b/magicblock-committor-service/src/committor_processor.rs index 67ad56aba..8e5f464d3 100644 --- a/magicblock-committor-service/src/committor_processor.rs +++ b/magicblock-committor-service/src/committor_processor.rs @@ -1,5 +1,6 @@ use std::{collections::HashSet, path::Path, sync::Arc}; +use light_client::indexer::photon_indexer::PhotonIndexer; use magicblock_rpc_client::MagicblockRpcClient; use magicblock_table_mania::{GarbageCollectorConfig, TableMania}; use solana_keypair::Keypair; @@ -35,6 +36,7 @@ impl CommittorProcessor { authority: Keypair, persist_file: P, chain_config: ChainConfig, + photon_client: Arc, ) -> CommittorServiceResult where P: AsRef, @@ -60,6 +62,7 @@ impl CommittorProcessor { // Create commit scheduler let commits_scheduler = IntentExecutionManager::new( magic_block_rpc_client.clone(), + photon_client.clone(), DummyDB::new(), Some(persister.clone()), table_mania.clone(), diff --git a/magicblock-committor-service/src/intent_execution_manager.rs b/magicblock-committor-service/src/intent_execution_manager.rs index 5c85354ed..3ea4df6a0 100644 --- a/magicblock-committor-service/src/intent_execution_manager.rs +++ b/magicblock-committor-service/src/intent_execution_manager.rs @@ -5,6 +5,7 @@ pub mod intent_scheduler; use std::sync::Arc; pub use intent_execution_engine::BroadcastedIntentExecutionResult; +use light_client::indexer::photon_indexer::PhotonIndexer; use magicblock_rpc_client::MagicblockRpcClient; use magicblock_table_mania::TableMania; use tokio::sync::{broadcast, mpsc, mpsc::error::TrySendError}; @@ -32,6 +33,7 @@ pub struct IntentExecutionManager { impl IntentExecutionManager { pub fn new( rpc_client: MagicblockRpcClient, + photon_client: Arc, db: D, intent_persister: Option

, table_mania: TableMania, @@ -39,8 +41,10 @@ impl IntentExecutionManager { ) -> Self { let db = Arc::new(db); - let commit_id_tracker = - Arc::new(CacheTaskInfoFetcher::new(rpc_client.clone())); + let commit_id_tracker = Arc::new(CacheTaskInfoFetcher::new( + rpc_client.clone(), + photon_client, + )); let executor_factory = IntentExecutorFactoryImpl { rpc_client, table_mania, diff --git a/magicblock-committor-service/src/intent_executor/mod.rs b/magicblock-committor-service/src/intent_executor/mod.rs index 3d366506b..f2f828b38 100644 --- a/magicblock-committor-service/src/intent_executor/mod.rs +++ b/magicblock-committor-service/src/intent_executor/mod.rs @@ -307,7 +307,7 @@ where /// Handles out of sync commit id error, fixes current strategy /// Returns strategy to be cleaned up - /// TODO(edwin): TransactionStrategy -> CleanuoStrategy or something, naming it confusing for something that is cleaned up + /// TODO(edwin): TransactionStrategy -> CleanupStrategy or something, naming it confusing for something that is cleaned up async fn handle_commit_id_error( &self, committed_pubkeys: &[Pubkey], @@ -338,11 +338,24 @@ where // We re-fetch them to fix out of sync tasks self.task_info_fetcher .reset(ResetType::Specific(committed_pubkeys)); - let commit_ids = self - .task_info_fetcher - .fetch_next_commit_ids(committed_pubkeys, min_context_slot) - .await - .map_err(TaskBuilderError::CommitTasksBuildError)?; + let commit_ids = { + let (commit_ids_not_compressed, commit_ids_compressed) = tokio::join!( + self.task_info_fetcher.fetch_next_commit_ids( + committed_pubkeys, + min_context_slot, + false + ), + self.task_info_fetcher.fetch_next_commit_ids( + committed_pubkeys, + min_context_slot, + true + ) + ); + + let mut commit_ids = commit_ids_not_compressed?; + commit_ids.extend(commit_ids_compressed?); + commit_ids + }; // Here we find the broken tasks and reset them // Broken tasks are prepared incorrectly so they have to be cleaned up @@ -617,6 +630,7 @@ where &self, transaction_strategy: &mut TransactionStrategy, persister: &Option

, + commit_signature: Option, ) -> IntentExecutorResult< IntentExecutorResult, TransactionPreparatorError, @@ -628,6 +642,8 @@ where &self.authority, transaction_strategy, persister, + &self.task_info_fetcher, + commit_signature, ) .await?; diff --git a/magicblock-committor-service/src/intent_executor/single_stage_executor.rs b/magicblock-committor-service/src/intent_executor/single_stage_executor.rs index ce423b6bc..2e1442359 100644 --- a/magicblock-committor-service/src/intent_executor/single_stage_executor.rs +++ b/magicblock-committor-service/src/intent_executor/single_stage_executor.rs @@ -64,6 +64,7 @@ where .prepare_and_execute_strategy( &mut self.transaction_strategy, persister, + None, ) .await .map_err(IntentExecutorError::FailedFinalizePreparationError)?; @@ -218,6 +219,7 @@ where lookup_tables_keys: vec![], }, &None::, + None, ) .await .map_err(IntentExecutorError::FailedFinalizePreparationError)? diff --git a/magicblock-committor-service/src/intent_executor/task_info_fetcher.rs b/magicblock-committor-service/src/intent_executor/task_info_fetcher.rs index 415d12437..5e2fa5b50 100644 --- a/magicblock-committor-service/src/intent_executor/task_info_fetcher.rs +++ b/magicblock-committor-service/src/intent_executor/task_info_fetcher.rs @@ -1,12 +1,30 @@ use std::{ - collections::HashMap, num::NonZeroUsize, sync::Mutex, time::Duration, + collections::HashMap, + num::NonZeroUsize, + sync::{Arc, Mutex}, + time::Duration, }; use async_trait::async_trait; +use borsh::BorshDeserialize; +use compressed_delegation_client::CompressedDelegationRecord; use dlp::{ delegation_metadata_seeds_from_delegated_account, state::DelegationMetadata, }; +use futures_util::{stream::FuturesUnordered, TryStreamExt}; +use light_client::{ + indexer::{ + photon_indexer::PhotonIndexer, Indexer, IndexerError, IndexerRpcConfig, + RetryConfig, + }, + rpc::RpcError as LightRpcError, +}; +use light_sdk::instruction::{ + account_meta::CompressedAccountMeta, PackedAccounts, + SystemAccountMetaConfig, +}; use lru::LruCache; +use magicblock_core::compression::derive_cda_from_pda; use magicblock_metrics::metrics; use magicblock_rpc_client::{MagicBlockRpcClientError, MagicblockRpcClient}; use solana_account::Account; @@ -20,6 +38,8 @@ use solana_rpc_client_api::{ use solana_signature::Signature; use tracing::{error, info, warn}; +use crate::tasks::task_builder::CompressedData; + const NUM_FETCH_RETRIES: NonZeroUsize = NonZeroUsize::new(5).unwrap(); const MUTEX_POISONED_MSG: &str = "CacheTaskInfoFetcher mutex poisoned!"; @@ -31,6 +51,7 @@ pub trait TaskInfoFetcher: Send + Sync + 'static { &self, pubkeys: &[Pubkey], min_context_slot: u64, + compressed: bool, ) -> TaskInfoFetcherResult>; /// Fetches rent reimbursement address for pubkeys @@ -51,6 +72,29 @@ pub trait TaskInfoFetcher: Send + Sync + 'static { pubkeys: &[Pubkey], min_context_slot: u64, ) -> TaskInfoFetcherResult>; + + async fn get_compressed_data( + &self, + pubkey: &Pubkey, + min_context_slot: Option, + ) -> TaskInfoFetcherResult; + + async fn get_compressed_data_for_accounts( + &self, + pubkeys: &[Pubkey], + min_context_slot: Option, + ) -> TaskInfoFetcherResult>> { + pubkeys + .iter() + .map(|pubkey| async move { + Ok(Some( + self.get_compressed_data(pubkey, min_context_slot).await?, + )) + }) + .collect::>() + .try_collect::>() + .await + } } pub enum ResetType<'a> { @@ -60,15 +104,20 @@ pub enum ResetType<'a> { pub struct CacheTaskInfoFetcher { rpc_client: MagicblockRpcClient, + photon_client: Arc, cache: Mutex>, } impl CacheTaskInfoFetcher { - pub fn new(rpc_client: MagicblockRpcClient) -> Self { + pub fn new( + rpc_client: MagicblockRpcClient, + photon_client: Arc, + ) -> Self { const CACHE_SIZE: NonZeroUsize = NonZeroUsize::new(1000).unwrap(); Self { rpc_client, + photon_client, cache: Mutex::new(LruCache::new(CACHE_SIZE)), } } @@ -162,6 +211,22 @@ impl CacheTaskInfoFetcher { TaskInfoFetcherError::MagicBlockRpcClientError(ref err) => { warn!(error = ?err, attempt = i, "Fetch account error"); } + TaskInfoFetcherError::IndexerError(ref err) => { + warn!(error = ?err, attempt = i, "Fetch compressed delegation records error"); + } + TaskInfoFetcherError::NoCompressedAccount(_) => break Err(err), + TaskInfoFetcherError::NoCompressedData(_) => break Err(err), + TaskInfoFetcherError::DeserializeError(_) => break Err(err), + TaskInfoFetcherError::LightRpcError(ref err) => { + warn!(error = ?err, attempt = i, "Fetch account error"); + } + TaskInfoFetcherError::PhotonClientNotFound => break Err(err), + TaskInfoFetcherError::LightSdkError(ref err) => { + warn!(error = ?err, attempt = i, "LightSdk error"); + } + TaskInfoFetcherError::MissingStateTrees => break Err(err), + TaskInfoFetcherError::MissingAddress => break Err(err), + TaskInfoFetcherError::MissingCompressedData => break Err(err), } if i >= max_retries.get() { @@ -222,6 +287,64 @@ impl CacheTaskInfoFetcher { Ok(accounts) } + + /// Fetches delegation records using Photon Indexer + /// Photon + pub async fn fetch_compressed_delegation_records_with_retries( + photon_client: &PhotonIndexer, + pubkeys: &[Pubkey], + min_context_slot: u64, + max_retries: NonZeroUsize, + ) -> TaskInfoFetcherResult> { + // Early return if no pubkeys to process + if pubkeys.is_empty() { + return Ok(Vec::new()); + } + + let cdas = pubkeys + .iter() + .map(|pubkey| derive_cda_from_pda(pubkey).to_bytes()) + .collect::>(); + let compressed_accounts = photon_client + .get_multiple_compressed_accounts( + Some(cdas), + None, + Some(IndexerRpcConfig { + slot: min_context_slot, + retry_config: RetryConfig { + num_retries: max_retries.get() as u32, + ..Default::default() + }, + }), + ) + .await + .map_err(|err| TaskInfoFetcherError::IndexerError(Box::new(err)))? + .value; + + metrics::inc_task_info_fetcher_compressed_count(); + + let compressed_delegation_records = compressed_accounts + .items + .into_iter() + .zip(pubkeys.iter()) + .map(|(acc, pubkey)| { + let delegation_record = + CompressedDelegationRecord::try_from_slice( + &acc.ok_or(TaskInfoFetcherError::NoCompressedAccount( + *pubkey, + ))? + .data + .ok_or(TaskInfoFetcherError::NoCompressedData(*pubkey))? + .data, + ) + .map_err(TaskInfoFetcherError::DeserializeError)?; + + Ok::<_, TaskInfoFetcherError>(delegation_record) + }) + .collect::, _>>()?; + + Ok(compressed_delegation_records) + } } /// TaskInfoFetcher implementation that also caches most used 1000 keys @@ -233,6 +356,7 @@ impl TaskInfoFetcher for CacheTaskInfoFetcher { &self, pubkeys: &[Pubkey], min_context_slot: u64, + compressed: bool, ) -> TaskInfoFetcherResult> { if pubkeys.is_empty() { return Ok(HashMap::new()); @@ -271,15 +395,29 @@ impl TaskInfoFetcher for CacheTaskInfoFetcher { to_request.sort(); to_request.dedup(); - let remaining_ids = Self::fetch_metadata_with_retries( - &self.rpc_client, - &to_request, - min_context_slot, - NUM_FETCH_RETRIES, - ) - .await? - .into_iter() - .map(|metadata| metadata.last_update_nonce); + let remaining_ids = if compressed { + Self::fetch_compressed_delegation_records_with_retries( + &self.photon_client, + &to_request, + min_context_slot, + NUM_FETCH_RETRIES, + ) + .await? + .into_iter() + .map(|metadata| metadata.last_update_nonce) + .collect::>() + } else { + Self::fetch_metadata_with_retries( + &self.rpc_client, + &to_request, + min_context_slot, + NUM_FETCH_RETRIES, + ) + .await? + .into_iter() + .map(|metadata| metadata.last_update_nonce) + .collect::>() + }; // We don't care if anything changed in between with cache - just update and return our ids. { @@ -355,10 +493,83 @@ impl TaskInfoFetcher for CacheTaskInfoFetcher { Ok(pubkeys.iter().copied().zip(accounts).collect()) } + + async fn get_compressed_data( + &self, + pubkey: &Pubkey, + min_context_slot: Option, + ) -> TaskInfoFetcherResult { + let cda = derive_cda_from_pda(pubkey); + let compressed_delegation_record = self + .photon_client + .get_compressed_account( + cda.to_bytes(), + min_context_slot.map(|slot| IndexerRpcConfig { + slot, + retry_config: RetryConfig::default(), + }), + ) + .await + .map_err(|err| TaskInfoFetcherError::IndexerError(Box::new(err)))? + .value; + let proof_result = self + .photon_client + .get_validity_proof( + vec![compressed_delegation_record.hash], + vec![], + min_context_slot.map(|slot| IndexerRpcConfig { + slot, + retry_config: RetryConfig::default(), + }), + ) + .await + .map_err(|err| TaskInfoFetcherError::IndexerError(Box::new(err)))? + .value; + + let system_account_meta_config = + SystemAccountMetaConfig::new(compressed_delegation_client::ID); + let mut remaining_accounts = PackedAccounts::default(); + remaining_accounts + .add_system_accounts_v2(system_account_meta_config) + .map_err(|err| { + TaskInfoFetcherError::LightSdkError(Box::new(err)) + })?; + let packed_tree_accounts = proof_result + .pack_tree_infos(&mut remaining_accounts) + .state_trees + .ok_or(TaskInfoFetcherError::MissingStateTrees)?; + + let tree_info = packed_tree_accounts + .packed_tree_infos + .first() + .copied() + .ok_or(TaskInfoFetcherError::MissingStateTrees)?; + + let account_meta = CompressedAccountMeta { + tree_info, + address: compressed_delegation_record + .address + .ok_or(TaskInfoFetcherError::MissingAddress)?, + output_state_tree_index: packed_tree_accounts.output_tree_index, + }; + + Ok(CompressedData { + hash: compressed_delegation_record.hash, + compressed_delegation_record_bytes: compressed_delegation_record + .data + .ok_or(TaskInfoFetcherError::MissingCompressedData)? + .data, + remaining_accounts: remaining_accounts.to_account_metas().0, + account_meta, + proof: proof_result.proof, + }) + } } #[derive(thiserror::Error, Debug)] pub enum TaskInfoFetcherError { + #[error("LightRpcError: {0}")] + LightRpcError(#[from] Box), #[error("Metadata not found for: {0}")] AccountNotFoundError(Pubkey), #[error("InvalidAccountDataError for: {0}")] @@ -367,6 +578,24 @@ pub enum TaskInfoFetcherError { MinContextSlotNotReachedError(u64, Box), #[error("MagicBlockRpcClientError: {0}")] MagicBlockRpcClientError(Box), + #[error("IndexerError: {0}")] + IndexerError(#[from] Box), + #[error("NoCompressedAccount: {0}")] + NoCompressedAccount(Pubkey), + #[error("CompressedAccountDataNotFound: {0}")] + NoCompressedData(Pubkey), + #[error("CompressedAccountDataDeserializeError: {0}")] + DeserializeError(#[from] std::io::Error), + #[error("PhotonClientNotFound")] + PhotonClientNotFound, + #[error("LightSdkError: {0}")] + LightSdkError(#[from] Box), + #[error("MissingStateTrees")] + MissingStateTrees, + #[error("MissingAddress")] + MissingAddress, + #[error("MissingCompressedData")] + MissingCompressedData, } impl TaskInfoFetcherError { @@ -417,6 +646,16 @@ impl TaskInfoFetcherError { Self::InvalidAccountDataError(_) => None, Self::MinContextSlotNotReachedError(_, err) => err.signature(), Self::MagicBlockRpcClientError(err) => err.signature(), + Self::IndexerError(_) => None, + Self::NoCompressedAccount(_) => None, + Self::NoCompressedData(_) => None, + Self::DeserializeError(_) => None, + Self::LightRpcError(_) => None, + Self::PhotonClientNotFound => None, + Self::LightSdkError(_) => None, + Self::MissingStateTrees => None, + Self::MissingAddress => None, + Self::MissingCompressedData => None, } } } diff --git a/magicblock-committor-service/src/intent_executor/two_stage_executor.rs b/magicblock-committor-service/src/intent_executor/two_stage_executor.rs index 73e41651b..bc9049a60 100644 --- a/magicblock-committor-service/src/intent_executor/two_stage_executor.rs +++ b/magicblock-committor-service/src/intent_executor/two_stage_executor.rs @@ -90,6 +90,7 @@ where .prepare_and_execute_strategy( &mut self.state.commit_strategy, persister, + None, ) .await .map_err(IntentExecutorError::FailedCommitPreparationError)?; @@ -228,6 +229,7 @@ where lookup_tables_keys: vec![], }, &None::, + None, ) .await .map_err(IntentExecutorError::FailedCommitPreparationError)? @@ -265,6 +267,7 @@ where .prepare_and_execute_strategy( &mut self.state.finalize_strategy, persister, + Some(self.state.commit_signature), ) .await .map_err(IntentExecutorError::FailedFinalizePreparationError)?; diff --git a/magicblock-committor-service/src/pubkeys_provider.rs b/magicblock-committor-service/src/pubkeys_provider.rs index 0630a5fc7..96663186d 100644 --- a/magicblock-committor-service/src/pubkeys_provider.rs +++ b/magicblock-committor-service/src/pubkeys_provider.rs @@ -42,6 +42,7 @@ pub fn provide_common_pubkeys(validator: &Pubkey) -> HashSet { let mut set = HashSet::new(); let deleg_program = dlp::id(); + let compressed_delegation_program = compressed_delegation_client::id(); let protocol_fees_vault = pda::fees_vault_pda(); let validator_fees_vault = pda::validator_fees_vault_pda_from_validator(validator); @@ -50,6 +51,7 @@ pub fn provide_common_pubkeys(validator: &Pubkey) -> HashSet { trace!( validator = %validator, deleg_program = %deleg_program, + compressed_delegation_program = %compressed_delegation_program, protocol_fees_vault = %protocol_fees_vault, validator_fees_vault = %validator_fees_vault, committor_program = %committor_program, @@ -59,6 +61,7 @@ pub fn provide_common_pubkeys(validator: &Pubkey) -> HashSet { set.insert(*validator); set.insert(system_program_id()); set.insert(deleg_program); + set.insert(compressed_delegation_program); set.insert(protocol_fees_vault); set.insert(validator_fees_vault); set.insert(committor_program); diff --git a/magicblock-committor-service/src/service.rs b/magicblock-committor-service/src/service.rs index 2263f8785..c38c37394 100644 --- a/magicblock-committor-service/src/service.rs +++ b/magicblock-committor-service/src/service.rs @@ -1,5 +1,6 @@ use std::{path::Path, sync::Arc, time::Instant}; +use light_client::indexer::photon_indexer::PhotonIndexer; use solana_keypair::Keypair; use solana_pubkey::Pubkey; use solana_signature::Signature; @@ -99,6 +100,7 @@ impl CommittorActor { authority: Keypair, persist_file: P, chain_config: ChainConfig, + photon_client: Arc, ) -> CommittorServiceResult where P: AsRef, @@ -107,6 +109,7 @@ impl CommittorActor { authority, persist_file, chain_config, + photon_client, )?); Ok(Self { @@ -265,6 +268,7 @@ impl CommittorService { authority: Keypair, persist_file: P, chain_config: ChainConfig, + photon_client: Arc, ) -> CommittorServiceResult where P: AsRef, @@ -279,6 +283,7 @@ impl CommittorService { authority, persist_file, chain_config, + photon_client, )?; tokio::spawn(async move { actor.run(cancel_token).await; diff --git a/magicblock-committor-service/src/tasks/args_task.rs b/magicblock-committor-service/src/tasks/args_task.rs index 6ce9eadb7..cf6fbc62b 100644 --- a/magicblock-committor-service/src/tasks/args_task.rs +++ b/magicblock-committor-service/src/tasks/args_task.rs @@ -1,3 +1,4 @@ +use compressed_delegation_client::CommitAndFinalizeArgs; use dlp::{ args::{CallHandlerArgs, CommitDiffArgs, CommitStateArgs}, compute_diff, @@ -5,7 +6,7 @@ use dlp::{ call_handler_size_budget, commit_diff_size_budget, commit_size_budget, finalize_size_budget, undelegate_size_budget, }, - AccountSizeClass, + total_size_budget, AccountSizeClass, }; use magicblock_metrics::metrics::LabelValue; use solana_account::ReadableAccount; @@ -16,15 +17,18 @@ use solana_pubkey::Pubkey; use crate::tasks::TaskStrategy; use crate::tasks::{ buffer_task::{BufferTask, BufferTaskType}, + task_builder::CompressedData, visitor::Visitor, BaseActionTask, BaseTask, BaseTaskError, BaseTaskResult, CommitDiffTask, - CommitTask, FinalizeTask, PreparationState, TaskType, UndelegateTask, + CommitTask, CompressedCommitTask, FinalizeTask, PreparationState, + PreparationTask, TaskType, UndelegateTask, }; /// Task that will be executed on Base layer via arguments #[derive(Clone)] pub enum ArgsTaskType { Commit(CommitTask), + CompressedCommitAndFinalize(CompressedCommitTask), CommitDiff(CommitDiffTask), Finalize(FinalizeTask), Undelegate(UndelegateTask), // Special action really @@ -45,8 +49,19 @@ impl From for ArgsTask { impl ArgsTask { pub fn new(task_type: ArgsTaskType) -> Self { + // Only prepare compressed tasks [`ArgsTaskType`] type + let preparation_state = match task_type { + ArgsTaskType::Commit(_) + | ArgsTaskType::CommitDiff(_) + | ArgsTaskType::Finalize(_) + | ArgsTaskType::Undelegate(_) + | ArgsTaskType::BaseAction(_) => PreparationState::NotNeeded, + ArgsTaskType::CompressedCommitAndFinalize(_) => { + PreparationState::Required(PreparationTask::Compressed) + } + }; Self { - preparation_state: PreparationState::NotNeeded, + preparation_state, task_type, } } @@ -69,6 +84,28 @@ impl BaseTask for ArgsTask { args, ) } + ArgsTaskType::CompressedCommitAndFinalize(value) => { + compressed_delegation_client::builders::CommitAndFinalizeBuilder { + validator: *validator, + delegated_account: value.committed_account.pubkey, + remaining_accounts: value + .compressed_data + .remaining_accounts + .clone(), + args: CommitAndFinalizeArgs { + current_compressed_delegated_account_data: value + .compressed_data + .compressed_delegation_record_bytes + .clone(), + new_data: value.committed_account.account.data.clone(), + account_meta: value.compressed_data.account_meta, + validity_proof: value.compressed_data.proof, + update_nonce: value.commit_id, + allow_undelegation: value.allow_undelegation, + }, + } + .instruction() + } ArgsTaskType::CommitDiff(value) => { let args = CommitDiffArgs { nonce: value.commit_id, @@ -143,11 +180,11 @@ impl BaseTask for ArgsTask { } ArgsTaskType::BaseAction(_) | ArgsTaskType::Finalize(_) - | ArgsTaskType::Undelegate(_) => Err(self), + | ArgsTaskType::Undelegate(_) + | ArgsTaskType::CompressedCommitAndFinalize(_) => Err(self), } } - /// Nothing to prepare for [`ArgsTaskType`] type fn preparation_state(&self) -> &PreparationState { &self.preparation_state } @@ -159,7 +196,7 @@ impl BaseTask for ArgsTask { if !matches!(new_state, PreparationState::NotNeeded) { Err(BaseTaskError::PreparationStateTransitionError) } else { - // Do nothing + self.preparation_state = new_state; Ok(()) } } @@ -171,6 +208,7 @@ impl BaseTask for ArgsTask { ArgsTaskType::BaseAction(task) => task.action.compute_units, ArgsTaskType::Undelegate(_) => 70_000, ArgsTaskType::Finalize(_) => 70_000, + ArgsTaskType::CompressedCommitAndFinalize(_) => 250_000, } } @@ -186,6 +224,9 @@ impl BaseTask for ArgsTask { task.committed_account.account.data.len() as u32, )) } + ArgsTaskType::CompressedCommitAndFinalize(_task) => { + compressed_task_accounts_size_budget() + } ArgsTaskType::BaseAction(task) => { // assume all other accounts are Small accounts. let other_accounts_budget = @@ -214,6 +255,9 @@ impl BaseTask for ArgsTask { fn task_type(&self) -> TaskType { match &self.task_type { ArgsTaskType::Commit(_) => TaskType::Commit, + ArgsTaskType::CompressedCommitAndFinalize(_) => { + TaskType::CompressedCommitAndFinalize + } ArgsTaskType::CommitDiff(_) => TaskType::Commit, ArgsTaskType::BaseAction(_) => TaskType::Action, ArgsTaskType::Undelegate(_) => TaskType::Undelegate, @@ -231,6 +275,9 @@ impl BaseTask for ArgsTask { ArgsTaskType::Commit(task) => { task.commit_id = commit_id; } + ArgsTaskType::CompressedCommitAndFinalize(task) => { + task.commit_id = commit_id; + } ArgsTaskType::CommitDiff(task) => { task.commit_id = commit_id; } @@ -239,6 +286,60 @@ impl BaseTask for ArgsTask { | ArgsTaskType::Undelegate(_) => {} }; } + + fn is_compressed(&self) -> bool { + matches!( + &self.task_type, + ArgsTaskType::CompressedCommitAndFinalize(_) + ) + } + + fn set_compressed_data(&mut self, compressed_data: CompressedData) { + if let ArgsTaskType::CompressedCommitAndFinalize(value) = + &mut self.task_type + { + value.compressed_data = compressed_data; + } + } + + fn get_compressed_data(&self) -> Option<&CompressedData> { + if let ArgsTaskType::CompressedCommitAndFinalize(value) = + &self.task_type + { + Some(&value.compressed_data) + } else { + None + } + } + + fn delegated_account(&self) -> Option { + match &self.task_type { + ArgsTaskType::Commit(value) => Some(value.committed_account.pubkey), + ArgsTaskType::CompressedCommitAndFinalize(value) => { + Some(value.committed_account.pubkey) + } + ArgsTaskType::CommitDiff(value) => { + Some(value.committed_account.pubkey) + } + ArgsTaskType::Finalize(value) => Some(value.delegated_account), + ArgsTaskType::Undelegate(value) => Some(value.delegated_account), + ArgsTaskType::BaseAction(_) => None, + } + } +} + +fn compressed_task_accounts_size_budget() -> u32 { + total_size_budget(&[ + AccountSizeClass::Tiny, // validator + AccountSizeClass::ExtraLarge, // Light System Program + AccountSizeClass::Tiny, // CPI Signer + AccountSizeClass::Tiny, // Registered Program PDA + AccountSizeClass::Tiny, // Account Compression Authority + AccountSizeClass::Tiny, // Account Compression Program + AccountSizeClass::Tiny, // System Program + AccountSizeClass::Huge, // Batch Merkle Tree + AccountSizeClass::ExtraLarge, // Output Queue + ]) } impl LabelValue for ArgsTask { @@ -249,6 +350,9 @@ impl LabelValue for ArgsTask { ArgsTaskType::BaseAction(_) => "args_action", ArgsTaskType::Finalize(_) => "args_finalize", ArgsTaskType::Undelegate(_) => "args_undelegate", + ArgsTaskType::CompressedCommitAndFinalize(_) => { + "args_compressed_commit_and_finalize" + } } } } diff --git a/magicblock-committor-service/src/tasks/buffer_task.rs b/magicblock-committor-service/src/tasks/buffer_task.rs index 700d7f8e5..56090ad3b 100644 --- a/magicblock-committor-service/src/tasks/buffer_task.rs +++ b/magicblock-committor-service/src/tasks/buffer_task.rs @@ -17,8 +17,8 @@ use crate::{ consts::MAX_WRITE_CHUNK_SIZE, tasks::{ visitor::Visitor, BaseTask, BaseTaskError, BaseTaskResult, - CommitDiffTask, CommitTask, PreparationState, PreparationTask, - TaskType, + BufferPreparationTask, CommitDiffTask, CommitTask, PreparationState, + PreparationTask, TaskType, }, }; @@ -61,12 +61,14 @@ impl BufferTask { let chunks = Chunks::from_data_length(data.len(), MAX_WRITE_CHUNK_SIZE); - PreparationState::Required(PreparationTask { - commit_id: task.commit_id, - pubkey: task.committed_account.pubkey, - committed_data: data, - chunks, - }) + PreparationState::Required(PreparationTask::Buffer( + BufferPreparationTask { + commit_id: task.commit_id, + pubkey: task.committed_account.pubkey, + committed_data: data, + chunks, + }, + )) } BufferTaskType::CommitDiff(task) => { @@ -78,12 +80,14 @@ impl BufferTask { let chunks = Chunks::from_data_length(diff.len(), MAX_WRITE_CHUNK_SIZE); - PreparationState::Required(PreparationTask { - commit_id: task.commit_id, - pubkey: task.committed_account.pubkey, - committed_data: diff, - chunks, - }) + PreparationState::Required(PreparationTask::Buffer( + BufferPreparationTask { + commit_id: task.commit_id, + pubkey: task.committed_account.pubkey, + committed_data: diff, + chunks, + }, + )) } } } @@ -220,6 +224,34 @@ impl BaseTask for BufferTask { self.preparation_state = Self::preparation_required(&self.task_type) } + + fn is_compressed(&self) -> bool { + false + } + + fn set_compressed_data( + &mut self, + _compressed_data: super::task_builder::CompressedData, + ) { + // No-op + } + + fn get_compressed_data( + &self, + ) -> Option<&super::task_builder::CompressedData> { + None + } + + fn delegated_account(&self) -> Option { + match &self.task_type { + BufferTaskType::Commit(value) => { + Some(value.committed_account.pubkey) + } + BufferTaskType::CommitDiff(value) => { + Some(value.committed_account.pubkey) + } + } + } } impl LabelValue for BufferTask { diff --git a/magicblock-committor-service/src/tasks/mod.rs b/magicblock-committor-service/src/tasks/mod.rs index 18e870059..ca463913f 100644 --- a/magicblock-committor-service/src/tasks/mod.rs +++ b/magicblock-committor-service/src/tasks/mod.rs @@ -19,7 +19,7 @@ use solana_instruction::Instruction; use solana_pubkey::Pubkey; use thiserror::Error; -use crate::tasks::visitor::Visitor; +use crate::tasks::{task_builder::CompressedData, visitor::Visitor}; pub mod args_task; pub mod buffer_task; @@ -34,6 +34,7 @@ pub use task_builder::TaskBuilderImpl; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum TaskType { Commit, + CompressedCommitAndFinalize, Finalize, Undelegate, Action, @@ -98,6 +99,18 @@ pub trait BaseTask: Send + Sync + DynClone + LabelValue { /// Calls [`Visitor`] with specific task type fn visit(&self, visitor: &mut dyn Visitor); + /// Returns true if task is compressed + fn is_compressed(&self) -> bool; + + /// Sets compressed data for task + fn set_compressed_data(&mut self, compressed_data: CompressedData); + + /// Gets compressed data for task + fn get_compressed_data(&self) -> Option<&CompressedData>; + + /// Delegated account for task + fn delegated_account(&self) -> Option; + /// Resets commit id fn reset_commit_id(&mut self, commit_id: u64); } @@ -119,6 +132,14 @@ pub struct CommitDiffTask { pub base_account: Account, } +#[derive(Clone)] +pub struct CompressedCommitTask { + pub commit_id: u64, + pub allow_undelegation: bool, + pub committed_account: CommittedAccount, + pub compressed_data: CompressedData, +} + #[derive(Clone)] pub struct UndelegateTask { pub delegated_account: Pubkey, @@ -126,18 +147,43 @@ pub struct UndelegateTask { pub rent_reimbursement: Pubkey, } +#[derive(Clone)] +pub struct CompressedUndelegateTask { + pub delegated_account: Pubkey, + pub owner_program: Pubkey, + pub compressed_data: CompressedData, +} + #[derive(Clone)] pub struct FinalizeTask { pub delegated_account: Pubkey, } +#[derive(Clone)] +pub struct CompressedFinalizeTask { + pub delegated_account: Pubkey, + pub compressed_data: CompressedData, +} + #[derive(Clone)] pub struct BaseActionTask { pub action: BaseAction, } +/// Task that will be executed on Base layer via arguments +#[derive(Clone)] +pub enum ArgsTask { + Commit(CommitTask), + CompressedCommit(CompressedCommitTask), + Finalize(FinalizeTask), + CompressedFinalize(CompressedFinalizeTask), + Undelegate(UndelegateTask), // Special action really + CompressedUndelegate(CompressedUndelegateTask), + BaseAction(BaseActionTask), +} + #[derive(Clone, Debug)] -pub struct PreparationTask { +pub struct BufferPreparationTask { pub commit_id: u64, pub pubkey: Pubkey, pub chunks: Chunks, @@ -146,16 +192,17 @@ pub struct PreparationTask { pub committed_data: Vec, } -impl PreparationTask { +#[derive(Clone, Debug)] +pub enum PreparationTask { + Buffer(BufferPreparationTask), + Compressed, +} + +impl BufferPreparationTask { /// Returns initialization [`Instruction`] pub fn init_instruction(&self, authority: &Pubkey) -> Instruction { - // // SAFETY: as object_length internally uses only already allocated or static buffers, - // // and we don't use any fs writers, so the only error that may occur here is of kind - // // OutOfMemory or WriteZero. This is impossible due to: - // // Chunks::new panics if its size exceeds MAX_ACCOUNT_ALLOC_PER_INSTRUCTION_SIZE or 10_240 - // // https://github.com/near/borsh-rs/blob/f1b75a6b50740bfb6231b7d0b1bd93ea58ca5452/borsh/src/ser/helpers.rs#L59 let chunks_account_size = - borsh::object_length(&self.chunks).unwrap() as u64; + Chunks::struct_size(self.chunks.count()) as u64; let buffer_account_size = self.committed_data.len() as u64; let (instruction, _, _) = create_init_ix(CreateInitIxArgs { @@ -306,7 +353,6 @@ pub type BaseTaskResult = Result; #[cfg(test)] mod serialization_safety_test { - use magicblock_program::{ args::ShortAccountMeta, magic_scheduled_base_intent::ProgramArgs, }; @@ -357,6 +403,29 @@ mod serialization_safety_test { })); assert_serializable(&finalize_task.instruction(&validator)); + // Test CompressedCommitAndFinalize variant + let compressed_commit_and_finalize_task: ArgsTask = + ArgsTaskType::CompressedCommitAndFinalize(CompressedCommitTask { + commit_id: 123, + allow_undelegation: true, + committed_account: CommittedAccount { + pubkey: Pubkey::new_unique(), + account: Account { + lamports: 1000, + data: vec![1, 2, 3], + owner: Pubkey::new_unique(), + executable: false, + rent_epoch: 0, + }, + remote_slot: Default::default(), + }, + compressed_data: CompressedData::default(), + }) + .into(); + assert_serializable( + &compressed_commit_and_finalize_task.instruction(&validator), + ); + // Test Undelegate variant let undelegate_task: ArgsTask = ArgsTaskType::Undelegate(UndelegateTask { @@ -441,6 +510,9 @@ mod serialization_safety_test { else { panic!("invalid preparation state on creation!"); }; + let PreparationTask::Buffer(preparation_task) = preparation_task else { + panic!("invalid preparation task on creation!"); + }; assert_serializable(&preparation_task.init_instruction(&authority)); for ix in preparation_task.realloc_instructions(&authority) { assert_serializable(&ix); diff --git a/magicblock-committor-service/src/tasks/task_builder.rs b/magicblock-committor-service/src/tasks/task_builder.rs index 38d87d94b..6d31a414b 100644 --- a/magicblock-committor-service/src/tasks/task_builder.rs +++ b/magicblock-committor-service/src/tasks/task_builder.rs @@ -1,11 +1,15 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; +use light_sdk::instruction::{ + account_meta::CompressedAccountMeta, ValidityProof, +}; use magicblock_program::magic_scheduled_base_intent::{ CommitType, CommittedAccount, MagicBaseIntent, ScheduledBaseIntent, UndelegateType, }; use solana_account::Account; +use solana_instruction::AccountMeta; use solana_pubkey::Pubkey; use solana_signature::Signature; use tracing::error; @@ -18,15 +22,26 @@ use crate::{ persist::IntentPersister, tasks::{ args_task::{ArgsTask, ArgsTaskType}, - BaseActionTask, BaseTask, FinalizeTask, UndelegateTask, + task_strategist::TaskStrategistError, + BaseActionTask, BaseTask, CompressedCommitTask, FinalizeTask, + UndelegateTask, }, }; +#[derive(Clone, Debug, Default, PartialEq)] +pub struct CompressedData { + pub hash: [u8; 32], + pub compressed_delegation_record_bytes: Vec, + pub remaining_accounts: Vec, + pub account_meta: CompressedAccountMeta, + pub proof: ValidityProof, +} + #[async_trait] pub trait TasksBuilder { // Creates tasks for commit stage async fn commit_tasks( - commit_id_fetcher: &Arc, + info_fetcher: &Arc, base_intent: &ScheduledBaseIntent, persister: &Option

, ) -> TaskBuilderResult>>; @@ -53,6 +68,7 @@ pub const COMMIT_STATE_SIZE_THRESHOLD: usize = 256; impl TaskBuilderImpl { pub fn create_commit_task( commit_id: u64, + compressed_data: Option<&CompressedData>, allow_undelegation: bool, account: CommittedAccount, base_account: Option, @@ -64,7 +80,14 @@ impl TaskBuilderImpl { None }; - if let Some(base_account) = base_account { + if let Some(compressed_data) = compressed_data { + ArgsTaskType::CompressedCommitAndFinalize(CompressedCommitTask { + commit_id, + allow_undelegation, + committed_account: account.clone(), + compressed_data: compressed_data.clone(), + }) + } else if let Some(base_account) = base_account { ArgsTaskType::CommitDiff(CommitDiffTask { commit_id, allow_undelegation, @@ -86,31 +109,40 @@ impl TaskBuilderImpl { impl TasksBuilder for TaskBuilderImpl { /// Returns [`Task`]s for Commit stage async fn commit_tasks( - commit_id_fetcher: &Arc, + info_fetcher: &Arc, base_intent: &ScheduledBaseIntent, persister: &Option

, ) -> TaskBuilderResult>> { - let (accounts, allow_undelegation) = match &base_intent.base_intent { - MagicBaseIntent::BaseActions(actions) => { - let tasks = actions - .iter() - .map(|el| { - let task = BaseActionTask { action: el.clone() }; - let task = - ArgsTask::new(ArgsTaskType::BaseAction(task)); - Box::new(task) as Box - }) - .collect(); + let (accounts, allow_undelegation, compressed) = + match &base_intent.base_intent { + MagicBaseIntent::BaseActions(actions) => { + let tasks = actions + .iter() + .map(|el| { + let task = BaseActionTask { action: el.clone() }; + let task = + ArgsTask::new(ArgsTaskType::BaseAction(task)); + Box::new(task) as Box + }) + .collect(); - return Ok(tasks); - } - MagicBaseIntent::Commit(t) => (t.get_committed_accounts(), false), - MagicBaseIntent::CommitAndUndelegate(t) => { - (t.commit_action.get_committed_accounts(), true) - } - }; + return Ok(tasks); + } + MagicBaseIntent::Commit(t) => { + (t.get_committed_accounts(), false, false) + } + MagicBaseIntent::CommitAndUndelegate(t) => { + (t.commit_action.get_committed_accounts(), true, false) + } + MagicBaseIntent::CompressedCommit(t) => { + (t.get_committed_accounts(), false, true) + } + MagicBaseIntent::CompressedCommitAndUndelegate(t) => { + (t.commit_action.get_committed_accounts(), true, true) + } + }; - let (commit_ids, base_accounts) = { + let (commit_ids, base_accounts, associated_compressed_data) = { let mut min_context_slot = 0; let committed_pubkeys = accounts .iter() @@ -130,19 +162,44 @@ impl TasksBuilder for TaskBuilderImpl { .collect::>(); tokio::join!( - commit_id_fetcher.fetch_next_commit_ids( + info_fetcher.fetch_next_commit_ids( &committed_pubkeys, - min_context_slot + min_context_slot, + compressed, ), - commit_id_fetcher.get_base_accounts( + info_fetcher.get_base_accounts( diffable_pubkeys.as_slice(), min_context_slot - ) + ), + async { + if compressed { + let pks = accounts + .iter() + .map(|account| account.pubkey) + .collect::>(); + Ok(info_fetcher + .get_compressed_data_for_accounts( + &pks, + Some(min_context_slot), + ) + .await? + .iter() + .zip(pks) + .filter_map(|(data, pk)| { + data.as_ref().map(|data| (pk, data.clone())) + }) + .collect::>()) + } else { + Ok(HashMap::new()) + } + } ) }; let commit_ids = commit_ids.map_err(TaskBuilderError::CommitTasksBuildError)?; + let associated_compressed_data = associated_compressed_data + .map_err(TaskBuilderError::CommitTasksBuildError)?; let base_accounts = match base_accounts { Ok(map) => map, @@ -155,7 +212,7 @@ impl TasksBuilder for TaskBuilderImpl { // Persist commit ids for commitees commit_ids .iter() - .for_each(|(pubkey, commit_id) | { + .for_each(|(pubkey, commit_id)| { if let Err(err) = persister.set_commit_id(base_intent.id, pubkey, *commit_id) { error!(intent_id = base_intent.id, pubkey = %pubkey, error = ?err, "Failed to persist commit id"); } @@ -164,14 +221,35 @@ impl TasksBuilder for TaskBuilderImpl { let tasks = accounts .iter() .map(|account| { - let commit_id = *commit_ids.get(&account.pubkey).expect("CommitIdFetcher provide commit ids for all listed pubkeys, or errors!"); + let commit_id = *commit_ids + .get(&account.pubkey) + .ok_or(TaskBuilderError::MissingCommitId(account.pubkey))?; + let compressed_data = if compressed { + Some( + associated_compressed_data.get(&account.pubkey).ok_or( + TaskBuilderError::MissingCompressedData( + account.pubkey, + ), + )?, + ) + } else { + None + }; + // TODO (snawaz): if accounts do not have duplicate, then we can use remove // instead: // let base_account = base_accounts.remove(&account.pubkey); let base_account = base_accounts.get(&account.pubkey).cloned(); - let task = Self::create_commit_task(commit_id, allow_undelegation, account.clone(), base_account); - Box::new(task) as Box - }).collect(); + let task = Self::create_commit_task( + commit_id, + compressed_data, + allow_undelegation, + account.clone(), + base_account, + ); + Ok::<_, TaskBuilderError>(Box::new(task) as Box) + }) + .collect::, _>>()?; Ok(tasks) } @@ -203,18 +281,24 @@ impl TasksBuilder for TaskBuilderImpl { } // Helper to process commit types - fn process_commit(commit: &CommitType) -> Vec> { + fn process_commit( + commit: &CommitType, + ) -> TaskBuilderResult>> { match commit { - CommitType::Standalone(accounts) => { - accounts.iter().map(finalize_task).collect() + CommitType::Standalone(committed_accounts) => { + Ok(committed_accounts + .iter() + .map(|account| finalize_task(account)) + .collect()) } CommitType::WithBaseActions { committed_accounts, base_actions, + .. } => { let mut tasks = committed_accounts .iter() - .map(finalize_task) + .map(|account| finalize_task(account)) .collect::>(); tasks.extend(base_actions.iter().map(|action| { let task = BaseActionTask { @@ -224,18 +308,18 @@ impl TasksBuilder for TaskBuilderImpl { ArgsTask::new(ArgsTaskType::BaseAction(task)); Box::new(task) as Box })); - tasks + Ok(tasks) } } } match &base_intent.base_intent { MagicBaseIntent::BaseActions(_) => Ok(vec![]), - MagicBaseIntent::Commit(commit) => Ok(process_commit(commit)), + MagicBaseIntent::Commit(commit) + | MagicBaseIntent::CompressedCommit(commit) => { + Ok(process_commit(commit)?) + } MagicBaseIntent::CommitAndUndelegate(t) => { - let mut tasks = process_commit(&t.commit_action); - - // Get rent reimbursments for undelegated accounts let accounts = t.get_committed_accounts(); let mut min_context_slot = 0; let pubkeys = accounts @@ -248,6 +332,7 @@ impl TasksBuilder for TaskBuilderImpl { account.pubkey }) .collect::>(); + let mut tasks = process_commit(&t.commit_action)?; let rent_reimbursements = info_fetcher .fetch_rent_reimbursements(&pubkeys, min_context_slot) .await @@ -259,6 +344,36 @@ impl TasksBuilder for TaskBuilderImpl { }, )); + match &t.undelegate_action { + UndelegateType::Standalone => Ok(tasks), + UndelegateType::WithBaseActions(actions) => { + tasks.extend(actions.iter().map(|action| { + let task = BaseActionTask { + action: action.clone(), + }; + let task = + ArgsTask::new(ArgsTaskType::BaseAction(task)); + Box::new(task) as Box + })); + + Ok(tasks) + } + } + } + MagicBaseIntent::CompressedCommitAndUndelegate(t) => { + let mut tasks = process_commit(&t.commit_action)?; + + // TODO: Compressed undelegate is not supported yet + // This is because the validator would have to pay rent out of pocket. + // This could be solved by using the ephemeral payer to ensure the user can pay the rent. + // https://github.com/magicblock-labs/magicblock-validator/issues/651 + + // tasks.extend(accounts.iter().zip(rent_reimbursements).map( + // |(account, rent_reimbursement)| { + // undelegate_task(account, &rent_reimbursement, None) + // }, + // )); + match &t.undelegate_action { UndelegateType::Standalone => Ok(tasks), UndelegateType::WithBaseActions(actions) => { @@ -285,6 +400,14 @@ pub enum TaskBuilderError { CommitTasksBuildError(#[source] TaskInfoFetcherError), #[error("FinalizedTasksBuildError: {0}")] FinalizedTasksBuildError(#[source] TaskInfoFetcherError), + #[error("TaskStrategistError: {0}")] + TaskStrategistError(#[from] TaskStrategistError), + #[error("MissingCommitId: {0}")] + MissingCommitId(Pubkey), + #[error("TaskInfoFetcherError: {0}")] + TaskInfoFetcherError(#[from] TaskInfoFetcherError), + #[error("MissingCompressedData: {0}")] + MissingCompressedData(Pubkey), } impl TaskBuilderError { @@ -292,6 +415,10 @@ impl TaskBuilderError { match self { Self::CommitTasksBuildError(err) => err.signature(), Self::FinalizedTasksBuildError(err) => err.signature(), + Self::TaskStrategistError(_) => None, + Self::MissingCommitId(_) => None, + Self::TaskInfoFetcherError(err) => err.signature(), + Self::MissingCompressedData(_) => None, } } } diff --git a/magicblock-committor-service/src/tasks/task_strategist.rs b/magicblock-committor-service/src/tasks/task_strategist.rs index a206d71fb..a6b32a96c 100644 --- a/magicblock-committor-service/src/tasks/task_strategist.rs +++ b/magicblock-committor-service/src/tasks/task_strategist.rs @@ -23,6 +23,15 @@ pub struct TransactionStrategy { } impl TransactionStrategy { + pub fn try_new( + optimized_tasks: Vec>, + lookup_tables_keys: Vec, + ) -> Result { + Ok(Self { + optimized_tasks, + lookup_tables_keys, + }) + } /// In case old strategy used ALTs recalculate old value /// NOTE: this can be used when full revaluation is unnecessary, like: /// some tasks were reset, number of tasks didn't increase @@ -80,6 +89,18 @@ impl TaskStrategist { ) -> TaskStrategistResult { const MAX_UNITED_TASKS_LEN: usize = 22; + // Compressed commits and finalize must be executed in two stages + if commit_tasks.iter().any(|t| t.is_compressed()) + || finalize_tasks.iter().any(|t| t.is_compressed()) + { + return Self::build_two_stage( + commit_tasks, + finalize_tasks, + authority, + persister, + ); + } + // We can unite in 1 tx a lot of commits // but then there's a possibility of hitting CPI limit, aka // MaxInstructionTraceLengthExceeded error. @@ -187,10 +208,7 @@ impl TaskStrategist { .for_each(|task| task.visit(&mut persistor_visitor)); } - Ok(TransactionStrategy { - optimized_tasks: tasks, - lookup_tables_keys: vec![], - }) + TransactionStrategy::try_new(tasks, vec![]) } // In case task optimization didn't work // attempt using lookup tables for all keys involved in tasks @@ -209,10 +227,7 @@ impl TaskStrategist { // Get lookup table keys let lookup_tables_keys = Self::collect_lookup_table_keys(validator, &tasks); - Ok(TransactionStrategy { - optimized_tasks: tasks, - lookup_tables_keys, - }) + TransactionStrategy::try_new(tasks, lookup_tables_keys) } else { Err(TaskStrategistError::FailedToFitError) } @@ -283,7 +298,7 @@ impl TaskStrategist { /// the limit MAX_ENCODED_TRANSACTION_SIZE. The caller needs to check and make decision accordingly. fn try_optimize_tx_size_if_needed( tasks: &mut [Box], - ) -> Result { + ) -> Result { // Get initial transaction size let calculate_tx_length = |tasks: &[Box]| { match TransactionUtils::assemble_tasks_tx( @@ -294,7 +309,7 @@ impl TaskStrategist { ) { Ok(tx) => Ok(serialize_and_encode_base64(&tx).len()), Err(TaskStrategistError::FailedToFitError) => Ok(usize::MAX), - Err(TaskStrategistError::SignerError(err)) => Err(err), + Err(err) => Err(err), } }; @@ -367,7 +382,7 @@ impl TaskStrategist { } } -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, PartialEq)] pub enum TaskStrategistError { #[error("Failed to fit in single TX")] FailedToFitError, @@ -398,7 +413,8 @@ mod tests { persist::IntentPersisterImpl, tasks::{ task_builder::{ - TaskBuilderImpl, TasksBuilder, COMMIT_STATE_SIZE_THRESHOLD, + CompressedData, TaskBuilderImpl, TasksBuilder, + COMMIT_STATE_SIZE_THRESHOLD, }, BaseActionTask, TaskStrategy, UndelegateTask, }, @@ -413,6 +429,7 @@ mod tests { &self, pubkeys: &[Pubkey], _: u64, + _compressed: bool, ) -> TaskInfoFetcherResult> { Ok(pubkeys.iter().map(|pubkey| (*pubkey, 0)).collect()) } @@ -438,6 +455,14 @@ mod tests { ) -> TaskInfoFetcherResult> { Ok(Default::default()) } + + async fn get_compressed_data( + &self, + _pubkey: &Pubkey, + _min_context_slot: Option, + ) -> TaskInfoFetcherResult { + Ok(Default::default()) + } } // Helper to create a simple commit task @@ -461,6 +486,7 @@ mod tests { if diff_len == 0 { TaskBuilderImpl::create_commit_task( commit_id, + None, false, committed_account, None, @@ -476,6 +502,7 @@ mod tests { }; TaskBuilderImpl::create_commit_task( commit_id, + None, false, committed_account, Some(base_account), diff --git a/magicblock-committor-service/src/tasks/task_visitors/utility_visitor.rs b/magicblock-committor-service/src/tasks/task_visitors/utility_visitor.rs index c0c62b966..3ed536dbb 100644 --- a/magicblock-committor-service/src/tasks/task_visitors/utility_visitor.rs +++ b/magicblock-committor-service/src/tasks/task_visitors/utility_visitor.rs @@ -11,6 +11,7 @@ pub struct CommitMeta { pub committed_pubkey: Pubkey, pub commit_id: u64, pub remote_slot: u64, + pub is_compressed: bool, } impl From for FinalizeTask { @@ -46,6 +47,7 @@ impl Visitor for TaskVisitorUtils { committed_pubkey: task.committed_account.pubkey, commit_id: task.commit_id, remote_slot: task.committed_account.remote_slot, + is_compressed: false, }) } ArgsTaskType::CommitDiff(task) => { @@ -53,6 +55,15 @@ impl Visitor for TaskVisitorUtils { committed_pubkey: task.committed_account.pubkey, commit_id: task.commit_id, remote_slot: task.committed_account.remote_slot, + is_compressed: false, + }) + } + ArgsTaskType::CompressedCommitAndFinalize(task) => { + *commit_meta = Some(CommitMeta { + committed_pubkey: task.committed_account.pubkey, + commit_id: task.commit_id, + remote_slot: task.committed_account.remote_slot, + is_compressed: true, }) } _ => *commit_meta = None, @@ -68,6 +79,7 @@ impl Visitor for TaskVisitorUtils { committed_pubkey: task.committed_account.pubkey, commit_id: task.commit_id, remote_slot: task.committed_account.remote_slot, + is_compressed: false, }) } BufferTaskType::CommitDiff(task) => { @@ -75,6 +87,7 @@ impl Visitor for TaskVisitorUtils { committed_pubkey: task.committed_account.pubkey, commit_id: task.commit_id, remote_slot: task.committed_account.remote_slot, + is_compressed: false, }) } } diff --git a/magicblock-committor-service/src/transaction_preparator/delivery_preparator.rs b/magicblock-committor-service/src/transaction_preparator/delivery_preparator.rs index 1aee95d7f..7db9a5876 100644 --- a/magicblock-committor-service/src/transaction_preparator/delivery_preparator.rs +++ b/magicblock-committor-service/src/transaction_preparator/delivery_preparator.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, ops::ControlFlow, time::Duration}; +use std::{collections::HashSet, ops::ControlFlow, sync::Arc, time::Duration}; use futures_util::future::{join, join_all, try_join_all}; use magicblock_committor_program::{ @@ -14,6 +14,7 @@ use magicblock_rpc_client::{ MagicBlockSendTransactionOutcome, MagicblockRpcClient, }; use magicblock_table_mania::{error::TableManiaError, TableMania}; +use solana_commitment_config::CommitmentConfig; use solana_compute_budget_interface::ComputeBudgetInstruction; use solana_instruction::{error::InstructionError, Instruction}; use solana_keypair::Keypair; @@ -21,17 +22,21 @@ use solana_message::{ v0::Message, AddressLookupTableAccount, CompileError, VersionedMessage, }; use solana_pubkey::Pubkey; +use solana_rpc_client_api::config::RpcTransactionConfig; use solana_signature::Signature; use solana_signer::{Signer, SignerError}; use solana_transaction::versioned::VersionedTransaction; use solana_transaction_error::TransactionError; -use tracing::{error, info}; +use tracing::{error, info, warn}; use crate::{ + intent_executor::task_info_fetcher::{ + TaskInfoFetcher, TaskInfoFetcherError, + }, persist::{CommitStatus, IntentPersister}, tasks::{ task_strategist::TransactionStrategy, BaseTask, BaseTaskError, - CleanupTask, PreparationState, PreparationTask, + BufferPreparationTask, CleanupTask, PreparationState, PreparationTask, }, utils::persist_status_update, ComputeBudgetConfig, @@ -56,12 +61,36 @@ impl DeliveryPreparator { } } + async fn commit_slot_for_signature( + &self, + signature: Signature, + ) -> Option { + self.rpc_client + .get_transaction( + &signature, + Some(RpcTransactionConfig { + commitment: Some(CommitmentConfig::confirmed()), + max_supported_transaction_version: Some(0), + ..Default::default() + }), + ) + .await + .inspect_err(|err| error!("Failed to get commit slot: {}", err)) + .map(|tx| tx.slot) + .ok() + } + /// Prepares buffers and necessary pieces for optimized TX - pub async fn prepare_for_delivery( + pub async fn prepare_for_delivery< + P: IntentPersister, + C: TaskInfoFetcher, + >( &self, authority: &Keypair, strategy: &mut TransactionStrategy, persister: &Option

, + info_fetcher: &Arc, + commit_signature: Option, ) -> DeliveryPreparatorResult> { let preparation_futures = strategy.optimized_tasks.iter_mut().map(|task| async move { @@ -69,8 +98,14 @@ impl DeliveryPreparator { metrics::observe_committor_intent_task_preparation_time( task.as_ref(), ); - self.prepare_task_handling_errors(authority, task, persister) - .await + self.prepare_task_handling_errors( + authority, + task, + persister, + info_fetcher, + commit_signature, + ) + .await }); let task_preparations = join_all(preparation_futures); @@ -92,11 +127,13 @@ impl DeliveryPreparator { } /// Prepares necessary parts for TX if needed, otherwise returns immediately - pub async fn prepare_task( + pub async fn prepare_task( &self, authority: &Keypair, task: &mut dyn BaseTask, persister: &Option

, + info_fetcher: &Arc, + commit_signature: Option, ) -> DeliveryPreparatorResult<(), InternalError> { let PreparationState::Required(preparation_task) = task.preparation_state() @@ -104,54 +141,99 @@ impl DeliveryPreparator { return Ok(()); }; - // Persist as failed until rewritten - let update_status = CommitStatus::BufferAndChunkPartiallyInitialized; - persist_status_update( - persister, - &preparation_task.pubkey, - preparation_task.commit_id, - update_status, - ); + match preparation_task { + PreparationTask::Buffer(buffer_info) => { + // Persist as failed until rewritten + let update_status = + CommitStatus::BufferAndChunkPartiallyInitialized; + persist_status_update( + persister, + &buffer_info.pubkey, + buffer_info.commit_id, + update_status, + ); - // Initialize buffer account. Init + reallocs - self.initialize_buffer_account(authority, preparation_task) - .await?; + // Initialize buffer account. Init + reallocs + self.initialize_buffer_account(authority, buffer_info) + .await?; + + // Persist initialization success + let update_status = CommitStatus::BufferAndChunkInitialized; + persist_status_update( + persister, + &buffer_info.pubkey, + buffer_info.commit_id, + update_status, + ); - // Persist initialization success - let update_status = CommitStatus::BufferAndChunkInitialized; - persist_status_update( - persister, - &preparation_task.pubkey, - preparation_task.commit_id, - update_status, - ); + // Writing chunks with some retries + self.write_buffer_with_retries(authority, buffer_info) + .await?; + // Persist that buffer account initiated successfully + let update_status = + CommitStatus::BufferAndChunkFullyInitialized; + persist_status_update( + persister, + &buffer_info.pubkey, + buffer_info.commit_id, + update_status, + ); - // Writing chunks with some retries - self.write_buffer_with_retries(authority, preparation_task) - .await?; - // Persist that buffer account initiated successfully - let update_status = CommitStatus::BufferAndChunkFullyInitialized; - persist_status_update( - persister, - &preparation_task.pubkey, - preparation_task.commit_id, - update_status, - ); + let cleanup_task = buffer_info.cleanup_task(); + task.switch_preparation_state(PreparationState::Cleanup( + cleanup_task, + ))?; + } + PreparationTask::Compressed => { + if task.get_compressed_data().is_some() { + return Ok(()); + } + + warn!("Compressed data should have been fetched already"); + + // Trying to fetch fresh data from the indexer + let commit_slot = if let Some(sig) = commit_signature { + self.commit_slot_for_signature(sig).await + } else { + None + }; + + let delegated_account = task + .delegated_account() + .ok_or(InternalError::DelegatedAccountNotFound)?; + + let compressed_data = info_fetcher + .get_compressed_data(&delegated_account, commit_slot) + .await?; + task.set_compressed_data(compressed_data); + } + } - let cleanup_task = preparation_task.cleanup_task(); - task.switch_preparation_state(PreparationState::Cleanup(cleanup_task))?; Ok(()) } /// Runs `prepare_task` and, if the buffer was already initialized, /// performs cleanup and retries once. - pub async fn prepare_task_handling_errors( + pub async fn prepare_task_handling_errors< + P: IntentPersister, + C: TaskInfoFetcher, + >( &self, authority: &Keypair, task: &mut Box, persister: &Option

, + info_fetcher: &Arc, + commit_signature: Option, ) -> Result<(), InternalError> { - let res = self.prepare_task(authority, task.as_mut(), persister).await; + let res = self + .prepare_task( + authority, + task.as_mut(), + persister, + info_fetcher, + commit_signature, + ) + .await; match res { Err(InternalError::BufferExecutionError( BufferExecutionError::AccountAlreadyInitializedError( @@ -165,9 +247,10 @@ impl DeliveryPreparator { res => return res, } - // Prepare cleanup task - let PreparationState::Required(preparation_task) = - task.preparation_state().clone() + // Prepare buffer cleanup task + let PreparationState::Required(PreparationTask::Buffer( + preparation_task, + )) = task.preparation_state().clone() else { return Ok(()); }; @@ -177,10 +260,17 @@ impl DeliveryPreparator { self.cleanup(authority, std::slice::from_ref(task), &[]) .await?; task.switch_preparation_state(PreparationState::Required( - preparation_task, + PreparationTask::Buffer(preparation_task), ))?; - self.prepare_task(authority, task.as_mut(), persister).await + self.prepare_task( + authority, + task.as_mut(), + persister, + info_fetcher, + commit_signature, + ) + .await } /// Initializes buffer account for future writes @@ -188,8 +278,8 @@ impl DeliveryPreparator { async fn initialize_buffer_account( &self, authority: &Keypair, - preparation_task: &PreparationTask, - ) -> DeliveryPreparatorResult<(), BufferExecutionError> { + preparation_task: &BufferPreparationTask, + ) -> DeliveryPreparatorResult<(), InternalError> { let authority_pubkey = authority.pubkey(); let init_instruction = preparation_task.init_instruction(&authority_pubkey); @@ -238,7 +328,7 @@ impl DeliveryPreparator { async fn write_buffer_with_retries( &self, authority: &Keypair, - preparation_task: &PreparationTask, + preparation_task: &BufferPreparationTask, ) -> DeliveryPreparatorResult<(), InternalError> { let authority_pubkey = authority.pubkey(); let write_instructions = @@ -545,8 +635,18 @@ pub enum InternalError { BorshError(#[from] std::io::Error), #[error("TableManiaError: {0}")] TableManiaError(#[from] TableManiaError), + #[error("TransactionCreationError: {0}")] + TransactionCreationError(#[from] CompileError), + #[error("TransactionSigningError: {0}")] + TransactionSigningError(#[from] SignerError), #[error("MagicBlockRpcClientError: {0}")] MagicBlockRpcClientError(Box), + #[error("Delegated account not found")] + DelegatedAccountNotFound, + #[error("PhotonClientNotFound")] + PhotonClientNotFound, + #[error("TaskInfoFetcherError: {0}")] + TaskInfoFetcherError(#[from] TaskInfoFetcherError), #[error("BufferExecutionError: {0}")] BufferExecutionError(#[from] BufferExecutionError), #[error("BaseTaskError: {0}")] diff --git a/magicblock-committor-service/src/transaction_preparator/mod.rs b/magicblock-committor-service/src/transaction_preparator/mod.rs index 4eef17995..46746c0a7 100644 --- a/magicblock-committor-service/src/transaction_preparator/mod.rs +++ b/magicblock-committor-service/src/transaction_preparator/mod.rs @@ -1,11 +1,15 @@ +use std::sync::Arc; + use async_trait::async_trait; use magicblock_rpc_client::MagicblockRpcClient; use magicblock_table_mania::TableMania; use solana_keypair::Keypair; use solana_message::VersionedMessage; use solana_pubkey::Pubkey; +use solana_signature::Signature; use crate::{ + intent_executor::task_info_fetcher::TaskInfoFetcher, persist::IntentPersister, tasks::{ task_strategist::TransactionStrategy, utils::TransactionUtils, BaseTask, @@ -26,11 +30,13 @@ pub mod error; pub trait TransactionPreparator: Send + Sync + 'static { /// Return [`VersionedMessage`] corresponding to [`TransactionStrategy`] /// Handles all necessary preparation needed for successful [`BaseTask`] execution - async fn prepare_for_strategy( + async fn prepare_for_strategy( &self, authority: &Keypair, transaction_strategy: &mut TransactionStrategy, intent_persister: &Option

, + info_fetcher: &Arc, + commit_signature: Option, ) -> PreparatorResult; /// Cleans up after strategy @@ -71,11 +77,13 @@ impl TransactionPreparatorImpl { #[async_trait] impl TransactionPreparator for TransactionPreparatorImpl { - async fn prepare_for_strategy( + async fn prepare_for_strategy( &self, authority: &Keypair, tx_strategy: &mut TransactionStrategy, intent_persister: &Option

, + info_fetcher: &Arc, + commit_signature: Option, ) -> PreparatorResult { // If message won't fit, there's no reason to prepare anything // Fail early @@ -94,7 +102,13 @@ impl TransactionPreparator for TransactionPreparatorImpl { // Pre tx preparations. Create buffer accs + lookup tables let lookup_tables = self .delivery_preparator - .prepare_for_delivery(authority, tx_strategy, intent_persister) + .prepare_for_delivery( + authority, + tx_strategy, + intent_persister, + info_fetcher, + commit_signature, + ) .await?; let message = TransactionUtils::assemble_tasks_tx( diff --git a/magicblock-committor-service/src/types.rs b/magicblock-committor-service/src/types.rs index d2076df46..a6b1f8451 100644 --- a/magicblock-committor-service/src/types.rs +++ b/magicblock-committor-service/src/types.rs @@ -25,6 +25,10 @@ impl metrics::LabelValue for ScheduledBaseIntentWrapper { MagicBaseIntent::BaseActions(_) => "actions", MagicBaseIntent::Commit(_) => "commit", MagicBaseIntent::CommitAndUndelegate(_) => "commit_and_undelegate", + MagicBaseIntent::CompressedCommit(_) => "compressed_commit", + MagicBaseIntent::CompressedCommitAndUndelegate(_) => { + "compressed_commit_and_undelegate" + } } } } diff --git a/magicblock-config/README.md b/magicblock-config/README.md index 79b88c99d..53b3b8a86 100644 --- a/magicblock-config/README.md +++ b/magicblock-config/README.md @@ -86,13 +86,14 @@ block-size = "block256" The configuration is split into domain-specific structs available in `src/config/`: - * **`ValidatorConfig`**: Identity keypair, base fees. - * **`LedgerConfig`**: Block production timing, verification settings. - * **`AccountsDbConfig`**: Snapshotting, indexing, and storage size tuning. - * **`ChainOperationConfig`**: On-chain registration details (Country code, FQDN). - * **`ChainLinkConfig`**: Account cloning settings. - * **`CommitStrategy`**: Compute unit pricing for base chain commits. - * **`TaskSchedulerConfig`**: Task scheduling settings. +- **`ValidatorConfig`**: Identity keypair, base fees. +- **`LedgerConfig`**: Block production timing, verification settings. +- **`AccountsDbConfig`**: Snapshotting, indexing, and storage size tuning. +- **`ChainOperationConfig`**: On-chain registration details (Country code, FQDN). +- **`ChainLinkConfig`**: Account cloning settings. +- **`CommitStrategy`**: Compute unit pricing for base chain commits. +- **`TaskSchedulerConfig`**: Task scheduling settings. +- **`CompressionConfig`**: Compressed accounts and interactions settings. ## Testing @@ -101,4 +102,3 @@ This crate includes a comprehensive test suite verifying the precedence logic, o ```bash cargo test -p magicblock-config ``` - diff --git a/magicblock-config/src/config/compression.rs b/magicblock-config/src/config/compression.rs new file mode 100644 index 000000000..c24cf98ab --- /dev/null +++ b/magicblock-config/src/config/compression.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +/// Configuration for the compression service. +#[derive(Deserialize, Serialize, Debug, Default, Clone)] +#[serde(rename_all = "kebab-case", deny_unknown_fields, default)] +pub struct CompressionConfig { + /// The URL of the Photon indexer. + pub photon_url: String, + /// The API key for the Photon indexer. + pub api_key: Option, +} diff --git a/magicblock-config/src/config/mod.rs b/magicblock-config/src/config/mod.rs index 1b3d7fba1..81b6bd8b8 100644 --- a/magicblock-config/src/config/mod.rs +++ b/magicblock-config/src/config/mod.rs @@ -2,6 +2,7 @@ pub mod accounts; pub mod aperture; pub mod chain; pub mod cli; +pub mod compression; pub mod ledger; pub mod lifecycle; pub mod metrics; @@ -15,6 +16,7 @@ pub use aperture::ApertureConfig; pub use chain::{ AllowedProgram, ChainLinkConfig, ChainOperationConfig, CommittorConfig, }; +pub use compression::CompressionConfig; pub use ledger::LedgerConfig; pub use lifecycle::LifecycleMode; pub use program::LoadableProgram; diff --git a/magicblock-config/src/lib.rs b/magicblock-config/src/lib.rs index c698b872a..04a8b1f9b 100644 --- a/magicblock-config/src/lib.rs +++ b/magicblock-config/src/lib.rs @@ -22,8 +22,8 @@ pub mod types; use crate::{ config::{ AccountsDbConfig, ChainLinkConfig, ChainOperationConfig, - CommittorConfig, LedgerConfig, LoadableProgram, TaskSchedulerConfig, - ValidatorConfig, + CommittorConfig, CompressionConfig, LedgerConfig, LoadableProgram, + TaskSchedulerConfig, ValidatorConfig, }, types::Remote, }; @@ -61,6 +61,7 @@ pub struct ValidatorParams { pub chainlink: ChainLinkConfig, pub chain_operation: Option, pub task_scheduler: TaskSchedulerConfig, + pub compression: CompressionConfig, pub programs: Vec, } diff --git a/magicblock-config/src/tests.rs b/magicblock-config/src/tests.rs index 63c259a55..04a771dab 100644 --- a/magicblock-config/src/tests.rs +++ b/magicblock-config/src/tests.rs @@ -558,7 +558,7 @@ fn test_env_vars_full_coverage() { assert_eq!(config.chainlink.max_monitored_accounts, 123); assert_eq!( config.chainlink.resubscription_delay, - Duration::from_millis(150) + std::time::Duration::from_millis(150) ); // Task Scheduler diff --git a/magicblock-core/Cargo.toml b/magicblock-core/Cargo.toml index 308b441a1..984227db1 100644 --- a/magicblock-core/Cargo.toml +++ b/magicblock-core/Cargo.toml @@ -8,10 +8,11 @@ license.workspace = true edition.workspace = true [dependencies] - -tokio = { workspace = true, features = ["sync"] } +compressed-delegation-client = { workspace = true } flume = { workspace = true } - +light-compressed-account = { workspace = true } +light-sdk = { workspace = true } +magicblock-magic-program-api = { workspace = true } solana-account = { workspace = true } solana-account-decoder = { workspace = true } solana-hash = { workspace = true } @@ -22,10 +23,10 @@ solana-transaction = { workspace = true, features = ["blake3", "verify"] } solana-transaction-context = { workspace = true } solana-transaction-error = { workspace = true } solana-transaction-status-client-types = { workspace = true } -magicblock-magic-program-api = { workspace = true } spl-token = { workspace = true } spl-token-2022 = { workspace = true } +tokio = { workspace = true, features = ["sync"] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["time"] } tracing-log = { workspace = true } diff --git a/magicblock-core/src/compression/mod.rs b/magicblock-core/src/compression/mod.rs new file mode 100644 index 000000000..3af81d64b --- /dev/null +++ b/magicblock-core/src/compression/mod.rs @@ -0,0 +1,44 @@ +use light_compressed_account::address::derive_address; +use light_sdk::light_hasher::hash_to_field_size::hashv_to_bn254_field_size_be_const_array; +use solana_pubkey::Pubkey; + +// Light protocol V2 accounts: +// https://www.zkcompression.com/resources/addresses-and-urls#v2-2 +pub const ADDRESS_TREE: Pubkey = + Pubkey::from_str_const("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"); +pub const OUTPUT_QUEUE: Pubkey = + Pubkey::from_str_const("oq1na8gojfdUhsfCpyjNt6h4JaDWtHf1yQj4koBWfto"); + +/// Derives a CDA (Compressed derived Address) from a PDA (Program derived Address) +/// of a compressed account we want to use in our validator in uncompressed form. +pub fn derive_cda_from_pda(pda: &Pubkey) -> Pubkey { + // Since the PDA is already unique we use the delegation program's id + // as a program id. + // SAFETY: BN254 hash of PDA must succeed for a 32-byte PDA seed + let seed = + hashv_to_bn254_field_size_be_const_array::<3>(&[&pda.to_bytes()]) + .expect("BN254 hash of PDA must succeed for a 32-byte PDA seed"); + let address = derive_address( + &seed, + &ADDRESS_TREE.to_bytes(), + &compressed_delegation_client::ID.to_bytes(), + ); + Pubkey::new_from_array(address) +} + +#[cfg(test)] +mod tests { + use solana_pubkey::pubkey; + + use super::*; + + #[test] + fn test_derive_cda_from_pda() { + let pda = pubkey!("6pyGAQnqveUcHJ4iT1B6N72iJSBWcb6KRht315Fo7mLX"); + let cda = derive_cda_from_pda(&pda); + assert_eq!( + cda, + pubkey!("13CJjg6sMzZ8Lsn1oyQggzcyq5nFHYt97i7bhMu7BNu9") + ); + } +} diff --git a/magicblock-core/src/lib.rs b/magicblock-core/src/lib.rs index f18f8a6f1..0e8270fdf 100644 --- a/magicblock-core/src/lib.rs +++ b/magicblock-core/src/lib.rs @@ -13,6 +13,7 @@ macro_rules! debug_panic { ) } +pub mod compression; pub mod link; pub mod tls; pub mod token_programs; diff --git a/magicblock-magic-program-api/src/instruction.rs b/magicblock-magic-program-api/src/instruction.rs index a4050e4e7..3fb1c6b44 100644 --- a/magicblock-magic-program-api/src/instruction.rs +++ b/magicblock-magic-program-api/src/instruction.rs @@ -106,6 +106,38 @@ pub enum MagicBlockInstruction { /// Noop instruction Noop(u64), + + /// Schedules the compressed accounts provided at end of accounts Vec to be committed. + /// It should be invoked from the program whose PDA accounts are to be + /// committed. + /// + /// This is the first part of scheduling a commit. + /// A second transaction [MagicBlockInstruction::AcceptScheduleCommits] has to run in order + /// to finish scheduling the commit. + /// + /// # Account references + /// - **0.** `[WRITE, SIGNER]` Payer requesting the commit to be scheduled + /// - **1.** `[WRITE]` Magic Context Account containing to which we store + /// the scheduled commits + /// - **2..n** `[]` Accounts to be committed + ScheduleCompressedCommit, + + /// This is the exact same instruction as [MagicBlockInstruction::ScheduleCompressedCommit] except + /// that the [ScheduledCommit] is flagged such that when accounts are committed, a request + /// to undelegate them is included with the same transaction. + /// Additionally the validator will refuse anymore transactions for the specific account + /// since they are no longer considered delegated to it. + /// + /// This is the first part of scheduling a commit. + /// A second transaction [MagicBlockInstruction::AcceptScheduleCommits] has to run in order + /// to finish scheduling the commit. + /// + /// # Account references + /// - **0.** `[WRITE, SIGNER]` Payer requesting the commit to be scheduled + /// - **1.** `[WRITE]` Magic Context Account containing to which we store + /// the scheduled commits + /// - **2..n** `[]` Accounts to be committed and undelegated + ScheduleCompressedCommitAndUndelegate, } impl MagicBlockInstruction { @@ -122,6 +154,7 @@ pub struct AccountModification { pub executable: Option, pub data: Option>, pub delegated: Option, + pub compressed: Option, pub confined: Option, pub remote_slot: Option, } @@ -133,6 +166,7 @@ pub struct AccountModificationForInstruction { pub executable: Option, pub data_key: Option, pub delegated: Option, + pub compressed: Option, pub confined: Option, pub remote_slot: Option, } diff --git a/magicblock-metrics/src/metrics/mod.rs b/magicblock-metrics/src/metrics/mod.rs index f09e5c24c..5a0fe243a 100644 --- a/magicblock-metrics/src/metrics/mod.rs +++ b/magicblock-metrics/src/metrics/mod.rs @@ -45,10 +45,12 @@ lazy_static::lazy_static! { "Number of cloned accounts in the RemoteAccountClonerWorker" ) .unwrap(); +} - // ----------------- - // Ledger - // ----------------- +// ----------------- +// Ledger +// ----------------- +lazy_static::lazy_static! { static ref LEDGER_SIZE_GAUGE: IntGauge = IntGauge::new( "ledger_size_gauge", "Ledger size in Bytes", ).unwrap(); @@ -137,10 +139,12 @@ lazy_static::lazy_static! { vec![0.1, 1.0, 2.0, 3.0, 10.0, 60.0] ), ).unwrap(); +} - // ----------------- - // Accounts - // ----------------- +// ----------------- +// Accounts +// ----------------- +lazy_static::lazy_static! { static ref ACCOUNTS_SIZE_GAUGE: IntGauge = IntGauge::new( "accounts_size_gauge", "Size of persisted accounts (in bytes) currently on disk", ).unwrap(); @@ -181,10 +185,12 @@ lazy_static::lazy_static! { &["client_id"], ) .unwrap(); +} - // ----------------- - // RPC/Aperture - // ----------------- +// ----------------- +// RPC/Aperture +// ----------------- +lazy_static::lazy_static! { pub static ref ENSURE_ACCOUNTS_TIME: HistogramVec = HistogramVec::new( HistogramOpts::new("ensure_accounts_time", "Time spent in ensuring account presence") .buckets( @@ -271,6 +277,39 @@ lazy_static::lazy_static! { ) .unwrap(); + // Account fetch results from Photon (Compressed) + pub static ref COMPRESSED_ACCOUNT_FETCHES_SUCCESS_COUNT: IntCounter = + IntCounter::new( + "compressed_account_fetches_success_count", + "Total number of successful network compressed account fetches", + ) + .unwrap(); + + pub static ref COMPRESSED_ACCOUNT_FETCHES_FAILED_COUNT: IntCounter = + IntCounter::new( + "compressed_account_fetches_failed_count", + "Total number of failed network compressed account fetches \ + (RPC errors)", + ) + .unwrap(); + + pub static ref COMPRESSED_ACCOUNT_FETCHES_FOUND_COUNT: IntCounterVec = IntCounterVec::new( + Opts::new( + "compressed_account_fetches_found_count", + "Total number of network compressed account fetches that found an account", + ), + &["origin"], + ) + .unwrap(); + + pub static ref COMPRESSED_ACCOUNT_FETCHES_NOT_FOUND_COUNT: IntCounterVec = IntCounterVec::new( + Opts::new( + "compressed_account_fetches_not_found_count", + "Total number of network compressed account fetches where account was not found", + ), + &["origin"], + ).unwrap(); + pub static ref PER_PROGRAM_ACCOUNT_FETCH_STATS: IntCounterVec = IntCounterVec::new( Opts::new( "per_program_account_fetch_stats", @@ -300,11 +339,12 @@ lazy_static::lazy_static! { "Total number of undelegating accounts found to be already undelegated on chain", ) .unwrap(); +} - - // ----------------- - // Transaction Execution - // ----------------- +// ----------------- +// Transaction Execution +// ----------------- +lazy_static::lazy_static! { pub static ref TRANSACTION_COUNT: IntCounter = IntCounter::new( "transaction_count", "Total number of executed transactions" ).unwrap(); @@ -316,14 +356,12 @@ lazy_static::lazy_static! { "max_lock_contention_queue_size", "Maximum observed queue size for an account lock contention" ).unwrap(); +} - - - - - // ----------------- - // CommittorService - // ----------------- +// ----------------- +// CommittorService +// ----------------- +lazy_static::lazy_static! { static ref COMMITTOR_INTENTS_COUNT: IntCounter = IntCounter::new( "committor_intents_count", "Total number of scheduled committor intents" ).unwrap(); @@ -365,6 +403,10 @@ lazy_static::lazy_static! { "task_info_fetcher_a_count", "Get mupltiple account count" ).unwrap(); + static ref TASK_INFO_FETCHER_COMPRESSED_COUNT: IntCounter = IntCounter::new( + "task_info_fetcher_compressed_count", "Get multiple compressed delegation records count" + ).unwrap(); + static ref TABLE_MANIA_A_COUNT: IntCounter = IntCounter::new( "table_mania_a_count", "Get mupltiple account count" ).unwrap(); @@ -394,10 +436,12 @@ lazy_static::lazy_static! { vec![1.0, 3.0, 5.0, 10.0, 15.0, 17.0, 20.0] ), ).unwrap(); +} - // ----------------- - // Pubsub Clients - // ----------------- +// ----------------- +// Pubsub Clients +// ----------------- +lazy_static::lazy_static! { static ref CONNECTED_PUBSUB_CLIENTS_GAUGE: IntGauge = IntGauge::new( "connected_pubsub_clients_gauge", "Total number of connected pubsub clients" @@ -474,6 +518,10 @@ pub(crate) fn register() { register!(ACCOUNT_FETCHES_FAILED_COUNT); register!(ACCOUNT_FETCHES_FOUND_COUNT); register!(ACCOUNT_FETCHES_NOT_FOUND_COUNT); + register!(COMPRESSED_ACCOUNT_FETCHES_SUCCESS_COUNT); + register!(COMPRESSED_ACCOUNT_FETCHES_FAILED_COUNT); + register!(COMPRESSED_ACCOUNT_FETCHES_FOUND_COUNT); + register!(COMPRESSED_ACCOUNT_FETCHES_NOT_FOUND_COUNT); register!(PER_PROGRAM_ACCOUNT_FETCH_STATS); register!(UNDELEGATION_REQUESTED_COUNT); register!(UNDELEGATION_COMPLETED_COUNT); @@ -482,6 +530,7 @@ pub(crate) fn register() { register!(MAX_LOCK_CONTENTION_QUEUE_SIZE); register!(REMOTE_ACCOUNT_PROVIDER_A_COUNT); register!(TASK_INFO_FETCHER_A_COUNT); + register!(TASK_INFO_FETCHER_COMPRESSED_COUNT); register!(TABLE_MANIA_A_COUNT); register!(TABLE_MANIA_CLOSED_A_COUNT); register!(CONNECTED_PUBSUB_CLIENTS_GAUGE); @@ -676,6 +725,32 @@ pub fn inc_account_fetches_not_found( .inc_by(count); } +pub fn inc_compressed_account_fetches_success(count: u64) { + COMPRESSED_ACCOUNT_FETCHES_SUCCESS_COUNT.inc_by(count); +} + +pub fn inc_compressed_account_fetches_failed(count: u64) { + COMPRESSED_ACCOUNT_FETCHES_FAILED_COUNT.inc_by(count); +} + +pub fn inc_compressed_account_fetches_found( + fetch_origin: AccountFetchOrigin, + count: u64, +) { + COMPRESSED_ACCOUNT_FETCHES_FOUND_COUNT + .with_label_values(&[fetch_origin.value()]) + .inc_by(count); +} + +pub fn inc_compressed_account_fetches_not_found( + fetch_origin: AccountFetchOrigin, + count: u64, +) { + COMPRESSED_ACCOUNT_FETCHES_NOT_FOUND_COUNT + .with_label_values(&[fetch_origin.value()]) + .inc_by(count); +} + pub fn inc_program_subscription_account_updates_count( client_id: &impl LabelValue, ) { @@ -722,6 +797,10 @@ pub fn inc_task_info_fetcher_a_count() { TASK_INFO_FETCHER_A_COUNT.inc() } +pub fn inc_task_info_fetcher_compressed_count() { + TASK_INFO_FETCHER_COMPRESSED_COUNT.inc() +} + pub fn inc_table_mania_a_count() { TABLE_MANIA_A_COUNT.inc() } diff --git a/magicblock-rpc-client/src/lib.rs b/magicblock-rpc-client/src/lib.rs index fe06bc3a1..bba4dc98b 100644 --- a/magicblock-rpc-client/src/lib.rs +++ b/magicblock-rpc-client/src/lib.rs @@ -247,6 +247,10 @@ impl MagicblockRpcClient { Self { client } } + pub fn url(&self) -> String { + self.client.url() + } + pub async fn get_latest_blockhash( &self, ) -> MagicBlockRpcClientResult { diff --git a/programs/elfs/guinea.so b/programs/elfs/guinea.so index 62c6cedb4..2528e2e6d 100755 Binary files a/programs/elfs/guinea.so and b/programs/elfs/guinea.so differ diff --git a/programs/magicblock/src/magic_context.rs b/programs/magicblock/src/magic_context.rs index f36893873..f47b57dd2 100644 --- a/programs/magicblock/src/magic_context.rs +++ b/programs/magicblock/src/magic_context.rs @@ -47,7 +47,7 @@ impl MagicContext { pub fn has_scheduled_commits(data: &[u8]) -> bool { // Currently we only store a vec of scheduled commits in the MagicContext - // The first 8 bytes contain the length of the vec + // The first bytes 8..16 contain the length of the vec // This works even if the length is actually stored as a u32 // since we zero out the entire context whenever we update the vec !is_zeroed(&data[8..16]) diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 4f2b8cc18..d5b9003e8 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -110,6 +110,14 @@ impl ScheduledBaseIntent { pub fn is_empty(&self) -> bool { self.base_intent.is_empty() } + + pub fn is_compressed(&self) -> bool { + matches!( + &self.base_intent, + MagicBaseIntent::CompressedCommit(_) + | MagicBaseIntent::CompressedCommitAndUndelegate(_) + ) + } } // BaseIntent user wants to send to base layer @@ -119,6 +127,8 @@ pub enum MagicBaseIntent { BaseActions(Vec), Commit(CommitType), CommitAndUndelegate(CommitAndUndelegate), + CompressedCommit(CommitType), + CompressedCommitAndUndelegate(CommitAndUndelegate), } impl MagicBaseIntent { @@ -151,6 +161,8 @@ impl MagicBaseIntent { MagicBaseIntent::BaseActions(_) => false, MagicBaseIntent::Commit(_) => false, MagicBaseIntent::CommitAndUndelegate(_) => true, + MagicBaseIntent::CompressedCommit(_) => false, + MagicBaseIntent::CompressedCommitAndUndelegate(_) => true, } } @@ -161,6 +173,12 @@ impl MagicBaseIntent { MagicBaseIntent::CommitAndUndelegate(t) => { Some(t.get_committed_accounts()) } + MagicBaseIntent::CompressedCommit(t) => { + Some(t.get_committed_accounts()) + } + MagicBaseIntent::CompressedCommitAndUndelegate(t) => { + Some(t.get_committed_accounts()) + } } } @@ -173,6 +191,12 @@ impl MagicBaseIntent { MagicBaseIntent::CommitAndUndelegate(t) => { Some(t.get_committed_accounts_mut()) } + MagicBaseIntent::CompressedCommit(t) => { + Some(t.get_committed_accounts_mut()) + } + MagicBaseIntent::CompressedCommitAndUndelegate(t) => { + Some(t.get_committed_accounts_mut()) + } } } @@ -187,6 +211,8 @@ impl MagicBaseIntent { MagicBaseIntent::BaseActions(actions) => actions.is_empty(), MagicBaseIntent::Commit(t) => t.is_empty(), MagicBaseIntent::CommitAndUndelegate(t) => t.is_empty(), + MagicBaseIntent::CompressedCommit(t) => t.is_empty(), + MagicBaseIntent::CompressedCommitAndUndelegate(t) => t.is_empty(), } } } diff --git a/programs/magicblock/src/magicblock_processor.rs b/programs/magicblock/src/magicblock_processor.rs index a555da4dd..4afe94b48 100644 --- a/programs/magicblock/src/magicblock_processor.rs +++ b/programs/magicblock/src/magicblock_processor.rs @@ -50,6 +50,15 @@ declare_process_instruction!( invoke_context, ProcessScheduleCommitOptions { request_undelegation: false, + compressed: false, + }, + ), + ScheduleCompressedCommit => process_schedule_commit( + signers, + invoke_context, + ProcessScheduleCommitOptions { + request_undelegation: false, + compressed: true, }, ), ScheduleCommitAndUndelegate => process_schedule_commit( @@ -57,6 +66,15 @@ declare_process_instruction!( invoke_context, ProcessScheduleCommitOptions { request_undelegation: true, + compressed: false, + }, + ), + ScheduleCompressedCommitAndUndelegate => process_schedule_commit( + signers, + invoke_context, + ProcessScheduleCommitOptions { + request_undelegation: true, + compressed: true, }, ), AcceptScheduleCommits => { diff --git a/programs/magicblock/src/mutate_accounts/process_mutate_accounts.rs b/programs/magicblock/src/mutate_accounts/process_mutate_accounts.rs index 8deb32296..b7e42797e 100644 --- a/programs/magicblock/src/mutate_accounts/process_mutate_accounts.rs +++ b/programs/magicblock/src/mutate_accounts/process_mutate_accounts.rs @@ -212,6 +212,14 @@ pub(crate) fn process_mutate_accounts( ); account.borrow_mut().set_delegated(delegated); } + if let Some(compressed) = modification.compressed { + ic_msg!( + invoke_context, + "MutateAccounts: setting compressed to {}", + compressed + ); + account.borrow_mut().set_compressed(compressed); + } if let Some(confined) = modification.confined { ic_msg!( invoke_context, @@ -329,6 +337,7 @@ mod tests { executable: Some(true), data: Some(vec![1, 2, 3, 4, 5]), delegated: Some(true), + compressed: Some(true), confined: Some(true), remote_slot: None, }; @@ -376,6 +385,7 @@ mod tests { let modified_account: AccountSharedData = accounts.drain(0..1).next().unwrap(); assert!(modified_account.delegated()); + assert!(modified_account.compressed()); assert!(modified_account.confined()); assert_matches!( modified_account.into(), diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs index fd26282c9..47743dac8 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs @@ -27,6 +27,7 @@ use crate::{ #[derive(Default)] pub(crate) struct ProcessScheduleCommitOptions { pub request_undelegation: bool, + pub compressed: bool, } pub(crate) fn process_schedule_commit( @@ -192,6 +193,21 @@ pub(crate) fn process_schedule_commit( committed_accounts.push(committed); } + // TODO(dode): handle compressed commit with lamports + // Currently, compressed accounts are assumed to always have 0 lamports. + // Undelegating a compressed account with lamports would cause users to pay more than rent-exemption. + if opts.compressed + && committed_accounts + .iter() + .any(|acc| acc.account.lamports > 0) + { + ic_msg!( + invoke_context, + "ScheduleCommit: compressed commit is not supported for accounts with lamports", + ); + return Err(InstructionError::InvalidAccountData); + } + if opts.request_undelegation { // If the account is scheduled to be undelegated then we need to lock it // immediately in order to prevent the following actions: @@ -251,13 +267,25 @@ pub(crate) fn process_schedule_commit( InstructionUtils::scheduled_commit_sent(intent_id, blockhash); let commit_sent_sig = action_sent_transaction.signatures[0]; - let base_intent = if opts.request_undelegation { - MagicBaseIntent::CommitAndUndelegate(CommitAndUndelegate { - commit_action: CommitType::Standalone(committed_accounts), - undelegate_action: UndelegateType::Standalone, - }) - } else { - MagicBaseIntent::Commit(CommitType::Standalone(committed_accounts)) + let base_intent = match (opts.request_undelegation, opts.compressed) { + (true, true) => MagicBaseIntent::CompressedCommitAndUndelegate( + CommitAndUndelegate { + commit_action: CommitType::Standalone(committed_accounts), + undelegate_action: UndelegateType::Standalone, + }, + ), + (true, false) => { + MagicBaseIntent::CommitAndUndelegate(CommitAndUndelegate { + commit_action: CommitType::Standalone(committed_accounts), + undelegate_action: UndelegateType::Standalone, + }) + } + (false, true) => MagicBaseIntent::CompressedCommit( + CommitType::Standalone(committed_accounts), + ), + (false, false) => { + MagicBaseIntent::Commit(CommitType::Standalone(committed_accounts)) + } }; let scheduled_base_intent = ScheduledBaseIntent { id: intent_id, diff --git a/programs/magicblock/src/utils/instruction_utils.rs b/programs/magicblock/src/utils/instruction_utils.rs index eb42ab0ba..fb9727b8e 100644 --- a/programs/magicblock/src/utils/instruction_utils.rs +++ b/programs/magicblock/src/utils/instruction_utils.rs @@ -43,17 +43,38 @@ impl InstructionUtils { payer: &Pubkey, pdas: Vec, ) -> Instruction { - let mut account_metas = vec![ - AccountMeta::new(*payer, true), - AccountMeta::new(MAGIC_CONTEXT_PUBKEY, false), - ]; - for pubkey in &pdas { - account_metas.push(AccountMeta::new_readonly(*pubkey, true)); - } - Instruction::new_with_bincode( - crate::id(), - &MagicBlockInstruction::ScheduleCommit, - account_metas, + schedule_commit_instruction_helper( + payer, + pdas, + MagicBlockInstruction::ScheduleCommit, + ) + } + + // ----------------- + // Schedule Compressed Commit + // ----------------- + #[cfg(test)] + pub fn schedule_compressed_commit( + payer: &Keypair, + pubkeys: Vec, + recent_blockhash: Hash, + ) -> Transaction { + let ix = Self::schedule_compressed_commit_instruction( + &payer.pubkey(), + pubkeys, + ); + Self::into_transaction(payer, ix, recent_blockhash) + } + + #[cfg(test)] + pub(crate) fn schedule_compressed_commit_instruction( + payer: &Pubkey, + pdas: Vec, + ) -> Instruction { + schedule_commit_instruction_helper( + payer, + pdas, + MagicBlockInstruction::ScheduleCompressedCommit, ) } @@ -78,17 +99,37 @@ impl InstructionUtils { payer: &Pubkey, pdas: Vec, ) -> Instruction { - let mut account_metas = vec![ - AccountMeta::new(*payer, true), - AccountMeta::new(MAGIC_CONTEXT_PUBKEY, false), - ]; - for pubkey in &pdas { - account_metas.push(AccountMeta::new(*pubkey, true)); - } - Instruction::new_with_bincode( - crate::id(), - &MagicBlockInstruction::ScheduleCommitAndUndelegate, - account_metas, + schedule_commit_instruction_helper( + payer, + pdas, + MagicBlockInstruction::ScheduleCommitAndUndelegate, + ) + } + // ----------------- + // Schedule Compressed Commit and Undelegate + // ----------------- + #[cfg(test)] + pub fn schedule_compressed_commit_and_undelegate( + payer: &Keypair, + pubkeys: Vec, + recent_blockhash: Hash, + ) -> Transaction { + let ix = Self::schedule_compressed_commit_and_undelegate_instruction( + &payer.pubkey(), + pubkeys, + ); + Self::into_transaction(payer, ix, recent_blockhash) + } + + #[cfg(test)] + pub(crate) fn schedule_compressed_commit_and_undelegate_instruction( + payer: &Pubkey, + pdas: Vec, + ) -> Instruction { + schedule_commit_instruction_helper( + payer, + pdas, + MagicBlockInstruction::ScheduleCompressedCommitAndUndelegate, ) } @@ -182,6 +223,7 @@ impl InstructionUtils { .data .map(set_account_mod_data), delegated: account_modification.delegated, + compressed: account_modification.compressed, confined: account_modification.confined, remote_slot: account_modification.remote_slot, }; @@ -311,3 +353,29 @@ impl InstructionUtils { ) } } + +/// Schedule commit instructions use exactly the same accounts +#[cfg(test)] +fn schedule_commit_instruction_helper( + payer: &Pubkey, + pdas: Vec, + instruction: MagicBlockInstruction, +) -> Instruction { + let mut account_metas = vec![ + AccountMeta::new(*payer, true), + AccountMeta::new(MAGIC_CONTEXT_PUBKEY, false), + ]; + let is_undelegation = matches!( + instruction, + MagicBlockInstruction::ScheduleCommitAndUndelegate + | MagicBlockInstruction::ScheduleCompressedCommitAndUndelegate + ); + for pubkey in &pdas { + if is_undelegation { + account_metas.push(AccountMeta::new(*pubkey, false)); + } else { + account_metas.push(AccountMeta::new_readonly(*pubkey, true)); + } + } + Instruction::new_with_bincode(crate::id(), &instruction, account_metas) +} diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index 432353beb..76203a253 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -86,7 +86,7 @@ dependencies = [ "solana-hash", "solana-message", "solana-packet", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-short-vec", "solana-signature", @@ -126,6 +126,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-sized" +version = "1.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.112", +] + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -223,14 +233,17 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] [[package]] name = "ark-bn254" @@ -238,9 +251,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" dependencies = [ - "ark-ec", - "ark-ff", - "ark-std", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", ] [[package]] @@ -249,10 +273,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", "itertools 0.10.5", @@ -260,16 +284,37 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash 0.8.12", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe 0.6.0", + "fnv", + "hashbrown 0.15.2", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "digest 0.10.7", "itertools 0.10.5", @@ -280,6 +325,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe 0.6.0", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "zeroize", +] + [[package]] name = "ark-ff-asm" version = "0.4.2" @@ -290,6 +355,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.112", +] + [[package]] name = "ark-ff-macros" version = "0.4.2" @@ -303,27 +378,68 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.112", +] + [[package]] name = "ark-poly" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", ] +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash 0.8.12", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe 0.6.0", + "fnv", + "hashbrown 0.15.2", +] + [[package]] name = "ark-serialize" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ - "ark-serialize-derive", - "ark-std", + "ark-serialize-derive 0.4.2", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", + "arrayvec", "digest 0.10.7", "num-bigint 0.4.6", ] @@ -339,6 +455,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.112", +] + [[package]] name = "ark-std" version = "0.4.0" @@ -349,6 +476,17 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", + "rayon", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -438,9 +576,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener 5.4.1", "event-listener-strategy", @@ -466,7 +604,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -477,7 +615,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -639,7 +777,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -657,7 +795,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -780,7 +918,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -875,22 +1013,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -936,9 +1074,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "jobserver", @@ -981,7 +1119,7 @@ checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -1059,7 +1197,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -1113,6 +1251,34 @@ dependencies = [ "memchr", ] +[[package]] +name = "compressed-delegation-api" +version = "0.1.0" +dependencies = [ + "borsh 0.10.4", + "light-compressed-account", + "light-hasher", + "light-sdk", + "solana-pubkey 2.2.1", +] + +[[package]] +name = "compressed-delegation-client" +version = "0.6.1" +dependencies = [ + "borsh 0.10.4", + "compressed-delegation-api", + "light-compressed-account", + "light-hasher", + "light-sdk", + "light-sdk-types", + "solana-account-info", + "solana-cpi", + "solana-instruction 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", +] + [[package]] name = "compression-codecs" version = "0.4.35" @@ -1352,7 +1518,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -1376,7 +1542,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -1387,7 +1553,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -1453,24 +1619,24 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 2.0.111", + "syn 2.0.112", "unicode-xid", ] @@ -1529,7 +1695,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -1552,7 +1718,7 @@ checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -1614,12 +1780,24 @@ version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" dependencies = [ - "enum-ordinalize", + "enum-ordinalize 3.1.15", "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize 4.3.2", + "proc-macro2", + "quote", + "syn 2.0.112", +] + [[package]] name = "either" version = "1.15.0" @@ -1658,7 +1836,7 @@ checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -1671,7 +1849,27 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.112", ] [[package]] @@ -1704,11 +1902,11 @@ dependencies = [ "solana-account", "solana-account-info", "solana-cpi", - "solana-instruction", - "solana-program-error", + "solana-instruction 2.2.1", + "solana-program-error 2.2.2", "solana-program-memory", - "solana-pubkey", - "solana-system-interface", + "solana-pubkey 2.2.1", + "solana-system-interface 1.0.0", "solana-sysvar", ] @@ -1862,7 +2060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.59.0", ] @@ -1906,9 +2104,18 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + +[[package]] +name = "five8" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "23f76610e969fa1784327ded240f1e28a3fd9520c9cec93b636fcf62dd37f772" +dependencies = [ + "five8_core 1.0.0", +] [[package]] name = "five8_const" @@ -1916,7 +2123,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26dec3da8bc3ef08f2c04f61eab298c3ab334523e55f076354d6d6f613799a7b" dependencies = [ - "five8_core", + "five8_core 0.1.2", +] + +[[package]] +name = "five8_const" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0f1728185f277989ca573a402716ae0beaaea3f76a8ff87ef9dd8fb19436c5" +dependencies = [ + "five8_core 1.0.0", ] [[package]] @@ -1925,12 +2141,24 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5" +[[package]] +name = "five8_core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059c31d7d36c43fe39d89e55711858b4da8be7eb6dabac23c7289b1a19489406" + [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.5" @@ -2088,7 +2316,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -2210,7 +2438,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -2239,6 +2467,21 @@ dependencies = [ "spinning_top", ] +[[package]] +name = "groth16-solana" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6d1ffb18dbf5cfc60b11bd7da88474c672870247c1e5b498619bcb6ba3d8f5" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "num-bigint 0.4.6", + "solana-bn254", + "thiserror 1.0.69", +] + [[package]] name = "guinea" version = "0.6.1" @@ -2377,7 +2620,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.6", "rand 0.8.5", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "sha2 0.10.9", @@ -2604,6 +2847,22 @@ dependencies = [ "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls 0.23.35", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", +] + [[package]] name = "hyper-timeout" version = "0.5.2" @@ -2630,12 +2889,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -2643,12 +2919,16 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "hyper 1.8.1", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2 0.6.1", + "system-configuration 0.6.1", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2880,7 +3160,7 @@ name = "integration-test-tools" version = "0.0.0" dependencies = [ "anyhow", - "borsh 1.6.0", + "borsh 0.10.4", "color-backtrace", "magicblock-config", "magicblock-core", @@ -2888,7 +3168,8 @@ dependencies = [ "random-port", "rayon", "serde", - "solana-pubkey", + "shlex", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-rpc-client-api", "solana-sdk", @@ -2906,6 +3187,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2940,11 +3231,29 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jni" @@ -3048,13 +3357,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.6.0", + "redox_syscall 0.7.0", ] [[package]] @@ -3143,15 +3452,381 @@ dependencies = [ ] [[package]] -name = "light-poseidon" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +name = "light-account-checks" +version = "0.3.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "solana-account-info", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "solana-sysvar", + "thiserror 2.0.17", +] + +[[package]] +name = "light-batched-merkle-tree" +version = "0.3.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "aligned-sized", + "borsh 0.10.4", + "light-account-checks", + "light-bloom-filter", + "light-compressed-account", + "light-hasher", + "light-macros", + "light-merkle-tree-metadata", + "light-verifier", + "light-zero-copy", + "solana-account-info", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "solana-sysvar", + "thiserror 2.0.17", + "zerocopy", +] + +[[package]] +name = "light-bloom-filter" +version = "0.3.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "bitvec", + "num-bigint 0.4.6", + "solana-nostd-keccak", + "solana-program-error 2.2.2", + "thiserror 2.0.17", +] + +[[package]] +name = "light-bounded-vec" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58cfa375d028164719e3ffef93d2e5c27855cc8a5bb5bf257b868d17c12a3e66" +dependencies = [ + "bytemuck", + "memoffset", + "solana-program-error 2.2.2", + "thiserror 1.0.69", +] + +[[package]] +name = "light-client" +version = "0.13.1" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "async-trait", + "base64 0.13.1", + "borsh 0.10.4", + "bs58", + "bytemuck", + "lazy_static", + "light-compressed-account", + "light-concurrent-merkle-tree", + "light-hasher", + "light-indexed-merkle-tree", + "light-merkle-tree-metadata", + "light-prover-client", + "light-sdk", + "num-bigint 0.4.6", + "num-traits", + "photon-api", + "rand 0.8.5", + "solana-account", + "solana-account-decoder-client-types", + "solana-address-lookup-table-interface", + "solana-clock", + "solana-commitment-config", + "solana-compute-budget-interface", + "solana-epoch-info", + "solana-hash", + "solana-instruction 2.2.1", + "solana-keypair", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "solana-rpc-client", + "solana-rpc-client-api", + "solana-signature", + "solana-transaction", + "solana-transaction-error", + "solana-transaction-status-client-types", + "thiserror 2.0.17", + "tokio", + "tracing", +] + +[[package]] +name = "light-compressed-account" +version = "0.3.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "borsh 0.10.4", + "bytemuck", + "light-hasher", + "light-macros", + "light-program-profiler", + "light-zero-copy", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "thiserror 2.0.17", + "zerocopy", +] + +[[package]] +name = "light-concurrent-merkle-tree" +version = "2.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "borsh 0.10.4", + "light-bounded-vec", + "light-hasher", + "memoffset", + "solana-program-error 2.2.2", + "thiserror 2.0.17", +] + +[[package]] +name = "light-hasher" +version = "3.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "arrayvec", + "borsh 0.10.4", + "light-poseidon 0.3.0", + "num-bigint 0.4.6", + "sha2 0.10.9", + "sha3", + "solana-nostd-keccak", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "thiserror 2.0.17", +] + +[[package]] +name = "light-indexed-array" +version = "0.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "light-hasher", + "num-bigint 0.4.6", + "num-traits", + "thiserror 2.0.17", +] + +[[package]] +name = "light-indexed-merkle-tree" +version = "2.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "light-bounded-vec", + "light-concurrent-merkle-tree", + "light-hasher", + "light-merkle-tree-reference", + "num-bigint 0.4.6", + "num-traits", + "solana-program-error 2.2.2", + "thiserror 2.0.17", +] + +[[package]] +name = "light-macros" +version = "2.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "syn 2.0.112", +] + +[[package]] +name = "light-merkle-tree-metadata" +version = "0.3.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "borsh 0.10.4", + "bytemuck", + "light-compressed-account", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-sysvar", + "thiserror 2.0.17", + "zerocopy", +] + +[[package]] +name = "light-merkle-tree-reference" +version = "2.0.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "light-hasher", + "light-indexed-array", + "num-bigint 0.4.6", + "num-traits", + "thiserror 2.0.17", +] + +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254 0.4.0", + "ark-ff 0.4.2", + "num-bigint 0.4.6", + "thiserror 1.0.69", +] + +[[package]] +name = "light-poseidon" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3d87542063daaccbfecd78b60f988079b6ec4e089249658b9455075c78d42" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "num-bigint 0.4.6", + "thiserror 1.0.69", +] + +[[package]] +name = "light-profiler-macro" +version = "0.1.0" +source = "git+https://github.com/Lightprotocol/light-program-profiler?rev=36a75e14f54dd862bf2f338c97435ffc7e3e8de9#36a75e14f54dd862bf2f338c97435ffc7e3e8de9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.112", +] + +[[package]] +name = "light-program-profiler" +version = "0.1.0" +source = "git+https://github.com/Lightprotocol/light-program-profiler?rev=36a75e14f54dd862bf2f338c97435ffc7e3e8de9#36a75e14f54dd862bf2f338c97435ffc7e3e8de9" +dependencies = [ + "light-profiler-macro", +] + +[[package]] +name = "light-prover-client" +version = "2.0.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "ark-bn254 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "light-hasher", + "light-indexed-array", + "light-sparse-merkle-tree", + "num-bigint 0.4.6", + "num-traits", + "reqwest 0.11.27", + "serde", + "serde_json", + "solana-bn254", + "thiserror 2.0.17", + "tokio", + "tracing", +] + +[[package]] +name = "light-sdk" +version = "0.13.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "borsh 0.10.4", + "light-account-checks", + "light-compressed-account", + "light-hasher", + "light-macros", + "light-sdk-macros", + "light-sdk-types", + "light-zero-copy", + "num-bigint 0.4.6", + "solana-account-info", + "solana-cpi", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "thiserror 2.0.17", +] + +[[package]] +name = "light-sdk-macros" +version = "0.13.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "light-hasher", + "light-poseidon 0.3.0", + "proc-macro2", + "quote", + "solana-pubkey 2.2.1", + "syn 2.0.112", +] + +[[package]] +name = "light-sdk-types" +version = "0.13.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "borsh 0.10.4", + "light-account-checks", + "light-compressed-account", + "light-hasher", + "light-macros", + "light-zero-copy", + "solana-msg 2.2.1", + "thiserror 2.0.17", +] + +[[package]] +name = "light-sparse-merkle-tree" +version = "0.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "light-hasher", + "light-indexed-array", + "num-bigint 0.4.6", + "num-traits", + "thiserror 2.0.17", +] + +[[package]] +name = "light-verifier" +version = "2.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "groth16-solana", + "light-compressed-account", + "thiserror 2.0.17", +] + +[[package]] +name = "light-zero-copy" +version = "0.2.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "light-zero-copy-derive", + "solana-program-error 2.2.2", + "zerocopy", +] + +[[package]] +name = "light-zero-copy-derive" +version = "0.1.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" dependencies = [ - "ark-bn254", - "ark-ff", - "num-bigint 0.4.6", - "thiserror 1.0.69", + "lazy_static", + "proc-macro2", + "quote", + "syn 2.0.112", ] [[package]] @@ -3252,7 +3927,7 @@ dependencies = [ "borsh 1.6.0", "bytemuck_derive", "solana-program", - "solana-system-interface", + "solana-system-interface 3.0.0", ] [[package]] @@ -3273,10 +3948,10 @@ dependencies = [ "rand 0.9.2", "solana-account", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-loader-v3-interface 3.0.0", "solana-loader-v4-interface", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-signature", "solana-signer", @@ -3299,7 +3974,7 @@ dependencies = [ "magicblock-core", "magicblock-program", "solana-hash", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-transaction", "solana-transaction-error", "thiserror 1.0.69", @@ -3320,7 +3995,7 @@ dependencies = [ "parking_lot", "reflink-copy", "solana-account", - "solana-pubkey", + "solana-pubkey 2.2.1", "thiserror 1.0.69", "tracing", ] @@ -3359,7 +4034,7 @@ dependencies = [ "solana-fee-structure", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client-api", "solana-signature", "solana-system-transaction", @@ -3380,6 +4055,7 @@ dependencies = [ "anyhow", "borsh 1.6.0", "fd-lock", + "light-client", "magic-domain-program", "magicblock-account-cloner", "magicblock-accounts", @@ -3407,12 +4083,12 @@ dependencies = [ "solana-genesis-config", "solana-hash", "solana-inline-spl", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", "solana-native-token", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-rpc-client", "solana-sha256-hasher", @@ -3435,8 +4111,11 @@ dependencies = [ "arc-swap", "async-trait", "bincode", + "borsh 0.10.4", + "compressed-delegation-client", "futures-util", "helius-laserstream", + "light-client", "lru", "magicblock-config", "magicblock-core", @@ -3453,20 +4132,20 @@ dependencies = [ "solana-commitment-config", "solana-feature-set", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-loader-v3-interface 3.0.0", "solana-loader-v4-interface", "solana-message", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-pubsub-client", "solana-rpc-client", "solana-rpc-client-api", "solana-sdk-ids", "solana-signature", "solana-signer", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-sysvar", "solana-transaction", "solana-transaction-error", @@ -3484,11 +4163,11 @@ dependencies = [ name = "magicblock-committor-program" version = "0.6.1" dependencies = [ - "borsh 1.6.0", + "borsh 0.10.4", "paste", "solana-account", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "thiserror 1.0.69", ] @@ -3499,11 +4178,16 @@ dependencies = [ "async-trait", "base64 0.21.7", "bincode", - "borsh 1.6.0", + "borsh 0.10.4", + "compressed-delegation-client", "dyn-clone", "futures-util", + "light-client", + "light-sdk", "lru", "magicblock-committor-program", + "magicblock-config", + "magicblock-core", "magicblock-delegation-program 1.1.3 (git+https://github.com/magicblock-labs/delegation-program.git?rev=1874b4f5f5f55cb9ab54b64de2cc0d41107d1435)", "magicblock-metrics", "magicblock-program", @@ -3516,11 +4200,11 @@ dependencies = [ "solana-commitment-config", "solana-compute-budget-interface", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-rpc-client-api", "solana-signature", @@ -3548,7 +4232,7 @@ dependencies = [ "serde", "serde_with", "solana-keypair", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signer", "toml 0.8.23", "url", @@ -3558,13 +4242,16 @@ dependencies = [ name = "magicblock-core" version = "0.6.1" dependencies = [ + "compressed-delegation-client", "flume", + "light-compressed-account", + "light-sdk", "magicblock-magic-program-api 0.6.1", "solana-account", "solana-account-decoder", "solana-hash", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-transaction", "solana-transaction-context", @@ -3605,7 +4292,7 @@ dependencies = [ "pinocchio-pubkey", "pinocchio-system", "rkyv 0.7.45", - "solana-curve25519", + "solana-curve25519 3.1.5", "solana-program", "solana-security-txt", "static_assertions", @@ -3634,12 +4321,12 @@ dependencies = [ "solana-account-decoder", "solana-clock", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-measure", "solana-message", "solana-metrics", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-signer", "solana-storage-proto", @@ -3709,7 +4396,7 @@ dependencies = [ "solana-loader-v4-program", "solana-program", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent-collector", "solana-sdk-ids", "solana-svm 2.2.1 (git+https://github.com/magicblock-labs/magicblock-svm.git?rev=3e9456ec4)", @@ -3740,11 +4427,11 @@ dependencies = [ "solana-clock", "solana-fee-calculator", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-log-collector", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-seed-derivable", "solana-signature", @@ -3765,8 +4452,8 @@ dependencies = [ "solana-clock", "solana-commitment-config", "solana-hash", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-rpc-client-api", "solana-signature", @@ -3790,10 +4477,10 @@ dependencies = [ "solana-clock", "solana-commitment-config", "solana-compute-budget-interface", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-signer", "solana-slot-hashes", @@ -3815,9 +4502,9 @@ dependencies = [ "magicblock-ledger", "magicblock-program", "rusqlite", - "solana-instruction", + "solana-instruction 2.2.1", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-rpc-client-api", "solana-signature", @@ -3857,7 +4544,7 @@ dependencies = [ "solana-feature-set", "solana-frozen-abi-macro", "solana-rpc-client-api", - "solana-sanitize", + "solana-sanitize 2.2.1", ] [[package]] @@ -4017,6 +4704,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "munge" version = "0.4.7" @@ -4034,7 +4727,7 @@ checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -4055,7 +4748,7 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", "security-framework 2.11.1", @@ -4170,6 +4863,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "serde", ] [[package]] @@ -4196,7 +4890,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -4279,7 +4973,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -4362,7 +5056,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -4371,6 +5065,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "openssl-sys" version = "0.9.111" @@ -4466,7 +5166,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -4499,10 +5199,34 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset", + "fixedbitset 0.4.2", + "indexmap 2.12.1", +] + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset 0.5.7", "indexmap 2.12.1", ] +[[package]] +name = "photon-api" +version = "0.51.0" +source = "git+https://github.com/magicblock-labs/light-protocol?rev=849e6aa#849e6aae59b6afc6fa03872c2a8c6fb312be12f1" +dependencies = [ + "reqwest 0.12.28", + "serde", + "serde_derive", + "serde_json", + "serde_with", + "url", + "uuid", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -4520,7 +5244,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -4567,7 +5291,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0225638cadcbebae8932cb7f49cb5da7c15c21beb19f048f05a5ca7d93f065" dependencies = [ - "five8_const", + "five8_const 0.1.4", "pinocchio", "sha2-const-stable", ] @@ -4602,9 +5326,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "potential_utf" @@ -4672,12 +5396,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.36" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -4695,7 +5419,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.9", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -4721,9 +5445,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -4736,7 +5460,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", "version_check", "yansi", ] @@ -4746,8 +5470,13 @@ name = "program-flexi-counter" version = "0.0.0" dependencies = [ "bincode", - "borsh 1.6.0", + "borsh 0.10.4", + "compressed-delegation-client", "ephemeral-rollups-sdk", + "light-batched-merkle-tree", + "light-hasher", + "light-sdk", + "light-sdk-types", "magicblock-magic-program-api 0.6.1", "serde", "solana-program", @@ -4761,7 +5490,7 @@ dependencies = [ "solana-program-test", "solana-sdk", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", "tokio", ] @@ -4769,7 +5498,7 @@ dependencies = [ name = "program-schedulecommit" version = "0.0.0" dependencies = [ - "borsh 1.6.0", + "borsh 0.10.4", "ephemeral-rollups-sdk", "magicblock-delegation-program 1.1.3 (git+https://github.com/magicblock-labs/delegation-program.git?rev=1874b4f5f5f55cb9ab54b64de2cc0d41107d1435)", "magicblock-magic-program-api 0.6.1", @@ -4782,7 +5511,7 @@ dependencies = [ name = "program-schedulecommit-security" version = "0.0.0" dependencies = [ - "borsh 1.6.0", + "borsh 0.10.4", "ephemeral-rollups-sdk", "program-schedulecommit", "solana-program", @@ -4844,8 +5573,8 @@ dependencies = [ "itertools 0.10.5", "lazy_static", "log", - "multimap", - "petgraph", + "multimap 0.8.3", + "petgraph 0.6.5", "prettyplease 0.1.25", "prost 0.11.9", "prost-types 0.11.9", @@ -4865,14 +5594,14 @@ dependencies = [ "heck 0.5.0", "itertools 0.12.1", "log", - "multimap", + "multimap 0.10.1", "once_cell", - "petgraph", - "prettyplease 0.2.36", + "petgraph 0.6.5", + "prettyplease 0.2.37", "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.111", + "syn 2.0.112", "tempfile", ] @@ -4883,16 +5612,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck 0.5.0", - "itertools 0.12.1", + "itertools 0.14.0", "log", - "multimap", + "multimap 0.10.1", "once_cell", - "petgraph", - "prettyplease 0.2.36", + "petgraph 0.7.1", + "prettyplease 0.2.37", "prost 0.13.5", "prost-types 0.13.5", "regex", - "syn 2.0.111", + "syn 2.0.112", "tempfile", ] @@ -4919,7 +5648,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -4929,10 +5658,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -5014,7 +5743,7 @@ checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -5034,7 +5763,7 @@ checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -5299,9 +6028,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ "bitflags 2.10.0", ] @@ -5323,7 +6052,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -5334,7 +6063,7 @@ checksum = "23bbed272e39c47a095a5242218a67412a220006842558b03fe2935e8f3d7b92" dependencies = [ "cfg-if", "libc", - "rustix 1.1.2", + "rustix 1.1.3", "windows", ] @@ -5398,8 +6127,8 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", - "hyper-rustls", - "hyper-tls", + "hyper-rustls 0.24.2", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -5415,7 +6144,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", "tokio-rustls 0.24.1", @@ -5429,6 +6158,48 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.12", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-tls 0.6.0", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-native-tls", + "tower 0.5.2", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "reqwest-middleware" version = "0.2.5" @@ -5438,7 +6209,7 @@ dependencies = [ "anyhow", "async-trait", "http 0.2.12", - "reqwest", + "reqwest 0.11.27", "serde", "task-local-extensions", "thiserror 1.0.69", @@ -5513,7 +6284,7 @@ checksum = "bd83f5f173ff41e00337d97f6572e416d022ef8a19f371817259ae960324c482" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -5590,9 +6361,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", @@ -5630,11 +6401,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe", + "openssl-probe 0.2.0", "rustls-pki-types", "schannel", "security-framework 3.5.1", @@ -5724,9 +6495,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -5760,7 +6531,7 @@ name = "schedulecommit-client" version = "0.0.0" dependencies = [ "anyhow", - "borsh 1.6.0", + "borsh 0.10.4", "integration-test-tools", "magicblock-core", "magicblock-delegation-program 1.1.3 (git+https://github.com/magicblock-labs/delegation-program.git?rev=1874b4f5f5f55cb9ab54b64de2cc0d41107d1435)", @@ -5777,10 +6548,17 @@ name = "schedulecommit-committor-service" version = "0.0.0" dependencies = [ "async-trait", - "borsh 1.6.0", + "borsh 0.10.4", + "compressed-delegation-client", "futures", + "light-client", + "light-compressed-account", + "light-sdk", + "light-sdk-types", + "magicblock-chainlink", "magicblock-committor-program", "magicblock-committor-service", + "magicblock-core", "magicblock-delegation-program 1.1.3 (git+https://github.com/magicblock-labs/delegation-program.git?rev=1874b4f5f5f55cb9ab54b64de2cc0d41107d1435)", "magicblock-program", "magicblock-rpc-client", @@ -5789,7 +6567,7 @@ dependencies = [ "program-schedulecommit", "rand 0.8.5", "solana-account", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-rpc-client-api", "solana-sdk", @@ -5802,7 +6580,7 @@ dependencies = [ name = "schedulecommit-test-scenarios" version = "0.0.0" dependencies = [ - "borsh 1.6.0", + "borsh 0.10.4", "ephemeral-rollups-sdk", "integration-test-tools", "magicblock-core", @@ -5822,6 +6600,7 @@ dependencies = [ name = "schedulecommit-test-security" version = "0.0.0" dependencies = [ + "borsh 0.10.4", "integration-test-tools", "magicblock-core", "magicblock-magic-program-api 0.6.1", @@ -5846,9 +6625,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -5981,20 +6760,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -6030,7 +6809,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.12.1", "schemars 0.9.0", - "schemars 1.1.0", + "schemars 1.2.0", "serde_core", "serde_json", "serde_with_macros", @@ -6046,7 +6825,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -6072,7 +6851,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -6143,10 +6922,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -6234,8 +7014,8 @@ dependencies = [ "serde_derive", "solana-account-info", "solana-clock", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-sysvar", ] @@ -6261,11 +7041,11 @@ dependencies = [ "solana-config-program", "solana-epoch-schedule", "solana-fee-calculator", - "solana-instruction", + "solana-instruction 2.2.1", "solana-nonce", "solana-program", "solana-program-pack", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-slot-hashes", @@ -6291,7 +7071,7 @@ dependencies = [ "serde_derive", "serde_json", "solana-account", - "solana-pubkey", + "solana-pubkey 2.2.1", "zstd", ] @@ -6303,9 +7083,9 @@ checksum = "e0c17d606a298a205fae325489fbed88ee6dc4463c111672172327e741c8905d" dependencies = [ "bincode", "serde", - "solana-program-error", + "solana-program-error 2.2.2", "solana-program-memory", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -6347,7 +7127,7 @@ dependencies = [ "solana-measure", "solana-metrics", "solana-nohash-hasher", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rayon-threadlimit", "solana-sdk", "solana-svm-transaction", @@ -6357,6 +7137,21 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "solana-address" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37320fd2945c5d654b2c6210624a52d66c3f1f73b653ed211ab91a703b35bdd" +dependencies = [ + "five8", + "five8_const 1.0.0", + "serde", + "serde_derive", + "solana-define-syscall 4.0.1", + "solana-program-error 3.0.0", + "solana-sanitize 3.0.1", +] + [[package]] name = "solana-address-lookup-table-interface" version = "2.2.2" @@ -6368,8 +7163,8 @@ dependencies = [ "serde", "serde_derive", "solana-clock", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-slot-hashes", ] @@ -6389,12 +7184,12 @@ dependencies = [ "solana-bincode", "solana-clock", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-packet", "solana-program-runtime", - "solana-pubkey", - "solana-system-interface", + "solana-pubkey 2.2.1", + "solana-system-interface 1.0.0", "solana-transaction-context", "thiserror 2.0.17", ] @@ -6467,7 +7262,7 @@ checksum = "75db7f2bbac3e62cfd139065d15bcda9e2428883ba61fc8d27ccb251081e7567" dependencies = [ "num-bigint 0.4.6", "num-traits", - "solana-define-syscall", + "solana-define-syscall 2.2.1", ] [[package]] @@ -6478,7 +7273,7 @@ checksum = "19a3787b8cf9c9fe3dd360800e8b70982b9e5a8af9e11c354b6665dd4a003adc" dependencies = [ "bincode", "serde", - "solana-instruction", + "solana-instruction 2.2.1", ] [[package]] @@ -6488,9 +7283,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a0801e25a1b31a14494fc80882a036be0ffd290efc4c2d640bfcca120a4672" dependencies = [ "blake3", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "solana-hash", - "solana-sanitize", + "solana-sanitize 2.2.1", ] [[package]] @@ -6499,12 +7294,12 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9abc69625158faaab02347370b91c0d8e0fe347bf9287239f0fbe8f5864d91da" dependencies = [ - "ark-bn254", - "ark-ec", - "ark-ff", - "ark-serialize", + "ark-bn254 0.4.0", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", "bytemuck", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "thiserror 2.0.17", ] @@ -6537,10 +7332,10 @@ dependencies = [ "solana-clock", "solana-compute-budget", "solana-cpi", - "solana-curve25519", + "solana-curve25519 2.2.1", "solana-feature-set", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keccak-hasher", "solana-loader-v3-interface 3.0.0", "solana-loader-v4-interface", @@ -6552,13 +7347,13 @@ dependencies = [ "solana-program-entrypoint", "solana-program-memory", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sbpf", "solana-sdk-ids", "solana-secp256k1-recover", "solana-sha256-hasher", "solana-stable-layout", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-sysvar", "solana-sysvar-id", "solana-timings", @@ -6583,7 +7378,7 @@ dependencies = [ "rand 0.8.5", "solana-clock", "solana-measure", - "solana-pubkey", + "solana-pubkey 2.2.1", "tempfile", ] @@ -6600,7 +7395,7 @@ dependencies = [ "solana-feature-set", "solana-loader-v4-program", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-stake-program", "solana-system-program", @@ -6625,7 +7420,7 @@ dependencies = [ "solana-config-program", "solana-feature-set", "solana-loader-v4-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-stake-program", "solana-system-program", @@ -6654,11 +7449,11 @@ dependencies = [ "solana-connection-cache", "solana-epoch-info", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-measure", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-pubsub-client", "solana-quic-client", "solana-quic-definitions", @@ -6688,13 +7483,13 @@ dependencies = [ "solana-commitment-config", "solana-epoch-info", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-signer", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction", "solana-transaction-error", ] @@ -6755,9 +7550,9 @@ dependencies = [ "solana-compute-budget", "solana-compute-budget-interface", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-packet", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-svm-transaction", "solana-transaction-error", @@ -6773,7 +7568,7 @@ dependencies = [ "borsh 1.6.0", "serde", "serde_derive", - "solana-instruction", + "solana-instruction 2.2.1", "solana-sdk-ids", ] @@ -6799,15 +7594,15 @@ dependencies = [ "serde_derive", "solana-account", "solana-bincode", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-short-vec", "solana-stake-interface", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction-context", ] @@ -6855,11 +7650,11 @@ dependencies = [ "solana-fee-structure", "solana-metrics", "solana-packet", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-runtime-transaction", "solana-sdk-ids", "solana-svm-transaction", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction-error", "solana-vote-program", ] @@ -6871,10 +7666,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dc71126edddc2ba014622fc32d0f5e2e78ec6c5a1e0eb511b85618c09e9ea11" dependencies = [ "solana-account-info", - "solana-define-syscall", - "solana-instruction", - "solana-program-error", - "solana-pubkey", + "solana-define-syscall 2.2.1", + "solana-instruction 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "solana-stable-layout", ] @@ -6887,7 +7682,21 @@ dependencies = [ "bytemuck", "bytemuck_derive", "curve25519-dalek 4.1.3", - "solana-define-syscall", + "solana-define-syscall 2.2.1", + "subtle", + "thiserror 2.0.17", +] + +[[package]] +name = "solana-curve25519" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebca352e7716ff1a0877272f87c772c958489c1d568a92d318dc0c75939d2884" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "solana-define-syscall 3.0.0", "subtle", "thiserror 2.0.17", ] @@ -6907,6 +7716,18 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf784bb2cb3e02cac9801813c30187344228d2ae952534902108f6150573a33d" +[[package]] +name = "solana-define-syscall" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9697086a4e102d28a156b8d6b521730335d6951bd39a5e766512bbe09007cee" + +[[package]] +name = "solana-define-syscall" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e5b1c0bc1d4a4d10c88a4100499d954c09d3fecfae4912c1a074dff68b1738" + [[package]] name = "solana-derivation-path" version = "2.2.1" @@ -6928,7 +7749,7 @@ dependencies = [ "bytemuck_derive", "ed25519-dalek", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-precompile-error", "solana-sdk-ids", ] @@ -6965,7 +7786,7 @@ checksum = "96c5fd2662ae7574810904585fd443545ed2b568dbd304b25a31e79ccc76e81b" dependencies = [ "siphasher 0.3.11", "solana-hash", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -6992,13 +7813,13 @@ dependencies = [ "solana-address-lookup-table-interface", "solana-clock", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keccak-hasher", "solana-message", "solana-nonce", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", "thiserror 2.0.17", ] @@ -7013,12 +7834,12 @@ dependencies = [ "serde_derive", "solana-account", "solana-account-info", - "solana-instruction", - "solana-program-error", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", ] [[package]] @@ -7031,7 +7852,7 @@ dependencies = [ "lazy_static", "solana-epoch-schedule", "solana-hash", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sha256-hasher", ] @@ -7077,7 +7898,7 @@ checksum = "b83f88a126213cbcb57672c5e70ddb9791eff9b480e9f39fe9285fd2abca66fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -7102,7 +7923,7 @@ dependencies = [ "solana-logger", "solana-native-token", "solana-poh-config", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-sha256-hasher", @@ -7135,7 +7956,7 @@ dependencies = [ "serde", "serde_derive", "solana-atomic-u64", - "solana-sanitize", + "solana-sanitize 2.2.1", "wasm-bindgen", ] @@ -7156,7 +7977,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "951545bd7d0ab4a878cfc7375ac9f1a475cb6936626677b2ba1d25e7b9f3910b" dependencies = [ "bytemuck", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -7172,11 +7993,35 @@ dependencies = [ "num-traits", "serde", "serde_derive", - "solana-define-syscall", - "solana-pubkey", + "solana-define-syscall 2.2.1", + "solana-pubkey 2.2.1", "wasm-bindgen", ] +[[package]] +name = "solana-instruction" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1b699a2c1518028a9982e255e0eca10c44d90006542d9d7f9f40dbce3f7c78" +dependencies = [ + "bincode", + "borsh 1.6.0", + "serde", + "solana-define-syscall 4.0.1", + "solana-instruction-error", + "solana-pubkey 4.0.0", +] + +[[package]] +name = "solana-instruction-error" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b04259e03c05faf38a8c24217b5cfe4c90572ae6184ab49cddb1584fdd756d3f" +dependencies = [ + "num-traits", + "solana-program-error 3.0.0", +] + [[package]] name = "solana-instructions-sysvar" version = "2.2.1" @@ -7185,10 +8030,10 @@ checksum = "427f2d0d6dc0bb49f16cef5e7f975180d2e80aab9bdd3b2af68e2d029ec63f43" dependencies = [ "bitflags 2.10.0", "solana-account-info", - "solana-instruction", - "solana-program-error", - "solana-pubkey", - "solana-sanitize", + "solana-instruction 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-serialize-utils", "solana-sysvar-id", @@ -7201,9 +8046,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7aeb957fbd42a451b99235df4942d96db7ef678e8d5061ef34c9b34cae12f79" dependencies = [ "sha3", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "solana-hash", - "solana-sanitize", + "solana-sanitize 2.2.1", ] [[package]] @@ -7217,7 +8062,7 @@ dependencies = [ "ed25519-dalek-bip32", "rand 0.7.3", "solana-derivation-path", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-seed-derivable", "solana-seed-phrase", "solana-signature", @@ -7259,8 +8104,8 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", ] @@ -7273,10 +8118,10 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", ] [[package]] @@ -7288,8 +8133,8 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", ] @@ -7302,10 +8147,10 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", ] [[package]] @@ -7320,14 +8165,14 @@ dependencies = [ "solana-bincode", "solana-bpf-loader-program", "solana-compute-budget", - "solana-instruction", + "solana-instruction 2.2.1", "solana-loader-v3-interface 3.0.0", "solana-loader-v4-interface", "solana-log-collector", "solana-measure", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sbpf", "solana-sdk-ids", "solana-transaction-context", @@ -7373,12 +8218,12 @@ dependencies = [ "serde_derive", "solana-bincode", "solana-hash", - "solana-instruction", - "solana-pubkey", - "solana-sanitize", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-short-vec", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction-error", "wasm-bindgen", ] @@ -7393,7 +8238,7 @@ dependencies = [ "gethostname", "lazy_static", "log", - "reqwest", + "reqwest 0.11.27", "solana-clock", "solana-cluster-type", "solana-sha256-hasher", @@ -7407,7 +8252,16 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36a1a14399afaabc2781a1db09cb14ee4cc4ee5c7a5a3cfcc601811379a8092" dependencies = [ - "solana-define-syscall", + "solana-define-syscall 2.2.1", +] + +[[package]] +name = "solana-msg" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "264275c556ea7e22b9d3f87d56305546a38d4eee8ec884f3b126236cb7dcbbb4" +dependencies = [ + "solana-define-syscall 3.0.0", ] [[package]] @@ -7454,7 +8308,7 @@ dependencies = [ "serde_derive", "solana-fee-calculator", "solana-hash", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sha256-hasher", ] @@ -7470,6 +8324,15 @@ dependencies = [ "solana-sdk-ids", ] +[[package]] +name = "solana-nostd-keccak" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8ced70920435b1baa58f76e6f84bbc1110ddd1d6161ec76b6d731ae8431e9c4" +dependencies = [ + "sha3", +] + [[package]] name = "solana-offchain-message" version = "2.2.1" @@ -7479,8 +8342,8 @@ dependencies = [ "num_enum", "solana-hash", "solana-packet", - "solana-pubkey", - "solana-sanitize", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", "solana-sha256-hasher", "solana-signature", "solana-signer", @@ -7524,7 +8387,7 @@ dependencies = [ "solana-message", "solana-metrics", "solana-packet", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rayon-threadlimit", "solana-sdk-ids", "solana-short-vec", @@ -7548,9 +8411,9 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ad1ea160d08dc423c35021fa3e437a5783eb256f5ab8bc3024e27db913acf42" dependencies = [ - "ark-bn254", - "light-poseidon", - "solana-define-syscall", + "ark-bn254 0.4.0", + "light-poseidon 0.2.0", + "solana-define-syscall 2.2.1", "thiserror 2.0.17", ] @@ -7575,7 +8438,7 @@ dependencies = [ "solana-feature-set", "solana-message", "solana-precompile-error", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-secp256k1-program", "solana-secp256r1-program", @@ -7587,7 +8450,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81a57a24e6a4125fc69510b6774cd93402b943191b6cddad05de7281491c90fe" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-signer", ] @@ -7627,14 +8490,14 @@ dependencies = [ "solana-clock", "solana-cpi", "solana-decode-error", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "solana-epoch-rewards", "solana-epoch-schedule", "solana-example-mocks", "solana-feature-gate-interface", "solana-fee-calculator", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-instructions-sysvar", "solana-keccak-hasher", "solana-last-restart-slot", @@ -7642,17 +8505,17 @@ dependencies = [ "solana-loader-v3-interface 3.0.0", "solana-loader-v4-interface", "solana-message", - "solana-msg", + "solana-msg 2.2.1", "solana-native-token", "solana-nonce", "solana-program-entrypoint", - "solana-program-error", + "solana-program-error 2.2.2", "solana-program-memory", "solana-program-option", "solana-program-pack", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-sdk-macro", "solana-secp256k1-recover", @@ -7664,7 +8527,7 @@ dependencies = [ "solana-slot-history", "solana-stable-layout", "solana-stake-interface", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-sysvar", "solana-sysvar-id", "solana-vote-interface", @@ -7679,9 +8542,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "473ffe73c68d93e9f2aa726ad2985fe52760052709aaab188100a42c618060ec" dependencies = [ "solana-account-info", - "solana-msg", - "solana-program-error", - "solana-pubkey", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", ] [[package]] @@ -7695,11 +8558,17 @@ dependencies = [ "serde", "serde_derive", "solana-decode-error", - "solana-instruction", - "solana-msg", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-pubkey 2.2.1", ] +[[package]] +name = "solana-program-error" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1af32c995a7b692a915bb7414d5f8e838450cf7c70414e763d8abcae7b51f28" + [[package]] name = "solana-program-memory" version = "2.2.1" @@ -7707,7 +8576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b0268f6c89825fb634a34bd0c3b8fdaeaecfc3728be1d622a8ee6dd577b60d4" dependencies = [ "num-traits", - "solana-define-syscall", + "solana-define-syscall 2.2.1", ] [[package]] @@ -7722,7 +8591,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "319f0ef15e6e12dc37c597faccb7d62525a509fec5f6975ecb9419efddeb277b" dependencies = [ - "solana-program-error", + "solana-program-error 2.2.2", ] [[package]] @@ -7746,13 +8615,13 @@ dependencies = [ "solana-epoch-schedule", "solana-feature-set", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-last-restart-slot", "solana-log-collector", "solana-measure", "solana-metrics", "solana-precompiles", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sbpf", "solana-sdk-ids", @@ -7788,7 +8657,7 @@ dependencies = [ "solana-compute-budget", "solana-feature-set", "solana-inline-spl", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-logger", "solana-program-runtime", @@ -7815,7 +8684,7 @@ dependencies = [ "bytemuck", "bytemuck_derive", "curve25519-dalek 4.1.3", - "five8_const", + "five8_const 0.1.4", "getrandom 0.2.16", "js-sys", "num-traits", @@ -7824,12 +8693,21 @@ dependencies = [ "serde_derive", "solana-atomic-u64", "solana-decode-error", - "solana-define-syscall", - "solana-sanitize", + "solana-define-syscall 2.2.1", + "solana-sanitize 2.2.1", "solana-sha256-hasher", "wasm-bindgen", ] +[[package]] +name = "solana-pubkey" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f7104d456b58e1418c21a8581e89810278d1190f70f27ece7fc0b2c9282a57" +dependencies = [ + "solana-address", +] + [[package]] name = "solana-pubsub-client" version = "2.2.1" @@ -7839,14 +8717,14 @@ dependencies = [ "crossbeam-channel", "futures-util", "log", - "reqwest", + "reqwest 0.11.27", "semver", "serde", "serde_derive", "serde_json", "solana-account-decoder-client-types", "solana-clock", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client-api", "solana-signature", "thiserror 2.0.17", @@ -7877,7 +8755,7 @@ dependencies = [ "solana-measure", "solana-metrics", "solana-net-utils", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-quic-definitions", "solana-rpc-client-api", "solana-signer", @@ -7932,7 +8810,7 @@ dependencies = [ "solana-clock", "solana-epoch-schedule", "solana-genesis-config", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", ] @@ -7943,7 +8821,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f6f9113c6003492e74438d1288e30cffa8ccfdc2ef7b49b9e816d8034da18cd" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-reward-info", ] @@ -7955,7 +8833,7 @@ checksum = "2b293f4246626c0e0a991531f08848a713ada965612e99dc510963f04d12cae7" dependencies = [ "lazy_static", "solana-feature-set", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", ] @@ -7981,7 +8859,7 @@ dependencies = [ "bs58", "indicatif", "log", - "reqwest", + "reqwest 0.11.27", "reqwest-middleware", "semver", "serde", @@ -7995,9 +8873,9 @@ dependencies = [ "solana-epoch-schedule", "solana-feature-gate-interface", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client-api", "solana-signature", "solana-transaction", @@ -8017,7 +8895,7 @@ dependencies = [ "base64 0.22.1", "bs58", "jsonrpc-core", - "reqwest", + "reqwest 0.11.27", "reqwest-middleware", "semver", "serde", @@ -8030,7 +8908,7 @@ dependencies = [ "solana-fee-calculator", "solana-inflation", "solana-inline-spl", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signer", "solana-transaction-error", "solana-transaction-status-client-types", @@ -8049,7 +8927,7 @@ dependencies = [ "solana-hash", "solana-message", "solana-nonce", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-sdk-ids", "thiserror 2.0.17", @@ -8117,7 +8995,7 @@ dependencies = [ "solana-perf", "solana-program", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rayon-threadlimit", "solana-runtime-transaction", "solana-sdk", @@ -8153,7 +9031,7 @@ dependencies = [ "solana-compute-budget-instruction", "solana-hash", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-signature", "solana-svm-transaction", @@ -8168,6 +9046,12 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" +[[package]] +name = "solana-sanitize" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09694a0fc14e5ffb18f9b7b7c0f15ecb6eac5b5610bf76a1853459d19daf9" + [[package]] name = "solana-sbpf" version = "0.10.0" @@ -8213,7 +9097,7 @@ dependencies = [ "solana-genesis-config", "solana-hard-forks", "solana-inflation", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", "solana-native-token", @@ -8226,13 +9110,13 @@ dependencies = [ "solana-presigner", "solana-program", "solana-program-memory", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-quic-definitions", "solana-rent-collector", "solana-rent-debits", "solana-reserved-account-keys", "solana-reward-info", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-sdk-macro", "solana-secp256k1-program", @@ -8262,7 +9146,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5d8b9cc68d5c88b062a33e23a6466722467dde0035152d8fb1afbcdf350a5f" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -8274,7 +9158,7 @@ dependencies = [ "bs58", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -8290,7 +9174,7 @@ dependencies = [ "serde_derive", "sha3", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-precompile-error", "solana-sdk-ids", ] @@ -8303,7 +9187,7 @@ checksum = "baa3120b6cdaa270f39444f5093a90a7b03d296d362878f7a6991d6de3bbe496" dependencies = [ "borsh 1.6.0", "libsecp256k1", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "thiserror 2.0.17", ] @@ -8316,7 +9200,7 @@ dependencies = [ "bytemuck", "openssl", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-precompile-error", "solana-sdk-ids", ] @@ -8393,9 +9277,9 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "817a284b63197d2b27afdba829c5ab34231da4a9b4e763466a003c40ca4f535e" dependencies = [ - "solana-instruction", - "solana-pubkey", - "solana-sanitize", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", + "solana-sanitize 2.2.1", ] [[package]] @@ -8405,7 +9289,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0037386961c0d633421f53560ad7c80675c0447cba4d1bb66d60974dd486c7ea" dependencies = [ "sha2 0.10.9", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "solana-hash", ] @@ -8441,7 +9325,7 @@ dependencies = [ "serde", "serde-big-array", "serde_derive", - "solana-sanitize", + "solana-sanitize 2.2.1", ] [[package]] @@ -8450,7 +9334,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-transaction-error", ] @@ -8487,8 +9371,8 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f14f7d02af8f2bc1b5efeeae71bc1c2b7f0f65cd75bcc7d8180f2c762a57f54" dependencies = [ - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", ] [[package]] @@ -8505,10 +9389,10 @@ dependencies = [ "solana-clock", "solana-cpi", "solana-decode-error", - "solana-instruction", - "solana-program-error", - "solana-pubkey", - "solana-system-interface", + "solana-instruction 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", + "solana-system-interface 1.0.0", "solana-sysvar-id", ] @@ -8526,12 +9410,12 @@ dependencies = [ "solana-config-program", "solana-feature-set", "solana-genesis-config", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-native-token", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-stake-interface", @@ -8552,9 +9436,9 @@ dependencies = [ "serde", "solana-account-decoder", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-transaction", "solana-transaction-context", @@ -8596,7 +9480,7 @@ dependencies = [ "solana-net-utils", "solana-packet", "solana-perf", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-quic-definitions", "solana-signature", "solana-signer", @@ -8630,7 +9514,7 @@ dependencies = [ "solana-feature-set", "solana-fee-structure", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-instructions-sysvar", "solana-loader-v4-program", "solana-log-collector", @@ -8641,7 +9525,7 @@ dependencies = [ "solana-precompiles", "solana-program", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-rent-debits", "solana-sdk", @@ -8674,7 +9558,7 @@ dependencies = [ "solana-feature-set", "solana-fee-structure", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-instructions-sysvar", "solana-loader-v4-program", "solana-log-collector", @@ -8685,7 +9569,7 @@ dependencies = [ "solana-precompiles", "solana-program", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-rent-debits", "solana-reserved-account-keys", @@ -8717,7 +9601,7 @@ checksum = "4fc4392f0eed412141a376e99dfb052069b96f13697a9abb335504babe29387a" dependencies = [ "solana-hash", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-signature", "solana-transaction", @@ -8734,11 +9618,26 @@ dependencies = [ "serde", "serde_derive", "solana-decode-error", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "wasm-bindgen", ] +[[package]] +name = "solana-system-interface" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14591d6508042ebefb110305d3ba761615927146a26917ade45dc332d8e1ecde" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-address", + "solana-instruction 3.1.0", + "solana-msg 3.0.0", + "solana-program-error 3.0.0", +] + [[package]] name = "solana-system-program" version = "2.2.1" @@ -8751,15 +9650,15 @@ dependencies = [ "serde_derive", "solana-account", "solana-bincode", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-nonce", "solana-nonce-account", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-sysvar", "solana-transaction-context", "solana-type-overrides", @@ -8774,9 +9673,9 @@ dependencies = [ "solana-hash", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signer", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction", ] @@ -8795,20 +9694,20 @@ dependencies = [ "serde_derive", "solana-account-info", "solana-clock", - "solana-define-syscall", + "solana-define-syscall 2.2.1", "solana-epoch-rewards", "solana-epoch-schedule", "solana-fee-calculator", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-instructions-sysvar", "solana-last-restart-slot", "solana-program-entrypoint", - "solana-program-error", + "solana-program-error 2.2.2", "solana-program-memory", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-sdk-macro", "solana-slot-hashes", @@ -8823,7 +9722,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5762b273d3325b047cfda250787f8d796d781746860d5d0a746ee29f3e8812c1" dependencies = [ - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", ] @@ -8843,15 +9742,15 @@ dependencies = [ "solana-connection-cache", "solana-epoch-info", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-rpc-client-api", "solana-signature", "solana-signer", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction", "solana-transaction-error", ] @@ -8870,7 +9769,7 @@ checksum = "49d9eabdce318cb07c60a23f1cc367b43e177c79225b5c2a081869ad182172ad" dependencies = [ "eager", "enum-iterator", - "solana-pubkey", + "solana-pubkey 2.2.1", ] [[package]] @@ -8881,7 +9780,7 @@ checksum = "a228df037e560a02aac132193f492bdd761e2f90188cd16a440f149882f589b1" dependencies = [ "rustls 0.23.35", "solana-keypair", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signer", "x509-parser", ] @@ -8907,7 +9806,7 @@ dependencies = [ "solana-measure", "solana-message", "solana-net-utils", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-pubsub-client", "solana-quic-definitions", "solana-rpc-client", @@ -8932,18 +9831,18 @@ dependencies = [ "solana-bincode", "solana-feature-set", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-message", "solana-precompiles", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-reserved-account-keys", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-sdk-ids", "solana-short-vec", "solana-signature", "solana-signer", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction-error", "wasm-bindgen", ] @@ -8958,8 +9857,8 @@ dependencies = [ "serde", "serde_derive", "solana-account", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-rent", "solana-signature", ] @@ -8972,8 +9871,8 @@ checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1" dependencies = [ "serde", "serde_derive", - "solana-instruction", - "solana-sanitize", + "solana-instruction 2.2.1", + "solana-sanitize 2.2.1", ] [[package]] @@ -9012,16 +9911,16 @@ dependencies = [ "solana-account-decoder", "solana-clock", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-loader-v2-interface", "solana-message", "solana-program", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-reserved-account-keys", "solana-reward-info", "solana-sdk-ids", "solana-signature", - "solana-system-interface", + "solana-system-interface 1.0.0", "solana-transaction", "solana-transaction-error", "solana-transaction-status-client-types", @@ -9090,7 +9989,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe7e48cbf4e70c05199f50d5f14aafc58331ad39229747c795320bcb362ed063" dependencies = [ "assert_matches", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-runtime-transaction", "solana-transaction", "static_assertions", @@ -9112,7 +10011,7 @@ dependencies = [ "serde", "serde_derive", "solana-feature-set", - "solana-sanitize", + "solana-sanitize 2.2.1", "solana-serde-varint", ] @@ -9130,9 +10029,9 @@ dependencies = [ "solana-bincode", "solana-clock", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-packet", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-signature", "solana-svm-transaction", @@ -9155,14 +10054,14 @@ dependencies = [ "solana-clock", "solana-decode-error", "solana-hash", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-serde-varint", "solana-serialize-utils", "solana-short-vec", - "solana-system-interface", + "solana-system-interface 1.0.0", ] [[package]] @@ -9183,11 +10082,11 @@ dependencies = [ "solana-epoch-schedule", "solana-feature-set", "solana-hash", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-packet", "solana-program-runtime", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rent", "solana-sdk-ids", "solana-signer", @@ -9207,7 +10106,7 @@ dependencies = [ "bytemuck", "num-derive", "num-traits", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-program-runtime", "solana-sdk-ids", @@ -9238,8 +10137,8 @@ dependencies = [ "serde_json", "sha3", "solana-derivation-path", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-seed-derivable", "solana-seed-phrase", @@ -9261,7 +10160,7 @@ dependencies = [ "num-derive", "num-traits", "solana-feature-set", - "solana-instruction", + "solana-instruction 2.2.1", "solana-log-collector", "solana-program-runtime", "solana-sdk-ids", @@ -9290,10 +10189,10 @@ dependencies = [ "serde_derive", "serde_json", "sha3", - "solana-curve25519", + "solana-curve25519 2.2.1", "solana-derivation-path", - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", "solana-sdk-ids", "solana-seed-derivable", "solana-seed-phrase", @@ -9383,8 +10282,8 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f8349dbcbe575f354f9a533a21f272f3eb3808a49e2fdc1c34393b88ba76cb" dependencies = [ - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", ] [[package]] @@ -9394,7 +10293,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7398da23554a31660f17718164e31d31900956054f54f52d5ec1be51cb4f4b3" dependencies = [ "bytemuck", - "solana-program-error", + "solana-program-error 2.2.2", "solana-sha256-hasher", "spl-discriminator-derive", ] @@ -9407,7 +10306,7 @@ checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -9419,7 +10318,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.9", - "syn 2.0.111", + "syn 2.0.112", "thiserror 1.0.69", ] @@ -9443,11 +10342,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f09647c0974e33366efeb83b8e2daebb329f0420149e74d3a4bd2c08cf9f7cb" dependencies = [ "solana-account-info", - "solana-instruction", - "solana-msg", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", "solana-program-entrypoint", - "solana-program-error", - "solana-pubkey", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", ] [[package]] @@ -9456,8 +10355,8 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24af0730130fea732616be9425fe8eb77782e2aab2f0e76837b6a66aaba96c6b" dependencies = [ - "solana-instruction", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-pubkey 2.2.1", ] [[package]] @@ -9472,10 +10371,10 @@ dependencies = [ "num-derive", "num-traits", "solana-decode-error", - "solana-msg", - "solana-program-error", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", "solana-program-option", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-zk-sdk", "thiserror 2.0.17", ] @@ -9502,7 +10401,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.9", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -9516,10 +10415,10 @@ dependencies = [ "num-traits", "solana-account-info", "solana-decode-error", - "solana-instruction", - "solana-msg", - "solana-program-error", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "spl-discriminator", "spl-pod", "spl-program-error", @@ -9606,7 +10505,7 @@ checksum = "170378693c5516090f6d37ae9bad2b9b6125069be68d9acd4865bbe9fc8499fd" dependencies = [ "base64 0.22.1", "bytemuck", - "solana-curve25519", + "solana-curve25519 2.2.1", "solana-zk-sdk", ] @@ -9617,7 +10516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eff2d6a445a147c9d6dd77b8301b1e116c8299601794b558eafa409b342faf96" dependencies = [ "bytemuck", - "solana-curve25519", + "solana-curve25519 2.2.1", "solana-program", "solana-zk-sdk", "spl-pod", @@ -9656,10 +10555,10 @@ dependencies = [ "num-derive", "num-traits", "solana-decode-error", - "solana-instruction", - "solana-msg", - "solana-program-error", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "spl-discriminator", "spl-pod", "thiserror 1.0.69", @@ -9676,10 +10575,10 @@ dependencies = [ "num-traits", "solana-borsh", "solana-decode-error", - "solana-instruction", - "solana-msg", - "solana-program-error", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "spl-discriminator", "spl-pod", "spl-type-length-value", @@ -9699,10 +10598,10 @@ dependencies = [ "solana-account-info", "solana-cpi", "solana-decode-error", - "solana-instruction", - "solana-msg", - "solana-program-error", - "solana-pubkey", + "solana-instruction 2.2.1", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.2.1", "spl-discriminator", "spl-pod", "spl-program-error", @@ -9722,8 +10621,8 @@ dependencies = [ "num-traits", "solana-account-info", "solana-decode-error", - "solana-msg", - "solana-program-error", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", "spl-discriminator", "spl-pod", "thiserror 1.0.69", @@ -9787,7 +10686,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -9815,9 +10714,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" dependencies = [ "proc-macro2", "quote", @@ -9835,6 +10734,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -9856,7 +10758,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -9867,7 +10769,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", ] [[package]] @@ -9880,6 +10793,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "sysvars" version = "0.0.0" @@ -9956,14 +10879,14 @@ checksum = "28768569381ae187ca3e46026f5c0786c4120e146fc74bda02ab930dc1b94bd3" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -9987,23 +10910,32 @@ name = "test-chainlink" version = "0.0.0" dependencies = [ "bincode", + "borsh 0.10.4", + "compressed-delegation-client", "futures", "integration-test-tools", + "light-client", + "light-compressed-account", + "light-hasher", + "light-sdk", "magicblock-chainlink", "magicblock-config", + "magicblock-core", "magicblock-delegation-program 1.1.3 (git+https://github.com/magicblock-labs/delegation-program.git?rev=1874b4f5f5f55cb9ab54b64de2cc0d41107d1435)", "program-flexi-counter", "program-mini", "solana-account", + "solana-compute-budget-interface", "solana-loader-v2-interface", "solana-loader-v3-interface 4.0.1", "solana-loader-v4-interface", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-rpc-client-api", "solana-sdk", "solana-sdk-ids", - "solana-system-interface", + "solana-system-interface 1.0.0", + "solana-transaction-status", "spl-token", "tokio", "tracing", @@ -10052,7 +10984,7 @@ dependencies = [ "magicblock-ledger", "magicblock-processor", "solana-account", - "solana-instruction", + "solana-instruction 2.2.1", "solana-keypair", "solana-program", "solana-rpc-client", @@ -10151,7 +11083,7 @@ dependencies = [ "magicblock-rpc-client", "magicblock-table-mania", "paste", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-rpc-client", "solana-sdk", "test-kit", @@ -10204,7 +11136,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -10215,7 +11147,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -10308,7 +11240,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -10349,7 +11281,7 @@ checksum = "911a61637386b789af998ee23f50aa30d5fd7edcec8d6d3dedae5e5815205466" dependencies = [ "bincode", "bytes", - "educe", + "educe 0.4.23", "futures-core", "futures-sink", "pin-project", @@ -10447,9 +11379,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] @@ -10470,21 +11402,21 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.9" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap 2.12.1", - "toml_datetime 0.7.3", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] @@ -10549,11 +11481,11 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" dependencies = [ - "prettyplease 0.2.36", + "prettyplease 0.2.37", "proc-macro2", "prost-build 0.12.6", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -10562,12 +11494,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" dependencies = [ - "prettyplease 0.2.36", + "prettyplease 0.2.37", "proc-macro2", "prost-build 0.13.5", "prost-types 0.13.5", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -10613,6 +11545,25 @@ dependencies = [ "futures-util", "pin-project-lite", "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower 0.5.2", "tower-layer", "tower-service", ] @@ -10631,9 +11582,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -10649,14 +11600,14 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -10866,6 +11817,7 @@ checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "getrandom 0.3.4", "js-sys", + "serde_core", "wasm-bindgen", ] @@ -10978,7 +11930,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", "wasm-bindgen-shared", ] @@ -11149,7 +12101,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -11160,7 +12112,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -11179,6 +12131,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -11568,7 +12531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.1.2", + "rustix 1.1.3", ] [[package]] @@ -11607,7 +12570,7 @@ dependencies = [ "solana-clock", "solana-hash", "solana-message", - "solana-pubkey", + "solana-pubkey 2.2.1", "solana-signature", "solana-transaction", "solana-transaction-context", @@ -11636,7 +12599,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", "synstructure 0.13.2", ] @@ -11657,7 +12620,7 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -11677,7 +12640,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", "synstructure 0.13.2", ] @@ -11692,13 +12655,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] [[package]] @@ -11731,9 +12694,15 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.112", ] +[[package]] +name = "zmij" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9747e91771f56fd7893e1164abd78febd14a670ceec257caad15e051de35f06" + [[package]] name = "zstd" version = "0.13.3" diff --git a/test-integration/Cargo.toml b/test-integration/Cargo.toml index f975fccba..5ea1678b4 100644 --- a/test-integration/Cargo.toml +++ b/test-integration/Cargo.toml @@ -31,10 +31,12 @@ edition = "2021" anyhow = "1.0.86" async-trait = "0.1.77" bincode = "1.3.3" -borsh = { version = "1.2.1", features = ["derive", "unstable__schema"] } +borsh = "0.10.4" chrono = "0.4" cleanass = "0.0.1" color-backtrace = { version = "0.7" } +compressed-delegation-api = { path = "../compressed-delegation-api" } +compressed-delegation-client = { path = "../compressed-delegation-client" } ctrlc = "3.4.7" ephemeral-rollups-sdk = { git = "https://github.com/magicblock-labs/ephemeral-rollups-sdk.git", rev = "36f9485", default-features = false, features = [ "modular-sdk", @@ -43,6 +45,12 @@ futures = "0.3.31" integration-test-tools = { path = "test-tools" } isocountry = "0.3.2" lazy_static = "1.4.0" +light-batched-merkle-tree = { git = "https://github.com/magicblock-labs/light-protocol", rev = "849e6aa" } +light-client = { git = "https://github.com/magicblock-labs/light-protocol", rev = "849e6aa" } +light-compressed-account = { git = "https://github.com/magicblock-labs/light-protocol", rev = "849e6aa" } +light-hasher = { git = "https://github.com/magicblock-labs/light-protocol", rev = "849e6aa" } +light-sdk = { git = "https://github.com/magicblock-labs/light-protocol", rev = "849e6aa" } +light-sdk-types = { git = "https://github.com/magicblock-labs/light-protocol", rev = "849e6aa" } magicblock-api = { path = "../magicblock-api" } magicblock-chainlink = { path = "../magicblock-chainlink", features = [ "dev-context", @@ -78,7 +86,9 @@ rkyv = "0.7.45" schedulecommit-client = { path = "schedulecommit/client" } serde = "1.0.217" serial_test = "3.2.0" +shlex = "1.3.0" solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "2246929" } +solana-compute-budget-interface = "2.2" solana-loader-v2-interface = "2.2" solana-loader-v3-interface = "4.0" solana-loader-v4-interface = "2.1" diff --git a/test-integration/configs/chainlink-conf.devnet.toml b/test-integration/configs/chainlink-conf.devnet.toml index 16da71f89..e4d40198e 100644 --- a/test-integration/configs/chainlink-conf.devnet.toml +++ b/test-integration/configs/chainlink-conf.devnet.toml @@ -36,6 +36,10 @@ path = "../programs/redline/redline.so" id = "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh" path = "../schedulecommit/elfs/dlp.so" +[[programs]] +id = "DEL2rPzhFaS5qzo8XY9ZNxSzuunWueySq3p2dxJfwPbT" +path = "../programs/compressed_delegation/compressed_delegation.so" + [[programs]] id = "DmnRGfyyftzacFb1XadYhWF6vWqXwtQk5tbr6XgR3BA1" path = "../schedulecommit/elfs/mdp.so" diff --git a/test-integration/configs/committor-conf.devnet.toml b/test-integration/configs/committor-conf.devnet.toml index 62fc3ef10..31192cd9a 100644 --- a/test-integration/configs/committor-conf.devnet.toml +++ b/test-integration/configs/committor-conf.devnet.toml @@ -32,6 +32,10 @@ block-time = "50ms" id = "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh" path = "../schedulecommit/elfs/dlp.so" +[[programs]] +id = "DEL2rPzhFaS5qzo8XY9ZNxSzuunWueySq3p2dxJfwPbT" +path = "../programs/compressed_delegation/compressed_delegation.so" + # NOTE: `cargo build-sbf` needs to run from the root to build the program [[programs]] id = "ComtrB2KEaWgXsW1dhr1xYL4Ht4Bjj3gXnnL6KMdABq" diff --git a/test-integration/programs/compressed_delegation/compressed_delegation.so b/test-integration/programs/compressed_delegation/compressed_delegation.so new file mode 100755 index 000000000..2bd2eed3e Binary files /dev/null and b/test-integration/programs/compressed_delegation/compressed_delegation.so differ diff --git a/test-integration/programs/flexi-counter/Cargo.toml b/test-integration/programs/flexi-counter/Cargo.toml index 1df0f5c68..4eb96cd4b 100644 --- a/test-integration/programs/flexi-counter/Cargo.toml +++ b/test-integration/programs/flexi-counter/Cargo.toml @@ -6,10 +6,15 @@ edition.workspace = true [dependencies] bincode = { workspace = true } borsh = { workspace = true } +compressed-delegation-client = { workspace = true } ephemeral-rollups-sdk = { workspace = true } +light-batched-merkle-tree = { workspace = true } +light-hasher = { workspace = true } +light-sdk = { workspace = true, features = ["v2"] } +light-sdk-types = { workspace = true, features = ["v2"] } +magicblock_magic_program_api = { workspace = true } serde = { workspace = true } solana-program = { workspace = true } -magicblock_magic_program_api = { workspace = true } [lib] crate-type = ["cdylib", "lib"] diff --git a/test-integration/programs/flexi-counter/src/instruction.rs b/test-integration/programs/flexi-counter/src/instruction.rs index 8986f4850..7ed1ad247 100644 --- a/test-integration/programs/flexi-counter/src/instruction.rs +++ b/test-integration/programs/flexi-counter/src/instruction.rs @@ -3,6 +3,9 @@ use ephemeral_rollups_sdk::{ consts::{MAGIC_CONTEXT_ID, MAGIC_PROGRAM_ID}, delegate_args::{DelegateAccountMetas, DelegateAccounts}, }; +use light_sdk::instruction::{ + account_meta::CompressedAccountMeta, PackedAddressTreeInfo, ValidityProof, +}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, @@ -31,6 +34,26 @@ pub struct CancelArgs { pub task_id: i64, } +#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] +pub struct InitDelegationRecordArgs { + /// The proof of the account data + pub validity_proof: ValidityProof, + /// The address tree info + pub address_tree_info: PackedAddressTreeInfo, + /// The output state tree index + pub output_state_tree_index: u8, +} + +#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] +pub struct DelegateCompressedArgs { + /// The validator authority that is added to the delegation record + pub validator: Option, + /// The proof of the account data + pub validity_proof: ValidityProof, + /// The account meta + pub account_meta: CompressedAccountMeta, +} + pub const MAX_ACCOUNT_ALLOC_PER_INSTRUCTION_SIZE: u16 = 10_240; /// The counter has both mul and add instructions in order to facilitate tests where @@ -184,6 +207,38 @@ pub enum FlexiCounterInstruction { /// 1. `[signer]` The payer that created and is cancelling the task. /// 2. `[write]` Task context account. Cancel(CancelArgs), + + /// Initializes a compressed delegation record for the FlexiCounter account + /// + /// Accounts: + /// 0. `[signer]` The payer that is initializing the delegation record. + /// 1. `[write]` The counter PDA account that will be initialized. + /// 2. `[]` The compressed delegation program id + /// 3. `[]` The CPI signer of the compressed delegation program + /// 4. `[]` The system program + /// + /// 5..N `[]` Remaining accounts using by the Light program + InitCompressedDelegationRecord(InitDelegationRecordArgs), + + /// Compressed delegation of the FlexiCounter account to an ephemaral validator + /// + /// Accounts: + /// 0. `[signer]` The payer that is delegating the account. + /// 1. `[write]` The counter PDA account that will be delegated. + /// 2. `[]` The compressed delegation program id + /// 3. `[]` The CPI signer of the compressed delegation program + /// + /// 4..N `[]` Remaining accounts using by the Light program + DelegateCompressed(DelegateCompressedArgs), + + /// Commits the compressed FlexiCounter + /// + /// Accounts: + /// 0. `[signer]` The payer that created the account. + /// 1. `[write]` The counter PDA account that will be updated. + /// 2. `[]` MagicContext (used to record scheduled commit) + /// 3. `[]` MagicBlock Program (used to schedule commit) + ScheduleCommitCompressed, } pub fn create_init_ix(payer: Pubkey, label: String) -> Instruction { @@ -194,9 +249,9 @@ pub fn create_init_ix(payer: Pubkey, label: String) -> Instruction { AccountMeta::new(pda, false), AccountMeta::new_readonly(system_program::id(), false), ]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::Init { label, bump }, + &borsh::to_vec(&FlexiCounterInstruction::Init { label, bump }).unwrap(), accounts, ) } @@ -213,12 +268,13 @@ pub fn create_realloc_ix( AccountMeta::new(pda, false), AccountMeta::new_readonly(system_program::id(), false), ]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::Realloc { + &borsh::to_vec(&FlexiCounterInstruction::Realloc { bytes, invocation_count, - }, + }) + .unwrap(), accounts, ) } @@ -230,9 +286,9 @@ pub fn create_add_ix(payer: Pubkey, count: u8) -> Instruction { AccountMeta::new_readonly(payer, true), AccountMeta::new(pda, false), ]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::Add { count }, + &borsh::to_vec(&FlexiCounterInstruction::Add { count }).unwrap(), accounts, ) } @@ -241,9 +297,10 @@ pub fn create_add_unsigned_ix(payer: Pubkey, count: u8) -> Instruction { let program_id = &crate::id(); let (pda, _) = FlexiCounter::pda(&payer); let accounts = vec![AccountMeta::new(pda, false)]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::AddUnsigned { count }, + &borsh::to_vec(&FlexiCounterInstruction::AddUnsigned { count }) + .unwrap(), accounts, ) } @@ -252,9 +309,9 @@ pub fn create_add_error_ix(payer: Pubkey, count: u8) -> Instruction { let program_id = &crate::id(); let (pda, _) = FlexiCounter::pda(&payer); let accounts = vec![AccountMeta::new(pda, false)]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::AddError { count }, + &borsh::to_vec(&FlexiCounterInstruction::AddError { count }).unwrap(), accounts, ) } @@ -264,9 +321,9 @@ pub fn create_mul_ix(payer: Pubkey, multiplier: u8) -> Instruction { let (pda, _) = FlexiCounter::pda(&payer); let accounts = vec![AccountMeta::new(payer, true), AccountMeta::new(pda, false)]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::Mul { multiplier }, + &borsh::to_vec(&FlexiCounterInstruction::Mul { multiplier }).unwrap(), accounts, ) } @@ -300,9 +357,9 @@ pub fn create_delegate_ix_with_commit_frequency_ms( commit_frequency_ms, }; - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::Delegate(args), + &borsh::to_vec(&FlexiCounterInstruction::Delegate(args)).unwrap(), account_metas, ) } @@ -320,9 +377,13 @@ pub fn create_add_and_schedule_commit_ix( AccountMeta::new(MAGIC_CONTEXT_ID, false), AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false), ]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::AddAndScheduleCommit { count, undelegate }, + &borsh::to_vec(&FlexiCounterInstruction::AddAndScheduleCommit { + count, + undelegate, + }) + .unwrap(), accounts, ) } @@ -339,9 +400,9 @@ pub fn create_add_counter_ix( AccountMeta::new(pda_main, false), AccountMeta::new_readonly(pda_source, false), ]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::AddCounter, + &borsh::to_vec(&FlexiCounterInstruction::AddCounter).unwrap(), accounts, ) } @@ -365,15 +426,16 @@ pub fn create_intent_single_committee_ix( AccountMeta::new(counter, false), ]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::CreateIntent { + &borsh::to_vec(&FlexiCounterInstruction::CreateIntent { num_committees: 1, // Has no effect in non-undelegate case counter_diffs: vec![counter_diff], is_undelegate, compute_units, - }, + }) + .unwrap(), accounts, ) } @@ -408,15 +470,16 @@ pub fn create_intent_ix( accounts.extend(payers_meta); accounts.extend(counter_metas); - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::CreateIntent { + &borsh::to_vec(&FlexiCounterInstruction::CreateIntent { num_committees: payers.len() as u8, // Has no effect in non-undelegate case counter_diffs, is_undelegate, compute_units, - }, + }) + .unwrap(), accounts, ) } @@ -436,15 +499,16 @@ pub fn create_schedule_task_ix( AccountMeta::new(payer, true), AccountMeta::new(pda, false), ]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::Schedule(ScheduleArgs { + &borsh::to_vec(&FlexiCounterInstruction::Schedule(ScheduleArgs { task_id, execution_interval_millis, iterations, error, signer, - }), + })) + .unwrap(), accounts, ) } @@ -455,9 +519,73 @@ pub fn create_cancel_task_ix(payer: Pubkey, task_id: i64) -> Instruction { AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false), AccountMeta::new(payer, true), ]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( + *program_id, + &borsh::to_vec(&FlexiCounterInstruction::Cancel(CancelArgs { + task_id, + })) + .unwrap(), + accounts, + ) +} + +pub fn create_init_compressed_delegation_record_ix( + payer: Pubkey, + remaining_accounts: &[AccountMeta], + args: InitDelegationRecordArgs, +) -> Instruction { + let program_id = &crate::id(); + let (pda, _) = FlexiCounter::pda(&payer); + let mut accounts = vec![ + AccountMeta::new(payer, true), + AccountMeta::new(pda, false), + AccountMeta::new_readonly(compressed_delegation_client::ID, false), + ]; + accounts.extend(remaining_accounts.iter().cloned()); + Instruction::new_with_bytes( + *program_id, + &borsh::to_vec( + &FlexiCounterInstruction::InitCompressedDelegationRecord(args), + ) + .unwrap(), + accounts, + ) +} + +pub fn create_delegate_compressed_ix( + payer: Pubkey, + remaining_accounts: &[AccountMeta], + args: DelegateCompressedArgs, +) -> Instruction { + let program_id = &crate::id(); + let (pda, _) = FlexiCounter::pda(&payer); + let mut accounts = vec![ + AccountMeta::new(payer, true), + AccountMeta::new(pda, false), + AccountMeta::new_readonly(compressed_delegation_client::ID, false), + ]; + accounts.extend(remaining_accounts.iter().cloned()); + Instruction::new_with_bytes( + *program_id, + &borsh::to_vec(&FlexiCounterInstruction::DelegateCompressed(args)) + .unwrap(), + accounts, + ) +} + +pub fn create_schedule_commit_compressed_ix(payer: Pubkey) -> Instruction { + let program_id = &crate::id(); + let (pda, _) = FlexiCounter::pda(&payer); + let accounts = vec![ + AccountMeta::new(payer, true), + AccountMeta::new(pda, false), + AccountMeta::new(MAGIC_CONTEXT_ID, false), + AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false), + ]; + Instruction::new_with_bytes( *program_id, - &FlexiCounterInstruction::Cancel(CancelArgs { task_id }), + &borsh::to_vec(&FlexiCounterInstruction::ScheduleCommitCompressed) + .unwrap(), accounts, ) } diff --git a/test-integration/programs/flexi-counter/src/processor.rs b/test-integration/programs/flexi-counter/src/processor.rs index 47ad47cff..5f1f4ee0f 100644 --- a/test-integration/programs/flexi-counter/src/processor.rs +++ b/test-integration/programs/flexi-counter/src/processor.rs @@ -2,8 +2,15 @@ mod call_handler; mod schedule_intent; use borsh::{to_vec, BorshDeserialize}; +use compressed_delegation_client::{ + ExternalUndelegateArgs, + EXTERNAL_UNDELEGATE_DISCRIMINATOR as EXTERNAL_UNDELEGATE_COMPRESSED_DISCRIMINATOR, +}; use ephemeral_rollups_sdk::{ - consts::{EXTERNAL_UNDELEGATE_DISCRIMINATOR, MAGIC_PROGRAM_ID}, + consts::{ + DEFAULT_VALIDATOR_IDENTITY, EXTERNAL_UNDELEGATE_DISCRIMINATOR, + MAGIC_CONTEXT_ID, MAGIC_PROGRAM_ID, + }, cpi::{ delegate_account, undelegate_account, DelegateAccounts, DelegateConfig, }, @@ -28,7 +35,8 @@ use solana_program::{ use crate::{ instruction::{ create_add_error_ix, create_add_ix, create_add_unsigned_ix, CancelArgs, - DelegateArgs, FlexiCounterInstruction, ScheduleArgs, + DelegateArgs, DelegateCompressedArgs, FlexiCounterInstruction, + InitDelegationRecordArgs, ScheduleArgs, MAX_ACCOUNT_ALLOC_PER_INSTRUCTION_SIZE, }, processor::{ @@ -52,6 +60,9 @@ pub fn process( if disc == EXTERNAL_UNDELEGATE_DISCRIMINATOR { return process_undelegate_request(accounts, data); + } else if disc == EXTERNAL_UNDELEGATE_COMPRESSED_DISCRIMINATOR { + let args = ExternalUndelegateArgs::try_from_slice(data)?; + return process_external_undelegate_compressed(accounts, args); } } @@ -94,6 +105,13 @@ pub fn process( } => process_undelegate_action_handler(accounts, amount, counter_diff), Schedule(args) => process_schedule_task(accounts, args), Cancel(args) => process_cancel_task(accounts, args), + InitCompressedDelegationRecord(args) => { + process_init_compressed_delegation_record(accounts, args) + } + DelegateCompressed(args) => process_delegate_compressed(accounts, args), + ScheduleCommitCompressed => { + process_schedule_commit_compressed(accounts) + } }?; Ok(()) } @@ -325,6 +343,9 @@ fn process_add_and_schedule_commit( let magic_context_info = next_account_info(account_info_iter)?; let magic_program_info = next_account_info(account_info_iter)?; + assert_magic_context(magic_context_info)?; + assert_magic_program(magic_program_info)?; + // Perform the add operation add(payer_info, counter_pda_info, count)?; @@ -415,10 +436,12 @@ fn process_schedule_task( msg!("ScheduleTask"); let account_info_iter = &mut accounts.iter(); - let _magic_program_info = next_account_info(account_info_iter)?; + let magic_program_info = next_account_info(account_info_iter)?; let payer_info = next_account_info(account_info_iter)?; let counter_pda_info = next_account_info(account_info_iter)?; + assert_magic_program(magic_program_info)?; + let (counter_pda, bump) = FlexiCounter::pda(payer_info.key); if counter_pda_info.key.ne(&counter_pda) { msg!( @@ -448,7 +471,7 @@ fn process_schedule_task( })?; let ix = Instruction::new_with_bytes( - MAGIC_PROGRAM_ID, + *magic_program_info.key, &ix_data, vec![ AccountMeta::new(*payer_info.key, true), @@ -472,9 +495,11 @@ fn process_cancel_task( msg!("CancelTask"); let account_info_iter = &mut accounts.iter(); - let _magic_program_info = next_account_info(account_info_iter)?; + let magic_program_info = next_account_info(account_info_iter)?; let payer_info = next_account_info(account_info_iter)?; + assert_magic_program(magic_program_info)?; + let ix_data = bincode::serialize(&MagicBlockInstruction::CancelTask { task_id: args.task_id, }) @@ -484,7 +509,7 @@ fn process_cancel_task( })?; let ix = Instruction::new_with_bytes( - MAGIC_PROGRAM_ID, + *magic_program_info.key, &ix_data, vec![AccountMeta::new(*payer_info.key, true)], ); @@ -493,3 +518,246 @@ fn process_cancel_task( Ok(()) } + +fn process_init_compressed_delegation_record( + accounts: &[AccountInfo], + args: InitDelegationRecordArgs, +) -> ProgramResult { + msg!("InitCompressedDelegationRecord"); + + let [payer_info, counter_pda_info, compressed_delegation_program_info, remaining_accounts @ ..] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if compressed_delegation_program_info + .key + .ne(&compressed_delegation_client::ID) + { + return Err(ProgramError::IncorrectProgramId); + } + + let pda_seeds = FlexiCounter::seeds(payer_info.key); + let (counter_pda, bump) = FlexiCounter::pda(payer_info.key); + if counter_pda_info.key.ne(&counter_pda) { + msg!( + "Invalid counter PDA {}, should be {}", + counter_pda_info.key, + counter_pda + ); + return Err(ProgramError::InvalidSeeds); + } + + // Cpi into delegation program + let bump_slice = &[bump]; + let signer_seeds = + FlexiCounter::seeds_with_bump(payer_info.key, bump_slice); + let signer = [signer_seeds.as_ref()]; + compressed_delegation_client::cpi::InitDelegationRecordCpi { + payer: payer_info.clone(), + delegated_account: counter_pda_info.clone(), + remaining_accounts: remaining_accounts + .iter() + .map(|account| { + (account.clone(), account.is_signer, account.is_writable) + }) + .collect(), + args: compressed_delegation_client::InitDelegationRecordArgs { + owner_program_id: crate::ID, + pda_seeds: pda_seeds + .iter() + .map(|seed| seed.to_vec()) + .collect::>(), + bump, + output_state_tree_index: args.output_state_tree_index, + address_tree_info: args.address_tree_info, + validity_proof: args.validity_proof, + }, + } + .invoke_signed(&signer)?; + + Ok(()) +} + +fn process_delegate_compressed( + accounts: &[AccountInfo], + args: DelegateCompressedArgs, +) -> ProgramResult { + msg!("DelegateCompressed"); + + let [payer_info, counter_pda_info, compressed_delegation_program_info, remaining_accounts @ ..] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if compressed_delegation_program_info + .key + .ne(&compressed_delegation_client::ID) + { + return Err(ProgramError::IncorrectProgramId); + } + + let pda_seeds = FlexiCounter::seeds(payer_info.key); + let (counter_pda, bump) = FlexiCounter::pda(payer_info.key); + if counter_pda_info.key.ne(&counter_pda) { + msg!( + "Invalid counter PDA {}, should be {}", + counter_pda_info.key, + counter_pda + ); + return Err(ProgramError::InvalidSeeds); + } + + // Close the counter + let account_data = { counter_pda_info.data.borrow().to_vec() }; + counter_pda_info.realloc(0, false)?; + **payer_info.try_borrow_mut_lamports()? += counter_pda_info.lamports(); + **counter_pda_info.try_borrow_mut_lamports()? = 0; + + // Cpi into delegation program + let bump_slice = &[bump]; + let signer_seeds = + FlexiCounter::seeds_with_bump(payer_info.key, bump_slice); + let signer = [signer_seeds.as_ref()]; + compressed_delegation_client::cpi::DelegateCpi { + payer: payer_info.clone(), + delegated_account: counter_pda_info.clone(), + remaining_accounts: remaining_accounts + .iter() + .map(|account| { + (account.clone(), account.is_signer, account.is_writable) + }) + .collect(), + args: compressed_delegation_client::DelegateArgs { + validator: args.validator.unwrap_or(DEFAULT_VALIDATOR_IDENTITY), + validity_proof: args.validity_proof, + account_meta: args.account_meta, + lamports: 0, + account_data, + pda_seeds: pda_seeds + .iter() + .map(|seed| seed.to_vec()) + .collect::>(), + bump, + owner_program_id: crate::ID, + }, + } + .invoke_signed(&signer)?; + + Ok(()) +} + +fn process_schedule_commit_compressed( + accounts: &[AccountInfo], +) -> ProgramResult { + msg!("ScheduleCommitCompressed"); + + let [payer, counter, magic_context, magic_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + assert_magic_context(magic_context)?; + assert_magic_program(magic_program)?; + + let (pda, _bump) = FlexiCounter::pda(payer.key); + assert_keys_equal(counter.key, &pda, || { + format!("Invalid counter PDA {}, should be {}", counter.key, pda) + })?; + + let instruction_data = MagicBlockInstruction::ScheduleCommitAndUndelegate + .try_to_vec() + .map_err(|_| { + ProgramError::BorshIoError( + "ScheduleCommitAndUndelegate".to_string(), + ) + })?; + + let account_metas = vec![ + AccountMeta::new(*payer.key, true), + AccountMeta::new(*magic_context.key, false), + AccountMeta::new(*counter.key, false), + ]; + + let account_refs = + vec![payer.clone(), magic_context.clone(), counter.clone()]; + + let ix = Instruction { + program_id: *magic_program.key, + data: instruction_data, + accounts: account_metas, + }; + + invoke(&ix, &account_refs)?; + + Ok(()) +} + +fn process_external_undelegate_compressed( + accounts: &[AccountInfo], + args: ExternalUndelegateArgs, +) -> ProgramResult { + msg!("External Undelegate Compressed"); + + let [payer, delegated_account, _system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let (pda, bump) = FlexiCounter::pda(payer.key); + if &pda != delegated_account.key { + msg!("Invalid seeds: {:?} != {:?}", pda, delegated_account.key); + return Err(ProgramError::InvalidSeeds); + } + + // Pay rent-exemption + // TODO(dode): restore the lamports in the delegation record + let rent = Rent::get()?; + let rent_exemption = + rent.minimum_balance(args.delegation_record.data.len()); + if rent_exemption > delegated_account.lamports() { + invoke( + &system_instruction::transfer( + payer.key, + delegated_account.key, + rent_exemption - delegated_account.lamports(), + ), + &[payer.clone(), delegated_account.clone()], + )?; + } else if rent_exemption < delegated_account.lamports() { + let bump = &[bump]; + let seeds = FlexiCounter::seeds_with_bump(payer.key, bump); + invoke_signed( + &system_instruction::transfer( + delegated_account.key, + payer.key, + delegated_account.lamports() - rent_exemption, + ), + &[delegated_account.clone(), payer.clone()], + &[&seeds], + )?; + } + + // Reset data + delegated_account.realloc(args.delegation_record.data.len(), false)?; + delegated_account + .data + .borrow_mut() + .copy_from_slice(&args.delegation_record.data); + + Ok(()) +} + +fn assert_magic_context(account_info: &AccountInfo) -> ProgramResult { + if account_info.key != &MAGIC_CONTEXT_ID { + return Err(ProgramError::InvalidAccountData); + } + Ok(()) +} + +fn assert_magic_program(account_info: &AccountInfo) -> ProgramResult { + if account_info.key != &MAGIC_PROGRAM_ID { + return Err(ProgramError::IncorrectProgramId); + } + Ok(()) +} diff --git a/test-integration/programs/flexi-counter/src/state.rs b/test-integration/programs/flexi-counter/src/state.rs index 2eb9a5255..57732b219 100644 --- a/test-integration/programs/flexi-counter/src/state.rs +++ b/test-integration/programs/flexi-counter/src/state.rs @@ -1,7 +1,18 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use light_sdk::{LightDiscriminator, LightHasher}; use solana_program::pubkey::Pubkey; -#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq)] +#[derive( + BorshSerialize, + BorshDeserialize, + Default, + Debug, + Clone, + PartialEq, + Eq, + LightDiscriminator, + LightHasher, +)] pub struct FlexiCounter { pub count: u64, pub updates: u64, diff --git a/test-integration/programs/schedulecommit/src/api.rs b/test-integration/programs/schedulecommit/src/api.rs index 8f359a56c..1740c6fb7 100644 --- a/test-integration/programs/schedulecommit/src/api.rs +++ b/test-integration/programs/schedulecommit/src/api.rs @@ -41,9 +41,9 @@ pub fn init_account_instruction( AccountMeta::new_readonly(system_program::id(), false), ]; - Instruction::new_with_borsh( + build_instruction( program_id, - &ScheduleCommitInstruction::Init, + ScheduleCommitInstruction::Init, account_metas, ) } @@ -61,9 +61,9 @@ pub fn init_order_book_instruction( AccountMeta::new_readonly(system_program::id(), false), ]; - Instruction::new_with_borsh( + build_instruction( program_id, - &ScheduleCommitInstruction::InitOrderBook, + ScheduleCommitInstruction::InitOrderBook, account_metas, ) } @@ -82,9 +82,9 @@ pub fn grow_order_book_instruction( AccountMeta::new_readonly(system_program::id(), false), ]; - Instruction::new_with_borsh( + build_instruction( program_id, - &ScheduleCommitInstruction::GrowOrderBook(additional_space), + ScheduleCommitInstruction::GrowOrderBook(additional_space), account_metas, ) } @@ -138,9 +138,9 @@ pub fn delegate_account_cpi_instruction( delegate_metas.system_program, ]; - Instruction::new_with_borsh( + build_instruction( program_id, - &match user_seed { + match user_seed { UserSeeds::MagicScheduleCommit => { ScheduleCommitInstruction::DelegateCpi(DelegateCpiArgs { valid_until: i64::MAX, @@ -205,9 +205,9 @@ pub fn update_order_book_instruction( AccountMeta::new(order_book, false), ]; - Instruction::new_with_borsh( + build_instruction( program_id, - &ScheduleCommitInstruction::UpdateOrderBook(update), + ScheduleCommitInstruction::UpdateOrderBook(update), account_metas, ) } @@ -226,9 +226,9 @@ pub fn schedule_commit_diff_instruction_for_order_book( AccountMeta::new_readonly(magic_program_id, false), ]; - Instruction::new_with_borsh( + build_instruction( program_id, - &ScheduleCommitInstruction::ScheduleCommitForOrderBook, + ScheduleCommitInstruction::ScheduleCommitForOrderBook, account_metas, ) } @@ -303,7 +303,7 @@ fn schedule_commit_cpi_instruction_impl( commit_payer: args.commit_payer, }; let ix = ScheduleCommitInstruction::ScheduleCommitCpi(cpi_args); - Instruction::new_with_borsh(program_id, &ix, account_metas) + build_instruction(program_id, ix, account_metas) } pub fn schedule_commit_and_undelegate_cpi_with_mod_after_instruction( @@ -323,13 +323,10 @@ pub fn schedule_commit_and_undelegate_cpi_with_mod_after_instruction( account_metas.push(AccountMeta::new(*committee, false)); } - Instruction::new_with_borsh( - program_id, - &ScheduleCommitInstruction::ScheduleCommitAndUndelegateCpiModAfter( - players.to_vec(), - ), - account_metas, - ) + let ix = ScheduleCommitInstruction::ScheduleCommitAndUndelegateCpiModAfter( + players.to_vec(), + ); + build_instruction(program_id, ix, account_metas) } pub fn schedule_commit_and_undelegate_cpi_twice( @@ -349,9 +346,9 @@ pub fn schedule_commit_and_undelegate_cpi_twice( account_metas.push(AccountMeta::new(*committee, false)); } - Instruction::new_with_borsh( + build_instruction( program_id, - &ScheduleCommitInstruction::ScheduleCommitAndUndelegateCpiTwice( + ScheduleCommitInstruction::ScheduleCommitAndUndelegateCpiTwice( players.to_vec(), ), account_metas, @@ -361,9 +358,9 @@ pub fn schedule_commit_and_undelegate_cpi_twice( pub fn increase_count_instruction(committee: Pubkey) -> Instruction { let program_id = crate::id(); let account_metas = vec![AccountMeta::new(committee, false)]; - Instruction::new_with_borsh( + build_instruction( program_id, - &ScheduleCommitInstruction::IncreaseCount, + ScheduleCommitInstruction::IncreaseCount, account_metas, ) } @@ -371,9 +368,9 @@ pub fn increase_count_instruction(committee: Pubkey) -> Instruction { pub fn set_count_instruction(committee: Pubkey, count: u64) -> Instruction { let program_id = crate::id(); let account_metas = vec![AccountMeta::new(committee, false)]; - Instruction::new_with_borsh( + build_instruction( program_id, - &ScheduleCommitInstruction::SetCount(count), + ScheduleCommitInstruction::SetCount(count), account_metas, ) } @@ -406,3 +403,16 @@ pub fn pda_and_bump(acc_id: &Pubkey) -> (Pubkey, u8) { let seeds = pda_seeds(acc_id); Pubkey::find_program_address(&seeds, &program_id) } + +fn build_instruction( + program_id: Pubkey, + instruction: ScheduleCommitInstruction, + account_metas: Vec, +) -> Instruction { + Instruction::new_with_bytes( + program_id, + &borsh::to_vec(&instruction) + .expect("Serialization of instruction should never fail"), + account_metas, + ) +} diff --git a/test-integration/programs/schedulecommit/src/lib.rs b/test-integration/programs/schedulecommit/src/lib.rs index 32d2e39f5..d8f408a61 100644 --- a/test-integration/programs/schedulecommit/src/lib.rs +++ b/test-integration/programs/schedulecommit/src/lib.rs @@ -32,7 +32,6 @@ use crate::{ }; pub mod api; -pub mod magicblock_program; mod order_book; mod utils; diff --git a/test-integration/programs/schedulecommit/src/magicblock_program.rs b/test-integration/programs/schedulecommit/src/magicblock_program.rs deleted file mode 100644 index 9cb90cf17..000000000 --- a/test-integration/programs/schedulecommit/src/magicblock_program.rs +++ /dev/null @@ -1,25 +0,0 @@ -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum MagicBlockInstruction { - ModifyAccounts, - ScheduleCommit, - ScheduleCommitAndUndelegate, - ScheduledCommitSent(u64), -} - -#[allow(unused)] -impl MagicBlockInstruction { - pub(crate) fn index(&self) -> u8 { - use MagicBlockInstruction::*; - match self { - ModifyAccounts => 0, - ScheduleCommit => 1, - ScheduleCommitAndUndelegate => 2, - ScheduledCommitSent(_) => 3, - } - } - - pub(crate) fn discriminant(&self) -> [u8; 4] { - let idx = self.index(); - [idx, 0, 0, 0] - } -} diff --git a/test-integration/programs/schedulecommit/src/order_book.rs b/test-integration/programs/schedulecommit/src/order_book.rs index 5881387e4..348fa9d3d 100644 --- a/test-integration/programs/schedulecommit/src/order_book.rs +++ b/test-integration/programs/schedulecommit/src/order_book.rs @@ -53,7 +53,7 @@ impl From<&OrderBook<'_>> for OrderBookOwned { } impl borsh::de::BorshDeserialize for OrderBookOwned { - fn deserialize(buf: &mut &[u8]) -> Result { + fn deserialize(buf: &mut &[u8]) -> Result { let (book_bytes, rest) = buf.split_at(buf.len()); *buf = rest; // rest is actually empty @@ -71,12 +71,12 @@ impl borsh::de::BorshDeserialize for OrderBookOwned { slice::from_raw_parts_mut(book_bytes.as_mut_ptr(), book_bytes.len()) }) .map(|book| OrderBookOwned::from(&book)) - .map_err(|_| borsh::io::Error::from(borsh::io::ErrorKind::InvalidData)) + .map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData)) } - fn deserialize_reader( + fn deserialize_reader( _reader: &mut R, - ) -> ::core::result::Result { + ) -> ::core::result::Result { unimplemented!("deserialize_reader() not implemented. Please use buffer version as it needs to know size of the buffer") } } diff --git a/test-integration/schedulecommit/test-scenarios/Cargo.toml b/test-integration/schedulecommit/test-scenarios/Cargo.toml index f1913660d..24406c9c2 100644 --- a/test-integration/schedulecommit/test-scenarios/Cargo.toml +++ b/test-integration/schedulecommit/test-scenarios/Cargo.toml @@ -4,6 +4,7 @@ version.workspace = true edition.workspace = true [dev-dependencies] +borsh = { workspace = true } ephemeral-rollups-sdk = { workspace = true } integration-test-tools = { workspace = true } tracing = { workspace = true } @@ -17,4 +18,3 @@ solana-rpc-client-api = { workspace = true } solana-sdk = { workspace = true } test-kit = { workspace = true } rand = { workspace = true } -borsh = { workspace = true } diff --git a/test-integration/schedulecommit/test-scenarios/tests/01_commits.rs b/test-integration/schedulecommit/test-scenarios/tests/01_commits.rs index cd9ab6490..6f68bd89a 100644 --- a/test-integration/schedulecommit/test-scenarios/tests/01_commits.rs +++ b/test-integration/schedulecommit/test-scenarios/tests/01_commits.rs @@ -323,5 +323,9 @@ fn schedule_commit_cpi_illegal_owner( commit_payer: true, }; let ix = ScheduleCommitInstruction::ScheduleCommitCpi(cpi_args); - Instruction::new_with_borsh(program_id, &ix, account_metas) + Instruction::new_with_bytes( + program_id, + &borsh::to_vec(&ix).expect("Serializing instruction should never fail"), + account_metas, + ) } diff --git a/test-integration/schedulecommit/test-security/Cargo.toml b/test-integration/schedulecommit/test-security/Cargo.toml index 0429dd46b..a3254a59b 100644 --- a/test-integration/schedulecommit/test-security/Cargo.toml +++ b/test-integration/schedulecommit/test-security/Cargo.toml @@ -4,6 +4,7 @@ version.workspace = true edition.workspace = true [dev-dependencies] +borsh = { workspace = true } program-schedulecommit = { workspace = true, features = ["no-entrypoint"] } program-schedulecommit-security = { workspace = true, features = [ "no-entrypoint", diff --git a/test-integration/schedulecommit/test-security/tests/utils/mod.rs b/test-integration/schedulecommit/test-security/tests/utils/mod.rs index 21a25333b..3e4281f64 100644 --- a/test-integration/schedulecommit/test-security/tests/utils/mod.rs +++ b/test-integration/schedulecommit/test-security/tests/utils/mod.rs @@ -29,11 +29,14 @@ pub fn create_sibling_schedule_cpis_instruction( is_writable: true, }); } - Instruction::new_with_borsh( + Instruction::new_with_bytes( program_schedulecommit_security::id(), - &ScheduleCommitSecurityInstruction::SiblingScheduleCommitCpis( - player_pubkeys.to_vec(), - ), + &borsh::to_vec( + &ScheduleCommitSecurityInstruction::SiblingScheduleCommitCpis( + player_pubkeys.to_vec(), + ), + ) + .unwrap(), account_metas, ) } @@ -61,11 +64,14 @@ pub fn create_nested_schedule_cpis_instruction( is_writable: true, }); } - Instruction::new_with_borsh( + Instruction::new_with_bytes( program_schedulecommit_security::id(), - &ScheduleCommitSecurityInstruction::DirectScheduleCommitCpi( - player_pubkeys.to_vec(), - ), + &borsh::to_vec( + &ScheduleCommitSecurityInstruction::DirectScheduleCommitCpi( + player_pubkeys.to_vec(), + ), + ) + .unwrap(), account_metas, ) } @@ -74,9 +80,9 @@ pub fn create_nested_schedule_cpis_instruction( /// It could be added to confuse our algorithm to detect the invoking program. pub fn create_sibling_non_cpi_instruction(payer: Pubkey) -> Instruction { let account_metas = vec![AccountMeta::new(payer, true)]; - Instruction::new_with_borsh( + Instruction::new_with_bytes( program_schedulecommit_security::id(), - &ScheduleCommitSecurityInstruction::NonCpi, + &borsh::to_vec(&ScheduleCommitSecurityInstruction::NonCpi).unwrap(), account_metas, ) } diff --git a/test-integration/test-chainlink/Cargo.toml b/test-integration/test-chainlink/Cargo.toml index 227c1efc8..5b274c620 100644 --- a/test-integration/test-chainlink/Cargo.toml +++ b/test-integration/test-chainlink/Cargo.toml @@ -4,15 +4,23 @@ version.workspace = true edition.workspace = true [dependencies] +borsh = { workspace = true } bincode = { workspace = true } +compressed-delegation-client = { workspace = true } futures = { workspace = true } -tracing = { workspace = true } +integration-test-tools = { workspace = true } +light-client = { workspace = true, features = ["v2"] } +light-compressed-account = { workspace = true } +light-hasher = { workspace = true } +light-sdk = { workspace = true, features = ["v2"] } +magicblock-core = { workspace = true } magicblock-chainlink = { workspace = true } magicblock-config = { workspace = true } magicblock-delegation-program = { workspace = true } program-mini = { workspace = true, features = ["no-entrypoint"] } program-flexi-counter = { workspace = true, features = ["no-entrypoint"] } solana-account = { workspace = true } +solana-compute-budget-interface = { workspace = true } solana-loader-v2-interface = { workspace = true, features = ["serde"] } solana-loader-v3-interface = { workspace = true, features = ["serde"] } solana-loader-v4-interface = { workspace = true, features = ["serde"] } @@ -22,6 +30,7 @@ solana-rpc-client-api = { workspace = true } solana-sdk = { workspace = true } solana-sdk-ids = { workspace = true } solana-system-interface = { workspace = true } +solana-transaction-status = { workspace = true } spl-token = { workspace = true } -integration-test-tools = { workspace = true } tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true } diff --git a/test-integration/test-chainlink/scripts/miniv2-json-from-so.js b/test-integration/test-chainlink/scripts/miniv2-json-from-so.js index 8baa272e6..2b1d78460 100644 --- a/test-integration/test-chainlink/scripts/miniv2-json-from-so.js +++ b/test-integration/test-chainlink/scripts/miniv2-json-from-so.js @@ -1,10 +1,10 @@ #!/node -const fs = require('fs') +const fs = require("fs"); const [, , inputSoFullPath, outputJsonFullPath] = process.argv; if (!inputSoFullPath || !outputJsonFullPath) { console.error( - "Usage: miniv2-json-from-so.js ", + "Usage: miniv2-json-from-so.js " ); process.exit(1); } diff --git a/test-integration/test-chainlink/src/ixtest_context.rs b/test-integration/test-chainlink/src/ixtest_context.rs index 914b4479a..e622b8a5f 100644 --- a/test-integration/test-chainlink/src/ixtest_context.rs +++ b/test-integration/test-chainlink/src/ixtest_context.rs @@ -1,6 +1,18 @@ use std::sync::Arc; +use borsh::{BorshDeserialize, BorshSerialize}; +use compressed_delegation_client::{ + CommitAndFinalizeArgs, CompressedDelegationRecord, UndelegateArgs, +}; use integration_test_tools::dlp_interface; +use light_client::indexer::{ + photon_indexer::PhotonIndexer, AddressWithTree, CompressedAccount, Indexer, + ValidityProofWithContext, +}; +use light_sdk::instruction::{ + account_meta::CompressedAccountMeta, PackedAccounts, PackedAddressTreeInfo, + SystemAccountMetaConfig, +}; use magicblock_chainlink::{ accounts_bank::mock::AccountsBankStub, cloner::{AccountCloneRequest, Cloner}, @@ -9,16 +21,29 @@ use magicblock_chainlink::{ native_program_accounts, remote_account_provider::{ chain_rpc_client::ChainRpcClientImpl, - chain_updates_client::ChainUpdatesClient, Endpoint, Endpoints, + chain_updates_client::ChainUpdatesClient, + photon_client::PhotonClientImpl, Endpoint, Endpoints, RemoteAccountProvider, }, submux::SubMuxClient, - testing::cloner_stub::ClonerStub, + testing::{ + cloner_stub::ClonerStub, + utils::{PHOTON_URL, RPC_URL}, + }, Chainlink, }; use magicblock_config::config::{ChainLinkConfig, LifecycleMode}; -use program_flexi_counter::state::FlexiCounter; +use magicblock_core::compression::{ + derive_cda_from_pda, ADDRESS_TREE, OUTPUT_QUEUE, +}; +use program_flexi_counter::{ + instruction::{ + create_init_compressed_delegation_record_ix, InitDelegationRecordArgs, + }, + state::FlexiCounter, +}; use solana_account::AccountSharedData; +use solana_compute_budget_interface::ComputeBudgetInstruction; use solana_pubkey::Pubkey; use solana_rpc_client::nonblocking::rpc_client::RpcClient; use solana_rpc_client_api::config::RpcSendTransactionConfig; @@ -26,7 +51,7 @@ use solana_sdk::{ commitment_config::CommitmentConfig, native_token::LAMPORTS_PER_SOL, signature::Keypair, signer::Signer, transaction::Transaction, }; -use solana_sdk_ids::native_loader; +use solana_sdk_ids::{native_loader, system_program}; use tokio::task; use tracing::*; @@ -37,11 +62,13 @@ pub type IxtestChainlink = Chainlink< SubMuxClient, AccountsBankStub, ClonerStub, + PhotonClientImpl, >; #[derive(Clone)] pub struct IxtestContext { pub rpc_client: Arc, + pub photon_indexer: Arc, pub chainlink: Arc, pub bank: Arc, pub remote_account_provider: Option< @@ -49,6 +76,7 @@ pub struct IxtestContext { RemoteAccountProvider< ChainRpcClientImpl, SubMuxClient, + PhotonClientImpl, >, >, >, @@ -56,13 +84,13 @@ pub struct IxtestContext { pub validator_kp: Arc, } -const RPC_URL: &str = "http://localhost:7799"; pub const TEST_AUTHORITY: [u8; 64] = [ 251, 62, 129, 184, 107, 49, 62, 184, 1, 147, 178, 128, 185, 157, 247, 92, 56, 158, 145, 53, 51, 226, 202, 96, 178, 248, 195, 133, 133, 237, 237, 146, 13, 32, 77, 204, 244, 56, 166, 172, 66, 113, 150, 218, 112, 42, 110, 181, 98, 158, 222, 194, 130, 93, 175, 100, 190, 106, 9, 69, 156, 80, 96, 72, ]; + impl IxtestContext { pub async fn init() -> Self { Self::init_with_config(ChainlinkConfig::default_with_lifecycle_mode( @@ -79,7 +107,7 @@ impl IxtestContext { let bank = Arc::::default(); let cloner = Arc::new(ClonerStub::new(bank.clone())); let (tx, rx) = tokio::sync::mpsc::channel(100); - let (fetch_cloner, remote_account_provider) = { + let (fetch_cloner, remote_account_provider, photon_indexer) = { let endpoints_vec = vec![ Endpoint::Rpc { url: RPC_URL.to_string(), @@ -89,6 +117,10 @@ impl IxtestContext { url: "ws://localhost:7800".to_string(), label: "test-ws".to_string(), }, + Endpoint::Compression { + url: PHOTON_URL.to_string(), + api_key: None, + }, ]; let endpoints = Endpoints::from(endpoints_vec.as_slice()); // Add all native programs @@ -115,6 +147,9 @@ impl IxtestContext { ) .await; + let photon_indexer = + Arc::new(PhotonIndexer::new(PHOTON_URL.to_string(), None)); + match remote_account_provider { Ok(Some(remote_account_provider)) => { debug!("Initializing FetchCloner"); @@ -130,12 +165,13 @@ impl IxtestContext { None, )), Some(provider), + photon_indexer, ) } Err(err) => { panic!("Failed to create remote account provider: {err:?}"); } - _ => (None, None), + _ => (None, None, photon_indexer), } }; let chainlink = Chainlink::try_new( @@ -150,6 +186,7 @@ impl IxtestContext { let rpc_client = IxtestContext::get_rpc_client(commitment); Self { rpc_client: Arc::new(rpc_client), + photon_indexer, chainlink: Arc::new(chainlink), bank, remote_account_provider, @@ -353,6 +390,317 @@ impl IxtestContext { self } + pub async fn init_compressed_delegation_record( + &self, + counter_auth: &Keypair, + ) -> &Self { + debug!( + "Initializing compressed delegation record for counter account {}", + counter_auth.pubkey() + ); + + let auth = counter_auth.pubkey(); + let (pda, _bump) = FlexiCounter::pda(&auth); + let record_address = derive_cda_from_pda(&pda); + + let mut remaining_accounts = self.get_packed_accounts(); + + let rpc_result: ValidityProofWithContext = self + .photon_indexer + .get_validity_proof( + vec![], + vec![AddressWithTree { + address: record_address.to_bytes(), + tree: ADDRESS_TREE, + }], + None, + ) + .await + .unwrap() + .value; + + // Insert trees in accounts + let address_merkle_tree_pubkey_index = + remaining_accounts.insert_or_get(ADDRESS_TREE); + let state_queue_pubkey_index = + remaining_accounts.insert_or_get(OUTPUT_QUEUE); + + let packed_address_tree_info = PackedAddressTreeInfo { + root_index: rpc_result.addresses[0].root_index, + address_merkle_tree_pubkey_index, + address_queue_pubkey_index: address_merkle_tree_pubkey_index, + }; + + let (remaining_accounts_metas, _, _) = + remaining_accounts.to_account_metas(); + + let init_compressed_delegation_record_ix = + create_init_compressed_delegation_record_ix( + counter_auth.pubkey(), + &remaining_accounts_metas, + InitDelegationRecordArgs { + validity_proof: rpc_result.proof, + address_tree_info: packed_address_tree_info, + output_state_tree_index: state_queue_pubkey_index, + }, + ); + + let latest_block_hash = + self.rpc_client.get_latest_blockhash().await.unwrap(); + let tx = Transaction::new_signed_with_payer( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(300_000), + init_compressed_delegation_record_ix, + ], + Some(&counter_auth.pubkey()), + &[&counter_auth], + latest_block_hash, + ); + debug!( + "Init compressed delegation record transaction: {:?}", + tx.signatures[0] + ); + self.rpc_client + .send_and_confirm_transaction_with_spinner_and_config( + &tx, + CommitmentConfig::confirmed(), + RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }, + ) + .await + .expect("Failed to init compressed delegation record"); + + // Wait for the indexer to index the account + sleep_ms(1500).await; + + self + } + + pub async fn delegate_compressed_counter( + &self, + counter_auth: &Keypair, + ) -> &Self { + debug!( + "Delegating compressed counter account {}", + counter_auth.pubkey() + ); + use program_flexi_counter::instruction::*; + + let auth = counter_auth.pubkey(); + let (pda, _bump) = FlexiCounter::pda(&auth); + let record_address = derive_cda_from_pda(&pda); + + let mut remaining_accounts = self.get_packed_accounts(); + + let (compressed_delegated_record, proof) = + self.get_compressed_account_and_proof(&record_address).await; + + let packed_state_tree = proof + .pack_tree_infos(&mut remaining_accounts) + .state_trees + .unwrap(); + let account_meta = CompressedAccountMeta { + tree_info: packed_state_tree.packed_tree_infos[0], + address: compressed_delegated_record.address.unwrap(), + output_state_tree_index: packed_state_tree.output_tree_index, + }; + + let (remaining_accounts_metas, _, _) = + remaining_accounts.to_account_metas(); + + let delegate_ix = create_delegate_compressed_ix( + counter_auth.pubkey(), + &remaining_accounts_metas, + DelegateCompressedArgs { + validator: Some(self.validator_kp.pubkey()), + validity_proof: proof.proof, + account_meta, + }, + ); + + let latest_block_hash = + self.rpc_client.get_latest_blockhash().await.unwrap(); + let tx = Transaction::new_signed_with_payer( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(300_000), + delegate_ix, + ], + Some(&counter_auth.pubkey()), + &[&counter_auth], + latest_block_hash, + ); + debug!("Delegate transaction: {:?}", tx.signatures[0]); + self.rpc_client + .send_and_confirm_transaction_with_spinner_and_config( + &tx, + CommitmentConfig::confirmed(), + RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }, + ) + .await + .expect("Failed to delegate account"); + + // Wait for the indexer to index the account + sleep_ms(1500).await; + + self + } + + pub async fn undelegate_compressed_counter( + &self, + counter_auth: &Keypair, + redelegate: bool, + ) -> &Self { + debug!( + "Undelegating compressed counter account {}", + counter_auth.pubkey() + ); + let counter_pda = self.counter_pda(&counter_auth.pubkey()); + // The committor service will call this in order to have + // chainlink subscribe to account updates of the counter account + self.chainlink + .undelegation_requested(counter_pda) + .await + .unwrap(); + + // In order to make the account undelegatable we first need to + // commmit and finalize + let (pda, _bump) = FlexiCounter::pda(&counter_auth.pubkey()); + let record_address = derive_cda_from_pda(&pda); + let (compressed_account, proof) = + self.get_compressed_account_and_proof(&record_address).await; + let mut remaining_accounts = self.get_packed_accounts(); + + let packed_tree_accounts = proof + .pack_tree_infos(&mut remaining_accounts) + .state_trees + .unwrap(); + + let account_meta = CompressedAccountMeta { + tree_info: packed_tree_accounts.packed_tree_infos[0], + address: compressed_account.address.unwrap(), + output_state_tree_index: packed_tree_accounts.output_tree_index, + }; + + let (remaining_accounts_metas, _, _) = + remaining_accounts.to_account_metas(); + + let commit_ix = + compressed_delegation_client::builders::CommitAndFinalizeBuilder { + validator: self.validator_kp.pubkey(), + delegated_account: pda, + args: CommitAndFinalizeArgs { + current_compressed_delegated_account_data: + compressed_account.data.unwrap().data.to_vec(), + new_data: FlexiCounter::new("COUNTER".to_string()) + .try_to_vec() + .unwrap(), + account_meta, + validity_proof: proof.proof, + update_nonce: 1, + allow_undelegation: true, + }, + remaining_accounts: remaining_accounts_metas, + } + .instruction(); + + let latest_block_hash = + self.rpc_client.get_latest_blockhash().await.unwrap(); + self.rpc_client + .send_and_confirm_transaction_with_spinner_and_config( + &Transaction::new_signed_with_payer( + &[commit_ix], + Some(&self.validator_kp.pubkey()), + &[&self.validator_kp], + latest_block_hash, + ), + CommitmentConfig::confirmed(), + RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }, + ) + .await + .expect("Failed to commit account"); + + // Wait for the indexer to index the account + sleep_ms(500).await; + + let (compressed_account, proof) = + self.get_compressed_account_and_proof(&record_address).await; + + let mut remaining_accounts = self.get_packed_accounts(); + + let packed_state_tree = proof + .pack_tree_infos(&mut remaining_accounts) + .state_trees + .unwrap(); + let account_meta = CompressedAccountMeta { + tree_info: packed_state_tree.packed_tree_infos[0], + address: compressed_account.address.unwrap(), + output_state_tree_index: packed_state_tree.output_tree_index, + }; + + let (remaining_accounts_metas, _, _) = + remaining_accounts.to_account_metas(); + + let undelegate_ix = + compressed_delegation_client::builders::UndelegateBuilder { + payer: counter_auth.pubkey(), + delegated_account: counter_pda, + owner_program: program_flexi_counter::ID, + system_program: system_program::ID, + args: UndelegateArgs { + validity_proof: proof.proof, + delegation_record_account_meta: account_meta, + compressed_delegated_record: + CompressedDelegationRecord::try_from_slice( + &compressed_account.data.clone().unwrap().data, + ) + .unwrap(), + }, + remaining_accounts: remaining_accounts_metas, + } + .instruction(); + let latest_block_hash = + self.rpc_client.get_latest_blockhash().await.unwrap(); + let tx = Transaction::new_signed_with_payer( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(250_000), + undelegate_ix, + ], + Some(&counter_auth.pubkey()), + &[&counter_auth], + latest_block_hash, + ); + debug!("Undelegate transaction: {:?}", tx.signatures[0]); + self.rpc_client + .send_and_confirm_transaction_with_spinner_and_config( + &tx, + CommitmentConfig::confirmed(), + RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }, + ) + .await + .expect("Failed to undelegate account"); + + // Build instructions and required signers + if redelegate { + // Wait for the indexer to index the account + sleep_ms(1500).await; + + self.delegate_compressed_counter(counter_auth).await + } else { + self + } + } + pub async fn top_up_ephemeral_fee_balance( &self, payer: &Keypair, @@ -390,11 +738,40 @@ impl IxtestContext { pub async fn get_remote_account( &self, pubkey: &Pubkey, - ) -> Option { + ) -> Option { self.rpc_client.get_account(pubkey).await.ok() } pub fn get_rpc_client(commitment: CommitmentConfig) -> RpcClient { RpcClient::new_with_commitment(RPC_URL.to_string(), commitment) } + + fn get_packed_accounts(&self) -> PackedAccounts { + let system_account_meta_config = + SystemAccountMetaConfig::new(compressed_delegation_client::ID); + let mut remaining_accounts = PackedAccounts::default(); + remaining_accounts + .add_system_accounts_v2(system_account_meta_config) + .unwrap(); + remaining_accounts + } + + async fn get_compressed_account_and_proof( + &self, + pubkey: &Pubkey, + ) -> (CompressedAccount, ValidityProofWithContext) { + let compressed_account = self + .photon_indexer + .get_compressed_account(pubkey.to_bytes(), None) + .await + .unwrap() + .value; + let validity_proof = self + .photon_indexer + .get_validity_proof(vec![compressed_account.hash], vec![], None) + .await + .unwrap() + .value; + (compressed_account, validity_proof) + } } diff --git a/test-integration/test-chainlink/src/programs.rs b/test-integration/test-chainlink/src/programs.rs index e13e82d57..2eeddb425 100644 --- a/test-integration/test-chainlink/src/programs.rs +++ b/test-integration/test-chainlink/src/programs.rs @@ -164,8 +164,8 @@ pub mod resolve_deploy { #[macro_export] macro_rules! fetch_and_assert_loaded_program_v1_v2_v4 { ($rpc_client:expr, $program_id:expr, $expected:expr) => {{ + use solana_account::AccountSharedData; use solana_loader_v4_interface::state::LoaderV4Status; - use solana_sdk::account::AccountSharedData; use tracing::*; let program_account = $rpc_client diff --git a/test-integration/test-chainlink/src/test_context.rs b/test-integration/test-chainlink/src/test_context.rs index af839f405..b0379f250 100644 --- a/test-integration/test-chainlink/src/test_context.rs +++ b/test-integration/test-chainlink/src/test_context.rs @@ -17,6 +17,7 @@ use magicblock_chainlink::{ accounts::account_shared_with_owner, cloner_stub::ClonerStub, deleg::add_delegation_record_for, + photon_client_mock::PhotonClientMock, rpc_client_mock::{ChainRpcClientMock, ChainRpcClientMockBuilder}, utils::{create_test_lru_cache, create_test_lru_cache_with_config}, }, @@ -35,6 +36,7 @@ pub type TestChainlink = Chainlink< ChainPubsubClientMock, AccountsBankStub, ClonerStub, + PhotonClientMock, >; #[derive(Clone)] @@ -44,7 +46,13 @@ pub struct TestContext { pub chainlink: Arc, pub bank: Arc, pub remote_account_provider: Option< - Arc>, + Arc< + RemoteAccountProvider< + ChainRpcClientMock, + ChainPubsubClientMock, + PhotonClientMock, + >, + >, >, pub cloner: Arc, pub validator_pubkey: Pubkey, @@ -52,13 +60,14 @@ pub struct TestContext { impl TestContext { pub async fn init(slot: Slot) -> Self { - let (rpc_client, pubsub_client) = { + let (rpc_client, pubsub_client, photon_indexer) = { let rpc_client = ChainRpcClientMockBuilder::new().slot(slot).build(); let (updates_sndr, updates_rcvr) = mpsc::channel(100); let pubsub_client = ChainPubsubClientMock::new(updates_sndr, updates_rcvr); - (rpc_client, pubsub_client) + let photon_indexer = PhotonClientMock::new(); + (rpc_client, pubsub_client, photon_indexer) }; let lifecycle_mode = LifecycleMode::Ephemeral; @@ -82,6 +91,7 @@ impl TestContext { RemoteAccountProvider::try_from_clients_and_mode( rpc_client.clone(), pubsub_client.clone(), + photon_indexer.clone(), tx, &config, subscribed_accounts, diff --git a/test-integration/test-chainlink/tests/ix_01_ensure-accounts.rs b/test-integration/test-chainlink/tests/ix_01_ensure-accounts.rs index b48f19e6d..e2c63d7d2 100644 --- a/test-integration/test-chainlink/tests/ix_01_ensure-accounts.rs +++ b/test-integration/test-chainlink/tests/ix_01_ensure-accounts.rs @@ -94,3 +94,44 @@ async fn ixtest_write_existing_account_valid_delegation_record() { // TODO(thlorenz): @ implement this test when we can actually delegate to a specific // authority: test_write_existing_account_other_authority + +// ----------------- +// BasicScenarios: Compressed account is initialized and already delegated to us +// ----------------- +#[tokio::test] +async fn ixtest_write_existing_account_compressed() { + init_logger(); + + let ctx = IxtestContext::init().await; + + let counter_auth = Keypair::new(); + ctx.init_counter(&counter_auth) + .await + .init_compressed_delegation_record(&counter_auth) + .await + .delegate_compressed_counter(&counter_auth) + .await; + + let counter_pda = ctx.counter_pda(&counter_auth.pubkey()); + info!(counter =% counter_pda, "init"); + let pubkeys = [counter_pda]; + + ctx.chainlink + .ensure_accounts( + &pubkeys, + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + + let account = ctx.cloner.get_account(&counter_pda).unwrap(); + assert_cloned_as_delegated!( + ctx.cloner, + &[counter_pda], + account.remote_slot(), + program_flexi_counter::id() + ); + assert_not_subscribed!(ctx.chainlink, &[&counter_pda]); +} diff --git a/test-integration/test-chainlink/tests/ix_03_deleg_after_sub.rs b/test-integration/test-chainlink/tests/ix_03_deleg_after_sub.rs index 8c91555ae..e7badfdbc 100644 --- a/test-integration/test-chainlink/tests/ix_03_deleg_after_sub.rs +++ b/test-integration/test-chainlink/tests/ix_03_deleg_after_sub.rs @@ -95,3 +95,90 @@ async fn ixtest_deleg_after_subscribe_case2() { ); } } + +#[tokio::test] +async fn ixtest_deleg_after_subscribe_case2_compressed() { + init_logger(); + + let ctx = IxtestContext::init().await; + + let counter_auth = Keypair::new(); + let counter_pda = ctx.counter_pda(&counter_auth.pubkey()); + let pubkeys = [counter_pda]; + info!(counter =% counter_pda, "init"); + + // 1. Initially the account does not exist + { + info!("1. Initially the account does not exist"); + let res = ctx + .chainlink + .ensure_accounts( + &pubkeys, + None, + AccountFetchOrigin::GetAccount, + None, + ) + .await + .unwrap(); + + assert_not_found!(res, &pubkeys); + assert_not_cloned!(ctx.cloner, &pubkeys); + assert_not_subscribed!(ctx.chainlink, &[&counter_pda]); + } + + // 2. Account created with original owner (program) + { + info!("2. Create account owned by program_flexi_counter"); + ctx.init_counter(&counter_auth).await; + + ctx.chainlink + .ensure_accounts( + &pubkeys, + Some(&pubkeys), + AccountFetchOrigin::GetAccount, + None, + ) + .await + .unwrap(); + + // Assert cloned account state matches the remote account and slot + let account = ctx.cloner.get_account(&counter_pda).unwrap(); + assert_cloned_as_undelegated!( + ctx.cloner, + &[counter_pda], + account.remote_slot(), + program_flexi_counter::id() + ); + + assert_subscribed_without_delegation_record!(ctx.chainlink, &pubkeys); + } + + // 3. Account delegated to us + { + info!("3. Delegate account to us"); + ctx.init_compressed_delegation_record(&counter_auth) + .await + .delegate_compressed_counter(&counter_auth) + .await; + + ctx.chainlink + .ensure_accounts( + &pubkeys, + None, + AccountFetchOrigin::GetAccount, + None, + ) + .await + .unwrap(); + + let account = ctx.cloner.get_account(&counter_pda).unwrap(); + assert_cloned_as_delegated!( + ctx.cloner, + &[counter_pda], + account.remote_slot(), + program_flexi_counter::id() + ); + + assert_not_subscribed!(ctx.chainlink, &[&counter_pda]); + } +} diff --git a/test-integration/test-chainlink/tests/ix_06_redeleg_us_separate_slots.rs b/test-integration/test-chainlink/tests/ix_06_redeleg_us_separate_slots.rs index 64a6821e0..186852cff 100644 --- a/test-integration/test-chainlink/tests/ix_06_redeleg_us_separate_slots.rs +++ b/test-integration/test-chainlink/tests/ix_06_redeleg_us_separate_slots.rs @@ -12,6 +12,8 @@ use solana_sdk::{signature::Keypair, signer::Signer}; use test_chainlink::{ixtest_context::IxtestContext, sleep_ms}; use tracing::*; +const RETRIES: usize = 30; + #[tokio::test] async fn ixtest_undelegate_redelegate_to_us_in_separate_slots() { init_logger(); @@ -51,7 +53,7 @@ async fn ixtest_undelegate_redelegate_to_us_in_separate_slots() { &[counter_pda], account.remote_slot(), program_flexi_counter::id(), - 30 + RETRIES ); // Accounts delegated to us should not be tracked via subscription @@ -95,7 +97,7 @@ async fn ixtest_undelegate_redelegate_to_us_in_separate_slots() { &[counter_pda], account.remote_slot(), program_flexi_counter::id(), - 30 + RETRIES ); // Accounts delegated to us should not be tracked via subscription @@ -105,3 +107,102 @@ async fn ixtest_undelegate_redelegate_to_us_in_separate_slots() { ); } } + +#[tokio::test] +async fn ixtest_undelegate_redelegate_to_us_in_separate_slots_compressed() { + init_logger(); + + let ctx = IxtestContext::init().await; + + // Create and delegate a counter account to us + let counter_auth = Keypair::new(); + ctx.init_counter(&counter_auth) + .await + .init_compressed_delegation_record(&counter_auth) + .await + .delegate_compressed_counter(&counter_auth) + .await; + + let counter_pda = ctx.counter_pda(&counter_auth.pubkey()); + let pubkeys = [counter_pda]; + + // 1. Account delegated to us - readable and writable + { + info!("1. Account delegated to us"); + + ctx.chainlink + .ensure_accounts( + &pubkeys, + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + sleep_ms(1_500).await; + + // Account should be cloned as delegated + let account = ctx.cloner.get_account(&counter_pda).unwrap(); + assert_cloned_as_delegated_with_retries!( + ctx.cloner, + &[counter_pda], + account.remote_slot(), + program_flexi_counter::id(), + RETRIES + ); + + // Accounts delegated to us should not be tracked via subscription + assert_not_subscribed!(ctx.chainlink, &[&counter_pda]); + } + + // 2. Account is undelegated - writes refused, subscription set + { + info!( + "2. Account is undelegated - Would refuse write (undelegated on chain)" + ); + + ctx.undelegate_compressed_counter(&counter_auth, false) + .await; + sleep_ms(1_500).await; + + // Account should be cloned as undelegated (owned by program again) + let account = ctx.cloner.get_account(&counter_pda).unwrap(); + assert_cloned_as_undelegated!( + ctx.cloner, + &[counter_pda], + account.remote_slot(), + program_flexi_counter::id() + ); + + assert_subscribed_without_delegation_record!(ctx.chainlink, &pubkeys); + } + + // 3. Account redelegated to us (separate slot) - writes allowed again + { + info!("3. Account redelegated to us - Would allow write"); + ctx.delegate_compressed_counter(&counter_auth).await; + + ctx.chainlink + .ensure_accounts( + &pubkeys, + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + + // Account should be cloned as delegated back to us + let account = ctx.cloner.get_account(&counter_pda).unwrap(); + assert_cloned_as_delegated_with_retries!( + ctx.cloner, + &[counter_pda], + account.remote_slot(), + program_flexi_counter::id(), + RETRIES + ); + + // Accounts delegated to us should not be tracked via subscription + assert_not_subscribed!(ctx.chainlink, &[&counter_pda]); + } +} diff --git a/test-integration/test-chainlink/tests/ix_07_redeleg_us_same_slot.rs b/test-integration/test-chainlink/tests/ix_07_redeleg_us_same_slot.rs index 75641f651..94dd8d583 100644 --- a/test-integration/test-chainlink/tests/ix_07_redeleg_us_same_slot.rs +++ b/test-integration/test-chainlink/tests/ix_07_redeleg_us_same_slot.rs @@ -87,3 +87,74 @@ async fn ixtest_undelegate_redelegate_to_us_in_same_slot() { ); } } + +#[tokio::test] +async fn ixtest_undelegate_redelegate_to_us_in_same_slot_compressed() { + init_logger(); + + let ctx = IxtestContext::init().await; + + // Create and delegate a counter account to us + let counter_auth = Keypair::new(); + ctx.init_counter(&counter_auth) + .await + .init_compressed_delegation_record(&counter_auth) + .await + .delegate_compressed_counter(&counter_auth) + .await; + + let counter_pda = ctx.counter_pda(&counter_auth.pubkey()); + let pubkeys = [counter_pda]; + + // 1. Account delegated to us - readable and writable + { + info!("1. Account delegated to us"); + + ctx.chainlink + .ensure_accounts( + &pubkeys, + None, + AccountFetchOrigin::GetMultipleAccounts, + None, + ) + .await + .unwrap(); + sleep_ms(1_500).await; + + // Account should be cloned as delegated + let account = ctx.cloner.get_account(&counter_pda).unwrap(); + assert_cloned_as_delegated!( + ctx.cloner, + &[counter_pda], + account.remote_slot(), + program_flexi_counter::id() + ); + + // Accounts delegated to us should not be tracked via subscription + assert_not_subscribed!(ctx.chainlink, &[&counter_pda]); + } + + // 2. Account is undelegated and redelegated to us (same slot) - writes allowed again + { + info!( + "2. Account is undelegated and redelegated to us in the same slot" + ); + + ctx.undelegate_compressed_counter(&counter_auth, true).await; + + // Wait for pubsub update to trigger subscription handler + sleep_ms(1_500).await; + + // Account should still be cloned as delegated to us + let account = ctx.cloner.get_account(&counter_pda).unwrap(); + assert_cloned_as_delegated!( + ctx.cloner, + &[counter_pda], + account.remote_slot(), + program_flexi_counter::id() + ); + + // Accounts delegated to us should not be tracked via subscription + assert_not_subscribed!(ctx.chainlink, &[&counter_pda]); + } +} diff --git a/test-integration/test-chainlink/tests/ix_remote_account_provider.rs b/test-integration/test-chainlink/tests/ix_remote_account_provider.rs index 4099bd002..47b71a416 100644 --- a/test-integration/test-chainlink/tests/ix_remote_account_provider.rs +++ b/test-integration/test-chainlink/tests/ix_remote_account_provider.rs @@ -2,15 +2,15 @@ use magicblock_chainlink::{ remote_account_provider::{ chain_rpc_client::ChainRpcClientImpl, chain_updates_client::ChainUpdatesClient, - config::RemoteAccountProviderConfig, Endpoint, Endpoints, - RemoteAccountProvider, RemoteAccountUpdateSource, + config::RemoteAccountProviderConfig, photon_client::PhotonClientImpl, + Endpoint, Endpoints, RemoteAccountProvider, RemoteAccountUpdateSource, }, submux::SubMuxClient, testing::utils::{ airdrop, await_next_slot, current_slot, dump_remote_account_lamports, dump_remote_account_update_source, get_remote_account_lamports, get_remote_account_update_sources, init_logger, random_pubkey, - sleep_ms, PUBSUB_URL, RPC_URL, + sleep_ms, PHOTON_URL, PUBSUB_URL, RPC_URL, }, AccountFetchOrigin, }; @@ -22,9 +22,11 @@ use solana_sdk::commitment_config::CommitmentConfig; use tokio::sync::mpsc; use tracing::{debug, info}; -async fn init_remote_account_provider( -) -> RemoteAccountProvider> -{ +async fn init_remote_account_provider() -> RemoteAccountProvider< + ChainRpcClientImpl, + SubMuxClient, + PhotonClientImpl, +> { let (fwd_tx, _fwd_rx) = mpsc::channel(100); let endpoints_vec = vec![ Endpoint::Rpc { @@ -35,11 +37,16 @@ async fn init_remote_account_provider( url: PUBSUB_URL.to_string(), label: "test-ws".to_string(), }, + Endpoint::Compression { + url: PHOTON_URL.to_string(), + api_key: None, + }, ]; let endpoints = Endpoints::from(endpoints_vec.as_slice()); RemoteAccountProvider::< ChainRpcClientImpl, SubMuxClient, + PhotonClientImpl, >::try_from_urls_and_config( &endpoints, CommitmentConfig::confirmed(), @@ -47,7 +54,10 @@ async fn init_remote_account_provider( &RemoteAccountProviderConfig::default_with_lifecycle_mode( LifecycleMode::Ephemeral, ), - ).await.expect("Failed to create RemoteAccountProvider").unwrap() + ) + .await + .expect("Failed to create RemoteAccountProvider") + .unwrap() } #[tokio::test] diff --git a/test-integration/test-committor-service/Cargo.toml b/test-integration/test-committor-service/Cargo.toml index 4d6af1f58..061fc1de6 100644 --- a/test-integration/test-committor-service/Cargo.toml +++ b/test-integration/test-committor-service/Cargo.toml @@ -6,14 +6,20 @@ edition.workspace = true [dev-dependencies] async-trait = { workspace = true } borsh = { workspace = true } -tracing = { workspace = true } +compressed-delegation-client = { workspace = true } +light-client = { workspace = true, features = ["v2"] } +light-compressed-account = { workspace = true } +light-sdk = { workspace = true, features = ["v2"] } +light-sdk-types = { workspace = true, features = ["v2"] } futures = { workspace = true } +magicblock-chainlink = { workspace = true } magicblock-committor-program = { workspace = true, features = [ "no-entrypoint", ] } magicblock-committor-service = { workspace = true, features = [ "dev-context-only-utils", ] } +magicblock-core = { workspace = true } magicblock-delegation-program = { workspace = true, features = [ "no-entrypoint", ] } @@ -32,6 +38,7 @@ solana-rpc-client-api = { workspace = true } solana-sdk = { workspace = true } test-kit = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } [features] test_table_close = [] diff --git a/test-integration/test-committor-service/tests/common.rs b/test-integration/test-committor-service/tests/common.rs index 81adae320..b1f4426b5 100644 --- a/test-integration/test-committor-service/tests/common.rs +++ b/test-integration/test-committor-service/tests/common.rs @@ -7,6 +7,10 @@ use std::{ }; use async_trait::async_trait; +use light_client::indexer::photon_indexer::PhotonIndexer; +use light_compressed_account::instruction_data::compressed_proof::ValidityProof; +use light_sdk_types::instruction::account_meta::CompressedAccountMeta; +use magicblock_chainlink::testing::utils::PHOTON_URL; use magicblock_committor_service::{ intent_executor::{ task_info_fetcher::{ @@ -15,7 +19,7 @@ use magicblock_committor_service::{ }, IntentExecutorImpl, }, - tasks::CommitTask, + tasks::{task_builder::CompressedData, CommitTask, CompressedCommitTask}, transaction_preparator::{ delivery_preparator::DeliveryPreparator, TransactionPreparatorImpl, }, @@ -40,9 +44,16 @@ pub async fn create_test_client() -> MagicblockRpcClient { MagicblockRpcClient::new(Arc::new(rpc_client)) } +// Helper function to create a test PhotonIndexer +pub fn create_test_photon_indexer() -> Arc { + let url = PHOTON_URL.to_string(); + Arc::new(PhotonIndexer::new(url, None)) +} + // Test fixture structure pub struct TestFixture { pub rpc_client: MagicblockRpcClient, + pub _photon_indexer: Arc, pub table_mania: TableMania, pub authority: Keypair, pub compute_budget_config: ComputeBudgetConfig, @@ -58,6 +69,8 @@ impl TestFixture { pub async fn new_with_keypair(authority: Keypair) -> Self { let rpc_client = create_test_client().await; + let _photon_indexer = create_test_photon_indexer(); + // TableMania let gc_config = GarbageCollectorConfig::default(); let table_mania = @@ -75,6 +88,7 @@ impl TestFixture { let compute_budget_config = ComputeBudgetConfig::new(1_000_000); Self { rpc_client, + _photon_indexer, table_mania, authority, compute_budget_config, @@ -127,6 +141,7 @@ impl TaskInfoFetcher for MockTaskInfoFetcher { &self, pubkeys: &[Pubkey], _: u64, + _compressed: bool, ) -> TaskInfoFetcherResult> { Ok(pubkeys.iter().map(|pubkey| (*pubkey, 0)).collect()) } @@ -164,6 +179,14 @@ impl TaskInfoFetcher for MockTaskInfoFetcher { .collect() }) } + + async fn get_compressed_data( + &self, + _pubkey: &Pubkey, + _: Option, + ) -> TaskInfoFetcherResult { + Ok(CompressedData::default()) + } } #[allow(dead_code)] @@ -194,6 +217,40 @@ pub fn create_commit_task(data: &[u8]) -> CommitTask { } } +#[allow(dead_code)] +/// Test-only helper. Uses dummy compressed_data (empty delegation record, +/// default proof/meta) and must not be used for on-chain compressed-delegation +/// transactions. +pub fn create_dummy_compressed_commit_task( + pubkey: Pubkey, + hash: [u8; 32], + data: &[u8], +) -> CompressedCommitTask { + static COMMIT_ID: AtomicU64 = AtomicU64::new(0); + CompressedCommitTask { + commit_id: COMMIT_ID.fetch_add(1, Ordering::Relaxed), + compressed_data: CompressedData { + hash, + compressed_delegation_record_bytes: vec![], + remaining_accounts: vec![], + account_meta: CompressedAccountMeta::default(), + proof: ValidityProof::default(), + }, + allow_undelegation: false, + committed_account: CommittedAccount { + pubkey, + account: Account { + lamports: 1000, + data: data.to_vec(), + owner: dlp::id(), + executable: false, + rent_epoch: 0, + }, + remote_slot: Default::default(), + }, + } +} + #[allow(dead_code)] pub fn create_committed_account(data: &[u8]) -> CommittedAccount { CommittedAccount { diff --git a/test-integration/test-committor-service/tests/test_delivery_preparator.rs b/test-integration/test-committor-service/tests/test_delivery_preparator.rs index dded722ee..aade86b0e 100644 --- a/test-integration/test-committor-service/tests/test_delivery_preparator.rs +++ b/test-integration/test-committor-service/tests/test_delivery_preparator.rs @@ -1,4 +1,5 @@ use borsh::BorshDeserialize; +use compressed_delegation_client::CompressedDelegationRecord; use futures::future::join_all; use magicblock_committor_program::Chunks; use magicblock_committor_service::{ @@ -10,11 +11,22 @@ use magicblock_committor_service::{ BaseTask, PreparationState, }, }; -use solana_sdk::signer::Signer; +use magicblock_program::validator::{ + generate_validator_authority_if_needed, validator_authority_id, +}; +use solana_sdk::{rent::Rent, signature::Keypair, signer::Signer}; +use test_kit::init_logger; -use crate::common::{create_commit_task, generate_random_bytes, TestFixture}; +use crate::{ + common::{ + create_commit_task, create_dummy_compressed_commit_task, + generate_random_bytes, TestFixture, + }, + utils::transactions::init_and_delegate_compressed_record_on_chain, +}; mod common; +mod utils; #[tokio::test] async fn test_prepare_10kb_buffer() { @@ -36,6 +48,8 @@ async fn test_prepare_10kb_buffer() { &fixture.authority, &mut strategy, &None::, + &fixture.create_task_info_fetcher(), + None, ) .await; @@ -104,6 +118,8 @@ async fn test_prepare_multiple_buffers() { &fixture.authority, &mut strategy, &None::, + &fixture.create_task_info_fetcher(), + None, ) .await; @@ -183,6 +199,8 @@ async fn test_lookup_tables() { &fixture.authority, &mut strategy, &None::, + &fixture.create_task_info_fetcher(), + None, ) .await; assert!(result.is_ok(), "Failed to prepare lookup tables"); @@ -222,6 +240,8 @@ async fn test_already_initialized_error_handled() { &fixture.authority, &mut strategy, &None::, + &fixture.create_task_info_fetcher(), + None, ) .await; assert!(result.is_ok(), "Preparation failed: {:?}", result.err()); @@ -261,6 +281,8 @@ async fn test_already_initialized_error_handled() { &fixture.authority, &mut strategy, &None::, + &fixture.create_task_info_fetcher(), + None, ) .await; assert!(result.is_ok(), "Preparation failed: {:?}", result.err()); @@ -285,8 +307,6 @@ async fn test_already_initialized_error_handled() { #[tokio::test] async fn test_prepare_cleanup_and_reprepare_mixed_tasks() { - use borsh::BorshDeserialize; - let fixture = TestFixture::new().await; let preparator = fixture.create_delivery_preparator(); @@ -328,6 +348,8 @@ async fn test_prepare_cleanup_and_reprepare_mixed_tasks() { &fixture.authority, &mut strategy, &None::, + &fixture.create_task_info_fetcher(), + None, ) .await; assert!(res.is_ok(), "Initial prepare failed: {:?}", res.err()); @@ -401,7 +423,7 @@ async fn test_prepare_cleanup_and_reprepare_mixed_tasks() { .truncate(buf_b_data.len() - 5); } - // --- Step 4: re-prepare with the same logical tasks (same commit IDs, mutated data) --- + // --- Step 3: re-prepare with the same logical tasks (same commit IDs, mutated data) --- let mut strategy2 = TransactionStrategy { optimized_tasks: vec![ { @@ -427,6 +449,8 @@ async fn test_prepare_cleanup_and_reprepare_mixed_tasks() { &fixture.authority, &mut strategy2, &None::, + &fixture.create_task_info_fetcher(), + None, ) .await; assert!( @@ -488,3 +512,67 @@ async fn test_prepare_cleanup_and_reprepare_mixed_tasks() { ); } } + +#[tokio::test] +async fn test_prepare_compressed_commit() { + let fixture = TestFixture::new().await; + let preparator = fixture.create_delivery_preparator(); + + generate_validator_authority_if_needed(); + init_logger!(); + + let counter_auth = Keypair::new(); + let (pda, _address, account) = + init_and_delegate_compressed_record_on_chain(&counter_auth).await; + + let data = generate_random_bytes(10); + let mut task = + Box::new(ArgsTask::new(ArgsTaskType::CompressedCommitAndFinalize( + create_dummy_compressed_commit_task( + pda, + Default::default(), + data.as_slice(), + ), + ))) as Box; + let compressed_data = task.get_compressed_data().cloned(); + + preparator + .prepare_task( + &fixture.authority, + &mut *task, + &None::, + &fixture.create_task_info_fetcher(), + None, + ) + .await + .expect("Failed to prepare compressed commit"); + + // Verify the compressed data was updated + let new_compressed_data = task.get_compressed_data().cloned(); + assert_ne!( + new_compressed_data, compressed_data, + "Compressed data size mismatch" + ); + + // Verify the delegation record is correct + let delegation_record = CompressedDelegationRecord::try_from_slice( + &new_compressed_data + .unwrap() + .compressed_delegation_record_bytes, + ) + .unwrap(); + let expected = CompressedDelegationRecord { + authority: validator_authority_id(), + pda, + delegation_slot: delegation_record.delegation_slot, + lamports: Rent::default().minimum_balance(account.data.len()), + data: account.data, + last_update_nonce: 0, + is_undelegatable: false, + owner: program_flexi_counter::id(), + }; + assert_eq!( + delegation_record, expected, + "Delegation record should be the same" + ); +} diff --git a/test-integration/test-committor-service/tests/test_intent_executor.rs b/test-integration/test-committor-service/tests/test_intent_executor.rs index edde6dcd0..28db72dad 100644 --- a/test-integration/test-committor-service/tests/test_intent_executor.rs +++ b/test-integration/test-committor-service/tests/test_intent_executor.rs @@ -87,8 +87,10 @@ impl TestEnv { .await; let transaction_preparator = fixture.create_transaction_preparator(); - let task_info_fetcher = - Arc::new(CacheTaskInfoFetcher::new(fixture.rpc_client.clone())); + let task_info_fetcher = Arc::new(CacheTaskInfoFetcher::new( + fixture.rpc_client.clone(), + fixture._photon_indexer.clone(), + )); let tm = &fixture.table_mania; let mut pre_test_tablemania_state = HashMap::new(); @@ -140,6 +142,7 @@ async fn test_commit_id_error_parsing() { .fetch_next_commit_ids( &intent.get_committed_pubkeys().unwrap(), remote_slot, + false, ) .await .unwrap(); @@ -154,6 +157,7 @@ async fn test_commit_id_error_parsing() { .prepare_and_execute_strategy( &mut transaction_strategy, &None::, + None, ) .await; assert!(execution_result.is_ok(), "Preparation is expected to pass!"); @@ -205,6 +209,7 @@ async fn test_undelegation_error_parsing() { .prepare_and_execute_strategy( &mut transaction_strategy, &None::, + None, ) .await; assert!(execution_result.is_ok(), "Preparation is expected to pass!"); @@ -265,6 +270,7 @@ async fn test_action_error_parsing() { .prepare_and_execute_strategy( &mut transaction_strategy, &None::, + None, ) .await; assert!(execution_result.is_ok(), "Preparation is expected to pass!"); @@ -277,7 +283,12 @@ async fn test_action_error_parsing() { execution_err, TransactionStrategyExecutionError::ActionsError(_, _) )); - assert!(execution_err.to_string().contains(EXPECTED_ERR_MSG)); + assert!( + execution_err.to_string().contains(EXPECTED_ERR_MSG), + "{} != {}", + execution_err, + EXPECTED_ERR_MSG + ); } #[tokio::test] @@ -322,6 +333,7 @@ async fn test_cpi_limits_error_parsing() { .prepare_and_execute_strategy( &mut transaction_strategy, &None::, + None, ) .await; assert!(execution_result.is_ok(), "Preparation is expected to pass!"); @@ -414,7 +426,7 @@ async fn test_commit_id_error_recovery() { // Invalidate commit nonce cache let res = task_info_fetcher - .fetch_next_commit_ids(&[committed_account.pubkey], remote_slot) + .fetch_next_commit_ids(&[committed_account.pubkey], remote_slot, false) .await; assert!(res.is_ok()); assert!(res.unwrap().contains_key(&committed_account.pubkey)); @@ -615,7 +627,7 @@ async fn test_commit_id_and_action_errors_recovery() { // Invalidate commit nonce cache let res = task_info_fetcher - .fetch_next_commit_ids(&[committed_account.pubkey], remote_slot) + .fetch_next_commit_ids(&[committed_account.pubkey], remote_slot, false) .await; assert!(res.is_ok()); assert!(res.unwrap().contains_key(&committed_account.pubkey)); @@ -821,7 +833,7 @@ async fn test_commit_id_actions_cpi_limit_errors_recovery() { // Force CommitIDError by invalidating the commit-nonce cache before running let pubkeys: Vec<_> = committed_accounts.iter().map(|c| c.pubkey).collect(); let mut invalidated_keys = task_info_fetcher - .fetch_next_commit_ids(&pubkeys, Default::default()) + .fetch_next_commit_ids(&pubkeys, Default::default(), false) .await .unwrap(); diff --git a/test-integration/test-committor-service/tests/test_ix_commit_local.rs b/test-integration/test-committor-service/tests/test_ix_commit_local.rs index 58497e5bc..4c65b0c42 100644 --- a/test-integration/test-committor-service/tests/test_ix_commit_local.rs +++ b/test-integration/test-committor-service/tests/test_ix_commit_local.rs @@ -4,7 +4,12 @@ use std::{ time::{Duration, Instant}, }; -use borsh::to_vec; +use borsh::{to_vec, BorshDeserialize}; +use compressed_delegation_client::CompressedDelegationRecord; +use light_client::indexer::{ + photon_indexer::PhotonIndexer, CompressedAccount, Indexer, +}; +use magicblock_chainlink::testing::utils::{PHOTON_URL, RPC_URL}; use magicblock_committor_service::{ config::ChainConfig, intent_executor::ExecutionOutput, @@ -13,6 +18,7 @@ use magicblock_committor_service::{ types::{ScheduledBaseIntentWrapper, TriggerType}, BaseIntentCommittor, CommittorService, ComputeBudgetConfig, }; +use magicblock_core::compression::derive_cda_from_pda; use magicblock_program::magic_scheduled_base_intent::{ CommitAndUndelegate, CommitType, CommittedAccount, MagicBaseIntent, ScheduledBaseIntent, UndelegateType, @@ -23,8 +29,8 @@ use solana_account::{Account, ReadableAccount}; use solana_pubkey::Pubkey; use solana_rpc_client::nonblocking::rpc_client::RpcClient; use solana_sdk::{ - commitment_config::CommitmentConfig, hash::Hash, signature::Keypair, - signer::Signer, transaction::Transaction, + commitment_config::CommitmentConfig, hash::Hash, rent::Rent, + signature::Keypair, signer::Signer, transaction::Transaction, }; use test_kit::init_logger; use tokio::task::JoinSet; @@ -37,6 +43,7 @@ use crate::utils::{ transactions::{ fund_validator_auth_and_ensure_validator_fees_vault, init_and_delegate_account_on_chain, + init_and_delegate_compressed_record_on_chain, }, }; @@ -45,8 +52,16 @@ mod utils; // ----------------- // Utilities and Setup // ----------------- + type ExpectedStrategies = HashMap; +enum CommitAccountMode { + Commit, + CompressedCommit, + CommitAndUndelegate, + CompressedCommitAndUndelegate, +} + fn expect_strategies( strategies: &[(CommitStrategy, u8)], ) -> ExpectedStrategies { @@ -67,62 +82,122 @@ fn expect_strategies( #[tokio::test] async fn test_ix_commit_single_account_100_bytes() { - commit_single_account(100, CommitStrategy::StateArgs, false).await; + commit_single_account( + 100, + CommitStrategy::StateArgs, + CommitAccountMode::Commit, + ) + .await; } #[tokio::test] async fn test_ix_commit_single_account_100_bytes_and_undelegate() { - commit_single_account(100, CommitStrategy::StateArgs, true).await; + commit_single_account( + 100, + CommitStrategy::StateArgs, + CommitAccountMode::CommitAndUndelegate, + ) + .await; } #[tokio::test] async fn test_ix_commit_single_account_256_bytes() { - commit_single_account(256, CommitStrategy::StateArgs, false).await; + commit_single_account( + 256, + CommitStrategy::StateArgs, + CommitAccountMode::Commit, + ) + .await; } #[tokio::test] async fn test_ix_commit_single_account_257_bytes() { - commit_single_account(257, CommitStrategy::DiffArgs, false).await; + commit_single_account( + 257, + CommitStrategy::DiffArgs, + CommitAccountMode::Commit, + ) + .await; } #[tokio::test] async fn test_ix_commit_single_account_256_bytes_and_undelegate() { - commit_single_account(256, CommitStrategy::StateArgs, true).await; + commit_single_account( + 256, + CommitStrategy::StateArgs, + CommitAccountMode::CommitAndUndelegate, + ) + .await; } #[tokio::test] async fn test_ix_commit_single_account_257_bytes_and_undelegate() { - commit_single_account(257, CommitStrategy::DiffArgs, true).await; + commit_single_account( + 257, + CommitStrategy::DiffArgs, + CommitAccountMode::CommitAndUndelegate, + ) + .await; } #[tokio::test] async fn test_ix_commit_single_account_800_bytes() { - commit_single_account(800, CommitStrategy::DiffArgs, false).await; + commit_single_account( + 800, + CommitStrategy::DiffArgs, + CommitAccountMode::Commit, + ) + .await; } #[tokio::test] async fn test_ix_commit_single_account_800_bytes_and_undelegate() { - commit_single_account(800, CommitStrategy::DiffArgs, true).await; + commit_single_account( + 800, + CommitStrategy::DiffArgs, + CommitAccountMode::CommitAndUndelegate, + ) + .await; } #[tokio::test] async fn test_ix_commit_single_account_one_kb() { - commit_single_account(1024, CommitStrategy::DiffArgs, false).await; + commit_single_account( + 1024, + CommitStrategy::DiffArgs, + CommitAccountMode::Commit, + ) + .await; } #[tokio::test] async fn test_ix_commit_single_account_ten_kb() { - commit_single_account(10 * 1024, CommitStrategy::DiffArgs, false).await; + commit_single_account( + 10 * 1024, + CommitStrategy::DiffArgs, + CommitAccountMode::Commit, + ) + .await; } #[tokio::test] async fn test_ix_commit_order_book_change_100_bytes() { - commit_book_order_account(100, CommitStrategy::DiffArgs, false).await; + commit_book_order_account( + 100, + CommitStrategy::DiffArgs, + CommitAccountMode::Commit, + ) + .await; } #[tokio::test] async fn test_ix_commit_order_book_change_671_bytes() { - commit_book_order_account(671, CommitStrategy::DiffArgs, false).await; + commit_book_order_account( + 671, + CommitStrategy::DiffArgs, + CommitAccountMode::Commit, + ) + .await; } #[tokio::test] @@ -131,38 +206,65 @@ async fn test_ix_commit_order_book_change_673_bytes() { // of size 1644 (which is the max limit), but while the size of raw bytes for 671 is within // 1232 limit, the size for 672 exceeds by 1 (1233). That is why we used // 673 as changed_len where CommitStrategy goes from Args to FromBuffer. - commit_book_order_account(673, CommitStrategy::DiffBuffer, false).await; + commit_book_order_account( + 673, + CommitStrategy::DiffBuffer, + CommitAccountMode::Commit, + ) + .await; } #[tokio::test] async fn test_ix_commit_order_book_change_10k_bytes() { - commit_book_order_account(10 * 1024, CommitStrategy::DiffBuffer, false) - .await; + commit_book_order_account( + 10 * 1024, + CommitStrategy::DiffBuffer, + CommitAccountMode::Commit, + ) + .await; } async fn commit_single_account( bytes: usize, expected_strategy: CommitStrategy, - undelegate: bool, + mode: CommitAccountMode, ) { init_logger!(); let validator_auth = ensure_validator_authority(); fund_validator_auth_and_ensure_validator_fees_vault(&validator_auth).await; + let photon_client = + Arc::new(PhotonIndexer::new(PHOTON_URL.to_string(), None)); + // Run each test with and without finalizing let service = CommittorService::try_start( validator_auth.insecure_clone(), ":memory:", ChainConfig::local(ComputeBudgetConfig::new(1_000_000)), + photon_client, ) .unwrap(); let service = CommittorServiceExt::new(Arc::new(service)); let counter_auth = Keypair::new(); - let (pubkey, mut account) = - init_and_delegate_account_on_chain(&counter_auth, bytes as u64, None) - .await; + let (pubkey, mut account) = match mode { + CommitAccountMode::Commit | CommitAccountMode::CommitAndUndelegate => { + init_and_delegate_account_on_chain( + &counter_auth, + bytes as u64, + None, + ) + .await + } + CommitAccountMode::CompressedCommit + | CommitAccountMode::CompressedCommitAndUndelegate => { + let (pubkey, _address, account) = + init_and_delegate_compressed_record_on_chain(&counter_auth) + .await; + (pubkey, account) + } + }; let counter = FlexiCounter { label: "Counter".to_string(), @@ -179,13 +281,29 @@ async fn commit_single_account( account, remote_slot: Default::default(), }; - let base_intent = if undelegate { - MagicBaseIntent::CommitAndUndelegate(CommitAndUndelegate { - commit_action: CommitType::Standalone(vec![account]), - undelegate_action: UndelegateType::Standalone, - }) - } else { - MagicBaseIntent::Commit(CommitType::Standalone(vec![account])) + let base_intent = match mode { + CommitAccountMode::CommitAndUndelegate => { + MagicBaseIntent::CommitAndUndelegate(CommitAndUndelegate { + commit_action: CommitType::Standalone(vec![account]), + undelegate_action: UndelegateType::Standalone, + }) + } + CommitAccountMode::Commit => { + MagicBaseIntent::Commit(CommitType::Standalone(vec![account])) + } + CommitAccountMode::CompressedCommit => { + MagicBaseIntent::CompressedCommit(CommitType::Standalone(vec![ + account, + ])) + } + CommitAccountMode::CompressedCommitAndUndelegate => { + MagicBaseIntent::CompressedCommitAndUndelegate( + CommitAndUndelegate { + commit_action: CommitType::Standalone(vec![account]), + undelegate_action: UndelegateType::Standalone, + }, + ) + } }; let intent = ScheduledBaseIntentWrapper { @@ -212,18 +330,22 @@ async fn commit_single_account( async fn commit_book_order_account( changed_len: usize, expected_strategy: CommitStrategy, - undelegate: bool, + mode: CommitAccountMode, ) { init_logger!(); let validator_auth = ensure_validator_authority(); fund_validator_auth_and_ensure_validator_fees_vault(&validator_auth).await; + let photon_client = + Arc::new(PhotonIndexer::new(PHOTON_URL.to_string(), None)); + // Run each test with and without finalizing let service = CommittorService::try_start( validator_auth.insecure_clone(), ":memory:", ChainConfig::local(ComputeBudgetConfig::new(1_000_000)), + photon_client, ) .unwrap(); let service = CommittorServiceExt::new(Arc::new(service)); @@ -246,7 +368,11 @@ async fn commit_book_order_account( account: order_book_ac, remote_slot: Default::default(), }; - let base_intent = if undelegate { + let base_intent = if matches!( + mode, + CommitAccountMode::CommitAndUndelegate + | CommitAccountMode::CompressedCommitAndUndelegate + ) { MagicBaseIntent::CommitAndUndelegate(CommitAndUndelegate { commit_action: CommitType::Standalone(vec![account]), undelegate_action: UndelegateType::Standalone, @@ -288,43 +414,43 @@ async fn test_ix_commit_two_accounts_1kb_2kb() { commit_multiple_accounts( &[1024, 2048], 1, - false, + CommitAccountMode::Commit, expect_strategies(&[(CommitStrategy::DiffArgs, 2)]), ) .await; } #[tokio::test] -async fn test_ix_commit_two_accounts_512kb() { +async fn test_ix_commit_two_accounts_512_bytes() { init_logger!(); commit_multiple_accounts( &[512, 512], 1, - false, + CommitAccountMode::Commit, expect_strategies(&[(CommitStrategy::DiffArgs, 2)]), ) .await; } #[tokio::test] -async fn test_ix_commit_three_accounts_512kb() { +async fn test_ix_commit_three_accounts_512_bytes() { init_logger!(); commit_multiple_accounts( &[512, 512, 512], 1, - false, + CommitAccountMode::Commit, expect_strategies(&[(CommitStrategy::DiffArgs, 3)]), ) .await; } #[tokio::test] -async fn test_ix_commit_six_accounts_512kb() { +async fn test_ix_commit_six_accounts_512_bytes() { init_logger!(); commit_multiple_accounts( &[512, 512, 512, 512, 512, 512], 1, - false, + CommitAccountMode::Commit, expect_strategies(&[(CommitStrategy::DiffArgs, 6)]), ) .await; @@ -336,7 +462,7 @@ async fn test_ix_commit_four_accounts_1kb_2kb_5kb_10kb_single_bundle() { commit_multiple_accounts( &[1024, 2 * 1024, 5 * 1024, 10 * 1024], 1, - false, + CommitAccountMode::Commit, expect_strategies(&[(CommitStrategy::DiffArgs, 4)]), ) .await; @@ -356,7 +482,7 @@ async fn test_commit_5_accounts_1kb_bundle_size_3() { commit_5_accounts_1kb( 3, expect_strategies(&[(CommitStrategy::DiffArgs, 5)]), - false, + CommitAccountMode::Commit, ) .await; } @@ -369,7 +495,7 @@ async fn test_commit_5_accounts_1kb_bundle_size_3_undelegate_all() { // Intent fits in 1 TX only with ALT, see IntentExecutorImpl::try_unite_tasks (CommitStrategy::DiffArgs, 5), ]), - true, + CommitAccountMode::CommitAndUndelegate, ) .await; } @@ -382,7 +508,7 @@ async fn test_commit_5_accounts_1kb_bundle_size_4() { (CommitStrategy::DiffArgs, 1), (CommitStrategy::DiffBufferWithLookupTable, 4), ]), - false, + CommitAccountMode::Commit, ) .await; } @@ -395,7 +521,7 @@ async fn test_commit_5_accounts_1kb_bundle_size_4_undelegate_all() { (CommitStrategy::DiffArgs, 1), (CommitStrategy::DiffBufferWithLookupTable, 4), ]), - true, + CommitAccountMode::CommitAndUndelegate, ) .await; } @@ -405,7 +531,7 @@ async fn test_commit_5_accounts_1kb_bundle_size_5_undelegate_all() { commit_5_accounts_1kb( 5, expect_strategies(&[(CommitStrategy::DiffBufferWithLookupTable, 5)]), - true, + CommitAccountMode::CommitAndUndelegate, ) .await; } @@ -476,57 +602,205 @@ async fn test_commit_20_accounts_1kb_bundle_size_8() { .await; } +// ----------------- +// Compressed Account Commits +// ----------------- + +#[tokio::test] +async fn test_ix_commit_single_compressed_account_100_bytes() { + commit_single_account( + 100, + CommitStrategy::StateArgs, + CommitAccountMode::CompressedCommit, + ) + .await; +} + +#[tokio::test] +async fn test_ix_commit_single_compressed_account_100_bytes_and_undelegate() { + commit_single_account( + 100, + CommitStrategy::StateArgs, + CommitAccountMode::CompressedCommitAndUndelegate, + ) + .await; +} + +#[tokio::test] +async fn test_ix_commit_single_compressed_account_500_bytes() { + commit_single_account( + 500, + CommitStrategy::StateArgs, + CommitAccountMode::CompressedCommit, + ) + .await; +} + +#[tokio::test] +async fn test_ix_commit_single_compressed_account_500_bytes_and_undelegate() { + commit_single_account( + 500, + CommitStrategy::StateArgs, + CommitAccountMode::CompressedCommitAndUndelegate, + ) + .await; +} + +#[tokio::test] +async fn test_ix_commit_two_compressed_accounts_512_bytes() { + commit_multiple_accounts( + &[512, 512], + 1, + CommitAccountMode::CompressedCommit, + expect_strategies(&[(CommitStrategy::StateArgs, 2)]), + ) + .await; +} + +#[tokio::test] +async fn test_ix_commit_three_compressed_accounts_512_bytes() { + init_logger!(); + commit_multiple_accounts( + &[512, 512, 512], + 1, + CommitAccountMode::CompressedCommit, + expect_strategies(&[(CommitStrategy::StateArgs, 3)]), + ) + .await; +} + +#[tokio::test] +async fn test_ix_commit_six_compressed_accounts_512_bytes() { + init_logger!(); + commit_multiple_accounts( + &[512, 512, 512, 512, 512, 512], + 1, + CommitAccountMode::CompressedCommit, + expect_strategies(&[(CommitStrategy::StateArgs, 6)]), + ) + .await; +} + +#[tokio::test] +async fn test_commit_20_compressed_accounts_100bytes_bundle_size_2() { + commit_20_compressed_accounts_100bytes( + 2, + expect_strategies(&[(CommitStrategy::StateArgs, 20)]), + ) + .await; +} + +#[tokio::test] +async fn test_commit_5_compressed_accounts_100bytes_bundle_size_2() { + commit_5_compressed_accounts_100bytes( + 2, + expect_strategies(&[(CommitStrategy::StateArgs, 5)]), + CommitAccountMode::CompressedCommit, + ) + .await; +} + +#[tokio::test] +async fn test_commit_5_compressed_accounts_100bytes_bundle_size_2_undelegate_all( +) { + commit_5_compressed_accounts_100bytes( + 2, + expect_strategies(&[(CommitStrategy::StateArgs, 5)]), + CommitAccountMode::CompressedCommitAndUndelegate, + ) + .await; +} + async fn commit_5_accounts_1kb( bundle_size: usize, expected_strategies: ExpectedStrategies, - undelegate_all: bool, + mode_all: CommitAccountMode, ) { init_logger!(); let accs = (0..5).map(|_| 1024).collect::>(); + commit_multiple_accounts(&accs, bundle_size, mode_all, expected_strategies) + .await; +} + +async fn commit_5_compressed_accounts_100bytes( + bundle_size: usize, + expected_strategies: ExpectedStrategies, + mode_all: CommitAccountMode, +) { + init_logger!(); + let accs = (0..5).map(|_| 100).collect::>(); + commit_multiple_accounts(&accs, bundle_size, mode_all, expected_strategies) + .await; +} + +async fn commit_8_accounts_1kb( + bundle_size: usize, + expected_strategies: ExpectedStrategies, +) { + init_logger!(); + let accs = (0..8).map(|_| 1024).collect::>(); commit_multiple_accounts( &accs, bundle_size, - undelegate_all, + CommitAccountMode::Commit, expected_strategies, ) .await; } -async fn commit_8_accounts_1kb( +async fn commit_20_accounts_1kb( bundle_size: usize, expected_strategies: ExpectedStrategies, ) { init_logger!(); - let accs = (0..8).map(|_| 1024).collect::>(); - commit_multiple_accounts(&accs, bundle_size, false, expected_strategies) - .await; + let accs = (0..20).map(|_| 1024).collect::>(); + commit_multiple_accounts( + &accs, + bundle_size, + CommitAccountMode::Commit, + expected_strategies, + ) + .await; } -async fn commit_20_accounts_1kb( +async fn commit_20_compressed_accounts_100bytes( bundle_size: usize, expected_strategies: ExpectedStrategies, ) { init_logger!(); - let accs = (0..20).map(|_| 1024).collect::>(); - commit_multiple_accounts(&accs, bundle_size, false, expected_strategies) - .await; + let accs = (0..20).map(|_| 100).collect::>(); + commit_multiple_accounts( + &accs, + bundle_size, + CommitAccountMode::CompressedCommit, + expected_strategies, + ) + .await; } async fn create_bundles( bundle_size: usize, bytess: &[usize], + compressed: bool, ) -> Vec> { let mut join_set = JoinSet::new(); for bytes in bytess { let bytes = *bytes; join_set.spawn(async move { let counter_auth = Keypair::new(); - let (pda, mut pda_acc) = init_and_delegate_account_on_chain( - &counter_auth, - bytes as u64, - None, - ) - .await; + let (pda, mut pda_acc) = if !compressed { + init_and_delegate_account_on_chain( + &counter_auth, + bytes as u64, + None, + ) + .await + } else { + let (pda, _address, pda_acc) = + init_and_delegate_compressed_record_on_chain(&counter_auth) + .await; + (pda, pda_acc) + }; pda_acc.owner = program_flexi_counter::id(); pda_acc.data = vec![0u8; bytes]; @@ -549,7 +823,7 @@ async fn create_bundles( async fn commit_multiple_accounts( bytess: &[usize], bundle_size: usize, - undelegate_all: bool, + mode_all: CommitAccountMode, expected_strategies: ExpectedStrategies, ) { init_logger!(); @@ -557,28 +831,55 @@ async fn commit_multiple_accounts( let validator_auth = ensure_validator_authority(); fund_validator_auth_and_ensure_validator_fees_vault(&validator_auth).await; + let photon_client = + Arc::new(PhotonIndexer::new(PHOTON_URL.to_string(), None)); + let service = CommittorService::try_start( validator_auth.insecure_clone(), ":memory:", ChainConfig::local(ComputeBudgetConfig::new(1_000_000)), + photon_client, ) .unwrap(); let service = CommittorServiceExt::new(Arc::new(service)); // Create bundles of committed accounts - let bundles_of_committees = create_bundles(bundle_size, bytess).await; + let bundles_of_committees = create_bundles( + bundle_size, + bytess, + matches!( + mode_all, + CommitAccountMode::CompressedCommit + | CommitAccountMode::CompressedCommitAndUndelegate + ), + ) + .await; // Create intent for each bundle let intents = bundles_of_committees .into_iter() - .map(|committees| { - if undelegate_all { + .map(|committees| match mode_all { + CommitAccountMode::CommitAndUndelegate => { MagicBaseIntent::CommitAndUndelegate(CommitAndUndelegate { commit_action: CommitType::Standalone(committees), undelegate_action: UndelegateType::Standalone, }) - } else { + } + CommitAccountMode::Commit => { MagicBaseIntent::Commit(CommitType::Standalone(committees)) } + CommitAccountMode::CompressedCommit => { + MagicBaseIntent::CompressedCommit(CommitType::Standalone( + committees, + )) + } + CommitAccountMode::CompressedCommitAndUndelegate => { + MagicBaseIntent::CompressedCommitAndUndelegate( + CommitAndUndelegate { + commit_action: CommitType::Standalone(committees), + undelegate_action: UndelegateType::Standalone, + }, + ) + } }) .enumerate() .map(|(id, base_intent)| ScheduledBaseIntent { @@ -641,7 +942,8 @@ async fn ix_commit_local( assert_eq!(execution_outputs.len(), base_intents.len()); service.release_common_pubkeys().await.unwrap(); - let rpc_client = RpcClient::new("http://localhost:7799".to_string()); + let rpc_client = RpcClient::new(RPC_URL.to_string()); + let photon_indexer = PhotonIndexer::new(PHOTON_URL.to_string(), None); let mut strategies = ExpectedStrategies::new(); for (execution_result, base_intent) in execution_outputs.into_iter().zip(base_intents.into_iter()) @@ -661,15 +963,15 @@ async fn ix_commit_local( "No errors expected to be patched" ); assert!( - tx_logs_contain(&rpc_client, &commit_signature, "CommitState") - .await + tx_logs_contain(&rpc_client, &commit_signature, "Commit").await ); assert!( tx_logs_contain(&rpc_client, &finalize_signature, "Finalize").await ); let is_undelegate = base_intent.is_undelegate(); - if is_undelegate { + let is_compressed = base_intent.is_compressed(); + if is_undelegate && !is_compressed { // Undelegate is part of atomic Finalization Stage assert!( tx_logs_contain(&rpc_client, &finalize_signature, "Undelegate") @@ -706,26 +1008,65 @@ async fn ix_commit_local( assert_eq!(statuses.len(), committed_accounts.len()); for commit_status in statuses { - let account = committed_accounts - .remove(&commit_status.pubkey) - .expect("Account should be persisted"); - let lamports = account.account.lamports; - get_account!( - rpc_client, - account.pubkey, - "delegated state", - |acc: &Account, remaining_tries: u8| { - validate_account( - acc, - remaining_tries, - &account.account.data, - lamports, - expected_owner, - account.pubkey, - is_undelegate, - ) - } - ); + if is_compressed { + let account = committed_accounts + .remove(&commit_status.pubkey) + .expect("Account should be persisted"); + let lamports = Rent::default().minimum_balance(0); + get_account!( + rpc_client, + account.pubkey, + "delegated state", + |acc: &Account, remaining_tries: u8| { + validate_account( + acc, + remaining_tries, + &[], + lamports, + compressed_delegation_client::ID, + account.pubkey, + is_undelegate, + ) + } + ); + + let address = derive_cda_from_pda(&account.pubkey); + // NOTE: defaults to 10 retry + let compressed_account = photon_indexer + .get_compressed_account(address.to_bytes(), None) + .await + .unwrap() + .value; + assert!(validate_compressed_account( + &compressed_account, + &account.account.data, + account.account.lamports, + program_flexi_counter::id(), + account.pubkey, + is_undelegate + )); + } else { + let account = committed_accounts + .remove(&commit_status.pubkey) + .expect("Account should be persisted"); + let lamports = account.account.lamports; + get_account!( + rpc_client, + account.pubkey, + "delegated state", + |acc: &Account, remaining_tries: u8| { + validate_account( + acc, + remaining_tries, + &account.account.data, + lamports, + expected_owner, + account.pubkey, + is_undelegate, + ) + } + ); + } // Track the strategy used let strategy = commit_status.commit_strategy; @@ -853,3 +1194,53 @@ fn validate_account( } matches_all } + +fn validate_compressed_account( + acc: &CompressedAccount, + expected_data: &[u8], + expected_lamports: u64, + expected_owner: Pubkey, + account_pubkey: Pubkey, + is_undelegate: bool, +) -> bool { + let Some(data) = acc.data.as_ref().and_then(|data| { + CompressedDelegationRecord::try_from_slice(&data.data).ok() + }) else { + trace!( + "Compressed account ({}) data is not present", + account_pubkey + ); + return false; + }; + let matches_data = + data.data == expected_data && data.lamports == expected_lamports; + let matches_undelegation = data.owner.eq(&expected_owner); + let matches_all = matches_data && matches_undelegation; + + if !matches_all { + if !matches_data { + trace!( + "Compressed account ({}) data {} != {} || {} != {}", + account_pubkey, + data.data.len(), + expected_data.len(), + data.lamports, + expected_lamports + ); + } + if !matches_undelegation { + trace!( + "Compressed account ({}) is {} but should be. Owner {} != {}", + account_pubkey, + if is_undelegate { + "not undelegated" + } else { + "undelegated" + }, + data.owner, + expected_owner, + ); + } + } + matches_all +} diff --git a/test-integration/test-committor-service/tests/test_transaction_preparator.rs b/test-integration/test-committor-service/tests/test_transaction_preparator.rs index b82aa3aa5..78267bd6d 100644 --- a/test-integration/test-committor-service/tests/test_transaction_preparator.rs +++ b/test-integration/test-committor-service/tests/test_transaction_preparator.rs @@ -37,6 +37,7 @@ async fn test_prepare_commit_tx_with_single_account() { let tasks = vec![ Box::new(TaskBuilderImpl::create_commit_task( 1, + None, true, committed_account.clone(), None, @@ -56,6 +57,8 @@ async fn test_prepare_commit_tx_with_single_account() { &fixture.authority, &mut tx_strategy, &None::, + &fixture.create_task_info_fetcher(), + None, ) .await; @@ -93,6 +96,7 @@ async fn test_prepare_commit_tx_with_multiple_accounts() { let buffer_commit_task = BufferTask::new_preparation_required( TaskBuilderImpl::create_commit_task( 1, + None, true, committed_account2.clone(), None, @@ -105,6 +109,7 @@ async fn test_prepare_commit_tx_with_multiple_accounts() { // account 1 Box::new(TaskBuilderImpl::create_commit_task( 1, + None, true, committed_account1.clone(), None, @@ -131,6 +136,8 @@ async fn test_prepare_commit_tx_with_multiple_accounts() { &fixture.authority, &mut tx_strategy, &None::, + &fixture.create_task_info_fetcher(), + None, ) .await .unwrap(); @@ -194,6 +201,7 @@ async fn test_prepare_commit_tx_with_base_actions() { let buffer_commit_task = BufferTask::new_preparation_required( TaskBuilderImpl::create_commit_task( 1, + None, true, committed_account.clone(), None, @@ -226,6 +234,8 @@ async fn test_prepare_commit_tx_with_base_actions() { &fixture.authority, &mut tx_strategy, &None::, + &fixture.create_task_info_fetcher(), + None, ) .await .unwrap(); @@ -300,6 +310,8 @@ async fn test_prepare_finalize_tx_with_undelegate_with_atls() { &fixture.authority, &mut tx_strategy, &None::, + &fixture.create_task_info_fetcher(), + None, ) .await; diff --git a/test-integration/test-committor-service/tests/utils/instructions.rs b/test-integration/test-committor-service/tests/utils/instructions.rs index 0fcb37f97..39cb8f376 100644 --- a/test-integration/test-committor-service/tests/utils/instructions.rs +++ b/test-integration/test-committor-service/tests/utils/instructions.rs @@ -1,3 +1,25 @@ +use std::sync::Arc; + +use light_client::indexer::{ + photon_indexer::PhotonIndexer, AddressWithTree, Indexer, + ValidityProofWithContext, +}; +use light_sdk::instruction::{ + account_meta::CompressedAccountMeta, PackedAccounts, PackedAddressTreeInfo, + SystemAccountMetaConfig, +}; +use magicblock_core::compression::{ + derive_cda_from_pda, ADDRESS_TREE, OUTPUT_QUEUE, +}; +use magicblock_program::validator::validator_authority_id; +use program_flexi_counter::{ + instruction::{ + create_delegate_compressed_ix, + create_init_compressed_delegation_record_ix, create_init_ix, + DelegateCompressedArgs, InitDelegationRecordArgs, + }, + state::FlexiCounter, +}; use solana_pubkey::Pubkey; use solana_sdk::{instruction::Instruction, rent::Rent, signature::Keypair}; use test_kit::Signer; @@ -15,7 +37,19 @@ pub struct InitAccountAndDelegateIxs { pub reallocs: Vec, pub delegate: Instruction, pub pda: Pubkey, - pub rent_excempt: u64, + pub rent_exempt: u64, +} + +pub struct InitAccountAndCompressedRecordIxs { + pub init: Instruction, + pub init_record: Instruction, + pub pda: Pubkey, +} + +pub struct DelegateCompressedIx { + pub delegate: Instruction, + pub pda: Pubkey, + pub address: [u8; 32], } pub fn init_account_and_delegate_ixs( @@ -47,7 +81,125 @@ pub fn init_account_and_delegate_ixs( reallocs: realloc_ixs, delegate: delegate_ix, pda, - rent_excempt: rent_exempt, + rent_exempt, + } +} + +pub async fn init_account_and_compressed_record_ixs( + payer: Pubkey, + photon_indexer: Arc, +) -> InitAccountAndCompressedRecordIxs { + let (pda, _bump) = FlexiCounter::pda(&payer); + let record_address = derive_cda_from_pda(&pda); + + let init_counter_ix = create_init_ix(payer, "COUNTER".to_string()); + + let system_account_meta_config = + SystemAccountMetaConfig::new(compressed_delegation_client::ID); + let mut remaining_accounts = PackedAccounts::default(); + remaining_accounts + .add_system_accounts_v2(system_account_meta_config) + .unwrap(); + + let rpc_result: ValidityProofWithContext = photon_indexer + .get_validity_proof( + vec![], + vec![AddressWithTree { + address: record_address.to_bytes(), + tree: ADDRESS_TREE, + }], + None, + ) + .await + .unwrap() + .value; + + // Insert trees in accounts + let address_merkle_tree_pubkey_index = + remaining_accounts.insert_or_get(ADDRESS_TREE); + let state_queue_pubkey_index = + remaining_accounts.insert_or_get(OUTPUT_QUEUE); + + let packed_address_tree_info = PackedAddressTreeInfo { + root_index: rpc_result.addresses[0].root_index, + address_merkle_tree_pubkey_index, + address_queue_pubkey_index: address_merkle_tree_pubkey_index, + }; + + let (remaining_accounts_metas, _, _) = + remaining_accounts.to_account_metas(); + + let init_record_ix = create_init_compressed_delegation_record_ix( + payer, + &remaining_accounts_metas, + InitDelegationRecordArgs { + validity_proof: rpc_result.proof, + address_tree_info: packed_address_tree_info, + output_state_tree_index: state_queue_pubkey_index, + }, + ); + + InitAccountAndCompressedRecordIxs { + init: init_counter_ix, + init_record: init_record_ix, + pda, + } +} + +pub async fn delegate_compressed_ixs( + payer: Pubkey, + photon_indexer: Arc, +) -> DelegateCompressedIx { + let (pda, _bump) = FlexiCounter::pda(&payer); + let record_address = derive_cda_from_pda(&pda); + + let compressed_account = photon_indexer + .get_compressed_account(record_address.to_bytes(), None) + .await + .unwrap() + .value; + + let system_account_meta_config = + SystemAccountMetaConfig::new(compressed_delegation_client::ID); + let mut remaining_accounts = PackedAccounts::default(); + remaining_accounts + .add_system_accounts_v2(system_account_meta_config) + .unwrap(); + + let rpc_result: ValidityProofWithContext = photon_indexer + .get_validity_proof(vec![compressed_account.hash], vec![], None) + .await + .unwrap() + .value; + + // Insert trees in accounts + let state_queue_pubkey_index = + remaining_accounts.insert_or_get(OUTPUT_QUEUE); + + let packed_trees_info = rpc_result.pack_tree_infos(&mut remaining_accounts); + let packed_state_tree_info = packed_trees_info.state_trees.unwrap(); + + let (remaining_accounts_metas, _, _) = + remaining_accounts.to_account_metas(); + + let delegate_ix = create_delegate_compressed_ix( + payer, + &remaining_accounts_metas, + DelegateCompressedArgs { + validator: Some(validator_authority_id()), + validity_proof: rpc_result.proof, + account_meta: CompressedAccountMeta { + tree_info: packed_state_tree_info.packed_tree_infos[0], + address: compressed_account.address.unwrap(), + output_state_tree_index: state_queue_pubkey_index, + }, + }, + ); + + DelegateCompressedIx { + delegate: delegate_ix, + pda, + address: record_address.to_bytes(), } } diff --git a/test-integration/test-committor-service/tests/utils/mod.rs b/test-integration/test-committor-service/tests/utils/mod.rs index 8a049e7fc..4d9f0468f 100644 --- a/test-integration/test-committor-service/tests/utils/mod.rs +++ b/test-integration/test-committor-service/tests/utils/mod.rs @@ -21,6 +21,7 @@ pub async fn sleep_millis(millis: u64) { /// https://github.com/magicblock-labs/delegation-program/blob/7fc0ae9a59e48bea5b046b173ea0e34fd433c3c7/tests/fixtures/accounts.rs#L46 /// It is compiled in as the authority for the validator vault when we build via /// `cargo build-sbf --features=unit_test_config` +#[allow(dead_code)] pub fn get_validator_auth() -> Keypair { const VALIDATOR_AUTHORITY: [u8; 64] = [ 251, 62, 129, 184, 107, 49, 62, 184, 1, 147, 178, 128, 185, 157, 247, @@ -32,6 +33,7 @@ pub fn get_validator_auth() -> Keypair { Keypair::from_bytes(&VALIDATOR_AUTHORITY).unwrap() } +#[allow(dead_code)] pub fn ensure_validator_authority() -> Keypair { static ONCE: Once = Once::new(); diff --git a/test-integration/test-committor-service/tests/utils/transactions.rs b/test-integration/test-committor-service/tests/utils/transactions.rs index b1f44e209..5621dfe96 100644 --- a/test-integration/test-committor-service/tests/utils/transactions.rs +++ b/test-integration/test-committor-service/tests/utils/transactions.rs @@ -1,3 +1,7 @@ +use std::sync::Arc; + +use light_client::indexer::{photon_indexer::PhotonIndexer, Indexer}; +use magicblock_chainlink::testing::utils::{PHOTON_URL, RPC_URL}; use solana_account::Account; use solana_pubkey::Pubkey; use solana_rpc_client::nonblocking::rpc_client::RpcClient; @@ -6,6 +10,7 @@ use solana_rpc_client_api::config::{ }; use solana_sdk::{ commitment_config::CommitmentConfig, + compute_budget::ComputeBudgetInstruction, native_token::LAMPORTS_PER_SOL, signature::{Keypair, Signature, Signer}, transaction::Transaction, @@ -13,8 +18,10 @@ use solana_sdk::{ use tracing::{debug, error}; use crate::utils::instructions::{ + delegate_compressed_ixs, init_account_and_compressed_record_ixs, init_account_and_delegate_ixs, init_order_book_account_and_delegate_ixs, - init_validator_fees_vault_ix, InitAccountAndDelegateIxs, + init_validator_fees_vault_ix, DelegateCompressedIx, + InitAccountAndCompressedRecordIxs, InitAccountAndDelegateIxs, InitOrderBookAndDelegateIxs, }; @@ -68,6 +75,53 @@ macro_rules! get_account { }}; } +#[macro_export] +macro_rules! get_compressed_account { + ($photon_client:ident, $address:expr, $label:literal, $predicate:expr) => {{ + const GET_ACCOUNT_RETRIES: u8 = 12; + + let mut remaining_tries = GET_ACCOUNT_RETRIES; + loop { + let acc = $photon_client + .get_compressed_account($address, None) + .await + .ok() + .map(|acc| acc.value.clone()); + if let Some(acc) = acc { + if $predicate(&acc, remaining_tries) { + break acc; + } + remaining_tries -= 1; + if remaining_tries == 0 { + panic!( + "{} account ({:?}) does not match condition after {} retries", + $label, $address, GET_ACCOUNT_RETRIES + ); + } + $crate::utils::sleep_millis(800).await; + } else { + remaining_tries -= 1; + if remaining_tries == 0 { + panic!( + "Unable to get {} account ({:?}) matching condition after {} retries", + $label, $address, GET_ACCOUNT_RETRIES + ); + } + if remaining_tries % 10 == 0 { + debug!( + "Waiting for {} account ({:?}) to become available", + $label, $address + ); + } + $crate::utils::sleep_millis(800).await; + } + } + }}; + ($photon_client:ident, $pubkey:expr, $label:literal) => {{ + get_compressed_account!($photon_client, $pubkey, $label, |_: &light_client::indexer::CompressedAccount, _: u8| true) + }}; +} + #[allow(dead_code)] pub async fn fetch_tx_logs( rpc_client: &RpcClient, @@ -155,7 +209,7 @@ pub async fn init_and_delegate_account_on_chain( bytes: u64, label: Option, ) -> (Pubkey, Account) { - let rpc_client = RpcClient::new("http://localhost:7799".to_string()); + let rpc_client = RpcClient::new(RPC_URL.to_string()); rpc_client .request_airdrop(&counter_auth.pubkey(), 777 * LAMPORTS_PER_SOL) @@ -168,7 +222,7 @@ pub async fn init_and_delegate_account_on_chain( reallocs: realloc_ixs, delegate: delegate_ix, pda, - rent_excempt, + rent_exempt, } = init_account_and_delegate_ixs(counter_auth.pubkey(), bytes, label); let latest_block_hash = rpc_client.get_latest_blockhash().await.unwrap(); @@ -192,15 +246,12 @@ pub async fn init_and_delegate_account_on_chain( debug!("Init account: {:?}", pda); // 2. Airdrop to account for extra rent needed for reallocs - rpc_client - .request_airdrop(&pda, rent_excempt) - .await - .unwrap(); + rpc_client.request_airdrop(&pda, rent_exempt).await.unwrap(); debug!( "Airdropped to account: {:4} {}SOL to pay rent for {} bytes", pda, - rent_excempt as f64 / LAMPORTS_PER_SOL as f64, + rent_exempt as f64 / LAMPORTS_PER_SOL as f64, bytes ); @@ -249,12 +300,100 @@ pub async fn init_and_delegate_account_on_chain( (pda, pda_acc) } +/// This needs to be run for each test that required a new counter to be compressed delegated +#[allow(dead_code)] +pub async fn init_and_delegate_compressed_record_on_chain( + counter_auth: &Keypair, +) -> (Pubkey, [u8; 32], Account) { + let rpc_client = RpcClient::new(RPC_URL.to_string()); + let photon_indexer = + Arc::new(PhotonIndexer::new(PHOTON_URL.to_string(), None)); + + rpc_client + .request_airdrop(&counter_auth.pubkey(), 777 * LAMPORTS_PER_SOL) + .await + .unwrap(); + debug!("Airdropped to counter auth: {} SOL", 777 * LAMPORTS_PER_SOL); + + let InitAccountAndCompressedRecordIxs { + init: init_counter_ix, + init_record: init_record_ix, + pda, + } = init_account_and_compressed_record_ixs( + counter_auth.pubkey(), + photon_indexer.clone(), + ) + .await; + + let latest_block_hash = rpc_client.get_latest_blockhash().await.unwrap(); + // 1. Init account and record + rpc_client + .send_and_confirm_transaction_with_spinner_and_config( + &Transaction::new_signed_with_payer( + &[init_counter_ix, init_record_ix], + Some(&counter_auth.pubkey()), + &[&counter_auth], + latest_block_hash, + ), + CommitmentConfig::confirmed(), + RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }, + ) + .await + .expect("Failed to init account"); + debug!("Init account: {:?}", pda); + + let pda_acc = get_account!(rpc_client, pda, "pda"); + + // 2. Delegate + let DelegateCompressedIx { + delegate: delegate_ix, + pda, + address, + } = delegate_compressed_ixs(counter_auth.pubkey(), photon_indexer.clone()) + .await; + let tx = Transaction::new_signed_with_payer( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(250_000), + delegate_ix, + ], + Some(&counter_auth.pubkey()), + &[&counter_auth], + latest_block_hash, + ); + rpc_client + .send_and_confirm_transaction_with_spinner_and_config( + &tx, + CommitmentConfig::confirmed(), + RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }, + ) + .await + .inspect_err(|err| { + error!( + "Failed to init compressed record: {err:?}, signature: {:?}", + tx.signatures[0] + ) + }) + .expect("Failed to init compressed record"); + + let compressed_account = + get_compressed_account!(photon_indexer, address, "compressed record"); + debug!("Compressed record: {:?}", compressed_account); + + (pda, address, pda_acc) +} + /// This needs to be run for each test that required a new order_book to be delegated #[allow(dead_code)] pub async fn init_and_delegate_order_book_on_chain( payer: &Keypair, ) -> (Pubkey, Account) { - let rpc_client = RpcClient::new("http://localhost:7799".to_string()); + let rpc_client = RpcClient::new(RPC_URL.to_string()); rpc_client .request_airdrop(&payer.pubkey(), 777 * LAMPORTS_PER_SOL) @@ -312,10 +451,11 @@ pub async fn init_and_delegate_order_book_on_chain( } /// This needs to be run once for all tests +#[allow(dead_code)] pub async fn fund_validator_auth_and_ensure_validator_fees_vault( validator_auth: &Keypair, ) { - let rpc_client = RpcClient::new("http://localhost:7799".to_string()); + let rpc_client = RpcClient::new(RPC_URL.to_string()); rpc_client .request_airdrop(&validator_auth.pubkey(), 777 * LAMPORTS_PER_SOL) .await diff --git a/test-integration/test-runner/bin/run_tests.rs b/test-integration/test-runner/bin/run_tests.rs index 6a5a6e9c7..77ba8b5a3 100644 --- a/test-integration/test-runner/bin/run_tests.rs +++ b/test-integration/test-runner/bin/run_tests.rs @@ -9,13 +9,16 @@ use integration_test_tools::{ loaded_accounts::LoadedAccounts, toml_to_args::ProgramLoader, validator::{ - resolve_workspace_dir, start_magic_block_validator_with_config, + resolve_workspace_dir, start_light_validator_with_config, + start_magic_block_validator_with_config, start_test_validator_with_config, TestRunnerPaths, }, }; use teepee::Teepee; use test_runner::{ - cleanup::{cleanup_devnet_only, cleanup_validators}, + cleanup::{ + cleanup_devnet_only, cleanup_light_validator, cleanup_validators, + }, env_config::TestConfigViaEnvVars, signal::wait_for_ctrlc, }; @@ -152,7 +155,7 @@ fn run_restore_ledger_tests( } else { let devnet_validator = config.setup_devnet(TEST_NAME).then(start_devnet_validator); - wait_for_ctrlc(devnet_validator, None, success_output()) + wait_for_ctrlc(devnet_validator, None, None, success_output()) } } @@ -189,7 +192,7 @@ fn run_chainlink_tests( }; let start_devnet_validator = || match start_validator( "chainlink-conf.devnet.toml", - ValidatorCluster::Chain(None), + ValidatorCluster::Light, &loaded_chain_accounts, ) { Some(validator) => validator, @@ -207,16 +210,16 @@ fn run_chainlink_tests( Ok(output) => output, Err(err) => { eprintln!("Failed to run chainlink tests: {:?}", err); - cleanup_devnet_only(&mut devnet_validator); + cleanup_light_validator(&mut devnet_validator, "light"); return Err(err.into()); } }; - cleanup_devnet_only(&mut devnet_validator); + cleanup_light_validator(&mut devnet_validator, "light"); Ok(output) } else { let devnet_validator = config.setup_devnet(TEST_NAME).then(start_devnet_validator); - wait_for_ctrlc(devnet_validator, None, success_output()) + wait_for_ctrlc(None, None, devnet_validator, success_output()) } } @@ -240,7 +243,7 @@ fn run_table_mania_and_committor_tests( let start_devnet_validator = || match start_validator( "committor-conf.devnet.toml", - ValidatorCluster::Chain(None), + ValidatorCluster::Light, &loaded_chain_accounts, ) { Some(validator) => validator, @@ -303,7 +306,7 @@ fn run_table_mania_and_committor_tests( || config.setup_devnet(COMMITTOR_TEST); let devnet_validator = setup_needed.then(start_devnet_validator); Ok(( - wait_for_ctrlc(devnet_validator, None, success_output())?, + wait_for_ctrlc(devnet_validator, None, None, success_output())?, success_output(), )) } @@ -398,7 +401,12 @@ fn run_schedule_commit_tests( let ephem_validator = config.setup_ephem(TEST_NAME).then(start_ephem_validator); eprintln!("Setup validator(s)"); - wait_for_ctrlc(devnet_validator, ephem_validator, success_output())?; + wait_for_ctrlc( + devnet_validator, + ephem_validator, + None, + success_output(), + )?; Ok((success_output(), success_output())) } } @@ -474,7 +482,12 @@ fn run_cloning_tests( config.setup_devnet(TEST_NAME).then(start_devnet_validator); let ephem_validator = config.setup_ephem(TEST_NAME).then(start_ephem_validator); - wait_for_ctrlc(devnet_validator, ephem_validator, success_output()) + wait_for_ctrlc( + devnet_validator, + ephem_validator, + None, + success_output(), + ) } } @@ -531,7 +544,12 @@ fn run_magicblock_api_tests( config.setup_devnet(TEST_NAME).then(start_devnet_validator); let ephem_validator = config.setup_ephem(TEST_NAME).then(start_ephem_validator); - wait_for_ctrlc(devnet_validator, ephem_validator, success_output()) + wait_for_ctrlc( + devnet_validator, + ephem_validator, + None, + success_output(), + ) } } @@ -590,7 +608,12 @@ fn run_magicblock_pubsub_tests( config.setup_devnet(TEST_NAME).then(start_devnet_validator); let ephem_validator = config.setup_ephem(TEST_NAME).then(start_ephem_validator); - wait_for_ctrlc(devnet_validator, ephem_validator, success_output()) + wait_for_ctrlc( + devnet_validator, + ephem_validator, + None, + success_output(), + ) } } @@ -637,7 +660,7 @@ fn run_config_tests( } else { let devnet_validator = config.setup_devnet(TEST_NAME).then(start_devnet_validator); - wait_for_ctrlc(devnet_validator, None, success_output()) + wait_for_ctrlc(devnet_validator, None, None, success_output()) } } @@ -697,7 +720,12 @@ fn run_schedule_intents_tests( config.setup_devnet(TEST_NAME).then(start_devnet_validator); let ephem_validator = config.setup_ephem(TEST_NAME).then(start_ephem_validator); - wait_for_ctrlc(devnet_validator, ephem_validator, success_output()) + wait_for_ctrlc( + devnet_validator, + ephem_validator, + None, + success_output(), + ) } } @@ -746,7 +774,7 @@ fn run_task_scheduler_tests( } else { let devnet_validator = config.setup_devnet(TEST_NAME).then(start_devnet_validator); - wait_for_ctrlc(devnet_validator, None, success_output()) + wait_for_ctrlc(devnet_validator, None, None, success_output()) } } @@ -817,6 +845,7 @@ fn resolve_paths(config_file: &str) -> TestRunnerPaths { enum ValidatorCluster { Chain(Option), Ephem, + Light, } impl ValidatorCluster { @@ -824,6 +853,7 @@ impl ValidatorCluster { match self { ValidatorCluster::Chain(_) => "CHAIN", ValidatorCluster::Ephem => "EPHEM", + ValidatorCluster::Light => "LIGHT", } } } @@ -847,6 +877,12 @@ fn start_validator( log_suffix, ) } + ValidatorCluster::Light => start_light_validator_with_config( + &test_runner_paths, + None, + loaded_chain_accounts, + log_suffix, + ), _ => start_magic_block_validator_with_config( &test_runner_paths, log_suffix, diff --git a/test-integration/test-runner/src/cleanup.rs b/test-integration/test-runner/src/cleanup.rs index b595559fe..009108be6 100644 --- a/test-integration/test-runner/src/cleanup.rs +++ b/test-integration/test-runner/src/cleanup.rs @@ -9,11 +9,34 @@ pub fn cleanup_validators( kill_validators(); } +pub fn cleanup_validators_with_light( + ephem_validator: &mut Child, + light_validator: &mut Child, +) { + cleanup_validator(ephem_validator, "ephemeral"); + cleanup_light_validator(light_validator, "light"); + kill_validators(); +} + pub fn cleanup_devnet_only(devnet_validator: &mut Child) { cleanup_validator(devnet_validator, "devnet"); kill_validators(); } +pub fn cleanup_light_validator(validator: &mut Child, label: &str) { + validator.kill().unwrap_or_else(|err| { + panic!("Failed to kill {} validator ({:?})", label, err) + }); + let command = process::Command::new("light") + .arg("test-validator") + .arg("--stop") + .output() + .unwrap(); + if !command.status.success() { + panic!("Failed to stop light validator: {:?}", command); + } +} + pub fn cleanup_validator(validator: &mut Child, label: &str) { validator.kill().unwrap_or_else(|err| { panic!("Failed to kill {} validator ({:?})", label, err) diff --git a/test-integration/test-runner/src/signal.rs b/test-integration/test-runner/src/signal.rs index 75fd97c0c..90fd299b8 100644 --- a/test-integration/test-runner/src/signal.rs +++ b/test-integration/test-runner/src/signal.rs @@ -4,11 +4,12 @@ use std::{ sync::mpsc::channel, }; -use crate::cleanup::cleanup_validator; +use crate::cleanup::{cleanup_light_validator, cleanup_validator}; pub fn wait_for_ctrlc( devnet_validator: Option, ephem_validator: Option, + light_validator: Option, output: Output, ) -> Result> { let (tx, rx) = channel(); @@ -25,6 +26,8 @@ pub fn wait_for_ctrlc( if let Some(mut validator) = ephem_validator { cleanup_validator(&mut validator, "ephemeral"); } - + if let Some(mut validator) = light_validator { + cleanup_light_validator(&mut validator, "light"); + } Ok(output) } diff --git a/test-integration/test-tools/Cargo.toml b/test-integration/test-tools/Cargo.toml index 3bccbbe57..f692224e6 100644 --- a/test-integration/test-tools/Cargo.toml +++ b/test-integration/test-tools/Cargo.toml @@ -11,6 +11,7 @@ tracing = { workspace = true } random-port = { workspace = true } rayon = { workspace = true } serde = { workspace = true } +shlex = { workspace = true } ureq = { workspace = true } url = { workspace = true } magicblock-core = { workspace = true } diff --git a/test-integration/test-tools/src/integration_test_context.rs b/test-integration/test-tools/src/integration_test_context.rs index 9298c6391..e4c829e1c 100644 --- a/test-integration/test-tools/src/integration_test_context.rs +++ b/test-integration/test-tools/src/integration_test_context.rs @@ -726,13 +726,16 @@ impl IntegrationTestContext { } fn assert_transaction_error(res: &Result) { - assert!(matches!( - res, - Err(ClientError { - kind: ClientErrorKind::TransactionError(_), - .. - }) - )); + assert!( + matches!( + res, + Err(ClientError { + kind: ClientErrorKind::TransactionError(_), + .. + }) + ), + "Transaction error: {res:?}" + ); } #[allow(clippy::result_large_err)] diff --git a/test-integration/test-tools/src/validator.rs b/test-integration/test-tools/src/validator.rs index 6b9080a22..3311c9314 100644 --- a/test-integration/test-tools/src/validator.rs +++ b/test-integration/test-tools/src/validator.rs @@ -83,40 +83,7 @@ pub fn start_test_validator_with_config( let mut args = config_to_args(config_path, program_loader); let accounts_dir = workspace_dir.join("configs").join("accounts"); - let accounts = [ - ( - loaded_accounts.validator_authority().to_string(), - "validator-authority.json".to_string(), - ), - ( - loaded_accounts.luzid_authority().to_string(), - "luzid-authority.json".to_string(), - ), - ( - loaded_accounts.validator_fees_vault().to_string(), - "validator-fees-vault.json".to_string(), - ), - ( - loaded_accounts.protocol_fees_vault().to_string(), - "protocol-fees-vault.json".to_string(), - ), - ( - "9yXjZTevvMp1XgZSZEaziPRgFiXtAQChpnP2oX9eCpvt".to_string(), - "non-delegated-cloneable-account1.json".to_string(), - ), - ( - "BHBuATGifAD4JbRpM5nVdyhKzPgv3p2CxLEHAqwBzAj5".to_string(), - "non-delegated-cloneable-account2.json".to_string(), - ), - ( - "2o48ieM95rmHqMWC5B3tTX4DL7cLm4m1Kuwjay3keQSv".to_string(), - "non-delegated-cloneable-account3.json".to_string(), - ), - ( - "2EmfL3MqL3YHABudGNmajjCpR13NNEn9Y4LWxbDm6SwR".to_string(), - "non-delegated-cloneable-account4.json".to_string(), - ), - ]; + let accounts = devnet_accounts(loaded_accounts); let resolved_extra_accounts = loaded_accounts.extra_accounts(workspace_dir, &accounts_dir); let accounts = accounts.iter().chain(&resolved_extra_accounts); @@ -153,6 +120,101 @@ pub fn start_test_validator_with_config( wait_for_validator(validator, port) } +pub fn start_light_validator_with_config( + test_runner_paths: &TestRunnerPaths, + program_loader: Option, + loaded_accounts: &LoadedAccounts, + log_suffix: &str, +) -> Option { + let TestRunnerPaths { + config_path, + root_dir, + workspace_dir, + } = test_runner_paths; + + let port = rpc_port_from_config(config_path); + let mut devnet_args = config_to_args(config_path, program_loader); + + // Remove args already set by light test-validator (and their values) + let args_to_remove = [ + ("--rpc-port", true), + ("--limit-ledger-size", true), + ("--log", false), + ("-r", false), + ]; + let mut filtered_devnet_args = Vec::with_capacity(devnet_args.len()); + let mut skip_next = false; + for arg in devnet_args { + if skip_next { + skip_next = false; + continue; + } + if let Some(&(_, has_value)) = + args_to_remove.iter().find(|&&(flag, _)| flag == arg) + { + skip_next = has_value; + continue; + } + filtered_devnet_args.push(arg); + } + devnet_args = filtered_devnet_args; + + // Add accounts to the validator args + let accounts_dir = workspace_dir.join("configs").join("accounts"); + let accounts = devnet_accounts(loaded_accounts); + let resolved_extra_accounts = + loaded_accounts.extra_accounts(workspace_dir, &accounts_dir); + let account_args = accounts + .iter() + .chain(&resolved_extra_accounts) + .flat_map(|(account, file)| { + let account_path = accounts_dir.join(file).canonicalize().unwrap(); + vec![ + "--account".to_string(), + account.clone(), + account_path.to_str().unwrap().to_string(), + ] + }) + .collect::>(); + devnet_args.extend(account_args); + + // Split args using shlex so that the light CLI can pass them to the validator + let validator_args = shlex::split( + format!("--validator-args=\"{}\"", devnet_args.join(" ")).as_str(), + ) + .ok_or_else(|| anyhow::anyhow!("invalid validator args")) + .unwrap(); + + let prover_port = 3001; + let mut light_args = vec![ + "--rpc-port".to_string(), + port.to_string(), + "--prover-port".to_string(), + prover_port.to_string(), + ]; + light_args.extend(validator_args); + + let mut script = "#!/bin/bash\nlight test-validator".to_string(); + for arg in &light_args { + script.push_str(&format!(" \\\n {}", arg)); + } + let mut command = process::Command::new("light"); + let rust_log_style = + std::env::var("RUST_LOG_STYLE").unwrap_or(log_suffix.to_string()); + command + .arg("test-validator") + .args(light_args) + .env("RUST_LOG", "solana=warn") + .env("RUST_LOG_STYLE", rust_log_style) + .current_dir(root_dir); + + eprintln!("Starting light validator with {:?}", command); + eprintln!("{}", script); + let validator = command.spawn().expect("Failed to start validator"); + // Waiting for the prover, which is the last thing to start + wait_for_validator(validator, prover_port) +} + pub fn wait_for_validator(mut validator: Child, port: u16) -> Option { const SLEEP_DURATION: Duration = Duration::from_millis(400); let max_retries = if std::env::var("CI").is_ok() { @@ -324,6 +386,43 @@ pub fn resolve_programs( // Utilities // ----------------- +fn devnet_accounts(loaded_accounts: &LoadedAccounts) -> [(String, String); 8] { + [ + ( + loaded_accounts.validator_authority().to_string(), + "validator-authority.json".to_string(), + ), + ( + loaded_accounts.luzid_authority().to_string(), + "luzid-authority.json".to_string(), + ), + ( + loaded_accounts.validator_fees_vault().to_string(), + "validator-fees-vault.json".to_string(), + ), + ( + loaded_accounts.protocol_fees_vault().to_string(), + "protocol-fees-vault.json".to_string(), + ), + ( + "9yXjZTevvMp1XgZSZEaziPRgFiXtAQChpnP2oX9eCpvt".to_string(), + "non-delegated-cloneable-account1.json".to_string(), + ), + ( + "BHBuATGifAD4JbRpM5nVdyhKzPgv3p2CxLEHAqwBzAj5".to_string(), + "non-delegated-cloneable-account2.json".to_string(), + ), + ( + "2o48ieM95rmHqMWC5B3tTX4DL7cLm4m1Kuwjay3keQSv".to_string(), + "non-delegated-cloneable-account3.json".to_string(), + ), + ( + "2EmfL3MqL3YHABudGNmajjCpR13NNEn9Y4LWxbDm6SwR".to_string(), + "non-delegated-cloneable-account4.json".to_string(), + ), + ] +} + /// Unwraps the provided result and ensures to kill the validator before panicking /// if the result was an error #[macro_export] diff --git a/test-manual/helius-laser/configs/helius-config-template.toml b/test-manual/helius-laser/configs/helius-config-template.toml index 13cbd015d..58e43b9e5 100644 --- a/test-manual/helius-laser/configs/helius-config-template.toml +++ b/test-manual/helius-laser/configs/helius-config-template.toml @@ -1,4 +1,5 @@ lifecycle = "ephemeral" +listen = "127.0.0.1:9988" remotes = [ "https://devnet.helius-rpc.com?api-key=", @@ -8,9 +9,6 @@ remotes = [ storage = "magicblock-test-storage" -[aperture] -listen = "127.0.0.1:9988" - [chainlink] max-monitored-accounts = 100 @@ -30,8 +28,6 @@ index-size = 2048576 max-snapshots = 7 # how frequently (slot-wise) we should take snapshots snapshot-frequency = 1024 -# Wipes the accounts database on every startup. -reset = true [ledger] reset = true diff --git a/test-manual/helius-laser/configs/triton-config-template.toml b/test-manual/helius-laser/configs/triton-config-template.toml index a34e5a03d..30edd38cd 100644 --- a/test-manual/helius-laser/configs/triton-config-template.toml +++ b/test-manual/helius-laser/configs/triton-config-template.toml @@ -1,4 +1,5 @@ lifecycle = "ephemeral" +listen = "127.0.0.1:9988" remotes = [ "https://devnet.helius-rpc.com?api-key=", @@ -8,9 +9,6 @@ remotes = [ storage = "magicblock-test-storage" -[aperture] -listen = "127.0.0.1:9988" - [chainlink] max-monitored-accounts = 100 @@ -30,8 +28,6 @@ index-size = 2048576 max-snapshots = 7 # how frequently (slot-wise) we should take snapshots snapshot-frequency = 1024 -# Wipes the accounts database on every startup. -reset = true [ledger] reset = true diff --git a/test-manual/helius-laser/sh/04_start-validator.sh b/test-manual/helius-laser/sh/04_start-validator.sh index 02810638a..030a7dcd5 100755 --- a/test-manual/helius-laser/sh/04_start-validator.sh +++ b/test-manual/helius-laser/sh/04_start-validator.sh @@ -2,6 +2,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -RUST_LOG=warn,magicblock=info,magicblock_chainlink=debug,magicblock_chainlink::chainlink::fetch_cloner=trace,magicblock_accounts=debug \ +RUST_LOG=warn,magicblock=info,magicblock_chainlink=debug,magicblock_chainlink::fetch_cloner=trace,magicblock_accounts=debug \ cargo run --bin magicblock-validator --manifest-path=$DIR/../../../Cargo.toml \ -- /tmp/mb-test-laser.toml diff --git a/test-manual/helius-laser/sh/05_run-laser-test.sh b/test-manual/helius-laser/sh/05_run-laser-test.sh index 612bbd219..2b7458617 100755 --- a/test-manual/helius-laser/sh/05_run-laser-test.sh +++ b/test-manual/helius-laser/sh/05_run-laser-test.sh @@ -1,5 +1,3 @@ #!/usr/bin/env bash -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -RUST_LOG=info cargo run --bin helius-laser --manifest-path "$DIR/../Cargo.toml" +RUST_LOG=info cargo run --bin helius-laser