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