Skip to content

Commit e55efc6

Browse files
wen-codingclaude
andcommitted
feat: add AUTOBAHN option to local docker cluster (CON-247)
- NodeKey.SaveAs writes node_pubkey.txt alongside node_key.json - FilePVKey.Save writes validator_pubkey.txt alongside priv_validator_key.json - step1 copies pubkey files to shared volume for cross-node access - step4 generates autobahn JSON config from all nodes' pubkeys when AUTOBAHN=true - Wired through docker-compose.yml and Makefile Usage: AUTOBAHN=true make docker-cluster-start Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8d7e329 commit e55efc6

11 files changed

Lines changed: 228 additions & 72 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ docker-cluster-start: docker-cluster-stop build-docker-node
293293
else \
294294
DETACH_FLAG=""; \
295295
fi; \
296-
DOCKER_PLATFORM=$(DOCKER_PLATFORM) USERID=$(shell id -u) GROUPID=$(shell id -g) GOCACHE=$(shell go env GOCACHE) NUM_ACCOUNTS=10 INVARIANT_CHECK_INTERVAL=${INVARIANT_CHECK_INTERVAL} UPGRADE_VERSION_LIST=${UPGRADE_VERSION_LIST} MOCK_BALANCES=${MOCK_BALANCES} GIGA_EXECUTOR=${GIGA_EXECUTOR} GIGA_OCC=${GIGA_OCC} RECEIPT_BACKEND=${RECEIPT_BACKEND} docker compose up $$DETACH_FLAG
296+
DOCKER_PLATFORM=$(DOCKER_PLATFORM) USERID=$(shell id -u) GROUPID=$(shell id -g) GOCACHE=$(shell go env GOCACHE) NUM_ACCOUNTS=10 INVARIANT_CHECK_INTERVAL=${INVARIANT_CHECK_INTERVAL} UPGRADE_VERSION_LIST=${UPGRADE_VERSION_LIST} MOCK_BALANCES=${MOCK_BALANCES} GIGA_EXECUTOR=${GIGA_EXECUTOR} GIGA_OCC=${GIGA_OCC} RECEIPT_BACKEND=${RECEIPT_BACKEND} AUTOBAHN=${AUTOBAHN} docker compose up $$DETACH_FLAG
297297

298298
.PHONY: localnet-start
299299

docker/docker-compose.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ services:
1919
- GIGA_EXECUTOR
2020
- GIGA_OCC
2121
- RECEIPT_BACKEND
22+
- AUTOBAHN
2223
volumes:
2324
- "${PROJECT_HOME}:/sei-protocol/sei-chain:Z"
2425
- "${PROJECT_HOME}/../sei-tendermint:/sei-protocol/sei-tendermint:Z"
@@ -50,6 +51,7 @@ services:
5051
- GIGA_EXECUTOR
5152
- GIGA_OCC
5253
- RECEIPT_BACKEND
54+
- AUTOBAHN
5355
volumes:
5456
- "${PROJECT_HOME}:/sei-protocol/sei-chain:Z"
5557
- "${PROJECT_HOME}/../sei-tendermint:/sei-protocol/sei-tendermint:Z"
@@ -77,6 +79,7 @@ services:
7779
- GIGA_EXECUTOR
7880
- GIGA_OCC
7981
- RECEIPT_BACKEND
82+
- AUTOBAHN
8083
ports:
8184
- "26662-26664:26656-26658"
8285
- "9094-9095:9090-9091"
@@ -108,6 +111,7 @@ services:
108111
- GIGA_EXECUTOR
109112
- GIGA_OCC
110113
- RECEIPT_BACKEND
114+
- AUTOBAHN
111115
ports:
112116
- "26665-26667:26656-26658"
113117
- "9096-9097:9090-9091"

docker/localnode/scripts/step1_configure_init.sh

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ echo "Configure and initialize environment"
88
cp build/seid "$GOBIN"/
99

1010
# Prepare shared folders
11+
NODE_DIR="build/generated/node_${NODE_ID}"
1112
mkdir -p build/generated/gentx/
1213
mkdir -p build/generated/exported_keys/
13-
mkdir -p build/generated/node_"$NODE_ID"
14+
mkdir -p "$NODE_DIR"
1415

1516
# Testing whether seid works or not
1617
seid version # Uncomment the below line if there are any dependency issues
@@ -22,16 +23,22 @@ MONIKER="sei-node-$NODE_ID"
2223
seid init "$MONIKER" --chain-id sei >/dev/null 2>&1
2324

