mirror of https://github.com/hyperledger/besu
Snap server rebase (#6640)
* initial snap server implementation Signed-off-by: garyschulte <garyschulte@gmail.com> Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>pull/6856/head 24.3.1
parent
deaea9b34d
commit
34fc5eed58
@ -0,0 +1,629 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors |
||||
* |
||||
* 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 static org.hyperledger.besu.ethereum.eth.manager.snap.SnapServer.HASH_LAST; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.never; |
||||
import static org.mockito.Mockito.spy; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
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.ByteCodesMessage; |
||||
import org.hyperledger.besu.ethereum.eth.messages.snap.GetAccountRangeMessage; |
||||
import org.hyperledger.besu.ethereum.eth.messages.snap.GetByteCodesMessage; |
||||
import org.hyperledger.besu.ethereum.eth.messages.snap.GetStorageRangeMessage; |
||||
import org.hyperledger.besu.ethereum.eth.messages.snap.GetTrieNodesMessage; |
||||
import org.hyperledger.besu.ethereum.eth.messages.snap.StorageRangeMessage; |
||||
import org.hyperledger.besu.ethereum.eth.messages.snap.TrieNodesMessage; |
||||
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.storage.BonsaiWorldStateKeyValueStorage; |
||||
import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat.FlatDbStrategyProvider; |
||||
import org.hyperledger.besu.ethereum.trie.patricia.SimpleMerklePatriciaTrie; |
||||
import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; |
||||
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; |
||||
import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; |
||||
import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; |
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; |
||||
import org.hyperledger.besu.metrics.ObservableMetricsSystem; |
||||
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; |
||||
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; |
||||
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; |
||||
import org.hyperledger.besu.services.kvstore.SegmentedInMemoryKeyValueStorage; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.NavigableMap; |
||||
import java.util.Optional; |
||||
import java.util.Random; |
||||
import java.util.function.Function; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.IntStream; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
public class SnapServerTest { |
||||
static Random rand = new Random(); |
||||
|
||||
record SnapTestAccount( |
||||
Hash addressHash, |
||||
StateTrieAccountValue accountValue, |
||||
MerkleTrie<Bytes32, Bytes> storage, |
||||
Bytes code) { |
||||
Bytes accountRLP() { |
||||
return RLP.encode(accountValue::writeTo); |
||||
} |
||||
} |
||||
|
||||
static final ObservableMetricsSystem noopMetrics = new NoOpMetricsSystem(); |
||||
final SegmentedInMemoryKeyValueStorage storage = new SegmentedInMemoryKeyValueStorage(); |
||||
|
||||
// force a full flat db with code stored by code hash:
|
||||
final BonsaiWorldStateKeyValueStorage inMemoryStorage = |
||||
new BonsaiWorldStateKeyValueStorage( |
||||
new FlatDbStrategyProvider(noopMetrics, DataStorageConfiguration.DEFAULT_BONSAI_CONFIG) { |
||||
@Override |
||||
public FlatDbMode getFlatDbMode() { |
||||
return FlatDbMode.FULL; |
||||
} |
||||
|
||||
@Override |
||||
protected boolean deriveUseCodeStorageByHash( |
||||
final SegmentedKeyValueStorage composedWorldStateStorage) { |
||||
return true; |
||||
} |
||||
}, |
||||
storage, |
||||
new InMemoryKeyValueStorage()); |
||||
|
||||
final WorldStateStorageCoordinator storageCoordinator = |
||||
new WorldStateStorageCoordinator(inMemoryStorage); |
||||
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie = |
||||
new StoredMerklePatriciaTrie<>( |
||||
inMemoryStorage::getAccountStateTrieNode, Function.identity(), Function.identity()); |
||||
final WorldStateProofProvider proofProvider = new WorldStateProofProvider(storageCoordinator); |
||||
|
||||
final Function<Hash, Optional<BonsaiWorldStateKeyValueStorage>> spyProvider = |
||||
spy( |
||||
new Function<Hash, Optional<BonsaiWorldStateKeyValueStorage>>() { |
||||
// explicit non-final class is necessary for Mockito to spy:
|
||||
@Override |
||||
public Optional<BonsaiWorldStateKeyValueStorage> apply(final Hash hash) { |
||||
return Optional.of(inMemoryStorage); |
||||
} |
||||
}); |
||||
|
||||
final SnapServer snapServer = |
||||
new SnapServer(new EthMessages(), storageCoordinator, spyProvider).start(); |
||||
|
||||
final SnapTestAccount acct1 = createTestAccount("10"); |
||||
final SnapTestAccount acct2 = createTestAccount("20"); |
||||
final SnapTestAccount acct3 = createTestContractAccount("30", inMemoryStorage); |
||||
final SnapTestAccount acct4 = createTestContractAccount("40", inMemoryStorage); |
||||
|
||||
@BeforeEach |
||||
public void setup() { |
||||
snapServer.start(); |
||||
} |
||||
|
||||
@Test |
||||
public void assertNoStartNoOp() { |
||||
// account found at startHash
|
||||
insertTestAccounts(acct4, acct3, acct1, acct2); |
||||
|
||||
// stop snap server so that we should not be processing snap requests
|
||||
snapServer.stop(); |
||||
|
||||
var rangeData = requestAccountRange(acct1.addressHash, acct4.addressHash).accountData(false); |
||||
|
||||
// assert empty account response and no attempt to fetch worldstate
|
||||
assertThat(rangeData.accounts().isEmpty()).isTrue(); |
||||
assertThat(rangeData.proofs().isEmpty()).isTrue(); |
||||
verify(spyProvider, never()).apply(any()); |
||||
|
||||
// assert empty storage response and no attempt to fetch worldstate
|
||||
var storageRange = |
||||
requestStorageRange(List.of(acct3.addressHash), Hash.ZERO, HASH_LAST).slotsData(false); |
||||
assertThat(storageRange.slots().isEmpty()).isTrue(); |
||||
assertThat(storageRange.proofs().isEmpty()).isTrue(); |
||||
verify(spyProvider, never()).apply(any()); |
||||
|
||||
// assert empty trie nodes response and no attempt to fetch worldstate
|
||||
var trieNodes = |
||||
requestTrieNodes(storageTrie.getRootHash(), List.of(List.of(Bytes.fromHexString("0x01")))) |
||||
.nodes(false); |
||||
assertThat(trieNodes.isEmpty()).isTrue(); |
||||
verify(spyProvider, never()).apply(any()); |
||||
|
||||
// assert empty code response and no attempt to fetch worldstate
|
||||
var codes = |
||||
requestByteCodes(List.of(acct3.accountValue.getCodeHash())).bytecodes(false).codes(); |
||||
assertThat(codes.isEmpty()).isTrue(); |
||||
verify(spyProvider, never()).apply(any()); |
||||
} |
||||
|
||||
@Test |
||||
public void assertEmptyRangeLeftProofOfExclusionAndNextAccount() { |
||||
// for a range request that returns empty, we should return just a proof of exclusion on the
|
||||
// left and the next account after the limit hash
|
||||
insertTestAccounts(acct1, acct4); |
||||
|
||||
var rangeData = |
||||
getAndVerifyAccountRangeData(requestAccountRange(acct2.addressHash, acct3.addressHash), 1); |
||||
|
||||
// expect to find only one value acct4, outside the requested range
|
||||
var outOfRangeVal = rangeData.accounts().entrySet().stream().findFirst(); |
||||
assertThat(outOfRangeVal).isPresent(); |
||||
assertThat(outOfRangeVal.get().getKey()).isEqualTo(acct4.addressHash()); |
||||
|
||||
// assert proofs are valid for the requested range
|
||||
assertThat(assertIsValidAccountRangeProof(acct2.addressHash, rangeData)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void assertAccountLimitRangeResponse() { |
||||
// assert we limit the range response according to size
|
||||
final int acctCount = 2000; |
||||
final long acctRLPSize = 105; |
||||
|
||||
List<Integer> randomLoad = IntStream.range(1, 4096).boxed().collect(Collectors.toList()); |
||||
Collections.shuffle(randomLoad); |
||||
randomLoad.stream() |
||||
.forEach( |
||||
i -> |
||||
insertTestAccounts( |
||||
createTestAccount( |
||||
Bytes.concatenate( |
||||
Bytes.fromHexString("0x40"), |
||||
Bytes.fromHexStringLenient(Integer.toHexString(i * 256))) |
||||
.toHexString()))); |
||||
|
||||
final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); |
||||
tmp.startList(); |
||||
tmp.writeBytes(storageTrie.getRootHash()); |
||||
tmp.writeBytes(Hash.ZERO); |
||||
tmp.writeBytes(HASH_LAST); |
||||
tmp.writeBigIntegerScalar(BigInteger.valueOf(acctRLPSize * acctCount)); |
||||
tmp.endList(); |
||||
var tinyRangeLimit = new GetAccountRangeMessage(tmp.encoded()).wrapMessageData(BigInteger.ONE); |
||||
|
||||
var rangeData = |
||||
getAndVerifyAccountRangeData( |
||||
(AccountRangeMessage) snapServer.constructGetAccountRangeResponse(tinyRangeLimit), |
||||
// TODO: after sorting out the request fudge factor, adjust this assertion to match
|
||||
acctCount * 90 / 100 - 1); |
||||
|
||||
// assert proofs are valid for the requested range
|
||||
assertThat(assertIsValidAccountRangeProof(Hash.ZERO, rangeData)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void assertLastEmptyRange() { |
||||
// When our final range request is empty, no next account is possible,
|
||||
// and we should return just a proof of exclusion of the right
|
||||
insertTestAccounts(acct1, acct2); |
||||
var rangeData = |
||||
getAndVerifyAccountRangeData(requestAccountRange(acct3.addressHash, acct4.addressHash), 0); |
||||
|
||||
// assert proofs are valid for the requested range
|
||||
assertThat(assertIsValidAccountRangeProof(acct3.addressHash, rangeData)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void assertAccountFoundAtStartHashProof() { |
||||
// account found at startHash
|
||||
insertTestAccounts(acct4, acct3, acct1, acct2); |
||||
var rangeData = |
||||
getAndVerifyAccountRangeData(requestAccountRange(acct1.addressHash, acct4.addressHash), 4); |
||||
|
||||
// assert proofs are valid for requested range
|
||||
assertThat(assertIsValidAccountRangeProof(acct1.addressHash, rangeData)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void assertCompleteStorageForSingleAccount() { |
||||
insertTestAccounts(acct1, acct2, acct3, acct4); |
||||
var rangeData = requestStorageRange(List.of(acct3.addressHash), Hash.ZERO, HASH_LAST); |
||||
assertThat(rangeData).isNotNull(); |
||||
var slotsData = rangeData.slotsData(false); |
||||
assertThat(slotsData).isNotNull(); |
||||
assertThat(slotsData.slots()).isNotNull(); |
||||
assertThat(slotsData.slots().size()).isEqualTo(1); |
||||
var firstAccountStorages = slotsData.slots().first(); |
||||
assertThat(firstAccountStorages.size()).isEqualTo(10); |
||||
// no proofs for complete storage range:
|
||||
assertThat(slotsData.proofs().size()).isEqualTo(0); |
||||
|
||||
assertThat( |
||||
assertIsValidStorageProof(acct3, Hash.ZERO, firstAccountStorages, slotsData.proofs())) |
||||
.isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void assertPartialStorageForSingleAccountEmptyRange() { |
||||
insertTestAccounts(acct3); |
||||
var rangeData = |
||||
requestStorageRange( |
||||
List.of(acct3.addressHash), Hash.ZERO, Hash.fromHexStringLenient("0x00ff")); |
||||
assertThat(rangeData).isNotNull(); |
||||
var slotsData = rangeData.slotsData(false); |
||||
assertThat(slotsData).isNotNull(); |
||||
assertThat(slotsData.slots()).isNotNull(); |
||||
// expect 1 slot PAST the requested empty range
|
||||
assertThat(slotsData.slots().size()).isEqualTo(1); |
||||
// expect left and right proofs for empty storage range:
|
||||
assertThat(slotsData.proofs().size()).isGreaterThan(0); |
||||
// assert proofs are valid for the requested range
|
||||
assertThat( |
||||
assertIsValidStorageProof( |
||||
acct3, Hash.ZERO, slotsData.slots().first(), slotsData.proofs())) |
||||
.isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void assertLastEmptyPartialStorageForSingleAccount() { |
||||
// When our final range request is empty, no next account is possible,
|
||||
// and we should return just a proof of exclusion of the right
|
||||
|
||||
insertTestAccounts(acct3); |
||||
var rangeData = requestStorageRange(List.of(acct3.addressHash), HASH_LAST, HASH_LAST); |
||||
assertThat(rangeData).isNotNull(); |
||||
var slotsData = rangeData.slotsData(false); |
||||
assertThat(slotsData).isNotNull(); |
||||
assertThat(slotsData.slots()).isNotNull(); |
||||
// expect no slots PAST the requested empty range
|
||||
assertThat(slotsData.slots().size()).isEqualTo(0); |
||||
// expect left and right proofs for empty storage range:
|
||||
assertThat(slotsData.proofs().size()).isGreaterThan(0); |
||||
// assert proofs are valid for the requested range
|
||||
assertThat( |
||||
assertIsValidStorageProof( |
||||
acct3, |
||||
Hash.fromHexStringLenient("0xFF"), |
||||
Collections.emptyNavigableMap(), |
||||
slotsData.proofs())) |
||||
.isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void assertStorageLimitRangeResponse() { |
||||
// assert we limit the range response according to bytessize
|
||||
final int storageSlotSize = 70; |
||||
final int storageSlotCount = 16; |
||||
insertTestAccounts(acct1, acct2, acct3, acct4); |
||||
|
||||
final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); |
||||
tmp.startList(); |
||||
tmp.writeBigIntegerScalar(BigInteger.ONE); |
||||
tmp.writeBytes(storageTrie.getRootHash()); |
||||
tmp.writeList( |
||||
List.of(acct3.addressHash, acct4.addressHash), |
||||
(hash, rlpOutput) -> rlpOutput.writeBytes(hash)); |
||||
tmp.writeBytes(Hash.ZERO); |
||||
tmp.writeBytes(HASH_LAST); |
||||
tmp.writeBigIntegerScalar(BigInteger.valueOf(storageSlotCount * storageSlotSize)); |
||||
tmp.endList(); |
||||
var tinyRangeLimit = new GetStorageRangeMessage(tmp.encoded()); |
||||
|
||||
var rangeData = |
||||
(StorageRangeMessage) snapServer.constructGetStorageRangeResponse(tinyRangeLimit); |
||||
|
||||
// assert proofs are valid for the requested range
|
||||
assertThat(rangeData).isNotNull(); |
||||
var slotsData = rangeData.slotsData(false); |
||||
assertThat(slotsData).isNotNull(); |
||||
assertThat(slotsData.slots()).isNotNull(); |
||||
assertThat(slotsData.slots().size()).isEqualTo(2); |
||||
var firstAccountStorages = slotsData.slots().first(); |
||||
// expecting to see complete 10 slot storage for acct3
|
||||
assertThat(firstAccountStorages.size()).isEqualTo(10); |
||||
var secondAccountStorages = slotsData.slots().last(); |
||||
// expecting to see only 6 since request was limited to 16 slots
|
||||
// TODO: after sorting out the request fudge factor, adjust this assertion to match
|
||||
assertThat(secondAccountStorages.size()).isEqualTo(6 * 90 / 100 - 1); |
||||
// proofs required for interrupted storage range:
|
||||
assertThat(slotsData.proofs().size()).isNotEqualTo(0); |
||||
|
||||
assertThat( |
||||
assertIsValidStorageProof(acct4, Hash.ZERO, secondAccountStorages, slotsData.proofs())) |
||||
.isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void assertAccountTriePathRequest() { |
||||
insertTestAccounts(acct1, acct2, acct3, acct4); |
||||
var partialPathToAcct2 = CompactEncoding.bytesToPath(acct2.addressHash).slice(0, 1); |
||||
var partialPathToAcct1 = Bytes.fromHexString("0x01"); // first nibble is 1
|
||||
var trieNodeRequest = |
||||
requestTrieNodes( |
||||
storageTrie.getRootHash(), |
||||
List.of(List.of(partialPathToAcct2), List.of(partialPathToAcct1))); |
||||
assertThat(trieNodeRequest).isNotNull(); |
||||
List<Bytes> trieNodes = trieNodeRequest.nodes(false); |
||||
assertThat(trieNodes).isNotNull(); |
||||
assertThat(trieNodes.size()).isEqualTo(2); |
||||
} |
||||
|
||||
@Test |
||||
public void assertAccountTrieLimitRequest() { |
||||
insertTestAccounts(acct1, acct2, acct3, acct4); |
||||
final int accountNodeSize = 147; |
||||
final int accountNodeLimit = 3; |
||||
|
||||
var partialPathToAcct1 = Bytes.fromHexString("0x01"); // first nibble is 1
|
||||
var partialPathToAcct2 = CompactEncoding.bytesToPath(acct2.addressHash).slice(0, 1); |
||||
var partialPathToAcct3 = Bytes.fromHexString("0x03"); // first nibble is 1
|
||||
var partialPathToAcct4 = Bytes.fromHexString("0x04"); // first nibble is 1
|
||||
final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); |
||||
tmp.startList(); |
||||
tmp.writeBigIntegerScalar(BigInteger.ONE); |
||||
tmp.writeBytes(storageTrie.getRootHash()); |
||||
tmp.writeList( |
||||
List.of( |
||||
List.of(partialPathToAcct4), |
||||
List.of(partialPathToAcct3), |
||||
List.of(partialPathToAcct2), |
||||
List.of(partialPathToAcct1)), |
||||
(path, rlpOutput) -> |
||||
rlpOutput.writeList(path, (b, subRlpOutput) -> subRlpOutput.writeBytes(b))); |
||||
tmp.writeBigIntegerScalar(BigInteger.valueOf(accountNodeLimit * accountNodeSize)); |
||||
tmp.endList(); |
||||
|
||||
var trieNodeRequest = |
||||
(TrieNodesMessage) |
||||
snapServer.constructGetTrieNodesResponse(new GetTrieNodesMessage(tmp.encoded())); |
||||
|
||||
assertThat(trieNodeRequest).isNotNull(); |
||||
List<Bytes> trieNodes = trieNodeRequest.nodes(false); |
||||
assertThat(trieNodes).isNotNull(); |
||||
// TODO: adjust this assertion after sorting out the request fudge factor
|
||||
assertThat(trieNodes.size()).isEqualTo(accountNodeLimit * 90 / 100); |
||||
} |
||||
|
||||
@Test |
||||
public void assertStorageTriePathRequest() { |
||||
insertTestAccounts(acct1, acct2, acct3, acct4); |
||||
var pathToSlot11 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0101")); |
||||
var pathToSlot12 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0102")); |
||||
var pathToSlot1a = CompactEncoding.encode(Bytes.fromHexStringLenient("0x010A")); // not present
|
||||
var trieNodeRequest = |
||||
requestTrieNodes( |
||||
storageTrie.getRootHash(), |
||||
List.of( |
||||
List.of(acct3.addressHash, pathToSlot11, pathToSlot12, pathToSlot1a), |
||||
List.of(acct4.addressHash, pathToSlot11, pathToSlot12, pathToSlot1a))); |
||||
assertThat(trieNodeRequest).isNotNull(); |
||||
List<Bytes> trieNodes = trieNodeRequest.nodes(false); |
||||
assertThat(trieNodes).isNotNull(); |
||||
assertThat(trieNodes.size()).isEqualTo(4); |
||||
} |
||||
|
||||
@Test |
||||
public void assertStorageTrieLimitRequest() { |
||||
insertTestAccounts(acct1, acct2, acct3, acct4); |
||||
final int trieNodeSize = 69; |
||||
final int trieNodeLimit = 3; |
||||
|
||||
var pathToSlot11 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0101")); |
||||
var pathToSlot12 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0102")); |
||||
var pathToSlot1a = CompactEncoding.encode(Bytes.fromHexStringLenient("0x010A")); // not present
|
||||
|
||||
final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); |
||||
tmp.startList(); |
||||
tmp.writeBigIntegerScalar(BigInteger.ONE); |
||||
tmp.writeBytes(storageTrie.getRootHash()); |
||||
tmp.writeList( |
||||
List.of( |
||||
List.of(acct3.addressHash, pathToSlot11, pathToSlot12, pathToSlot1a), |
||||
List.of(acct4.addressHash, pathToSlot11, pathToSlot12, pathToSlot1a)), |
||||
(path, rlpOutput) -> |
||||
rlpOutput.writeList(path, (b, subRlpOutput) -> subRlpOutput.writeBytes(b))); |
||||
tmp.writeBigIntegerScalar(BigInteger.valueOf(trieNodeLimit * trieNodeSize)); |
||||
tmp.endList(); |
||||
|
||||
var trieNodeRequest = |
||||
(TrieNodesMessage) |
||||
snapServer.constructGetTrieNodesResponse(new GetTrieNodesMessage(tmp.encoded())); |
||||
|
||||
assertThat(trieNodeRequest).isNotNull(); |
||||
List<Bytes> trieNodes = trieNodeRequest.nodes(false); |
||||
assertThat(trieNodes).isNotNull(); |
||||
// TODO: adjust this assertion after sorting out the request fudge factor
|
||||
assertThat(trieNodes.size()).isEqualTo(trieNodeLimit * 90 / 100); |
||||
} |
||||
|
||||
@Test |
||||
public void assertCodePresent() { |
||||
insertTestAccounts(acct1, acct2, acct3, acct4); |
||||
var codeRequest = |
||||
requestByteCodes( |
||||
List.of(acct3.accountValue.getCodeHash(), acct4.accountValue.getCodeHash())); |
||||
assertThat(codeRequest).isNotNull(); |
||||
ByteCodesMessage.ByteCodes codes = codeRequest.bytecodes(false); |
||||
assertThat(codes).isNotNull(); |
||||
assertThat(codes.codes().size()).isEqualTo(2); |
||||
} |
||||
|
||||
@Test |
||||
public void assertCodeLimitRequest() { |
||||
insertTestAccounts(acct1, acct2, acct3, acct4); |
||||
final int codeSize = 32; |
||||
final int codeLimit = 2; |
||||
|
||||
final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); |
||||
tmp.startList(); |
||||
tmp.writeBigIntegerScalar(BigInteger.ONE); |
||||
tmp.writeList( |
||||
List.of(acct3.accountValue.getCodeHash(), acct4.accountValue.getCodeHash()), |
||||
(hash, rlpOutput) -> rlpOutput.writeBytes(hash)); |
||||
tmp.writeBigIntegerScalar(BigInteger.valueOf(codeSize * codeLimit)); |
||||
tmp.endList(); |
||||
|
||||
var codeRequest = |
||||
(ByteCodesMessage) |
||||
snapServer.constructGetBytecodesResponse(new GetByteCodesMessage(tmp.encoded())); |
||||
|
||||
assertThat(codeRequest).isNotNull(); |
||||
ByteCodesMessage.ByteCodes codes = codeRequest.bytecodes(false); |
||||
assertThat(codes).isNotNull(); |
||||
// TODO adjust this assertion after sorting out the request fudge factor
|
||||
assertThat(codes.codes().size()).isEqualTo(codeLimit * 90 / 100); |
||||
} |
||||
|
||||
static SnapTestAccount createTestAccount(final String hexAddr) { |
||||
return new SnapTestAccount( |
||||
Hash.wrap(Bytes32.rightPad(Bytes.fromHexString(hexAddr))), |
||||
new StateTrieAccountValue( |
||||
rand.nextInt(0, 1), Wei.of(rand.nextLong(0L, 1L)), Hash.EMPTY_TRIE_HASH, Hash.EMPTY), |
||||
new SimpleMerklePatriciaTrie<>(a -> a), |
||||
Bytes.EMPTY); |
||||
} |
||||
|
||||
static SnapTestAccount createTestContractAccount( |
||||
final String hexAddr, final BonsaiWorldStateKeyValueStorage storage) { |
||||
Hash acctHash = Hash.wrap(Bytes32.rightPad(Bytes.fromHexString(hexAddr))); |
||||
MerkleTrie<Bytes32, Bytes> trie = |
||||
new StoredMerklePatriciaTrie<>( |
||||
(loc, hash) -> storage.getAccountStorageTrieNode(acctHash, loc, hash), |
||||
Hash.EMPTY_TRIE_HASH, |
||||
a -> a, |
||||
a -> a); |
||||
Bytes32 mockCode = Bytes32.random(); |
||||
|
||||
// mock some storage data
|
||||
var flatdb = storage.getFlatDbStrategy(); |
||||
var updater = storage.updater(); |
||||
updater.putCode(Hash.hash(mockCode), mockCode); |
||||
IntStream.range(10, 20) |
||||
.boxed() |
||||
.forEach( |
||||
i -> { |
||||
Bytes32 mockBytes32 = Bytes32.rightPad(Bytes.fromHexString(i.toString())); |
||||
var rlpOut = new BytesValueRLPOutput(); |
||||
rlpOut.writeBytes(mockBytes32); |
||||
trie.put(mockBytes32, rlpOut.encoded()); |
||||
flatdb.putFlatAccountStorageValueByStorageSlotHash( |
||||
updater.getWorldStateTransaction(), |
||||
acctHash, |
||||
Hash.wrap(mockBytes32), |
||||
mockBytes32); |
||||
}); |
||||
trie.commit( |
||||
(location, key, value) -> |
||||
updater.putAccountStorageTrieNode(acctHash, location, key, value)); |
||||
updater.commit(); |
||||
return new SnapTestAccount( |
||||
acctHash, |
||||
new StateTrieAccountValue( |
||||
rand.nextInt(0, 1), Wei.of(rand.nextLong(0L, 1L)), |
||||
Hash.wrap(trie.getRootHash()), Hash.hash(mockCode)), |
||||
trie, |
||||
mockCode); |
||||
} |
||||
|
||||
void insertTestAccounts(final SnapTestAccount... accounts) { |
||||
final var updater = inMemoryStorage.updater(); |
||||
for (SnapTestAccount account : accounts) { |
||||
updater.putAccountInfoState(account.addressHash(), account.accountRLP()); |
||||
storageTrie.put(account.addressHash(), account.accountRLP()); |
||||
} |
||||
storageTrie.commit(updater::putAccountStateTrieNode); |
||||
updater.commit(); |
||||
} |
||||
|
||||
boolean assertIsValidAccountRangeProof( |
||||
final Hash startHash, final AccountRangeMessage.AccountRangeData accountRange) { |
||||
Bytes32 lastKey = |
||||
Optional.of(accountRange.accounts()) |
||||
.filter(z -> z.size() > 0) |
||||
.map(NavigableMap::lastKey) |
||||
.orElse(startHash); |
||||
|
||||
return proofProvider.isValidRangeProof( |
||||
startHash, |
||||
lastKey, |
||||
storageTrie.getRootHash(), |
||||
accountRange.proofs(), |
||||
accountRange.accounts()); |
||||
} |
||||
|
||||
boolean assertIsValidStorageProof( |
||||
final SnapTestAccount account, |
||||
final Hash startHash, |
||||
final NavigableMap<Bytes32, Bytes> slotRangeData, |
||||
final List<Bytes> proofs) { |
||||
|
||||
Bytes32 lastKey = |
||||
Optional.of(slotRangeData) |
||||
.filter(z -> z.size() > 0) |
||||
.map(NavigableMap::lastKey) |
||||
.orElse(startHash); |
||||
|
||||
// this is only working for single account ranges for now
|
||||
return proofProvider.isValidRangeProof( |
||||
startHash, lastKey, account.accountValue.getStorageRoot(), proofs, slotRangeData); |
||||
} |
||||
|
||||
AccountRangeMessage requestAccountRange(final Hash startHash, final Hash limitHash) { |
||||
return (AccountRangeMessage) |
||||
snapServer.constructGetAccountRangeResponse( |
||||
GetAccountRangeMessage.create( |
||||
Hash.wrap(storageTrie.getRootHash()), startHash, limitHash) |
||||
.wrapMessageData(BigInteger.ONE)); |
||||
} |
||||
|
||||
StorageRangeMessage requestStorageRange( |
||||
final List<Bytes32> accountHashes, final Hash startHash, final Hash limitHash) { |
||||
return (StorageRangeMessage) |
||||
snapServer.constructGetStorageRangeResponse( |
||||
GetStorageRangeMessage.create( |
||||
Hash.wrap(storageTrie.getRootHash()), accountHashes, startHash, limitHash) |
||||
.wrapMessageData(BigInteger.ONE)); |
||||
} |
||||
|
||||
TrieNodesMessage requestTrieNodes(final Bytes32 rootHash, final List<List<Bytes>> trieNodesList) { |
||||
return (TrieNodesMessage) |
||||
snapServer.constructGetTrieNodesResponse( |
||||
GetTrieNodesMessage.create(Hash.wrap(rootHash), trieNodesList) |
||||
.wrapMessageData(BigInteger.ONE)); |
||||
} |
||||
|
||||
ByteCodesMessage requestByteCodes(final List<Bytes32> codeHashes) { |
||||
return (ByteCodesMessage) |
||||
snapServer.constructGetBytecodesResponse( |
||||
GetByteCodesMessage.create(codeHashes).wrapMessageData(BigInteger.ONE)); |
||||
} |
||||
|
||||
AccountRangeMessage.AccountRangeData getAndVerifyAccountRangeData( |
||||
final AccountRangeMessage range, final int expectedSize) { |
||||
assertThat(range).isNotNull(); |
||||
var accountData = range.accountData(false); |
||||
assertThat(accountData).isNotNull(); |
||||
assertThat(accountData.accounts().size()).isEqualTo(expectedSize); |
||||
return accountData; |
||||
} |
||||
} |
@ -1,61 +0,0 @@ |
||||
/* |
||||
* 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.task; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthMessages; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthPeers; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; |
||||
import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; |
||||
import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager; |
||||
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; |
||||
import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
public class SnapProtocolManagerTestUtil { |
||||
|
||||
public static SnapProtocolManager create(final EthPeers ethPeers) { |
||||
return create( |
||||
BlockchainSetupUtil.forTesting(DataStorageFormat.FOREST).getWorldArchive(), ethPeers); |
||||
} |
||||
|
||||
public static SnapProtocolManager create( |
||||
final WorldStateArchive worldStateArchive, final EthPeers ethPeers) { |
||||
|
||||
EthMessages messages = new EthMessages(); |
||||
|
||||
return new SnapProtocolManager(Collections.emptyList(), ethPeers, messages, worldStateArchive); |
||||
} |
||||
|
||||
public static SnapProtocolManager create( |
||||
final WorldStateArchive worldStateArchive, |
||||
final EthPeers ethPeers, |
||||
final EthMessages snapMessages) { |
||||
return new SnapProtocolManager( |
||||
Collections.emptyList(), ethPeers, snapMessages, worldStateArchive); |
||||
} |
||||
|
||||
public static RespondingEthPeer createPeer( |
||||
final EthProtocolManager ethProtocolManager, |
||||
final SnapProtocolManager snapProtocolManager, |
||||
final long estimatedHeight) { |
||||
return RespondingEthPeer.builder() |
||||
.ethProtocolManager(ethProtocolManager) |
||||
.snapProtocolManager(snapProtocolManager) |
||||
.estimatedHeight(estimatedHeight) |
||||
.build(); |
||||
} |
||||
} |
Loading…
Reference in new issue