Create a Custom Blockchain
Introduction
Avalanche supports creating blockchains with virtual machines in subnets. In this tutorial, we’ll create a custom blockchain using a custom Virtual Machine (Timestamp VM).
If you want a blockchain that has capabilities of X-Chain (AVM) or C-Chain (EVM), see Create AVM Blockchain and Create EVM Blockchain respectively.
Note: IDs of Blockchains, Subnets, Transactions and Addresses can be different for each run/network. It means that some inputs, endpoints etc. in the tutorial can be different when you try.
Prerequisites
You will need a running node, a user on the node, and some AVAX in the address controlled by the user. All of that is covered in the Run an Avalanche Node tutorial.
Next, you need to have your node be a validator on the Primary Network. You can find out how to do that in the Add a Validator tutorial. It is recommended you do that with API calls, since that is the way you will be interacting with your node in the rest of this tutorial.
Create the Virtual Machine
Every blockchain is an instance of a virtual machine. For example X-Chain is an instance of AVM and C-Chain is EVM's instance. Avalanche supports creating new blockchains (instances) from Virtual Machines. In this case we will use Timestamp VM, which is an external VM plugin. Timestamp VM will communicate with our AvalancheGo node through RPC.
Create the Subnet
Every blockchain is validated by a subnet. Before you can create a blockchain, you’ll need a subnet to validate it. You can also use a subnet that already exists if you have a sufficient number of its control keys.
info
Add Validators to the Subnet
The subnet needs validators in it to, well, validate blockchains.
Create the Genesis Data
Each blockchain has some genesis state when it’s created. Each VM defines the format and semantics of its genesis data. TimestampVM uses CB58 encoded data as genesis data. There is encode
and decode
static API methods that can be used to encode/decode string data. See TimestampVM API.
Let's generate a simple genesis data for TimestampVM:
curl -X POST --data '{
"jsonrpc": "2.0",
"method": "timestampvm.encode",
"params":{
"data":"helloworld"
},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/vm/tGas3T58KzdjLHhBDMnH2TvrddhqTji5iZAMZ3RXs2NLpSnhH
{
"jsonrpc": "2.0",
"result": {
"bytes": "fP1vxkpyLWnH9dD6BQA",
"encoding": "cb58"
},
"id": 1
}
Our genesis data will be fP1vxkpyLWnH9dD6BQA
.
Create the Blockchain
Now let’s create the new blockchain. To do so, we call platform.createBlockchain
. Your call should look like the one below. You have to change subnetID
to the subnet that will validate your blockchain, and supply a username
that controls a sufficient number of the subnet’s control keys. As a reminder, you can find out what a subnet’s threshold and control keys are by calling platform.getSubnets
.
Recall that we used tGas3T58KzdjLHhBDMnH2TvrddhqTji5iZAMZ3RXs2NLpSnhH
as our VM ID in Create A Virtual Machine(VM).
curl -X POST --data '{
"jsonrpc": "2.0",
"method": "platform.createBlockchain",
"params" : {
"subnetID": "KL1e8io1Zi2kr8cTXxvi321pAzfQuUa8tmBfadqpf9K2dc2TT",
"vmID":"tGas3T58KzdjLHhBDMnH2TvrddhqTji5iZAMZ3RXs2NLpSnhH",
"name":"My new TSVM",
"genesisData": "fP1vxkpyLWnH9dD6BQA",
"username":"USERNAME GOES HERE",
"password":"PASSWORD GOES HERE"
},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/P
The response contains the transaction ID:
{
"jsonrpc": "2.0",
"result": {
"txID": "sw813hGSWH8pdU9uzaYy9fCtYFfY7AjDd2c9rm64SbApnvjmk",
"changeAddr": "P-avax103y30cxeulkjfe3kwfnpt432ylmnxux8r73r8u"
},
"id": 1
}
Verify Success
After a few seconds, the transaction to create our blockchain should have been accepted and the blockchain should exist (assuming the request was well-formed, etc.)
To check, call platform.getBlockchains
. This returns a list of all blockchains that exist.
curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"platform.getBlockchains",
"params" :{}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/P
The response confirms that the blockchain was created:
{
"jsonrpc": "2.0",
"result": {
"blockchains": [
{
"id": "AXerNQX7voY2AABaRdTAyXcawBURBR6thPRJp43LtPpZZi6Qz",
"name": "X-Chain",
"subnetID": "11111111111111111111111111111111LpoYY",
"vmID": "jvYyfQTxGMJLuGWa55kdP2p2zSUYsQ5Raupu4TW34ZAUBAbtq"
},
{
"id": "tZGm6RCkeGpVETUTp11DW3UYFZmm69zfqxchpHrSF7wgy8rmw",
"name": "C-Chain",
"subnetID": "11111111111111111111111111111111LpoYY",
"vmID": "mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6"
},
{
"id": "sw813hGSWH8pdU9uzaYy9fCtYFfY7AjDd2c9rm64SbApnvjmk",
"name": "My new TSVM",
"subnetID": "KL1e8io1Zi2kr8cTXxvi321pAzfQuUa8tmBfadqpf9K2dc2TT",
"vmID": "tGas3T58KzdjLHhBDMnH2TvrddhqTji5iZAMZ3RXs2NLpSnhH"
},
{
"id": "xAd5n5PQFV6RRo8UgH54Gf5tJs8oQdctQS2ygp5F2dKZDckYH",
"name": "My new AVM",
"subnetID": "KL1e8io1Zi2kr8cTXxvi321pAzfQuUa8tmBfadqpf9K2dc2TT",
"vmID": "jvYyfQTxGMJLuGWa55kdP2p2zSUYsQ5Raupu4TW34ZAUBAbtq"
}
]
},
"id": 1
}
Validating the Blockchain
Every blockchain needs a set of validators to validate and process transactions on it. You can check if a node is validating a given blockchain by calling platform.getBlockchainStatus
on that node:
curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"platform.getBlockchainStatus",
"params" :{
"blockchainID":"sw813hGSWH8pdU9uzaYy9fCtYFfY7AjDd2c9rm64SbApnvjmk"
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/P
{
"jsonrpc": "2.0",
"result": {
"status": "Validating"
},
"id": 1
}
If it responds "Validating"
, the node is validating the given chain. If it responds "Syncing"
, then the chain tracked by this node but it is not validating. If it responde "Created"
then the chain exists but it is not being synced. Note that in order to validate or watch a subnet, you need to start your node with argument --whitelisted-subnets=[subnet ID goes here]
(e.g. --whitelisted-subnets=KL1e8io1Zi2kr8cTXxvi321pAzfQuUa8tmBfadqpf9K2dc2TT
) as well as add the node to the subnet's validator set.
More information can be found in the Adding a Subnet Validator tutorial.
Interacting with the New Blockchain
You can interact with this new instance of the VM. The API endpoint of the blockchain is 127.0.0.1:9650/ext/bc/sw813hGSWH8pdU9uzaYy9fCtYFfY7AjDd2c9rm64SbApnvjmk
. The last part in the endpoint is the blockchain ID, which is sw813hGSWH8pdU9uzaYy9fCtYFfY7AjDd2c9rm64SbApnvjmk
. Every blockchain ID is different from each other, so this is not a static ID. Your blockchain ID and the endpoint can be different.
You can also alias this chain ID with timestampbc
, or whatever you like, for simpler API URLs. More information: admin.aliasChain
Verify Genesis Block
In the genesis we specified fP1vxkpyLWnH9dD6BQA
as the genesis data. Let’s verify that:
curl -X POST --data '{
"jsonrpc": "2.0",
"method": "timestampvm.getBlock",
"params":{},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/sw813hGSWH8pdU9uzaYy9fCtYFfY7AjDd2c9rm64SbApnvjmk
{
"jsonrpc": "2.0",
"result": {
"timestamp": "0",
"data": "nyfJkNxEwKeQ9KpPducrm3jRaDzpPNJXUdZtgCWeMZTUxPqGp",
"id": "24kWScv7DMA4LwdoFwmN1iRU3idyHRrrA2UxN9k6AuXihoK3mn",
"parentID": "11111111111111111111111111111111LpoYY"
},
"id": 1
}
As you can see our first block has timestamp: 0
. Also the parent ID (11111111111111111111111111111111LpoYY
) is the P-chain's ID. Let's decode the genesis data with VM's static API method. Recall that our TimestampVM ID is aliased with timestampvm
:
curl -X POST --data '{
"jsonrpc": "2.0",
"id" : 1,
"method" : "timestampvm.decode",
"params" : {
"bytes": "nyfJkNxEwKeQ9KpPducrm3jRaDzpPNJXUdZtgCWeMZTUxPqGp"
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/vm/tGas3T58KzdjLHhBDMnH2TvrddhqTji5iZAMZ3RXs2NLpSnhH
{
"jsonrpc": "2.0",
"result": {
"data":"helloworld",
"encoding": "cb58"
},
"id": 1
}
We can see genesis data has the helloworld
string.
Propose New Block
We can propose new blocks to our blockchain with some data in it.
Let's get encoded data first. Blocks expect to have 32-length bytes. There is a length
argument in encode method:
curl -X POST --data '{
"jsonrpc": "2.0",
"id" : 1,
"method" : "timestampvm.encode",
"params" : {
"data": "mynewblock",
"length": 32
}
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/vm/tGas3T58KzdjLHhBDMnH2TvrddhqTji5iZAMZ3RXs2NLpSnhH
Result:
{
"jsonrpc": "2.0",
"result": {
"bytes": "qDNkrS9xuyGmaAgdHAjbmANSvCKnK5BHvyCybJaFCAqx46Z8y",
"encoding": "cb58"
},
"id": 1
}
Now we can propose a new block with the data:
curl -X POST --data '{
"jsonrpc": "2.0",
"method": "timestampvm.proposeBlock",
"params":{
"data":"qDNkrS9xuyGmaAgdHAjbmANSvCKnK5BHvyCybJaFCAqx46Z8y"
},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/sw813hGSWH8pdU9uzaYy9fCtYFfY7AjDd2c9rm64SbApnvjmk
Result:
{
"jsonrpc": "2.0",
"result": {
"Success": true
},
"id": 1
}
Let's check latest block to verify existence of our proposed block:
curl -X POST --data '{
"jsonrpc": "2.0",
"method": "timestampvm.getBlock",
"params":{},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9650/ext/bc/sw813hGSWH8pdU9uzaYy9fCtYFfY7AjDd2c9rm64SbApnvjmk
Result:
{
"jsonrpc": "2.0",
"result": {
"timestamp": "1625674027",
"data": "qDNkrS9xuyGmaAgdHAjbmANSvCKnK5BHvyCybJaFCAqx46Z8y",
"id": "Br36bggr9vEEoNTNVPsSCD7QHHoCqE31Coui6uh1rA71EGPve",
"parentID": "24kWScv7DMA4LwdoFwmN1iRU3idyHRrrA2UxN9k6AuXihoK3mn"
},
"id": 1
}
Result contains data
field has qDNkrS9xuyGmaAgdHAjbmANSvCKnK5BHvyCybJaFCAqx46Z8y
. This is the same data as our proposed data in the previous step.