2018-07-14 19:48:42 +00:00
|
|
|
package powchain
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"strings"
|
|
|
|
|
2018-07-19 16:31:50 +00:00
|
|
|
"github.com/ethereum/go-ethereum"
|
2018-07-14 19:48:42 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
gethTypes "github.com/ethereum/go-ethereum/core/types"
|
|
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
|
|
"github.com/ethereum/go-ethereum/rpc"
|
2018-07-28 19:53:02 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/types"
|
2018-07-21 17:51:18 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2018-07-14 19:48:42 +00:00
|
|
|
)
|
|
|
|
|
2018-07-21 17:51:18 +00:00
|
|
|
var log = logrus.WithField("prefix", "powchain")
|
|
|
|
|
2018-07-14 19:48:42 +00:00
|
|
|
// Web3Service fetches important information about the canonical
|
|
|
|
// Ethereum PoW chain via a web3 endpoint using an ethclient. The Random
|
|
|
|
// Beacon Chain requires synchronization with the PoW chain's current
|
|
|
|
// blockhash, block number, and access to logs within the
|
|
|
|
// Validator Registration Contract on the PoW chain to kick off the beacon
|
|
|
|
// chain's validator registration process.
|
|
|
|
type Web3Service struct {
|
2018-07-19 16:31:50 +00:00
|
|
|
ctx context.Context
|
|
|
|
cancel context.CancelFunc
|
2018-07-31 04:41:27 +00:00
|
|
|
client *ethclient.Client
|
2018-07-19 16:31:50 +00:00
|
|
|
headerChan chan *gethTypes.Header
|
|
|
|
logChan chan gethTypes.Log
|
|
|
|
pubKey string
|
|
|
|
endpoint string
|
|
|
|
validatorRegistered bool
|
|
|
|
vrcAddress common.Address
|
2018-08-07 17:56:28 +00:00
|
|
|
reader types.Reader
|
|
|
|
logger types.Logger
|
2018-07-19 16:31:50 +00:00
|
|
|
blockNumber *big.Int // the latest PoW chain blocknumber.
|
|
|
|
blockHash common.Hash // the latest PoW chain blockhash.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Web3ServiceConfig defines a config struct for web3 service to use through its life cycle.
|
|
|
|
type Web3ServiceConfig struct {
|
|
|
|
Endpoint string
|
|
|
|
Pubkey string
|
|
|
|
VrcAddr common.Address
|
2018-07-14 19:48:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewWeb3Service sets up a new instance with an ethclient when
|
|
|
|
// given a web3 endpoint as a string.
|
2018-07-19 16:31:50 +00:00
|
|
|
func NewWeb3Service(ctx context.Context, config *Web3ServiceConfig) (*Web3Service, error) {
|
|
|
|
if !strings.HasPrefix(config.Endpoint, "ws") && !strings.HasPrefix(config.Endpoint, "ipc") {
|
|
|
|
return nil, fmt.Errorf("web3service requires either an IPC or WebSocket endpoint, provided %s", config.Endpoint)
|
2018-07-14 19:48:42 +00:00
|
|
|
}
|
2018-07-31 04:41:27 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
2018-07-14 19:48:42 +00:00
|
|
|
return &Web3Service{
|
2018-07-31 04:41:27 +00:00
|
|
|
ctx: ctx,
|
2018-07-19 16:31:50 +00:00
|
|
|
cancel: cancel,
|
|
|
|
headerChan: make(chan *gethTypes.Header),
|
|
|
|
logChan: make(chan gethTypes.Log),
|
|
|
|
pubKey: config.Pubkey,
|
|
|
|
endpoint: config.Endpoint,
|
|
|
|
validatorRegistered: false,
|
|
|
|
blockNumber: nil,
|
|
|
|
blockHash: common.BytesToHash([]byte{}),
|
|
|
|
vrcAddress: config.VrcAddr,
|
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-07-14 19:48:42 +00:00
|
|
|
rpcClient, err := rpc.Dial(w.endpoint)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Cannot connect to PoW chain RPC client: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2018-07-31 04:41:27 +00:00
|
|
|
w.client = ethclient.NewClient(rpcClient)
|
2018-08-07 17:56:28 +00:00
|
|
|
w.reader, w.logger = w.client, w.client
|
|
|
|
go w.run(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 {
|
|
|
|
defer w.cancel()
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-08-07 17:56:28 +00:00
|
|
|
func (w *Web3Service) run(done <-chan struct{}) {
|
|
|
|
headSub, err := w.reader.SubscribeNewHead(w.ctx, w.headerChan)
|
2018-07-31 04:41:27 +00:00
|
|
|
if err != nil {
|
2018-07-14 19:48:42 +00:00
|
|
|
log.Errorf("Unable to subscribe to incoming PoW chain headers: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2018-07-19 16:31:50 +00:00
|
|
|
query := ethereum.FilterQuery{
|
|
|
|
Addresses: []common.Address{
|
|
|
|
w.vrcAddress,
|
|
|
|
},
|
|
|
|
}
|
2018-08-07 17:56:28 +00:00
|
|
|
logSub, err := w.logger.SubscribeFilterLogs(w.ctx, query, w.logChan)
|
2018-07-19 16:31:50 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Unable to query logs from VRC: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2018-07-31 04:41:27 +00:00
|
|
|
defer logSub.Unsubscribe()
|
|
|
|
defer headSub.Unsubscribe()
|
2018-08-07 17:56:28 +00:00
|
|
|
|
2018-07-19 16:31:50 +00:00
|
|
|
for {
|
|
|
|
select {
|
2018-08-07 17:56:28 +00:00
|
|
|
case <-done:
|
|
|
|
log.Debug("Powchain service context closed, exiting goroutine")
|
2018-07-31 04:41:27 +00:00
|
|
|
return
|
|
|
|
case <-headSub.Err():
|
|
|
|
log.Debug("Unsubscribed to head events, exiting goroutine")
|
|
|
|
return
|
|
|
|
case <-logSub.Err():
|
|
|
|
log.Debug("Unsubscribed to log events, exiting goroutine")
|
2018-07-19 16:31:50 +00:00
|
|
|
return
|
2018-07-27 18:41:00 +00:00
|
|
|
case header := <-w.headerChan:
|
|
|
|
w.blockNumber = header.Number
|
|
|
|
w.blockHash = header.Hash()
|
|
|
|
log.WithFields(logrus.Fields{
|
|
|
|
"blockNumber": w.blockNumber,
|
|
|
|
"blockHash": w.blockHash.Hex(),
|
|
|
|
}).Debug("Latest web3 chain event")
|
2018-07-19 16:31:50 +00:00
|
|
|
case VRClog := <-w.logChan:
|
|
|
|
// public key is the second topic from validatorRegistered log and strip off 0x
|
|
|
|
pubKeyLog := VRClog.Topics[1].Hex()[2:]
|
|
|
|
if pubKeyLog == w.pubKey {
|
2018-07-21 17:51:18 +00:00
|
|
|
log.WithFields(logrus.Fields{
|
|
|
|
"publicKey": pubKeyLog,
|
|
|
|
}).Info("Validator registered in VRC with public key")
|
2018-07-19 16:31:50 +00:00
|
|
|
w.validatorRegistered = true
|
2018-07-27 18:41:00 +00:00
|
|
|
w.logChan = nil
|
2018-07-19 16:31:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-14 19:48:42 +00:00
|
|
|
// LatestBlockNumber is a getter for blockNumber to make it read-only.
|
|
|
|
func (w *Web3Service) LatestBlockNumber() *big.Int {
|
|
|
|
return w.blockNumber
|
|
|
|
}
|
|
|
|
|
|
|
|
// LatestBlockHash is a getter for blockHash to make it read-only.
|
|
|
|
func (w *Web3Service) LatestBlockHash() common.Hash {
|
|
|
|
return w.blockHash
|
|
|
|
}
|
2018-07-19 16:31:50 +00:00
|
|
|
|
|
|
|
// ValidatorRegistered is a getter for validatorRegistered to make it read-only.
|
|
|
|
func (w *Web3Service) ValidatorRegistered() bool {
|
|
|
|
return w.validatorRegistered
|
|
|
|
}
|
2018-07-31 04:41:27 +00:00
|
|
|
|
|
|
|
// Client returns the underlying web3 client.
|
|
|
|
func (w *Web3Service) Client() types.POWChainClient {
|
|
|
|
return w.client
|
|
|
|
}
|