diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index e7a1a0a59e..9b9112a699 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -4661,13 +4661,13 @@ paths: type: enum: - blockchain_batch_pin + - blockchain_invoke - publicstorage_batch_broadcast - dataexchange_batch_send - dataexchange_blob_send - token_create_pool - - token_announce_pool + - token_activate_pool - token_transfer - - contract_invoke type: string updated: {} type: object @@ -5423,13 +5423,13 @@ paths: type: enum: - blockchain_batch_pin + - blockchain_invoke - publicstorage_batch_broadcast - dataexchange_batch_send - dataexchange_blob_send - token_create_pool - - token_announce_pool + - token_activate_pool - token_transfer - - contract_invoke type: string updated: {} type: object @@ -5489,13 +5489,13 @@ paths: type: enum: - blockchain_batch_pin + - blockchain_invoke - publicstorage_batch_broadcast - dataexchange_batch_send - dataexchange_blob_send - token_create_pool - - token_announce_pool + - token_activate_pool - token_transfer - - contract_invoke type: string updated: {} type: object @@ -8063,13 +8063,13 @@ paths: type: enum: - blockchain_batch_pin + - blockchain_invoke - publicstorage_batch_broadcast - dataexchange_batch_send - dataexchange_blob_send - token_create_pool - - token_announce_pool + - token_activate_pool - token_transfer - - contract_invoke type: string updated: {} type: object diff --git a/internal/assets/token_pool.go b/internal/assets/token_pool.go index 42c928242f..c4ae5386ce 100644 --- a/internal/assets/token_pool.go +++ b/internal/assets/token_pool.go @@ -90,7 +90,13 @@ func (am *assetManager) createTokenPoolInternal(ctx context.Context, pool *fftyp return nil, err } - return pool, plugin.CreateTokenPool(ctx, op.ID, pool) + if complete, err := plugin.CreateTokenPool(ctx, op.ID, pool); err != nil { + return nil, err + } else if complete { + update := database.OperationQueryFactory.NewUpdate(ctx).Set("status", fftypes.OpStatusSucceeded) + return pool, am.database.UpdateOperation(ctx, op.ID, update) + } + return pool, nil } func (am *assetManager) ActivateTokenPool(ctx context.Context, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) error { @@ -98,7 +104,24 @@ func (am *assetManager) ActivateTokenPool(ctx context.Context, pool *fftypes.Tok if err != nil { return err } - return plugin.ActivateTokenPool(ctx, nil, pool, event) + + op := fftypes.NewOperation( + plugin, + pool.Namespace, + pool.TX.ID, + "", + fftypes.OpTypeTokenActivatePool) + if err := am.database.InsertOperation(ctx, op); err != nil { + return err + } + + if complete, err := plugin.ActivateTokenPool(ctx, op.ID, pool, event); err != nil { + return err + } else if complete { + update := database.OperationQueryFactory.NewUpdate(ctx).Set("status", fftypes.OpStatusSucceeded) + return am.database.UpdateOperation(ctx, op.ID, update) + } + return nil } func (am *assetManager) GetTokenPools(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenPool, *database.FilterResult, error) { diff --git a/internal/assets/token_pool_test.go b/internal/assets/token_pool_test.go index 9c675bba1f..a2568519b7 100644 --- a/internal/assets/token_pool_test.go +++ b/internal/assets/token_pool_test.go @@ -61,7 +61,7 @@ func TestCreateTokenPoolUnknownConnectorSuccess(t *testing.T) { mth := am.txHelper.(*txcommonmocks.Helper) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) - mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(nil) + mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(false, nil) mth.On("SubmitNewTransaction", context.Background(), "ns1", fftypes.TransactionTypeTokenPool).Return(fftypes.NewUUID(), nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) @@ -121,7 +121,7 @@ func TestCreateTokenPoolMissingNamespace(t *testing.T) { mth := am.txHelper.(*txcommonmocks.Helper) 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.Anything).Return(nil).Times(1) + mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything).Return(false, nil).Times(1) mth.On("SubmitNewTransaction", context.Background(), "ns1", fftypes.TransactionTypeTokenPool).Return(fftypes.NewUUID(), nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil).Times(1) msa.On("WaitForTokenPool", context.Background(), "ns1", mock.Anything, mock.Anything). @@ -209,7 +209,7 @@ func TestCreateTokenPoolFail(t *testing.T) { mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) mth.On("SubmitNewTransaction", context.Background(), "ns1", fftypes.TransactionTypeTokenPool).Return(fftypes.NewUUID(), nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) - mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(false, fmt.Errorf("pop")) _, err := am.CreateTokenPool(context.Background(), "ns1", pool, false) assert.Regexp(t, "pop", err) @@ -235,7 +235,7 @@ func TestCreateTokenPoolTransactionFail(t *testing.T) { assert.Regexp(t, "pop", err) } -func TestCreateTokenPoolOperationFail(t *testing.T) { +func TestCreateTokenPoolOpInsertFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -257,6 +257,31 @@ func TestCreateTokenPoolOperationFail(t *testing.T) { assert.Regexp(t, "pop", err) } +func TestCreateTokenPoolOpUpdateFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + pool := &fftypes.TokenPool{ + Connector: "magic-tokens", + Name: "testpool", + } + + mdi := am.database.(*databasemocks.Plugin) + mdm := am.data.(*datamocks.Manager) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mim := am.identity.(*identitymanagermocks.Manager) + mth := am.txHelper.(*txcommonmocks.Helper) + mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + mth.On("SubmitNewTransaction", context.Background(), "ns1", fftypes.TransactionTypeTokenPool).Return(fftypes.NewUUID(), nil) + mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) + mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(true, nil) + mdi.On("UpdateOperation", context.Background(), mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + + _, err := am.CreateTokenPool(context.Background(), "ns1", pool, false) + assert.Regexp(t, "pop", err) +} + func TestCreateTokenPoolSuccess(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -273,7 +298,7 @@ func TestCreateTokenPoolSuccess(t *testing.T) { mth := am.txHelper.(*txcommonmocks.Helper) mim.On("GetLocalOrganization", context.Background()).Return(&fftypes.Organization{Identity: "0x12345"}, nil) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) - mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(nil) + mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(false, nil) mth.On("SubmitNewTransaction", context.Background(), "ns1", fftypes.TransactionTypeTokenPool).Return(fftypes.NewUUID(), nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil) @@ -298,7 +323,7 @@ func TestCreateTokenPoolConfirm(t *testing.T) { mth := am.txHelper.(*txcommonmocks.Helper) 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.Anything).Return(nil).Times(1) + mti.On("CreateTokenPool", context.Background(), mock.Anything, mock.Anything).Return(false, nil).Times(1) mth.On("SubmitNewTransaction", context.Background(), "ns1", fftypes.TransactionTypeTokenPool).Return(fftypes.NewUUID(), nil) mdi.On("InsertOperation", context.Background(), mock.Anything).Return(nil).Times(1) msa.On("WaitForTokenPool", context.Background(), "ns1", mock.Anything, mock.Anything). @@ -323,9 +348,13 @@ func TestActivateTokenPool(t *testing.T) { ev := &fftypes.BlockchainEvent{} mdm := am.data.(*datamocks.Manager) + mdi := am.database.(*databasemocks.Plugin) mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) - mti.On("ActivateTokenPool", context.Background(), mock.Anything, pool, ev).Return(nil) + mdi.On("InsertOperation", context.Background(), mock.MatchedBy(func(op *fftypes.Operation) bool { + return op.Type == fftypes.OpTypeTokenActivatePool + })).Return(nil) + mti.On("ActivateTokenPool", context.Background(), mock.Anything, pool, ev).Return(false, nil) err := am.ActivateTokenPool(context.Background(), pool, ev) assert.NoError(t, err) @@ -348,6 +377,74 @@ func TestActivateTokenPoolBadConnector(t *testing.T) { assert.Regexp(t, "FF10272", err) } +func TestActivateTokenPoolOpInsertFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + pool := &fftypes.TokenPool{ + Namespace: "ns1", + Connector: "magic-tokens", + } + ev := &fftypes.BlockchainEvent{} + + mdm := am.data.(*datamocks.Manager) + mdi := am.database.(*databasemocks.Plugin) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + mdi.On("InsertOperation", context.Background(), mock.MatchedBy(func(op *fftypes.Operation) bool { + return op.Type == fftypes.OpTypeTokenActivatePool + })).Return(fmt.Errorf("pop")) + + err := am.ActivateTokenPool(context.Background(), pool, ev) + assert.EqualError(t, err, "pop") +} + +func TestActivateTokenPoolFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + pool := &fftypes.TokenPool{ + Namespace: "ns1", + Connector: "magic-tokens", + } + ev := &fftypes.BlockchainEvent{} + + mdm := am.data.(*datamocks.Manager) + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + mdi.On("InsertOperation", context.Background(), mock.MatchedBy(func(op *fftypes.Operation) bool { + return op.Type == fftypes.OpTypeTokenActivatePool + })).Return(nil) + mti.On("ActivateTokenPool", context.Background(), mock.Anything, pool, ev).Return(false, fmt.Errorf("pop")) + + err := am.ActivateTokenPool(context.Background(), pool, ev) + assert.EqualError(t, err, "pop") +} + +func TestActivateTokenPoolOpUpdateFail(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + pool := &fftypes.TokenPool{ + Namespace: "ns1", + Connector: "magic-tokens", + } + ev := &fftypes.BlockchainEvent{} + + mdm := am.data.(*datamocks.Manager) + mdi := am.database.(*databasemocks.Plugin) + mti := am.tokens["magic-tokens"].(*tokenmocks.Plugin) + mdm.On("VerifyNamespaceExists", context.Background(), "ns1").Return(nil) + mdi.On("InsertOperation", context.Background(), mock.MatchedBy(func(op *fftypes.Operation) bool { + return op.Type == fftypes.OpTypeTokenActivatePool + })).Return(nil) + mti.On("ActivateTokenPool", context.Background(), mock.Anything, pool, ev).Return(true, nil) + mdi.On("UpdateOperation", context.Background(), mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + + err := am.ActivateTokenPool(context.Background(), pool, ev) + assert.EqualError(t, err, "pop") +} + func TestGetTokenPool(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() diff --git a/internal/contracts/manager.go b/internal/contracts/manager.go index ec7031fb9a..acdda7b01e 100644 --- a/internal/contracts/manager.go +++ b/internal/contracts/manager.go @@ -188,7 +188,7 @@ func (cm *contractManager) InvokeContract(ctx context.Context, ns string, req *f ns, txid, "", - fftypes.OpTypeContractInvoke) + fftypes.OpTypeBlockchainInvoke) op.Input = req.Input return cm.database.InsertOperation(ctx, op) }) diff --git a/internal/contracts/manager_test.go b/internal/contracts/manager_test.go index 946317e16b..faee5e9f45 100644 --- a/internal/contracts/manager_test.go +++ b/internal/contracts/manager_test.go @@ -994,7 +994,7 @@ func TestInvokeContract(t *testing.T) { mim.On("ResolveSigningKey", mock.Anything, "").Return("key-resolved", nil) mdi.On("InsertOperation", mock.Anything, mock.MatchedBy(func(op *fftypes.Operation) bool { - return op.Namespace == "ns1" && op.Type == fftypes.OpTypeContractInvoke && op.Plugin == "mockblockchain" + return op.Namespace == "ns1" && op.Type == fftypes.OpTypeBlockchainInvoke && op.Plugin == "mockblockchain" })).Return(nil) mbi.On("InvokeContract", mock.Anything, mock.AnythingOfType("*fftypes.UUID"), "key-resolved", req.Location, req.Method, req.Input).Return(struct{}{}, nil) @@ -1170,7 +1170,7 @@ func TestQueryContract(t *testing.T) { mim.On("ResolveSigningKey", mock.Anything, "").Return("key-resolved", nil) mth.On("SubmitNewTransaction", mock.Anything, "ns1", fftypes.TransactionTypeContractInvoke).Return(fftypes.NewUUID(), nil) mdi.On("InsertOperation", mock.Anything, mock.MatchedBy(func(op *fftypes.Operation) bool { - return op.Namespace == "ns1" && op.Type == fftypes.OpTypeContractInvoke && op.Plugin == "mockblockchain" + return op.Namespace == "ns1" && op.Type == fftypes.OpTypeBlockchainInvoke && op.Plugin == "mockblockchain" })).Return(nil) mbi.On("QueryContract", mock.Anything, req.Location, req.Method, req.Input).Return(struct{}{}, nil) @@ -1200,7 +1200,7 @@ func TestCallContractInvalidType(t *testing.T) { mim.On("ResolveSigningKey", mock.Anything, "").Return("key-resolved", nil) mth.On("SubmitNewTransaction", mock.Anything, "ns1", fftypes.TransactionTypeContractInvoke).Return(fftypes.NewUUID(), nil) mdi.On("InsertOperation", mock.Anything, mock.MatchedBy(func(op *fftypes.Operation) bool { - return op.Namespace == "ns1" && op.Type == fftypes.OpTypeContractInvoke && op.Plugin == "mockblockchain" + return op.Namespace == "ns1" && op.Type == fftypes.OpTypeBlockchainInvoke && op.Plugin == "mockblockchain" })).Return(nil) assert.PanicsWithValue(t, "unknown call type: ", func() { @@ -1352,7 +1352,7 @@ func TestInvokeContractAPI(t *testing.T) { mdb.On("GetFFIMethod", mock.Anything, "ns1", mock.Anything, mock.Anything).Return(&fftypes.FFIMethod{Name: "peel"}, nil) mth.On("SubmitNewTransaction", mock.Anything, "ns1", fftypes.TransactionTypeContractInvoke).Return(fftypes.NewUUID(), nil) mdi.On("InsertOperation", mock.Anything, mock.MatchedBy(func(op *fftypes.Operation) bool { - return op.Namespace == "ns1" && op.Type == fftypes.OpTypeContractInvoke && op.Plugin == "mockblockchain" + return op.Namespace == "ns1" && op.Type == fftypes.OpTypeBlockchainInvoke && op.Plugin == "mockblockchain" })).Return(nil) mbi.On("InvokeContract", mock.Anything, mock.AnythingOfType("*fftypes.UUID"), "key-resolved", req.Location, mock.AnythingOfType("*fftypes.FFIMethod"), req.Input).Return(struct{}{}, nil) diff --git a/internal/definitions/definition_handler_tokenpool.go b/internal/definitions/definition_handler_tokenpool.go index 167632affc..24bbfaa153 100644 --- a/internal/definitions/definition_handler_tokenpool.go +++ b/internal/definitions/definition_handler_tokenpool.go @@ -24,35 +24,9 @@ import ( "github.com/hyperledger/firefly/pkg/fftypes" ) -func (dh *definitionHandlers) confirmPoolAnnounceOp(ctx context.Context, pool *fftypes.TokenPool) error { - // Find a matching operation within this transaction - fb := database.OperationQueryFactory.NewFilter(ctx) - filter := fb.And( - fb.Eq("tx", pool.TX.ID), - fb.Eq("type", fftypes.OpTypeTokenAnnouncePool), - ) - if operations, _, err := dh.database.GetOperations(ctx, filter); err != nil { - return err - } else if len(operations) > 0 { - op := operations[0] - update := database.OperationQueryFactory.NewUpdate(ctx). - Set("status", fftypes.OpStatusSucceeded). - Set("output", fftypes.JSONObject{"message": pool.Message}) - if err := dh.database.UpdateOperation(ctx, op.ID, update); err != nil { - return err - } - } - return nil -} - func (dh *definitionHandlers) persistTokenPool(ctx context.Context, announce *fftypes.TokenPoolAnnouncement) (valid bool, err error) { pool := announce.Pool - // Mark announce operation (if any) completed - if err := dh.confirmPoolAnnounceOp(ctx, pool); err != nil { - return false, err // retryable - } - // Create the pool in pending state pool.State = fftypes.TokenPoolStatePending err = dh.database.UpsertTokenPool(ctx, pool) diff --git a/internal/definitions/definition_handler_tokenpool_test.go b/internal/definitions/definition_handler_tokenpool_test.go index 53e8ff1500..6a7d4e370f 100644 --- a/internal/definitions/definition_handler_tokenpool_test.go +++ b/internal/definitions/definition_handler_tokenpool_test.go @@ -73,13 +73,9 @@ func TestHandleDefinitionBroadcastTokenPoolActivateOK(t *testing.T) { pool := announce.Pool msg, data, err := buildPoolDefinitionMessage(announce) assert.NoError(t, err) - opID := fftypes.NewUUID() - operations := []*fftypes.Operation{{ID: opID}} mdi := sh.database.(*databasemocks.Plugin) mam := sh.assets.(*assetmocks.Manager) - mdi.On("GetOperations", context.Background(), mock.Anything).Return(operations, nil, nil) - mdi.On("UpdateOperation", context.Background(), opID, mock.Anything).Return(nil) mdi.On("GetTokenPoolByID", context.Background(), pool.ID).Return(nil, nil) mdi.On("UpsertTokenPool", context.Background(), mock.MatchedBy(func(p *fftypes.TokenPool) bool { return *p.ID == *pool.ID && p.Message == msg.Header.ID @@ -96,28 +92,6 @@ func TestHandleDefinitionBroadcastTokenPoolActivateOK(t *testing.T) { mdi.AssertExpectations(t) } -func TestHandleDefinitionBroadcastTokenPoolUpdateOpFail(t *testing.T) { - sh := newTestDefinitionHandlers(t) - - announce := newPoolAnnouncement() - pool := announce.Pool - msg, data, err := buildPoolDefinitionMessage(announce) - assert.NoError(t, err) - opID := fftypes.NewUUID() - operations := []*fftypes.Operation{{ID: opID}} - - mdi := sh.database.(*databasemocks.Plugin) - mdi.On("GetOperations", context.Background(), mock.Anything).Return(operations, nil, nil) - mdi.On("UpdateOperation", context.Background(), opID, mock.Anything).Return(fmt.Errorf("pop")) - mdi.On("GetTokenPoolByID", context.Background(), pool.ID).Return(nil, nil) - - action, _, err := sh.HandleDefinitionBroadcast(context.Background(), msg, data) - assert.Equal(t, ActionRetry, action) - assert.EqualError(t, err, "pop") - - mdi.AssertExpectations(t) -} - func TestHandleDefinitionBroadcastTokenPoolGetPoolFail(t *testing.T) { sh := newTestDefinitionHandlers(t) @@ -143,11 +117,9 @@ func TestHandleDefinitionBroadcastTokenPoolExisting(t *testing.T) { pool := announce.Pool msg, data, err := buildPoolDefinitionMessage(announce) assert.NoError(t, err) - operations := []*fftypes.Operation{} mdi := sh.database.(*databasemocks.Plugin) mam := sh.assets.(*assetmocks.Manager) - mdi.On("GetOperations", context.Background(), mock.Anything).Return(operations, nil, nil) mdi.On("GetTokenPoolByID", context.Background(), pool.ID).Return(&fftypes.TokenPool{}, nil) mdi.On("UpsertTokenPool", context.Background(), mock.MatchedBy(func(p *fftypes.TokenPool) bool { return *p.ID == *pool.ID && p.Message == msg.Header.ID @@ -189,12 +161,8 @@ func TestHandleDefinitionBroadcastTokenPoolIDMismatch(t *testing.T) { pool := announce.Pool msg, data, err := buildPoolDefinitionMessage(announce) assert.NoError(t, err) - opID := fftypes.NewUUID() - operations := []*fftypes.Operation{{ID: opID}} mdi := sh.database.(*databasemocks.Plugin) - mdi.On("GetOperations", context.Background(), mock.Anything).Return(operations, nil, nil) - mdi.On("UpdateOperation", context.Background(), opID, mock.Anything).Return(nil) mdi.On("GetTokenPoolByID", context.Background(), pool.ID).Return(nil, nil) mdi.On("UpsertTokenPool", context.Background(), mock.MatchedBy(func(p *fftypes.TokenPool) bool { return *p.ID == *pool.ID && p.Message == msg.Header.ID @@ -217,12 +185,8 @@ func TestHandleDefinitionBroadcastTokenPoolFailUpsert(t *testing.T) { pool := announce.Pool msg, data, err := buildPoolDefinitionMessage(announce) assert.NoError(t, err) - opID := fftypes.NewUUID() - operations := []*fftypes.Operation{{ID: opID}} mdi := sh.database.(*databasemocks.Plugin) - mdi.On("GetOperations", context.Background(), mock.Anything).Return(operations, nil, nil) - mdi.On("UpdateOperation", context.Background(), opID, mock.Anything).Return(nil) mdi.On("GetTokenPoolByID", context.Background(), pool.ID).Return(nil, nil) mdi.On("UpsertTokenPool", context.Background(), mock.MatchedBy(func(p *fftypes.TokenPool) bool { return *p.ID == *pool.ID && p.Message == msg.Header.ID @@ -235,25 +199,6 @@ func TestHandleDefinitionBroadcastTokenPoolFailUpsert(t *testing.T) { mdi.AssertExpectations(t) } -func TestHandleDefinitionBroadcastTokenPoolOpsFail(t *testing.T) { - sh := newTestDefinitionHandlers(t) - - announce := newPoolAnnouncement() - pool := announce.Pool - msg, data, err := buildPoolDefinitionMessage(announce) - assert.NoError(t, err) - - mdi := sh.database.(*databasemocks.Plugin) - mdi.On("GetOperations", context.Background(), mock.Anything).Return(nil, nil, fmt.Errorf("pop")) - mdi.On("GetTokenPoolByID", context.Background(), pool.ID).Return(nil, nil) - - action, _, err := sh.HandleDefinitionBroadcast(context.Background(), msg, data) - assert.Equal(t, ActionRetry, action) - assert.EqualError(t, err, "pop") - - mdi.AssertExpectations(t) -} - func TestHandleDefinitionBroadcastTokenPoolActivateFail(t *testing.T) { sh := newTestDefinitionHandlers(t) @@ -261,13 +206,9 @@ func TestHandleDefinitionBroadcastTokenPoolActivateFail(t *testing.T) { pool := announce.Pool msg, data, err := buildPoolDefinitionMessage(announce) assert.NoError(t, err) - opID := fftypes.NewUUID() - operations := []*fftypes.Operation{{ID: opID}} mdi := sh.database.(*databasemocks.Plugin) mam := sh.assets.(*assetmocks.Manager) - mdi.On("GetOperations", context.Background(), mock.Anything).Return(operations, nil, nil) - mdi.On("UpdateOperation", context.Background(), opID, mock.Anything).Return(nil) mdi.On("GetTokenPoolByID", context.Background(), pool.ID).Return(nil, nil) mdi.On("UpsertTokenPool", context.Background(), mock.MatchedBy(func(p *fftypes.TokenPool) bool { return *p.ID == *pool.ID && p.Message == msg.Header.ID diff --git a/internal/events/token_pool_created.go b/internal/events/token_pool_created.go index 20f0a2ab3c..9822c8e6e7 100644 --- a/internal/events/token_pool_created.go +++ b/internal/events/token_pool_created.go @@ -91,7 +91,7 @@ func (em *eventManager) shouldConfirm(ctx context.Context, pool *tokens.TokenPoo return existingPool, nil } -func (em *eventManager) shouldAnnounce(ctx context.Context, ti tokens.Plugin, pool *tokens.TokenPool) (announcePool *fftypes.TokenPool, err error) { +func (em *eventManager) shouldAnnounce(ctx context.Context, pool *tokens.TokenPool) (announcePool *fftypes.TokenPool, err error) { op, err := em.findTokenPoolCreateOp(ctx, pool.TransactionID) if err != nil { return nil, err @@ -105,14 +105,7 @@ func (em *eventManager) shouldAnnounce(ctx context.Context, ti tokens.Plugin, po return nil, nil } addPoolDetailsFromPlugin(announcePool, pool) - - nextOp := fftypes.NewOperation( - ti, - op.Namespace, - op.Transaction, - "", - fftypes.OpTypeTokenAnnouncePool) - return announcePool, em.database.InsertOperation(ctx, nextOp) + return announcePool, nil } // It is expected that this method might be invoked twice for each pool, depending on the behavior of the connector. @@ -148,7 +141,7 @@ func (em *eventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPoo } // See if this pool was submitted locally and needs to be announced - if announcePool, err = em.shouldAnnounce(ctx, ti, pool); err != nil { + if announcePool, err = em.shouldAnnounce(ctx, pool); err != nil { return err } else if announcePool != nil { return nil // trigger announce after completion of database transaction diff --git a/internal/events/token_pool_created_test.go b/internal/events/token_pool_created_test.go index 48a3a95212..d58bc853d8 100644 --- a/internal/events/token_pool_created_test.go +++ b/internal/events/token_pool_created_test.go @@ -375,13 +375,9 @@ func TestTokenPoolCreatedAnnounce(t *testing.T) { }, } - mti.On("Name").Return("mock-tokens") mdi.On("GetTokenPoolByProtocolID", em.ctx, "erc1155", "123").Return(nil, nil).Times(2) mdi.On("GetOperations", em.ctx, mock.Anything).Return(nil, nil, fmt.Errorf("pop")).Once() mdi.On("GetOperations", em.ctx, mock.Anything).Return(operations, nil, nil).Once() - mdi.On("InsertOperation", em.ctx, mock.MatchedBy(func(op *fftypes.Operation) bool { - return op.Type == fftypes.OpTypeTokenAnnouncePool - })).Return(nil) mbm.On("BroadcastTokenPool", em.ctx, "test-ns", mock.MatchedBy(func(pool *fftypes.TokenPoolAnnouncement) bool { return pool.Pool.Namespace == "test-ns" && pool.Pool.Name == "my-pool" && *pool.Pool.ID == *poolID }), false).Return(nil, nil) diff --git a/internal/orchestrator/txn_status_test.go b/internal/orchestrator/txn_status_test.go index 435f390efb..298366be73 100644 --- a/internal/orchestrator/txn_status_test.go +++ b/internal/orchestrator/txn_status_test.go @@ -539,7 +539,7 @@ func TestGetTransactionStatusContractInvokeSuccess(t *testing.T) { { Status: fftypes.OpStatusSucceeded, ID: fftypes.NewUUID(), - Type: fftypes.OpTypeContractInvoke, + Type: fftypes.OpTypeBlockchainInvoke, Updated: fftypes.UnixTime(0), Output: fftypes.JSONObject{"transactionHash": "0x100"}, }, @@ -558,7 +558,7 @@ func TestGetTransactionStatusContractInvokeSuccess(t *testing.T) { "details": [ { "type": "Operation", - "subtype": "contract_invoke", + "subtype": "blockchain_invoke", "status": "Succeeded", "timestamp": "1970-01-01T00:00:00Z", "id": "` + ops[0].ID.String() + `", diff --git a/internal/tokens/fftokens/fftokens.go b/internal/tokens/fftokens/fftokens.go index 7db99b9f5e..ce24ef958b 100644 --- a/internal/tokens/fftokens/fftokens.go +++ b/internal/tokens/fftokens/fftokens.go @@ -370,7 +370,7 @@ func (ft *FFTokens) eventLoop() { } } -func (ft *FFTokens) CreateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool) error { +func (ft *FFTokens) CreateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool) (complete bool, err error) { data, _ := json.Marshal(tokenData{ TX: pool.TX.ID, }) @@ -386,20 +386,20 @@ func (ft *FFTokens) CreateTokenPool(ctx context.Context, opID *fftypes.UUID, poo }). Post("/api/v1/createpool") if err != nil || !res.IsSuccess() { - return restclient.WrapRestErr(ctx, res, err, i18n.MsgTokensRESTErr) + return false, restclient.WrapRestErr(ctx, res, err, i18n.MsgTokensRESTErr) } if res.StatusCode() == 200 { // Handle synchronous response (202 will be handled by later websocket listener) var obj fftypes.JSONObject if err := json.Unmarshal(res.Body(), &obj); err != nil { - return i18n.WrapError(ctx, err, i18n.MsgJSONObjectParseFailed, res.Body()) + return false, i18n.WrapError(ctx, err, i18n.MsgJSONObjectParseFailed, res.Body()) } - return ft.handleTokenPoolCreate(ctx, obj) + return true, ft.handleTokenPoolCreate(ctx, obj) } - return nil + return false, nil } -func (ft *FFTokens) ActivateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) error { +func (ft *FFTokens) ActivateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) (complete bool, err error) { res, err := ft.client.R().SetContext(ctx). SetBody(&activatePool{ RequestID: opID.String(), @@ -408,17 +408,17 @@ func (ft *FFTokens) ActivateTokenPool(ctx context.Context, opID *fftypes.UUID, p }). Post("/api/v1/activatepool") if err != nil || !res.IsSuccess() { - return restclient.WrapRestErr(ctx, res, err, i18n.MsgTokensRESTErr) + return false, restclient.WrapRestErr(ctx, res, err, i18n.MsgTokensRESTErr) } if res.StatusCode() == 200 { // Handle synchronous response (202 will be handled by later websocket listener) var obj fftypes.JSONObject if err := json.Unmarshal(res.Body(), &obj); err != nil { - return i18n.WrapError(ctx, err, i18n.MsgJSONObjectParseFailed, res.Body()) + return false, i18n.WrapError(ctx, err, i18n.MsgJSONObjectParseFailed, res.Body()) } - return ft.handleTokenPoolCreate(ctx, obj) + return true, ft.handleTokenPoolCreate(ctx, obj) } - return nil + return false, nil } func (ft *FFTokens) MintTokens(ctx context.Context, opID *fftypes.UUID, poolProtocolID string, mint *fftypes.TokenTransfer) error { diff --git a/internal/tokens/fftokens/fftokens_test.go b/internal/tokens/fftokens/fftokens_test.go index 9b0b71624f..75746dea38 100644 --- a/internal/tokens/fftokens/fftokens_test.go +++ b/internal/tokens/fftokens/fftokens_test.go @@ -143,7 +143,8 @@ func TestCreateTokenPool(t *testing.T) { return res, nil }) - err := h.CreateTokenPool(context.Background(), opID, pool) + complete, err := h.CreateTokenPool(context.Background(), opID, pool) + assert.False(t, complete) assert.NoError(t, err) } @@ -162,7 +163,8 @@ func TestCreateTokenPoolError(t *testing.T) { httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/createpool", httpURL), httpmock.NewJsonResponderOrPanic(500, fftypes.JSONObject{})) - err := h.CreateTokenPool(context.Background(), fftypes.NewUUID(), pool) + complete, err := h.CreateTokenPool(context.Background(), fftypes.NewUUID(), pool) + assert.False(t, complete) assert.Regexp(t, "FF10274", err) } @@ -214,7 +216,8 @@ func TestCreateTokenPoolSynchronous(t *testing.T) { return p.ProtocolID == "F1" && p.Type == fftypes.TokenTypeFungible && *p.TransactionID == *pool.TX.ID && p.Event.ProtocolID == "000000000010/000020/000030/000040" })).Return(nil) - err := h.CreateTokenPool(context.Background(), opID, pool) + complete, err := h.CreateTokenPool(context.Background(), opID, pool) + assert.True(t, complete) assert.NoError(t, err) } @@ -255,7 +258,8 @@ func TestCreateTokenPoolSynchronousBadResponse(t *testing.T) { return res, nil }) - err := h.CreateTokenPool(context.Background(), opID, pool) + complete, err := h.CreateTokenPool(context.Background(), opID, pool) + assert.False(t, complete) assert.Regexp(t, "FF10151", err) } @@ -295,7 +299,8 @@ func TestActivateTokenPool(t *testing.T) { return res, nil }) - err := h.ActivateTokenPool(context.Background(), opID, pool, ev) + complete, err := h.ActivateTokenPool(context.Background(), opID, pool, ev) + assert.False(t, complete) assert.NoError(t, err) } @@ -315,7 +320,8 @@ func TestActivateTokenPoolError(t *testing.T) { httpmock.RegisterResponder("POST", fmt.Sprintf("%s/api/v1/activatepool", httpURL), httpmock.NewJsonResponderOrPanic(500, fftypes.JSONObject{})) - err := h.ActivateTokenPool(context.Background(), fftypes.NewUUID(), pool, ev) + complete, err := h.ActivateTokenPool(context.Background(), fftypes.NewUUID(), pool, ev) + assert.False(t, complete) assert.Regexp(t, "FF10274", err) } @@ -364,7 +370,8 @@ func TestActivateTokenPoolSynchronous(t *testing.T) { return p.ProtocolID == "F1" && p.Type == fftypes.TokenTypeFungible && p.TransactionID == nil && p.Event.ProtocolID == "" })).Return(nil) - err := h.ActivateTokenPool(context.Background(), opID, pool, ev) + complete, err := h.ActivateTokenPool(context.Background(), opID, pool, ev) + assert.True(t, complete) assert.NoError(t, err) } @@ -409,7 +416,8 @@ func TestActivateTokenPoolSynchronousBadResponse(t *testing.T) { return p.ProtocolID == "F1" && p.Type == fftypes.TokenTypeFungible && p.TransactionID == nil && p.Event.ProtocolID == "" })).Return(nil) - err := h.ActivateTokenPool(context.Background(), opID, pool, ev) + complete, err := h.ActivateTokenPool(context.Background(), opID, pool, ev) + assert.False(t, complete) assert.Regexp(t, "FF10151", err) } diff --git a/mocks/tokenmocks/plugin.go b/mocks/tokenmocks/plugin.go index f8b7475761..24903df2f7 100644 --- a/mocks/tokenmocks/plugin.go +++ b/mocks/tokenmocks/plugin.go @@ -20,17 +20,24 @@ type Plugin struct { } // ActivateTokenPool provides a mock function with given fields: ctx, opID, pool, event -func (_m *Plugin) ActivateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) error { +func (_m *Plugin) ActivateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) (bool, error) { ret := _m.Called(ctx, opID, pool, event) - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, *fftypes.TokenPool, *fftypes.BlockchainEvent) error); ok { + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, *fftypes.TokenPool, *fftypes.BlockchainEvent) bool); ok { r0 = rf(ctx, opID, pool, event) } else { - r0 = ret.Error(0) + r0 = ret.Get(0).(bool) } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *fftypes.UUID, *fftypes.TokenPool, *fftypes.BlockchainEvent) error); ok { + r1 = rf(ctx, opID, pool, event) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // BurnTokens provides a mock function with given fields: ctx, opID, poolProtocolID, burn @@ -64,17 +71,24 @@ func (_m *Plugin) Capabilities() *tokens.Capabilities { } // CreateTokenPool provides a mock function with given fields: ctx, opID, pool -func (_m *Plugin) CreateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool) error { +func (_m *Plugin) CreateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool) (bool, error) { ret := _m.Called(ctx, opID, pool) - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, *fftypes.TokenPool) error); ok { + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, *fftypes.TokenPool) bool); ok { r0 = rf(ctx, opID, pool) } else { - r0 = ret.Error(0) + r0 = ret.Get(0).(bool) } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *fftypes.UUID, *fftypes.TokenPool) error); ok { + r1 = rf(ctx, opID, pool) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // Init provides a mock function with given fields: ctx, name, prefix, callbacks diff --git a/pkg/fftypes/operation.go b/pkg/fftypes/operation.go index 463cd16669..4932cc018c 100644 --- a/pkg/fftypes/operation.go +++ b/pkg/fftypes/operation.go @@ -24,6 +24,8 @@ type OpType = FFEnum var ( // OpTypeBlockchainBatchPin is a blockchain transaction to pin a batch OpTypeBlockchainBatchPin OpType = ffEnum("optype", "blockchain_batch_pin") + // OpTypeBlockchainInvoke is a smart contract invoke + OpTypeBlockchainInvoke OpType = ffEnum("optype", "blockchain_invoke") // OpTypePublicStorageBatchBroadcast is a public storage operation to store broadcast data OpTypePublicStorageBatchBroadcast OpType = ffEnum("optype", "publicstorage_batch_broadcast") // OpTypeDataExchangeBatchSend is a private send @@ -32,12 +34,10 @@ var ( OpTypeDataExchangeBlobSend OpType = ffEnum("optype", "dataexchange_blob_send") // OpTypeTokenCreatePool is a token pool creation OpTypeTokenCreatePool OpType = ffEnum("optype", "token_create_pool") - // OpTypeTokenAnnouncePool is a broadcast of token pool info - OpTypeTokenAnnouncePool OpType = ffEnum("optype", "token_announce_pool") + // OpTypeTokenActivatePool is a token pool activation + OpTypeTokenActivatePool OpType = ffEnum("optype", "token_activate_pool") // OpTypeTokenTransfer is a token transfer OpTypeTokenTransfer OpType = ffEnum("optype", "token_transfer") - // OpTypeContractInvoke is a smart contract invoke - OpTypeContractInvoke OpType = ffEnum("optype", "contract_invoke") ) // OpStatus is the current status of an operation diff --git a/pkg/tokens/plugin.go b/pkg/tokens/plugin.go index 3a23e73db7..706b0b2e65 100644 --- a/pkg/tokens/plugin.go +++ b/pkg/tokens/plugin.go @@ -42,10 +42,10 @@ type Plugin interface { Capabilities() *Capabilities // CreateTokenPool creates a new (fungible or non-fungible) pool of tokens - CreateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool) error + CreateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool) (complete bool, err error) // ActivateTokenPool activates a pool in order to begin receiving events - ActivateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) error + ActivateTokenPool(ctx context.Context, opID *fftypes.UUID, pool *fftypes.TokenPool, event *fftypes.BlockchainEvent) (complete bool, err error) // MintTokens mints new tokens in a pool and adds them to the recipient's account MintTokens(ctx context.Context, opID *fftypes.UUID, poolProtocolID string, mint *fftypes.TokenTransfer) error