diff --git a/ethereum/core/build.gradle b/ethereum/core/build.gradle index b616413dd22..d302031fedd 100644 --- a/ethereum/core/build.gradle +++ b/ethereum/core/build.gradle @@ -47,6 +47,7 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'com.google.guava:guava' + implementation 'com.github.ben-manes.caffeine:caffeine' implementation 'com.google.dagger:dagger' annotationProcessor 'com.google.dagger:dagger-compiler' implementation 'io.opentelemetry:opentelemetry-api' diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java index 6d1e84acb90..b04cbc85da4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.bonsai.cache; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiSnapshotWorldStateKeyValueStorage; @@ -38,6 +40,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.LongStream; import java.util.stream.Stream; @@ -52,6 +55,10 @@ public class CachedWorldStorageManager extends AbstractTrieLogManager private static final Logger LOG = LoggerFactory.getLogger(CachedWorldStorageManager.class); private final BonsaiWorldStateProvider archive; private final ObservableMetricsSystem metricsSystem; + private final Cache stateRootToBlockHashCache = Caffeine.newBuilder() + .maximumSize(512) + .expireAfterWrite(100, TimeUnit.MINUTES) + .build(); CachedWorldStorageManager( final BonsaiWorldStateProvider archive, @@ -128,6 +135,8 @@ public synchronized void addCachedLayer( blockHeader, ((BonsaiWorldStateLayerStorage) forWorldState.getWorldStateStorage()).clone())); } + // add stateroot -> blockHeader cache entry + stateRootToBlockHashCache.put(blockHeader.getStateRoot(), blockHeader.getBlockHash()); } scrubCachedLayers(blockHeader.getNumber()); } @@ -205,7 +214,7 @@ public Optional getHeadWorldState( @Override public Optional getStorageByRootHash(final Optional rootHash) { - return rootHash + return rootHash.map(stateRootToBlockHashCache::getIfPresent) .map(Optional::of) .orElseGet(rootWorldStateStorage::getWorldStateBlockHash) .map(cachedWorldStatesByHash::get); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java index 6a1c648b0b8..cceb86c076c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java @@ -74,14 +74,13 @@ class SnapServer { worldStateStorageProvider; SnapServer(final EthMessages snapMessages, final WorldStateArchive archive) { - this.snapMessages = snapMessages; - // TODO remove dirty bonsai cast: - this.worldStateStorageProvider = + this(snapMessages, rootHash -> + // TODO remove dirty bonsai cast: ((BonsaiWorldStateProvider) archive) .getTrieLogManager() .getStorageByRootHash(rootHash) - .map(CachedBonsaiWorldView::getWorldStateStorage); + .map(CachedBonsaiWorldView::getWorldStateStorage)); } SnapServer( @@ -90,6 +89,7 @@ class SnapServer { worldStateStorageProvider) { this.snapMessages = snapMessages; this.worldStateStorageProvider = worldStateStorageProvider; + registerResponseConstructors(); } private void registerResponseConstructors() { @@ -109,10 +109,12 @@ MessageData constructGetAccountRangeResponse(final MessageData message) { final int maxResponseBytes = Math.min(range.responseBytes().intValue(), MAX_RESPONSE_SIZE); // TODO: drop to TRACE - LOGGER.info( - "Receive get account range message from {} to {}", - range.startKeyHash().toHexString(), - range.endKeyHash().toHexString()); + LOGGER.atInfo() + .setMessage("Receive getAccountRangeMessage for {} from {} to {}") + .addArgument(range.worldStateRootHash()::toHexString) + .addArgument(range.startKeyHash()::toHexString) + .addArgument(range.endKeyHash()::toHexString) + .log(); var worldStateHash = getAccountRangeMessage.range(true).worldStateRootHash(); @@ -141,9 +143,15 @@ MessageData constructGetAccountRangeResponse(final MessageData message) { worldStateProof.getAccountProofRelatedNodes( range.worldStateRootHash(), Hash.wrap(accounts.lastKey()))); } - return AccountRangeMessage.create(accounts, proof); + var resp = AccountRangeMessage.create(accounts, proof); + LOGGER.info("returned message with {} accounts and {} proofs", + accounts.size(), proof.size()); + return resp; }) - .orElse(EMPTY_ACCOUNT_RANGE); + .orElseGet(() -> { + LOGGER.info("returned empty account range due to worldstate not present"); + return EMPTY_ACCOUNT_RANGE; + }); } MessageData constructGetStorageRangeResponse(final MessageData message) { @@ -211,10 +219,15 @@ MessageData constructGetStorageRangeResponse(final MessageData message) { .orElse(EMPTY_STORAGE_RANGE); } - private MessageData constructGetBytecodesResponse(final MessageData message) { + MessageData constructGetBytecodesResponse(final MessageData message) { // TODO implement once code is stored by hash final GetByteCodesMessage getByteCodesMessage = GetByteCodesMessage.readFrom(message); final GetByteCodesMessage.CodeHashes codeHashes = getByteCodesMessage.codeHashes(true); + // TODO: drop to TRACE + LOGGER.atInfo() + .setMessage("Receive get bytecodes message for {} hashes") + .addArgument(codeHashes.hashes()::size) + .log(); // there is no worldstate root or block header for us to use, so default to head. This // can cause problems for self-destructed contracts pre-shanghai. for now since this impl diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java index 92543f66687..3db3c62b65b 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java @@ -22,7 +22,9 @@ import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; 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; @@ -202,6 +204,19 @@ public void assertStorageTriePathRequest() { assertThat(trieNodes.size()).isEqualTo(4); } + @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); + } + static SnapTestAccount createTestAccount(final String hexAddr) { return new SnapTestAccount( Hash.wrap(Bytes32.rightPad(Bytes.fromHexString(hexAddr))), @@ -225,6 +240,7 @@ static SnapTestAccount createTestContractAccount( // mock some storage data var flatdb = storage.getFlatDbStrategy(); var updater = storage.updater(); + updater.putCode(Hash.hash(mockCode), mockCode); IntStream.range(10, 20) .boxed() .forEach( @@ -314,6 +330,13 @@ TrieNodesMessage requestTrieNodes(final Bytes32 rootHash, final List .wrapMessageData(BigInteger.ONE)); } + ByteCodesMessage requestByteCodes(final List codeHashes) { + return (ByteCodesMessage) + snapServer.constructGetBytecodesResponse(GetByteCodesMessage.create( + codeHashes) + .wrapMessageData(BigInteger.ONE)); + } + AccountRangeMessage.AccountRangeData getAndVerifyAcountRangeData( final AccountRangeMessage range, final int expectedSize) { assertThat(range).isNotNull();