github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/store/store_test.go (about) 1 package store 2 3 import ( 4 "bytes" 5 "crypto/sha256" 6 "fmt" 7 "os" 8 "runtime/debug" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/gogo/protobuf/proto" 14 mdutils "github.com/ipfs/go-merkledag/test" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 18 cfg "github.com/lazyledger/lazyledger-core/config" 19 "github.com/lazyledger/lazyledger-core/crypto" 20 dbm "github.com/lazyledger/lazyledger-core/libs/db" 21 "github.com/lazyledger/lazyledger-core/libs/db/memdb" 22 "github.com/lazyledger/lazyledger-core/libs/log" 23 tmrand "github.com/lazyledger/lazyledger-core/libs/rand" 24 tmstore "github.com/lazyledger/lazyledger-core/proto/tendermint/store" 25 tmversion "github.com/lazyledger/lazyledger-core/proto/tendermint/version" 26 sm "github.com/lazyledger/lazyledger-core/state" 27 "github.com/lazyledger/lazyledger-core/types" 28 tmtime "github.com/lazyledger/lazyledger-core/types/time" 29 "github.com/lazyledger/lazyledger-core/version" 30 ) 31 32 // A cleanupFunc cleans up any config / test files created for a particular 33 // test. 34 type cleanupFunc func() 35 36 // make a Commit with a single vote containing just the height and a timestamp 37 func makeTestCommit(height int64, timestamp time.Time) *types.Commit { 38 commitSigs := []types.CommitSig{{ 39 BlockIDFlag: types.BlockIDFlagCommit, 40 ValidatorAddress: tmrand.Bytes(crypto.AddressSize), 41 Timestamp: timestamp, 42 Signature: []byte("Signature"), 43 }} 44 45 var hash = make([]byte, 32) 46 hh := sha256.Sum256([]byte("Headerhash")) 47 copy(hash, hh[:]) 48 49 return types.NewCommit(height, 0, 50 types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: []byte(""), Total: 2}}, commitSigs) 51 } 52 53 func makeTxs(height int64) (txs []types.Tx) { 54 for i := 0; i < 10; i++ { 55 txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) 56 } 57 return txs 58 } 59 60 func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block { 61 block, _ := state.MakeBlock(height, makeTxs(height), nil, 62 nil, types.Messages{}, lastCommit, state.Validators.GetProposer().Address) 63 return block 64 } 65 66 func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore, cleanupFunc) { 67 config := cfg.ResetTestRoot("blockchain_reactor_test") 68 // blockDB := dbm.NewDebugDB("blockDB", memdb.NewDB()) 69 // stateDB := dbm.NewDebugDB("stateDB", memdb.NewDB()) 70 blockDB := memdb.NewDB() 71 stateDB := memdb.NewDB() 72 stateStore := sm.NewStore(stateDB) 73 state, err := stateStore.LoadFromDBOrGenesisFile(config.GenesisFile()) 74 if err != nil { 75 panic(fmt.Errorf("error constructing state from genesis file: %w", err)) 76 } 77 return state, NewBlockStore(blockDB, mdutils.Mock()), func() { os.RemoveAll(config.RootDir) } 78 } 79 80 func TestLoadBlockStoreState(t *testing.T) { 81 82 type blockStoreTest struct { 83 testName string 84 bss *tmstore.BlockStoreState 85 want tmstore.BlockStoreState 86 } 87 88 testCases := []blockStoreTest{ 89 {"success", &tmstore.BlockStoreState{Base: 100, Height: 1000}, 90 tmstore.BlockStoreState{Base: 100, Height: 1000}}, 91 {"empty", &tmstore.BlockStoreState{}, tmstore.BlockStoreState{}}, 92 {"no base", &tmstore.BlockStoreState{Height: 1000}, tmstore.BlockStoreState{Base: 1, Height: 1000}}, 93 } 94 95 for _, tc := range testCases { 96 db := memdb.NewDB() 97 SaveBlockStoreState(tc.bss, db) 98 retrBSJ := LoadBlockStoreState(db) 99 assert.Equal(t, tc.want, retrBSJ, "expected the retrieved DBs to match: %s", tc.testName) 100 } 101 } 102 103 func TestNewBlockStore(t *testing.T) { 104 db := memdb.NewDB() 105 bss := tmstore.BlockStoreState{Base: 100, Height: 10000} 106 bz, _ := proto.Marshal(&bss) 107 err := db.Set(blockStoreKey, bz) 108 require.NoError(t, err) 109 bs := NewBlockStore(db, mdutils.Mock()) 110 require.Equal(t, int64(100), bs.Base(), "failed to properly parse blockstore") 111 require.Equal(t, int64(10000), bs.Height(), "failed to properly parse blockstore") 112 113 panicCausers := []struct { 114 data []byte 115 wantErr string 116 }{ 117 {[]byte("artful-doger"), "not unmarshal bytes"}, 118 {[]byte(" "), "unmarshal bytes"}, 119 } 120 121 for i, tt := range panicCausers { 122 tt := tt 123 // Expecting a panic here on trying to parse an invalid blockStore 124 _, _, panicErr := doFn(func() (interface{}, error) { 125 err := db.Set(blockStoreKey, tt.data) 126 require.NoError(t, err) 127 _ = NewBlockStore(db, mdutils.Mock()) 128 return nil, nil 129 }) 130 require.NotNil(t, panicErr, "#%d panicCauser: %q expected a panic", i, tt.data) 131 assert.Contains(t, fmt.Sprintf("%#v", panicErr), tt.wantErr, "#%d data: %q", i, tt.data) 132 } 133 134 err = db.Set(blockStoreKey, []byte{}) 135 require.NoError(t, err) 136 bs = NewBlockStore(db, mdutils.Mock()) 137 assert.Equal(t, bs.Height(), int64(0), "expecting empty bytes to be unmarshaled alright") 138 } 139 140 func freshBlockStore() (*BlockStore, dbm.DB) { 141 db := memdb.NewDB() 142 return NewBlockStore(db, mdutils.Mock()), db 143 } 144 145 var ( 146 state sm.State 147 block *types.Block 148 partSet *types.PartSet 149 part1 *types.Part 150 part2 *types.Part 151 seenCommit1 *types.Commit 152 ) 153 154 func TestMain(m *testing.M) { 155 var cleanup cleanupFunc 156 state, _, cleanup = makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) 157 block = makeBlock(1, state, new(types.Commit)) 158 partSet = block.MakePartSet(2) 159 part1 = partSet.GetPart(0) 160 part2 = partSet.GetPart(1) 161 seenCommit1 = makeTestCommit(10, tmtime.Now()) 162 code := m.Run() 163 cleanup() 164 os.Exit(code) 165 } 166 167 // TODO: This test should be simplified ... 168 169 func TestBlockStoreSaveLoadBlock(t *testing.T) { 170 state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) 171 defer cleanup() 172 require.Equal(t, bs.Base(), int64(0), "initially the base should be zero") 173 require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") 174 175 // check there are no blocks at various heights 176 noBlockHeights := []int64{0, -1, 100, 1000, 2} 177 for i, height := range noBlockHeights { 178 if g := bs.LoadBlock(height); g != nil { 179 t.Errorf("#%d: height(%d) got a block; want nil", i, height) 180 } 181 } 182 183 // save a block 184 block := makeBlock(bs.Height()+1, state, new(types.Commit)) 185 validPartSet := block.MakePartSet(2) 186 seenCommit := makeTestCommit(10, tmtime.Now()) 187 bs.SaveBlock(block, partSet, seenCommit) 188 require.EqualValues(t, 1, bs.Base(), "expecting the new height to be changed") 189 require.EqualValues(t, block.Header.Height, bs.Height(), "expecting the new height to be changed") 190 191 incompletePartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 2}) 192 uncontiguousPartSet := types.NewPartSetFromHeader(types.PartSetHeader{Total: 0}) 193 _, err := uncontiguousPartSet.AddPart(part2) 194 require.Error(t, err) 195 196 header1 := types.Header{ 197 Version: tmversion.Consensus{Block: version.BlockProtocol}, 198 Height: 1, 199 ChainID: "block_test", 200 Time: tmtime.Now(), 201 ProposerAddress: tmrand.Bytes(crypto.AddressSize), 202 } 203 204 // End of setup, test data 205 206 commitAtH10 := makeTestCommit(10, tmtime.Now()) 207 tuples := []struct { 208 block *types.Block 209 parts *types.PartSet 210 seenCommit *types.Commit 211 wantPanic string 212 wantErr bool 213 214 corruptBlockInDB bool 215 corruptCommitInDB bool 216 corruptSeenCommitInDB bool 217 eraseCommitInDB bool 218 eraseSeenCommitInDB bool 219 }{ 220 { 221 block: newBlock(header1, commitAtH10), 222 parts: validPartSet, 223 seenCommit: seenCommit1, 224 }, 225 226 { 227 block: nil, 228 wantPanic: "only save a non-nil block", 229 }, 230 231 { 232 block: newBlock( // New block at height 5 in empty block store is fine 233 types.Header{ 234 Version: tmversion.Consensus{Block: version.BlockProtocol}, 235 Height: 5, 236 ChainID: "block_test", 237 Time: tmtime.Now(), 238 ProposerAddress: tmrand.Bytes(crypto.AddressSize)}, 239 makeTestCommit(5, tmtime.Now()), 240 ), 241 parts: validPartSet, 242 seenCommit: makeTestCommit(5, tmtime.Now()), 243 }, 244 245 { 246 block: newBlock(header1, commitAtH10), 247 parts: incompletePartSet, 248 wantPanic: "only save complete block", // incomplete parts 249 }, 250 251 { 252 block: newBlock(header1, commitAtH10), 253 parts: validPartSet, 254 seenCommit: seenCommit1, 255 corruptCommitInDB: true, // Corrupt the DB's commit entry 256 wantPanic: "error reading block commit", 257 }, 258 259 { 260 block: newBlock(header1, commitAtH10), 261 parts: validPartSet, 262 seenCommit: seenCommit1, 263 wantPanic: "unmarshal to tmproto.BlockMeta", 264 corruptBlockInDB: true, // Corrupt the DB's block entry 265 }, 266 267 { 268 block: newBlock(header1, commitAtH10), 269 parts: validPartSet, 270 seenCommit: seenCommit1, 271 272 // Expecting no error and we want a nil back 273 eraseSeenCommitInDB: true, 274 }, 275 276 { 277 block: newBlock(header1, commitAtH10), 278 parts: validPartSet, 279 seenCommit: seenCommit1, 280 281 corruptSeenCommitInDB: true, 282 wantPanic: "error reading block seen commit", 283 }, 284 285 { 286 block: newBlock(header1, commitAtH10), 287 parts: validPartSet, 288 seenCommit: seenCommit1, 289 290 // Expecting no error and we want a nil back 291 eraseCommitInDB: true, 292 }, 293 } 294 295 type quad struct { 296 block *types.Block 297 commit *types.Commit 298 meta *types.BlockMeta 299 300 seenCommit *types.Commit 301 } 302 303 for i, tuple := range tuples { 304 tuple := tuple 305 bs, db := freshBlockStore() 306 // SaveBlock 307 res, err, panicErr := doFn(func() (interface{}, error) { 308 bs.SaveBlock(tuple.block, tuple.parts, tuple.seenCommit) 309 if tuple.block == nil { 310 return nil, nil 311 } 312 313 if tuple.corruptBlockInDB { 314 err := db.Set(calcBlockMetaKey(tuple.block.Height), []byte("block-bogus")) 315 require.NoError(t, err) 316 } 317 bBlock := bs.LoadBlock(tuple.block.Height) 318 bBlockMeta := bs.LoadBlockMeta(tuple.block.Height) 319 320 if tuple.eraseSeenCommitInDB { 321 err := db.Delete(calcSeenCommitKey(tuple.block.Height)) 322 require.NoError(t, err) 323 } 324 if tuple.corruptSeenCommitInDB { 325 err := db.Set(calcSeenCommitKey(tuple.block.Height), []byte("bogus-seen-commit")) 326 require.NoError(t, err) 327 } 328 bSeenCommit := bs.LoadSeenCommit(tuple.block.Height) 329 330 commitHeight := tuple.block.Height - 1 331 if tuple.eraseCommitInDB { 332 err := db.Delete(calcBlockCommitKey(commitHeight)) 333 require.NoError(t, err) 334 } 335 if tuple.corruptCommitInDB { 336 err := db.Set(calcBlockCommitKey(commitHeight), []byte("foo-bogus")) 337 require.NoError(t, err) 338 } 339 bCommit := bs.LoadBlockCommit(commitHeight) 340 return &quad{block: bBlock, seenCommit: bSeenCommit, commit: bCommit, 341 meta: bBlockMeta}, nil 342 }) 343 344 if subStr := tuple.wantPanic; subStr != "" { 345 if panicErr == nil { 346 t.Errorf("#%d: want a non-nil panic", i) 347 } else if got := fmt.Sprintf("%#v", panicErr); !strings.Contains(got, subStr) { 348 t.Errorf("#%d:\n\tgotErr: %q\nwant substring: %q", i, got, subStr) 349 } 350 continue 351 } 352 353 if tuple.wantErr { 354 if err == nil { 355 t.Errorf("#%d: got nil error", i) 356 } 357 continue 358 } 359 360 assert.Nil(t, panicErr, "#%d: unexpected panic", i) 361 assert.Nil(t, err, "#%d: expecting a non-nil error", i) 362 qua, ok := res.(*quad) 363 if !ok || qua == nil { 364 t.Errorf("#%d: got nil quad back; gotType=%T", i, res) 365 continue 366 } 367 if tuple.eraseSeenCommitInDB { 368 assert.Nil(t, qua.seenCommit, 369 "erased the seenCommit in the DB hence we should get back a nil seenCommit") 370 } 371 if tuple.eraseCommitInDB { 372 assert.Nil(t, qua.commit, 373 "erased the commit in the DB hence we should get back a nil commit") 374 } 375 } 376 } 377 378 func TestLoadBaseMeta(t *testing.T) { 379 config := cfg.ResetTestRoot("blockchain_reactor_test") 380 defer os.RemoveAll(config.RootDir) 381 stateStore := sm.NewStore(memdb.NewDB()) 382 state, err := stateStore.LoadFromDBOrGenesisFile(config.GenesisFile()) 383 require.NoError(t, err) 384 bs := NewBlockStore(memdb.NewDB(), mdutils.Mock()) 385 386 for h := int64(1); h <= 10; h++ { 387 block := makeBlock(h, state, new(types.Commit)) 388 partSet := block.MakePartSet(2) 389 seenCommit := makeTestCommit(h, tmtime.Now()) 390 bs.SaveBlock(block, partSet, seenCommit) 391 } 392 393 _, err = bs.PruneBlocks(4) 394 require.NoError(t, err) 395 396 baseBlock := bs.LoadBaseMeta() 397 assert.EqualValues(t, 4, baseBlock.Header.Height) 398 assert.EqualValues(t, 4, bs.Base()) 399 } 400 401 func TestLoadBlockPart(t *testing.T) { 402 bs, db := freshBlockStore() 403 height, index := int64(10), 1 404 loadPart := func() (interface{}, error) { 405 part := bs.LoadBlockPart(height, index) 406 return part, nil 407 } 408 409 // Initially no contents. 410 // 1. Requesting for a non-existent block shouldn't fail 411 res, _, panicErr := doFn(loadPart) 412 require.Nil(t, panicErr, "a non-existent block part shouldn't cause a panic") 413 require.Nil(t, res, "a non-existent block part should return nil") 414 415 // 2. Next save a corrupted block then try to load it 416 err := db.Set(calcBlockPartKey(height, index), []byte("Tendermint")) 417 require.NoError(t, err) 418 res, _, panicErr = doFn(loadPart) 419 require.NotNil(t, panicErr, "expecting a non-nil panic") 420 require.Contains(t, panicErr.Error(), "unmarshal to tmproto.Part failed") 421 422 // 3. A good block serialized and saved to the DB should be retrievable 423 pb1, err := part1.ToProto() 424 require.NoError(t, err) 425 err = db.Set(calcBlockPartKey(height, index), mustEncode(pb1)) 426 require.NoError(t, err) 427 gotPart, _, panicErr := doFn(loadPart) 428 require.Nil(t, panicErr, "an existent and proper block should not panic") 429 require.Nil(t, res, "a properly saved block should return a proper block") 430 require.Equal(t, gotPart.(*types.Part), part1, 431 "expecting successful retrieval of previously saved block") 432 } 433 434 func TestPruneBlocks(t *testing.T) { 435 config := cfg.ResetTestRoot("blockchain_reactor_test") 436 defer os.RemoveAll(config.RootDir) 437 stateStore := sm.NewStore(memdb.NewDB()) 438 state, err := stateStore.LoadFromDBOrGenesisFile(config.GenesisFile()) 439 require.NoError(t, err) 440 db := memdb.NewDB() 441 bs := NewBlockStore(db, mdutils.Mock()) 442 assert.EqualValues(t, 0, bs.Base()) 443 assert.EqualValues(t, 0, bs.Height()) 444 assert.EqualValues(t, 0, bs.Size()) 445 446 // pruning an empty store should error, even when pruning to 0 447 _, err = bs.PruneBlocks(1) 448 require.Error(t, err) 449 450 _, err = bs.PruneBlocks(0) 451 require.Error(t, err) 452 453 // make more than 1000 blocks, to test batch deletions 454 for h := int64(1); h <= 1500; h++ { 455 block := makeBlock(h, state, new(types.Commit)) 456 partSet := block.MakePartSet(2) 457 seenCommit := makeTestCommit(h, tmtime.Now()) 458 bs.SaveBlock(block, partSet, seenCommit) 459 } 460 461 assert.EqualValues(t, 1, bs.Base()) 462 assert.EqualValues(t, 1500, bs.Height()) 463 assert.EqualValues(t, 1500, bs.Size()) 464 465 prunedBlock := bs.LoadBlock(1199) 466 467 // Check that basic pruning works 468 pruned, err := bs.PruneBlocks(1200) 469 require.NoError(t, err) 470 assert.EqualValues(t, 1199, pruned) 471 assert.EqualValues(t, 1200, bs.Base()) 472 assert.EqualValues(t, 1500, bs.Height()) 473 assert.EqualValues(t, 301, bs.Size()) 474 assert.EqualValues(t, tmstore.BlockStoreState{ 475 Base: 1200, 476 Height: 1500, 477 }, LoadBlockStoreState(db)) 478 479 require.NotNil(t, bs.LoadBlock(1200)) 480 require.Nil(t, bs.LoadBlock(1199)) 481 require.Nil(t, bs.LoadBlockByHash(prunedBlock.Hash())) 482 require.Nil(t, bs.LoadBlockCommit(1199)) 483 require.Nil(t, bs.LoadBlockMeta(1199)) 484 require.Nil(t, bs.LoadBlockPart(1199, 1)) 485 486 for i := int64(1); i < 1200; i++ { 487 require.Nil(t, bs.LoadBlock(i)) 488 } 489 for i := int64(1200); i <= 1500; i++ { 490 require.NotNil(t, bs.LoadBlock(i)) 491 } 492 493 // Pruning below the current base should error 494 _, err = bs.PruneBlocks(1199) 495 require.Error(t, err) 496 497 // Pruning to the current base should work 498 pruned, err = bs.PruneBlocks(1200) 499 require.NoError(t, err) 500 assert.EqualValues(t, 0, pruned) 501 502 // Pruning again should work 503 pruned, err = bs.PruneBlocks(1300) 504 require.NoError(t, err) 505 assert.EqualValues(t, 100, pruned) 506 assert.EqualValues(t, 1300, bs.Base()) 507 508 // Pruning beyond the current height should error 509 _, err = bs.PruneBlocks(1501) 510 require.Error(t, err) 511 512 // Pruning to the current height should work 513 pruned, err = bs.PruneBlocks(1500) 514 require.NoError(t, err) 515 assert.EqualValues(t, 200, pruned) 516 assert.Nil(t, bs.LoadBlock(1499)) 517 assert.NotNil(t, bs.LoadBlock(1500)) 518 assert.Nil(t, bs.LoadBlock(1501)) 519 } 520 521 func TestLoadBlockMeta(t *testing.T) { 522 bs, db := freshBlockStore() 523 height := int64(10) 524 loadMeta := func() (interface{}, error) { 525 meta := bs.LoadBlockMeta(height) 526 return meta, nil 527 } 528 529 // Initially no contents. 530 // 1. Requesting for a non-existent blockMeta shouldn't fail 531 res, _, panicErr := doFn(loadMeta) 532 require.Nil(t, panicErr, "a non-existent blockMeta shouldn't cause a panic") 533 require.Nil(t, res, "a non-existent blockMeta should return nil") 534 535 // 2. Next save a corrupted blockMeta then try to load it 536 err := db.Set(calcBlockMetaKey(height), []byte("Tendermint-Meta")) 537 require.NoError(t, err) 538 res, _, panicErr = doFn(loadMeta) 539 require.NotNil(t, panicErr, "expecting a non-nil panic") 540 require.Contains(t, panicErr.Error(), "unmarshal to tmproto.BlockMeta") 541 542 // 3. A good blockMeta serialized and saved to the DB should be retrievable 543 meta := &types.BlockMeta{Header: types.Header{ 544 Version: tmversion.Consensus{ 545 Block: version.BlockProtocol, App: 0}, Height: 1, ProposerAddress: tmrand.Bytes(crypto.AddressSize)}} 546 pbm, err := meta.ToProto() 547 require.NoError(t, err) 548 err = db.Set(calcBlockMetaKey(height), mustEncode(pbm)) 549 require.NoError(t, err) 550 gotMeta, _, panicErr := doFn(loadMeta) 551 require.Nil(t, panicErr, "an existent and proper block should not panic") 552 require.Nil(t, res, "a properly saved blockMeta should return a proper blocMeta ") 553 pbmeta, err := meta.ToProto() 554 require.NoError(t, err) 555 if gmeta, ok := gotMeta.(*types.BlockMeta); ok { 556 pbgotMeta, err := gmeta.ToProto() 557 require.NoError(t, err) 558 require.Equal(t, mustEncode(pbmeta), mustEncode(pbgotMeta), 559 "expecting successful retrieval of previously saved blockMeta") 560 } 561 } 562 563 func TestBlockFetchAtHeight(t *testing.T) { 564 state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) 565 defer cleanup() 566 require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") 567 block := makeBlock(bs.Height()+1, state, new(types.Commit)) 568 569 partSet := block.MakePartSet(2) 570 seenCommit := makeTestCommit(10, tmtime.Now()) 571 bs.SaveBlock(block, partSet, seenCommit) 572 require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed") 573 574 blockAtHeight := bs.LoadBlock(bs.Height()) 575 b1, err := block.ToProto() 576 require.NoError(t, err) 577 b2, err := blockAtHeight.ToProto() 578 require.NoError(t, err) 579 bz1 := mustEncode(b1) 580 bz2 := mustEncode(b2) 581 require.Equal(t, bz1, bz2) 582 require.Equal(t, block.Hash(), blockAtHeight.Hash(), 583 "expecting a successful load of the last saved block") 584 585 blockAtHeightPlus1 := bs.LoadBlock(bs.Height() + 1) 586 require.Nil(t, blockAtHeightPlus1, "expecting an unsuccessful load of Height()+1") 587 blockAtHeightPlus2 := bs.LoadBlock(bs.Height() + 2) 588 require.Nil(t, blockAtHeightPlus2, "expecting an unsuccessful load of Height()+2") 589 } 590 591 func doFn(fn func() (interface{}, error)) (res interface{}, err error, panicErr error) { 592 defer func() { 593 if r := recover(); r != nil { 594 switch e := r.(type) { 595 case error: 596 panicErr = e 597 case string: 598 panicErr = fmt.Errorf("%s", e) 599 default: 600 if st, ok := r.(fmt.Stringer); ok { 601 panicErr = fmt.Errorf("%s", st) 602 } else { 603 panicErr = fmt.Errorf("%s", debug.Stack()) 604 } 605 } 606 } 607 }() 608 609 res, err = fn() 610 return res, err, panicErr 611 } 612 613 func newBlock(hdr types.Header, lastCommit *types.Commit) *types.Block { 614 return &types.Block{ 615 Header: hdr, 616 LastCommit: lastCommit, 617 } 618 }