2425
# Copy configs
25-
APP_CONFIG_FILE="build/generated/node_$NODE_ID/app.toml"
26-
TENDERMINT_CONFIG_FILE="build/generated/node_$NODE_ID/config.toml"
26+
APP_CONFIG_FILE="$NODE_DIR/app.toml"
27+
TENDERMINT_CONFIG_FILE="$NODE_DIR/config.toml"
2728
cp docker/localnode/config/app.toml "$APP_CONFIG_FILE"
2829
cp docker/localnode/config/config.toml "$TENDERMINT_CONFIG_FILE"
2930

3031

3132
# Set up persistent peers
3233
SEI_NODE_ID=$(seid tendermint show-node-id)
3334
NODE_IP=$(hostname -i | awk '{print $1}')
34-
echo "$SEI_NODE_ID@$NODE_IP:26656" >> build/generated/persistent_peers.txt
35+
P2P_PORT=26656 # Must match [p2p] laddr in config.toml
36+
echo "$SEI_NODE_ID@$NODE_IP:$P2P_PORT" >> build/generated/persistent_peers.txt
37+
38+
# Store autobahn-compatible pubkeys and address for config generation
39+
cp ~/.sei/config/validator_pubkey.txt "$NODE_DIR/" || { echo "ERROR: failed to copy validator_pubkey.txt"; exit 1; }
40+
cp ~/.sei/config/node_pubkey.txt "$NODE_DIR/" || { echo "ERROR: failed to copy node_pubkey.txt"; exit 1; }
41+
echo "$NODE_IP:$P2P_PORT" > "$NODE_DIR/autobahn_address.txt"
3542

3643
# Create a new account
3744
ACCOUNT_NAME="node_admin"

docker/localnode/scripts/step4_config_override.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
NODE_ID=${ID:-0}
44
GIGA_EXECUTOR=${GIGA_EXECUTOR:-false}
55
GIGA_OCC=${GIGA_OCC:-false}
6+
AUTOBAHN=${AUTOBAHN:-false}
67

78
APP_CONFIG_FILE="build/generated/node_$NODE_ID/app.toml"
89
TENDERMINT_CONFIG_FILE="build/generated/node_$NODE_ID/config.toml"
@@ -59,3 +60,22 @@ if [ -n "$RECEIPT_BACKEND" ]; then
5960
echo "rs-backend = \"$RECEIPT_BACKEND\"" >> ~/.sei/config/app.toml
6061
fi
6162
fi
63+
64+
# Generate Autobahn (GigaRouter) config if requested
65+
if [ "$AUTOBAHN" = "true" ]; then
66+
echo "Generating Autobahn config for node $NODE_ID..."
67+
AUTOBAHN_CONFIG="$HOME/.sei/config/autobahn.json"
68+
69+
# Collect node directories as arguments
70+
NODE_DIRS=""
71+
for i in $(seq 0 $((CLUSTER_SIZE - 1))); do
72+
NODE_DIRS="$NODE_DIRS build/generated/node_${i}"
73+
done
74+
75+
# Generate autobahn config using seid
76+
seid tendermint gen-autobahn-config $NODE_DIRS --output "$AUTOBAHN_CONFIG"
77+
78+
# Inject autobahn config file path into config.toml
79+
sed -i 's|autobahn-config-file = ""|autobahn-config-file = "'"$AUTOBAHN_CONFIG"'"|' ~/.sei/config/config.toml
80+
echo "Autobahn config written to $AUTOBAHN_CONFIG"
81+
fi

