Testing Custom VMs
Deploy and test your custom Virtual Machine on a local temporary network
This guide shows you how to use tmpnet to test your custom Virtual Machine (VM) or L1 blockchain before deploying to testnet or mainnet.
Overview
Testing custom VMs with tmpnet allows you to deploy your VM to a multi-node network, test consensus behavior, and iterate quickly before public deployment.
Prerequisites
- tmpnet installed - See Installation
- VM binary compiled - Your VM binary in
~/.avalanchego/plugins/ - Genesis file - Genesis configuration for your chain
Quick Example: Testing a Custom VM
Here's a complete workflow for testing a custom VM:
1. Prepare Your VM Binary
Build your VM and place it in the plugins directory:
# Build your VM (example)
cd /path/to/your-vm
go build -o ~/.avalanchego/plugins/your-vm ./cmd/your-vm
# Verify the binary exists
ls -lh ~/.avalanchego/plugins/your-vmThe binary name should match your VM name.
2. Create Genesis Configuration
Create a genesis file for your chain. The exact format depends on your VM. Example for a simple VM:
{
"config": {
"chainId": 12345,
"feeConfig": {
"gasLimit": 8000000
}
},
"alloc": {
"0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": {
"balance": "0x295BE96E64066972000000"
}
}
}Save this as genesis.json.
3. Test Your VM Using Go Code
Create a test script to deploy your VM:
package main
import (
"context"
"os"
"time"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/tests/fixture/tmpnet"
"github.com/ava-labs/avalanchego/utils/logging"
)
func main() {
// Read genesis
genesisBytes, _ := os.ReadFile("genesis.json")
// Define your VM chain
chain := &tmpnet.Chain{
VMID: ids.ID{'y', 'o', 'u', 'r', 'v', 'm', 'i', 'd'},
Genesis: genesisBytes,
Config: `{"blockGasLimit": 8000000}`,
}
// Create subnet with your chain
subnet := &tmpnet.Subnet{
Name: "my-vm-subnet",
Chains: []*tmpnet.Chain{chain},
}
// Create 5-node network
network := &tmpnet.Network{
Nodes: tmpnet.NewNodesOrPanic(5),
Subnets: []*tmpnet.Subnet{subnet},
DefaultRuntimeConfig: tmpnet.NodeRuntimeConfig{
Process: &tmpnet.ProcessRuntimeConfig{
AvalancheGoPath: "./bin/avalanchego",
PluginDir: "./build/plugins",
},
},
}
// All nodes validate the subnet
for _, node := range network.Nodes {
subnet.ValidatorIDs = append(subnet.ValidatorIDs, node.NodeID)
}
// Bootstrap network
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
err := tmpnet.BootstrapNewNetwork(
ctx,
logging.NoLog{}, // Or use a real logger
network,
"", // Empty string uses default path (~/.tmpnet/networks)
)
if err != nil {
panic(err)
}
println("Chain ID:", subnet.Chains[0].ChainID.String())
// Test your chain...
network.Stop(context.Background())
}Key steps:
- Read your genesis configuration
- Define a
Chainwith your VM ID and genesis - Create a
Subnetcontaining your chain - Set up
DefaultRuntimeConfigwith paths to avalanchego and plugins - Assign validator nodes to the subnet
- Bootstrap and test
4. Interact with Your Chain
Once your chain is deployed, interact with it using the chain's API:
# Get the chain ID from the network configuration
CHAIN_ID=$(cat ~/.tmpnet/networks/latest/subnets/your-vm-subnet.json | jq -r '.chains[0].chainId')
# Get a node URI
NODE_URI=$(cat ~/.tmpnet/networks/latest/NodeID-*/process.json | jq -r '.uri' | head -1)
# Call your chain's API
curl -X POST --data '{
"jsonrpc": "2.0",
"method": "your.method",
"params": {},
"id": 1
}' -H 'content-type:application/json;' ${NODE_URI}/ext/bc/${CHAIN_ID}/rpcDevelopment Workflow
Iterative Testing
1. Make code changes 2. Rebuild your VM:
go build -o ~/.avalanchego/plugins/your-vm ./cmd/your-vm3. Restart the network:
tmpnetctl stop-network
go run test-vm.goAutomated Testing
Integrate tmpnet into your test suite:
func TestYourVM(t *testing.T) {
network := setupNetwork(t)
defer network.Stop(context.Background())
chain := deployChain(t, network)
t.Run("BasicTransaction", func(t *testing.T) {
// Test transaction functionality
})
t.Run("Consensus", func(t *testing.T) {
// Test consensus behavior
})
}Monitor Logs
# Watch logs from all nodes
tail -f ~/.tmpnet/networks/latest/NodeID-*/logs/main.log
# Search for errors
grep -i error ~/.tmpnet/networks/latest/NodeID-*/logs/main.logCommon Patterns
Testing VM Upgrades
// Start with v1
network := createNetworkWithVM("your-vm", "v1.0.0")
// ... test v1 behavior ...
network.Stop(ctx)
// Replace plugin binary with v2
// cp your-vm-v2 ~/.avalanchego/plugins/your-vm
network.Restart(ctx)
// ... verify v2 upgrade ...Custom Genesis Allocations
Pre-fund specific addresses:
genesis := map[string]interface{}{
"alloc": map[string]interface{}{
"0xYourAddress": map[string]interface{}{
"balance": "0x1000000000000000000000",
},
},
}
genesisBytes, _ := json.Marshal(genesis)
chain.Genesis = genesisBytesTesting with Debug Logging
Enable verbose logging for debugging:
network.DefaultFlags = tmpnet.FlagsMap{
"log-level": "debug",
"log-display-level": "debug",
}Troubleshooting
VM Binary Not Found
Error: plugin binary not found
Solution: Ensure your VM binary is in the correct location:
ls -lh ~/.avalanchego/plugins/your-vmThe plugin directory can be customized with --plugin-dir flag.
Genesis Validation Fails
Error: invalid genesis
Solution: Verify your genesis format matches your VM's expectations. Check logs:
grep -i genesis ~/.tmpnet/networks/latest/NodeID-*/logs/main.logChain Not Starting
Error: Chain doesn't appear in network
Solution:
- Verify VM binary is executable:
chmod +x ~/.avalanchego/plugins/your-vm - Check node logs for VM loading errors
- Ensure VM ID is correct and unique
Subnet Validators Not Active
Error: Validators don't become active
Solution:
- Ensure sufficient validators are assigned to the subnet
- Check that nodes are tracking the subnet
- Verify subnet creation succeeded in logs
Next Steps
Subnet Testing
Advanced subnet testing scenarios
Runtime Environments
Choose between local and Kubernetes runtimes
Monitoring
Set up monitoring for your test network
Additional Resources
Is this guide helpful?