Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,4 @@ cmd/simulator/.simulator/*
# goreleaser
dist/

# generator rpc file for e2e tests
contract-examples/dynamic_rpc.json
node_modules
1 change: 1 addition & 0 deletions accounts/abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
// Returns the topics for the event including the event signature (if non-anonymous event) and
// hashes derived from indexed arguments and the packed data of non-indexed args according to
// the event ABI specification.
// The order of arguments must match the order of the event definition.
// https://docs.soliditylang.org/en/v0.8.17/abi-spec.html#indexed-event-encoding.
// Note: PackEvent does not support array (fixed or dynamic-size) or struct types.
func (abi ABI) PackEvent(name string, args ...interface{}) ([]common.Hash, []byte, error) {
Expand Down
2 changes: 1 addition & 1 deletion core/predicate_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@ func checkProposerPrecompilePredicates(rules params.Rules, predicateContext *pre
}

return nil
}
}
43 changes: 22 additions & 21 deletions core/predicate_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,34 @@ import (
"github.com/stretchr/testify/require"
)

var (
var (
_ precompileconfig.PrecompilePredicater = (*mockPredicater)(nil)
_ precompileconfig.ProposerPredicater = (*mockProposerPredicater)(nil)
)
_ precompileconfig.ProposerPredicater = (*mockProposerPredicater)(nil)
)

type mockPredicater struct {
type mockPredicater struct {
predicateFunc func(*precompileconfig.PrecompilePredicateContext, []byte) error
}

func (m *mockPredicater) VerifyPredicate(predicateContext *precompileconfig.PrecompilePredicateContext, b []byte) error { return m.predicateFunc(predicateContext, b) }

func (m *mockPredicater) VerifyPredicate(predicateContext *precompileconfig.PrecompilePredicateContext, b []byte) error {
return m.predicateFunc(predicateContext, b)
}

type mockProposerPredicater struct {
predicateFunc func(*precompileconfig.ProposerPredicateContext, []byte) error
}

func (m *mockProposerPredicater) VerifyPredicate(predicateContext *precompileconfig.ProposerPredicateContext, b []byte) error { return m.predicateFunc(predicateContext, b) }


func (m *mockProposerPredicater) VerifyPredicate(predicateContext *precompileconfig.ProposerPredicateContext, b []byte) error {
return m.predicateFunc(predicateContext, b)
}

type predicateCheckTest struct {
address common.Address
predicater precompileconfig.PrecompilePredicater
proposerPredicater precompileconfig.ProposerPredicater
accessList types.AccessList
address common.Address
predicater precompileconfig.PrecompilePredicater
proposerPredicater precompileconfig.ProposerPredicater
accessList types.AccessList
proposerPredicateContext precompileconfig.ProposerPredicateContext
expectedErr error
expectedErr error
}

func TestCheckPredicate(t *testing.T) {
Expand All @@ -61,13 +62,13 @@ func TestCheckPredicate(t *testing.T) {
expectedErr: nil,
},
"proposer predicate, no access list passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
proposerPredicater: &mockProposerPredicater{predicateFunc: func(*precompileconfig.ProposerPredicateContext, []byte) error { return nil }},
expectedErr: nil,
expectedErr: nil,
},
"predicate, no access list passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
predicater: &mockPredicater{predicateFunc: func(*precompileconfig.PrecompilePredicateContext, []byte) error { return nil }},
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
predicater: &mockPredicater{predicateFunc: func(*precompileconfig.PrecompilePredicateContext, []byte) error { return nil }},
expectedErr: nil,
},
"predicate with valid access list passes": {
Expand All @@ -78,7 +79,7 @@ func TestCheckPredicate(t *testing.T) {
} else {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
}},
}},
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
Expand Down Expand Up @@ -116,7 +117,7 @@ func TestCheckPredicate(t *testing.T) {
} else {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
}},
}},
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
Expand Down Expand Up @@ -157,7 +158,7 @@ func TestCheckPredicate(t *testing.T) {
if test.predicater != nil {
rules.PredicatePrecompiles[test.address] = test.predicater
}

// Specify only the access list, since this test should not depend on any other values
tx := types.NewTx(&types.DynamicFeeTx{
AccessList: test.accessList,
Expand Down
24 changes: 20 additions & 4 deletions plugin/evm/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,35 @@ func (b *Block) Accept(context.Context) error {
if err := vm.blockChain.Accept(b.ethBlock); err != nil {
return fmt.Errorf("chain could not accept %s: %w", b.ID(), err)
}

// Call Accept for relevant precompile logs. This should apply DB operations to the VM's versionDB
// to be committed atomically with marking this block as accepted.
if err := b.handlePrecompileAccept(); err != nil {
sharedMemoryWriter := NewSharedMemoryWriter()
if err := b.handlePrecompileAccept(sharedMemoryWriter); err != nil {
return err
}
if err := vm.acceptedBlockDB.Put(lastAcceptedKey, b.id[:]); err != nil {
return fmt.Errorf("failed to put %s as the last accepted block: %w", b.ID(), err)
}

return vm.db.Commit()
// Get pending operations on the vm's versionDB so we can apply them atomically
// with the shared memory requests.
vdbBatch, err := vm.db.CommitBatch()
if err != nil {
return fmt.Errorf("failed to get commit batch: %w", err)
}

// Apply any shared memory requests that accumulated from processing the logs
// of the accepted block (generated by precompiles) atomically with other pending
// changes to the vm's versionDB.
return vm.ctx.SharedMemory.Apply(sharedMemoryWriter.requests, vdbBatch)
}

// handlePrecompileAccept calls Accept on any logs generated with an active precompile address that implements
// 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.
func (b *Block) handlePrecompileAccept() error {
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
if len(rules.AccepterPrecompiles) == 0 {
Expand All @@ -97,7 +109,11 @@ func (b *Block) handlePrecompileAccept() error {
continue
}

if err := accepter.Accept(log.TxHash, txIndex, log.Topics, log.Data); err != nil {
acceptCtx := &precompileconfig.AcceptContext{
SnowCtx: b.vm.ctx,
SharedMemory: sharedMemoryWriter,
}
if err := accepter.Accept(acceptCtx, log.TxHash, txIndex, log.Topics, log.Data); err != nil {
return err
}
}
Expand Down
37 changes: 37 additions & 0 deletions plugin/evm/shared_memory_writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package evm

import (
"github.com/ava-labs/avalanchego/chains/atomic"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
)

var _ precompileconfig.SharedMemoryWriter = &sharedMemoryWriter{}

type sharedMemoryWriter struct {
requests map[ids.ID]*atomic.Requests
}

func NewSharedMemoryWriter() *sharedMemoryWriter {
return &sharedMemoryWriter{
requests: make(map[ids.ID]*atomic.Requests),
}
}

func (s *sharedMemoryWriter) AddSharedMemoryRequests(chainID ids.ID, requests *atomic.Requests) {
mergeAtomicOpsToMap(s.requests, chainID, requests)
}

// mergeAtomicOps merges atomic ops for [chainID] represented by [requests]
// to the [output] map provided.
func mergeAtomicOpsToMap(output map[ids.ID]*atomic.Requests, chainID ids.ID, requests *atomic.Requests) {
if request, exists := output[chainID]; exists {
request.PutRequests = append(request.PutRequests, requests.PutRequests...)
request.RemoveRequests = append(request.RemoveRequests, requests.RemoveRequests...)
} else {
output[chainID] = requests
}
}
18 changes: 13 additions & 5 deletions plugin/evm/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ import (
"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/api/keystore"
"github.com/ava-labs/avalanchego/chains/atomic"
"github.com/ava-labs/avalanchego/database/manager"
"github.com/ava-labs/avalanchego/database/prefixdb"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow"
"github.com/ava-labs/avalanchego/snow/choices"
Expand Down Expand Up @@ -145,12 +147,14 @@ func NewContext() *snow.Context {
}

// If [genesisJSON] is empty, defaults to using [genesisJSONLatest]
func setupGenesis(t *testing.T,
func setupGenesis(
t *testing.T,
genesisJSON string,
) (*snow.Context,
manager.Manager,
[]byte,
chan engCommon.Message,
*atomic.Memory,
) {
if len(genesisJSON) == 0 {
genesisJSON = genesisJSONLatest
Expand All @@ -164,6 +168,10 @@ func setupGenesis(t *testing.T,
Patch: 5,
})

// initialize the atomic memory
atomicMemory := atomic.NewMemory(prefixdb.New([]byte{0}, baseDBManager.Current().Database))
ctx.SharedMemory = atomicMemory.NewSharedMemory(ctx.ChainID)

// NB: this lock is intentionally left locked when this function returns.
// The caller of this function is responsible for unlocking.
ctx.Lock.Lock()
Expand All @@ -180,7 +188,7 @@ func setupGenesis(t *testing.T,

issuer := make(chan engCommon.Message, 1)
prefixedDBManager := baseDBManager.NewPrefixDBManager([]byte{1})
return ctx, prefixedDBManager, genesisBytes, issuer
return ctx, prefixedDBManager, genesisBytes, issuer, atomicMemory
}

// GenesisVM creates a VM instance with the genesis test bytes and returns
Expand All @@ -197,7 +205,7 @@ func GenesisVM(t *testing.T,
*engCommon.SenderTest,
) {
vm := &VM{}
ctx, dbManager, genesisBytes, issuer := setupGenesis(t, genesisJSON)
ctx, dbManager, genesisBytes, issuer, _ := setupGenesis(t, genesisJSON)
appSender := &engCommon.SenderTest{T: t}
appSender.CantSendAppGossip = true
appSender.SendAppGossipF = func(context.Context, []byte) error { return nil }
Expand Down Expand Up @@ -406,7 +414,7 @@ func TestSubnetEVMUpgradeRequiredAtGenesis(t *testing.T) {
}

for _, test := range genesisTests {
ctx, dbManager, genesisBytes, issuer := setupGenesis(t, test.genesisJSON)
ctx, dbManager, genesisBytes, issuer, _ := setupGenesis(t, test.genesisJSON)
vm := &VM{}
err := vm.Initialize(
context.Background(),
Expand Down Expand Up @@ -2030,7 +2038,7 @@ func TestConfigureLogLevel(t *testing.T) {
for _, test := range configTests {
t.Run(test.name, func(t *testing.T) {
vm := &VM{}
ctx, dbManager, genesisBytes, issuer := setupGenesis(t, test.genesisJSON)
ctx, dbManager, genesisBytes, issuer, _ := setupGenesis(t, test.genesisJSON)
appSender := &engCommon.SenderTest{T: t}
appSender.CantSendAppGossip = true
appSender.SendAppGossipF = func(context.Context, []byte) error { return nil }
Expand Down
9 changes: 7 additions & 2 deletions plugin/evm/vm_upgrade_bytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,14 @@ func TestVMUpgradeBytesPrecompile(t *testing.T) {
}

// restart the vm
ctx := NewContext()
// Hack: registering metrics uses global variables, so we need to disable metrics here so that we
// can initialize the VM twice.
metrics.Enabled = false
defer func() {
metrics.Enabled = true
}()
if err := vm.Initialize(
context.Background(), ctx, dbManager, []byte(genesisJSONSubnetEVM), upgradeBytesJSON, []byte{}, issuer, []*common.Fx{}, appSender,
context.Background(), vm.ctx, dbManager, []byte(genesisJSONSubnetEVM), upgradeBytesJSON, []byte{}, issuer, []*common.Fx{}, appSender,
); err != nil {
t.Fatal(err)
}
Expand Down
16 changes: 15 additions & 1 deletion precompile/precompileconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package precompileconfig
import (
"math/big"

"github.com/ava-labs/avalanchego/chains/atomic"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow"
"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -72,11 +74,23 @@ type ProposerPredicater interface {
VerifyPredicate(proposerPredicateContext *ProposerPredicateContext, storageSlots []byte) error
}

// SharedMemoryWriter defines an interface to allow a precompile's Accepter to write operations
// into shared memory to be committed atomically on block accept.
type SharedMemoryWriter interface {
AddSharedMemoryRequests(chainID ids.ID, requests *atomic.Requests)
}

// AcceptContext defines the context passed in to a precompileconfig's Accepter
type AcceptContext struct {
SnowCtx *snow.Context
SharedMemory SharedMemoryWriter
}

// Accepter is an optional interface for StatefulPrecompiledContracts to implement.
// If implemented, Accept will be called for every log with the address of the precompile when the block is accepted.
// WARNING: If you are implementing a custom precompile, beware that subnet-evm
// will not maintain backwards compatibility of this interface and your code should not
// rely on this. Designed for use only by precompiles that ship with subnet-evm.
type Accepter interface {
Accept(txHash common.Hash, logIndex int, topics []common.Hash, logData []byte) error
Accept(acceptCtx *AcceptContext, txHash common.Hash, logIndex int, topics []common.Hash, logData []byte) error
}