From bd3227dcd6f7389fe798cfdb969419a9e8e7467b Mon Sep 17 00:00:00 2001 From: Andrew Richardson Date: Tue, 12 Oct 2021 13:32:53 -0400 Subject: [PATCH 1/8] Pass ID into sync-async bridge instead of generating it internally Signed-off-by: Andrew Richardson --- internal/assets/manager.go | 6 +- internal/assets/manager_test.go | 19 +++---- internal/broadcast/manager.go | 3 +- internal/broadcast/message_test.go | 11 ++-- internal/privatemessaging/message.go | 3 +- internal/privatemessaging/message_test.go | 11 ++-- internal/privatemessaging/privatemessaging.go | 3 +- .../privatemessaging/privatemessaging_test.go | 6 +- internal/syncasync/sync_async_bridge.go | 36 ++++++------ internal/syncasync/sync_async_bridge_test.go | 38 ++++++------- mocks/syncasyncmocks/bridge.go | 56 +++++++++---------- 11 files changed, 90 insertions(+), 102 deletions(-) diff --git a/internal/assets/manager.go b/internal/assets/manager.go index ac892cbe44..72ff5167ec 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -151,7 +151,8 @@ func (am *assetManager) createTokenPoolWithID(ctx context.Context, id *fftypes.U } if waitConfirm { - return am.syncasync.SendConfirmTokenPool(ctx, ns, func(requestID *fftypes.UUID) error { + requestID := fftypes.NewUUID() + return am.syncasync.SendConfirmTokenPool(ctx, ns, requestID, func() error { _, err := am.createTokenPoolWithID(ctx, requestID, ns, typeName, pool, false) return err }) @@ -345,7 +346,8 @@ func (am *assetManager) transferTokensWithID(ctx context.Context, id *fftypes.UU } if waitConfirm { - return am.syncasync.SendConfirmTokenTransfer(ctx, ns, func(requestID *fftypes.UUID) error { + requestID := fftypes.NewUUID() + return am.syncasync.SendConfirmTokenTransfer(ctx, ns, requestID, func() error { _, err := am.transferTokensWithID(ctx, requestID, ns, typeName, poolName, transfer, false) return err }) diff --git a/internal/assets/manager_test.go b/internal/assets/manager_test.go index 577e779e4c..aa6686479e 100644 --- a/internal/assets/manager_test.go +++ b/internal/assets/manager_test.go @@ -182,8 +182,6 @@ func TestCreateTokenPoolConfirm(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - requestID := fftypes.NewUUID() - mdi := am.database.(*databasemocks.Plugin) mdm := am.data.(*datamocks.Manager) msa := am.syncasync.(*syncasyncmocks.Bridge) @@ -191,17 +189,15 @@ func TestCreateTokenPoolConfirm(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil).Times(2) - mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.MatchedBy(func(pool *fftypes.TokenPool) bool { - return pool.ID == requestID - })).Return(nil).Times(1) + mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything).Return(nil).Times(1) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenPool }), false).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil).Times(1) - msa.On("SendConfirmTokenPool", context.Background(), "ns1", mock.Anything). + msa.On("SendConfirmTokenPool", context.Background(), "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { - send := args[2].(syncasync.RequestSender) - send(requestID) + send := args[3].(syncasync.RequestSender) + send() }). Return(nil, nil) @@ -462,7 +458,6 @@ func TestMintTokensConfirm(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - requestID := fftypes.NewUUID() mint := &fftypes.TokenTransfer{} mint.Amount.Int().SetInt64(5) @@ -478,10 +473,10 @@ func TestMintTokensConfirm(t *testing.T) { mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(nil) - msa.On("SendConfirmTokenTransfer", context.Background(), "ns1", mock.Anything). + msa.On("SendConfirmTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { - send := args[2].(syncasync.RequestSender) - send(requestID) + send := args[3].(syncasync.RequestSender) + send() }). Return(nil, nil) diff --git a/internal/broadcast/manager.go b/internal/broadcast/manager.go index 2cde037a4d..99ddd4d58d 100644 --- a/internal/broadcast/manager.go +++ b/internal/broadcast/manager.go @@ -146,7 +146,8 @@ func (bm *broadcastManager) broadcastMessageCommon(ctx context.Context, msg *fft return msg, bm.database.InsertMessageLocal(ctx, msg) } - return bm.syncasync.SendConfirm(ctx, msg.Header.Namespace, func(requestID *fftypes.UUID) error { + requestID := fftypes.NewUUID() + return bm.syncasync.SendConfirm(ctx, msg.Header.Namespace, requestID, func() error { _, err := bm.broadcastMessageWithID(ctx, msg.Header.Namespace, requestID, nil, msg, false) return err }) diff --git a/internal/broadcast/message_test.go b/internal/broadcast/message_test.go index f944a51e7a..b3e6e7184e 100644 --- a/internal/broadcast/message_test.go +++ b/internal/broadcast/message_test.go @@ -201,22 +201,19 @@ func TestBroadcastMessageWaitConfirmOk(t *testing.T) { }, []*fftypes.DataAndBlob{}, nil) mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) - requestID := fftypes.NewUUID() replyMsg := &fftypes.Message{ Header: fftypes.MessageHeader{ Namespace: "ns1", ID: fftypes.NewUUID(), }, } - msa.On("SendConfirm", ctx, "ns1", mock.Anything). + msa.On("SendConfirm", ctx, "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { - send := args[2].(syncasync.RequestSender) - send(requestID) + send := args[3].(syncasync.RequestSender) + send() }). Return(replyMsg, nil) - mdi.On("InsertMessageLocal", ctx, mock.MatchedBy(func(msg *fftypes.Message) bool { - return msg.Header.ID == requestID - })).Return(nil) + mdi.On("InsertMessageLocal", ctx, mock.Anything).Return(nil) msg, err := bm.BroadcastMessage(ctx, "ns1", &fftypes.MessageInOut{ Message: fftypes.Message{ diff --git a/internal/privatemessaging/message.go b/internal/privatemessaging/message.go index e583138eb5..eb66128b60 100644 --- a/internal/privatemessaging/message.go +++ b/internal/privatemessaging/message.go @@ -118,7 +118,8 @@ func (pm *privateMessaging) sendOrWaitMessage(ctx context.Context, msg *fftypes. // Pass it to the sync-async handler to wait for the confirmation to come back in. // NOTE: Our caller makes sure we are not in a RunAsGroup (which would be bad) - return pm.syncasync.SendConfirm(ctx, msg.Header.Namespace, func(requestID *fftypes.UUID) error { + requestID := fftypes.NewUUID() + return pm.syncasync.SendConfirm(ctx, msg.Header.Namespace, requestID, func() error { _, err := pm.sendMessageWithID(ctx, msg.Header.Namespace, requestID, nil, msg, false) return err }) diff --git a/internal/privatemessaging/message_test.go b/internal/privatemessaging/message_test.go index 4bbb3deb52..14045adddb 100644 --- a/internal/privatemessaging/message_test.go +++ b/internal/privatemessaging/message_test.go @@ -70,22 +70,19 @@ func TestSendConfirmMessageE2EOk(t *testing.T) { {Hash: fftypes.NewRandB32()}, }, nil, nil).Once() - requestID := fftypes.NewUUID() retMsg := &fftypes.Message{ Header: fftypes.MessageHeader{ ID: fftypes.NewUUID(), }, } msa := pm.syncasync.(*syncasyncmocks.Bridge) - msa.On("SendConfirm", pm.ctx, "ns1", mock.Anything). + msa.On("SendConfirm", pm.ctx, "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { - send := args[2].(syncasync.RequestSender) - send(requestID) + send := args[3].(syncasync.RequestSender) + send() }). Return(retMsg, nil).Once() - mdi.On("InsertMessageLocal", pm.ctx, mock.MatchedBy(func(msg *fftypes.Message) bool { - return msg.Header.ID == requestID - })).Return(nil).Once() + mdi.On("InsertMessageLocal", pm.ctx, mock.Anything).Return(nil).Once() msg, err := pm.SendMessage(pm.ctx, "ns1", &fftypes.MessageInOut{ InlineData: fftypes.InlineData{ diff --git a/internal/privatemessaging/privatemessaging.go b/internal/privatemessaging/privatemessaging.go index e9bf698c1e..40c055e14f 100644 --- a/internal/privatemessaging/privatemessaging.go +++ b/internal/privatemessaging/privatemessaging.go @@ -239,7 +239,8 @@ func (pm *privateMessaging) RequestReply(ctx context.Context, ns string, unresol if unresolved.Header.CID != nil { return nil, i18n.NewError(ctx, i18n.MsgRequestCannotHaveCID) } - return pm.syncasync.RequestReply(ctx, ns, func(requestID *fftypes.UUID) error { + requestID := fftypes.NewUUID() + return pm.syncasync.RequestReply(ctx, ns, requestID, func() error { _, err := pm.sendMessageWithID(ctx, ns, requestID, unresolved, &unresolved.Message, false) return err }) diff --git a/internal/privatemessaging/privatemessaging_test.go b/internal/privatemessaging/privatemessaging_test.go index 8b8cdf1bf1..dba69b9b93 100644 --- a/internal/privatemessaging/privatemessaging_test.go +++ b/internal/privatemessaging/privatemessaging_test.go @@ -435,10 +435,10 @@ func TestRequestReplySuccess(t *testing.T) { mim.On("ResolveInputIdentity", pm.ctx, mock.Anything).Return(nil) msa := pm.syncasync.(*syncasyncmocks.Bridge) - msa.On("RequestReply", pm.ctx, "ns1", mock.Anything). + msa.On("RequestReply", pm.ctx, "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { - send := args[2].(syncasync.RequestSender) - send(fftypes.NewUUID()) + send := args[3].(syncasync.RequestSender) + send() }). Return(nil, nil) diff --git a/internal/syncasync/sync_async_bridge.go b/internal/syncasync/sync_async_bridge.go index 9ca4a291e6..48c9dd311b 100644 --- a/internal/syncasync/sync_async_bridge.go +++ b/internal/syncasync/sync_async_bridge.go @@ -36,16 +36,16 @@ type Bridge interface { Init(sysevents sysmessaging.SystemEvents) // Request performs a request/reply exchange taking a message as input, and returning a message as a response // The input message must have a tag, and a group, to be routed appropriately. - RequestReply(ctx context.Context, ns string, send RequestSender) (*fftypes.MessageInOut, error) + RequestReply(ctx context.Context, ns string, id *fftypes.UUID, send RequestSender) (*fftypes.MessageInOut, error) // SendConfirm blocks until the message is confirmed (or rejected), but does not look for a reply. - SendConfirm(ctx context.Context, ns string, send RequestSender) (*fftypes.Message, error) + SendConfirm(ctx context.Context, ns string, id *fftypes.UUID, send RequestSender) (*fftypes.Message, error) // SendConfirmTokenPool blocks until the token pool is confirmed (or rejected) - SendConfirmTokenPool(ctx context.Context, ns string, send RequestSender) (*fftypes.TokenPool, error) + SendConfirmTokenPool(ctx context.Context, ns string, id *fftypes.UUID, send RequestSender) (*fftypes.TokenPool, error) // SendConfirmTokenTransfer blocks until the token transfer is confirmed - SendConfirmTokenTransfer(ctx context.Context, ns string, send RequestSender) (*fftypes.TokenTransfer, error) + SendConfirmTokenTransfer(ctx context.Context, ns string, id *fftypes.UUID, send RequestSender) (*fftypes.TokenTransfer, error) } -type RequestSender func(requestID *fftypes.UUID) error +type RequestSender func() error type requestType int @@ -94,9 +94,9 @@ func (sa *syncAsyncBridge) Init(sysevents sysmessaging.SystemEvents) { sa.sysevents = sysevents } -func (sa *syncAsyncBridge) addInFlight(ns string, reqType requestType) (*inflightRequest, error) { +func (sa *syncAsyncBridge) addInFlight(ns string, id *fftypes.UUID, reqType requestType) (*inflightRequest, error) { inflight := &inflightRequest{ - id: fftypes.NewUUID(), + id: id, startTime: time.Now(), response: make(chan inflightResponse), reqType: reqType, @@ -298,8 +298,8 @@ func (sa *syncAsyncBridge) resolveConfirmedTokenTransfer(inflight *inflightReque inflight.response <- inflightResponse{id: transfer.LocalID, data: transfer} } -func (sa *syncAsyncBridge) sendAndWait(ctx context.Context, ns string, reqType requestType, send RequestSender) (interface{}, error) { - inflight, err := sa.addInFlight(ns, reqType) +func (sa *syncAsyncBridge) sendAndWait(ctx context.Context, ns string, id *fftypes.UUID, reqType requestType, send RequestSender) (interface{}, error) { + inflight, err := sa.addInFlight(ns, id, reqType) if err != nil { return nil, err } @@ -314,7 +314,7 @@ func (sa *syncAsyncBridge) sendAndWait(ctx context.Context, ns string, reqType r } }() - err = send(inflight.id) + err = send() if err != nil { return nil, err } @@ -328,32 +328,32 @@ func (sa *syncAsyncBridge) sendAndWait(ctx context.Context, ns string, reqType r } } -func (sa *syncAsyncBridge) RequestReply(ctx context.Context, ns string, send RequestSender) (*fftypes.MessageInOut, error) { - reply, err := sa.sendAndWait(ctx, ns, messageReply, send) +func (sa *syncAsyncBridge) RequestReply(ctx context.Context, ns string, id *fftypes.UUID, send RequestSender) (*fftypes.MessageInOut, error) { + reply, err := sa.sendAndWait(ctx, ns, id, messageReply, send) if err != nil { return nil, err } return reply.(*fftypes.MessageInOut), err } -func (sa *syncAsyncBridge) SendConfirm(ctx context.Context, ns string, send RequestSender) (*fftypes.Message, error) { - reply, err := sa.sendAndWait(ctx, ns, messageConfirm, send) +func (sa *syncAsyncBridge) SendConfirm(ctx context.Context, ns string, id *fftypes.UUID, send RequestSender) (*fftypes.Message, error) { + reply, err := sa.sendAndWait(ctx, ns, id, messageConfirm, send) if err != nil { return nil, err } return reply.(*fftypes.Message), err } -func (sa *syncAsyncBridge) SendConfirmTokenPool(ctx context.Context, ns string, send RequestSender) (*fftypes.TokenPool, error) { - reply, err := sa.sendAndWait(ctx, ns, tokenPoolConfirm, send) +func (sa *syncAsyncBridge) SendConfirmTokenPool(ctx context.Context, ns string, id *fftypes.UUID, send RequestSender) (*fftypes.TokenPool, error) { + reply, err := sa.sendAndWait(ctx, ns, id, tokenPoolConfirm, send) if err != nil { return nil, err } return reply.(*fftypes.TokenPool), err } -func (sa *syncAsyncBridge) SendConfirmTokenTransfer(ctx context.Context, ns string, send RequestSender) (*fftypes.TokenTransfer, error) { - reply, err := sa.sendAndWait(ctx, ns, tokenTransferConfirm, send) +func (sa *syncAsyncBridge) SendConfirmTokenTransfer(ctx context.Context, ns string, id *fftypes.UUID, send RequestSender) (*fftypes.TokenTransfer, error) { + reply, err := sa.sendAndWait(ctx, ns, id, tokenTransferConfirm, send) if err != nil { return nil, err } diff --git a/internal/syncasync/sync_async_bridge_test.go b/internal/syncasync/sync_async_bridge_test.go index cf074270dd..2e4cb3275e 100644 --- a/internal/syncasync/sync_async_bridge_test.go +++ b/internal/syncasync/sync_async_bridge_test.go @@ -44,7 +44,7 @@ func TestRequestReplyOk(t *testing.T) { sa, cancel := newTestSyncAsyncBridge(t) defer cancel() - var requestID *fftypes.UUID + requestID := fftypes.NewUUID() replyID := fftypes.NewUUID() dataID := fftypes.NewUUID() @@ -73,8 +73,7 @@ func TestRequestReplyOk(t *testing.T) { {ID: dataID, Value: fftypes.Byteable(`"response data"`)}, }, true, nil) - reply, err := sa.RequestReply(sa.ctx, "ns1", func(id *fftypes.UUID) error { - requestID = id + reply, err := sa.RequestReply(sa.ctx, "ns1", requestID, func() error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -98,7 +97,7 @@ func TestAwaitConfirmationOk(t *testing.T) { sa, cancel := newTestSyncAsyncBridge(t) defer cancel() - var requestID *fftypes.UUID + requestID := fftypes.NewUUID() dataID := fftypes.NewUUID() mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) @@ -122,8 +121,7 @@ func TestAwaitConfirmationOk(t *testing.T) { {ID: dataID, Value: fftypes.Byteable(`"response data"`)}, }, true, nil) - reply, err := sa.SendConfirm(sa.ctx, "ns1", func(id *fftypes.UUID) error { - requestID = id + reply, err := sa.SendConfirm(sa.ctx, "ns1", requestID, func() error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -146,7 +144,7 @@ func TestAwaitConfirmationRejected(t *testing.T) { sa, cancel := newTestSyncAsyncBridge(t) defer cancel() - var requestID *fftypes.UUID + requestID := fftypes.NewUUID() dataID := fftypes.NewUUID() mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) @@ -170,8 +168,7 @@ func TestAwaitConfirmationRejected(t *testing.T) { {ID: dataID, Value: fftypes.Byteable(`"response data"`)}, }, true, nil) - _, err := sa.SendConfirm(sa.ctx, "ns1", func(id *fftypes.UUID) error { - requestID = id + _, err := sa.SendConfirm(sa.ctx, "ns1", requestID, func() error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -195,7 +192,7 @@ func TestRequestReplyTimeout(t *testing.T) { mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) - _, err := sa.RequestReply(sa.ctx, "ns1", func(requestID *fftypes.UUID) error { + _, err := sa.RequestReply(sa.ctx, "ns1", fftypes.NewUUID(), func() error { return nil }) assert.Regexp(t, "FF10260", err) @@ -209,7 +206,7 @@ func TestRequestSetupSystemListenerFail(t *testing.T) { mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(fmt.Errorf("pop")) - _, err := sa.RequestReply(sa.ctx, "ns1", func(requestID *fftypes.UUID) error { + _, err := sa.RequestReply(sa.ctx, "ns1", fftypes.NewUUID(), func() error { return nil }) assert.Regexp(t, "pop", err) @@ -501,7 +498,7 @@ func TestAwaitTokenPoolConfirmation(t *testing.T) { sa, cancel := newTestSyncAsyncBridge(t) defer cancel() - var requestID *fftypes.UUID + requestID := fftypes.NewUUID() mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) @@ -519,8 +516,7 @@ func TestAwaitTokenPoolConfirmation(t *testing.T) { } } - reply, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", func(id *fftypes.UUID) error { - requestID = id + reply, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", requestID, func() error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -546,7 +542,7 @@ func TestAwaitTokenPoolConfirmationSendFail(t *testing.T) { mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) - _, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", func(id *fftypes.UUID) error { + _, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", fftypes.NewUUID(), func() error { return fmt.Errorf("pop") }) assert.EqualError(t, err, "pop") @@ -557,7 +553,7 @@ func TestAwaitTokenPoolConfirmationRejected(t *testing.T) { sa, cancel := newTestSyncAsyncBridge(t) defer cancel() - var requestID *fftypes.UUID + requestID := fftypes.NewUUID() mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) @@ -575,8 +571,7 @@ func TestAwaitTokenPoolConfirmationRejected(t *testing.T) { } } - _, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", func(id *fftypes.UUID) error { - requestID = id + _, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", requestID, func() error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -597,7 +592,7 @@ func TestAwaitTokenTransferConfirmation(t *testing.T) { sa, cancel := newTestSyncAsyncBridge(t) defer cancel() - var requestID *fftypes.UUID + requestID := fftypes.NewUUID() mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) @@ -615,8 +610,7 @@ func TestAwaitTokenTransferConfirmation(t *testing.T) { } } - reply, err := sa.SendConfirmTokenTransfer(sa.ctx, "ns1", func(id *fftypes.UUID) error { - requestID = id + reply, err := sa.SendConfirmTokenTransfer(sa.ctx, "ns1", requestID, func() error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -642,7 +636,7 @@ func TestAwaitTokenTransferConfirmationSendFail(t *testing.T) { mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) - _, err := sa.SendConfirmTokenTransfer(sa.ctx, "ns1", func(id *fftypes.UUID) error { + _, err := sa.SendConfirmTokenTransfer(sa.ctx, "ns1", fftypes.NewUUID(), func() error { return fmt.Errorf("pop") }) assert.EqualError(t, err, "pop") diff --git a/mocks/syncasyncmocks/bridge.go b/mocks/syncasyncmocks/bridge.go index c07c18675f..02b5773b79 100644 --- a/mocks/syncasyncmocks/bridge.go +++ b/mocks/syncasyncmocks/bridge.go @@ -23,13 +23,13 @@ func (_m *Bridge) Init(sysevents sysmessaging.SystemEvents) { _m.Called(sysevents) } -// RequestReply provides a mock function with given fields: ctx, ns, send -func (_m *Bridge) RequestReply(ctx context.Context, ns string, send syncasync.RequestSender) (*fftypes.MessageInOut, error) { - ret := _m.Called(ctx, ns, send) +// RequestReply provides a mock function with given fields: ctx, ns, id, send +func (_m *Bridge) RequestReply(ctx context.Context, ns string, id *fftypes.UUID, send syncasync.RequestSender) (*fftypes.MessageInOut, error) { + ret := _m.Called(ctx, ns, id, send) var r0 *fftypes.MessageInOut - if rf, ok := ret.Get(0).(func(context.Context, string, syncasync.RequestSender) *fftypes.MessageInOut); ok { - r0 = rf(ctx, ns, send) + if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.UUID, syncasync.RequestSender) *fftypes.MessageInOut); ok { + r0 = rf(ctx, ns, id, send) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*fftypes.MessageInOut) @@ -37,8 +37,8 @@ func (_m *Bridge) RequestReply(ctx context.Context, ns string, send syncasync.Re } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string, syncasync.RequestSender) error); ok { - r1 = rf(ctx, ns, send) + if rf, ok := ret.Get(1).(func(context.Context, string, *fftypes.UUID, syncasync.RequestSender) error); ok { + r1 = rf(ctx, ns, id, send) } else { r1 = ret.Error(1) } @@ -46,13 +46,13 @@ func (_m *Bridge) RequestReply(ctx context.Context, ns string, send syncasync.Re return r0, r1 } -// SendConfirm provides a mock function with given fields: ctx, ns, send -func (_m *Bridge) SendConfirm(ctx context.Context, ns string, send syncasync.RequestSender) (*fftypes.Message, error) { - ret := _m.Called(ctx, ns, send) +// SendConfirm provides a mock function with given fields: ctx, ns, id, send +func (_m *Bridge) SendConfirm(ctx context.Context, ns string, id *fftypes.UUID, send syncasync.RequestSender) (*fftypes.Message, error) { + ret := _m.Called(ctx, ns, id, send) var r0 *fftypes.Message - if rf, ok := ret.Get(0).(func(context.Context, string, syncasync.RequestSender) *fftypes.Message); ok { - r0 = rf(ctx, ns, send) + if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.UUID, syncasync.RequestSender) *fftypes.Message); ok { + r0 = rf(ctx, ns, id, send) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*fftypes.Message) @@ -60,8 +60,8 @@ func (_m *Bridge) SendConfirm(ctx context.Context, ns string, send syncasync.Req } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string, syncasync.RequestSender) error); ok { - r1 = rf(ctx, ns, send) + if rf, ok := ret.Get(1).(func(context.Context, string, *fftypes.UUID, syncasync.RequestSender) error); ok { + r1 = rf(ctx, ns, id, send) } else { r1 = ret.Error(1) } @@ -69,13 +69,13 @@ func (_m *Bridge) SendConfirm(ctx context.Context, ns string, send syncasync.Req return r0, r1 } -// SendConfirmTokenPool provides a mock function with given fields: ctx, ns, send -func (_m *Bridge) SendConfirmTokenPool(ctx context.Context, ns string, send syncasync.RequestSender) (*fftypes.TokenPool, error) { - ret := _m.Called(ctx, ns, send) +// SendConfirmTokenPool provides a mock function with given fields: ctx, ns, id, send +func (_m *Bridge) SendConfirmTokenPool(ctx context.Context, ns string, id *fftypes.UUID, send syncasync.RequestSender) (*fftypes.TokenPool, error) { + ret := _m.Called(ctx, ns, id, send) var r0 *fftypes.TokenPool - if rf, ok := ret.Get(0).(func(context.Context, string, syncasync.RequestSender) *fftypes.TokenPool); ok { - r0 = rf(ctx, ns, send) + if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.UUID, syncasync.RequestSender) *fftypes.TokenPool); ok { + r0 = rf(ctx, ns, id, send) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*fftypes.TokenPool) @@ -83,8 +83,8 @@ func (_m *Bridge) SendConfirmTokenPool(ctx context.Context, ns string, send sync } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string, syncasync.RequestSender) error); ok { - r1 = rf(ctx, ns, send) + if rf, ok := ret.Get(1).(func(context.Context, string, *fftypes.UUID, syncasync.RequestSender) error); ok { + r1 = rf(ctx, ns, id, send) } else { r1 = ret.Error(1) } @@ -92,13 +92,13 @@ func (_m *Bridge) SendConfirmTokenPool(ctx context.Context, ns string, send sync return r0, r1 } -// SendConfirmTokenTransfer provides a mock function with given fields: ctx, ns, send -func (_m *Bridge) SendConfirmTokenTransfer(ctx context.Context, ns string, send syncasync.RequestSender) (*fftypes.TokenTransfer, error) { - ret := _m.Called(ctx, ns, send) +// SendConfirmTokenTransfer provides a mock function with given fields: ctx, ns, id, send +func (_m *Bridge) SendConfirmTokenTransfer(ctx context.Context, ns string, id *fftypes.UUID, send syncasync.RequestSender) (*fftypes.TokenTransfer, error) { + ret := _m.Called(ctx, ns, id, send) var r0 *fftypes.TokenTransfer - if rf, ok := ret.Get(0).(func(context.Context, string, syncasync.RequestSender) *fftypes.TokenTransfer); ok { - r0 = rf(ctx, ns, send) + if rf, ok := ret.Get(0).(func(context.Context, string, *fftypes.UUID, syncasync.RequestSender) *fftypes.TokenTransfer); ok { + r0 = rf(ctx, ns, id, send) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*fftypes.TokenTransfer) @@ -106,8 +106,8 @@ func (_m *Bridge) SendConfirmTokenTransfer(ctx context.Context, ns string, send } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string, syncasync.RequestSender) error); ok { - r1 = rf(ctx, ns, send) + if rf, ok := ret.Get(1).(func(context.Context, string, *fftypes.UUID, syncasync.RequestSender) error); ok { + r1 = rf(ctx, ns, id, send) } else { r1 = ret.Error(1) } From fe03d25aeeb6efc3e58af296ffdcf4d60456cbab Mon Sep 17 00:00:00 2001 From: Andrew Richardson Date: Wed, 13 Oct 2021 11:19:21 -0400 Subject: [PATCH 2/8] Unify some logic in message.go between broadcast and privatemessaging Make the flows and method naming in these packages as similar as possible, and attempt to clarify the branches that result in an early send (within the DB transaction) vs a deferred send. Signed-off-by: Andrew Richardson --- internal/broadcast/manager.go | 27 ++-- internal/broadcast/message.go | 91 +++++------ internal/broadcast/message_test.go | 55 ++++--- internal/privatemessaging/message.go | 138 ++++++++++------- internal/privatemessaging/message_test.go | 141 ++++++++++++++++-- internal/privatemessaging/privatemessaging.go | 14 -- .../privatemessaging/privatemessaging_test.go | 63 -------- 7 files changed, 311 insertions(+), 218 deletions(-) diff --git a/internal/broadcast/manager.go b/internal/broadcast/manager.go index 99ddd4d58d..ac6a72b129 100644 --- a/internal/broadcast/manager.go +++ b/internal/broadcast/manager.go @@ -135,20 +135,25 @@ func (bm *broadcastManager) submitTXAndUpdateDB(ctx context.Context, batch *ffty } func (bm *broadcastManager) broadcastMessageCommon(ctx context.Context, msg *fftypes.Message, waitConfirm bool) (*fftypes.Message, error) { + if waitConfirm { + return bm.broadcastMessageSync(ctx, msg) + } + return bm.broadcastMessageAsync(ctx, msg) +} - if !waitConfirm { - // Seal the message - if err := msg.Seal(ctx); err != nil { - return nil, err - } - - // Store the message - this asynchronously triggers the next step in process - return msg, bm.database.InsertMessageLocal(ctx, msg) +func (bm *broadcastManager) broadcastMessageAsync(ctx context.Context, msg *fftypes.Message) (*fftypes.Message, error) { + // Seal the message + if err := msg.Seal(ctx); err != nil { + return nil, err } - requestID := fftypes.NewUUID() - return bm.syncasync.SendConfirm(ctx, msg.Header.Namespace, requestID, func() error { - _, err := bm.broadcastMessageWithID(ctx, msg.Header.Namespace, requestID, nil, msg, false) + // Store the message - this asynchronously triggers the next step in process + return msg, bm.database.InsertMessageLocal(ctx, msg) +} + +func (bm *broadcastManager) broadcastMessageSync(ctx context.Context, msg *fftypes.Message) (*fftypes.Message, error) { + return bm.syncasync.SendConfirm(ctx, msg.Header.Namespace, msg.Header.ID, func() error { + _, err := bm.resolveAndSend(ctx, nil, msg, false) return err }) } diff --git a/internal/broadcast/message.go b/internal/broadcast/message.go index 89febe7a71..7a111d3102 100644 --- a/internal/broadcast/message.go +++ b/internal/broadcast/message.go @@ -27,66 +27,75 @@ import ( ) func (bm *broadcastManager) BroadcastMessage(ctx context.Context, ns string, in *fftypes.MessageInOut, waitConfirm bool) (out *fftypes.Message, err error) { - return bm.broadcastMessageWithID(ctx, ns, nil, in, nil, waitConfirm) + if err := bm.prepareMessage(ctx, ns, in); err != nil { + return nil, err + } + return bm.resolveAndSend(ctx, in, nil, waitConfirm) } -func (bm *broadcastManager) broadcastMessageWithID(ctx context.Context, ns string, id *fftypes.UUID, unresolved *fftypes.MessageInOut, resolved *fftypes.Message, waitConfirm bool) (out *fftypes.Message, err error) { - if unresolved != nil { - resolved = &unresolved.Message +func (bm *broadcastManager) prepareMessage(ctx context.Context, ns string, msg *fftypes.MessageInOut) error { + msg.Header.ID = fftypes.NewUUID() + msg.Header.Namespace = ns + if msg.Header.Type == "" { + msg.Header.Type = fftypes.MessageTypeBroadcast } - resolved.Header.ID = id - resolved.Header.Namespace = ns - - if resolved.Header.Type == "" { - resolved.Header.Type = fftypes.MessageTypeBroadcast + if msg.Header.TxType == "" { + msg.Header.TxType = fftypes.TransactionTypeBatchPin } - if resolved.Header.TxType == "" { - resolved.Header.TxType = fftypes.TransactionTypeBatchPin - } - - if !bm.isRootOrgBroadcast(ctx, resolved) { + if !bm.isRootOrgBroadcast(ctx, &msg.Message) { // Resolve the sending identity - if err := bm.identity.ResolveInputIdentity(ctx, &resolved.Header.Identity); err != nil { - return nil, i18n.WrapError(ctx, err, i18n.MsgAuthorInvalid) + if err := bm.identity.ResolveInputIdentity(ctx, &msg.Header.Identity); err != nil { + return i18n.WrapError(ctx, err, i18n.MsgAuthorInvalid) } } - // We optimize the DB storage of all the parts of the message using transaction semantics (assuming those are supported by the DB plugin + return nil +} + +func (bm *broadcastManager) resolveAndSend(ctx context.Context, unresolved *fftypes.MessageInOut, resolved *fftypes.Message, waitConfirm bool) (out *fftypes.Message, err error) { + if unresolved != nil { + resolved = &unresolved.Message + } + + // We optimize the DB storage of all the parts of the message using transaction semantics (assuming those are supported by the DB plugin) + sent := false var dataToPublish []*fftypes.DataAndBlob - err = bm.database.RunAsGroup(ctx, func(ctx context.Context) error { + err = bm.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { if unresolved != nil { - // The data manager is responsible for the heavy lifting of storing/validating all our in-line data elements - resolved.Data, dataToPublish, err = bm.data.ResolveInlineDataBroadcast(ctx, ns, unresolved.InlineData) + dataToPublish, err = bm.resolveMessage(ctx, unresolved) if err != nil { return err } } - // If we have data to publish, we break out of the DB transaction, do the publishes, then - // do the send later - as that could take a long time (multiple seconds) depending on the size - // - // Same for waiting for confirmation of the send from the blockchain - if len(dataToPublish) > 0 || waitConfirm { - return nil + // For the simple case where we have no data to publish and aren't waiting for blockchain confirmation, + // insert the local message immediately within the same DB transaction. + // Otherwise, break out of the DB transaction (since those operations could take multiple seconds). + if len(dataToPublish) == 0 && !waitConfirm { + out, err = bm.broadcastMessageAsync(ctx, resolved) + sent = true } - - out, err = bm.broadcastMessageCommon(ctx, resolved, false) return err }) - if err != nil { - return nil, err + + if err != nil || sent { + return out, err } // Perform deferred processing if len(dataToPublish) > 0 { - return bm.publishBlobsAndSend(ctx, resolved, dataToPublish, waitConfirm) - } else if waitConfirm { - return bm.broadcastMessageCommon(ctx, resolved, true) + if err := bm.publishBlobs(ctx, dataToPublish); err != nil { + return nil, err + } } + return bm.broadcastMessageCommon(ctx, resolved, waitConfirm) +} - // The broadcastMessage function modifies the input message to create all the refs - return out, err +func (bm *broadcastManager) resolveMessage(ctx context.Context, in *fftypes.MessageInOut) (dataToPublish []*fftypes.DataAndBlob, err error) { + // The data manager is responsible for the heavy lifting of storing/validating all our in-line data elements + in.Data, dataToPublish, err = bm.data.ResolveInlineDataBroadcast(ctx, in.Header.Namespace, in.InlineData) + return dataToPublish, err } func (bm *broadcastManager) isRootOrgBroadcast(ctx context.Context, message *fftypes.Message) bool { @@ -112,21 +121,21 @@ func (bm *broadcastManager) isRootOrgBroadcast(ctx context.Context, message *fft return false } -func (bm *broadcastManager) publishBlobsAndSend(ctx context.Context, msg *fftypes.Message, dataToPublish []*fftypes.DataAndBlob, waitConfirm bool) (*fftypes.Message, error) { +func (bm *broadcastManager) publishBlobs(ctx context.Context, dataToPublish []*fftypes.DataAndBlob) error { for _, d := range dataToPublish { // Stream from the local data exchange ... reader, err := bm.exchange.DownloadBLOB(ctx, d.Blob.PayloadRef) if err != nil { - return nil, i18n.WrapError(ctx, err, i18n.MsgDownloadBlobFailed, d.Blob.PayloadRef) + return i18n.WrapError(ctx, err, i18n.MsgDownloadBlobFailed, d.Blob.PayloadRef) } defer reader.Close() // ... to the public storage publicRef, err := bm.publicstorage.PublishData(ctx, reader) if err != nil { - return nil, err + return err } log.L(ctx).Infof("Published blob with hash '%s' for data '%s' to public storage: '%s'", d.Data.Blob, d.Data.ID, publicRef) @@ -135,11 +144,9 @@ func (bm *broadcastManager) publishBlobsAndSend(ctx context.Context, msg *fftype update := database.DataQueryFactory.NewUpdate(ctx).Set("blob.public", publicRef) err = bm.database.UpdateData(ctx, d.Data.ID, update) if err != nil { - return nil, err + return err } - } - // Now we broadcast the message, as all data has been published - return bm.broadcastMessageCommon(ctx, msg, waitConfirm) + return nil } diff --git a/internal/broadcast/message_test.go b/internal/broadcast/message_test.go index b3e6e7184e..b676ab1c01 100644 --- a/internal/broadcast/message_test.go +++ b/internal/broadcast/message_test.go @@ -354,26 +354,23 @@ func TestPublishBlobsSendMessageFail(t *testing.T) { bm, cancel := newTestBroadcast(t) defer cancel() mdi := bm.database.(*databasemocks.Plugin) + mdm := bm.data.(*datamocks.Manager) mdx := bm.exchange.(*dataexchangemocks.Plugin) - mps := bm.publicstorage.(*publicstoragemocks.Plugin) mim := bm.identity.(*identitymanagermocks.Manager) blobHash := fftypes.NewRandB32() dataID := fftypes.NewUUID() ctx := context.Background() - mdx.On("DownloadBLOB", ctx, "blob/1").Return(ioutil.NopCloser(bytes.NewReader([]byte(`some data`))), nil) - mps.On("PublishData", ctx, mock.MatchedBy(func(reader io.ReadCloser) bool { - b, err := ioutil.ReadAll(reader) - assert.NoError(t, err) - assert.Equal(t, "some data", string(b)) - return true - })).Return("payload-ref", nil) - mdi.On("UpdateData", ctx, mock.Anything, mock.Anything).Return(nil) - mdi.On("InsertMessageLocal", ctx, mock.Anything).Return(fmt.Errorf("pop")) + rag := mdi.On("RunAsGroup", ctx, mock.Anything) + rag.RunFn = func(a mock.Arguments) { + var fn = a[1].(func(context.Context) error) + rag.ReturnArguments = mock.Arguments{fn(a[0].(context.Context))} + } mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) - - _, err := bm.publishBlobsAndSend(ctx, &fftypes.Message{}, []*fftypes.DataAndBlob{ + mdm.On("ResolveInlineDataBroadcast", ctx, "ns1", mock.Anything).Return(fftypes.DataRefs{ + {ID: dataID, Hash: fftypes.NewRandB32()}, + }, []*fftypes.DataAndBlob{ { Data: &fftypes.Data{ ID: dataID, @@ -386,10 +383,30 @@ func TestPublishBlobsSendMessageFail(t *testing.T) { PayloadRef: "blob/1", }, }, + }, nil) + mdx.On("DownloadBLOB", ctx, "blob/1").Return(nil, fmt.Errorf("pop")) + + _, err := bm.BroadcastMessage(ctx, "ns1", &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Identity: fftypes.Identity{ + Author: "did:firefly:org/abcd", + Key: "0x12345", + }, + }, + }, + InlineData: fftypes.InlineData{ + {Blob: &fftypes.BlobRef{ + Hash: blobHash, + }}, + }, }, false) - assert.EqualError(t, err, "pop") + assert.Regexp(t, "FF10240", err) mdi.AssertExpectations(t) + mdm.AssertExpectations(t) + mdx.AssertExpectations(t) + mim.AssertExpectations(t) } func TestPublishBlobsUpdateDataFail(t *testing.T) { @@ -414,7 +431,7 @@ func TestPublishBlobsUpdateDataFail(t *testing.T) { mdi.On("UpdateData", ctx, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) - _, err := bm.publishBlobsAndSend(ctx, &fftypes.Message{}, []*fftypes.DataAndBlob{ + err := bm.publishBlobs(ctx, []*fftypes.DataAndBlob{ { Data: &fftypes.Data{ ID: dataID, @@ -427,7 +444,7 @@ func TestPublishBlobsUpdateDataFail(t *testing.T) { PayloadRef: "blob/1", }, }, - }, false) + }) assert.EqualError(t, err, "pop") mdi.AssertExpectations(t) @@ -454,7 +471,7 @@ func TestPublishBlobsPublishFail(t *testing.T) { })).Return("", fmt.Errorf("pop")) mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) - _, err := bm.publishBlobsAndSend(ctx, &fftypes.Message{}, []*fftypes.DataAndBlob{ + err := bm.publishBlobs(ctx, []*fftypes.DataAndBlob{ { Data: &fftypes.Data{ ID: dataID, @@ -467,7 +484,7 @@ func TestPublishBlobsPublishFail(t *testing.T) { PayloadRef: "blob/1", }, }, - }, false) + }) assert.EqualError(t, err, "pop") mdi.AssertExpectations(t) @@ -487,7 +504,7 @@ func TestPublishBlobsDownloadFail(t *testing.T) { mdx.On("DownloadBLOB", ctx, "blob/1").Return(nil, fmt.Errorf("pop")) mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) - _, err := bm.publishBlobsAndSend(ctx, &fftypes.Message{}, []*fftypes.DataAndBlob{ + err := bm.publishBlobs(ctx, []*fftypes.DataAndBlob{ { Data: &fftypes.Data{ ID: dataID, @@ -500,7 +517,7 @@ func TestPublishBlobsDownloadFail(t *testing.T) { PayloadRef: "blob/1", }, }, - }, false) + }) assert.Regexp(t, "FF10240", err) mdi.AssertExpectations(t) diff --git a/internal/privatemessaging/message.go b/internal/privatemessaging/message.go index eb66128b60..8314e229e2 100644 --- a/internal/privatemessaging/message.go +++ b/internal/privatemessaging/message.go @@ -25,50 +25,77 @@ import ( ) func (pm *privateMessaging) SendMessage(ctx context.Context, ns string, in *fftypes.MessageInOut, waitConfirm bool) (out *fftypes.Message, err error) { - return pm.sendMessageWithID(ctx, ns, nil, in, nil, waitConfirm) + if err := pm.prepareMessage(ctx, ns, in); err != nil { + return nil, err + } + return pm.resolveAndSend(ctx, in, nil, waitConfirm) } -func (pm *privateMessaging) sendMessageWithID(ctx context.Context, ns string, id *fftypes.UUID, unresolved *fftypes.MessageInOut, resolved *fftypes.Message, waitConfirm bool) (*fftypes.Message, error) { - if unresolved != nil { - resolved = &unresolved.Message +func (pm *privateMessaging) RequestReply(ctx context.Context, ns string, in *fftypes.MessageInOut) (*fftypes.MessageInOut, error) { + if in.Header.Tag == "" { + return nil, i18n.NewError(ctx, i18n.MsgRequestReplyTagRequired) + } + if in.Header.CID != nil { + return nil, i18n.NewError(ctx, i18n.MsgRequestCannotHaveCID) } + if err := pm.prepareMessage(ctx, ns, in); err != nil { + return nil, err + } + return pm.syncasync.RequestReply(ctx, ns, in.Header.ID, func() error { + _, err := pm.resolveAndSend(ctx, in, &in.Message, false) + return err + }) +} - resolved.Header.ID = id - resolved.Header.Namespace = ns - resolved.Header.Type = fftypes.MessageTypePrivate - if resolved.Header.TxType == "" { - resolved.Header.TxType = fftypes.TransactionTypeBatchPin +func (pm *privateMessaging) prepareMessage(ctx context.Context, ns string, msg *fftypes.MessageInOut) error { + msg.Header.ID = fftypes.NewUUID() + msg.Header.Namespace = ns + if msg.Header.Type == "" { + msg.Header.Type = fftypes.MessageTypePrivate + } + if msg.Header.TxType == "" { + msg.Header.TxType = fftypes.TransactionTypeBatchPin } // Resolve the sending identity - if err := pm.identity.ResolveInputIdentity(ctx, &resolved.Header.Identity); err != nil { - return nil, i18n.WrapError(ctx, err, i18n.MsgAuthorInvalid) + if err := pm.identity.ResolveInputIdentity(ctx, &msg.Header.Identity); err != nil { + return i18n.WrapError(ctx, err, i18n.MsgAuthorInvalid) } - // We optimize the DB storage of all the parts of the message using transaction semantics (assuming those are supported by the DB plugin - var err error - err = pm.database.RunAsGroup(ctx, func(ctx context.Context) error { + return nil +} + +func (pm *privateMessaging) resolveAndSend(ctx context.Context, unresolved *fftypes.MessageInOut, resolved *fftypes.Message, waitConfirm bool) (out *fftypes.Message, err error) { + if unresolved != nil { + resolved = &unresolved.Message + } + + // We optimize the DB storage of all the parts of the message using transaction semantics (assuming those are supported by the DB plugin) + sent := false + err = pm.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { if unresolved != nil { err = pm.resolveMessage(ctx, unresolved) + if err != nil { + return err + } } - if err == nil && !waitConfirm { - // We can safely optimize the send into the same DB transaction - resolved, err = pm.sendOrWaitMessage(ctx, resolved, false) + + // If we aren't waiting for blockchain confirmation, insert the local message immediately within the same DB transaction. + if !waitConfirm { + out, err = pm.sendMessageAsync(ctx, resolved) + sent = true } return err }) - if err != nil { - return nil, err - } - if waitConfirm { - // perform the send and wait for the confirmation after closing the original DB transaction - return pm.sendOrWaitMessage(ctx, resolved, true) + + if err != nil || sent { + return out, err } - return resolved, err + + return pm.sendMessageCommon(ctx, resolved, waitConfirm) } func (pm *privateMessaging) resolveMessage(ctx context.Context, in *fftypes.MessageInOut) (err error) { - // Resolve the member list into a group if err = pm.resolveReceipientList(ctx, in); err != nil { return err @@ -79,51 +106,54 @@ func (pm *privateMessaging) resolveMessage(ctx context.Context, in *fftypes.Mess return err } -func (pm *privateMessaging) sendOrWaitMessage(ctx context.Context, msg *fftypes.Message, waitConfirm bool) (*fftypes.Message, error) { +func (pm *privateMessaging) sendMessageCommon(ctx context.Context, msg *fftypes.Message, waitConfirm bool) (*fftypes.Message, error) { + if waitConfirm && msg.Header.TxType != fftypes.TransactionTypeNone { + return pm.sendMessageSync(ctx, msg) + } + return pm.sendMessageAsync(ctx, msg) +} +func (pm *privateMessaging) sendMessageAsync(ctx context.Context, msg *fftypes.Message) (*fftypes.Message, error) { immediateConfirm := msg.Header.TxType == fftypes.TransactionTypeNone - if immediateConfirm || !waitConfirm { + // Seal the message + if err := msg.Seal(ctx); err != nil { + return nil, err + } - // Seal the message - if err := msg.Seal(ctx); err != nil { - return nil, err - } + if immediateConfirm { + msg.Confirmed = fftypes.Now() + msg.Pending = false + // msg.Header.Key = "" // there is no on-chain signing assurance with this message + } - if immediateConfirm { - msg.Confirmed = fftypes.Now() - msg.Pending = false - // msg.Header.Key = "" // there is no on-chain signing assurance with this message - } + // Store the message - this asynchronously triggers the next step in process + if err := pm.database.InsertMessageLocal(ctx, msg); err != nil { + return nil, err + } - // Store the message - this asynchronously triggers the next step in process - if err := pm.database.InsertMessageLocal(ctx, msg); err != nil { + if immediateConfirm { + if err := pm.sendUnpinnedMessage(ctx, msg); err != nil { return nil, err } - if immediateConfirm { - if err := pm.sendUnpinnedMessage(ctx, msg); err != nil { - return nil, err - } - - // Emit a confirmation event locally immediately - event := fftypes.NewEvent(fftypes.EventTypeMessageConfirmed, msg.Header.Namespace, msg.Header.ID) - if err := pm.database.InsertEvent(ctx, event); err != nil { - return nil, err - } + // Emit a confirmation event locally immediately + event := fftypes.NewEvent(fftypes.EventTypeMessageConfirmed, msg.Header.Namespace, msg.Header.ID) + if err := pm.database.InsertEvent(ctx, event); err != nil { + return nil, err } - - return msg, nil } + return msg, nil +} + +func (pm *privateMessaging) sendMessageSync(ctx context.Context, msg *fftypes.Message) (*fftypes.Message, error) { // Pass it to the sync-async handler to wait for the confirmation to come back in. // NOTE: Our caller makes sure we are not in a RunAsGroup (which would be bad) - requestID := fftypes.NewUUID() - return pm.syncasync.SendConfirm(ctx, msg.Header.Namespace, requestID, func() error { - _, err := pm.sendMessageWithID(ctx, msg.Header.Namespace, requestID, nil, msg, false) + return pm.syncasync.SendConfirm(ctx, msg.Header.Namespace, msg.Header.ID, func() error { + _, err := pm.resolveAndSend(ctx, nil, msg, false) return err }) - } func (pm *privateMessaging) sendUnpinnedMessage(ctx context.Context, message *fftypes.Message) (err error) { diff --git a/internal/privatemessaging/message_test.go b/internal/privatemessaging/message_test.go index 14045adddb..8ea1dde572 100644 --- a/internal/privatemessaging/message_test.go +++ b/internal/privatemessaging/message_test.go @@ -177,6 +177,34 @@ func TestSendUnpinnedMessageE2EOk(t *testing.T) { } +func TestSendMessageBadGroup(t *testing.T) { + + pm, cancel := newTestPrivateMessaging(t) + defer cancel() + + mim := pm.identity.(*identitymanagermocks.Manager) + mim.On("ResolveInputIdentity", pm.ctx, mock.Anything).Return(nil) + + mdi := pm.database.(*databasemocks.Plugin) + rag := mdi.On("RunAsGroup", pm.ctx, mock.Anything).Return(nil) + rag.RunFn = func(a mock.Arguments) { + err := a[1].(func(context.Context) error)(a[0].(context.Context)) + rag.ReturnArguments = mock.Arguments{err} + } + + _, err := pm.SendMessage(pm.ctx, "ns1", &fftypes.MessageInOut{ + InlineData: fftypes.InlineData{ + {Value: fftypes.Byteable(`{"some": "data"}`)}, + }, + Group: &fftypes.InputGroup{}, + }, true) + assert.Regexp(t, "FF10219", err) + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + +} + func TestSendMessageBadIdentity(t *testing.T) { pm, cancel := newTestPrivateMessaging(t) @@ -237,20 +265,6 @@ func TestSendMessageFail(t *testing.T) { } -func TestResolveAndSendBadMembers(t *testing.T) { - - pm, cancel := newTestPrivateMessaging(t) - defer cancel() - - err := pm.resolveMessage(pm.ctx, &fftypes.MessageInOut{ - InlineData: fftypes.InlineData{ - {Value: fftypes.Byteable(`{"some": "data"}`)}, - }, - }) - assert.Regexp(t, "FF10219", err) - -} - func TestResolveAndSendBadInlineData(t *testing.T) { pm, cancel := newTestPrivateMessaging(t) @@ -293,7 +307,7 @@ func TestSealFail(t *testing.T) { defer cancel() id1 := fftypes.NewUUID() - _, err := pm.sendOrWaitMessage(pm.ctx, &fftypes.Message{ + _, err := pm.sendMessageCommon(pm.ctx, &fftypes.Message{ Header: fftypes.MessageHeader{Namespace: "ns1"}, Data: fftypes.DataRefs{ {ID: id1}, @@ -596,3 +610,100 @@ func TestSendUnpinnedMessageEventFail(t *testing.T) { mim.AssertExpectations(t) } + +func TestRequestReplyMissingTag(t *testing.T) { + pm, cancel := newTestPrivateMessaging(t) + defer cancel() + + msa := pm.syncasync.(*syncasyncmocks.Bridge) + msa.On("RequestReply", pm.ctx, "ns1", mock.Anything).Return(nil, nil) + + _, err := pm.RequestReply(pm.ctx, "ns1", &fftypes.MessageInOut{}) + assert.Regexp(t, "FF10261", err) +} + +func TestRequestReplyInvalidCID(t *testing.T) { + pm, cancel := newTestPrivateMessaging(t) + defer cancel() + + msa := pm.syncasync.(*syncasyncmocks.Bridge) + msa.On("RequestReply", pm.ctx, "ns1", mock.Anything).Return(nil, nil) + + _, err := pm.RequestReply(pm.ctx, "ns1", &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Tag: "mytag", + CID: fftypes.NewUUID(), + Group: fftypes.NewRandB32(), + }, + }, + }) + assert.Regexp(t, "FF10262", err) +} + +func TestRequestReplySuccess(t *testing.T) { + pm, cancel := newTestPrivateMessaging(t) + defer cancel() + + mim := pm.identity.(*identitymanagermocks.Manager) + mim.On("ResolveInputIdentity", pm.ctx, mock.Anything).Return(nil) + + msa := pm.syncasync.(*syncasyncmocks.Bridge) + msa.On("RequestReply", pm.ctx, "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send() + }). + Return(nil, nil) + + mdm := pm.data.(*datamocks.Manager) + mdm.On("ResolveInlineDataPrivate", pm.ctx, "ns1", mock.Anything).Return(fftypes.DataRefs{ + {ID: fftypes.NewUUID(), Hash: fftypes.NewRandB32()}, + }, nil) + + mdi := pm.database.(*databasemocks.Plugin) + rag := mdi.On("RunAsGroup", pm.ctx, mock.Anything) + rag.RunFn = func(a mock.Arguments) { + fn := a[1].(func(context.Context) error) + rag.ReturnArguments = mock.Arguments{fn(a[0].(context.Context))} + } + mdi.On("InsertMessageLocal", pm.ctx, mock.Anything).Return(nil).Once() + + _, err := pm.RequestReply(pm.ctx, "ns1", &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Tag: "mytag", + Group: fftypes.NewRandB32(), + Identity: fftypes.Identity{ + Author: "org1", + }, + }, + }, + }) + assert.NoError(t, err) +} + +func TestRequestReplyBadIdentity(t *testing.T) { + + pm, cancel := newTestPrivateMessaging(t) + defer cancel() + + mim := pm.identity.(*identitymanagermocks.Manager) + mim.On("ResolveInputIdentity", pm.ctx, mock.Anything).Return(fmt.Errorf("pop")) + + _, err := pm.RequestReply(pm.ctx, "ns1", &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Tag: "mytag", + Group: fftypes.NewRandB32(), + Identity: fftypes.Identity{ + Author: "org1", + }, + }, + }, + }) + assert.Regexp(t, "FF10206.*pop", err) + + mim.AssertExpectations(t) + +} diff --git a/internal/privatemessaging/privatemessaging.go b/internal/privatemessaging/privatemessaging.go index 40c055e14f..304ca0c121 100644 --- a/internal/privatemessaging/privatemessaging.go +++ b/internal/privatemessaging/privatemessaging.go @@ -231,17 +231,3 @@ func (pm *privateMessaging) sendAndSubmitBatch(ctx context.Context, batch *fftyp func (pm *privateMessaging) writeTransaction(ctx context.Context, batch *fftypes.Batch, contexts []*fftypes.Bytes32) error { return pm.batchpin.SubmitPinnedBatch(ctx, batch, contexts) } - -func (pm *privateMessaging) RequestReply(ctx context.Context, ns string, unresolved *fftypes.MessageInOut) (*fftypes.MessageInOut, error) { - if unresolved.Header.Tag == "" { - return nil, i18n.NewError(ctx, i18n.MsgRequestReplyTagRequired) - } - if unresolved.Header.CID != nil { - return nil, i18n.NewError(ctx, i18n.MsgRequestCannotHaveCID) - } - requestID := fftypes.NewUUID() - return pm.syncasync.RequestReply(ctx, ns, requestID, func() error { - _, err := pm.sendMessageWithID(ctx, ns, requestID, unresolved, &unresolved.Message, false) - return err - }) -} diff --git a/internal/privatemessaging/privatemessaging_test.go b/internal/privatemessaging/privatemessaging_test.go index dba69b9b93..695cc1ed8f 100644 --- a/internal/privatemessaging/privatemessaging_test.go +++ b/internal/privatemessaging/privatemessaging_test.go @@ -22,7 +22,6 @@ import ( "testing" "github.com/hyperledger/firefly/internal/config" - "github.com/hyperledger/firefly/internal/syncasync" "github.com/hyperledger/firefly/mocks/batchmocks" "github.com/hyperledger/firefly/mocks/batchpinmocks" "github.com/hyperledger/firefly/mocks/blockchainmocks" @@ -397,68 +396,6 @@ func TestTransferBlobsOpInsertFail(t *testing.T) { assert.Regexp(t, "pop", err) } -func TestRequestReplyMissingTag(t *testing.T) { - pm, cancel := newTestPrivateMessaging(t) - defer cancel() - - msa := pm.syncasync.(*syncasyncmocks.Bridge) - msa.On("RequestReply", pm.ctx, "ns1", mock.Anything).Return(nil, nil) - - _, err := pm.RequestReply(pm.ctx, "ns1", &fftypes.MessageInOut{}) - assert.Regexp(t, "FF10261", err) -} - -func TestRequestReplyInvalidCID(t *testing.T) { - pm, cancel := newTestPrivateMessaging(t) - defer cancel() - - msa := pm.syncasync.(*syncasyncmocks.Bridge) - msa.On("RequestReply", pm.ctx, "ns1", mock.Anything).Return(nil, nil) - - _, err := pm.RequestReply(pm.ctx, "ns1", &fftypes.MessageInOut{ - Message: fftypes.Message{ - Header: fftypes.MessageHeader{ - Tag: "mytag", - CID: fftypes.NewUUID(), - Group: fftypes.NewRandB32(), - }, - }, - }) - assert.Regexp(t, "FF10262", err) -} - -func TestRequestReplySuccess(t *testing.T) { - pm, cancel := newTestPrivateMessaging(t) - defer cancel() - - mim := pm.identity.(*identitymanagermocks.Manager) - mim.On("ResolveInputIdentity", pm.ctx, mock.Anything).Return(nil) - - msa := pm.syncasync.(*syncasyncmocks.Bridge) - msa.On("RequestReply", pm.ctx, "ns1", mock.Anything, mock.Anything). - Run(func(args mock.Arguments) { - send := args[3].(syncasync.RequestSender) - send() - }). - Return(nil, nil) - - mdi := pm.database.(*databasemocks.Plugin) - mdi.On("RunAsGroup", pm.ctx, mock.Anything).Return(nil) - - _, err := pm.RequestReply(pm.ctx, "ns1", &fftypes.MessageInOut{ - Message: fftypes.Message{ - Header: fftypes.MessageHeader{ - Tag: "mytag", - Group: fftypes.NewRandB32(), - Identity: fftypes.Identity{ - Author: "org1", - }, - }, - }, - }) - assert.NoError(t, err) -} - func TestStart(t *testing.T) { pm, cancel := newTestPrivateMessaging(t) defer cancel() From 297aa68365f73d27b79e35437f3580c140eca94e Mon Sep 17 00:00:00 2001 From: Andrew Richardson Date: Thu, 14 Oct 2021 15:05:31 -0400 Subject: [PATCH 3/8] Group message sending methods on stateful "sender" objects Introduce new types for Broadcast and PrivateMessage, which can track state information as a message is resolved and sent. Currently we have two types of "send" (Send or SendAndWait), but this will also allow future customizations to the send flow. Signed-off-by: Andrew Richardson --- Makefile | 2 + internal/broadcast/definition.go | 35 ++-- internal/broadcast/manager.go | 51 +++-- internal/broadcast/manager_test.go | 181 +++++++++++++--- internal/broadcast/message.go | 150 +++++++------- internal/broadcast/message_test.go | 114 ----------- internal/privatemessaging/message.go | 156 +++++++------- internal/privatemessaging/message_test.go | 193 +++++++++--------- internal/privatemessaging/privatemessaging.go | 6 + .../privatemessaging/privatemessaging_test.go | 14 +- internal/privatemessaging/recipients.go | 2 +- internal/privatemessaging/recipients_test.go | 22 +- internal/syshandlers/reply_sender.go | 7 +- internal/syshandlers/reply_sender_test.go | 25 ++- mocks/broadcastmocks/broadcast.go | 42 ++++ mocks/broadcastmocks/manager.go | 19 ++ mocks/privatemessagingmocks/manager.go | 18 ++ .../privatemessagingmocks/private_message.go | 42 ++++ 18 files changed, 639 insertions(+), 440 deletions(-) create mode 100644 mocks/broadcastmocks/broadcast.go create mode 100644 mocks/privatemessagingmocks/private_message.go diff --git a/Makefile b/Makefile index 5b467f941c..ee83a1cdd8 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,9 @@ $(eval $(call makemock, internal/syncasync, Bridge, syncasyncmock $(eval $(call makemock, internal/data, Manager, datamocks)) $(eval $(call makemock, internal/batch, Manager, batchmocks)) $(eval $(call makemock, internal/broadcast, Manager, broadcastmocks)) +$(eval $(call makemock, internal/broadcast, Broadcast, broadcastmocks)) $(eval $(call makemock, internal/privatemessaging, Manager, privatemessagingmocks)) +$(eval $(call makemock, internal/privatemessaging, PrivateMessage, privatemessagingmocks)) $(eval $(call makemock, internal/syshandlers, SystemHandlers, syshandlersmocks)) $(eval $(call makemock, internal/events, EventManager, eventmocks)) $(eval $(call makemock, internal/networkmap, Manager, networkmapmocks)) diff --git a/internal/broadcast/definition.go b/internal/broadcast/definition.go index 5d786456de..8337dbe1ca 100644 --- a/internal/broadcast/definition.go +++ b/internal/broadcast/definition.go @@ -68,21 +68,30 @@ func (bm *broadcastManager) broadcastDefinitionCommon(ctx context.Context, def f } // Create a broadcast message referring to the data - msg = &fftypes.Message{ - Header: fftypes.MessageHeader{ - Namespace: fftypes.SystemNamespace, - Type: fftypes.MessageTypeDefinition, - Identity: *signingIdentity, - Topics: fftypes.FFNameArray{def.Topic()}, - Tag: string(tag), - TxType: fftypes.TransactionTypeBatchPin, - }, - Data: fftypes.DataRefs{ - {ID: data.ID, Hash: data.Hash}, + in := &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Namespace: fftypes.SystemNamespace, + Type: fftypes.MessageTypeDefinition, + Identity: *signingIdentity, + Topics: fftypes.FFNameArray{def.Topic()}, + Tag: string(tag), + TxType: fftypes.TransactionTypeBatchPin, + }, + Data: fftypes.DataRefs{ + {ID: data.ID, Hash: data.Hash}, + }, }, } // Broadcast the message - return bm.broadcastMessageCommon(ctx, msg, waitConfirm) - + sender := broadcastSender{ + mgr: bm, + namespace: fftypes.SystemNamespace, + msg: in, + resolved: true, + } + sender.setDefaults() + err = sender.sendInternal(ctx, waitConfirm) + return &in.Message, err } diff --git a/internal/broadcast/manager.go b/internal/broadcast/manager.go index ac6a72b129..55df38a73d 100644 --- a/internal/broadcast/manager.go +++ b/internal/broadcast/manager.go @@ -27,6 +27,7 @@ import ( "github.com/hyperledger/firefly/internal/data" "github.com/hyperledger/firefly/internal/i18n" "github.com/hyperledger/firefly/internal/identity" + "github.com/hyperledger/firefly/internal/log" "github.com/hyperledger/firefly/internal/syncasync" "github.com/hyperledger/firefly/pkg/blockchain" "github.com/hyperledger/firefly/pkg/database" @@ -35,7 +36,13 @@ import ( "github.com/hyperledger/firefly/pkg/publicstorage" ) +type Broadcast interface { + Send(ctx context.Context) error + SendAndWait(ctx context.Context) error +} + type Manager interface { + NewBroadcast(ns string, in *fftypes.MessageInOut) Broadcast BroadcastDatatype(ctx context.Context, ns string, datatype *fftypes.Datatype, waitConfirm bool) (msg *fftypes.Message, err error) BroadcastNamespace(ctx context.Context, ns *fftypes.Namespace, waitConfirm bool) (msg *fftypes.Message, err error) BroadcastMessage(ctx context.Context, ns string, in *fftypes.MessageInOut, waitConfirm bool) (out *fftypes.Message, err error) @@ -134,28 +141,32 @@ func (bm *broadcastManager) submitTXAndUpdateDB(ctx context.Context, batch *ffty return bm.batchpin.SubmitPinnedBatch(ctx, batch, contexts) } -func (bm *broadcastManager) broadcastMessageCommon(ctx context.Context, msg *fftypes.Message, waitConfirm bool) (*fftypes.Message, error) { - if waitConfirm { - return bm.broadcastMessageSync(ctx, msg) - } - return bm.broadcastMessageAsync(ctx, msg) -} - -func (bm *broadcastManager) broadcastMessageAsync(ctx context.Context, msg *fftypes.Message) (*fftypes.Message, error) { - // Seal the message - if err := msg.Seal(ctx); err != nil { - return nil, err +func (bm *broadcastManager) publishBlobs(ctx context.Context, dataToPublish []*fftypes.DataAndBlob) error { + for _, d := range dataToPublish { + // Stream from the local data exchange ... + reader, err := bm.exchange.DownloadBLOB(ctx, d.Blob.PayloadRef) + if err != nil { + return i18n.WrapError(ctx, err, i18n.MsgDownloadBlobFailed, d.Blob.PayloadRef) + } + defer reader.Close() + + // ... to the public storage + publicRef, err := bm.publicstorage.PublishData(ctx, reader) + if err != nil { + return err + } + log.L(ctx).Infof("Published blob with hash '%s' for data '%s' to public storage: '%s'", d.Data.Blob, d.Data.ID, publicRef) + + // Update the data in the database, with the public reference. + // We do this independently for each piece of data + update := database.DataQueryFactory.NewUpdate(ctx).Set("blob.public", publicRef) + err = bm.database.UpdateData(ctx, d.Data.ID, update) + if err != nil { + return err + } } - // Store the message - this asynchronously triggers the next step in process - return msg, bm.database.InsertMessageLocal(ctx, msg) -} - -func (bm *broadcastManager) broadcastMessageSync(ctx context.Context, msg *fftypes.Message) (*fftypes.Message, error) { - return bm.syncasync.SendConfirm(ctx, msg.Header.Namespace, msg.Header.ID, func() error { - _, err := bm.resolveAndSend(ctx, nil, msg, false) - return err - }) + return nil } func (bm *broadcastManager) Start() error { diff --git a/internal/broadcast/manager_test.go b/internal/broadcast/manager_test.go index c535423da9..89264cee70 100644 --- a/internal/broadcast/manager_test.go +++ b/internal/broadcast/manager_test.go @@ -17,8 +17,11 @@ package broadcast import ( + "bytes" "context" "fmt" + "io" + "io/ioutil" "testing" "github.com/hyperledger/firefly/internal/config" @@ -48,11 +51,20 @@ func newTestBroadcast(t *testing.T) (*broadcastManager, func()) { msa := &syncasyncmocks.Bridge{} mbp := &batchpinmocks.Submitter{} mbi.On("Name").Return("ut_blockchain").Maybe() + mpi.On("Name").Return("ut_publicstorage").Maybe() mba.On("RegisterDispatcher", []fftypes.MessageType{ fftypes.MessageTypeBroadcast, fftypes.MessageTypeDefinition, fftypes.MessageTypeTransferBroadcast, }, mock.Anything, mock.Anything).Return() + + rag := mdi.On("RunAsGroup", mock.Anything, mock.Anything).Maybe() + rag.RunFn = func(a mock.Arguments) { + rag.ReturnArguments = mock.Arguments{ + a[1].(func(context.Context) error)(a[0].(context.Context)), + } + } + ctx, cancel := context.WithCancel(context.Background()) b, err := NewBroadcastManager(ctx, mdi, mim, mdm, mbi, mdx, mpi, mba, msa, mbp) assert.NoError(t, err) @@ -68,12 +80,15 @@ func TestBroadcastMessageGood(t *testing.T) { bm, cancel := newTestBroadcast(t) defer cancel() - msg := &fftypes.Message{} - bm.database.(*databasemocks.Plugin).On("InsertMessageLocal", mock.Anything, msg).Return(nil) + msg := &fftypes.MessageInOut{} + bm.database.(*databasemocks.Plugin).On("InsertMessageLocal", mock.Anything, &msg.Message).Return(nil) - msgRet, err := bm.broadcastMessageCommon(context.Background(), msg, false) + broadcast := broadcastSender{ + mgr: bm, + msg: msg, + } + err := broadcast.sendInternal(context.Background(), false) assert.NoError(t, err) - assert.Equal(t, msg, msgRet) bm.Start() bm.WaitStop() @@ -84,14 +99,20 @@ func TestBroadcastMessageBad(t *testing.T) { defer cancel() dupID := fftypes.NewUUID() - msg := &fftypes.Message{ - Data: fftypes.DataRefs{ - {ID: dupID /* missing hash */}, + msg := &fftypes.MessageInOut{ + Message: fftypes.Message{ + Data: fftypes.DataRefs{ + {ID: dupID /* missing hash */}, + }, }, } bm.database.(*databasemocks.Plugin).On("UpsertMessage", mock.Anything, msg, false).Return(nil) - _, err := bm.broadcastMessageCommon(context.Background(), msg, false) + broadcast := broadcastSender{ + mgr: bm, + msg: msg, + } + err := broadcast.sendInternal(context.Background(), false) assert.Regexp(t, "FF10144", err) } @@ -113,7 +134,6 @@ func TestDispatchBatchInvalidData(t *testing.T) { func TestDispatchBatchUploadFail(t *testing.T) { bm, cancel := newTestBroadcast(t) defer cancel() - bm.publicstorage.(*publicstoragemocks.Plugin).On("PublishData", mock.Anything, mock.Anything).Return("", fmt.Errorf("pop")) err := bm.dispatchBatch(context.Background(), &fftypes.Batch{}, []*fftypes.Bytes32{fftypes.NewRandB32()}) @@ -124,11 +144,19 @@ func TestDispatchBatchSubmitBatchPinSucceed(t *testing.T) { bm, cancel := newTestBroadcast(t) defer cancel() + batch := &fftypes.Batch{ + ID: fftypes.NewUUID(), + } + mdi := bm.database.(*databasemocks.Plugin) - mdi.On("RunAsGroup", mock.Anything, mock.Anything).Return(nil) - bm.publicstorage.(*publicstoragemocks.Plugin).On("PublishData", mock.Anything, mock.Anything).Return("id1", nil) + mps := bm.publicstorage.(*publicstoragemocks.Plugin) + mbp := bm.batchpin.(*batchpinmocks.Submitter) + mps.On("PublishData", mock.Anything, mock.Anything).Return("id1", nil) + mdi.On("UpdateBatch", mock.Anything, batch.ID, mock.Anything).Return(nil) + mdi.On("UpsertOperation", mock.Anything, mock.Anything, false).Return(nil) + mbp.On("SubmitPinnedBatch", mock.Anything, mock.Anything, mock.Anything).Return(nil) - err := bm.dispatchBatch(context.Background(), &fftypes.Batch{}, []*fftypes.Bytes32{fftypes.NewRandB32()}) + err := bm.dispatchBatch(context.Background(), batch, []*fftypes.Bytes32{fftypes.NewRandB32()}) assert.NoError(t, err) } @@ -139,19 +167,13 @@ func TestDispatchBatchSubmitBroadcastFail(t *testing.T) { mdi := bm.database.(*databasemocks.Plugin) mps := bm.publicstorage.(*publicstoragemocks.Plugin) mbp := bm.batchpin.(*batchpinmocks.Submitter) - mdi.On("RunAsGroup", mock.Anything, mock.Anything).Return(nil) mps.On("PublishData", mock.Anything, mock.Anything).Return("id1", nil) - mps.On("Name").Return("ut_publicstorage") - - err := bm.dispatchBatch(context.Background(), &fftypes.Batch{Identity: fftypes.Identity{Author: "wrong", Key: "wrong"}}, []*fftypes.Bytes32{fftypes.NewRandB32()}) - assert.NoError(t, err) - mdi.On("UpdateBatch", mock.Anything, mock.Anything, mock.Anything).Return(nil) mdi.On("UpsertOperation", mock.Anything, mock.Anything, false).Return(nil) mbp.On("SubmitPinnedBatch", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - fn := mdi.Calls[0].Arguments[1].(func(ctx context.Context) error) - err = fn(context.Background()) - assert.Regexp(t, "pop", err) + + err := bm.dispatchBatch(context.Background(), &fftypes.Batch{Identity: fftypes.Identity{Author: "wrong", Key: "wrong"}}, []*fftypes.Bytes32{fftypes.NewRandB32()}) + assert.EqualError(t, err, "pop") } func TestSubmitTXAndUpdateDBUpdateBatchFail(t *testing.T) { @@ -178,7 +200,6 @@ func TestSubmitTXAndUpdateDBAddOp1Fail(t *testing.T) { mdi.On("UpsertOperation", mock.Anything, mock.Anything, false).Return(fmt.Errorf("pop")) mbi.On("SubmitBatchPin", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("txid", nil) mbi.On("Name").Return("unittest") - bm.publicstorage.(*publicstoragemocks.Plugin).On("Name").Return("ut_publicstorage") batch := &fftypes.Batch{ Identity: fftypes.Identity{Author: "org1", Key: "0x12345"}, @@ -208,8 +229,6 @@ func TestSubmitTXAndUpdateDBSucceed(t *testing.T) { mbi.On("SubmitBatchPin", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) mbp.On("SubmitPinnedBatch", mock.Anything, mock.Anything, mock.Anything).Return(nil) - bm.publicstorage.(*publicstoragemocks.Plugin).On("Name").Return("ut_publicstorage") - msgID := fftypes.NewUUID() batch := &fftypes.Batch{ Identity: fftypes.Identity{Author: "org1", Key: "0x12345"}, @@ -237,3 +256,117 @@ func TestSubmitTXAndUpdateDBSucceed(t *testing.T) { assert.Equal(t, fftypes.OpTypePublicStorageBatchBroadcast, op.Type) } + +func TestPublishBlobsUpdateDataFail(t *testing.T) { + bm, cancel := newTestBroadcast(t) + defer cancel() + mdi := bm.database.(*databasemocks.Plugin) + mdx := bm.exchange.(*dataexchangemocks.Plugin) + mps := bm.publicstorage.(*publicstoragemocks.Plugin) + mim := bm.identity.(*identitymanagermocks.Manager) + + blobHash := fftypes.NewRandB32() + dataID := fftypes.NewUUID() + + ctx := context.Background() + mdx.On("DownloadBLOB", ctx, "blob/1").Return(ioutil.NopCloser(bytes.NewReader([]byte(`some data`))), nil) + mps.On("PublishData", ctx, mock.MatchedBy(func(reader io.ReadCloser) bool { + b, err := ioutil.ReadAll(reader) + assert.NoError(t, err) + assert.Equal(t, "some data", string(b)) + return true + })).Return("payload-ref", nil) + mdi.On("UpdateData", ctx, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) + + err := bm.publishBlobs(ctx, []*fftypes.DataAndBlob{ + { + Data: &fftypes.Data{ + ID: dataID, + Blob: &fftypes.BlobRef{ + Hash: blobHash, + }, + }, + Blob: &fftypes.Blob{ + Hash: blobHash, + PayloadRef: "blob/1", + }, + }, + }) + assert.EqualError(t, err, "pop") + + mdi.AssertExpectations(t) +} + +func TestPublishBlobsPublishFail(t *testing.T) { + bm, cancel := newTestBroadcast(t) + defer cancel() + mdi := bm.database.(*databasemocks.Plugin) + mdx := bm.exchange.(*dataexchangemocks.Plugin) + mps := bm.publicstorage.(*publicstoragemocks.Plugin) + mim := bm.identity.(*identitymanagermocks.Manager) + + blobHash := fftypes.NewRandB32() + dataID := fftypes.NewUUID() + + ctx := context.Background() + mdx.On("DownloadBLOB", ctx, "blob/1").Return(ioutil.NopCloser(bytes.NewReader([]byte(`some data`))), nil) + mps.On("PublishData", ctx, mock.MatchedBy(func(reader io.ReadCloser) bool { + b, err := ioutil.ReadAll(reader) + assert.NoError(t, err) + assert.Equal(t, "some data", string(b)) + return true + })).Return("", fmt.Errorf("pop")) + mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) + + err := bm.publishBlobs(ctx, []*fftypes.DataAndBlob{ + { + Data: &fftypes.Data{ + ID: dataID, + Blob: &fftypes.BlobRef{ + Hash: blobHash, + }, + }, + Blob: &fftypes.Blob{ + Hash: blobHash, + PayloadRef: "blob/1", + }, + }, + }) + assert.EqualError(t, err, "pop") + + mdi.AssertExpectations(t) +} + +func TestPublishBlobsDownloadFail(t *testing.T) { + bm, cancel := newTestBroadcast(t) + defer cancel() + mdi := bm.database.(*databasemocks.Plugin) + mdx := bm.exchange.(*dataexchangemocks.Plugin) + mim := bm.identity.(*identitymanagermocks.Manager) + + blobHash := fftypes.NewRandB32() + dataID := fftypes.NewUUID() + + ctx := context.Background() + mdx.On("DownloadBLOB", ctx, "blob/1").Return(nil, fmt.Errorf("pop")) + mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) + + err := bm.publishBlobs(ctx, []*fftypes.DataAndBlob{ + { + Data: &fftypes.Data{ + ID: dataID, + Blob: &fftypes.BlobRef{ + Hash: blobHash, + }, + }, + Blob: &fftypes.Blob{ + Hash: blobHash, + PayloadRef: "blob/1", + }, + }, + }) + assert.Regexp(t, "FF10240", err) + + mdi.AssertExpectations(t) +} diff --git a/internal/broadcast/message.go b/internal/broadcast/message.go index 7a111d3102..57a40d9518 100644 --- a/internal/broadcast/message.go +++ b/internal/broadcast/message.go @@ -21,87 +21,127 @@ import ( "encoding/json" "github.com/hyperledger/firefly/internal/i18n" - "github.com/hyperledger/firefly/internal/log" - "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" ) -func (bm *broadcastManager) BroadcastMessage(ctx context.Context, ns string, in *fftypes.MessageInOut, waitConfirm bool) (out *fftypes.Message, err error) { - if err := bm.prepareMessage(ctx, ns, in); err != nil { - return nil, err +func (bm *broadcastManager) NewBroadcast(ns string, in *fftypes.MessageInOut) Broadcast { + broadcast := &broadcastSender{ + mgr: bm, + namespace: ns, + msg: in, } - return bm.resolveAndSend(ctx, in, nil, waitConfirm) + broadcast.setDefaults() + return broadcast } -func (bm *broadcastManager) prepareMessage(ctx context.Context, ns string, msg *fftypes.MessageInOut) error { - msg.Header.ID = fftypes.NewUUID() - msg.Header.Namespace = ns - if msg.Header.Type == "" { - msg.Header.Type = fftypes.MessageTypeBroadcast - } - if msg.Header.TxType == "" { - msg.Header.TxType = fftypes.TransactionTypeBatchPin +func (bm *broadcastManager) BroadcastMessage(ctx context.Context, ns string, in *fftypes.MessageInOut, waitConfirm bool) (out *fftypes.Message, err error) { + broadcast := bm.NewBroadcast(ns, in) + if waitConfirm { + err = broadcast.SendAndWait(ctx) + } else { + err = broadcast.Send(ctx) } + return &in.Message, err +} - if !bm.isRootOrgBroadcast(ctx, &msg.Message) { - // Resolve the sending identity - if err := bm.identity.ResolveInputIdentity(ctx, &msg.Header.Identity); err != nil { - return i18n.WrapError(ctx, err, i18n.MsgAuthorInvalid) - } - } +type broadcastSender struct { + mgr *broadcastManager + namespace string + msg *fftypes.MessageInOut + resolved bool +} + +func (s *broadcastSender) Send(ctx context.Context) error { + return s.resolveAndSend(ctx, false) +} - return nil +func (s *broadcastSender) SendAndWait(ctx context.Context) error { + return s.resolveAndSend(ctx, true) } -func (bm *broadcastManager) resolveAndSend(ctx context.Context, unresolved *fftypes.MessageInOut, resolved *fftypes.Message, waitConfirm bool) (out *fftypes.Message, err error) { - if unresolved != nil { - resolved = &unresolved.Message +func (s *broadcastSender) setDefaults() { + s.msg.Header.ID = fftypes.NewUUID() + s.msg.Header.Namespace = s.namespace + if s.msg.Header.Type == "" { + s.msg.Header.Type = fftypes.MessageTypeBroadcast } + if s.msg.Header.TxType == "" { + s.msg.Header.TxType = fftypes.TransactionTypeBatchPin + } +} - // We optimize the DB storage of all the parts of the message using transaction semantics (assuming those are supported by the DB plugin) +func (s *broadcastSender) resolveAndSend(ctx context.Context, waitConfirm bool) error { sent := false + + // We optimize the DB storage of all the parts of the message using transaction semantics (assuming those are supported by the DB plugin) var dataToPublish []*fftypes.DataAndBlob - err = bm.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { - if unresolved != nil { - dataToPublish, err = bm.resolveMessage(ctx, unresolved) - if err != nil { + err := s.mgr.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { + if !s.resolved { + if dataToPublish, err = s.resolveMessage(ctx); err != nil { return err } + s.resolved = true } // For the simple case where we have no data to publish and aren't waiting for blockchain confirmation, // insert the local message immediately within the same DB transaction. // Otherwise, break out of the DB transaction (since those operations could take multiple seconds). if len(dataToPublish) == 0 && !waitConfirm { - out, err = bm.broadcastMessageAsync(ctx, resolved) sent = true + return s.sendInternal(ctx, waitConfirm) } - return err + return nil }) if err != nil || sent { - return out, err + return err } // Perform deferred processing if len(dataToPublish) > 0 { - if err := bm.publishBlobs(ctx, dataToPublish); err != nil { - return nil, err + if err := s.mgr.publishBlobs(ctx, dataToPublish); err != nil { + return err } } - return bm.broadcastMessageCommon(ctx, resolved, waitConfirm) + return s.sendInternal(ctx, waitConfirm) } -func (bm *broadcastManager) resolveMessage(ctx context.Context, in *fftypes.MessageInOut) (dataToPublish []*fftypes.DataAndBlob, err error) { +func (s *broadcastSender) resolveMessage(ctx context.Context) ([]*fftypes.DataAndBlob, error) { + // Resolve the sending identity + if !s.isRootOrgBroadcast(ctx) { + if err := s.mgr.identity.ResolveInputIdentity(ctx, &s.msg.Header.Identity); err != nil { + return nil, i18n.WrapError(ctx, err, i18n.MsgAuthorInvalid) + } + } + // The data manager is responsible for the heavy lifting of storing/validating all our in-line data elements - in.Data, dataToPublish, err = bm.data.ResolveInlineDataBroadcast(ctx, in.Header.Namespace, in.InlineData) + dataRefs, dataToPublish, err := s.mgr.data.ResolveInlineDataBroadcast(ctx, s.namespace, s.msg.InlineData) + s.msg.Message.Data = dataRefs return dataToPublish, err } -func (bm *broadcastManager) isRootOrgBroadcast(ctx context.Context, message *fftypes.Message) bool { +func (s *broadcastSender) sendInternal(ctx context.Context, waitConfirm bool) (err error) { + if waitConfirm { + out, err := s.mgr.syncasync.SendConfirm(ctx, s.namespace, s.msg.Header.ID, func() error { + return s.Send(ctx) + }) + s.msg.Message = *out + return err + } + + // Seal the message + if err := s.msg.Seal(ctx); err != nil { + return err + } + + // Store the message - this asynchronously triggers the next step in process + return s.mgr.database.InsertMessageLocal(ctx, &s.msg.Message) +} + +func (s *broadcastSender) isRootOrgBroadcast(ctx context.Context) bool { // Look into message to see if it contains a data item that is a root organization definition - if message.Header.Type == fftypes.MessageTypeDefinition { - messageData, ok, err := bm.data.GetMessageData(ctx, message, true) + if s.msg.Header.Type == fftypes.MessageTypeDefinition { + messageData, ok, err := s.mgr.data.GetMessageData(ctx, &s.msg.Message, true) if ok && err == nil { if len(messageData) > 0 { dataItem := messageData[0] @@ -120,33 +160,3 @@ func (bm *broadcastManager) isRootOrgBroadcast(ctx context.Context, message *fft } return false } - -func (bm *broadcastManager) publishBlobs(ctx context.Context, dataToPublish []*fftypes.DataAndBlob) error { - - for _, d := range dataToPublish { - - // Stream from the local data exchange ... - reader, err := bm.exchange.DownloadBLOB(ctx, d.Blob.PayloadRef) - if err != nil { - return i18n.WrapError(ctx, err, i18n.MsgDownloadBlobFailed, d.Blob.PayloadRef) - } - defer reader.Close() - - // ... to the public storage - publicRef, err := bm.publicstorage.PublishData(ctx, reader) - if err != nil { - return err - } - log.L(ctx).Infof("Published blob with hash '%s' for data '%s' to public storage: '%s'", d.Data.Blob, d.Data.ID, publicRef) - - // Update the data in the database, with the public reference. - // We do this independently for each piece of data - update := database.DataQueryFactory.NewUpdate(ctx).Set("blob.public", publicRef) - err = bm.database.UpdateData(ctx, d.Data.ID, update) - if err != nil { - return err - } - } - - return nil -} diff --git a/internal/broadcast/message_test.go b/internal/broadcast/message_test.go index b676ab1c01..743c4ef34d 100644 --- a/internal/broadcast/message_test.go +++ b/internal/broadcast/message_test.go @@ -408,117 +408,3 @@ func TestPublishBlobsSendMessageFail(t *testing.T) { mdx.AssertExpectations(t) mim.AssertExpectations(t) } - -func TestPublishBlobsUpdateDataFail(t *testing.T) { - bm, cancel := newTestBroadcast(t) - defer cancel() - mdi := bm.database.(*databasemocks.Plugin) - mdx := bm.exchange.(*dataexchangemocks.Plugin) - mps := bm.publicstorage.(*publicstoragemocks.Plugin) - mim := bm.identity.(*identitymanagermocks.Manager) - - blobHash := fftypes.NewRandB32() - dataID := fftypes.NewUUID() - - ctx := context.Background() - mdx.On("DownloadBLOB", ctx, "blob/1").Return(ioutil.NopCloser(bytes.NewReader([]byte(`some data`))), nil) - mps.On("PublishData", ctx, mock.MatchedBy(func(reader io.ReadCloser) bool { - b, err := ioutil.ReadAll(reader) - assert.NoError(t, err) - assert.Equal(t, "some data", string(b)) - return true - })).Return("payload-ref", nil) - mdi.On("UpdateData", ctx, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) - - err := bm.publishBlobs(ctx, []*fftypes.DataAndBlob{ - { - Data: &fftypes.Data{ - ID: dataID, - Blob: &fftypes.BlobRef{ - Hash: blobHash, - }, - }, - Blob: &fftypes.Blob{ - Hash: blobHash, - PayloadRef: "blob/1", - }, - }, - }) - assert.EqualError(t, err, "pop") - - mdi.AssertExpectations(t) -} - -func TestPublishBlobsPublishFail(t *testing.T) { - bm, cancel := newTestBroadcast(t) - defer cancel() - mdi := bm.database.(*databasemocks.Plugin) - mdx := bm.exchange.(*dataexchangemocks.Plugin) - mps := bm.publicstorage.(*publicstoragemocks.Plugin) - mim := bm.identity.(*identitymanagermocks.Manager) - - blobHash := fftypes.NewRandB32() - dataID := fftypes.NewUUID() - - ctx := context.Background() - mdx.On("DownloadBLOB", ctx, "blob/1").Return(ioutil.NopCloser(bytes.NewReader([]byte(`some data`))), nil) - mps.On("PublishData", ctx, mock.MatchedBy(func(reader io.ReadCloser) bool { - b, err := ioutil.ReadAll(reader) - assert.NoError(t, err) - assert.Equal(t, "some data", string(b)) - return true - })).Return("", fmt.Errorf("pop")) - mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) - - err := bm.publishBlobs(ctx, []*fftypes.DataAndBlob{ - { - Data: &fftypes.Data{ - ID: dataID, - Blob: &fftypes.BlobRef{ - Hash: blobHash, - }, - }, - Blob: &fftypes.Blob{ - Hash: blobHash, - PayloadRef: "blob/1", - }, - }, - }) - assert.EqualError(t, err, "pop") - - mdi.AssertExpectations(t) -} - -func TestPublishBlobsDownloadFail(t *testing.T) { - bm, cancel := newTestBroadcast(t) - defer cancel() - mdi := bm.database.(*databasemocks.Plugin) - mdx := bm.exchange.(*dataexchangemocks.Plugin) - mim := bm.identity.(*identitymanagermocks.Manager) - - blobHash := fftypes.NewRandB32() - dataID := fftypes.NewUUID() - - ctx := context.Background() - mdx.On("DownloadBLOB", ctx, "blob/1").Return(nil, fmt.Errorf("pop")) - mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) - - err := bm.publishBlobs(ctx, []*fftypes.DataAndBlob{ - { - Data: &fftypes.Data{ - ID: dataID, - Blob: &fftypes.BlobRef{ - Hash: blobHash, - }, - }, - Blob: &fftypes.Blob{ - Hash: blobHash, - PayloadRef: "blob/1", - }, - }, - }) - assert.Regexp(t, "FF10240", err) - - mdi.AssertExpectations(t) -} diff --git a/internal/privatemessaging/message.go b/internal/privatemessaging/message.go index 8314e229e2..5d96e988a5 100644 --- a/internal/privatemessaging/message.go +++ b/internal/privatemessaging/message.go @@ -24,11 +24,24 @@ import ( "github.com/hyperledger/firefly/pkg/fftypes" ) +func (pm *privateMessaging) NewMessage(ns string, in *fftypes.MessageInOut) PrivateMessage { + message := &messageSender{ + mgr: pm, + namespace: ns, + msg: in, + } + message.setDefaults() + return message +} + func (pm *privateMessaging) SendMessage(ctx context.Context, ns string, in *fftypes.MessageInOut, waitConfirm bool) (out *fftypes.Message, err error) { - if err := pm.prepareMessage(ctx, ns, in); err != nil { - return nil, err + message := pm.NewMessage(ns, in) + if waitConfirm { + err = message.SendAndWait(ctx) + } else { + err = message.Send(ctx) } - return pm.resolveAndSend(ctx, in, nil, waitConfirm) + return &in.Message, err } func (pm *privateMessaging) RequestReply(ctx context.Context, ns string, in *fftypes.MessageInOut) (*fftypes.MessageInOut, error) { @@ -38,140 +51,141 @@ func (pm *privateMessaging) RequestReply(ctx context.Context, ns string, in *fft if in.Header.CID != nil { return nil, i18n.NewError(ctx, i18n.MsgRequestCannotHaveCID) } - if err := pm.prepareMessage(ctx, ns, in); err != nil { - return nil, err - } + message := pm.NewMessage(ns, in) return pm.syncasync.RequestReply(ctx, ns, in.Header.ID, func() error { - _, err := pm.resolveAndSend(ctx, in, &in.Message, false) - return err + return message.Send(ctx) }) } -func (pm *privateMessaging) prepareMessage(ctx context.Context, ns string, msg *fftypes.MessageInOut) error { - msg.Header.ID = fftypes.NewUUID() - msg.Header.Namespace = ns - if msg.Header.Type == "" { - msg.Header.Type = fftypes.MessageTypePrivate - } - if msg.Header.TxType == "" { - msg.Header.TxType = fftypes.TransactionTypeBatchPin - } +type messageSender struct { + mgr *privateMessaging + namespace string + msg *fftypes.MessageInOut + resolved bool +} - // Resolve the sending identity - if err := pm.identity.ResolveInputIdentity(ctx, &msg.Header.Identity); err != nil { - return i18n.WrapError(ctx, err, i18n.MsgAuthorInvalid) - } +func (s *messageSender) Send(ctx context.Context) error { + return s.resolveAndSend(ctx, false) +} - return nil +func (s *messageSender) SendAndWait(ctx context.Context) error { + return s.resolveAndSend(ctx, true) } -func (pm *privateMessaging) resolveAndSend(ctx context.Context, unresolved *fftypes.MessageInOut, resolved *fftypes.Message, waitConfirm bool) (out *fftypes.Message, err error) { - if unresolved != nil { - resolved = &unresolved.Message +func (s *messageSender) setDefaults() { + s.msg.Header.ID = fftypes.NewUUID() + s.msg.Header.Namespace = s.namespace + if s.msg.Header.Type == "" { + s.msg.Header.Type = fftypes.MessageTypePrivate + } + if s.msg.Header.TxType == "" { + s.msg.Header.TxType = fftypes.TransactionTypeBatchPin } +} - // We optimize the DB storage of all the parts of the message using transaction semantics (assuming those are supported by the DB plugin) +func (s *messageSender) resolveAndSend(ctx context.Context, waitConfirm bool) error { sent := false - err = pm.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { - if unresolved != nil { - err = pm.resolveMessage(ctx, unresolved) - if err != nil { + + // We optimize the DB storage of all the parts of the message using transaction semantics (assuming those are supported by the DB plugin) + err := s.mgr.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { + if !s.resolved { + if err := s.resolveMessage(ctx); err != nil { return err } + s.resolved = true } // If we aren't waiting for blockchain confirmation, insert the local message immediately within the same DB transaction. if !waitConfirm { - out, err = pm.sendMessageAsync(ctx, resolved) + err = s.sendInternal(ctx, waitConfirm) sent = true } return err }) if err != nil || sent { - return out, err + return err } - return pm.sendMessageCommon(ctx, resolved, waitConfirm) + return s.sendInternal(ctx, waitConfirm) } -func (pm *privateMessaging) resolveMessage(ctx context.Context, in *fftypes.MessageInOut) (err error) { +func (s *messageSender) resolveMessage(ctx context.Context) error { + // Resolve the sending identity + if err := s.mgr.identity.ResolveInputIdentity(ctx, &s.msg.Header.Identity); err != nil { + return i18n.WrapError(ctx, err, i18n.MsgAuthorInvalid) + } + // Resolve the member list into a group - if err = pm.resolveReceipientList(ctx, in); err != nil { + if err := s.mgr.resolveRecipientList(ctx, s.msg); err != nil { return err } // The data manager is responsible for the heavy lifting of storing/validating all our in-line data elements - in.Message.Data, err = pm.data.ResolveInlineDataPrivate(ctx, in.Header.Namespace, in.InlineData) + dataRefs, err := s.mgr.data.ResolveInlineDataPrivate(ctx, s.namespace, s.msg.InlineData) + s.msg.Message.Data = dataRefs return err } -func (pm *privateMessaging) sendMessageCommon(ctx context.Context, msg *fftypes.Message, waitConfirm bool) (*fftypes.Message, error) { - if waitConfirm && msg.Header.TxType != fftypes.TransactionTypeNone { - return pm.sendMessageSync(ctx, msg) - } - return pm.sendMessageAsync(ctx, msg) -} +func (s *messageSender) sendInternal(ctx context.Context, waitConfirm bool) error { + immediateConfirm := s.msg.Header.TxType == fftypes.TransactionTypeNone -func (pm *privateMessaging) sendMessageAsync(ctx context.Context, msg *fftypes.Message) (*fftypes.Message, error) { - immediateConfirm := msg.Header.TxType == fftypes.TransactionTypeNone + if waitConfirm && !immediateConfirm { + // Pass it to the sync-async handler to wait for the confirmation to come back in. + // NOTE: Our caller makes sure we are not in a RunAsGroup (which would be bad) + out, err := s.mgr.syncasync.SendConfirm(ctx, s.namespace, s.msg.Header.ID, func() error { + return s.Send(ctx) + }) + s.msg.Message = *out + return err + } // Seal the message - if err := msg.Seal(ctx); err != nil { - return nil, err + if err := s.msg.Seal(ctx); err != nil { + return err } if immediateConfirm { - msg.Confirmed = fftypes.Now() - msg.Pending = false + s.msg.Confirmed = fftypes.Now() + s.msg.Pending = false // msg.Header.Key = "" // there is no on-chain signing assurance with this message } // Store the message - this asynchronously triggers the next step in process - if err := pm.database.InsertMessageLocal(ctx, msg); err != nil { - return nil, err + if err := s.mgr.database.InsertMessageLocal(ctx, &s.msg.Message); err != nil { + return err } if immediateConfirm { - if err := pm.sendUnpinnedMessage(ctx, msg); err != nil { - return nil, err + if err := s.sendUnpinned(ctx); err != nil { + return err } // Emit a confirmation event locally immediately - event := fftypes.NewEvent(fftypes.EventTypeMessageConfirmed, msg.Header.Namespace, msg.Header.ID) - if err := pm.database.InsertEvent(ctx, event); err != nil { - return nil, err + event := fftypes.NewEvent(fftypes.EventTypeMessageConfirmed, s.namespace, s.msg.Header.ID) + if err := s.mgr.database.InsertEvent(ctx, event); err != nil { + return err } } - return msg, nil -} - -func (pm *privateMessaging) sendMessageSync(ctx context.Context, msg *fftypes.Message) (*fftypes.Message, error) { - // Pass it to the sync-async handler to wait for the confirmation to come back in. - // NOTE: Our caller makes sure we are not in a RunAsGroup (which would be bad) - return pm.syncasync.SendConfirm(ctx, msg.Header.Namespace, msg.Header.ID, func() error { - _, err := pm.resolveAndSend(ctx, nil, msg, false) - return err - }) + return nil } -func (pm *privateMessaging) sendUnpinnedMessage(ctx context.Context, message *fftypes.Message) (err error) { - +func (s *messageSender) sendUnpinned(ctx context.Context) (err error) { // Retrieve the group - group, nodes, err := pm.groupManager.getGroupNodes(ctx, message.Header.Group) + group, nodes, err := s.mgr.groupManager.getGroupNodes(ctx, s.msg.Header.Group) if err != nil { return err } - data, _, err := pm.data.GetMessageData(ctx, message, true) + data, _, err := s.mgr.data.GetMessageData(ctx, &s.msg.Message, true) if err != nil { return err } payload, err := json.Marshal(&fftypes.TransportWrapper{ Type: fftypes.TransportPayloadTypeMessage, - Message: message, + Message: &s.msg.Message, Data: data, Group: group, }) @@ -179,5 +193,5 @@ func (pm *privateMessaging) sendUnpinnedMessage(ctx context.Context, message *ff return i18n.WrapError(ctx, err, i18n.MsgSerializationFailed) } - return pm.sendData(ctx, "message", message.Header.ID, message.Header.Group, message.Header.Namespace, nodes, payload, nil, data) + return s.mgr.sendData(ctx, "message", s.msg.Header.ID, s.msg.Header.Group, s.namespace, nodes, payload, nil, data) } diff --git a/internal/privatemessaging/message_test.go b/internal/privatemessaging/message_test.go index 8ea1dde572..dcfc82cf17 100644 --- a/internal/privatemessaging/message_test.go +++ b/internal/privatemessaging/message_test.go @@ -17,7 +17,6 @@ package privatemessaging import ( - "context" "fmt" "testing" @@ -49,11 +48,6 @@ func TestSendConfirmMessageE2EOk(t *testing.T) { }, nil) mdi := pm.database.(*databasemocks.Plugin) - rag := mdi.On("RunAsGroup", pm.ctx, mock.Anything).Return(nil) - rag.RunFn = func(a mock.Arguments) { - err := a[1].(func(context.Context) error)(a[0].(context.Context)) - rag.ReturnArguments = mock.Arguments{err} - } mdi.On("GetOrganizationByName", pm.ctx, "localorg").Return(&fftypes.Organization{ ID: fftypes.NewUUID(), }, nil) @@ -125,11 +119,6 @@ func TestSendUnpinnedMessageE2EOk(t *testing.T) { }, true, nil).Once() mdi := pm.database.(*databasemocks.Plugin) - rag := mdi.On("RunAsGroup", pm.ctx, mock.Anything).Return(nil) - rag.RunFn = func(a mock.Arguments) { - err := a[1].(func(context.Context) error)(a[0].(context.Context)) - rag.ReturnArguments = mock.Arguments{err} - } mdi.On("GetGroupByHash", pm.ctx, groupID).Return(&fftypes.Group{ Hash: groupID, GroupIdentity: fftypes.GroupIdentity{ @@ -185,13 +174,6 @@ func TestSendMessageBadGroup(t *testing.T) { mim := pm.identity.(*identitymanagermocks.Manager) mim.On("ResolveInputIdentity", pm.ctx, mock.Anything).Return(nil) - mdi := pm.database.(*databasemocks.Plugin) - rag := mdi.On("RunAsGroup", pm.ctx, mock.Anything).Return(nil) - rag.RunFn = func(a mock.Arguments) { - err := a[1].(func(context.Context) error)(a[0].(context.Context)) - rag.ReturnArguments = mock.Arguments{err} - } - _, err := pm.SendMessage(pm.ctx, "ns1", &fftypes.MessageInOut{ InlineData: fftypes.InlineData{ {Value: fftypes.Byteable(`{"some": "data"}`)}, @@ -201,7 +183,6 @@ func TestSendMessageBadGroup(t *testing.T) { assert.Regexp(t, "FF10219", err) mim.AssertExpectations(t) - mdi.AssertExpectations(t) } @@ -235,33 +216,47 @@ func TestSendMessageFail(t *testing.T) { defer cancel() mim := pm.identity.(*identitymanagermocks.Manager) + mim.On("ResolveLocalOrgDID", pm.ctx).Return("localorg", nil) + mim.On("GetLocalOrganization", pm.ctx).Return(&fftypes.Organization{Identity: "localorg"}, nil) mim.On("ResolveInputIdentity", pm.ctx, mock.Anything).Run(func(args mock.Arguments) { identity := args[1].(*fftypes.Identity) identity.Author = "localorg" identity.Key = "localkey" }).Return(nil) + mdi := pm.database.(*databasemocks.Plugin) + mdi.On("GetOrganizationByName", pm.ctx, "localorg").Return(&fftypes.Organization{ + ID: fftypes.NewUUID(), + }, nil) + mdi.On("GetNodes", pm.ctx, mock.Anything).Return([]*fftypes.Node{ + {ID: fftypes.NewUUID(), Name: "node1", Owner: "localorg"}, + }, nil, nil) + mdi.On("GetGroups", pm.ctx, mock.Anything).Return([]*fftypes.Group{ + {Hash: fftypes.NewRandB32()}, + }, nil, nil) + mdi.On("InsertMessageLocal", pm.ctx, mock.Anything).Return(fmt.Errorf("pop")) + dataID := fftypes.NewUUID() mdm := pm.data.(*datamocks.Manager) mdm.On("ResolveInlineDataPrivate", pm.ctx, "ns1", mock.Anything).Return(fftypes.DataRefs{ {ID: dataID, Hash: fftypes.NewRandB32()}, }, nil) - mdi := pm.database.(*databasemocks.Plugin) - mdi.On("RunAsGroup", pm.ctx, mock.Anything).Return(fmt.Errorf("pop")) _, err := pm.SendMessage(pm.ctx, "ns1", &fftypes.MessageInOut{ InlineData: fftypes.InlineData{ {Value: fftypes.Byteable(`{"some": "data"}`)}, }, Group: &fftypes.InputGroup{ Members: []fftypes.MemberInput{ - {Identity: "org1"}, + {Identity: "localorg"}, }, }, }, false) assert.EqualError(t, err, "pop") mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mdm.AssertExpectations(t) } @@ -273,6 +268,11 @@ func TestResolveAndSendBadInlineData(t *testing.T) { mim := pm.identity.(*identitymanagermocks.Manager) mim.On("ResolveLocalOrgDID", pm.ctx).Return("localorg", nil) mim.On("GetLocalOrganization", pm.ctx).Return(&fftypes.Organization{Identity: "localorg"}, nil) + mim.On("ResolveInputIdentity", pm.ctx, mock.Anything).Run(func(args mock.Arguments) { + identity := args[1].(*fftypes.Identity) + identity.Author = "localorg" + identity.Key = "localkey" + }).Return(nil) mdi := pm.database.(*databasemocks.Plugin) @@ -289,16 +289,26 @@ func TestResolveAndSendBadInlineData(t *testing.T) { mdm := pm.data.(*datamocks.Manager) mdm.On("ResolveInlineDataPrivate", pm.ctx, "ns1", mock.Anything).Return(nil, fmt.Errorf("pop")) - err := pm.resolveMessage(pm.ctx, &fftypes.MessageInOut{ - Message: fftypes.Message{Header: fftypes.MessageHeader{Namespace: "ns1"}}, - Group: &fftypes.InputGroup{ - Members: []fftypes.MemberInput{ - {Identity: "localorg"}, + message := &messageSender{ + mgr: pm, + namespace: "ns1", + msg: &fftypes.MessageInOut{ + Message: fftypes.Message{Header: fftypes.MessageHeader{Namespace: "ns1"}}, + Group: &fftypes.InputGroup{ + Members: []fftypes.MemberInput{ + {Identity: "localorg"}, + }, }, }, - }) + } + + err := message.resolveMessage(pm.ctx) assert.Regexp(t, "pop", err) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mdm.AssertExpectations(t) + } func TestSealFail(t *testing.T) { @@ -307,13 +317,20 @@ func TestSealFail(t *testing.T) { defer cancel() id1 := fftypes.NewUUID() - _, err := pm.sendMessageCommon(pm.ctx, &fftypes.Message{ - Header: fftypes.MessageHeader{Namespace: "ns1"}, - Data: fftypes.DataRefs{ - {ID: id1}, - {ID: id1}, // duplicate + message := &messageSender{ + mgr: pm, + msg: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{Namespace: "ns1"}, + Data: fftypes.DataRefs{ + {ID: id1}, + {ID: id1}, // duplicate + }, + }, }, - }, false) + } + + err := message.sendInternal(pm.ctx, false) assert.Regexp(t, "FF10144", err) } @@ -354,15 +371,22 @@ func TestSendUnpinnedMessageMarshalFail(t *testing.T) { ID: nodeID2, Name: "node2", Owner: "org1", DX: fftypes.DXInfo{Peer: "peer2-remote"}, }, nil).Once() - err := pm.sendUnpinnedMessage(pm.ctx, &fftypes.Message{ - Header: fftypes.MessageHeader{ - Identity: fftypes.Identity{ - Author: "localorg", + message := &messageSender{ + mgr: pm, + msg: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Identity: fftypes.Identity{ + Author: "localorg", + }, + TxType: fftypes.TransactionTypeNone, + Group: groupID, + }, }, - TxType: fftypes.TransactionTypeNone, - Group: groupID, }, - }) + } + + err := message.sendUnpinned(pm.ctx) assert.Regexp(t, "FF10137", err) mdm.AssertExpectations(t) @@ -398,15 +422,22 @@ func TestSendUnpinnedMessageGetDataFail(t *testing.T) { ID: nodeID2, Name: "node2", Owner: "org1", DX: fftypes.DXInfo{Peer: "peer2-remote"}, }, nil).Once() - err := pm.sendUnpinnedMessage(pm.ctx, &fftypes.Message{ - Header: fftypes.MessageHeader{ - Identity: fftypes.Identity{ - Author: "localorg", + message := &messageSender{ + mgr: pm, + msg: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Identity: fftypes.Identity{ + Author: "localorg", + }, + TxType: fftypes.TransactionTypeNone, + Group: groupID, + }, }, - TxType: fftypes.TransactionTypeNone, - Group: groupID, }, - }) + } + + err := message.sendUnpinned(pm.ctx) assert.Regexp(t, "pop", err) mdm.AssertExpectations(t) @@ -423,15 +454,22 @@ func TestSendUnpinnedMessageGroupLookupFail(t *testing.T) { mdi := pm.database.(*databasemocks.Plugin) mdi.On("GetGroupByHash", pm.ctx, groupID).Return(nil, fmt.Errorf("pop")).Once() - err := pm.sendUnpinnedMessage(pm.ctx, &fftypes.Message{ - Header: fftypes.MessageHeader{ - Identity: fftypes.Identity{ - Author: "org1", + message := &messageSender{ + mgr: pm, + msg: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Header: fftypes.MessageHeader{ + Identity: fftypes.Identity{ + Author: "org1", + }, + TxType: fftypes.TransactionTypeNone, + Group: groupID, + }, }, - TxType: fftypes.TransactionTypeNone, - Group: groupID, }, - }) + } + + err := message.sendUnpinned(pm.ctx) assert.Regexp(t, "pop", err) mdi.AssertExpectations(t) @@ -457,11 +495,6 @@ func TestSendUnpinnedMessageInsertFail(t *testing.T) { }, nil) mdi := pm.database.(*databasemocks.Plugin) - rag := mdi.On("RunAsGroup", pm.ctx, mock.Anything).Return(nil) - rag.RunFn = func(a mock.Arguments) { - err := a[1].(func(context.Context) error)(a[0].(context.Context)) - rag.ReturnArguments = mock.Arguments{err} - } mdi.On("InsertMessageLocal", pm.ctx, mock.Anything).Return(fmt.Errorf("pop")).Once() _, err := pm.SendMessage(pm.ctx, "ns1", &fftypes.MessageInOut{ @@ -504,11 +537,6 @@ func TestSendUnpinnedMessageResolveGroupFail(t *testing.T) { }, nil) mdi := pm.database.(*databasemocks.Plugin) - rag := mdi.On("RunAsGroup", pm.ctx, mock.Anything).Return(nil) - rag.RunFn = func(a mock.Arguments) { - err := a[1].(func(context.Context) error)(a[0].(context.Context)) - rag.ReturnArguments = mock.Arguments{err} - } mdi.On("GetGroupByHash", pm.ctx, groupID).Return(nil, fmt.Errorf("pop")).Once() mdi.On("InsertMessageLocal", pm.ctx, mock.Anything).Return(nil).Once() @@ -561,11 +589,6 @@ func TestSendUnpinnedMessageEventFail(t *testing.T) { }, true, nil).Once() mdi := pm.database.(*databasemocks.Plugin) - rag := mdi.On("RunAsGroup", pm.ctx, mock.Anything).Return(nil) - rag.RunFn = func(a mock.Arguments) { - err := a[1].(func(context.Context) error)(a[0].(context.Context)) - rag.ReturnArguments = mock.Arguments{err} - } mdi.On("GetGroupByHash", pm.ctx, groupID).Return(&fftypes.Group{ Hash: groupID, GroupIdentity: fftypes.GroupIdentity{ @@ -662,11 +685,6 @@ func TestRequestReplySuccess(t *testing.T) { }, nil) mdi := pm.database.(*databasemocks.Plugin) - rag := mdi.On("RunAsGroup", pm.ctx, mock.Anything) - rag.RunFn = func(a mock.Arguments) { - fn := a[1].(func(context.Context) error) - rag.ReturnArguments = mock.Arguments{fn(a[0].(context.Context))} - } mdi.On("InsertMessageLocal", pm.ctx, mock.Anything).Return(nil).Once() _, err := pm.RequestReply(pm.ctx, "ns1", &fftypes.MessageInOut{ @@ -682,28 +700,3 @@ func TestRequestReplySuccess(t *testing.T) { }) assert.NoError(t, err) } - -func TestRequestReplyBadIdentity(t *testing.T) { - - pm, cancel := newTestPrivateMessaging(t) - defer cancel() - - mim := pm.identity.(*identitymanagermocks.Manager) - mim.On("ResolveInputIdentity", pm.ctx, mock.Anything).Return(fmt.Errorf("pop")) - - _, err := pm.RequestReply(pm.ctx, "ns1", &fftypes.MessageInOut{ - Message: fftypes.Message{ - Header: fftypes.MessageHeader{ - Tag: "mytag", - Group: fftypes.NewRandB32(), - Identity: fftypes.Identity{ - Author: "org1", - }, - }, - }, - }) - assert.Regexp(t, "FF10206.*pop", err) - - mim.AssertExpectations(t) - -} diff --git a/internal/privatemessaging/privatemessaging.go b/internal/privatemessaging/privatemessaging.go index 304ca0c121..921c0bafcb 100644 --- a/internal/privatemessaging/privatemessaging.go +++ b/internal/privatemessaging/privatemessaging.go @@ -36,10 +36,16 @@ import ( "github.com/karlseguin/ccache" ) +type PrivateMessage interface { + Send(ctx context.Context) error + SendAndWait(ctx context.Context) error +} + type Manager interface { GroupManager Start() error + NewMessage(ns string, msg *fftypes.MessageInOut) PrivateMessage SendMessage(ctx context.Context, ns string, in *fftypes.MessageInOut, waitConfirm bool) (out *fftypes.Message, err error) RequestReply(ctx context.Context, ns string, request *fftypes.MessageInOut) (reply *fftypes.MessageInOut, err error) } diff --git a/internal/privatemessaging/privatemessaging_test.go b/internal/privatemessaging/privatemessaging_test.go index 695cc1ed8f..c1412018d2 100644 --- a/internal/privatemessaging/privatemessaging_test.go +++ b/internal/privatemessaging/privatemessaging_test.go @@ -56,6 +56,13 @@ func newTestPrivateMessaging(t *testing.T) (*privateMessaging, func()) { fftypes.MessageTypeTransferPrivate, }, mock.Anything, mock.Anything).Return() + rag := mdi.On("RunAsGroup", mock.Anything, mock.Anything).Maybe() + rag.RunFn = func(a mock.Arguments) { + rag.ReturnArguments = mock.Arguments{ + a[1].(func(context.Context) error)(a[0].(context.Context)), + } + } + ctx, cancel := context.WithCancel(context.Background()) pm, err := NewPrivateMessaging(ctx, mdi, mim, mdx, mbi, mba, mdm, msa, mbp) assert.NoError(t, err) @@ -88,13 +95,6 @@ func TestDispatchBatchWithBlobs(t *testing.T) { mdx := pm.exchange.(*dataexchangemocks.Plugin) mim := pm.identity.(*identitymanagermocks.Manager) - rag := mdi.On("RunAsGroup", pm.ctx, mock.Anything).Maybe() - rag.RunFn = func(a mock.Arguments) { - rag.ReturnArguments = mock.Arguments{ - a[1].(func(context.Context) error)(a[0].(context.Context)), - } - } - mim.On("ResolveInputIdentity", pm.ctx, mock.Anything).Run(func(args mock.Arguments) { identity := args[1].(*fftypes.Identity) assert.Equal(t, "org1", identity.Author) diff --git a/internal/privatemessaging/recipients.go b/internal/privatemessaging/recipients.go index 604818aad4..6092cb2dee 100644 --- a/internal/privatemessaging/recipients.go +++ b/internal/privatemessaging/recipients.go @@ -26,7 +26,7 @@ import ( "github.com/hyperledger/firefly/pkg/fftypes" ) -func (pm *privateMessaging) resolveReceipientList(ctx context.Context, in *fftypes.MessageInOut) error { +func (pm *privateMessaging) resolveRecipientList(ctx context.Context, in *fftypes.MessageInOut) error { if in.Header.Group != nil { log.L(ctx).Debugf("Group '%s' specified for message", in.Header.Group) return nil // validity of existing group checked later diff --git a/internal/privatemessaging/recipients_test.go b/internal/privatemessaging/recipients_test.go index 93a619370b..ed0beb9c0a 100644 --- a/internal/privatemessaging/recipients_test.go +++ b/internal/privatemessaging/recipients_test.go @@ -91,7 +91,7 @@ func TestResolveMemberListNewGroupE2E(t *testing.T) { assert.Equal(t, *dataID, *msg.Data[0].ID) } - err := pm.resolveReceipientList(pm.ctx, &fftypes.MessageInOut{ + err := pm.resolveRecipientList(pm.ctx, &fftypes.MessageInOut{ Message: fftypes.Message{ Header: fftypes.MessageHeader{ Namespace: "ns1", @@ -126,7 +126,7 @@ func TestResolveMemberListExistingGroup(t *testing.T) { mim.On("ResolveLocalOrgDID", pm.ctx).Return("localorg", nil) mim.On("GetLocalOrganization", pm.ctx).Return(&fftypes.Organization{Identity: "localorg"}, nil) - err := pm.resolveReceipientList(pm.ctx, &fftypes.MessageInOut{ + err := pm.resolveRecipientList(pm.ctx, &fftypes.MessageInOut{ Message: fftypes.Message{ Header: fftypes.MessageHeader{ Identity: fftypes.Identity{ @@ -158,7 +158,7 @@ func TestResolveMemberListGetGroupsFail(t *testing.T) { mim.On("ResolveLocalOrgDID", pm.ctx).Return("localorg", nil) mim.On("GetLocalOrganization", pm.ctx).Return(&fftypes.Organization{Identity: "localorg"}, nil) - err := pm.resolveReceipientList(pm.ctx, &fftypes.MessageInOut{ + err := pm.resolveRecipientList(pm.ctx, &fftypes.MessageInOut{ Message: fftypes.Message{ Header: fftypes.MessageHeader{ Identity: fftypes.Identity{ @@ -185,7 +185,7 @@ func TestResolveMemberListLocalOrgUnregistered(t *testing.T) { mim := pm.identity.(*identitymanagermocks.Manager) mim.On("ResolveLocalOrgDID", pm.ctx).Return("", fmt.Errorf("pop")) - err := pm.resolveReceipientList(pm.ctx, &fftypes.MessageInOut{ + err := pm.resolveRecipientList(pm.ctx, &fftypes.MessageInOut{ Message: fftypes.Message{ Header: fftypes.MessageHeader{ Identity: fftypes.Identity{ @@ -212,7 +212,7 @@ func TestResolveMemberListLocalOrgLookupFailed(t *testing.T) { mim.On("ResolveLocalOrgDID", pm.ctx).Return("", nil) mim.On("GetLocalOrganization", pm.ctx).Return(nil, fmt.Errorf("pop")) - err := pm.resolveReceipientList(pm.ctx, &fftypes.MessageInOut{ + err := pm.resolveRecipientList(pm.ctx, &fftypes.MessageInOut{ Message: fftypes.Message{ Header: fftypes.MessageHeader{ Identity: fftypes.Identity{ @@ -243,7 +243,7 @@ func TestResolveMemberListMissingLocalMemberLookupFailed(t *testing.T) { mim.On("ResolveLocalOrgDID", pm.ctx).Return("localorg", nil) mim.On("GetLocalOrganization", pm.ctx).Return(&fftypes.Organization{Identity: "localorg"}, nil) - err := pm.resolveReceipientList(pm.ctx, &fftypes.MessageInOut{ + err := pm.resolveRecipientList(pm.ctx, &fftypes.MessageInOut{ Message: fftypes.Message{ Header: fftypes.MessageHeader{ Identity: fftypes.Identity{ @@ -274,7 +274,7 @@ func TestResolveMemberListNodeNotFound(t *testing.T) { mim.On("ResolveLocalOrgDID", pm.ctx).Return("localorg", nil) mim.On("GetLocalOrganization", pm.ctx).Return(&fftypes.Organization{Identity: "localorg"}, nil) - err := pm.resolveReceipientList(pm.ctx, &fftypes.MessageInOut{ + err := pm.resolveRecipientList(pm.ctx, &fftypes.MessageInOut{ Message: fftypes.Message{ Header: fftypes.MessageHeader{ Identity: fftypes.Identity{ @@ -305,7 +305,7 @@ func TestResolveMemberOrgNameNotFound(t *testing.T) { mim.On("ResolveLocalOrgDID", pm.ctx).Return("localorg", nil) mim.On("GetLocalOrganization", pm.ctx).Return(&fftypes.Organization{Identity: "localorg"}, nil) - err := pm.resolveReceipientList(pm.ctx, &fftypes.MessageInOut{ + err := pm.resolveRecipientList(pm.ctx, &fftypes.MessageInOut{ Message: fftypes.Message{ Header: fftypes.MessageHeader{ Identity: fftypes.Identity{ @@ -340,7 +340,7 @@ func TestResolveMemberNodeOwnedParentOrg(t *testing.T) { mim.On("ResolveLocalOrgDID", pm.ctx).Return("localorg", nil) mim.On("GetLocalOrganization", pm.ctx).Return(&fftypes.Organization{Identity: "localorg"}, nil) - err := pm.resolveReceipientList(pm.ctx, &fftypes.MessageInOut{ + err := pm.resolveRecipientList(pm.ctx, &fftypes.MessageInOut{ Message: fftypes.Message{ Header: fftypes.MessageHeader{ Identity: fftypes.Identity{ @@ -417,7 +417,7 @@ func TestResolveReceipientListExisting(t *testing.T) { pm, cancel := newTestPrivateMessaging(t) defer cancel() - err := pm.resolveReceipientList(pm.ctx, &fftypes.MessageInOut{ + err := pm.resolveRecipientList(pm.ctx, &fftypes.MessageInOut{ Message: fftypes.Message{ Header: fftypes.MessageHeader{ Group: fftypes.NewRandB32(), @@ -431,7 +431,7 @@ func TestResolveReceipientListEmptyList(t *testing.T) { pm, cancel := newTestPrivateMessaging(t) defer cancel() - err := pm.resolveReceipientList(pm.ctx, &fftypes.MessageInOut{}) + err := pm.resolveRecipientList(pm.ctx, &fftypes.MessageInOut{}) assert.Regexp(t, "FF10219", err) } diff --git a/internal/syshandlers/reply_sender.go b/internal/syshandlers/reply_sender.go index 655a6abeaf..040796c662 100644 --- a/internal/syshandlers/reply_sender.go +++ b/internal/syshandlers/reply_sender.go @@ -25,15 +25,14 @@ import ( func (sh *systemHandlers) SendReply(ctx context.Context, event *fftypes.Event, reply *fftypes.MessageInOut) { var err error - var msg *fftypes.Message if reply.Header.Group != nil { - msg, err = sh.messaging.SendMessage(ctx, event.Namespace, reply, false) + err = sh.messaging.NewMessage(event.Namespace, reply).Send(ctx) } else { - msg, err = sh.broadcast.BroadcastMessage(ctx, event.Namespace, reply, false) + err = sh.broadcast.NewBroadcast(event.Namespace, reply).Send(ctx) } if err != nil { log.L(ctx).Errorf("Failed to send reply: %s", err) } else { - log.L(ctx).Infof("Sent reply %s:%s (%s) cid=%s to event '%s'", msg.Header.Namespace, msg.Header.ID, msg.Header.Type, msg.Header.CID, event.ID) + log.L(ctx).Infof("Sent reply %s:%s (%s) cid=%s to event '%s'", reply.Header.Namespace, reply.Header.ID, reply.Header.Type, reply.Header.CID, event.ID) } } diff --git a/internal/syshandlers/reply_sender_test.go b/internal/syshandlers/reply_sender_test.go index b2f23a7ee9..2a9f02df54 100644 --- a/internal/syshandlers/reply_sender_test.go +++ b/internal/syshandlers/reply_sender_test.go @@ -29,19 +29,27 @@ import ( func TestSendReplyBroadcastFail(t *testing.T) { sh := newTestSystemHandlers(t) + mbs := &broadcastmocks.Broadcast{} mbm := sh.broadcast.(*broadcastmocks.Manager) - mbm.On("BroadcastMessage", mock.Anything, "ns1", mock.Anything, false).Return(nil, fmt.Errorf("pop")) + mbm.On("NewBroadcast", "ns1", mock.Anything).Return(mbs) + mbs.On("Send", context.Background()).Return(fmt.Errorf("pop")) + sh.SendReply(context.Background(), &fftypes.Event{ ID: fftypes.NewUUID(), Namespace: "ns1", }, &fftypes.MessageInOut{}) + mbm.AssertExpectations(t) + mbs.AssertExpectations(t) } -func TestSendReplyPrivatetFail(t *testing.T) { +func TestSendReplyPrivateFail(t *testing.T) { sh := newTestSystemHandlers(t) + mps := &privatemessagingmocks.PrivateMessage{} mpm := sh.messaging.(*privatemessagingmocks.Manager) - mpm.On("SendMessage", mock.Anything, "ns1", mock.Anything, false).Return(nil, fmt.Errorf("pop")) + mpm.On("NewMessage", "ns1", mock.Anything).Return(mps) + mps.On("Send", context.Background()).Return(fmt.Errorf("pop")) + sh.SendReply(context.Background(), &fftypes.Event{ ID: fftypes.NewUUID(), Namespace: "ns1", @@ -52,10 +60,12 @@ func TestSendReplyPrivatetFail(t *testing.T) { }, }, }) + mpm.AssertExpectations(t) + mps.AssertExpectations(t) } -func TestSendReplyPrivatetOk(t *testing.T) { +func TestSendReplyPrivateOk(t *testing.T) { sh := newTestSystemHandlers(t) msg := &fftypes.Message{ @@ -64,13 +74,18 @@ func TestSendReplyPrivatetOk(t *testing.T) { }, } + mps := &privatemessagingmocks.PrivateMessage{} mpm := sh.messaging.(*privatemessagingmocks.Manager) - mpm.On("SendMessage", mock.Anything, "ns1", mock.Anything, false).Return(msg, nil) + mpm.On("NewMessage", "ns1", mock.Anything).Return(mps) + mps.On("Send", context.Background()).Return(nil) + sh.SendReply(context.Background(), &fftypes.Event{ ID: fftypes.NewUUID(), Namespace: "ns1", }, &fftypes.MessageInOut{ Message: *msg, }) + mpm.AssertExpectations(t) + mps.AssertExpectations(t) } diff --git a/mocks/broadcastmocks/broadcast.go b/mocks/broadcastmocks/broadcast.go new file mode 100644 index 0000000000..d1c565ba1a --- /dev/null +++ b/mocks/broadcastmocks/broadcast.go @@ -0,0 +1,42 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package broadcastmocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// Broadcast is an autogenerated mock type for the Broadcast type +type Broadcast struct { + mock.Mock +} + +// Send provides a mock function with given fields: ctx +func (_m *Broadcast) Send(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SendAndWait provides a mock function with given fields: ctx +func (_m *Broadcast) SendAndWait(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/mocks/broadcastmocks/manager.go b/mocks/broadcastmocks/manager.go index b3d1da3ddf..690fa762ed 100644 --- a/mocks/broadcastmocks/manager.go +++ b/mocks/broadcastmocks/manager.go @@ -5,7 +5,10 @@ package broadcastmocks import ( context "context" + broadcast "github.com/hyperledger/firefly/internal/broadcast" + fftypes "github.com/hyperledger/firefly/pkg/fftypes" + mock "github.com/stretchr/testify/mock" ) @@ -175,6 +178,22 @@ func (_m *Manager) BroadcastTokenPool(ctx context.Context, ns string, pool *ffty return r0, r1 } +// NewBroadcast provides a mock function with given fields: ns, in +func (_m *Manager) NewBroadcast(ns string, in *fftypes.MessageInOut) broadcast.Broadcast { + ret := _m.Called(ns, in) + + var r0 broadcast.Broadcast + if rf, ok := ret.Get(0).(func(string, *fftypes.MessageInOut) broadcast.Broadcast); ok { + r0 = rf(ns, in) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(broadcast.Broadcast) + } + } + + return r0 +} + // Start provides a mock function with given fields: func (_m *Manager) Start() error { ret := _m.Called() diff --git a/mocks/privatemessagingmocks/manager.go b/mocks/privatemessagingmocks/manager.go index ec78181ae2..f214fe80a7 100644 --- a/mocks/privatemessagingmocks/manager.go +++ b/mocks/privatemessagingmocks/manager.go @@ -9,6 +9,8 @@ import ( fftypes "github.com/hyperledger/firefly/pkg/fftypes" mock "github.com/stretchr/testify/mock" + + privatemessaging "github.com/hyperledger/firefly/internal/privatemessaging" ) // Manager is an autogenerated mock type for the Manager type @@ -92,6 +94,22 @@ func (_m *Manager) GetGroups(ctx context.Context, filter database.AndFilter) ([] return r0, r1, r2 } +// NewMessage provides a mock function with given fields: ns, msg +func (_m *Manager) NewMessage(ns string, msg *fftypes.MessageInOut) privatemessaging.PrivateMessage { + ret := _m.Called(ns, msg) + + var r0 privatemessaging.PrivateMessage + if rf, ok := ret.Get(0).(func(string, *fftypes.MessageInOut) privatemessaging.PrivateMessage); ok { + r0 = rf(ns, msg) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(privatemessaging.PrivateMessage) + } + } + + return r0 +} + // RequestReply provides a mock function with given fields: ctx, ns, request func (_m *Manager) RequestReply(ctx context.Context, ns string, request *fftypes.MessageInOut) (*fftypes.MessageInOut, error) { ret := _m.Called(ctx, ns, request) diff --git a/mocks/privatemessagingmocks/private_message.go b/mocks/privatemessagingmocks/private_message.go new file mode 100644 index 0000000000..9ee82bfb88 --- /dev/null +++ b/mocks/privatemessagingmocks/private_message.go @@ -0,0 +1,42 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package privatemessagingmocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// PrivateMessage is an autogenerated mock type for the PrivateMessage type +type PrivateMessage struct { + mock.Mock +} + +// Send provides a mock function with given fields: ctx +func (_m *PrivateMessage) Send(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SendAndWait provides a mock function with given fields: ctx +func (_m *PrivateMessage) SendAndWait(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} From 41cb5c0c39ae442236e0e9a371f3801d0f2aaa4d Mon Sep 17 00:00:00 2001 From: Andrew Richardson Date: Thu, 14 Oct 2021 15:27:57 -0400 Subject: [PATCH 4/8] Add messaging callback after message is sealed Signed-off-by: Andrew Richardson --- internal/broadcast/manager.go | 1 + internal/broadcast/message.go | 17 +++++-- internal/broadcast/message_test.go | 26 ++++++++++ internal/privatemessaging/message.go | 17 +++++-- internal/privatemessaging/message_test.go | 49 ++++++++++++++----- internal/privatemessaging/privatemessaging.go | 1 + mocks/broadcastmocks/broadcast.go | 18 +++++++ .../privatemessagingmocks/private_message.go | 17 +++++++ 8 files changed, 126 insertions(+), 20 deletions(-) diff --git a/internal/broadcast/manager.go b/internal/broadcast/manager.go index 55df38a73d..6b137490e6 100644 --- a/internal/broadcast/manager.go +++ b/internal/broadcast/manager.go @@ -39,6 +39,7 @@ import ( type Broadcast interface { Send(ctx context.Context) error SendAndWait(ctx context.Context) error + AfterSeal(cb func(ctx context.Context)) Broadcast } type Manager interface { diff --git a/internal/broadcast/message.go b/internal/broadcast/message.go index 57a40d9518..0d097d1801 100644 --- a/internal/broadcast/message.go +++ b/internal/broadcast/message.go @@ -45,10 +45,11 @@ func (bm *broadcastManager) BroadcastMessage(ctx context.Context, ns string, in } type broadcastSender struct { - mgr *broadcastManager - namespace string - msg *fftypes.MessageInOut - resolved bool + mgr *broadcastManager + namespace string + msg *fftypes.MessageInOut + resolved bool + sealCallback func(ctx context.Context) } func (s *broadcastSender) Send(ctx context.Context) error { @@ -59,6 +60,11 @@ func (s *broadcastSender) SendAndWait(ctx context.Context) error { return s.resolveAndSend(ctx, true) } +func (s *broadcastSender) AfterSeal(cb func(ctx context.Context)) Broadcast { + s.sealCallback = cb + return s +} + func (s *broadcastSender) setDefaults() { s.msg.Header.ID = fftypes.NewUUID() s.msg.Header.Namespace = s.namespace @@ -133,6 +139,9 @@ func (s *broadcastSender) sendInternal(ctx context.Context, waitConfirm bool) (e if err := s.msg.Seal(ctx); err != nil { return err } + if s.sealCallback != nil { + s.sealCallback(ctx) + } // Store the message - this asynchronously triggers the next step in process return s.mgr.database.InsertMessageLocal(ctx, &s.msg.Message) diff --git a/internal/broadcast/message_test.go b/internal/broadcast/message_test.go index 743c4ef34d..45bccce985 100644 --- a/internal/broadcast/message_test.go +++ b/internal/broadcast/message_test.go @@ -408,3 +408,29 @@ func TestPublishBlobsSendMessageFail(t *testing.T) { mdx.AssertExpectations(t) mim.AssertExpectations(t) } + +func TestSealCallback(t *testing.T) { + bm, cancel := newTestBroadcast(t) + defer cancel() + + id1 := fftypes.NewUUID() + message := bm.NewBroadcast("ns1", &fftypes.MessageInOut{ + Message: fftypes.Message{ + Data: fftypes.DataRefs{ + {ID: id1, Hash: fftypes.NewRandB32()}, + }, + }, + }) + + called := false + message.AfterSeal(func(ctx context.Context) { + called = true + }) + + mdi := bm.database.(*databasemocks.Plugin) + mdi.On("InsertMessageLocal", bm.ctx, mock.Anything).Return(nil) + + err := message.(*broadcastSender).sendInternal(bm.ctx, false) + assert.NoError(t, err) + assert.True(t, called) +} diff --git a/internal/privatemessaging/message.go b/internal/privatemessaging/message.go index 5d96e988a5..11f91c1c53 100644 --- a/internal/privatemessaging/message.go +++ b/internal/privatemessaging/message.go @@ -58,10 +58,11 @@ func (pm *privateMessaging) RequestReply(ctx context.Context, ns string, in *fft } type messageSender struct { - mgr *privateMessaging - namespace string - msg *fftypes.MessageInOut - resolved bool + mgr *privateMessaging + namespace string + msg *fftypes.MessageInOut + resolved bool + sealCallback func(ctx context.Context) } func (s *messageSender) Send(ctx context.Context) error { @@ -72,6 +73,11 @@ func (s *messageSender) SendAndWait(ctx context.Context) error { return s.resolveAndSend(ctx, true) } +func (s *messageSender) AfterSeal(cb func(ctx context.Context)) PrivateMessage { + s.sealCallback = cb + return s +} + func (s *messageSender) setDefaults() { s.msg.Header.ID = fftypes.NewUUID() s.msg.Header.Namespace = s.namespace @@ -144,6 +150,9 @@ func (s *messageSender) sendInternal(ctx context.Context, waitConfirm bool) erro if err := s.msg.Seal(ctx); err != nil { return err } + if s.sealCallback != nil { + s.sealCallback(ctx) + } if immediateConfirm { s.msg.Confirmed = fftypes.Now() diff --git a/internal/privatemessaging/message_test.go b/internal/privatemessaging/message_test.go index dcfc82cf17..df93065197 100644 --- a/internal/privatemessaging/message_test.go +++ b/internal/privatemessaging/message_test.go @@ -17,6 +17,7 @@ package privatemessaging import ( + "context" "fmt" "testing" @@ -317,21 +318,45 @@ func TestSealFail(t *testing.T) { defer cancel() id1 := fftypes.NewUUID() - message := &messageSender{ - mgr: pm, - msg: &fftypes.MessageInOut{ - Message: fftypes.Message{ - Header: fftypes.MessageHeader{Namespace: "ns1"}, - Data: fftypes.DataRefs{ - {ID: id1}, - {ID: id1}, // duplicate - }, + message := pm.NewMessage("ns1", &fftypes.MessageInOut{ + Message: fftypes.Message{ + Data: fftypes.DataRefs{ + {ID: id1, Hash: fftypes.NewRandB32()}, + {ID: id1, Hash: fftypes.NewRandB32()}, // duplicate ID }, }, - } + }) + + err := message.(*messageSender).sendInternal(pm.ctx, false) + assert.Regexp(t, "FF10145", err) + +} + +func TestSealCallback(t *testing.T) { + + pm, cancel := newTestPrivateMessaging(t) + defer cancel() - err := message.sendInternal(pm.ctx, false) - assert.Regexp(t, "FF10144", err) + id1 := fftypes.NewUUID() + message := pm.NewMessage("ns1", &fftypes.MessageInOut{ + Message: fftypes.Message{ + Data: fftypes.DataRefs{ + {ID: id1, Hash: fftypes.NewRandB32()}, + }, + }, + }) + + called := false + message.AfterSeal(func(ctx context.Context) { + called = true + }) + + mdi := pm.database.(*databasemocks.Plugin) + mdi.On("InsertMessageLocal", pm.ctx, mock.Anything).Return(nil) + + err := message.(*messageSender).sendInternal(pm.ctx, false) + assert.NoError(t, err) + assert.True(t, called) } diff --git a/internal/privatemessaging/privatemessaging.go b/internal/privatemessaging/privatemessaging.go index 921c0bafcb..97dc2e2450 100644 --- a/internal/privatemessaging/privatemessaging.go +++ b/internal/privatemessaging/privatemessaging.go @@ -39,6 +39,7 @@ import ( type PrivateMessage interface { Send(ctx context.Context) error SendAndWait(ctx context.Context) error + AfterSeal(cb func(ctx context.Context)) PrivateMessage } type Manager interface { diff --git a/mocks/broadcastmocks/broadcast.go b/mocks/broadcastmocks/broadcast.go index d1c565ba1a..be9e961977 100644 --- a/mocks/broadcastmocks/broadcast.go +++ b/mocks/broadcastmocks/broadcast.go @@ -5,6 +5,8 @@ package broadcastmocks import ( context "context" + broadcast "github.com/hyperledger/firefly/internal/broadcast" + mock "github.com/stretchr/testify/mock" ) @@ -13,6 +15,22 @@ type Broadcast struct { mock.Mock } +// AfterSeal provides a mock function with given fields: cb +func (_m *Broadcast) AfterSeal(cb func(context.Context)) broadcast.Broadcast { + ret := _m.Called(cb) + + var r0 broadcast.Broadcast + if rf, ok := ret.Get(0).(func(func(context.Context)) broadcast.Broadcast); ok { + r0 = rf(cb) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(broadcast.Broadcast) + } + } + + return r0 +} + // Send provides a mock function with given fields: ctx func (_m *Broadcast) Send(ctx context.Context) error { ret := _m.Called(ctx) diff --git a/mocks/privatemessagingmocks/private_message.go b/mocks/privatemessagingmocks/private_message.go index 9ee82bfb88..090e789e41 100644 --- a/mocks/privatemessagingmocks/private_message.go +++ b/mocks/privatemessagingmocks/private_message.go @@ -5,6 +5,7 @@ package privatemessagingmocks import ( context "context" + privatemessaging "github.com/hyperledger/firefly/internal/privatemessaging" mock "github.com/stretchr/testify/mock" ) @@ -13,6 +14,22 @@ type PrivateMessage struct { mock.Mock } +// AfterSeal provides a mock function with given fields: cb +func (_m *PrivateMessage) AfterSeal(cb func(context.Context)) privatemessaging.PrivateMessage { + ret := _m.Called(cb) + + var r0 privatemessaging.PrivateMessage + if rf, ok := ret.Get(0).(func(func(context.Context)) privatemessaging.PrivateMessage); ok { + r0 = rf(cb) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(privatemessaging.PrivateMessage) + } + } + + return r0 +} + // Send provides a mock function with given fields: ctx func (_m *PrivateMessage) Send(ctx context.Context) error { ret := _m.Called(ctx) From d649ddd2dcdda77b50858aa70fe1b3dfca7c8141 Mon Sep 17 00:00:00 2001 From: Andrew Richardson Date: Fri, 15 Oct 2021 11:05:22 -0400 Subject: [PATCH 5/8] Use a common MessageSender interface for both broadcast and private Signed-off-by: Andrew Richardson --- Makefile | 3 +- internal/broadcast/manager.go | 9 +-- internal/broadcast/message.go | 11 ++-- internal/broadcast/message_test.go | 30 +++++++++- internal/privatemessaging/message.go | 11 ++-- internal/privatemessaging/message_test.go | 32 +++++++++- internal/privatemessaging/privatemessaging.go | 9 +-- internal/syshandlers/reply_sender_test.go | 25 ++++---- internal/sysmessaging/message_sender.go | 27 +++++++++ mocks/broadcastmocks/manager.go | 13 ++-- mocks/privatemessagingmocks/manager.go | 10 ++-- .../privatemessagingmocks/private_message.go | 59 ------------------- .../message_sender.go} | 21 ++++--- 13 files changed, 140 insertions(+), 120 deletions(-) create mode 100644 internal/sysmessaging/message_sender.go delete mode 100644 mocks/privatemessagingmocks/private_message.go rename mocks/{broadcastmocks/broadcast.go => sysmessagingmocks/message_sender.go} (55%) diff --git a/Makefile b/Makefile index ee83a1cdd8..1494f3bf43 100644 --- a/Makefile +++ b/Makefile @@ -49,13 +49,12 @@ $(eval $(call makemock, pkg/wsclient, WSClient, wsmocks)) $(eval $(call makemock, internal/identity, Manager, identitymanagermocks)) $(eval $(call makemock, internal/batchpin, Submitter, batchpinmocks)) $(eval $(call makemock, internal/sysmessaging, SystemEvents, sysmessagingmocks)) +$(eval $(call makemock, internal/sysmessaging, MessageSender, sysmessagingmocks)) $(eval $(call makemock, internal/syncasync, Bridge, syncasyncmocks)) $(eval $(call makemock, internal/data, Manager, datamocks)) $(eval $(call makemock, internal/batch, Manager, batchmocks)) $(eval $(call makemock, internal/broadcast, Manager, broadcastmocks)) -$(eval $(call makemock, internal/broadcast, Broadcast, broadcastmocks)) $(eval $(call makemock, internal/privatemessaging, Manager, privatemessagingmocks)) -$(eval $(call makemock, internal/privatemessaging, PrivateMessage, privatemessagingmocks)) $(eval $(call makemock, internal/syshandlers, SystemHandlers, syshandlersmocks)) $(eval $(call makemock, internal/events, EventManager, eventmocks)) $(eval $(call makemock, internal/networkmap, Manager, networkmapmocks)) diff --git a/internal/broadcast/manager.go b/internal/broadcast/manager.go index 6b137490e6..13e713a7ed 100644 --- a/internal/broadcast/manager.go +++ b/internal/broadcast/manager.go @@ -29,6 +29,7 @@ import ( "github.com/hyperledger/firefly/internal/identity" "github.com/hyperledger/firefly/internal/log" "github.com/hyperledger/firefly/internal/syncasync" + "github.com/hyperledger/firefly/internal/sysmessaging" "github.com/hyperledger/firefly/pkg/blockchain" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/dataexchange" @@ -36,14 +37,8 @@ import ( "github.com/hyperledger/firefly/pkg/publicstorage" ) -type Broadcast interface { - Send(ctx context.Context) error - SendAndWait(ctx context.Context) error - AfterSeal(cb func(ctx context.Context)) Broadcast -} - type Manager interface { - NewBroadcast(ns string, in *fftypes.MessageInOut) Broadcast + NewBroadcast(ns string, in *fftypes.MessageInOut) sysmessaging.MessageSender BroadcastDatatype(ctx context.Context, ns string, datatype *fftypes.Datatype, waitConfirm bool) (msg *fftypes.Message, err error) BroadcastNamespace(ctx context.Context, ns *fftypes.Namespace, waitConfirm bool) (msg *fftypes.Message, err error) BroadcastMessage(ctx context.Context, ns string, in *fftypes.MessageInOut, waitConfirm bool) (out *fftypes.Message, err error) diff --git a/internal/broadcast/message.go b/internal/broadcast/message.go index 0d097d1801..a38510c570 100644 --- a/internal/broadcast/message.go +++ b/internal/broadcast/message.go @@ -21,10 +21,11 @@ import ( "encoding/json" "github.com/hyperledger/firefly/internal/i18n" + "github.com/hyperledger/firefly/internal/sysmessaging" "github.com/hyperledger/firefly/pkg/fftypes" ) -func (bm *broadcastManager) NewBroadcast(ns string, in *fftypes.MessageInOut) Broadcast { +func (bm *broadcastManager) NewBroadcast(ns string, in *fftypes.MessageInOut) sysmessaging.MessageSender { broadcast := &broadcastSender{ mgr: bm, namespace: ns, @@ -49,7 +50,7 @@ type broadcastSender struct { namespace string msg *fftypes.MessageInOut resolved bool - sealCallback func(ctx context.Context) + sealCallback sysmessaging.SealCallback } func (s *broadcastSender) Send(ctx context.Context) error { @@ -60,7 +61,7 @@ func (s *broadcastSender) SendAndWait(ctx context.Context) error { return s.resolveAndSend(ctx, true) } -func (s *broadcastSender) AfterSeal(cb func(ctx context.Context)) Broadcast { +func (s *broadcastSender) AfterSeal(cb sysmessaging.SealCallback) sysmessaging.MessageSender { s.sealCallback = cb return s } @@ -140,7 +141,9 @@ func (s *broadcastSender) sendInternal(ctx context.Context, waitConfirm bool) (e return err } if s.sealCallback != nil { - s.sealCallback(ctx) + if err := s.sealCallback(ctx); err != nil { + return err + } } // Store the message - this asynchronously triggers the next step in process diff --git a/internal/broadcast/message_test.go b/internal/broadcast/message_test.go index 45bccce985..c89f235d95 100644 --- a/internal/broadcast/message_test.go +++ b/internal/broadcast/message_test.go @@ -423,8 +423,9 @@ func TestSealCallback(t *testing.T) { }) called := false - message.AfterSeal(func(ctx context.Context) { + message.AfterSeal(func(ctx context.Context) error { called = true + return nil }) mdi := bm.database.(*databasemocks.Plugin) @@ -434,3 +435,30 @@ func TestSealCallback(t *testing.T) { assert.NoError(t, err) assert.True(t, called) } + +func TestSealCallbackFail(t *testing.T) { + bm, cancel := newTestBroadcast(t) + defer cancel() + + id1 := fftypes.NewUUID() + message := bm.NewBroadcast("ns1", &fftypes.MessageInOut{ + Message: fftypes.Message{ + Data: fftypes.DataRefs{ + {ID: id1, Hash: fftypes.NewRandB32()}, + }, + }, + }) + + called := false + message.AfterSeal(func(ctx context.Context) error { + called = true + return fmt.Errorf("pop") + }) + + mdi := bm.database.(*databasemocks.Plugin) + mdi.On("InsertMessageLocal", bm.ctx, mock.Anything).Return(nil) + + err := message.(*broadcastSender).sendInternal(bm.ctx, false) + assert.EqualError(t, err, "pop") + assert.True(t, called) +} diff --git a/internal/privatemessaging/message.go b/internal/privatemessaging/message.go index 11f91c1c53..2bf09f39a9 100644 --- a/internal/privatemessaging/message.go +++ b/internal/privatemessaging/message.go @@ -21,10 +21,11 @@ import ( "encoding/json" "github.com/hyperledger/firefly/internal/i18n" + "github.com/hyperledger/firefly/internal/sysmessaging" "github.com/hyperledger/firefly/pkg/fftypes" ) -func (pm *privateMessaging) NewMessage(ns string, in *fftypes.MessageInOut) PrivateMessage { +func (pm *privateMessaging) NewMessage(ns string, in *fftypes.MessageInOut) sysmessaging.MessageSender { message := &messageSender{ mgr: pm, namespace: ns, @@ -62,7 +63,7 @@ type messageSender struct { namespace string msg *fftypes.MessageInOut resolved bool - sealCallback func(ctx context.Context) + sealCallback sysmessaging.SealCallback } func (s *messageSender) Send(ctx context.Context) error { @@ -73,7 +74,7 @@ func (s *messageSender) SendAndWait(ctx context.Context) error { return s.resolveAndSend(ctx, true) } -func (s *messageSender) AfterSeal(cb func(ctx context.Context)) PrivateMessage { +func (s *messageSender) AfterSeal(cb sysmessaging.SealCallback) sysmessaging.MessageSender { s.sealCallback = cb return s } @@ -151,7 +152,9 @@ func (s *messageSender) sendInternal(ctx context.Context, waitConfirm bool) erro return err } if s.sealCallback != nil { - s.sealCallback(ctx) + if err := s.sealCallback(ctx); err != nil { + return err + } } if immediateConfirm { diff --git a/internal/privatemessaging/message_test.go b/internal/privatemessaging/message_test.go index df93065197..48ae6bd6fa 100644 --- a/internal/privatemessaging/message_test.go +++ b/internal/privatemessaging/message_test.go @@ -347,8 +347,9 @@ func TestSealCallback(t *testing.T) { }) called := false - message.AfterSeal(func(ctx context.Context) { + message.AfterSeal(func(ctx context.Context) error { called = true + return nil }) mdi := pm.database.(*databasemocks.Plugin) @@ -360,6 +361,35 @@ func TestSealCallback(t *testing.T) { } +func TestSealCallbackFail(t *testing.T) { + + pm, cancel := newTestPrivateMessaging(t) + defer cancel() + + id1 := fftypes.NewUUID() + message := pm.NewMessage("ns1", &fftypes.MessageInOut{ + Message: fftypes.Message{ + Data: fftypes.DataRefs{ + {ID: id1, Hash: fftypes.NewRandB32()}, + }, + }, + }) + + called := false + message.AfterSeal(func(ctx context.Context) error { + called = true + return fmt.Errorf("pop") + }) + + mdi := pm.database.(*databasemocks.Plugin) + mdi.On("InsertMessageLocal", pm.ctx, mock.Anything).Return(nil) + + err := message.(*messageSender).sendInternal(pm.ctx, false) + assert.EqualError(t, err, "pop") + assert.True(t, called) + +} + func TestSendUnpinnedMessageMarshalFail(t *testing.T) { pm, cancel := newTestPrivateMessaging(t) diff --git a/internal/privatemessaging/privatemessaging.go b/internal/privatemessaging/privatemessaging.go index 97dc2e2450..1ca3ccdedc 100644 --- a/internal/privatemessaging/privatemessaging.go +++ b/internal/privatemessaging/privatemessaging.go @@ -29,6 +29,7 @@ import ( "github.com/hyperledger/firefly/internal/log" "github.com/hyperledger/firefly/internal/retry" "github.com/hyperledger/firefly/internal/syncasync" + "github.com/hyperledger/firefly/internal/sysmessaging" "github.com/hyperledger/firefly/pkg/blockchain" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/dataexchange" @@ -36,17 +37,11 @@ import ( "github.com/karlseguin/ccache" ) -type PrivateMessage interface { - Send(ctx context.Context) error - SendAndWait(ctx context.Context) error - AfterSeal(cb func(ctx context.Context)) PrivateMessage -} - type Manager interface { GroupManager Start() error - NewMessage(ns string, msg *fftypes.MessageInOut) PrivateMessage + NewMessage(ns string, msg *fftypes.MessageInOut) sysmessaging.MessageSender SendMessage(ctx context.Context, ns string, in *fftypes.MessageInOut, waitConfirm bool) (out *fftypes.Message, err error) RequestReply(ctx context.Context, ns string, request *fftypes.MessageInOut) (reply *fftypes.MessageInOut, err error) } diff --git a/internal/syshandlers/reply_sender_test.go b/internal/syshandlers/reply_sender_test.go index 2a9f02df54..775a829d1a 100644 --- a/internal/syshandlers/reply_sender_test.go +++ b/internal/syshandlers/reply_sender_test.go @@ -23,16 +23,17 @@ import ( "github.com/hyperledger/firefly/mocks/broadcastmocks" "github.com/hyperledger/firefly/mocks/privatemessagingmocks" + "github.com/hyperledger/firefly/mocks/sysmessagingmocks" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/mock" ) func TestSendReplyBroadcastFail(t *testing.T) { sh := newTestSystemHandlers(t) - mbs := &broadcastmocks.Broadcast{} + mms := &sysmessagingmocks.MessageSender{} mbm := sh.broadcast.(*broadcastmocks.Manager) - mbm.On("NewBroadcast", "ns1", mock.Anything).Return(mbs) - mbs.On("Send", context.Background()).Return(fmt.Errorf("pop")) + mbm.On("NewBroadcast", "ns1", mock.Anything).Return(mms) + mms.On("Send", context.Background()).Return(fmt.Errorf("pop")) sh.SendReply(context.Background(), &fftypes.Event{ ID: fftypes.NewUUID(), @@ -40,15 +41,15 @@ func TestSendReplyBroadcastFail(t *testing.T) { }, &fftypes.MessageInOut{}) mbm.AssertExpectations(t) - mbs.AssertExpectations(t) + mms.AssertExpectations(t) } func TestSendReplyPrivateFail(t *testing.T) { sh := newTestSystemHandlers(t) - mps := &privatemessagingmocks.PrivateMessage{} + mms := &sysmessagingmocks.MessageSender{} mpm := sh.messaging.(*privatemessagingmocks.Manager) - mpm.On("NewMessage", "ns1", mock.Anything).Return(mps) - mps.On("Send", context.Background()).Return(fmt.Errorf("pop")) + mpm.On("NewMessage", "ns1", mock.Anything).Return(mms) + mms.On("Send", context.Background()).Return(fmt.Errorf("pop")) sh.SendReply(context.Background(), &fftypes.Event{ ID: fftypes.NewUUID(), @@ -62,7 +63,7 @@ func TestSendReplyPrivateFail(t *testing.T) { }) mpm.AssertExpectations(t) - mps.AssertExpectations(t) + mms.AssertExpectations(t) } func TestSendReplyPrivateOk(t *testing.T) { @@ -74,10 +75,10 @@ func TestSendReplyPrivateOk(t *testing.T) { }, } - mps := &privatemessagingmocks.PrivateMessage{} + mms := &sysmessagingmocks.MessageSender{} mpm := sh.messaging.(*privatemessagingmocks.Manager) - mpm.On("NewMessage", "ns1", mock.Anything).Return(mps) - mps.On("Send", context.Background()).Return(nil) + mpm.On("NewMessage", "ns1", mock.Anything).Return(mms) + mms.On("Send", context.Background()).Return(nil) sh.SendReply(context.Background(), &fftypes.Event{ ID: fftypes.NewUUID(), @@ -87,5 +88,5 @@ func TestSendReplyPrivateOk(t *testing.T) { }) mpm.AssertExpectations(t) - mps.AssertExpectations(t) + mms.AssertExpectations(t) } diff --git a/internal/sysmessaging/message_sender.go b/internal/sysmessaging/message_sender.go new file mode 100644 index 0000000000..a2308b4749 --- /dev/null +++ b/internal/sysmessaging/message_sender.go @@ -0,0 +1,27 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sysmessaging + +import "context" + +type SealCallback func(ctx context.Context) error + +type MessageSender interface { + Send(ctx context.Context) error + SendAndWait(ctx context.Context) error + AfterSeal(cb SealCallback) MessageSender +} diff --git a/mocks/broadcastmocks/manager.go b/mocks/broadcastmocks/manager.go index 690fa762ed..816b4d61c9 100644 --- a/mocks/broadcastmocks/manager.go +++ b/mocks/broadcastmocks/manager.go @@ -5,11 +5,10 @@ package broadcastmocks import ( context "context" - broadcast "github.com/hyperledger/firefly/internal/broadcast" - fftypes "github.com/hyperledger/firefly/pkg/fftypes" - mock "github.com/stretchr/testify/mock" + + sysmessaging "github.com/hyperledger/firefly/internal/sysmessaging" ) // Manager is an autogenerated mock type for the Manager type @@ -179,15 +178,15 @@ func (_m *Manager) BroadcastTokenPool(ctx context.Context, ns string, pool *ffty } // NewBroadcast provides a mock function with given fields: ns, in -func (_m *Manager) NewBroadcast(ns string, in *fftypes.MessageInOut) broadcast.Broadcast { +func (_m *Manager) NewBroadcast(ns string, in *fftypes.MessageInOut) sysmessaging.MessageSender { ret := _m.Called(ns, in) - var r0 broadcast.Broadcast - if rf, ok := ret.Get(0).(func(string, *fftypes.MessageInOut) broadcast.Broadcast); ok { + var r0 sysmessaging.MessageSender + if rf, ok := ret.Get(0).(func(string, *fftypes.MessageInOut) sysmessaging.MessageSender); ok { r0 = rf(ns, in) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(broadcast.Broadcast) + r0 = ret.Get(0).(sysmessaging.MessageSender) } } diff --git a/mocks/privatemessagingmocks/manager.go b/mocks/privatemessagingmocks/manager.go index f214fe80a7..4634da107f 100644 --- a/mocks/privatemessagingmocks/manager.go +++ b/mocks/privatemessagingmocks/manager.go @@ -10,7 +10,7 @@ import ( mock "github.com/stretchr/testify/mock" - privatemessaging "github.com/hyperledger/firefly/internal/privatemessaging" + sysmessaging "github.com/hyperledger/firefly/internal/sysmessaging" ) // Manager is an autogenerated mock type for the Manager type @@ -95,15 +95,15 @@ func (_m *Manager) GetGroups(ctx context.Context, filter database.AndFilter) ([] } // NewMessage provides a mock function with given fields: ns, msg -func (_m *Manager) NewMessage(ns string, msg *fftypes.MessageInOut) privatemessaging.PrivateMessage { +func (_m *Manager) NewMessage(ns string, msg *fftypes.MessageInOut) sysmessaging.MessageSender { ret := _m.Called(ns, msg) - var r0 privatemessaging.PrivateMessage - if rf, ok := ret.Get(0).(func(string, *fftypes.MessageInOut) privatemessaging.PrivateMessage); ok { + var r0 sysmessaging.MessageSender + if rf, ok := ret.Get(0).(func(string, *fftypes.MessageInOut) sysmessaging.MessageSender); ok { r0 = rf(ns, msg) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(privatemessaging.PrivateMessage) + r0 = ret.Get(0).(sysmessaging.MessageSender) } } diff --git a/mocks/privatemessagingmocks/private_message.go b/mocks/privatemessagingmocks/private_message.go deleted file mode 100644 index 090e789e41..0000000000 --- a/mocks/privatemessagingmocks/private_message.go +++ /dev/null @@ -1,59 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package privatemessagingmocks - -import ( - context "context" - - privatemessaging "github.com/hyperledger/firefly/internal/privatemessaging" - mock "github.com/stretchr/testify/mock" -) - -// PrivateMessage is an autogenerated mock type for the PrivateMessage type -type PrivateMessage struct { - mock.Mock -} - -// AfterSeal provides a mock function with given fields: cb -func (_m *PrivateMessage) AfterSeal(cb func(context.Context)) privatemessaging.PrivateMessage { - ret := _m.Called(cb) - - var r0 privatemessaging.PrivateMessage - if rf, ok := ret.Get(0).(func(func(context.Context)) privatemessaging.PrivateMessage); ok { - r0 = rf(cb) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(privatemessaging.PrivateMessage) - } - } - - return r0 -} - -// Send provides a mock function with given fields: ctx -func (_m *PrivateMessage) Send(ctx context.Context) error { - ret := _m.Called(ctx) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// SendAndWait provides a mock function with given fields: ctx -func (_m *PrivateMessage) SendAndWait(ctx context.Context) error { - ret := _m.Called(ctx) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} diff --git a/mocks/broadcastmocks/broadcast.go b/mocks/sysmessagingmocks/message_sender.go similarity index 55% rename from mocks/broadcastmocks/broadcast.go rename to mocks/sysmessagingmocks/message_sender.go index be9e961977..e3df479422 100644 --- a/mocks/broadcastmocks/broadcast.go +++ b/mocks/sysmessagingmocks/message_sender.go @@ -1,30 +1,29 @@ // Code generated by mockery v1.0.0. DO NOT EDIT. -package broadcastmocks +package sysmessagingmocks import ( context "context" - broadcast "github.com/hyperledger/firefly/internal/broadcast" - + sysmessaging "github.com/hyperledger/firefly/internal/sysmessaging" mock "github.com/stretchr/testify/mock" ) -// Broadcast is an autogenerated mock type for the Broadcast type -type Broadcast struct { +// MessageSender is an autogenerated mock type for the MessageSender type +type MessageSender struct { mock.Mock } // AfterSeal provides a mock function with given fields: cb -func (_m *Broadcast) AfterSeal(cb func(context.Context)) broadcast.Broadcast { +func (_m *MessageSender) AfterSeal(cb sysmessaging.SealCallback) sysmessaging.MessageSender { ret := _m.Called(cb) - var r0 broadcast.Broadcast - if rf, ok := ret.Get(0).(func(func(context.Context)) broadcast.Broadcast); ok { + var r0 sysmessaging.MessageSender + if rf, ok := ret.Get(0).(func(sysmessaging.SealCallback) sysmessaging.MessageSender); ok { r0 = rf(cb) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(broadcast.Broadcast) + r0 = ret.Get(0).(sysmessaging.MessageSender) } } @@ -32,7 +31,7 @@ func (_m *Broadcast) AfterSeal(cb func(context.Context)) broadcast.Broadcast { } // Send provides a mock function with given fields: ctx -func (_m *Broadcast) Send(ctx context.Context) error { +func (_m *MessageSender) Send(ctx context.Context) error { ret := _m.Called(ctx) var r0 error @@ -46,7 +45,7 @@ func (_m *Broadcast) Send(ctx context.Context) error { } // SendAndWait provides a mock function with given fields: ctx -func (_m *Broadcast) SendAndWait(ctx context.Context) error { +func (_m *MessageSender) SendAndWait(ctx context.Context) error { ret := _m.Called(ctx) var r0 error From f96c9388089ddcf28aa41bd58367813db90148c1 Mon Sep 17 00:00:00 2001 From: Andrew Richardson Date: Fri, 15 Oct 2021 11:55:01 -0400 Subject: [PATCH 6/8] Use MessageSender for token transfers Signed-off-by: Andrew Richardson --- docs/swagger/swagger.yaml | 218 +++++++++++++++++ internal/apiserver/route_post_token_burn.go | 4 +- .../apiserver/route_post_token_burn_test.go | 4 +- internal/apiserver/route_post_token_mint.go | 4 +- .../apiserver/route_post_token_mint_test.go | 4 +- internal/assets/manager.go | 188 ++++++++++----- internal/assets/manager_test.go | 225 +++++++++++++++--- internal/broadcast/message.go | 4 +- internal/privatemessaging/message.go | 4 +- mocks/assetmocks/manager.go | 30 ++- 10 files changed, 582 insertions(+), 103 deletions(-) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index ed4d8ba364..15f27e31d4 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -4743,12 +4743,120 @@ paths: schema: properties: amount: {} + created: {} from: type: string key: type: string + localId: {} + message: + properties: + batch: {} + confirmed: {} + data: + items: + properties: + blob: + properties: + hash: {} + public: + type: string + type: object + datatype: + properties: + name: + type: string + version: + type: string + type: object + hash: {} + id: {} + validator: + type: string + value: + format: byte + type: string + type: object + type: array + group: + properties: + ledger: {} + members: + items: + properties: + identity: + type: string + node: + type: string + type: object + type: array + name: + type: string + type: object + hash: {} + header: + properties: + author: + type: string + cid: {} + created: {} + datahash: {} + group: {} + id: {} + key: + type: string + namespace: + type: string + tag: + type: string + topics: + items: + type: string + type: array + txtype: + type: string + type: + enum: + - definition + - broadcast + - private + - groupinit + - transfer_broadcast + - transfer_private + type: string + type: object + local: + type: boolean + pending: + type: boolean + pins: + items: + type: string + type: array + rejected: + type: boolean + type: object + messageHash: {} + poolProtocolId: + type: string + protocolId: + type: string + to: + type: string tokenIndex: type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + enum: + - mint + - burn + - transfer + type: string type: object responses: "200": @@ -4857,10 +4965,120 @@ paths: schema: properties: amount: {} + created: {} + from: + type: string key: type: string + localId: {} + message: + properties: + batch: {} + confirmed: {} + data: + items: + properties: + blob: + properties: + hash: {} + public: + type: string + type: object + datatype: + properties: + name: + type: string + version: + type: string + type: object + hash: {} + id: {} + validator: + type: string + value: + format: byte + type: string + type: object + type: array + group: + properties: + ledger: {} + members: + items: + properties: + identity: + type: string + node: + type: string + type: object + type: array + name: + type: string + type: object + hash: {} + header: + properties: + author: + type: string + cid: {} + created: {} + datahash: {} + group: {} + id: {} + key: + type: string + namespace: + type: string + tag: + type: string + topics: + items: + type: string + type: array + txtype: + type: string + type: + enum: + - definition + - broadcast + - private + - groupinit + - transfer_broadcast + - transfer_private + type: string + type: object + local: + type: boolean + pending: + type: boolean + pins: + items: + type: string + type: array + rejected: + type: boolean + type: object + messageHash: {} + poolProtocolId: + type: string + protocolId: + type: string to: type: string + tokenIndex: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + enum: + - mint + - burn + - transfer + type: string type: object responses: "200": diff --git a/internal/apiserver/route_post_token_burn.go b/internal/apiserver/route_post_token_burn.go index 607a8a8b9f..35e579b55f 100644 --- a/internal/apiserver/route_post_token_burn.go +++ b/internal/apiserver/route_post_token_burn.go @@ -40,13 +40,13 @@ var postTokenBurn = &oapispec.Route{ }, FilterFactory: nil, Description: i18n.MsgTBD, - JSONInputValue: func() interface{} { return &fftypes.TokenTransfer{} }, + JSONInputValue: func() interface{} { return &fftypes.TokenTransferInput{} }, JSONInputMask: []string{"Type", "LocalID", "PoolProtocolID", "To", "ProtocolID", "MessageHash", "TX", "Created"}, JSONOutputValue: func() interface{} { return &fftypes.TokenTransfer{} }, JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK}, JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - return r.Or.Assets().BurnTokens(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransfer), waitConfirm) + return r.Or.Assets().BurnTokens(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) }, } diff --git a/internal/apiserver/route_post_token_burn_test.go b/internal/apiserver/route_post_token_burn_test.go index a2addefaf1..6b4de5ee98 100644 --- a/internal/apiserver/route_post_token_burn_test.go +++ b/internal/apiserver/route_post_token_burn_test.go @@ -32,14 +32,14 @@ func TestPostTokenBurn(t *testing.T) { o, r := newTestAPIServer() mam := &assetmocks.Manager{} o.On("Assets").Return(mam) - input := fftypes.TokenTransfer{} + input := fftypes.TokenTransferInput{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/tokens/tok1/pools/pool1/burn", &buf) req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mam.On("BurnTokens", mock.Anything, "ns1", "tok1", "pool1", mock.AnythingOfType("*fftypes.TokenTransfer"), false). + mam.On("BurnTokens", mock.Anything, "ns1", "tok1", "pool1", mock.AnythingOfType("*fftypes.TokenTransferInput"), false). Return(&fftypes.TokenTransfer{}, nil) r.ServeHTTP(res, req) diff --git a/internal/apiserver/route_post_token_mint.go b/internal/apiserver/route_post_token_mint.go index ed874752e8..b134aef51f 100644 --- a/internal/apiserver/route_post_token_mint.go +++ b/internal/apiserver/route_post_token_mint.go @@ -40,13 +40,13 @@ var postTokenMint = &oapispec.Route{ }, FilterFactory: nil, Description: i18n.MsgTBD, - JSONInputValue: func() interface{} { return &fftypes.TokenTransfer{} }, + JSONInputValue: func() interface{} { return &fftypes.TokenTransferInput{} }, JSONInputMask: []string{"Type", "LocalID", "PoolProtocolID", "TokenIndex", "From", "ProtocolID", "MessageHash", "TX", "Created"}, JSONOutputValue: func() interface{} { return &fftypes.TokenTransfer{} }, JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK}, JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - return r.Or.Assets().MintTokens(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransfer), waitConfirm) + return r.Or.Assets().MintTokens(r.Ctx, r.PP["ns"], r.PP["type"], r.PP["name"], r.Input.(*fftypes.TokenTransferInput), waitConfirm) }, } diff --git a/internal/apiserver/route_post_token_mint_test.go b/internal/apiserver/route_post_token_mint_test.go index 28af3cf91b..1b4eca0c64 100644 --- a/internal/apiserver/route_post_token_mint_test.go +++ b/internal/apiserver/route_post_token_mint_test.go @@ -32,14 +32,14 @@ func TestPostTokenMint(t *testing.T) { o, r := newTestAPIServer() mam := &assetmocks.Manager{} o.On("Assets").Return(mam) - input := fftypes.TokenTransfer{} + input := fftypes.TokenTransferInput{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/tokens/tok1/pools/pool1/mint", &buf) req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mam.On("MintTokens", mock.Anything, "ns1", "tok1", "pool1", mock.AnythingOfType("*fftypes.TokenTransfer"), false). + mam.On("MintTokens", mock.Anything, "ns1", "tok1", "pool1", mock.AnythingOfType("*fftypes.TokenTransferInput"), false). Return(&fftypes.TokenTransfer{}, nil) r.ServeHTTP(res, req) diff --git a/internal/assets/manager.go b/internal/assets/manager.go index 72ff5167ec..f8cbce2b40 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -28,6 +28,7 @@ import ( "github.com/hyperledger/firefly/internal/privatemessaging" "github.com/hyperledger/firefly/internal/retry" "github.com/hyperledger/firefly/internal/syncasync" + "github.com/hyperledger/firefly/internal/sysmessaging" "github.com/hyperledger/firefly/internal/txcommon" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" @@ -41,8 +42,9 @@ type Manager interface { GetTokenAccounts(ctx context.Context, ns, typeName, poolName string, filter database.AndFilter) ([]*fftypes.TokenAccount, *database.FilterResult, error) ValidateTokenPoolTx(ctx context.Context, pool *fftypes.TokenPool, protocolTxID string) error GetTokenTransfers(ctx context.Context, ns, typeName, poolName string, filter database.AndFilter) ([]*fftypes.TokenTransfer, *database.FilterResult, error) - MintTokens(ctx context.Context, ns, typeName, poolName string, transfer *fftypes.TokenTransfer, waitConfirm bool) (*fftypes.TokenTransfer, error) - BurnTokens(ctx context.Context, ns, typeName, poolName string, transfer *fftypes.TokenTransfer, waitConfirm bool) (*fftypes.TokenTransfer, error) + NewTransfer(ns, typeName, poolName string, transfer *fftypes.TokenTransferInput) sysmessaging.MessageSender + MintTokens(ctx context.Context, ns, typeName, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) + BurnTokens(ctx context.Context, ns, typeName, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) TransferTokens(ctx context.Context, ns, typeName, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) // Bound token callbacks @@ -254,7 +256,45 @@ func (am *assetManager) GetTokenTransfers(ctx context.Context, ns, typeName, nam return am.database.GetTokenTransfers(ctx, filter.Condition(filter.Builder().Eq("poolprotocolid", pool.ProtocolID))) } -func (am *assetManager) MintTokens(ctx context.Context, ns, typeName, poolName string, transfer *fftypes.TokenTransfer, waitConfirm bool) (*fftypes.TokenTransfer, error) { +func (am *assetManager) NewTransfer(ns, typeName, poolName string, transfer *fftypes.TokenTransferInput) sysmessaging.MessageSender { + sender := &transferSender{ + mgr: am, + namespace: ns, + typeName: typeName, + poolName: poolName, + transfer: transfer, + } + sender.setDefaults() + return sender +} + +type transferSender struct { + mgr *assetManager + namespace string + typeName string + poolName string + transfer *fftypes.TokenTransferInput + sealCallback sysmessaging.SealCallback +} + +func (s *transferSender) Send(ctx context.Context) error { + return s.resolveAndSend(ctx, false) +} + +func (s *transferSender) SendAndWait(ctx context.Context) error { + return s.resolveAndSend(ctx, true) +} + +func (s *transferSender) AfterSeal(cb sysmessaging.SealCallback) sysmessaging.MessageSender { + s.sealCallback = cb + return s +} + +func (s *transferSender) setDefaults() { + s.transfer.LocalID = fftypes.NewUUID() +} + +func (am *assetManager) MintTokens(ctx context.Context, ns, typeName, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { transfer.Type = fftypes.TokenTransferTypeMint if transfer.Key == "" { org, err := am.identity.GetLocalOrganization(ctx) @@ -267,10 +307,17 @@ func (am *assetManager) MintTokens(ctx context.Context, ns, typeName, poolName s if transfer.To == "" { transfer.To = transfer.Key } - return am.transferTokensWithID(ctx, fftypes.NewUUID(), ns, typeName, poolName, transfer, waitConfirm) + + sender := am.NewTransfer(ns, typeName, poolName, transfer) + if waitConfirm { + err = sender.SendAndWait(ctx) + } else { + err = sender.Send(ctx) + } + return &transfer.TokenTransfer, err } -func (am *assetManager) BurnTokens(ctx context.Context, ns, typeName, poolName string, transfer *fftypes.TokenTransfer, waitConfirm bool) (*fftypes.TokenTransfer, error) { +func (am *assetManager) BurnTokens(ctx context.Context, ns, typeName, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { transfer.Type = fftypes.TokenTransferTypeBurn if transfer.Key == "" { org, err := am.identity.GetLocalOrganization(ctx) @@ -283,28 +330,17 @@ func (am *assetManager) BurnTokens(ctx context.Context, ns, typeName, poolName s transfer.From = transfer.Key } transfer.To = "" - return am.transferTokensWithID(ctx, fftypes.NewUUID(), ns, typeName, poolName, transfer, waitConfirm) -} -func (am *assetManager) sendTransferMessage(ctx context.Context, ns string, in *fftypes.MessageInOut) (*fftypes.Message, error) { - allowedTypes := []fftypes.FFEnum{ - fftypes.MessageTypeTransferBroadcast, - fftypes.MessageTypeTransferPrivate, - } - if in.Header.Type == "" { - in.Header.Type = fftypes.MessageTypeTransferBroadcast - } - switch in.Header.Type { - case fftypes.MessageTypeTransferBroadcast: - return am.broadcast.BroadcastMessage(ctx, ns, in, false) - case fftypes.MessageTypeTransferPrivate: - return am.messaging.SendMessage(ctx, ns, in, false) - default: - return nil, i18n.NewError(ctx, i18n.MsgInvalidMessageType, allowedTypes) + sender := am.NewTransfer(ns, typeName, poolName, transfer) + if waitConfirm { + err = sender.SendAndWait(ctx) + } else { + err = sender.Send(ctx) } + return &transfer.TokenTransfer, err } -func (am *assetManager) TransferTokens(ctx context.Context, ns, typeName, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { +func (am *assetManager) TransferTokens(ctx context.Context, ns, typeName, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (out *fftypes.TokenTransfer, err error) { transfer.Type = fftypes.TokenTransferTypeTransfer if transfer.Key == "" { org, err := am.identity.GetLocalOrganization(ctx) @@ -323,81 +359,113 @@ func (am *assetManager) TransferTokens(ctx context.Context, ns, typeName, poolNa return nil, i18n.NewError(ctx, i18n.MsgCannotTransferToSelf) } - if transfer.Message != nil { - msg, err := am.sendTransferMessage(ctx, ns, transfer.Message) - if err != nil { - return nil, err - } - transfer.MessageHash = msg.Hash + sender := am.NewTransfer(ns, typeName, poolName, transfer) + if waitConfirm { + err = sender.SendAndWait(ctx) + } else { + err = sender.Send(ctx) } - - result, err := am.transferTokensWithID(ctx, fftypes.NewUUID(), ns, typeName, poolName, &transfer.TokenTransfer, waitConfirm) - return result, err + return &transfer.TokenTransfer, err } -func (am *assetManager) transferTokensWithID(ctx context.Context, id *fftypes.UUID, ns, typeName, poolName string, transfer *fftypes.TokenTransfer, waitConfirm bool) (*fftypes.TokenTransfer, error) { - plugin, err := am.selectTokenPlugin(ctx, typeName) +func (s *transferSender) resolveAndSend(ctx context.Context, waitConfirm bool) (err error) { + plugin, err := s.mgr.selectTokenPlugin(ctx, s.typeName) if err != nil { - return nil, err + return err } - pool, err := am.GetTokenPool(ctx, ns, typeName, poolName) + pool, err := s.mgr.GetTokenPool(ctx, s.namespace, s.typeName, s.poolName) if err != nil { - return nil, err + return err } + s.transfer.PoolProtocolID = pool.ProtocolID if waitConfirm { - requestID := fftypes.NewUUID() - return am.syncasync.SendConfirmTokenTransfer(ctx, ns, requestID, func() error { - _, err := am.transferTokensWithID(ctx, requestID, ns, typeName, poolName, transfer, false) + return s.sendSync(ctx) + } + + if s.transfer.Message != nil { + if err := s.sendTransferMessage(ctx, s.namespace, s.transfer.Message); err != nil { return err - }) + } + s.transfer.MessageHash = s.transfer.Message.Hash } tx := &fftypes.Transaction{ ID: fftypes.NewUUID(), Subject: fftypes.TransactionSubject{ - Namespace: ns, + Namespace: s.namespace, Type: fftypes.TransactionTypeTokenTransfer, - Signer: transfer.Key, - Reference: id, + Signer: s.transfer.Key, + Reference: s.transfer.LocalID, }, Created: fftypes.Now(), Status: fftypes.OpStatusPending, } tx.Hash = tx.Subject.Hash() - err = am.database.UpsertTransaction(ctx, tx, false /* should be new, or idempotent replay */) + err = s.mgr.database.UpsertTransaction(ctx, tx, false /* should be new, or idempotent replay */) if err != nil { - return nil, err + return err } - transfer.LocalID = id - transfer.PoolProtocolID = pool.ProtocolID - transfer.TX.ID = tx.ID - transfer.TX.Type = tx.Subject.Type + s.transfer.TX.ID = tx.ID + s.transfer.TX.Type = tx.Subject.Type op := fftypes.NewTXOperation( plugin, - ns, + s.namespace, tx.ID, "", fftypes.OpTypeTokenTransfer, fftypes.OpStatusPending, "") - addTokenTransferInputs(op, transfer) - err = am.database.UpsertOperation(ctx, op, false) - if err != nil { - return nil, err + addTokenTransferInputs(op, &s.transfer.TokenTransfer) + if err := s.mgr.database.UpsertOperation(ctx, op, false); err != nil { + return err + } + + if s.sealCallback != nil { + if err := s.sealCallback(ctx); err != nil { + return err + } } - switch transfer.Type { + switch s.transfer.Type { case fftypes.TokenTransferTypeMint: - return transfer, plugin.MintTokens(ctx, op.ID, transfer) + return plugin.MintTokens(ctx, op.ID, &s.transfer.TokenTransfer) case fftypes.TokenTransferTypeTransfer: - return transfer, plugin.TransferTokens(ctx, op.ID, transfer) + return plugin.TransferTokens(ctx, op.ID, &s.transfer.TokenTransfer) case fftypes.TokenTransferTypeBurn: - return transfer, plugin.BurnTokens(ctx, op.ID, transfer) + return plugin.BurnTokens(ctx, op.ID, &s.transfer.TokenTransfer) + default: + panic(fmt.Sprintf("unknown transfer type: %v", s.transfer.Type)) + } +} + +func (s *transferSender) sendSync(ctx context.Context) error { + out, err := s.mgr.syncasync.SendConfirmTokenTransfer(ctx, s.namespace, s.transfer.LocalID, func() error { + return s.Send(ctx) + }) + if out != nil { + s.transfer.TokenTransfer = *out + } + return err +} + +func (s *transferSender) sendTransferMessage(ctx context.Context, ns string, in *fftypes.MessageInOut) error { + allowedTypes := []fftypes.FFEnum{ + fftypes.MessageTypeTransferBroadcast, + fftypes.MessageTypeTransferPrivate, + } + if in.Header.Type == "" { + in.Header.Type = fftypes.MessageTypeTransferBroadcast + } + switch in.Header.Type { + case fftypes.MessageTypeTransferBroadcast: + return s.mgr.broadcast.NewBroadcast(ns, in).Send(ctx) + case fftypes.MessageTypeTransferPrivate: + return s.mgr.messaging.NewMessage(ns, in).Send(ctx) default: - panic(fmt.Sprintf("unknown transfer type: %v", transfer.Type)) + return i18n.NewError(ctx, i18n.MsgInvalidMessageType, allowedTypes) } } diff --git a/internal/assets/manager_test.go b/internal/assets/manager_test.go index aa6686479e..14761fb4ed 100644 --- a/internal/assets/manager_test.go +++ b/internal/assets/manager_test.go @@ -27,6 +27,7 @@ import ( "github.com/hyperledger/firefly/mocks/identitymanagermocks" "github.com/hyperledger/firefly/mocks/privatemessagingmocks" "github.com/hyperledger/firefly/mocks/syncasyncmocks" + "github.com/hyperledger/firefly/mocks/sysmessagingmocks" "github.com/hyperledger/firefly/mocks/tokenmocks" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" @@ -351,7 +352,7 @@ func TestMintTokensSuccess(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - mint := &fftypes.TokenTransfer{} + mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) @@ -359,7 +360,7 @@ func TestMintTokensSuccess(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - mti.On("MintTokens", context.Background(), mock.Anything, mint).Return(nil) + mti.On("MintTokens", context.Background(), mock.Anything, &mint.TokenTransfer).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer @@ -376,7 +377,7 @@ func TestMintTokensBadPlugin(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) - _, err := am.MintTokens(context.Background(), "", "", "", &fftypes.TokenTransfer{}, false) + _, err := am.MintTokens(context.Background(), "", "", "", &fftypes.TokenTransferInput{}, false) assert.Regexp(t, "FF10272", err) } @@ -384,7 +385,7 @@ func TestMintTokensBadPool(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - mint := &fftypes.TokenTransfer{} + mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) @@ -400,7 +401,7 @@ func TestMintTokensIdentityFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - mint := &fftypes.TokenTransfer{} + mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) @@ -416,7 +417,7 @@ func TestMintTokensFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - mint := &fftypes.TokenTransfer{} + mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) @@ -425,7 +426,7 @@ func TestMintTokensFail(t *testing.T) { mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - mti.On("MintTokens", context.Background(), mock.Anything, mint).Return(fmt.Errorf("pop")) + mti.On("MintTokens", context.Background(), mock.Anything, &mint.TokenTransfer).Return(fmt.Errorf("pop")) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(nil) @@ -438,7 +439,7 @@ func TestMintTokensOperationFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - mint := &fftypes.TokenTransfer{} + mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) @@ -458,7 +459,7 @@ func TestMintTokensConfirm(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - mint := &fftypes.TokenTransfer{} + mint := &fftypes.TokenTransferInput{} mint.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) @@ -468,7 +469,7 @@ func TestMintTokensConfirm(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - mti.On("MintTokens", context.Background(), mock.Anything, mint).Return(nil) + mti.On("MintTokens", context.Background(), mock.Anything, &mint.TokenTransfer).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer @@ -478,7 +479,7 @@ func TestMintTokensConfirm(t *testing.T) { send := args[3].(syncasync.RequestSender) send() }). - Return(nil, nil) + Return(&fftypes.TokenTransfer{}, nil) _, err := am.MintTokens(context.Background(), "ns1", "magic-tokens", "pool1", mint, true) assert.NoError(t, err) @@ -493,7 +494,7 @@ func TestBurnTokensSuccess(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - burn := &fftypes.TokenTransfer{} + burn := &fftypes.TokenTransferInput{} burn.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) @@ -501,7 +502,7 @@ func TestBurnTokensSuccess(t *testing.T) { mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - mti.On("BurnTokens", context.Background(), mock.Anything, burn).Return(nil) + mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer @@ -519,7 +520,7 @@ func TestBurnTokensIdentityFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - burn := &fftypes.TokenTransfer{} + burn := &fftypes.TokenTransferInput{} burn.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) @@ -531,6 +532,41 @@ func TestBurnTokensIdentityFail(t *testing.T) { assert.EqualError(t, err, "pop") } +func TestBurnTokensConfirm(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + burn := &fftypes.TokenTransferInput{} + burn.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + msa := am.syncasync.(*syncasyncmocks.Bridge) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("BurnTokens", context.Background(), mock.Anything, &burn.TokenTransfer).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + msa.On("SendConfirmTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send(context.Background()) + }). + Return(&fftypes.TokenTransfer{}, nil) + + _, err := am.BurnTokens(context.Background(), "ns1", "magic-tokens", "pool1", burn, true) + assert.NoError(t, err) + + mdi.AssertExpectations(t) + mdm.AssertExpectations(t) + msa.AssertExpectations(t) + mti.AssertExpectations(t) +} + func TestTransferTokensSuccess(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -602,24 +638,31 @@ func TestTransferTokensInvalidType(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() - transfer := &fftypes.TokenTransfer{ - From: "A", - To: "B", + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, } transfer.Amount.Int().SetInt64(5) mdi := am.database.(*databasemocks.Plugin) - mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) - mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) - mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + mdi.On("GetTokenPool", am.ctx, "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mdi.On("UpsertTransaction", am.ctx, mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(nil) - - assert.Panics(t, func() { - am.transferTokensWithID(context.Background(), fftypes.NewUUID(), "ns1", "magic-tokens", "pool1", transfer, false) + mdi.On("UpsertOperation", am.ctx, mock.Anything, false).Return(nil) + + sender := &transferSender{ + mgr: am, + namespace: "ns1", + typeName: "magic-tokens", + poolName: "pool1", + transfer: transfer, + } + assert.PanicsWithValue(t, "unknown transfer type: ", func() { + sender.Send(am.ctx) }) - - mdi.AssertExpectations(t) } func TestTransferTokensTransactionFail(t *testing.T) { @@ -672,6 +715,7 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) mbm := am.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) @@ -679,7 +723,8 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(nil) - mbm.On("BroadcastMessage", context.Background(), "ns1", transfer.Message, false).Return(&transfer.Message.Message, nil) + mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) + mms.On("Send", context.Background()).Return(nil) _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.NoError(t, err) @@ -688,6 +733,7 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { mim.AssertExpectations(t) mdi.AssertExpectations(t) mti.AssertExpectations(t) + mms.AssertExpectations(t) } func TestTransferTokensWithPrivateMessage(t *testing.T) { @@ -718,6 +764,7 @@ func TestTransferTokensWithPrivateMessage(t *testing.T) { mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) mpm := am.messaging.(*privatemessagingmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) @@ -725,7 +772,8 @@ func TestTransferTokensWithPrivateMessage(t *testing.T) { mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer }), false).Return(nil) - mpm.On("SendMessage", context.Background(), "ns1", transfer.Message, false).Return(&transfer.Message.Message, nil) + mpm.On("NewMessage", "ns1", transfer.Message).Return(mms) + mms.On("Send", context.Background()).Return(nil) _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.NoError(t, err) @@ -734,6 +782,7 @@ func TestTransferTokensWithPrivateMessage(t *testing.T) { mim.AssertExpectations(t) mdi.AssertExpectations(t) mti.AssertExpectations(t) + mms.AssertExpectations(t) } func TestTransferTokensWithInvalidMessage(t *testing.T) { @@ -760,11 +809,133 @@ func TestTransferTokensWithInvalidMessage(t *testing.T) { } transfer.Amount.Int().SetInt64(5) + mdi := am.database.(*databasemocks.Plugin) mim := am.identity.(*identitymanagermocks.Manager) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.Regexp(t, "FF10287", err) mim.AssertExpectations(t) + mdi.AssertExpectations(t) +} + +func TestTransferTokensConfirm(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + msa := am.syncasync.(*syncasyncmocks.Bridge) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + msa.On("SendConfirmTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). + Run(func(args mock.Arguments) { + send := args[3].(syncasync.RequestSender) + send(context.Background()) + }). + Return(&fftypes.TokenTransfer{}, nil) + + _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, true) + assert.NoError(t, err) + + mdi.AssertExpectations(t) + mdm.AssertExpectations(t) + msa.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestTransferTokensSealCallback(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + Type: fftypes.TokenTransferTypeTransfer, + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + + sender := am.NewTransfer("ns1", "magic-tokens", "pool1", transfer) + + called := false + sender.AfterSeal(func(ctx context.Context) error { + called = true + return nil + }) + + err := sender.Send(context.Background()) + assert.NoError(t, err) + assert.True(t, called) + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) +} + +func TestTransferTokensSealCallbackFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + Type: fftypes.TokenTransferTypeTransfer, + From: "A", + To: "B", + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + + sender := am.NewTransfer("ns1", "magic-tokens", "pool1", transfer) + + called := false + sender.AfterSeal(func(ctx context.Context) error { + called = true + return fmt.Errorf("pop") + }) + + err := sender.Send(context.Background()) + assert.EqualError(t, err, "pop") + assert.True(t, called) + + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) } diff --git a/internal/broadcast/message.go b/internal/broadcast/message.go index a38510c570..eee34cf243 100644 --- a/internal/broadcast/message.go +++ b/internal/broadcast/message.go @@ -132,7 +132,9 @@ func (s *broadcastSender) sendInternal(ctx context.Context, waitConfirm bool) (e out, err := s.mgr.syncasync.SendConfirm(ctx, s.namespace, s.msg.Header.ID, func() error { return s.Send(ctx) }) - s.msg.Message = *out + if out != nil { + s.msg.Message = *out + } return err } diff --git a/internal/privatemessaging/message.go b/internal/privatemessaging/message.go index 2bf09f39a9..bc56ac1487 100644 --- a/internal/privatemessaging/message.go +++ b/internal/privatemessaging/message.go @@ -143,7 +143,9 @@ func (s *messageSender) sendInternal(ctx context.Context, waitConfirm bool) erro out, err := s.mgr.syncasync.SendConfirm(ctx, s.namespace, s.msg.Header.ID, func() error { return s.Send(ctx) }) - s.msg.Message = *out + if out != nil { + s.msg.Message = *out + } return err } diff --git a/mocks/assetmocks/manager.go b/mocks/assetmocks/manager.go index 12618baa24..850689dd60 100644 --- a/mocks/assetmocks/manager.go +++ b/mocks/assetmocks/manager.go @@ -10,6 +10,8 @@ import ( mock "github.com/stretchr/testify/mock" + sysmessaging "github.com/hyperledger/firefly/internal/sysmessaging" + tokens "github.com/hyperledger/firefly/pkg/tokens" ) @@ -19,11 +21,11 @@ type Manager struct { } // BurnTokens provides a mock function with given fields: ctx, ns, typeName, poolName, transfer, waitConfirm -func (_m *Manager) BurnTokens(ctx context.Context, ns string, typeName string, poolName string, transfer *fftypes.TokenTransfer, waitConfirm bool) (*fftypes.TokenTransfer, error) { +func (_m *Manager) BurnTokens(ctx context.Context, ns string, typeName string, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { ret := _m.Called(ctx, ns, typeName, poolName, transfer, waitConfirm) var r0 *fftypes.TokenTransfer - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *fftypes.TokenTransfer, bool) *fftypes.TokenTransfer); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *fftypes.TokenTransferInput, bool) *fftypes.TokenTransfer); ok { r0 = rf(ctx, ns, typeName, poolName, transfer, waitConfirm) } else { if ret.Get(0) != nil { @@ -32,7 +34,7 @@ func (_m *Manager) BurnTokens(ctx context.Context, ns string, typeName string, p } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string, string, string, *fftypes.TokenTransfer, bool) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, *fftypes.TokenTransferInput, bool) error); ok { r1 = rf(ctx, ns, typeName, poolName, transfer, waitConfirm) } else { r1 = ret.Error(1) @@ -184,11 +186,11 @@ func (_m *Manager) GetTokenTransfers(ctx context.Context, ns string, typeName st } // MintTokens provides a mock function with given fields: ctx, ns, typeName, poolName, transfer, waitConfirm -func (_m *Manager) MintTokens(ctx context.Context, ns string, typeName string, poolName string, transfer *fftypes.TokenTransfer, waitConfirm bool) (*fftypes.TokenTransfer, error) { +func (_m *Manager) MintTokens(ctx context.Context, ns string, typeName string, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) { ret := _m.Called(ctx, ns, typeName, poolName, transfer, waitConfirm) var r0 *fftypes.TokenTransfer - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *fftypes.TokenTransfer, bool) *fftypes.TokenTransfer); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *fftypes.TokenTransferInput, bool) *fftypes.TokenTransfer); ok { r0 = rf(ctx, ns, typeName, poolName, transfer, waitConfirm) } else { if ret.Get(0) != nil { @@ -197,7 +199,7 @@ func (_m *Manager) MintTokens(ctx context.Context, ns string, typeName string, p } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string, string, string, *fftypes.TokenTransfer, bool) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, *fftypes.TokenTransferInput, bool) error); ok { r1 = rf(ctx, ns, typeName, poolName, transfer, waitConfirm) } else { r1 = ret.Error(1) @@ -206,6 +208,22 @@ func (_m *Manager) MintTokens(ctx context.Context, ns string, typeName string, p return r0, r1 } +// NewTransfer provides a mock function with given fields: ns, typeName, poolName, transfer +func (_m *Manager) NewTransfer(ns string, typeName string, poolName string, transfer *fftypes.TokenTransferInput) sysmessaging.MessageSender { + ret := _m.Called(ns, typeName, poolName, transfer) + + var r0 sysmessaging.MessageSender + if rf, ok := ret.Get(0).(func(string, string, string, *fftypes.TokenTransferInput) sysmessaging.MessageSender); ok { + r0 = rf(ns, typeName, poolName, transfer) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(sysmessaging.MessageSender) + } + } + + return r0 +} + // Start provides a mock function with given fields: func (_m *Manager) Start() error { ret := _m.Called() From 712b3192f914405ff99473841f8ea73268e2cef6 Mon Sep 17 00:00:00 2001 From: Andrew Richardson Date: Fri, 15 Oct 2021 13:34:28 -0400 Subject: [PATCH 7/8] Align RequestSender and MessageSender signatures Signed-off-by: Andrew Richardson --- internal/assets/manager.go | 6 ++---- internal/assets/manager_test.go | 4 ++-- internal/broadcast/message.go | 4 +--- internal/broadcast/message_test.go | 2 +- internal/privatemessaging/message.go | 8 ++------ internal/privatemessaging/message_test.go | 4 ++-- internal/syncasync/sync_async_bridge.go | 4 ++-- internal/syncasync/sync_async_bridge_test.go | 20 ++++++++++---------- 8 files changed, 22 insertions(+), 30 deletions(-) diff --git a/internal/assets/manager.go b/internal/assets/manager.go index f8cbce2b40..592dbc165a 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -154,7 +154,7 @@ func (am *assetManager) createTokenPoolWithID(ctx context.Context, id *fftypes.U if waitConfirm { requestID := fftypes.NewUUID() - return am.syncasync.SendConfirmTokenPool(ctx, ns, requestID, func() error { + return am.syncasync.SendConfirmTokenPool(ctx, ns, requestID, func(ctx context.Context) error { _, err := am.createTokenPoolWithID(ctx, requestID, ns, typeName, pool, false) return err }) @@ -442,9 +442,7 @@ func (s *transferSender) resolveAndSend(ctx context.Context, waitConfirm bool) ( } func (s *transferSender) sendSync(ctx context.Context) error { - out, err := s.mgr.syncasync.SendConfirmTokenTransfer(ctx, s.namespace, s.transfer.LocalID, func() error { - return s.Send(ctx) - }) + out, err := s.mgr.syncasync.SendConfirmTokenTransfer(ctx, s.namespace, s.transfer.LocalID, s.Send) if out != nil { s.transfer.TokenTransfer = *out } diff --git a/internal/assets/manager_test.go b/internal/assets/manager_test.go index 14761fb4ed..01f0192438 100644 --- a/internal/assets/manager_test.go +++ b/internal/assets/manager_test.go @@ -198,7 +198,7 @@ func TestCreateTokenPoolConfirm(t *testing.T) { msa.On("SendConfirmTokenPool", context.Background(), "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { send := args[3].(syncasync.RequestSender) - send() + send(context.Background()) }). Return(nil, nil) @@ -477,7 +477,7 @@ func TestMintTokensConfirm(t *testing.T) { msa.On("SendConfirmTokenTransfer", context.Background(), "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { send := args[3].(syncasync.RequestSender) - send() + send(context.Background()) }). Return(&fftypes.TokenTransfer{}, nil) diff --git a/internal/broadcast/message.go b/internal/broadcast/message.go index eee34cf243..260427695f 100644 --- a/internal/broadcast/message.go +++ b/internal/broadcast/message.go @@ -129,9 +129,7 @@ func (s *broadcastSender) resolveMessage(ctx context.Context) ([]*fftypes.DataAn func (s *broadcastSender) sendInternal(ctx context.Context, waitConfirm bool) (err error) { if waitConfirm { - out, err := s.mgr.syncasync.SendConfirm(ctx, s.namespace, s.msg.Header.ID, func() error { - return s.Send(ctx) - }) + out, err := s.mgr.syncasync.SendConfirm(ctx, s.namespace, s.msg.Header.ID, s.Send) if out != nil { s.msg.Message = *out } diff --git a/internal/broadcast/message_test.go b/internal/broadcast/message_test.go index c89f235d95..3319c9ca60 100644 --- a/internal/broadcast/message_test.go +++ b/internal/broadcast/message_test.go @@ -210,7 +210,7 @@ func TestBroadcastMessageWaitConfirmOk(t *testing.T) { msa.On("SendConfirm", ctx, "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { send := args[3].(syncasync.RequestSender) - send() + send(ctx) }). Return(replyMsg, nil) mdi.On("InsertMessageLocal", ctx, mock.Anything).Return(nil) diff --git a/internal/privatemessaging/message.go b/internal/privatemessaging/message.go index bc56ac1487..340b2ef344 100644 --- a/internal/privatemessaging/message.go +++ b/internal/privatemessaging/message.go @@ -53,9 +53,7 @@ func (pm *privateMessaging) RequestReply(ctx context.Context, ns string, in *fft return nil, i18n.NewError(ctx, i18n.MsgRequestCannotHaveCID) } message := pm.NewMessage(ns, in) - return pm.syncasync.RequestReply(ctx, ns, in.Header.ID, func() error { - return message.Send(ctx) - }) + return pm.syncasync.RequestReply(ctx, ns, in.Header.ID, message.Send) } type messageSender struct { @@ -140,9 +138,7 @@ func (s *messageSender) sendInternal(ctx context.Context, waitConfirm bool) erro if waitConfirm && !immediateConfirm { // Pass it to the sync-async handler to wait for the confirmation to come back in. // NOTE: Our caller makes sure we are not in a RunAsGroup (which would be bad) - out, err := s.mgr.syncasync.SendConfirm(ctx, s.namespace, s.msg.Header.ID, func() error { - return s.Send(ctx) - }) + out, err := s.mgr.syncasync.SendConfirm(ctx, s.namespace, s.msg.Header.ID, s.Send) if out != nil { s.msg.Message = *out } diff --git a/internal/privatemessaging/message_test.go b/internal/privatemessaging/message_test.go index 48ae6bd6fa..9d10872378 100644 --- a/internal/privatemessaging/message_test.go +++ b/internal/privatemessaging/message_test.go @@ -74,7 +74,7 @@ func TestSendConfirmMessageE2EOk(t *testing.T) { msa.On("SendConfirm", pm.ctx, "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { send := args[3].(syncasync.RequestSender) - send() + send(pm.ctx) }). Return(retMsg, nil).Once() mdi.On("InsertMessageLocal", pm.ctx, mock.Anything).Return(nil).Once() @@ -730,7 +730,7 @@ func TestRequestReplySuccess(t *testing.T) { msa.On("RequestReply", pm.ctx, "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { send := args[3].(syncasync.RequestSender) - send() + send(pm.ctx) }). Return(nil, nil) diff --git a/internal/syncasync/sync_async_bridge.go b/internal/syncasync/sync_async_bridge.go index 48c9dd311b..391647c243 100644 --- a/internal/syncasync/sync_async_bridge.go +++ b/internal/syncasync/sync_async_bridge.go @@ -45,7 +45,7 @@ type Bridge interface { SendConfirmTokenTransfer(ctx context.Context, ns string, id *fftypes.UUID, send RequestSender) (*fftypes.TokenTransfer, error) } -type RequestSender func() error +type RequestSender func(ctx context.Context) error type requestType int @@ -314,7 +314,7 @@ func (sa *syncAsyncBridge) sendAndWait(ctx context.Context, ns string, id *fftyp } }() - err = send() + err = send(ctx) if err != nil { return nil, err } diff --git a/internal/syncasync/sync_async_bridge_test.go b/internal/syncasync/sync_async_bridge_test.go index 2e4cb3275e..a0f8508295 100644 --- a/internal/syncasync/sync_async_bridge_test.go +++ b/internal/syncasync/sync_async_bridge_test.go @@ -73,7 +73,7 @@ func TestRequestReplyOk(t *testing.T) { {ID: dataID, Value: fftypes.Byteable(`"response data"`)}, }, true, nil) - reply, err := sa.RequestReply(sa.ctx, "ns1", requestID, func() error { + reply, err := sa.RequestReply(sa.ctx, "ns1", requestID, func(ctx context.Context) error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -121,7 +121,7 @@ func TestAwaitConfirmationOk(t *testing.T) { {ID: dataID, Value: fftypes.Byteable(`"response data"`)}, }, true, nil) - reply, err := sa.SendConfirm(sa.ctx, "ns1", requestID, func() error { + reply, err := sa.SendConfirm(sa.ctx, "ns1", requestID, func(ctx context.Context) error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -168,7 +168,7 @@ func TestAwaitConfirmationRejected(t *testing.T) { {ID: dataID, Value: fftypes.Byteable(`"response data"`)}, }, true, nil) - _, err := sa.SendConfirm(sa.ctx, "ns1", requestID, func() error { + _, err := sa.SendConfirm(sa.ctx, "ns1", requestID, func(ctx context.Context) error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -192,7 +192,7 @@ func TestRequestReplyTimeout(t *testing.T) { mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) - _, err := sa.RequestReply(sa.ctx, "ns1", fftypes.NewUUID(), func() error { + _, err := sa.RequestReply(sa.ctx, "ns1", fftypes.NewUUID(), func(ctx context.Context) error { return nil }) assert.Regexp(t, "FF10260", err) @@ -206,7 +206,7 @@ func TestRequestSetupSystemListenerFail(t *testing.T) { mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(fmt.Errorf("pop")) - _, err := sa.RequestReply(sa.ctx, "ns1", fftypes.NewUUID(), func() error { + _, err := sa.RequestReply(sa.ctx, "ns1", fftypes.NewUUID(), func(ctx context.Context) error { return nil }) assert.Regexp(t, "pop", err) @@ -516,7 +516,7 @@ func TestAwaitTokenPoolConfirmation(t *testing.T) { } } - reply, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", requestID, func() error { + reply, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", requestID, func(ctx context.Context) error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -542,7 +542,7 @@ func TestAwaitTokenPoolConfirmationSendFail(t *testing.T) { mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) - _, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", fftypes.NewUUID(), func() error { + _, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", fftypes.NewUUID(), func(ctx context.Context) error { return fmt.Errorf("pop") }) assert.EqualError(t, err, "pop") @@ -571,7 +571,7 @@ func TestAwaitTokenPoolConfirmationRejected(t *testing.T) { } } - _, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", requestID, func() error { + _, err := sa.SendConfirmTokenPool(sa.ctx, "ns1", requestID, func(ctx context.Context) error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -610,7 +610,7 @@ func TestAwaitTokenTransferConfirmation(t *testing.T) { } } - reply, err := sa.SendConfirmTokenTransfer(sa.ctx, "ns1", requestID, func() error { + reply, err := sa.SendConfirmTokenTransfer(sa.ctx, "ns1", requestID, func(ctx context.Context) error { go func() { sa.eventCallback(&fftypes.EventDelivery{ Event: fftypes.Event{ @@ -636,7 +636,7 @@ func TestAwaitTokenTransferConfirmationSendFail(t *testing.T) { mse := sa.sysevents.(*sysmessagingmocks.SystemEvents) mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) - _, err := sa.SendConfirmTokenTransfer(sa.ctx, "ns1", fftypes.NewUUID(), func() error { + _, err := sa.SendConfirmTokenTransfer(sa.ctx, "ns1", fftypes.NewUUID(), func(ctx context.Context) error { return fmt.Errorf("pop") }) assert.EqualError(t, err, "pop") From aafc76c33ee9ed49c07f2364523c84bf4f3280b9 Mon Sep 17 00:00:00 2001 From: Andrew Richardson Date: Fri, 15 Oct 2021 15:06:49 -0400 Subject: [PATCH 8/8] Fix logic for transfer+message with waitConfirm=true Ensure the pieces are sent in the correct order and wait for both to be confirmed. Signed-off-by: Andrew Richardson --- internal/assets/manager.go | 49 ++++++---- internal/assets/manager_test.go | 110 +++++++++++++++++++++- internal/broadcast/message.go | 10 +- internal/broadcast/message_test.go | 8 +- internal/privatemessaging/message.go | 10 +- internal/privatemessaging/message_test.go | 8 +- internal/sysmessaging/message_sender.go | 4 +- mocks/sysmessagingmocks/message_sender.go | 6 +- 8 files changed, 161 insertions(+), 44 deletions(-) diff --git a/internal/assets/manager.go b/internal/assets/manager.go index 592dbc165a..33f9bd6ded 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -179,10 +179,8 @@ func (am *assetManager) createTokenPoolWithID(ctx context.Context, id *fftypes.U pool.ID = id pool.Namespace = ns - pool.TX = fftypes.TransactionRef{ - ID: tx.ID, - Type: tx.Subject.Type, - } + pool.TX.ID = tx.ID + pool.TX.Type = tx.Subject.Type op := fftypes.NewTXOperation( plugin, @@ -274,7 +272,7 @@ type transferSender struct { typeName string poolName string transfer *fftypes.TokenTransferInput - sealCallback sysmessaging.SealCallback + sendCallback sysmessaging.BeforeSendCallback } func (s *transferSender) Send(ctx context.Context) error { @@ -285,8 +283,8 @@ func (s *transferSender) SendAndWait(ctx context.Context) error { return s.resolveAndSend(ctx, true) } -func (s *transferSender) AfterSeal(cb sysmessaging.SealCallback) sysmessaging.MessageSender { - s.sealCallback = cb +func (s *transferSender) BeforeSend(cb sysmessaging.BeforeSendCallback) sysmessaging.MessageSender { + s.sendCallback = cb return s } @@ -379,12 +377,29 @@ func (s *transferSender) resolveAndSend(ctx context.Context, waitConfirm bool) ( } s.transfer.PoolProtocolID = pool.ProtocolID - if waitConfirm { - return s.sendSync(ctx) + var messageSender sysmessaging.MessageSender + if s.transfer.Message != nil { + if messageSender, err = s.buildTransferMessage(ctx, s.namespace, s.transfer.Message); err != nil { + return err + } } - if s.transfer.Message != nil { - if err := s.sendTransferMessage(ctx, s.namespace, s.transfer.Message); err != nil { + switch { + case waitConfirm && messageSender != nil: + // prepare the message, send the transfer async, then send the message and wait + return messageSender. + BeforeSend(func(ctx context.Context) error { + s.transfer.MessageHash = s.transfer.Message.Hash + s.transfer.Message = nil + return s.Send(ctx) + }). + SendAndWait(ctx) + case waitConfirm: + // no message - just send the transfer and wait + return s.sendSync(ctx) + case messageSender != nil: + // send the message async and then move on to the transfer + if err := messageSender.Send(ctx); err != nil { return err } s.transfer.MessageHash = s.transfer.Message.Hash @@ -423,8 +438,8 @@ func (s *transferSender) resolveAndSend(ctx context.Context, waitConfirm bool) ( return err } - if s.sealCallback != nil { - if err := s.sealCallback(ctx); err != nil { + if s.sendCallback != nil { + if err := s.sendCallback(ctx); err != nil { return err } } @@ -449,7 +464,7 @@ func (s *transferSender) sendSync(ctx context.Context) error { return err } -func (s *transferSender) sendTransferMessage(ctx context.Context, ns string, in *fftypes.MessageInOut) error { +func (s *transferSender) buildTransferMessage(ctx context.Context, ns string, in *fftypes.MessageInOut) (sysmessaging.MessageSender, error) { allowedTypes := []fftypes.FFEnum{ fftypes.MessageTypeTransferBroadcast, fftypes.MessageTypeTransferPrivate, @@ -459,11 +474,11 @@ func (s *transferSender) sendTransferMessage(ctx context.Context, ns string, in } switch in.Header.Type { case fftypes.MessageTypeTransferBroadcast: - return s.mgr.broadcast.NewBroadcast(ns, in).Send(ctx) + return s.mgr.broadcast.NewBroadcast(ns, in), nil case fftypes.MessageTypeTransferPrivate: - return s.mgr.messaging.NewMessage(ns, in).Send(ctx) + return s.mgr.messaging.NewMessage(ns, in), nil default: - return i18n.NewError(ctx, i18n.MsgInvalidMessageType, allowedTypes) + return nil, i18n.NewError(ctx, i18n.MsgInvalidMessageType, allowedTypes) } } diff --git a/internal/assets/manager_test.go b/internal/assets/manager_test.go index 01f0192438..09ee71c88b 100644 --- a/internal/assets/manager_test.go +++ b/internal/assets/manager_test.go @@ -21,6 +21,7 @@ import ( "github.com/hyperledger/firefly/internal/config" "github.com/hyperledger/firefly/internal/syncasync" + "github.com/hyperledger/firefly/internal/sysmessaging" "github.com/hyperledger/firefly/mocks/broadcastmocks" "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/datamocks" @@ -696,12 +697,16 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() + hash := fftypes.NewRandB32() transfer := &fftypes.TokenTransferInput{ TokenTransfer: fftypes.TokenTransfer{ From: "A", To: "B", }, Message: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Hash: hash, + }, InlineData: fftypes.InlineData{ { Value: []byte("test data"), @@ -728,6 +733,7 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.NoError(t, err) + assert.Equal(t, *hash, *transfer.MessageHash) mbm.AssertExpectations(t) mim.AssertExpectations(t) @@ -736,10 +742,48 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { mms.AssertExpectations(t) } +func TestTransferTokensWithBroadcastMessageFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mbm := am.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) + mms.On("Send", context.Background()).Return(fmt.Errorf("pop")) + + _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) + assert.EqualError(t, err, "pop") + + mbm.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mms.AssertExpectations(t) +} + func TestTransferTokensWithPrivateMessage(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() + hash := fftypes.NewRandB32() transfer := &fftypes.TokenTransferInput{ TokenTransfer: fftypes.TokenTransfer{ From: "A", @@ -750,6 +794,7 @@ func TestTransferTokensWithPrivateMessage(t *testing.T) { Header: fftypes.MessageHeader{ Type: fftypes.MessageTypeTransferPrivate, }, + Hash: hash, }, InlineData: fftypes.InlineData{ { @@ -777,6 +822,7 @@ func TestTransferTokensWithPrivateMessage(t *testing.T) { _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, false) assert.NoError(t, err) + assert.Equal(t, *hash, *transfer.MessageHash) mpm.AssertExpectations(t) mim.AssertExpectations(t) @@ -861,7 +907,7 @@ func TestTransferTokensConfirm(t *testing.T) { mti.AssertExpectations(t) } -func TestTransferTokensSealCallback(t *testing.T) { +func TestTransferTokensBeforeSendCallback(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -887,7 +933,7 @@ func TestTransferTokensSealCallback(t *testing.T) { sender := am.NewTransfer("ns1", "magic-tokens", "pool1", transfer) called := false - sender.AfterSeal(func(ctx context.Context) error { + sender.BeforeSend(func(ctx context.Context) error { called = true return nil }) @@ -901,7 +947,7 @@ func TestTransferTokensSealCallback(t *testing.T) { mti.AssertExpectations(t) } -func TestTransferTokensSealCallbackFail(t *testing.T) { +func TestTransferTokensBeforeSendCallbackFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -926,7 +972,7 @@ func TestTransferTokensSealCallbackFail(t *testing.T) { sender := am.NewTransfer("ns1", "magic-tokens", "pool1", transfer) called := false - sender.AfterSeal(func(ctx context.Context) error { + sender.BeforeSend(func(ctx context.Context) error { called = true return fmt.Errorf("pop") }) @@ -939,3 +985,59 @@ func TestTransferTokensSealCallbackFail(t *testing.T) { mdi.AssertExpectations(t) mti.AssertExpectations(t) } + +func TestTransferTokensWithBroadcastMessageConfirm(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + hash := fftypes.NewRandB32() + transfer := &fftypes.TokenTransferInput{ + TokenTransfer: fftypes.TokenTransfer{ + From: "A", + To: "B", + }, + Message: &fftypes.MessageInOut{ + Message: fftypes.Message{ + Hash: hash, + }, + InlineData: fftypes.InlineData{ + { + Value: []byte("test data"), + }, + }, + }, + } + transfer.Amount.Int().SetInt64(5) + + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mbm := am.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdi.On("GetTokenPool", context.Background(), "ns1", "pool1").Return(&fftypes.TokenPool{}, nil) + mti.On("TransferTokens", context.Background(), mock.Anything, &transfer.TokenTransfer).Return(nil) + mdi.On("UpsertOperation", context.Background(), mock.Anything, false).Return(nil) + mdi.On("UpsertTransaction", context.Background(), mock.MatchedBy(func(tx *fftypes.Transaction) bool { + return tx.Subject.Type == fftypes.TransactionTypeTokenTransfer + }), false).Return(nil) + mbm.On("NewBroadcast", "ns1", transfer.Message).Return(mms) + mms.On("BeforeSend", mock.Anything). + Run(func(args mock.Arguments) { + cb := args[0].(sysmessaging.BeforeSendCallback) + cb(context.Background()) + }). + Return(mms) + mms.On("SendAndWait", context.Background()).Return(nil) + + _, err := am.TransferTokens(context.Background(), "ns1", "magic-tokens", "pool1", transfer, true) + assert.NoError(t, err) + assert.Nil(t, transfer.Message) + assert.Equal(t, *hash, *transfer.MessageHash) + + mbm.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mti.AssertExpectations(t) + mms.AssertExpectations(t) +} diff --git a/internal/broadcast/message.go b/internal/broadcast/message.go index 260427695f..15d978b77a 100644 --- a/internal/broadcast/message.go +++ b/internal/broadcast/message.go @@ -50,7 +50,7 @@ type broadcastSender struct { namespace string msg *fftypes.MessageInOut resolved bool - sealCallback sysmessaging.SealCallback + sendCallback sysmessaging.BeforeSendCallback } func (s *broadcastSender) Send(ctx context.Context) error { @@ -61,8 +61,8 @@ func (s *broadcastSender) SendAndWait(ctx context.Context) error { return s.resolveAndSend(ctx, true) } -func (s *broadcastSender) AfterSeal(cb sysmessaging.SealCallback) sysmessaging.MessageSender { - s.sealCallback = cb +func (s *broadcastSender) BeforeSend(cb sysmessaging.BeforeSendCallback) sysmessaging.MessageSender { + s.sendCallback = cb return s } @@ -140,8 +140,8 @@ func (s *broadcastSender) sendInternal(ctx context.Context, waitConfirm bool) (e if err := s.msg.Seal(ctx); err != nil { return err } - if s.sealCallback != nil { - if err := s.sealCallback(ctx); err != nil { + if s.sendCallback != nil { + if err := s.sendCallback(ctx); err != nil { return err } } diff --git a/internal/broadcast/message_test.go b/internal/broadcast/message_test.go index 3319c9ca60..e00999e2d3 100644 --- a/internal/broadcast/message_test.go +++ b/internal/broadcast/message_test.go @@ -409,7 +409,7 @@ func TestPublishBlobsSendMessageFail(t *testing.T) { mim.AssertExpectations(t) } -func TestSealCallback(t *testing.T) { +func TestBeforeSendCallback(t *testing.T) { bm, cancel := newTestBroadcast(t) defer cancel() @@ -423,7 +423,7 @@ func TestSealCallback(t *testing.T) { }) called := false - message.AfterSeal(func(ctx context.Context) error { + message.BeforeSend(func(ctx context.Context) error { called = true return nil }) @@ -436,7 +436,7 @@ func TestSealCallback(t *testing.T) { assert.True(t, called) } -func TestSealCallbackFail(t *testing.T) { +func TestBeforeSendCallbackFail(t *testing.T) { bm, cancel := newTestBroadcast(t) defer cancel() @@ -450,7 +450,7 @@ func TestSealCallbackFail(t *testing.T) { }) called := false - message.AfterSeal(func(ctx context.Context) error { + message.BeforeSend(func(ctx context.Context) error { called = true return fmt.Errorf("pop") }) diff --git a/internal/privatemessaging/message.go b/internal/privatemessaging/message.go index 340b2ef344..b9379434a2 100644 --- a/internal/privatemessaging/message.go +++ b/internal/privatemessaging/message.go @@ -61,7 +61,7 @@ type messageSender struct { namespace string msg *fftypes.MessageInOut resolved bool - sealCallback sysmessaging.SealCallback + sendCallback sysmessaging.BeforeSendCallback } func (s *messageSender) Send(ctx context.Context) error { @@ -72,8 +72,8 @@ func (s *messageSender) SendAndWait(ctx context.Context) error { return s.resolveAndSend(ctx, true) } -func (s *messageSender) AfterSeal(cb sysmessaging.SealCallback) sysmessaging.MessageSender { - s.sealCallback = cb +func (s *messageSender) BeforeSend(cb sysmessaging.BeforeSendCallback) sysmessaging.MessageSender { + s.sendCallback = cb return s } @@ -149,8 +149,8 @@ func (s *messageSender) sendInternal(ctx context.Context, waitConfirm bool) erro if err := s.msg.Seal(ctx); err != nil { return err } - if s.sealCallback != nil { - if err := s.sealCallback(ctx); err != nil { + if s.sendCallback != nil { + if err := s.sendCallback(ctx); err != nil { return err } } diff --git a/internal/privatemessaging/message_test.go b/internal/privatemessaging/message_test.go index 9d10872378..eec5df50ea 100644 --- a/internal/privatemessaging/message_test.go +++ b/internal/privatemessaging/message_test.go @@ -332,7 +332,7 @@ func TestSealFail(t *testing.T) { } -func TestSealCallback(t *testing.T) { +func TestBeforeSendCallback(t *testing.T) { pm, cancel := newTestPrivateMessaging(t) defer cancel() @@ -347,7 +347,7 @@ func TestSealCallback(t *testing.T) { }) called := false - message.AfterSeal(func(ctx context.Context) error { + message.BeforeSend(func(ctx context.Context) error { called = true return nil }) @@ -361,7 +361,7 @@ func TestSealCallback(t *testing.T) { } -func TestSealCallbackFail(t *testing.T) { +func TestBeforeSendCallbackFail(t *testing.T) { pm, cancel := newTestPrivateMessaging(t) defer cancel() @@ -376,7 +376,7 @@ func TestSealCallbackFail(t *testing.T) { }) called := false - message.AfterSeal(func(ctx context.Context) error { + message.BeforeSend(func(ctx context.Context) error { called = true return fmt.Errorf("pop") }) diff --git a/internal/sysmessaging/message_sender.go b/internal/sysmessaging/message_sender.go index a2308b4749..603e266fab 100644 --- a/internal/sysmessaging/message_sender.go +++ b/internal/sysmessaging/message_sender.go @@ -18,10 +18,10 @@ package sysmessaging import "context" -type SealCallback func(ctx context.Context) error +type BeforeSendCallback func(ctx context.Context) error type MessageSender interface { Send(ctx context.Context) error SendAndWait(ctx context.Context) error - AfterSeal(cb SealCallback) MessageSender + BeforeSend(cb BeforeSendCallback) MessageSender } diff --git a/mocks/sysmessagingmocks/message_sender.go b/mocks/sysmessagingmocks/message_sender.go index e3df479422..96f3b1ee02 100644 --- a/mocks/sysmessagingmocks/message_sender.go +++ b/mocks/sysmessagingmocks/message_sender.go @@ -14,12 +14,12 @@ type MessageSender struct { mock.Mock } -// AfterSeal provides a mock function with given fields: cb -func (_m *MessageSender) AfterSeal(cb sysmessaging.SealCallback) sysmessaging.MessageSender { +// BeforeSend provides a mock function with given fields: cb +func (_m *MessageSender) BeforeSend(cb sysmessaging.BeforeSendCallback) sysmessaging.MessageSender { ret := _m.Called(cb) var r0 sysmessaging.MessageSender - if rf, ok := ret.Get(0).(func(sysmessaging.SealCallback) sysmessaging.MessageSender); ok { + if rf, ok := ret.Get(0).(func(sysmessaging.BeforeSendCallback) sysmessaging.MessageSender); ok { r0 = rf(cb) } else { if ret.Get(0) != nil {