From 7065d656c219f4ef03085b0519016000a1718e80 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Thu, 16 Mar 2023 12:19:25 -0400 Subject: [PATCH 1/5] Add warp precompile preparation --- .../precompile_module_template.go | 2 +- core/predicate_check.go | 13 +--- core/predicate_check_test.go | 3 +- core/state/statedb.go | 6 +- miner/worker.go | 4 +- params/precompile_upgrade.go | 1 - plugin/evm/block.go | 2 +- precompile/precompileconfig/config.go | 6 ++ utils/bytes.go | 74 +++++++++++++++++-- utils/bytes_test.go | 45 +++++------ warp/backend.go | 9 +-- warp/backend_test.go | 11 ++- warp/handlers/signature_request.go | 2 +- warp/handlers/signature_request_test.go | 4 +- warp/warp_service.go | 2 +- 15 files changed, 117 insertions(+), 67 deletions(-) diff --git a/accounts/abi/bind/precompilebind/precompile_module_template.go b/accounts/abi/bind/precompilebind/precompile_module_template.go index 6a7b27dc92..541af9f0ad 100644 --- a/accounts/abi/bind/precompilebind/precompile_module_template.go +++ b/accounts/abi/bind/precompilebind/precompile_module_template.go @@ -28,7 +28,7 @@ const ConfigKey = "{{decapitalise .Contract.Type}}Config" // ContractAddress is the defined address of the precompile contract. // This should be unique across all precompile contracts. -// See params/precompile_modules.go for registered precompile contracts and more information. +// See precompile/registry/registry.go for registered precompile contracts and more information. var ContractAddress = common.HexToAddress("{ASUITABLEHEXADDRESS}") // SET A SUITABLE HEX ADDRESS HERE // Module is the precompile module. It is used to register the precompile contract. diff --git a/core/predicate_check.go b/core/predicate_check.go index 1fbf3614d1..4618b80d76 100644 --- a/core/predicate_check.go +++ b/core/predicate_check.go @@ -4,7 +4,6 @@ package core import ( - "errors" "fmt" "github.com/ava-labs/subnet-evm/core/types" @@ -14,8 +13,6 @@ import ( "github.com/ethereum/go-ethereum/common" ) -var errNilProposerVMBlockCtxWithProposerPredicate = errors.New("engine cannot specify nil ProposerVM block context with non-empty proposer predicates") - // CheckPredicates checks that all precompile predicates are satisfied within the current [predicateContext] for [tx] func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.ProposerPredicateContext, tx *types.Transaction) error { if err := checkPrecompilePredicates(rules, &predicateContext.PrecompilePredicateContext, tx); err != nil { @@ -43,7 +40,8 @@ func checkPrecompilePredicates(rules params.Rules, predicateContext *precompilec return fmt.Errorf("predicate %s failed verification for tx %s: specified %s in access list multiple times", address, tx.Hash(), address) } precompileAddressChecks[address] = struct{}{} - if err := predicater.VerifyPredicate(predicateContext, utils.HashSliceToBytes(accessTuple.StorageKeys)); err != nil { + predicateBytes, _ := utils.HashSliceToBytes(accessTuple.StorageKeys) + if err := predicater.VerifyPredicate(predicateContext, predicateBytes); err != nil { return fmt.Errorf("predicate %s failed verification for tx %s: %w", address, tx.Hash(), err) } } @@ -56,10 +54,6 @@ func checkProposerPrecompilePredicates(rules params.Rules, predicateContext *pre if len(rules.ProposerPredicates) == 0 { return nil } - // If a proposer predicate is specified, reuqire that the ProposerVMBlockCtx is non-nil. - if predicateContext.ProposerVMBlockCtx == nil { - return errNilProposerVMBlockCtxWithProposerPredicate - } precompilePredicates := rules.ProposerPredicates // Track addresses that we've performed a predicate check for precompileAddressChecks := make(map[common.Address]struct{}) @@ -74,7 +68,8 @@ func checkProposerPrecompilePredicates(rules params.Rules, predicateContext *pre return fmt.Errorf("predicate %s failed verification for tx %s: specified %s in access list multiple times", address, tx.Hash(), address) } precompileAddressChecks[address] = struct{}{} - if err := predicater.VerifyPredicate(predicateContext, utils.HashSliceToBytes(accessTuple.StorageKeys)); err != nil { + predicateBytes, _ := utils.HashSliceToBytes(accessTuple.StorageKeys) + if err := predicater.VerifyPredicate(predicateContext, predicateBytes); err != nil { return fmt.Errorf("predicate %s failed verification for tx %s: %w", address, tx.Hash(), err) } } diff --git a/core/predicate_check_test.go b/core/predicate_check_test.go index 76bc9ce1cf..0718159c8f 100644 --- a/core/predicate_check_test.go +++ b/core/predicate_check_test.go @@ -148,11 +148,10 @@ func TestCheckPredicate(t *testing.T) { }), expectedErr: fmt.Errorf("unexpected bytes: 0x%x", common.Hash{2}.Bytes()), }, - "proposer predicate with empty proposer block ctx": { + "proposer predicate with empty proposer block ctx passes": { address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"), proposerPredicater: &mockProposerPredicater{predicateFunc: func(_ *precompileconfig.ProposerPredicateContext, b []byte) error { return nil }}, emptyProposerBlockCtx: true, - expectedErr: errNilProposerVMBlockCtxWithProposerPredicate, }, } { test := test diff --git a/core/state/statedb.go b/core/state/statedb.go index bfbc29dd10..d3ea6baec0 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1097,7 +1097,11 @@ func (s *StateDB) preparePredicateStorageSlots(rules params.Rules, list types.Ac if !rules.PredicateExists(el.Address) { continue } - s.predicateStorageSlots[el.Address] = utils.HashSliceToBytes(el.StorageKeys) + b, exists := utils.HashSliceToBytes(el.StorageKeys) + if !exists { + continue + } + s.predicateStorageSlots[el.Address] = b } } diff --git a/miner/worker.go b/miner/worker.go index 3d4b978eb4..e74c7b61da 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -368,7 +368,7 @@ func (w *worker) handleResult(env *environment, block *types.Block, createdAt ti logs = append(logs, receipt.Logs...) } - log.Info("Commit new mining work", "number", block.Number(), "hash", hash, "uncles", 0, "txs", env.tcount, + log.Info("Commit new mining work", "number", block.Number(), "hash", hash, "timestamp", block.Time(), "uncles", 0, "txs", env.tcount, "gas", block.GasUsed(), "fees", totalFees(block, receipts), "elapsed", common.PrettyDuration(time.Since(env.start))) // Note: the miner no longer emits a NewMinedBlock event. Instead the caller @@ -409,7 +409,7 @@ func (w *worker) enforcePredicates( ) map[common.Address]types.Transactions { // Short circuit early if there are no precompile predicates to verify and return the // unmodified pending transactions. - if len(rules.PredicatePrecompiles) == 0 { + if !rules.PredicatesExist() { return pending } result := make(map[common.Address]types.Transactions, len(pending)) diff --git a/params/precompile_upgrade.go b/params/precompile_upgrade.go index e8346174bf..8b346d91ab 100644 --- a/params/precompile_upgrade.go +++ b/params/precompile_upgrade.go @@ -27,7 +27,6 @@ type PrecompileUpgrade struct { // UnmarshalJSON unmarshals the json into the correct precompile config type // based on the key. Keys are defined in each precompile module, and registered in -// params/precompile_modules.go. // precompile/registry/registry.go. // Ex: {"feeManagerConfig": {...}} where "feeManagerConfig" is the key func (u *PrecompileUpgrade) UnmarshalJSON(data []byte) error { diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 6384c7934b..51e9ce6c3d 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -89,7 +89,6 @@ func (b *Block) Accept(context.Context) error { // contract.Accepter // This function assumes that the Accept function will ONLY operate on state maintained in the VM's versiondb. // This ensures that any DB operations are performed atomically with marking the block as accepted. -// Passes in sharedMemoryWriter to accumulate any requests from shared memory to commit on block accept. func (b *Block) handlePrecompileAccept(sharedMemoryWriter *sharedMemoryWriter) error { rules := b.vm.chainConfig.AvalancheRules(b.ethBlock.Number(), b.ethBlock.Timestamp()) // Short circuit early if there are no precompile accepters to execute @@ -113,6 +112,7 @@ func (b *Block) handlePrecompileAccept(sharedMemoryWriter *sharedMemoryWriter) e acceptCtx := &precompileconfig.AcceptContext{ SnowCtx: b.vm.ctx, SharedMemory: sharedMemoryWriter, + Warp: b.vm.warpBackend, } if err := accepter.Accept(acceptCtx, log.TxHash, txIndex, log.Topics, log.Data); err != nil { return err diff --git a/precompile/precompileconfig/config.go b/precompile/precompileconfig/config.go index 41576c4588..4282c1c62b 100644 --- a/precompile/precompileconfig/config.go +++ b/precompile/precompileconfig/config.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ethereum/go-ethereum/common" ) @@ -76,10 +77,15 @@ type SharedMemoryWriter interface { AddSharedMemoryRequests(chainID ids.ID, requests *atomic.Requests) } +type WarpMessageWriter interface { + AddMessage(unsignedMessage *warp.UnsignedMessage) error +} + // AcceptContext defines the context passed in to a precompileconfig's Accepter type AcceptContext struct { SnowCtx *snow.Context SharedMemory SharedMemoryWriter + Warp WarpMessageWriter } // Accepter is an optional interface for StatefulPrecompiledContracts to implement. diff --git a/utils/bytes.go b/utils/bytes.go index ec7032f711..6cb206caae 100644 --- a/utils/bytes.go +++ b/utils/bytes.go @@ -3,7 +3,11 @@ package utils -import "github.com/ethereum/go-ethereum/common" +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" +) // IncrOne increments bytes value by one func IncrOne(bytes []byte) { @@ -19,11 +23,67 @@ func IncrOne(bytes []byte) { } } -// HashSliceToBytes serializes a []common.Hash into a []byte -func HashSliceToBytes(hashes []common.Hash) []byte { - bytes := make([]byte, common.HashLength*len(hashes)) - for i, hash := range hashes { - copy(bytes[i*common.HashLength:], hash[:]) +const hashSlicePrefixByte byte = 0xff + +// zeroStrippedSlice returns a sub-slice of input with all leading zero bytes stripped +func zeroStrippedSlice(input []byte) []byte { + zeroStrippedBytes := input + for i, b := range zeroStrippedBytes { + if b != 0 { + return zeroStrippedBytes[i:] + } + } + return zeroStrippedBytes +} + +// HashSliceToBytes serializes a []common.Hash into a byte slice +// Strips all zero padding from the first hash and [hashSlicePrefixByte] to +// confirm that it has been encoded correctly. +func HashSliceToBytes(hashes []common.Hash) ([]byte, bool) { + if len(hashes) == 0 { + return nil, false } - return bytes + + zeroStrippedBytes := zeroStrippedSlice(hashes[0][:]) + + prefixStrippedBytes, hasPrefix := bytes.CutPrefix(zeroStrippedBytes, []byte{hashSlicePrefixByte}) + if !hasPrefix { + return nil, false + } + + copiedBytes := make([]byte, len(prefixStrippedBytes)+common.HashLength*(len(hashes)-1)) + copy(copiedBytes, prefixStrippedBytes) + offset := len(prefixStrippedBytes) + for _, hash := range hashes[1:] { + copy(copiedBytes[offset:], hash[:]) + offset += common.HashLength + } + return copiedBytes, true +} + +// BytesToHashSlice packs input into a slice of hashes. +// Packs with zero padding and a prefix of hashSlicePrefixByte to +// indicate the start of the actual bytes. +func BytesToHashSlice(input []byte) []common.Hash { + var output []common.Hash + + // Calculate the number of bytes to add for zero padding at the beginning + totalLen := (len(input) + 1 + 31) / 32 * 32 + paddingLen := totalLen - (len(input) + 1) + + // Create a new slice with the padded bytes at the beginning + paddedInput := make([]byte, totalLen) + paddedInput[paddingLen] = hashSlicePrefixByte + copy(paddedInput[paddingLen+1:], input) + + // Loop through the input bytes + for i := 0; i < len(paddedInput); i += common.HashLength { + hash := common.Hash{} + copy(hash[:], paddedInput[i:i+common.HashLength]) + + // Add the padded block to the output + output = append(output, hash) + } + + return output } diff --git a/utils/bytes_test.go b/utils/bytes_test.go index 86f9299ccc..5262bbc391 100644 --- a/utils/bytes_test.go +++ b/utils/bytes_test.go @@ -6,8 +6,10 @@ package utils import ( "testing" + "github.com/ava-labs/avalanchego/utils" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestIncrOne(t *testing.T) { @@ -37,33 +39,20 @@ func TestIncrOne(t *testing.T) { } } -func TestHashSliceToBytes(t *testing.T) { - type test struct { - input []common.Hash - expected []byte - } - for name, test := range map[string]test{ - "empty slice": { - input: []common.Hash{}, - expected: []byte{}, - }, - "convert single hash": { - input: []common.Hash{ - common.BytesToHash([]byte{1, 2, 3}), - }, - expected: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3}, - }, - "convert hash slice": { - input: []common.Hash{ - common.BytesToHash([]byte{1, 2, 3}), - common.BytesToHash([]byte{4, 5, 6}), - }, - expected: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6}, - }, - } { - t.Run(name, func(t *testing.T) { - output := HashSliceToBytes(test.input) - assert.Equal(t, output, test.expected) - }) +func testBytesToHashSlice(t testing.TB, b []byte) { + hashSlice := BytesToHashSlice(b) + + copiedBytes, ok := HashSliceToBytes(hashSlice) + require.True(t, ok) + require.Equal(t, b, copiedBytes) +} + +func FuzzHashSliceToBytes(f *testing.F) { + for i := 0; i < 100; i++ { + f.Add(utils.RandomBytes(i)) } + + f.Fuzz(func(t *testing.T, a []byte) { + testBytesToHashSlice(t, a) + }) } diff --git a/warp/backend.go b/warp/backend.go index ed22f7d655..b9df23502a 100644 --- a/warp/backend.go +++ b/warp/backend.go @@ -4,7 +4,6 @@ package warp import ( - "context" "fmt" "github.com/ava-labs/avalanchego/cache" @@ -22,10 +21,10 @@ var _ WarpBackend = &warpBackend{} // The backend is also used to query for warp message signatures by the signature request handler. type WarpBackend interface { // AddMessage signs [unsignedMessage] and adds it to the warp backend database - AddMessage(ctx context.Context, unsignedMessage *avalancheWarp.UnsignedMessage) error + AddMessage(unsignedMessage *avalancheWarp.UnsignedMessage) error // GetSignature returns the signature of the requested message hash. - GetSignature(ctx context.Context, messageHash ids.ID) ([bls.SignatureLen]byte, error) + GetSignature(messageHash ids.ID) ([bls.SignatureLen]byte, error) } // warpBackend implements WarpBackend, keeps track of warp messages, and generates message signatures. @@ -44,7 +43,7 @@ func NewWarpBackend(snowCtx *snow.Context, db database.Database, signatureCacheS } } -func (w *warpBackend) AddMessage(ctx context.Context, unsignedMessage *avalancheWarp.UnsignedMessage) error { +func (w *warpBackend) AddMessage(unsignedMessage *avalancheWarp.UnsignedMessage) error { messageID := hashing.ComputeHash256Array(unsignedMessage.Bytes()) // In the case when a node restarts, and possibly changes its bls key, the cache gets emptied but the database does not. @@ -65,7 +64,7 @@ func (w *warpBackend) AddMessage(ctx context.Context, unsignedMessage *avalanche return nil } -func (w *warpBackend) GetSignature(ctx context.Context, messageID ids.ID) ([bls.SignatureLen]byte, error) { +func (w *warpBackend) GetSignature(messageID ids.ID) ([bls.SignatureLen]byte, error) { if sig, ok := w.signatureCache.Get(messageID); ok { return sig, nil } diff --git a/warp/backend_test.go b/warp/backend_test.go index 65e8b317f8..a131bf5e85 100644 --- a/warp/backend_test.go +++ b/warp/backend_test.go @@ -4,7 +4,6 @@ package warp import ( - "context" "testing" "github.com/ava-labs/avalanchego/database/memdb" @@ -34,12 +33,12 @@ func TestAddAndGetValidMessage(t *testing.T) { // Create a new unsigned message and add it to the warp backend. unsignedMsg, err := avalancheWarp.NewUnsignedMessage(sourceChainID, destinationChainID, payload) require.NoError(t, err) - err = backend.AddMessage(context.Background(), unsignedMsg) + err = backend.AddMessage(unsignedMsg) require.NoError(t, err) // Verify that a signature is returned successfully, and compare to expected signature. messageID := hashing.ComputeHash256Array(unsignedMsg.Bytes()) - signature, err := backend.GetSignature(context.Background(), messageID) + signature, err := backend.GetSignature(messageID) require.NoError(t, err) expectedSig, err := snowCtx.WarpSigner.Sign(unsignedMsg) @@ -56,7 +55,7 @@ func TestAddAndGetUnknownMessage(t *testing.T) { // Try getting a signature for a message that was not added. messageID := hashing.ComputeHash256Array(unsignedMsg.Bytes()) - _, err = backend.GetSignature(context.Background(), messageID) + _, err = backend.GetSignature(messageID) require.Error(t, err) } @@ -74,12 +73,12 @@ func TestZeroSizedCache(t *testing.T) { // Create a new unsigned message and add it to the warp backend. unsignedMsg, err := avalancheWarp.NewUnsignedMessage(sourceChainID, destinationChainID, payload) require.NoError(t, err) - err = backend.AddMessage(context.Background(), unsignedMsg) + err = backend.AddMessage(unsignedMsg) require.NoError(t, err) // Verify that a signature is returned successfully, and compare to expected signature. messageID := hashing.ComputeHash256Array(unsignedMsg.Bytes()) - signature, err := backend.GetSignature(context.Background(), messageID) + signature, err := backend.GetSignature(messageID) require.NoError(t, err) expectedSig, err := snowCtx.WarpSigner.Sign(unsignedMsg) diff --git a/warp/handlers/signature_request.go b/warp/handlers/signature_request.go index 6cf28efd74..a0198509fa 100644 --- a/warp/handlers/signature_request.go +++ b/warp/handlers/signature_request.go @@ -50,7 +50,7 @@ func (s *signatureRequestHandler) OnSignatureRequest(ctx context.Context, nodeID s.stats.UpdateSignatureRequestTime(time.Since(startTime)) }() - signature, err := s.backend.GetSignature(ctx, signatureRequest.MessageID) + signature, err := s.backend.GetSignature(signatureRequest.MessageID) if err != nil { log.Debug("Unknown warp signature requested", "messageID", signatureRequest.MessageID) s.stats.IncSignatureMiss() diff --git a/warp/handlers/signature_request_test.go b/warp/handlers/signature_request_test.go index 4c47ead388..a986deb80a 100644 --- a/warp/handlers/signature_request_test.go +++ b/warp/handlers/signature_request_test.go @@ -33,8 +33,8 @@ func TestSignatureHandler(t *testing.T) { require.NoError(t, err) messageID := hashing.ComputeHash256Array(msg.Bytes()) - require.NoError(t, warpBackend.AddMessage(context.Background(), msg)) - signature, err := warpBackend.GetSignature(context.Background(), messageID) + require.NoError(t, warpBackend.AddMessage(msg)) + signature, err := warpBackend.GetSignature(messageID) require.NoError(t, err) unknownMessageID := ids.GenerateTestID() diff --git a/warp/warp_service.go b/warp/warp_service.go index 3e2a003207..329088307e 100644 --- a/warp/warp_service.go +++ b/warp/warp_service.go @@ -18,7 +18,7 @@ type WarpAPI struct { // GetSignature returns the BLS signature associated with a messageID. func (api *WarpAPI) GetSignature(ctx context.Context, messageID ids.ID) (hexutil.Bytes, error) { - signature, err := api.Backend.GetSignature(ctx, messageID) + signature, err := api.Backend.GetSignature(messageID) if err != nil { return nil, fmt.Errorf("failed to get signature for with error %w", err) } From 5f365673ec1263baf1d767629dcedf762b56be1b Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Thu, 16 Mar 2023 12:49:50 -0400 Subject: [PATCH 2/5] Update hash slice packing --- core/predicate_check.go | 4 +- core/state/statedb.go | 5 +-- utils/bytes.go | 83 ++++++++++------------------------------- utils/bytes_test.go | 14 +++++-- 4 files changed, 33 insertions(+), 73 deletions(-) diff --git a/core/predicate_check.go b/core/predicate_check.go index 4618b80d76..faa9b801d7 100644 --- a/core/predicate_check.go +++ b/core/predicate_check.go @@ -40,7 +40,7 @@ func checkPrecompilePredicates(rules params.Rules, predicateContext *precompilec return fmt.Errorf("predicate %s failed verification for tx %s: specified %s in access list multiple times", address, tx.Hash(), address) } precompileAddressChecks[address] = struct{}{} - predicateBytes, _ := utils.HashSliceToBytes(accessTuple.StorageKeys) + predicateBytes := utils.HashSliceToBytes(accessTuple.StorageKeys) if err := predicater.VerifyPredicate(predicateContext, predicateBytes); err != nil { return fmt.Errorf("predicate %s failed verification for tx %s: %w", address, tx.Hash(), err) } @@ -68,7 +68,7 @@ func checkProposerPrecompilePredicates(rules params.Rules, predicateContext *pre return fmt.Errorf("predicate %s failed verification for tx %s: specified %s in access list multiple times", address, tx.Hash(), address) } precompileAddressChecks[address] = struct{}{} - predicateBytes, _ := utils.HashSliceToBytes(accessTuple.StorageKeys) + predicateBytes := utils.HashSliceToBytes(accessTuple.StorageKeys) if err := predicater.VerifyPredicate(predicateContext, predicateBytes); err != nil { return fmt.Errorf("predicate %s failed verification for tx %s: %w", address, tx.Hash(), err) } diff --git a/core/state/statedb.go b/core/state/statedb.go index d3ea6baec0..7473920a71 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1097,10 +1097,7 @@ func (s *StateDB) preparePredicateStorageSlots(rules params.Rules, list types.Ac if !rules.PredicateExists(el.Address) { continue } - b, exists := utils.HashSliceToBytes(el.StorageKeys) - if !exists { - continue - } + b := utils.HashSliceToBytes(el.StorageKeys) s.predicateStorageSlots[el.Address] = b } } diff --git a/utils/bytes.go b/utils/bytes.go index 6cb206caae..54258b20f4 100644 --- a/utils/bytes.go +++ b/utils/bytes.go @@ -3,11 +3,7 @@ package utils -import ( - "bytes" - - "github.com/ethereum/go-ethereum/common" -) +import "github.com/ethereum/go-ethereum/common" // IncrOne increments bytes value by one func IncrOne(bytes []byte) { @@ -23,67 +19,26 @@ func IncrOne(bytes []byte) { } } -const hashSlicePrefixByte byte = 0xff - -// zeroStrippedSlice returns a sub-slice of input with all leading zero bytes stripped -func zeroStrippedSlice(input []byte) []byte { - zeroStrippedBytes := input - for i, b := range zeroStrippedBytes { - if b != 0 { - return zeroStrippedBytes[i:] - } - } - return zeroStrippedBytes -} - -// HashSliceToBytes serializes a []common.Hash into a byte slice -// Strips all zero padding from the first hash and [hashSlicePrefixByte] to -// confirm that it has been encoded correctly. -func HashSliceToBytes(hashes []common.Hash) ([]byte, bool) { - if len(hashes) == 0 { - return nil, false - } - - zeroStrippedBytes := zeroStrippedSlice(hashes[0][:]) - - prefixStrippedBytes, hasPrefix := bytes.CutPrefix(zeroStrippedBytes, []byte{hashSlicePrefixByte}) - if !hasPrefix { - return nil, false - } - - copiedBytes := make([]byte, len(prefixStrippedBytes)+common.HashLength*(len(hashes)-1)) - copy(copiedBytes, prefixStrippedBytes) - offset := len(prefixStrippedBytes) - for _, hash := range hashes[1:] { - copy(copiedBytes[offset:], hash[:]) - offset += common.HashLength +// HashSliceToBytes serializes a []common.Hash into a tightly packed byte array. +func HashSliceToBytes(hashes []common.Hash) []byte { + bytes := make([]byte, common.HashLength*len(hashes)) + for i, hash := range hashes { + copy(bytes[i*common.HashLength:], hash[:]) } - return copiedBytes, true + return bytes } -// BytesToHashSlice packs input into a slice of hashes. -// Packs with zero padding and a prefix of hashSlicePrefixByte to -// indicate the start of the actual bytes. -func BytesToHashSlice(input []byte) []common.Hash { - var output []common.Hash - - // Calculate the number of bytes to add for zero padding at the beginning - totalLen := (len(input) + 1 + 31) / 32 * 32 - paddingLen := totalLen - (len(input) + 1) - - // Create a new slice with the padded bytes at the beginning - paddedInput := make([]byte, totalLen) - paddedInput[paddingLen] = hashSlicePrefixByte - copy(paddedInput[paddingLen+1:], input) - - // Loop through the input bytes - for i := 0; i < len(paddedInput); i += common.HashLength { - hash := common.Hash{} - copy(hash[:], paddedInput[i:i+common.HashLength]) - - // Add the padded block to the output - output = append(output, hash) +// BytesToHashSlice packs [b] into a slice of hash values with zero padding +// to the right if the length of b is not a multiple of 32. +func BytesToHashSlice(b []byte) []common.Hash { + var ( + numHashes = (len(b) + 31) / 32 + hashes = make([]common.Hash, numHashes) + ) + + for i := range hashes { + start := i * common.HashLength + copy(hashes[i][:], b[start:]) } - - return output + return hashes } diff --git a/utils/bytes_test.go b/utils/bytes_test.go index 5262bbc391..7f3619c16a 100644 --- a/utils/bytes_test.go +++ b/utils/bytes_test.go @@ -4,6 +4,7 @@ package utils import ( + "bytes" "testing" "github.com/ava-labs/avalanchego/utils" @@ -42,9 +43,16 @@ func TestIncrOne(t *testing.T) { func testBytesToHashSlice(t testing.TB, b []byte) { hashSlice := BytesToHashSlice(b) - copiedBytes, ok := HashSliceToBytes(hashSlice) - require.True(t, ok) - require.Equal(t, b, copiedBytes) + copiedBytes := HashSliceToBytes(hashSlice) + + if len(b)%32 == 0 { + require.Equal(t, b, copiedBytes) + } else { + require.Equal(t, b, copiedBytes[:len(b)]) + // Require that any additional padding is all zeroes + padding := copiedBytes[len(b):] + require.Equal(t, bytes.Repeat([]byte{0x00}, len(padding)), padding) + } } func FuzzHashSliceToBytes(f *testing.F) { From 37c3138fe2e025810ad10caf77750cdf9441e7bb Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Thu, 16 Mar 2023 12:51:49 -0400 Subject: [PATCH 3/5] Remove unnecessary local var --- core/state/statedb.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 7473920a71..bfbc29dd10 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1097,8 +1097,7 @@ func (s *StateDB) preparePredicateStorageSlots(rules params.Rules, list types.Ac if !rules.PredicateExists(el.Address) { continue } - b := utils.HashSliceToBytes(el.StorageKeys) - s.predicateStorageSlots[el.Address] = b + s.predicateStorageSlots[el.Address] = utils.HashSliceToBytes(el.StorageKeys) } } From ac5a82804164c04f638000b4c2d9d061c7429f32 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Thu, 16 Mar 2023 13:34:13 -0400 Subject: [PATCH 4/5] Add VM type assertion --- plugin/evm/vm.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 92426d3477..2ef050b4d0 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -77,8 +77,9 @@ import ( ) var ( - _ block.ChainVM = &VM{} - _ block.HeightIndexedChainVM = &VM{} + _ block.ChainVM = &VM{} + _ block.HeightIndexedChainVM = &VM{} + _ block.BuildBlockWithContextChainVM = &VM{} ) const ( From 8d626834768130fd6319b66573c8699207c3fd3e Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Thu, 16 Mar 2023 13:34:30 -0400 Subject: [PATCH 5/5] Enable Warp API by default --- plugin/evm/config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/evm/config.go b/plugin/evm/config.go index 6abaed1093..86cd6cf37a 100644 --- a/plugin/evm/config.go +++ b/plugin/evm/config.go @@ -47,6 +47,7 @@ const ( defaultPopulateMissingTriesParallelism = 1024 defaultStateSyncServerTrieCache = 64 // MB defaultAcceptedCacheSize = 32 // blocks + defaultWarpAPIEnabled = true // defaultStateSyncMinBlocks is the minimum number of blocks the blockchain // should be ahead of local last accepted to perform state sync. @@ -224,6 +225,7 @@ func (c *Config) SetDefaults() { c.RPCGasCap = defaultRpcGasCap c.RPCTxFeeCap = defaultRpcTxFeeCap c.MetricsExpensiveEnabled = defaultMetricsExpensiveEnabled + c.WarpAPIEnabled = defaultWarpAPIEnabled c.TxPoolJournal = core.DefaultTxPoolConfig.Journal c.TxPoolRejournal = Duration{core.DefaultTxPoolConfig.Rejournal}