From c59211b6c9fd2f54ee6d584a34488ba86faf99fb Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Mon, 13 Jul 2026 16:20:30 +0100 Subject: [PATCH 1/3] feat: ledger 8.2 Signed-off-by: Giles Cope --- Cargo.lock | 531 ++++++++++++++---- Cargo.toml | 31 +- changes/node/changed/ledger-8-2-upgrade.md | 17 + ledger/Cargo.toml | 10 + ledger/helpers/Cargo.toml | 6 + ledger/helpers/src/extract_tx_with_context.rs | 15 +- ledger/helpers/src/fork/fork_7_to_8.rs | 25 +- ledger/helpers/src/fork/fork_8_to_9.rs | 41 +- ledger/helpers/src/fork/raw_block_data.rs | 12 +- ledger/helpers/src/lib.rs | 10 +- .../src/versions/common/wallet/dust.rs | 2 +- ledger/src/host_api/ledger_8.rs | 2 +- ledger/src/host_api/ledger_9.rs | 2 +- ledger/src/lib.rs | 14 +- ledger/src/versions/common/mod.rs | 29 +- ledger/src/versions/common/storage.rs | 9 +- util/toolkit/src/remote_prover.rs | 4 +- .../src/tx_generator/builder/builders/mod.rs | 43 +- 18 files changed, 588 insertions(+), 215 deletions(-) create mode 100644 changes/node/changed/ledger-8-2-upgrade.md diff --git a/Cargo.lock b/Cargo.lock index d2451ef4f..732fa8b86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3382,6 +3382,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.0", + "objc2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -7569,6 +7579,38 @@ dependencies = [ "zeroize", ] +[[package]] +name = "midnight-base-crypto" +version = "1.0.0" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" +dependencies = [ + "anyhow", + "atomic-write-file", + "const-hex", + "ethnum", + "fake", + "ff", + "flate2", + "futures", + "group", + "indicatif 0.18.4", + "k256", + "lazy_static", + "midnight-base-crypto-derive 1.0.0", + "midnight-serialize 1.1.0", + "pastey", + "rand 0.8.5", + "reqwest 0.13.3", + "serde", + "serde_bytes", + "serde_json", + "sha2 0.11.0", + "signature", + "subtle 2.6.1", + "tracing", + "zeroize", +] + [[package]] name = "midnight-base-crypto" version = "1.1.0" @@ -7586,8 +7628,8 @@ dependencies = [ "indicatif 0.18.4", "k256", "lazy_static", - "midnight-base-crypto-derive", - "midnight-serialize", + "midnight-base-crypto-derive 1.1.0", + "midnight-serialize 1.2.0", "pastey", "rand 0.8.5", "reqwest 0.13.3", @@ -7601,6 +7643,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "midnight-base-crypto-derive" +version = "1.0.0" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" +dependencies = [ + "proc-macro2", + "quote 1.0.45", + "syn 2.0.117", +] + [[package]] name = "midnight-base-crypto-derive" version = "1.1.0" @@ -7690,15 +7742,31 @@ checksum = "295ff7814b0288a9f0cbfea0cf13e5bfaa9a631f0525fd43175d572ca9af03ce" dependencies = [ "fake", "lazy_static", - "midnight-base-crypto", - "midnight-serialize", - "midnight-storage-core", + "midnight-base-crypto 1.1.0", + "midnight-serialize 1.2.0", + "midnight-storage-core 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-transient-crypto 2.2.0", "rand 0.8.5", "serde", "zeroize", ] +[[package]] +name = "midnight-coin-structure" +version = "2.0.1" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" +dependencies = [ + "fake", + "lazy_static", + "midnight-base-crypto 1.0.0", + "midnight-serialize 1.1.0", + "midnight-storage-core 1.2.0 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-transient-crypto 2.1.0", + "rand 0.8.5", + "serde", + "zeroize", +] + [[package]] name = "midnight-coin-structure" version = "3.0.0" @@ -7706,9 +7774,9 @@ source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=coin-structur dependencies = [ "fake", "lazy_static", - "midnight-base-crypto", - "midnight-serialize", - "midnight-storage-core", + "midnight-base-crypto 1.1.0", + "midnight-serialize 1.2.0", + "midnight-storage-core 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-transient-crypto 3.0.0", "rand 0.8.5", "serde", @@ -7787,11 +7855,11 @@ dependencies = [ "is_sorted", "itertools 0.14.0", "lazy_static", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", - "midnight-ledger-static", + "midnight-base-crypto 1.1.0", + "midnight-coin-structure 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "midnight-ledger-static 9.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-onchain-runtime 2.0.1", - "midnight-serialize", + "midnight-serialize 1.2.0", "midnight-storage 1.1.1", "midnight-transient-crypto 2.2.0", "midnight-zswap 7.0.3", @@ -7807,12 +7875,12 @@ dependencies = [ [[package]] name = "midnight-ledger" -version = "8.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2182054f3a43ccabac514448fff2487437be293f517a01afc23905df701e8548" +version = "8.2.0-rc.1" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" dependencies = [ "anyhow", "derive-where", + "digest-io", "fake", "futures", "introspection", @@ -7820,18 +7888,18 @@ dependencies = [ "is_sorted", "itertools 0.14.0", "lazy_static", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", - "midnight-ledger-static", + "midnight-base-crypto 1.0.0", + "midnight-coin-structure 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-ledger-static 9.0.0 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", "midnight-onchain-runtime 3.1.0", - "midnight-serialize", - "midnight-storage 2.0.1", - "midnight-transient-crypto 2.2.0", - "midnight-zswap 8.1.0", + "midnight-serialize 1.1.0", + "midnight-storage 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-transient-crypto 2.1.0", + "midnight-zswap 8.2.0-rc.1", "rand 0.8.5", "rayon", "serde", - "sha2 0.10.9", + "sha2 0.11.0", "tokio", "tracing", "tracing-subscriber", @@ -7848,6 +7916,15 @@ dependencies = [ "quote 1.0.45", ] +[[package]] +name = "midnight-ledger-static" +version = "9.0.0" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" +dependencies = [ + "proc-macro2", + "quote 1.0.45", +] + [[package]] name = "midnight-ledger-v9" version = "1.0.0" @@ -7863,15 +7940,15 @@ dependencies = [ "is_sorted", "itertools 0.14.0", "lazy_static", - "midnight-base-crypto", + "midnight-base-crypto 1.1.0", "midnight-coin-structure 3.0.0", - "midnight-ledger-static", + "midnight-ledger-static 9.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-onchain-runtime 4.0.0", - "midnight-serialize", - "midnight-storage 2.0.1", + "midnight-serialize 1.2.0", + "midnight-storage 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-transient-crypto 2.2.0", "midnight-transient-crypto 3.0.0", - "midnight-zkir", + "midnight-zkir 2.2.0", "midnight-zswap 9.0.0", "rand 0.8.5", "rayon", @@ -7919,7 +7996,7 @@ dependencies = [ "midnight-primitives-reserve-observation", "midnight-primitives-session-info", "midnight-primitives-system-parameters", - "midnight-storage-core", + "midnight-storage-core 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "mmr-gadget", "mmr-rpc", "pallet-cnight-observation", @@ -8055,11 +8132,13 @@ dependencies = [ "hex", "lazy_static", "log", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", + "midnight-base-crypto 1.0.0", + "midnight-base-crypto 1.1.0", + "midnight-coin-structure 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "midnight-coin-structure 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", "midnight-coin-structure 3.0.0", "midnight-ledger 7.0.3", - "midnight-ledger 8.1.0", + "midnight-ledger 8.2.0-rc.1", "midnight-ledger-v9", "midnight-node-ledger-helpers", "midnight-node-res", @@ -8067,14 +8146,17 @@ dependencies = [ "midnight-onchain-runtime 3.1.0", "midnight-onchain-runtime 4.0.0", "midnight-primitives-ledger", - "midnight-serialize", + "midnight-serialize 1.1.0", + "midnight-serialize 1.2.0", "midnight-storage 1.1.1", - "midnight-storage 2.0.1", - "midnight-storage-core", + "midnight-storage 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "midnight-storage 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-storage-core 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "midnight-transient-crypto 2.1.0", "midnight-transient-crypto 2.2.0", "midnight-transient-crypto 3.0.0", "midnight-zswap 7.0.3", - "midnight-zswap 8.1.0", + "midnight-zswap 8.2.0-rc.1", "midnight-zswap 9.0.0", "moka 0.11.3", "parity-db 0.5.4", @@ -8106,23 +8188,29 @@ dependencies = [ "itertools 0.14.0", "lazy_static", "log", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", + "midnight-base-crypto 1.0.0", + "midnight-base-crypto 1.1.0", + "midnight-coin-structure 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "midnight-coin-structure 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", "midnight-coin-structure 3.0.0", "midnight-ledger 7.0.3", - "midnight-ledger 8.1.0", + "midnight-ledger 8.2.0-rc.1", "midnight-ledger-v9", "midnight-onchain-runtime 2.0.1", "midnight-onchain-runtime 3.1.0", "midnight-onchain-runtime 4.0.0", - "midnight-serialize", + "midnight-serialize 1.1.0", + "midnight-serialize 1.2.0", "midnight-storage 1.1.1", - "midnight-storage 2.0.1", + "midnight-storage 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "midnight-storage 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-transient-crypto 2.1.0", "midnight-transient-crypto 2.2.0", "midnight-transient-crypto 3.0.0", - "midnight-zkir", + "midnight-zkir 2.1.0", + "midnight-zkir 2.2.0", "midnight-zswap 7.0.3", - "midnight-zswap 8.1.0", + "midnight-zswap 8.2.0-rc.1", "midnight-zswap 9.0.0", "rand 0.8.5", "rayon", @@ -8156,7 +8244,7 @@ dependencies = [ "midnight-primitives-ics-observation", "midnight-primitives-reserve-observation", "midnight-primitives-system-parameters", - "midnight-serialize", + "midnight-serialize 1.2.0", "pallet-cnight-observation", "sc-service", "serde", @@ -8312,11 +8400,11 @@ dependencies = [ "hex", "konst 0.3.16", "lazy_static", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", + "midnight-base-crypto 1.1.0", + "midnight-coin-structure 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-onchain-state 2.0.1", "midnight-onchain-vm 1.0.2", - "midnight-serialize", + "midnight-serialize 1.2.0", "midnight-storage 1.1.1", "midnight-transient-crypto 2.2.0", "rand 0.8.5", @@ -8328,8 +8416,7 @@ dependencies = [ [[package]] name = "midnight-onchain-runtime" version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cbc5ffd4440bcadc02ad13d7e608c48de5facdc860f949eef4a22be50cb29b7" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" dependencies = [ "derive-where", "enum_index", @@ -8338,13 +8425,13 @@ dependencies = [ "hex", "konst 0.4.3", "lazy_static", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", + "midnight-base-crypto 1.0.0", + "midnight-coin-structure 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", "midnight-onchain-state 3.0.0", "midnight-onchain-vm 3.1.0", - "midnight-serialize", - "midnight-storage 2.0.1", - "midnight-transient-crypto 2.2.0", + "midnight-serialize 1.1.0", + "midnight-storage 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-transient-crypto 2.1.0", "rand 0.8.5", "serde", "serde_bytes", @@ -8363,12 +8450,12 @@ dependencies = [ "hex", "konst 0.4.3", "lazy_static", - "midnight-base-crypto", + "midnight-base-crypto 1.1.0", "midnight-coin-structure 3.0.0", "midnight-onchain-state 4.0.0", "midnight-onchain-vm 4.0.0", - "midnight-serialize", - "midnight-storage 2.0.1", + "midnight-serialize 1.2.0", + "midnight-storage 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-transient-crypto 3.0.0", "rand 0.8.5", "serde", @@ -8385,9 +8472,9 @@ dependencies = [ "derive-where", "fake", "hex", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", - "midnight-serialize", + "midnight-base-crypto 1.1.0", + "midnight-coin-structure 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "midnight-serialize 1.2.0", "midnight-storage 1.1.1", "midnight-transient-crypto 2.2.0", "rand 0.8.5", @@ -8398,17 +8485,16 @@ dependencies = [ [[package]] name = "midnight-onchain-state" version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cf32b5175d303251d7051fb30532d4fdef3c19c7e36c5831c002a27e8440163" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" dependencies = [ "derive-where", "fake", "hex", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", - "midnight-serialize", - "midnight-storage 2.0.1", - "midnight-transient-crypto 2.2.0", + "midnight-base-crypto 1.0.0", + "midnight-coin-structure 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-serialize 1.1.0", + "midnight-storage 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-transient-crypto 2.1.0", "rand 0.8.5", "serde", "serde_bytes", @@ -8422,10 +8508,10 @@ dependencies = [ "derive-where", "fake", "hex", - "midnight-base-crypto", + "midnight-base-crypto 1.1.0", "midnight-coin-structure 3.0.0", - "midnight-serialize", - "midnight-storage 2.0.1", + "midnight-serialize 1.2.0", + "midnight-storage 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-transient-crypto 2.2.0", "midnight-transient-crypto 3.0.0", "rand 0.8.5", @@ -8443,10 +8529,10 @@ dependencies = [ "fake", "hex", "konst 0.3.16", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", + "midnight-base-crypto 1.1.0", + "midnight-coin-structure 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-onchain-state 2.0.1", - "midnight-serialize", + "midnight-serialize 1.2.0", "midnight-storage 1.1.1", "midnight-transient-crypto 2.2.0", "rand 0.8.5", @@ -8458,19 +8544,18 @@ dependencies = [ [[package]] name = "midnight-onchain-vm" version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "950c687ea706f5dd92270e06474829c732b0d7e8119430a14ea60e635050071a" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" dependencies = [ "derive-where", "fake", "hex", "konst 0.4.3", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", + "midnight-base-crypto 1.0.0", + "midnight-coin-structure 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", "midnight-onchain-state 3.0.0", - "midnight-serialize", - "midnight-storage 2.0.1", - "midnight-transient-crypto 2.2.0", + "midnight-serialize 1.1.0", + "midnight-storage 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-transient-crypto 2.1.0", "rand 0.8.5", "rpds 1.2.1", "serde", @@ -8486,11 +8571,11 @@ dependencies = [ "fake", "hex", "konst 0.4.3", - "midnight-base-crypto", + "midnight-base-crypto 1.1.0", "midnight-coin-structure 3.0.0", "midnight-onchain-state 4.0.0", - "midnight-serialize", - "midnight-storage 2.0.1", + "midnight-serialize 1.2.0", + "midnight-storage 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-transient-crypto 3.0.0", "rand 0.8.5", "rpds 1.2.1", @@ -8672,6 +8757,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "midnight-serialize" +version = "1.1.0" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" +dependencies = [ + "crypto", + "konst 0.4.3", + "lazy_static", + "midnight-serialize-macros 1.0.0 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "serde", + "serde_bytes", +] + [[package]] name = "midnight-serialize" version = "1.2.0" @@ -8680,7 +8778,7 @@ dependencies = [ "crypto", "konst 0.4.3", "lazy_static", - "midnight-serialize-macros", + "midnight-serialize-macros 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_bytes", ] @@ -8696,6 +8794,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "midnight-serialize-macros" +version = "1.0.0" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" +dependencies = [ + "proc-macro2", + "quote 1.0.45", + "syn 2.0.117", +] + [[package]] name = "midnight-storage" version = "1.1.1" @@ -8704,9 +8812,9 @@ checksum = "df40bcb4aa0a709f5c4441f6d05aa15e853f434d777c6ef8a4692c00f24d3f6e" dependencies = [ "crypto", "derive-where", - "midnight-base-crypto", - "midnight-serialize", - "midnight-storage-core", + "midnight-base-crypto 1.1.0", + "midnight-serialize 1.2.0", + "midnight-storage-core 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.12.5", "rand 0.8.5", "serde", @@ -8722,9 +8830,9 @@ checksum = "210e601a89aee79ce2b007cf96c1a56c23baa306b0c40b9b98d12c27e18d1981" dependencies = [ "crypto", "derive-where", - "midnight-base-crypto", - "midnight-serialize", - "midnight-storage-core", + "midnight-base-crypto 1.1.0", + "midnight-serialize 1.2.0", + "midnight-storage-core 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.12.5", "rand 0.8.5", "serde", @@ -8733,6 +8841,25 @@ dependencies = [ "tempfile", ] +[[package]] +name = "midnight-storage" +version = "2.0.1" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" +dependencies = [ + "crypto", + "derive-where", + "digest 0.11.3", + "midnight-base-crypto 1.0.0", + "midnight-serialize 1.1.0", + "midnight-storage-core 1.2.0 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "parking_lot 0.12.5", + "rand 0.8.5", + "serde", + "sha2 0.11.0", + "sysinfo 0.39.6", + "tempfile", +] + [[package]] name = "midnight-storage-core" version = "1.2.0" @@ -8747,9 +8874,9 @@ dependencies = [ "itertools 0.14.0", "konst 0.4.3", "lru 0.16.4", - "midnight-base-crypto", - "midnight-serialize", - "midnight-storage-macros", + "midnight-base-crypto 1.1.0", + "midnight-serialize 1.2.0", + "midnight-storage-macros 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-db 0.5.4", "parking_lot 0.12.5", "rand 0.8.5", @@ -8759,18 +8886,91 @@ dependencies = [ "tempfile", ] +[[package]] +name = "midnight-storage-core" +version = "1.2.0" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" +dependencies = [ + "archery 1.2.2", + "crypto", + "derive-where", + "digest 0.11.3", + "fake", + "hex", + "itertools 0.14.0", + "konst 0.4.3", + "lru 0.18.0", + "midnight-base-crypto 1.0.0", + "midnight-serialize 1.1.0", + "midnight-storage-macros 1.0.0 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "parity-db 0.5.4", + "parking_lot 0.12.5", + "rand 0.8.5", + "serde", + "sha2 0.11.0", + "sysinfo 0.39.6", + "tempfile", +] + [[package]] name = "midnight-storage-macros" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aeb4dc8c8ea5a6495036a88943c38cce40c23fc0181e0073ca52b7124800960" dependencies = [ - "midnight-serialize-macros", + "midnight-serialize-macros 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote 1.0.45", + "syn 2.0.117", +] + +[[package]] +name = "midnight-storage-macros" +version = "1.0.0" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" +dependencies = [ + "midnight-serialize-macros 1.0.0 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", "proc-macro2", "quote 1.0.45", "syn 2.0.117", ] +[[package]] +name = "midnight-transient-crypto" +version = "2.1.0" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" +dependencies = [ + "anyhow", + "blake2b_simd", + "const-hex", + "derive-where", + "fake", + "ff", + "flate2", + "futures", + "group", + "k256", + "lazy_static", + "lru 0.18.0", + "midnight-base-crypto 1.0.0", + "midnight-base-crypto-derive 1.0.0", + "midnight-circuits 6.2.1", + "midnight-curves 0.2.1", + "midnight-proofs 0.7.2", + "midnight-serialize 1.1.0", + "midnight-storage-core 1.2.0 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-zk-stdlib 1.2.1", + "pastey", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_json", + "sha2 0.11.0", + "signature", + "tracing", + "zeroize", +] + [[package]] name = "midnight-transient-crypto" version = "2.2.0" @@ -8788,13 +8988,13 @@ dependencies = [ "k256", "lazy_static", "lru 0.18.0", - "midnight-base-crypto", - "midnight-base-crypto-derive", + "midnight-base-crypto 1.1.0", + "midnight-base-crypto-derive 1.1.0", "midnight-circuits 6.2.1", "midnight-curves 0.2.1", "midnight-proofs 0.7.2", - "midnight-serialize", - "midnight-storage-core", + "midnight-serialize 1.2.0", + "midnight-storage-core 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-zk-stdlib 1.2.1", "pastey", "rand 0.8.5", @@ -8823,13 +9023,13 @@ dependencies = [ "k256", "lazy_static", "lru 0.18.0", - "midnight-base-crypto", - "midnight-base-crypto-derive", + "midnight-base-crypto 1.1.0", + "midnight-base-crypto-derive 1.1.0", "midnight-circuits 7.2.1", "midnight-curves 0.3.1", "midnight-proofs 0.8.1", - "midnight-serialize", - "midnight-storage-core", + "midnight-serialize 1.2.0", + "midnight-storage-core 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-zk-stdlib 2.3.1", "pastey", "rand 0.8.5", @@ -8889,6 +9089,31 @@ dependencies = [ "sha3-circuit 0.2.0", ] +[[package]] +name = "midnight-zkir" +version = "2.1.0" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" +dependencies = [ + "anyhow", + "const-hex", + "group", + "hex", + "midnight-base-crypto 1.0.0", + "midnight-circuits 6.2.1", + "midnight-proofs 0.7.2", + "midnight-serialize 1.1.0", + "midnight-transient-crypto 2.1.0", + "midnight-zk-stdlib 1.2.1", + "num-bigint", + "rand 0.8.5", + "rand_chacha 0.3.1", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "tracing", +] + [[package]] name = "midnight-zkir" version = "2.2.0" @@ -8899,12 +9124,12 @@ dependencies = [ "flate2", "group", "hex", - "midnight-base-crypto", + "midnight-base-crypto 1.1.0", "midnight-circuits 6.2.1", "midnight-circuits 7.2.1", "midnight-proofs 0.7.2", "midnight-proofs 0.8.1", - "midnight-serialize", + "midnight-serialize 1.2.0", "midnight-transient-crypto 2.2.0", "midnight-transient-crypto 3.0.0", "midnight-zk-stdlib 1.2.1", @@ -8931,11 +9156,11 @@ dependencies = [ "is_sorted", "itertools 0.14.0", "lazy_static", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", - "midnight-ledger-static", + "midnight-base-crypto 1.1.0", + "midnight-coin-structure 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "midnight-ledger-static 9.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-onchain-runtime 2.0.1", - "midnight-serialize", + "midnight-serialize 1.2.0", "midnight-storage 1.1.1", "midnight-transient-crypto 2.2.0", "rand 0.8.5", @@ -8946,9 +9171,8 @@ dependencies = [ [[package]] name = "midnight-zswap" -version = "8.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c83f946d8ac03abea38ca470599801fa08e91e2ddf3cb0963027a7701180c7c" +version = "8.2.0-rc.1" +source = "git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1#4a5e2c862d03c2c4689dcbd32812edfaab30d20c" dependencies = [ "derive-where", "fake", @@ -8956,13 +9180,13 @@ dependencies = [ "is_sorted", "itertools 0.14.0", "lazy_static", - "midnight-base-crypto", - "midnight-coin-structure 2.0.1", - "midnight-ledger-static", + "midnight-base-crypto 1.0.0", + "midnight-coin-structure 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-ledger-static 9.0.0 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", "midnight-onchain-runtime 3.1.0", - "midnight-serialize", - "midnight-storage 2.0.1", - "midnight-transient-crypto 2.2.0", + "midnight-serialize 1.1.0", + "midnight-storage 2.0.1 (git+https://github.com/midnightntwrk/midnight-ledger?tag=ledger-8.2.0-rc.1)", + "midnight-transient-crypto 2.1.0", "rand 0.8.5", "serde", "tracing", @@ -8981,15 +9205,15 @@ dependencies = [ "is_sorted", "itertools 0.14.0", "lazy_static", - "midnight-base-crypto", + "midnight-base-crypto 1.1.0", "midnight-coin-structure 3.0.0", - "midnight-ledger-static", + "midnight-ledger-static 9.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-onchain-runtime 4.0.0", - "midnight-serialize", - "midnight-storage 2.0.1", + "midnight-serialize 1.2.0", + "midnight-storage 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "midnight-transient-crypto 2.2.0", "midnight-transient-crypto 3.0.0", - "midnight-zkir", + "midnight-zkir 2.2.0", "rand 0.8.5", "serde", "tracing", @@ -9710,6 +9934,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + [[package]] name = "objc2-core-foundation" version = "0.3.2" @@ -9717,6 +9950,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.11.0", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.0", + "objc2", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-open-directory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb82bed227edf5201dfedf072bba4015a33d3d4a98519837295a90f0a23f676d" +dependencies = [ + "objc2", + "objc2-core-foundation", + "objc2-foundation", ] [[package]] @@ -18526,6 +18798,21 @@ dependencies = [ "windows 0.57.0", ] +[[package]] +name = "sysinfo" +version = "0.39.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "objc2-open-directory", + "windows 0.62.2", +] + [[package]] name = "system-configuration" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index ea5822b85..0d0ead41a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,18 +84,31 @@ zswap = { version = "=7.0.3", package = "midnight-zswap" } # transient-crypto 2.2.0's new tagged-IR trait methods. zkir = { version = "^2.2.0", package = "midnight-zkir" } -# Ledger 8 (compatible with layout-v2) -# coin-structure and transient-crypto (2.x) share versions with L7 so reuse those entries. -mn-ledger-8 = { version = "=8.1.0", package = "midnight-ledger" } -ledger-storage-ledger-8 = { version = "=2.0.1", package = "midnight-storage", features = ["parity-db"] } -onchain-runtime-ledger-8 = { version = "=3.1.0", package = "midnight-onchain-runtime" } -zswap-ledger-8 = { version = "=8.1.0", package = "midnight-zswap" } +# Ledger 8 (8.2.0-rc.1, compatible with layout-v2) — sourced from the git tag, not crates.io. +# 8.2.0-rc.1 is unpublished and the raw tag carries path deps, so the *whole* L8 companion set +# must resolve from the same git rev to unify types: a mixed crates.io/git graph forks a crate +# like midnight-storage-core 1.2.0 into two source-incompatible copies (E0277: trait `DB` not +# satisfied). Consequently L8 no longer shares coin-structure/base-crypto/transient-crypto/ +# serialize with L7 — each gets a dedicated `-ledger-8` entry pinned to the tag. +mn-ledger-8 = { git = "https://github.com/midnightntwrk/midnight-ledger", tag = "ledger-8.2.0-rc.1", package = "midnight-ledger" } +zswap-ledger-8 = { git = "https://github.com/midnightntwrk/midnight-ledger", tag = "ledger-8.2.0-rc.1", package = "midnight-zswap" } +onchain-runtime-ledger-8 = { git = "https://github.com/midnightntwrk/midnight-ledger", tag = "ledger-8.2.0-rc.1", package = "midnight-onchain-runtime" } +ledger-storage-ledger-8 = { git = "https://github.com/midnightntwrk/midnight-ledger", tag = "ledger-8.2.0-rc.1", package = "midnight-storage", features = ["parity-db"] } +coin-structure-ledger-8 = { git = "https://github.com/midnightntwrk/midnight-ledger", tag = "ledger-8.2.0-rc.1", package = "midnight-coin-structure" } +base-crypto-ledger-8 = { git = "https://github.com/midnightntwrk/midnight-ledger", tag = "ledger-8.2.0-rc.1", package = "midnight-base-crypto" } +transient-crypto-ledger-8 = { git = "https://github.com/midnightntwrk/midnight-ledger", tag = "ledger-8.2.0-rc.1", package = "midnight-transient-crypto" } +serialize-ledger-8 = { git = "https://github.com/midnightntwrk/midnight-ledger", tag = "ledger-8.2.0-rc.1", package = "midnight-serialize" } +# The workspace `zkir` (2.2.0) is bound to the 3.x-crypto stack and can't prove against L8's +# transient-crypto 2.1.0. Take zkir 2.1.0 from the same tag so L8's proving stack is self-consistent. +zkir-ledger-8 = { git = "https://github.com/midnightntwrk/midnight-ledger", tag = "ledger-8.2.0-rc.1", package = "midnight-zkir" } # Ledger 9 (compatible with layout-v2; midnight-ledger-v9 crate) -# storage shares versions with L8 so reuses that entry. coin-structure and -# transient-crypto went semver-major in L9 so get their own entries. The workspace `zkir` -# (midnight-zkir 2.2.0) serves all of L7/L8/L9. +# midnight-ledger-v9 uses registry deps, so its storage resolves to crates.io 2.0.1; L8 moved to +# the git tag for 8.2, so the two no longer share a storage entry — L9 keeps its own crates.io one. +# coin-structure and transient-crypto went semver-major in L9 so get their own entries. The +# workspace `zkir` (midnight-zkir 2.2.0) serves all of L7/L8/L9. mn-ledger-9 = { version = "=1.0.0", package = "midnight-ledger-v9" } +ledger-storage-ledger-9 = { version = "=2.0.1", package = "midnight-storage", features = ["parity-db"] } onchain-runtime-ledger-9 = { version = "=4.0.0", package = "midnight-onchain-runtime" } zswap-ledger-9 = { version = "=9.0.0", package = "midnight-zswap" } coin-structure-ledger-9 = { version = "=3.0.0", package = "midnight-coin-structure" } diff --git a/changes/node/changed/ledger-8-2-upgrade.md b/changes/node/changed/ledger-8-2-upgrade.md new file mode 100644 index 000000000..93154e226 --- /dev/null +++ b/changes/node/changed/ledger-8-2-upgrade.md @@ -0,0 +1,17 @@ +#ledger #dependencies +# Upgrade ledger 8 from 8.1.0 to 8.2.0-rc.1 + +Bumps the ledger-8 stack to `midnight-ledger` 8.2.0-rc.1 (picking up +`apply_guaranteed_only`, infallible `post_block_update`, split-phase execution +with deferred events, and the segment-type refactor), keeping ledger 9 as-is. + +8.2.0-rc.1 is not published to crates.io, so the ledger-8 crates are sourced +directly from the `ledger-8.2.0-rc.1` git tag. Because the tag carries path +deps, ledger 8 no longer shares its companion crates with ledger 7 — each gets +a dedicated `-ledger-8` entry pinned to the tag. Per-generation storage code +now uses each ledger's own `DefaultHasher` instead of a hardcoded `sha2::Sha256` +so it tracks the git-8.2 storage-core's sha2 0.11 bump automatically, and the +hard-fork state-migration helpers serialize-round-trip across the (now +distinct) L7/L8/L9 storage arenas instead of reinterpreting arena keys in place. + +PR: TODO diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index 4cbea9a5f..45578bf5e 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -20,9 +20,14 @@ mn-ledger-8 = { workspace = true, features = ["proving"], optional = true } onchain-runtime-ledger-8 = { workspace = true, optional = true } ledger-storage-ledger-8 = { workspace = true, optional = true } zswap-ledger-8 = { workspace = true, optional = true } +coin-structure-ledger-8 = { workspace = true, optional = true } +base-crypto-ledger-8 = { workspace = true, optional = true } +transient-crypto-ledger-8 = { workspace = true, optional = true } +serialize-ledger-8 = { workspace = true, optional = true } mn-ledger-9 = { workspace = true, features = ["proving"], optional = true } onchain-runtime-ledger-9 = { workspace = true, optional = true } +ledger-storage-ledger-9 = { workspace = true, optional = true } zswap-ledger-9 = { workspace = true, optional = true } coin-structure-ledger-9 = { workspace = true, optional = true } transient-crypto-ledger-9 = { workspace = true, optional = true } @@ -83,8 +88,13 @@ std = [ "onchain-runtime-ledger-8", "ledger-storage-ledger-8", "zswap-ledger-8", + "coin-structure-ledger-8", + "base-crypto-ledger-8", + "transient-crypto-ledger-8", + "serialize-ledger-8", "mn-ledger-9", "onchain-runtime-ledger-9", + "ledger-storage-ledger-9", "zswap-ledger-9", "coin-structure-ledger-9", "transient-crypto-ledger-9", diff --git a/ledger/helpers/Cargo.toml b/ledger/helpers/Cargo.toml index 76c286887..d15f86ea4 100644 --- a/ledger/helpers/Cargo.toml +++ b/ledger/helpers/Cargo.toml @@ -29,9 +29,15 @@ mn-ledger-8 = { workspace = true, features = ["proving"] } onchain-runtime-ledger-8 = { workspace = true } ledger-storage-ledger-8 = { workspace = true } zswap-ledger-8 = { workspace = true } +coin-structure-ledger-8 = { workspace = true } +base-crypto-ledger-8 = { workspace = true } +transient-crypto-ledger-8 = { workspace = true } +serialize-ledger-8 = { workspace = true } +zkir-ledger-8 = { workspace = true } mn-ledger-9 = { workspace = true, features = ["proving", "test-utilities"] } onchain-runtime-ledger-9 = { workspace = true } +ledger-storage-ledger-9 = { workspace = true } zswap-ledger-9 = { workspace = true } coin-structure-ledger-9 = { workspace = true } transient-crypto-ledger-9 = { workspace = true } diff --git a/ledger/helpers/src/extract_tx_with_context.rs b/ledger/helpers/src/extract_tx_with_context.rs index 881426630..f5dc9d834 100644 --- a/ledger/helpers/src/extract_tx_with_context.rs +++ b/ledger/helpers/src/extract_tx_with_context.rs @@ -15,22 +15,15 @@ pub fn extract_tx_with_context_ledger_9(bytes: &[u8]) -> (Vec, crate::ledger #[cfg(feature = "can-panic")] pub fn extract_tx_with_context_ledger_8(bytes: &[u8]) -> (Vec, crate::ledger_8::BlockContext) { - use crate::fork::raw_block_data::RawTransaction; - - let serialized_tx: SerializedTx = + // The `BlockContext` JSON wire shape is version-agnostic (see `SerializedTx` + // docs), so deserialize straight into L8's context type. + let serialized_tx: SerializedTx = serde_json::from_slice(bytes).expect("failed to deserialize as SerializedTx"); let RawTransaction::Midnight(tx_bytes) = serialized_tx.tx else { panic!("expected test to run against midnight transaction"); }; - let block_context = crate::ledger_8::BlockContext { - tblock: serialized_tx.context.tblock, - tblock_err: serialized_tx.context.tblock_err, - parent_block_hash: serialized_tx.context.parent_block_hash, - last_block_time: serialized_tx.context.last_block_time, - }; - - (tx_bytes, block_context) + (tx_bytes, serialized_tx.context) } #[cfg(feature = "can-panic")] diff --git a/ledger/helpers/src/fork/fork_7_to_8.rs b/ledger/helpers/src/fork/fork_7_to_8.rs index 279b16e6f..57980e787 100644 --- a/ledger/helpers/src/fork/fork_7_to_8.rs +++ b/ledger/helpers/src/fork/fork_7_to_8.rs @@ -12,22 +12,23 @@ use crate::ledger_8::{ Timestamp as Timestamp8, UnshieldedWallet, Wallet, WalletSeed, WalletState, default_storage, }; +/// Move a storage-pointer value from ledger-7 storage into ledger-8 storage. +/// +/// L7 (crates.io storage-core) and L8 (git-8.2 storage-core) no longer share a storage arena, so +/// this serializes `t1` in the L7 format, deserializes it as `T2` in the L8 format, and allocates +/// the result into L8 storage. Hence the `Serializable`/`Deserializable` bounds. pub fn old_to_new_sp( - mut t1: crate::ledger_7::Sp, + t1: crate::ledger_7::Sp, ) -> Result, std::io::Error> where - T1: crate::ledger_7::Storable, - T2: crate::ledger_8::Storable + crate::ledger_8::Tagged, + T1: crate::ledger_7::Storable + crate::ledger_7::Serializable + crate::ledger_7::Tagged, + T2: crate::ledger_8::Storable + crate::ledger_8::Deserializable + crate::ledger_8::Tagged, { - t1.persist(); - let old_root = t1.as_typed_key().key; - // Both ArenaKey types are the same type (unified via midnight-storage-core patch). - let new_arena_key: crate::ledger_8::ArenaKey = old_root; - let new_root = crate::ledger_8::mn_ledger_storage::arena::TypedArenaKey::< - T2, - ::Hasher, - >::from(new_arena_key); - default_storage::().arena.get_lazy(&new_root) + // L7 (crates.io storage-core) and L8 (git-8.2 storage-core) no longer share a storage arena, + // so move the value across by serializing in the L7 format and deserializing in L8, then + // allocate it into L8 storage. (Previously this reinterpreted the arena key in place.) + let value: T2 = old_to_new_ser(&*t1)?; + Ok(default_storage::().arena.alloc(value)) } /// Serialize with ledger-7 format, deserialize with ledger-8 format (tagged). diff --git a/ledger/helpers/src/fork/fork_8_to_9.rs b/ledger/helpers/src/fork/fork_8_to_9.rs index 918cda2e9..50bf85c19 100644 --- a/ledger/helpers/src/fork/fork_8_to_9.rs +++ b/ledger/helpers/src/fork/fork_8_to_9.rs @@ -12,22 +12,23 @@ use crate::ledger_9::{ UnshieldedWallet, Wallet, WalletSeed, WalletState, default_storage, }; +/// Move a storage-pointer value from ledger-8 storage into ledger-9 storage. +/// +/// L8 (git-8.2 storage-core) and L9 (crates.io storage-core) no longer share a storage arena, so +/// this serializes `t1` in the L8 format, deserializes it as `T2` in the L9 format, and allocates +/// the result into L9 storage. Hence the `Serializable`/`Deserializable` bounds. pub fn old_to_new_sp( - mut t1: crate::ledger_8::Sp, + t1: crate::ledger_8::Sp, ) -> Result, std::io::Error> where - T1: crate::ledger_8::Storable + crate::ledger_8::Tagged, - T2: crate::ledger_9::Storable + crate::ledger_9::Tagged, + T1: crate::ledger_8::Storable + crate::ledger_8::Serializable + crate::ledger_8::Tagged, + T2: crate::ledger_9::Storable + crate::ledger_9::Deserializable + crate::ledger_9::Tagged, { - t1.persist(); - let old_root = t1.as_typed_key().key; - // Both ArenaKey types are the same type (unified via midnight-storage-core patch). - let new_arena_key: crate::ledger_9::ArenaKey = old_root; - let new_root = crate::ledger_9::mn_ledger_storage::arena::TypedArenaKey::< - T2, - ::Hasher, - >::from(new_arena_key); - default_storage::().arena.get_lazy(&new_root) + // L8 (git-8.2 storage-core) and L9 (crates.io storage-core) no longer share a storage arena, + // so the old in-place arena-key reinterpret is invalid. Move the value across by serializing in + // the L8 format and deserializing in L9, then allocate it into L9 storage. + let value: T2 = old_to_new_ser(&*t1)?; + Ok(default_storage::().arena.alloc(value)) } /// Serialize with ledger-8 format, deserialize with ledger-9 format (tagged). @@ -52,14 +53,18 @@ pub fn old_to_new_ser_untagged< crate::ledger_9::deserialize_untagged(&mut &t_bytes[..]) } -/// Convert a ledger-8 BlockContext to a ledger-9 BlockContext. +/// Convert a ledger-8 `BlockContext` to a ledger-9 one. /// +/// L8 and L9 base-crypto diverged (1.0.0 vs 1.1.0), so the `Timestamp` and `HashOutput` fields are +/// distinct types and are rebuilt by value rather than moved. pub fn block_context_8_to_9(ctx8: &crate::ledger_8::BlockContext) -> BlockContext9 { + use crate::ledger_9::base_crypto::time::Timestamp; + // L8/L9 base-crypto diverged (1.0.0 vs 1.1.0), so timestamps are rebuilt by value. BlockContext9 { - tblock: ctx8.tblock, + tblock: Timestamp::from_secs(ctx8.tblock.to_secs()), tblock_err: ctx8.tblock_err, parent_block_hash: HashOutput9(ctx8.parent_block_hash.0), - last_block_time: ctx8.last_block_time, + last_block_time: Timestamp::from_secs(ctx8.last_block_time.to_secs()), } } @@ -67,7 +72,7 @@ pub fn fork_context_8_to_9( context8: LedgerContext8, ) -> Result, std::io::Error> { let ledger_state_8 = context8.ledger_state.lock().expect("failed to lock ledger state"); - let ledger_state: crate::ledger_9::Sp, Db8> = + let ledger_state: crate::ledger_9::Sp, Db9> = old_to_new_sp(ledger_state_8.clone())?; let mut wallets = HashMap::new(); @@ -89,7 +94,7 @@ pub fn fork_context_8_to_9( .expect("wallet seed different length between versions") }), shielded: ShieldedWallet { - state: (*old_to_new_sp::<_, WalletState>(crate::ledger_8::Sp::new( + state: (*old_to_new_sp::<_, WalletState>(crate::ledger_8::Sp::new( v.shielded.state.clone(), ))?) .clone(), @@ -101,7 +106,7 @@ pub fn fork_context_8_to_9( v.unshielded.clone(), ))?) .clone(), - dust: (*old_to_new_sp::<_, DustWallet>(crate::ledger_8::Sp::new(v.dust.clone()))?) + dust: (*old_to_new_sp::<_, DustWallet>(crate::ledger_8::Sp::new(v.dust.clone()))?) .clone(), }; let new_key: WalletSeed = old_to_new_ser_untagged(&k)?; diff --git a/ledger/helpers/src/fork/raw_block_data.rs b/ledger/helpers/src/fork/raw_block_data.rs index ef65b83e1..cd0118a04 100644 --- a/ledger/helpers/src/fork/raw_block_data.rs +++ b/ledger/helpers/src/fork/raw_block_data.rs @@ -158,18 +158,24 @@ impl RawBlockData { } /// A single serialized transaction ready for sending or file output. +/// +/// `C` is the target ledger version's `BlockContext` (defaults to the current, +/// ledger-9, type). Every ledger version serializes `BlockContext` through the +/// same `SerdeBlockContext` bridge, so the JSON wire shape is version-agnostic +/// and the context can be deserialized directly as the version the caller +/// needs, e.g. `SerializedTx`. #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SerializedTx { +pub struct SerializedTx { /// Serialized `Transaction` — the payload for `send_mn_transaction`. pub tx: RawTransaction, /// Serialized `BlockContext` - pub context: BlockContext, + pub context: C, /// Transaction hash for logging. #[serde(with = "hex_or_bytes_32")] pub tx_hash: [u8; 32], } -impl SerializedTx { +impl SerializedTx { pub fn tx_byte_len(&self) -> usize { match &self.tx { RawTransaction::Midnight(tx) => tx.len(), diff --git a/ledger/helpers/src/lib.rs b/ledger/helpers/src/lib.rs index 314cf5299..605d20e62 100644 --- a/ledger/helpers/src/lib.rs +++ b/ledger/helpers/src/lib.rs @@ -166,9 +166,11 @@ pub mod ledger_8 { /// Workspace dependency name of the ledger crate backing this module. pub const CRATE_NAME: &str = "mn-ledger-8"; pub use { - base_crypto, coin_structure, ledger_storage_ledger_8 as ledger_storage, midnight_serialize, - mn_ledger_8 as mn_ledger, onchain_runtime_ledger_8 as onchain_runtime, transient_crypto, - zkir, zswap_ledger_8 as zswap, + base_crypto_ledger_8 as base_crypto, coin_structure_ledger_8 as coin_structure, + ledger_storage_ledger_8 as ledger_storage, mn_ledger_8 as mn_ledger, + onchain_runtime_ledger_8 as onchain_runtime, serialize_ledger_8 as midnight_serialize, + transient_crypto_ledger_8 as transient_crypto, zkir_ledger_8 as zkir, + zswap_ledger_8 as zswap, }; // Vendored test-utilities shim for v8. @@ -285,7 +287,7 @@ pub mod ledger_9 { pub const CRATE_NAME: &str = "mn-ledger-9"; pub use { base_crypto, coin_structure_ledger_9 as coin_structure, - ledger_storage_ledger_8 as ledger_storage, midnight_serialize, mn_ledger_9 as mn_ledger, + ledger_storage_ledger_9 as ledger_storage, midnight_serialize, mn_ledger_9 as mn_ledger, onchain_runtime_ledger_9 as onchain_runtime, transient_crypto_ledger_9 as transient_crypto, zkir, zswap_ledger_9 as zswap, }; diff --git a/ledger/helpers/src/versions/common/wallet/dust.rs b/ledger/helpers/src/versions/common/wallet/dust.rs index f05461afa..f57d2f28c 100644 --- a/ledger/helpers/src/versions/common/wallet/dust.rs +++ b/ledger/helpers/src/versions/common/wallet/dust.rs @@ -194,8 +194,8 @@ impl TryFrom<&WalletAddress> for DustWallet { #[cfg(test)] mod tests { + use super::storage::DefaultDB; use super::{DerivationPath, DustWallet, Role, WalletSeed}; - use crate::DefaultDB; fn test_seed() -> WalletSeed { WalletSeed::from([0u8; 32]) diff --git a/ledger/src/host_api/ledger_8.rs b/ledger/src/host_api/ledger_8.rs index 13bf1a9c0..7e19dbfdc 100644 --- a/ledger/src/host_api/ledger_8.rs +++ b/ledger/src/host_api/ledger_8.rs @@ -32,7 +32,7 @@ type Signature = crate::ledger_8::base_crypto_local::signatures::Signature; type DbSeparate = crate::ledger_8::ledger_storage_local::db::ParityDb; #[cfg(feature = "std")] type DbUnified = crate::ledger_8::ledger_storage_local::db::ParityDb< - sha2::Sha256, + crate::ledger_8::ledger_storage_local::DefaultHasher, crate::ledger_8::ledger_storage_local::db::paritydb::OwnedDb, { LedgerStorageExt::COLUMN_OFFSET }, >; diff --git a/ledger/src/host_api/ledger_9.rs b/ledger/src/host_api/ledger_9.rs index 35bf5617b..9ebc267d1 100644 --- a/ledger/src/host_api/ledger_9.rs +++ b/ledger/src/host_api/ledger_9.rs @@ -477,7 +477,7 @@ pub trait Ledger9Bridge { /// drops new version's storage and initializes normal storage. /// Returns true if storage was (re)initialized, false if already correct. fn ensure_storage_initialized(&mut self) -> bool { - use ledger_storage_ledger_8::storage::try_get_default_storage; + use ledger_storage_ledger_9::storage::try_get_default_storage; let unified = is_unified(*self); diff --git a/ledger/src/lib.rs b/ledger/src/lib.rs index a62f0290a..b99501fa6 100644 --- a/ledger/src/lib.rs +++ b/ledger/src/lib.rs @@ -71,12 +71,12 @@ pub mod ledger_7 { pub mod ledger_8 { #[cfg(feature = "std")] pub(crate) use { - base_crypto as base_crypto_local, coin_structure as coin_structure_local, + base_crypto_ledger_8 as base_crypto_local, coin_structure_ledger_8 as coin_structure_local, ledger_storage_ledger_8 as ledger_storage_local, - midnight_node_ledger_helpers::ledger_8 as helpers_local, - midnight_serialize as midnight_serialize_local, mn_ledger_8 as mn_ledger_local, + midnight_node_ledger_helpers::ledger_8 as helpers_local, mn_ledger_8 as mn_ledger_local, onchain_runtime_ledger_8 as onchain_runtime_local, - transient_crypto as transient_crypto_local, zswap_ledger_8 as zswap_local, + serialize_ledger_8 as midnight_serialize_local, + transient_crypto_ledger_8 as transient_crypto_local, zswap_ledger_8 as zswap_local, }; #[path = "block_context/post_ledger_8.rs"] @@ -108,7 +108,7 @@ pub mod ledger_9 { #[cfg(feature = "std")] pub(crate) use { base_crypto as base_crypto_local, coin_structure_ledger_9 as coin_structure_local, - ledger_storage_ledger_8 as ledger_storage_local, + ledger_storage_ledger_9 as ledger_storage_local, midnight_node_ledger_helpers::ledger_9 as helpers_local, midnight_serialize as midnight_serialize_local, mn_ledger_9 as mn_ledger_local, onchain_runtime_ledger_9 as onchain_runtime_local, @@ -167,7 +167,7 @@ pub mod types { mod tests { use frame_support::assert_ok; use ledger_storage_ledger_8::{ - Storage, + DefaultHasher, Storage, db::ParityDb, storage::{set_default_storage, try_get_default_storage, unsafe_drop_default_storage}, }; @@ -185,7 +185,7 @@ mod tests { panic!("Failed to create dir {}, err {}", db_path.display(), err) }); - let db = ParityDb::::open(&db_path); + let db = ParityDb::::open(&db_path); Storage::new(0, db) }); diff --git a/ledger/src/versions/common/mod.rs b/ledger/src/versions/common/mod.rs index 270e71c5d..40f941e5a 100644 --- a/ledger/src/versions/common/mod.rs +++ b/ledger/src/versions/common/mod.rs @@ -24,8 +24,6 @@ use super::{ #[cfg(feature = "std")] use midnight_serialize_local::Tagged; #[cfg(feature = "std")] -use sha2::digest::{OutputSizeUser, generic_array::typenum::U32}; -#[cfg(feature = "std")] use transient_crypto_local::commitment::PureGeneratorPedersen; use alloc::vec::Vec; @@ -58,7 +56,7 @@ use { }, coin_structure_local::coin::Nonce, ledger_storage_local::{ - Storage, + DefaultHasher, Storage, arena::{ArenaKey, Sp, TypedArenaKey}, db::{DB, ParityDb, paritydb::OwnedDb}, storage::{default_storage, set_default_storage}, @@ -166,7 +164,6 @@ pub struct Bridge, D: DB> { impl + std::fmt::Debug, D: DB> Bridge where mn_ledger_local::structure::Transaction: Tagged, - D::Hasher: OutputSizeUser, { pub fn set_default_storage(mut externalities: &mut dyn Externalities) { let maybe_storage = externalities.extension::(); @@ -175,7 +172,7 @@ where LedgerStorageDb::UnifiedDb(db) => { let res = set_default_storage(|| { let db = - ParityDb::::from_existing_db(OwnedDb(db.clone())); + ParityDb::::from_existing_db(OwnedDb(db.clone())); Storage::new(storage.cache_size, db) }); if res.is_err() { @@ -187,7 +184,7 @@ where }, LedgerStorageDb::SeparateDb(db_path) => { let res = set_default_storage(|| { - let db = ParityDb::::open(db_path.as_path()); + let db = ParityDb::::open(db_path.as_path()); Storage::new(storage.0.cache_size, db) }); if res.is_err() { @@ -941,8 +938,14 @@ where VerifiedTransaction: Send + Sync + 'static, { let state_hash = ledger.state.state_hash(); - let strict_key = - StrictTxValidationKey { state_hash: state_hash.0.into(), tx_hash: tx_hash.0 }; + let strict_key = StrictTxValidationKey { + // The hasher output is `GenericArray` (digest 0.10, used by L7/L9) or hybrid-array + // `Array` (digest 0.11, used by L8's git-8.2 storage-core). Both deref to `[u8]`, so + // go via the slice rather than a version-specific `OutputSize = U32` type bound. + state_hash: <[u8; 32]>::try_from(&state_hash.0[..]) + .expect("persistent state hash must be 32 bytes"), + tx_hash: tx_hash.0, + }; // Check strict cache if let Some(cached) = STRICT_TX_VALIDATION_CACHE.get(&strict_key) { @@ -1063,8 +1066,14 @@ where // Check strict cache to determine if this is a cache hit let state_hash = ledger.state.state_hash(); - let strict_key = - StrictTxValidationKey { state_hash: state_hash.0.into(), tx_hash: tx_hash.0 }; + let strict_key = StrictTxValidationKey { + // The hasher output is `GenericArray` (digest 0.10, used by L7/L9) or hybrid-array + // `Array` (digest 0.11, used by L8's git-8.2 storage-core). Both deref to `[u8]`, so + // go via the slice rather than a version-specific `OutputSize = U32` type bound. + state_hash: <[u8; 32]>::try_from(&state_hash.0[..]) + .expect("persistent state hash must be 32 bytes"), + tx_hash: tx_hash.0, + }; let was_cached = STRICT_TX_VALIDATION_CACHE.get(&strict_key).is_some(); let verified_tx = Self::get_verified_transaction(ledger, tx, block_context, tx_hash)?; diff --git a/ledger/src/versions/common/storage.rs b/ledger/src/versions/common/storage.rs index ef39ea8d9..82d219639 100644 --- a/ledger/src/versions/common/storage.rs +++ b/ledger/src/versions/common/storage.rs @@ -15,6 +15,7 @@ use midnight_primitives_ledger::LedgerStorageExt; use super::LOG_TARGET; use super::ledger_storage_local::{ + DefaultHasher, db::{ParityDb, paritydb::OwnedDb}, storage::{try_get_default_storage, unsafe_drop_default_storage}, }; @@ -24,7 +25,7 @@ use super::ledger_storage_local::{ // (column offset 0) for `Separate`, or column offset = NUM_COLUMNS_POLKADOT // for `Unified`. Drop whichever exists. type DbSeparate = ParityDb; -type DbUnified = ParityDb; +type DbUnified = ParityDb; pub fn drop_default_storage_if_exists() { if try_get_default_storage::().is_some() { @@ -128,7 +129,7 @@ pub fn init_storage_paritydb_separate>( std::fs::create_dir_all(dir.as_ref()) .unwrap_or_else(|_| panic!("Failed to create dir {}", dir.as_ref().display())); - let db = ParityDb::::open(dir.as_ref()); + let db = ParityDb::::open(dir.as_ref()); Storage::new(cache_size, db) }); if res.is_err() { @@ -159,14 +160,14 @@ pub fn init_storage_paritydb_unified< use super::ledger_storage_local::{Storage, db::ParityDb, storage::set_default_storage}; let res = set_default_storage(|| { - let db = ParityDb::::from_existing_db(db_instance); + let db = ParityDb::::from_existing_db(db_instance); Storage::new(cache_size, db) }); if res.is_err() { log::warn!("Warning: Failed to set default storage: {res:?}"); } - alloc_with_initial_state::>( + alloc_with_initial_state::>( genesis_state, ) } diff --git a/util/toolkit/src/remote_prover.rs b/util/toolkit/src/remote_prover.rs index 0ae3aa46b..1d9563909 100644 --- a/util/toolkit/src/remote_prover.rs +++ b/util/toolkit/src/remote_prover.rs @@ -73,7 +73,9 @@ impl ProofProvider for RemoteProofServer { } #[async_trait] -impl midnight_node_ledger_helpers::ledger_8::ProofProvider for RemoteProofServer { +impl + midnight_node_ledger_helpers::ledger_8::ProofProvider for RemoteProofServer +{ async fn prove( &self, tx: midnight_node_ledger_helpers::ledger_8::Transaction< diff --git a/util/toolkit/src/tx_generator/builder/builders/mod.rs b/util/toolkit/src/tx_generator/builder/builders/mod.rs index 59c328a1d..de54b27d3 100644 --- a/util/toolkit/src/tx_generator/builder/builders/mod.rs +++ b/util/toolkit/src/tx_generator/builder/builders/mod.rs @@ -11,17 +11,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod ledger_7; +pub mod ledger_8; pub mod ledger_9; pub use ledger_9::*; -pub mod ledger_7; -pub mod ledger_8; // Conversion impls for encoded zswap types to ledger types. // These live here (not in common/) because common/ is compiled once per ledger // version, which would cause E0119 conflicts wherever versions share types. -// Ledger 9 uses coin-structure 3.x; ledgers 7 and 8 share coin-structure 2.x, -// so there are exactly two impl sets. +// Ledger 9 uses coin-structure 3.x; ledger 7 uses crates.io coin-structure 2.x; ledger 8 uses +// git-8.2 coin-structure 2.x (a distinct family since the 8.2 move). So there are three impl sets. use crate::toolkit_js::encoded_zswap_local_state::{ EncodedOutput, EncodedQualifiedShieldedCoinInfo, EncodedRecipient, }; @@ -30,6 +30,13 @@ use midnight_node_ledger_helpers::ledger_7::{ Nonce as NonceV2, QualifiedInfo as QualifiedInfoV2, Recipient as RecipientV2, ShieldedTokenType as ShieldedTokenTypeV2, }; +// L8's coin-structure (git-8.2) is now a distinct family from L7's (crates.io), so it needs its +// own conversion impls with its own base-crypto `HashOutput` (git-8.2 1.0.0). +use midnight_node_ledger_helpers::ledger_8::{ + CoinInfo as CoinInfoL8, CoinPublicKey as CoinPublicKeyL8, ContractAddress as ContractAddressL8, + HashOutput as HashOutputL8, Nonce as NonceL8, QualifiedInfo as QualifiedInfoL8, + Recipient as RecipientL8, ShieldedTokenType as ShieldedTokenTypeL8, +}; use midnight_node_ledger_helpers::ledger_9::{ CoinInfo, CoinPublicKey, ContractAddress, HashOutput, Nonce, QualifiedInfo, Recipient, ShieldedTokenType, @@ -39,14 +46,14 @@ use midnight_node_ledger_helpers::ledger_9::{ // coin-structure 2.x and 3.x (the same invariant type_convert.rs relies on), // so conversion is direct byte reconstruction. macro_rules! impl_encoded_zswap_conversions { - ($Recipient:ident, $CoinPublicKey:ident, $ContractAddress:ident, + ($HashOutput:ident, $Recipient:ident, $CoinPublicKey:ident, $ContractAddress:ident, $CoinInfo:ident, $Nonce:ident, $ShieldedTokenType:ident, $QualifiedInfo:ident) => { impl From<&EncodedRecipient> for $Recipient { fn from(value: &EncodedRecipient) -> Self { if value.is_left { - $Recipient::User($CoinPublicKey(HashOutput(value.left.0.0.0))) + $Recipient::User($CoinPublicKey($HashOutput(value.left.0.0.0))) } else { - $Recipient::Contract($ContractAddress(HashOutput(value.right.0.0.0))) + $Recipient::Contract($ContractAddress($HashOutput(value.right.0.0.0))) } } } @@ -54,8 +61,8 @@ macro_rules! impl_encoded_zswap_conversions { impl From<&EncodedOutput> for $CoinInfo { fn from(value: &EncodedOutput) -> Self { $CoinInfo { - nonce: $Nonce(HashOutput(value.coin_info.nonce)), - type_: $ShieldedTokenType(HashOutput(value.coin_info.color)), + nonce: $Nonce($HashOutput(value.coin_info.nonce)), + type_: $ShieldedTokenType($HashOutput(value.coin_info.color)), value: value.coin_info.value, } } @@ -64,8 +71,8 @@ macro_rules! impl_encoded_zswap_conversions { impl From<&EncodedQualifiedShieldedCoinInfo> for $CoinInfo { fn from(value: &EncodedQualifiedShieldedCoinInfo) -> Self { $CoinInfo { - nonce: $Nonce(HashOutput(value.nonce)), - type_: $ShieldedTokenType(HashOutput(value.color)), + nonce: $Nonce($HashOutput(value.nonce)), + type_: $ShieldedTokenType($HashOutput(value.color)), value: value.value, } } @@ -79,7 +86,9 @@ macro_rules! impl_encoded_zswap_conversions { }; } +// L7 shares base-crypto (1.1.0) with L9, so both use L9's `HashOutput`. impl_encoded_zswap_conversions!( + HashOutput, RecipientV2, CoinPublicKeyV2, ContractAddressV2, @@ -89,6 +98,7 @@ impl_encoded_zswap_conversions!( QualifiedInfoV2 ); impl_encoded_zswap_conversions!( + HashOutput, Recipient, CoinPublicKey, ContractAddress, @@ -97,3 +107,14 @@ impl_encoded_zswap_conversions!( ShieldedTokenType, QualifiedInfo ); +// L8 (git-8.2) uses its own base-crypto `HashOutput` (1.0.0). +impl_encoded_zswap_conversions!( + HashOutputL8, + RecipientL8, + CoinPublicKeyL8, + ContractAddressL8, + CoinInfoL8, + NonceL8, + ShieldedTokenTypeL8, + QualifiedInfoL8 +); From cd43e007d6ed797a8e07b0c3d694c7e12667f5a7 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Mon, 13 Jul 2026 16:56:49 +0100 Subject: [PATCH 2/3] fix: add PR link to change file, fmt Signed-off-by: Giles Cope --- changes/node/changed/ledger-8-2-upgrade.md | 2 +- util/toolkit/src/tx_generator/builder/builders/mod.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/changes/node/changed/ledger-8-2-upgrade.md b/changes/node/changed/ledger-8-2-upgrade.md index 93154e226..d548ac7cf 100644 --- a/changes/node/changed/ledger-8-2-upgrade.md +++ b/changes/node/changed/ledger-8-2-upgrade.md @@ -14,4 +14,4 @@ so it tracks the git-8.2 storage-core's sha2 0.11 bump automatically, and the hard-fork state-migration helpers serialize-round-trip across the (now distinct) L7/L8/L9 storage arenas instead of reinterpreting arena keys in place. -PR: TODO +PR: https://github.com/midnightntwrk/midnight-node/pull/1869 diff --git a/util/toolkit/src/tx_generator/builder/builders/mod.rs b/util/toolkit/src/tx_generator/builder/builders/mod.rs index de54b27d3..c9bfe8823 100644 --- a/util/toolkit/src/tx_generator/builder/builders/mod.rs +++ b/util/toolkit/src/tx_generator/builder/builders/mod.rs @@ -16,7 +16,6 @@ pub mod ledger_8; pub mod ledger_9; pub use ledger_9::*; - // Conversion impls for encoded zswap types to ledger types. // These live here (not in common/) because common/ is compiled once per ledger // version, which would cause E0119 conflicts wherever versions share types. From 0aef210126f052a75b3e1b035d7aab711748dc05 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Tue, 14 Jul 2026 09:38:08 +0100 Subject: [PATCH 3/3] feat: be compatible with ledger 8 geneses Signed-off-by: Giles Cope --- .github/workflows/continuous-integration.yml | 17 +- .../genesis-ledger-version-dispatch.md | 20 +++ changes/node/changed/ledger-8-2-upgrade.md | 7 + ledger/src/lib.rs | 152 ++++++++++++++++++ ledger/src/versions/common/storage.rs | 12 ++ .../guaranteed_validation/ledger_8.rs | 14 +- node/src/backend/custom_parity_db.rs | 4 +- node/src/chain_spec/mod.rs | 4 +- .../genesis_block_undeployed_ledger8.mn | 1 + .../genesis_state_undeployed_ledger8.mn | Bin 0 -> 59956 bytes scripts/tests/startup-dev-e2e.sh | 29 +++- 11 files changed, 245 insertions(+), 15 deletions(-) create mode 100644 changes/node/changed/genesis-ledger-version-dispatch.md create mode 100644 res/genesis/genesis_block_undeployed_ledger8.mn create mode 100644 res/genesis/genesis_state_undeployed_ledger8.mn diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 543bae1dc..3cfb6c9d0 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -915,9 +915,22 @@ jobs: permissions: contents: read actions: write - name: Startup E2E in dev mode + # One leg per genesis ledger version, in parallel. Each leg is its own + # required status check ("Startup E2E in dev mode (ledger-N)"); the + # main-protection ruleset must require both. + name: Startup E2E in dev mode (${{ matrix.ledger }}) needs: run runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - ledger: ledger-9 + genesis: ledger9 + - ledger: ledger-8 + genesis: ledger8 + env: + STARTUP_GENESIS: ${{ matrix.genesis }} steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0 @@ -925,6 +938,8 @@ jobs: submodules: true - id: guard uses: ./.github/actions/tree-cache-guard + with: + matrix-key: ${{ matrix.ledger }} - uses: ./.github/actions/reusable-e2e-tests if: steps.guard.outputs.hit != 'true' with: diff --git a/changes/node/changed/genesis-ledger-version-dispatch.md b/changes/node/changed/genesis-ledger-version-dispatch.md new file mode 100644 index 000000000..0c4dfb4f1 --- /dev/null +++ b/changes/node/changed/genesis-ledger-version-dispatch.md @@ -0,0 +1,20 @@ +#node #ledger +# Dispatch genesis bootstrap on the genesis blob's ledger version + +Genesis storage init and root derivation previously hardcoded the newest +ledger, so a node could not bootstrap a chain whose immutable genesis was +serialized with an older ledger (e.g. live mainnet, `ledger-state[v13]`). +The new `genesis_version` module reads the blob's serialization tag and +routes `get_root` / paritydb genesis init to the matching compiled-in ledger, +newest first (ledgers 7 and 8 share the `v13` wire tag, so `v13` blobs route +to ledger 8; `v18` to ledger 9). This is not state migration - each chain +keeps its own ledger version; only the forced-newest assumption at the +genesis boundary is gone. + +Regression test pins detection for every shipped genesis blob (mainnet -> L8, +undeployed -> L9, garbage -> unrecognised). The startup E2E is now a parallel +matrix - one leg boots the stock ledger-9 genesis, the other a ledger-8 (`v13`) +undeployed genesis fixture - each asserting finality advances. Each leg is its +own required status check ("Startup E2E in dev mode (ledger-N)"). + +PR: https://github.com/midnightntwrk/midnight-node/pull/1869 diff --git a/changes/node/changed/ledger-8-2-upgrade.md b/changes/node/changed/ledger-8-2-upgrade.md index d548ac7cf..325b6c29c 100644 --- a/changes/node/changed/ledger-8-2-upgrade.md +++ b/changes/node/changed/ledger-8-2-upgrade.md @@ -14,4 +14,11 @@ so it tracks the git-8.2 storage-core's sha2 0.11 bump automatically, and the hard-fork state-migration helpers serialize-round-trip across the (now distinct) L7/L8/L9 storage arenas instead of reinterpreting arena keys in place. +The Ledger-8 guaranteed-segment validation shim now calls 8.2's +`apply_guaranteed_only` (matching the Ledger-9 shim) instead of dry-running the +full `apply()`. Under the old path, mempool/pre-dispatch validation executed the +fallible segment even though acceptance depends only on the guaranteed segment - +an avoidable validation-cost/DoS vector that the 8.2 split-phase API exists to +close. + PR: https://github.com/midnightntwrk/midnight-node/pull/1869 diff --git a/ledger/src/lib.rs b/ledger/src/lib.rs index b99501fa6..23c7fa2b0 100644 --- a/ledger/src/lib.rs +++ b/ledger/src/lib.rs @@ -154,6 +154,143 @@ pub fn drop_all_default_storage() { ledger_9::storage::drop_default_storage_if_exists(); } +/// Genesis bootstrap that dispatches on the ledger version embedded in the +/// genesis state, instead of assuming the newest. +/// +/// All ledger versions are compiled in, but a genesis blob is serialized with +/// exactly one version's `LedgerState`, and its deserializer rejects any other +/// version's tag. Hardcoding the latest (as each ledger bump historically did) +/// makes the node unable to bootstrap a chain still on an older genesis — e.g. +/// live mainnet, whose genesis is immutable. These wrappers read the blob's tag +/// and route to the matching module. This is *not* state migration: each chain +/// keeps running its own ledger version; we only stop forcing the newest at the +/// genesis boundary. +#[cfg(feature = "std")] +pub mod genesis_version { + /// Ledger storage version a tagged genesis blob was produced with. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum LedgerVersion { + /// ledger 7 + V7, + /// ledger 8 + V8, + /// ledger 9 + V9, + } + + /// Detect a genesis blob's ledger version by matching its leading tag + /// against each compiled-in module's own `LedgerState` tag. Newest first: + /// ledgers 7 and 8 currently share the `ledger-state[v13]` tag (same wire + /// format), so a `v13` blob routes to [`LedgerVersion::V8`] and the V7 arm + /// is unreachable while that tie holds - it stays as self-healing dispatch + /// should the tags ever diverge. `None` if no known version matches + /// (corrupt or future-version blob). + pub fn detect(genesis_state: &[u8]) -> Option { + let head = &genesis_state[..genesis_state.len().min(64)]; + let has = |tag: &str| head.windows(tag.len()).any(|w| w == tag.as_bytes()); + if has(&super::ledger_9::storage::genesis_ledger_state_tag()) { + Some(LedgerVersion::V9) + } else if has(&super::ledger_8::storage::genesis_ledger_state_tag()) { + Some(LedgerVersion::V8) + } else if has(&super::ledger_7::storage::genesis_ledger_state_tag()) { + Some(LedgerVersion::V7) + } else { + None + } + } + + fn unknown_version(genesis_state: &[u8]) -> String { + format!( + "unrecognised ledger genesis version tag in {}-byte genesis blob (known: {}, {}, {})", + genesis_state.len(), + super::ledger_9::storage::genesis_ledger_state_tag(), + super::ledger_8::storage::genesis_ledger_state_tag(), + super::ledger_7::storage::genesis_ledger_state_tag(), + ) + } + + /// Version-dispatched [`get_root`](super::ledger_9::storage::get_root). + pub fn get_root(genesis_state: &[u8], network_id: Option<&str>) -> Result, String> { + match detect(genesis_state) { + Some(LedgerVersion::V9) => { + super::ledger_9::storage::get_root(genesis_state, network_id) + .map_err(|e| e.to_string()) + }, + Some(LedgerVersion::V8) => { + super::ledger_8::storage::get_root(genesis_state, network_id) + .map_err(|e| e.to_string()) + }, + Some(LedgerVersion::V7) => { + super::ledger_7::storage::get_root(genesis_state, network_id) + .map_err(|e| e.to_string()) + }, + None => Err(unknown_version(genesis_state)), + } + } + + /// Version-dispatched separate-db genesis init. + /// + /// # Panics + /// Panics if the genesis blob's ledger version tag is unrecognised + /// (corrupt or future-version blob) - the node cannot start without a + /// bootable genesis, so there is nothing to recover to. + pub fn init_storage_paritydb_separate>( + dir: P, + genesis_state: &[u8], + cache_size: usize, + ) -> Vec { + match detect(genesis_state) { + Some(LedgerVersion::V9) => super::ledger_9::storage::init_storage_paritydb_separate( + dir, + genesis_state, + cache_size, + ), + Some(LedgerVersion::V8) => super::ledger_8::storage::init_storage_paritydb_separate( + dir, + genesis_state, + cache_size, + ), + Some(LedgerVersion::V7) => super::ledger_7::storage::init_storage_paritydb_separate( + dir, + genesis_state, + cache_size, + ), + None => panic!("{}", unknown_version(genesis_state)), + } + } + + /// Version-dispatched unified-db genesis init. + /// + /// # Panics + /// Panics if the genesis blob's ledger version tag is unrecognised + /// (corrupt or future-version blob) - the node cannot start without a + /// bootable genesis, so there is nothing to recover to. + pub fn init_storage_paritydb_unified< + D: std::ops::Deref + Default + Send + Sync + 'static, + const COLUMN_OFFSET: u8, + >( + db_instance: D, + genesis_state: &[u8], + cache_size: usize, + ) -> Vec { + match detect(genesis_state) { + Some(LedgerVersion::V9) => super::ledger_9::storage::init_storage_paritydb_unified::< + D, + COLUMN_OFFSET, + >(db_instance, genesis_state, cache_size), + Some(LedgerVersion::V8) => super::ledger_8::storage::init_storage_paritydb_unified::< + D, + COLUMN_OFFSET, + >(db_instance, genesis_state, cache_size), + Some(LedgerVersion::V7) => super::ledger_7::storage::init_storage_paritydb_unified::< + D, + COLUMN_OFFSET, + >(db_instance, genesis_state, cache_size), + None => panic!("{}", unknown_version(genesis_state)), + } + } +} + mod common; pub mod types { @@ -173,6 +310,21 @@ mod tests { }; use std::path::PathBuf; + /// Every shipped genesis blob must be recognised, and mainnet's immutable + /// pre-ledger-9 genesis must route to ledger 8 — the case + /// [`crate::genesis_version`] exists for. (Ledgers 7 and 8 share the `v13` + /// tag; newest-first dispatch means V8 claims it.) + #[test] + fn genesis_version_detects_all_shipped_genesis_states() { + use crate::genesis_version::{LedgerVersion, detect}; + use midnight_node_res::networks::{MidnightNetwork, UndeployedNetwork}; + + assert_eq!(detect(UndeployedNetwork.genesis_state()), Some(LedgerVersion::V9)); + let mainnet = include_bytes!("../../res/genesis/genesis_state_mainnet.mn"); + assert_eq!(detect(mainnet), Some(LedgerVersion::V8)); + assert_eq!(detect(b"midnight:not-a-ledger-state"), None); + } + #[test] fn set_and_drop_default_storage() { let mut db_path: PathBuf = std::env::temp_dir(); diff --git a/ledger/src/versions/common/storage.rs b/ledger/src/versions/common/storage.rs index 82d219639..e751343b2 100644 --- a/ledger/src/versions/common/storage.rs +++ b/ledger/src/versions/common/storage.rs @@ -75,6 +75,18 @@ impl core::fmt::Display for GetRootError { } } +/// The `LedgerState` serialization tag for this ledger version, e.g. +/// `ledger-state[v13]` (ledgers 7 and 8 - they share a wire format) or +/// `ledger-state[v18]` (ledger 9). A tagged genesis blob carries this +/// verbatim, so it lets the bootstrap pick the right deserializer before +/// touching the body (see [`crate::genesis_version`]). +#[cfg(feature = "std")] +pub fn genesis_ledger_state_tag() -> std::borrow::Cow<'static, str> { + use super::ledger_storage_local::DefaultDB; + use super::midnight_serialize_local::Tagged; + as Tagged>::tag() +} + pub fn get_root(state: &[u8], network_id: Option<&str>) -> Result, GetRootError> { // Get empty state key use super::api::Ledger; diff --git a/ledger/src/versions/guaranteed_validation/ledger_8.rs b/ledger/src/versions/guaranteed_validation/ledger_8.rs index 122b17b3b..16bd53d1f 100644 --- a/ledger/src/versions/guaranteed_validation/ledger_8.rs +++ b/ledger/src/versions/guaranteed_validation/ledger_8.rs @@ -13,8 +13,10 @@ //! Ledger-8 dry-run validation of the guaranteed transaction segment. //! -//! Ledger 8 has no `apply_guaranteed_only`; fall back to a full `apply()` dry-run -//! and treat `PartialSuccess` as acceptable (guaranteed segment succeeded). +//! Uses ledger 8.2's split-phase `apply_guaranteed_only` API so validation does +//! not execute the fallible segment - acceptance depends only on the guaranteed +//! segment, and mempool/pre-dispatch checks must not pay (or fail on) fallible +//! work. #![cfg(feature = "std")] @@ -22,7 +24,7 @@ use super::{ ledger_storage_local::db::DB, mn_ledger_local::{ error::TransactionInvalid, - semantics::{TransactionContext, TransactionResult}, + semantics::TransactionContext, structure::{LedgerState, VerifiedTransaction}, }, }; @@ -32,9 +34,5 @@ pub fn validate_guaranteed_execution( verified_tx: VerifiedTransaction, ctx: &TransactionContext, ) -> Result<(), TransactionInvalid> { - let (_next_state, result) = state.apply(&verified_tx, ctx); - match result { - TransactionResult::Success(_) | TransactionResult::PartialSuccess(_, _) => Ok(()), - TransactionResult::Failure(reason) => Err(reason), - } + state.apply_guaranteed_only(verified_tx, ctx).map(|_| ()) } diff --git a/node/src/backend/custom_parity_db.rs b/node/src/backend/custom_parity_db.rs index 5cd5ab729..688380d89 100644 --- a/node/src/backend/custom_parity_db.rs +++ b/node/src/backend/custom_parity_db.rs @@ -109,7 +109,7 @@ pub fn open>( match storage_config.separation { StorageSeparation::Separate => { - midnight_node_ledger::ledger_9::storage::init_storage_paritydb_separate( + midnight_node_ledger::genesis_version::init_storage_paritydb_separate( &storage_config.db_path, &storage_config.genesis_state, storage_config.cache_size, @@ -117,7 +117,7 @@ pub fn open>( Ok((OwnedDb(db), LedgerStorageDb::SeparateDb(storage_config.db_path.clone()))) }, StorageSeparation::Unified => { - midnight_node_ledger::ledger_9::storage::init_storage_paritydb_unified::< + midnight_node_ledger::genesis_version::init_storage_paritydb_unified::< _, NUM_COLUMNS_POLKADOT, >(OwnedDb(db.clone()), &storage_config.genesis_state, storage_config.cache_size); diff --git a/node/src/chain_spec/mod.rs b/node/src/chain_spec/mod.rs index 05ac37a03..71fd6ba7b 100644 --- a/node/src/chain_spec/mod.rs +++ b/node/src/chain_spec/mod.rs @@ -56,7 +56,7 @@ pub enum ChainSpecInitError { Missing(String), ParseError(String), Serialization(String), - GenesisStateError(midnight_node_ledger::ledger_9::storage::GetRootError), + GenesisStateError(String), } impl fmt::Display for ChainSpecInitError { @@ -268,7 +268,7 @@ fn genesis_config(genesis: T) -> Resultdo@FS>n7NFl%pqgQOk|$Pn2<4J=1_(xLWT%MhE%3P zQRa*p{^y)q@3-%N{npRB*896_ai7iJpU>X=Jm-1#e(vCCXL-}k#@79;gO#O?m8*c8 zySclSk*A2L$ytFDz%X&AIspL05yt?o=63-9MKeIN^9B&N;tCv8x&oVHzCfM|5#ox1 z4bZ9b9XQ4!3>Z?|0nt9yfax`NK=*z%;K`lf+sNVxS3fD&{z{2 z1|hO60ZlC-q*{wBWfK6X>pS4xExSo;I?amIX+_F|e_At_#8V(wq9u-)11LwX0e4mN zfVx>Dz*;E{$;*HM4D%5{-3fLiP-{Q9X^y<9;fWWv3mkZ;APqJaPg-+h@D9xHA;W19 zfQUMC_%5PU83CxVX5XE#qAb8yz8C{@F=WoGBa8s55Kpvi=9aiI{=2gIB?G!e` z)7WHwxrToXQJOHIO}TyyV3zS_4)(~R20RsAkPF%}RgKe$Cn=X6UQR)KNFXVF0ObC8 zYH|P>I!X%wNI(vN{zeFlQ267K0IcdI1Bl;GNFr%K0gDG9+rEr9BiCGy>;c}ewb!6&w+ zgMNOak|M(cRUGa4)rKs3E_|GED;JTh?P+B4b;%f%w@#TlX;UFMdJ|L;Jn=z4zc2=o z>A$b59q@Ty@P(AIPgO~Ylq%iF``9v8P$gaET&6*py-iAbf_}vtPEzHf3vHM=);l6b zpX-@P8LomVQ*wH5W!bsniSg~-Zml;DMCCBNDWCE$?27NOlRE!Y6jUip_Ksp^EWgy! z#5f9|XpF0=WE+wy9>13P%BekPtxpE3Fqq!#yxF4I*L?AS?Z+-D6{lPM#MH8HU#RgM zVevgtbx`HH@nfTRW0_Gu$z6|O@+hrFcR%zy?R@(@7JcC`}##e9T}?$J(5vL}(`qs@QA2bSL!oTyjlNp-^UWYP$1)wfeNT_jC{HjTeJK z=rK^`8)>eaan;%2xKPD;NZUpfctGMTdRRw-15gs8*d&CIj% zpi06UWSS{UrNA$3F)`7WB=_7*u%)K3_CXXK~GBB&kxaD{w=W4p)B82S zLs5E*+2J(r+tfc=tVoX?XUAvGdZ}^9^HIo3?EuOyFW$(>%Ds?)Av30VmXmemcHK*l zquN}fh@;mE-dMYE0FeMO;iYOWlGc~Q6@s4L%@6zD_h`SfzBl$wz~r6Xjl_)-1f=tj z&8nG+R@UR^AQ%12$-_H#W+ZaAo4%aWRr|G;=@*E=M8+gw@Bjoz84}KZ)|TB_YjXK@ z-ozZ&BO^d$r-+`WBO{{qw#KPdIS`KeMUa1M1`AoK{hqGEXv}WfuD%tauSs=`5^roa zwY&vHc`pB^9lM~0_^E@Mvu5;3X5~rxs?_g~jkd*T5MF2=mO)_dXXZeZn9zF#;4>1W zR-}qOtTJyUAddUk%;MpG;hKl``k8}k%5qA|>K`;DptOp=E4t_ZQlVTk(DzNuQou_+ zT1|peA5M6PzxMp_zWS{`$b1FmvoQNy=;=w*0Oi>6q}Bn0@4lOrKrdb?r})aX%jS@G z*Kc2Fq$Rfxz8v^%3?SpYS}fHpI%eSBxGz6S{*2(lHUd*s^YnK@4m?{9&TyYC`re6n zbk)XO%wnBQlu*msEM&0Ks)AeD8icdzqbpW9JVD*E`c!NWALJ*ji_E&I1%AfZ_3Bd#w zzXhM6X+Xp(e^P5iillPyzW3Q|!|}$`5`i_BxLK%Y$YJ%+$=c-?{6T)RELEn`SnAI{T#J>2dx~#@mrkD_(_2 zWzSMYf3qb8;oOXtXObNf1nwhsn*_!`@w|NC8h2HD*4EACb^A=H?-?GGg!~5?#6POkx1&_k5tx;q+i>na{n&$Y2S$5H_oRo&edK(O!;IR9 z_bc^MrpBMTD4MT)_!WH*-P<`p6&6hEognS{l<_d)LaUo~nZrwG^{$@b&p0>k$fK_w zMhh}07w#EzzIECI7U!luy&7dwXVSh-mb6WCvvJsJ(S`aYbCyCKL0CQuOE)tjlR9mH=BmQ5FxM#3dHZ6gy|q=-?U6Jjp(=cS+Fak%QOjsUAv-c zmHa+DS8rfD1tLBQY7AnCPTsrN6Lc~7{4Iv*Gmn^e1&TMFeVuIg&(&8#tWT`IeC1Q2 zMwHPtSId%H$SPr7PWT>|`(D_#{*9@Ja24Uw4l2m}u_!o&Cu z2|xoBn2DtP)a=g#&$81rjOk7yi|%cjKOCQCC&U{}w6Q#Q{iPeILa7}kdfZH5H)Z*> zVEpaz_IqbmsKkJnA-m7QsiaDmPlGC>kuMdkm)Ec=t!mo+j3p;NWdap1M89lqnZ0_C z=t^<{RmeUO-&(0OtsAJxm3^p{bvUG!`c?9}s9EC0vxFs^j*?(tHA^Wb8H?QL1{o>rKwe z-Q@xVMjoMrP=kX9SznMQa@``yX`p-cFw*Gf)4l+jZ$7REcdF0FN2x|C^j!d1pTE|? z*DxB^e>whxrOPJVS9mhS^14a!dyy@$=?OWgVz4Aixi|jYDpiesDO)R$#KDBsGk~`Jd{5b_lU|AMuYvs2)Ko$& zZ{>u1Povyv6<8lKOdRAt%Vs{YO(N-_moXdz`8eA?vvoE{>9UL$Cxu}7Eyc&@GMZjX ztzbAKE&Uju3Ya4>b_hp=%b&~$6km{-wVYeu?r+jeul4dl{3?D%+q`=W?^4iJ6zeNd z5OJ;*7q4}sCzv^3_rhEgEX_SwvaV^9;Ux`0y!z9Ky{Hx%$x~^!N zWKL>QbIj3mMw5d{sQRc40VPaXrFwyR*SbN3tLFShZo;j2-F3vAaK6Bv@tWduxd-5`e$XFg>e z3>XBtPkIX7uvfg0GI@NzCn+>Q!HH)r`3_}tjC=p^D#t`F>ga#!v#&#Mp3rR$vM1--NM=U}TF(?sZ z%vKQ_kVFDXBpBlz#6BcpKna71M+aVnxi2;-t zFsv>}4@f!=O2;vb-bg=4Vgw~d3|$a143d~Yi3vj;g^YzHW>8|rkS8LOA&CW)Sb)Hz zE;s<7bCHEe09As7`o#)l3?53x7g%QO;Q7QhwO%FNSNS^EY-h3E;fvBKzW3ab%&)pu zfWV^)33M$C+5m$#;XsvI`#a24KV4HhuUa)YGzAYbu}*>UELJ&GH4^pr zqZJ9x0U+?GQVQJ-Q}w}A1IPqClx(2DL6z(RVIP0n$(4k%s*4xn8BXYC%m3s$(32Q@ zc9~EZ2t2A9MUTO#<1p$pE&!ek^{Vy$qC_%v-!|3fQa9P$#T?)7H+VP^)}&UO8 zZNlhz7<3T^UB!Xkb~+zdHCQZKmQQ16HmOT4$!s?waN1s7l9)7vI_r%bYWAt#NAq|3!X;8FEA+5-mlhC%&s zpoxjB5f!_2f(Is-lOo>eNGM8Vo@4s*Z1LTx;mKli4>^=PeEoFr%<)|Cf_`61aCrp+ zkLvZ%Q7~I9%$A5_8TF8l47% zX2GDjIM9US_$+i?VsZsy3(8?8ygWkDY4^B37hFbkak!f3yW{31G$oT^_Y3iGH|tTQ zG`a$&s)ng*aa6n6=+uU??b!2!_4IG83a1vkE>-w{)LO3ZSK4%-A_14+BibZ%6Aao4 zgLdFRmmLNRgD!0<3g20e4R59_&VfB2^D3e6hgz>K5{Wk0!+86JXPIHr6=fO#7z+KL6@M#SEf$z(sYC7~Z%ry&h z&Evrf`&84-VPe6bQhbJ!lp72d%gD(`&CGr&osF*CCP3bTHn`y(Q8S@eVbl#6bq5#0 zlVQ=~U$WbQ{0~fDtW%tyS*0(HdMLa-`%%>wng*t#hpCuQ za8^3VX}Ii}yrz5GQ_qDzsccL&?mq1u=h`KO_}Q<`hWp_^8>=?161K}Wb?RO2)6eHw0|Jj~MbP3fs3Z(3jRU=2)j-84!bY)F z$7oOV?i!-~XQj^N2RpaF7mM}Xl5_^MBJw94D3m-jgtKx~*MU}psWf0J9b8tz+c)%n z6gIwih@2`2C0YCV^)};9!L<&V?4!prFK;Thb9@+p#HGxshaRKbpA2$nZ)!|F& z&zsF@R@ZRsHYzJIym>b9FyhoR8D${wsOAT42ZK7ope{JjyGy>&Vlu1~A8ZWHihZ3J z`Go#;A-tvfJC6_T)-S_HK;Tga6liZ4)DH#?!hs5?39CpHYb$!&Idc*_^qqYxQPEJu(GbO*Ir`#*X40al@3mTXt@Es6^P0WRx%-ikuaC?ZGy2 zO%4F@6H!^9zYC$uBp2xB(JkRuYO=MK@!5ssAg-A99ZfP$ngMHXCN1PfkrygTR`agU^-GBSq_b*=`T(Z4zdyUIj`yJ5BeCusI@7&G!hufu@ zyQh7(c8!Ss@%6soL}fz0dmWY73Yq#?e)v;y3g?~fK~}X8)$$Gp)Agf#U5g_0zHxcV z;}H#QNoV?S0Vm_3sj7_XgR@2CnxDRkeg)9Ou&-l(`#S!YuhPD^H!qZ>YI_+zh@+`~ z-&LCDZ)CG*vGl`$U_)pJ72|iIVbMLJzkvr!KbO1RQcstE)|A*Zd!%u`7hrW zH)O>D^epV_{NKJV{>#@ZlnJ*(?KPTfE}qf5f4a0rqp-!wlj^qHlNs9Z9XAc# zYT#oP{>_d=lK^r)?Y5@KZY6FF0N0NgZ{ZH1p+JD0QUuS$hx%aaQUk9v$ zmFO|A9O;9pu>i%9=Se;OB>n0BhXrQ=^gbLW0{^e6j*nmdpQ$bzXm5PUZp=NbCV0E| zo^At%F0D>~^oi;_#+CNWxEP8<*8g|TmKIZWnLStxb^Jy`@Yv{l+H3Wn29q?Zso&dG zou+AAg8?)aAG$9o{&toI|L^-Ul{-h5;WqopZGE%Hk2EJ7;{u7Y8a47&dC?JZq2!+b z`1-%jb>489?dcZw3)cu%i-QV3EW}*k3bni0aM!GSugM(aQURcuU|-q(_LbvbzG7CH zh4i|TcnFA$I#HfmJqcbj+a8|M6 z097N%D(o7@OGTjY;L^^+ir4S(|M6Ac`pn|pPPHHIE6r@mLw7S=q^H#Qx;(GAehmct zxJxLGT43_opIw@~wMz2bnX0n3E7#T1DRuAKg`jUO_slp52g-~Av^?yq(%-(S{pG8) z?=??9^qCu5=B{&W9V{0@`-|j4OFO<$O#F%)qT#+j_5aS-5`v;tiBcUSqK4=5ht;17 z%q1u~2o|PZedLyr6uqhc4nXU`zUuw$tHHl~Z7trJEYB8vh~hSnU0ffdl!|u>CK$b6 z=;+Ar@Zs^497^Nw>0ts2|4a}$X;3u(`~ToSF(iOl{e6eXf1>g7!#5jsV|bFr7{PUzi#mZXoe(!&_I+l#BxD^x$HIXm{vAm2 zzXB<~W32x;hK`u@*WArE!>BsI9E zSHQkj|Ltq-zkH?YFwS1JEclBifsOTB@MSqv+MkI5E$Jor zs0RH1({1^`M)H2Tsi=Q;i~oJvJDM_*I*W&k{Mno%3KuH-4y``q*9-yZRydN5za#1X zS0qSOa+k0lf6-nUk{d%@C7_$>Q>48rla+TpgdL-#)MSbZh@IOSNLw#*giXW9MA}^fc`2 z?BBl5|I62mnD!um9_E*RV&JF>_8% z@pN8Dq4*NVbEEu>p#bMKlOU!~?~CN-ivaX0?CZwgzV7_XSB=MK^J^lW91Mp3yfs>= zl$Muzs&~sf>Qqm(#DtK+3%NdUGLPe<7IAZWAAb5jRk8o;A`*J3MN^DBowA}kdvM3k zm#mt`&8c@-J;y?N#!N%&vIl_1Cx8k=B7(oFSS-QcRcxTFFDZAA5S55UCH;CnFKNpv z6JhPxsSq17qItbr$HC2X2So%1AOHtpg^!BDZ^B0qP!Mn#e{ukvhaWD$TPN~nFyfMAvOZ&ES`e^?t0Ud<^$+& zoC%_=h?pnFP`kPsscjy6nWCl?N^!M%)T0swo(H1@U<(lfMm!WghJ_uZI;x>UOTtvr zu%SFeB^xNdXQ3wi%ny0@ZE+)}KgjVQD=ebZQrUDpn|{6i$u#(4NIYvwebvtx!Nlk0 zZ+ooud6Wa(Rq5W0>)VA~+l#!9TtlnDs2VV;4gq|;MM2Lt|1F>42~*DPoG05WHT!t_ z^Sv=X7u8dMULO=Mc(`)JU5qw>K}}##a~x<9-9rA`PQlO0B1VG!2MQ;B%aGrmWg~NB zU!7y1TjZBR=@I-ce64;D;M1X_T4J;#OyvSoc|cUszL~WYG*4yp-TGpUtrG5JXL+5l z-nubQ&WgEgo?f!R3IratVxs+E&>$Ez3Wg3N46G`o?3R zBzvwk!pp>^j zzYDk1FCOwE`gM5$JT(@aahTpa@s%~Cmne^-+(?UMgJwy?qn&?~;n84YULv{{25o>r zn{c4*);Y-0q=Vu~smn_u#5>GQ38*1BWLzNAC`SHpg{;=KQzs-wzFbT>@Z z2U88;s7g1n!k678KywE!^=spaJAcXJK26~eukwd}@H(ph$KGbfbdo&0?pNX@(< zUhL27a>HPPX9+goG~*NE?l17wa3FB^T~*!B9$_@G%4U9Q2y;w8!Jr&CP+z%_jS|Idxo|F%j%%Ov5K(l;fQbRZNY;_gbD}(HK%h(8LtW;)3TbcZ zEcAmsieSYBN5TR&PEN<+3w!<-4EoRlFsKL&D*hXEaTOrWn(A?g?Rk^+Owmb@z2REX zs=~v2_a94#{mcdek1GDq(lDqz461|!74G;z!n^zHQxjwFNRRpgA?rG=pLKOL-xo{1 zs7z8GIeIw1JkW1RLN#2n;arOvR+gA_sVt^DXEbLZT23tnMIV<0$g;n5b%$LqVdp{X zz+`$bnE{SWq4>B$>Gih!o=ZiSCBzgvkFDy?Kbx_?W$-w(V{P&V5a@dAC+CxMAMjQ` zMNq6rWo}2=ic5yso*L}`JP_7aFjgg2%j8!egCvX=`jQC`J+ z&Z$eORGIGRCJS~wLmgq469M1>{ZZ{bIt)gQf>C2}0Z^31(-|=`MdwXao~ArS%p@z2 z8}iXbx4x^GjQ1xa_$sSq-0@HLZ}mdd99$ivUyNN+&3aJlr}ENNCdVGVq2+aePKH6# zV9+casFpg#ilWJTO6=qKIEzQ_i>cGfI(xJvrTt_F3`G;GK%nhYti!jsa!#h?%8V01 z`f{zWmVcfpbu3fPze;t@<#imo5C$!QK`S6oY2UU}d7m#S1uwf)u2oFF3{6_J zPAtD`4u1EfX29EN!@QZa6fmslwmz{3#_QMhD!kN4QZj;+&6aLkaL>ssxBWanl7t*Z zkHetTFz74}w8TA7*f%MY*eKdrdsce4vRGu-K9->lh%kR5_+u1)N5B~D_WYkn;AweO zhmT%`sWxD$9f(TC_Y-xRfXUmiB;(xtlQg65SHY9qv>)cgHzy9Ium$SV(9X0-xc`R+ zx)0!;wN!yKvgOphffkElxvzYNUqmigR_i1vU8GM*cTg(LbU@>yq1BQI4Xu_~G)$E# zLqZk#>l!hW<@RQZL$TKMpmZUs$kqcrVCa57fEWmTmACe%?^AbuU>|0QmV`m2VNiJ-=)2E1 z+&39#_w*NHob)udr+)UQQ75v!yXa#){J`Vl77+NuAa^YA@vcpZWbBuNZWe)<_{~Ra z9EYP)LZo#)eigQ8H5gO_2GzlV>PPR7>(bw9z!4Z2CEr?ik`Hhpg+I0>eM*{2FZs zgF3>XE;vw(<(xS_H{S&jA`15N-v%fRxeHCBmq=2=wPIFoawo}&qfts|Jv7SdKNryh z4bDLDZ4yT{MQA^mEeK`{!?8u>WP$JV@Nr)u9KHGSyst2-|J4nWH9OgrN>S>ZI562q zy+VeLg+UWx&}4KZ9_rk!R-tz&EjCWxmhB-f=SQ7=!A~e}UJR#@DnIAnE;0a?)=v*R zSesMqoK0V}mtd2Y?*BY~GD$`%e{;6f|CNYYJ30%-%!M%vahQD^pRqU3y)rN%BqJ)Q zpG~+E-??2BX{)36yrb-DEjX#u{*w>z7zG@IlNwclriNbUhhA33J)+Rnzt!M-wyt*4 zpoY(qFXQfOldm#fv`h;RXc(?t@oqJ0=PUUF;G-L0N19+qT5%bR*z*n;iuGNQrWVeg zdm$x|y6)Z*|DJz$@M2M-c!)F*IO$G$gP|{;Ky!tbzU)FwyJI$CA*D9OZ4_@$_3~5g zWpp{lTZ0oX(YXL4k}%7b$m^#1n%bRO(RAs}fONFD+@vKs|^ zO~^zIHqpSD{Fl9^5=^WE{f4*`VITt-$OIR}f740o;oLXJJ%O_X>=Pl6>|h{A9MBPc zBG?5&CLXYfH_qh0=mK3}PCpzoE{JXzC=3RQ!T}x8%K2NAzc4_XU}hz$O(qlcTz?STL|OqCz;l>fb?vr5D&+K~N1aR1*&Bh~5h9oFJ19 z*rXe0azy6@_C=7%0Bkb++vNY=7lB<5WHJt$Oyf+B>Uvt?xcZ>qV??vKAfXs_V5mhH zY83}{L{9^DED-w+Y_g9tIjUpX_?=bU4uC=-h@l(70|p`@hO05+|EeDWyA6n(2DYIm z#w{BE_ih91B_IiaVAF`w?YMi6|oD<>-U=-3LGB|A%($6Q8=U{YId+9hfES- zlVqI9(G_{@pNQdWkp|CD9vCPW1}ek>9Z_$C)ilIi0h?6gOpd6g!CDzIX@E_da3)98 z%3vi7nRLJ=-8hpYDq*nBg-ixulVP06QFU$!F{+lhl^E6cpVW*ILv=1##X?xqFxD&% z>xe2AtWhD8Mc8DO_`j%8!3q?z*nutfaTZ5bpd0`36kKFTa1|&%$^WkcohK#&{{=mu zU9n+V`cj;syoVJ|sIM1FgV z^LG(Ol7x|@N&dUakBLZ<$ciY4sFrA+2%ngOSdBP{IE#3k7)v5fB2NOQp#XeK;b8Nl zN$P_7?&p`ecY~$2707bMD(-WVAX_vexenBKppS5yda=CCT$$NLSIr}t#RgN{dQ&%? zc7}SDS4UUWZXQU1D%WV|P{DjkI@9E_VoylwcYn=qu{BnWF=PVlR%rH~v!IH}&GMYW zKCju($+cSnU88G77D(lp&H6>NY0hsK4vzJAfIp&0aYl6o-K)a zUCAEzV0gl1{)v#*+?T3T6Krc6}0z2)I4*8=rTOBEJSMcQH_IYZAt zZawXo9aohTe++e&haOBN{^)XOUi)vo;QCiU49;_^(g(YDDH_9=7RzW^T(NI8bqKWY?yJ_-7Hsd$0dg(^A4q22vs4`SSvEpCBk@?m`_*<~lN2U(_Z9W(NOI+W) zg6%nf9E0*=H4*cc_4yO~)7G@DPRb+X3hAz5wa*4B#Cz3-h*FcUfGYYDXCJ4_*WS$v z?aJNwMm)Gh&{EE{E&Fl{@1yfXK$aS)k{le>nslK~tU^h_W-XS?EO?R43;&(2xL2@B zL`DP)F{tuu54}?VA=snwNr_8eTTWyp)6mCGyU<)-8Gpet^gAn1Wt-!!w6dvQvS!Hb zi;bHS;bz-5102j+&sW0@d+)vZzznK%?@&G;wg3E}4S9p}0oTKDp_geSfNsgVJLfgT zz4{s<6}y}-K`0H2r%nJ!}^7u1xAB($jglGKxgvIO-{C1#O*HqSP4x zREgodjM=far@SZRnc=W^#tQuWc;ory+MACK`xA_G3jjnIjh*9;&&Pa{eib_#{nP$Qh1`oo3^f!W;Nrr-+RdLy5!EC!4a$9o8GN1oPM^N@ z4{MTKUb@Q01#t#zondV*_G_jiUPod*zFt!pt=ZCkb>_h4$xC;B?G^wtKr&1+26JMX zNcu>|p&=3obdnmsOzk8foXN%Gs6W_L>}R+>z4&l4{Y3`nX|0jGPoRTYNj@1P#3{8> zeQV@;1rFcYc8i|!c6?_cb^bt5)JqS(NgJrO6Oqc)3w6jtoxdPoxy+%?VfrQ|jxhA$%Yld*qG=Lddu0Z(x4jRuGRzY3@w7}w%9EIGf%Qevic_}IVchv z3G4z{okL27$5PF$*RAHXCibVR=e8a_m%*@$fq=9DHKJV640ZOZvHCr)WG;%-cK5%j z(HHJ;GS_V1UxGNNM;OVUe&obZnXc&k;`!b_%jqBME!ZGJ%-F!iMYhSXd<>};MYv4~o|PtH`kq5A~- z7`RQRe<*cQ9m>?1my+jvWz@{wYah4sn?Dk41hKNzWkIX&3FRKWgRU8CzYl5Y0YN6YIL|1ta9q^%1=l2C@|*egOU=mxVc$=&j&+&yPlxxJGQY`r@Ye-D zs%QtlRVWl-S0!Q=`?|R?c`s;G_#ow7@3$oKTNej7i(SN2iae_U3=c*CBZA8z4Tb|F z4(AZ0k3Gv;<5%t#W=!(*Y{XLOi@w;=3u5;DK5Cn`&5BozZ-6RqUsp<=47=NWl3+=$ zOEpk~PAt1C*td4aQp%)Cx!Xztq<Tq=#N5PAYl1|mF1;B(W2w2lBZ7hh;uh#UVL3<&3| z*q>wG@qZ-mcprzaa$P%AxUxD!=T_QnrYA7g-;~ zf`?R{{x%040Y1`Kv6YE;K#*%%I)x*`A{#fwRO{RlBi)I5AUJrClXGRL!(U}qqh=>0dGS#yU!S(K_IDv+_mCQ? zSDK$Mf;$$lY)-u~5_l zp)p&~QmV*-pQS>aWL!Wwb43ok-fsGe!)c|m)$DDYM9JM;kLl0EW_hmU_B|UBOLH*1 z=Y0aC|2nX|SHsdlZ9*b7>n6g@9jRAyL*e#SoA2s~D7L^n$ipn9?g$P0GrI2Le6@Bf z^SYFrVvx|Co4v}In;Ifj1slYVBS;4qq(lD*i5L)1znj`mE ze(|5+=W|pV@otP$8$ZR|5O1JdvggxFjJ1Vof$5Vb#J3m&^EvlaNt4ZAKQ9ZNT9S72z8dCwR<{oCWsPY!I!y*D9FZaJm`P!J#j}+x))LAb) zi#Xn!oMKMNx#R8)V5Tv%n0Z_d`!M5}ML36G$o<@?lZk;f+*vbg`lsVO>?Ci~SxF$a zql9Kl$%CF+Tm@AUBu`rH68MU0C72CPFRy`Df!O zt%~y7rL|Y!Hip^9Ah6)|gzAZ&;+Z7bPJh-nF{;!?l9Y}s%W)H%Qq3(pJ+?oG`ka+P zK(1C!f-ae>Ja5*<=*Ram8b8k{uhFwL3;LcV`FNYm9^!Nka?dA{Wvsq$bkoC!Z8|WQ z?z|oA{xk1yPiboIT3-M#L|81A0_MbQVDPat&=85~#;juKp&=f|5z~QTf`%weHHjUD z4I1Ku{xx0eIAhAqKb(?tn&&B=xxze(728w)MXxiHDEpqA5L7C28Q$ zJO8X7#TwEO+xVhWz)SM!HO@JxYLx!Y-tR5B<0WmL@X0?(+#xTPczvsqFL$5OoU~K7 zjuTW#m(FXIYxO|9PA`6Vspl2qVZq_23Lne%TjXr5GN@6CTfz6!Z0J(& zK)D)hJ?0!rZjo3)Nz8L5v+SkeEk>#Y-RJ?C52J5GuAl*oBvu+L55*5%AF74IqSvxX zh6+QObzbH?=n~73N(pWf;Fp;JmzCr6XrSi3)231j98^dTu) zKAiI{ZA(_~Aj#XyRO>ta&0jM8#g`j`Tp%FoqAq{Fs83GbAdBEKhv~xZH2*P;OQMTW zp7TSAjK?8PC4${(P5r$W7ExzxDm1w}TbPxs8wgM3S~_d6Hh8}UFfLdRtT!$zCRj(T zADk782-W}_1P!6}(A(2*!X?hxsZm6NB-X^-qxae)n@9ZUjI{?QZYf0JG@we-jZ6%o zGPBVezvHvqb-KNXP?FVU$2f@;?|a(z$0eb!X47+Md5n4-_egM6-_880!RP%=Oq?<& z`P2vYUVCC^OQ@<3!HYP^@%$QIJ#C-9KK@zvE#Je?uM%HDa?|a>U!rp z?EfKZDtT*<@lmH05qM|;eupPQi2oHSxjuS-|1h8WRhiPo)w(uZwTVV}9_NsZ4?U7`6~wg3DnnHVaz;=MW5;?AF%q zq-Nymi^u+2oJ6#BbRRH{A8kcrRQeh15)eI(ph}4ByJNMviz8Nc6kN~VQ$-C->D9O5 zC$T34kxo1j$^g51!2V^jWexU<1%t4Q1jV+cTIA&_m7!RC?8%#^;?9q`ArGBYL_aR3CMfXYa6V^&+pvJ{8sS`I!40zJey8%0nHLvB;%T&voIz45At0I|r9sVzC%G zM<%|Hz_kVTfCu_zHP^U8h#+FSBh2iO-w$X-wU2y0D%H*QH{J& zcwFQc8;S}{>Qe{@K0H`+SnnT1$0-Q)L(!YL8V?@ZJ-f&kDzSHiQ^M)A_^;-6``~<|=HbBpx!8&l>=TIW+Sc0TzMTtzh z3TyeC@b`wf@R5fA20@BXN<<1K6oFa9?qjjg5L)4$PS4$Wx=+ESA6s~E@s-P!Dy0ea z(_DnlejziQnIvyQ2MTN+Jw*6A1@7)Sk^Yhaj4G;X9oTgO1MC&NAXhT^coLBQ^Q&1R zcLlEToduC{*B=)YUo?6t z)9m!z@DeX(=6=4H%Nqf-FI3V8Lxy-w$oyy!-15~+1_ke21r2#Q7|LA9pHf8U)GbKjroNU&c#fvYB zRV<*H6xu zqV%_Y=B4V_6uOqQ%1l)o5o-sQhm1MOPZSH+UucVo)yw}oX z)tDEULjzJ1Qgd7ml}PnSt>7GjA=4e4C~#wSKaEJ!8AeTdEmXGFX{m9YSN|0{b2VHD@%jEa{JcTpzVT> z{66PCZk{gL@)lrfxvnGJ3VHZl<51vA73Y~y;`<*TQS-fv+oBEkW1TP3#1m&6p_ zF;Wjw?>|||v)0ahj<$6^Ax)CKE}KWNl>74v(Jck@JNM`Mm(?_(UZU|ya?-7UQ}^$w zX*cOJ8}m+(7K*iR7kI>G!GQS%{G*X?|w&YX7J@(}) zN#5dDj@B}zLg{tV-3pCw088P>M^)^#O40U_?8U&!zWP?_Uxk-N)gGsHNQMQ!ALT6JLl4y6s1UQ z<~P%v-I_zp^-ETJUEqF2+C+s=Vroe{NQa>zw0cHN2$RK`ihsm(E+bnR zC4bbNMYps}G~EmysJZvNJPTSJ#?|FU!}n9QP0OQXT6NOZERuVYT~R45zf?GveYdQ^ zoe9_>&!J){^un@eKCr)=s6`^kHFU`H*=?|bPVO%GJ`?2Oa&Bht=boz{EKY*L?f+kt>37c`f(&wpbLq$<3uvMEbAxz z<&&`&tUBv5TPhdj$gGNf+JZIC9$m!sWWKTBG(97zcU2JMT=U|l zlhe1^jiP1Nret+y!FZM!s+b#)z0!REeq1>eNQeniY^(+kI4Q_z$mk)@p_xT_%D1NW=&s)J#=g1K)=dgLyLhDR>|aS7#jGERqeIX33RVEN;=PNqbr2WJICL?%LJ01fdl6{PrNCeRS756dQ+ zE^ye;lz+*VKR^@yf2^H#T$IcE_lZRoSYp{-xNMUTmk1^ z-P>XQ9pwI@s{CPLlw;q{_4Br#LNO5hk16QoKDRVVJc@giZFZhmF>O{i5~>fRE?pOV zAH1Ue+DxB-bqg3femHYAroK$=P^wE9py>A+`Ov?<`Lc~EFxj&$(BQ>Op!PTdoktm6 z3oSkh%*vP3uG7o1E21mxy|(bYl;IVw zQ>Ps#spS>Qji(bQ>|A}WSJ7|CFjh;P5W7|$;;MzG&&EjhtML0)PgUsC(A$mI^W^XV z=Aid)rCG;G%cA8}Q@1OJpJK27x~Fu%?FXH-_1KFmjOg)-G>kL~WWpRs{Yc{g6Ntk} zBCfRM>NPWl&7PFEUy*icOuea7!nPCf z^KrK&uL4feOLcrx;5$0CqK$p(2mfG;^u&{!j$Ew*upFW`Qq0AbuuP&(Qp~NDuymqcQp}Z=ur#7UQp|mouoR+EQp{zQ zutcH>Qal{=I}pR-h-OHM0h0t4MKni>0!&g^7||jrJz$c-f{0d0*#Q#{^CQ|I1DDsR&>qVD3bFq|$(igt-tMkSYTv3g$p`OsWo;6fheiY%)E-q=Z=!;gOjDCKb$_ zh=9xrFsWfCM8sqcfJp;0B7&2-112p@j|fHP2bgp)Eh1{NFuHgTT-5VZbyw!BaQsTy%M^Qk?q+#m7C-88fnItB8phk? zmpq!Oz%=eGVS;nW_(AlBu6TE3S1!%*gY1r)ZcB(f_=d`4U}nXi?wUZaI(Kh7MLJt`u>VbC-%Wir`8l_T9n2EAbnElwD7KIZ`v(EN*Cc z_NtU!FORl)EFs%~0Ml!F`NwZdsnA7Yq$jV+d-#@?-H#;l3 zknRMMK|1dc8E!B{EqXgkT{Q@S)yu@#LtAf;Pvb2pthz1q00Xodx!m_!vm)EEfd$1? zyUYsx8sVM$TOD=_KLyh9PGW#3S{Q7!txoQ_5JSqO)rS0q9y#DjNdE|Ql-SmGe=;$^ z&WSD}>zTy9hrB+&x?UehaP8LekeLKBjEypCSiNiir-Ccc1#;cCekx;mPgJz$%Tp2d zJEB`y-$YFrIIoEC)9?Bymy@H5=w|xd>VEr>ZG4$JU(5fS-{iWoHn4&4Q=ah_f&LNkN4lEOVN2~`Oe2%>8ot1S4Q2fb4_K(ga z&^GcF$XIUMn}8yFJQ%u2QRJ)T%6M)eLpH0Ox^8FbBK=R-38M&xuSCwhKB3_U@{6eF z-Ft*~gLkvMuWbCN5up(zI+sd1^Nh!V)q>`S(=iZIU13F)Lw-X8T9>k4J@=Sx;KP-} z*`D`-&&}wCt-hh8L>JkT^n4*O#QjKeFy@eCDvGD$Rd_^(uy<3l`VnJ?ZP5*MkqzQR z*@4;kD&{iwJ4xPUgS$F8j8m@+lCp2iYG%v#>7k1t+lX9RC>rxwl*&jthea1|v9I{Y zSB)%`GtLK8Sf95=7h%4;`N;Ir&V)Y!v*eS4O`PA{MHx?U6v}JEy_r80sR6-IwiE6p zEJH@rpSQRsEW0LQ(TAv?HkK-}g*YTcqzKJm-Do^3VI`M?VA&JMHF4(oz#< zyfY8)BDYco`MosJ7deABy8W>;o9axGcPfv+oYlw?>qGN}@z}UNBT#4M1zt(zw$1Qc zV#LW`KLFHR_32IGH{}rYh(kiYZ(_4AS7yxVyk{K7O1v&+xA>o-d&}loJS@8w+e>UB zt+=f}n2D2nS4h<+164F}GF{g@Poc1FvOTf`GW1{wbRKd#tis(z4>T1StQHt7{T^8R z<{HF(jrnAyoXjb6yKxEMOr&DR`8BX>+l|%OPgyDf)TmKTAB1 z?PN(PJl@>)9lcjA`@{EZa2pkhOCh$4v?1eYDEQH-M8GeRb9_H5AIEopDF){HEKl;$*fv1qatESzA6EIBT6k zVc2jyH~}2+3v7ezn2Z=Ofo>0GKBya~T$&LH`!qCb$MfZBfwU@iVYv#!_KVSXqvrPL zBGp*kFS_(?1yqF*?e-k4Tc&Cb=G|{RO9s`NhYH%*NP!Bq<<)Nz_KDcDv|LlTBeC?{ z<>kj$!U2XegTiz#)`{r;bA<*XMzB`tGaV`eC}MutN(OB@e4wXZGQEjpIlOp%I@>84) zoIgKA1okGort{zroyyPxSAVtU@E zyQl#RR6j(dQ!iT6)t+y4C|2`NRzGgwzW&DQ_N!1OuUXNEbzsI2yhAARu+6iBCT{%Z zb%!U9qlGj_V_n7RU)ESvj>}LP0M-9e{P^OF>esThy}!li$@lbLz(y)BTCbl-v{sMA ze*W;EtFOPpoFE`{vE4zKK;n7B;mo8lL zP7WBk%z8~DpLd=_uS4;IlazGzR>6OUdXI><3JqpJoS*a-%}l6btBOt(uNZ{9n~t;2 z|CvH$cM656!qwqgf5&3qf|6a(s9<_;hvu9?qi?Ex5gpf;=y|(L7eAa|t`DpLiMcP8 z;}i-W*kZ$yM}wu|O{GRzd(U&v%+%zgcg?&{4Zi+o z=q(fc{NL+8b|0H#>9jpk>ZUzkow`>vtb8tv(um%N#^e+VGlHAI%`vg44A+BOfw71V zo%@QO7hUwNt)-pxs~9x9B7!;?U8mrmb>t{{M7`Lc=+z_`a8PZWeRBX?K!ydKJRDy=*gNh@#dWPA}F4{vOAt7|0GQUNW75$JFAo_ z$G^4EM?G2PNe32x=t7rlZ12-hA>?k~fGmG25)FNAPk?BQNh1BlM_G`53jNbpKHF8) z=&e*vYj}z%^)6D2>bcz(y)p7QfOS4o#uFy0l~JpHM+=w5{(JJ z(9@*g^BQKzt5w8V)1NgvakIV%cKgpKPLNvyVQDIHEc@jKmW~_Y6d5G1Vb?lvtdEth zrK-Q475@W;Jh}+_1gDGHF+Vc@i8|k*e#r&9OL<>z*0R0DUm@jF2u_6q&U$Jn!#s{# zxALAdUf$q6wQ-epAL1}jsIrUIes!iB!eL^; z*i%o3`nn=s#fME);tCFT%`E)5RM_gTWTza|tQCSRb99T#?~JKVW9X;|ir)Z05$sa0tzw>XWPcWOvL|#)B?t z#-)S}nUq-&`Ykh4$s%Jc?&jWWh_ZM^BoNYWLC)w-Wm`Kf+8JqdsgDn6n5!)?KT0%XBwg#_n#5T$~{NQU` zJSXqYt5ogJ!YqTIkadRh&gvd_|_ds4s%V!R(00Ddc;Ba zD174YxN2j`y}OSi6E)FwEqOONa6j}gJB0jQNpft?)nR)C0A;nD$qRH2zqVpX6K)rcJ0q& zYR<_%l64?wA>vEdJt;c+$a@dqd5p~^RWH8TP@TJZwOs7ifcv3fg-Em&O^qD3;Ul|1 zk_q`rgN_Yv+96-de!f=<^CuCdp)Y?7>7Z6(a@gEo#h!W}HV0pXuVSL97d`{u0HX<( z3Gak&112!zJ0x8%s(EwOBrR2;S{#=$o6f0w?!3)qj^f*@dbX1v(L0518w#xZS+375 zgstu`Bqg`rO(x|go>k3>-=L~ZoV*vnjxIvQy_TnVXK{z+hF+*3Rm!bWQkQ(5kaPWU z<2gSNlsibk(^4@bJ)bv*u09wr!S7KhBxbz)@JFO_G0))o$i{hMeW2!zijr)?x=DH( zoeS9M<_WJl`ZEcHWH{EnmkpSzA>uarCe$QF>5tV8;S70`*a3&Pb}~&Z`C}};J(W^Q zA{){?tvSGpQP)11T*mKxwOJ|rk_{_L%iXMiaMFgi5l1nR{^K)%m#v)Zr559T_jiz3 zV5Pm>u{l)r*LVJXvqb<$uB77s6}*>ISd){rbzJto=+;AhyqZzPc*0dT!>U@)R{9Y&Bgn2xJQLU z)HF$@0M8@77r}&=KF`E)aShJ%1l(gJ6qJ|!6_S^W+x;*pZ!tUzeMtVq!IF$UniR@dCwldHOc}C@TE5U;T~!Mg|Ss^fzIrJQ$oDMNSQR z19l82CZ`8Xpcfk) z{?zMSK|Iyop7e5a3*NYcNtH19d7`UDxN%Sy8^CW^&B8ML!A>4GPDU4pOnt};p^I0@ zsDE2hnng>t-M;{^!W~g-{WXV~$qRK7A9p=oH2UFREryi}DURGDk)QpnMXfOR_2*NC z4{PYP%HNZZTIW$=(pTJ{jPmzI-YZd^`BxNik@J!Z{2fJP!!NOxNfDYvoP*M3IgGuL zfxp+uY!;R2mV?dR@glhaPb}4|-=p23iWZ~TF|PbnC0zC>z!bOW#aams6E(&61wb9c zl|(A(mO&$PX-QqFs{Y-KLOmE!&V*F2B(-9F1Qo7jHTka8HI~vmj zst%Lyn(+OIoAvX!Vt;vJLp^;NuVCfU>?R$FZ-IC44M&vG5;vtx)<<@wl36W)MLUxC zs`0Ip^=f{>8!YU%m-yGLEZ7n}99|CzYQ{>xR+*&?&(G-&QXx9r5z`AEZd%YGmMdym zWA^OS`mvCoar2)Dk|38Rm&Zg9JGltCG8jRy12{9eDq!Nm(&2k>b-;wc=E&0ET7U^; z>Bfgvm*paF3pl=gJ8s!y~;QwHeIJ)1tKP`WBw9@L`y#Jwx)<_fJxN&;C;sgyvcOdXWkb)BaHzb zlob1AE30+S1*@olq?A0%qIsOOI)`UJ9#U{hE;xo0DbxKVx_0=?Gml3$?+*Tt7#u2E zW-o<1)=^%+m1r!srEymzTy0=BfqI!3Pw3;3dP3z^N2+LfqUACQXKT(?$ z5hbq~*yEj{Jb)((KS}OKGBY#wPa7SJyrvm{V zKgVk`obq5^44fS~$D0wrxKQO79 zdGytm8IR>f`6u&ZnUcPS{oY%+0B<6`u4D8FmJ>TU(|w~N4}yj7X{Yhi++go^v&Q=` zMy%Q(s226nzVp zY&Q8W?Ar&ZyJU1yrYgx%sv$4$NQ|Gx_km-)^5*q7=~+^E`NO3X&V8RhkCMals4e!c z^gR3C`m(ejhQD658eiv4KbNHJBeGL<0I##Vxca4}9U<>hS^e`SX4=K&xuQ$yP}3LD z!GzcS_y8W#Xe;*|0c=0?=i$1Oqvhz`S6IoMX6Ge>NS{u*#4ss#w;Aw{sxrPCuN_(^ za$?{4@o5-Ok`JE<2VRdE+($oX@9p{Kv(dEd20LH4y3E9lRwTj zLonw7p@b+2ga=*ZbLE5H968IF%- zpIenkt=T3wKlt#Oa$Q^C!Wx@HY>?v7aEdEX^K2*93Km9Q62a0%YZBbG<%W4f?n||a z+$z<*>|@<6R_v5>VnG@*o)64|pRbC&oM4uyl2tN4XN%5@baj4Nfr~q&=V}A6#+HL? zcPKqvetlj`Ypu+Nh_<(OmOOi+P}^HUiu+v{VBM0sklEO&;cm!E__+NAt?CcG6sD_p zQ41W@L5Qpy705SX?6X@}TiaSoNV5gF@b#_9Y}RpV@^xbkA+9&q%VKc(&@5Opc^i4> z-|w+gc-?M4QJ;+d2D z%{6)te~Q-FPEyd`qu{>(4p64Y6lRT;iXV(uxR!ZC`A)N8>tn$I<905ZsrlW>`D9@y z8d*k~r8QsC&87PeiikEzj;6X-ZW)C_k$#4AQq2GKBmhr!fp51VHZrh;Vz}}G4pw*N zE2ASLGcl)cepyrzKj8q6LxN1gqX}onBwkvHQ|PNT`*QiopOHR+wd74*?t zWBp>s!AF9)+}IHsee(v>FY`rx2ckk0WfM2GJpPHdLGn@Z2~51zllPL(fboV7kETd9 zedoit-u5N!F@$#WTY6LF@85sW%~x*cwJ>}#Lnnf{rEEPJr9?mMx@Lt+)yH{9(c*YB zj_K{wd@*8g7>RN*D^Qn5A9}T-Ychd0eHpZ-WC!u@)WVfPOJQq0f_#3v4`cc8&E@R%RwEhS%8enBy!R|4VYnB`yn3RITlC8 zV0)Z@Y#7UT%7d+vZ;)^Q9Z80g9DAYR>4=Q9g1ej96O0|)121X4^Y9%%#r*KmxTA+v zijC#+L0=}JUiJaGG_me02{HFi`+`?iwP=u5gBn}T+`xAhr+`pJrNl`uNY!^KgL5}K zi))|8f^ zXUu>1omElVT^H#S+v{pJD1PR_%NLeRAVtNf*UTT^2snfPmvKL(8_rv0!5(t58Bs&| z9Mp;12dU474-w1ul=wpRl*Kg@?wZf|g%@Je_mDKZ<4Uf%^A}B-sN6|>g1kPR@?Zz# z$K==uU_t;}B;O;)15EUpW%4<40>H$DnUGhK69Xo&C`a?IzeiarIJ(o;(&~#^^c{<* zNmz>ldtRg={=WT5^5}!dZf1HSPC0pyb%t}VIma$5iqiTI!NXA%=@R+Wy$Yl+Z=s9i zUYLs;*Col5d-E5v`Q>l+8UIocuAC#n&}H;VE_+OAnMddw3O z7Hr=yvD(^Wap+A%QUZP|-5y<8e2~D&pFTI63boO&z(YoK`a3F}8`te9;{k)Tq?f^D zSdLNo`=d;v45^v)TlpC%#YoapO89W?TNj>9#z&<#}CyF^u0;%y#CgBV~Wf5&ae&#v2;^kc6y~Cw}l2h z&=HqAH)trxr@O~eGM78;J-QcOTmQN%Jg1Lz7L|8!bpnW*NXcEhTlMn21J8a<-LBk& zCaUgf^%wP5f0*6cP@aCT-SI3J+0myj#i%nCb-`a)IfiF|`Uo+IEl17!`hB5e53Wlr zrqfSzZctFUJ8B^_5??##_R6#M8MS7wr5Ascjc0M`j0eEFecsxRG|)=>L`3hIgfPL& zC}m2W7?L2I;k?PaSLvRc^@wwIBnNk+U|Bqz}>>AOgQ`0K|Kq0dEM zY;`XqzeH-5UnCRv)YmbK7-8_kbrWMxO#6D`cglmQBGeIDe@BqSQ^^%8h5chn#Na74hV22`YVmM*Nxl z4WKU2tA1WaQ=>j}wdGooctfKN`;!;*kRVDzhxC5c{u)#r3yiF`t#l?pp08GCj%A2G zKqAsaP~c_Y6`oH-rt<%HsxK_&OA90Td2N_QCDW9^WNnZvQvD$Nu5>k1FDKswxL`oN zrNWjmx+|)xl^Z{JMV3?ocBGUouJmz$&_r#~(8F}tqiN{kQWyRc+NRaRb_gjga}ql) z|20uxQ8OmC*o~EcykUeeL6~DANg1JsumU3qMvah1*Z?LlK|4pJRb}$rj)YH6g!-bD z-dshNT2-)wApaNr%lrxy5Mp$Zn~_&vh1&jN>*a9Xep$l1yF+t%tC>`spt5+#SFO(i zNFCCW4o@b!E@O@BziQ7ll-#+*GuhdPENG%S?E@~|Mmsn+MePkKg^slnxq zMqq_E3wr~j;>)@@1f=D|JnbSS$|aMHuFX4S?_*s)tvS#)WXX3pTYGHQA*b$>o!9JA zYARFFhA;28cV`r&Y(=aBycQ<5d2-aWKvsd z=aGjmkbp8ro!YC*Rh7d%;u1@=tTs_2QTSM;or^1n*V|Xhy81XyiU!+y8}wE${I2@` zUfVmPb*qQ6gI;Ml8i)6$1Btor|M5g{F5ysl*+Rkc6a{x_KpEo)oK0Lc=q(Elark?K z@p^#Q?vg(@`GE2|B>IqGipuT5%nXOtLE51?4Ps8`L#rX~ch$7rD?(k4vwGj(cs!|D zpmT#qtzAU*hCt#=?#2;b!gwMI5r;^`#G5-J43PrH8#+9??#B%$+EvgSCK8~?SE$du zBj%yVw=2pDY1-``Q~LyDmJJyP4_92|y1zanQRMb*Jz~U6KzOq(e=Bj-X!4FiH$C`S z?e$U9YpK2BzpX4?{`C9H%db)LjfpNv%CjSos8TJ)zpSSBI| zk^grT!M|4Cn|oV#L@%xCp6SL-B*_2?2hc^j=x2^DSW#4G(Hw-1jf zK(!mub4$fZ$n4u=euZ0wH#XP-bp$aoUxPd@t{Ns?>z2>scXugJrTkD)?Z+9ty5}$^ zfMBlZ@~jKC|Lx_&z6|A8fV)z7c^=$l!6V4}ifwCe&2g#@;N=WHokkrsw%1tAuXQ$0VV`yL=H!E0wx}8hD?vV7chZ? zQslZHe@y$vyl{4_!FwKC$m$vWEz`r>rwSEi7~%ePivV4OWzSftMfi5e(#}N5b*0gM zs2Rc?H;4jWao>nLM+*ha{x0@KWwunA%ZPRxMeBCYS?14MZEI3;COZ!!Si~dMfILse zeBR6WZx%x~kM|notEJYe!ZPd4=eXzBX1xV?SG<4@vR5odzESSwy(I}&nfef)k?C-S z_?rzSMz)(#M`I0lfh5k8tC3q>E%PU2w1x70hSi(}Chzb!KTEi?buHKLE0~xAZLl)b zb=yd#H*VO6tv^@&hCOtnKy{P)`ac6Qs*dl zAU~eRi3rL_Ki&G7fmghc=wZ2)TI}7!Gb|_!@T>*=U+2oheQg+VdE_^w1VWk3r1;;2 zNL{zFL6n-BI26M-qVaok2w$ZcaNQ}Mra(V`XiuCjL7)*SB2}~KY<9|nts*uM+d$(1 zGns0WH7_@2tp_uHAw99|%+7nh8DU?m2inLdj$}XN@PXSnRgL3T+%l^ZhuA;0GwvCb z@t*rK#&n_^Ca&M@R&c8cP-pY3+8`xU^Qz=weBY+ucFdRH6!s#htm3u1R7*ET4a9k$ zj}|J_;n1jlFemog%`$pDLYB`tG%YWB5!H>`vyXeK4)8{P*O#U0Xy=pkBZun2ObaYjokYsAO*cq96PSdCG$wAdV5(NWdGgMZ_Kg4={mQARDzX z%)QL}n-hm7K36J1dzHG6cF_KB_LAc(=Z3G6s-cU-y?qnpydoG?=fu8u+-NV9H^R&m zqb4kM$sK;Z`r|Vupe|MS*=OEfqxDzidPf>Pdr)GmwKSmITBWiga^fP`2#&`5b#|$F z=k6mM3vXe`+Rn-BRtqtvx|-d=4b|O(c;pyxEui9bcN@v3uH8}RvJveqTk%a|r5hz3 z_7Ay9mrX5Y{1!8X_1BLS`u0y+w^UmxtV_=WZ6((XRlhKG zARimOe<`5J@44)42mPO)ytO{xKd+xX9Kc>2{YxI%FZFCugWn(5#H4X8p+E}iQa{l%uT;8WFB_NUr zm@A~13^>*0W+9-<0m?5laLGI3H#ACPzNy{tLnRw#%>(M55O^-q%dtL{>a6A5e>EZF zPppksEMMG=cW;*WcCJrkpwdLA5SnN z*^ykBC?ZDEBYD9ng3TcakOF`S%m6j*)2H7;T?j34f9Xw*9WfX(GHPko{(@f(pjk8{zc7R)HZ$wSQ^%%|X+0Ks2DV(e{^tlUodF`aZ`(Z>_X;sJuErebEI~8lEW0~f&DU%>tW|=nw zK+T;URUIZomBpuSvJ$hUcudYSKjK49>NH&QY7B|8ox3Hfnh1S<(E6Jjr z>ibK^ybg~^uD%Vt6Eh`)YxS3x?Oh%orHA$mAvSQ+-&M$nuo`js#!pZ!NNXul-Q<4_ zuxe*k{VP9tv=*i5DQ*y*FZsY_W=3ynlaizoN=T&2qm2LZeN)X?SrG$KPXg<0{qaIt zy3&crE4UvYJJ{A-dL8KdxGN7gkRiR{N& z+T(clgiIeO&hUgxC{$@EmYcyx%q4xw+>nua>DB6=(}cS6X-jx*dyWY2QRp&7a555&j~{u>dDJ){vP=A@D8NE0yT&>_OJ*HFJ3f-JfG9Pfb;f%N#e=b(P?fc^V&cJZZ`e)7Ny zNSB1YW1Q^sSKYBo7pHjy*;<;HVe4~?%BAd+w(ovBS!Rn}`5>XZ`B1lkJPIY9!f_OztI4ueA4fv=};yrjG2$-^JNov}?X~ zmt~N>AjfCgv*|w7%^EciF3O?eBP5Q|97RS(yBV&NJ?68D^tNLXX(#1<#^!OwQ;%k1zQ>K)a1Lo?kxrzDO9=_{vq3r_UH*=uBDndd2uo+wCGaeeGW#2?7t-+AC9NRKF!Zky4eXA2}Lvwlg*hw zcVIY{)!%QOONrW6XkEwMMtdISh4e!PVIs&1>5dEoBM2seG)G1OCN69SA%ctpOkjo< zbsQP51$V1D|3EKcDscdL4C}Ac#Us@%^PcYh@ z_{!S25s$0aa`W}_)-K>9dhvS_k2fVGgUyn2aSi5(m<(@ReN`AQgCNQ`E~fp>>4?3# zB1D(TH{=#JZ8hyNH1|}(+hJ7qBtyNCpJPrQar#LEcttr2d3*gfl=GiPr98Ng-nO#a z^67kQOmS(dS&Lk?`Y$|E>m^9Gr8OzMO1QpquD(7MN6UJ9BA9KzRWmvG(bB(?88eYN z$b7I}(B3G}j?8PlGz*)Ouyxa5Rn<0@e&&&~Mg{preHV+|N#6ueR~{`cY4!G1sLS(> z4Pyh$${%e$UlHvrxT>ph`(Nzo|1h+(rhEkX8VvRgO|hVrjJGzsQ7n{8i( zd{)~JEdH$Yq0N?vI`^5is;uTnS4U0gl;^XG5;>eVr#x6GvJzPfdIOe@EJD@;CNM(_ z)Yg1c&cajAu{Tspz;zrLaZmg+>Sw90vcbyAW~#Uyx=3sS*1DqNl~-#GW1SZ-&%8@{ zTd1U6M?U!a#!^w^1HZd~?|i;Q^qM`CpJ1QpPLo7gy?a=5Cvm__f7W1>mQ>$O8K}!# zLVxAg6f&8o7P)Gpqv`V>+g2ZF4>|c>PRB$PoI@TaBbw`tXh8kAV zyE?Bo{g-4dab`!?|EWtSvKNUtWeaOV4kG_LVVj0*Mvem2!iA+ECy;-guAM+6B4>cV z;=x7{bI89=*G`ZPA{K$a;=@MCR*^HB-=p#b-K2fY#aI4>Do@RJ>*}fYum^U zkjFq-7_6D>7>Ng*OC^TYlVPKX0h0t)ONNI+0VXM|l8gXF513@IQZix`J7B_LMPzUk zFJO|x^2tyr5x_*ia>%Gr(twGCWs=dOlmQb3ODAJSsRJejERBpEr3aXluoN;blnG!` z!4k=MQC5IS4T~caKsf*=4J?XG1mzBxw6HKT36vjT(!qkrq)}mjNe}ZQlSjn?CIifi zOc|8|n2cD=*nc0A`cqcspHgI@>L}o0R9YzDEUq33JRVzhRh@zG`tC8jG9AujeuG_> zS=%gzCSkPn(z3_znI$Y@Y;=U2HDLp40)ov!uoVXEbSK<5q<HvaWK(IRoOcig(TcaE7IZ^@KyVla{FTxqwvbPb#piM1P7>ih@;sG)WkuFoO+~_kFKlCB z81PxAuFyCToCtzb{;F}wCsby8(}bBno7{x33r^-=z&J5MOeFHR%hQ z4uUg5a1I9ScuQw!h_BmFdA2c){@x>*2G7KrDcEiE3sMes0$ql3Mkt643IhIkp&(JH zG*mwNpHQ5?TK}vW7tm5rQY9#<79)u*>BlmoV@3H$z+^9_UJQM(B=IfY>PUXY3;De)=jNV7&L%_eK^<|<76U%( zumT+f!J{B}0t5bX@q-ExGTO)hAD3dv-qrdTSO>=;5gWx?7 ze1HKvs&=o+k?h!gnz_M!Y2F5p|0?a^INa2A=jm1q{t6)meAdilC^iMqqvBBjJt_eO z*rRH3ix!3^)8E^}cK3C9Ys%HSw3FmWESRC_Afw8za34HqUytfVL1s{p4b(9@;E5^z z74&D_{ez-FNz|YudW@voa$;ZSzADM6-$UP&CRm%^^W$2}Zs;%kBLc!&U6qZ0g}|SN zm_H@{L)k$(7f9#D(7Tf(W_5an+e9CHz5Tw#POg^mSo{VfhsmX#_MZY@tuZZe*4$vI z2ndz{!O{R&+E>W|Wny(F^_hU(3es3`nB4}8hQ1=I8+U5u)6Ro_CJgwjDR@w25UdJ< z)iL0in>gkJ<%!6AdhZxh!*gI`2ilo^Owm? zAprbc4|oA?VEFIj|DVniK}|rZ=Acw7OylgARLUNWcO`|OTxkv(gjpC;UmeM1c;B6n z^H{47@5A`$tjS(b2N3K6g55D-r6;X;6+yCT=)28X{kqmL`PodLzjIqTLYGzgRv2Fi)T$mt)wyZ=IUcwzLUwV**0ktyImup7M|$MvC6 z`rIJ(U*p1A6L+AAAUFjCr(wW?hEF(%=9uo`r(Ap0!#LK#H$KB-Y*aTNCBvkDq(+LV z@mUv9p_w2!2L$J1z>>4_;pwsbId!|<%vN4I3)lLD-zD^kr7-v%Xe9~*Pfv^koS%N$ z0!0+Sg95<`;aN8pp_QPXT2N0tMh{+iKv$7_Hv1k0-$+EK^KRS7OoZtTlxMw2AJt`9 zSq%8Bc^J?(5ZnoZdoke0eCRtVZ63bzU~zb`JHfbU=>GCKF3+Pk`&U#l>`LP?;Il5= zLPtUH1PGqNfKjx8HY(R#A(OQD4g|VRGr1(Y^viz!nrvmsR!?}MvFNe?KOJ9!E`sn? z5WazdvsrOj*5}DT2-v(IMl)JTUpBpGGf=WJBzjn0!1`+yQ|q(lIzsnA@Bs)u#(;zU zN=yjp3loC+?20L|NMsmqy@&3ldFYh%|;8(_d^%?5(vQ33-q0VOan6H|f%^Es|m z@}bOe`oD2<8RB z0svUrch@EG7Y8&vPoFG1S}(ORSBmeO3;pt^f(zI$&ANuoFyOQ16hkFIurvsk$AA~q z8J0Oji*0t#{m1$kivT~%v0N{RSn5Ug^ZAeFai;_)wT1t$6Rru{00<0JM981Py5 zYN0_OI1B_wVZa@_m%k~8jEOJLID6xwZfi7lbjn5HCCjtSy``+T_WU0L;7^yfKodcF z3P?`_=;(>!!voPI_kDr}t==Aa5(nxC#V&j?l@lMOsX)@tKT0swK5KFdG!q2pfZ%)# zSU#C@f#A4f5i7>=O>lV0ts$l^GaSdTA19({yPfP!W~L30>E7G$W~=dmFEQ@vb-ND)55v>ST3C5s5%OD*xuA=wQ7L-YLU zls0q~q;G)qZ47-ByFohdhfb3guM{neHSAdgXUI1?EhBX)kMvu7*zB07JnMEh^Z*1O zgJ5hb@Z<1JiP?KW&ryDbmDWgha^s*$(lw&VHC*{6ZD)u3gxtTX{iojePuDg<38;W! znwSb0rr{X6^&afWmbas+%*ShfLp*#c1m*X(^(XZ&CeCiP7#Pg{{WS?41?WdcQ$bKv z|9mkx>v3BsJt&76l*10lk@4l&DP%TrK|fJS!v*VHu6z_>BR)xuh0RYh(eiRTW?h*4@cN*`}Qdv(b-UU!>flgiup) zQAtpNO@V$n9{Mx^I>9d{_ftrJ0D-djh0pHm;*Js4dbaHG;S-54jpwNR`t2okJ$gS1h7bcp- zfT{6W4;w>WK(IRq_5#4tzHYuE&o`llq*OibX4B%01tbp)4DHO0i&kQvl!|`F!hp}Z zISUN}!C@dc3Ik3E)KK1bQM(OK{p|Scv&U}e<#92bd~Bx_o4MbvInHu^m^P0)ZF=xC z_|GiuPb-Okx>pC90%}SFHKk)(p>3e+_d=O6m6hx-jv*%{n*7(A%|3T?jrATQJ}fZ& zwVHF*%uQ$x2+jw=MHsLTMfFfa@MH#ZCt*xR=#o6rOvV5*ReZJmgpP|v;htMsONd>i;i$&t|Dgx?)16w-Hjv&4 z(t9!V)E2wO7vs;kM0;=vZ@QC58E#20%jt@86#09+F<#o2Gof<8G=Cx$B$KKXZ8uXv z2GL!>Urlk=^a6etx9Y6U-t#7%t@Lx`gW2|9*kx$}2b6EpRk9wg7K!Em z%Oigpnf`R;8*~+8}(c=u!@MQ6i1C(M-2Zf&3jfKao^SGo}IEa#N{nZbg zHRA?)0D_M}Fg7(f22oP0tuTz3Bq((9-&7NDh!dYUkLx5&otwJ1-Y+miiHX3orrtma zsKK$A8XSu;U=8+H3EF~O!J#@5_?nCtO9U-BJsf2p5|{F1RY{9vfMf9@5CwZ+DBw|} zsL?$E8Ai}qk6lCQK|#!*Aa-i>1DvHiHDsgwZ_}8%CQ28(Um!kwf2F*oeE*5TPO#X% z;ZPHXd)9LxP+pKL0CGhzT-6Y#3s1i(w>t#tKG1>AkQ)gmx~ojCa8YS1@bWPIpE(JH zi&_Hk0u&-mjeZO?4&>$k5SD+s&k?E&YEuQZsbd=H74yrHJR`@^xXT*f<(I!ZS`48i zT&mpDFRr}&K2Xtuamrb5O@QiwU?UK0f&u3jX5OctqiXxA(=j6AV^t|L+ZvTcA9B0! zo2h^TtM$LC{ihkqpYB40T7h&MknVt?>)~?7j$gW}>fJ4=+HL7w8L2sr$D1Xa2yCu#DE!j78j z1PGo1!E+d}R${l4@=1V2OhDH(u1!oIRVLw0-FRD7tL($K4a-{?@L5l|L03WW1_<89 zfF<5g#fpdW6y4e&`NYZe_Pn@w`SpDq2kyR+O@YT^Y8dcY6PTa}Aov&rW7B{`uDnc( z-gg;$Gi1t>?P8_Y3z4gORgVWEdT>T9zq*p|W58#m+YL#Ft%_tR;Fv?~gkmDW}~K&NoaLBi0zSqgOybtQEhHDr_;vPiv04Z)*9(Lizj zWAe(MQUjs%pe$xk7CRtI+E);w=$K$(@QnbQBw2*NMw?KU)n=$&Y}f?xp}GAKtC(>z|jF^3U4Es_dPxpCJj6_-;=Tr?%;FRH6i^fLEz|1V?S zpDucZYJqe;kZy#b8_Jy=d!kATeW62~V$Bg+cJwV+g^ErSF_GTSl!fX3nukc!K-58J zn4n*Q=@@mA>bmVIQCW=J9>(JRWmr>OLON*UbB# zdC!?S=e$k`5e}n;_YBP0d7T~ma@VtMiVm-mD{^_|c`L*Hj|la`8x(=_|KxwBng^4` zF^VGU(L_ClR@aUazu)2J{e@F-z@jS{n)W_EVR}+e_3-L4pIEOTo&Qm1y8AHMv7!W` zo=DVFX!Wx;k!5Q}e0p3r_;$S9wAx(Jp!93R*bPeZ2SJcgT!Co!Y2T?M;Q!(D$_Pf3<8wO+>hb z2)EI~$$zXo{>j1eVqxTsiia24Z(CPMUA1z~jJZ6hsTTQd(*L9AF*)9%PNLpT)O%?4 zUJ-2FVD!!EvSRc=aCwzaP<(-rZBb`tW#D6lu1T46`YB<<1~%+q_t7N)=N~JnnyR{+ zdYF2ax|+sDjRp-z%?3>^Es2&E+moHk9$=3i;lctGlZ73Cfr!PC=ip{t4r$iqkUO=x zf#k;AnhFvB<-{N=&wn6!ecBbl{Q%d$c-%QhdddLB^zs8hE>h!YVJ?qwMKt%)(E9TQ zV=AW~qpq7xEABNP)nwgQTlOh49YjskxV{`vOc4ZdCer5^Vy-dan$X<4(?Wl>*kRY7 zcxHQX$1U$N&1*I~!QtxJHxeEjk1O8-D5j15jG5t=6+3J+~D>neeWK#y<0TjlGz}zUpji$NF z18qv4-OPU1n{)r4HI1w8widg8(GB|gDX@CU^_Q%hfMT*!0`N~1%Spi8M8Zv>xi{~p z$~C{{J3Kd#-xEDz>A5#OmqPCp<{mNp!!zO+hcrMj-Rc1pqI6CM=1K`Si{={MPvD=L z`fA<*tp~pZi+fTv<1`y5&$vAD>s#IVBk!aFiphQuz)+OWDa70&!Y!e>Hq*nDer{^l zF#9Ro)jQ1N+?ze9_oK7LB@^e~cHjKXfMOc629O>LRdQ-DxPgG1XmIelK<<&g?UTJf zdqoe|AE|c_W~hMuY)m z#zKxdC`mqEqir#0yh@P0Rp=KB9PL93wy2MKes!>J z?$k>NP)rt#0PaO0+%U|IAlxXLYY_5__oJ`tgA9h9?H8=O)4w>(Zffse`q?9vq_4Lr zT?;5CPX_=$p%`u~<|YtsBF$y#ep0(LlqDCEX>0uDV$QST&)FwmBpS!u%Hziyth2F_ zaHD_xz$xE%WNr2o)4*bYv{)#en}PMDL@$fhd-%`PB(dOD!nxU9S;cPE0Y2v8r#4^u zyLz~(Z+iYacR(?jbplACd~PA;77=a<&ArM^@hqgi%)JJ*P&+g zMB&lN4}fCw7Xq*z^>GIglqemb)sv#pz3%xz8t6WX{?PD5jSN09mt;9#0>G4GGwo2G21y zOgW_8oSVZ1*+TEFaS7|%UBHw!Jk_pZ+L+EM&$LW3V*=+tT2s!GM?= z`}b_&G+$VI^^!vO=|5j==9c%%%MUeZE_4DEQ+H?pEs!J633FWt*Nx`Z<<+}AD1LN0 zZ&ANwhUr9=-%`{6+^?RpYRv~}j=9z~D=3ov2C~Hj@7VD?@Tcq{n__ae0q`yI=J{d0 zjYKbq)>E(7l@tuV)EL4QRfigzb)VjQ-+Hz3yxfztC)v(PdjQ4ckN}V>3x)8)FgSvM zqiAqMk(rO-tfi&DeVnu9c6i_taa_k6UEbQacRGL4`Dix}P)zne0KP^syjaXlAlyWn zJN$LUyK~>p1Yg{eH(cl9Gq7ayt&_`MJXPYmx2c$BS>OUI`4ORLUJBVIDTvAR9zbW5 z&db1ZQX-c{%S~E!bK;JerMi~#3blvV*W_nYXALsjw(K}#JQ^AbP63L^t{y;Fl+P=~ z+#2Y50f1^ zfE!Q;uM^946S*E*uJ1C>K+#2Mm_6~;gUHA}3%#1*from{y>Z-EJ$Jh10*cA>9Y8NM zz#GC`7N69B_#`6;pYuySw^g;)+*ALuB^DW(jtyg+rVbN@U=xeLAxD&6%jtonWw z8bv;jZzogq;Nt|O2`Hw`-1!0YL81w$!pZsp63oZ?7eI_ zzQ&;*P)vhW0klHV{1|+&v4opIbN8u~OkVIHYFqM#L_7cHk}=MU?rhz=;=su0nP*P; zgILaq`%vAiQ(<{wO86~; zmGpa=fki)dNa-nHumc3J5tZ{Rv04pLYoOI6LHZB(1(&oo22RTTD^L`k21~I`h#wVI z!z$Rk4ktCH{?XV83$^guFt>wnJ85q6*v+f!TP4+tx^CS{%l>6n_tKoKQof$Hy?&kf zmxZ!Q3cWOuljz2uKPoAb@23^TPvpZAeh;5Kl`(a{08j-D@Q3hWummJ02}n-*dDej@ z{^Xe*5l8B;OO2f>4o@}qKW9bs~Wn>sb>$ zTUzfPu)Qj*V{0CoS8ZN@-dCa+{!qjJl->wSm75oz){5~|y$rU@0G>yV0w>IMAzU|_ zd*TVlS*QN6;<6Ea!g+r!$UWX2^~c#8MeP0SOIF>0_W{LZSq$J6UnJyuScs+?5quFkc^lw-gyZOmnw;s!u>lqN{W z+zi5%(%cnSX2xWxEqol&nY1$Ys841Skry?QqG5;`v>1EnS3Sya%7=gK|Tf- z5^xa>jxkO^BDal`Esc@~N9F$g@?G~w&K<*VU!onZJ>6ikgub3r1X(09vcg#+AO(|Y z!WsbAph`gvmTMq#P4w^4Wpv9mWcE8PU(07og$q1=4pK=2I-yloz8-c)tcogp-7-vu z3E+6tCg{N2PQvY`xs@{q+dt2GIVJeBYZLX*yJth9zu#*A6G5>jGggcj%>fjX?*~A3 zEYv3$z~CVQW(i56iRruS^6SYZpUYJzWxN{IH+J--o$-g9y$rz|&d}U~AuCw^Z)9+A zS_C};c_Fz6Gxcl)a0*fustR!e63P;r7Ogk$;!$bf8|k4JiQA4pa&tG^ZMX8Doc()J z3$}`XUp~$gKLjvXP6GG=iG+HXt53LwG*?IB<+grya&~KG_URqtYi~ZU*ke&L^-Rh9 zuE52U7g_>}Y3>UEcOes@8Rm)!*P7-Y`dqwT-s#hY#2%g0wG01AzSn)J@!pe+gcgaDTf88yD!u~$(pisq;qqj zmHb&FKry-N1DJ|Dgr1n|O}KtE_s_2BCQ-JVuEUbL>oYI(c%SyVFxyfrelOR02pxNN zg)Z-ILRm?N8_9;;g_&#z0g`7Si7*7qg%P<3Iy}K2mTqEGUdBUwscq)rcAu5a3SDY7 zg^BZ35@tPlybVxHZq@)6qiA6a=Ef3k0(~0cZyGCn-w%i8@fZ6hu||F<@M$wI3tSl= z+HIn!=NgEsfDF!n04d|zk1!2`(+N0(1}}^|@%D_SB;Ic*dhGo-CnMbV|1P<$^qX7r z-XtY8HX!jyq{AZ%kyJ?T?>wQg5ZdB@2g$yQ!XH_9mGuCnm}cPta68Hu7UJV5BF9le zA4ksVNS)nA`m60$y=ti!?^~+7Ij(ZMrInX-!MTzLMTY^!G)EMG>rth!26Gz-w~6Nd z+$J?K-CS({p>)BGPp@VRD-Os_C{KGY_4T&BQg{D@6@HH*I|Z_|kUjGIGxeMYP#1Lw zJF!|fQR|`A8t;A1`FZ%TL1h{$?s=WU?sl1LcIB#vro43FGf4miD5e>d0NjcOghQCi z5|QeUh+G}1zWt93oI^GGp2$yVij?k{nBtzPeRkJ!ld?+x?QGeT2<-d!H1HpaB61c? zgZ2Sjh*U*tBK%5-sPSZKmZ3WLI;u*JB_y3q^Eu#QI;rW})T3H8ii%$zpZ`f5S=In3 zra2b?yomHf`j~4-xW+Vhq|I;Byk_Z^vM;tRgh~8p-ru&3>|XOliu)^WIjBzn6qCU@ zfcKG^NQ}AGglkK4BY7Lv-E7_zztdK1*jnnH@nUa}#ls1D7b;h6xiYu;A)uIM`2%nX zauhjXt_$J1(cE;?{ZkB#BRSJ{7nm+wcvqg<``ePFsr|iYjz1pQ(BcLtrmkE7enFlh zZ_M>0+>JE%s#)4|i|5KwwTGrC|8gXBp5>->S$D&`>zdw;satS<44{}Sx&drP5>W`| zh7oQA%?-;ilPYg^nN$A7AeWO|tv+>1{OP#WI~$&kKK`>{mc3P*5GspAA`_9F$U}s$ zUA#u&6<@cL0L5hM58!PSBZ|eE*v|vszY=Ad;?S9vf>Z8K3t7vyxo{n;B0qUPc$K5m z-w~d6r1)I!u@8V^90`;r!jF*YgqcAz>sE?as0D2@zo`GME4py6C}Z!pR3680;ilNV zu47ui*HL6wY>Egb)Ca9|bspkVVDd@?NS}quMU@y_L%}dfF(Q*gs4(~F5JvK5& zFKTu8JsYD9HufghT>-^3R}O&5s72IzS?X?9birgEU zw$9X7(Njqo0gY9}DiRfCZ9BD;5kqW6Z8dF?_9QtVbYYQ?NdUspQd}2PP*t>5($jXH ZB-!NU9k?MN#M=vu#cd~*Ypywg{{zli98UlM literal 0 HcmV?d00001 diff --git a/scripts/tests/startup-dev-e2e.sh b/scripts/tests/startup-dev-e2e.sh index 3e53c0ec4..67fbd595f 100755 --- a/scripts/tests/startup-dev-e2e.sh +++ b/scripts/tests/startup-dev-e2e.sh @@ -26,8 +26,32 @@ if [ -z "$NODE_IMAGE" ]; then exit 1 fi +# Which genesis ledger version to boot against. The CI workflow fans this out +# as a matrix (one job per leg, in parallel); locally, override to test the +# other leg, e.g. `STARTUP_GENESIS=ledger8 just startup-dev-e2e `. +STARTUP_GENESIS="${STARTUP_GENESIS:-ledger9}" + +# Extra `docker run` args selecting the genesis. ledger9 is the stock dev +# genesis (no override); ledger8 points at the pinned pre-ledger-9 (v13) +# undeployed fixtures, exercising the genesis_version dispatch path. +GENESIS_ARGS=() +case "$STARTUP_GENESIS" in + ledger9) ;; + ledger8) + GENESIS_ARGS+=( + -e CHAINSPEC_GENESIS_STATE=res/genesis/genesis_state_undeployed_ledger8.mn + -e CHAINSPEC_GENESIS_BLOCK=res/genesis/genesis_block_undeployed_ledger8.mn + ) + ;; + *) + echo "❌ Unknown STARTUP_GENESIS='${STARTUP_GENESIS}' (expected ledger9 or ledger8)" + exit 1 + ;; +esac + echo "🧪 Running Startup E2E test with:" echo " NODE_IMAGE=${NODE_IMAGE}" +echo " STARTUP_GENESIS=${STARTUP_GENESIS}" # Setup working directory WORKDIR=$(mktemp -d) @@ -44,14 +68,15 @@ docker run -d --rm \ -p 9944:9944 \ -e CFG_PRESET=dev \ -e SIDECHAIN_BLOCK_BENEFICIARY="04bcf7ad3be7a5c790460be82a713af570f22e0f801f6659ab8e84a52be6969e" \ + "${GENESIS_ARGS[@]}" \ "${NODE_IMAGE}" # Smoke test: assert finality is advancing, matching the old `finalized #1` # log-grep semantics this script used to have. if wait_for_finalized_block http://localhost:9944 1 60; then - echo "✅ Node started successfully with CFG_PRESET=dev" + echo "✅ Node started successfully with CFG_PRESET=dev (${STARTUP_GENESIS})" else - echo "❌ Node failed to start with CFG_PRESET=dev" + echo "❌ Node failed to start with CFG_PRESET=dev (${STARTUP_GENESIS})" TEST_FAILED=true fi