sei-cosmos/server/util.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ func AddCommands(
349349
ShowValidatorCmd(),
350350
ShowAddressCmd(),
351351
VersionCmd(),
352+
commands.MakeGenAutobahnConfigCommand(),
352353
commands.MakeGenValidatorCommand(),
353354
commands.MakeReindexEventCommand(conf),
354355
commands.MakeLightCommand(conf),
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package commands
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"time"
10+
11+
"github.com/spf13/cobra"
12+
13+
"github.com/sei-protocol/sei-chain/sei-tendermint/config"
14+
atypes "github.com/sei-protocol/sei-chain/sei-tendermint/internal/autobahn/types"
15+
"github.com/sei-protocol/sei-chain/sei-tendermint/internal/p2p"
16+
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils"
17+
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/tcp"
18+
)
19+
20+
// MakeGenAutobahnConfigCommand creates a cobra command that generates an autobahn JSON config file.
21+
// Each node directory must contain validator_pubkey.txt, node_pubkey.txt, and autobahn_address.txt.
22+
func MakeGenAutobahnConfigCommand() *cobra.Command {
23+
cmd := &cobra.Command{
24+
Use: "gen-autobahn-config [node-dirs...]",
25+
Short: "Generate autobahn JSON config from node pubkey files",
26+
Long: `Generate an autobahn JSON config file by reading validator_pubkey.txt,
27+
node_pubkey.txt, and autobahn_address.txt from each node directory.
28+
Output is written to the file specified by --output.`,
29+
Args: cobra.MinimumNArgs(1),
30+
RunE: func(cmd *cobra.Command, args []string) error {
31+
output, _ := cmd.Flags().GetString("output")
32+
if output == "" {
33+
return fmt.Errorf("--output flag is required")
34+
}
35+
36+
var validators []config.AutobahnValidator
37+
for _, dir := range args {
38+
valKeyRaw, err := os.ReadFile(filepath.Clean(filepath.Join(dir, "validator_pubkey.txt")))
39+
if err != nil {
40+
return fmt.Errorf("reading validator_pubkey.txt from %s: %w", dir, err)
41+
}
42+
var valKey atypes.PublicKey
43+
if err := valKey.UnmarshalText([]byte(strings.TrimSpace(string(valKeyRaw)))); err != nil {
44+
return fmt.Errorf("parsing validator key from %s: %w", dir, err)
45+
}
46+
47+
nodeKeyRaw, err := os.ReadFile(filepath.Clean(filepath.Join(dir, "node_pubkey.txt")))
48+
if err != nil {
49+
return fmt.Errorf("reading node_pubkey.txt from %s: %w", dir, err)
50+
}
51+
var nodeKey p2p.NodePublicKey
52+
if err := nodeKey.UnmarshalText([]byte(strings.TrimSpace(string(nodeKeyRaw)))); err != nil {
53+
return fmt.Errorf("parsing node key from %s: %w", dir, err)
54+
}
55+
56+
addrRaw, err := os.ReadFile(filepath.Clean(filepath.Join(dir, "autobahn_address.txt")))
57+
if err != nil {
58+
return fmt.Errorf("reading autobahn_address.txt from %s: %w", dir, err)
59+
}
60+
addr, err := tcp.ParseHostPort(strings.TrimSpace(string(addrRaw)))
61+
if err != nil {
62+
return fmt.Errorf("parsing address from %s: %w", dir, err)
63+
}
64+
65+
validators = append(validators, config.AutobahnValidator{
66+
ValidatorKey: valKey,
67+
NodeKey: nodeKey,
68+
Address: addr,
69+
})
70+
}
71+
72+
cfg := config.AutobahnFileConfig{
73+
Validators: validators,
74+
MaxGasPerBlock: 50_000_000,
75+
MaxTxsPerBlock: 5_000,
76+
MempoolSize: 5_000,
77+
BlockInterval: utils.Duration(400 * time.Millisecond),
78+
AllowEmptyBlocks: false,
79+
ViewTimeout: utils.Duration(1500 * time.Millisecond),
80+
DialInterval: utils.Duration(10 * time.Second),
81+
}
82+
83+
data, err := json.MarshalIndent(cfg, "", " ")
84+
if err != nil {
85+
return fmt.Errorf("marshaling config: %w", err)
86+
}
87+
if err := os.WriteFile(output, data, 0600); err != nil {
88+
return fmt.Errorf("writing config to %s: %w", output, err)
89+
}
90+
fmt.Printf("Autobahn config written to %s\n", output)
91+
return nil
92+
},
93+
}
94+
cmd.Flags().StringP("output", "o", "", "output file path for the autobahn config")
95+
return cmd
96+
}

sei-tendermint/config/autobahn.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package config
2+
3+
import (
4+
"errors"
5+
6+
atypes "github.com/sei-protocol/sei-chain/sei-tendermint/internal/autobahn/types"
7+
"github.com/sei-protocol/sei-chain/sei-tendermint/internal/p2p"
8+
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils"
9+
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/tcp"
10+
)
11+
12+
// AutobahnValidator represents a validator entry in the autobahn config file.
13+
type AutobahnValidator struct {
14+
ValidatorKey atypes.PublicKey `json:"validator_key"`
15+
NodeKey p2p.NodePublicKey `json:"node_key"`
16+
Address tcp.HostPort `json:"address"`
17+
}
18+
19+
// AutobahnFileConfig is the JSON structure of the autobahn config file.
20+
type AutobahnFileConfig struct {
21+
Validators []AutobahnValidator `json:"validators"`
22+
MaxGasPerBlock uint64 `json:"max_gas_per_block"`
23+
MaxTxsPerBlock uint64 `json:"max_txs_per_block"`
24+
MaxTxsPerSecond utils.Option[uint64] `json:"max_txs_per_second"`
25+
MempoolSize uint64 `json:"mempool_size"`
26+
BlockInterval utils.Duration `json:"block_interval"`
27+
AllowEmptyBlocks bool `json:"allow_empty_blocks"`
28+
ViewTimeout utils.Duration `json:"view_timeout"`
29+
PersistentStateDir utils.Option[string] `json:"persistent_state_dir"`
30+
DialInterval utils.Duration `json:"dial_interval"`
31+
}
32+
33+
// Validate performs basic validation of the autobahn file config.
34+
func (fc *AutobahnFileConfig) Validate() error {
35+
if len(fc.Validators) == 0 {
36+
return errors.New("validators must not be empty")
37+
}
38+
if fc.MaxGasPerBlock == 0 {
39+
return errors.New("max_gas_per_block must be > 0")
40+
}
41+
if fc.MaxTxsPerBlock == 0 {
42+
return errors.New("max_txs_per_block must be > 0")
43+
}
44+
if fc.MempoolSize == 0 {
45+
return errors.New("mempool_size must be > 0")
46+
}
47+
if fc.BlockInterval <= 0 {
48+
return errors.New("block_interval must be > 0")
49+
}
50+
if fc.ViewTimeout <= 0 {
51+
return errors.New("view_timeout must be > 0")
52+
}
53+
if fc.DialInterval <= 0 {
54+
return errors.New("dial_interval must be > 0")
55+
}
56+
return nil
57+
}

