github.com/ethxdao/go-ethereum@v0.0.0-20221218102228-5ae34a9cc189/cmd/checkpoint-admin/exec.go (about)

     1  // Copyright 2019 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/binary"
    23  	"fmt"
    24  	"math/big"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/ethxdao/go-ethereum/accounts"
    29  	"github.com/ethxdao/go-ethereum/cmd/utils"
    30  	"github.com/ethxdao/go-ethereum/common"
    31  	"github.com/ethxdao/go-ethereum/common/hexutil"
    32  	"github.com/ethxdao/go-ethereum/contracts/checkpointoracle"
    33  	"github.com/ethxdao/go-ethereum/contracts/checkpointoracle/contract"
    34  	"github.com/ethxdao/go-ethereum/crypto"
    35  	"github.com/ethxdao/go-ethereum/ethclient"
    36  	"github.com/ethxdao/go-ethereum/log"
    37  	"github.com/ethxdao/go-ethereum/params"
    38  	"github.com/ethxdao/go-ethereum/rpc"
    39  )
    40  
    41  var commandDeploy = &cli.Command{
    42  	Name:  "deploy",
    43  	Usage: "Deploy a new checkpoint oracle contract",
    44  	Flags: []cli.Flag{
    45  		nodeURLFlag,
    46  		clefURLFlag,
    47  		signerFlag,
    48  		signersFlag,
    49  		thresholdFlag,
    50  	},
    51  	Action: deploy,
    52  }
    53  
    54  var commandSign = &cli.Command{
    55  	Name:  "sign",
    56  	Usage: "Sign the checkpoint with the specified key",
    57  	Flags: []cli.Flag{
    58  		nodeURLFlag,
    59  		clefURLFlag,
    60  		signerFlag,
    61  		indexFlag,
    62  		hashFlag,
    63  		oracleFlag,
    64  	},
    65  	Action: sign,
    66  }
    67  
    68  var commandPublish = &cli.Command{
    69  	Name:  "publish",
    70  	Usage: "Publish a checkpoint into the oracle",
    71  	Flags: []cli.Flag{
    72  		nodeURLFlag,
    73  		clefURLFlag,
    74  		signerFlag,
    75  		indexFlag,
    76  		signaturesFlag,
    77  	},
    78  	Action: publish,
    79  }
    80  
    81  // deploy deploys the checkpoint registrar contract.
    82  //
    83  // Note the network where the contract is deployed depends on
    84  // the network where the connected node is located.
    85  func deploy(ctx *cli.Context) error {
    86  	// Gather all the addresses that should be permitted to sign
    87  	var addrs []common.Address
    88  	for _, account := range strings.Split(ctx.String(signersFlag.Name), ",") {
    89  		if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
    90  			utils.Fatalf("Invalid account in --signers: '%s'", trimmed)
    91  		}
    92  		addrs = append(addrs, common.HexToAddress(account))
    93  	}
    94  	// Retrieve and validate the signing threshold
    95  	needed := ctx.Int(thresholdFlag.Name)
    96  	if needed == 0 || needed > len(addrs) {
    97  		utils.Fatalf("Invalid signature threshold %d", needed)
    98  	}
    99  	// Print a summary to ensure the user understands what they're signing
   100  	fmt.Printf("Deploying new checkpoint oracle:\n\n")
   101  	for i, addr := range addrs {
   102  		fmt.Printf("Admin %d => %s\n", i+1, addr.Hex())
   103  	}
   104  	fmt.Printf("\nSignatures needed to publish: %d\n", needed)
   105  
   106  	// setup clef signer, create an abigen transactor and an RPC client
   107  	transactor, client := newClefSigner(ctx), newClient(ctx)
   108  
   109  	// Deploy the checkpoint oracle
   110  	fmt.Println("Sending deploy request to Clef...")
   111  	oracle, tx, _, err := contract.DeployCheckpointOracle(transactor, client, addrs, big.NewInt(int64(params.CheckpointFrequency)),
   112  		big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(int64(needed)))
   113  	if err != nil {
   114  		utils.Fatalf("Failed to deploy checkpoint oracle %v", err)
   115  	}
   116  	log.Info("Deployed checkpoint oracle", "address", oracle, "tx", tx.Hash().Hex())
   117  
   118  	return nil
   119  }
   120  
   121  // sign creates the signature for specific checkpoint
   122  // with local key. Only contract admins have the permission to
   123  // sign checkpoint.
   124  func sign(ctx *cli.Context) error {
   125  	var (
   126  		offline bool // The indicator whether we sign checkpoint by offline.
   127  		chash   common.Hash
   128  		cindex  uint64
   129  		address common.Address
   130  
   131  		node   *rpc.Client
   132  		oracle *checkpointoracle.CheckpointOracle
   133  	)
   134  	if !ctx.IsSet(nodeURLFlag.Name) {
   135  		// Offline mode signing
   136  		offline = true
   137  		if !ctx.IsSet(hashFlag.Name) {
   138  			utils.Fatalf("Please specify the checkpoint hash (--hash) to sign in offline mode")
   139  		}
   140  		chash = common.HexToHash(ctx.String(hashFlag.Name))
   141  
   142  		if !ctx.IsSet(indexFlag.Name) {
   143  			utils.Fatalf("Please specify checkpoint index (--index) to sign in offline mode")
   144  		}
   145  		cindex = ctx.Uint64(indexFlag.Name)
   146  
   147  		if !ctx.IsSet(oracleFlag.Name) {
   148  			utils.Fatalf("Please specify oracle address (--oracle) to sign in offline mode")
   149  		}
   150  		address = common.HexToAddress(ctx.String(oracleFlag.Name))
   151  	} else {
   152  		// Interactive mode signing, retrieve the data from the remote node
   153  		node = newRPCClient(ctx.String(nodeURLFlag.Name))
   154  
   155  		checkpoint := getCheckpoint(ctx, node)
   156  		chash, cindex, address = checkpoint.Hash(), checkpoint.SectionIndex, getContractAddr(node)
   157  
   158  		// Check the validity of checkpoint
   159  		reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
   160  		defer cancelFn()
   161  
   162  		head, err := ethclient.NewClient(node).HeaderByNumber(reqCtx, nil)
   163  		if err != nil {
   164  			return err
   165  		}
   166  		num := head.Number.Uint64()
   167  		if num < ((cindex+1)*params.CheckpointFrequency + params.CheckpointProcessConfirmations) {
   168  			utils.Fatalf("Invalid future checkpoint")
   169  		}
   170  		_, oracle = newContract(node)
   171  		latest, _, h, err := oracle.Contract().GetLatestCheckpoint(nil)
   172  		if err != nil {
   173  			return err
   174  		}
   175  		if cindex < latest {
   176  			utils.Fatalf("Checkpoint is too old")
   177  		}
   178  		if cindex == latest && (latest != 0 || h.Uint64() != 0) {
   179  			utils.Fatalf("Stale checkpoint, latest registered %d, given %d", latest, cindex)
   180  		}
   181  	}
   182  	var (
   183  		signature string
   184  		signer    string
   185  	)
   186  	// isAdmin checks whether the specified signer is admin.
   187  	isAdmin := func(addr common.Address) error {
   188  		signers, err := oracle.Contract().GetAllAdmin(nil)
   189  		if err != nil {
   190  			return err
   191  		}
   192  		for _, s := range signers {
   193  			if s == addr {
   194  				return nil
   195  			}
   196  		}
   197  		return fmt.Errorf("signer %v is not the admin", addr.Hex())
   198  	}
   199  	// Print to the user the data thy are about to sign
   200  	fmt.Printf("Oracle     => %s\n", address.Hex())
   201  	fmt.Printf("Index %4d => %s\n", cindex, chash.Hex())
   202  
   203  	// Sign checkpoint in clef mode.
   204  	signer = ctx.String(signerFlag.Name)
   205  
   206  	if !offline {
   207  		if err := isAdmin(common.HexToAddress(signer)); err != nil {
   208  			return err
   209  		}
   210  	}
   211  	clef := newRPCClient(ctx.String(clefURLFlag.Name))
   212  	p := make(map[string]string)
   213  	buf := make([]byte, 8)
   214  	binary.BigEndian.PutUint64(buf, cindex)
   215  	p["address"] = address.Hex()
   216  	p["message"] = hexutil.Encode(append(buf, chash.Bytes()...))
   217  
   218  	fmt.Println("Sending signing request to Clef...")
   219  	if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil {
   220  		utils.Fatalf("Failed to sign checkpoint, err %v", err)
   221  	}
   222  	fmt.Printf("Signer     => %s\n", signer)
   223  	fmt.Printf("Signature  => %s\n", signature)
   224  	return nil
   225  }
   226  
   227  // sighash calculates the hash of the data to sign for the checkpoint oracle.
   228  func sighash(index uint64, oracle common.Address, hash common.Hash) []byte {
   229  	buf := make([]byte, 8)
   230  	binary.BigEndian.PutUint64(buf, index)
   231  
   232  	data := append([]byte{0x19, 0x00}, append(oracle[:], append(buf, hash[:]...)...)...)
   233  	return crypto.Keccak256(data)
   234  }
   235  
   236  // ecrecover calculates the sender address from a sighash and signature combo.
   237  func ecrecover(sighash []byte, sig []byte) common.Address {
   238  	sig[64] -= 27
   239  	defer func() { sig[64] += 27 }()
   240  
   241  	signer, err := crypto.SigToPub(sighash, sig)
   242  	if err != nil {
   243  		utils.Fatalf("Failed to recover sender from signature %x: %v", sig, err)
   244  	}
   245  	return crypto.PubkeyToAddress(*signer)
   246  }
   247  
   248  // publish registers the specified checkpoint which generated by connected node
   249  // with a authorised private key.
   250  func publish(ctx *cli.Context) error {
   251  	// Print the checkpoint oracle's current status to make sure we're interacting
   252  	// with the correct network and contract.
   253  	status(ctx)
   254  
   255  	// Gather the signatures from the CLI
   256  	var sigs [][]byte
   257  	for _, sig := range strings.Split(ctx.String(signaturesFlag.Name), ",") {
   258  		trimmed := strings.TrimPrefix(strings.TrimSpace(sig), "0x")
   259  		if len(trimmed) != 130 {
   260  			utils.Fatalf("Invalid signature in --signature: '%s'", trimmed)
   261  		} else {
   262  			sigs = append(sigs, common.Hex2Bytes(trimmed))
   263  		}
   264  	}
   265  	// Retrieve the checkpoint we want to sign to sort the signatures
   266  	var (
   267  		client       = newRPCClient(ctx.String(nodeURLFlag.Name))
   268  		addr, oracle = newContract(client)
   269  		checkpoint   = getCheckpoint(ctx, client)
   270  		sighash      = sighash(checkpoint.SectionIndex, addr, checkpoint.Hash())
   271  	)
   272  	for i := 0; i < len(sigs); i++ {
   273  		for j := i + 1; j < len(sigs); j++ {
   274  			signerA := ecrecover(sighash, sigs[i])
   275  			signerB := ecrecover(sighash, sigs[j])
   276  			if bytes.Compare(signerA.Bytes(), signerB.Bytes()) > 0 {
   277  				sigs[i], sigs[j] = sigs[j], sigs[i]
   278  			}
   279  		}
   280  	}
   281  	// Retrieve recent header info to protect replay attack
   282  	reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
   283  	defer cancelFn()
   284  
   285  	head, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, nil)
   286  	if err != nil {
   287  		return err
   288  	}
   289  	num := head.Number.Uint64()
   290  	recent, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, big.NewInt(int64(num-128)))
   291  	if err != nil {
   292  		return err
   293  	}
   294  	// Print a summary of the operation that's going to be performed
   295  	fmt.Printf("Publishing %d => %s:\n\n", checkpoint.SectionIndex, checkpoint.Hash().Hex())
   296  	for i, sig := range sigs {
   297  		fmt.Printf("Signer %d => %s\n", i+1, ecrecover(sighash, sig).Hex())
   298  	}
   299  	fmt.Println()
   300  	fmt.Printf("Sentry number => %d\nSentry hash   => %s\n", recent.Number, recent.Hash().Hex())
   301  
   302  	// Publish the checkpoint into the oracle
   303  	fmt.Println("Sending publish request to Clef...")
   304  	tx, err := oracle.RegisterCheckpoint(newClefSigner(ctx), checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs)
   305  	if err != nil {
   306  		utils.Fatalf("Register contract failed %v", err)
   307  	}
   308  	log.Info("Successfully registered checkpoint", "tx", tx.Hash().Hex())
   309  	return nil
   310  }