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