cmd: polish up the checkpoint admin CLI

This commit is contained in:
Péter Szilágyi 2019-06-26 14:07:25 +03:00
parent 3525d60b37
commit aa2697f585
No known key found for this signature in database
GPG key ID: E9AE538CEDF8293D
6 changed files with 413 additions and 407 deletions

View file

@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/contracts/registrar" "github.com/ethereum/go-ethereum/contracts/registrar"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
@ -35,12 +34,10 @@ import (
// newClient creates a client with specified remote URL. // newClient creates a client with specified remote URL.
func newClient(ctx *cli.Context) *ethclient.Client { func newClient(ctx *cli.Context) *ethclient.Client {
url := ctx.GlobalString(nodeURLFlag.Name) client, err := ethclient.Dial(ctx.GlobalString(nodeURLFlag.Name))
client, err := ethclient.Dial(url)
if err != nil { if err != nil {
utils.Fatalf("Failed to setup ethereum client at url %s: %v", url, err) utils.Fatalf("Failed to connect to Ethereum node: %v", err)
} }
log.Info("Setup ethereum client", "URL", url)
return client return client
} }
@ -48,9 +45,8 @@ func newClient(ctx *cli.Context) *ethclient.Client {
func newRPCClient(url string) *rpc.Client { func newRPCClient(url string) *rpc.Client {
client, err := rpc.Dial(url) client, err := rpc.Dial(url)
if err != nil { if err != nil {
utils.Fatalf("Failed to setup rpc client at url %s: %v", url, err) utils.Fatalf("Failed to connect to Ethereum node: %v", err)
} }
log.Info("Setup rpc client", "URL", url)
return client return client
} }
@ -58,9 +54,8 @@ func newRPCClient(url string) *rpc.Client {
// rpc request. // rpc request.
func getContractAddr(client *rpc.Client) common.Address { func getContractAddr(client *rpc.Client) common.Address {
var addr string var addr string
err := client.Call(&addr, "les_getCheckpointContractAddress") if err := client.Call(&addr, "les_getCheckpointContractAddress"); err != nil {
if err != nil { utils.Fatalf("Failed to fetch checkpoint oracle address: %v", err)
utils.Fatalf("Failed to fetch checkpoint contract address, err %v", err)
} }
return common.HexToAddress(addr) return common.HexToAddress(addr)
} }
@ -104,7 +99,7 @@ func getCheckpoint(ctx *cli.Context, client *rpc.Client) *params.TrustedCheckpoi
// newContract creates a registrar contract instance with specified // newContract creates a registrar contract instance with specified
// contract address or the default contracts for mainnet or testnet. // contract address or the default contracts for mainnet or testnet.
func newContract(client *rpc.Client) *registrar.Registrar { func newContract(client *rpc.Client) (common.Address, *registrar.Registrar) {
addr := getContractAddr(client) addr := getContractAddr(client)
if addr == (common.Address{}) { if addr == (common.Address{}) {
utils.Fatalf("No specified registrar contract address") utils.Fatalf("No specified registrar contract address")
@ -113,8 +108,7 @@ func newContract(client *rpc.Client) *registrar.Registrar {
if err != nil { if err != nil {
utils.Fatalf("Failed to setup registrar contract %s: %v", addr, err) utils.Fatalf("Failed to setup registrar contract %s: %v", addr, err)
} }
log.Info("Setup registrar contract", "address", addr) return addr, contract
return contract
} }
// promptPassphrase prompts the user for a passphrase. // promptPassphrase prompts the user for a passphrase.

View file

@ -0,0 +1,335 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"math/big"
"strings"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/contracts/registrar"
"github.com/ethereum/go-ethereum/contracts/registrar/contract"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"gopkg.in/urfave/cli.v1"
)
var commandDeploy = cli.Command{
Name: "deploy",
Usage: "Deploy a new checkpoint oracle contract",
Flags: []cli.Flag{
nodeURLFlag,
clefURLFlag,
signersFlag,
thresholdFlag,
keyFileFlag,
utils.PasswordFileFlag,
},
Action: utils.MigrateFlags(deploy),
}
var commandSign = cli.Command{
Name: "sign",
Usage: "Sign the checkpoint with the specified key",
Flags: []cli.Flag{
nodeURLFlag,
clefURLFlag,
indexFlag,
hashFlag,
addressFlag,
keyFileFlag,
signersFlag,
utils.PasswordFileFlag,
},
Action: utils.MigrateFlags(sign),
}
var commandPublish = cli.Command{
Name: "publish",
Usage: "Publish a checkpoint into the oracle",
Flags: []cli.Flag{
nodeURLFlag,
indexFlag,
signersFlag,
signaturesFlag,
keyFileFlag,
utils.PasswordFileFlag,
},
Action: utils.MigrateFlags(publish),
}
// deploy deploys the checkpoint registrar contract.
//
// Note the network where the contract is deployed depends on
// the network where the connected node is located.
func deploy(ctx *cli.Context) error {
// Gather all the addresses that should be permitted to sign
var addrs []common.Address
for _, account := range strings.Split(ctx.String(signersFlag.Name), ",") {
if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
utils.Fatalf("Invalid account in --signers: '%s'", trimmed)
}
addrs = append(addrs, common.HexToAddress(account))
}
// Retrieve and validate the signing threshold
needed := ctx.Int(thresholdFlag.Name)
if needed == 0 || needed > len(addrs) {
utils.Fatalf("Invalid signature threshold %d", needed)
}
// Print a summary to ensure the user understands what they're signing
fmt.Printf("Deploying new checkpoint oracle:\n\n")
for i, addr := range addrs {
fmt.Printf("Admin %d => %s\n", i+1, addr.Hex())
}
fmt.Printf("\nSignatures needed to publish: %d\n", needed)
// Retrieve the private key, create an abigen transactor and an RPC client
transactor := bind.NewKeyedTransactor(getKey(ctx).PrivateKey)
client := newClient(ctx)
// Deploy the checkpoint oracle
oracle, tx, _, err := contract.DeployContract(transactor, client, addrs, big.NewInt(int64(params.CheckpointFrequency)),
big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(int64(needed)))
if err != nil {
utils.Fatalf("Failed to deploy checkpoint oracle %v", err)
}
log.Info("Deployed checkpoint oracle", "address", oracle, "tx", tx.Hash().Hex())
return nil
}
// sign creates the signature for specific checkpoint
// with local key. Only contract admins have the permission to
// sign checkpoint.
func sign(ctx *cli.Context) error {
var (
offline bool // The indicator whether we sign checkpoint by offline.
chash common.Hash
cindex uint64
address common.Address
node *rpc.Client
oracle *registrar.Registrar
)
if !ctx.GlobalIsSet(nodeURLFlag.Name) {
// Offline mode signing
offline = true
if !ctx.IsSet(hashFlag.Name) {
utils.Fatalf("Please specify the checkpoint hash (--hash) to sign in offline mode")
}
chash = common.HexToHash(ctx.String(hashFlag.Name))
if !ctx.IsSet(indexFlag.Name) {
utils.Fatalf("Please specify checkpoint index (--index) to sign in offline mode")
}
cindex = ctx.Uint64(indexFlag.Name)
if !ctx.IsSet(addressFlag.Name) {
utils.Fatalf("Please specify oracle address (--address) to sign in offline mode")
}
address = common.HexToAddress(ctx.String(addressFlag.Name))
} else {
// Interactive mode signing, retrieve the data from the remote node
node = newRPCClient(ctx.GlobalString(nodeURLFlag.Name))
checkpoint := getCheckpoint(ctx, node)
chash = checkpoint.Hash()
cindex = checkpoint.SectionIndex
address = getContractAddr(node)
// Check the validity of checkpoint
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFn()
head, err := ethclient.NewClient(node).HeaderByNumber(reqCtx, nil)
if err != nil {
return err
}
num := head.Number.Uint64()
if num < ((cindex+1)*params.CheckpointFrequency + params.CheckpointProcessConfirmations) {
utils.Fatalf("Invalid future checkpoint")
}
_, oracle = newContract(node)
latest, _, h, err := oracle.Contract().GetLatestCheckpoint(nil)
if err != nil {
return err
}
if cindex < latest {
utils.Fatalf("Checkpoint is too old")
}
if cindex == latest && (latest != 0 || h.Uint64() != 0) {
utils.Fatalf("Stale checkpoint, latest registered %d, given %d", latest, cindex)
}
}
var (
signature string
signer string
)
// isAdmin checks whether the specified signer is admin.
isAdmin := func(addr common.Address) error {
signers, err := oracle.Contract().GetAllAdmin(nil)
if err != nil {
return err
}
for _, s := range signers {
if s == addr {
return nil
}
}
return fmt.Errorf("signer %v is not the admin", addr.Hex())
}
// Print to the user the data thy are about to sign
fmt.Printf("Oracle => %s\n", address.Hex())
fmt.Printf("Index %4d => %s\n", cindex, chash.Hex())
switch {
case ctx.GlobalIsSet(clefURLFlag.Name):
// Sign checkpoint in clef mode.
signer = ctx.GlobalString(signersFlag.Name)
if !offline {
if err := isAdmin(common.HexToAddress(signer)); err != nil {
return err
}
}
clef := newRPCClient(ctx.GlobalString(clefURLFlag.Name))
p := make(map[string]string)
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, cindex)
p["address"] = address.Hex()
p["message"] = hexutil.Encode(append(buf, chash.Bytes()...))
if err := clef.Call(&signature, "account_signData", "data/validator", signer, p); err != nil {
utils.Fatalf("Failed to sign checkpoint, err %v", err)
}
case ctx.GlobalIsSet(keyFileFlag.Name):
// Sign checkpoint in raw private key file mode.
key := getKey(ctx)
signer = key.Address.Hex()
if !offline {
if err := isAdmin(key.Address); err != nil {
return err
}
}
sig, err := crypto.Sign(sighash(cindex, address, chash), key.PrivateKey)
if err != nil {
utils.Fatalf("Failed to sign checkpoint, err %v", err)
}
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
signature = common.Bytes2Hex(sig)
default:
utils.Fatalf("Please specify clef URL or private key file path to sign checkpoint")
}
fmt.Printf("Signer => %s\n", signer)
fmt.Printf("Signature => %s\n", signature)
return nil
}
// sighash calculates the hash of the data to sign for the checkpoint oracle.
func sighash(index uint64, oracle common.Address, hash common.Hash) []byte {
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, index)
data := append([]byte{0x19, 0x00}, append(oracle[:], append(buf, hash[:]...)...)...)
return crypto.Keccak256(data)
}
// ecrecover calculates the sender address from a sighash and signature combo.
func ecrecover(sighash []byte, sig []byte) common.Address {
sig[64] -= 27
defer func() { sig[64] += 27 }()
signer, err := crypto.SigToPub(sighash, sig)
if err != nil {
utils.Fatalf("Failed to recover sender from signature %x: %v", sig, err)
}
return crypto.PubkeyToAddress(*signer)
}
// publish registers the specified checkpoint which generated by connected node
// with a authorised private key.
func publish(ctx *cli.Context) error {
// Print the checkpoint oracle's current status to make sure we're interacting
// with the correct network and contract.
status(ctx)
// Gather the signatures from the CLI
var sigs [][]byte
for _, sig := range strings.Split(ctx.String(signaturesFlag.Name), ",") {
trimmed := strings.TrimPrefix(strings.TrimSpace(sig), "0x")
if len(trimmed) != 130 {
utils.Fatalf("Invalid signature in --signature: '%s'", trimmed)
} else {
sigs = append(sigs, common.Hex2Bytes(trimmed))
}
}
// Retrieve the checkpoint we want to sign to sort the signatures
var (
client = newRPCClient(ctx.GlobalString(nodeURLFlag.Name))
addr, oracle = newContract(client)
checkpoint = getCheckpoint(ctx, client)
sighash = sighash(checkpoint.SectionIndex, addr, checkpoint.Hash())
)
for i := 0; i < len(sigs); i++ {
for j := i + 1; j < len(sigs); j++ {
signerA := ecrecover(sighash, sigs[i])
signerB := ecrecover(sighash, sigs[j])
if bytes.Compare(signerA.Bytes(), signerB.Bytes()) > 0 {
sigs[i], sigs[j] = sigs[j], sigs[i]
}
}
}
// Retrieve recent header info to protect replay attack
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFn()
head, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, nil)
if err != nil {
return err
}
num := head.Number.Uint64()
recent, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, big.NewInt(int64(num-128)))
if err != nil {
return err
}
// Print a summary of the operation that's going to be performed
fmt.Printf("Publishing %d => %s:\n\n", checkpoint.SectionIndex, checkpoint.Hash().Hex())
for i, sig := range sigs {
fmt.Printf("Signer %d => %s\n", i+1, ecrecover(sighash, sig).Hex())
}
fmt.Println()
fmt.Printf("Sentry number => %d\nSentry hash => %s\n", recent.Number, recent.Hash().Hex())
// Publish the checkpoint into the oracle
tx, err := oracle.RegisterCheckpoint(getKey(ctx).PrivateKey, checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs)
if err != nil {
utils.Fatalf("Register contract failed %v", err)
}
log.Info("Successfully registered checkpoint", "tx", tx.Hash().Hex())
return nil
}

