/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package peer

import (
	"context"
	"fmt"
	"io"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/hyperledger/fabric-protos-go/common"
	"github.com/hyperledger/fabric-protos-go/orderer"
	"github.com/hyperledger/fabric-protos-go/peer"
	"github.com/hyperledger/fabric/common/deliver"
	"github.com/hyperledger/fabric/common/ledger/blockledger"
	"github.com/hyperledger/fabric/common/metrics/disabled"
	"github.com/hyperledger/fabric/common/policies"
	"github.com/hyperledger/fabric/common/util"
	"github.com/hyperledger/fabric/core/ledger"
	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil"
	fake "github.com/hyperledger/fabric/core/peer/mock"
	"github.com/hyperledger/fabric/protoutil"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"google.golang.org/grpc/metadata"
	peer2 "google.golang.org/grpc/peer"
)

// defaultPolicyCheckerProvider policy checker provider used by default,
// generates policy checker which always accepts regardless of arguments
// passed in
var defaultPolicyCheckerProvider = func(_ string) deliver.PolicyCheckerFunc {
	return func(_ *common.Envelope, _ string) error {
		return nil
	}
}

//go:generate counterfeiter -o mock/peer_ledger.go -fake-name PeerLedger . peerLedger

type peerLedger interface {
	ledger.PeerLedger
}

//go:generate counterfeiter -o mock/identity_deserializer_manager.go -fake-name IdentityDeserializerManager . identityDeserializerManager

type identityDeserializerManager interface {
	IdentityDeserializerManager
}

//go:generate counterfeiter -o mock/collection_policy_checker.go -fake-name CollectionPolicyChecker . collectionPolicyChecker

type collectionPolicyChecker interface {
	CollectionPolicyChecker
}

// mockIterator mock structure implementing
// the blockledger.Iterator interface
type mockIterator struct {
	mock.Mock
}

func (m *mockIterator) Next() (*common.Block, common.Status) {
	args := m.Called()
	return args.Get(0).(*common.Block), args.Get(1).(common.Status)
}

func (m *mockIterator) ReadyChan() <-chan struct{} {
	panic("implement me")
}

func (m *mockIterator) Close() {

}

// mockReader mock structure implementing
// the blockledger.Reader interface
type mockReader struct {
	mock.Mock
}

func (m *mockReader) Iterator(startType *orderer.SeekPosition) (blockledger.Iterator, uint64) {
	args := m.Called(startType)
	return args.Get(0).(blockledger.Iterator), args.Get(1).(uint64)
}

func (m *mockReader) Height() uint64 {
	args := m.Called()
	return args.Get(0).(uint64)
}

// mockChainSupport
type mockChainSupport struct {
	mock.Mock
}

func (m *mockChainSupport) Sequence() uint64 {
	return m.Called().Get(0).(uint64)
}

func (m *mockChainSupport) Ledger() ledger.PeerLedger {
	return m.Called().Get(0).(ledger.PeerLedger)
}

func (m *mockChainSupport) PolicyManager() policies.Manager {
	panic("implement me")
}

func (m *mockChainSupport) Reader() blockledger.Reader {
	return m.Called().Get(0).(blockledger.Reader)
}

func (*mockChainSupport) Errored() <-chan struct{} {
	return make(chan struct{})
}

// mockChainManager mock implementation of the ChainManager interface
type mockChainManager struct {
	mock.Mock
}

func (m *mockChainManager) GetChain(channelID string) deliver.Chain {
	args := m.Called(channelID)
	return args.Get(0).(deliver.Chain)
}

// mockDeliverServer mock implementation of the Deliver_DeliverServer
type mockDeliverServer struct {
	mock.Mock
}

func (m *mockDeliverServer) Context() context.Context {
	return m.Called().Get(0).(context.Context)
}

func (m *mockDeliverServer) Recv() (*common.Envelope, error) {
	args := m.Called()
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*common.Envelope), args.Error(1)
}

func (m *mockDeliverServer) Send(response *peer.DeliverResponse) error {
	args := m.Called(response)
	return args.Error(0)
}

