Skip to content

Commit

Permalink
Warp preparation (#573)
Browse files Browse the repository at this point in the history
* Add warp precompile preparation

* Update hash slice packing

* Remove unnecessary local var

* Add VM type assertion

* Enable Warp API by default
  • Loading branch information
aaronbuchwald authored Mar 16, 2023
1 parent 8f8ae38 commit 6f61b14
Show file tree
Hide file tree
Showing 16 changed files with 73 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
13 changes: 4 additions & 9 deletions core/predicate_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package core

import (
"errors"
"fmt"

"github.com/ava-labs/subnet-evm/core/types"
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
}
Expand All @@ -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{})
Expand All @@ -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)
}
}
Expand Down
3 changes: 1 addition & 2 deletions core/predicate_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down
1 change: 0 additions & 1 deletion params/precompile_upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion plugin/evm/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions plugin/evm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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}
Expand Down
5 changes: 3 additions & 2 deletions plugin/evm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ import (
)

var (
_ block.ChainVM = &VM{}
_ block.HeightIndexedChainVM = &VM{}
_ block.ChainVM = &VM{}
_ block.HeightIndexedChainVM = &VM{}
_ block.BuildBlockWithContextChainVM = &VM{}
)

const (
Expand Down
6 changes: 6 additions & 0 deletions precompile/precompileconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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.
Expand Down
17 changes: 16 additions & 1 deletion utils/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,26 @@ func IncrOne(bytes []byte) {
}
}

// HashSliceToBytes serializes a []common.Hash into a []byte
// 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 bytes
}

// 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 hashes
}
51 changes: 24 additions & 27 deletions utils/bytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
package utils

import (
"bytes"
"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) {
Expand Down Expand Up @@ -37,33 +40,27 @@ func TestIncrOne(t *testing.T) {
}
}

func TestHashSliceToBytes(t *testing.T) {
type test struct {
input []common.Hash
expected []byte
func testBytesToHashSlice(t testing.TB, b []byte) {
hashSlice := BytesToHashSlice(b)

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)
}
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 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)
})
}
9 changes: 4 additions & 5 deletions warp/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package warp

import (
"context"
"fmt"

"github.com/ava-labs/avalanchego/cache"
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
}
Expand Down
11 changes: 5 additions & 6 deletions warp/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package warp

import (
"context"
"testing"

"github.com/ava-labs/avalanchego/database/memdb"
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}

Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion warp/handlers/signature_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions warp/handlers/signature_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion warp/warp_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down

0 comments on commit 6f61b14

Please sign in to comment.