View file

@ -14,8 +14,8 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// registrar is a utility that can be used to query checkpoint information // checkpoint-admin is a utility that can be used to query checkpoint information
// and register stable checkpoint into the contract. // and register stable checkpoints into an oracle contract.
package main package main
import ( import (
@ -52,22 +52,16 @@ var app *cli.App
func init() { func init() {
app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool") app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool")
app.Commands = []cli.Command{ app.Commands = []cli.Command{
commandQueryAdmin, commandStatus,
commandQueryCheckpoint, commandDeploy,
commandDeployContract, commandSign,
commandSignCheckpoint, commandPublish,
commandRegisterCheckpoint,
} }
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
indexFlag,
hashFlag,
addressFlag, addressFlag,
thresholdFlag,
keyFileFlag, keyFileFlag,
nodeURLFlag, nodeURLFlag,
clefURLFlag, clefURLFlag,
signerFlag,
signatureFlag,
utils.PasswordFileFlag, utils.PasswordFileFlag,
} }
cli.CommandHelpTemplate = commandHelperTemplate cli.CommandHelpTemplate = commandHelperTemplate
@ -105,12 +99,12 @@ var (
Value: "http://localhost:8550", Value: "http://localhost:8550",
Usage: "The rpc endpoint of clef", Usage: "The rpc endpoint of clef",
} }
signerFlag = cli.StringFlag{ signersFlag = cli.StringFlag{
Name: "signer", Name: "signers",
Usage: "Comma separated accounts of trusted checkpoint signers", Usage: "Comma separated accounts of trusted checkpoint signers",
} }
signatureFlag = cli.StringFlag{ signaturesFlag = cli.StringFlag{
Name: "signature", Name: "signatures",
Usage: "Comma separated checkpoint signatures to submit", Usage: "Comma separated checkpoint signatures to submit",
} }
) )

