From e0f2876d544f703b94a6d5bdc68b41ff1f7489da Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 12 Mar 2026 15:09:52 +1000 Subject: [PATCH 01/20] feat: separate archive CFs into FREEZER and ARCHIVE variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 1 of archive CF isolation: - Rename ACCOUNT_INFO_STATE_ARCHIVE → ACCOUNT_INFO_STATE_FREEZER (holds frozen historical state) - Rename ACCOUNT_STORAGE_ARCHIVE → ACCOUNT_STORAGE_FREEZER - Add new ACCOUNT_INFO_STATE_ARCHIVE (for active migration writes) - Add new ACCOUNT_STORAGE_ARCHIVE (for active migration writes) FREEZER CFs: Used by BonsaiArchiver and BonsaiArchiveFlatDbStrategy fallback (cold data) ARCHIVE CFs: Used by BonsaiFlatDbToArchiveMigrator (hot migration data) Signed-off-by: Jason Frame --- .../storage/keyvalue/KeyValueSegmentIdentifier.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java index c072528fd5e..bb4758256bc 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java @@ -39,6 +39,18 @@ public enum KeyValueSegmentIdentifier implements SegmentIdentifier { ACCOUNT_STORAGE_STORAGE(new byte[] {8}, EnumSet.of(BONSAI, X_BONSAI_ARCHIVE), false, true, false), TRIE_BRANCH_STORAGE(new byte[] {9}, EnumSet.of(BONSAI, X_BONSAI_ARCHIVE), false, true, false), TRIE_LOG_STORAGE(new byte[] {10}, EnumSet.of(BONSAI, X_BONSAI_ARCHIVE), true, false, true), + ACCOUNT_INFO_STATE_FREEZER( + "ACCOUNT_INFO_STATE_FREEZER".getBytes(StandardCharsets.UTF_8), + EnumSet.of(X_BONSAI_ARCHIVE), + true, + false, + true), + ACCOUNT_STORAGE_FREEZER( + "ACCOUNT_STORAGE_FREEZER".getBytes(StandardCharsets.UTF_8), + EnumSet.of(X_BONSAI_ARCHIVE), + true, + false, + true), ACCOUNT_INFO_STATE_ARCHIVE( "ACCOUNT_INFO_STATE_ARCHIVE".getBytes(StandardCharsets.UTF_8), EnumSet.of(X_BONSAI_ARCHIVE), From b5259748185a9b56ca48380a6324d6d58564b038 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 12 Mar 2026 15:10:55 +1000 Subject: [PATCH 02/20] refactor: update archive methods to use FREEZER CFs PathBasedWorldStateKeyValueStorage archive methods now use: - ACCOUNT_INFO_STATE_FREEZER (was ACCOUNT_INFO_STATE_ARCHIVE) - ACCOUNT_STORAGE_FREEZER (was ACCOUNT_STORAGE_ARCHIVE) These CFs hold frozen historical state archived by BonsaiArchiver. Signed-off-by: Jason Frame --- .../storage/PathBasedWorldStateKeyValueStorage.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/storage/PathBasedWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/storage/PathBasedWorldStateKeyValueStorage.java index 57274456da8..fc03f4dbf49 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/storage/PathBasedWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/storage/PathBasedWorldStateKeyValueStorage.java @@ -15,8 +15,8 @@ package org.hyperledger.besu.ethereum.trie.pathbased.common.storage; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; @@ -255,7 +255,7 @@ public int archivePreviousAccountState( (nearestKey) -> { moveDBEntry( ACCOUNT_INFO_STATE, - ACCOUNT_INFO_STATE_ARCHIVE, + ACCOUNT_INFO_STATE_FREEZER, nearestKey.key().toArrayUnsafe(), nearestKey.value().get()); archivedStateCount.getAndIncrement(); @@ -339,7 +339,7 @@ public int archivePreviousStorageState( } moveDBEntry( ACCOUNT_STORAGE_STORAGE, - ACCOUNT_STORAGE_ARCHIVE, + ACCOUNT_STORAGE_FREEZER, nearestKey.key().toArrayUnsafe(), nearestKey.value().get()); archivedStorageCount.getAndIncrement(); @@ -395,7 +395,7 @@ private void moveDBEntry( public Optional getLatestArchivedBlock() { return composedWorldStateStorage - .get(ACCOUNT_INFO_STATE_ARCHIVE, ARCHIVED_BLOCKS) + .get(ACCOUNT_INFO_STATE_FREEZER, ARCHIVED_BLOCKS) .map(Bytes::wrap) .map(Bytes::toLong); } @@ -403,7 +403,7 @@ public Optional getLatestArchivedBlock() { public void setLatestArchivedBlock(final Long blockNumber) { SegmentedKeyValueStorageTransaction tx = composedWorldStateStorage.startTransaction(); tx.put( - ACCOUNT_INFO_STATE_ARCHIVE, + ACCOUNT_INFO_STATE_FREEZER, ARCHIVED_BLOCKS, Bytes.ofUnsignedLong(blockNumber).toArrayUnsafe()); tx.commit(); From 9600549b7218f4485b9b7856fa764c74b9466cd6 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 12 Mar 2026 15:11:38 +1000 Subject: [PATCH 03/20] refactor: update BonsaiArchiveFlatDbStrategy fallback reads to use FREEZER CFs Fallback state lookups now read from FREEZER column families: - ACCOUNT_INFO_STATE_FREEZER (was ACCOUNT_INFO_STATE_ARCHIVE) - ACCOUNT_STORAGE_FREEZER (was ACCOUNT_STORAGE_ARCHIVE) These CFs hold frozen historical state for archive mode queries. Signed-off-by: Jason Frame --- .../bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java index 8e6f33669b1..2d18273ef76 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java @@ -15,8 +15,8 @@ package org.hyperledger.besu.ethereum.trie.pathbased.bonsai.storage.flat; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; import static org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage.WORLD_BLOCK_NUMBER_KEY; @@ -143,7 +143,7 @@ public Optional getFlatAccount( if (nearestAccount.isEmpty()) { accountFound = storage - .getNearestBefore(ACCOUNT_INFO_STATE_ARCHIVE, keyNearest) + .getNearestBefore(ACCOUNT_INFO_STATE_FREEZER, keyNearest) .filter( found -> accountHash.getBytes().commonPrefixLength(found.key()) @@ -341,7 +341,7 @@ public Optional getFlatStorageValueByStorageSlotKey( // Check the archived storage as old state is moved out of the primary DB segment storageFound = storage - .getNearestBefore(ACCOUNT_STORAGE_ARCHIVE, keyNearest) + .getNearestBefore(ACCOUNT_STORAGE_FREEZER, keyNearest) // don't return accounts that do not have a matching account hash .filter( found -> From a3a6befaf483db69d14fe72ccd7e1a17a41e132c Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 12 Mar 2026 15:13:02 +1000 Subject: [PATCH 04/20] test: update ArchiverTests to use FREEZER CFs Test assertions now check for state in: - ACCOUNT_INFO_STATE_FREEZER (was ACCOUNT_INFO_STATE_ARCHIVE) - ACCOUNT_STORAGE_FREEZER (was ACCOUNT_STORAGE_ARCHIVE) Signed-off-by: Jason Frame --- .../pathbased/common/trielog/ArchiverTests.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/common/trielog/ArchiverTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/common/trielog/ArchiverTests.java index df49f147080..c958ac3fb79 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/common/trielog/ArchiverTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/common/trielog/ArchiverTests.java @@ -863,7 +863,7 @@ public Optional load(final Hash blockHash) { // records the latest archived block assertThat( testWorldStateStorage.getComposedWorldStateStorage().stream( - KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE) + KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER) .count()) .isEqualTo(3); @@ -872,7 +872,7 @@ public Optional load(final Hash blockHash) { testWorldStateStorage .getComposedWorldStateStorage() .containsKey( - KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE, + KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), Bytes.fromHexString("0x0000000000000096").toArrayUnsafe()))) @@ -881,7 +881,7 @@ public Optional load(final Hash blockHash) { testWorldStateStorage .getComposedWorldStateStorage() .containsKey( - KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE, + KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), Bytes.fromHexString("0x0000000000000097").toArrayUnsafe()))) @@ -1090,7 +1090,7 @@ public Optional load(final Hash blockHash) { // All 3 previous storage states should be in the storage archiver assertThat( testWorldStateStorage.getComposedWorldStateStorage().stream( - KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE) + KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER) .count()) .isEqualTo(3); @@ -1099,7 +1099,7 @@ public Optional load(final Hash blockHash) { testWorldStateStorage .getComposedWorldStateStorage() .containsKey( - KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE, + KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), slotKey.getSlotHash().getBytes().toArrayUnsafe(), @@ -1109,7 +1109,7 @@ public Optional load(final Hash blockHash) { testWorldStateStorage .getComposedWorldStateStorage() .containsKey( - KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE, + KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), slotKey.getSlotHash().getBytes().toArrayUnsafe(), @@ -1119,7 +1119,7 @@ public Optional load(final Hash blockHash) { testWorldStateStorage .getComposedWorldStateStorage() .containsKey( - KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE, + KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), slotKey.getSlotHash().getBytes().toArrayUnsafe(), From 9c1c3347757e13ced20dd8b06d0e550aba473b28 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 12 Mar 2026 15:14:05 +1000 Subject: [PATCH 05/20] feat: update BonsaiArchiveFlatDbStrategy to write migration data to ARCHIVE CFs Archive migration writes now go to: - ACCOUNT_INFO_STATE_ARCHIVE (account state) - ACCOUNT_STORAGE_ARCHIVE (storage state) Fallback reads still use FREEZER CFs (frozen historical state). Writes use new ARCHIVE CFs for active migration data. Signed-off-by: Jason Frame --- .../storage/flat/BonsaiArchiveFlatDbStrategy.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java index 2d18273ef76..ce8e9e6950c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java @@ -15,7 +15,9 @@ package org.hyperledger.besu.ethereum.trie.pathbased.bonsai.storage.flat; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; @@ -287,7 +289,7 @@ public void putFlatAccount( calculateArchiveKeyWithMinSuffix( getStateArchiveContextForWrite(storage).get(), accountHash.getBytes().toArrayUnsafe()); - transaction.put(ACCOUNT_INFO_STATE, keySuffixed, accountValue.toArrayUnsafe()); + transaction.put(ACCOUNT_INFO_STATE_ARCHIVE, keySuffixed, accountValue.toArrayUnsafe()); } @Override @@ -301,7 +303,7 @@ public void removeFlatAccount( calculateArchiveKeyWithMinSuffix( getStateArchiveContextForWrite(storage).get(), accountHash.getBytes().toArrayUnsafe()); - transaction.put(ACCOUNT_INFO_STATE, keySuffixed, DELETED_ACCOUNT_VALUE); + transaction.put(ACCOUNT_INFO_STATE_ARCHIVE, keySuffixed, DELETED_ACCOUNT_VALUE); } private byte[] trimSuffix(final byte[] suffixedAddress) { @@ -389,7 +391,7 @@ public void putFlatAccountStorageValueByStorageSlotHash( byte[] keyNearest = calculateArchiveKeyWithMinSuffix(getStateArchiveContextForWrite(storage).get(), naturalKey); - transaction.put(ACCOUNT_STORAGE_STORAGE, keyNearest, storageValue.toArrayUnsafe()); + transaction.put(ACCOUNT_STORAGE_ARCHIVE, keyNearest, storageValue.toArrayUnsafe()); } /* @@ -408,7 +410,7 @@ public void removeFlatAccountStorageValueByStorageSlotHash( byte[] keySuffixed = calculateArchiveKeyWithMinSuffix(getStateArchiveContextForWrite(storage).get(), naturalKey); - transaction.put(ACCOUNT_STORAGE_STORAGE, keySuffixed, DELETED_STORAGE_VALUE); + transaction.put(ACCOUNT_STORAGE_ARCHIVE, keySuffixed, DELETED_STORAGE_VALUE); } public static byte[] calculateNaturalSlotKey(final Hash accountHash, final Hash slotHash) { From 3793d4bdc40fb0c1bce66f45ea8785f0dbda5c96 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 12 Mar 2026 15:18:19 +1000 Subject: [PATCH 06/20] test: update archive migration tests to use ARCHIVE CFs Test assertions updated to read from: - ACCOUNT_INFO_STATE_ARCHIVE (instead of ACCOUNT_INFO_STATE) - ACCOUNT_STORAGE_ARCHIVE (instead of ACCOUNT_STORAGE_STORAGE) Tests verify archive migration writes to the new ARCHIVE column families. Signed-off-by: Jason Frame --- .../flat/BonsaiArchiveFlatDbStrategyTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java index dabf34d14d7..0feacb2753d 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java @@ -15,7 +15,7 @@ package org.hyperledger.besu.ethereum.trie.pathbased.bonsai.storage.flat; import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; import static org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage.WORLD_BLOCK_NUMBER_KEY; @@ -57,7 +57,7 @@ public void genesisBlockUsesZeroSuffixWhenWorldBlockNumberKeyNotSet() { final byte[] expectedKey = Bytes.concatenate(accountHash.getBytes(), Bytes.ofUnsignedLong(0)).toArrayUnsafe(); - final Optional storedValue = storage.get(ACCOUNT_INFO_STATE, expectedKey); + final Optional storedValue = storage.get(ACCOUNT_INFO_STATE_ARCHIVE, expectedKey); assertThat(storedValue).isPresent(); assertThat(Bytes.wrap(storedValue.get())).isEqualTo(accountValue); @@ -77,14 +77,14 @@ public void block1UsesOneSuffixWhenWorldBlockNumberKeyIsZero() { final byte[] expectedKey = Bytes.concatenate(accountHash.getBytes(), Bytes.ofUnsignedLong(1)).toArrayUnsafe(); - final Optional storedValue = storage.get(ACCOUNT_INFO_STATE, expectedKey); + final Optional storedValue = storage.get(ACCOUNT_INFO_STATE_ARCHIVE, expectedKey); assertThat(storedValue).isPresent(); assertThat(Bytes.wrap(storedValue.get())).isEqualTo(accountValue); final byte[] genesisKey = Bytes.concatenate(accountHash.getBytes(), Bytes.ofUnsignedLong(0)).toArrayUnsafe(); - assertThat(storage.get(ACCOUNT_INFO_STATE, genesisKey)).isEmpty(); + assertThat(storage.get(ACCOUNT_INFO_STATE_ARCHIVE, genesisKey)).isEmpty(); } @Test @@ -101,7 +101,7 @@ public void block2UsesTwoSuffixWhenWorldBlockNumberKeyIsOne() { final byte[] expectedKey = Bytes.concatenate(accountHash.getBytes(), Bytes.ofUnsignedLong(2)).toArrayUnsafe(); - final Optional storedValue = storage.get(ACCOUNT_INFO_STATE, expectedKey); + final Optional storedValue = storage.get(ACCOUNT_INFO_STATE_ARCHIVE, expectedKey); assertThat(storedValue).isPresent(); assertThat(Bytes.wrap(storedValue.get())).isEqualTo(accountValue); @@ -129,8 +129,8 @@ public void genesisAndBlock1AccountsDoNotOverwrite() { final byte[] block1Key = Bytes.concatenate(accountHash.getBytes(), Bytes.ofUnsignedLong(1)).toArrayUnsafe(); - final Optional genesisValue = storage.get(ACCOUNT_INFO_STATE, genesisKey); - final Optional block1Value = storage.get(ACCOUNT_INFO_STATE, block1Key); + final Optional genesisValue = storage.get(ACCOUNT_INFO_STATE_ARCHIVE, genesisKey); + final Optional block1Value = storage.get(ACCOUNT_INFO_STATE_ARCHIVE, block1Key); assertThat(genesisValue).isPresent(); assertThat(Bytes.wrap(genesisValue.get())).isEqualTo(genesisAccountValue); @@ -176,7 +176,7 @@ public void sequentialBlocksUseIncrementingSuffixes() { for (long blockNum = 0; blockNum <= 3; blockNum++) { final byte[] key = Bytes.concatenate(accountHash.getBytes(), Bytes.ofUnsignedLong(blockNum)).toArrayUnsafe(); - final Optional value = storage.get(ACCOUNT_INFO_STATE, key); + final Optional value = storage.get(ACCOUNT_INFO_STATE_ARCHIVE, key); assertThat(value).as("Block " + blockNum + " should have stored value").isPresent(); assertThat(Bytes.wrap(value.get())).isEqualTo(expectedValues[(int) blockNum]); } From 72d8202dbe26f8a44b07e5fbeec5d8268025f396 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 13:02:43 +1000 Subject: [PATCH 07/20] move the freezer archive column families to after the archive column families for the definitions Signed-off-by: Jason Frame --- .../keyvalue/KeyValueSegmentIdentifier.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java index bb4758256bc..7968f3cc1d6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java @@ -39,26 +39,26 @@ public enum KeyValueSegmentIdentifier implements SegmentIdentifier { ACCOUNT_STORAGE_STORAGE(new byte[] {8}, EnumSet.of(BONSAI, X_BONSAI_ARCHIVE), false, true, false), TRIE_BRANCH_STORAGE(new byte[] {9}, EnumSet.of(BONSAI, X_BONSAI_ARCHIVE), false, true, false), TRIE_LOG_STORAGE(new byte[] {10}, EnumSet.of(BONSAI, X_BONSAI_ARCHIVE), true, false, true), - ACCOUNT_INFO_STATE_FREEZER( - "ACCOUNT_INFO_STATE_FREEZER".getBytes(StandardCharsets.UTF_8), + ACCOUNT_INFO_STATE_ARCHIVE( + "ACCOUNT_INFO_STATE_ARCHIVE".getBytes(StandardCharsets.UTF_8), EnumSet.of(X_BONSAI_ARCHIVE), true, false, true), - ACCOUNT_STORAGE_FREEZER( - "ACCOUNT_STORAGE_FREEZER".getBytes(StandardCharsets.UTF_8), + ACCOUNT_STORAGE_ARCHIVE( + "ACCOUNT_STORAGE_ARCHIVE".getBytes(StandardCharsets.UTF_8), EnumSet.of(X_BONSAI_ARCHIVE), true, false, true), - ACCOUNT_INFO_STATE_ARCHIVE( - "ACCOUNT_INFO_STATE_ARCHIVE".getBytes(StandardCharsets.UTF_8), + ACCOUNT_INFO_STATE_FREEZER( + "ACCOUNT_INFO_STATE_FREEZER".getBytes(StandardCharsets.UTF_8), EnumSet.of(X_BONSAI_ARCHIVE), true, false, true), - ACCOUNT_STORAGE_ARCHIVE( - "ACCOUNT_STORAGE_ARCHIVE".getBytes(StandardCharsets.UTF_8), + ACCOUNT_STORAGE_FREEZER( + "ACCOUNT_STORAGE_FREEZER".getBytes(StandardCharsets.UTF_8), EnumSet.of(X_BONSAI_ARCHIVE), true, false, From 8a866971c1b4a68725d2f437aac76f2f21678cd4 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 13:12:38 +1000 Subject: [PATCH 08/20] Update remaining uses of to use archive columns in BonsaiArchiveFlatDbStrategy Signed-off-by: Jason Frame --- .../flat/BonsaiArchiveFlatDbStrategy.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java index ce8e9e6950c..381ba1a62ca 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java @@ -14,12 +14,10 @@ */ package org.hyperledger.besu.ethereum.trie.pathbased.bonsai.storage.flat; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; import static org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage.WORLD_BLOCK_NUMBER_KEY; @@ -135,7 +133,7 @@ public Optional getFlatAccount( // Find the nearest account state for this address and block context Optional nearestAccount = storage - .getNearestBefore(ACCOUNT_INFO_STATE, keyNearest) + .getNearestBefore(ACCOUNT_INFO_STATE_ARCHIVE, keyNearest) .filter( found -> accountHash.getBytes().commonPrefixLength(found.key()) @@ -182,7 +180,7 @@ protected Stream> accountsToPairStream( final Stream> stream = storage .streamFromKey( - ACCOUNT_INFO_STATE, + ACCOUNT_INFO_STATE_ARCHIVE, calculateArchiveKeyNoContextMinSuffix(startKeyHash.toArrayUnsafe()), calculateArchiveKeyNoContextMaxSuffix(endKeyHash.toArrayUnsafe())) .map(e -> Bytes.of(calculateArchiveKeyNoContextMaxSuffix(trimSuffix(e.getKey())))) @@ -192,7 +190,11 @@ protected Stream> accountsToPairStream( new Pair<>( Bytes32.wrap(trimSuffix(e.toArrayUnsafe())), Bytes.of( - storage.getNearestBefore(ACCOUNT_INFO_STATE, e).get().value().get()))); + storage + .getNearestBefore(ACCOUNT_INFO_STATE_ARCHIVE, e) + .get() + .value() + .get()))); return stream; } @@ -202,7 +204,7 @@ protected Stream> accountsToPairStream( final Stream> stream = storage .streamFromKey( - ACCOUNT_INFO_STATE, + ACCOUNT_INFO_STATE_ARCHIVE, calculateArchiveKeyNoContextMinSuffix(startKeyHash.toArrayUnsafe())) .map(e -> Bytes.of(calculateArchiveKeyNoContextMaxSuffix(trimSuffix(e.getKey())))) .distinct() @@ -211,7 +213,11 @@ protected Stream> accountsToPairStream( new Pair( Bytes32.wrap(trimSuffix(e.toArrayUnsafe())), Bytes.of( - storage.getNearestBefore(ACCOUNT_INFO_STATE, e).get().value().get()))); + storage + .getNearestBefore(ACCOUNT_INFO_STATE_ARCHIVE, e) + .get() + .value() + .get()))); return stream; } @@ -223,7 +229,7 @@ protected Stream> storageToPairStream( final Function valueMapper) { return storage .streamFromKey( - ACCOUNT_STORAGE_STORAGE, + ACCOUNT_STORAGE_ARCHIVE, calculateArchiveKeyNoContextMinSuffix( calculateNaturalSlotKey(accountHash, Hash.wrap(Bytes32.wrap(startKeyHash))))) .map(e -> Bytes.of(calculateArchiveKeyNoContextMaxSuffix(trimSuffix(e.getKey())))) @@ -236,7 +242,7 @@ protected Stream> storageToPairStream( valueMapper.apply( Bytes.of( storage - .getNearestBefore(ACCOUNT_STORAGE_STORAGE, key) + .getNearestBefore(ACCOUNT_STORAGE_ARCHIVE, key) .get() .value() .get()) @@ -252,7 +258,7 @@ protected Stream> storageToPairStream( final Function valueMapper) { return storage .streamFromKey( - ACCOUNT_STORAGE_STORAGE, + ACCOUNT_STORAGE_ARCHIVE, calculateArchiveKeyNoContextMinSuffix( calculateNaturalSlotKey(accountHash, Hash.wrap(Bytes32.wrap(startKeyHash)))), calculateArchiveKeyNoContextMaxSuffix( @@ -267,7 +273,7 @@ protected Stream> storageToPairStream( valueMapper.apply( Bytes.of( storage - .getNearestBefore(ACCOUNT_STORAGE_STORAGE, key) + .getNearestBefore(ACCOUNT_STORAGE_ARCHIVE, key) .get() .value() .get()) @@ -334,7 +340,7 @@ public Optional getFlatStorageValueByStorageSlotKey( // Find the nearest storage for this address, slot key hash, and block context Optional nearestStorage = storage - .getNearestBefore(ACCOUNT_STORAGE_STORAGE, keyNearest) + .getNearestBefore(ACCOUNT_STORAGE_ARCHIVE, keyNearest) .filter( found -> Bytes.of(naturalKey).commonPrefixLength(found.key()) >= naturalKey.length); From 5847f5bba7fa603829f5a5804fe8d23fc0ede3e6 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 13:20:25 +1000 Subject: [PATCH 09/20] Change main bonsai flatDB segments to archive segments for archiving Signed-off-by: Jason Frame --- .../PathBasedWorldStateKeyValueStorage.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/storage/PathBasedWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/storage/PathBasedWorldStateKeyValueStorage.java index fc03f4dbf49..363a057fdca 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/storage/PathBasedWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/common/storage/PathBasedWorldStateKeyValueStorage.java @@ -15,7 +15,9 @@ package org.hyperledger.besu.ethereum.trie.pathbased.common.storage; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; @@ -216,9 +218,9 @@ public boolean pruneTrieLog(final Hash blockHash) { } /** - * Move old account state from the primary DB segment to the archive segment that will only be - * used for historic state queries. This prevents performance degradation over time for writes to - * the primary DB segments. + * Move old account state from the primary DB archive segment to the archive freezer segment that + * will only be used for historic state queries. This prevents performance degradation over time + * for writes to the primary archive DB segments. * * @param previousBlockHeader the block header for the previous block, used to get the "nearest * before" state @@ -243,7 +245,7 @@ public int archivePreviousAccountState( // Move all entries that match this address hash to the archive DB segment while ((nextMatch = composedWorldStateStorage - .getNearestBefore(ACCOUNT_INFO_STATE, previousKey) + .getNearestBefore(ACCOUNT_INFO_STATE_ARCHIVE, previousKey) .filter( found -> found.value().isPresent() @@ -254,7 +256,7 @@ public int archivePreviousAccountState( .forEach( (nearestKey) -> { moveDBEntry( - ACCOUNT_INFO_STATE, + ACCOUNT_INFO_STATE_ARCHIVE, ACCOUNT_INFO_STATE_FREEZER, nearestKey.key().toArrayUnsafe(), nearestKey.value().get()); @@ -287,9 +289,9 @@ public int archivePreviousAccountState( } /** - * Move old storage state from the primary DB segment to the archive segment that will only be - * used for historic state queries. This prevents performance degradation over time for writes to - * the primary DB segments. + * Move old storage state from the primary archive DB segment to the archive freezer segment that + * will only be used for historic state queries. This prevents performance degradation over time + * for writes to the primary DB segments. * * @param previousBlockHeader the block header for the previous block, used to get the "nearest * before" state @@ -315,7 +317,7 @@ public int archivePreviousStorageState( // to the archive DB segment while ((nextMatch = composedWorldStateStorage - .getNearestBefore(ACCOUNT_STORAGE_STORAGE, previousKey) + .getNearestBefore(ACCOUNT_STORAGE_ARCHIVE, previousKey) .filter( found -> found.value().isPresent() @@ -338,7 +340,7 @@ public int archivePreviousStorageState( .log(); } moveDBEntry( - ACCOUNT_STORAGE_STORAGE, + ACCOUNT_STORAGE_ARCHIVE, ACCOUNT_STORAGE_FREEZER, nearestKey.key().toArrayUnsafe(), nearestKey.value().get()); From 4403f503f490479f4499a57bfd98be9862e78f6b Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 13:25:22 +1000 Subject: [PATCH 10/20] Move archive tests to the correct package to match BonsaiArchiver Signed-off-by: Jason Frame --- .../worldview/BonsaiArchiverTests.java} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/{common/trielog/ArchiverTests.java => bonsai/worldview/BonsaiArchiverTests.java} (99%) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/common/trielog/ArchiverTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java similarity index 99% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/common/trielog/ArchiverTests.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java index c958ac3fb79..7f10de2ca5e 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/common/trielog/ArchiverTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.ethereum.trie.pathbased.common.trielog; +package org.hyperledger.besu.ethereum.trie.pathbased.bonsai.worldview; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -41,8 +41,8 @@ import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.cache.CodeCache; import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.storage.BonsaiPreImageProxy; import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.worldview.BonsaiArchiver; -import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.pathbased.common.trielog.TrieLogLayer; +import org.hyperledger.besu.ethereum.trie.pathbased.common.trielog.TrieLogManager; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -66,7 +66,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -public class ArchiverTests { +public class BonsaiArchiverTests { // Number of blocks in the chain. This is different to the number of blocks // we have successfully archived state for @@ -109,7 +109,7 @@ public Optional load(final Hash blockHash) { @SuppressWarnings("BannedMethod") @BeforeEach public void setup() { - Configurator.setLevel(LogManager.getLogger(ArchiverTests.class).getName(), Level.TRACE); + Configurator.setLevel(LogManager.getLogger(BonsaiArchiverTests.class).getName(), Level.TRACE); worldStateStorage = Mockito.mock(BonsaiWorldStateKeyValueStorage.class); blockchain = Mockito.mock(Blockchain.class); trieLogManager = Mockito.mock(TrieLogManager.class); From fa86737e72b1177581baa9c32ccaead9d4c2f3a6 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 13:32:50 +1000 Subject: [PATCH 11/20] Fix BonsaiArchiver and BonsaiWorldStateKeyValueStorage tests for ARCHIVE segment changes - Update BonsaiArchiverTests to put test data into ACCOUNT_INFO_STATE_ARCHIVE and ACCOUNT_STORAGE_ARCHIVE segments instead of primary segments - Update test assertions to check ACCOUNT_INFO_STATE_ARCHIVE and ACCOUNT_STORAGE_ARCHIVE for latest state - Add clearAll() and resetOnResync() overrides to BonsaiArchiveFlatDbStrategy to clear ARCHIVE and FREEZER segments - Update BonsaiWorldStateKeyValueStorageTest to check correct segment based on FlatDbMode (ACCOUNT_INFO_STATE_ARCHIVE for ARCHIVE mode) - Create flatDbModeKeyMapperAndSegment() method source for parameterized tests that need segment identifier Fixes failing tests: - BonsaiArchiverTests.archiveInMemoryDBArchivesAccountStateCorrectly - BonsaiArchiverTests.archiverInMemoryDBArchivesStorageStateCorrectly - BonsaiWorldStateKeyValueStorageTest clear_reloadFlatDbStrategy (ARCHIVE) - BonsaiWorldStateKeyValueStorageTest clear_streamFlatAccountsMultipleStateChanges (ARCHIVE) - BonsaiWorldStateKeyValueStorageTest clear_streamFlatAccounts (ARCHIVE) - BonsaiWorldStateKeyValueStorageTest clear_putGetAccountFlatDbStrategy (ARCHIVE) Co-Authored-By: Claude Haiku 4.5 Signed-off-by: Jason Frame --- .../flat/BonsaiArchiveFlatDbStrategy.java | 22 +++++++++++ .../BonsaiWorldStateKeyValueStorageTest.java | 35 +++++++++++++---- .../bonsai/worldview/BonsaiArchiverTests.java | 38 +++++++++---------- 3 files changed, 68 insertions(+), 27 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java index 381ba1a62ca..eea5733ada9 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java @@ -441,6 +441,28 @@ public static Bytes calculateArchiveKeyWithMaxSuffix( return Bytes.of(calculateArchiveKeyWithSuffix(context, naturalKey, MAX_BLOCK_SUFFIX)); } + @Override + public void clearAll(final SegmentedKeyValueStorage storage) { + // Clear archive segments first + storage.clear(ACCOUNT_INFO_STATE_ARCHIVE); + storage.clear(ACCOUNT_STORAGE_ARCHIVE); + storage.clear(ACCOUNT_INFO_STATE_FREEZER); + storage.clear(ACCOUNT_STORAGE_FREEZER); + // Then call parent to clear other segments + super.clearAll(storage); + } + + @Override + public void resetOnResync(final SegmentedKeyValueStorage storage) { + // Clear archive segments + storage.clear(ACCOUNT_INFO_STATE_ARCHIVE); + storage.clear(ACCOUNT_STORAGE_ARCHIVE); + storage.clear(ACCOUNT_INFO_STATE_FREEZER); + storage.clear(ACCOUNT_STORAGE_FREEZER); + // Then call parent to reset other segments + super.resetOnResync(storage); + } + // TODO JF: move this out of this class so can be used with ArchiveCodeStorageStrategy without // being static public static byte[] calculateArchiveKeyWithSuffix( diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java index 11c740999a3..15312f01487 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java @@ -16,6 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; import static org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage.WORLD_BLOCK_NUMBER_KEY; import static org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; @@ -92,6 +93,20 @@ public static Stream flatDbModeAndKeyMapper() { Arguments.of(FlatDbMode.ARCHIVE, flatDBArchiveKey)); } + public static Stream flatDbModeKeyMapperAndSegment() { + Function flatDBKey = (key) -> key; // No-op + + // For archive we want <32-byte-hex>000000000000000n where n is the current archive block number + Function flatDBArchiveKey = + (key) -> + org.bouncycastle.util.Arrays.concatenate(key, Bytes.ofUnsignedLong(2).toArrayUnsafe()); + + return Stream.of( + Arguments.of(FlatDbMode.FULL, flatDBKey, ACCOUNT_INFO_STATE), + Arguments.of(FlatDbMode.PARTIAL, flatDBKey, ACCOUNT_INFO_STATE), + Arguments.of(FlatDbMode.ARCHIVE, flatDBArchiveKey, ACCOUNT_INFO_STATE_ARCHIVE)); + } + public static Collection flatDbModeAndCodeStorageMode() { return Arrays.asList( new Object[][] { @@ -461,9 +476,11 @@ void clear_reloadFlatDbStrategy(final FlatDbMode flatDbMode) { } @ParameterizedTest - @MethodSource("flatDbModeAndKeyMapper") + @MethodSource("flatDbModeKeyMapperAndSegment") void clear_putGetAccountFlatDbStrategy( - final FlatDbMode flatDbMode, final Function keyMapper) { + final FlatDbMode flatDbMode, + final Function keyMapper, + final KeyValueSegmentIdentifier segment) { final BonsaiWorldStateKeyValueStorage storage = spy(setUp(flatDbMode)); // save world state root hash @@ -495,7 +512,7 @@ void clear_putGetAccountFlatDbStrategy( byte[] lookupKey = keyMapper.apply(account.addressHash().getBytes().toArrayUnsafe()); assertThat( Bytes.wrap( - storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE, lookupKey).get())) + storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) .isEqualTo( Bytes.fromHexString( "0xF84E823D98887B5E41A364EA8BFCA056E81F171BCC55A6FF8345E692C0F86E5B48E01B996CADC001622FB5E363B421A0C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470")); @@ -518,9 +535,11 @@ void clear_putGetAccountFlatDbStrategy( } @ParameterizedTest - @MethodSource({"flatDbModeAndKeyMapper"}) + @MethodSource({"flatDbModeKeyMapperAndSegment"}) void clear_streamFlatAccounts( - final FlatDbMode flatDbMode, final Function keyMapper) { + final FlatDbMode flatDbMode, + final Function keyMapper, + final KeyValueSegmentIdentifier segment) { final BonsaiWorldStateKeyValueStorage storage = spy(setUp(flatDbMode)); // save world state root hash @@ -551,17 +570,17 @@ void clear_streamFlatAccounts( byte[] lookupKey = keyMapper.apply(account1.addressHash().getBytes().toArrayUnsafe()); assertThat( Bytes32.wrap( - storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE, lookupKey).get())) + storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) .isEqualTo(account1Value); lookupKey = keyMapper.apply(account2.addressHash().getBytes().toArrayUnsafe()); assertThat( Bytes32.wrap( - storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE, lookupKey).get())) + storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) .isEqualTo(account2Value); lookupKey = keyMapper.apply(account3.addressHash().getBytes().toArrayUnsafe()); assertThat( Bytes32.wrap( - storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE, lookupKey).get())) + storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) .isEqualTo(account3Value); // Streaming the entire range to ensure we get all 3 accounts back diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java index 7f10de2ca5e..e025a3932f5 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java @@ -705,8 +705,8 @@ public Optional load(final Long blockNumber) { // Generate some trie logs to return for a specific block - // For state to be moved from the primary DB segment to the archive DB segment, we need the - // primary DB segment to have the account in already + // For state to be moved from the archive DB segment to the archive freezer DB segment, we need the + // archive DB segment to have the account in already SegmentedKeyValueStorageTransaction tx = testWorldStateStorage.getComposedWorldStateStorage().startTransaction(); final BonsaiAccount block150Account = @@ -747,7 +747,7 @@ public Optional load(final Long blockNumber) { BytesValueRLPOutput out = new BytesValueRLPOutput(); block150Account.writeTo(out); tx.put( - KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE, + KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), Bytes.fromHexString("0x0000000000000096").toArrayUnsafe()), @@ -755,7 +755,7 @@ public Optional load(final Long blockNumber) { out = new BytesValueRLPOutput(); block151Account.writeTo(out); tx.put( - KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE, + KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), Bytes.fromHexString("0x0000000000000097").toArrayUnsafe()), @@ -763,7 +763,7 @@ public Optional load(final Long blockNumber) { out = new BytesValueRLPOutput(); block152Account.writeTo(out); tx.put( - KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE, + KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), Bytes.fromHexString("0x0000000000000098").toArrayUnsafe()), @@ -852,14 +852,14 @@ public Optional load(final Hash blockHash) { // We should have marked up to block 200 as archived assertThat(testWorldStateStorage.getLatestArchivedBlock().get()).isEqualTo(200); - // Only the latest/current state of the account should be in the primary DB segment + // Only the latest/current state of the account should be in the archive DB segment assertThat( testWorldStateStorage.getComposedWorldStateStorage().stream( - KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE) + KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE) .count()) .isEqualTo(1); - // Both the previous account states should be in the archive segment, plus the special key that + // Both the previous account states should be in the archive freezer segment, plus the special key that // records the latest archived block assertThat( testWorldStateStorage.getComposedWorldStateStorage().stream( @@ -890,7 +890,7 @@ public Optional load(final Hash blockHash) { testWorldStateStorage .getComposedWorldStateStorage() .containsKey( - KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE, + KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), Bytes.fromHexString("0x0000000000000098").toArrayUnsafe()))) @@ -945,36 +945,36 @@ public Optional load(final Long blockNumber) { // Generate some trie logs to return for a specific block - // For storage to be moved from the primary DB segment to the archive DB segment, we need the - // primary DB segment to have the storage in already + // For storage to be moved from the archive DB segment to the archive freezer DB segment, we need the + // archive DB segment to have the storage in already SegmentedKeyValueStorageTransaction tx = testWorldStateStorage.getComposedWorldStateStorage().startTransaction(); StorageSlotKey slotKey = new StorageSlotKey(UInt256.fromHexString("0x1")); // The key for a bonsai-archive flat DB storage entry is suffixed with the block number where // that state change took place, hence the "0x0000000000000096" suffix to the address hash below tx.put( - KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE, + KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), slotKey.getSlotHash().getBytes().toArrayUnsafe(), Bytes.fromHexString("0x0000000000000096").toArrayUnsafe()), Bytes.fromHexString("0x0123").toArrayUnsafe()); tx.put( - KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE, + KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), slotKey.getSlotHash().getBytes().toArrayUnsafe(), Bytes.fromHexString("0x0000000000000097").toArrayUnsafe()), Bytes.fromHexString("0x0234").toArrayUnsafe()); tx.put( - KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE, + KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), slotKey.getSlotHash().getBytes().toArrayUnsafe(), Bytes.fromHexString("0x0000000000000098").toArrayUnsafe()), Bytes.fromHexString("0x0345").toArrayUnsafe()); tx.put( - KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE, + KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), slotKey.getSlotHash().getBytes().toArrayUnsafe(), @@ -1080,14 +1080,14 @@ public Optional load(final Hash blockHash) { // We should have marked up to block 200 as archived assertThat(testWorldStateStorage.getLatestArchivedBlock().get()).isEqualTo(200); - // Only the latest/current state of the account should be in the primary DB segment + // Only the latest/current state of the account should be in the archive DB segment assertThat( testWorldStateStorage.getComposedWorldStateStorage().stream( - KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE) + KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE) .count()) .isEqualTo(1); - // All 3 previous storage states should be in the storage archiver + // All 3 previous storage states should be in the storage archive freezer assertThat( testWorldStateStorage.getComposedWorldStateStorage().stream( KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER) @@ -1129,7 +1129,7 @@ public Optional load(final Hash blockHash) { testWorldStateStorage .getComposedWorldStateStorage() .containsKey( - KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE, + KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE, Arrays.concatenate( address.addressHash().getBytes().toArrayUnsafe(), slotKey.getSlotHash().getBytes().toArrayUnsafe(), From 54a77aafcf2d1360148f4d42ee24bcf7f914efb1 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 13:43:30 +1000 Subject: [PATCH 12/20] spotless Signed-off-by: Jason Frame --- .../BonsaiWorldStateKeyValueStorageTest.java | 16 ++++------------ .../bonsai/worldview/BonsaiArchiverTests.java | 9 ++++++--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java index 15312f01487..fbcda4e2956 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java @@ -510,9 +510,7 @@ void clear_putGetAccountFlatDbStrategy( // and flat archive DB // and we want to ensure keys put to the archive DB include the archive block context/suffix byte[] lookupKey = keyMapper.apply(account.addressHash().getBytes().toArrayUnsafe()); - assertThat( - Bytes.wrap( - storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) + assertThat(Bytes.wrap(storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) .isEqualTo( Bytes.fromHexString( "0xF84E823D98887B5E41A364EA8BFCA056E81F171BCC55A6FF8345E692C0F86E5B48E01B996CADC001622FB5E363B421A0C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470")); @@ -568,19 +566,13 @@ void clear_streamFlatAccounts( // Convert the key to lookup the entry we expect to find in K/V storage. No-op for everything // except ARCHIVE, which needs to append the 000000000000000x suffix to the key byte[] lookupKey = keyMapper.apply(account1.addressHash().getBytes().toArrayUnsafe()); - assertThat( - Bytes32.wrap( - storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) + assertThat(Bytes32.wrap(storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) .isEqualTo(account1Value); lookupKey = keyMapper.apply(account2.addressHash().getBytes().toArrayUnsafe()); - assertThat( - Bytes32.wrap( - storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) + assertThat(Bytes32.wrap(storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) .isEqualTo(account2Value); lookupKey = keyMapper.apply(account3.addressHash().getBytes().toArrayUnsafe()); - assertThat( - Bytes32.wrap( - storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) + assertThat(Bytes32.wrap(storage.getComposedWorldStateStorage().get(segment, lookupKey).get())) .isEqualTo(account3Value); // Streaming the entire range to ensure we get all 3 accounts back diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java index e025a3932f5..5cf101c2235 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java @@ -705,7 +705,8 @@ public Optional load(final Long blockNumber) { // Generate some trie logs to return for a specific block - // For state to be moved from the archive DB segment to the archive freezer DB segment, we need the + // For state to be moved from the archive DB segment to the archive freezer DB segment, we need + // the // archive DB segment to have the account in already SegmentedKeyValueStorageTransaction tx = testWorldStateStorage.getComposedWorldStateStorage().startTransaction(); @@ -859,7 +860,8 @@ public Optional load(final Hash blockHash) { .count()) .isEqualTo(1); - // Both the previous account states should be in the archive freezer segment, plus the special key that + // Both the previous account states should be in the archive freezer segment, plus the special + // key that // records the latest archived block assertThat( testWorldStateStorage.getComposedWorldStateStorage().stream( @@ -945,7 +947,8 @@ public Optional load(final Long blockNumber) { // Generate some trie logs to return for a specific block - // For storage to be moved from the archive DB segment to the archive freezer DB segment, we need the + // For storage to be moved from the archive DB segment to the archive freezer DB segment, we + // need the // archive DB segment to have the storage in already SegmentedKeyValueStorageTransaction tx = testWorldStateStorage.getComposedWorldStateStorage().startTransaction(); From d9ca67ed40fc42422d8e459674a514179b872c0c Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 14:38:56 +1000 Subject: [PATCH 13/20] Remove duplicate clear archive segment code Signed-off-by: Jason Frame --- .../storage/flat/BonsaiArchiveFlatDbStrategy.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java index eea5733ada9..18f7cb02099 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java @@ -443,24 +443,23 @@ public static Bytes calculateArchiveKeyWithMaxSuffix( @Override public void clearAll(final SegmentedKeyValueStorage storage) { - // Clear archive segments first - storage.clear(ACCOUNT_INFO_STATE_ARCHIVE); - storage.clear(ACCOUNT_STORAGE_ARCHIVE); - storage.clear(ACCOUNT_INFO_STATE_FREEZER); - storage.clear(ACCOUNT_STORAGE_FREEZER); + clearArchiveSegments(storage); // Then call parent to clear other segments super.clearAll(storage); } @Override public void resetOnResync(final SegmentedKeyValueStorage storage) { - // Clear archive segments + clearArchiveSegments(storage); + // Then call parent to reset other segments + super.resetOnResync(storage); + } + + private static void clearArchiveSegments(SegmentedKeyValueStorage storage) { storage.clear(ACCOUNT_INFO_STATE_ARCHIVE); storage.clear(ACCOUNT_STORAGE_ARCHIVE); storage.clear(ACCOUNT_INFO_STATE_FREEZER); storage.clear(ACCOUNT_STORAGE_FREEZER); - // Then call parent to reset other segments - super.resetOnResync(storage); } // TODO JF: move this out of this class so can be used with ArchiveCodeStorageStrategy without From 80b0e6c1bc49e2d058f2189e8110dc3cdd8e6e87 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 15:03:59 +1000 Subject: [PATCH 14/20] add final on method arg Signed-off-by: Jason Frame --- .../bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java index 18f7cb02099..a9ea82c151b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategy.java @@ -455,7 +455,7 @@ public void resetOnResync(final SegmentedKeyValueStorage storage) { super.resetOnResync(storage); } - private static void clearArchiveSegments(SegmentedKeyValueStorage storage) { + private static void clearArchiveSegments(final SegmentedKeyValueStorage storage) { storage.clear(ACCOUNT_INFO_STATE_ARCHIVE); storage.clear(ACCOUNT_STORAGE_ARCHIVE); storage.clear(ACCOUNT_INFO_STATE_FREEZER); From 664f0fe3304a8a6a246f39193c2d46e390913baa Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 15:09:06 +1000 Subject: [PATCH 15/20] Add freezer segment cleanup tests for BonsaiWorldStateKeyValueStorage Add unit tests to verify that clearAll() and resetOnResync() operations properly remove data from ACCOUNT_INFO_STATE_FREEZER and ACCOUNT_STORAGE_FREEZER segments, in addition to archive segments. This ensures that regressions around freezer cleanup are caught. New tests: - clearAll_removesDataFromAccountInfoStateFreezer - clearAll_removesDataFromAccountStorageFreezer - resetOnResync_removesDataFromAccountInfoStateFreezer - resetOnResync_removesDataFromAccountStorageFreezer Addresses review comment from GitHub PR #10058 Co-Authored-By: Claude Haiku 4.5 Signed-off-by: Jason Frame --- .../BonsaiWorldStateKeyValueStorageTest.java | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java index fbcda4e2956..c365b19b377 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java @@ -17,6 +17,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; import static org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage.WORLD_BLOCK_NUMBER_KEY; import static org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; @@ -1067,4 +1070,116 @@ private static void updateStorageArchiveBlock( Bytes.ofUnsignedLong(blockNumber).toArrayUnsafe()); tx.commit(); } + + @Test + void clearAll_removesDataFromAccountInfoStateFreezer() { + final BonsaiWorldStateKeyValueStorage storage = spy(setUp(FlatDbMode.ARCHIVE)); + + // Put data into the freezer segment + SegmentedKeyValueStorageTransaction tx = + storage.getComposedWorldStateStorage().startTransaction(); + byte[] accountKey = Hash.fromHexString("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") + .getBytes() + .toArrayUnsafe(); + byte[] accountValue = Bytes32.random().toArrayUnsafe(); + tx.put(ACCOUNT_INFO_STATE_FREEZER, accountKey, accountValue); + tx.commit(); + + // Verify data exists + assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE_FREEZER, accountKey)) + .isNotEmpty(); + + // Clear all + storage.clear(); + + // Verify data is removed + assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE_FREEZER, accountKey)) + .isEmpty(); + } + + @Test + void clearAll_removesDataFromAccountStorageFreezer() { + final BonsaiWorldStateKeyValueStorage storage = spy(setUp(FlatDbMode.ARCHIVE)); + + // Put data into the storage freezer segment + SegmentedKeyValueStorageTransaction tx = + storage.getComposedWorldStateStorage().startTransaction(); + byte[] storageKey = org.bouncycastle.util.Arrays.concatenate( + Hash.fromHexString("0x1111111111111111111111111111111111111111111111111111111111111111") + .getBytes() + .toArrayUnsafe(), + Hash.fromHexString("0x2222222222222222222222222222222222222222222222222222222222222222") + .getBytes() + .toArrayUnsafe()); + byte[] storageValue = Bytes.fromHexString("0xdeadbeef").toArrayUnsafe(); + tx.put(ACCOUNT_STORAGE_FREEZER, storageKey, storageValue); + tx.commit(); + + // Verify data exists + assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_STORAGE_FREEZER, storageKey)) + .isNotEmpty(); + + // Clear all + storage.clear(); + + // Verify data is removed + assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_STORAGE_FREEZER, storageKey)) + .isEmpty(); + } + + @Test + void resetOnResync_removesDataFromAccountInfoStateFreezer() { + final BonsaiWorldStateKeyValueStorage storage = spy(setUp(FlatDbMode.ARCHIVE)); + + // Put data into the freezer segment + SegmentedKeyValueStorageTransaction tx = + storage.getComposedWorldStateStorage().startTransaction(); + byte[] accountKey = Hash.fromHexString("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890") + .getBytes() + .toArrayUnsafe(); + byte[] accountValue = Bytes32.random().toArrayUnsafe(); + tx.put(ACCOUNT_INFO_STATE_FREEZER, accountKey, accountValue); + tx.commit(); + + // Verify data exists + assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE_FREEZER, accountKey)) + .isNotEmpty(); + + // Reset on resync + storage.clearFlatDatabase(); + + // Verify data is removed + assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE_FREEZER, accountKey)) + .isEmpty(); + } + + @Test + void resetOnResync_removesDataFromAccountStorageFreezer() { + final BonsaiWorldStateKeyValueStorage storage = spy(setUp(FlatDbMode.ARCHIVE)); + + // Put data into the storage freezer segment + SegmentedKeyValueStorageTransaction tx = + storage.getComposedWorldStateStorage().startTransaction(); + byte[] storageKey = org.bouncycastle.util.Arrays.concatenate( + Hash.fromHexString("0x3333333333333333333333333333333333333333333333333333333333333333") + .getBytes() + .toArrayUnsafe(), + Hash.fromHexString("0x4444444444444444444444444444444444444444444444444444444444444444") + .getBytes() + .toArrayUnsafe()); + byte[] storageValue = Bytes.fromHexString("0xcafebabe").toArrayUnsafe(); + tx.put(ACCOUNT_STORAGE_FREEZER, storageKey, storageValue); + tx.commit(); + + // Verify data exists + assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_STORAGE_FREEZER, storageKey)) + .isNotEmpty(); + + // Reset on resync + storage.clearFlatDatabase(); + + // Verify data is removed + assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_STORAGE_FREEZER, storageKey)) + .isEmpty(); + } } From 9f37abbc573ac2f076835a3e97058c1a97fe38fd Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 15:17:56 +1000 Subject: [PATCH 16/20] Add freezer segment cleanup tests to BonsaiArchiveFlatDbStrategyTest Add unit tests directly in BonsaiArchiveFlatDbStrategyTest to verify that the clearAll() and resetOnResync() methods properly remove data from ACCOUNT_INFO_STATE_FREEZER and ACCOUNT_STORAGE_FREEZER segments. This ensures that regressions around freezer cleanup are caught at the strategy level. New tests: - clearAll_removesDataFromAccountInfoStateFreezer - clearAll_removesDataFromAccountStorageFreezer - resetOnResync_removesDataFromAccountInfoStateFreezer - resetOnResync_removesDataFromAccountStorageFreezer Addresses review comment from GitHub PR #10058 Co-Authored-By: Claude Haiku 4.5 Signed-off-by: Jason Frame --- .../flat/BonsaiArchiveFlatDbStrategyTest.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java index 0feacb2753d..56d92fd4071 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java @@ -16,6 +16,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; import static org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage.WORLD_BLOCK_NUMBER_KEY; @@ -26,6 +29,7 @@ import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; import org.hyperledger.besu.services.kvstore.SegmentedInMemoryKeyValueStorage; +import org.bouncycastle.util.Arrays; import java.util.Optional; @@ -182,6 +186,98 @@ public void sequentialBlocksUseIncrementingSuffixes() { } } + @Test + public void clearAll_removesDataFromAccountInfoStateFreezer() { + // Put data into the freezer segment + byte[] accountKey = Hash.fromHexString("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") + .getBytes() + .toArrayUnsafe(); + byte[] accountValue = Bytes.fromHexString("0xAABBCCDD").toArrayUnsafe(); + SegmentedKeyValueStorageTransaction tx = storage.startTransaction(); + tx.put(ACCOUNT_INFO_STATE_FREEZER, accountKey, accountValue); + tx.commit(); + + // Verify data exists + assertThat(storage.get(ACCOUNT_INFO_STATE_FREEZER, accountKey)).isNotEmpty(); + + // Clear all - this should remove freezer data + archiveFlatDbStrategy.clearAll(storage); + + // Verify data is removed + assertThat(storage.get(ACCOUNT_INFO_STATE_FREEZER, accountKey)).isEmpty(); + } + + @Test + public void clearAll_removesDataFromAccountStorageFreezer() { + // Put data into the storage freezer segment + byte[] storageKey = Arrays.concatenate( + Hash.fromHexString("0x1111111111111111111111111111111111111111111111111111111111111111") + .getBytes() + .toArrayUnsafe(), + Hash.fromHexString("0x2222222222222222222222222222222222222222222222222222222222222222") + .getBytes() + .toArrayUnsafe()); + byte[] storageValue = Bytes.fromHexString("0xdeadbeef").toArrayUnsafe(); + SegmentedKeyValueStorageTransaction tx = storage.startTransaction(); + tx.put(ACCOUNT_STORAGE_FREEZER, storageKey, storageValue); + tx.commit(); + + // Verify data exists + assertThat(storage.get(ACCOUNT_STORAGE_FREEZER, storageKey)).isNotEmpty(); + + // Clear all - this should remove freezer data + archiveFlatDbStrategy.clearAll(storage); + + // Verify data is removed + assertThat(storage.get(ACCOUNT_STORAGE_FREEZER, storageKey)).isEmpty(); + } + + @Test + public void resetOnResync_removesDataFromAccountInfoStateFreezer() { + // Put data into the freezer segment + byte[] accountKey = Hash.fromHexString("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890") + .getBytes() + .toArrayUnsafe(); + byte[] accountValue = Bytes.fromHexString("0x11223344").toArrayUnsafe(); + SegmentedKeyValueStorageTransaction tx = storage.startTransaction(); + tx.put(ACCOUNT_INFO_STATE_FREEZER, accountKey, accountValue); + tx.commit(); + + // Verify data exists + assertThat(storage.get(ACCOUNT_INFO_STATE_FREEZER, accountKey)).isNotEmpty(); + + // Reset on resync - this should remove freezer data + archiveFlatDbStrategy.resetOnResync(storage); + + // Verify data is removed + assertThat(storage.get(ACCOUNT_INFO_STATE_FREEZER, accountKey)).isEmpty(); + } + + @Test + public void resetOnResync_removesDataFromAccountStorageFreezer() { + // Put data into the storage freezer segment + byte[] storageKey = Arrays.concatenate( + Hash.fromHexString("0x3333333333333333333333333333333333333333333333333333333333333333") + .getBytes() + .toArrayUnsafe(), + Hash.fromHexString("0x4444444444444444444444444444444444444444444444444444444444444444") + .getBytes() + .toArrayUnsafe()); + byte[] storageValue = Bytes.fromHexString("0xcafebabe").toArrayUnsafe(); + SegmentedKeyValueStorageTransaction tx = storage.startTransaction(); + tx.put(ACCOUNT_STORAGE_FREEZER, storageKey, storageValue); + tx.commit(); + + // Verify data exists + assertThat(storage.get(ACCOUNT_STORAGE_FREEZER, storageKey)).isNotEmpty(); + + // Reset on resync - this should remove freezer data + archiveFlatDbStrategy.resetOnResync(storage); + + // Verify data is removed + assertThat(storage.get(ACCOUNT_STORAGE_FREEZER, storageKey)).isEmpty(); + } + private void setWorldBlockNumber(final long blockNumber) { final SegmentedKeyValueStorageTransaction tx = storage.startTransaction(); tx.put( From 452510edf457b8868b1cf6c49bfcd7567fd6afa6 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 15:20:04 +1000 Subject: [PATCH 17/20] Clean up unnecessary comments in freezer cleanup tests Remove verbose comments from the new freezer cleanup tests to match the concise style of existing tests in BonsaiArchiveFlatDbStrategyTest. The test method names and assertion statements are self-documenting. Co-Authored-By: Claude Haiku 4.5 Signed-off-by: Jason Frame --- .../flat/BonsaiArchiveFlatDbStrategyTest.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java index 56d92fd4071..f63c1b1579e 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java @@ -188,7 +188,6 @@ public void sequentialBlocksUseIncrementingSuffixes() { @Test public void clearAll_removesDataFromAccountInfoStateFreezer() { - // Put data into the freezer segment byte[] accountKey = Hash.fromHexString("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") .getBytes() .toArrayUnsafe(); @@ -197,19 +196,15 @@ public void clearAll_removesDataFromAccountInfoStateFreezer() { tx.put(ACCOUNT_INFO_STATE_FREEZER, accountKey, accountValue); tx.commit(); - // Verify data exists assertThat(storage.get(ACCOUNT_INFO_STATE_FREEZER, accountKey)).isNotEmpty(); - // Clear all - this should remove freezer data archiveFlatDbStrategy.clearAll(storage); - // Verify data is removed assertThat(storage.get(ACCOUNT_INFO_STATE_FREEZER, accountKey)).isEmpty(); } @Test public void clearAll_removesDataFromAccountStorageFreezer() { - // Put data into the storage freezer segment byte[] storageKey = Arrays.concatenate( Hash.fromHexString("0x1111111111111111111111111111111111111111111111111111111111111111") .getBytes() @@ -222,19 +217,15 @@ public void clearAll_removesDataFromAccountStorageFreezer() { tx.put(ACCOUNT_STORAGE_FREEZER, storageKey, storageValue); tx.commit(); - // Verify data exists assertThat(storage.get(ACCOUNT_STORAGE_FREEZER, storageKey)).isNotEmpty(); - // Clear all - this should remove freezer data archiveFlatDbStrategy.clearAll(storage); - // Verify data is removed assertThat(storage.get(ACCOUNT_STORAGE_FREEZER, storageKey)).isEmpty(); } @Test public void resetOnResync_removesDataFromAccountInfoStateFreezer() { - // Put data into the freezer segment byte[] accountKey = Hash.fromHexString("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890") .getBytes() .toArrayUnsafe(); @@ -243,19 +234,15 @@ public void resetOnResync_removesDataFromAccountInfoStateFreezer() { tx.put(ACCOUNT_INFO_STATE_FREEZER, accountKey, accountValue); tx.commit(); - // Verify data exists assertThat(storage.get(ACCOUNT_INFO_STATE_FREEZER, accountKey)).isNotEmpty(); - // Reset on resync - this should remove freezer data archiveFlatDbStrategy.resetOnResync(storage); - // Verify data is removed assertThat(storage.get(ACCOUNT_INFO_STATE_FREEZER, accountKey)).isEmpty(); } @Test public void resetOnResync_removesDataFromAccountStorageFreezer() { - // Put data into the storage freezer segment byte[] storageKey = Arrays.concatenate( Hash.fromHexString("0x3333333333333333333333333333333333333333333333333333333333333333") .getBytes() @@ -268,13 +255,10 @@ public void resetOnResync_removesDataFromAccountStorageFreezer() { tx.put(ACCOUNT_STORAGE_FREEZER, storageKey, storageValue); tx.commit(); - // Verify data exists assertThat(storage.get(ACCOUNT_STORAGE_FREEZER, storageKey)).isNotEmpty(); - // Reset on resync - this should remove freezer data archiveFlatDbStrategy.resetOnResync(storage); - // Verify data is removed assertThat(storage.get(ACCOUNT_STORAGE_FREEZER, storageKey)).isEmpty(); } From 89c7669a0efb1c5da751ac1eaea67834a70562bb Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 15:43:53 +1000 Subject: [PATCH 18/20] Remove archive clear tests from BonsaiWorldStateKeyValueStorageTest Signed-off-by: Jason Frame --- .../BonsaiWorldStateKeyValueStorageTest.java | 122 +----------------- .../flat/BonsaiArchiveFlatDbStrategyTest.java | 47 +++---- 2 files changed, 28 insertions(+), 141 deletions(-) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java index c365b19b377..2a845a1c418 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java @@ -15,11 +15,9 @@ package org.hyperledger.besu.ethereum.trie.pathbased.bonsai.storage; import static org.assertj.core.api.Assertions.assertThat; +import static org.bouncycastle.util.Arrays.concatenate; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; import static org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage.WORLD_BLOCK_NUMBER_KEY; import static org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; @@ -87,8 +85,7 @@ public static Stream flatDbModeAndKeyMapper() { // For archive we want <32-byte-hex>000000000000000n where n is the current archive block number Function flatDBArchiveKey = - (key) -> - org.bouncycastle.util.Arrays.concatenate(key, Bytes.ofUnsignedLong(2).toArrayUnsafe()); + (key) -> concatenate(key, Bytes.ofUnsignedLong(2).toArrayUnsafe()); return Stream.of( Arguments.of(FlatDbMode.FULL, flatDBKey), @@ -101,8 +98,7 @@ public static Stream flatDbModeKeyMapperAndSegment() { // For archive we want <32-byte-hex>000000000000000n where n is the current archive block number Function flatDBArchiveKey = - (key) -> - org.bouncycastle.util.Arrays.concatenate(key, Bytes.ofUnsignedLong(2).toArrayUnsafe()); + (key) -> concatenate(key, Bytes.ofUnsignedLong(2).toArrayUnsafe()); return Stream.of( Arguments.of(FlatDbMode.FULL, flatDBKey, ACCOUNT_INFO_STATE), @@ -1070,116 +1066,4 @@ private static void updateStorageArchiveBlock( Bytes.ofUnsignedLong(blockNumber).toArrayUnsafe()); tx.commit(); } - - @Test - void clearAll_removesDataFromAccountInfoStateFreezer() { - final BonsaiWorldStateKeyValueStorage storage = spy(setUp(FlatDbMode.ARCHIVE)); - - // Put data into the freezer segment - SegmentedKeyValueStorageTransaction tx = - storage.getComposedWorldStateStorage().startTransaction(); - byte[] accountKey = Hash.fromHexString("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") - .getBytes() - .toArrayUnsafe(); - byte[] accountValue = Bytes32.random().toArrayUnsafe(); - tx.put(ACCOUNT_INFO_STATE_FREEZER, accountKey, accountValue); - tx.commit(); - - // Verify data exists - assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE_FREEZER, accountKey)) - .isNotEmpty(); - - // Clear all - storage.clear(); - - // Verify data is removed - assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE_FREEZER, accountKey)) - .isEmpty(); - } - - @Test - void clearAll_removesDataFromAccountStorageFreezer() { - final BonsaiWorldStateKeyValueStorage storage = spy(setUp(FlatDbMode.ARCHIVE)); - - // Put data into the storage freezer segment - SegmentedKeyValueStorageTransaction tx = - storage.getComposedWorldStateStorage().startTransaction(); - byte[] storageKey = org.bouncycastle.util.Arrays.concatenate( - Hash.fromHexString("0x1111111111111111111111111111111111111111111111111111111111111111") - .getBytes() - .toArrayUnsafe(), - Hash.fromHexString("0x2222222222222222222222222222222222222222222222222222222222222222") - .getBytes() - .toArrayUnsafe()); - byte[] storageValue = Bytes.fromHexString("0xdeadbeef").toArrayUnsafe(); - tx.put(ACCOUNT_STORAGE_FREEZER, storageKey, storageValue); - tx.commit(); - - // Verify data exists - assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_STORAGE_FREEZER, storageKey)) - .isNotEmpty(); - - // Clear all - storage.clear(); - - // Verify data is removed - assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_STORAGE_FREEZER, storageKey)) - .isEmpty(); - } - - @Test - void resetOnResync_removesDataFromAccountInfoStateFreezer() { - final BonsaiWorldStateKeyValueStorage storage = spy(setUp(FlatDbMode.ARCHIVE)); - - // Put data into the freezer segment - SegmentedKeyValueStorageTransaction tx = - storage.getComposedWorldStateStorage().startTransaction(); - byte[] accountKey = Hash.fromHexString("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890") - .getBytes() - .toArrayUnsafe(); - byte[] accountValue = Bytes32.random().toArrayUnsafe(); - tx.put(ACCOUNT_INFO_STATE_FREEZER, accountKey, accountValue); - tx.commit(); - - // Verify data exists - assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE_FREEZER, accountKey)) - .isNotEmpty(); - - // Reset on resync - storage.clearFlatDatabase(); - - // Verify data is removed - assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_INFO_STATE_FREEZER, accountKey)) - .isEmpty(); - } - - @Test - void resetOnResync_removesDataFromAccountStorageFreezer() { - final BonsaiWorldStateKeyValueStorage storage = spy(setUp(FlatDbMode.ARCHIVE)); - - // Put data into the storage freezer segment - SegmentedKeyValueStorageTransaction tx = - storage.getComposedWorldStateStorage().startTransaction(); - byte[] storageKey = org.bouncycastle.util.Arrays.concatenate( - Hash.fromHexString("0x3333333333333333333333333333333333333333333333333333333333333333") - .getBytes() - .toArrayUnsafe(), - Hash.fromHexString("0x4444444444444444444444444444444444444444444444444444444444444444") - .getBytes() - .toArrayUnsafe()); - byte[] storageValue = Bytes.fromHexString("0xcafebabe").toArrayUnsafe(); - tx.put(ACCOUNT_STORAGE_FREEZER, storageKey, storageValue); - tx.commit(); - - // Verify data exists - assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_STORAGE_FREEZER, storageKey)) - .isNotEmpty(); - - // Reset on resync - storage.clearFlatDatabase(); - - // Verify data is removed - assertThat(storage.getComposedWorldStateStorage().get(ACCOUNT_STORAGE_FREEZER, storageKey)) - .isEmpty(); - } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java index f63c1b1579e..b3b85185f35 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/flat/BonsaiArchiveFlatDbStrategyTest.java @@ -17,7 +17,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_ARCHIVE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_ARCHIVE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_FREEZER; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; import static org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage.WORLD_BLOCK_NUMBER_KEY; @@ -29,11 +28,11 @@ import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; import org.hyperledger.besu.services.kvstore.SegmentedInMemoryKeyValueStorage; -import org.bouncycastle.util.Arrays; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; +import org.bouncycastle.util.Arrays; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -188,9 +187,10 @@ public void sequentialBlocksUseIncrementingSuffixes() { @Test public void clearAll_removesDataFromAccountInfoStateFreezer() { - byte[] accountKey = Hash.fromHexString("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") - .getBytes() - .toArrayUnsafe(); + byte[] accountKey = + Hash.fromHexString("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") + .getBytes() + .toArrayUnsafe(); byte[] accountValue = Bytes.fromHexString("0xAABBCCDD").toArrayUnsafe(); SegmentedKeyValueStorageTransaction tx = storage.startTransaction(); tx.put(ACCOUNT_INFO_STATE_FREEZER, accountKey, accountValue); @@ -205,13 +205,14 @@ public void clearAll_removesDataFromAccountInfoStateFreezer() { @Test public void clearAll_removesDataFromAccountStorageFreezer() { - byte[] storageKey = Arrays.concatenate( - Hash.fromHexString("0x1111111111111111111111111111111111111111111111111111111111111111") - .getBytes() - .toArrayUnsafe(), - Hash.fromHexString("0x2222222222222222222222222222222222222222222222222222222222222222") - .getBytes() - .toArrayUnsafe()); + byte[] storageKey = + Arrays.concatenate( + Hash.fromHexString("0x1111111111111111111111111111111111111111111111111111111111111111") + .getBytes() + .toArrayUnsafe(), + Hash.fromHexString("0x2222222222222222222222222222222222222222222222222222222222222222") + .getBytes() + .toArrayUnsafe()); byte[] storageValue = Bytes.fromHexString("0xdeadbeef").toArrayUnsafe(); SegmentedKeyValueStorageTransaction tx = storage.startTransaction(); tx.put(ACCOUNT_STORAGE_FREEZER, storageKey, storageValue); @@ -226,9 +227,10 @@ public void clearAll_removesDataFromAccountStorageFreezer() { @Test public void resetOnResync_removesDataFromAccountInfoStateFreezer() { - byte[] accountKey = Hash.fromHexString("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890") - .getBytes() - .toArrayUnsafe(); + byte[] accountKey = + Hash.fromHexString("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890") + .getBytes() + .toArrayUnsafe(); byte[] accountValue = Bytes.fromHexString("0x11223344").toArrayUnsafe(); SegmentedKeyValueStorageTransaction tx = storage.startTransaction(); tx.put(ACCOUNT_INFO_STATE_FREEZER, accountKey, accountValue); @@ -243,13 +245,14 @@ public void resetOnResync_removesDataFromAccountInfoStateFreezer() { @Test public void resetOnResync_removesDataFromAccountStorageFreezer() { - byte[] storageKey = Arrays.concatenate( - Hash.fromHexString("0x3333333333333333333333333333333333333333333333333333333333333333") - .getBytes() - .toArrayUnsafe(), - Hash.fromHexString("0x4444444444444444444444444444444444444444444444444444444444444444") - .getBytes() - .toArrayUnsafe()); + byte[] storageKey = + Arrays.concatenate( + Hash.fromHexString("0x3333333333333333333333333333333333333333333333333333333333333333") + .getBytes() + .toArrayUnsafe(), + Hash.fromHexString("0x4444444444444444444444444444444444444444444444444444444444444444") + .getBytes() + .toArrayUnsafe()); byte[] storageValue = Bytes.fromHexString("0xcafebabe").toArrayUnsafe(); SegmentedKeyValueStorageTransaction tx = storage.startTransaction(); tx.put(ACCOUNT_STORAGE_FREEZER, storageKey, storageValue); From 07707528229c0d075b68ccff17bf1e17efbfd310 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Tue, 17 Mar 2026 15:50:34 +1000 Subject: [PATCH 19/20] remove unused method Signed-off-by: Jason Frame --- .../BonsaiWorldStateKeyValueStorageTest.java | 13 ------------- .../bonsai/worldview/BonsaiArchiverTests.java | 9 +++------ 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java index 2a845a1c418..944dca41967 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java @@ -80,19 +80,6 @@ public static Collection flatDbMode() { new Object[][] {{FlatDbMode.FULL}, {FlatDbMode.PARTIAL}, {FlatDbMode.ARCHIVE}}); } - public static Stream flatDbModeAndKeyMapper() { - Function flatDBKey = (key) -> key; // No-op - - // For archive we want <32-byte-hex>000000000000000n where n is the current archive block number - Function flatDBArchiveKey = - (key) -> concatenate(key, Bytes.ofUnsignedLong(2).toArrayUnsafe()); - - return Stream.of( - Arguments.of(FlatDbMode.FULL, flatDBKey), - Arguments.of(FlatDbMode.PARTIAL, flatDBKey), - Arguments.of(FlatDbMode.ARCHIVE, flatDBArchiveKey)); - } - public static Stream flatDbModeKeyMapperAndSegment() { Function flatDBKey = (key) -> key; // No-op diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java index 5cf101c2235..95b381dfb54 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BonsaiArchiverTests.java @@ -706,8 +706,7 @@ public Optional load(final Long blockNumber) { // Generate some trie logs to return for a specific block // For state to be moved from the archive DB segment to the archive freezer DB segment, we need - // the - // archive DB segment to have the account in already + // the archive DB segment to have the account in already SegmentedKeyValueStorageTransaction tx = testWorldStateStorage.getComposedWorldStateStorage().startTransaction(); final BonsaiAccount block150Account = @@ -861,8 +860,7 @@ public Optional load(final Hash blockHash) { .isEqualTo(1); // Both the previous account states should be in the archive freezer segment, plus the special - // key that - // records the latest archived block + // key that records the latest archived block assertThat( testWorldStateStorage.getComposedWorldStateStorage().stream( KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE_FREEZER) @@ -948,8 +946,7 @@ public Optional load(final Long blockNumber) { // Generate some trie logs to return for a specific block // For storage to be moved from the archive DB segment to the archive freezer DB segment, we - // need the - // archive DB segment to have the storage in already + // need the archive DB segment to have the storage in already SegmentedKeyValueStorageTransaction tx = testWorldStateStorage.getComposedWorldStateStorage().startTransaction(); StorageSlotKey slotKey = new StorageSlotKey(UInt256.fromHexString("0x1")); From 6508a3b648cc61966c16942a935d409a9607afc3 Mon Sep 17 00:00:00 2001 From: Jason Frame Date: Thu, 19 Mar 2026 12:24:03 +1000 Subject: [PATCH 20/20] Changelog entry Signed-off-by: Jason Frame --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8d81f8ea1f..84b5a51034e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Breaking Changes - Clique consensus has been removed. Besu can no longer start or mine on pure Clique networks. Syncing networks that started as Clique and have since transitioned to PoS via `terminalTotalDifficulty` (e.g. Linea Mainnet) are still supported. [#9852](https://github.com/hyperledger/besu/pull/9852) +- Experimental Bonsai Archive column families have changed to improve performance during bonsai to archive migration. If you are using the Bonsai archive you will need to do a full resync [#10058](https://github.com/besu-eth/besu/pull/10058/changes) ### Upcoming Breaking Changes - RPC changes to enhance compatibility with other ELs