Skip to content

Commit

Permalink
[FABG-735] Allow client to provide nonce/creator
Browse files Browse the repository at this point in the history
Options were added allow clients to provide the
nonce and/or creator in the transaction header.

Change-Id: I0773fe740ced408e18962a0b88ae5d846078fc25
Signed-off-by: Bob Stasyszyn <Bob.Stasyszyn@securekey.com>
  • Loading branch information
bstasyszyn committed Aug 23, 2018
1 parent 8d8c7b8 commit 72066f7
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 20 deletions.
28 changes: 24 additions & 4 deletions pkg/client/channel/invoke/txnhandler.go
Expand Up @@ -21,9 +21,14 @@ import (
pb "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/peer"
)

// TxnHeaderOptsProvider provides transaction header options which allow
// the provider to specify a custom creator and/or nonce.
type TxnHeaderOptsProvider func() []fab.TxnHeaderOpt

//EndorsementHandler for handling endorse transactions
type EndorsementHandler struct {
next Handler
next Handler
headerOptsProvider TxnHeaderOptsProvider
}

//Handle for endorsing transactions
Expand All @@ -35,7 +40,17 @@ func (e *EndorsementHandler) Handle(requestContext *RequestContext, clientContex
}

// Endorse Tx
transactionProposalResponses, proposal, err := createAndSendTransactionProposal(clientContext.Transactor, &requestContext.Request, peer.PeersToTxnProcessors(requestContext.Opts.Targets))
var TxnHeaderOpts []fab.TxnHeaderOpt
if e.headerOptsProvider != nil {
TxnHeaderOpts = e.headerOptsProvider()
}

transactionProposalResponses, proposal, err := createAndSendTransactionProposal(
clientContext.Transactor,
&requestContext.Request,
peer.PeersToTxnProcessors(requestContext.Opts.Targets),
TxnHeaderOpts...,
)

requestContext.Response.Proposal = proposal
requestContext.Response.TransactionID = proposal.TxnID // TODO: still needed?
Expand Down Expand Up @@ -214,6 +229,11 @@ func NewEndorsementHandler(next ...Handler) *EndorsementHandler {
return &EndorsementHandler{next: getNext(next)}
}

//NewEndorsementHandlerWithOpts returns a handler that endorses a transaction proposal
func NewEndorsementHandlerWithOpts(next Handler, provider TxnHeaderOptsProvider) *EndorsementHandler {
return &EndorsementHandler{next: next, headerOptsProvider: provider}
}

