github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/contracts/checkpointoracle/oracle_test.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 checkpointoracle 19 20 import ( 21 "bytes" 22 "crypto/ecdsa" 23 "encoding/binary" 24 "errors" 25 "math/big" 26 "reflect" 27 "sort" 28 "testing" 29 "time" 30 31 "github.com/AigarNetwork/aigar/accounts/abi/bind" 32 "github.com/AigarNetwork/aigar/accounts/abi/bind/backends" 33 "github.com/AigarNetwork/aigar/common" 34 "github.com/AigarNetwork/aigar/contracts/checkpointoracle/contract" 35 "github.com/AigarNetwork/aigar/core" 36 "github.com/AigarNetwork/aigar/crypto" 37 "github.com/AigarNetwork/aigar/params" 38 ) 39 40 var ( 41 emptyHash = [32]byte{} 42 43 checkpoint0 = params.TrustedCheckpoint{ 44 SectionIndex: 0, 45 SectionHead: common.HexToHash("0x7fa3c32f996c2bfb41a1a65b3d8ea3e0a33a1674cde43678ad6f4235e764d17d"), 46 CHTRoot: common.HexToHash("0x98fc5d3de23a0fecebad236f6655533c157d26a1aedcd0852a514dc1169e6350"), 47 BloomRoot: common.HexToHash("0x99b5adb52b337fe25e74c1c6d3835b896bd638611b3aebddb2317cce27a3f9fa"), 48 } 49 checkpoint1 = params.TrustedCheckpoint{ 50 SectionIndex: 1, 51 SectionHead: common.HexToHash("0x2d4dee68102125e59b0cc61b176bd89f0d12b3b91cfaf52ef8c2c82fb920c2d2"), 52 CHTRoot: common.HexToHash("0x7d428008ece3b4c4ef5439f071930aad0bb75108d381308df73beadcd01ded95"), 53 BloomRoot: common.HexToHash("0x652571f7736de17e7bbb427ac881474da684c6988a88bf51b10cca9a2ee148f4"), 54 } 55 checkpoint2 = params.TrustedCheckpoint{ 56 SectionIndex: 2, 57 SectionHead: common.HexToHash("0x61c0de578c0115b1dff8ef39aa600588c7c6ecb8a2f102003d7cf4c4146e9291"), 58 CHTRoot: common.HexToHash("0x407a08a407a2bc3838b74ca3eb206903c9c8a186ccf5ef14af07794efff1970b"), 59 BloomRoot: common.HexToHash("0x058b4161f558ce295a92925efc57f34f9210d5a30088d7475c183e0d3e58f5ac"), 60 } 61 ) 62 63 var ( 64 // The block frequency for creating checkpoint(only used in test) 65 sectionSize = big.NewInt(512) 66 67 // The number of confirmations needed to generate a checkpoint(only used in test). 68 processConfirms = big.NewInt(4) 69 ) 70 71 // validateOperation executes the operation, watches and delivers all events fired by the backend and ensures the 72 // correctness by assert function. 73 func validateOperation(t *testing.T, c *contract.CheckpointOracle, backend *backends.SimulatedBackend, operation func(), 74 assert func(<-chan *contract.CheckpointOracleNewCheckpointVote) error, opName string) { 75 // Watch all events and deliver them to assert function 76 var ( 77 sink = make(chan *contract.CheckpointOracleNewCheckpointVote) 78 sub, _ = c.WatchNewCheckpointVote(nil, sink, nil) 79 ) 80 defer func() { 81 // Close all subscribers 82 sub.Unsubscribe() 83 }() 84 operation() 85 86 // flush pending block 87 backend.Commit() 88 if err := assert(sink); err != nil { 89 t.Errorf("operation {%s} failed, err %s", opName, err) 90 } 91 } 92 93 // validateEvents checks that the correct number of contract events 94 // fired by contract backend. 95 func validateEvents(target int, sink interface{}) (bool, []reflect.Value) { 96 chanval := reflect.ValueOf(sink) 97 chantyp := chanval.Type() 98 if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.RecvDir == 0 { 99 return false, nil 100 } 101 count := 0 102 var recv []reflect.Value 103 timeout := time.After(1 * time.Second) 104 cases := []reflect.SelectCase{{Chan: chanval, Dir: reflect.SelectRecv}, {Chan: reflect.ValueOf(timeout), Dir: reflect.SelectRecv}} 105 for { 106 chose, v, _ := reflect.Select(cases) 107 if chose == 1 { 108 // Not enough event received 109 return false, nil 110 } 111 count += 1 112 recv = append(recv, v) 113 if count == target { 114 break 115 } 116 } 117 done := time.After(50 * time.Millisecond) 118 cases = cases[:1] 119 cases = append(cases, reflect.SelectCase{Chan: reflect.ValueOf(done), Dir: reflect.SelectRecv}) 120 chose, _, _ := reflect.Select(cases) 121 // If chose equal 0, it means receiving redundant events. 122 return chose == 1, recv 123 } 124 125 func signCheckpoint(addr common.Address, privateKey *ecdsa.PrivateKey, index uint64, hash common.Hash) []byte { 126 // EIP 191 style signatures 127 // 128 // Arguments when calculating hash to validate 129 // 1: byte(0x19) - the initial 0x19 byte 130 // 2: byte(0) - the version byte (data with intended validator) 131 // 3: this - the validator address 132 // -- Application specific data 133 // 4 : checkpoint section_index(uint64) 134 // 5 : checkpoint hash (bytes32) 135 // hash = keccak256(checkpoint_index, section_head, cht_root, bloom_root) 136 buf := make([]byte, 8) 137 binary.BigEndian.PutUint64(buf, index) 138 data := append([]byte{0x19, 0x00}, append(addr.Bytes(), append(buf, hash.Bytes()...)...)...) 139 sig, _ := crypto.Sign(crypto.Keccak256(data), privateKey) 140 sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper 141 return sig 142 } 143 144 // assertSignature verifies whether the recovered signers are equal with expected. 145 func assertSignature(addr common.Address, index uint64, hash [32]byte, r, s [32]byte, v uint8, expect common.Address) bool { 146 buf := make([]byte, 8) 147 binary.BigEndian.PutUint64(buf, index) 148 data := append([]byte{0x19, 0x00}, append(addr.Bytes(), append(buf, hash[:]...)...)...) 149 pubkey, err := crypto.Ecrecover(crypto.Keccak256(data), append(r[:], append(s[:], v-27)...)) 150 if err != nil { 151 return false 152 } 153 var signer common.Address 154 copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) 155 return bytes.Equal(signer.Bytes(), expect.Bytes()) 156 } 157 158 type Account struct { 159 key *ecdsa.PrivateKey 160 addr common.Address 161 } 162 type Accounts []Account 163 164 func (a Accounts) Len() int { return len(a) } 165 func (a Accounts) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 166 func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), a[j].addr.Bytes()) < 0 } 167 168 func TestCheckpointRegister(t *testing.T) { 169 // Initialize test accounts 170 var accounts Accounts 171 for i := 0; i < 3; i++ { 172 key, _ := crypto.GenerateKey() 173 addr := crypto.PubkeyToAddress(key.PublicKey) 174 accounts = append(accounts, Account{key: key, addr: addr}) 175 } 176 sort.Sort(accounts) 177 178 // Deploy registrar contract 179 contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{accounts[0].addr: {Balance: big.NewInt(1000000000)}, accounts[1].addr: {Balance: big.NewInt(1000000000)}, accounts[2].addr: {Balance: big.NewInt(1000000000)}}, 10000000) 180 defer contractBackend.Close() 181 182 transactOpts := bind.NewKeyedTransactor(accounts[0].key) 183 184 // 3 trusted signers, threshold 2 185 contractAddr, _, c, err := contract.DeployCheckpointOracle(transactOpts, contractBackend, []common.Address{accounts[0].addr, accounts[1].addr, accounts[2].addr}, sectionSize, processConfirms, big.NewInt(2)) 186 if err != nil { 187 t.Error("Failed to deploy registrar contract", err) 188 } 189 contractBackend.Commit() 190 191 // getRecent returns block height and hash of the head parent. 192 getRecent := func() (*big.Int, common.Hash) { 193 parentNumber := new(big.Int).Sub(contractBackend.Blockchain().CurrentHeader().Number, big.NewInt(1)) 194 parentHash := contractBackend.Blockchain().CurrentHeader().ParentHash 195 return parentNumber, parentHash 196 } 197 // collectSig generates specified number signatures. 198 collectSig := func(index uint64, hash common.Hash, n int, unauthorized *ecdsa.PrivateKey) (v []uint8, r [][32]byte, s [][32]byte) { 199 for i := 0; i < n; i++ { 200 sig := signCheckpoint(contractAddr, accounts[i].key, index, hash) 201 if unauthorized != nil { 202 sig = signCheckpoint(contractAddr, unauthorized, index, hash) 203 } 204 r = append(r, common.BytesToHash(sig[:32])) 205 s = append(s, common.BytesToHash(sig[32:64])) 206 v = append(v, sig[64]) 207 } 208 return v, r, s 209 } 210 // insertEmptyBlocks inserts a batch of empty blocks to blockchain. 211 insertEmptyBlocks := func(number int) { 212 for i := 0; i < number; i++ { 213 contractBackend.Commit() 214 } 215 } 216 // assert checks whether the current contract status is same with 217 // the expected. 218 assert := func(index uint64, hash [32]byte, height *big.Int) error { 219 lindex, lhash, lheight, err := c.GetLatestCheckpoint(nil) 220 if err != nil { 221 return err 222 } 223 if lindex != index { 224 return errors.New("latest checkpoint index mismatch") 225 } 226 if !bytes.Equal(lhash[:], hash[:]) { 227 return errors.New("latest checkpoint hash mismatch") 228 } 229 if lheight.Cmp(height) != 0 { 230 return errors.New("latest checkpoint height mismatch") 231 } 232 return nil 233 } 234 235 // Test future checkpoint registration 236 validateOperation(t, c, contractBackend, func() { 237 number, hash := getRecent() 238 v, r, s := collectSig(0, checkpoint0.Hash(), 2, nil) 239 c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s) 240 }, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { 241 return assert(0, emptyHash, big.NewInt(0)) 242 }, "test future checkpoint registration") 243 244 insertEmptyBlocks(int(sectionSize.Uint64() + processConfirms.Uint64())) 245 246 // Test transaction replay protection 247 validateOperation(t, c, contractBackend, func() { 248 number, _ := getRecent() 249 v, r, s := collectSig(0, checkpoint0.Hash(), 2, nil) 250 hash := common.HexToHash("deadbeef") 251 c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s) 252 }, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { 253 return assert(0, emptyHash, big.NewInt(0)) 254 }, "test transaction replay protection") 255 256 // Test unauthorized signature checking 257 validateOperation(t, c, contractBackend, func() { 258 number, hash := getRecent() 259 u, _ := crypto.GenerateKey() 260 v, r, s := collectSig(0, checkpoint0.Hash(), 2, u) 261 c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s) 262 }, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { 263 return assert(0, emptyHash, big.NewInt(0)) 264 }, "test unauthorized signature checking") 265 266 // Test un-multi-signature checkpoint registration 267 validateOperation(t, c, contractBackend, func() { 268 number, hash := getRecent() 269 v, r, s := collectSig(0, checkpoint0.Hash(), 1, nil) 270 c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s) 271 }, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { 272 return assert(0, emptyHash, big.NewInt(0)) 273 }, "test un-multi-signature checkpoint registration") 274 275 // Test valid checkpoint registration 276 validateOperation(t, c, contractBackend, func() { 277 number, hash := getRecent() 278 v, r, s := collectSig(0, checkpoint0.Hash(), 2, nil) 279 c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s) 280 }, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { 281 if valid, recv := validateEvents(2, events); !valid { 282 return errors.New("receive incorrect number of events") 283 } else { 284 for i := 0; i < len(recv); i++ { 285 event := recv[i].Interface().(*contract.CheckpointOracleNewCheckpointVote) 286 if !assertSignature(contractAddr, event.Index, event.CheckpointHash, event.R, event.S, event.V, accounts[i].addr) { 287 return errors.New("recover signer failed") 288 } 289 } 290 } 291 number, _ := getRecent() 292 return assert(0, checkpoint0.Hash(), number.Add(number, big.NewInt(1))) 293 }, "test valid checkpoint registration") 294 295 distance := 3*sectionSize.Uint64() + processConfirms.Uint64() - contractBackend.Blockchain().CurrentHeader().Number.Uint64() 296 insertEmptyBlocks(int(distance)) 297 298 // Test uncontinuous checkpoint registration 299 validateOperation(t, c, contractBackend, func() { 300 number, hash := getRecent() 301 v, r, s := collectSig(2, checkpoint2.Hash(), 2, nil) 302 c.SetCheckpoint(transactOpts, number, hash, checkpoint2.Hash(), 2, v, r, s) 303 }, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { 304 if valid, recv := validateEvents(2, events); !valid { 305 return errors.New("receive incorrect number of events") 306 } else { 307 for i := 0; i < len(recv); i++ { 308 event := recv[i].Interface().(*contract.CheckpointOracleNewCheckpointVote) 309 if !assertSignature(contractAddr, event.Index, event.CheckpointHash, event.R, event.S, event.V, accounts[i].addr) { 310 return errors.New("recover signer failed") 311 } 312 } 313 } 314 number, _ := getRecent() 315 return assert(2, checkpoint2.Hash(), number.Add(number, big.NewInt(1))) 316 }, "test uncontinuous checkpoint registration") 317 318 // Test old checkpoint registration 319 validateOperation(t, c, contractBackend, func() { 320 number, hash := getRecent() 321 v, r, s := collectSig(1, checkpoint1.Hash(), 2, nil) 322 c.SetCheckpoint(transactOpts, number, hash, checkpoint1.Hash(), 1, v, r, s) 323 }, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { 324 number, _ := getRecent() 325 return assert(2, checkpoint2.Hash(), number) 326 }, "test uncontinuous checkpoint registration") 327 328 // Test stale checkpoint registration 329 validateOperation(t, c, contractBackend, func() { 330 number, hash := getRecent() 331 v, r, s := collectSig(2, checkpoint2.Hash(), 2, nil) 332 c.SetCheckpoint(transactOpts, number, hash, checkpoint2.Hash(), 2, v, r, s) 333 }, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { 334 number, _ := getRecent() 335 return assert(2, checkpoint2.Hash(), number.Sub(number, big.NewInt(1))) 336 }, "test stale checkpoint registration") 337 }