Skip to content

Commit

Permalink
remove shared memory from precompiles and accept (#1383)
Browse files Browse the repository at this point in the history
* add regression test

* remove shared memory from accept and precompile interface

* Update plugin/evm/vm_test.go

Co-authored-by: Darioush Jalali <darioush.jalali@avalabs.org>
Signed-off-by: Ceyhun Onur <ceyhunonur54@gmail.com>

* revert changes to test helper

---------

Signed-off-by: Ceyhun Onur <ceyhunonur54@gmail.com>
Co-authored-by: Darioush Jalali <darioush.jalali@avalabs.org>
  • Loading branch information
ceyonur and darioush authored Nov 13, 2024
1 parent b5836b0 commit ec46ce5
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 76 deletions.
28 changes: 6 additions & 22 deletions plugin/evm/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,9 @@ func (b *Block) Accept(context.Context) error {

// Call Accept for relevant precompile logs. Note we do this prior to
// calling Accept on the blockChain so any side effects (eg warp signatures)
// take place before the accepted log is emitted to subscribers. Use of the
// sharedMemoryWriter ensures shared memory requests generated by
// precompiles are committed atomically with the vm's lastAcceptedKey.
// take place before the accepted log is emitted to subscribers.
rules := b.vm.chainConfig.Rules(b.ethBlock.Number(), b.ethBlock.Timestamp())
sharedMemoryWriter := NewSharedMemoryWriter()
if err := b.handlePrecompileAccept(rules, sharedMemoryWriter); err != nil {
if err := b.handlePrecompileAccept(rules); err != nil {
return err
}
if err := vm.blockChain.Accept(b.ethBlock); err != nil {
Expand All @@ -77,24 +74,12 @@ func (b *Block) Accept(context.Context) error {
return fmt.Errorf("failed to put %s as the last accepted block: %w", b.ID(), err)
}

// Get pending operations on the vm's versionDB so we can apply them atomically
// with the shared memory requests.
vdbBatch, err := b.vm.db.CommitBatch()
if err != nil {
return fmt.Errorf("could not create commit batch processing block[%s]: %w", b.ID(), 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)
return b.vm.db.Commit()
}

// 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(rules params.Rules, sharedMemoryWriter *sharedMemoryWriter) error {
func (b *Block) handlePrecompileAccept(rules params.Rules) error {
// Short circuit early if there are no precompile accepters to execute
if len(rules.AccepterPrecompiles) == 0 {
return nil
Expand All @@ -108,9 +93,8 @@ func (b *Block) handlePrecompileAccept(rules params.Rules, sharedMemoryWriter *s
return fmt.Errorf("failed to fetch receipts for accepted block with non-empty root hash (%s) (Block: %s, Height: %d)", b.ethBlock.ReceiptHash(), b.ethBlock.Hash(), b.ethBlock.NumberU64())
}
acceptCtx := &precompileconfig.AcceptContext{
SnowCtx: b.vm.ctx,
SharedMemory: sharedMemoryWriter,
Warp: b.vm.warpBackend,
SnowCtx: b.vm.ctx,
Warp: b.vm.warpBackend,
}
for _, receipt := range receipts {
for logIdx, log := range receipt.Logs {
Expand Down
2 changes: 1 addition & 1 deletion plugin/evm/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,5 @@ func TestHandlePrecompileAccept(t *testing.T) {
precompileAddr: mockAccepter,
},
}
require.NoError(blk.handlePrecompileAccept(rules, nil))
require.NoError(blk.handlePrecompileAccept(rules))
}
37 changes: 0 additions & 37 deletions plugin/evm/shared_memory_writer.go

This file was deleted.

4 changes: 1 addition & 3 deletions plugin/evm/syncervm_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func (client *stateSyncerClient) Shutdown() error {
}

// finishSync is responsible for updating disk and memory pointers so the VM is prepared
// for bootstrapping. Executes any shared memory operations from the atomic trie to shared memory.
// for bootstrapping.
func (client *stateSyncerClient) finishSync() error {
stateBlock, err := client.state.GetBlock(context.TODO(), ids.ID(client.syncSummary.BlockHash))
if err != nil {
Expand Down Expand Up @@ -349,8 +349,6 @@ func (client *stateSyncerClient) finishSync() error {

// updateVMMarkers updates the following markers in the VM's database
// and commits them atomically:
// - updates atomic trie so it will have necessary metadata for the last committed root
// - updates atomic trie so it will resume applying operations to shared memory on initialize
// - updates lastAcceptedKey
// - removes state sync progress markers
func (client *stateSyncerClient) updateVMMarkers() error {
Expand Down
3 changes: 1 addition & 2 deletions plugin/evm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -1255,8 +1255,7 @@ func (vm *VM) initializeDBs(avaDB database.Database) error {
// skip standalone database initialization if we are running in unit tests
if vm.ctx.NetworkID != avalancheconstants.UnitTestID {
// first initialize the accepted block database to check if we need to use a standalone database
verDB := versiondb.New(avaDB)
acceptedDB := prefixdb.New(acceptedPrefix, verDB)
acceptedDB := prefixdb.New(acceptedPrefix, avaDB)
useStandAloneDB, err := vm.useStandaloneDatabase(acceptedDB)
if err != nil {
return err
Expand Down
62 changes: 62 additions & 0 deletions plugin/evm/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/api/keystore"
Expand Down Expand Up @@ -3151,3 +3152,64 @@ func TestParentBeaconRootBlock(t *testing.T) {
})
}
}

func TestStandaloneDB(t *testing.T) {
vm := &VM{}
ctx := NewContext()
baseDB := memdb.New()
atomicMemory := atomic.NewMemory(prefixdb.New([]byte{0}, baseDB))
ctx.SharedMemory = atomicMemory.NewSharedMemory(ctx.ChainID)
issuer := make(chan commonEng.Message, 1)
sharedDB := prefixdb.New([]byte{1}, baseDB)
genesisBytes := buildGenesisTest(t, genesisJSONLatest)
// alter network ID to use standalone database
ctx.NetworkID = 123456
appSender := &enginetest.Sender{T: t}
appSender.CantSendAppGossip = true
appSender.SendAppGossipF = func(context.Context, commonEng.SendConfig, []byte) error { return nil }
configJSON := `{"database-type": "memdb"}`

isDBEmpty := func(db database.Database) bool {
it := db.NewIterator()
defer it.Release()
return !it.Next()
}
// Ensure that the database is empty
require.True(t, isDBEmpty(baseDB))

err := vm.Initialize(
context.Background(),
ctx,
sharedDB,
genesisBytes,
nil,
[]byte(configJSON),
issuer,
[]*commonEng.Fx{},
appSender,
)
defer vm.Shutdown(context.Background())
require.NoError(t, err, "error initializing VM")
require.NoError(t, vm.SetState(context.Background(), snow.Bootstrapping))
require.NoError(t, vm.SetState(context.Background(), snow.NormalOp))

// Issue a block
acceptedBlockEvent := make(chan core.ChainEvent, 1)
vm.blockChain.SubscribeChainAcceptedEvent(acceptedBlockEvent)
tx0 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil)
signedTx0, err := types.SignTx(tx0, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0])
require.NoError(t, err)
errs := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx0})
require.NoError(t, errs[0])

// accept block
blk := issueAndAccept(t, issuer, vm)
newBlock := <-acceptedBlockEvent
require.Equal(t, newBlock.Block.Hash(), common.Hash(blk.ID()))

// Ensure that the shared database is empty
assert.True(t, isDBEmpty(baseDB))
// Ensure that the standalone database is not empty
assert.False(t, isDBEmpty(vm.db))
assert.False(t, isDBEmpty(vm.acceptedBlockDB))
}
13 changes: 2 additions & 11 deletions precompile/precompileconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
package precompileconfig

import (
"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/ava-labs/avalanchego/vms/platformvm/warp"
Expand Down Expand Up @@ -54,21 +52,14 @@ type Predicater interface {
VerifyPredicate(predicateContext *PredicateContext, predicateBytes []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)
}

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
SnowCtx *snow.Context
Warp WarpMessageWriter
}

// Accepter is an optional interface for StatefulPrecompiledContracts to implement.
Expand Down

0 comments on commit ec46ce5

Please sign in to comment.