sei-tendermint/node/setup.go

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import (
3232
tmnet "github.com/sei-protocol/sei-chain/sei-tendermint/libs/net"
3333
tmstrings "github.com/sei-protocol/sei-chain/sei-tendermint/libs/strings"
3434
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils"
35-
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/tcp"
3635
"github.com/sei-protocol/sei-chain/sei-tendermint/privval"
3736
tmgrpc "github.com/sei-protocol/sei-chain/sei-tendermint/privval/grpc"
3837
"github.com/sei-protocol/sei-chain/sei-tendermint/types"
@@ -198,62 +197,16 @@ func createEvidenceReactor(
198197
return evidenceReactor, evidencePool, evidenceDB.Close, nil
199198
}
200199

201-
// autobahnValidator represents a validator entry in the autobahn config file.
202-
type autobahnValidator struct {
203-
ValidatorKey atypes.PublicKey `json:"validator_key"` // autobahn validator public key
204-
NodeKey p2p.NodePublicKey `json:"node_key"` // p2p node public key
205-
Address tcp.HostPort `json:"address"` // network address (host:port)
206-
}
207-
208-
// autobahnFileConfig is the JSON structure of the autobahn config file.
209-
type autobahnFileConfig struct {
210-
Validators []autobahnValidator `json:"validators"`
211-
MaxGasPerBlock uint64 `json:"max_gas_per_block"`
212-
MaxTxsPerBlock uint64 `json:"max_txs_per_block"`
213-
MaxTxsPerSecond utils.Option[uint64] `json:"max_txs_per_second"`
214-
MempoolSize uint64 `json:"mempool_size"`
215-
BlockInterval utils.Duration `json:"block_interval"`
216-
AllowEmptyBlocks bool `json:"allow_empty_blocks"`
217-
ViewTimeout utils.Duration `json:"view_timeout"`
218-
PersistentStateDir utils.Option[string] `json:"persistent_state_dir"`
219-
DialInterval utils.Duration `json:"dial_interval"`
220-
}
221-
222-
func (fc *autobahnFileConfig) validate() error {
223-
if len(fc.Validators) == 0 {
224-
return errors.New("validators must not be empty")
225-
}
226-
if fc.MaxGasPerBlock == 0 {
227-
return errors.New("max_gas_per_block must be > 0")
228-
}
229-
if fc.MaxTxsPerBlock == 0 {
230-
return errors.New("max_txs_per_block must be > 0")
231-
}
232-
if fc.MempoolSize == 0 {
233-
return errors.New("mempool_size must be > 0")
234-
}
235-
if fc.BlockInterval <= 0 {
236-
return errors.New("block_interval must be > 0")
237-
}
238-
if fc.ViewTimeout <= 0 {
239-
return errors.New("view_timeout must be > 0")
240-
}
241-
if fc.DialInterval <= 0 {
242-
return errors.New("dial_interval must be > 0")
243-
}
244-
return nil
245-
}
246-
247-
func loadAutobahnFileConfig(path string) (*autobahnFileConfig, error) {
200+
func loadAutobahnFileConfig(path string) (*config.AutobahnFileConfig, error) {
248201
data, err := os.ReadFile(path) //nolint:gosec // G304: path is from operator-controlled config
249202
if err != nil {
250203
return nil, err
251204
}
252-
var fc autobahnFileConfig
205+
var fc config.AutobahnFileConfig
253206
if err := json.Unmarshal(data, &fc); err != nil {
254207
return nil, err
255208
}
256-
if err := fc.validate(); err != nil {
209+
if err := fc.Validate(); err != nil {
257210
return nil, err
258211
}
259212
return &fc, nil

0 commit comments

Comments
 (0)