func (*mockDeliverServer) RecvMsg(m interface{}) error {
	panic("implement me")
}

func (*mockDeliverServer) SendHeader(metadata.MD) error {
	panic("implement me")
}

func (*mockDeliverServer) SendMsg(m interface{}) error {
	panic("implement me")
}

func (*mockDeliverServer) SetHeader(metadata.MD) error {
	panic("implement me")
}

func (*mockDeliverServer) SetTrailer(metadata.MD) {
	panic("implement me")
}

type testConfig struct {
	channelID     string
	eventName     string
	chaincodeName string
	txID          string
	payload       *common.Payload
	*require.Assertions
}

type testCase struct {
	name    string
	prepare func(wg *sync.WaitGroup) (deliver.ChainManager, peer.Deliver_DeliverServer)
}

func TestFilteredBlockResponseSenderIsFiltered(t *testing.T) {
	var fbrs interface{} = &filteredBlockResponseSender{}
	filtered, ok := fbrs.(deliver.Filtered)
	require.True(t, ok, "should be filtered")
	require.True(t, filtered.IsFiltered(), "should return true from IsFiltered")
}

func TestEventsServer_DeliverFiltered(t *testing.T) {
	tests := []testCase{
		{
			name: "Testing deliver of the filtered block events",
			prepare: func(config testConfig) func(wg *sync.WaitGroup) (deliver.ChainManager, peer.Deliver_DeliverServer) {
				return func(wg *sync.WaitGroup) (deliver.ChainManager, peer.Deliver_DeliverServer) {
					wg.Add(2)
					p := &peer2.Peer{}
					chaincodeActionPayload, err := createChaincodeAction(config.chaincodeName, config.eventName, config.txID)
					config.NoError(err)

					chainManager := createDefaultSupportMamangerMock(config, chaincodeActionPayload, nil)
					// setup mock deliver server
					deliverServer := &mockDeliverServer{}
					deliverServer.On("Context").Return(peer2.NewContext(context.TODO(), p))

					deliverServer.On("Recv").Return(&common.Envelope{
						Payload: protoutil.MarshalOrPanic(config.payload),
					}, nil).Run(func(_ mock.Arguments) {
						// once we are getting new message we need to mock
						// Recv call to get io.EOF to stop the looping for
						// next message and we can assert for the DeliverResponse
						// value we are getting from the deliver server
						deliverServer.Mock = mock.Mock{}
						deliverServer.On("Context").Return(peer2.NewContext(context.TODO(), p))
						deliverServer.On("Recv").Return(&common.Envelope{}, io.EOF)
						deliverServer.On("Send", mock.Anything).Run(func(args mock.Arguments) {
							defer wg.Done()
							response := args.Get(0).(*peer.DeliverResponse)
							switch response.Type.(type) {
							case *peer.DeliverResponse_Status:
								config.Equal(common.Status_SUCCESS, response.GetStatus())
							case *peer.DeliverResponse_FilteredBlock:
								block := response.GetFilteredBlock()
								config.Equal(uint64(0), block.Number)
								config.Equal(config.channelID, block.ChannelId)
								config.Equal(1, len(block.FilteredTransactions))
								tx := block.FilteredTransactions[0]
								config.Equal(config.txID, tx.Txid)
								config.Equal(peer.TxValidationCode_VALID, tx.TxValidationCode)
								config.Equal(common.HeaderType_ENDORSER_TRANSACTION, tx.Type)
								transactionActions := tx.GetTransactionActions()
								config.NotNil(transactionActions)
								chaincodeActions := transactionActions.ChaincodeActions
								config.Equal(1, len(chaincodeActions))
								config.Equal(config.eventName, chaincodeActions[0].ChaincodeEvent.EventName)
								config.Equal(config.txID, chaincodeActions[0].ChaincodeEvent.TxId)
								config.Equal(config.chaincodeName, chaincodeActions[0].ChaincodeEvent.ChaincodeId)
							default:
								config.FailNow("Unexpected response type")
							}
						}).Return(nil)
					})

					return chainManager, deliverServer
				}
			}(testConfig{
				channelID:     "testChannelID",
				eventName:     "testEvent",
				chaincodeName: "mycc",
				txID:          "testID",
				payload: &common.Payload{
					Header: &common.Header{
						ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{
							ChannelId: "testChannelID",
							Timestamp: util.CreateUtcTimestamp(),
						}),
						SignatureHeader: protoutil.MarshalOrPanic(&common.SignatureHeader{}),
					},
					Data: protoutil.MarshalOrPanic(&orderer.SeekInfo{
						Start:    &orderer.SeekPosition{Type: &orderer.SeekPosition_Specified{Specified: &orderer.SeekSpecified{Number: 0}}},
						Stop:     &orderer.SeekPosition{Type: &orderer.SeekPosition_Newest{Newest: &orderer.SeekNewest{}}},
						Behavior: orderer.SeekInfo_BLOCK_UNTIL_READY,
					}),
				},
				Assertions: require.New(t),
			}),
		},
		{
			name: "Testing deliver of the filtered block events with nil chaincode action payload",
			prepare: func(config testConfig) func(wg *sync.WaitGroup) (deliver.ChainManager, peer.Deliver_DeliverServer) {
				return func(wg *sync.WaitGroup) (deliver.ChainManager, peer.Deliver_DeliverServer) {
					wg.Add(2)
					p := &peer2.Peer{}
					chainManager := createDefaultSupportMamangerMock(config, nil, nil)

					// setup mock deliver server
					deliverServer := &mockDeliverServer{}
					deliverServer.On("Context").Return(peer2.NewContext(context.TODO(), p))

					deliverServer.On("Recv").Return(&common.Envelope{
						Payload: protoutil.MarshalOrPanic(config.payload),
					}, nil).Run(func(_ mock.Arguments) {
						// once we are getting new message we need to mock
						// Recv call to get io.EOF to stop the looping for
						// next message and we can assert for the DeliverResponse
						// value we are getting from the deliver server
						deliverServer.Mock = mock.Mock{}
						deliverServer.On("Context").Return(peer2.NewContext(context.TODO(), p))
						deliverServer.On("Recv").Return(&common.Envelope{}, io.EOF)
						deliverServer.On("Send", mock.Anything).Run(func(args mock.Arguments) {
							defer wg.Done()
							response := args.Get(0).(*peer.DeliverResponse)
							switch response.Type.(type) {
							case *peer.DeliverResponse_Status:
								config.Equal(common.Status_SUCCESS, response.GetStatus())
							case *peer.DeliverResponse_FilteredBlock:
								block := response.GetFilteredBlock()
								config.Equal(uint64(0), block.Number)
								config.Equal(config.channelID, block.ChannelId)
								config.Equal(1, len(block.FilteredTransactions))
								tx := block.FilteredTransactions[0]
								config.Equal(config.txID, tx.Txid)
								config.Equal(peer.TxValidationCode_VALID, tx.TxValidationCode)
								config.Equal(common.HeaderType_ENDORSER_TRANSACTION, tx.Type)
								transactionActions := tx.GetTransactionActions()
								config.NotNil(transactionActions)
								chaincodeActions := transactionActions.ChaincodeActions
								// we expecting to get zero chaincode action,
								// since provided nil payload
								config.Equal(0, len(chaincodeActions))
							default:
								config.FailNow("Unexpected response type")
							}
						}).Return(nil)
					})

					return chainManager, deliverServer
				}
			}(testConfig{
				channelID:     "testChannelID",
				eventName:     "testEvent",
				chaincodeName: "mycc",
				txID:          "testID",
				payload: &common.Payload{
					Header: &common.Header{
						ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{
							ChannelId: "testChannelID",
							Timestamp: util.CreateUtcTimestamp(),
						}),
						SignatureHeader: protoutil.MarshalOrPanic(&common.SignatureHeader{}),
					},
					Data: protoutil.MarshalOrPanic(&orderer.SeekInfo{
						Start:    &orderer.SeekPosition{Type: &orderer.SeekPosition_Specified{Specified: &orderer.SeekSpecified{Number: 0}}},
						Stop:     &orderer.SeekPosition{Type: &orderer.SeekPosition_Newest{Newest: &orderer.SeekNewest{}}},
						Behavior: orderer.SeekInfo_BLOCK_UNTIL_READY,
					}),
				},
				Assertions: require.New(t),
			}),
		},
		{
			name: "Testing deliver of the filtered block events with nil payload header",
			prepare: func(config testConfig) func(wg *sync.WaitGroup) (deliver.ChainManager, peer.Deliver_DeliverServer) {
				return func(wg *sync.WaitGroup) (deliver.ChainManager, peer.Deliver_DeliverServer) {
					wg.Add(1)
					p := &peer2.Peer{}
					chainManager := createDefaultSupportMamangerMock(config, nil, nil)

					// setup mock deliver server
					deliverServer := &mockDeliverServer{}
					deliverServer.On("Context").Return(peer2.NewContext(context.TODO(), p))

					deliverServer.On("Recv").Return(&common.Envelope{
						Payload: protoutil.MarshalOrPanic(config.payload),
					}, nil).Run(func(_ mock.Arguments) {
						// once we are getting new message we need to mock
						// Recv call to get io.EOF to stop the looping for
						// next message and we can assert for the DeliverResponse
						// value we are getting from the deliver server
						deliverServer.Mock = mock.Mock{}
						deliverServer.On("Context").Return(peer2.NewContext(context.TODO(), p))
						deliverServer.On("Recv").Return(&common.Envelope{}, io.EOF)
						deliverServer.On("Send", mock.Anything).Run(func(args mock.Arguments) {
							defer wg.Done()
							response := args.Get(0).(*peer.DeliverResponse)
							switch response.Type.(type) {
							case *peer.DeliverResponse_Status:
								config.Equal(common.Status_BAD_REQUEST, response.GetStatus())
							case *peer.DeliverResponse_FilteredBlock:
								config.FailNow("Unexpected response type")
							default:
								config.FailNow("Unexpected response type")
							}
						}).Return(nil)
					})

					return chainManager, deliverServer
				}
			}(testConfig{
				channelID:     "testChannelID",
				eventName:     "testEvent",
				chaincodeName: "mycc",
				txID:          "testID",
				payload: &common.Payload{
					Header: nil,
					Data: protoutil.MarshalOrPanic(&orderer.SeekInfo{
						Start:    &orderer.SeekPosition{Type: &orderer.SeekPosition_Specified{Specified: &orderer.SeekSpecified{Number: 0}}},
						Stop:     &orderer.SeekPosition{Type: &orderer.SeekPosition_Newest{Newest: &orderer.SeekNewest{}}},
						Behavior: orderer.SeekInfo_BLOCK_UNTIL_READY,
					}),
				},
				Assertions: require.New(t),
			}),
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			wg := &sync.WaitGroup{}
			chainManager, deliverServer := test.prepare(wg)

			metrics := deliver.NewMetrics(&disabled.Provider{})
			server := &DeliverServer{
				DeliverHandler:        deliver.NewHandler(chainManager, time.Second, false, metrics, false),
				PolicyCheckerProvider: defaultPolicyCheckerProvider,
			}

			err := server.DeliverFiltered(deliverServer)
			wg.Wait()
			// no error expected
			require.NoError(t, err)
		})
	}
}

