go_study/fabric-main/common/ledger/testutil/test_helper.go

495 lines
14 KiB
Go

/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package testutil
import (
"fmt"
"testing"
"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric-protos-go/common"
pb "github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/bccsp/sw"
"github.com/hyperledger/fabric/common/configtx/test"
"github.com/hyperledger/fabric/common/crypto"
"github.com/hyperledger/fabric/internal/pkg/txflags"
"github.com/hyperledger/fabric/common/ledger/testutil/fakes"
"github.com/hyperledger/fabric/msp"
mspmgmt "github.com/hyperledger/fabric/msp/mgmt"
msptesttools "github.com/hyperledger/fabric/msp/mgmt/testtools"
"github.com/hyperledger/fabric/protoutil"
"github.com/stretchr/testify/require"
)
var signer msp.SigningIdentity
func init() {
// setup the MSP manager so that we can sign/verify
err := msptesttools.LoadMSPSetupForTesting()
if err != nil {
panic(fmt.Errorf("Could not load msp config, err %s", err))
}
cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
if err != nil {
panic(fmt.Errorf("Initialize cryptoProvider failed: %s", err))
}
signer, err = mspmgmt.GetLocalMSP(cryptoProvider).GetDefaultSigningIdentity()
if err != nil {
panic(fmt.Errorf("Could not initialize msp/signer"))
}
}
// BlockGenerator generates a series of blocks for testing
type BlockGenerator struct {
blockNum uint64
previousHash []byte
signTxs bool
t *testing.T
}
type TxDetails struct {
TxID string
ChaincodeName, ChaincodeVersion string
SimulationResults []byte
ChaincodeEvents []byte
Type common.HeaderType
}
type BlockDetails struct {
BlockNum uint64
PreviousHash []byte
Txs []*TxDetails
}
//go:generate counterfeiter -o fakes/signing_identity.go --fake-name SigningIdentity . signingIdentity
type signingIdentity interface {
msp.SigningIdentity
}
// NewBlockGenerator instantiates new BlockGenerator for testing
func NewBlockGenerator(t *testing.T, ledgerID string, signTxs bool) (*BlockGenerator, *common.Block) {
gb, err := test.MakeGenesisBlock(ledgerID)
require.NoError(t, err)
gb.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = txflags.NewWithValues(len(gb.Data.Data), pb.TxValidationCode_VALID)
return &BlockGenerator{1, protoutil.BlockHeaderHash(gb.GetHeader()), signTxs, t}, gb
}
// NextBlock constructs next block in sequence that includes a number of transactions - one per simulationResults
func (bg *BlockGenerator) NextBlock(simulationResults [][]byte) *common.Block {
block := ConstructBlock(bg.t, bg.blockNum, bg.previousHash, simulationResults, bg.signTxs)
bg.blockNum++
bg.previousHash = protoutil.BlockHeaderHash(block.Header)
return block
}
// NextBlockWithTxid constructs next block in sequence that includes a number of transactions - one per simulationResults
func (bg *BlockGenerator) NextBlockWithTxid(simulationResults [][]byte, txids []string) *common.Block {
// Length of simulationResults should be same as the length of txids.
if len(simulationResults) != len(txids) {
return nil
}
block := ConstructBlockWithTxid(bg.t, bg.blockNum, bg.previousHash, simulationResults, txids, bg.signTxs)
bg.blockNum++
bg.previousHash = protoutil.BlockHeaderHash(block.Header)
return block
}
// NextTestBlock constructs next block in sequence block with 'numTx' number of transactions for testing
func (bg *BlockGenerator) NextTestBlock(numTx int, txSize int) *common.Block {
simulationResults := [][]byte{}
for i := 0; i < numTx; i++ {
simulationResults = append(simulationResults, ConstructRandomBytes(bg.t, txSize))
}
return bg.NextBlock(simulationResults)
}
// NextTestBlocks constructs 'numBlocks' number of blocks for testing
func (bg *BlockGenerator) NextTestBlocks(numBlocks int) []*common.Block {
blocks := []*common.Block{}
numTx := 10
for i := 0; i < numBlocks; i++ {
block := bg.NextTestBlock(numTx, 100)
block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = txflags.NewWithValues(numTx, pb.TxValidationCode_VALID)
blocks = append(blocks, block)
}
return blocks
}
// ConstructTransaction constructs a transaction for testing
func ConstructTransaction(
t *testing.T,
simulationResults []byte,
txid string,
sign bool,
) (*common.Envelope, string, error) {
return ConstructTransactionWithHeaderType(
t,
simulationResults,
txid,
sign,
common.HeaderType_ENDORSER_TRANSACTION,
)
}
// ConstructTransaction constructs a transaction for testing with header type
func ConstructTransactionWithHeaderType(
t *testing.T,
simulationResults []byte,
txid string,
sign bool,
headerType common.HeaderType,
) (*common.Envelope, string, error) {
return ConstructTransactionFromTxDetails(
&TxDetails{
ChaincodeName: "foo",
ChaincodeVersion: "v1",
TxID: txid,
SimulationResults: simulationResults,
Type: headerType,
},
sign,
)
}
func ConstructTransactionFromTxDetails(txDetails *TxDetails, sign bool) (*common.Envelope, string, error) {
ccid := &pb.ChaincodeID{
Name: txDetails.ChaincodeName,
Version: txDetails.ChaincodeVersion,
}
var txEnv *common.Envelope
var err error
var txID string
if sign {
txEnv, txID, err = ConstructSignedTxEnvWithDefaultSigner(
"testchannelid",
ccid,
nil,
txDetails.SimulationResults,
txDetails.TxID,
txDetails.ChaincodeEvents,
nil,
txDetails.Type,
)
} else {
txEnv, txID, err = ConstructUnsignedTxEnv(
"testchannelid",
ccid,
nil,
txDetails.SimulationResults,
txDetails.TxID,
txDetails.ChaincodeEvents,
nil,
txDetails.Type,
)
}
return txEnv, txID, err
}
func ConstructBlockFromBlockDetails(t *testing.T, blockDetails *BlockDetails, sign bool) *common.Block {
var envs []*common.Envelope
for _, txDetails := range blockDetails.Txs {
env, _, err := ConstructTransactionFromTxDetails(txDetails, sign)
if err != nil {
t.Fatalf("ConstructTestTransaction failed, err %s", err)
}
envs = append(envs, env)
}
return NewBlock(envs, blockDetails.BlockNum, blockDetails.PreviousHash)
}
func ConstructBlockWithTxid(
t *testing.T,
blockNum uint64,
previousHash []byte,
simulationResults [][]byte,
txids []string,
sign bool,
) *common.Block {
return ConstructBlockWithTxidHeaderType(
t,
blockNum,
previousHash,
simulationResults,
txids,
sign,
common.HeaderType_ENDORSER_TRANSACTION,
)
}
func ConstructBlockWithTxidHeaderType(
t *testing.T,
blockNum uint64,
previousHash []byte,
simulationResults [][]byte,
txids []string,
sign bool,
headerType common.HeaderType,
) *common.Block {
envs := []*common.Envelope{}
for i := 0; i < len(simulationResults); i++ {
env, _, err := ConstructTransactionWithHeaderType(
t,
simulationResults[i],
txids[i],
sign,
headerType,
)
if err != nil {
t.Fatalf("ConstructTestTransaction failed, err %s", err)
}
envs = append(envs, env)
}
return NewBlock(envs, blockNum, previousHash)
}
// ConstructBlock constructs a single block
func ConstructBlock(
t *testing.T,
blockNum uint64,
previousHash []byte,
simulationResults [][]byte,
sign bool,
) *common.Block {
envs := []*common.Envelope{}
for i := 0; i < len(simulationResults); i++ {
env, _, err := ConstructTransaction(t, simulationResults[i], "", sign)
if err != nil {
t.Fatalf("ConstructTestTransaction failed, err %s", err)
}
envs = append(envs, env)
}
return NewBlock(envs, blockNum, previousHash)
}
// ConstructTestBlock constructs a single block with random contents
func ConstructTestBlock(t *testing.T, blockNum uint64, numTx int, txSize int) *common.Block {
simulationResults := [][]byte{}
for i := 0; i < numTx; i++ {
simulationResults = append(simulationResults, ConstructRandomBytes(t, txSize))
}
return ConstructBlock(t, blockNum, ConstructRandomBytes(t, 32), simulationResults, false)
}
// ConstructTestBlocks returns a series of blocks starting with blockNum=0.
// The first block in the returned array is a config tx block that represents a genesis block
// Except the genesis block, the size of each of the block would be the same.
func ConstructTestBlocks(t *testing.T, numBlocks int) []*common.Block {
bg, gb := NewBlockGenerator(t, "testchannelid", false)
blocks := []*common.Block{}
if numBlocks != 0 {
blocks = append(blocks, gb)
}
return append(blocks, bg.NextTestBlocks(numBlocks-1)...)
}
// ConstructBytesProposalResponsePayload constructs a ProposalResponse byte with given chaincode version and simulationResults for testing
func ConstructBytesProposalResponsePayload(version string, simulationResults []byte) ([]byte, error) {
ccid := &pb.ChaincodeID{
Name: "foo",
Version: version,
}
return constructBytesProposalResponsePayload("testchannelid", ccid, nil, simulationResults)
}
func NewBlock(env []*common.Envelope, blockNum uint64, previousHash []byte) *common.Block {
block := protoutil.NewBlock(blockNum, previousHash)
for i := 0; i < len(env); i++ {
txEnvBytes, _ := proto.Marshal(env[i])
block.Data.Data = append(block.Data.Data, txEnvBytes)
}
block.Header.DataHash = protoutil.BlockDataHash(block.Data)
protoutil.InitBlockMetadata(block)
block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = txflags.NewWithValues(len(env), pb.TxValidationCode_VALID)
return block
}
// constructBytesProposalResponsePayload constructs a ProposalResponsePayload byte for tests with a default signer.
func constructBytesProposalResponsePayload(channelID string, ccid *pb.ChaincodeID, pResponse *pb.Response, simulationResults []byte) ([]byte, error) {
ss, err := signer.Serialize()
if err != nil {
return nil, err
}
prop, _, err := protoutil.CreateChaincodeProposal(common.HeaderType_ENDORSER_TRANSACTION, channelID, &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: ccid}}, ss)
if err != nil {
return nil, err
}
presp, err := protoutil.CreateProposalResponse(prop.Header, prop.Payload, pResponse, simulationResults, nil, ccid, signer)
if err != nil {
return nil, err
}
return presp.Payload, nil
}
// ConstructSignedTxEnvWithDefaultSigner constructs a transaction envelop for tests with a default signer.
// This method helps other modules to construct a transaction with supplied parameters
func ConstructSignedTxEnvWithDefaultSigner(
chainID string,
ccid *pb.ChaincodeID,
response *pb.Response,
simulationResults []byte,
txid string,
events []byte,
visibility []byte,
headerType common.HeaderType,
) (*common.Envelope, string, error) {
return ConstructSignedTxEnv(
chainID,
ccid,
response,
simulationResults,
txid,
events,
visibility,
signer,
headerType,
)
}
// ConstructUnsignedTxEnv creates a Transaction envelope from given inputs
func ConstructUnsignedTxEnv(
chainID string,
ccid *pb.ChaincodeID,
response *pb.Response,
simulationResults []byte,
txid string,
events []byte,
visibility []byte,
headerType common.HeaderType,
) (*common.Envelope, string, error) {
sigId := &fakes.SigningIdentity{}
return ConstructSignedTxEnv(
chainID,
ccid,
response,
simulationResults,
txid,
events,
visibility,
sigId,
headerType,
)
}
// ConstructSignedTxEnv constructs a transaction envelop for tests
func ConstructSignedTxEnv(
channelID string,
ccid *pb.ChaincodeID,
pResponse *pb.Response,
simulationResults []byte,
txid string,
events []byte,
visibility []byte,
signer msp.SigningIdentity,
headerType common.HeaderType,
) (*common.Envelope, string, error) {
ss, err := signer.Serialize()
if err != nil {
return nil, "", err
}
var prop *pb.Proposal
if txid == "" {
// if txid is not set, then we need to generate one while creating the proposal message
prop, txid, err = protoutil.CreateChaincodeProposal(
common.HeaderType_ENDORSER_TRANSACTION,
channelID,
&pb.ChaincodeInvocationSpec{
ChaincodeSpec: &pb.ChaincodeSpec{
ChaincodeId: ccid,
},
},
ss,
)
} else {
// if txid is set, we should not generate a txid instead reuse the given txid
var nonce []byte
nonce, err = crypto.GetRandomNonce()
if err != nil {
return nil, "", err
}
prop, txid, err = protoutil.CreateChaincodeProposalWithTxIDNonceAndTransient(
txid,
headerType,
channelID,
&pb.ChaincodeInvocationSpec{
ChaincodeSpec: &pb.ChaincodeSpec{
ChaincodeId: ccid,
},
},
nonce,
ss,
nil,
)
}
if err != nil {
return nil, "", err
}
presp, err := protoutil.CreateProposalResponse(
prop.Header,
prop.Payload,
pResponse,
simulationResults,
events,
ccid,
signer,
)
if err != nil {
return nil, "", err
}
env, err := protoutil.CreateSignedTx(prop, signer, presp)
if err != nil {
return nil, "", err
}
return env, txid, nil
}
func SetTxID(t *testing.T, block *common.Block, txNum int, txID string) {
envelopeBytes := block.Data.Data[txNum]
envelope, err := protoutil.UnmarshalEnvelope(envelopeBytes)
if err != nil {
t.Fatalf("error unmarshalling envelope: %s", err)
}
payload, err := protoutil.UnmarshalPayload(envelope.Payload)
if err != nil {
t.Fatalf("error getting payload from envelope: %s", err)
}
channelHeader, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader)
if err != nil {
t.Fatalf("error unmarshalling channel header: %s", err)
}
channelHeader.TxId = txID
channelHeaderBytes, err := proto.Marshal(channelHeader)
if err != nil {
t.Fatalf("error marshaling channel header: %s", err)
}
payload.Header.ChannelHeader = channelHeaderBytes
payloadBytes, err := proto.Marshal(payload)
if err != nil {
t.Fatalf("error marshaling payload: %s", err)
}
envelope.Payload = payloadBytes
envelopeBytes, err = proto.Marshal(envelope)
if err != nil {
t.Fatalf("error marshaling envelope: %s", err)
}
block.Data.Data[txNum] = envelopeBytes
}