mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-08 20:47:59 -05:00
Fix Snap Server Account Range tests (#7552)
Signed-off-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>
This commit is contained in:
committed by
GitHub
parent
33085dd233
commit
578104e222
@@ -85,6 +85,13 @@ public class BlockchainSetupUtil {
|
||||
return blockchain;
|
||||
}
|
||||
|
||||
public Blockchain importAllBlocks(
|
||||
final HeaderValidationMode headerValidationMode,
|
||||
final HeaderValidationMode ommerValidationMode) {
|
||||
importBlocks(blocks, headerValidationMode, ommerValidationMode);
|
||||
return blockchain;
|
||||
}
|
||||
|
||||
public void importFirstBlocks(final int count) {
|
||||
importBlocks(blocks.subList(0, count));
|
||||
}
|
||||
@@ -126,6 +133,10 @@ public class BlockchainSetupUtil {
|
||||
return createForEthashChain(BlockTestUtil.getUpgradedForkResources(), DataStorageFormat.FOREST);
|
||||
}
|
||||
|
||||
public static BlockchainSetupUtil forSnapTesting(final DataStorageFormat storageFormat) {
|
||||
return createForEthashChain(BlockTestUtil.getSnapTestChainResources(), storageFormat);
|
||||
}
|
||||
|
||||
public static BlockchainSetupUtil createForEthashChain(
|
||||
final ChainResources chainResources, final DataStorageFormat storageFormat) {
|
||||
return create(
|
||||
@@ -241,6 +252,13 @@ public class BlockchainSetupUtil {
|
||||
}
|
||||
|
||||
private void importBlocks(final List<Block> blocks) {
|
||||
importBlocks(blocks, HeaderValidationMode.FULL, HeaderValidationMode.FULL);
|
||||
}
|
||||
|
||||
private void importBlocks(
|
||||
final List<Block> blocks,
|
||||
final HeaderValidationMode headerValidationMode,
|
||||
final HeaderValidationMode ommerValidationMode) {
|
||||
for (final Block block : blocks) {
|
||||
if (block.getHeader().getNumber() == BlockHeader.GENESIS_BLOCK_NUMBER) {
|
||||
continue;
|
||||
@@ -248,7 +266,8 @@ public class BlockchainSetupUtil {
|
||||
final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(block.getHeader());
|
||||
final BlockImporter blockImporter = protocolSpec.getBlockImporter();
|
||||
final BlockImportResult result =
|
||||
blockImporter.importBlock(protocolContext, block, HeaderValidationMode.FULL);
|
||||
blockImporter.importBlock(
|
||||
protocolContext, block, headerValidationMode, ommerValidationMode);
|
||||
if (!result.isImported()) {
|
||||
throw new IllegalStateException("Unable to import block " + block.getHeader().getNumber());
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration;
|
||||
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData;
|
||||
import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider;
|
||||
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
|
||||
import org.hyperledger.besu.ethereum.rlp.RLP;
|
||||
import org.hyperledger.besu.ethereum.trie.CompactEncoding;
|
||||
import org.hyperledger.besu.ethereum.trie.MerkleTrie;
|
||||
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider;
|
||||
@@ -240,12 +241,9 @@ class SnapServer implements BesuEvents.InitialSyncCompletionListener {
|
||||
stopWatch,
|
||||
maxResponseBytes,
|
||||
(pair) -> {
|
||||
var rlpOutput = new BytesValueRLPOutput();
|
||||
rlpOutput.startList();
|
||||
rlpOutput.writeBytes(pair.getFirst());
|
||||
rlpOutput.writeRLPBytes(pair.getSecond());
|
||||
rlpOutput.endList();
|
||||
return rlpOutput.encodedSize();
|
||||
Bytes bytes =
|
||||
AccountRangeMessage.toSlimAccount(RLP.input(pair.getSecond()));
|
||||
return Hash.SIZE + bytes.size();
|
||||
});
|
||||
|
||||
final Bytes32 endKeyBytes = range.endKeyHash();
|
||||
@@ -257,11 +255,15 @@ class SnapServer implements BesuEvents.InitialSyncCompletionListener {
|
||||
storage.streamFlatAccounts(range.startKeyHash(), shouldContinuePredicate);
|
||||
|
||||
if (accounts.isEmpty() && shouldContinuePredicate.shouldContinue.get()) {
|
||||
var fromNextHash =
|
||||
range.endKeyHash().compareTo(range.startKeyHash()) >= 0
|
||||
? range.endKeyHash()
|
||||
: range.startKeyHash();
|
||||
// fetch next account after range, if it exists
|
||||
LOGGER.debug(
|
||||
"found no accounts in range, taking first value starting from {}",
|
||||
asLogHash(range.endKeyHash()));
|
||||
accounts = storage.streamFlatAccounts(range.endKeyHash(), UInt256.MAX_VALUE, 1L);
|
||||
asLogHash(fromNextHash));
|
||||
accounts = storage.streamFlatAccounts(fromNextHash, UInt256.MAX_VALUE, 1L);
|
||||
}
|
||||
|
||||
final var worldStateProof =
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
*/
|
||||
package org.hyperledger.besu.ethereum.eth.messages.snap;
|
||||
|
||||
import org.hyperledger.besu.datatypes.Hash;
|
||||
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractSnapMessageData;
|
||||
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData;
|
||||
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
|
||||
@@ -28,6 +29,7 @@ import java.util.NavigableMap;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.Maps;
|
||||
import kotlin.collections.ArrayDeque;
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
@@ -117,7 +119,8 @@ public final class AccountRangeMessage extends AbstractSnapMessageData {
|
||||
return ImmutableAccountRangeData.builder().accounts(accounts).proofs(proofs).build();
|
||||
}
|
||||
|
||||
private Bytes toFullAccount(final RLPInput rlpInput) {
|
||||
@VisibleForTesting
|
||||
public static Bytes toFullAccount(final RLPInput rlpInput) {
|
||||
final StateTrieAccountValue accountValue = StateTrieAccountValue.readFrom(rlpInput);
|
||||
|
||||
final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
|
||||
@@ -131,6 +134,26 @@ public final class AccountRangeMessage extends AbstractSnapMessageData {
|
||||
return rlpOutput.encoded();
|
||||
}
|
||||
|
||||
public static Bytes toSlimAccount(final RLPInput rlpInput) {
|
||||
StateTrieAccountValue accountValue = StateTrieAccountValue.readFrom(rlpInput);
|
||||
var rlpOutput = new BytesValueRLPOutput();
|
||||
rlpOutput.startList();
|
||||
rlpOutput.writeLongScalar(accountValue.getNonce());
|
||||
rlpOutput.writeUInt256Scalar(accountValue.getBalance());
|
||||
if (accountValue.getStorageRoot().equals(Hash.EMPTY_TRIE_HASH)) {
|
||||
rlpOutput.writeNull();
|
||||
} else {
|
||||
rlpOutput.writeBytes(accountValue.getStorageRoot());
|
||||
}
|
||||
if (accountValue.getCodeHash().equals(Hash.EMPTY)) {
|
||||
rlpOutput.writeNull();
|
||||
} else {
|
||||
rlpOutput.writeBytes(accountValue.getCodeHash());
|
||||
}
|
||||
rlpOutput.endList();
|
||||
return rlpOutput.encoded();
|
||||
}
|
||||
|
||||
@Value.Immutable
|
||||
public interface AccountRangeData {
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.rlp.RLPInput;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
import org.apache.tuweni.bytes.Bytes32;
|
||||
import org.immutables.value.Value;
|
||||
@@ -48,12 +49,21 @@ public final class GetAccountRangeMessage extends AbstractSnapMessageData {
|
||||
|
||||
public static GetAccountRangeMessage create(
|
||||
final Hash worldStateRootHash, final Bytes32 startKeyHash, final Bytes32 endKeyHash) {
|
||||
return create(worldStateRootHash, startKeyHash, endKeyHash, SIZE_REQUEST);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static GetAccountRangeMessage create(
|
||||
final Hash worldStateRootHash,
|
||||
final Bytes32 startKeyHash,
|
||||
final Bytes32 endKeyHash,
|
||||
final BigInteger sizeRequest) {
|
||||
final BytesValueRLPOutput tmp = new BytesValueRLPOutput();
|
||||
tmp.startList();
|
||||
tmp.writeBytes(worldStateRootHash);
|
||||
tmp.writeBytes(startKeyHash);
|
||||
tmp.writeBytes(endKeyHash);
|
||||
tmp.writeBigIntegerScalar(SIZE_REQUEST);
|
||||
tmp.writeBigIntegerScalar(sizeRequest);
|
||||
tmp.endList();
|
||||
return new GetAccountRangeMessage(tmp.encoded());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
* Copyright contributors to Hyperledger Besu.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.hyperledger.besu.ethereum.eth.manager.snap;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
import org.hyperledger.besu.datatypes.Hash;
|
||||
import org.hyperledger.besu.ethereum.ProtocolContext;
|
||||
import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthMessages;
|
||||
import org.hyperledger.besu.ethereum.eth.messages.snap.AccountRangeMessage;
|
||||
import org.hyperledger.besu.ethereum.eth.messages.snap.GetAccountRangeMessage;
|
||||
import org.hyperledger.besu.ethereum.eth.sync.snapsync.ImmutableSnapSyncConfiguration;
|
||||
import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration;
|
||||
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode;
|
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedWorldStateProvider;
|
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator;
|
||||
import org.hyperledger.besu.plugin.services.storage.DataStorageFormat;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.NavigableMap;
|
||||
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
import org.apache.tuweni.bytes.Bytes32;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class SnapServerGetAccountRangeTest {
|
||||
private Hash rootHash;
|
||||
public Bytes32 firstAccount;
|
||||
public Bytes32 secondAccount;
|
||||
private SnapServer snapServer;
|
||||
private static ProtocolContext protocolContext;
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() {
|
||||
// Setup local blockchain for testing
|
||||
BlockchainSetupUtil localBlockchainSetup =
|
||||
BlockchainSetupUtil.forSnapTesting(DataStorageFormat.BONSAI);
|
||||
localBlockchainSetup.importAllBlocks(
|
||||
HeaderValidationMode.LIGHT_DETACHED_ONLY, HeaderValidationMode.LIGHT);
|
||||
|
||||
protocolContext = localBlockchainSetup.getProtocolContext();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setupTest() {
|
||||
WorldStateStorageCoordinator worldStateStorageCoordinator =
|
||||
new WorldStateStorageCoordinator(
|
||||
((DiffBasedWorldStateProvider) protocolContext.getWorldStateArchive())
|
||||
.getWorldStateKeyValueStorage());
|
||||
|
||||
SnapSyncConfiguration snapSyncConfiguration =
|
||||
ImmutableSnapSyncConfiguration.builder().isSnapServerEnabled(true).build();
|
||||
snapServer =
|
||||
new SnapServer(
|
||||
snapSyncConfiguration,
|
||||
new EthMessages(),
|
||||
worldStateStorageCoordinator,
|
||||
protocolContext)
|
||||
.start();
|
||||
initAccounts();
|
||||
}
|
||||
|
||||
/**
|
||||
* In this test, we request the entire state range, but limit the response to 4000 bytes.
|
||||
* Expected: 86 accounts.
|
||||
*/
|
||||
@Test
|
||||
public void test0_RequestEntireStateRangeWith4000BytesLimit() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.responseBytes(4000)
|
||||
.expectedAccounts(86)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(
|
||||
Bytes32.fromHexString(
|
||||
"0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"))
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* In this test, we request the entire state range, but limit the response to 3000 bytes.
|
||||
* Expected: 65 accounts.
|
||||
*/
|
||||
@Test
|
||||
public void test1_RequestEntireStateRangeWith3000BytesLimit() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.responseBytes(3000)
|
||||
.expectedAccounts(65)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(
|
||||
Bytes32.fromHexString(
|
||||
"0x2e6fe1362b3e388184fd7bf08e99e74170b26361624ffd1c5f646da7067b58b6"))
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* In this test, we request the entire state range, but limit the response to 2000 bytes.
|
||||
* Expected: 44 accounts.
|
||||
*/
|
||||
@Test
|
||||
public void test2_RequestEntireStateRangeWith2000BytesLimit() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.responseBytes(2000)
|
||||
.expectedAccounts(44)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(
|
||||
Bytes32.fromHexString(
|
||||
"0x1c3f74249a4892081ba0634a819aec9ed25f34c7653f5719b9098487e65ab595"))
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* In this test, we request the entire state range, but limit the response to 1 byte. The server
|
||||
* should return the first account of the state. Expected: 1 account.
|
||||
*/
|
||||
@Test
|
||||
public void test3_RequestEntireStateRangeWith1ByteLimit() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.responseBytes(1)
|
||||
.expectedAccounts(1)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(firstAccount)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Here we request with a responseBytes limit of zero. The server should return one account.
|
||||
* Expected: 1 account.
|
||||
*/
|
||||
@Test
|
||||
public void test4_RequestEntireStateRangeWithZeroBytesLimit() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.responseBytes(0)
|
||||
.expectedAccounts(1)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(firstAccount)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* In this test, we request a range where startingHash is before the first available account key,
|
||||
* and limitHash is after. The server should return the first and second account of the state
|
||||
* (because the second account is the 'next available'). Expected: 2 accounts.
|
||||
*/
|
||||
@Test
|
||||
public void test5_RequestRangeBeforeFirstAccountKey() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.startHash(hashAdd(firstAccount, -500))
|
||||
.limitHash(hashAdd(firstAccount, 1))
|
||||
.expectedAccounts(2)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(secondAccount)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Here we request range where both bounds are before the first available account key. This should
|
||||
* return the first account (even though it's out of bounds). Expected: 1 account.
|
||||
*/
|
||||
@Test
|
||||
public void test6_RequestRangeBothBoundsBeforeFirstAccountKey() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.startHash(hashAdd(firstAccount, -500))
|
||||
.limitHash(hashAdd(firstAccount, -400))
|
||||
.expectedAccounts(1)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(firstAccount)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* In this test, both startingHash and limitHash are zero. The server should return the first
|
||||
* available account. Expected: 1 account.
|
||||
*/
|
||||
@Test
|
||||
public void test7_RequestBothBoundsZero() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.startHash(Hash.ZERO)
|
||||
.limitHash(Hash.ZERO)
|
||||
.expectedAccounts(1)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(firstAccount)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* In this test, startingHash is exactly the first available account key. The server should return
|
||||
* the first available account of the state as the first item. Expected: 86 accounts.
|
||||
*/
|
||||
@Test
|
||||
public void test8_RequestStartingHashFirstAvailableAccountKey() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.startHash(firstAccount)
|
||||
.responseBytes(4000)
|
||||
.expectedAccounts(86)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(
|
||||
Bytes32.fromHexString(
|
||||
"0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"))
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* In this test, startingHash is after the first available key. The server should return the
|
||||
* second account of the state as the first item. Expected: 86 accounts.
|
||||
*/
|
||||
@Test
|
||||
public void test9_RequestStartingHashAfterFirstAvailableKey() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.startHash(secondAccount)
|
||||
.responseBytes(4000)
|
||||
.expectedAccounts(86)
|
||||
.expectedFirstAccount(secondAccount)
|
||||
.expectedLastAccount(
|
||||
Bytes32.fromHexString(
|
||||
"0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa"))
|
||||
.build());
|
||||
}
|
||||
|
||||
/** This test requests a non-existent state root. Expected: 0 accounts. */
|
||||
@Test
|
||||
public void test10_RequestNonExistentStateRoot() {
|
||||
Hash rootHash =
|
||||
Hash.fromHexString("1337000000000000000000000000000000000000000000000000000000000000");
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder().rootHash(rootHash).expectedAccounts(0).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* This test requests data at a state root that is 127 blocks old. We expect the server to have
|
||||
* this state available. Expected: 84 accounts.
|
||||
*/
|
||||
@Test
|
||||
public void test12_RequestStateRoot127BlocksOld() {
|
||||
|
||||
Hash rootHash =
|
||||
protocolContext
|
||||
.getBlockchain()
|
||||
.getBlockHeader((protocolContext.getBlockchain().getChainHeadBlockNumber() - 127))
|
||||
.orElseThrow()
|
||||
.getStateRoot();
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.expectedAccounts(84)
|
||||
.responseBytes(4000)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(
|
||||
Bytes32.fromHexString(
|
||||
"0x580aa878e2f92d113a12c0a3ce3c21972b03dbe80786858d49a72097e2c491a3"))
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* This test requests data at a state root that is actually the storage root of an existing
|
||||
* account. The server is supposed to ignore this request. Expected: 0 accounts.
|
||||
*/
|
||||
@Test
|
||||
public void test13_RequestStateRootIsStorageRoot() {
|
||||
Hash rootHash =
|
||||
Hash.fromHexString("df97f94bc47471870606f626fb7a0b42eed2d45fcc84dc1200ce62f7831da990");
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder().rootHash(rootHash).expectedAccounts(0).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* In this test, the startingHash is after limitHash (wrong order). The server should ignore this
|
||||
* invalid request. Expected: 0 accounts.
|
||||
*/
|
||||
@Test
|
||||
public void test14_RequestStartingHashAfterLimitHash() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.startHash(Hash.LAST)
|
||||
.limitHash(Hash.ZERO)
|
||||
.expectedAccounts(0)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* In this test, the startingHash is the first available key, and limitHash is a key before
|
||||
* startingHash (wrong order). The server should return the first available key. Expected: 1
|
||||
* account.
|
||||
*/
|
||||
@Test
|
||||
public void test15_RequestStartingHashFirstAvailableKeyAndLimitHashBefore() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.startHash(firstAccount)
|
||||
.limitHash(hashAdd(firstAccount, -1))
|
||||
.expectedAccounts(1)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(firstAccount)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* In this test, the startingHash is the first available key and limitHash is zero. (wrong order).
|
||||
* The server should return the first available key. Expected: 1 account.
|
||||
*/
|
||||
@Test
|
||||
public void test16_RequestStartingHashFirstAvailableKeyAndLimitHashZero() {
|
||||
testAccountRangeRequest(
|
||||
new AccountRangeRequestParams.Builder()
|
||||
.rootHash(rootHash)
|
||||
.startHash(firstAccount)
|
||||
.limitHash(Hash.ZERO)
|
||||
.expectedAccounts(1)
|
||||
.expectedFirstAccount(firstAccount)
|
||||
.expectedLastAccount(firstAccount)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void testAccountRangeRequest(final AccountRangeRequestParams params) {
|
||||
NavigableMap<Bytes32, Bytes> accounts = getAccountRange(params);
|
||||
assertThat(accounts.size()).isEqualTo(params.getExpectedAccounts());
|
||||
|
||||
if (params.getExpectedAccounts() > 0) {
|
||||
assertThat(accounts.firstKey()).isEqualTo(params.getExpectedFirstAccount());
|
||||
assertThat(accounts.lastKey()).isEqualTo(params.getExpectedLastAccount());
|
||||
}
|
||||
}
|
||||
|
||||
private NavigableMap<Bytes32, Bytes> getAccountRange(final AccountRangeRequestParams params) {
|
||||
Hash rootHash = params.getRootHash();
|
||||
Bytes32 startHash = params.getStartHash();
|
||||
Bytes32 limitHash = params.getLimitHash();
|
||||
BigInteger sizeRequest = BigInteger.valueOf(params.getResponseBytes());
|
||||
|
||||
GetAccountRangeMessage requestMessage =
|
||||
GetAccountRangeMessage.create(rootHash, startHash, limitHash, sizeRequest);
|
||||
|
||||
AccountRangeMessage resultMessage =
|
||||
AccountRangeMessage.readFrom(
|
||||
snapServer.constructGetAccountRangeResponse(
|
||||
requestMessage.wrapMessageData(BigInteger.ONE)));
|
||||
NavigableMap<Bytes32, Bytes> accounts = resultMessage.accountData(false).accounts();
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedVariable")
|
||||
private void initAccounts() {
|
||||
rootHash = protocolContext.getWorldStateArchive().getMutable().rootHash();
|
||||
GetAccountRangeMessage requestMessage =
|
||||
GetAccountRangeMessage.create(rootHash, Hash.ZERO, Hash.LAST, BigInteger.valueOf(4000));
|
||||
AccountRangeMessage resultMessage =
|
||||
AccountRangeMessage.readFrom(
|
||||
snapServer.constructGetAccountRangeResponse(
|
||||
requestMessage.wrapMessageData(BigInteger.ONE)));
|
||||
NavigableMap<Bytes32, Bytes> accounts = resultMessage.accountData(false).accounts();
|
||||
firstAccount = accounts.firstEntry().getKey();
|
||||
secondAccount =
|
||||
accounts.entrySet().stream().skip(1).findFirst().orElse(accounts.firstEntry()).getKey();
|
||||
}
|
||||
|
||||
private Bytes32 hashAdd(final Bytes32 hash, final int value) {
|
||||
var result = Hash.wrap(hash).toBigInteger().add(BigInteger.valueOf(value));
|
||||
Bytes resultBytes = Bytes.wrap(result.toByteArray());
|
||||
return Bytes32.leftPad(resultBytes);
|
||||
}
|
||||
|
||||
public static class AccountRangeRequestParams {
|
||||
private final Hash rootHash;
|
||||
private final Bytes32 startHash;
|
||||
private final Bytes32 limitHash;
|
||||
private final int responseBytes;
|
||||
private final int expectedAccounts;
|
||||
private final Bytes32 expectedFirstAccount;
|
||||
private final Bytes32 expectedLastAccount;
|
||||
|
||||
private AccountRangeRequestParams(final Builder builder) {
|
||||
this.rootHash = builder.rootHash;
|
||||
this.startHash = builder.startHash;
|
||||
this.limitHash = builder.limitHash;
|
||||
this.responseBytes = builder.responseBytes;
|
||||
this.expectedAccounts = builder.expectedAccounts;
|
||||
this.expectedFirstAccount = builder.expectedFirstAccount;
|
||||
this.expectedLastAccount = builder.expectedLastAccount;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private Hash rootHash = null;
|
||||
private Bytes32 startHash = Bytes32.ZERO;
|
||||
private Bytes32 limitHash = Hash.LAST;
|
||||
private int responseBytes = Integer.MAX_VALUE;
|
||||
private int expectedAccounts = 0;
|
||||
private Bytes32 expectedFirstAccount = null;
|
||||
private Bytes32 expectedLastAccount = null;
|
||||
|
||||
public Builder rootHash(final Hash rootHashHex) {
|
||||
this.rootHash = rootHashHex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder startHash(final Bytes32 startHashHex) {
|
||||
this.startHash = startHashHex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder limitHash(final Bytes32 limitHashHex) {
|
||||
this.limitHash = limitHashHex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder responseBytes(final int responseBytes) {
|
||||
this.responseBytes = responseBytes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder expectedAccounts(final int expectedAccounts) {
|
||||
this.expectedAccounts = expectedAccounts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder expectedFirstAccount(final Bytes32 expectedFirstAccount) {
|
||||
this.expectedFirstAccount = expectedFirstAccount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder expectedLastAccount(final Bytes32 expectedLastAccount) {
|
||||
this.expectedLastAccount = expectedLastAccount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountRangeRequestParams build() {
|
||||
return new AccountRangeRequestParams(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Getters for each field
|
||||
public Hash getRootHash() {
|
||||
return rootHash;
|
||||
}
|
||||
|
||||
public Bytes32 getStartHash() {
|
||||
return startHash;
|
||||
}
|
||||
|
||||
public Bytes32 getLimitHash() {
|
||||
return limitHash;
|
||||
}
|
||||
|
||||
public int getResponseBytes() {
|
||||
return responseBytes;
|
||||
}
|
||||
|
||||
public int getExpectedAccounts() {
|
||||
return expectedAccounts;
|
||||
}
|
||||
|
||||
public Bytes32 getExpectedFirstAccount() {
|
||||
return expectedFirstAccount;
|
||||
}
|
||||
|
||||
public Bytes32 getExpectedLastAccount() {
|
||||
return expectedLastAccount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,7 @@ public class SnapServerTest {
|
||||
public void assertAccountLimitRangeResponse() {
|
||||
// assert we limit the range response according to size
|
||||
final int acctCount = 2000;
|
||||
final long acctRLPSize = 105;
|
||||
final long acctRLPSize = 37;
|
||||
|
||||
List<Integer> randomLoad = IntStream.range(1, 4096).boxed().collect(Collectors.toList());
|
||||
Collections.shuffle(randomLoad);
|
||||
|
||||
@@ -14,11 +14,15 @@
|
||||
*/
|
||||
package org.hyperledger.besu.ethereum.eth.messages.snap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.hyperledger.besu.datatypes.Hash;
|
||||
import org.hyperledger.besu.datatypes.Wei;
|
||||
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData;
|
||||
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage;
|
||||
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
|
||||
import org.hyperledger.besu.ethereum.rlp.RLP;
|
||||
import org.hyperledger.besu.ethereum.rlp.RLPInput;
|
||||
import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -28,7 +32,6 @@ import java.util.Map;
|
||||
|
||||
import org.apache.tuweni.bytes.Bytes;
|
||||
import org.apache.tuweni.bytes.Bytes32;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public final class AccountRangeMessageTest {
|
||||
@@ -51,7 +54,81 @@ public final class AccountRangeMessageTest {
|
||||
|
||||
// check match originals.
|
||||
final AccountRangeMessage.AccountRangeData range = message.accountData(false);
|
||||
Assertions.assertThat(range.accounts()).isEqualTo(keys);
|
||||
Assertions.assertThat(range.proofs()).isEqualTo(proofs);
|
||||
assertThat(range.accounts()).isEqualTo(keys);
|
||||
assertThat(range.proofs()).isEqualTo(proofs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toSlimAccountTest() {
|
||||
// Initialize nonce and balance
|
||||
long nonce = 1L;
|
||||
Wei balance = Wei.of(2L);
|
||||
|
||||
// Create a StateTrieAccountValue with the given nonce and balance
|
||||
final StateTrieAccountValue accountValue =
|
||||
new StateTrieAccountValue(nonce, balance, Hash.EMPTY_TRIE_HASH, Hash.EMPTY);
|
||||
|
||||
// Encode the account value to RLP
|
||||
final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput();
|
||||
accountValue.writeTo(rlpOut);
|
||||
|
||||
// Convert the encoded account value to a slim account representation
|
||||
Bytes slimAccount = AccountRangeMessage.toSlimAccount(RLP.input(rlpOut.encoded()));
|
||||
|
||||
// Read the slim account RLP input
|
||||
RLPInput in = RLP.input(slimAccount);
|
||||
in.enterList();
|
||||
|
||||
// Verify the nonce and balance
|
||||
final long expectedNonce = in.readLongScalar();
|
||||
final Wei expectedWei = Wei.of(in.readUInt256Scalar());
|
||||
assertThat(expectedNonce).isEqualTo(nonce);
|
||||
assertThat(expectedWei).isEqualTo(balance);
|
||||
|
||||
// Check that the storageRoot is empty
|
||||
assertThat(in.nextIsNull()).isTrue();
|
||||
in.skipNext();
|
||||
|
||||
// Check that the codeHash is empty
|
||||
assertThat(in.nextIsNull()).isTrue();
|
||||
in.skipNext();
|
||||
|
||||
// Exit the list
|
||||
in.leaveList();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toFullAccountTest() {
|
||||
// Initialize nonce and balance
|
||||
long nonce = 1L;
|
||||
Wei balance = Wei.of(2L);
|
||||
|
||||
// Create a StateTrieAccountValue with the given nonce and balance
|
||||
final StateTrieAccountValue accountValue =
|
||||
new StateTrieAccountValue(nonce, balance, Hash.EMPTY_TRIE_HASH, Hash.EMPTY);
|
||||
|
||||
// Encode the account value to RLP
|
||||
final BytesValueRLPOutput rlpOut = new BytesValueRLPOutput();
|
||||
accountValue.writeTo(rlpOut);
|
||||
|
||||
// Convert the encoded account value to a full account representation
|
||||
Bytes fullAccount = AccountRangeMessage.toFullAccount(RLP.input(rlpOut.encoded()));
|
||||
|
||||
// Read the full account RLP input
|
||||
RLPInput in = RLP.input(fullAccount);
|
||||
in.enterList();
|
||||
|
||||
// Verify the nonce and balance
|
||||
final long expectedNonce = in.readLongScalar();
|
||||
final Wei expectedWei = Wei.of(in.readUInt256Scalar());
|
||||
assertThat(expectedNonce).isEqualTo(nonce);
|
||||
assertThat(expectedWei).isEqualTo(balance);
|
||||
|
||||
// Verify the storageRoot and codeHash
|
||||
assertThat(in.readBytes32()).isEqualTo(Hash.EMPTY_TRIE_HASH);
|
||||
assertThat(in.readBytes32()).isEqualTo(Hash.EMPTY);
|
||||
|
||||
// Exit the list
|
||||
in.leaveList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public final class BlockTestUtil {
|
||||
Suppliers.memoize(BlockTestUtil::supplyUpgradedForkResources);
|
||||
private static final Supplier<ChainResources> testRpcCompactChainSupplier =
|
||||
Suppliers.memoize(BlockTestUtil::supplyTestRpcCompactResources);
|
||||
private static final Supplier<ChainResources> snapTestChainSupplier =
|
||||
Suppliers.memoize(BlockTestUtil::supplySnapTestChainResources);
|
||||
|
||||
/**
|
||||
* Gets test blockchain url.
|
||||
@@ -156,6 +158,15 @@ public final class BlockTestUtil {
|
||||
return testRpcCompactChainSupplier.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets test chain resources for Snap tests.
|
||||
*
|
||||
* @return the test chain resources
|
||||
*/
|
||||
public static ChainResources getSnapTestChainResources() {
|
||||
return snapTestChainSupplier.get();
|
||||
}
|
||||
|
||||
private static ChainResources supplyTestChainResources() {
|
||||
final URL genesisURL =
|
||||
ensureFileUrl(BlockTestUtil.class.getClassLoader().getResource("testGenesis.json"));
|
||||
@@ -164,6 +175,15 @@ public final class BlockTestUtil {
|
||||
return new ChainResources(genesisURL, blocksURL);
|
||||
}
|
||||
|
||||
private static ChainResources supplySnapTestChainResources() {
|
||||
final URL genesisURL =
|
||||
ensureFileUrl(BlockTestUtil.class.getClassLoader().getResource("snap/snapGenesis.json"));
|
||||
final URL blocksURL =
|
||||
ensureFileUrl(
|
||||
BlockTestUtil.class.getClassLoader().getResource("snap/testBlockchain.blocks"));
|
||||
return new ChainResources(genesisURL, blocksURL);
|
||||
}
|
||||
|
||||
private static ChainResources supplyHiveTestChainResources() {
|
||||
final URL genesisURL =
|
||||
ensureFileUrl(BlockTestUtil.class.getClassLoader().getResource("hive/testGenesis.json"));
|
||||
|
||||
111
testutil/src/main/resources/snap/snapGenesis.json
Normal file
111
testutil/src/main/resources/snap/snapGenesis.json
Normal file
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"config": {
|
||||
"ethash": {},
|
||||
"chainID": 3503995874084926,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 6,
|
||||
"eip155Block": 12,
|
||||
"eip158Block": 12,
|
||||
"byzantiumBlock": 18,
|
||||
"constantinopleBlock": 24,
|
||||
"constantinopleFixBlock": 30,
|
||||
"istanbulBlock": 36,
|
||||
"muirGlacierBlock": 42,
|
||||
"berlinBlock": 48,
|
||||
"londonBlock": 54,
|
||||
"arrowGlacierBlock": 60,
|
||||
"grayGlacierBlock": 66,
|
||||
"mergeNetsplitBlock": 72,
|
||||
"terminalTotalDifficulty": 9454784,
|
||||
"shanghaiTime": 780,
|
||||
"cancunTime": 840
|
||||
},
|
||||
"nonce": "0x0",
|
||||
"timestamp": "0x0",
|
||||
"extraData": "0x68697665636861696e",
|
||||
"gasLimit": "0x23f3e20",
|
||||
"difficulty": "0x20000",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||
"alloc": {
|
||||
"000f3df6d732807ef1319fb7b8bb8522d0beac02": {
|
||||
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
|
||||
"balance": "0x2a"
|
||||
},
|
||||
"0c2c51a0990aee1d73c1228de158688341557508": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"14e46043e63d0e3cdcf2530519f4cfaf35058cb2": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"1f5bde34b4afc686f136c7a3cb6ec376f7357759": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"2d389075be5be9f2246ad654ce152cf05990b209": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"4340ee1b812acb40a1eb561c019c327b243b92df": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"4a0f1452281bcec5bd90c3dce6162a5995bfe9df": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"4dde844b71bcdf95512fb4dc94e84fb67b512ed8": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"5f552da00dfb4d3749d9e62dcee3c918855a86a0": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"654aa64f5fbefb84c270ec74211b81ca8c44a72e": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"717f8aa2b982bee0e29f573d31df288663e1ce16": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"83c7e323d189f18725ac510004fdc2941f8c4a78": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"84e75c28348fb86acea1a93a39426d7d60f4cc46": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"8bebc8ba651aee624937e7d897853ac30c95a067": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001",
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002",
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003"
|
||||
},
|
||||
"balance": "0x1",
|
||||
"nonce": "0x1"
|
||||
},
|
||||
"c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"d803681e487e6ac18053afc5a6cd813c86ec3e4d": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"eda8645ba6948855e3b3cd596bbb07596d59c603": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
}
|
||||
},
|
||||
"number": "0x0",
|
||||
"gasUsed": "0x0",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"baseFeePerGas": null,
|
||||
"excessBlobGas": null,
|
||||
"blobGasUsed": null
|
||||
}
|
||||
BIN
testutil/src/main/resources/snap/testBlockchain.blocks
Normal file
BIN
testutil/src/main/resources/snap/testBlockchain.blocks
Normal file
Binary file not shown.
Reference in New Issue
Block a user