github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/powchain/service.go (about) 1 // Package powchain defines a runtime service which is tasked with 2 // communicating with an eth1 endpoint, processing logs from a deposit 3 // contract, and the latest eth1 data headers for usage in the beacon node. 4 package powchain 5 6 import ( 7 "context" 8 "fmt" 9 "math/big" 10 "reflect" 11 "runtime/debug" 12 "sort" 13 "sync" 14 "time" 15 16 "github.com/ethereum/go-ethereum" 17 "github.com/ethereum/go-ethereum/accounts/abi/bind" 18 "github.com/ethereum/go-ethereum/common" 19 "github.com/ethereum/go-ethereum/common/hexutil" 20 gethTypes "github.com/ethereum/go-ethereum/core/types" 21 "github.com/ethereum/go-ethereum/ethclient" 22 gethRPC "github.com/ethereum/go-ethereum/rpc" 23 "github.com/pkg/errors" 24 "github.com/prometheus/client_golang/prometheus" 25 "github.com/prometheus/client_golang/prometheus/promauto" 26 "github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache" 27 statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state" 28 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 29 "github.com/prysmaticlabs/prysm/beacon-chain/core/state" 30 "github.com/prysmaticlabs/prysm/beacon-chain/db" 31 "github.com/prysmaticlabs/prysm/beacon-chain/powchain/types" 32 iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface" 33 "github.com/prysmaticlabs/prysm/beacon-chain/state/stategen" 34 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1" 35 contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract" 36 protodb "github.com/prysmaticlabs/prysm/proto/beacon/db" 37 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 38 "github.com/prysmaticlabs/prysm/shared/bytesutil" 39 "github.com/prysmaticlabs/prysm/shared/clientstats" 40 "github.com/prysmaticlabs/prysm/shared/httputils" 41 "github.com/prysmaticlabs/prysm/shared/httputils/authorizationmethod" 42 "github.com/prysmaticlabs/prysm/shared/logutil" 43 "github.com/prysmaticlabs/prysm/shared/params" 44 "github.com/prysmaticlabs/prysm/shared/timeutils" 45 "github.com/prysmaticlabs/prysm/shared/trieutil" 46 "github.com/sirupsen/logrus" 47 ) 48 49 var ( 50 validDepositsCount = promauto.NewCounter(prometheus.CounterOpts{ 51 Name: "powchain_valid_deposits_received", 52 Help: "The number of valid deposits received in the deposit contract", 53 }) 54 blockNumberGauge = promauto.NewGauge(prometheus.GaugeOpts{ 55 Name: "powchain_block_number", 56 Help: "The current block number in the proof-of-work chain", 57 }) 58 missedDepositLogsCount = promauto.NewCounter(prometheus.CounterOpts{ 59 Name: "powchain_missed_deposit_logs", 60 Help: "The number of times a missed deposit log is detected", 61 }) 62 ) 63 64 var ( 65 // time to wait before trying to reconnect with the eth1 node. 66 backOffPeriod = 15 * time.Second 67 // amount of times before we log the status of the eth1 dial attempt. 68 logThreshold = 8 69 // period to log chainstart related information 70 logPeriod = 1 * time.Minute 71 // threshold of how old we will accept an eth1 node's head to be. 72 eth1Threshold = 20 * time.Minute 73 // error when eth1 node is not synced. 74 errNotSynced = errors.New("eth1 node is still syncing") 75 // error when eth1 node is too far behind. 76 errFarBehind = errors.Errorf("eth1 head is more than %s behind from current wall clock time", eth1Threshold.String()) 77 ) 78 79 // ChainStartFetcher retrieves information pertaining to the chain start event 80 // of the beacon chain for usage across various services. 81 type ChainStartFetcher interface { 82 ChainStartDeposits() []*ethpb.Deposit 83 ChainStartEth1Data() *ethpb.Eth1Data 84 PreGenesisState() iface.BeaconState 85 ClearPreGenesisData() 86 } 87 88 // ChainInfoFetcher retrieves information about eth1 metadata at the Ethereum consensus genesis time. 89 type ChainInfoFetcher interface { 90 Eth2GenesisPowchainInfo() (uint64, *big.Int) 91 IsConnectedToETH1() bool 92 } 93 94 // POWBlockFetcher defines a struct that can retrieve mainchain blocks. 95 type POWBlockFetcher interface { 96 BlockTimeByHeight(ctx context.Context, height *big.Int) (uint64, error) 97 BlockByTimestamp(ctx context.Context, time uint64) (*types.HeaderInfo, error) 98 BlockHashByHeight(ctx context.Context, height *big.Int) (common.Hash, error) 99 BlockExists(ctx context.Context, hash common.Hash) (bool, *big.Int, error) 100 BlockExistsWithCache(ctx context.Context, hash common.Hash) (bool, *big.Int, error) 101 } 102 103 // Chain defines a standard interface for the powchain service in Prysm. 104 type Chain interface { 105 ChainStartFetcher 106 ChainInfoFetcher 107 POWBlockFetcher 108 } 109 110 // RPCDataFetcher defines a subset of methods conformed to by ETH1.0 RPC clients for 111 // fetching eth1 data from the clients. 112 type RPCDataFetcher interface { 113 HeaderByNumber(ctx context.Context, number *big.Int) (*gethTypes.Header, error) 114 HeaderByHash(ctx context.Context, hash common.Hash) (*gethTypes.Header, error) 115 SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) 116 } 117 118 // RPCClient defines the rpc methods required to interact with the eth1 node. 119 type RPCClient interface { 120 BatchCall(b []gethRPC.BatchElem) error 121 } 122 123 // Service fetches important information about the canonical 124 // Ethereum ETH1.0 chain via a web3 endpoint using an ethclient. The Random 125 // Beacon Chain requires synchronization with the ETH1.0 chain's current 126 // blockhash, block number, and access to logs within the 127 // Validator Registration Contract on the ETH1.0 chain to kick off the beacon 128 // chain's validator registration process. 129 type Service struct { 130 connectedETH1 bool 131 isRunning bool 132 processingLock sync.RWMutex 133 cfg *Web3ServiceConfig 134 ctx context.Context 135 cancel context.CancelFunc 136 headTicker *time.Ticker 137 httpEndpoints []httputils.Endpoint 138 currHttpEndpoint httputils.Endpoint 139 httpLogger bind.ContractFilterer 140 eth1DataFetcher RPCDataFetcher 141 rpcClient RPCClient 142 headerCache *headerCache // cache to store block hash/block height. 143 latestEth1Data *protodb.LatestETH1Data 144 depositContractCaller *contracts.DepositContractCaller 145 depositTrie *trieutil.SparseMerkleTrie 146 chainStartData *protodb.ChainStartData 147 lastReceivedMerkleIndex int64 // Keeps track of the last received index to prevent log spam. 148 runError error 149 preGenesisState iface.BeaconState 150 bsUpdater BeaconNodeStatsUpdater 151 } 152 153 // Web3ServiceConfig defines a config struct for web3 service to use through its life cycle. 154 type Web3ServiceConfig struct { 155 HttpEndpoints []string 156 DepositContract common.Address 157 BeaconDB db.HeadAccessDatabase 158 DepositCache *depositcache.DepositCache 159 StateNotifier statefeed.Notifier 160 StateGen *stategen.State 161 Eth1HeaderReqLimit uint64 162 BeaconNodeStatsUpdater BeaconNodeStatsUpdater 163 } 164 165 // NewService sets up a new instance with an ethclient when 166 // given a web3 endpoint as a string in the config. 167 func NewService(ctx context.Context, config *Web3ServiceConfig) (*Service, error) { 168 ctx, cancel := context.WithCancel(ctx) 169 _ = cancel // govet fix for lost cancel. Cancel is handled in service.Stop() 170 depositTrie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth) 171 if err != nil { 172 cancel() 173 return nil, errors.Wrap(err, "could not setup deposit trie") 174 } 175 genState, err := state.EmptyGenesisState() 176 if err != nil { 177 return nil, errors.Wrap(err, "could not setup genesis state") 178 } 179 180 if config.Eth1HeaderReqLimit == 0 { 181 config.Eth1HeaderReqLimit = defaultEth1HeaderReqLimit 182 } 183 184 stringEndpoints := dedupEndpoints(config.HttpEndpoints) 185 endpoints := make([]httputils.Endpoint, len(stringEndpoints)) 186 for i, e := range stringEndpoints { 187 endpoints[i] = HttpEndpoint(e) 188 } 189 190 // Select first http endpoint in the provided list. 191 var currEndpoint httputils.Endpoint 192 if len(config.HttpEndpoints) > 0 { 193 currEndpoint = endpoints[0] 194 } 195 s := &Service{ 196 ctx: ctx, 197 cancel: cancel, 198 cfg: config, 199 httpEndpoints: endpoints, 200 currHttpEndpoint: currEndpoint, 201 latestEth1Data: &protodb.LatestETH1Data{ 202 BlockHeight: 0, 203 BlockTime: 0, 204 BlockHash: []byte{}, 205 LastRequestedBlock: 0, 206 }, 207 headerCache: newHeaderCache(), 208 depositTrie: depositTrie, 209 chainStartData: &protodb.ChainStartData{ 210 Eth1Data: ðpb.Eth1Data{}, 211 ChainstartDeposits: make([]*ethpb.Deposit, 0), 212 }, 213 lastReceivedMerkleIndex: -1, 214 preGenesisState: genState, 215 headTicker: time.NewTicker(time.Duration(params.BeaconConfig().SecondsPerETH1Block) * time.Second), 216 // use the nop updater by default, rely on upstream set up to pass in an appropriate impl 217 bsUpdater: config.BeaconNodeStatsUpdater, 218 } 219 220 if config.BeaconNodeStatsUpdater == nil { 221 s.bsUpdater = &NopBeaconNodeStatsUpdater{} 222 } 223 224 if err := s.ensureValidPowchainData(ctx); err != nil { 225 return nil, errors.Wrap(err, "unable to validate powchain data") 226 } 227 228 eth1Data, err := config.BeaconDB.PowchainData(ctx) 229 if err != nil { 230 return nil, errors.Wrap(err, "unable to retrieve eth1 data") 231 } 232 if err := s.initializeEth1Data(ctx, eth1Data); err != nil { 233 return nil, err 234 } 235 236 return s, nil 237 } 238 239 // Start a web3 service's main event loop. 240 func (s *Service) Start() { 241 // If the chain has not started already and we don't have access to eth1 nodes, we will not be 242 // able to generate the genesis state. 243 if !s.chainStartData.Chainstarted && s.currHttpEndpoint.Url == "" { 244 // check for genesis state before shutting down the node, 245 // if a genesis state exists, we can continue on. 246 genState, err := s.cfg.BeaconDB.GenesisState(s.ctx) 247 if err != nil { 248 log.Fatal(err) 249 } 250 if genState == nil || genState.IsNil() { 251 log.Fatal("cannot create genesis state: no eth1 http endpoint defined") 252 } 253 } 254 255 // Exit early if eth1 endpoint is not set. 256 if s.currHttpEndpoint.Url == "" { 257 return 258 } 259 go func() { 260 s.isRunning = true 261 s.waitForConnection() 262 if s.ctx.Err() != nil { 263 log.Info("Context closed, exiting pow goroutine") 264 return 265 } 266 s.run(s.ctx.Done()) 267 }() 268 } 269 270 // Stop the web3 service's main event loop and associated goroutines. 271 func (s *Service) Stop() error { 272 if s.cancel != nil { 273 defer s.cancel() 274 } 275 s.closeClients() 276 return nil 277 } 278 279 // ChainStartDeposits returns a slice of validator deposit data processed 280 // by the deposit contract and cached in the powchain service. 281 func (s *Service) ChainStartDeposits() []*ethpb.Deposit { 282 return s.chainStartData.ChainstartDeposits 283 } 284 285 // ClearPreGenesisData clears out the stored chainstart deposits and beacon state. 286 func (s *Service) ClearPreGenesisData() { 287 s.chainStartData.ChainstartDeposits = []*ethpb.Deposit{} 288 s.preGenesisState = &v1.BeaconState{} 289 } 290 291 // ChainStartEth1Data returns the eth1 data at chainstart. 292 func (s *Service) ChainStartEth1Data() *ethpb.Eth1Data { 293 return s.chainStartData.Eth1Data 294 } 295 296 // PreGenesisState returns a state that contains 297 // pre-chainstart deposits. 298 func (s *Service) PreGenesisState() iface.BeaconState { 299 return s.preGenesisState 300 } 301 302 // Status is service health checks. Return nil or error. 303 func (s *Service) Status() error { 304 // Service don't start 305 if !s.isRunning { 306 return nil 307 } 308 // get error from run function 309 if s.runError != nil { 310 return s.runError 311 } 312 return nil 313 } 314 315 func (s *Service) updateBeaconNodeStats() { 316 bs := clientstats.BeaconNodeStats{} 317 if len(s.httpEndpoints) > 1 { 318 bs.SyncEth1FallbackConfigured = true 319 } 320 if s.IsConnectedToETH1() { 321 if s.primaryConnected() { 322 bs.SyncEth1Connected = true 323 } else { 324 bs.SyncEth1FallbackConnected = true 325 } 326 } 327 s.bsUpdater.Update(bs) 328 } 329 330 func (s *Service) updateCurrHttpEndpoint(endpoint httputils.Endpoint) { 331 s.currHttpEndpoint = endpoint 332 s.updateBeaconNodeStats() 333 } 334 335 func (s *Service) updateConnectedETH1(state bool) { 336 s.connectedETH1 = state 337 s.updateBeaconNodeStats() 338 } 339 340 // IsConnectedToETH1 checks if the beacon node is connected to a ETH1 Node. 341 func (s *Service) IsConnectedToETH1() bool { 342 return s.connectedETH1 343 } 344 345 // DepositRoot returns the Merkle root of the latest deposit trie 346 // from the ETH1.0 deposit contract. 347 func (s *Service) DepositRoot() [32]byte { 348 return s.depositTrie.Root() 349 } 350 351 // DepositTrie returns the sparse Merkle trie used for storing 352 // deposits from the ETH1.0 deposit contract. 353 func (s *Service) DepositTrie() *trieutil.SparseMerkleTrie { 354 return s.depositTrie 355 } 356 357 // LatestBlockHeight in the ETH1.0 chain. 358 func (s *Service) LatestBlockHeight() *big.Int { 359 return big.NewInt(int64(s.latestEth1Data.BlockHeight)) 360 } 361 362 // LatestBlockHash in the ETH1.0 chain. 363 func (s *Service) LatestBlockHash() common.Hash { 364 return bytesutil.ToBytes32(s.latestEth1Data.BlockHash) 365 } 366 367 // AreAllDepositsProcessed determines if all the logs from the deposit contract 368 // are processed. 369 func (s *Service) AreAllDepositsProcessed() (bool, error) { 370 s.processingLock.RLock() 371 defer s.processingLock.RUnlock() 372 countByte, err := s.depositContractCaller.GetDepositCount(&bind.CallOpts{}) 373 if err != nil { 374 return false, errors.Wrap(err, "could not get deposit count") 375 } 376 count := bytesutil.FromBytes8(countByte) 377 deposits := s.cfg.DepositCache.AllDeposits(s.ctx, nil) 378 if count != uint64(len(deposits)) { 379 return false, nil 380 } 381 return true, nil 382 } 383 384 // refers to the latest eth1 block which follows the condition: eth1_timestamp + 385 // SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= current_unix_time 386 func (s *Service) followBlockHeight(ctx context.Context) (uint64, error) { 387 latestValidBlock := uint64(0) 388 if s.latestEth1Data.BlockHeight > params.BeaconConfig().Eth1FollowDistance { 389 latestValidBlock = s.latestEth1Data.BlockHeight - params.BeaconConfig().Eth1FollowDistance 390 } 391 return latestValidBlock, nil 392 } 393 394 func (s *Service) connectToPowChain() error { 395 httpClient, rpcClient, err := s.dialETH1Nodes(s.currHttpEndpoint) 396 if err != nil { 397 return errors.Wrap(err, "could not dial eth1 nodes") 398 } 399 400 depositContractCaller, err := contracts.NewDepositContractCaller(s.cfg.DepositContract, httpClient) 401 if err != nil { 402 return errors.Wrap(err, "could not create deposit contract caller") 403 } 404 405 if httpClient == nil || rpcClient == nil || depositContractCaller == nil { 406 return errors.New("eth1 client is nil") 407 } 408 409 s.initializeConnection(httpClient, rpcClient, depositContractCaller) 410 return nil 411 } 412 413 func (s *Service) dialETH1Nodes(endpoint httputils.Endpoint) (*ethclient.Client, *gethRPC.Client, error) { 414 httpRPCClient, err := gethRPC.Dial(endpoint.Url) 415 if err != nil { 416 return nil, nil, err 417 } 418 if endpoint.Auth.Method != authorizationmethod.None { 419 header, err := endpoint.Auth.ToHeaderValue() 420 if err != nil { 421 return nil, nil, err 422 } 423 httpRPCClient.SetHeader("Authorization", header) 424 } 425 httpClient := ethclient.NewClient(httpRPCClient) 426 // Add a method to clean-up and close clients in the event 427 // of any connection failure. 428 closeClients := func() { 429 httpRPCClient.Close() 430 httpClient.Close() 431 } 432 syncProg, err := httpClient.SyncProgress(s.ctx) 433 if err != nil { 434 closeClients() 435 return nil, nil, err 436 } 437 if syncProg != nil { 438 closeClients() 439 return nil, nil, errors.New("eth1 node has not finished syncing yet") 440 } 441 // Make a simple call to ensure we are actually connected to a working node. 442 cID, err := httpClient.ChainID(s.ctx) 443 if err != nil { 444 closeClients() 445 return nil, nil, err 446 } 447 nID, err := httpClient.NetworkID(s.ctx) 448 if err != nil { 449 closeClients() 450 return nil, nil, err 451 } 452 if cID.Uint64() != params.BeaconConfig().DepositChainID { 453 closeClients() 454 return nil, nil, fmt.Errorf("eth1 node using incorrect chain id, %d != %d", cID.Uint64(), params.BeaconConfig().DepositChainID) 455 } 456 if nID.Uint64() != params.BeaconConfig().DepositNetworkID { 457 closeClients() 458 return nil, nil, fmt.Errorf("eth1 node using incorrect network id, %d != %d", nID.Uint64(), params.BeaconConfig().DepositNetworkID) 459 } 460 461 return httpClient, httpRPCClient, nil 462 } 463 464 func (s *Service) initializeConnection( 465 httpClient *ethclient.Client, 466 rpcClient *gethRPC.Client, 467 contractCaller *contracts.DepositContractCaller, 468 ) { 469 s.httpLogger = httpClient 470 s.eth1DataFetcher = httpClient 471 s.depositContractCaller = contractCaller 472 s.rpcClient = rpcClient 473 } 474 475 // closes down our active eth1 clients. 476 func (s *Service) closeClients() { 477 gethClient, ok := s.rpcClient.(*gethRPC.Client) 478 if ok { 479 gethClient.Close() 480 } 481 httpClient, ok := s.eth1DataFetcher.(*ethclient.Client) 482 if ok { 483 httpClient.Close() 484 } 485 } 486 487 func (s *Service) waitForConnection() { 488 errConnect := s.connectToPowChain() 489 if errConnect == nil { 490 synced, errSynced := s.isEth1NodeSynced() 491 // Resume if eth1 node is synced. 492 if synced { 493 s.updateConnectedETH1(true) 494 s.runError = nil 495 log.WithFields(logrus.Fields{ 496 "endpoint": logutil.MaskCredentialsLogging(s.currHttpEndpoint.Url), 497 }).Info("Connected to eth1 proof-of-work chain") 498 return 499 } 500 if errSynced != nil { 501 s.runError = errSynced 502 log.WithError(errSynced).Error("Could not check sync status of eth1 chain") 503 } 504 } 505 if errConnect != nil { 506 s.runError = errConnect 507 log.WithError(errConnect).Error("Could not connect to powchain endpoint") 508 } 509 // Use a custom logger to only log errors 510 // once in a while. 511 logCounter := 0 512 errorLogger := func(err error, msg string) { 513 if logCounter > logThreshold { 514 log.Errorf("%s: %v", msg, err) 515 logCounter = 0 516 } 517 logCounter++ 518 } 519 520 ticker := time.NewTicker(backOffPeriod) 521 defer ticker.Stop() 522 for { 523 select { 524 case <-ticker.C: 525 log.Debugf("Trying to dial endpoint: %s", logutil.MaskCredentialsLogging(s.currHttpEndpoint.Url)) 526 errConnect := s.connectToPowChain() 527 if errConnect != nil { 528 errorLogger(errConnect, "Could not connect to powchain endpoint") 529 s.runError = errConnect 530 s.fallbackToNextEndpoint() 531 continue 532 } 533 synced, errSynced := s.isEth1NodeSynced() 534 if errSynced != nil { 535 errorLogger(errSynced, "Could not check sync status of eth1 chain") 536 s.runError = errSynced 537 s.fallbackToNextEndpoint() 538 continue 539 } 540 if synced { 541 s.updateConnectedETH1(true) 542 s.runError = nil 543 log.WithFields(logrus.Fields{ 544 "endpoint": logutil.MaskCredentialsLogging(s.currHttpEndpoint.Url), 545 }).Info("Connected to eth1 proof-of-work chain") 546 return 547 } 548 s.runError = errNotSynced 549 log.Debug("Eth1 node is currently syncing") 550 case <-s.ctx.Done(): 551 log.Debug("Received cancelled context,closing existing powchain service") 552 return 553 } 554 } 555 } 556 557 // checks if the eth1 node is healthy and ready to serve before 558 // fetching data from it. 559 func (s *Service) isEth1NodeSynced() (bool, error) { 560 syncProg, err := s.eth1DataFetcher.SyncProgress(s.ctx) 561 if err != nil { 562 return false, err 563 } 564 if syncProg != nil { 565 return false, nil 566 } 567 head, err := s.eth1DataFetcher.HeaderByNumber(s.ctx, nil) 568 if err != nil { 569 return false, err 570 } 571 return !eth1HeadIsBehind(head.Time), nil 572 } 573 574 // Reconnect to eth1 node in case of any failure. 575 func (s *Service) retryETH1Node(err error) { 576 s.runError = err 577 s.updateConnectedETH1(false) 578 // Back off for a while before 579 // resuming dialing the eth1 node. 580 time.Sleep(backOffPeriod) 581 s.waitForConnection() 582 // Reset run error in the event of a successful connection. 583 s.runError = nil 584 } 585 586 func (s *Service) initDepositCaches(ctx context.Context, ctrs []*protodb.DepositContainer) error { 587 if len(ctrs) == 0 { 588 return nil 589 } 590 s.cfg.DepositCache.InsertDepositContainers(ctx, ctrs) 591 if !s.chainStartData.Chainstarted { 592 // do not add to pending cache 593 // if no genesis state exists. 594 validDepositsCount.Add(float64(s.preGenesisState.Eth1DepositIndex())) 595 return nil 596 } 597 genesisState, err := s.cfg.BeaconDB.GenesisState(ctx) 598 if err != nil { 599 return err 600 } 601 // Default to all deposits post-genesis deposits in 602 // the event we cannot find a finalized state. 603 currIndex := genesisState.Eth1DepositIndex() 604 chkPt, err := s.cfg.BeaconDB.FinalizedCheckpoint(ctx) 605 if err != nil { 606 return err 607 } 608 rt := bytesutil.ToBytes32(chkPt.Root) 609 if rt != [32]byte{} { 610 fState, err := s.cfg.StateGen.StateByRoot(ctx, rt) 611 if err != nil { 612 return errors.Wrap(err, "could not get finalized state") 613 } 614 if fState == nil || fState.IsNil() { 615 return errors.Errorf("finalized state with root %#x does not exist in the db", rt) 616 } 617 // Set deposit index to the one in the current archived state. 618 currIndex = fState.Eth1DepositIndex() 619 } 620 validDepositsCount.Add(float64(currIndex)) 621 // Only add pending deposits if the container slice length 622 // is more than the current index in state. 623 if uint64(len(ctrs)) > currIndex { 624 for _, c := range ctrs[currIndex:] { 625 s.cfg.DepositCache.InsertPendingDeposit(ctx, c.Deposit, c.Eth1BlockHeight, c.Index, bytesutil.ToBytes32(c.DepositRoot)) 626 } 627 } 628 return nil 629 } 630 631 // processBlockHeader adds a newly observed eth1 block to the block cache and 632 // updates the latest blockHeight, blockHash, and blockTime properties of the service. 633 func (s *Service) processBlockHeader(header *gethTypes.Header) { 634 defer safelyHandlePanic() 635 blockNumberGauge.Set(float64(header.Number.Int64())) 636 s.latestEth1Data.BlockHeight = header.Number.Uint64() 637 s.latestEth1Data.BlockHash = header.Hash().Bytes() 638 s.latestEth1Data.BlockTime = header.Time 639 log.WithFields(logrus.Fields{ 640 "blockNumber": s.latestEth1Data.BlockHeight, 641 "blockHash": hexutil.Encode(s.latestEth1Data.BlockHash), 642 }).Debug("Latest eth1 chain event") 643 } 644 645 // batchRequestHeaders requests the block range specified in the arguments. Instead of requesting 646 // each block in one call, it batches all requests into a single rpc call. 647 func (s *Service) batchRequestHeaders(startBlock, endBlock uint64) ([]*gethTypes.Header, error) { 648 if startBlock > endBlock { 649 return nil, fmt.Errorf("start block height %d cannot be > end block height %d", startBlock, endBlock) 650 } 651 requestRange := (endBlock - startBlock) + 1 652 elems := make([]gethRPC.BatchElem, 0, requestRange) 653 headers := make([]*gethTypes.Header, 0, requestRange) 654 errs := make([]error, 0, requestRange) 655 if requestRange == 0 { 656 return headers, nil 657 } 658 for i := startBlock; i <= endBlock; i++ { 659 header := &gethTypes.Header{} 660 err := error(nil) 661 elems = append(elems, gethRPC.BatchElem{ 662 Method: "eth_getBlockByNumber", 663 Args: []interface{}{hexutil.EncodeBig(big.NewInt(int64(i))), false}, 664 Result: header, 665 Error: err, 666 }) 667 headers = append(headers, header) 668 errs = append(errs, err) 669 } 670 ioErr := s.rpcClient.BatchCall(elems) 671 if ioErr != nil { 672 return nil, ioErr 673 } 674 for _, e := range errs { 675 if e != nil { 676 return nil, e 677 } 678 } 679 for _, h := range headers { 680 if h != nil { 681 if err := s.headerCache.AddHeader(h); err != nil { 682 return nil, err 683 } 684 } 685 } 686 return headers, nil 687 } 688 689 // safelyHandleHeader will recover and log any panic that occurs from the 690 // block 691 func safelyHandlePanic() { 692 if r := recover(); r != nil { 693 log.WithFields(logrus.Fields{ 694 "r": r, 695 }).Error("Panicked when handling data from ETH 1.0 Chain! Recovering...") 696 697 debug.PrintStack() 698 } 699 } 700 701 func (s *Service) handleETH1FollowDistance() { 702 defer safelyHandlePanic() 703 ctx := s.ctx 704 705 // use a 5 minutes timeout for block time, because the max mining time is 278 sec (block 7208027) 706 // (analyzed the time of the block from 2018-09-01 to 2019-02-13) 707 fiveMinutesTimeout := timeutils.Now().Add(-5 * time.Minute) 708 // check that web3 client is syncing 709 if time.Unix(int64(s.latestEth1Data.BlockTime), 0).Before(fiveMinutesTimeout) { 710 log.Warn("eth1 client is not syncing") 711 } 712 if !s.chainStartData.Chainstarted { 713 if err := s.checkBlockNumberForChainStart(ctx, big.NewInt(int64(s.latestEth1Data.LastRequestedBlock))); err != nil { 714 s.runError = err 715 log.Error(err) 716 return 717 } 718 } 719 // If the last requested block has not changed, 720 // we do not request batched logs as this means there are no new 721 // logs for the powchain service to process. Also is a potential 722 // failure condition as would mean we have not respected the protocol 723 // threshold. 724 if s.latestEth1Data.LastRequestedBlock == s.latestEth1Data.BlockHeight { 725 log.Error("Beacon node is not respecting the follow distance") 726 return 727 } 728 if err := s.requestBatchedHeadersAndLogs(ctx); err != nil { 729 s.runError = err 730 log.Error(err) 731 return 732 } 733 // Reset the Status. 734 if s.runError != nil { 735 s.runError = nil 736 } 737 } 738 739 func (s *Service) initPOWService() { 740 741 // Run in a select loop to retry in the event of any failures. 742 for { 743 select { 744 case <-s.ctx.Done(): 745 return 746 default: 747 ctx := s.ctx 748 header, err := s.eth1DataFetcher.HeaderByNumber(ctx, nil) 749 if err != nil { 750 log.Errorf("Unable to retrieve latest ETH1.0 chain header: %v", err) 751 s.retryETH1Node(err) 752 continue 753 } 754 755 s.latestEth1Data.BlockHeight = header.Number.Uint64() 756 s.latestEth1Data.BlockHash = header.Hash().Bytes() 757 s.latestEth1Data.BlockTime = header.Time 758 759 if err := s.processPastLogs(ctx); err != nil { 760 log.Errorf("Unable to process past logs %v", err) 761 s.retryETH1Node(err) 762 continue 763 } 764 // Cache eth1 headers from our voting period. 765 if err := s.cacheHeadersForEth1DataVote(ctx); err != nil { 766 log.Errorf("Unable to process past headers %v", err) 767 s.retryETH1Node(err) 768 continue 769 } 770 // Handle edge case with embedded genesis state by fetching genesis header to determine 771 // its height. 772 if s.chainStartData.Chainstarted && s.chainStartData.GenesisBlock == 0 { 773 genHeader, err := s.eth1DataFetcher.HeaderByHash(ctx, common.BytesToHash(s.chainStartData.Eth1Data.BlockHash)) 774 if err != nil { 775 log.Errorf("Unable to retrieve genesis ETH1.0 chain header: %v", err) 776 s.retryETH1Node(err) 777 continue 778 } 779 s.chainStartData.GenesisBlock = genHeader.Number.Uint64() 780 if err := s.savePowchainData(ctx); err != nil { 781 log.Errorf("Unable to save powchain data: %v", err) 782 } 783 } 784 return 785 } 786 } 787 } 788 789 // run subscribes to all the services for the ETH1.0 chain. 790 func (s *Service) run(done <-chan struct{}) { 791 s.runError = nil 792 793 s.initPOWService() 794 795 chainstartTicker := time.NewTicker(logPeriod) 796 defer chainstartTicker.Stop() 797 798 for { 799 select { 800 case <-done: 801 s.isRunning = false 802 s.runError = nil 803 s.updateConnectedETH1(false) 804 log.Debug("Context closed, exiting goroutine") 805 return 806 case <-s.headTicker.C: 807 head, err := s.eth1DataFetcher.HeaderByNumber(s.ctx, nil) 808 if err != nil { 809 log.WithError(err).Debug("Could not fetch latest eth1 header") 810 s.retryETH1Node(err) 811 continue 812 } 813 if eth1HeadIsBehind(head.Time) { 814 log.WithError(errFarBehind).Debug("Could not get an up to date eth1 header") 815 s.retryETH1Node(errFarBehind) 816 continue 817 } 818 s.processBlockHeader(head) 819 s.handleETH1FollowDistance() 820 s.checkDefaultEndpoint() 821 case <-chainstartTicker.C: 822 if s.chainStartData.Chainstarted { 823 chainstartTicker.Stop() 824 continue 825 } 826 s.logTillChainStart() 827 } 828 } 829 } 830 831 // logs the current thresholds required to hit chainstart every minute. 832 func (s *Service) logTillChainStart() { 833 if s.chainStartData.Chainstarted { 834 return 835 } 836 _, blockTime, err := s.retrieveBlockHashAndTime(s.ctx, big.NewInt(int64(s.latestEth1Data.LastRequestedBlock))) 837 if err != nil { 838 log.Error(err) 839 return 840 } 841 valCount, genesisTime := s.currentCountAndTime(blockTime) 842 valNeeded := uint64(0) 843 if valCount < params.BeaconConfig().MinGenesisActiveValidatorCount { 844 valNeeded = params.BeaconConfig().MinGenesisActiveValidatorCount - valCount 845 } 846 secondsLeft := uint64(0) 847 if genesisTime < params.BeaconConfig().MinGenesisTime { 848 secondsLeft = params.BeaconConfig().MinGenesisTime - genesisTime 849 } 850 851 fields := logrus.Fields{ 852 "Additional validators needed": valNeeded, 853 } 854 if secondsLeft > 0 { 855 fields["Generating genesis state in"] = time.Duration(secondsLeft) * time.Second 856 } 857 858 log.WithFields(fields).Info("Currently waiting for chainstart") 859 } 860 861 // cacheHeadersForEth1DataVote makes sure that voting for eth1data after startup utilizes cached headers 862 // instead of making multiple RPC requests to the ETH1 endpoint. 863 func (s *Service) cacheHeadersForEth1DataVote(ctx context.Context) error { 864 // Find the end block to request from. 865 end, err := s.followBlockHeight(ctx) 866 if err != nil { 867 return err 868 } 869 start, err := s.determineEarliestVotingBlock(ctx, end) 870 if err != nil { 871 return err 872 } 873 // We call batchRequestHeaders for its header caching side-effect, so we don't need the return value. 874 _, err = s.batchRequestHeaders(start, end) 875 if err != nil { 876 return err 877 } 878 return nil 879 } 880 881 // determines the earliest voting block from which to start caching all our previous headers from. 882 func (s *Service) determineEarliestVotingBlock(ctx context.Context, followBlock uint64) (uint64, error) { 883 genesisTime := s.chainStartData.GenesisTime 884 currSlot := helpers.CurrentSlot(genesisTime) 885 886 // In the event genesis has not occurred yet, we just request go back follow_distance blocks. 887 if genesisTime == 0 || currSlot == 0 { 888 earliestBlk := uint64(0) 889 if followBlock > params.BeaconConfig().Eth1FollowDistance { 890 earliestBlk = followBlock - params.BeaconConfig().Eth1FollowDistance 891 } 892 return earliestBlk, nil 893 } 894 votingTime := helpers.VotingPeriodStartTime(genesisTime, currSlot) 895 followBackDist := 2 * params.BeaconConfig().SecondsPerETH1Block * params.BeaconConfig().Eth1FollowDistance 896 if followBackDist > votingTime { 897 return 0, errors.Errorf("invalid genesis time provided. %d > %d", followBackDist, votingTime) 898 } 899 earliestValidTime := votingTime - followBackDist 900 hdr, err := s.BlockByTimestamp(ctx, earliestValidTime) 901 if err != nil { 902 return 0, err 903 } 904 return hdr.Number.Uint64(), nil 905 } 906 907 // This performs a health check on our primary endpoint, and if it 908 // is ready to serve we connect to it again. This method is only 909 // relevant if we are on our backup endpoint. 910 func (s *Service) checkDefaultEndpoint() { 911 primaryEndpoint := s.httpEndpoints[0] 912 // Return early if we are running on our primary 913 // endpoint. 914 if s.currHttpEndpoint.Equals(primaryEndpoint) { 915 return 916 } 917 918 httpClient, rpcClient, err := s.dialETH1Nodes(primaryEndpoint) 919 if err != nil { 920 log.Debugf("Primary endpoint not ready: %v", err) 921 return 922 } 923 log.Info("Primary endpoint ready again, switching back to it") 924 // Close the clients and let our main connection routine 925 // properly connect with it. 926 httpClient.Close() 927 rpcClient.Close() 928 // Close current active clients. 929 s.closeClients() 930 931 // Switch back to primary endpoint and try connecting 932 // to it again. 933 s.updateCurrHttpEndpoint(primaryEndpoint) 934 s.retryETH1Node(nil) 935 } 936 937 // This is an inefficient way to search for the next endpoint, but given N is expected to be 938 // small ( < 25), it is fine to search this way. 939 func (s *Service) fallbackToNextEndpoint() { 940 currEndpoint := s.currHttpEndpoint 941 currIndex := 0 942 totalEndpoints := len(s.httpEndpoints) 943 944 for i, endpoint := range s.httpEndpoints { 945 if endpoint.Equals(currEndpoint) { 946 currIndex = i 947 break 948 } 949 } 950 nextIndex := currIndex + 1 951 if nextIndex >= totalEndpoints { 952 nextIndex = 0 953 } 954 if nextIndex != currIndex { 955 log.Infof("Falling back to alternative endpoint: %s", logutil.MaskCredentialsLogging(s.currHttpEndpoint.Url)) 956 } 957 s.updateCurrHttpEndpoint(s.httpEndpoints[nextIndex]) 958 } 959 960 // initializes our service from the provided eth1data object by initializing all the relevant 961 // fields and data. 962 func (s *Service) initializeEth1Data(ctx context.Context, eth1DataInDB *protodb.ETH1ChainData) error { 963 // The node has no eth1data persisted on disk, so we exit and instead 964 // request from contract logs. 965 if eth1DataInDB == nil { 966 return nil 967 } 968 s.depositTrie = trieutil.CreateTrieFromProto(eth1DataInDB.Trie) 969 s.chainStartData = eth1DataInDB.ChainstartData 970 var err error 971 if !reflect.ValueOf(eth1DataInDB.BeaconState).IsZero() { 972 s.preGenesisState, err = v1.InitializeFromProto(eth1DataInDB.BeaconState) 973 if err != nil { 974 return errors.Wrap(err, "Could not initialize state trie") 975 } 976 } 977 s.latestEth1Data = eth1DataInDB.CurrentEth1Data 978 numOfItems := s.depositTrie.NumOfItems() 979 s.lastReceivedMerkleIndex = int64(numOfItems - 1) 980 if err := s.initDepositCaches(ctx, eth1DataInDB.DepositContainers); err != nil { 981 return errors.Wrap(err, "could not initialize caches") 982 } 983 return nil 984 } 985 986 // validates that all deposit containers are valid and have their relevant indices 987 // in order. 988 func (s *Service) validateDepositContainers(ctrs []*protodb.DepositContainer) bool { 989 ctrLen := len(ctrs) 990 // Exit for empty containers. 991 if ctrLen == 0 { 992 return true 993 } 994 // Sort deposits in ascending order. 995 sort.Slice(ctrs, func(i, j int) bool { 996 return ctrs[i].Index < ctrs[j].Index 997 }) 998 startIndex := int64(0) 999 for _, c := range ctrs { 1000 if c.Index != startIndex { 1001 log.Info("Recovering missing deposit containers, node is re-requesting missing deposit data") 1002 return false 1003 } 1004 startIndex++ 1005 } 1006 return true 1007 } 1008 1009 // validates the current powchain data saved and makes sure that any 1010 // embedded genesis state is correctly accounted for. 1011 func (s *Service) ensureValidPowchainData(ctx context.Context) error { 1012 genState, err := s.cfg.BeaconDB.GenesisState(ctx) 1013 if err != nil { 1014 return err 1015 } 1016 // Exit early if no genesis state is saved. 1017 if genState == nil || genState.IsNil() { 1018 return nil 1019 } 1020 eth1Data, err := s.cfg.BeaconDB.PowchainData(ctx) 1021 if err != nil { 1022 return errors.Wrap(err, "unable to retrieve eth1 data") 1023 } 1024 if eth1Data == nil || !eth1Data.ChainstartData.Chainstarted || !s.validateDepositContainers(eth1Data.DepositContainers) { 1025 pbState, err := v1.ProtobufBeaconState(s.preGenesisState.InnerStateUnsafe()) 1026 if err != nil { 1027 return err 1028 } 1029 s.chainStartData = &protodb.ChainStartData{ 1030 Chainstarted: true, 1031 GenesisTime: genState.GenesisTime(), 1032 GenesisBlock: 0, 1033 Eth1Data: genState.Eth1Data(), 1034 ChainstartDeposits: make([]*ethpb.Deposit, 0), 1035 } 1036 eth1Data = &protodb.ETH1ChainData{ 1037 CurrentEth1Data: s.latestEth1Data, 1038 ChainstartData: s.chainStartData, 1039 BeaconState: pbState, 1040 Trie: s.depositTrie.ToProto(), 1041 DepositContainers: s.cfg.DepositCache.AllDepositContainers(ctx), 1042 } 1043 return s.cfg.BeaconDB.SavePowchainData(ctx, eth1Data) 1044 } 1045 return nil 1046 } 1047 1048 func dedupEndpoints(endpoints []string) []string { 1049 selectionMap := make(map[string]bool) 1050 newEndpoints := make([]string, 0, len(endpoints)) 1051 for _, point := range endpoints { 1052 if selectionMap[point] { 1053 continue 1054 } 1055 newEndpoints = append(newEndpoints, point) 1056 selectionMap[point] = true 1057 } 1058 return newEndpoints 1059 } 1060 1061 // Checks if the provided timestamp is beyond the prescribed bound from 1062 // the current wall clock time. 1063 func eth1HeadIsBehind(timestamp uint64) bool { 1064 timeout := timeutils.Now().Add(-eth1Threshold) 1065 // check that web3 client is syncing 1066 return time.Unix(int64(timestamp), 0).Before(timeout) 1067 } 1068 1069 func (s *Service) primaryConnected() bool { 1070 return s.currHttpEndpoint.Equals(s.httpEndpoints[0]) 1071 }