How to Create a Custom Blockchain on Avalanche ?
On the Avalanche network, a subnet, also known as a subnetwork, is a dynamic group of validators who collaborate to reach a consensus on the state of a group of blockchains. One subnet is responsible for validating each blockchain. A subnet can validate many blockchains, and a node can belong to many subnets.
Avalanche subnets create an ideal environment for custom blockchain development by facilitating the creation of networks optimized for various institutional applications, such as DeFi or Web3 gaming. Subnets work in unison with the rest of the Avalanche ecosystem; they don’t compete for network resources, and can scale indefinitely.
By creating subnets, users can create their own virtual machine and deploy blockchains tailored to their own application requirements and lay forth the rules for how the blockchain should work. This way Avalanche enables easy launch of EVM compatible dApps and blockchains that can confirm transactions instantly and process thousands of transactions per second, far better than Ethereum or many other decentralized blockchain platforms.
In this tutorial, you will learn how to build a custom blockchain by creating your own subnet and deploying a evm-based blockchain on that subnet through a Node.js application using AvalancheJS.
- What is Virtual Machine (VM) in Avalanche?
- What is the advantage of Avalanche VM-based blockchains?
- Steps for custom blockchain development on Avalanche
What is Virtual Machine (VM) in Avalanche?
On Avalanche, every blockchain is an instance of a virtual machine (VM). VMs define the application-level logic of a blockchain. It defines the state of the blockchain, its state transition function, transactions, and the API via which users can interact with the blockchain.
Consider a virtual machine (VM) to be a blueprint for a blockchain; you may use the same VM to generate many blockchains, each of which follows the same set of rules but is logically separate from the others.
What is the advantage of Avalanche VM-based blockchains?
Initially, blockchain networks consisted of a single Virtual Machine (VM) with a pre-defined, unchanging set of capabilities. The network’s strict, monolithic design limited the types of blockchain-based apps that could be operated on such a VM.
People who desired specialized decentralized applications had to start from scratch and build their own blockchain network. This took a lot of time and effort, provided limited security, and ultimately resulted in a customized, weak blockchain that never took off.
With smart contracts, Ethereum took a step toward overcoming this problem. Developers didn’t have to worry about networking or consensus, yet it was still difficult to create decentralized applications because to write EVM, smart contract developers need specialized expertise in Solidity and a few other additional languages. Avalanche virtual machines make defining a blockchain-based decentralized application simple becuse developers can write virtual machines in Go instead of new, constrained languages like Solidity.
Besides, on Avalanche, writing a virtual machine for a blockchain doesn’t require bothering about lower-level functionality like networking, consensus, or the blockchain topology, as Avalanche handles all these behind the scenes.
Steps for custom blockchain development on Avalanche
Custom blockchain development on Avalanche requires you to first create a subnet and deploy a EVM-based blockchain on it
Thus the priority steps are to create a subnet- EVM and genesis.json, which will serve as the VM blueprint for the blockchain.
Requirements
- Go version >= 1.17.9
- NodeJS Node >= 10.16 and npm >= 5.6
Comprehensive development services to help you lead the future-ready Avalanche projects.
Launch your Avalanche project with LeewayHertz
Step1: Set up AvalancheGo and Subnet EVM Binaries
AvalancheGo is for node implementation, enabling clients to issue API calls for interacting with Avalanche blockchains.
To set up the AvalancheGo binary, follow the following steps:
Clone AvalancheGo repository
git clone https://github.com/ava-labs/avalanchego avalanchego
Build the binary and run it.
./scripts/build.sh
- The above command will create an avalanchego binary inside the build/ directory. Also, it will install the coreth evm binary inside the build/plugins directory.
Clone Subnet-EVM Repository
- The next step is to clone the subnet-evm repository, build the VM’s binary, and then copy it to the AvalancheGo’s build/plugins directory. Run the below command to clone the repository from the subnet-evm-demo directory.
git clone https://github.com/ava-labs/subnet-evm subnet-evm
Build Subnet-EVM Binary and copy to AvalancheGo’s plugins
./scripts/build.sh build/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dycp build/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy ../avalanchego/build/plugins
- Running the above command will build the VM’s binary inside the build/ directory, named as srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy. It is the VM’s id.
Step 2: Setting up Local Avalanche Network
For custom blockchain development on Avalanche, you can use Avalanche Network Runner (ANR) to simulate the actual network. This requires installing an ANR binary that helps interact with the actual Avalanche network through RPCs.
Clone Avalanche Network Runner
- Move to subnet-evm-demo, and clone the repository.
git clone GitHub – ava-labs/avalanche-network-runner: Tool to run and interact with an Avalanche network locally
cd avalanche-network-runner
Install ANR binary
go install -v ./cmd/avalanche-network-runner
- The above command will install the ANR binary inside $GOPATH/bin. To run the binary without specifying the binary location in each command, make sure to set up the $GOPATH/bin path in the $PATH environment variable.
Start RPC Server
- Next, run the following command to start the RPC server.
avalanche-network-runner server
--port=":8080" --grpc-gateway-port=":8081"
- This command will deploy the local cluster of validating nodes. So, keep this tab open and continue writing the subsequent commands in the new terminal or tab.
Start 5 Node Cluster
- The following command will start a network cluster of 5 validating nodes.
avalanche-network-runner control start \--endpoint="0.0.0.0:8080" \--avalanchego-path ${HOME}/subnet-evm-demo/avalanchego/build/avalanchego
- All the nodes will run the AvalancheGo’s binary and have the plugins of the subnet-evm VM. Here, you need to put the avalanchego binary location per your requirement.
- In the next 10-15 minutes, your network will be set up, and you will see the node information. With this, you have the local simulation of the Avalanche network, with 5 validating nodes.
node1: node ID "NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg", URI "http://localhost:48607"node2: node ID "NodeID-MFrZFVCXPv5iCn6M9K6XduxGTYp891xXZ", URI "http://localhost:27236"node3: node ID "NodeID-NFBbbJ4qCmNaCzeW7sxErhvWqvEQMnYcN", URI "http://localhost:58800"node4: node ID "NodeID-GWPcbFJZFfZreETSoWjPimr846mXEKCtu", URI "http://localhost:65011"node5: node ID "NodeID-P7oB2McjBGgW2NXXWVYjV8JEDFoW9xDE5", URI "http://localhost:12023"
- To view the URIs of the nodes, run the following command.
avalanche-network-runner control uris \--endpoint="0.0.0.0:8080"
- For the demonstration purpose of this tutorial, node1 is chosen as the subject node for making requests. Thus, the Node 1 will act as a validator on the new-created subnet (process explained later in the tutorial. Make a copy of its URI and PORT, as it will be needed later.
- ANR also provides the details of the funded account along with the following credentials. These keys functions as the controller of subnets and are essential for signing transactions.
P-Chain Address 1: P-custom18jma8ppw3nhx5r4ap8clazz0dps7rv5u9xde7p P-Chain Address 1 Key: PrivateKey-ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN
Step 3: Setting up Node.js Project
Now, the next step is to set up the Node.js project. For this, you have to create a new folder for the Node.js project inside the subnet-evm-demo directory. This will give your project the following structure.
$HOME|_subnet-evm-demo |_avalanchego |_subnet-evm |_avalanche-network-runner |_subnet-evm-js
Installing dependencies
Here, subnet-evm-js is the node.js project folder. Now, move to the project directory and run the following command to install the following dependencies.
- avalanche (3.13.3 or above)
- dotenv
- yargs
npm install --save Avalanche dotenv yargs
Configuration and other details
Next, make a config.js file and store all the information about the Node and its respective URI. Here, you have to store information like network id for the local network, mainnet and fuji.
Then, put the port that you have copied earlier from the ANR’s output.
Import Libraries and Setup Instances for Avalanche APIs
The following code serves as the helper function for all other functions spread over different files. Running the follwoing code will instantiate and export the necessary Avalanche APIs by using AvalancheJS, so that other files can use it. Other files simply need to import the Avalanche APIs, and they can reuse. Make a new file importAPI.js and run the following code inside it.
const { Avalanche, BinTools, BN } = require("avalanche");// Importing node details and Private key from the config file.const { ip, port, protocol, networkID, privKey } = require("./config.js");// For encoding and decoding to CB58 and buffers.const bintools = BinTools.getInstance();// Avalanche instanceconst avalanche = new Avalanche(ip, port, protocol, networkID);// Platform and Info APIconst platform = avalanche.PChain();const info = avalanche.Info();// Keychain for signing transactionsconst pKeyChain = platform.keyChain();pKeyChain.importKey(privKey);const pAddressStrings = pKeyChain.getAddressStrings();// UTXOs for spending unspent outputsconst utxoSet = async () => { const platformUTXOs = await platform.getUTXOs(pAddressStrings); return platformUTXOs.utxos;};// Exporting these for other files to usemodule.exports = { platform, info, pKeyChain, pAddressStrings, bintools, utxoSet, BN,};
Step 4: Getting the Genesis Data
- When a blockchain gets created, it creates some genesis data. The format and semantics of this genesis data are defined by the VM. This tutorial uses the default genesis data provided by subnet-evm.
{ "config": { "chainId": 11111, "homesteadBlock": 0, "eip150Block": 0, "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0, "istanbulBlock": 0, "muirGlacierBlock": 0, "subnetEVMTimestamp": 0, "feeConfig": { "gasLimit": 20000000, "minBaseFee": 1000000000, "targetGas": 100000000, "baseFeeChangeDenominator": 48, "minBlockGasCost": 0, "maxBlockGasCost": 10000000, "targetBlockRate": 2, "blockGasCostStep": 500000 } }, "alloc": { "d109c2fCfc7fE7AE9ccdE37529E50772053Eb7EE": { "balance": "0x52B7D2DCC80CD2E4000000" } }, "nonce": "0x0", "timestamp": "0x0", "extraData": "0x00", "gasLimit": "0x1312D00", "difficulty": "0x0", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000", "number": "0x0", "gasUsed": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"}
- The genesis file is responsible for setting up of origin state of the chain, like transaction fees, initial balance allocations of the native asset, restricting smart contract access, etc. In the genesis file, you should look for 2 major parameters:
"alloc": { "d109c2fCfc7fE7AE9ccdE37529E50772053Eb7EE": { "balance": "0x52B7D2DCC80CD2E4000000" }}
- Next, put your Ethereum-derived hexadecimal address (without 0x) to receive the associated balance. You can also create a new address on MetaMask and use that here.
- Then put a unique number as chain id for your chain. Conflicting chain ids can cause problems.
"chainId": 00000
Step 5: Creating the Subnet
Getting started
- The next step is to create a new subnet by issuing buildCreateSubnetTx on AvalancheJS’ platform API. This function includes two arguments: subnet-owner and threshold.
- Subnet owners create signed transactions to control the subnets, add validators and create new chains, etc. The threshold defines the minimum number of signatures required for approval on behalf of all subnet owners. By default, the count is1, so by default, there is only one subnet owner. The following will run a transaction.
const { platform, pKeyChain, pAddressStrings, utxoSet,} = require("./importAPI.js");async function createSubnet() { // Creating unsgined tx const unsignedTx = await platform.buildCreateSubnetTx( await utxoSet(), // set of utxos this tx will consume pAddressStrings, // from pAddressStrings, // change address pAddressStrings // subnet owners' address array ); // signing unsgined tx with pKeyChain const tx = unsignedTx.sign(pKeyChain); // issuing tx const txId = await platform.issueTx(tx); console.log("Tx ID: ", txId);}createSubnet();
- You will receive txID for this transaction; keep it carefully. Once the transaction is accepted, a new subnet with the same ID will get created. Next, use the following code to run this program.
node createSubnet.js
Adding Subnet Validator
- The newly created subnet requires validators, who will validate the transactions on the subnet’s every blockchain. Thus the next step is to add validators to the subnet. To add validators to the subnet, write the code in addSubnetValidator.js.
const args = require("yargs").argv; const SubnetAuth = require("avalanche").platformvm.SubnetAuth; const { platform, info, pKeyChain, pAddressStrings, utxoSet, BN, } = require("./importAPI.js"); async function addSubnetValidator() { let { nodeID = await info.getNodeID(), startTime, endTime, weight = 20, subnetID, } = args; // Creating subnet auth const addressIndex = Buffer.alloc(4); addressIndex.writeUIntBE(0x0, 0, 4); const subnetAuth = new SubnetAuth([addressIndex]); // Creating unsgined tx const unsignedTx = await platform.buildAddSubnetValidatorTx( await utxoSet(), // set of utxos this tx will consume pAddressStrings, // from pAddressStrings, // change nodeID, // node id of the validator new BN(startTime), // timestamp after which validation starts new BN(endTime), // timestamp after which validation ends new BN(weight), // weight of the validator subnetID, // subnet id for validation subnetAuth // subnet owners' address indices signing this tx ); // signing unsgined tx with pKeyChain const tx = unsignedTx.sign(pKeyChain); // issuing tx const txId = await platform.issueTx(tx); console.log("Tx ID: ", txId); } addSubnetValidator();
Whitelisting Subnet from the Node
- The next step is to whitelist the subnet from the Node. Any node can be added to a subnet by its owner. That isn’t to say that the nodes begin validating their subnet without permission. If a node wants to validate a newly added subnet, it must restart its avalanchego binary and whitelist the new subnet.
avalanche-network-runner control restart-node \--request-timeout=3m \--endpoint="0.0.0.0:8080" \--node-name node1 \--avalanchego-path ${HOME}/subnet-evm-demo/avalanchego/build/avalanchego \--whitelisted-subnets=""
- The Node will be reassigned to a random API port once it has resumed. The config.js file must be updated with the new port.
Comprehensive development services to help you lead the future-ready Avalanche projects.
Launch your Avalanche project with LeewayHertz
Step 6: Creating the Blockchain
Subnet owners can deploy any number of blockchains after the subnet setup is complete, either by creating their own VMs or reusing existing ones. If the subnet validators are not using the new blockchain’s VM, each Node must place the new VM binary in its avalanchego/build/plugins/ folder.
This tutorial demonstrates the functions of constructing a new blockchain-based on the existing genesis. This chain will use json and subnet-evm as a blueprint (VM). The code will be written in stages. Follow the steps, understand and inserting each function into your createBlockchain.js file.
Importing Dependencies
- The following snippet imports the dependencies.
const args = require("yargs").argv;const SubnetAuth = require("avalanche").platformvm.SubnetAuth;const genesisJSON = require("./genesis.json");const { platform, pKeyChain, pAddressStrings, bintools, utxoSet,} = require("./importAPI");
Decoding CB58 vmID to String
- Following is the utility function that decodevmNas me from the vmID. vmID is a string that has been zero-extended in a 32-byte array and encoded in CB58.
// Returns string representing vmName of the provided vmIDfunction convertCB58ToString(cb58Str) { const buff = bintools.cb58Decode(cb58Str); return buff.toString();}
Creating Blockchain
- The createBlockchain() function accepts three to four command-line flags as input. SubnetID and chainName flags must be provided by the user. vmID or vmName can be used as the third argument, and the flags must be provided to either of them.
- In the function, the Chain name represents the name of the blockchain you want to build with the specified vmID or vmName. The vmID must match the one you used to construct the subnet-evm binary.
// Creating blockchain with the subnetID, chain name and vmID (CB58 encoded VM name)async function createBlockchain() { const { subnetID, chainName } = args; // Generating vmName if only vmID is provied, else assigning args.vmID const vmName = typeof args.vmName !== "undefined" ? args.vmName : convertCB58ToString(args.vmID); // Getting CB58 encoded bytes of genesis genesisBytes = JSON.stringify(genesisJSON); // Creating subnet auth const addressIndex = Buffer.alloc(4); addressIndex.writeUIntBE(0x0, 0, 4); const subnetAuth = new SubnetAuth([addressIndex]); // Creating unsgined tx const unsignedTx = await platform.buildCreateChainTx( await utxoSet(), // set of utxos this tx is consuming pAddressStrings, // from pAddressStrings, // change subnetID, // id of subnet on which chain is being created chainName, // Name of blockchain vmName, // Name of the VM this chain is referencing [], // Array of feature extensions genesisBytes, // Stringified geneis JSON file subnetAuth // subnet owners' address indices signing this tx ); // signing unsgined tx with pKeyChain const tx = unsignedTx.sign(pKeyChain); // issuing tx const txId = await platform.issueTx(tx); console.log("Create chain transaction ID: ", txId);}
- By running this code, be sure to keep the txID received. The txID will become the blockchainID or identification for the newly established chain once the transaction is committed.
- Run this program by running the following command.
node createBlockchain.js \--subnetID \--chainName \--vmName subnetevm
- With that, your new blockchain on the Avalanche network will be up and ready in a few seconds. You can also view the logs on the Avalanche Network Runner tab of the terminal.
Endnote
Avalanche is a decentralized smart contract platform that is open and programmable. It enables the creation of Solidity-compatible dApps in a quick and low-cost manner. It is one of the most EVM-compatible platforms for launching Ethereum dApps that immediately confirm transactions and perform hundreds of transactions per second, far exceeding any other decentralized blockchain platform currently available. Avalanche is designed to make creating and implementing customized private and public blockchains easier.
If you are looking forward to any Avalanche-related development assistance, contact our blockchain experts.
Start a conversation by filling the form
All information will be kept confidential.
Insights
How to Create a Virtual Machine on Avalanche
This article will explain the basics of Avalanche protocol, the importance of a virtual machine on Avalanche and how to create a virtual machine on Avalanche.
How to develop an NFT Marketplace on Avalanche?
NFT marketplace developed on Avalanche has a fast transaction speed and low network congestion, as it is based the PoS consensus mechanism.
How to Develop and Deploy an Avalanche Smart Contract
Avalanche is one of the fastest, programmable and interoperable smart contracts platforms that permit anyone to develop custom-made applications, solidity-based smart contracts, NFT solutions etc.