2019-01-28 08:45:28 +00:00
|
|
|
// Package powchain defines the services that interact with the ETH1.0 of Ethereum.
|
2018-07-14 19:48:42 +00:00
|
|
|
package powchain
|
|
|
|
|
|
|
|
import (
|
2019-02-12 17:06:52 +00:00
|
|
|
"bytes"
|
2018-07-14 19:48:42 +00:00
|
|
|
"context"
|
2019-01-16 14:01:21 +00:00
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
2018-07-14 19:48:42 +00:00
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"strings"
|
2019-01-17 15:14:32 +00:00
|
|
|
"time"
|
2018-07-14 19:48:42 +00:00
|
|
|
|
2019-03-04 20:37:24 +00:00
|
|
|
ethereum "github.com/ethereum/go-ethereum"
|
2019-01-16 14:01:21 +00:00
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
2018-07-14 19:48:42 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
gethTypes "github.com/ethereum/go-ethereum/core/types"
|
2019-02-06 15:10:49 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
2019-02-28 03:55:47 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
2019-02-05 17:25:09 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
2019-01-28 08:45:28 +00:00
|
|
|
contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract"
|
2019-02-03 22:44:48 +00:00
|
|
|
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
|
2019-01-28 11:59:37 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/event"
|
2019-01-16 14:01:21 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
2019-02-16 21:00:52 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
2019-01-31 02:53:58 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/trieutil"
|
2018-07-21 17:51:18 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2019-02-28 03:55:47 +00:00
|
|
|
"go.opencensus.io/trace"
|
2018-07-14 19:48:42 +00:00
|
|
|
)
|
|
|
|
|
2018-07-21 17:51:18 +00:00
|
|
|
var log = logrus.WithField("prefix", "powchain")
|
|
|
|
|
2019-02-06 15:10:49 +00:00
|
|
|
var (
|
|
|
|
validDepositsCount = promauto.NewCounter(prometheus.CounterOpts{
|
|
|
|
Name: "powchain_valid_deposits_received",
|
|
|
|
Help: "The number of valid deposits received in the deposit contract",
|
|
|
|
})
|
|
|
|
chainStartCount = promauto.NewCounter(prometheus.CounterOpts{
|
|
|
|
Name: "powchain_chainstart_logs",
|
|
|
|
Help: "The number of chainstart logs received from the deposit contract",
|
|
|
|
})
|
|
|
|
blockNumberGauge = promauto.NewGauge(prometheus.GaugeOpts{
|
|
|
|
Name: "powchain_block_number",
|
|
|
|
Help: "The current block number in the proof-of-work chain",
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
2018-10-18 17:33:38 +00:00
|
|
|
// Reader defines a struct that can fetch latest header events from a web3 endpoint.
|
|
|
|
type Reader interface {
|
|
|
|
SubscribeNewHead(ctx context.Context, ch chan<- *gethTypes.Header) (ethereum.Subscription, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// POWBlockFetcher defines a struct that can retrieve mainchain blocks.
|
|
|
|
type POWBlockFetcher interface {
|
|
|
|
BlockByHash(ctx context.Context, hash common.Hash) (*gethTypes.Block, error)
|
2019-02-24 02:58:13 +00:00
|
|
|
BlockByNumber(ctx context.Context, number *big.Int) (*gethTypes.Block, error)
|
2019-02-16 21:00:52 +00:00
|
|
|
HeaderByNumber(ctx context.Context, number *big.Int) (*gethTypes.Header, error)
|
2018-10-18 17:33:38 +00:00
|
|
|
}
|
|
|
|
|
2019-01-28 08:45:28 +00:00
|
|
|
// Client defines a struct that combines all relevant ETH1.0 mainchain interactions required
|
2018-10-18 17:33:38 +00:00
|
|
|
// by the beacon chain node.
|
|
|
|
type Client interface {
|
|
|
|
Reader
|
|
|
|
POWBlockFetcher
|
2019-01-16 14:01:21 +00:00
|
|
|
bind.ContractFilterer
|
|
|
|
bind.ContractCaller
|
2018-10-18 17:33:38 +00:00
|
|
|
}
|
|
|
|
|
2018-07-14 19:48:42 +00:00
|
|
|
// Web3Service fetches important information about the canonical
|
2019-01-28 08:45:28 +00:00
|
|
|
// Ethereum ETH1.0 chain via a web3 endpoint using an ethclient. The Random
|
|
|
|
// Beacon Chain requires synchronization with the ETH1.0 chain's current
|
2018-07-14 19:48:42 +00:00
|
|
|
// blockhash, block number, and access to logs within the
|
2019-01-28 08:45:28 +00:00
|
|
|
// Validator Registration Contract on the ETH1.0 chain to kick off the beacon
|
2018-07-14 19:48:42 +00:00
|
|
|
// chain's validator registration process.
|
|
|
|
type Web3Service struct {
|
2019-02-12 19:50:39 +00:00
|
|
|
ctx context.Context
|
|
|
|
cancel context.CancelFunc
|
|
|
|
client Client
|
|
|
|
headerChan chan *gethTypes.Header
|
|
|
|
endpoint string
|
|
|
|
depositContractAddress common.Address
|
|
|
|
chainStartFeed *event.Feed
|
|
|
|
reader Reader
|
|
|
|
logger bind.ContractFilterer
|
2019-02-16 21:00:52 +00:00
|
|
|
blockFetcher POWBlockFetcher
|
2019-02-20 17:57:02 +00:00
|
|
|
blockHeight *big.Int // the latest ETH1.0 chain blockHeight.
|
2019-02-12 19:50:39 +00:00
|
|
|
blockHash common.Hash // the latest ETH1.0 chain blockHash.
|
2019-03-04 20:37:24 +00:00
|
|
|
blockTime time.Time // the latest ETH1.0 chain blockTime.
|
2019-03-01 20:31:38 +00:00
|
|
|
blockCache *blockCache // cache to store block hash/block height.
|
2019-02-12 19:50:39 +00:00
|
|
|
depositContractCaller *contracts.DepositContractCaller
|
|
|
|
depositRoot []byte
|
2019-03-12 04:05:55 +00:00
|
|
|
depositTrie *trieutil.MerkleTrie
|
|
|
|
chainStartDeposits [][]byte
|
2019-02-12 19:50:39 +00:00
|
|
|
chainStarted bool
|
|
|
|
beaconDB *db.BeaconDB
|
|
|
|
lastReceivedMerkleIndex int64 // Keeps track of the last received index to prevent log spam.
|
2019-03-04 20:37:24 +00:00
|
|
|
isRunning bool
|
|
|
|
runError error
|
2019-02-14 17:11:03 +00:00
|
|
|
chainStartDelay uint64
|
2019-02-16 21:00:52 +00:00
|
|
|
lastRequestedBlock *big.Int
|
2018-07-19 16:31:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Web3ServiceConfig defines a config struct for web3 service to use through its life cycle.
|
|
|
|
type Web3ServiceConfig struct {
|
2019-01-16 14:01:21 +00:00
|
|
|
Endpoint string
|
2019-01-17 15:14:32 +00:00
|
|
|
DepositContract common.Address
|
2019-01-16 14:01:21 +00:00
|
|
|
Client Client
|
|
|
|
Reader Reader
|
|
|
|
Logger bind.ContractFilterer
|
2019-02-16 21:00:52 +00:00
|
|
|
BlockFetcher POWBlockFetcher
|
2019-01-16 14:01:21 +00:00
|
|
|
ContractBackend bind.ContractBackend
|
2019-02-05 17:25:09 +00:00
|
|
|
BeaconDB *db.BeaconDB
|
2019-02-14 17:11:03 +00:00
|
|
|
ChainStartDelay uint64
|
2018-07-14 19:48:42 +00:00
|
|
|
}
|
|
|
|
|
2019-01-16 14:01:21 +00:00
|
|
|
var (
|
2019-02-08 17:01:15 +00:00
|
|
|
depositEventSignature = []byte("Deposit(bytes32,bytes,bytes,bytes32[32])")
|
2019-01-28 08:45:28 +00:00
|
|
|
chainStartEventSignature = []byte("ChainStart(bytes32,bytes)")
|
2019-01-16 14:01:21 +00:00
|
|
|
)
|
|
|
|
|
2018-07-14 19:48:42 +00:00
|
|
|
// NewWeb3Service sets up a new instance with an ethclient when
|
2018-08-09 18:25:48 +00:00
|
|
|
// given a web3 endpoint as a string in the config.
|
2018-11-16 17:01:41 +00:00
|
|
|
func NewWeb3Service(ctx context.Context, config *Web3ServiceConfig) (*Web3Service, error) {
|
2018-07-19 16:31:50 +00:00
|
|
|
if !strings.HasPrefix(config.Endpoint, "ws") && !strings.HasPrefix(config.Endpoint, "ipc") {
|
2018-11-16 17:01:41 +00:00
|
|
|
return nil, fmt.Errorf(
|
2019-02-12 17:06:52 +00:00
|
|
|
"powchain service requires either an IPC or WebSocket endpoint, provided %s",
|
2018-11-16 17:01:41 +00:00
|
|
|
config.Endpoint,
|
|
|
|
)
|
2018-07-14 19:48:42 +00:00
|
|
|
}
|
2019-01-16 14:01:21 +00:00
|
|
|
|
2019-02-12 17:06:52 +00:00
|
|
|
depositContractCaller, err := contracts.NewDepositContractCaller(config.DepositContract, config.ContractBackend)
|
2019-01-16 14:01:21 +00:00
|
|
|
if err != nil {
|
2019-02-12 17:06:52 +00:00
|
|
|
return nil, fmt.Errorf("could not create deposit contract caller %v", err)
|
2019-01-16 14:01:21 +00:00
|
|
|
}
|
|
|
|
|
2018-07-31 04:41:27 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
2019-03-12 04:05:55 +00:00
|
|
|
depositTrie, err := trieutil.GenerateTrieFromItems([][]byte{{}}, int(params.BeaconConfig().DepositContractTreeDepth))
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not setup deposit trie: %v", err)
|
|
|
|
}
|
2018-07-14 19:48:42 +00:00
|
|
|
return &Web3Service{
|
2019-02-12 19:50:39 +00:00
|
|
|
ctx: ctx,
|
|
|
|
cancel: cancel,
|
|
|
|
headerChan: make(chan *gethTypes.Header),
|
|
|
|
endpoint: config.Endpoint,
|
2019-02-20 17:57:02 +00:00
|
|
|
blockHeight: nil,
|
2019-02-12 19:50:39 +00:00
|
|
|
blockHash: common.BytesToHash([]byte{}),
|
2019-03-01 20:31:38 +00:00
|
|
|
blockCache: newBlockCache(),
|
2019-02-12 19:50:39 +00:00
|
|
|
depositContractAddress: config.DepositContract,
|
|
|
|
chainStartFeed: new(event.Feed),
|
|
|
|
client: config.Client,
|
2019-03-12 04:05:55 +00:00
|
|
|
depositTrie: depositTrie,
|
2019-02-12 19:50:39 +00:00
|
|
|
reader: config.Reader,
|
|
|
|
logger: config.Logger,
|
2019-02-16 21:00:52 +00:00
|
|
|
blockFetcher: config.BlockFetcher,
|
2019-02-12 19:50:39 +00:00
|
|
|
depositContractCaller: depositContractCaller,
|
2019-03-12 04:05:55 +00:00
|
|
|
chainStartDeposits: [][]byte{},
|
2019-02-12 19:50:39 +00:00
|
|
|
beaconDB: config.BeaconDB,
|
|
|
|
lastReceivedMerkleIndex: -1,
|
2019-02-14 17:11:03 +00:00
|
|
|
chainStartDelay: config.ChainStartDelay,
|
2019-02-16 21:00:52 +00:00
|
|
|
lastRequestedBlock: big.NewInt(0),
|
2018-07-14 19:48:42 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start a web3 service's main event loop.
|
|
|
|
func (w *Web3Service) Start() {
|
2018-07-21 17:51:18 +00:00
|
|
|
log.WithFields(logrus.Fields{
|
|
|
|
"endpoint": w.endpoint,
|
2018-07-28 19:53:02 +00:00
|
|
|
}).Info("Starting service")
|
2018-08-07 17:56:28 +00:00
|
|
|
go w.run(w.ctx.Done())
|
2019-02-14 17:11:03 +00:00
|
|
|
|
|
|
|
if w.chainStartDelay > 0 {
|
|
|
|
go w.runDelayTimer(w.ctx.Done())
|
|
|
|
}
|
2018-07-14 19:48:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stop the web3 service's main event loop and associated goroutines.
|
|
|
|
func (w *Web3Service) Stop() error {
|
2019-03-04 20:10:03 +00:00
|
|
|
if w.cancel != nil {
|
|
|
|
defer w.cancel()
|
|
|
|
}
|
|
|
|
if w.headerChan != nil {
|
|
|
|
defer close(w.headerChan)
|
|
|
|
}
|
2018-07-28 19:53:02 +00:00
|
|
|
log.Info("Stopping service")
|
2018-07-14 19:48:42 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-01-28 11:59:37 +00:00
|
|
|
// ChainStartFeed returns a feed that is written to
|
|
|
|
// whenever the deposit contract fires a ChainStart log.
|
|
|
|
func (w *Web3Service) ChainStartFeed() *event.Feed {
|
|
|
|
return w.chainStartFeed
|
|
|
|
}
|
|
|
|
|
2019-03-12 04:05:55 +00:00
|
|
|
// ChainStartDeposits returns a slice of validator deposit data processed
|
2019-02-03 22:44:48 +00:00
|
|
|
// by the deposit contract and cached in the powchain service.
|
2019-03-12 04:05:55 +00:00
|
|
|
func (w *Web3Service) ChainStartDeposits() [][]byte {
|
2019-02-03 22:44:48 +00:00
|
|
|
return w.chainStartDeposits
|
|
|
|
}
|
|
|
|
|
2019-03-04 20:37:24 +00:00
|
|
|
// Status is service health checks. Return nil or error.
|
2018-12-30 21:20:43 +00:00
|
|
|
func (w *Web3Service) Status() error {
|
2019-03-04 20:37:24 +00:00
|
|
|
// Web3Service don't start
|
|
|
|
if !w.isRunning {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// get error from run function
|
|
|
|
if w.runError != nil {
|
|
|
|
return w.runError
|
|
|
|
}
|
|
|
|
// use a 5 minutes timeout for block time, because the max mining time is 278 sec (block 7208027)
|
|
|
|
// (analyzed the time of the block from 2018-09-01 to 2019-02-13)
|
|
|
|
fiveMinutesTimeout := time.Now().Add(-5 * time.Minute)
|
|
|
|
// check that web3 client is syncing
|
|
|
|
if w.blockTime.Before(fiveMinutesTimeout) {
|
2019-03-12 04:05:55 +00:00
|
|
|
return errors.New("eth1 client is not syncing")
|
2019-03-04 20:37:24 +00:00
|
|
|
}
|
2018-12-30 21:20:43 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-02-20 17:57:02 +00:00
|
|
|
// DepositRoot returns the Merkle root of the latest deposit trie
|
|
|
|
// from the ETH1.0 deposit contract.
|
|
|
|
func (w *Web3Service) DepositRoot() [32]byte {
|
|
|
|
return w.depositTrie.Root()
|
|
|
|
}
|
|
|
|
|
2019-03-12 04:05:55 +00:00
|
|
|
// DepositTrie returns the sparse Merkle trie used for storing
|
|
|
|
// deposits from the ETH1.0 deposit contract.
|
|
|
|
func (w *Web3Service) DepositTrie() *trieutil.MerkleTrie {
|
|
|
|
return w.depositTrie
|
|
|
|
}
|
|
|
|
|
2019-02-20 17:57:02 +00:00
|
|
|
// LatestBlockHeight in the ETH1.0 chain.
|
|
|
|
func (w *Web3Service) LatestBlockHeight() *big.Int {
|
|
|
|
return w.blockHeight
|
2019-01-30 12:28:53 +00:00
|
|
|
}
|
2019-01-16 14:01:21 +00:00
|
|
|
|
2019-01-30 12:28:53 +00:00
|
|
|
// LatestBlockHash in the ETH1.0 chain.
|
|
|
|
func (w *Web3Service) LatestBlockHash() common.Hash {
|
|
|
|
return w.blockHash
|
2019-01-16 14:01:21 +00:00
|
|
|
}
|
|
|
|
|
2019-02-24 02:58:13 +00:00
|
|
|
// BlockExists returns true if the block exists, it's height and any possible error encountered.
|
2019-02-28 03:55:47 +00:00
|
|
|
func (w *Web3Service) BlockExists(ctx context.Context, hash common.Hash) (bool, *big.Int, error) {
|
|
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.web3service.BlockExists")
|
|
|
|
defer span.End()
|
2019-03-01 20:31:38 +00:00
|
|
|
|
|
|
|
if exists, blkInfo, err := w.blockCache.BlockInfoByHash(hash); exists || err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return false, nil, err
|
|
|
|
}
|
|
|
|
span.AddAttributes(trace.BoolAttribute("blockCacheHit", true))
|
|
|
|
return true, blkInfo.Number, nil
|
|
|
|
}
|
|
|
|
span.AddAttributes(trace.BoolAttribute("blockCacheHit", false))
|
2019-02-28 03:55:47 +00:00
|
|
|
block, err := w.blockFetcher.BlockByHash(ctx, hash)
|
2019-02-24 02:58:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, big.NewInt(0), fmt.Errorf("could not query block with given hash: %v", err)
|
|
|
|
}
|
|
|
|
|
2019-03-01 20:31:38 +00:00
|
|
|
if err := w.blockCache.AddBlock(block); err != nil {
|
|
|
|
return false, big.NewInt(0), err
|
|
|
|
}
|
|
|
|
|
2019-02-24 02:58:13 +00:00
|
|
|
return true, block.Number(), nil
|
2019-02-20 17:57:02 +00:00
|
|
|
}
|
|
|
|
|
2019-02-24 02:58:13 +00:00
|
|
|
// BlockHashByHeight returns the block hash of the block at the given height.
|
2019-03-01 20:31:38 +00:00
|
|
|
func (w *Web3Service) BlockHashByHeight(ctx context.Context, height *big.Int) (common.Hash, error) {
|
|
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.web3service.BlockHashByHeight")
|
|
|
|
defer span.End()
|
|
|
|
|
|
|
|
if exists, blkInfo, err := w.blockCache.BlockInfoByHeight(height); exists || err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return [32]byte{}, err
|
|
|
|
}
|
|
|
|
span.AddAttributes(trace.BoolAttribute("blockCacheHit", true))
|
|
|
|
return blkInfo.Hash, nil
|
|
|
|
}
|
|
|
|
span.AddAttributes(trace.BoolAttribute("blockCacheHit", false))
|
2019-02-24 02:58:13 +00:00
|
|
|
block, err := w.blockFetcher.BlockByNumber(w.ctx, height)
|
|
|
|
if err != nil {
|
|
|
|
return [32]byte{}, fmt.Errorf("could not query block with given height: %v", err)
|
|
|
|
}
|
2019-03-01 20:31:38 +00:00
|
|
|
if err := w.blockCache.AddBlock(block); err != nil {
|
|
|
|
return [32]byte{}, err
|
|
|
|
}
|
2019-02-24 02:58:13 +00:00
|
|
|
return block.Hash(), nil
|
2019-02-20 17:57:02 +00:00
|
|
|
}
|
|
|
|
|
2019-01-30 12:28:53 +00:00
|
|
|
// Client for interacting with the ETH1.0 chain.
|
|
|
|
func (w *Web3Service) Client() Client {
|
|
|
|
return w.client
|
|
|
|
}
|
2019-01-16 14:01:21 +00:00
|
|
|
|
2019-01-30 12:28:53 +00:00
|
|
|
// HasChainStartLogOccurred queries all logs in the deposit contract to verify
|
|
|
|
// if ChainStart has occurred. If so, it returns true alongside the ChainStart timestamp.
|
2019-02-03 22:44:48 +00:00
|
|
|
func (w *Web3Service) HasChainStartLogOccurred() (bool, uint64, error) {
|
2019-02-12 17:06:52 +00:00
|
|
|
genesisTime, err := w.depositContractCaller.GenesisTime(&bind.CallOpts{})
|
2018-07-19 16:31:50 +00:00
|
|
|
if err != nil {
|
2019-02-12 17:06:52 +00:00
|
|
|
return false, 0, fmt.Errorf("could not query contract to verify chain started: %v", err)
|
2018-07-19 16:31:50 +00:00
|
|
|
}
|
2019-02-12 17:06:52 +00:00
|
|
|
// If chain has not yet started, the result will be an empty byte slice.
|
|
|
|
if bytes.Equal(genesisTime, []byte{}) {
|
|
|
|
return false, 0, nil
|
|
|
|
}
|
|
|
|
timestamp := binary.LittleEndian.Uint64(genesisTime)
|
|
|
|
return true, timestamp, nil
|
2018-07-19 16:31:50 +00:00
|
|
|
}
|
|
|
|
|
2019-01-17 15:14:32 +00:00
|
|
|
// ProcessLog is the main method which handles the processing of all
|
2019-01-28 08:45:28 +00:00
|
|
|
// logs from the deposit contract on the ETH1.0 chain.
|
2019-02-13 19:08:28 +00:00
|
|
|
func (w *Web3Service) ProcessLog(depositLog gethTypes.Log) {
|
2019-01-16 14:01:21 +00:00
|
|
|
// Process logs according to their event signature.
|
2019-02-13 19:08:28 +00:00
|
|
|
if depositLog.Topics[0] == hashutil.Hash(depositEventSignature) {
|
|
|
|
w.ProcessDepositLog(depositLog)
|
2019-01-16 14:01:21 +00:00
|
|
|
return
|
|
|
|
}
|
2019-02-13 19:08:28 +00:00
|
|
|
if depositLog.Topics[0] == hashutil.Hash(chainStartEventSignature) && !w.chainStarted {
|
|
|
|
w.ProcessChainStartLog(depositLog)
|
2019-01-16 14:01:21 +00:00
|
|
|
return
|
|
|
|
}
|
2019-02-13 19:08:28 +00:00
|
|
|
log.Debugf("Log is not of a valid event signature %#x", depositLog.Topics[0])
|
2019-01-16 14:01:21 +00:00
|
|
|
}
|
|
|
|
|
2019-01-17 15:14:32 +00:00
|
|
|
// ProcessDepositLog processes the log which had been received from
|
2019-01-28 08:45:28 +00:00
|
|
|
// the ETH1.0 chain by trying to ascertain which participant deposited
|
2019-01-16 14:01:21 +00:00
|
|
|
// in the contract.
|
2019-02-13 19:08:28 +00:00
|
|
|
func (w *Web3Service) ProcessDepositLog(depositLog gethTypes.Log) {
|
2019-03-12 04:05:55 +00:00
|
|
|
_, depositData, merkleTreeIndex, _, err := contracts.UnpackDepositLogData(depositLog.Data)
|
2019-01-16 14:01:21 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Could not unpack log %v", err)
|
|
|
|
return
|
|
|
|
}
|
2019-02-12 19:50:39 +00:00
|
|
|
// If we have already seen this Merkle index, skip processing the log.
|
|
|
|
// This can happen sometimes when we receive the same log twice from the
|
|
|
|
// ETH1.0 network, and prevents us from updating our trie
|
|
|
|
// with the same log twice, causing an inconsistent state root.
|
|
|
|
index := binary.LittleEndian.Uint64(merkleTreeIndex)
|
|
|
|
if int64(index) <= w.lastReceivedMerkleIndex {
|
|
|
|
return
|
|
|
|
}
|
2019-03-12 04:05:55 +00:00
|
|
|
|
2019-02-12 19:50:39 +00:00
|
|
|
w.lastReceivedMerkleIndex = int64(index)
|
2019-03-12 04:05:55 +00:00
|
|
|
|
|
|
|
// We then decode the deposit input in order to create a deposit object
|
|
|
|
// we can store in our persistent DB.
|
2019-02-19 18:05:34 +00:00
|
|
|
depositInput, err := helpers.DecodeDepositInput(depositData)
|
2019-01-16 14:01:21 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Could not decode deposit input %v", err)
|
|
|
|
return
|
|
|
|
}
|
2019-02-05 17:25:09 +00:00
|
|
|
deposit := &pb.Deposit{
|
2019-03-12 04:05:55 +00:00
|
|
|
DepositData: depositData,
|
|
|
|
MerkleTreeIndex: index,
|
2019-02-05 17:25:09 +00:00
|
|
|
}
|
2019-02-03 22:44:48 +00:00
|
|
|
if !w.chainStarted {
|
2019-03-12 04:05:55 +00:00
|
|
|
w.chainStartDeposits = append(w.chainStartDeposits, depositData)
|
2019-02-05 17:25:09 +00:00
|
|
|
} else {
|
2019-02-13 19:08:28 +00:00
|
|
|
w.beaconDB.InsertPendingDeposit(w.ctx, deposit, big.NewInt(int64(depositLog.BlockNumber)))
|
2019-02-03 22:44:48 +00:00
|
|
|
}
|
2019-03-12 04:05:55 +00:00
|
|
|
// We always store all historical deposits in the DB.
|
|
|
|
w.beaconDB.InsertDeposit(w.ctx, deposit, big.NewInt(int64(depositLog.BlockNumber)))
|
2019-01-16 14:01:21 +00:00
|
|
|
log.WithFields(logrus.Fields{
|
2019-02-08 21:25:35 +00:00
|
|
|
"publicKey": fmt.Sprintf("%#x", depositInput.Pubkey),
|
|
|
|
"merkleTreeIndex": index,
|
|
|
|
}).Info("Validator registered in deposit contract")
|
2019-02-06 15:10:49 +00:00
|
|
|
validDepositsCount.Inc()
|
2019-01-16 14:01:21 +00:00
|
|
|
}
|
|
|
|
|
2019-01-17 15:14:32 +00:00
|
|
|
// ProcessChainStartLog processes the log which had been received from
|
2019-01-28 08:45:28 +00:00
|
|
|
// the ETH1.0 chain by trying to determine when to start the beacon chain.
|
2019-02-13 19:08:28 +00:00
|
|
|
func (w *Web3Service) ProcessChainStartLog(depositLog gethTypes.Log) {
|
2019-02-06 15:10:49 +00:00
|
|
|
chainStartCount.Inc()
|
2019-03-12 04:05:55 +00:00
|
|
|
chainStartDepositRoot, timestampData, err := contracts.UnpackChainStartLogData(depositLog.Data)
|
2019-01-16 14:01:21 +00:00
|
|
|
if err != nil {
|
2019-01-17 15:14:32 +00:00
|
|
|
log.Errorf("Unable to unpack ChainStart log data %v", err)
|
|
|
|
return
|
2019-01-16 14:01:21 +00:00
|
|
|
}
|
|
|
|
|
2019-02-12 03:57:00 +00:00
|
|
|
timestamp := binary.LittleEndian.Uint64(timestampData)
|
2019-02-03 22:44:48 +00:00
|
|
|
w.chainStarted = true
|
2019-03-12 04:05:55 +00:00
|
|
|
w.depositRoot = chainStartDepositRoot[:]
|
2019-01-17 15:14:32 +00:00
|
|
|
chainStartTime := time.Unix(int64(timestamp), 0)
|
2019-03-12 04:05:55 +00:00
|
|
|
|
|
|
|
// We then update the in-memory deposit trie from the chain start
|
|
|
|
// deposits at this point, as this trie will be later needed for
|
|
|
|
// incoming, post-chain start deposits.
|
|
|
|
sparseMerkleTrie, err := trieutil.GenerateTrieFromItems(
|
|
|
|
w.chainStartDeposits,
|
|
|
|
int(params.BeaconConfig().DepositContractTreeDepth),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Unable to generate deposit trie from ChainStart deposits: %v", err)
|
|
|
|
}
|
|
|
|
w.depositTrie = sparseMerkleTrie
|
|
|
|
|
2019-01-17 15:14:32 +00:00
|
|
|
log.WithFields(logrus.Fields{
|
|
|
|
"ChainStartTime": chainStartTime,
|
2019-02-14 17:11:03 +00:00
|
|
|
}).Info("Minimum number of validators reached for beacon-chain to start")
|
2019-01-28 11:59:37 +00:00
|
|
|
w.chainStartFeed.Send(chainStartTime)
|
2019-01-16 14:01:21 +00:00
|
|
|
}
|
|
|
|
|
2019-02-14 17:11:03 +00:00
|
|
|
func (w *Web3Service) runDelayTimer(done <-chan struct{}) {
|
|
|
|
timer := time.NewTimer(time.Duration(w.chainStartDelay) * time.Second)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-done:
|
|
|
|
log.Debug("ETH1.0 chain service context closed, exiting goroutine")
|
|
|
|
timer.Stop()
|
|
|
|
return
|
|
|
|
case currentTime := <-timer.C:
|
|
|
|
w.chainStarted = true
|
|
|
|
log.WithFields(logrus.Fields{
|
|
|
|
"ChainStartTime": currentTime.Unix(),
|
|
|
|
}).Info("Minimum number of validators reached for beacon-chain to start")
|
|
|
|
w.chainStartFeed.Send(currentTime)
|
|
|
|
timer.Stop()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-12 17:06:52 +00:00
|
|
|
// initDataFromContract calls the deposit contract and finds the deposit count
|
2019-01-30 12:28:53 +00:00
|
|
|
// and deposit root.
|
2019-02-12 17:06:52 +00:00
|
|
|
func (w *Web3Service) initDataFromContract() error {
|
|
|
|
root, err := w.depositContractCaller.GetDepositRoot(&bind.CallOpts{})
|
2019-01-30 12:28:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not retrieve deposit root %v", err)
|
|
|
|
}
|
|
|
|
w.depositRoot = root[:]
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-01-17 15:14:32 +00:00
|
|
|
// processPastLogs processes all the past logs from the deposit contract and
|
|
|
|
// updates the deposit trie with the data from each individual log.
|
2019-02-16 21:00:52 +00:00
|
|
|
func (w *Web3Service) processPastLogs() error {
|
|
|
|
query := ethereum.FilterQuery{
|
|
|
|
Addresses: []common.Address{
|
|
|
|
w.depositContractAddress,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-01-16 14:01:21 +00:00
|
|
|
logs, err := w.logger.FilterLogs(w.ctx, query)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, log := range logs {
|
2019-01-17 15:14:32 +00:00
|
|
|
w.ProcessLog(log)
|
2019-01-16 14:01:21 +00:00
|
|
|
}
|
2019-02-20 17:57:02 +00:00
|
|
|
w.lastRequestedBlock.Set(w.blockHeight)
|
2019-02-16 21:00:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// requestBatchedLogs requests and processes all the logs from the period
|
|
|
|
// last polled to now.
|
|
|
|
func (w *Web3Service) requestBatchedLogs() error {
|
|
|
|
// We request for the nth block behind the current head, in order to have
|
|
|
|
// stabilised logs when we retrieve it from the 1.0 chain.
|
2019-02-20 17:57:02 +00:00
|
|
|
requestedBlock := big.NewInt(0).Sub(w.blockHeight, big.NewInt(params.BeaconConfig().LogBlockDelay))
|
2019-02-16 21:00:52 +00:00
|
|
|
query := ethereum.FilterQuery{
|
|
|
|
Addresses: []common.Address{
|
|
|
|
w.depositContractAddress,
|
|
|
|
},
|
|
|
|
FromBlock: w.lastRequestedBlock.Add(w.lastRequestedBlock, big.NewInt(1)),
|
|
|
|
ToBlock: requestedBlock,
|
|
|
|
}
|
|
|
|
logs, err := w.logger.FilterLogs(w.ctx, query)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only process log slices which are larger than zero.
|
|
|
|
if len(logs) > 0 {
|
|
|
|
log.Debug("Processing Batched Logs")
|
|
|
|
for _, log := range logs {
|
|
|
|
w.ProcessLog(log)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
w.lastRequestedBlock.Set(requestedBlock)
|
2019-01-16 14:01:21 +00:00
|
|
|
return nil
|
|
|
|
}
|
2019-03-10 22:53:28 +00:00
|
|
|
|
2019-03-12 04:05:55 +00:00
|
|
|
func (w *Web3Service) processSubscribedHeaders(header *gethTypes.Header) {
|
|
|
|
defer safelyHandlePanic()
|
|
|
|
blockNumberGauge.Set(float64(header.Number.Int64()))
|
|
|
|
w.blockHeight = header.Number
|
|
|
|
w.blockHash = header.Hash()
|
|
|
|
w.blockTime = time.Unix(header.Time.Int64(), 0)
|
|
|
|
log.WithFields(logrus.Fields{
|
|
|
|
"blockNumber": w.blockHeight,
|
|
|
|
"blockHash": w.blockHash.Hex(),
|
|
|
|
}).Debug("Latest eth1 chain event")
|
|
|
|
|
|
|
|
if err := w.blockCache.AddBlock(gethTypes.NewBlockWithHeader(header)); err != nil {
|
|
|
|
w.runError = err
|
|
|
|
log.Errorf("Unable to add block data to cache %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-10 22:53:28 +00:00
|
|
|
// safelyHandleHeader will recover and log any panic that occurs from the
|
|
|
|
// block
|
|
|
|
func safelyHandlePanic() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
log.WithFields(logrus.Fields{
|
|
|
|
"r": r,
|
|
|
|
}).Error("Panicked when handling data from ETH 1.0 Chain! Recovering...")
|
|
|
|
}
|
|
|
|
}
|
2019-03-12 04:05:55 +00:00
|
|
|
|
|
|
|
func (w *Web3Service) handleDelayTicker() {
|
|
|
|
defer safelyHandlePanic()
|
|
|
|
|
|
|
|
// If the last requested block has not changed,
|
|
|
|
// we do not request batched logs as this means there are no new
|
|
|
|
// logs for the powchain service to process.
|
|
|
|
if w.lastRequestedBlock.Cmp(w.blockHeight) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := w.requestBatchedLogs(); err != nil {
|
|
|
|
w.runError = err
|
|
|
|
log.Error(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// run subscribes to all the services for the ETH1.0 chain.
|
|
|
|
func (w *Web3Service) run(done <-chan struct{}) {
|
|
|
|
w.isRunning = true
|
|
|
|
w.runError = nil
|
|
|
|
if err := w.initDataFromContract(); err != nil {
|
|
|
|
log.Errorf("Unable to retrieve data from deposit contract %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
headSub, err := w.reader.SubscribeNewHead(w.ctx, w.headerChan)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Unable to subscribe to incoming ETH1.0 chain headers: %v", err)
|
|
|
|
w.runError = err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
header, err := w.blockFetcher.HeaderByNumber(w.ctx, nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Unable to retrieve latest ETH1.0 chain header: %v", err)
|
|
|
|
w.runError = err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.blockHeight = header.Number
|
|
|
|
w.blockHash = header.Hash()
|
|
|
|
|
|
|
|
// Only process logs if the chain start delay flag is not enabled.
|
|
|
|
if w.chainStartDelay == 0 {
|
|
|
|
if err := w.processPastLogs(); err != nil {
|
|
|
|
log.Errorf("Unable to process past logs %v", err)
|
|
|
|
w.runError = err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ticker := time.NewTicker(1 * time.Second)
|
|
|
|
defer headSub.Unsubscribe()
|
|
|
|
defer ticker.Stop()
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-done:
|
|
|
|
w.isRunning = false
|
|
|
|
w.runError = nil
|
|
|
|
log.Debug("ETH1.0 chain service context closed, exiting goroutine")
|
|
|
|
return
|
|
|
|
case w.runError = <-headSub.Err():
|
|
|
|
log.Debug("Unsubscribed to head events, exiting goroutine")
|
|
|
|
return
|
|
|
|
case header := <-w.headerChan:
|
|
|
|
w.processSubscribedHeaders(header)
|
|
|
|
case <-ticker.C:
|
|
|
|
w.handleDelayTicker()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|