Webux Lab

By Studio Webux

Private local Cardano cluster

TG
Tommy Gingras Studio Webux 2024-07-21

Local Private Cardano Cluster

Introduction

Here are my notes on deploying a local Cardano cluster. I wanted to learn more and experiment with various aspects of the Cardano ecosystem. Focusing on the infrastructure part allows me to understand the flow and interactions between different components. Additionally, I aim to improve my skills with the cardano-cli and cardano-node commands. I prefer CLI manipulation as it is easier for me compared to installing a bunch of dependencies for various languages.

Notes

For this cluster, I used three files: two for Docker-related tasks and one to move funds from the genesis addresses.

Prepare Container Recipe

Cardano provides a script to quickly and easily start a private cluster. I wrapped it in Docker to give me more flexibility.

Dockerfile

# docker build -t cardano-private --progress=plain .
FROM ubuntu:22.04

WORKDIR /app

RUN apt update && apt install curl git jq -y

RUN curl -OL https://github.com/IntersectMBO/cardano-node/releases/download/9.0.0/cardano-node-9.0.0-linux.tar.gz && \
    tar xvzf cardano-node-9.0.0-linux.tar.gz && \
    rm -f cardano-node-9.0.0-linux.tar.gz

RUN ln -s /app/bin/cardano-cli /usr/local/bin/cardano-cli && \
    ln -s /app/bin/cardano-node /usr/local/bin/cardano-node

RUN git clone https://github.com/IntersectMBO/cardano-node.git

RUN sed -i cardano-node/scripts/babbage/mkfiles.sh \
    -e 's|# echo "TestConwayHardForkAtEpoch: 0" >> "${ROOT}/configuration.yaml"|echo "TestConwayHardForkAtEpoch: 0" >> "${ROOT}/configuration.yaml"|'

CMD ["sleep", "infinity"]

Start and Initialize the Cluster

As of the time of writing, the cluster will be on the Conway era (2024-07-21).

docker-compose.yml

services:
  launcher:
    container_name: launcher
    build:
      context: .
    volumes:
      - ./cluster:/app/cardano-node/example

  node1:
    container_name: node1
    build:
      context: .
    volumes:
      - ./cluster:/app/cardano-node/example
    working_dir: /app/cardano-node/
    entrypoint: ./example/node-spo1.sh

  node2:
    container_name: node2
    build:
      context: .
    volumes:
      - ./cluster:/app/cardano-node/example
    working_dir: /app/cardano-node/
    entrypoint: ./example/node-spo2.sh

  node3:
    container_name: node3
    build:
      context: .
    volumes:
      - ./cluster:/app/cardano-node/example
    working_dir: /app/cardano-node/
    entrypoint: ./example/node-spo3.sh

  ogmios:
    container_name: ogmios
    image: cardanosolutions/ogmios:v6.5.0
    ports:
      - 1337:1337
    volumes:
      - ./cluster:/app/cardano-node/example
    command: '--node-socket /app/cardano-node/example/node-spo1/node.sock --node-config /app/cardano-node/example/configuration.yaml --host 0.0.0.0'

To build and start the cluster, execute the following commands in this order. The first time, the nodes will fail as the files aren’t created yet.

docker compose build
docker compose up
docker exec -it -w /app/cardano-node/ launcher bash -c "./scripts/babbage/mkfiles.sh"

Then, you have only 30 seconds to do the following:

docker compose up # (within 30 sec)

After that, return to the launcher container:

docker exec -it launcher bash

# Wait few seconds then it should work and show as below,
export CARDANO_NODE_SOCKET_PATH=/app/cardano-node/example/node-spo1/node.sock
cardano-cli query tip --testnet-magic 42

Example:

{
  "block": 2,
  "epoch": 0,
  "era": "Conway",
  "hash": "dd870e18b64d49a97c485085861f70c292efbaba98aecff97efed172686e97dc",
  "slot": 35,
  "slotInEpoch": 35,
  "slotsToEpochEnd": 465,
  "syncProgress": "100.00"
}

Move Funds

Reusing the launcher and execute the following:

docker exec -it launcher bash
export CARDANO_NODE_SOCKET_PATH=/app/cardano-node/example/node-spo1/node.sock

Extract the address from the genesis key:

cardano-cli signing-key-address \
    --testnet-magic 42 \
    --secret /app/cardano-node/example/byron-gen-command/genesis-keys.000.key > /app/cardano-node/example/byron-gen-command/genesis-keys.000.addr

