Skip to content

Commit 1650237

Browse files
authored
Exporter (#113)
* add CLI and design doc * refactor observers * ibft sync basis (read) * add storage for operator information * align with enr config * refcator eth1 * remove timeout for eth1 sync * eth1 and p2p config alignment * update config files * refactor ssv node > operator node * refactor operator storage * add stopper * add count for calculating indices * add config for eth1 SyncOffset * add tests * use normalized value of syncOffset * update make and docker * update docker compose * use interfaces to abstract ws library * support get validators * add gorilla adapter * close connections for /query endpoint, add port config * review fixes * go fmt
1 parent e147467 commit 1650237

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+2629
-1101
lines changed

.air.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ bin = "${BUILD_PATH}/cmd/ssvnode"
1414

1515
# Customize binary.
1616
# This is how you start to run your application. Since my application will works like CLI, so to run it, like to make a CLI call.
17-
full_bin="make BUILD_PATH=${BUILD_PATH} start-node"
17+
full_bin="make BUILD_PATH=${BUILD_PATH} ${RUN_CMD}"
1818

1919
# This log file places in your tmp_dir.
2020
log = "air_errors.log"
2121
# Watch these filename extensions.
2222
include_ext = ["go", "yaml"]
2323
# Ignore these filename extensions or directories.
24-
exclude_dir = ["/bin"]
24+
exclude_dir = ["/bin", "/data"]
2525
# It's not necessary to trigger build each time file changes if it's too frequent.
26-
delay = 1000 # ms
26+
delay = 10000 # ms
2727

2828
[log]
2929
# Show log time

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
bin
22
data
3+
docs

Makefile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ endif
1616
# node command builder
1717
NODE_COMMAND=--config=${CONFIG_PATH}
1818

19-
2019
ifneq ($(SHARE_CONFIG),)
2120
NODE_COMMAND+= --share-config=${SHARE_CONFIG}
2221
endif
@@ -62,7 +61,6 @@ ifdef DEBUG_PORT
6261
@echo "Running node-${NODE_ID} in debug mode"
6362
@dlv --continue --accept-multiclient --headless --listen=:${DEBUG_PORT} --api-version=2 exec \
6463
${BUILD_PATH} start-node -- ${NODE_COMMAND}
65-
6664
else
6765
@echo "Running node on address: ${HOST_ADDRESS})"
6866
@${BUILD_PATH} start-node ${NODE_COMMAND}
@@ -79,7 +77,6 @@ docker-image:
7977
@echo "node ${NODES_ID}"
8078
@sudo docker rm -f ssv_node && docker run -d --env-file .env --restart unless-stopped --name=ssv_node -p 13000:13000 -p 12000:12000 'bloxstaking/ssv-node:latest' make BUILD_PATH=/go/bin/ssvnode start-node
8179

82-
8380
NODES=ssv-node-1 ssv-node-2 ssv-node-3 ssv-node-4
8481
.PHONY: docker-all
8582
docker-all:
@@ -96,8 +93,12 @@ docker-debug:
9693
stop:
9794
@docker-compose down
9895

99-
10096
.PHONY: start-boot-node
10197
start-boot-node:
10298
@echo "Running start-boot-node"
10399
${BUILD_PATH} start-boot-node
100+
101+
.PHONY: start-exporter
102+
start-exporter:
103+
@echo "Running start-exporter"
104+
${BUILD_PATH} start-exporter ${NODE_COMMAND}

cli/bootnode/boot_node.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
type config struct {
1515
global_config.GlobalConfig `yaml:"global"`
16-
Options bootnode.Options `yaml:"bootnode"`
16+
Options bootnode.Options `yaml:"bootnode"`
1717
}
1818

1919
var cfg config
@@ -33,7 +33,7 @@ var StartBootNodeCmd = &cobra.Command{
3333
Logger := logex.Build(cmd.Parent().Short, loggerLevel)
3434

3535
if err != nil {
36-
Logger.Warn(fmt.Sprintf("Default log level set to %s", loggerLevel),zap.Error(err))
36+
Logger.Warn(fmt.Sprintf("Default log level set to %s", loggerLevel), zap.Error(err))
3737
}
3838

3939
cfg.Options.Logger = Logger

cli/cli.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package cli
22

33
import (
44
"github.com/bloxapp/ssv/cli/bootnode"
5-
"github.com/bloxapp/ssv/cli/ssvnode"
5+
"github.com/bloxapp/ssv/cli/exporter"
6+
"github.com/bloxapp/ssv/cli/operator"
67
"github.com/spf13/cobra"
78
"go.uber.org/zap"
89
"log"
@@ -13,10 +14,10 @@ var Logger *zap.Logger
1314

1415
// RootCmd represents the root command of SSV CLI
1516
var RootCmd = &cobra.Command{
16-
Use: "ssvnode",
17-
Short: "ssv-node",
18-
Long: `SSV node is a CLI for running SSV-related operations.`,
19-
PersistentPreRun: func(cmd *cobra.Command, args []string){
17+
Use: "ssvnode",
18+
Short: "ssv-node",
19+
Long: `SSV node is a CLI for running SSV-related operations.`,
20+
PersistentPreRun: func(cmd *cobra.Command, args []string) {
2021
},
2122
}
2223

@@ -29,7 +30,9 @@ func Execute(appName, version string) {
2930
log.Fatal("failed to execute root command", zap.Error(err))
3031
}
3132
}
32-
func init() {
33+
34+
func init() {
3335
RootCmd.AddCommand(bootnode.StartBootNodeCmd)
34-
RootCmd.AddCommand(ssvnode.StartNodeCmd)
35-
}
36+
RootCmd.AddCommand(exporter.StartExporterNodeCmd)
37+
RootCmd.AddCommand(operator.StartNodeCmd)
38+
}

cli/exporter/node.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package exporter
2+
3+
import (
4+
"crypto/rsa"
5+
"fmt"
6+
global_config "github.com/bloxapp/ssv/cli/config"
7+
"github.com/bloxapp/ssv/eth1"
8+
"github.com/bloxapp/ssv/eth1/goeth"
9+
"github.com/bloxapp/ssv/exporter"
10+
"github.com/bloxapp/ssv/exporter/api"
11+
"github.com/bloxapp/ssv/exporter/api/adapters/gorilla"
12+
"github.com/bloxapp/ssv/network/p2p"
13+
"github.com/bloxapp/ssv/storage"
14+
"github.com/bloxapp/ssv/storage/basedb"
15+
"github.com/bloxapp/ssv/utils/logex"
16+
"github.com/ilyakaznacheev/cleanenv"
17+
"github.com/spf13/cobra"
18+
"go.uber.org/zap"
19+
"log"
20+
"net/http"
21+
)
22+
23+
type config struct {
24+
global_config.GlobalConfig `yaml:"global"`
25+
DBOptions basedb.Options `yaml:"db"`
26+
P2pNetworkConfig p2p.Config `yaml:"p2p"`
27+
28+
ExporterConfig exporter.Options `yaml:"exporter"`
29+
30+
ETH1Addr string `yaml:"ETH1Addr" env-required:"true"`
31+
ETH1SyncOffset string `yaml:"ETH1SyncOffset" env:"ETH_1_SYNC_OFFSET"`
32+
Network string `yaml:"Network" env-default:"prater"`
33+
}
34+
35+
var cfg config
36+
37+
var globalArgs global_config.Args
38+
39+
// StartExporterNodeCmd is the command to start SSV boot node
40+
var StartExporterNodeCmd = &cobra.Command{
41+
Use: "start-exporter",
42+
Short: "Starts exporter node",
43+
Run: func(cmd *cobra.Command, args []string) {
44+
if err := cleanenv.ReadConfig(globalArgs.ConfigPath, &cfg); err != nil {
45+
log.Fatal(err)
46+
}
47+
// configure logger and db
48+
loggerLevel, err := logex.GetLoggerLevelValue(cfg.LogLevel)
49+
Logger := logex.Build(cmd.Parent().Short, loggerLevel)
50+
if err != nil {
51+
Logger.Warn(fmt.Sprintf("Default log level set to %s", loggerLevel), zap.Error(err))
52+
}
53+
cfg.DBOptions.Logger = Logger
54+
db, err := storage.GetStorageFactory(cfg.DBOptions)
55+
if err != nil {
56+
Logger.Fatal("failed to create db!", zap.Error(err))
57+
}
58+
59+
network, err := p2p.New(cmd.Context(), Logger, &cfg.P2pNetworkConfig)
60+
if err != nil {
61+
Logger.Fatal("failed to create network", zap.Error(err))
62+
}
63+
eth1Client, err := goeth.NewEth1Client(goeth.ClientOptions{
64+
Ctx: cmd.Context(), Logger: Logger, NodeAddr: cfg.ETH1Addr,
65+
// using an empty private key provider
66+
// because the exporter doesn't run in the context of an operator
67+
PrivKeyProvider: func() (*rsa.PrivateKey, error) {
68+
return nil, nil
69+
},
70+
})
71+
if err != nil {
72+
Logger.Fatal("failed to create eth1 client", zap.Error(err))
73+
}
74+
75+
cfg.ExporterConfig.Eth1Client = eth1Client
76+
cfg.ExporterConfig.Logger = Logger
77+
cfg.ExporterConfig.Network = network
78+
cfg.ExporterConfig.DB = db
79+
cfg.ExporterConfig.Ctx = cmd.Context()
80+
cfg.ExporterConfig.WS = api.NewWsServer(Logger, gorilla.NewGorillaAdapter(Logger), http.NewServeMux())
81+
82+
exporterNode := exporter.New(cfg.ExporterConfig)
83+
84+
if err := exporterNode.StartEth1(eth1.HexStringToSyncOffset(cfg.ETH1SyncOffset)); err != nil {
85+
Logger.Fatal("failed to start eth1", zap.Error(err))
86+
}
87+
if err := exporterNode.Start(); err != nil {
88+
Logger.Fatal("failed to start exporter", zap.Error(err))
89+
}
90+
},
91+
}
92+
93+
func init() {
94+
global_config.ProcessArgs(&cfg, &globalArgs, StartExporterNodeCmd)
95+
}

cli/generate_operator_keys.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var generateOperatorKeysCmd = &cobra.Command{
1717
logger := logex.Build(RootCmd.Short, zapcore.DebugLevel)
1818

1919
pk, sk, err := rsaencryption.GenerateKeys()
20-
if err != nil{
20+
if err != nil {
2121
logger.Fatal("Failed to generate operator keys", zap.Error(err))
2222
}
2323
logger.Info("generated public key (base64)", zap.Any("pk", pk))

cli/operator/node.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package operator
2+
3+
import (
4+
"fmt"
5+
"github.com/bloxapp/eth2-key-manager/core"
6+
"github.com/bloxapp/ssv/beacon/prysmgrpc"
7+
global_config "github.com/bloxapp/ssv/cli/config"
8+
"github.com/bloxapp/ssv/eth1"
9+
"github.com/bloxapp/ssv/eth1/goeth"
10+
"github.com/bloxapp/ssv/network/p2p"
11+
"github.com/bloxapp/ssv/operator"
12+
"github.com/bloxapp/ssv/shared/params"
13+
"github.com/bloxapp/ssv/storage"
14+
"github.com/bloxapp/ssv/storage/basedb"
15+
"github.com/bloxapp/ssv/utils/logex"
16+
"github.com/ilyakaznacheev/cleanenv"
17+
"github.com/spf13/cobra"
18+
"go.uber.org/zap"
19+
"log"
20+
)
21+
22+
type config struct {
23+
global_config.GlobalConfig `yaml:"global"`
24+
DBOptions basedb.Options `yaml:"db"`
25+
SSVOptions operator.Options `yaml:"ssv"`
26+
27+
Network string `yaml:"Network" env-default:"prater"`
28+
BeaconNodeAddr string `yaml:"BeaconNodeAddr" env:"BEACON_NODE_ADDR" env-required:"true"`
29+
OperatorKey string `yaml:"OperatorKey" env:"OPERATOR_KEY" env-description:"Operator private key, used to decrypt contract events"`
30+
ETH1Addr string `yaml:"ETH1Addr" env:"ETH_1_ADDR" env-required:"true"`
31+
ETH1SyncOffset string `yaml:"ETH1SyncOffset" env:"ETH_1_SYNC_OFFSET"`
32+
SmartContractAddr string `yaml:"SmartContractAddr" env:"SMART_CONTRACT_ADDR_KEY" env-description:"smart contract addr listen to event from" env-default:""`
33+
34+
P2pNetworkConfig p2p.Config `yaml:"p2p"`
35+
}
36+
37+
var cfg config
38+
39+
var globalArgs global_config.Args
40+
41+
// StartNodeCmd is the command to start SSV node
42+
var StartNodeCmd = &cobra.Command{
43+
Use: "start-node",
44+
Short: "Starts an instance of SSV node",
45+
Run: func(cmd *cobra.Command, args []string) {
46+
if err := cleanenv.ReadConfig(globalArgs.ConfigPath, &cfg); err != nil {
47+
log.Fatal(err)
48+
}
49+
if globalArgs.ShareConfigPath != "" {
50+
if err := cleanenv.ReadConfig(globalArgs.ShareConfigPath, &cfg); err != nil {
51+
log.Fatal(err)
52+
}
53+
}
54+
loggerLevel, err := logex.GetLoggerLevelValue(cfg.LogLevel)
55+
Logger := logex.Build(cmd.Parent().Short, loggerLevel)
56+
57+
if err != nil {
58+
Logger.Warn(fmt.Sprintf("Default log level set to %s", loggerLevel), zap.Error(err))
59+
}
60+
cfg.DBOptions.Logger = Logger
61+
db, err := storage.GetStorageFactory(cfg.DBOptions)
62+
if err != nil {
63+
Logger.Fatal("failed to create db!", zap.Error(err))
64+
}
65+
66+
// TODO Not refactored yet Start:
67+
eth2Network := core.NetworkFromString(cfg.Network)
68+
beaconClient, err := prysmgrpc.New(cmd.Context(), Logger, eth2Network, []byte("BloxStaking"), cfg.BeaconNodeAddr)
69+
if err != nil {
70+
Logger.Fatal("failed to create beacon client", zap.Error(err))
71+
}
72+
73+
network, err := p2p.New(cmd.Context(), Logger, &cfg.P2pNetworkConfig)
74+
if err != nil {
75+
Logger.Fatal("failed to create network", zap.Error(err))
76+
}
77+
// end Non refactored
78+
79+
ctx := cmd.Context()
80+
cfg.SSVOptions.Context = ctx
81+
cfg.SSVOptions.Logger = Logger
82+
cfg.SSVOptions.DB = db
83+
cfg.SSVOptions.Beacon = &beaconClient
84+
cfg.SSVOptions.ETHNetwork = &eth2Network
85+
cfg.SSVOptions.ValidatorOptions.ETHNetwork = &eth2Network
86+
cfg.SSVOptions.ValidatorOptions.Logger = Logger
87+
cfg.SSVOptions.ValidatorOptions.Context = ctx
88+
cfg.SSVOptions.ValidatorOptions.DB = db
89+
cfg.SSVOptions.ValidatorOptions.Network = network
90+
cfg.SSVOptions.ValidatorOptions.Beacon = beaconClient
91+
92+
operatorStorage := operator.NewOperatorNodeStorage(db, Logger)
93+
if err := operatorStorage.SetupPrivateKey(cfg.OperatorKey); err != nil {
94+
Logger.Fatal("failed to setup operator private key", zap.Error(err))
95+
}
96+
// create new eth1 client
97+
if cfg.SmartContractAddr != "" {
98+
Logger.Info("using smart contract addr from cfg", zap.String("addr", cfg.SmartContractAddr))
99+
params.SsvConfig().OperatorContractAddress = cfg.SmartContractAddr // TODO need to remove config and use in eth2 option cfg
100+
}
101+
cfg.SSVOptions.Eth1Client, err = goeth.NewEth1Client(goeth.ClientOptions{
102+
Ctx: cmd.Context(),
103+
Logger: Logger,
104+
NodeAddr: cfg.ETH1Addr,
105+
PrivKeyProvider: operatorStorage.GetPrivateKey,
106+
})
107+
if err != nil {
108+
Logger.Fatal("failed to create eth1 client", zap.Error(err))
109+
}
110+
111+
operatorNode := operator.New(cfg.SSVOptions)
112+
113+
if err := operatorNode.StartEth1(eth1.HexStringToSyncOffset(cfg.ETH1SyncOffset)); err != nil {
114+
Logger.Fatal("failed to start eth1", zap.Error(err))
115+
}
116+
if err := operatorNode.Start(); err != nil {
117+
Logger.Fatal("failed to start SSV node", zap.Error(err))
118+
}
119+
},
120+
}
121+
122+
func init() {
123+
global_config.ProcessArgs(&cfg, &globalArgs, StartNodeCmd)
124+
}

0 commit comments

Comments
 (0)