How to create custom blockchain on Avalanche?

LeewayHertz
Predict
Published in
9 min readAug 12, 2022

A subnet or subnetwork consists of a dynamic set of validators who cooperate to validate a group of blockchains on the Avalanche network. Each blockchain gets validated by one single subnet, while a subnet can validate many blockchains. The presence of Avalanche subnets creates an ideal ecosystem for custom blockchain development that enables building networks optimized for several institutional applications, like DeFi.

Users can design their Virtual Machines, deploy blockchains specific to their application requirements, and lay out the rules for how the blockchain should operate by creating subnets. Avalanche makes it simple to create EVM-compliant dApps and blockchains that can instantaneously confirm transactions and process thousands of transactions per second, vastly outperforming Ethereum and many other decentralized blockchain platforms.

Now, let us dive into the steps of Avalanche custom blockchain development.

How to develop Custom Blockchain on Avalanche?

On Avalanche, custom blockchain development begins with creating a subnet and deploying an EVM-based blockchain.

As a result, the priority is to establish a subnet-EVM and genesis.json, which will act as the blockchain’s VM blueprint.

Prerequisites

  • Go version >= 1.17.9
  • NodeJS Node >= 10.16 and npm >= 5.6

1. Setting Up AvalancheGo and Subnet EVM Binaries

AvalancheGo is used for node implementation, allowing clients to connect with Avalanche blockchains via API calls.

Following the given steps, you can set up the AvalancheGo binary:

Clone AvalancheGo repository

git clone https://github.com/ava-labs/avalanchegocd avalanchego

Build and run the binary

./scripts/build.sh

The avalanchego binary will be created in the build/ directory using the command above. Additionally, the coreth evm binary will be installed in the build/plugins directory.

Clone Subnet-EVM Repository

To clone the subnet-evm repository, generate the VM’sVM’s binary, and then copy it to the build/plugins directory of AvalancheGo. To clone the repository from the subnet-evm-demo directory, run the following:

git clone https://github.com/ava-labs/subnet-evmcd

Develop Subnet-EVM Binary and copy to AvalancheGo’sAvalancheGo’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, by the name,

‘’srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy,’’ which is the VM’sVM’s id.

2. Set up Local Avalanche Network

You can use Avalanche Network Runner (ANR) to replicate the actual network for custom blockchain development on Avalanche. This needs an ANR binary to be installed that facilitates RPC communication with the actual Avalanche network.

Clone the Avalanche Network Runner

Clone the repository from subnet-evm-demo.

Git clone GitHub — ava-labs/avalanche-network-runner, is a tool that can run and interact with an Avalanche network locally.

cd avalanche-network-runner

Install ANR binary

go install -v ./cmd/avalanche-network-runner

The code above installs the ANR binary in $GOPATH/bin. Set up the $GOPATH/bin path in the $PATH environment variable to run the binary without specifying the binary location in each command.

Start RPC server

Start the RPC server by running the given commands.

avalanche-network-runner server

1--port=":8080" 2--grpc-gateway-port=":8081"

The local cluster of validating nodes will be deployed with this command. Thus, keep this tab open and type the commands in the new terminal or tab.

Start 5 Node Cluster

Begin a network cluster of five validating nodes using:

avalanche-network-runner control start \--endpoint="0.0.0.0:8080" \--avalanchego-path ${HOME}/subnet-evm-demo/avalanchego/build/avalanchego

The AvalancheGo binary will be run on all nodes, along with the subnet-evm VM’sVM’s plugins. You must specify the avalanchego binary location here according to your requirements.

Your network will be set up in the next 10–15 minutes, and you will be able to see the node information. You may create a local Avalanche network simulation 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"

You can view the URIs of the nodes using:

avalanche-network-runner control uris \--endpoint="0.0.0.0:8080"

Node1 is the subject node initiating requests in this tutorial for demonstration purposes. So, Node 1 will serve as a validator on the newly established subnet. Ensure to keep a copy of its URI and PORT, which will be required later.

ANR also supplies the following credentials, as well as the details of the financed account. These keys serve as subnet controllers and are required for transaction signing.

1P-Chain Address 1: P-custom18jma8ppw3nhx5r4ap8clazz0dps7rv5u9xde7p 2P-Chain Address 1 Key: PrivateKey-ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN

3. Set up Node.js Project

The following step is to create the Node.js project. To do so, inside the subnet-evm-demo directory, create a new folder for the Node.js project. As a result, your project will have the following structure.

$HOME|_subnet-evm-demo |_avalanchego |_subnet-evm |_avalanche-network-runner |_subnet-evm-js

Install dependencies

The node.js project folder is subnet-evm-js in this case. To install the required dependencies, go to the project directory and run the following command.

  • avalanche (3.13.3 or above)
  • dotenv
  • yargs

npm install --save Avalanche dotenv yargs

Configuration and other details

After that, create a config.js file containing all the information about the Node and its URI. In this section, you must store information such as the local network’s id, mainnet, and Fuji.

Then, paste the port you copied from the ANR’sANR’s output earlier.

Import Libraries and Setup instances for Avalanche APIs

The code below is a helper for any other functions distributed over multiple files. The following code will utilize AvalancheJS to instantiate and export the required Avalanche APIs so that other files can use them. The Avalanche APIs must be imported into all other files before they can be reused. Please create a new file called importAPI.js and paste the code below into 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,};