func TestEventsServer_DeliverWithPrivateData(t *testing.T) {
	fakeDeserializerMgr := &fake.IdentityDeserializerManager{}
	fakeDeserializerMgr.DeserializerReturns(nil, nil)
	fakeCollPolicyChecker := &fake.CollectionPolicyChecker{}
	fakeCollPolicyChecker.CheckCollectionPolicyReturns(true, nil)
	tests := []testCase{
		{
			name: "Testing deliver block with private data events",
			prepare: func(config testConfig) func(wg *sync.WaitGroup) (deliver.ChainManager, peer.Deliver_DeliverServer) {
				return func(wg *sync.WaitGroup) (deliver.ChainManager, peer.Deliver_DeliverServer) {
					wg.Add(2)
					p := &peer2.Peer{}
					chaincodeActionPayload, err := createChaincodeAction(config.chaincodeName, config.eventName, config.txID)
					config.NoError(err)

					pvtData := []*ledger.TxPvtData{
						produceSamplePvtdataOrPanic(0, []string{"ns-0:coll-0", "ns-2:coll-20", "ns-2:coll-21"}),
					}
					chainManager := createDefaultSupportMamangerMock(config, chaincodeActionPayload, pvtData)

					deliverServer := &mockDeliverServer{}
					deliverServer.On("Context").Return(peer2.NewContext(context.TODO(), p))
					deliverServer.On("Recv").Return(&common.Envelope{
						Payload: protoutil.MarshalOrPanic(config.payload),
					}, nil).Run(func(_ mock.Arguments) {
						// mock Recv calls to get io.EOF to stop the looping for next message
						deliverServer.Mock = mock.Mock{}
						deliverServer.On("Context").Return(peer2.NewContext(context.TODO(), p))
						deliverServer.On("Recv").Return(&common.Envelope{}, io.EOF)
						deliverServer.On("Send", mock.Anything).Run(func(args mock.Arguments) {
							defer wg.Done()
							response := args.Get(0).(*peer.DeliverResponse)
							switch response.Type.(type) {
							case *peer.DeliverResponse_Status:
								config.Equal(common.Status_SUCCESS, response.GetStatus())
							case *peer.DeliverResponse_BlockAndPrivateData:
								blockAndPvtData := response.GetBlockAndPrivateData()
								block := blockAndPvtData.Block
								config.Equal(uint64(0), block.Header.Number)
								config.Equal(1, len(blockAndPvtData.PrivateDataMap))
								config.NotNil(blockAndPvtData.PrivateDataMap[uint64(0)])
								txPvtRwset := blockAndPvtData.PrivateDataMap[uint64(0)]
								// expect to have 2 NsPvtRwset (i.e., 2 namespaces)
								config.Equal(2, len(txPvtRwset.NsPvtRwset))
								// check namespace because the index may be out of order
								for _, nsPvtRwset := range txPvtRwset.NsPvtRwset {
									switch nsPvtRwset.Namespace {
									case "ns-0":
										config.Equal(1, len(nsPvtRwset.CollectionPvtRwset))
										config.Equal("coll-0", nsPvtRwset.CollectionPvtRwset[0].CollectionName)
									case "ns-2":
										config.Equal(2, len(nsPvtRwset.CollectionPvtRwset))
										config.Equal("coll-20", nsPvtRwset.CollectionPvtRwset[0].CollectionName)
										config.Equal("coll-21", nsPvtRwset.CollectionPvtRwset[1].CollectionName)
									default:
										config.FailNow("Wrong namespace " + nsPvtRwset.Namespace)
									}
								}
							default:
								config.FailNow("Unexpected response type")
							}
						}).Return(nil)
					})

					return chainManager, deliverServer
				}
			}(testConfig{
				channelID:     "testChannelID",
				eventName:     "testEvent",
				chaincodeName: "mycc",
				txID:          "testID",
				payload: &common.Payload{
					Header: &common.Header{
						ChannelHeader: protoutil.MarshalOrPanic(&common.ChannelHeader{
							ChannelId: "testChannelID",
							Timestamp: util.CreateUtcTimestamp(),
						}),
						SignatureHeader: protoutil.MarshalOrPanic(&common.SignatureHeader{}),
					},
					Data: protoutil.MarshalOrPanic(&orderer.SeekInfo{
						Start:    &orderer.SeekPosition{Type: &orderer.SeekPosition_Specified{Specified: &orderer.SeekSpecified{Number: 0}}},
						Stop:     &orderer.SeekPosition{Type: &orderer.SeekPosition_Newest{Newest: &orderer.SeekNewest{}}},
						Behavior: orderer.SeekInfo_BLOCK_UNTIL_READY,
					}),
				},
				Assertions: require.New(t),
			}),
		},
		{
			name: "Testing deliver of block with private data events with nil header",
			prepare: func(config testConfig) func(wg *sync.WaitGroup) (deliver.ChainManager, peer.Deliver_DeliverServer) {
				return func(wg *sync.WaitGroup) (deliver.ChainManager, peer.Deliver_DeliverServer) {
					wg.Add(1)
					p := &peer2.Peer{}
					chainManager := createDefaultSupportMamangerMock(config, nil, nil)

					deliverServer := &mockDeliverServer{}
					deliverServer.On("Context").Return(peer2.NewContext(context.TODO(), p))
					deliverServer.On("Recv").Return(&common.Envelope{
						Payload: protoutil.MarshalOrPanic(config.payload),
					}, nil).Run(func(_ mock.Arguments) {
						// mock Recv calls to get io.EOF to stop the looping for next message
						deliverServer.Mock = mock.Mock{}
						deliverServer.On("Context").Return(peer2.NewContext(context.TODO(), p))
						deliverServer.On("Recv").Return(&common.Envelope{}, io.EOF)
						deliverServer.On("Send", mock.Anything).Run(func(args mock.Arguments) {
							defer wg.Done()
							response := args.Get(0).(*peer.DeliverResponse)
							switch response.Type.(type) {
							case *peer.DeliverResponse_Status:
								config.Equal(common.Status_BAD_REQUEST, response.GetStatus())
							case *peer.DeliverResponse_BlockAndPrivateData:
								config.FailNow("Unexpected response type")
							default:
								config.FailNow("Unexpected response type")
							}
						}).Return(nil)
					})

					return chainManager, deliverServer
				}
			}(testConfig{
				channelID:     "testChannelID",
				eventName:     "testEvent",
				chaincodeName: "mycc",
				txID:          "testID",
				payload: &common.Payload{
					Header: nil,
					Data: protoutil.MarshalOrPanic(&orderer.SeekInfo{
						Start:    &orderer.SeekPosition{Type: &orderer.SeekPosition_Specified{Specified: &orderer.SeekSpecified{Number: 0}}},
						Stop:     &orderer.SeekPosition{Type: &orderer.SeekPosition_Newest{Newest: &orderer.SeekNewest{}}},
						Behavior: orderer.SeekInfo_BLOCK_UNTIL_READY,
					}),
				},
				Assertions: require.New(t),
			}),
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			wg := &sync.WaitGroup{}
			chainManager, deliverServer := test.prepare(wg)

			metrics := deliver.NewMetrics(&disabled.Provider{})
			handler := deliver.NewHandler(chainManager, time.Second, false, metrics, false)
			server := &DeliverServer{
				DeliverHandler:          handler,
				PolicyCheckerProvider:   defaultPolicyCheckerProvider,
				CollectionPolicyChecker: fakeCollPolicyChecker,
				IdentityDeserializerMgr: fakeDeserializerMgr,
			}

			err := server.DeliverWithPrivateData(deliverServer)
			wg.Wait()
			// no error expected
			require.NoError(t, err)
		})
	}
}

