mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
cmd: polish up the checkpoint admin CLI
This commit is contained in:
parent
3525d60b37
commit
aa2697f585
6 changed files with 413 additions and 407 deletions
|
|
@ -27,7 +27,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/console"
|
||||
"github.com/ethereum/go-ethereum/contracts/registrar"
|
||||
"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"
|
||||
|
|
@ -35,12 +34,10 @@ import (
|
|||
|
||||
// newClient creates a client with specified remote URL.
|
||||
func newClient(ctx *cli.Context) *ethclient.Client {
|
||||
url := ctx.GlobalString(nodeURLFlag.Name)
|
||||
client, err := ethclient.Dial(url)
|
||||
client, err := ethclient.Dial(ctx.GlobalString(nodeURLFlag.Name))
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -48,9 +45,8 @@ func newClient(ctx *cli.Context) *ethclient.Client {
|
|||
func newRPCClient(url string) *rpc.Client {
|
||||
client, err := rpc.Dial(url)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -58,9 +54,8 @@ func newRPCClient(url string) *rpc.Client {
|
|||
// rpc request.
|
||||
func getContractAddr(client *rpc.Client) common.Address {
|
||||
var addr string
|
||||
err := client.Call(&addr, "les_getCheckpointContractAddress")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to fetch checkpoint contract address, err %v", err)
|
||||
if err := client.Call(&addr, "les_getCheckpointContractAddress"); err != nil {
|
||||
utils.Fatalf("Failed to fetch checkpoint oracle address: %v", err)
|
||||
}
|
||||
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
|
||||
// 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)
|
||||
if addr == (common.Address{}) {
|
||||
utils.Fatalf("No specified registrar contract address")
|
||||
|
|
@ -113,8 +108,7 @@ func newContract(client *rpc.Client) *registrar.Registrar {
|
|||
if err != nil {
|
||||
utils.Fatalf("Failed to setup registrar contract %s: %v", addr, err)
|
||||
}
|
||||
log.Info("Setup registrar contract", "address", addr)
|
||||
return contract
|
||||
return addr, contract
|
||||
}
|
||||
|
||||
// promptPassphrase prompts the user for a passphrase.
|
||||
335
cmd/checkpoint-admin/exec.go
Normal file
335
cmd/checkpoint-admin/exec.go
Normal 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
|
||||
}
|
||||
|
|
@ -14,8 +14,8 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// registrar is a utility that can be used to query checkpoint information
|
||||
// and register stable checkpoint into the contract.
|
||||
// checkpoint-admin is a utility that can be used to query checkpoint information
|
||||
// and register stable checkpoints into an oracle contract.
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
@ -52,22 +52,16 @@ var app *cli.App
|
|||
func init() {
|
||||
app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool")
|
||||
app.Commands = []cli.Command{
|
||||
commandQueryAdmin,
|
||||
commandQueryCheckpoint,
|
||||
commandDeployContract,
|
||||
commandSignCheckpoint,
|
||||
commandRegisterCheckpoint,
|
||||
commandStatus,
|
||||
commandDeploy,
|
||||
commandSign,
|
||||
commandPublish,
|
||||
}
|
||||
app.Flags = []cli.Flag{
|
||||
indexFlag,
|
||||
hashFlag,
|
||||
addressFlag,
|
||||
thresholdFlag,
|
||||
keyFileFlag,
|
||||
nodeURLFlag,
|
||||
clefURLFlag,
|
||||
signerFlag,
|
||||
signatureFlag,
|
||||
utils.PasswordFileFlag,
|
||||
}
|
||||
cli.CommandHelpTemplate = commandHelperTemplate
|
||||
|
|
@ -105,12 +99,12 @@ var (
|
|||
Value: "http://localhost:8550",
|
||||
Usage: "The rpc endpoint of clef",
|
||||
}
|
||||
signerFlag = cli.StringFlag{
|
||||
Name: "signer",
|
||||
signersFlag = cli.StringFlag{
|
||||
Name: "signers",
|
||||
Usage: "Comma separated accounts of trusted checkpoint signers",
|
||||
}
|
||||
signatureFlag = cli.StringFlag{
|
||||
Name: "signature",
|
||||
signaturesFlag = cli.StringFlag{
|
||||
Name: "signatures",
|
||||
Usage: "Comma separated checkpoint signatures to submit",
|
||||
}
|
||||
)
|
||||
61
cmd/checkpoint-admin/status.go
Normal file
61
cmd/checkpoint-admin/status.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in a new issue