495 lines
14 KiB
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
|
|
}
|