Virtual Machines
Understand how Virtual Machines (VMs) define blockchain behavior in AvalancheGo, including the VM interface and built-in VMs.
A Virtual Machine (VM) defines the application-level logic of a blockchain. In AvalancheGo, VMs are decoupled from consensus, allowing developers to create custom blockchain behavior while reusing the battle-tested consensus layer.
What is a Virtual Machine?
Think of a VM as the "personality" of a blockchain:
| Aspect | What the VM Defines |
|---|---|
| State | What data the blockchain stores |
| Transactions | Valid operations and their effects |
| Blocks | How transactions are packaged |
| APIs | How users interact with the chain |
| Validation | Rules for accepting blocks |
VMs are reusable. Multiple blockchains can run the same VM, each with independent state. This is similar to how a class can have multiple instances in object-oriented programming.
VM Architecture
Core VM Interface
Every VM must implement the ChainVM interface for linear chain consensus:
type ChainVM interface {
common.VM
// Block building
BuildBlock(ctx context.Context) (snowman.Block, error)
// Block retrieval
GetBlock(ctx context.Context, blkID ids.ID) (snowman.Block, error)
ParseBlock(ctx context.Context, blockBytes []byte) (snowman.Block, error)
// Consensus integration
SetPreference(ctx context.Context, blkID ids.ID) error
LastAccepted(ctx context.Context) (ids.ID, error)
// Optional: height-indexed access
GetBlockIDAtHeight(ctx context.Context, height uint64) (ids.ID, error)
}Base VM Interface
The foundation interface that all VMs implement (source):
type VM interface {
common.AppHandler // AppRequest/AppResponse/AppGossip hooks
health.Checker // Exposed via /ext/health
validators.Connector // Called on peer connect/disconnect
// Lifecycle
Initialize(ctx context.Context, chainCtx *snow.Context,
db database.Database, genesisBytes []byte,
upgradeBytes []byte, configBytes []byte,
fxs []*common.Fx, appSender common.AppSender) error
SetState(ctx context.Context, state snow.State) error
Shutdown(ctx context.Context) error
// Info
Version(ctx context.Context) (string, error)
// APIs
CreateHandlers(ctx context.Context) (map[string]http.Handler, error)
NewHTTPHandler(ctx context.Context) (http.Handler, error)
// Engine notifications (PendingTxs, etc.)
WaitForEvent(ctx context.Context) (common.Message, error)
}Block Interface
Blocks are the fundamental unit of consensus (source):
type Block interface {
// ID(), Accept(), Reject(), Status() come from snow.Decidable
snow.Decidable
// Identity
Parent() ids.ID
Height() uint64
Timestamp() time.Time
Bytes() []byte
// Validation
Verify(context.Context) error
}Block Lifecycle
Block Status
type Status int
const (
Unknown Status = iota
Processing
Rejected
Accepted
)Built-in Virtual Machines
Platform VM (P-Chain)
The Platform VM (vms/platformvm) manages the Avalanche network itself:
type VM struct {
// State management
state state.State
atomicUTXOs atomic.SharedMemory
// Block building
Builder blockbuilder.Builder
Network network.Network
// Validators
Validators validators.Manager
}Responsibilities:
- Validator Management: Add/remove validators, track stake
- Subnet Creation: Create new validator sets
- Chain Creation: Launch new blockchains
- Staking: Manage delegation and rewards
- Warp Messaging: Sign cross-chain messages
Key Transaction Types:
| Transaction | Purpose | Era |
|---|---|---|
AddValidatorTx | Add a Primary Network validator | Apricot |
AddDelegatorTx | Delegate stake to a validator | Apricot |
CreateSubnetTx | Create a new subnet | Apricot |
CreateChainTx | Launch a blockchain on a subnet | Apricot |
ImportTx / ExportTx | Cross-chain asset transfers | Apricot |
AddPermissionlessValidatorTx | Add validator to permissionless subnet | Banff |
AddPermissionlessDelegatorTx | Delegate to permissionless validator | Banff |
TransformSubnetTx | Convert subnet to permissionless | Banff |
TransferSubnetOwnershipTx | Transfer subnet ownership | Durango |
ConvertSubnetToL1Tx | Convert subnet to Avalanche L1 | Etna |
RegisterL1ValidatorTx | Register validator on L1 | Etna |
SetL1ValidatorWeightTx | Set L1 validator weight | Etna |
IncreaseL1ValidatorBalanceTx | Add balance to L1 validator | Etna |
DisableL1ValidatorTx | Disable an L1 validator | Etna |
The Etna upgrade introduced 5 new transaction types for managing Avalanche L1s. These enable converting subnets to sovereign L1s with their own validator management.
AVM (X-Chain)
The AVM (vms/avm) handles asset creation and transfers using the UTXO model:
The X-Chain was linearized in the Cortina upgrade (April 2023). It now uses Snowman consensus like the P-Chain and C-Chain, rather than the legacy Avalanche DAG consensus. The AVM implements the LinearizableVMWithEngine interface to support this transition.
type VM struct {
// Asset management
fxs []*Fx
state state.State
// UTXO handling
utxoSet atomic.SharedMemory
}Features:
- Multi-Asset Support: Native AVAX and custom assets
- UTXO Model: Bitcoin-style transaction inputs/outputs
- Snowman Consensus: Linear chain consensus (linearized from DAG in Cortina upgrade)
- Feature Extensions (Fxs): Pluggable transaction types
secp256k1fx: Standard signaturesnftfx: Non-fungible tokenspropertyfx: Property ownership
Transaction Types:
| Transaction | Purpose |
|---|---|
CreateAssetTx | Create a new asset |
OperationTx | Mint/burn assets |
BaseTx | Transfer assets |
ImportTx | Import from other chains |
ExportTx | Export to other chains |
Coreth (C-Chain)
Coreth is the EVM implementation for the C-Chain:
Coreth is grafted into the AvalancheGo repository at graft/coreth/. The standalone ava-labs/coreth repository has been archived and is now read-only. All active development occurs within the AvalancheGo monorepo.
Features:
- Full Ethereum Virtual Machine compatibility
- Supports Solidity smart contracts
- Web3 JSON-RPC API (
eth,personal,txpool,debugnamespaces) - EIP-1559 dynamic fees
- Atomic transactions with other Avalanche chains (via shared memory)
- Support for Ethereum upgrades through Cancun
ProposerVM (Snowman++)
The ProposerVM (vms/proposervm) wraps other VMs to add Snowman++ proposer windows:
type VM struct {
// Wrapped VM
ChainVM block.ChainVM
// Proposer selection
windower proposer.Windower
// Block timing
MinBlockDelay time.Duration
}How it Works:
- Stake-weighted proposers are sampled for each height.
- Each proposer has a 5s slot (up to 6 slots) counted from the parent timestamp.
- Only the active proposer can build during its slot; after the final slot any validator may build.
- The wrapper enforces proposer signatures/timestamps before issuing to consensus.
Snowman++ is enabled on all Primary Network chains (P-Chain, C-Chain, X-Chain) to pace block production without sacrificing liveness.
Custom VMs
You can build custom VMs that run on Avalanche L1s. There are two approaches:
1. Native Go VM
Implement the ChainVM interface directly in Go:
type MyVM struct {
db database.Database
state *MyState
builder *BlockBuilder
pending <-chan struct{}
}
func (vm *MyVM) Initialize(ctx context.Context, ...) error {
// Set up database and state
vm.db = db
vm.state = NewState(db)
vm.pending = vm.builder.Subscribe() // emits when there are pending txs
return nil
}
func (vm *MyVM) WaitForEvent(ctx context.Context) (common.Message, error) {
select {
case <-ctx.Done():
return 0, ctx.Err()
case <-vm.pending:
return common.PendingTxs, nil
}
}
func (vm *MyVM) BuildBlock(ctx context.Context) (snowman.Block, error) {
// Collect pending transactions
txs := vm.builder.GetPendingTxs()
// Create new block
return NewBlock(vm.state.LastAccepted(), txs), nil
}2. RPC VM (Any Language)
Use the rpcchainvm interface to build VMs in any language:
Benefits:
- Write VMs in Rust, TypeScript, etc.
- Process isolation
- Independent deployment
Protocol:
// gRPC service definition
service VM {
rpc Initialize(InitializeRequest) returns (InitializeResponse);
rpc BuildBlock(BuildBlockRequest) returns (BuildBlockResponse);
rpc ParseBlock(ParseBlockRequest) returns (ParseBlockResponse);
rpc GetBlock(GetBlockRequest) returns (GetBlockResponse);
// ... more methods
}VM Registration
VMs are registered with the node at startup:
func (n *Node) initVMs() error {
// Built-in VMs
n.VMManager.RegisterFactory(ctx, constants.PlatformVMID,
&platformvm.Factory{})
n.VMManager.RegisterFactory(ctx, constants.AVMID,
&avm.Factory{})
n.VMManager.RegisterFactory(ctx, constants.EVMID,
&coreth.Factory{})
// Plugin VMs are discovered from the plugins directory
// (default: ~/.avalanchego/plugins/)
}Plugin Discovery:
- VMs are placed in
~/.avalanchego/plugins/ - Filename is the VM ID (e.g.,
srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy) - Node discovers and loads plugins at startup
VM Configuration
Each chain can have custom VM configuration:
{
"pruning-enabled": true,
"state-sync-enabled": true,
"eth-apis": ["eth", "eth-filter", "net", "web3"]
}Chain-Specific Configs:
- Stored in
~/.avalanchego/configs/chains/{chainID}/ config.json: VM configurationupgrade.json: Upgrade coordination
Best Practices
State Management
// Use versioned database for atomic commits
func (vm *VM) Accept(ctx context.Context, blk *Block) error {
batch := vm.db.NewBatch()
defer batch.Reset()
// Apply state changes
for _, tx := range blk.Txs {
if err := vm.state.Apply(batch, tx); err != nil {
return err
}
}
// Commit atomically
return batch.Write()
}Block Building
// Drive block production: return PendingTxs when mempool has work
func (vm *VM) WaitForEvent(ctx context.Context) (common.Message, error) {
select {
case <-ctx.Done():
return 0, ctx.Err()
case <-vm.pending:
return common.PendingTxs, nil
}
}API Design
func (vm *VM) CreateHandlers(ctx context.Context) (map[string]http.Handler, error) {
return map[string]http.Handler{
"/rpc": vm.newJSONRPCHandler(),
"/ws": vm.newWebSocketHandler(),
"/health": vm.newHealthHandler(),
}, nil
}Next Steps
Is this guide helpful?