Skip to content

Commit cb98e95

Browse files
committed
feat: Consensus initialization
1 parent 131b611 commit cb98e95

11 files changed

Lines changed: 353 additions & 35 deletions

File tree

.dockerignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ CODE_OF_CONDUCT.md
2323
CONTRIBUTING.md
2424

2525
# Scripts
26-
scripts/
26+
scripts/
27+
28+
.devbox/
29+
.plugins/
30+
tmp/
31+
snapshots/

cmd/juno/juno.go

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ const (
6161
p2pPeersF = "p2p-peers"
6262
p2pFeederNodeF = "p2p-feeder-node"
6363
p2pPrivateKey = "p2p-private-key"
64+
consensusF = "consensus"
65+
consensusAddrF = "consensus-addr"
66+
consensusPeersF = "consensus-peers"
67+
consensusDBPathF = "consensus-db-path"
68+
consensusMockIndexF = "consensus-mock-index" // TODO: remove this
69+
consensusMockCountF = "consensus-mock-count" // TODO: remove this
6470
metricsF = "metrics"
6571
metricsHostF = "metrics-host"
6672
metricsPortF = "metrics-port"
@@ -115,6 +121,12 @@ const (
115121
defaultP2pPeers = ""
116122
defaultP2pFeederNode = false
117123
defaultP2pPrivateKey = ""
124+
defaultConsensus = false
125+
defaultConsensusAddr = ""
126+
defaultConsensusPeers = ""
127+
defaultConsensusDBPath = ""
128+
defaultConsensusMockIndex = 0 // TODO: remove this
129+
defaultConsensusMockCount = 0 // TODO: remove this
118130
defaultMetrics = false
119131
defaultMetricsPort = 9090
120132
defaultGRPC = false
@@ -178,21 +190,27 @@ const (
178190
"These peers can be either Feeder or regular nodes."
179191
p2pFeederNodeUsage = "EXPERIMENTAL: Run juno as a feeder node which will only sync from feeder gateway and gossip the new" +
180192
" blocks to the network."
181-
p2pPrivateKeyUsage = "EXPERIMENTAL: Hexadecimal representation of a private key on the Ed25519 elliptic curve."
182-
metricsUsage = "Enables the Prometheus metrics endpoint on the default port."
183-
metricsHostUsage = "The interface on which the Prometheus endpoint will listen for requests."
184-
metricsPortUsage = "The port on which the Prometheus endpoint will listen for requests."
185-
grpcUsage = "Enable the HTTP gRPC server on the default port."
186-
grpcHostUsage = "The interface on which the gRPC server will listen for requests."
187-
grpcPortUsage = "The port on which the gRPC server will listen for requests."
188-
maxVMsUsage = "Maximum number for VM instances to be used for RPC calls concurrently"
189-
maxVMQueueUsage = "Maximum number for requests to queue after reaching max-vms before starting to reject incoming requests"
190-
remoteDBUsage = "gRPC URL of a remote Juno node"
191-
rpcMaxBlockScanUsage = "Maximum number of blocks scanned in single starknet_getEvents call"
192-
dbCacheSizeUsage = "Determines the amount of memory (in megabytes) allocated for caching data in the database."
193-
dbMaxHandlesUsage = "A soft limit on the number of open files that can be used by the DB"
194-
gwAPIKeyUsage = "API key for gateway endpoints to avoid throttling" //nolint: gosec
195-
gwTimeoutsUsage = "Timeouts for requests made to the gateway. Can be specified in three ways:\n" +
193+
p2pPrivateKeyUsage = "EXPERIMENTAL: Hexadecimal representation of a private key on the Ed25519 elliptic curve."
194+
consensusUsage = "EXPERIMENTAL: Enables the consensus server."
195+
consensusAddrUsage = "EXPERIMENTAL: Specify the address of the consensus server."
196+
consensusPeersUsage = "EXPERIMENTAL: Specify list of consensus peers split by a comma."
197+
consensusDBPathUsage = "EXPERIMENTAL: Specify the path to the consensus database."
198+
consensusMockIndexUsage = "EXPERIMENTAL: Specify the index of the mock consensus server to use." // TODO: remove this
199+
consensusMockCountUsage = "EXPERIMENTAL: Specify the number of mock consensus servers to use." // TODO: remove this
200+
metricsUsage = "Enables the Prometheus metrics endpoint on the default port."
201+
metricsHostUsage = "The interface on which the Prometheus endpoint will listen for requests."
202+
metricsPortUsage = "The port on which the Prometheus endpoint will listen for requests."
203+
grpcUsage = "Enable the HTTP gRPC server on the default port."
204+
grpcHostUsage = "The interface on which the gRPC server will listen for requests."
205+
grpcPortUsage = "The port on which the gRPC server will listen for requests."
206+
maxVMsUsage = "Maximum number for VM instances to be used for RPC calls concurrently"
207+
maxVMQueueUsage = "Maximum number for requests to queue after reaching max-vms before starting to reject incoming requests"
208+
remoteDBUsage = "gRPC URL of a remote Juno node"
209+
rpcMaxBlockScanUsage = "Maximum number of blocks scanned in single starknet_getEvents call"
210+
dbCacheSizeUsage = "Determines the amount of memory (in megabytes) allocated for caching data in the database."
211+
dbMaxHandlesUsage = "A soft limit on the number of open files that can be used by the DB"
212+
gwAPIKeyUsage = "API key for gateway endpoints to avoid throttling" //nolint: gosec
213+
gwTimeoutsUsage = "Timeouts for requests made to the gateway. Can be specified in three ways:\n" +
196214
"- Single value (e.g. '5s'): After each failure, the timeout will increase dynamically.\n" +
197215
"- Comma-separated list (e.g. '5s,10s,20s'): Each value will be used in sequence after failures.\n" +
198216
"- Single value with trailing comma (e.g. '5s,'): Uses a fixed timeout without dynamic adjustment."
@@ -382,6 +400,12 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr
382400
junoCmd.Flags().String(p2pPeersF, defaultP2pPeers, p2pPeersUsage)
383401
junoCmd.Flags().Bool(p2pFeederNodeF, defaultP2pFeederNode, p2pFeederNodeUsage)
384402
junoCmd.Flags().String(p2pPrivateKey, defaultP2pPrivateKey, p2pPrivateKeyUsage)
403+
junoCmd.Flags().Bool(consensusF, defaultConsensus, consensusUsage)
404+
junoCmd.Flags().String(consensusAddrF, defaultConsensusAddr, consensusAddrUsage)
405+
junoCmd.Flags().String(consensusPeersF, defaultConsensusPeers, consensusPeersUsage)
406+
junoCmd.Flags().String(consensusDBPathF, defaultConsensusDBPath, consensusDBPathUsage)
407+
junoCmd.Flags().Int(consensusMockIndexF, defaultConsensusMockIndex, consensusMockIndexUsage) // TODO: remove this
408+
junoCmd.Flags().Int(consensusMockCountF, defaultConsensusMockCount, consensusMockCountUsage) // TODO: remove this
385409
junoCmd.Flags().Bool(metricsF, defaultMetrics, metricsUsage)
386410
junoCmd.Flags().String(metricsHostF, defaultHost, metricsHostUsage)
387411
junoCmd.Flags().Uint16(metricsPortF, defaultMetricsPort, metricsPortUsage)

consensus-tests/Dockerfile.shooter

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FROM starknet/starkli:0.4.1
2+
3+
RUN apk add --no-cache bash curl jq

consensus-tests/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Juno Consensus Test
2+
3+
This is an experimental test setup for running 4 Juno nodes (0-3) with Tendermint consensus algorithm to observe network performance under load.
4+
5+
**Node topology**: Node 1 → Node 0, Node 2 → Node 1, Node 3 → Node 2
6+
7+
## Test Flow
8+
9+
1. **Node Setup**: 4 Juno nodes are initialized and connected in sequence using Docker containers
10+
2. **Account Setup**: A setup service creates `SHOOTER_COUNT` accounts, each funded with ETH and STRK tokens
11+
3. **Load Testing**: `SHOOTER_COUNT` shooter instances each send 1,000 transactions concurrently, load balanced across all 4 nodes
12+
13+
## Prerequisites
14+
15+
Docker and Docker Compose installed.
16+
17+
## Usage
18+
19+
Set the compose file location:
20+
```bash
21+
export COMPOSE_FILE=consensus-tests/docker-compose.yaml
22+
```
23+
24+
Run the test:
25+
```bash
26+
docker compose up -d --build
27+
```
28+
29+
View logs:
30+
```bash
31+
docker compose logs -f
32+
```
33+
34+
Stop and cleanup:
35+
```bash
36+
docker compose down -v
37+
```
38+
39+
## Configuration
40+
41+
- `SHOOTER_COUNT`: Number of shooter instances and funded accounts to create (default: 4)
42+
- Total transactions = `SHOOTER_COUNT × 1,000`
43+
- Adjust based on your system resources

consensus-tests/all.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
3+
DATA_DIR=${DATA_DIR:-"consensus-tests"}
4+
export STARKNET_RPC="http://node0:6060"
5+
6+
sleep 2 # TODO: Figure out why we need to wait
7+
8+
shooters="$1"
9+
10+
root_address="0x406a8f52e741619b17410fc90774e4b36f968e1a71ae06baacfe1f55d987923"
11+
export ROOT_PRIVATE_KEY="0x3a4791edf67fa0b32b812e41bc8bc4e9b79915412b1331f7669cbe23e97e15a"
12+
13+
until starkli account fetch "$root_address" --force --output "$DATA_DIR/root.json"; do
14+
echo "Failed to fetch root account, retrying..."
15+
sleep 1
16+
done
17+
18+
for ((i = 1; i <= shooters; i++)); do
19+
consensus-tests/setup.sh "$i"
20+
done
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
x-node: &node
2+
image: nethermind/juno:consensus
3+
command: [
4+
"--http",
5+
"--http-host", "0.0.0.0",
6+
"--network", "sepolia",
7+
"--log-level", "debug",
8+
"--disable-l1-verification",
9+
"--db-path", "/db/main",
10+
"--seq-genesis-file", "/genesis/genesis_prefund_accounts.json",
11+
"--consensus",
12+
"--consensus-db-path", "/db/consensus",
13+
"--consensus-addr", "/ip4/0.0.0.0/tcp/7070",
14+
"--consensus-mock-count", "4",
15+
]
16+
configs:
17+
- genesis
18+
19+
services:
20+
node0:
21+
<<: *node
22+
ports:
23+
- "6060:6060"
24+
environment:
25+
JUNO_CONSENSUS_MOCK_INDEX: 0
26+
JUNO_CONSENSUS_PEERS:
27+
28+
node1:
29+
<<: *node
30+
ports:
31+
- "6061:6060"
32+
depends_on:
33+
- node0
34+
environment:
35+
JUNO_CONSENSUS_MOCK_INDEX: 1
36+
JUNO_CONSENSUS_PEERS: /dns4/node0/tcp/7070/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN
37+
38+
node2:
39+
<<: *node
40+
ports:
41+
- "6062:6060"
42+
depends_on:
43+
- node1
44+
environment:
45+
JUNO_CONSENSUS_MOCK_INDEX: 2
46+
JUNO_CONSENSUS_PEERS: /dns4/node1/tcp/7070/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X
47+
48+
node3:
49+
<<: *node
50+
ports:
51+
- "6063:6060"
52+
depends_on:
53+
- node2
54+
environment:
55+
JUNO_CONSENSUS_MOCK_INDEX: 3
56+
JUNO_CONSENSUS_PEERS: /dns4/node2/tcp/7070/p2p/12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3
57+
58+
setup:
59+
build:
60+
context: .
61+
dockerfile: Dockerfile.shooter
62+
entrypoint: ["/consensus-tests/all.sh", "${SHOOTER_COUNT:-4}"]
63+
environment:
64+
DATA_DIR: /data
65+
depends_on:
66+
- node3
67+
configs:
68+
- consensus-tests
69+
volumes:
70+
- data:/data
71+
72+
shooters:
73+
build:
74+
context: .
75+
dockerfile: Dockerfile.shooter
76+
entrypoint: ["/consensus-tests/shoot.sh"]
77+
environment:
78+
DATA_DIR: /data
79+
depends_on:
80+
setup:
81+
condition: service_completed_successfully
82+
webdis:
83+
condition: service_started
84+
configs:
85+
- consensus-tests
86+
volumes:
87+
- data:/data
88+
scale: ${SHOOTER_COUNT:-4}
89+
90+
webdis:
91+
image: nicolas/webdis:latest
92+
93+
configs:
94+
genesis:
95+
file: ../genesis
96+
consensus-tests:
97+
file: ./
98+
99+
volumes:
100+
data:

consensus-tests/setup.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/bin/bash
2+
3+
DATA_DIR=${DATA_DIR:-"consensus-tests"}
4+
INDEX="$1"
5+
TARGET="$((INDEX % 4))"
6+
export STARKNET_RPC="http://node$TARGET:6060"
7+
8+
echo "Generate a new keypair for account #$INDEX"
9+
KEYPAIR=$(starkli signer gen-keypair)
10+
export PRIVATE_KEY=$(starkli signer gen-keypair | awk '/Private key/ {print $4}')
11+
12+
echo "Private key #$INDEX: $PRIVATE_KEY"
13+
14+
echo "Init account #$INDEX"
15+
ADDRESS=$(starkli account oz init "$DATA_DIR/$INDEX.json" --force --private-key "$PRIVATE_KEY" --class-hash "0x61dac032f228abef9c6626f995015233097ae253a7f72d68552db02f2971b8f" 2>&1 | tee /dev/stderr | tr -d ' ' | grep "^0x")
16+
17+
echo "Fund ETH to account #$INDEX"
18+
until starkli invoke eth transfer "$ADDRESS" u256:100000000000000000 --account "$DATA_DIR/root.json" --private-key "$ROOT_PRIVATE_KEY" --watch --poll-interval 500; do
19+
echo "Failed to fund ETH to account #$INDEX, retrying..."
20+
sleep 1
21+
done
22+
23+
echo "Fund STRK to account #$INDEX"
24+
until starkli invoke strk transfer "$ADDRESS" u256:100000000000000000 --account "$DATA_DIR/root.json" --private-key "$ROOT_PRIVATE_KEY" --watch --poll-interval 500; do
25+
echo "Failed to fund STRK to account #$INDEX, retrying..."
26+
sleep 1
27+
done
28+
29+
echo "Deploy account #$INDEX"
30+
until yes "" | starkli account deploy "$DATA_DIR/$INDEX.json" --private-key "$PRIVATE_KEY" --poll-interval 500; do
31+
echo "Failed to deploy account #$INDEX, retrying..."
32+
sleep 1
33+
done
34+
35+
echo "Write private key to be reused"
36+
echo "$PRIVATE_KEY" > "$DATA_DIR/$INDEX.key"

consensus-tests/shoot.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/bin/bash
2+
3+
DATA_DIR=${DATA_DIR:-"consensus-tests"}
4+
5+
if [ -z "$1" ]; then
6+
INDEX=$(curl --silent 'http://webdis:7379/INCR/lock' | jq '.INCR')
7+
else
8+
INDEX="$1"
9+
fi
10+
11+
echo "Shooting account #$INDEX"
12+
13+
PORT="$((6060 + INDEX % 4))"
14+
TARGET="$((INDEX % 4))"
15+
export STARKNET_RPC="http://node$TARGET:6060"
16+
17+
PRIVATE_KEY=$(cat "$DATA_DIR/$INDEX.key")
18+
19+
for i in {1..1000}; do
20+
starkli invoke eth transfer 0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e 1 0 \
21+
--account "$DATA_DIR/$INDEX.json" \
22+
--private-key "$PRIVATE_KEY" \
23+
--l1-gas 1000000 \
24+
--l1-gas-price-raw 1 \
25+
--l1-data-gas 1000000 \
26+
--l1-data-gas-price-raw 1 \
27+
--l2-gas 1000000 \
28+
--l2-gas-price-raw 1 \
29+
--nonce "$i" \
30+
>/dev/null 2>&1
31+
32+
if [ $((i % 100)) -eq 0 ]; then
33+
echo "Account #$INDEX sent $i transactions"
34+
fi
35+
done

consensus/consensus.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func Init(
5353

5454
tendermintDB := consensusDB.NewTendermintDB[starknet.Value, starknet.Hash, starknet.Address](database, currentHeight)
5555

56-
executor := builder.NewExecutor(blockchain, vm, logger, false, true) // TODO: We're currently skipping signature validation
56+
executor := builder.NewExecutor(blockchain, vm, logger, false, false)
5757
builder := builder.New(blockchain, executor)
5858

5959
proposalStore := proposal.ProposalStore[starknet.Hash]{}

consensus/mock.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616

1717
const (
1818
timeoutBase = 500 * time.Microsecond
19-
timeoutRoundFactor = 100 * time.Millisecond
19+
timeoutRoundFactor = 1000 * time.Millisecond
2020
)
2121

2222
type MockServices struct {

0 commit comments

Comments
 (0)