From aa2697f585d6dc941dd6814bd288184ebf58e13e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 26 Jun 2019 14:07:25 +0300 Subject: [PATCH] cmd: polish up the checkpoint admin CLI --- cmd/{registrar => checkpoint-admin}/common.go | 20 +- cmd/checkpoint-admin/exec.go | 335 ++++++++++++++++++ cmd/{registrar => checkpoint-admin}/main.go | 26 +- cmd/checkpoint-admin/status.go | 61 ++++ cmd/registrar/exec.go | 309 ---------------- cmd/registrar/query.go | 69 ---- 6 files changed, 413 insertions(+), 407 deletions(-) rename cmd/{registrar => checkpoint-admin}/common.go (89%) create mode 100644 cmd/checkpoint-admin/exec.go rename cmd/{registrar => checkpoint-admin}/main.go (87%) create mode 100644 cmd/checkpoint-admin/status.go delete mode 100644 cmd/registrar/exec.go delete mode 100644 cmd/registrar/query.go diff --git a/cmd/registrar/common.go b/cmd/checkpoint-admin/common.go similarity index 89% rename from cmd/registrar/common.go rename to cmd/checkpoint-admin/common.go index 592ab5390c..f57afb817c 100644 --- a/cmd/registrar/common.go +++ b/cmd/checkpoint-admin/common.go @@ -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. diff --git a/cmd/checkpoint-admin/exec.go b/cmd/checkpoint-admin/exec.go new file mode 100644 index 0000000000..457f876503 --- /dev/null +++ b/cmd/checkpoint-admin/exec.go @@ -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 . + +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 +} diff --git a/cmd/registrar/main.go b/cmd/checkpoint-admin/main.go similarity index 87% rename from cmd/registrar/main.go rename to cmd/checkpoint-admin/main.go index 1b5a1166be..3f26e4e108 100644 --- a/cmd/registrar/main.go +++ b/cmd/checkpoint-admin/main.go @@ -14,8 +14,8 @@ // You should have received a copy of the GNU General Public License // along with go-ethereum. If not, see . -// 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", } ) diff --git a/cmd/checkpoint-admin/status.go b/cmd/checkpoint-admin/status.go new file mode 100644 index 0000000000..c134ec090e --- /dev/null +++ b/cmd/checkpoint-admin/status.go @@ -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 . + +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 +} diff --git a/cmd/registrar/exec.go b/cmd/registrar/exec.go deleted file mode 100644 index 4859a07e70..0000000000 --- a/cmd/registrar/exec.go +++ /dev/null @@ -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 . - -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 -} diff --git a/cmd/registrar/query.go b/cmd/registrar/query.go deleted file mode 100644 index 7f085d0852..0000000000 --- a/cmd/registrar/query.go +++ /dev/null @@ -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 . - -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 -}