Content-Addressable Key-Value Store w/EIP-712 Compatibility and Fee-Based Metering
This code is similar to SpacesVM but does away with the hierarchical, authenticated namespace, user-specified keys, and key expiry.
Avalanche is a network composed of multiple sub-networks (called subnets) that each contain any number of blockchains. Each blockchain is an instance of a Virtual Machine (VM), much like an object in an object-oriented language is an instance of a class. That is, the VM defines the behavior of the blockchain where it is instantiated. For example, Coreth (EVM) is a VM that is instantiated by the C-Chain. Likewise, one could deploy another instance of the EVM as their own blockchain (to take this to its logical conclusion).
[v0.0.1] AvalancheGo@v1.7.7-v1.7.9
[v0.0.2] AvalancheGo@v1.7.7-v1.7.9
[v0.0.3] AvalancheGo@v1.7.10
[v0.0.4] AvalancheGo@v1.7.11-v1.7.12
[v0.0.5] AvalancheGo@v1.7.13-v1.7.18
[v0.0.6] AvalancheGo@v1.7.13-v1.7.18
[v0.0.7] AvalancheGo@v1.7.13-v1.7.18
[v0.0.7] AvalancheGo@v1.7.13-v1.7.18
[v0.0.8] AvalancheGo@v1.8.0-v1.8.6
[v0.0.9] AvalancheGo@v1.9.0
[v0.0.10] AvalancheGo@v1.9.1-1.9.2
[v0.0.11] AvalancheGo@v1.9.1-1.9.3
Just as Coreth powers the C-Chain, BlobVM can be used to power its own blockchain in an Avalanche Subnet. Instead of providing a place to execute Solidity smart contracts, however, BlobVM enables content-addressable storage of arbitrary keys/values using any EIP-712 compatible wallet.
All keys in BlobVM are keccak256 hashes (each of a unique value stored in
state). The max length of values is defined in genesis but typically ranges
between 64-200KB. Any number of values can be linked together to store files in
the > 100s of MBs range (as long as you have the BLB
to pay for it).
EIP-712 Compatible
The canonical digest of a BlobVM transaction is EIP-712 compliant, so any Web3 wallet that can sign typed data can interact with BlobVM.
EIP-712 compliance in this case, however, does not mean that BlobVM is an EVM or even an EVM derivative. BlobVM is a new Avalanche-native VM written from scratch to optimize for storage-related operations.
As soon as you have some BLB
, you can then use SetTx
to
persist some value blob into state. This value blob will be accessible at
keccak256(value)
. This value will live in state forever.
To support common blockchain use cases (like NFT storage), BlobVM
supports the storage of arbitrary size files using a basic metadata file format.
You can try this out using blob-cli set-file <filename>
.
When you want to view data stored in BlobVM, you call Resolve
on the value
path: <key>
. If you stored a file, use this command to retrieve it:
blob-cli resolve-file <root> <destination filepath>
.
If you want to share some of your BLB
with your friends, you can use
a TransferTx
to send to any EVM-style address.
All interactions with the BlobVM require the payment of fees (denominated in
BLB
). The VM Genesis includes support for allocating one-off BLB
to
different EVM-style addresses and to allocating BLB
to an airdrop list.
Nearly all fee-related params can be tuned by the BlobVM deployer.
To deter node operators from deleting data stored in state, each block header includes the hash of a randomly selected state value concatenated with the parent blockID. If values are pruned, node operators can't produce/verify blocks.
If you are interested in running the VM, not using it. Jump to Running the VM.
git clone https://github.com/ava-labs/blobvm.git;
cd blobvm;
go install -v ./cmd/blob-cli;
BlobVM CLI
Usage:
blob-cli [command]
Available Commands:
activity View recent activity on the network
completion Generate the autocompletion script for the specified shell
create Creates a new key in the default location
genesis Creates a new genesis in the default location
help Help about any command
network View information about this instance of the BlobVM
resolve Reads a value at key
resolve-file Reads a file at a root and saves it to disk
set Writes a value to BlobVM
set-file Writes a file to BlobVM (using multiple keys)
transfer Transfers units to another address
Flags:
--endpoint string RPC endpoint for VM
-h, --help help for blob-cli
--private-key-file string private key file path (default ".blob-cli-pk")
--verbose Print verbose information about operations
Use "blob-cli [command] --help" for more information about a command.
blob-cli set-file ~/Downloads/computer.gif -> 6fe5a52f52b34fb1e07ba90bad47811c645176d0d49ef0c7a7b4b22013f676c8
blob-cli resolve-file 6fe5a52f52b34fb1e07ba90bad47811c645176d0d49ef0c7a7b4b22013f676c8 computer_copy.gif
// Client defines blobvm client operations.
type Client interface {
// Pings the VM.
Ping(ctx context.Context) (bool, error)
// Network information about this instance of the VM
Network(ctx context.Context) (uint32, ids.ID, ids.ID, error)
// Returns the VM genesis.
Genesis(ctx context.Context) (*chain.Genesis, error)
// Accepted fetches the ID of the last accepted block.
Accepted(ctx context.Context) (ids.ID, error)
// Balance returns the balance of an account
Balance(ctx context.Context, addr common.Address) (bal uint64, err error)
// Resolve returns the value associated with a path
Resolve(ctx context.Context, key common.Hash) (exists bool, value []byte, valueMeta *chain.ValueMeta, err error)
// Requests the suggested price and cost from VM.
SuggestedRawFee(ctx context.Context) (uint64, uint64, error)
// Issues the transaction and returns the transaction ID.
IssueRawTx(ctx context.Context, d []byte) (ids.ID, error)
// Requests the suggested price and cost from VM, returns the input as
// TypedData.
SuggestedFee(ctx context.Context, i *chain.Input) (*tdata.TypedData, uint64, error)
// Issues a human-readable transaction and returns the transaction ID.
IssueTx(ctx context.Context, td *tdata.TypedData, sig []byte) (ids.ID, error)
// Checks the status of the transaction, and returns "true" if confirmed.
HasTx(ctx context.Context, id ids.ID) (bool, error)
// Polls the transactions until its status is confirmed.
PollTx(ctx context.Context, txID ids.ID) (confirmed bool, err error)
// Recent actions on the network (sorted from recent to oldest)
RecentActivity(ctx context.Context) ([]*chain.Activity, error)
}
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.ping",
"params":{},
"id": 1
}
>>> {"success":<bool>}
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.network",
"params":{},
"id": 1
}
>>> {"networkId":<uint32>, "subnetId":<ID>, "chainId":<ID>}
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.genesis",
"params":{},
"id": 1
}
>>> {"genesis":<genesis file>}
Provide your intent and get back a transaction to sign.
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.suggestedFee",
"params":{
"input":<chain.Input (tx abstractor)>
},
"id": 1
}
>>> {"typedData":<EIP-712 compliant typed data for signing>,
>>> "totalCost":<uint64>}
{
"type":<string>,
"key":<string>,
"value":<base64 encoded>,
"to":<hex encoded>,
"units":<uint64>
}
set {type,key,value}
transfer {type,to,units}
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.issueTx",
"params":{
"typedData":<EIP-712 compliant typed data>,
"signature":<hex-encoded sig>
},
"id": 1
}
>>> {"txId":<ID>}
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.hasTx",
"params":{
"txId":<transaction ID>
},
"id": 1
}
>>> {"accepted":<bool>}
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.lastAccepted",
"params":{},
"id": 1
}
>>> {"height":<uint64>, "blockId":<ID>}
{
"key":<string>,
"valueMeta":{
"created":<unix>,
"updated":<unix>,
"txId":<ID>, // where value was last set
"size":<uint64>
}
}
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.resolve",
"params":{
"key":<string>
},
"id": 1
}
>>> {"exists":<bool>, "value":<base64 encoded>, "valueMeta":<chain.ValueMeta>}
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.balance",
"params":{
"address":<hex encoded>
},
"id": 1
}
>>> {"balance":<uint64>}
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.recentActivity",
"params":{},
"id": 1
}
>>> {"activity":[<chain.Activity>,...]}
{
"timestamp":<unix>,
"sender":<address>,
"txId":<ID>,
"type":<string>,
"key":<string>,
"to":<hex encoded>,
"units":<uint64>
}
set {timestamp,sender,txId,type,key,value}
transfer {timestamp,sender,txId,type,to,units}
Can use this to get the current fee rate.
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.suggestedRawFee",
"params":{},
"id": 1
}
>>> {"price":<uint64>,"cost":<uint64>}
<<< POST
{
"jsonrpc": "2.0",
"method": "blobvm.issueRawTx",
"params":{
"tx":<raw tx bytes>
},
"id": 1
}
>>> {"txId":<ID>}
To build the VM (and blob-cli
), run ./scripts/build.sh
.
scripts/run.sh
automatically installs avalanchego, sets up a local network,
and creates a blobvm
genesis file. To build and run E2E tests, you need to set the variable E2E
before it: E2E=true ./scripts/run.sh 1.7.11
See tests/e2e
to see how it's set up and how its client requests are made.
# to startup a local cluster (good for development)
cd ${HOME}/go/src/github.com/ava-labs/blobvm
./scripts/run.sh 1.7.11
# to run full e2e tests and shut down cluster afterwards
cd ${HOME}/go/src/github.com/ava-labs/blobvm
E2E=true ./scripts/run.sh 1.7.11
# inspect cluster endpoints when ready
cat /tmp/avalanchego-v1.7.11/output.yaml
<<COMMENT
endpoint: /ext/bc/2VCAhX6vE3UnXC6s1CBPE6jJ4c4cHWMfPgCptuWS59pQ9vbeLM
logsDir: ...
pid: 12811
uris:
- http://localhost:56239
- http://localhost:56251
- http://localhost:56253
- http://localhost:56255
- http://localhost:56257
COMMENT
# ping the local cluster
curl --location --request POST 'http://localhost:61858/ext/bc/BJfusM2TpHCEfmt5i7qeE1MwVCbw5jU1TcZNz8MYUwG1PGYRL/public' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"method": "blobvm.ping",
"params":{},
"id": 1
}'
<<COMMENT
{"jsonrpc":"2.0","result":{"success":true},"id":1}
COMMENT
# resolve a path
curl --location --request POST 'http://localhost:61858/ext/bc/BJfusM2TpHCEfmt5i7qeE1MwVCbw5jU1TcZNz8MYUwG1PGYRL/public' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"method": "blobvm.resolve",
"params":{
"key": "0xd35882ae256d63123710cf8ab4343282d4a2c246281d3ff5e2b244744c8f7be4"
},
"id": 1
}'
<<COMMENT
{"jsonrpc":"2.0","result":{"exists":true, "value":"....", "valueMeta":{....}},"id":1}
COMMENT
# to terminate the cluster
kill 12811
Anyone can deploy their own instance of the BlobVM as a subnet on Avalanche. All you need to do is compile it, create a genesis, and send a few txs to the P-Chain.
You can do this by following the subnet tutorial or by using the subnet-cli.
BlobVM
does not include any built-in moderation mechanism to block/remove illicit
content. In the future, someone could implement an M-of-N governance contract
that can remove any value if it violates some code of conduct.
The current AccessProof
mechanism is naive and gameable (seeded by the parent
block hash and index). In the future, someone could implement an on-chain VRF
that could be used as a more robust seed.