mirror of https://github.com/hyperledger/besu
Bonsai code storage by hash (#6505)
Add a storage mode for Bonsai that can store the code by hash instead of account hash Signed-off-by: Jason Frame <jason.frame@consensys.net>pull/6539/head
parent
559fe71cf3
commit
2ae6c739ee
@ -0,0 +1,54 @@ |
||||
/* |
||||
* 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.trie.bonsai.storage.flat; |
||||
|
||||
import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; |
||||
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public class AccountHashCodeStorageStrategy implements CodeStorageStrategy { |
||||
@Override |
||||
public Optional<Bytes> getFlatCode( |
||||
final Hash codeHash, final Hash accountHash, final SegmentedKeyValueStorage storage) { |
||||
return storage |
||||
.get(CODE_STORAGE, accountHash.toArrayUnsafe()) |
||||
.map(Bytes::wrap) |
||||
.filter(b -> Hash.hash(b).equals(codeHash)); |
||||
} |
||||
|
||||
@Override |
||||
public void putFlatCode( |
||||
final SegmentedKeyValueStorageTransaction transaction, |
||||
final Hash accountHash, |
||||
final Hash codeHash, |
||||
final Bytes code) { |
||||
transaction.put(CODE_STORAGE, accountHash.toArrayUnsafe(), code.toArrayUnsafe()); |
||||
} |
||||
|
||||
@Override |
||||
public void removeFlatCode( |
||||
final SegmentedKeyValueStorageTransaction transaction, |
||||
final Hash accountHash, |
||||
final Hash codeHash) { |
||||
transaction.remove(CODE_STORAGE, accountHash.toArrayUnsafe()); |
||||
} |
||||
} |
@ -0,0 +1,55 @@ |
||||
/* |
||||
* 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.trie.bonsai.storage.flat; |
||||
|
||||
import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; |
||||
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public class CodeHashCodeStorageStrategy implements CodeStorageStrategy { |
||||
|
||||
@Override |
||||
public Optional<Bytes> getFlatCode( |
||||
final Hash codeHash, final Hash accountHash, final SegmentedKeyValueStorage storage) { |
||||
return storage.get(CODE_STORAGE, codeHash.toArrayUnsafe()).map(Bytes::wrap); |
||||
} |
||||
|
||||
@Override |
||||
public void putFlatCode( |
||||
final SegmentedKeyValueStorageTransaction transaction, |
||||
final Hash accountHash, |
||||
final Hash codeHash, |
||||
final Bytes code) { |
||||
transaction.put(CODE_STORAGE, codeHash.toArrayUnsafe(), code.toArrayUnsafe()); |
||||
} |
||||
|
||||
@Override |
||||
public void removeFlatCode( |
||||
final SegmentedKeyValueStorageTransaction transaction, |
||||
final Hash accountHash, |
||||
final Hash codeHash) {} |
||||
|
||||
public static boolean isCodeHashValue(final byte[] key, final byte[] value) { |
||||
final Hash valueHash = Hash.hash(Bytes.wrap(value)); |
||||
return Bytes.wrap(key).equals(valueHash); |
||||
} |
||||
} |
@ -0,0 +1,41 @@ |
||||
/* |
||||
* 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.trie.bonsai.storage.flat; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; |
||||
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public interface CodeStorageStrategy { |
||||
|
||||
Optional<Bytes> getFlatCode( |
||||
final Hash codeHash, final Hash accountHash, final SegmentedKeyValueStorage storage); |
||||
|
||||
void putFlatCode( |
||||
final SegmentedKeyValueStorageTransaction transaction, |
||||
final Hash accountHash, |
||||
final Hash codeHash, |
||||
final Bytes code); |
||||
|
||||
void removeFlatCode( |
||||
final SegmentedKeyValueStorageTransaction transaction, |
||||
final Hash accountHash, |
||||
final Hash codeHash); |
||||
} |
@ -0,0 +1,143 @@ |
||||
/* |
||||
* 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.trie.bonsai.worldview; |
||||
|
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.verifyNoInteractions; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; |
||||
import org.hyperledger.besu.ethereum.trie.bonsai.BonsaiValue; |
||||
import org.hyperledger.besu.ethereum.trie.bonsai.storage.BonsaiWorldStateKeyValueStorage; |
||||
import org.hyperledger.besu.evm.internal.EvmConfiguration; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.Arguments; |
||||
import org.junit.jupiter.params.provider.MethodSource; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
||||
@ExtendWith(MockitoExtension.class) |
||||
class BonsaiWorldStateTest { |
||||
@Mock BonsaiWorldStateUpdateAccumulator bonsaiWorldStateUpdateAccumulator; |
||||
@Mock BonsaiWorldStateKeyValueStorage.BonsaiUpdater bonsaiUpdater; |
||||
@Mock Blockchain blockchain; |
||||
@Mock BonsaiWorldStateKeyValueStorage bonsaiWorldStateKeyValueStorage; |
||||
|
||||
private static final Bytes CODE = Bytes.of(10); |
||||
private static final Hash CODE_HASH = Hash.hash(CODE); |
||||
private static final Hash ACCOUNT_HASH = Hash.hash(Address.ZERO); |
||||
private static final Address ACCOUNT = Address.ZERO; |
||||
|
||||
private BonsaiWorldState worldState; |
||||
|
||||
@BeforeEach |
||||
void setup() { |
||||
worldState = |
||||
new BonsaiWorldState( |
||||
InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive(blockchain), |
||||
bonsaiWorldStateKeyValueStorage, |
||||
EvmConfiguration.DEFAULT); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("priorAndUpdatedEmptyAndNullBytes") |
||||
void codeUpdateDoesNothingWhenMarkedAsDeletedButAlreadyDeleted( |
||||
final Bytes prior, final Bytes updated) { |
||||
final Map<Address, BonsaiValue<Bytes>> codeToUpdate = |
||||
Map.of(Address.ZERO, new BonsaiValue<>(prior, updated)); |
||||
when(bonsaiWorldStateUpdateAccumulator.getCodeToUpdate()).thenReturn(codeToUpdate); |
||||
worldState.updateCode(Optional.of(bonsaiUpdater), bonsaiWorldStateUpdateAccumulator); |
||||
|
||||
verifyNoInteractions(bonsaiUpdater); |
||||
} |
||||
|
||||
@Test |
||||
void codeUpdateDoesNothingWhenAddingSameAsExistingValue() { |
||||
final Map<Address, BonsaiValue<Bytes>> codeToUpdate = |
||||
Map.of(Address.ZERO, new BonsaiValue<>(CODE, CODE)); |
||||
when(bonsaiWorldStateUpdateAccumulator.getCodeToUpdate()).thenReturn(codeToUpdate); |
||||
worldState.updateCode(Optional.of(bonsaiUpdater), bonsaiWorldStateUpdateAccumulator); |
||||
|
||||
verifyNoInteractions(bonsaiUpdater); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("emptyAndNullBytes") |
||||
void removesCodeWhenMarkedAsDeleted(final Bytes updated) { |
||||
final Map<Address, BonsaiValue<Bytes>> codeToUpdate = |
||||
Map.of(Address.ZERO, new BonsaiValue<>(CODE, updated)); |
||||
when(bonsaiWorldStateUpdateAccumulator.getCodeToUpdate()).thenReturn(codeToUpdate); |
||||
worldState.updateCode(Optional.of(bonsaiUpdater), bonsaiWorldStateUpdateAccumulator); |
||||
|
||||
verify(bonsaiUpdater).removeCode(ACCOUNT_HASH, CODE_HASH); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("codeValueAndEmptyAndNullBytes") |
||||
void addsCodeForNewCodeValue(final Bytes prior) { |
||||
final Map<Address, BonsaiValue<Bytes>> codeToUpdate = |
||||
Map.of(ACCOUNT, new BonsaiValue<>(prior, CODE)); |
||||
|
||||
when(bonsaiWorldStateUpdateAccumulator.getCodeToUpdate()).thenReturn(codeToUpdate); |
||||
worldState.updateCode(Optional.of(bonsaiUpdater), bonsaiWorldStateUpdateAccumulator); |
||||
|
||||
verify(bonsaiUpdater).putCode(ACCOUNT_HASH, CODE_HASH, CODE); |
||||
} |
||||
|
||||
@Test |
||||
void updateCodeForMultipleValues() { |
||||
final Map<Address, BonsaiValue<Bytes>> codeToUpdate = new HashMap<>(); |
||||
codeToUpdate.put(Address.fromHexString("0x1"), new BonsaiValue<>(null, CODE)); |
||||
codeToUpdate.put(Address.fromHexString("0x2"), new BonsaiValue<>(CODE, null)); |
||||
codeToUpdate.put(Address.fromHexString("0x3"), new BonsaiValue<>(Bytes.of(9), CODE)); |
||||
|
||||
when(bonsaiWorldStateUpdateAccumulator.getCodeToUpdate()).thenReturn(codeToUpdate); |
||||
worldState.updateCode(Optional.of(bonsaiUpdater), bonsaiWorldStateUpdateAccumulator); |
||||
|
||||
verify(bonsaiUpdater).putCode(Address.fromHexString("0x1").addressHash(), CODE_HASH, CODE); |
||||
verify(bonsaiUpdater).removeCode(Address.fromHexString("0x2").addressHash(), CODE_HASH); |
||||
verify(bonsaiUpdater).putCode(Address.fromHexString("0x3").addressHash(), CODE_HASH, CODE); |
||||
} |
||||
|
||||
private static Stream<Bytes> emptyAndNullBytes() { |
||||
return Stream.of(Bytes.EMPTY, null); |
||||
} |
||||
|
||||
private static Stream<Bytes> codeValueAndEmptyAndNullBytes() { |
||||
return Stream.of(Bytes.EMPTY, null); |
||||
} |
||||
|
||||
private static Stream<Arguments> priorAndUpdatedEmptyAndNullBytes() { |
||||
return Stream.of( |
||||
Arguments.of(null, Bytes.EMPTY), |
||||
Arguments.of(Bytes.EMPTY, null), |
||||
Arguments.of(null, null), |
||||
Arguments.of(Bytes.EMPTY, Bytes.EMPTY)); |
||||
} |
||||
} |
Loading…
Reference in new issue