Run Testnet node

Unit Zero is an EVM-compatible blockchain network built on top of the Waves blockchain. This section explains how to operate a node on Unit Zero Testnet.

You have two options:

  • Run a validating node without mining blocks

  • Run a mining node and get rewards

In both options, your node synchronizes with the blockchain network and can propose new transactions. Running a validating node has no requirements for token balances.

Prerequisites

Additionally, if you'd like your node to mine:

  • Transfer some WAVES to the node’s account to cover fees.

    Note: The Consensus Client will invoke the Chain Contract on behalf of the account. Therefore, if you set a verifier for the account, the verifier must allow such transactions.

  • Lease (delegate) at least 20,000 WAVES to the node’s account to achieve the minimum generating balance.

Configuration

  1. Create a directory to serve as the main directory for storing data and configurations. Ensure the current user has read and write access to this directory.

  2. Create the data/waves subdirectory and place the Waves Testnet blockchain data inside it.

  3. Create the following subdirectories in the main directory:

    mkdir -p data/besu logs/besu
  4. Grant the user with uid 1000 write access to the data/besu and logs/besu subdirectories:

    chown 1000 data/besu logs/besu
  5. Generate node key and JWT secret:

    mkdir -p data/secrets
    openssl rand -hex 32 | tr -d "\n" > data/secrets/p2p-key
    openssl rand -hex 32 | tr -d "\n" > data/secrets/jwtsecret
  6. Prepare the waves.conf file using the template provided below:

    • Insert your Waves Testnet account's seed converted to a byte array and then encoded in base58, and choose an arbitrary string as a password.

    • Specify your server's static IP address. If your server does not have a static IP address, do not add the declared-address line.

    Place this file in the main directory.

    waves {
      blockchain.type = TESTNET
      extensions = [
        units.ConsensusClient
      ]
    
      wallet {
        seed = <your-base58-encoded-seed>
        password = <some-string-as-password>
        file = null
      }
    
      network {
        port = 6868
        declared-address = "<your server's IP address>:6868"   # Example: 11.22.33.44:6868.
      }
    
      rest-api.bind-address = "0.0.0.0"
    
      l2 {
        chain-contract = 3Msx4Aq69zWUKy4d1wyKnQ4ofzEDAfv5Ngf
        execution-client-address = "http://besu:8551"
        jwt-secret-file = /etc/secrets/jwtsecret
    
        network {
          port = 6865
          known-peers = [
            "testnet-l2-htz-hel1-1.wavesnodes.com:6865"
            "testnet-l2-htz-hel1-2.wavesnodes.com:6865"
            "testnet-htz-nbg1-1.wavesnodes.com:6865"
          ]
          declared-address = "<your server's IP address>:6865"   # Example: 11.22.33.44:6865
        }
        mining-enable = yes
      }
    }
  7. Place the following genesis.json file into the main directory.

    {
      "alloc": {
        "0x0000000000000000000000000000000000006a7e": {
          "code": "0x60806040526004361061006e575f3560e01c806396f396c31161004c57806396f396c3146100e3578063c4a4326d14610105578063e984df0e1461011d578063fccc281314610131575f80fd5b806339dd5d1b146100725780637157405a146100b957806378338413146100ce575b5f80fd5b34801561007d575f80fd5b506100a161008c36600461059e565b5f6020819052908152604090205461ffff1681565b60405161ffff90911681526020015b60405180910390f35b3480156100c4575f80fd5b506100a161040081565b6100e16100dc3660046105b5565b61015c565b005b3480156100ee575f80fd5b506100f761044e565b6040519081526020016100b0565b348015610110575f80fd5b506100f76402540be40081565b348015610128575f80fd5b506100f7610468565b34801561013c575f80fd5b506101445f81565b6040516001600160a01b0390911681526020016100b0565b61016c6402540be40060016105fc565b34101561017834610478565b61019061018b6402540be40060016105fc565b610478565b6040516020016101a1929190610630565b604051602081830303815290604052906101d75760405162461bcd60e51b81526004016101ce9190610688565b60405180910390fd5b506101ef6402540be400677fffffffffffffff6105fc565b3411156101fb34610478565b61021561018b6402540be400677fffffffffffffff6105fc565b6040516020016102269291906106bd565b604051602081830303815290604052906102535760405162461bcd60e51b81526004016101ce9190610688565b50435f8181526020819052604090205461ffff166104009081119061027790610478565b604051602001610287919061070c565b604051602081830303815290604052906102b45760405162461bcd60e51b81526004016101ce9190610688565b505f818152602081905260408120805461ffff16916102d283610786565b91906101000a81548161ffff021916908361ffff160217905550505f6402540be400346102ff91906107a6565b9050346103116402540be400836105fc565b1461031b34610478565b6103296402540be400610478565b60405160200161033a9291906107c5565b604051602081830303815290604052906103675760405162461bcd60e51b81526004016101ce9190610688565b506040515f90819034908281818185825af1925050503d805f81146103a7576040519150601f19603f3d011682016040523d82523d5f602084013e6103ac565b606091505b50509050806103fd5760405162461bcd60e51b815260206004820152601e60248201527f4661696c656420746f2073656e6420746f206275726e2061646472657373000060448201526064016101ce565b604080516bffffffffffffffffffffffff1986168152600784900b60208201527ffeadaf04de8d7c2594453835b9a93b747e20e7a09a7fdb9280579a6dbaf131a8910160405180910390a150505050565b6104656402540be400677fffffffffffffff6105fc565b81565b6104656402540be40060016105fc565b6060815f0361049e5750506040805180820190915260018152600360fc1b602082015290565b815f5b81156104c757806104b181610814565b91506104c09050600a836107a6565b91506104a1565b5f8167ffffffffffffffff8111156104e1576104e161082c565b6040519080825280601f01601f19166020018201604052801561050b576020820181803683370190505b509050815b851561059557610521600182610840565b90505f61052f600a886107a6565b61053a90600a6105fc565b6105449088610840565b61054f906030610853565b90505f8160f81b90508084848151811061056b5761056b61086c565b60200101906001600160f81b03191690815f1a90535061058c600a896107a6565b97505050610510565b50949350505050565b5f602082840312156105ae575f80fd5b5035919050565b5f602082840312156105c5575f80fd5b81356bffffffffffffffffffffffff19811681146105e1575f80fd5b9392505050565b634e487b7160e01b5f52601160045260245ffd5b8082028115828204841417610613576106136105e8565b92915050565b5f81518060208401855e5f93019283525090919050565b6a029b2b73a103b30b63ab2960ad1b81525f61064f600b830185610619565b7f206d7573742062652067726561746572206f7220657175616c20746f20000000815261067f601d820185610619565b95945050505050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b6a029b2b73a103b30b63ab2960ad1b81525f6106dc600b830185610619565b7f206d757374206265206c657373206f7220657175616c20746f20000000000000815261067f601a820185610619565b7f4d6178207472616e7366657273206c696d6974206f662000000000000000000081525f61073d6017830184610619565b7f207265616368656420696e207468697320626c6f636b2e2054727920746f207381527232b732103a3930b739b332b9399030b3b0b4b760691b60208201526033019392505050565b5f61ffff821661ffff810361079d5761079d6105e8565b60010192915050565b5f826107c057634e487b7160e01b5f52601260045260245ffd5b500490565b6a029b2b73a103b30b63ab2960ad1b81525f6107e4600b830185610619565b7f206d7573742062652061206d756c7469706c65206f6620000000000000000000815261067f6017820185610619565b5f60018201610825576108256105e8565b5060010190565b634e487b7160e01b5f52604160045260245ffd5b81810381811115610613576106136105e8565b60ff8181168382160190811115610613576106136105e8565b634e487b7160e01b5f52603260045260245ffdfea2646970667358221220106399f534da089226c14e2f183f8421d059a924c65c97d7e4f3e931c54fe1bb64736f6c634300081a0033",
          "balance": "0x0"
        }
      },
      "baseFeePerGas": "0x3b9aca00",
      "blobGasUsed": null,
      "coinbase": "0x0000000000000000000000000000000000000000",
      "config": {
        "chainId": 88817,
        "arrowGlacierBlock": 0,
        "berlinBlock": 0,
        "byzantiumBlock": 0,
        "cancunTime": 0,
        "constantinopleBlock": 0,
        "daoForkBlock": 0,
        "eip150Block": 0,
        "eip155Block": 0,
        "eip158Block": 0,
        "ethash": {},
        "grayGlacierBlock": 0,
        "homesteadBlock": 0,
        "istanbulBlock": 0,
        "londonBlock": 0,
        "muirGlacierBlock": 0,
        "petersburgBlock": 0,
        "shanghaiTime": 0,
        "terminalTotalDifficulty": 0,
        "terminalTotalDifficultyPassed": true
      },
      "difficulty": "0x0",
      "excessBlobGas": null,
      "extraData": "0x",
      "gasLimit": "0xe4e1c0",
      "gasUsed": "0x0",
      "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "nonce": "0x0",
      "number": "0x0",
      "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "timestamp": "0x66eac0c0"
    }
    
  8. Place the following log4j2.xml file into the main directory.

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="INFO" monitorInterval="5">
    
        <Properties>
            <Property name="root.log.pattern">%date %-5level [%-25.25thread] %35.35c{1.} - %msg%n%throwable</Property>
        </Properties>
    
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout alwaysWriteExceptions="false" pattern='${root.log.pattern}'/>
            </Console>
            <RollingFile name="RollingFile" fileName="/opt/besu/logs/besu.log" filePattern="/opt/besu/logs/besu-%d{yyyy-MM-dd}-%i.log.gz" >
                <PatternLayout alwaysWriteExceptions="false" pattern='${root.log.pattern}'/>
                <Policies>
                    <TimeBasedTriggeringPolicy />
                    <SizeBasedTriggeringPolicy size="1000 MB" />
                </Policies>
                <DefaultRolloverStrategy max="20"/>
            </RollingFile>
        </Appenders>
    
        <Loggers>
            <Logger name="oshi" level="OFF" additivity="false"/>
            <Logger name="io.vertx" level="OFF" additivity="false"/>
            <Root>
                <AppenderRef ref="Console" level="INFO" />
                <AppenderRef ref="RollingFile" level="TRACE" />
            </Root>
        </Loggers>
    
    </Configuration>
    
  9. Place the following static-nodes.json file into the main directory.

    [
      "enode://0a4d389579cfe536da57e2c1699d682d053eccac26412d1a23eca88fc4bdc0d3e2c45ef8fdfeca6eb20ee4ce0ded06fe37625c7e3bee7e097932d85a1b45308c@65.108.122.140:30303",
      "enode://116d602e8da9a87e643909d3864b12a931ff0d4e402dd4922a0842d66a49469c6fc392da0ead182a336962dd64ae9a655ceb9ffc9e6c2376efa7d3857f4aac90@94.130.105.239:30303",
      "enode://2e620d7214392b1e3ff3ac0e0342b90ee4984d57e0a1b90be16d4f60aac1ff68dd9b7a9fae3c68f77f6f3631c4227cf5c317770a78827f7f9ee02f35795e8ba2@213.199.56.23:30303",
      "enode://4425cf397612fb5477521ec1501754516e6dd26ad0bd3d3e1a5a8311d2d2d8bc30c761acbbe47634291d857d8aab7bb134a62e1eac0d08e78c8fa1546f759f5c@95.31.7.226:30303",
      "enode://4af5930b51cd10a80b347d4c78720145eb9c053a4df8d48b66d3d69f07bafb4b081da5351fd2f2320deee9c5ca6187f7ef4f6a6437f34f4d9088267b5e2c42e7@172.245.243.21:30303",
      "enode://a597a7e9648f43435751d12b69f6dfa48c34169d20e645b91ae131c1e6ff1b295a5858c9a4dbc5bad867df020a92e303fd6073343fe2c58090f7667a32844205@217.76.63.73:30303",
      "enode://ac0c711a1905bcfeb00b2bfd6e97fa3ca9c23e9053a5939ce124d2bd389dfdf604e29d793d428a1d88d9bf5a3cd3479eecb6b7450c1054edd67fe4a21799883a@194.61.28.96:30303",
      "enode://b6d46ed1b4f7ea995a701f67609c8b301a9786b4923a618787ed71dc9a7a4d4cadd421a934906a8ca9cc3dac3a21b1002dae4ea55648930ca1262436c4f8ad8b@37.27.27.236:30303",
      "enode://b6d46ed1b4f7ea995a701f67609c8b301a9786b4923a618787ed71dc9a7a4d4cadd421a934906a8ca9cc3dac3a21b1002dae4ea55648930ca1262436c4f8ad8b@37.27.27.236:30303", 
      "enode://cd6935c8f68987ff46fe826f9e28762958520ee4a62a10d8eedb507008c47919bc2b5b1f3c601bdd35f136d91ba0096f942708ac1bda7f2ad4517fbf62e29ad7@85.190.243.26:30303",
      "enode://f5acf3dc9eee4d9538b52aa77c00be90ba539d8a5295c1b084e0b246bb435c42c331d85484ac97559a521f01aa3ed282c707bfc863bc602a122186b21d864b22@95.216.102.211:30303",
      "enode://f632e549c9c44980f43f99a9218dda01cd6771379c154dec9dc84e9d039c9124294fab63ab45a2232ba23b1bbbb56a9ea913d98a212579ff114e80f36ab7e8f5@95.31.7.226:30303",
      "enode://fee48af34229c70af99fe58c8da48124f0e9e2adb3853c1928f936fe5f3c0e8dce12d5a2fa98d4bf672f3802fce3a0d62026f275bf176a0187c3b20a543f4751@213.238.172.133:30303"
    ]
  10. Set up the docker-compose.yml file as follows, assuming all data resides in the same directory. You may adjust the paths on your as necessary.