//NewEndorsementValidationHandler returns a handler that validates an endorsement
func NewEndorsementValidationHandler(next ...Handler) *EndorsementValidationHandler {
return &EndorsementValidationHandler{next: getNext(next)}
Expand Down Expand Up @@ -252,15 +272,15 @@ func createAndSendTransaction(sender fab.Sender, proposal *fab.TransactionPropos
return transactionResponse, nil
}

func createAndSendTransactionProposal(transactor fab.ProposalSender, chrequest *Request, targets []fab.ProposalProcessor) ([]*fab.TransactionProposalResponse, *fab.TransactionProposal, error) {
func createAndSendTransactionProposal(transactor fab.ProposalSender, chrequest *Request, targets []fab.ProposalProcessor, opts ...fab.TxnHeaderOpt) ([]*fab.TransactionProposalResponse, *fab.TransactionProposal, error) {
request := fab.ChaincodeInvokeRequest{
ChaincodeID: chrequest.ChaincodeID,
Fcn: chrequest.Fcn,
Args: chrequest.Args,
TransientMap: chrequest.TransientMap,
}

txh, err := transactor.CreateTransactionHeader()
txh, err := transactor.CreateTransactionHeader(opts...)
if err != nil {
return nil, nil, errors.WithMessage(err, "creating transaction header failed")
}
Expand Down
13 changes: 13 additions & 0 deletions pkg/client/channel/invoke/txnhandler_test.go
Expand Up @@ -174,6 +174,19 @@ func TestEndorsementHandler(t *testing.T) {
handler.Handle(requestContext, clientContext)
assert.Nil(t, requestContext.Error)

optsProviderCalled := false
optsProvider := func() []fab.TxnHeaderOpt {
optsProviderCalled = true
var opts []fab.TxnHeaderOpt
opts = append(opts, fab.WithCreator([]byte("somecreator")))
opts = append(opts, fab.WithNonce([]byte("somenonce")))
return opts
}

handler = NewEndorsementHandlerWithOpts(nil, optsProvider)
handler.Handle(requestContext, clientContext)
assert.Nil(t, requestContext.Error)
assert.Truef(t, optsProviderCalled, "expecting opts provider to be called")
}

// Target filter
Expand Down
2 changes: 1 addition & 1 deletion pkg/client/common/mocks/mocktransactor.go
Expand Up @@ -24,7 +24,7 @@ type MockTransactor struct {
}

// CreateTransactionHeader creates a Transaction Header based on the current context.
func (t *MockTransactor) CreateTransactionHeader() (fab.TransactionHeader, error) {
func (t *MockTransactor) CreateTransactionHeader(opts ...fab.TxnHeaderOpt) (fab.TransactionHeader, error) {
txh, err := txn.NewHeader(t.Ctx, t.ChannelID)
if err != nil {
return nil, errors.WithMessage(err, "new transaction ID failed")
Expand Down
25 changes: 24 additions & 1 deletion pkg/common/providers/fab/proposer.go
Expand Up @@ -17,9 +17,32 @@ type ProposalProcessor interface {
ProcessTransactionProposal(reqContext.Context, ProcessProposalRequest) (*TransactionProposalResponse, error)
}

// TxnHeaderOptions contains options for creating a Transaction Header
type TxnHeaderOptions struct {
Nonce []byte
Creator []byte
}

// TxnHeaderOpt is a Transaction Header option
type TxnHeaderOpt func(*TxnHeaderOptions)

// WithNonce specifies the nonce to use when creating the Transaction Header
func WithNonce(nonce []byte) TxnHeaderOpt {
return func(options *TxnHeaderOptions) {
options.Nonce = nonce
}
}

// WithCreator specifies the creator to use when creating the Transaction Header
func WithCreator(creator []byte) TxnHeaderOpt {
return func(options *TxnHeaderOptions) {
options.Creator = creator
}
}

// ProposalSender provides the ability for a transaction proposal to be created and sent.
type ProposalSender interface {
CreateTransactionHeader() (TransactionHeader, error)
CreateTransactionHeader(opts ...TxnHeaderOpt) (TransactionHeader, error)
SendTransactionProposal(*TransactionProposal, []ProposalProcessor) ([]*TransactionProposalResponse, error)
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/fab/channel/transactor.go
Expand Up @@ -147,14 +147,14 @@ func orderersByTarget(ctx context.Client) (map[string]fab.OrdererConfig, error)
}

// CreateTransactionHeader creates a Transaction Header based on the current context.
func (t *Transactor) CreateTransactionHeader() (fab.TransactionHeader, error) {
func (t *Transactor) CreateTransactionHeader(opts ...fab.TxnHeaderOpt) (fab.TransactionHeader, error) {

ctx, ok := contextImpl.RequestClientContext(t.reqCtx)
if !ok {
return nil, errors.New("failed get client context from reqContext for txn Header")
}

txh, err := txn.NewHeader(ctx, t.ChannelID)
txh, err := txn.NewHeader(ctx, t.ChannelID, opts...)
if err != nil {
return nil, errors.WithMessage(err, "new transaction ID failed")
}
Expand Down
18 changes: 15 additions & 3 deletions pkg/fab/channel/transactor_test.go
Expand Up @@ -26,7 +26,19 @@ import (

func TestCreateTxnID(t *testing.T) {
transactor := createTransactor(t)
createTxnID(t, transactor)

txh := createTxnID(t, transactor)
assert.NotEmpty(t, txh.Nonce())
assert.NotEmpty(t, txh.Creator())
assert.NotEmpty(t, txh.TransactionID())

creator := []byte("creator")
nonce := []byte("12345")

txh = createTxnID(t, transactor, fab.WithCreator(creator), fab.WithNonce(nonce))
assert.Equal(t, nonce, txh.Nonce())
assert.Equal(t, creator, txh.Creator())
assert.NotEmpty(t, txh.TransactionID())
}

func TestTransactionProposal(t *testing.T) {
Expand Down Expand Up @@ -78,8 +90,8 @@ func createTransactor(t *testing.T) *Transactor {
return transactor
}

func createTxnID(t *testing.T, transactor *Transactor) fab.TransactionHeader {
txh, err := transactor.CreateTransactionHeader()
func createTxnID(t *testing.T, transactor *Transactor, opts ...fab.TxnHeaderOpt) fab.TransactionHeader {
txh, err := transactor.CreateTransactionHeader(opts...)
assert.Nil(t, err, "creation of transaction ID failed")

return txh
Expand Down
2 changes: 1 addition & 1 deletion pkg/fab/mocks/mocktransactor.go
Expand Up @@ -22,7 +22,7 @@ type MockTransactor struct {
}

// CreateTransactionHeader creates a Transaction Header based on the current context.
func (t *MockTransactor) CreateTransactionHeader() (fab.TransactionHeader, error) {
func (t *MockTransactor) CreateTransactionHeader(opts ...fab.TxnHeaderOpt) (fab.TransactionHeader, error) {
return &MockTransactionHeader{}, nil
}

Expand Down
29 changes: 21 additions & 8 deletions pkg/fab/txn/env.go
Expand Up @@ -53,16 +53,29 @@ func (th *TransactionHeader) ChannelID() string {

// NewHeader computes a TransactionID from the current user context and holds
// metadata to create transaction proposals.
func NewHeader(ctx contextApi.Client, channelID string) (*TransactionHeader, error) {
// generate a random nonce
nonce, err := crypto.GetRandomNonce()
if err != nil {
return nil, errors.WithMessage(err, "nonce creation failed")
func NewHeader(ctx contextApi.Client, channelID string, opts ...fab.TxnHeaderOpt) (*TransactionHeader, error) {
var options fab.TxnHeaderOptions
for _, opt := range opts {
opt(&options)
}

creator, err := ctx.Serialize()
if err != nil {
return nil, errors.WithMessage(err, "identity from context failed")
nonce := options.Nonce
if nonce == nil {
// generate a random nonce
var err error
nonce, err = crypto.GetRandomNonce()
if err != nil {
return nil, errors.WithMessage(err, "nonce creation failed")
}
}

creator := options.Creator
if creator == nil {
var err error
creator, err = ctx.Serialize()
if err != nil {
return nil, errors.WithMessage(err, "identity from context failed")
}
}

ho := cryptosuite.GetSHA256Opts() // TODO: make configurable
Expand Down
25 changes: 25 additions & 0 deletions pkg/fab/txn/proposal_test.go
Expand Up @@ -11,6 +11,8 @@ import (
"strings"
"testing"

"github.com/stretchr/testify/require"

"github.com/golang/mock/gomock"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert"
Expand All @@ -30,6 +32,29 @@ const (
testChannel = "testchannel"
)

func TestNewHeader(t *testing.T) {
user := mspmocks.NewMockSigningIdentity("test", "1234")
ctx := mocks.NewMockContext(user)

creator, err := ctx.Serialize()
require.NoError(t, err)

txh, err := NewHeader(ctx, testChannel)
require.NoError(t, err)
require.NotEmptyf(t, txh.nonce, "Expecting nonce")
require.Equal(t, creator, txh.creator)
require.NotEmpty(t, txh.id)

creator = []byte("someothercreator")
nonce := []byte("123456")

txh, err = NewHeader(ctx, testChannel, fab.WithCreator(creator), fab.WithNonce(nonce))
require.NoError(t, err)
require.Equal(t, nonce, txh.nonce)
require.Equal(t, creator, txh.creator)
require.NotEmpty(t, txh.id)
}

func TestNewTransactionProposal(t *testing.T) {
user := mspmocks.NewMockSigningIdentity("test", "1234")
ctx := mocks.NewMockContext(user)
Expand Down

0 comments on commit 72066f7

Please sign in to comment.