cardano-cli signing-key-address \
    --testnet-magic 42 \
    --secret /app/cardano-node/example/byron-gen-command/genesis-keys.001.key > /app/cardano-node/example/byron-gen-command/genesis-keys.001.addr

cardano-cli signing-key-address \
    --testnet-magic 42 \
    --secret /app/cardano-node/example/byron-gen-command/genesis-keys.002.key > /app/cardano-node/example/byron-gen-command/genesis-keys.002.addr

Create a payment address (using the CLI): This address will receive the funds

mkdir -p /app/cardano-node/example/wallets/

cardano-cli address key-gen \
--verification-key-file /app/cardano-node/example/wallets/payment.vkey \
--signing-key-file /app/cardano-node/example/wallets/payment.skey

cardano-cli address build \
--payment-verification-key-file /app/cardano-node/example/wallets/payment.vkey \
--out-file /app/cardano-node/example/wallets/payment.addr \
--testnet-magic 42

cardano-cli query utxo --address $(cat /app/cardano-node/example/wallets/payment.addr) --testnet-magic 42

Extract the wallet address from the UTXO keys:

cardano-cli address build \
--payment-verification-key-file /app/cardano-node/example/utxo-keys/utxo1.vkey \
--out-file /app/cardano-node/example/wallets/utxo1.addr \
--testnet-magic 42
cardano-cli query utxo --address $(cat /app/cardano-node/example/wallets/utxo1.addr) --testnet-magic 42

cardano-cli address build \
--payment-verification-key-file /app/cardano-node/example/utxo-keys/utxo2.vkey \
--out-file /app/cardano-node/example/wallets/utxo2.addr \
--testnet-magic 42
cardano-cli query utxo --address $(cat /app/cardano-node/example/wallets/utxo2.addr) --testnet-magic 42

cardano-cli address build \
--payment-verification-key-file /app/cardano-node/example/utxo-keys/utxo3.vkey \
--out-file /app/cardano-node/example/wallets/utxo3.addr \
--testnet-magic 42
cardano-cli query utxo --address $(cat /app/cardano-node/example/wallets/utxo3.addr) --testnet-magic 42

Goal: Transfer 100 ADA from delegated genesis address 0 to the payment address.
1,000,000 lovelace = 1 ADA
100 ADA = 100,000,000 lovelace

mkdir -p /app/cardano-node/example/wallets/transactions
cd /app/cardano-node/example/wallets/transactions

cardano-cli query protocol-parameters \
    --testnet-magic 42 \
    --out-file protocol.json

# Print the UTXOs from the utxo1 address,
# you need this value in order to create and send lovelace
cardano-cli query utxo --address $(cat /app/cardano-node/example/wallets/utxo1.addr) --testnet-magic 42

                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
8d5a6eac5622f7b2f021ce6106d5eca201b7c1be3f1417051578efca482f4169     0        600000000000 lovelace + TxOutDatumNone

# The tx-out is the address from the payment address (created above using the cardano-cli)
cardano-cli transaction build-raw \
    --tx-in 8d5a6eac5622f7b2f021ce6106d5eca201b7c1be3f1417051578efca482f4169#0 \
    --tx-out $(cat /app/cardano-node/example/wallets/payment.addr)+0 \
    --tx-out $(cat /app/cardano-node/example/wallets/utxo1.addr)+0 \
    --invalid-hereafter 0 \
    --fee 0 \
    --out-file tx.draft

cardano-cli transaction calculate-min-fee \
    --tx-body-file tx.draft \
    --tx-in-count 1 \
    --tx-out-count 2 \
    --witness-count 1 \
    --byron-witness-count 0 \
    --testnet-magic 42 \
    --protocol-params-file protocol.json

# fee: 165105
# 600000000000 - 100000000 - 170000  = 599899830000 <- this number will be assigned to the change address (I increase the fee a little to avoid missing lovelace in fee)

cardano-cli query tip --testnet-magic 42

# slot: 50000 + 25000 (25000 because this private network goes fast, you can put the value you want this is your private cluster !) = 75000

# Fill the information with your data:
cardano-cli transaction build-raw \
    --tx-in 8d5a6eac5622f7b2f021ce6106d5eca201b7c1be3f1417051578efca482f4169#0 \
    --tx-out $(cat /app/cardano-node/example/wallets/payment.addr)+100000000 \ # <- The ADA sent to the payment address
    --tx-out $(cat /app/cardano-node/example/wallets/utxo1.addr)+599899830000 \ # <- The ADA sent back to the utxo1 address
    --invalid-hereafter 75000 \
    --fee 170000 \
    --out-file tx.raw