View file

@ -0,0 +1,61 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"gopkg.in/urfave/cli.v1"
)
var commandStatus = cli.Command{
Name: "status",
Usage: "Fetches the signers and checkpoint status of the oracle contract",
Flags: []cli.Flag{
nodeURLFlag,
},
Action: utils.MigrateFlags(status),
}
// status fetches the admin list of specified registrar contract.
func status(ctx *cli.Context) error {
// Create a wrapper around the checkpoint oracle contract
addr, oracle := newContract(newRPCClient(ctx.GlobalString(nodeURLFlag.Name)))
fmt.Printf("Oracle => %s\n", addr.Hex())
fmt.Println()
// Retrieve the list of authorized signers (admins)
admins, err := oracle.Contract().GetAllAdmin(nil)
if err != nil {
return err
}
for i, admin := range admins {
fmt.Printf("Admin %d => %s\n", i+1, admin.Hex())
}
fmt.Println()
// Retrieve the latest checkpoint
index, checkpoint, height, err := oracle.Contract().GetLatestCheckpoint(nil)
if err != nil {
return err
}
fmt.Printf("Checkpoint (published at #%d) %d => %s\n", height, index, common.Hash(checkpoint).Hex())
return nil
}

View file

@ -1,309 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"math/big"
"sort"
"strings"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/contracts/registrar"
"github.com/ethereum/go-ethereum/contracts/registrar/contract"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"gopkg.in/urfave/cli.v1"
)
var commandDeployContract = cli.Command{
Name: "deploy",
Usage: "Deploy a registrar contract with specified trusted signers.",
Flags: []cli.Flag{
signerFlag,
thresholdFlag,
nodeURLFlag,
keyFileFlag,
utils.PasswordFileFlag,
},
Action: utils.MigrateFlags(deployContract),
}
var commandSignCheckpoint = cli.Command{
Name: "sign",
Usage: "Sign the checkpoint with the specified key",
Flags: []cli.Flag{
nodeURLFlag,
clefURLFlag,
indexFlag,
hashFlag,
addressFlag,
keyFileFlag,
signerFlag,
utils.PasswordFileFlag,
},
Action: utils.MigrateFlags(signCheckpoint),
}
var commandRegisterCheckpoint = cli.Command{
Name: "register",
Usage: "Register specified checkpoint into contract",
Flags: []cli.Flag{
nodeURLFlag,
indexFlag,
signerFlag,
signatureFlag,
keyFileFlag,
utils.PasswordFileFlag,
},
Action: utils.MigrateFlags(registerCheckpoint),
}
// deployContract deploys the checkpoint registrar contract.
//
// Note the network where the contract is deployed depends on
// the network where the connected node is located.
func deployContract(ctx *cli.Context) error {
var addrs []common.Address
signers := strings.Split(ctx.GlobalString(signerFlag.Name), ",")
for _, account := range signers {
if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
utils.Fatalf("Invalid account in --signer: %s", trimmed)
} else {
addrs = append(addrs, common.HexToAddress(account))
}
}
t := ctx.GlobalInt64(thresholdFlag.Name)
if t == 0 || int(t) > len(signers) {
utils.Fatalf("Invalid signature threshold %d", t)
}
addr, tx, _, err := contract.DeployContract(bind.NewKeyedTransactor(getKey(ctx).PrivateKey), newClient(ctx), addrs, big.NewInt(int64(params.CheckpointFrequency)),
big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(t))
if err != nil {
utils.Fatalf("Failed to deploy registrar contract %v", err)
}
log.Info("Deployed registrar contract successfully", "address", addr, "tx", tx.Hash())
return nil
}
// signCheckpoint creates the signature for specific checkpoint
// with local key. Only contract admins have the permission to
// sign checkpoint.
func signCheckpoint(ctx *cli.Context) error {
var (
offline bool // The indicator whether we sign checkpoint by offline.
chash common.Hash
cindex uint64
address common.Address
node *rpc.Client
c *registrar.Registrar
)
if !ctx.GlobalIsSet(nodeURLFlag.Name) {
// Offline mode signing
offline = true
if !ctx.GlobalIsSet(hashFlag.Name) {
utils.Fatalf("Please specify checkpoint hash for offline mode signing")
}
chash = common.HexToHash(ctx.GlobalString(hashFlag.Name))
if !ctx.GlobalIsSet(indexFlag.Name) {
utils.Fatalf("Please specify checkpoint index for offline mode signing")
}
cindex = ctx.GlobalUint64(indexFlag.Name)
if !ctx.GlobalIsSet(addressFlag.Name) {
utils.Fatalf("Please specify contract address for offline mode signing")
}
address = common.HexToAddress(ctx.GlobalString(addressFlag.Name))
} else {
// Interactive mode signing
node = newRPCClient(ctx.GlobalString(nodeURLFlag.Name))
checkpoint := getCheckpoint(ctx, node)
chash = checkpoint.Hash()
cindex = checkpoint.SectionIndex
address = getContractAddr(node)
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFn()
// Check the validity of checkpoint.
head, err := ethclient.NewClient(node).HeaderByNumber(reqCtx, nil)
if err != nil {
return err
}
num := head.Number.Uint64()
if num < ((cindex+1)*params.CheckpointFrequency + params.CheckpointProcessConfirmations) {
utils.Fatalf("Invalid future checkpoint")
}
c = newContract(node)
latest, _, h, err := c.Contract().GetLatestCheckpoint(nil)
if err != nil {
return err
}
if cindex < latest {
utils.Fatalf("Checkpoint is too old")
}
if cindex == latest && (latest != 0 || h.Uint64() != 0) {
utils.Fatalf("Stale checkpoint, latest registered %d, given %d", latest, cindex)
}
}
var (
signature string
signer string
)
// isAdmin checks whether the specified signer is admin.
isAdmin := func(addr common.Address) error {
signers, err := c.Contract().GetAllAdmin(nil)
if err != nil {
return err
}
for _, s := range signers {
if s == addr {
return nil
}
}
return fmt.Errorf("signer %v is not the admin", addr.Hex())
}
switch {
case ctx.GlobalIsSet(clefURLFlag.Name):
// Sign checkpoint in clef mode.
signer = ctx.GlobalString(signerFlag.Name)
if !offline {
if err := isAdmin(common.HexToAddress(signer)); err != nil {
return err
}
}
clef := newRPCClient(ctx.GlobalString(clefURLFlag.Name))
p := make(map[string]string)
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, cindex)
p["address"] = address.Hex()
p["message"] = hexutil.Encode(append(buf, chash.Bytes()...))
if err := clef.Call(&signature, "account_signData", "data/validator", signer, p); err != nil {
utils.Fatalf("Failed to sign checkpoint, err %v", err)
}
case ctx.GlobalIsSet(keyFileFlag.Name):
// Sign checkpoint in raw private key file mode.
key := getKey(ctx)
signer = key.Address.Hex()
if !offline {
if err := isAdmin(key.Address); err != nil {
return err
}
}
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, cindex)
data := append([]byte{0x19, 0x00}, append(address[:], append(buf, chash.Bytes()...)...)...)
sig, err := crypto.Sign(crypto.Keccak256(data), key.PrivateKey)
if err != nil {
utils.Fatalf("Failed to sign checkpoint, err %v", err)
}
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
signature = common.Bytes2Hex(sig)
default:
utils.Fatalf("Please specify clef URL or private key file path to sign checkpoint")
}
log.Info("Successfully signed checkpoint", "index", cindex, "hash", chash.Hex(), "address", address.Hex(), "signer", signer, "signature", signature)
return nil
}
type Signer struct {
addr common.Address
sig []byte
}
type Signers []Signer
func (s Signers) Len() int { return len(s) }
func (s Signers) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s Signers) Less(i, j int) bool { return bytes.Compare(s[i].addr.Bytes(), s[j].addr.Bytes()) < 0 }
// registerCheckpoint registers the specified checkpoint which generated by connected
// node with a authorised private key.
func registerCheckpoint(ctx *cli.Context) error {
var (
addrs []common.Address
sigs [][]byte
signers Signers
)
signerStrs := strings.Split(ctx.GlobalString(signerFlag.Name), ",")
for _, account := range signerStrs {
if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
utils.Fatalf("Invalid account in --signer: %s", trimmed)
} else {
addrs = append(addrs, common.HexToAddress(account))
}
}
sigStrs := strings.Split(ctx.GlobalString(signatureFlag.Name), ",")
for _, sig := range sigStrs {
trimmed := strings.TrimPrefix(strings.TrimSpace(sig), "0x")
if len(trimmed) != 130 {
utils.Fatalf("Invalid signature in --signature: %s", trimmed)
} else {
sigs = append(sigs, common.Hex2Bytes(trimmed))
}
}
if len(addrs) != len(sigs) {
utils.Fatalf("The length of signer and corresponding signature mismatch")
}
for i := 0; i < len(addrs); i++ {
signers = append(signers, Signer{addr: addrs[i], sig: sigs[i]})
}
sort.Sort(signers)
sigs = sigs[:0]
for i := 0; i < len(signers); i++ {
sigs = append(sigs, signers[i].sig)
}
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFn()
// Retrieve recent header info to protect replay attack.
node := newRPCClient(ctx.GlobalString(nodeURLFlag.Name))
head, err := ethclient.NewClient(node).HeaderByNumber(reqCtx, nil)
if err != nil {
return err
}
num := head.Number.Uint64()
recent, err := ethclient.NewClient(node).HeaderByNumber(reqCtx, big.NewInt(int64(num-128)))
if err != nil {
return err
}
var (
c = newContract(node)
key = getKey(ctx)
checkpoint = getCheckpoint(ctx, node)
)
tx, err := c.RegisterCheckpoint(key.PrivateKey, checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs)
if err != nil {
utils.Fatalf("Register contract failed %v", err)
}
log.Info("Successfully registered checkpoint", "index", checkpoint.SectionIndex, "hash", checkpoint.Hash(), "signumber", len(signers), "txhash", tx.Hash())
return nil
}

