Skip to content
This repository was archived by the owner on Dec 16, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 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
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
58 changes: 56 additions & 2 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 @@ -206,8 +207,7 @@ func setupGenesis(
ctx.Keystore = userKeystore.NewBlockchainKeyStore(ctx.ChainID)

issuer := make(chan commonEng.Message, 1)
prefixedDB := prefixdb.New([]byte{1}, baseDB)
return ctx, prefixedDB, genesisBytes, issuer, atomicMemory
return ctx, baseDB, genesisBytes, issuer, atomicMemory
}

// GenesisVM creates a VM instance with the genesis test bytes and returns
Expand Down Expand Up @@ -3151,3 +3151,57 @@ func TestParentBeaconRootBlock(t *testing.T) {
})
}
}

func TestStandaloneDB(t *testing.T) {
vm := &VM{}
ctx, dbManager, genesisBytes, issuer, _ := setupGenesis(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(dbManager))
err := vm.Initialize(
context.Background(),
ctx,
dbManager,
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(dbManager))
// 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