func createDefaultSupportMamangerMock(config testConfig, chaincodeActionPayload *peer.ChaincodeActionPayload, pvtData []*ledger.TxPvtData) *mockChainManager {
	chainManager := &mockChainManager{}
	iter := &mockIterator{}
	reader := &mockReader{}
	chain := &mockChainSupport{}
	ldgr := &fake.PeerLedger{}

	payload, err := createEndorsement(config.channelID, config.txID, chaincodeActionPayload)
	config.NoError(err)

	payloadBytes, err := proto.Marshal(payload)
	config.NoError(err)

	block, err := createTestBlock([]*common.Envelope{{
		Payload:   payloadBytes,
		Signature: []byte{}}})
	config.NoError(err)

	iter.On("Next").Return(block, common.Status_SUCCESS)
	reader.On("Iterator", mock.Anything).Return(iter, uint64(1))
	reader.On("Height").Return(uint64(1))
	chain.On("Sequence").Return(uint64(0))
	chain.On("Reader").Return(reader)
	chain.On("Ledger").Return(ldgr)
	chainManager.On("GetChain", config.channelID).Return(chain, true)

	ldgr.GetPvtDataByNumReturns(pvtData, nil)

	return chainManager
}

func createEndorsement(channelID string, txID string, chaincodeActionPayload *peer.ChaincodeActionPayload) (*common.Payload, error) {
	var chActionBytes []byte
	var err error
	if chaincodeActionPayload != nil {
		chActionBytes, err = proto.Marshal(chaincodeActionPayload)
		if err != nil {
			return nil, err
		}
	}

	// the transaction
	txBytes, err := proto.Marshal(&peer.Transaction{
		Actions: []*peer.TransactionAction{
			{
				Payload: chActionBytes,
			},
		},
	})
	if err != nil {
		return nil, err
	}
	// channel header
	chdrBytes, err := proto.Marshal(&common.ChannelHeader{
		ChannelId: channelID,
		TxId:      txID,
		Type:      int32(common.HeaderType_ENDORSER_TRANSACTION),
	})
	if err != nil {
		return nil, err
	}

	// the payload
	payload := &common.Payload{
		Header: &common.Header{
			ChannelHeader: chdrBytes,
		},
		Data: txBytes,
	}
	return payload, nil
}

