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