cardano-cli transaction sign \
    --tx-body-file tx.raw \
    --signing-key-file /app/cardano-node/example/utxo-keys/utxo1.skey \
    --testnet-magic 42 \
    --out-file tx.signed

cardano-cli transaction submit \
    --tx-file tx.signed \
    --testnet-magic 42

Validation:

root@4ea4bf7ae5d4:/app/cardano-node/example#
*Command*
cardano-cli query utxo --address $(cat /app/cardano-node/example/wallets/utxo1.addr) --testnet-magic 42

*Output*
                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
22c03ae81249a443ca0956d03ab5dca5440ba05b8d5ee2ce359f16195cd3f718     1        599899830000 lovelace + TxOutDatumNone

*Command*
cardano-cli query utxo --address $(cat /app/cardano-node/example/wallets/payment.addr) --testnet-magic 42

*Output*
                           TxHash                                 TxIx        Amount
--------------------------------------------------------------------------------------
22c03ae81249a443ca0956d03ab5dca5440ba05b8d5ee2ce359f16195cd3f718     0        100000000 lovelace + TxOutDatumNone

Conclusion

So far, this is where I am in the discovery process.

Next steps include creating policies, NFTs, FTs, multiple addresses, connecting Deno to the exposed socket, a dummy faucet, understanding indexing, trying to connect external wallets, and much more!

You can also access ogmios at: http://localhost:1337

Sources and References

ening-for-payments-cli)

Errors

Command failed: transaction submit  Error: Error while submitting tx: ShelleyTxValidationError ShelleyBasedEraConway (ApplyTxError (ConwayUtxowFailure (UtxoFailure (ValueNotConservedUTxO (MaryValue (Coin 0) (MultiAsset (fromList []))) (MaryValue (Coin 600000000000) (MultiAsset (fromList []))))) :| [ConwayUtxowFailure (UtxoFailure (BadInputsUTxO (fromList [TxIn (TxId {unTxId = SafeHash "8d5a6eac5622f7b2f021ce6106d5eca201b7c1be3f1417051578efca482f4169"}) (TxIx {unTxIx = 0})])))]))

This likely indicates that you forgot to update the --tx-in with the UTXO obtained from the command cardano-cli query utxo --address $(cat /app/cardano-node/example/wallets/utxo1.addr) --testnet-magic 42.


Command failed: transaction submit  Error: Error while submitting tx: ShelleyTxValidationError ShelleyBasedEraConway (ApplyTxError (ConwayUtxowFailure (UtxoFailure (ValueNotConservedUTxO (MaryValue (Coin 0) (MultiAsset (fromList []))) (MaryValue (Coin 600000000000) (MultiAsset (fromList []))))) :| [ConwayUtxowFailure (UtxoFailure (BadInputsUTxO (fromList [TxIn (TxId {unTxId = SafeHash "8d5a6eac5622f7b2f021ce6106d5eca201b7c1be3f1417051578efca482f4169"}) (TxIx {unTxIx = 0})]))),ConwayUtxowFailure (UtxoFailure (FeeTooSmallUTxO (Coin 165721) (Coin 165105))),ConwayUtxowFailure (UtxoFailure (OutsideValidityIntervalUTxO (ValidityInterval {invalidBefore = SNothing, invalidHereafter = SJust (SlotNo 1000)}) (SlotNo 9309)))]))

Due to the rapid slot generation by private nodes, it’s necessary to set the slot value higher than usual.


Command failed: transaction submit  Error: Error while submitting tx: ShelleyTxValidationError ShelleyBasedEraConway (ApplyTxError (ConwayUtxowFailure (UtxoFailure (ValueNotConservedUTxO (MaryValue (Coin 0) (MultiAsset (fromList []))) (MaryValue (Coin 600000000000) (MultiAsset (fromList []))))) :| [ConwayUtxowFailure (UtxoFailure (BadInputsUTxO (fromList [TxIn (TxId {unTxId = SafeHash "8d5a6eac5622f7b2f021ce6106d5eca201b7c1be3f1417051578efca482f4169"}) (TxIx {unTxIx = 0})]))),ConwayUtxowFailure (UtxoFailure (FeeTooSmallUTxO (Coin 165809) (Coin 165105)))]))

I encountered an issue with my transaction fees being lower than needed. To fix this, I rounded up the fee to the nearest acceptable value. For example, I changed 165600 to 166000. This adjustment ensures that transactions are processed smoothly without errors due to insufficient fees.



Search