func createChaincodeAction(chaincodeName string, eventName string, txID string) (*peer.ChaincodeActionPayload, error) {
	// chaincode events
	eventsBytes, err := proto.Marshal(&peer.ChaincodeEvent{
		ChaincodeId: chaincodeName,
		EventName:   eventName,
		TxId:        txID,
	})
	if err != nil {
		return nil, err
	}

	// chaincode action
	actionBytes, err := proto.Marshal(&peer.ChaincodeAction{
		ChaincodeId: &peer.ChaincodeID{
			Name: chaincodeName,
		},
		Events: eventsBytes,
	})
	if err != nil {
		return nil, err
	}

	// proposal response
	proposalResBytes, err := proto.Marshal(&peer.ProposalResponsePayload{
		Extension: actionBytes,
	})
	if err != nil {
		return nil, err
	}

	// chaincode action
	chaincodeActionPayload := &peer.ChaincodeActionPayload{
		Action: &peer.ChaincodeEndorsedAction{
			ProposalResponsePayload: proposalResBytes,
			Endorsements:            []*peer.Endorsement{},
		},
	}
	return chaincodeActionPayload, err
}

func createTestBlock(data []*common.Envelope) (*common.Block, error) {
	// block
	block := &common.Block{
		Header: &common.BlockHeader{
			Number:   0,
			DataHash: []byte{},
		},
		Data:     &common.BlockData{},
		Metadata: &common.BlockMetadata{},
	}
	for _, d := range data {
		envBytes, err := proto.Marshal(d)
		if err != nil {
			return nil, err
		}

		block.Data.Data = append(block.Data.Data, envBytes)
	}
	// making up metadata
	block.Metadata.Metadata = make([][]byte, 4)
	block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = make([]byte, len(data))
	return block, nil
}

func produceSamplePvtdataOrPanic(txNum uint64, nsColls []string) *ledger.TxPvtData {
	builder := rwsetutil.NewRWSetBuilder()
	for _, nsColl := range nsColls {
		nsCollSplit := strings.Split(nsColl, ":")
		ns := nsCollSplit[0]
		coll := nsCollSplit[1]
		builder.AddToPvtAndHashedWriteSet(ns, coll, fmt.Sprintf("key-%s-%s", ns, coll), []byte(fmt.Sprintf("value-%s-%s", ns, coll)))
	}
	simRes, err := builder.GetTxSimulationResults()
	if err != nil {
		panic(err)
	}
	return &ledger.TxPvtData{SeqInBlock: txNum, WriteSet: simRes.PvtSimulationResults}
}
