382 lines
12 KiB
Go
382 lines
12 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package fileledger
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
cb "github.com/hyperledger/fabric-protos-go/common"
|
|
ab "github.com/hyperledger/fabric-protos-go/orderer"
|
|
"github.com/hyperledger/fabric-protos-go/peer"
|
|
"github.com/hyperledger/fabric/common/flogging"
|
|
cl "github.com/hyperledger/fabric/common/ledger"
|
|
"github.com/hyperledger/fabric/common/ledger/blkstorage/blkstoragetest"
|
|
"github.com/hyperledger/fabric/common/ledger/blockledger"
|
|
"github.com/hyperledger/fabric/common/ledger/testutil"
|
|
"github.com/hyperledger/fabric/common/metrics/disabled"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var genesisBlock = protoutil.NewBlock(0, nil)
|
|
|
|
func init() {
|
|
flogging.ActivateSpec("common.ledger.blockledger.file=DEBUG")
|
|
}
|
|
|
|
type testEnv struct {
|
|
t *testing.T
|
|
location string
|
|
flf blockledger.Factory
|
|
}
|
|
|
|
func initialize(t *testing.T) (*testEnv, *FileLedger) {
|
|
name := t.TempDir()
|
|
|
|
p, err := New(name, &disabled.Provider{})
|
|
require.NoError(t, err)
|
|
flf := p.(*fileLedgerFactory)
|
|
fl, err := flf.GetOrCreate("testchannelid")
|
|
require.NoError(t, err, "Error GetOrCreate channel")
|
|
fl.Append(genesisBlock)
|
|
return &testEnv{location: name, t: t, flf: flf}, fl.(*FileLedger)
|
|
}
|
|
|
|
func (tev *testEnv) tearDown() {
|
|
tev.shutDown()
|
|
}
|
|
|
|
func (tev *testEnv) shutDown() {
|
|
tev.flf.Close()
|
|
}
|
|
|
|
type mockBlockStore struct {
|
|
blockchainInfo *cb.BlockchainInfo
|
|
resultsIterator cl.ResultsIterator
|
|
block *cb.Block
|
|
envelope *cb.Envelope
|
|
txValidationCode peer.TxValidationCode
|
|
defaultError error
|
|
getBlockchainInfoError error
|
|
retrieveBlockByNumberError error
|
|
}
|
|
|
|
func (mbs *mockBlockStore) AddBlock(block *cb.Block) error {
|
|
return mbs.defaultError
|
|
}
|
|
|
|
func (mbs *mockBlockStore) GetBlockchainInfo() (*cb.BlockchainInfo, error) {
|
|
return mbs.blockchainInfo, mbs.getBlockchainInfoError
|
|
}
|
|
|
|
func (mbs *mockBlockStore) RetrieveBlocks(startNum uint64) (cl.ResultsIterator, error) {
|
|
return mbs.resultsIterator, mbs.defaultError
|
|
}
|
|
|
|
func (mbs *mockBlockStore) RetrieveBlockByHash(blockHash []byte) (*cb.Block, error) {
|
|
return mbs.block, mbs.defaultError
|
|
}
|
|
|
|
func (mbs *mockBlockStore) RetrieveBlockByNumber(blockNum uint64) (*cb.Block, error) {
|
|
return mbs.block, mbs.retrieveBlockByNumberError
|
|
}
|
|
|
|
func (mbs *mockBlockStore) RetrieveTxByID(txID string) (*cb.Envelope, error) {
|
|
return mbs.envelope, mbs.defaultError
|
|
}
|
|
|
|
func (mbs *mockBlockStore) RetrieveTxByBlockNumTranNum(blockNum uint64, tranNum uint64) (*cb.Envelope, error) {
|
|
return mbs.envelope, mbs.defaultError
|
|
}
|
|
|
|
func (mbs *mockBlockStore) RetrieveBlockByTxID(txID string) (*cb.Block, error) {
|
|
return mbs.block, mbs.defaultError
|
|
}
|
|
|
|
func (mbs *mockBlockStore) RetrieveTxValidationCodeByTxID(txID string) (peer.TxValidationCode, error) {
|
|
return mbs.txValidationCode, mbs.defaultError
|
|
}
|
|
|
|
func (*mockBlockStore) Shutdown() {
|
|
}
|
|
|
|
type mockBlockStoreIterator struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *mockBlockStoreIterator) Next() (cl.QueryResult, error) {
|
|
args := m.Called()
|
|
return args.Get(0), args.Error(1)
|
|
}
|
|
|
|
func (m *mockBlockStoreIterator) Close() {
|
|
m.Called()
|
|
}
|
|
|
|
func TestInitialization(t *testing.T) {
|
|
tev, fl := initialize(t)
|
|
defer tev.tearDown()
|
|
|
|
require.Equal(t, uint64(1), fl.Height(), "Block height should be 1")
|
|
|
|
block := blockledger.GetBlock(fl, 0)
|
|
require.NotNil(t, block, "Error retrieving genesis block")
|
|
require.Equal(t, protoutil.BlockHeaderHash(genesisBlock.Header), protoutil.BlockHeaderHash(block.Header), "Block hashes did no match")
|
|
}
|
|
|
|
func TestReinitialization(t *testing.T) {
|
|
tev, ledger1 := initialize(t)
|
|
defer tev.tearDown()
|
|
|
|
// create a block to add to the ledger
|
|
envelope := getSampleEnvelopeWithSignatureHeader()
|
|
b1 := blockledger.CreateNextBlock(ledger1, []*cb.Envelope{envelope})
|
|
|
|
// add the block to the ledger
|
|
ledger1.Append(b1)
|
|
|
|
fl, err := tev.flf.GetOrCreate("testchannelid")
|
|
ledger1, ok := fl.(*FileLedger)
|
|
require.NoError(t, err, "Expected to successfully get test channel")
|
|
require.Equal(t, 1, len(tev.flf.ChannelIDs()), "Exptected not new channel to be created")
|
|
require.True(t, ok, "Exptected type assertion to succeed")
|
|
require.Equal(t, uint64(2), ledger1.Height(), "Block height should be 2. Got %v", ledger1.Height())
|
|
|
|
// shut down the ledger provider
|
|
tev.shutDown()
|
|
|
|
// re-initialize the ledger provider (not the test ledger itself!)
|
|
provider2, err := New(tev.location, &disabled.Provider{})
|
|
require.NoError(t, err)
|
|
|
|
// assert expected ledgers exist
|
|
channels := provider2.ChannelIDs()
|
|
require.Equal(t, 1, len(channels), "Should have recovered the channel")
|
|
|
|
// get the existing test channel ledger
|
|
ledger2, err := provider2.GetOrCreate(channels[0])
|
|
require.NoError(t, err, "Unexpected error: %s", err)
|
|
|
|
fl = ledger2.(*FileLedger)
|
|
require.Equal(t, uint64(2), fl.Height(), "Block height should be 2. Got %v", fl.Height())
|
|
|
|
block := blockledger.GetBlock(fl, 1)
|
|
require.NotNil(t, block, "Error retrieving block 1")
|
|
require.Equal(t, protoutil.BlockHeaderHash(b1.Header), protoutil.BlockHeaderHash(block.Header), "Block hashes did no match")
|
|
}
|
|
|
|
func TestAddition(t *testing.T) {
|
|
tev, fl := initialize(t)
|
|
defer tev.tearDown()
|
|
info, _ := fl.blockStore.GetBlockchainInfo()
|
|
prevHash := info.CurrentBlockHash
|
|
envelope := getSampleEnvelopeWithSignatureHeader()
|
|
b1 := blockledger.CreateNextBlock(fl, []*cb.Envelope{envelope})
|
|
fl.Append(b1)
|
|
require.Equal(t, uint64(2), fl.Height(), "Block height should be 2")
|
|
|
|
block := blockledger.GetBlock(fl, 1)
|
|
require.NotNil(t, block, "Error retrieving genesis block")
|
|
require.Equal(t, prevHash, block.Header.PreviousHash, "Block hashes did no match")
|
|
}
|
|
|
|
func TestRetrieval(t *testing.T) {
|
|
tev, fl := initialize(t)
|
|
defer tev.tearDown()
|
|
envelope := getSampleEnvelopeWithSignatureHeader()
|
|
b1 := blockledger.CreateNextBlock(fl, []*cb.Envelope{envelope})
|
|
fl.Append(b1)
|
|
it, num := fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Oldest{}})
|
|
defer it.Close()
|
|
require.Zero(t, num, "Expected genesis block iterator, but got %d", num)
|
|
|
|
block, status := it.Next()
|
|
require.Equal(t, cb.Status_SUCCESS, status, "Expected to successfully read the genesis block")
|
|
require.Zero(t, block.Header.Number, "Expected to successfully retrieve the genesis block")
|
|
|
|
block, status = it.Next()
|
|
require.Equal(t, cb.Status_SUCCESS, status, "Expected to successfully read the second block")
|
|
require.Equal(
|
|
t,
|
|
uint64(1),
|
|
block.Header.Number,
|
|
"Expected to successfully retrieve the second block but got block number %d", block.Header.Number)
|
|
}
|
|
|
|
func TestBlockedRetrieval(t *testing.T) {
|
|
tev, fl := initialize(t)
|
|
defer tev.tearDown()
|
|
it, num := fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Specified{Specified: &ab.SeekSpecified{Number: 1}}})
|
|
defer it.Close()
|
|
if num != 1 {
|
|
t.Fatalf("Expected block iterator at 1, but got %d", num)
|
|
}
|
|
require.Equal(t, uint64(1), num, "Expected block iterator at 1, but got %d", num)
|
|
|
|
envelope := getSampleEnvelopeWithSignatureHeader()
|
|
b1 := blockledger.CreateNextBlock(fl, []*cb.Envelope{envelope})
|
|
fl.Append(b1)
|
|
|
|
block, status := it.Next()
|
|
require.Equal(t, cb.Status_SUCCESS, status, "Expected to successfully read the second block")
|
|
require.Equal(
|
|
t,
|
|
uint64(1),
|
|
block.Header.Number,
|
|
"Expected to successfully retrieve the second block but got block number %d", block.Header.Number)
|
|
|
|
b2 := blockledger.CreateNextBlock(fl, []*cb.Envelope{envelope})
|
|
fl.Append(b2)
|
|
|
|
block, status = it.Next()
|
|
require.Equal(t, cb.Status_SUCCESS, status, "Expected to successfully read the third block")
|
|
require.Equal(t, uint64(2), block.Header.Number, "Expected to successfully retrieve the third block")
|
|
|
|
// verify NextCommit seek position
|
|
it2, num2 := fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_NextCommit{NextCommit: &ab.SeekNextCommit{}}})
|
|
require.Equal(t, uint64(3), num2)
|
|
defer it2.Close()
|
|
|
|
b3 := blockledger.CreateNextBlock(fl, []*cb.Envelope{envelope})
|
|
fl.Append(b3)
|
|
|
|
block, status = it.Next()
|
|
require.Equal(t, cb.Status_SUCCESS, status)
|
|
require.Equal(t, uint64(3), block.Header.Number)
|
|
}
|
|
|
|
func TestBlockRetrievalWithSnapshot(t *testing.T) {
|
|
numBlocks := 5
|
|
// create an extra block to add later for iterator.Next testing
|
|
blocks := testutil.ConstructTestBlocks(t, numBlocks+1)
|
|
|
|
blockStore, cleanup := blkstoragetest.BootstrapBlockstoreFromSnapshot(t, "blockretrievalwithsnapshot", blocks[:numBlocks])
|
|
defer cleanup()
|
|
|
|
fl := NewFileLedger(blockStore)
|
|
|
|
// verify lastBlockInSnapshot, which should be numBlocks - 1
|
|
bcInfo, err := fl.blockStore.GetBlockchainInfo()
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(numBlocks-1), bcInfo.BootstrappingSnapshotInfo.LastBlockInSnapshot)
|
|
|
|
// verify iterator startingNum for Newest, NextCommit, and Specified
|
|
it, startingNum := fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Newest{}})
|
|
defer it.Close()
|
|
require.Equal(t, uint64(numBlocks), startingNum)
|
|
|
|
it2, startingNum2 := fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_NextCommit{}})
|
|
defer it2.Close()
|
|
require.Equal(t, uint64(numBlocks), startingNum2)
|
|
|
|
it3, startingNum3 := fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Specified{Specified: &ab.SeekSpecified{Number: uint64(numBlocks)}}})
|
|
defer it3.Close()
|
|
require.Equal(t, uint64(numBlocks), startingNum3)
|
|
|
|
it4, _ := fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Specified{Specified: &ab.SeekSpecified{Number: uint64(numBlocks - 1)}}})
|
|
defer it4.Close()
|
|
require.Equal(t, &blockledger.NotFoundErrorIterator{}, it4)
|
|
|
|
it5, _ := fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Specified{Specified: &ab.SeekSpecified{Number: uint64(numBlocks + 1)}}})
|
|
defer it5.Close()
|
|
require.Equal(t, &blockledger.NotFoundErrorIterator{}, it4)
|
|
|
|
// add a block and verify iterator.Next
|
|
nextBlk := blocks[numBlocks]
|
|
err = fl.Append(nextBlk)
|
|
require.NoError(t, err)
|
|
|
|
blk, status := it.Next()
|
|
require.Equal(t, cb.Status_SUCCESS, status)
|
|
require.Equal(t, nextBlk, blk)
|
|
|
|
blk, status = it2.Next()
|
|
require.Equal(t, cb.Status_SUCCESS, status)
|
|
require.Equal(t, nextBlk, blk)
|
|
|
|
blk, status = it3.Next()
|
|
require.Equal(t, cb.Status_SUCCESS, status)
|
|
require.Equal(t, nextBlk, blk)
|
|
}
|
|
|
|
func TestBlockstoreError(t *testing.T) {
|
|
// Since this test only ensures failed GetBlockchainInfo
|
|
// is properly handled. We don't bother creating fully
|
|
// legit ledgers here (without genesis block).
|
|
{
|
|
fl := &FileLedger{
|
|
blockStore: &mockBlockStore{
|
|
blockchainInfo: nil,
|
|
getBlockchainInfoError: fmt.Errorf("Error getting blockchain info"),
|
|
},
|
|
signal: make(chan struct{}),
|
|
}
|
|
require.Panics(
|
|
t,
|
|
func() {
|
|
fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Newest{}})
|
|
},
|
|
"Expected Iterator() to panic if blockstore operation fails")
|
|
|
|
require.Panics(
|
|
t,
|
|
func() { fl.Height() },
|
|
"Expected Height() to panic if blockstore operation fails ")
|
|
}
|
|
|
|
{
|
|
fl := &FileLedger{
|
|
blockStore: &mockBlockStore{
|
|
blockchainInfo: &cb.BlockchainInfo{Height: uint64(1)},
|
|
getBlockchainInfoError: nil,
|
|
retrieveBlockByNumberError: fmt.Errorf("Error retrieving block by number"),
|
|
},
|
|
signal: make(chan struct{}),
|
|
}
|
|
it, _ := fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Specified{Specified: &ab.SeekSpecified{Number: 42}}})
|
|
defer it.Close()
|
|
require.IsType(
|
|
t,
|
|
&blockledger.NotFoundErrorIterator{},
|
|
it,
|
|
"Expected Not Found Error if seek number is greater than ledger height")
|
|
}
|
|
|
|
{
|
|
resultsIterator := &mockBlockStoreIterator{}
|
|
resultsIterator.On("Next").Return(nil, errors.New("a mocked error"))
|
|
resultsIterator.On("Close").Return()
|
|
fl := &FileLedger{
|
|
blockStore: &mockBlockStore{
|
|
blockchainInfo: &cb.BlockchainInfo{Height: uint64(1)},
|
|
getBlockchainInfoError: nil,
|
|
retrieveBlockByNumberError: fmt.Errorf("Error retrieving block by number"),
|
|
resultsIterator: resultsIterator,
|
|
},
|
|
signal: make(chan struct{}),
|
|
}
|
|
it, _ := fl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Oldest{}})
|
|
defer it.Close()
|
|
_, status := it.Next()
|
|
require.Equal(t, cb.Status_SERVICE_UNAVAILABLE, status, "Expected service unavailable error")
|
|
}
|
|
}
|
|
|
|
func getSampleEnvelopeWithSignatureHeader() *cb.Envelope {
|
|
nonce := protoutil.CreateNonceOrPanic()
|
|
sighdr := &cb.SignatureHeader{Nonce: nonce}
|
|
sighdrBytes := protoutil.MarshalOrPanic(sighdr)
|
|
|
|
header := &cb.Header{SignatureHeader: sighdrBytes}
|
|
payload := &cb.Payload{Header: header}
|
|
payloadBytes := protoutil.MarshalOrPanic(payload)
|
|
return &cb.Envelope{Payload: payloadBytes}
|
|
}
|