services:
  besu:
    container_name: besu
    image: hyperledger/besu:latest
    pull_policy: always
    stop_grace_period: 5m
    command:
      - --logging=ALL
      - --host-allowlist=*
      - --rpc-http-enabled
      - --rpc-http-api=ETH,NET,WEB3,TXPOOL,TRACE
      - --rpc-http-cors-origins=all
      - --rpc-ws-enabled
      - --discovery-enabled=true
      - --engine-rpc-enabled
      - --engine-jwt-secret=/etc/secrets/jwtsecret
      - --engine-host-allowlist=*
      - --node-private-key-file=/etc/secrets/p2p-key
      - --data-path=/var/lib/besu
      - --genesis-file=/etc/besu/genesis.json
      - --static-nodes-file=/etc/besu/static-nodes.json
      - --data-storage-format=BONSAI
      - --network-id=88817
      - --target-gas-limit=15000000
    volumes:
      - ./genesis.json:/etc/besu/genesis.json
      - ./data/secrets:/etc/secrets:ro
      - ./log4j2.xml:/etc/besu/log4j2.xml
      - ./data/besu:/var/lib/besu
      - ./logs/besu:/opt/besu/logs
      - ./static-nodes.json:/etc/besu/static-nodes.json
    ports:
      - '30303:30303/tcp'
      - '30303:30303/udp'
      - '127.0.0.1:8545:8545'
    environment:
      - LOG4J_CONFIGURATION_FILE=/etc/besu/log4j2.xml

  besu-check:
    image: curlimages/curl:8.8.0
    command: >
      --retry 20 
      --retry-all-errors 
      --retry-max-time 60 
      -d '{"jsonrpc":"2.0","method":"engine_exchangeCapabilities","params":[[]],"id":1}' 
      http://besu:8551
    depends_on:
      - besu

  waves-node:
    container_name: waves-node
    image: ghcr.io/unitsnetwork/consensus-client:testnet
    ports:
      - "6865:6865"
      - "6868:6868"
      - "127.0.0.1:6869:6869"
    environment:
      - JAVA_OPTS=-Dwaves.config.directory=/etc/waves -Dlogback.file.level=TRACE
    volumes:
      - ./data/secrets:/etc/secrets:ro
      - ./data/waves:/var/lib/waves/data
      - ./waves.conf:/etc/waves/waves.conf:ro
      - ./logs/waves:/var/log/waves
    depends_on:
      besu-check:
        condition: service_completed_successfully

Docker and UFW interaction

There's a known incompatibility between docker and UFW. This incompatibility makes it impossible to map the container's port to host port with e.g. '8545:8545' and restrict access from external addresses with UFW. The sample docker-compose.yml maps Besu's JSON RPC port 8545 and Waves node's HTTP API port 6869 to the host's local address ('127.0.0.1:8545:8545' and '127.0.0.1:6869:6869'), making the endpoints available only to the consumers running on the host. If you intend to make JSON RPC or HTTP API reachable from external addresses, change port mapping to '8545:8545' and '6869:6869', or consider using reverse proxy like nginx.

Launch node

Start your node using the following command:

docker-compose up -d

Ensure the Consensus Client is synchronized with the network, as indicated by the message "Execution chain is synced" in the Waves log.

Start mining

Invoke the join() function of the Testnet Chain Contract on behalf of your node’s Waves account. Specify your address on the Unit Zero Testnet as an argument to receive epoch rewards and transaction fees.

Your node will begin mining when its turn comes.

If you encounter any difficulties, feel free to reach out via Developer chat for assistance.

Last updated