218 lines
7.3 KiB
Go
218 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
solana_go "github.com/gagliardetto/solana-go"
|
|
ipfslog "github.com/ipfs/go-log/v2"
|
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
|
"github.com/wormhole-foundation/wormhole-explorer/common/health"
|
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/config"
|
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/http/infrastructure"
|
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/ankr"
|
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/db"
|
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/solana"
|
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/terra"
|
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/processor"
|
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/watcher"
|
|
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"go.uber.org/ratelimit"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type exitCode int
|
|
|
|
func handleExit() {
|
|
if r := recover(); r != nil {
|
|
if e, ok := r.(exitCode); ok {
|
|
os.Exit(int(e))
|
|
}
|
|
panic(r) // not an Exit, bubble up
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
defer handleExit()
|
|
rootCtx, rootCtxCancel := context.WithCancel(context.Background())
|
|
|
|
config, err := config.New(rootCtx)
|
|
if err != nil {
|
|
log.Fatal("Error creating config", err)
|
|
}
|
|
|
|
level, err := ipfslog.LevelFromString(config.LogLevel)
|
|
if err != nil {
|
|
log.Fatal("Invalid log level", err)
|
|
}
|
|
|
|
logger := ipfslog.Logger("wormhole-explorer-contract-watcher").Desugar()
|
|
ipfslog.SetAllLoggers(level)
|
|
|
|
logger.Info("Starting wormhole-explorer-contract-watcher ...")
|
|
|
|
//setup DB connection
|
|
db, err := db.New(rootCtx, logger, config.MongoURI, config.MongoDatabase)
|
|
if err != nil {
|
|
logger.Fatal("failed to connect MongoDB", zap.Error(err))
|
|
}
|
|
|
|
// get health check functions.
|
|
healthChecks, err := newHealthChecks(rootCtx, db.Database)
|
|
if err != nil {
|
|
logger.Fatal("failed to create health checks", zap.Error(err))
|
|
}
|
|
|
|
// create repositories
|
|
repo := storage.NewRepository(db.Database, logger)
|
|
|
|
// create watchers
|
|
watchers := newWatchers(config, repo, logger)
|
|
|
|
//create processor
|
|
processor := processor.NewProcessor(watchers, logger)
|
|
processor.Start(rootCtx)
|
|
|
|
// create and start server.
|
|
server := infrastructure.NewServer(logger, config.Port, config.PprofEnabled, healthChecks...)
|
|
server.Start()
|
|
|
|
logger.Info("Started wormhole-explorer-contract-watcher")
|
|
|
|
// Waiting for signal
|
|
sigterm := make(chan os.Signal, 1)
|
|
signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)
|
|
select {
|
|
case <-rootCtx.Done():
|
|
logger.Warn("Terminating with root context cancelled.")
|
|
case signal := <-sigterm:
|
|
logger.Info("Terminating with signal.", zap.String("signal", signal.String()))
|
|
}
|
|
|
|
logger.Info("root context cancelled, exiting...")
|
|
rootCtxCancel()
|
|
|
|
logger.Info("Closing processor ...")
|
|
processor.Close()
|
|
logger.Info("Closing database connections ...")
|
|
db.Close()
|
|
logger.Info("Closing Http server ...")
|
|
server.Stop()
|
|
logger.Info("Finished wormhole-explorer-contract-watcher")
|
|
}
|
|
|
|
func newHealthChecks(ctx context.Context, db *mongo.Database) ([]health.Check, error) {
|
|
return []health.Check{health.Mongo(db)}, nil
|
|
}
|
|
|
|
type watcherBlockchain struct {
|
|
chainID vaa.ChainID
|
|
name string
|
|
address string
|
|
sizeBlocks uint8
|
|
waitSeconds uint16
|
|
initialBlock int64
|
|
}
|
|
|
|
type watchersConfig struct {
|
|
evms []watcherBlockchain
|
|
solana *watcherBlockchain
|
|
terra *watcherBlockchain
|
|
rateLimit rateLimitConfig
|
|
}
|
|
|
|
type rateLimitConfig struct {
|
|
evm int
|
|
solana int
|
|
terra int
|
|
}
|
|
|
|
func newWatchers(config *config.Configuration, repo *storage.Repository, logger *zap.Logger) []watcher.ContractWatcher {
|
|
var watchers *watchersConfig
|
|
switch config.P2pNetwork {
|
|
case domain.P2pMainNet:
|
|
watchers = newEVMWatchersForMainnet()
|
|
case domain.P2pTestNet:
|
|
watchers = newEVMWatchersForTestnet()
|
|
default:
|
|
watchers = &watchersConfig{}
|
|
}
|
|
|
|
// add evm watchers
|
|
result := make([]watcher.ContractWatcher, 0)
|
|
|
|
// add evm watchers
|
|
evmLimiter := ratelimit.New(watchers.rateLimit.evm, ratelimit.Per(time.Second))
|
|
ankrClient := ankr.NewAnkrSDK(config.AnkrUrl, evmLimiter)
|
|
for _, w := range watchers.evms {
|
|
params := watcher.EVMParams{ChainID: w.chainID, Blockchain: w.name, ContractAddress: w.address,
|
|
SizeBlocks: w.sizeBlocks, WaitSeconds: w.waitSeconds, InitialBlock: w.initialBlock}
|
|
result = append(result, watcher.NewEVMWatcher(ankrClient, repo, params, logger))
|
|
}
|
|
|
|
// add solana watcher
|
|
if watchers.solana != nil {
|
|
contractAddress, err := solana_go.PublicKeyFromBase58(watchers.solana.address)
|
|
if err != nil {
|
|
logger.Fatal("failed to parse solana contract address", zap.Error(err))
|
|
}
|
|
solanaLimiter := ratelimit.New(watchers.rateLimit.solana, ratelimit.Per(time.Second))
|
|
solanaClient := solana.NewSolanaSDK(config.SolanaUrl, solanaLimiter)
|
|
params := watcher.SolanaParams{Blockchain: watchers.solana.name, ContractAddress: contractAddress,
|
|
SizeBlocks: watchers.solana.sizeBlocks, WaitSeconds: watchers.solana.waitSeconds, InitialBlock: watchers.solana.initialBlock}
|
|
result = append(result, watcher.NewSolanaWatcher(solanaClient, repo, params, logger))
|
|
}
|
|
|
|
// add terra watcher
|
|
if watchers.terra != nil {
|
|
terraLimiter := ratelimit.New(watchers.rateLimit.terra, ratelimit.Per(time.Second))
|
|
terraClient := terra.NewTerraSDK(config.TerraUrl, terraLimiter)
|
|
params := watcher.TerraParams{ChainID: watchers.terra.chainID, Blockchain: watchers.terra.name,
|
|
ContractAddress: watchers.terra.address, WaitSeconds: watchers.terra.waitSeconds, InitialBlock: watchers.terra.initialBlock}
|
|
result = append(result, watcher.NewTerraWatcher(terraClient, params, repo, logger))
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func newEVMWatchersForMainnet() *watchersConfig {
|
|
return &watchersConfig{
|
|
evms: []watcherBlockchain{
|
|
{vaa.ChainIDEthereum, "eth", "0x3ee18B2214AFF97000D974cf647E7C347E8fa585", 100, 10, 16820790},
|
|
{vaa.ChainIDPolygon, "polygon", "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE", 100, 10, 40307020},
|
|
{vaa.ChainIDBSC, "bsc", "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7", 100, 10, 26436320},
|
|
{vaa.ChainIDFantom, "fantom", "0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2", 100, 10, 57525624},
|
|
},
|
|
solana: &watcherBlockchain{vaa.ChainIDSolana, "solana", "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb", 100, 10, 183675278},
|
|
terra: &watcherBlockchain{vaa.ChainIDTerra, "terra", "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf", 0, 10, 12005338},
|
|
rateLimit: rateLimitConfig{
|
|
evm: 1000,
|
|
solana: 3,
|
|
terra: 10,
|
|
},
|
|
}
|
|
}
|
|
|
|
func newEVMWatchersForTestnet() *watchersConfig {
|
|
return &watchersConfig{
|
|
evms: []watcherBlockchain{
|
|
{vaa.ChainIDEthereum, "eth_goerli", "0xF890982f9310df57d00f659cf4fd87e65adEd8d7", 100, 10, 8660321},
|
|
{vaa.ChainIDPolygon, "polygon_mumbai", "0x377D55a7928c046E18eEbb61977e714d2a76472a", 100, 10, 33151522},
|
|
{vaa.ChainIDBSC, "bsc_testnet_chapel", "0x9dcF9D205C9De35334D646BeE44b2D2859712A09", 100, 10, 28071327},
|
|
{vaa.ChainIDFantom, "fantom_testnet", "0x599CEa2204B4FaECd584Ab1F2b6aCA137a0afbE8", 100, 10, 14524466},
|
|
},
|
|
solana: &watcherBlockchain{vaa.ChainIDSolana, "solana", "DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe", 10, 10, 16820790},
|
|
rateLimit: rateLimitConfig{
|
|
evm: 10,
|
|
solana: 2,
|
|
terra: 5,
|
|
},
|
|
}
|
|
}
|