Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ CODE_OF_CONDUCT.md
CONTRIBUTING.md

# Scripts
scripts/
scripts/

.devbox/
.plugins/
tmp/
snapshots/
1 change: 1 addition & 0 deletions .github/workflows/juno-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, ubuntu-arm64-4-core]
iteration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
runs-on: ${{ matrix.os }}
env:
VM_DEBUG: true
Expand Down
66 changes: 51 additions & 15 deletions cmd/juno/juno.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ const (
p2pPeersF = "p2p-peers"
p2pFeederNodeF = "p2p-feeder-node"
p2pPrivateKey = "p2p-private-key"
consensusF = "consensus"
consensusAddrF = "consensus-addr"
consensusPeersF = "consensus-peers"
consensusDBPathF = "consensus-db-path"
consensusMockIndexF = "consensus-mock-index" // TODO: remove this
consensusMockCountF = "consensus-mock-count" // TODO: remove this
mempoolF = "mempool"
mempoolAddrF = "mempool-addr"
mempoolPeersF = "mempool-peers"
metricsF = "metrics"
metricsHostF = "metrics-host"
metricsPortF = "metrics-port"
Expand Down Expand Up @@ -116,6 +125,15 @@ const (
defaultP2pPeers = ""
defaultP2pFeederNode = false
defaultP2pPrivateKey = ""
defaultConsensus = false
defaultConsensusAddr = ""
defaultConsensusPeers = ""
defaultConsensusDBPath = ""
defaultConsensusMockIndex = 0 // TODO: remove this
defaultConsensusMockCount = 0 // TODO: remove this
defaultMempool = false
defaultMempoolAddr = ""
defaultMempoolPeers = ""
defaultMetrics = false
defaultMetricsPort = 9090
defaultGRPC = false
Expand Down Expand Up @@ -181,21 +199,30 @@ const (
"These peers can be either Feeder or regular nodes."
p2pFeederNodeUsage = "EXPERIMENTAL: Run juno as a feeder node which will only sync from feeder gateway and gossip the new" +
" blocks to the network."
p2pPrivateKeyUsage = "EXPERIMENTAL: Hexadecimal representation of a private key on the Ed25519 elliptic curve."
metricsUsage = "Enables the Prometheus metrics endpoint on the default port."
metricsHostUsage = "The interface on which the Prometheus endpoint will listen for requests."
metricsPortUsage = "The port on which the Prometheus endpoint will listen for requests."
grpcUsage = "Enable the HTTP gRPC server on the default port."
grpcHostUsage = "The interface on which the gRPC server will listen for requests."
grpcPortUsage = "The port on which the gRPC server will listen for requests."
maxVMsUsage = "Maximum number for VM instances to be used for RPC calls concurrently"
maxVMQueueUsage = "Maximum number for requests to queue after reaching max-vms before starting to reject incoming requests"
remoteDBUsage = "gRPC URL of a remote Juno node"
rpcMaxBlockScanUsage = "Maximum number of blocks scanned in single starknet_getEvents call"
dbCacheSizeUsage = "Determines the amount of memory (in megabytes) allocated for caching data in the database."
dbMaxHandlesUsage = "A soft limit on the number of open files that can be used by the DB"
gwAPIKeyUsage = "API key for gateway endpoints to avoid throttling" //nolint: gosec
gwTimeoutsUsage = "Timeouts for requests made to the gateway. Can be specified in three ways:\n" +
p2pPrivateKeyUsage = "EXPERIMENTAL: Hexadecimal representation of a private key on the Ed25519 elliptic curve."
consensusUsage = "EXPERIMENTAL: Enables the consensus server."
consensusAddrUsage = "EXPERIMENTAL: Specify the address of the consensus server."
consensusPeersUsage = "EXPERIMENTAL: Specify list of consensus peers split by a comma."
consensusDBPathUsage = "EXPERIMENTAL: Specify the path to the consensus database."
consensusMockIndexUsage = "EXPERIMENTAL: Specify the index of the mock consensus server to use." // TODO: remove this
consensusMockCountUsage = "EXPERIMENTAL: Specify the number of mock consensus servers to use." // TODO: remove this
mempoolUsage = "EXPERIMENTAL: Enables the mempool server."
mempoolAddrUsage = "EXPERIMENTAL: Specify the address of the mempool server."
mempoolPeersUsage = "EXPERIMENTAL: Specify list of mempool peers split by a comma."
metricsUsage = "Enables the Prometheus metrics endpoint on the default port."
metricsHostUsage = "The interface on which the Prometheus endpoint will listen for requests."
metricsPortUsage = "The port on which the Prometheus endpoint will listen for requests."
grpcUsage = "Enable the HTTP gRPC server on the default port."
grpcHostUsage = "The interface on which the gRPC server will listen for requests."
grpcPortUsage = "The port on which the gRPC server will listen for requests."
maxVMsUsage = "Maximum number for VM instances to be used for RPC calls concurrently"
maxVMQueueUsage = "Maximum number for requests to queue after reaching max-vms before starting to reject incoming requests"
remoteDBUsage = "gRPC URL of a remote Juno node"
rpcMaxBlockScanUsage = "Maximum number of blocks scanned in single starknet_getEvents call"
dbCacheSizeUsage = "Determines the amount of memory (in megabytes) allocated for caching data in the database."
dbMaxHandlesUsage = "A soft limit on the number of open files that can be used by the DB"
gwAPIKeyUsage = "API key for gateway endpoints to avoid throttling" //nolint: gosec
gwTimeoutsUsage = "Timeouts for requests made to the gateway. Can be specified in three ways:\n" +
"- Single value (e.g. '5s'): After each failure, the timeout will increase dynamically.\n" +
"- Comma-separated list (e.g. '5s,10s,20s'): Each value will be used in sequence after failures.\n" +
"- Single value with trailing comma (e.g. '5s,'): Uses a fixed timeout without dynamic adjustment."
Expand Down Expand Up @@ -386,6 +413,15 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr
junoCmd.Flags().String(p2pPeersF, defaultP2pPeers, p2pPeersUsage)
junoCmd.Flags().Bool(p2pFeederNodeF, defaultP2pFeederNode, p2pFeederNodeUsage)
junoCmd.Flags().String(p2pPrivateKey, defaultP2pPrivateKey, p2pPrivateKeyUsage)
junoCmd.Flags().Bool(consensusF, defaultConsensus, consensusUsage)
junoCmd.Flags().String(consensusAddrF, defaultConsensusAddr, consensusAddrUsage)
junoCmd.Flags().String(consensusPeersF, defaultConsensusPeers, consensusPeersUsage)
junoCmd.Flags().String(consensusDBPathF, defaultConsensusDBPath, consensusDBPathUsage)
junoCmd.Flags().Int(consensusMockIndexF, defaultConsensusMockIndex, consensusMockIndexUsage) // TODO: remove this
junoCmd.Flags().Int(consensusMockCountF, defaultConsensusMockCount, consensusMockCountUsage) // TODO: remove this
junoCmd.Flags().Bool(mempoolF, defaultMempool, mempoolUsage)
junoCmd.Flags().String(mempoolAddrF, defaultMempoolAddr, mempoolAddrUsage)
junoCmd.Flags().String(mempoolPeersF, defaultMempoolPeers, mempoolPeersUsage)
junoCmd.Flags().Bool(metricsF, defaultMetrics, metricsUsage)
junoCmd.Flags().String(metricsHostF, defaultHost, metricsHostUsage)
junoCmd.Flags().Uint16(metricsPortF, defaultMetricsPort, metricsPortUsage)
Expand Down
3 changes: 3 additions & 0 deletions consensus-tests/Dockerfile.shooter
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM starknet/starkli:0.4.1

RUN apk add --no-cache bash curl jq
43 changes: 43 additions & 0 deletions consensus-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Juno Consensus Test

This is an experimental test setup for running 4 Juno nodes (0-3) with Tendermint consensus algorithm to observe network performance under load.

**Node topology**: Node 1 → Node 0, Node 2 → Node 1, Node 3 → Node 2

## Test Flow

1. **Node Setup**: 4 Juno nodes are initialized and connected in sequence using Docker containers
2. **Account Setup**: A setup service creates `SHOOTER_COUNT` accounts, each funded with ETH and STRK tokens
3. **Load Testing**: `SHOOTER_COUNT` shooter instances each send 1,000 transactions concurrently, load balanced across all 4 nodes

## Prerequisites

Docker and Docker Compose installed.

## Usage

Set the compose file location:
```bash
export COMPOSE_FILE=consensus-tests/docker-compose.yaml
```

Run the test:
```bash
docker compose up -d --build
```

View logs:
```bash
docker compose logs -f
```

Stop and cleanup:
```bash
docker compose down -v
```

## Configuration

- `SHOOTER_COUNT`: Number of shooter instances and funded accounts to create (default: 4)
- Total transactions = `SHOOTER_COUNT × 1,000`
- Adjust based on your system resources
20 changes: 20 additions & 0 deletions consensus-tests/all.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

DATA_DIR=${DATA_DIR:-"consensus-tests"}
export STARKNET_RPC="http://node0:6060"

sleep 2 # TODO: Figure out why we need to wait

shooters="$1"

root_address="0x406a8f52e741619b17410fc90774e4b36f968e1a71ae06baacfe1f55d987923"
export ROOT_PRIVATE_KEY="0x3a4791edf67fa0b32b812e41bc8bc4e9b79915412b1331f7669cbe23e97e15a"

until starkli account fetch "$root_address" --force --output "$DATA_DIR/root.json"; do
echo "Failed to fetch root account, retrying..."
sleep 1
done

for ((i = 1; i <= shooters; i++)); do
consensus-tests/setup.sh "$i"
done
100 changes: 100 additions & 0 deletions consensus-tests/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
x-node: &node
image: nethermind/juno:consensus
command: [
"--http",
"--http-host", "0.0.0.0",
"--network", "sepolia",
"--log-level", "debug",
"--disable-l1-verification",
"--db-path", "/db/main",
"--seq-genesis-file", "/genesis/genesis_prefund_accounts.json",
"--consensus",
"--consensus-db-path", "/db/consensus",
"--consensus-addr", "/ip4/0.0.0.0/tcp/7070",
"--consensus-mock-count", "4",
"--mempool",
"--mempool-addr", "/ip4/0.0.0.0/tcp/7071",
]
configs:
- genesis

services:
node0:
<<: *node
ports:
- "6060:6060"
environment:
JUNO_CONSENSUS_MOCK_INDEX: 0
JUNO_CONSENSUS_PEERS:
JUNO_MEMPOOL_PEERS:

node1:
<<: *node
ports:
- "6061:6060"
environment:
JUNO_CONSENSUS_MOCK_INDEX: 1
JUNO_CONSENSUS_PEERS: /dns4/node0/tcp/7070/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN
JUNO_MEMPOOL_PEERS: /dns4/node0/tcp/7071/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN

node2:
<<: *node
ports:
- "6062:6060"
environment:
JUNO_CONSENSUS_MOCK_INDEX: 2
JUNO_CONSENSUS_PEERS: /dns4/node1/tcp/7070/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X
JUNO_MEMPOOL_PEERS: /dns4/node1/tcp/7071/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X

node3:
<<: *node
ports:
- "6063:6060"
environment:
JUNO_CONSENSUS_MOCK_INDEX: 3
JUNO_CONSENSUS_PEERS: /dns4/node2/tcp/7070/p2p/12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3
JUNO_MEMPOOL_PEERS: /dns4/node2/tcp/7071/p2p/12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3

setup:
build:
context: .
dockerfile: Dockerfile.shooter
entrypoint: ["/consensus-tests/all.sh", "${SHOOTER_COUNT:-4}"]
environment:
DATA_DIR: /data
depends_on:
- node0
configs:
- consensus-tests
volumes:
- data:/data

shooters:
build:
context: .
dockerfile: Dockerfile.shooter
entrypoint: ["/consensus-tests/shoot.sh"]
environment:
DATA_DIR: /data
depends_on:
setup:
condition: service_completed_successfully
webdis:
condition: service_started
configs:
- consensus-tests
volumes:
- data:/data
scale: ${SHOOTER_COUNT:-4}

webdis:
image: nicolas/webdis:latest

configs:
genesis:
file: ../genesis
consensus-tests:
file: ./

volumes:
data:
36 changes: 36 additions & 0 deletions consensus-tests/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash

DATA_DIR=${DATA_DIR:-"consensus-tests"}
INDEX="$1"
TARGET="$((INDEX % 4))"
export STARKNET_RPC="http://node$TARGET:6060"

echo "Generate a new keypair for account #$INDEX"
KEYPAIR=$(starkli signer gen-keypair)
export PRIVATE_KEY=$(starkli signer gen-keypair | awk '/Private key/ {print $4}')

echo "Private key #$INDEX: $PRIVATE_KEY"

echo "Init account #$INDEX"
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")

echo "Fund ETH to account #$INDEX"
until starkli invoke eth transfer "$ADDRESS" u256:100000000000000000 --account "$DATA_DIR/root.json" --private-key "$ROOT_PRIVATE_KEY" --watch --poll-interval 500; do
echo "Failed to fund ETH to account #$INDEX, retrying..."
sleep 1
done

echo "Fund STRK to account #$INDEX"
until starkli invoke strk transfer "$ADDRESS" u256:100000000000000000 --account "$DATA_DIR/root.json" --private-key "$ROOT_PRIVATE_KEY" --watch --poll-interval 500; do
echo "Failed to fund STRK to account #$INDEX, retrying..."
sleep 1
done

echo "Deploy account #$INDEX"
until yes "" | starkli account deploy "$DATA_DIR/$INDEX.json" --private-key "$PRIVATE_KEY" --poll-interval 500; do
echo "Failed to deploy account #$INDEX, retrying..."
sleep 1
done

echo "Write private key to be reused"
echo "$PRIVATE_KEY" > "$DATA_DIR/$INDEX.key"
35 changes: 35 additions & 0 deletions consensus-tests/shoot.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash

DATA_DIR=${DATA_DIR:-"consensus-tests"}

if [ -z "$1" ]; then
INDEX=$(curl --silent 'http://webdis:7379/INCR/lock' | jq '.INCR')
else
INDEX="$1"
fi

echo "Shooting account #$INDEX"

PORT="$((6060 + INDEX % 4))"
TARGET="$((INDEX % 4))"
export STARKNET_RPC="http://node$TARGET:6060"

PRIVATE_KEY=$(cat "$DATA_DIR/$INDEX.key")

for i in {1..1000}; do
starkli invoke eth transfer 0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e 1 0 \
--account "$DATA_DIR/$INDEX.json" \
--private-key "$PRIVATE_KEY" \
--l1-gas 1000000 \
--l1-gas-price-raw 1 \
--l1-data-gas 1000000 \
--l1-data-gas-price-raw 1 \
--l2-gas 1000000 \
--l2-gas-price-raw 1 \
--nonce "$i" \
>/dev/null 2>&1

if [ $((i % 100)) -eq 0 ]; then
echo "Account #$INDEX sent $i transactions"
fi
done
Loading
Loading