prysm-pulse/sharding/client.go
Preston Van Loon aa1a3b3405 working on testability with interfaces.
Former-commit-id: f8aa95e14151ae8d43d6781cdc64cced71a82e60 [formerly fbdb217576862532d2fe89f5a017d2641dfc6217]
Former-commit-id: 5879301b438746facd133f21ef6b299623b346c8
2018-02-07 21:00:20 -05:00

204 lines
5.9 KiB
Go

package sharding
//go:generate abigen --sol contracts/validator_manager.sol --pkg contracts --out contracts/validator_manager.go
import (
"context"
"fmt"
"io/ioutil"
"math/big"
"strings"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/sharding/contracts"
cli "gopkg.in/urfave/cli.v1"
)
const (
clientIdentifier = "geth" // Used to determine the ipc name.
)
// Client for sharding. Communicates to geth node via JSON RPC.
type Client struct {
endpoint string // Endpoint to JSON RPC
client *ethclient.Client // Ethereum RPC client.
keystore *keystore.KeyStore // Keystore containing the single signer
ctx *cli.Context // Command line context
vmc *contracts.VMC // The deployed validator management contract
}
// MakeShardingClient for interfacing with geth full node.
func MakeShardingClient(ctx *cli.Context) *Client {
path := node.DefaultDataDir()
if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
path = ctx.GlobalString(utils.DataDirFlag.Name)
}
endpoint := ctx.Args().First()
if endpoint == "" {
endpoint = fmt.Sprintf("%s/%s.ipc", path, clientIdentifier)
}
config := &node.Config{
DataDir: path,
}
scryptN, scryptP, keydir, err := config.AccountConfig()
if err != nil {
panic(err) // TODO(prestonvanloon): handle this
}
ks := keystore.NewKeyStore(keydir, scryptN, scryptP)
return &Client{
endpoint: endpoint,
keystore: ks,
ctx: ctx,
}
}
// Start the sharding client.
// * Connects to node.
// * Verifies or deploys the validator management contract.
func (c *Client) Start() error {
log.Info("Starting sharding client")
rpcClient, err := dialRPC(c.endpoint)
if err != nil {
return err
}
c.client = ethclient.NewClient(rpcClient)
defer rpcClient.Close()
if err := initVMC(c); err != nil {
return err
}
// Deposit 100ETH into the validator set in the VMC. Checks if account
// is already a validator in the VMC (in the case the client restarted).
// Once that's done we can subscribe to block headers.
//
// TODO: this function should store the validator's VMC index as a property
// in the client's struct
if err := joinValidatorSet(c); err != nil {
return err
}
// Listens to block headers from the geth node and if we are an eligible
// proposer, we fetch pending transactions and propose a collation
if err := subscribeBlockHeaders(c); err != nil {
return err
}
return nil
}
// Wait until sharding client is shutdown.
func (c *Client) Wait() {
log.Info("Sharding client has been shutdown...")
}
// WatchCollationHeaders checks the logs for add_header func calls
// and updates the head collation of the client. We can probably store
// this as a property of the client struct
func (c *Client) WatchCollationHeaders() {
}
// UnlockAccount will unlock the specified account using utils.PasswordFileFlag or empty string if unset.
func (c *Client) unlockAccount(account accounts.Account) error {
pass := ""
if c.ctx.GlobalIsSet(utils.PasswordFileFlag.Name) {
blob, err := ioutil.ReadFile(c.ctx.GlobalString(utils.PasswordFileFlag.Name))
if err != nil {
return fmt.Errorf("unable to read account password contents in file %s. %v", utils.PasswordFileFlag.Value, err)
}
// TODO: Use bufio.Scanner or other reader that doesn't include a trailing newline character.
pass = strings.Trim(string(blob), "\n") // Some text files end in new line, remove with strings.Trim.
}
return c.keystore.Unlock(account, pass)
}
func (c *Client) createTXOps(value *big.Int) (bind.TransactOpts, error) {
accounts := c.keystore.Accounts()
if len(accounts) == 0 {
return bind.TransactOpts{}, fmt.Errorf("no accounts found")
}
if err := c.unlockAccount(accounts[0]); err != nil {
return bind.TransactOpts{}, fmt.Errorf("unable to unlock account 0: %v", err)
}
if value.Cmp(big.NewInt(0)) == 0 {
return bind.TransactOpts{
From: accounts[0].Address,
Signer: func(signer types.Signer, addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
networkID, err := c.client.NetworkID(context.Background())
if err != nil {
return nil, fmt.Errorf("unable to fetch networkID: %v", err)
}
return c.keystore.SignTx(accounts[0], tx, networkID /* chainID */)
},
}, nil
}
return bind.TransactOpts{
From: accounts[0].Address,
Value: value,
Signer: func(signer types.Signer, addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
networkID, err := c.client.NetworkID(context.Background())
if err != nil {
return nil, fmt.Errorf("unable to fetch networkID: %v", err)
}
return c.keystore.SignTx(accounts[0], tx, networkID /* chainID */)
},
}, nil
}
// Account to use for sharding transations.
func (c *Client) Account() (*accounts.Account, error) {
accounts := c.keystore.Accounts()
if len(accounts) == 0 {
return nil, fmt.Errorf("no accounts found")
}
if err := c.unlockAccount(accounts[0]); err != nil {
return nil, fmt.Errorf("cannot unlock account. %v", err)
}
return &accounts[0], nil
}
// ChainReader for interacting with the chain.
func (c *Client) ChainReader() ethereum.ChainReader {
return ethereum.ChainReader(c.client)
}
// Client to interact with ethereum node.
func (c *Client) Client() *ethclient.Client {
return c.client
}
// VMCCaller to interact with the validator management contract.
func (c *Client) VMCCaller() *contracts.VMCCaller {
return &c.vmc.VMCCaller
}
// dialRPC endpoint to node.
func dialRPC(endpoint string) (*rpc.Client, error) {
if endpoint == "" {
endpoint = node.DefaultIPCEndpoint(clientIdentifier)
}
return rpc.Dial(endpoint)
}