github.com/ConsenSys/Quorum@v20.10.0+incompatible/consensus/istanbul/backend/engine_test.go (about) 1 // Copyright 2017 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 backend 18 19 import ( 20 "bytes" 21 "crypto/ecdsa" 22 "math/big" 23 "reflect" 24 "testing" 25 "time" 26 27 "github.com/ethereum/go-ethereum/common" 28 "github.com/ethereum/go-ethereum/common/hexutil" 29 "github.com/ethereum/go-ethereum/consensus" 30 "github.com/ethereum/go-ethereum/consensus/istanbul" 31 "github.com/ethereum/go-ethereum/core" 32 "github.com/ethereum/go-ethereum/core/rawdb" 33 "github.com/ethereum/go-ethereum/core/types" 34 "github.com/ethereum/go-ethereum/core/vm" 35 "github.com/ethereum/go-ethereum/crypto" 36 "github.com/ethereum/go-ethereum/params" 37 "github.com/ethereum/go-ethereum/rlp" 38 ) 39 40 // in this test, we can set n to 1, and it means we can process Istanbul and commit a 41 // block by one node. Otherwise, if n is larger than 1, we have to generate 42 // other fake events to process Istanbul. 43 func newBlockChain(n int) (*core.BlockChain, *backend) { 44 genesis, nodeKeys := getGenesisAndKeys(n) 45 memDB := rawdb.NewMemoryDatabase() 46 config := istanbul.DefaultConfig 47 // Use the first key as private key 48 b, _ := New(config, nodeKeys[0], memDB).(*backend) 49 genesis.MustCommit(memDB) 50 blockchain, err := core.NewBlockChain(memDB, nil, genesis.Config, b, vm.Config{}, nil) 51 if err != nil { 52 panic(err) 53 } 54 b.Start(blockchain, blockchain.CurrentBlock, blockchain.HasBadBlock) 55 snap, err := b.snapshot(blockchain, 0, common.Hash{}, nil) 56 if err != nil { 57 panic(err) 58 } 59 if snap == nil { 60 panic("failed to get snapshot") 61 } 62 proposerAddr := snap.ValSet.GetProposer().Address() 63 64 // find proposer key 65 for _, key := range nodeKeys { 66 addr := crypto.PubkeyToAddress(key.PublicKey) 67 if addr.String() == proposerAddr.String() { 68 b.privateKey = key 69 b.address = addr 70 } 71 } 72 73 return blockchain, b 74 } 75 76 func getGenesisAndKeys(n int) (*core.Genesis, []*ecdsa.PrivateKey) { 77 // Setup validators 78 var nodeKeys = make([]*ecdsa.PrivateKey, n) 79 var addrs = make([]common.Address, n) 80 for i := 0; i < n; i++ { 81 nodeKeys[i], _ = crypto.GenerateKey() 82 addrs[i] = crypto.PubkeyToAddress(nodeKeys[i].PublicKey) 83 } 84 85 // generate genesis block 86 genesis := core.DefaultGenesisBlock() 87 genesis.Config = params.TestChainConfig 88 // force enable Istanbul engine 89 genesis.Config.Istanbul = ¶ms.IstanbulConfig{} 90 genesis.Config.Ethash = nil 91 genesis.Difficulty = defaultDifficulty 92 genesis.Nonce = emptyNonce.Uint64() 93 genesis.Mixhash = types.IstanbulDigest 94 95 appendValidators(genesis, addrs) 96 return genesis, nodeKeys 97 } 98 99 func appendValidators(genesis *core.Genesis, addrs []common.Address) { 100 101 if len(genesis.ExtraData) < types.IstanbulExtraVanity { 102 genesis.ExtraData = append(genesis.ExtraData, bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity)...) 103 } 104 genesis.ExtraData = genesis.ExtraData[:types.IstanbulExtraVanity] 105 106 ist := &types.IstanbulExtra{ 107 Validators: addrs, 108 Seal: []byte{}, 109 CommittedSeal: [][]byte{}, 110 } 111 112 istPayload, err := rlp.EncodeToBytes(&ist) 113 if err != nil { 114 panic("failed to encode istanbul extra") 115 } 116 genesis.ExtraData = append(genesis.ExtraData, istPayload...) 117 } 118 119 func makeHeader(parent *types.Block, config *istanbul.Config) *types.Header { 120 header := &types.Header{ 121 ParentHash: parent.Hash(), 122 Number: parent.Number().Add(parent.Number(), common.Big1), 123 GasLimit: core.CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()), 124 GasUsed: 0, 125 Extra: parent.Extra(), 126 Time: parent.Time() + config.BlockPeriod, 127 128 Difficulty: defaultDifficulty, 129 } 130 return header 131 } 132 133 func makeBlock(chain *core.BlockChain, engine *backend, parent *types.Block) *types.Block { 134 block := makeBlockWithoutSeal(chain, engine, parent) 135 stopCh := make(chan struct{}) 136 resultCh := make(chan *types.Block, 10) 137 go engine.Seal(chain, block, resultCh, stopCh) 138 blk := <-resultCh 139 return blk 140 } 141 142 func makeBlockWithoutSeal(chain *core.BlockChain, engine *backend, parent *types.Block) *types.Block { 143 header := makeHeader(parent, engine.config) 144 engine.Prepare(chain, header) 145 state, _, _ := chain.StateAt(parent.Root()) 146 block, _ := engine.FinalizeAndAssemble(chain, header, state, nil, nil, nil) 147 return block 148 } 149 150 func TestPrepare(t *testing.T) { 151 chain, engine := newBlockChain(1) 152 header := makeHeader(chain.Genesis(), engine.config) 153 err := engine.Prepare(chain, header) 154 if err != nil { 155 t.Errorf("error mismatch: have %v, want nil", err) 156 } 157 header.ParentHash = common.StringToHash("1234567890") 158 err = engine.Prepare(chain, header) 159 if err != consensus.ErrUnknownAncestor { 160 t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrUnknownAncestor) 161 } 162 } 163 164 func TestSealStopChannel(t *testing.T) { 165 chain, engine := newBlockChain(4) 166 block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) 167 stop := make(chan struct{}, 1) 168 eventSub := engine.EventMux().Subscribe(istanbul.RequestEvent{}) 169 eventLoop := func() { 170 ev := <-eventSub.Chan() 171 _, ok := ev.Data.(istanbul.RequestEvent) 172 if !ok { 173 t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) 174 } 175 stop <- struct{}{} 176 eventSub.Unsubscribe() 177 } 178 go eventLoop() 179 resultCh := make(chan *types.Block, 10) 180 go func() { 181 err := engine.Seal(chain, block, resultCh, stop) 182 if err != nil { 183 t.Errorf("error mismatch: have %v, want nil", err) 184 } 185 }() 186 187 finalBlock := <-resultCh 188 if finalBlock != nil { 189 t.Errorf("block mismatch: have %v, want nil", finalBlock) 190 } 191 } 192 193 func TestSealCommittedOtherHash(t *testing.T) { 194 chain, engine := newBlockChain(4) 195 block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) 196 otherBlock := makeBlockWithoutSeal(chain, engine, block) 197 expectedCommittedSeal := append([]byte{1, 2, 3}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-3)...) 198 199 eventSub := engine.EventMux().Subscribe(istanbul.RequestEvent{}) 200 blockOutputChannel := make(chan *types.Block) 201 stopChannel := make(chan struct{}) 202 203 go func() { 204 ev := <-eventSub.Chan() 205 if _, ok := ev.Data.(istanbul.RequestEvent); !ok { 206 t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) 207 } 208 if err := engine.Commit(otherBlock, [][]byte{expectedCommittedSeal}); err != nil { 209 t.Error(err.Error()) 210 } 211 eventSub.Unsubscribe() 212 }() 213 214 go func() { 215 if err := engine.Seal(chain, block, blockOutputChannel, stopChannel); err != nil { 216 t.Error(err.Error()) 217 } 218 }() 219 220 select { 221 case <-blockOutputChannel: 222 t.Error("Wrong block found!") 223 default: 224 //no block found, stop the sealing 225 close(stopChannel) 226 } 227 228 output := <-blockOutputChannel 229 if output != nil { 230 t.Error("Block not nil!") 231 } 232 } 233 234 func TestSealCommitted(t *testing.T) { 235 chain, engine := newBlockChain(1) 236 block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) 237 expectedBlock, _ := engine.updateBlock(engine.chain.GetHeader(block.ParentHash(), block.NumberU64()-1), block) 238 resultCh := make(chan *types.Block, 10) 239 go func() { 240 err := engine.Seal(chain, block, resultCh, make(chan struct{})) 241 242 if err != nil { 243 t.Errorf("error mismatch: have %v, want %v", err, expectedBlock) 244 } 245 }() 246 247 finalBlock := <-resultCh 248 if finalBlock.Hash() != expectedBlock.Hash() { 249 t.Errorf("hash mismatch: have %v, want %v", finalBlock.Hash(), expectedBlock.Hash()) 250 } 251 } 252 253 func TestVerifyHeader(t *testing.T) { 254 chain, engine := newBlockChain(1) 255 256 // errEmptyCommittedSeals case 257 block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) 258 block, _ = engine.updateBlock(chain.Genesis().Header(), block) 259 err := engine.VerifyHeader(chain, block.Header(), false) 260 if err != errEmptyCommittedSeals { 261 t.Errorf("error mismatch: have %v, want %v", err, errEmptyCommittedSeals) 262 } 263 264 // short extra data 265 header := block.Header() 266 header.Extra = []byte{} 267 err = engine.VerifyHeader(chain, header, false) 268 if err != errInvalidExtraDataFormat { 269 t.Errorf("error mismatch: have %v, want %v", err, errInvalidExtraDataFormat) 270 } 271 // incorrect extra format 272 header.Extra = []byte("0000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000000000") 273 err = engine.VerifyHeader(chain, header, false) 274 if err != errInvalidExtraDataFormat { 275 t.Errorf("error mismatch: have %v, want %v", err, errInvalidExtraDataFormat) 276 } 277 278 // non zero MixDigest 279 block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) 280 header = block.Header() 281 header.MixDigest = common.StringToHash("123456789") 282 err = engine.VerifyHeader(chain, header, false) 283 if err != errInvalidMixDigest { 284 t.Errorf("error mismatch: have %v, want %v", err, errInvalidMixDigest) 285 } 286 287 // invalid uncles hash 288 block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) 289 header = block.Header() 290 header.UncleHash = common.StringToHash("123456789") 291 err = engine.VerifyHeader(chain, header, false) 292 if err != errInvalidUncleHash { 293 t.Errorf("error mismatch: have %v, want %v", err, errInvalidUncleHash) 294 } 295 296 // invalid difficulty 297 block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) 298 header = block.Header() 299 header.Difficulty = big.NewInt(2) 300 err = engine.VerifyHeader(chain, header, false) 301 if err != errInvalidDifficulty { 302 t.Errorf("error mismatch: have %v, want %v", err, errInvalidDifficulty) 303 } 304 305 // invalid timestamp 306 block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) 307 header = block.Header() 308 header.Time = chain.Genesis().Time() + (engine.config.BlockPeriod - 1) 309 err = engine.VerifyHeader(chain, header, false) 310 if err != errInvalidTimestamp { 311 t.Errorf("error mismatch: have %v, want %v", err, errInvalidTimestamp) 312 } 313 314 // future block 315 block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) 316 header = block.Header() 317 header.Time = uint64(now().Unix() + 10) 318 err = engine.VerifyHeader(chain, header, false) 319 if err != consensus.ErrFutureBlock { 320 t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrFutureBlock) 321 } 322 323 // future block which is within AllowedFutureBlockTime 324 block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) 325 header = block.Header() 326 header.Time = new(big.Int).Add(big.NewInt(now().Unix()), new(big.Int).SetUint64(10)).Uint64() 327 priorValue := engine.config.AllowedFutureBlockTime 328 engine.config.AllowedFutureBlockTime = 10 329 err = engine.VerifyHeader(chain, header, false) 330 engine.config.AllowedFutureBlockTime = priorValue //restore changed value 331 if err == consensus.ErrFutureBlock { 332 t.Errorf("error mismatch: have %v, want nil", err) 333 } 334 335 // invalid nonce 336 block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) 337 header = block.Header() 338 copy(header.Nonce[:], hexutil.MustDecode("0x111111111111")) 339 header.Number = big.NewInt(int64(engine.config.Epoch)) 340 err = engine.VerifyHeader(chain, header, false) 341 if err != errInvalidNonce { 342 t.Errorf("error mismatch: have %v, want %v", err, errInvalidNonce) 343 } 344 } 345 346 func TestVerifySeal(t *testing.T) { 347 chain, engine := newBlockChain(1) 348 genesis := chain.Genesis() 349 // cannot verify genesis 350 err := engine.VerifySeal(chain, genesis.Header()) 351 if err != errUnknownBlock { 352 t.Errorf("error mismatch: have %v, want %v", err, errUnknownBlock) 353 } 354 355 block := makeBlock(chain, engine, genesis) 356 // change block content 357 header := block.Header() 358 header.Number = big.NewInt(4) 359 block1 := block.WithSeal(header) 360 err = engine.VerifySeal(chain, block1.Header()) 361 if err != errUnauthorized { 362 t.Errorf("error mismatch: have %v, want %v", err, errUnauthorized) 363 } 364 365 // unauthorized users but still can get correct signer address 366 engine.privateKey, _ = crypto.GenerateKey() 367 err = engine.VerifySeal(chain, block.Header()) 368 if err != nil { 369 t.Errorf("error mismatch: have %v, want nil", err) 370 } 371 } 372 373 func TestVerifyHeaders(t *testing.T) { 374 chain, engine := newBlockChain(1) 375 genesis := chain.Genesis() 376 377 // success case 378 headers := []*types.Header{} 379 blocks := []*types.Block{} 380 size := 100 381 382 for i := 0; i < size; i++ { 383 var b *types.Block 384 if i == 0 { 385 b = makeBlockWithoutSeal(chain, engine, genesis) 386 b, _ = engine.updateBlock(genesis.Header(), b) 387 } else { 388 b = makeBlockWithoutSeal(chain, engine, blocks[i-1]) 389 b, _ = engine.updateBlock(blocks[i-1].Header(), b) 390 } 391 blocks = append(blocks, b) 392 headers = append(headers, blocks[i].Header()) 393 } 394 now = func() time.Time { 395 return time.Unix(int64(headers[size-1].Time), 0) 396 } 397 _, results := engine.VerifyHeaders(chain, headers, nil) 398 const timeoutDura = 2 * time.Second 399 timeout := time.NewTimer(timeoutDura) 400 index := 0 401 OUT1: 402 for { 403 select { 404 case err := <-results: 405 if err != nil { 406 if err != errEmptyCommittedSeals && err != errInvalidCommittedSeals && err != consensus.ErrUnknownAncestor { 407 t.Errorf("error mismatch: have %v, want errEmptyCommittedSeals|errInvalidCommittedSeals|ErrUnknownAncestor", err) 408 break OUT1 409 } 410 } 411 index++ 412 if index == size { 413 break OUT1 414 } 415 case <-timeout.C: 416 break OUT1 417 } 418 } 419 _, results = engine.VerifyHeaders(chain, headers, nil) 420 timeout = time.NewTimer(timeoutDura) 421 index = 0 422 OUT2: 423 for { 424 select { 425 case err := <-results: 426 if err != nil { 427 if err != errEmptyCommittedSeals && err != errInvalidCommittedSeals && err != consensus.ErrUnknownAncestor { 428 t.Errorf("error mismatch: have %v, want errEmptyCommittedSeals|errInvalidCommittedSeals|ErrUnknownAncestor", err) 429 break OUT2 430 } 431 } 432 case <-timeout.C: 433 break OUT2 434 } 435 } 436 // error header cases 437 headers[2].Number = big.NewInt(100) 438 _, results = engine.VerifyHeaders(chain, headers, nil) 439 timeout = time.NewTimer(timeoutDura) 440 index = 0 441 errors := 0 442 expectedErrors := 0 443 OUT3: 444 for { 445 select { 446 case err := <-results: 447 if err != nil { 448 if err != errEmptyCommittedSeals && err != errInvalidCommittedSeals && err != consensus.ErrUnknownAncestor { 449 errors++ 450 } 451 } 452 index++ 453 if index == size { 454 if errors != expectedErrors { 455 t.Errorf("error mismatch: have %v, want %v", errors, expectedErrors) 456 } 457 break OUT3 458 } 459 case <-timeout.C: 460 break OUT3 461 } 462 } 463 } 464 465 func TestPrepareExtra(t *testing.T) { 466 validators := make([]common.Address, 4) 467 validators[0] = common.BytesToAddress(hexutil.MustDecode("0x44add0ec310f115a0e603b2d7db9f067778eaf8a")) 468 validators[1] = common.BytesToAddress(hexutil.MustDecode("0x294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212")) 469 validators[2] = common.BytesToAddress(hexutil.MustDecode("0x6beaaed781d2d2ab6350f5c4566a2c6eaac407a6")) 470 validators[3] = common.BytesToAddress(hexutil.MustDecode("0x8be76812f765c24641ec63dc2852b378aba2b440")) 471 472 vanity := make([]byte, types.IstanbulExtraVanity) 473 expectedResult := append(vanity, hexutil.MustDecode("0xf858f8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b44080c0")...) 474 475 h := &types.Header{ 476 Extra: vanity, 477 } 478 479 payload, err := prepareExtra(h, validators) 480 if err != nil { 481 t.Errorf("error mismatch: have %v, want: nil", err) 482 } 483 if !reflect.DeepEqual(payload, expectedResult) { 484 t.Errorf("payload mismatch: have %v, want %v", payload, expectedResult) 485 } 486 487 // append useless information to extra-data 488 h.Extra = append(vanity, make([]byte, 15)...) 489 490 payload, err = prepareExtra(h, validators) 491 if !reflect.DeepEqual(payload, expectedResult) { 492 t.Errorf("payload mismatch: have %v, want %v", payload, expectedResult) 493 } 494 } 495 496 func TestWriteSeal(t *testing.T) { 497 vanity := bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity) 498 istRawData := hexutil.MustDecode("0xf858f8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b44080c0") 499 expectedSeal := append([]byte{1, 2, 3}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-3)...) 500 expectedIstExtra := &types.IstanbulExtra{ 501 Validators: []common.Address{ 502 common.BytesToAddress(hexutil.MustDecode("0x44add0ec310f115a0e603b2d7db9f067778eaf8a")), 503 common.BytesToAddress(hexutil.MustDecode("0x294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212")), 504 common.BytesToAddress(hexutil.MustDecode("0x6beaaed781d2d2ab6350f5c4566a2c6eaac407a6")), 505 common.BytesToAddress(hexutil.MustDecode("0x8be76812f765c24641ec63dc2852b378aba2b440")), 506 }, 507 Seal: expectedSeal, 508 CommittedSeal: [][]byte{}, 509 } 510 var expectedErr error 511 512 h := &types.Header{ 513 Extra: append(vanity, istRawData...), 514 } 515 516 // normal case 517 err := writeSeal(h, expectedSeal) 518 if err != expectedErr { 519 t.Errorf("error mismatch: have %v, want %v", err, expectedErr) 520 } 521 522 // verify istanbul extra-data 523 istExtra, err := types.ExtractIstanbulExtra(h) 524 if err != nil { 525 t.Errorf("error mismatch: have %v, want nil", err) 526 } 527 if !reflect.DeepEqual(istExtra, expectedIstExtra) { 528 t.Errorf("extra data mismatch: have %v, want %v", istExtra, expectedIstExtra) 529 } 530 531 // invalid seal 532 unexpectedSeal := append(expectedSeal, make([]byte, 1)...) 533 err = writeSeal(h, unexpectedSeal) 534 if err != errInvalidSignature { 535 t.Errorf("error mismatch: have %v, want %v", err, errInvalidSignature) 536 } 537 } 538 539 func TestWriteCommittedSeals(t *testing.T) { 540 vanity := bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity) 541 istRawData := hexutil.MustDecode("0xf858f8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b44080c0") 542 expectedCommittedSeal := append([]byte{1, 2, 3}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-3)...) 543 expectedIstExtra := &types.IstanbulExtra{ 544 Validators: []common.Address{ 545 common.BytesToAddress(hexutil.MustDecode("0x44add0ec310f115a0e603b2d7db9f067778eaf8a")), 546 common.BytesToAddress(hexutil.MustDecode("0x294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212")), 547 common.BytesToAddress(hexutil.MustDecode("0x6beaaed781d2d2ab6350f5c4566a2c6eaac407a6")), 548 common.BytesToAddress(hexutil.MustDecode("0x8be76812f765c24641ec63dc2852b378aba2b440")), 549 }, 550 Seal: []byte{}, 551 CommittedSeal: [][]byte{expectedCommittedSeal}, 552 } 553 var expectedErr error 554 555 h := &types.Header{ 556 Extra: append(vanity, istRawData...), 557 } 558 559 // normal case 560 err := writeCommittedSeals(h, [][]byte{expectedCommittedSeal}) 561 if err != expectedErr { 562 t.Errorf("error mismatch: have %v, want %v", err, expectedErr) 563 } 564 565 // verify istanbul extra-data 566 istExtra, err := types.ExtractIstanbulExtra(h) 567 if err != nil { 568 t.Errorf("error mismatch: have %v, want nil", err) 569 } 570 if !reflect.DeepEqual(istExtra, expectedIstExtra) { 571 t.Errorf("extra data mismatch: have %v, want %v", istExtra, expectedIstExtra) 572 } 573 574 // invalid seal 575 unexpectedCommittedSeal := append(expectedCommittedSeal, make([]byte, 1)...) 576 err = writeCommittedSeals(h, [][]byte{unexpectedCommittedSeal}) 577 if err != errInvalidCommittedSeals { 578 t.Errorf("error mismatch: have %v, want %v", err, errInvalidCommittedSeals) 579 } 580 }