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
Meet hardware requirements: 4 CPU cores, 8 GB RAM, 60+ GB SSD.
Install Docker if it is not already installed.
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
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.
Create the data/waves
subdirectory and place the Waves Testnet blockchain data inside it.
Create the following subdirectories in the main directory:
Copy mkdir -p data/besu logs/besu
Grant the user with uid 1000 write access to the data/besu
and logs/besu
subdirectories:
Copy chown 1000 data/besu logs/besu
Generate node key and JWT secret:
Copy 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
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.
Copy 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
}
}
Place the following genesis.json
file into the main directory.
Copy {
"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"
}
Place the following log4j2.xml
file into the main directory.
Copy <? 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 >
Place the following static-nodes.json
file into the main directory.
Copy [
"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"
]
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.
Copy 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:
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.