wormhole/node/cmd/guardiand/node.go

1357 lines
51 KiB
Go

package guardiand
import (
"context"
"encoding/base64"
"fmt"
"log"
"net/http"
_ "net/http/pprof" // #nosec G108 we are using a custom router (`router := mux.NewRouter()`) and thus not automatically expose pprof.
"os"
"os/signal"
"path"
"strings"
"syscall"
"github.com/certusone/wormhole/node/pkg/watchers/wormchain"
"github.com/certusone/wormhole/node/pkg/watchers/cosmwasm"
"github.com/certusone/wormhole/node/pkg/watchers/algorand"
"github.com/certusone/wormhole/node/pkg/watchers/aptos"
"github.com/certusone/wormhole/node/pkg/watchers/evm"
"github.com/certusone/wormhole/node/pkg/watchers/near"
"github.com/certusone/wormhole/node/pkg/watchers/solana"
"github.com/certusone/wormhole/node/pkg/watchers/sui"
"github.com/benbjohnson/clock"
"github.com/certusone/wormhole/node/pkg/db"
"github.com/certusone/wormhole/node/pkg/notify/discord"
"github.com/certusone/wormhole/node/pkg/telemetry"
"github.com/certusone/wormhole/node/pkg/version"
"github.com/gagliardetto/solana-go/rpc"
"go.uber.org/zap/zapcore"
solana_types "github.com/gagliardetto/solana-go"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/certusone/wormhole/node/pkg/common"
"github.com/certusone/wormhole/node/pkg/devnet"
"github.com/certusone/wormhole/node/pkg/governor"
"github.com/certusone/wormhole/node/pkg/p2p"
"github.com/certusone/wormhole/node/pkg/processor"
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
"github.com/certusone/wormhole/node/pkg/readiness"
"github.com/certusone/wormhole/node/pkg/reporter"
"github.com/certusone/wormhole/node/pkg/supervisor"
eth_common "github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/spf13/cobra"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.uber.org/zap"
ipfslog "github.com/ipfs/go-log/v2"
)
var (
p2pNetworkID *string
p2pPort *uint
p2pBootstrap *string
nodeKeyPath *string
adminSocketPath *string
publicGRPCSocketPath *string
dataDir *string
statusAddr *string
guardianKeyPath *string
solanaContract *string
ethRPC *string
ethContract *string
bscRPC *string
bscContract *string
polygonRPC *string
polygonContract *string
polygonRootChainRpc *string
polygonRootChainContractAddress *string
auroraRPC *string
auroraContract *string
fantomRPC *string
fantomContract *string
avalancheRPC *string
avalancheContract *string
oasisRPC *string
oasisContract *string
karuraRPC *string
karuraContract *string
acalaRPC *string
acalaContract *string
klaytnRPC *string
klaytnContract *string
celoRPC *string
celoContract *string
moonbeamRPC *string
moonbeamContract *string
neonRPC *string
neonContract *string
terraWS *string
terraLCD *string
terraContract *string
terra2WS *string
terra2LCD *string
terra2Contract *string
injectiveWS *string
injectiveLCD *string
injectiveContract *string
xplaWS *string
xplaLCD *string
xplaContract *string
algorandIndexerRPC *string
algorandIndexerToken *string
algorandAlgodRPC *string
algorandAlgodToken *string
algorandAppID *uint64
nearRPC *string
nearContract *string
wormchainWS *string
wormchainLCD *string
aptosRPC *string
aptosAccount *string
aptosHandle *string
suiRPC *string
suiWS *string
suiAccount *string
suiPackage *string
solanaRPC *string
pythnetContract *string
pythnetRPC *string
arbitrumRPC *string
arbitrumContract *string
optimismRPC *string
optimismContract *string
optimismCtcRpc *string
optimismCtcContractAddress *string
logLevel *string
unsafeDevMode *bool
testnetMode *bool
devNumGuardians *uint
nodeName *string
publicRPC *string
publicWeb *string
tlsHostname *string
tlsProdEnv *bool
disableHeartbeatVerify *bool
disableTelemetry *bool
telemetryKey *string
discordToken *string
discordChannel *string
bigTablePersistenceEnabled *bool
bigTableGCPProject *string
bigTableInstanceName *string
bigTableTableName *string
bigTableTopicName *string
bigTableKeyPath *string
chainGovernorEnabled *bool
)
func init() {
p2pNetworkID = NodeCmd.Flags().String("network", "/wormhole/dev", "P2P network identifier")
p2pPort = NodeCmd.Flags().Uint("port", 8999, "P2P UDP listener port")
p2pBootstrap = NodeCmd.Flags().String("bootstrap", "", "P2P bootstrap peers (comma-separated)")
statusAddr = NodeCmd.Flags().String("statusAddr", "[::]:6060", "Listen address for status server (disabled if blank)")
nodeKeyPath = NodeCmd.Flags().String("nodeKey", "", "Path to node key (will be generated if it doesn't exist)")
adminSocketPath = NodeCmd.Flags().String("adminSocket", "", "Admin gRPC service UNIX domain socket path")
publicGRPCSocketPath = NodeCmd.Flags().String("publicGRPCSocket", "", "Public gRPC service UNIX domain socket path")
dataDir = NodeCmd.Flags().String("dataDir", "", "Data directory")
guardianKeyPath = NodeCmd.Flags().String("guardianKey", "", "Path to guardian key (required)")
solanaContract = NodeCmd.Flags().String("solanaContract", "", "Address of the Solana program (required)")
ethRPC = NodeCmd.Flags().String("ethRPC", "", "Ethereum RPC URL")
ethContract = NodeCmd.Flags().String("ethContract", "", "Ethereum contract address")
bscRPC = NodeCmd.Flags().String("bscRPC", "", "Binance Smart Chain RPC URL")
bscContract = NodeCmd.Flags().String("bscContract", "", "Binance Smart Chain contract address")
polygonRPC = NodeCmd.Flags().String("polygonRPC", "", "Polygon RPC URL")
polygonContract = NodeCmd.Flags().String("polygonContract", "", "Polygon contract address")
polygonRootChainRpc = NodeCmd.Flags().String("polygonRootChainRpc", "", "Polygon root chain RPC")
polygonRootChainContractAddress = NodeCmd.Flags().String("polygonRootChainContractAddress", "", "Polygon root chain contract address")
avalancheRPC = NodeCmd.Flags().String("avalancheRPC", "", "Avalanche RPC URL")
avalancheContract = NodeCmd.Flags().String("avalancheContract", "", "Avalanche contract address")
oasisRPC = NodeCmd.Flags().String("oasisRPC", "", "Oasis RPC URL")
oasisContract = NodeCmd.Flags().String("oasisContract", "", "Oasis contract address")
auroraRPC = NodeCmd.Flags().String("auroraRPC", "", "Aurora Websocket RPC URL")
auroraContract = NodeCmd.Flags().String("auroraContract", "", "Aurora contract address")
fantomRPC = NodeCmd.Flags().String("fantomRPC", "", "Fantom Websocket RPC URL")
fantomContract = NodeCmd.Flags().String("fantomContract", "", "Fantom contract address")
karuraRPC = NodeCmd.Flags().String("karuraRPC", "", "Karura RPC URL")
karuraContract = NodeCmd.Flags().String("karuraContract", "", "Karura contract address")
acalaRPC = NodeCmd.Flags().String("acalaRPC", "", "Acala RPC URL")
acalaContract = NodeCmd.Flags().String("acalaContract", "", "Acala contract address")
klaytnRPC = NodeCmd.Flags().String("klaytnRPC", "", "Klaytn RPC URL")
klaytnContract = NodeCmd.Flags().String("klaytnContract", "", "Klaytn contract address")
celoRPC = NodeCmd.Flags().String("celoRPC", "", "Celo RPC URL")
celoContract = NodeCmd.Flags().String("celoContract", "", "Celo contract address")
moonbeamRPC = NodeCmd.Flags().String("moonbeamRPC", "", "Moonbeam RPC URL")
moonbeamContract = NodeCmd.Flags().String("moonbeamContract", "", "Moonbeam contract address")
neonRPC = NodeCmd.Flags().String("neonRPC", "", "Neon RPC URL")
neonContract = NodeCmd.Flags().String("neonContract", "", "Neon contract address")
terraWS = NodeCmd.Flags().String("terraWS", "", "Path to terrad root for websocket connection")
terraLCD = NodeCmd.Flags().String("terraLCD", "", "Path to LCD service root for http calls")
terraContract = NodeCmd.Flags().String("terraContract", "", "Wormhole contract address on Terra blockchain")
terra2WS = NodeCmd.Flags().String("terra2WS", "", "Path to terrad root for websocket connection")
terra2LCD = NodeCmd.Flags().String("terra2LCD", "", "Path to LCD service root for http calls")
terra2Contract = NodeCmd.Flags().String("terra2Contract", "", "Wormhole contract address on Terra 2 blockchain")
injectiveWS = NodeCmd.Flags().String("injectiveWS", "", "Path to root for Injective websocket connection")
injectiveLCD = NodeCmd.Flags().String("injectiveLCD", "", "Path to LCD service root for Injective http calls")
injectiveContract = NodeCmd.Flags().String("injectiveContract", "", "Wormhole contract address on Injective blockchain")
xplaWS = NodeCmd.Flags().String("xplaWS", "", "Path to root for XPLA websocket connection")
xplaLCD = NodeCmd.Flags().String("xplaLCD", "", "Path to LCD service root for XPLA http calls")
xplaContract = NodeCmd.Flags().String("xplaContract", "", "Wormhole contract address on XPLA blockchain")
algorandIndexerRPC = NodeCmd.Flags().String("algorandIndexerRPC", "", "Algorand Indexer RPC URL")
algorandIndexerToken = NodeCmd.Flags().String("algorandIndexerToken", "", "Algorand Indexer access token")
algorandAlgodRPC = NodeCmd.Flags().String("algorandAlgodRPC", "", "Algorand Algod RPC URL")
algorandAlgodToken = NodeCmd.Flags().String("algorandAlgodToken", "", "Algorand Algod access token")
algorandAppID = NodeCmd.Flags().Uint64("algorandAppID", 0, "Algorand app id")
nearRPC = NodeCmd.Flags().String("nearRPC", "", "near RPC URL")
nearContract = NodeCmd.Flags().String("nearContract", "", "near contract")
wormchainWS = NodeCmd.Flags().String("wormchainWS", "", "Path to wormchaind root for websocket connection")
wormchainLCD = NodeCmd.Flags().String("wormchainLCD", "", "Path to LCD service root for http calls")
aptosRPC = NodeCmd.Flags().String("aptosRPC", "", "aptos RPC URL")
aptosAccount = NodeCmd.Flags().String("aptosAccount", "", "aptos account")
aptosHandle = NodeCmd.Flags().String("aptosHandle", "", "aptos handle")
suiRPC = NodeCmd.Flags().String("suiRPC", "", "sui RPC URL")
suiWS = NodeCmd.Flags().String("suiWS", "", "sui WS URL")
suiAccount = NodeCmd.Flags().String("suiAccount", "", "sui account")
suiPackage = NodeCmd.Flags().String("suiPackage", "", "sui package")
solanaRPC = NodeCmd.Flags().String("solanaRPC", "", "Solana RPC URL (required")
pythnetContract = NodeCmd.Flags().String("pythnetContract", "", "Address of the PythNet program (required)")
pythnetRPC = NodeCmd.Flags().String("pythnetRPC", "", "PythNet RPC URL (required")
arbitrumRPC = NodeCmd.Flags().String("arbitrumRPC", "", "Arbitrum RPC URL")
arbitrumContract = NodeCmd.Flags().String("arbitrumContract", "", "Arbitrum contract address")
optimismRPC = NodeCmd.Flags().String("optimismRPC", "", "Optimism RPC URL")
optimismContract = NodeCmd.Flags().String("optimismContract", "", "Optimism contract address")
optimismCtcRpc = NodeCmd.Flags().String("optimismCtcRpc", "", "Optimism CTC RPC")
optimismCtcContractAddress = NodeCmd.Flags().String("optimismCtcContractAddress", "", "Optimism CTC contract address")
logLevel = NodeCmd.Flags().String("logLevel", "info", "Logging level (debug, info, warn, error, dpanic, panic, fatal)")
unsafeDevMode = NodeCmd.Flags().Bool("unsafeDevMode", false, "Launch node in unsafe, deterministic devnet mode")
testnetMode = NodeCmd.Flags().Bool("testnetMode", false, "Launch node in testnet mode (enables testnet-only features)")
devNumGuardians = NodeCmd.Flags().Uint("devNumGuardians", 5, "Number of devnet guardians to include in guardian set")
nodeName = NodeCmd.Flags().String("nodeName", "", "Node name to announce in gossip heartbeats")
publicRPC = NodeCmd.Flags().String("publicRPC", "", "Listen address for public gRPC interface")
publicWeb = NodeCmd.Flags().String("publicWeb", "", "Listen address for public REST and gRPC Web interface")
tlsHostname = NodeCmd.Flags().String("tlsHostname", "", "If set, serve publicWeb as TLS with this hostname using Let's Encrypt")
tlsProdEnv = NodeCmd.Flags().Bool("tlsProdEnv", false,
"Use the production Let's Encrypt environment instead of staging")
disableHeartbeatVerify = NodeCmd.Flags().Bool("disableHeartbeatVerify", false,
"Disable heartbeat signature verification (useful during network startup)")
disableTelemetry = NodeCmd.Flags().Bool("disableTelemetry", false,
"Disable telemetry")
telemetryKey = NodeCmd.Flags().String("telemetryKey", "",
"Telemetry write key")
discordToken = NodeCmd.Flags().String("discordToken", "", "Discord bot token (optional)")
discordChannel = NodeCmd.Flags().String("discordChannel", "", "Discord channel name (optional)")
bigTablePersistenceEnabled = NodeCmd.Flags().Bool("bigTablePersistenceEnabled", false, "Turn on forwarding events to BigTable")
bigTableGCPProject = NodeCmd.Flags().String("bigTableGCPProject", "", "Google Cloud project ID for storing events")
bigTableInstanceName = NodeCmd.Flags().String("bigTableInstanceName", "", "BigTable instance name for storing events")
bigTableTableName = NodeCmd.Flags().String("bigTableTableName", "", "BigTable table name to store events in")
bigTableTopicName = NodeCmd.Flags().String("bigTableTopicName", "", "GCP topic name to publish to")
bigTableKeyPath = NodeCmd.Flags().String("bigTableKeyPath", "", "Path to json Service Account key")
chainGovernorEnabled = NodeCmd.Flags().Bool("chainGovernorEnabled", false, "Run the chain governor")
}
var (
rootCtx context.Context
rootCtxCancel context.CancelFunc
)
// "Why would anyone do this?" are famous last words.
//
// We already forcibly override RPC URLs and keys in dev mode to prevent security
// risks from operator error, but an extra warning won't hurt.
const devwarning = `
+++++++++++++++++++++++++++++++++++++++++++++++++++
| NODE IS RUNNING IN INSECURE DEVELOPMENT MODE |
| |
| Do not use --unsafeDevMode in prod. |
+++++++++++++++++++++++++++++++++++++++++++++++++++
`
// NodeCmd represents the node command
var NodeCmd = &cobra.Command{
Use: "node",
Short: "Run the guardiand node",
Run: runNode,
}
// This variable may be overridden by the -X linker flag to "dev" in which case
// we enforce the --unsafeDevMode flag. Only development binaries/docker images
// are distributed. Production binaries are required to be built from source by
// guardians to reduce risk from a compromised builder.
var Build = "prod"
// observationRequestBufferSize is the buffer size of the per-network reobservation channel
const observationRequestBufferSize = 25
func runNode(cmd *cobra.Command, args []string) {
if Build == "dev" && !*unsafeDevMode {
fmt.Println("This is a development build. --unsafeDevMode must be enabled.")
os.Exit(1)
}
if *unsafeDevMode {
fmt.Print(devwarning)
}
common.LockMemory()
common.SetRestrictiveUmask()
// Refuse to run as root in production mode.
if !*unsafeDevMode && os.Geteuid() == 0 {
fmt.Println("can't run as uid 0")
os.Exit(1)
}
// Set up logging. The go-log zap wrapper that libp2p uses is compatible with our
// usage of zap in supervisor, which is nice.
lvl, err := ipfslog.LevelFromString(*logLevel)
if err != nil {
fmt.Println("Invalid log level")
os.Exit(1)
}
logger := zap.New(zapcore.NewCore(
consoleEncoder{zapcore.NewConsoleEncoder(
zap.NewDevelopmentEncoderConfig())},
zapcore.AddSync(zapcore.Lock(os.Stderr)),
zap.NewAtomicLevelAt(zapcore.Level(lvl))))
if *unsafeDevMode {
// Use the hostname as nodeName. For production, we don't want to do this to
// prevent accidentally leaking sensitive hostnames.
hostname, err := os.Hostname()
if err != nil {
panic(err)
}
*nodeName = hostname
// Put node name into the log for development.
logger = logger.Named(*nodeName)
}
// Override the default go-log config, which uses a magic environment variable.
ipfslog.SetAllLoggers(lvl)
if *statusAddr != "" {
// Use a custom routing instead of using http.DefaultServeMux directly to avoid accidentally exposing packages
// that register themselves with it by default (like pprof).
router := mux.NewRouter()
// pprof server. NOT necessarily safe to expose publicly - only enable it in dev mode to avoid exposing it by
// accident. There's benefit to having pprof enabled on production nodes, but we would likely want to expose it
// via a dedicated port listening on localhost, or via the admin UNIX socket.
if *unsafeDevMode {
// Pass requests to http.DefaultServeMux, which pprof automatically registers with as an import side-effect.
router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux)
}
// Simple endpoint exposing node readiness (safe to expose to untrusted clients)
router.HandleFunc("/readyz", readiness.Handler)
// Prometheus metrics (safe to expose to untrusted clients)
router.Handle("/metrics", promhttp.Handler())
go func() {
logger.Info("status server listening on [::]:6060")
// SECURITY: If making changes, ensure that we always do `router := mux.NewRouter()` before this to avoid accidentally exposing pprof
logger.Error("status server crashed", zap.Error(http.ListenAndServe(*statusAddr, router))) // #nosec G114 local status server not vulnerable to DoS attack
}()
}
// In devnet mode, we automatically set a number of flags that rely on deterministic keys.
if *unsafeDevMode {
g0key, err := peer.IDFromPrivateKey(devnet.DeterministicP2PPrivKeyByIndex(0))
if err != nil {
panic(err)
}
// Use the first guardian node as bootstrap
*p2pBootstrap = fmt.Sprintf("/dns4/guardian-0.guardian/udp/%d/quic/p2p/%s", *p2pPort, g0key.String())
// Deterministic ganache ETH devnet address.
*ethContract = unsafeDevModeEvmContractAddress(*ethContract)
*bscContract = unsafeDevModeEvmContractAddress(*bscContract)
*polygonContract = unsafeDevModeEvmContractAddress(*polygonContract)
*avalancheContract = unsafeDevModeEvmContractAddress(*avalancheContract)
*oasisContract = unsafeDevModeEvmContractAddress(*oasisContract)
*auroraContract = unsafeDevModeEvmContractAddress(*auroraContract)
*fantomContract = unsafeDevModeEvmContractAddress(*fantomContract)
*karuraContract = unsafeDevModeEvmContractAddress(*karuraContract)
*acalaContract = unsafeDevModeEvmContractAddress(*acalaContract)
*klaytnContract = unsafeDevModeEvmContractAddress(*klaytnContract)
*celoContract = unsafeDevModeEvmContractAddress(*celoContract)
*moonbeamContract = unsafeDevModeEvmContractAddress(*moonbeamContract)
*neonContract = unsafeDevModeEvmContractAddress(*neonContract)
*arbitrumContract = unsafeDevModeEvmContractAddress(*arbitrumContract)
*optimismContract = unsafeDevModeEvmContractAddress(*optimismContract)
}
// Verify flags
if *nodeKeyPath == "" && !*unsafeDevMode { // In devnet mode, keys are deterministically generated.
logger.Fatal("Please specify --nodeKey")
}
if *guardianKeyPath == "" {
logger.Fatal("Please specify --guardianKey")
}
if *adminSocketPath == "" {
logger.Fatal("Please specify --adminSocket")
}
if *adminSocketPath == *publicGRPCSocketPath {
logger.Fatal("--adminSocket must not equal --publicGRPCSocket")
}
if (*publicRPC != "" || *publicWeb != "") && *publicGRPCSocketPath == "" {
logger.Fatal("If either --publicRPC or --publicWeb is specified, --publicGRPCSocket must also be specified")
}
if *dataDir == "" {
logger.Fatal("Please specify --dataDir")
}
if *ethRPC == "" {
logger.Fatal("Please specify --ethRPC")
}
if *ethContract == "" {
logger.Fatal("Please specify --ethContract")
}
if *bscRPC == "" {
logger.Fatal("Please specify --bscRPC")
}
if *bscContract == "" {
logger.Fatal("Please specify --bscContract")
}
if *polygonRPC == "" {
logger.Fatal("Please specify --polygonRPC")
}
if *polygonContract == "" {
logger.Fatal("Please specify --polygonContract")
}
if *avalancheRPC == "" {
logger.Fatal("Please specify --avalancheRPC")
}
if *oasisRPC == "" {
logger.Fatal("Please specify --oasisRPC")
}
if *fantomRPC == "" {
logger.Fatal("Please specify --fantomRPC")
}
if *fantomContract == "" && !*unsafeDevMode {
logger.Fatal("Please specify --fantomContract")
}
if *auroraRPC == "" {
logger.Fatal("Please specify --auroraRPC")
}
if *auroraContract == "" && !*unsafeDevMode {
logger.Fatal("Please specify --auroraContract")
}
if *karuraRPC == "" {
logger.Fatal("Please specify --karuraRPC")
}
if *karuraContract == "" && !*unsafeDevMode {
logger.Fatal("Please specify --karuraContract")
}
if *acalaRPC == "" {
logger.Fatal("Please specify --acalaRPC")
}
if *acalaContract == "" && !*unsafeDevMode {
logger.Fatal("Please specify --acalaContract")
}
if *klaytnRPC == "" {
logger.Fatal("Please specify --klaytnRPC")
}
if *klaytnContract == "" && !*unsafeDevMode {
logger.Fatal("Please specify --klaytnContract")
}
if *celoRPC == "" {
logger.Fatal("Please specify --celoRPC")
}
if *celoContract == "" && !*unsafeDevMode {
logger.Fatal("Please specify --celoContract")
}
if *nearRPC != "" {
if *nearContract == "" {
logger.Fatal("If --nearRPC is specified, then --nearContract must be specified")
}
} else if *nearContract != "" {
logger.Fatal("If --nearRPC is not specified, then --nearContract must not be specified")
}
if *moonbeamRPC == "" {
logger.Fatal("Please specify --moonbeamRPC")
}
if *moonbeamContract == "" {
logger.Fatal("Please specify --moonbeamContract")
}
if *arbitrumRPC == "" {
logger.Fatal("Please specify --arbitrumRPC")
}
if *arbitrumContract == "" {
logger.Fatal("Please specify --arbitrumContract")
}
if *xplaWS != "" {
if *xplaLCD == "" || *xplaContract == "" {
logger.Fatal("If --xplaWS is specified, then --xplaLCD and --xplaContract must be specified")
}
} else if *xplaLCD != "" || *xplaContract != "" {
logger.Fatal("If --xplaWS is not specified, then --xplaLCD and --xplaContract must not be specified")
}
if *wormchainWS != "" {
if *wormchainLCD == "" {
logger.Fatal("If --wormchainWS is specified, then --wormchainLCD must be specified")
}
} else if *wormchainLCD != "" {
logger.Fatal("If --wormchainWS is not specified, then --wormchainLCD must not be specified")
}
if *aptosRPC != "" {
if *aptosAccount == "" {
logger.Fatal("If --aptosRPC is specified, then --aptosAccount must be specified")
}
if *aptosHandle == "" {
logger.Fatal("If --aptosRPC is specified, then --aptosHandle must be specified")
}
}
if *suiRPC != "" {
if *suiWS == "" {
logger.Fatal("If --suiRPC is specified, then --suiWS must be specified")
}
if *suiAccount == "" {
logger.Fatal("If --suiRPC is specified, then --suiAccount must be specified")
}
if *suiPackage == "" && !*unsafeDevMode {
logger.Fatal("If --suiRPC is specified, then --suiPackage must be specified")
}
}
if (*optimismRPC == "") != (*optimismContract == "") {
logger.Fatal("Both --optimismContract and --optimismRPC must be set together or both unset")
}
if *testnetMode {
if *neonRPC == "" {
logger.Fatal("Please specify --neonRPC")
}
if *neonContract == "" {
logger.Fatal("Please specify --neonContract")
}
} else {
if *neonRPC != "" && !*unsafeDevMode {
logger.Fatal("Please do not specify --neonRPC")
}
if *neonContract != "" && !*unsafeDevMode {
logger.Fatal("Please do not specify --neonContract")
}
}
if *nodeName == "" {
logger.Fatal("Please specify --nodeName")
}
// Solana, Terra Classic, Terra 2, and Algorand are optional in devnet
if !*unsafeDevMode {
if *solanaContract == "" {
logger.Fatal("Please specify --solanaContract")
}
if *solanaRPC == "" {
logger.Fatal("Please specify --solanaRPC")
}
if *terraWS == "" {
logger.Fatal("Please specify --terraWS")
}
if *terraLCD == "" {
logger.Fatal("Please specify --terraLCD")
}
if *terraContract == "" {
logger.Fatal("Please specify --terraContract")
}
if *terra2WS == "" {
logger.Fatal("Please specify --terra2WS")
}
if *terra2LCD == "" {
logger.Fatal("Please specify --terra2LCD")
}
if *terra2Contract == "" {
logger.Fatal("Please specify --terra2Contract")
}
if *algorandIndexerRPC == "" {
logger.Fatal("Please specify --algorandIndexerRPC")
}
if *algorandAlgodRPC == "" {
logger.Fatal("Please specify --algorandAlgodRPC")
}
if *algorandAlgodToken == "" {
logger.Fatal("Please specify --algorandAlgodToken")
}
if *algorandAppID == 0 {
logger.Fatal("Please specify --algorandAppID")
}
if *pythnetContract == "" {
logger.Fatal("Please specify --pythnetContract")
}
if *pythnetRPC == "" {
logger.Fatal("Please specify --pythnetRPC")
}
if *injectiveWS == "" {
logger.Fatal("Please specify --injectiveWS")
}
if *injectiveLCD == "" {
logger.Fatal("Please specify --injectiveLCD")
}
if *injectiveContract == "" {
logger.Fatal("Please specify --injectiveContract")
}
}
if *bigTablePersistenceEnabled {
if *bigTableGCPProject == "" {
logger.Fatal("Please specify --bigTableGCPProject")
}
if *bigTableInstanceName == "" {
logger.Fatal("Please specify --bigTableInstanceName")
}
if *bigTableTableName == "" {
logger.Fatal("Please specify --bigTableTableName")
}
if *bigTableTopicName == "" {
logger.Fatal("Please specify --bigTableTopicName")
}
if *bigTableKeyPath == "" {
logger.Fatal("Please specify --bigTableKeyPath")
}
}
// Complain about Infura on mainnet.
//
// As it turns out, Infura has a bug where it would sometimes incorrectly round
// block timestamps, which causes consensus issues - the timestamp is part of
// the VAA and nodes using Infura would sometimes derive an incorrect VAA,
// accidentally attacking the network by signing a conflicting VAA.
//
// Node operators do not usually rely on Infura in the first place - doing
// so is insecure, since nodes blindly trust the connected nodes to verify
// on-chain message proofs. However, node operators sometimes used
// Infura during migrations where their primary node was offline, causing
// the aforementioned consensus oddities which were eventually found to
// be Infura-related. This is generally to the detriment of network security
// and a judgement call made by individual operators. In the case of Infura,
// we know it's actively dangerous so let's make an opinionated argument.
//
// Insert "I'm a sign, not a cop" meme.
//
if strings.Contains(*ethRPC, "mainnet.infura.io") ||
strings.Contains(*polygonRPC, "polygon-mainnet.infura.io") {
logger.Fatal("Infura is known to send incorrect blocks - please use your own nodes")
}
ethContractAddr := eth_common.HexToAddress(*ethContract)
bscContractAddr := eth_common.HexToAddress(*bscContract)
polygonContractAddr := eth_common.HexToAddress(*polygonContract)
avalancheContractAddr := eth_common.HexToAddress(*avalancheContract)
oasisContractAddr := eth_common.HexToAddress(*oasisContract)
auroraContractAddr := eth_common.HexToAddress(*auroraContract)
fantomContractAddr := eth_common.HexToAddress(*fantomContract)
karuraContractAddr := eth_common.HexToAddress(*karuraContract)
acalaContractAddr := eth_common.HexToAddress(*acalaContract)
klaytnContractAddr := eth_common.HexToAddress(*klaytnContract)
celoContractAddr := eth_common.HexToAddress(*celoContract)
moonbeamContractAddr := eth_common.HexToAddress(*moonbeamContract)
neonContractAddr := eth_common.HexToAddress(*neonContract)
arbitrumContractAddr := eth_common.HexToAddress(*arbitrumContract)
optimismContractAddr := eth_common.HexToAddress(*optimismContract)
solAddress, err := solana_types.PublicKeyFromBase58(*solanaContract)
if err != nil {
logger.Fatal("invalid Solana contract address", zap.Error(err))
}
var pythnetAddress solana_types.PublicKey
pythnetAddress, err = solana_types.PublicKeyFromBase58(*pythnetContract)
if err != nil {
logger.Fatal("invalid PythNet contract address", zap.Error(err))
}
// In devnet mode, we generate a deterministic guardian key and write it to disk.
if *unsafeDevMode {
gk, err := generateDevnetGuardianKey()
if err != nil {
logger.Fatal("failed to generate devnet guardian key", zap.Error(err))
}
err = writeGuardianKey(gk, "auto-generated deterministic devnet key", *guardianKeyPath, true)
if err != nil {
logger.Fatal("failed to write devnet guardian key", zap.Error(err))
}
}
// Database
dbPath := path.Join(*dataDir, "db")
if err := os.MkdirAll(dbPath, 0700); err != nil {
logger.Fatal("failed to create database directory", zap.Error(err))
}
db, err := db.Open(dbPath)
if err != nil {
logger.Fatal("failed to open database", zap.Error(err))
}
defer db.Close()
// Guardian key
gk, err := loadGuardianKey(*guardianKeyPath)
if err != nil {
logger.Fatal("failed to load guardian key", zap.Error(err))
}
guardianAddr := ethcrypto.PubkeyToAddress(gk.PublicKey).String()
logger.Info("Loaded guardian key", zap.String(
"address", guardianAddr))
p2p.DefaultRegistry.SetGuardianAddress(guardianAddr)
// Node's main lifecycle context.
rootCtx, rootCtxCancel = context.WithCancel(context.Background())
defer rootCtxCancel()
sigterm := make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGTERM)
go func() {
<-sigterm
logger.Info("Received sigterm. exiting.")
rootCtxCancel()
}()
// Ethereum lock event channel
lockC := make(chan *common.MessagePublication)
// Ethereum incoming guardian set updates
setC := make(chan *common.GuardianSet)
// Outbound gossip message queue
sendC := make(chan []byte)
// Inbound observations
obsvC := make(chan *gossipv1.SignedObservation, 50)
// Inbound signed VAAs
signedInC := make(chan *gossipv1.SignedVAAWithQuorum, 50)
// Inbound observation requests from the p2p service (for all chains)
obsvReqC := make(chan *gossipv1.ObservationRequest, common.ObsvReqChannelSize)
// Outbound observation requests
obsvReqSendC := make(chan *gossipv1.ObservationRequest, common.ObsvReqChannelSize)
// Injected VAAs (manually generated rather than created via observation)
injectC := make(chan *vaa.VAA)
// Guardian set state managed by processor
gst := common.NewGuardianSetState(nil)
// Per-chain observation requests
chainObsvReqC := make(map[vaa.ChainID]chan *gossipv1.ObservationRequest)
var notifier *discord.DiscordNotifier
if *discordToken != "" {
notifier, err = discord.NewDiscordNotifier(*discordToken, *discordChannel, logger)
if err != nil {
logger.Error("failed to initialize Discord bot", zap.Error(err))
}
}
// Load p2p private key
var priv crypto.PrivKey
if *unsafeDevMode {
idx, err := devnet.GetDevnetIndex()
if err != nil {
logger.Fatal("Failed to parse hostname - are we running in devnet?")
}
priv = devnet.DeterministicP2PPrivKeyByIndex(int64(idx))
} else {
priv, err = common.GetOrCreateNodeKey(logger, *nodeKeyPath)
if err != nil {
logger.Fatal("Failed to load node key", zap.Error(err))
}
}
// Enable unless it is disabled. For devnet, only when --telemetryKey is set.
if !*disableTelemetry && (!*unsafeDevMode || *unsafeDevMode && *telemetryKey != "") {
logger.Info("Telemetry enabled")
if *telemetryKey == "" {
logger.Fatal("Please specify --telemetryKey")
}
creds, err := decryptTelemetryServiceAccount()
if err != nil {
logger.Fatal("Failed to decrypt telemetry service account", zap.Error(err))
}
// Get libp2p peer ID from private key
pk := priv.GetPublic()
peerID, err := peer.IDFromPublicKey(pk)
if err != nil {
logger.Fatal("Failed to get peer ID from private key", zap.Error(err))
}
tm, err := telemetry.New(context.Background(), telemetryProject, creds, map[string]string{
"node_name": *nodeName,
"node_key": peerID.Pretty(),
"guardian_addr": guardianAddr,
"network": *p2pNetworkID,
"version": version.Version(),
})
if err != nil {
logger.Fatal("Failed to initialize telemetry", zap.Error(err))
}
defer tm.Close()
logger = tm.WrapLogger(logger)
} else {
logger.Info("Telemetry disabled")
}
// Redirect ipfs logs to plain zap
ipfslog.SetPrimaryCore(logger.Core())
// provides methods for reporting progress toward message attestation, and channels for receiving attestation lifecyclye events.
attestationEvents := reporter.EventListener(logger)
var gov *governor.ChainGovernor
if *chainGovernorEnabled {
logger.Info("chain governor is enabled")
env := governor.MainNetMode
if *testnetMode {
env = governor.TestNetMode
} else if *unsafeDevMode {
env = governor.DevNetMode
}
gov = governor.NewChainGovernor(logger, db, env)
} else {
logger.Info("chain governor is disabled")
}
// local admin service socket
adminService, err := adminServiceRunnable(logger, *adminSocketPath, injectC, signedInC, obsvReqSendC, db, gst, gov, gk, ethRPC, ethContract)
if err != nil {
logger.Fatal("failed to create admin service socket", zap.Error(err))
}
// Run supervisor.
supervisor.New(rootCtx, logger, func(ctx context.Context) error {
if err := supervisor.Run(ctx, "p2p", p2p.Run(
obsvC, obsvReqC, obsvReqSendC, sendC, signedInC, priv, gk, gst, *p2pPort, *p2pNetworkID, *p2pBootstrap, *nodeName, *disableHeartbeatVerify, rootCtxCancel, gov, nil, nil)); err != nil {
return err
}
// For each chain that wants a watcher, we:
// - create and register a component for readiness checks.
// - create an observation request channel.
// - create the watcher.
//
// NOTE: The "none" is a special indicator to disable a watcher until it is desirable to turn it back on.
var ethWatcher *evm.Watcher
if shouldStart(ethRPC) {
logger.Info("Starting Ethereum watcher")
readiness.RegisterComponent(common.ReadinessEthSyncing)
chainObsvReqC[vaa.ChainIDEthereum] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
ethWatcher = evm.NewEthWatcher(*ethRPC, ethContractAddr, "eth", common.ReadinessEthSyncing, vaa.ChainIDEthereum, lockC, setC, chainObsvReqC[vaa.ChainIDEthereum], *unsafeDevMode)
if err := supervisor.Run(ctx, "ethwatch",
ethWatcher.Run); err != nil {
return err
}
}
if shouldStart(bscRPC) {
logger.Info("Starting BSC watcher")
readiness.RegisterComponent(common.ReadinessBSCSyncing)
chainObsvReqC[vaa.ChainIDBSC] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
bscWatcher := evm.NewEthWatcher(*bscRPC, bscContractAddr, "bsc", common.ReadinessBSCSyncing, vaa.ChainIDBSC, lockC, nil, chainObsvReqC[vaa.ChainIDBSC], *unsafeDevMode)
bscWatcher.SetWaitForConfirmations(true)
if err := supervisor.Run(ctx, "bscwatch", bscWatcher.Run); err != nil {
return err
}
}
if shouldStart(polygonRPC) {
// Checkpointing is required in mainnet, so we don't need to wait for confirmations.
waitForConfirmations := *unsafeDevMode || *testnetMode
if !waitForConfirmations && *polygonRootChainRpc == "" {
log.Fatal("Polygon checkpointing is required in mainnet")
}
logger.Info("Starting Polygon watcher")
readiness.RegisterComponent(common.ReadinessPolygonSyncing)
chainObsvReqC[vaa.ChainIDPolygon] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
polygonWatcher := evm.NewEthWatcher(*polygonRPC, polygonContractAddr, "polygon", common.ReadinessPolygonSyncing, vaa.ChainIDPolygon, lockC, nil, chainObsvReqC[vaa.ChainIDPolygon], *unsafeDevMode)
polygonWatcher.SetWaitForConfirmations(waitForConfirmations)
if err := polygonWatcher.SetRootChainParams(*polygonRootChainRpc, *polygonRootChainContractAddress); err != nil {
return err
}
if err := supervisor.Run(ctx, "polygonwatch", polygonWatcher.Run); err != nil {
return err
}
}
if shouldStart(avalancheRPC) {
logger.Info("Starting Avalanche watcher")
readiness.RegisterComponent(common.ReadinessAvalancheSyncing)
chainObsvReqC[vaa.ChainIDAvalanche] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "avalanchewatch",
evm.NewEthWatcher(*avalancheRPC, avalancheContractAddr, "avalanche", common.ReadinessAvalancheSyncing, vaa.ChainIDAvalanche, lockC, nil, chainObsvReqC[vaa.ChainIDAvalanche], *unsafeDevMode).Run); err != nil {
return err
}
}
if shouldStart(oasisRPC) {
logger.Info("Starting Oasis watcher")
chainObsvReqC[vaa.ChainIDOasis] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "oasiswatch",
evm.NewEthWatcher(*oasisRPC, oasisContractAddr, "oasis", common.ReadinessOasisSyncing, vaa.ChainIDOasis, lockC, nil, chainObsvReqC[vaa.ChainIDOasis], *unsafeDevMode).Run); err != nil {
return err
}
}
if shouldStart(auroraRPC) {
logger.Info("Starting Aurora watcher")
readiness.RegisterComponent(common.ReadinessAuroraSyncing)
chainObsvReqC[vaa.ChainIDAurora] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "aurorawatch",
evm.NewEthWatcher(*auroraRPC, auroraContractAddr, "aurora", common.ReadinessAuroraSyncing, vaa.ChainIDAurora, lockC, nil, chainObsvReqC[vaa.ChainIDAurora], *unsafeDevMode).Run); err != nil {
return err
}
}
if shouldStart(fantomRPC) {
logger.Info("Starting Fantom watcher")
readiness.RegisterComponent(common.ReadinessFantomSyncing)
chainObsvReqC[vaa.ChainIDFantom] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "fantomwatch",
evm.NewEthWatcher(*fantomRPC, fantomContractAddr, "fantom", common.ReadinessFantomSyncing, vaa.ChainIDFantom, lockC, nil, chainObsvReqC[vaa.ChainIDFantom], *unsafeDevMode).Run); err != nil {
return err
}
}
if shouldStart(karuraRPC) {
logger.Info("Starting Karura watcher")
readiness.RegisterComponent(common.ReadinessKaruraSyncing)
chainObsvReqC[vaa.ChainIDKarura] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "karurawatch",
evm.NewEthWatcher(*karuraRPC, karuraContractAddr, "karura", common.ReadinessKaruraSyncing, vaa.ChainIDKarura, lockC, nil, chainObsvReqC[vaa.ChainIDKarura], *unsafeDevMode).Run); err != nil {
return err
}
}
if shouldStart(acalaRPC) {
logger.Info("Starting Acala watcher")
readiness.RegisterComponent(common.ReadinessAcalaSyncing)
chainObsvReqC[vaa.ChainIDAcala] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "acalawatch",
evm.NewEthWatcher(*acalaRPC, acalaContractAddr, "acala", common.ReadinessAcalaSyncing, vaa.ChainIDAcala, lockC, nil, chainObsvReqC[vaa.ChainIDAcala], *unsafeDevMode).Run); err != nil {
return err
}
}
if shouldStart(klaytnRPC) {
logger.Info("Starting Klaytn watcher")
readiness.RegisterComponent(common.ReadinessKlaytnSyncing)
chainObsvReqC[vaa.ChainIDKlaytn] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "klaytnwatch",
evm.NewEthWatcher(*klaytnRPC, klaytnContractAddr, "klaytn", common.ReadinessKlaytnSyncing, vaa.ChainIDKlaytn, lockC, nil, chainObsvReqC[vaa.ChainIDKlaytn], *unsafeDevMode).Run); err != nil {
return err
}
}
if shouldStart(celoRPC) {
logger.Info("Starting Celo watcher")
readiness.RegisterComponent(common.ReadinessCeloSyncing)
chainObsvReqC[vaa.ChainIDCelo] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "celowatch",
evm.NewEthWatcher(*celoRPC, celoContractAddr, "celo", common.ReadinessCeloSyncing, vaa.ChainIDCelo, lockC, nil, chainObsvReqC[vaa.ChainIDCelo], *unsafeDevMode).Run); err != nil {
return err
}
}
if shouldStart(moonbeamRPC) {
logger.Info("Starting Moonbeam watcher")
readiness.RegisterComponent(common.ReadinessMoonbeamSyncing)
chainObsvReqC[vaa.ChainIDMoonbeam] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "moonbeamwatch",
evm.NewEthWatcher(*moonbeamRPC, moonbeamContractAddr, "moonbeam", common.ReadinessMoonbeamSyncing, vaa.ChainIDMoonbeam, lockC, nil, chainObsvReqC[vaa.ChainIDMoonbeam], *unsafeDevMode).Run); err != nil {
return err
}
}
if shouldStart(arbitrumRPC) {
if ethWatcher == nil {
log.Fatalf("if arbitrum is enabled then ethereum must also be enabled.")
}
logger.Info("Starting Arbitrum watcher")
readiness.RegisterComponent(common.ReadinessArbitrumSyncing)
chainObsvReqC[vaa.ChainIDArbitrum] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
arbitrumWatcher := evm.NewEthWatcher(*arbitrumRPC, arbitrumContractAddr, "arbitrum", common.ReadinessArbitrumSyncing, vaa.ChainIDArbitrum, lockC, nil, chainObsvReqC[vaa.ChainIDArbitrum], *unsafeDevMode)
arbitrumWatcher.SetL1Finalizer(ethWatcher)
if err := supervisor.Run(ctx, "arbitrumwatch", arbitrumWatcher.Run); err != nil {
return err
}
}
if shouldStart(optimismRPC) {
if ethWatcher == nil {
log.Fatalf("if optimism is enabled then ethereum must also be enabled.")
}
if !*unsafeDevMode {
if *optimismCtcRpc == "" || *optimismCtcContractAddress == "" {
log.Fatalf("--optimismCtcRpc and --optimismCtcContractAddress both need to be set.")
}
}
logger.Info("Starting Optimism watcher")
readiness.RegisterComponent(common.ReadinessOptimismSyncing)
chainObsvReqC[vaa.ChainIDOptimism] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
optimismWatcher := evm.NewEthWatcher(*optimismRPC, optimismContractAddr, "optimism", common.ReadinessOptimismSyncing, vaa.ChainIDOptimism, lockC, nil, chainObsvReqC[vaa.ChainIDOptimism], *unsafeDevMode)
optimismWatcher.SetL1Finalizer(ethWatcher)
if err := optimismWatcher.SetRootChainParams(*optimismCtcRpc, *optimismCtcContractAddress); err != nil {
return err
}
if err := supervisor.Run(ctx, "optimismwatch", optimismWatcher.Run); err != nil {
return err
}
}
if shouldStart(terraWS) {
logger.Info("Starting Terra watcher")
readiness.RegisterComponent(common.ReadinessTerraSyncing)
chainObsvReqC[vaa.ChainIDTerra] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "terrawatch",
cosmwasm.NewWatcher(*terraWS, *terraLCD, *terraContract, lockC, chainObsvReqC[vaa.ChainIDTerra], common.ReadinessTerraSyncing, vaa.ChainIDTerra).Run); err != nil {
return err
}
}
if shouldStart(terra2WS) {
logger.Info("Starting Terra 2 watcher")
readiness.RegisterComponent(common.ReadinessTerra2Syncing)
chainObsvReqC[vaa.ChainIDTerra2] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "terra2watch",
cosmwasm.NewWatcher(*terra2WS, *terra2LCD, *terra2Contract, lockC, chainObsvReqC[vaa.ChainIDTerra2], common.ReadinessTerra2Syncing, vaa.ChainIDTerra2).Run); err != nil {
return err
}
}
if shouldStart(xplaWS) {
logger.Info("Starting XPLA watcher")
readiness.RegisterComponent(common.ReadinessXplaSyncing)
chainObsvReqC[vaa.ChainIDXpla] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "xplawatch",
cosmwasm.NewWatcher(*xplaWS, *xplaLCD, *xplaContract, lockC, chainObsvReqC[vaa.ChainIDXpla], common.ReadinessXplaSyncing, vaa.ChainIDXpla).Run); err != nil {
return err
}
}
if shouldStart(algorandIndexerRPC) {
logger.Info("Starting Algorand watcher")
readiness.RegisterComponent(common.ReadinessAlgorandSyncing)
chainObsvReqC[vaa.ChainIDAlgorand] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "algorandwatch",
common.WrapWithScissors(algorand.NewWatcher(*algorandIndexerRPC, *algorandIndexerToken, *algorandAlgodRPC, *algorandAlgodToken, *algorandAppID, lockC, chainObsvReqC[vaa.ChainIDAlgorand]).Run, "algorandwatch")); err != nil {
return err
}
}
if shouldStart(nearRPC) {
logger.Info("Starting Near watcher")
readiness.RegisterComponent(common.ReadinessNearSyncing)
chainObsvReqC[vaa.ChainIDNear] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "nearwatch",
common.WrapWithScissors(near.NewWatcher(*nearRPC, *nearContract, lockC, chainObsvReqC[vaa.ChainIDNear], !(*unsafeDevMode || *testnetMode)).Run, "nearwatch")); err != nil {
return err
}
}
// Start Wormchain watcher only if configured
if shouldStart(wormchainWS) {
logger.Info("Starting Wormchain watcher")
readiness.RegisterComponent(common.ReadinessWormchainSyncing)
chainObsvReqC[vaa.ChainIDWormchain] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "wormchainwatch",
wormchain.NewWatcher(*wormchainWS, *wormchainLCD, lockC, setC, chainObsvReqC[vaa.ChainIDWormchain]).Run); err != nil {
return err
}
}
if shouldStart(aptosRPC) {
logger.Info("Starting Aptos watcher")
readiness.RegisterComponent(common.ReadinessAptosSyncing)
chainObsvReqC[vaa.ChainIDAptos] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "aptoswatch",
aptos.NewWatcher(*aptosRPC, *aptosAccount, *aptosHandle, lockC, chainObsvReqC[vaa.ChainIDAptos]).Run); err != nil {
return err
}
}
if shouldStart(suiRPC) {
logger.Info("Starting Sui watcher")
readiness.RegisterComponent(common.ReadinessSuiSyncing)
chainObsvReqC[vaa.ChainIDSui] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "suiwatch",
sui.NewWatcher(*suiRPC, *suiWS, *suiAccount, *suiPackage, *unsafeDevMode, lockC, chainObsvReqC[vaa.ChainIDSui]).Run); err != nil {
return err
}
}
var solanaFinalizedWatcher *solana.SolanaWatcher
if shouldStart(solanaRPC) {
logger.Info("Starting Solana watcher")
readiness.RegisterComponent(common.ReadinessSolanaSyncing)
chainObsvReqC[vaa.ChainIDSolana] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "solwatch-confirmed",
solana.NewSolanaWatcher(*solanaRPC, solAddress, lockC, nil, rpc.CommitmentConfirmed, common.ReadinessSolanaSyncing, vaa.ChainIDSolana).Run); err != nil {
return err
}
solanaFinalizedWatcher = solana.NewSolanaWatcher(*solanaRPC, solAddress, lockC, chainObsvReqC[vaa.ChainIDSolana], rpc.CommitmentFinalized, common.ReadinessSolanaSyncing, vaa.ChainIDSolana)
if err := supervisor.Run(ctx, "solwatch-finalized", solanaFinalizedWatcher.Run); err != nil {
return err
}
}
if shouldStart(pythnetRPC) {
logger.Info("Starting Pythnet watcher")
readiness.RegisterComponent(common.ReadinessPythNetSyncing)
chainObsvReqC[vaa.ChainIDPythNet] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "pythwatch-confirmed",
solana.NewSolanaWatcher(*pythnetRPC, pythnetAddress, lockC, nil, rpc.CommitmentConfirmed, common.ReadinessPythNetSyncing, vaa.ChainIDPythNet).Run); err != nil {
return err
}
if err := supervisor.Run(ctx, "pythwatch-finalized",
solana.NewSolanaWatcher(*pythnetRPC, pythnetAddress, lockC, chainObsvReqC[vaa.ChainIDPythNet], rpc.CommitmentFinalized, common.ReadinessPythNetSyncing, vaa.ChainIDPythNet).Run); err != nil {
return err
}
}
if shouldStart(injectiveWS) {
logger.Info("Starting Injective watcher")
readiness.RegisterComponent(common.ReadinessInjectiveSyncing)
chainObsvReqC[vaa.ChainIDInjective] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
if err := supervisor.Run(ctx, "injectivewatch",
cosmwasm.NewWatcher(*injectiveWS, *injectiveLCD, *injectiveContract, lockC, chainObsvReqC[vaa.ChainIDInjective], common.ReadinessInjectiveSyncing, vaa.ChainIDInjective).Run); err != nil {
return err
}
}
if *testnetMode {
if shouldStart(neonRPC) {
if solanaFinalizedWatcher == nil {
log.Fatalf("if neon is enabled then solana must also be enabled.")
}
logger.Info("Starting Neon watcher")
readiness.RegisterComponent(common.ReadinessNeonSyncing)
chainObsvReqC[vaa.ChainIDNeon] = make(chan *gossipv1.ObservationRequest, observationRequestBufferSize)
neonWatcher := evm.NewEthWatcher(*neonRPC, neonContractAddr, "neon", common.ReadinessNeonSyncing, vaa.ChainIDNeon, lockC, nil, chainObsvReqC[vaa.ChainIDNeon], *unsafeDevMode)
neonWatcher.SetL1Finalizer(solanaFinalizedWatcher)
if err := supervisor.Run(ctx, "neonwatch", neonWatcher.Run); err != nil {
return err
}
}
}
go handleReobservationRequests(rootCtx, clock.New(), logger, obsvReqC, chainObsvReqC)
if gov != nil {
err := gov.Run(ctx)
if err != nil {
log.Fatal("failed to create chain governor", zap.Error(err))
}
}
p := processor.NewProcessor(ctx,
db,
lockC,
setC,
sendC,
obsvC,
obsvReqSendC,
injectC,
signedInC,
gk,
gst,
*unsafeDevMode,
*devNumGuardians,
*ethRPC,
*wormchainLCD,
attestationEvents,
notifier,
gov,
)
if err := supervisor.Run(ctx, "processor", p.Run); err != nil {
return err
}
if err := supervisor.Run(ctx, "admin", adminService); err != nil {
return err
}
if shouldStart(publicGRPCSocketPath) {
// local public grpc service socket
publicrpcUnixService, publicrpcServer, err := publicrpcUnixServiceRunnable(logger, *publicGRPCSocketPath, db, gst, gov)
if err != nil {
logger.Fatal("failed to create publicrpc service socket", zap.Error(err))
}
if err := supervisor.Run(ctx, "publicrpcsocket", publicrpcUnixService); err != nil {
return err
}
if shouldStart(publicRPC) {
publicrpcService, err := publicrpcTcpServiceRunnable(logger, *publicRPC, db, gst, gov)
if err != nil {
log.Fatal("failed to create publicrpc tcp service", zap.Error(err))
}
if err := supervisor.Run(ctx, "publicrpc", publicrpcService); err != nil {
return err
}
}
if shouldStart(publicWeb) {
publicwebService, err := publicwebServiceRunnable(logger, *publicWeb, *publicGRPCSocketPath, publicrpcServer,
*tlsHostname, *tlsProdEnv, path.Join(*dataDir, "autocert"))
if err != nil {
log.Fatal("failed to create publicrpc web service", zap.Error(err))
}
if err := supervisor.Run(ctx, "publicweb", publicwebService); err != nil {
return err
}
}
}
if *bigTablePersistenceEnabled {
bigTableConnection := &reporter.BigTableConnectionConfig{
GcpProjectID: *bigTableGCPProject,
GcpInstanceName: *bigTableInstanceName,
TableName: *bigTableTableName,
TopicName: *bigTableTopicName,
GcpKeyFilePath: *bigTableKeyPath,
}
if err := supervisor.Run(ctx, "bigtable", reporter.BigTableWriter(attestationEvents, bigTableConnection)); err != nil {
return err
}
}
logger.Info("Started internal services")
<-ctx.Done()
return nil
},
// It's safer to crash and restart the process in case we encounter a panic,
// rather than attempting to reschedule the runnable.
supervisor.WithPropagatePanic)
<-rootCtx.Done()
logger.Info("root context cancelled, exiting...")
// TODO: wait for things to shut down gracefully
}
func decryptTelemetryServiceAccount() ([]byte, error) {
// Decrypt service account credentials
key, err := base64.StdEncoding.DecodeString(*telemetryKey)
if err != nil {
return nil, fmt.Errorf("failed to decode: %w", err)
}
ciphertext, err := base64.StdEncoding.DecodeString(telemetryServiceAccount)
if err != nil {
panic(err)
}
creds, err := common.DecryptAESGCM(ciphertext, key)
if err != nil {
return nil, fmt.Errorf("failed to decrypt: %w", err)
}
return creds, err
}
func shouldStart(rpc *string) bool {
return *rpc != "" && *rpc != "none"
}
func unsafeDevModeEvmContractAddress(contractAddr string) string {
if contractAddr != "" {
return contractAddr
}
return devnet.GanacheWormholeContractAddress.Hex()
}