Quick-start with Deku-P
In this 20-minute tutorial, we'll get you up and running with Deku-P sidechains. You'll learn how to:
- Write a Deku-P virtual machine in NodeJS
- Deploy a Deku-P network locally using Docker
- Interact with the network via the
deku-cli
Installing the Tools
For this example you'll need the following tools installed:
- NodeJS and npm.
- Docker and Docker Compose (ships with Docker in recent versions, check the installation guide for more info)
- The
deku-cli
(see footnote for OSX1):wget https://github.com/marigold-dev/deku/releases/download/v0.1.0/deku-clichmod +x ./deku-clicp ./deku-cli /usr/bin
Our First VM
Deku's consensus layer determines the order in which transactions are processed, ensuring no two nodes ever commit to inconsistent histories. However, it is up to you, the VM developer, to determine what these transactions mean.
A Deku VM is a program that receives transactions submitted by users and responds with a series of writes and reads to the Deku data store using the SDK. Deku VM's must be deterministic to ensure consistency across all nodes, but beyond that there is no restriction on their behavior.
To get started, we'll first need the NodeJS SDK.
npm init -ynpm install @marigold-dev/deku-p-sdk
Let's make a VM that maintains a global counter that can incremented and
decremented. Create a file called vm.js
and add the following:
const { main, get, set } = require("@marigold-dev/deku-p-sdk");const transition = (tx) => {// Parse the operation dataconst operation = JSON.parse(tx.operation);console.log("Parsed operation:", operation);const [operationKind, value] = operation;// Retrieve the current state of the 'counter' key.// This is an in-memory lookup - no IO required.const counter = JSON.parse(get("counter"));console.log("Current state:", counter);switch (operation[0]) {case "Increment":set("counter", JSON.stringify(counter + value));break;case "Decrement":set("counter", JSON.stringify(counter - value));default:const error = `Unrecognized operation kind: ${operationKind}`;console.error(error);// Signal an error to the user by returning a stringreturn error;}};// Here we define the initial state of blockchain's key-value// store on the genesis block.const initialState = {counter: JSON.stringify(42),};main(initialState, transition);
Testing the VM
During development we can use the deku-cli
to test our VM.
First we'll need to create an identity for the user that will run the transaction:
deku-cli generate-identity --output ./wallet.json
Next, we can run a mock transaction against our vm. In this case we'll submit an
Increment
operation:
deku-cli mock-transaction ./wallet.json '["Increment", 3]' 'node ./vm.js'
Packaging Your Sidechain
To simplify running the chain locally, we'll package our sidechain with Docker.
First create a file called ./start.sh:
#!/usr/bin/env bash# A list of of the tz1 public key hashes for each validator in this network# (derived from their secret keys)export DEKU_VALIDATORS=tz1fpf9DffkGAnzT6UKMDoS4hZjNmoEKhGsK,tz1PYdVbnLwiqKo3fLFXTKxw6K7BhpddQPh8,tz1Pv4viWq7ye4R6cr9SKR3tXiZGvpK34SKi,tz1cXKCCxLwYCHDSrx9hfD5Qmbs4W8w2UKDwexport DEKU_VALIDATOR_URIS=localhost:4440,localhost:4441,localhost:4442,localhost:4443export DEKU_TEZOS_RPC_NODE=http://flextesa:20000# The secret key of a Tezos with which to post updates to Tezos. In Flextesa networks# this wallet is pre-seeded with funds.export DEKU_TEZOS_SECRET=edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq# The address of the bridge contract deployed to the deku-flextesa Tezos network.export DEKU_TEZOS_CONSENSUS_ADDRESS=KT1LHcxdRTgyFp1TdrgodVekLFkQwzFnTJcY# During local development, it is sometimes useful to use smaller block sizes# and to artificially throttle the block rate so as to not consume all the CPU's resources.export DEKU_DEFAULT_BLOCK_SIZE=5000# Enable Debug logging for the NodeJS SDKexport DEKU_VM_DEBUG_LOGGING=true# Wait for Flextesa to startsleep 5# Start the deku-node in the background with the path# to the pipe for communicating with the VMdeku-node --named-pipe-path /run/deku/pipe &node ./vm.js /run/deku/pipe
Make ./start.sh executable:
chmod +x ./start.sh
Next, create a file called Dockerfile
and add the following:
# We'll use the official nodejs# image as our base, but you should be able# to use any image.FROM node# Copy the deku-node and it's required system libraries# into your image.COPY --from=ghcr.io/marigold-dev/deku:latest /nix /nix/COPY --from=ghcr.io/marigold-dev/deku:latest /bin/deku-node /binWORKDIR /app# Build your Deku VMCOPY package.json /appRUN npm installCOPY ./vm.js .COPY ./start.sh .CMD /app/start.sh
Build and tag the image as my-sidechain
:
docker build . -t my-sidechain
Building a Network with Docker Compose
With the VM written and packaged, we can now declare a runnable Deku network with Docker compose.
Here's an annotated configuration for a network of four Deku nodes.
Copy this into a file called docker-compose.yml
:
version: "3.6"services:# We run a full Tezos sandbox network using Flextesa (https://tezos.gitlab.io/flextesa/).# The deku-flextesa image has already been pre-populated with a Deku bridge contract# configured for these nodes.flextesa:container_name: deku_flextesa# FIXME: publish this image somewhereimage: ghcr.io/marigold-dev/deku-fextesa:latestcommand: kathmandubox startenvironment:- block_time=4- flextesa_node_cors_origin=*ports:- 127.0.0.1:20000:20000deku-node-0:container_name: deku-node-0image: my-sidechainenvironment:# A b58-encoded Ed25519 secret used to sign blocks- DEKU_SECRET=edsk4UWkJqpZrAm26qvJE8uY9ZFGFqQiFuBcDyEPASXeHxuD68WvvF- DEKU_PORT=4440# We'll enable the API for a single node on port 8080- DEKU_API_ENABLED=true- DEKU_API_PORT=8080network_mode: "host"deku-node-1:container_name: deku-node-1image: my-sidechainenvironment:- DEKU_SECRET=edsk2mbL2Z7bAmRnuYbmsRe8Yu9rgAq1h993SDxoZncmqyMHDECyBa- DEKU_PORT=4441network_mode: "host"deku-node-2:container_name: deku-node-2image: my-sidechainenvironment:- DEKU_SECRET=edsk3dx8ZfcaBXsuLsk8fawS1qxjHbZtEoEdpAwxhsjmYTQhoEUxFk- DEKU_PORT=4442network_mode: "host"deku-node-3:container_name: deku-node-3image: my-sidechainenvironment:- DEKU_SECRET=edsk3MwFfcGp5FsZgrX8FGiBiDutX2kfAuPzU6VdZpKYLyDRVPb879- DEKU_PORT=4443network_mode: "host"
You can now run your chain with docker compose
:
docker compose up
Shutting down the chain quickly during development can cause the chain
to hard-fork and get stuck. Use docker compose up --force-recreate
until
this is fixed
Interacting with the Chain
We can submit transactions with deku-cli using the wallet we created earlier:
deku-cli submit-transaction --api-uri http://localhost:8080 ./wallet.json '["Increment", 3]'
Once the operation is included, you can verify the result by querying the counter
key of
blockchain state via the REST API:
curl http://localhost:8080/api/v1/state/unix/counter
For more about writing browser-based DApp frontends, out @marigold-dev/deku package on npm. on this guide, or our blockchain game Decookies for an end-to-end example.
Next Steps
With just a few lines of Javascript, you created an application-specific blockchain distributed across 4 nodes.
However, blockchains are still intrinsically complex distributed systems, and there's lots more to cover!
In the following articles, we'll deep dive Deku's architecture, operational characteristics, and deployment best practices. We'll also cover some tips and tricks for a smooth development experience when writing Deku-P applications.