4. Get the Genesis Data

When a blockchain is generated, some genesis data is created. The VM specifies the format and semantics of this genesis data. Subnet-default EVM’s genesis data is used in this tutorial.

{ "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 establishing the chain’s initial state, such as transaction fees, native asset’s initial balance allocations, smart contract access restrictions, etc. You should search for two major parameters in the genesis file:

"alloc": { "d109c2fCfc7fE7AE9ccdE37529E50772053Eb7EE": { "balance": "0x52B7D2DCC80CD2E4000000" }}

Enter your Ethereum-derived hexadecimal address (without 0x) to receive the corresponding balance. You can also use MetaMask to create a new address and use it here.

Then give your chain a unique chain id. Inconsistent chain ids might cause issues.

"chainId": 00000

5. Create the Subnet

Getting Started

The next step is to use AvalancheJS’AvalancheJS’ platform API to create a new subnet by calling buildCreateSubnetTx. Subnet-owner and threshold are the two arguments for this function.

To control subnets, subnet owners create signed transactions, add validators, and construct new chains, among other things. The threshold defines the minimum number of signatures required for approval on behalf of all subnet owners. Because the count is set to 1, there is only one subnet owner by default. The commands below will execute 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();

For this transaction, you will be given a txID; keep it safe. After the transaction is accepted, a new subnet with the same ID will be formed. To run this application, use the following code.

node createSubnet.js

Adding Subnet Validator

Validators are needed for the newly constructed subnet, who will validate transactions on each subnet’s blockchains. As a result, we have to add validators to the subnet for which we write the code in addSubnetValidator.js.

1const args = require("yargs").argv; 2const SubnetAuth = require("avalanche").platformvm.SubnetAuth; 3const { 4platform, 5info, 6pKeyChain, 7pAddressStrings, 8utxoSet, 9BN, 10} = require("./importAPI.js"); 11async function addSubnetValidator() { 12let { 13nodeID = await info.getNodeID(), 14startTime, 15endTime, 16weight = 20, 17subnetID, 18} = args; 19// Creating subnet auth 20const addressIndex = Buffer.alloc(4); 21addressIndex.writeUIntBE(0x0, 0, 4); 22const subnetAuth = new SubnetAuth([addressIndex]); 23// Creating unsgined tx 24const unsignedTx = await platform.buildAddSubnetValidatorTx( 25await utxoSet(), // set of utxos this tx will consume 26pAddressStrings, // from 27pAddressStrings, // change 28nodeID, // node id of the validator 29new BN(startTime), // timestamp after which validation starts 30new BN(endTime), // timestamp after which validation ends 31new BN(weight), // weight of the validator 32subnetID, // subnet id for validation 33subnetAuth // subnet owners' address indices signing this tx 34); 35// signing unsgined tx with pKeyChain 36const tx = unsignedTx.sign(pKeyChain); 37// issuing tx 38const txId = await platform.issueTx(tx); 39console.log("Tx ID: ", txId); 40} 41addSubnetValidator();

Whitelist Subnet from the Node

The owner of a node can add it to a subnet. If a node wants to validate a recently added subnet, it must restart its avalanchego binary and add the new subnet to the whitelist.

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=""

Once the Node has been restarted, it will be assigned to a random API port. The new port must be added to the config.js file.

6. Create the blockchain

Following the completion of the subnet setup, subnet owners can deploy any number of blockchains by either establishing their own VMs or reusing existing ones. Each Node must place the new VM binary in its avalanchego/build/plugins/ folder if the subnet validators are not utilizing the new blockchain’s VM.

This article shows how to build a new blockchain using the existing genesis. As a blueprint, this chain will employ json and subnet-evm (VM). Follow the steps, making sure you understand each function before adding it to your createBlockchain.js file.

Importing dependencies

The dependencies are imported using the code below.

const args = require("yargs").argv;const SubnetAuth = require("avalanche").platformvm.SubnetAuth;const genesisJSON = require("./genesis.json");const { platform, pKeyChain, pAddressStrings, bintools, utxoSet,} = require("./importAPI");

Decode CB58 vmID to String

The utility function decodevmNas me from the vmID is listed below. vmID is a 32-byte array with a zero-extended string encoded in CB58.

// Returns string representing vmName of the provided vmIDfunction convertCB58ToString(cb58Str) { const buff = bintools.cb58Decode(cb58Str); return buff.toString();}

Creating Blockchain

Three to four command-line flags can be passed to the createBlockchain() function. The user must supply the subnetID and chainName flags, and the flags must be provided to either vmID or vmName as the third argument.

The Chain name parameter in the function refers to the name of the blockchain you want to create using the given vmID or vmName. The vmID you used to build the subnet-evm binary must be matched.

// 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);}

Make sure to save the txID received after running this code. When the transaction is done, the txID becomes the blockchainID or identification for the newly deployed chain.

Run the following command:

node createBlockchain.js \--subnetID \--chainName \--vmName subnetevm

Your new Avalanche blockchain will be up and running within a few seconds. You can also view the logs on the Avalanche Network Runner tab of the terminal.

--

--

LeewayHertz
Predict

AI development company enabling innovation and rapid development We build cutting edge software solutions for startup. https://www.leewayhertz.com