View file

@ -1,69 +0,0 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"gopkg.in/urfave/cli.v1"
)
var commandQueryAdmin = cli.Command{
Name: "queryadmin",
Usage: "Fetch the admin list of specified registrar contract",
Flags: []cli.Flag{
nodeURLFlag,
},
Action: utils.MigrateFlags(queryAdmin),
}
var commandQueryCheckpoint = cli.Command{
Name: "querycheckpoint",
Usage: "Fetch the latest registered checkpoint in the registrar contract",
Flags: []cli.Flag{
nodeURLFlag,
},
Action: utils.MigrateFlags(queryCheckpoint),
}
// queryAdmin fetches the admin list of specified registrar contract.
func queryAdmin(ctx *cli.Context) error {
contract := newContract(newRPCClient(ctx.GlobalString(nodeURLFlag.Name)))
admins, err := contract.Contract().GetAllAdmin(nil)
if err != nil {
return err
}
fmt.Println("Total admin number", len(admins))
for i, admin := range admins {
fmt.Printf("Admin %d => %s\n", i+1, admin.Hex())
}
return nil
}
// queryCheckpoint fetches the checkpoint hash with specified index from
// registrar contract.
func queryCheckpoint(ctx *cli.Context) error {
contract := newContract(newRPCClient(ctx.GlobalString(nodeURLFlag.Name)))
index, checkpoint, height, err := contract.Contract().GetLatestCheckpoint(nil)
if err != nil {
return err
}
fmt.Printf("Latest checkpoint(registered at height #%d) %d => %s\n", height, index, common.Hash(checkpoint).Hex())
return nil
}