From ef3ccb18d3754bb4ff1f6df2f5e05c4137802bf9 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Mon, 15 Nov 2021 16:44:56 -0500 Subject: [PATCH 1/7] Add data upsert optimization option Signed-off-by: Peter Broadhurst --- internal/database/sqlcommon/batch_sql.go | 4 +- .../database/sqlcommon/config_record_sql.go | 2 +- internal/database/sqlcommon/data_sql.go | 136 ++++++++++-------- internal/database/sqlcommon/data_sql_test.go | 24 ++-- internal/database/sqlcommon/datatype_sql.go | 4 +- internal/database/sqlcommon/event_sql.go | 2 +- internal/database/sqlcommon/group_sql.go | 4 +- internal/database/sqlcommon/message_sql.go | 4 +- internal/database/sqlcommon/namespace_sql.go | 2 +- internal/database/sqlcommon/nextpin_sql.go | 2 +- internal/database/sqlcommon/node_sql.go | 4 +- internal/database/sqlcommon/nonce_sql.go | 2 +- internal/database/sqlcommon/offset_sql.go | 4 +- internal/database/sqlcommon/operation_sql.go | 4 +- .../database/sqlcommon/organization_sql.go | 4 +- internal/database/sqlcommon/pin_sql.go | 2 +- internal/database/sqlcommon/sqlcommon.go | 10 +- internal/database/sqlcommon/sqlcommon_test.go | 2 +- .../database/sqlcommon/subscription_sql.go | 4 +- .../database/sqlcommon/tokenbalance_sql.go | 2 +- internal/database/sqlcommon/tokenpool_sql.go | 2 +- .../database/sqlcommon/tokentransfer_sql.go | 2 +- .../database/sqlcommon/transaction_sql.go | 4 +- pkg/database/plugin.go | 15 +- 24 files changed, 139 insertions(+), 106 deletions(-) diff --git a/internal/database/sqlcommon/batch_sql.go b/internal/database/sqlcommon/batch_sql.go index 7704d4a6da..8c491b4768 100644 --- a/internal/database/sqlcommon/batch_sql.go +++ b/internal/database/sqlcommon/batch_sql.go @@ -84,7 +84,7 @@ func (s *SQLCommon) UpsertBatch(ctx context.Context, batch *fftypes.Batch, allow if existing { // Update the batch - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("batches"). Set("btype", string(batch.Type)). Set("namespace", batch.Namespace). @@ -224,7 +224,7 @@ func (s *SQLCommon) UpdateBatch(ctx context.Context, id *fftypes.UUID, update da } query = query.Where(sq.Eq{"id": id}) - err = s.updateTx(ctx, tx, query, nil /* no change events on filter update */) + _, err = s.updateTx(ctx, tx, query, nil /* no change events on filter update */) if err != nil { return err } diff --git a/internal/database/sqlcommon/config_record_sql.go b/internal/database/sqlcommon/config_record_sql.go index d080326d30..ba64f7ff6e 100644 --- a/internal/database/sqlcommon/config_record_sql.go +++ b/internal/database/sqlcommon/config_record_sql.go @@ -62,7 +62,7 @@ func (s *SQLCommon) UpsertConfigRecord(ctx context.Context, configRecord *fftype if existing { // Update the config record - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("config"). Set("config_value", configRecord.Value). Where(sq.Eq{"config_key": configRecord.Key}), diff --git a/internal/database/sqlcommon/data_sql.go b/internal/database/sqlcommon/data_sql.go index 0ff4fe473a..afd67f358e 100644 --- a/internal/database/sqlcommon/data_sql.go +++ b/internal/database/sqlcommon/data_sql.go @@ -49,15 +49,79 @@ var ( } ) -func (s *SQLCommon) UpsertData(ctx context.Context, data *fftypes.Data, allowExisting, allowHashUpdate bool) (err error) { +func (s *SQLCommon) attemptDataUpdate(ctx context.Context, tx *txWrapper, data *fftypes.Data, datatype *fftypes.DatatypeRef, blob *fftypes.BlobRef) (int64, error) { + return s.updateTx(ctx, tx, + sq.Update("data"). + Set("validator", string(data.Validator)). + Set("namespace", data.Namespace). + Set("datatype_name", datatype.Name). + Set("datatype_version", datatype.Version). + Set("hash", data.Hash). + Set("created", data.Created). + Set("blob_hash", blob.Hash). + Set("blob_public", blob.Public). + Set("value", data.Value). + Where(sq.Eq{ + "id": data.ID, + "hash": data.Hash, + }), + func() { + s.callbacks.UUIDCollectionNSEvent(database.CollectionData, fftypes.ChangeEventTypeUpdated, data.Namespace, data.ID) + }) +} + +func (s *SQLCommon) attemptDataInsert(ctx context.Context, tx *txWrapper, data *fftypes.Data, datatype *fftypes.DatatypeRef, blob *fftypes.BlobRef) (int64, error) { + return s.insertTx(ctx, tx, + sq.Insert("data"). + Columns(dataColumnsWithValue...). + Values( + data.ID, + string(data.Validator), + data.Namespace, + datatype.Name, + datatype.Version, + data.Hash, + data.Created, + blob.Hash, + blob.Public, + data.Value, + ), + func() { + s.callbacks.UUIDCollectionNSEvent(database.CollectionData, fftypes.ChangeEventTypeCreated, data.Namespace, data.ID) + }) +} + +func (s *SQLCommon) UpsertData(ctx context.Context, data *fftypes.Data, optimization database.UpsertOptimization) (err error) { ctx, tx, autoCommit, err := s.beginOrUseTx(ctx) if err != nil { return err } defer s.rollbackTx(ctx, tx, autoCommit) - existing := false - if allowExisting { + datatype := data.Datatype + if datatype == nil { + datatype = &fftypes.DatatypeRef{} + } + blob := data.Blob + if blob == nil { + blob = &fftypes.BlobRef{} + } + + // This is a performance critical function, as we stream data into the database for every message, in every batch. + // + // First attempt the operation based on the optimization passed in. + // The expectation is that this will practically hit of the time, as only recovery paths + // require us to go down the un-optimized route. + optimized := false + if optimization == database.UpsertOptimizationNew { + _, opErr := s.attemptDataInsert(ctx, tx, data, datatype, blob) + optimized = opErr == nil + } else if optimization == database.UpsertOptimizationExisting { + rowsAffected, opErr := s.attemptDataUpdate(ctx, tx, data, datatype, blob) + optimized = opErr == nil && rowsAffected == 1 + } + + if !optimized { // Do a select within the transaction to detemine if the UUID already exists dataRows, _, err := s.queryTx(ctx, tx, sq.Select("hash"). @@ -68,8 +132,8 @@ func (s *SQLCommon) UpsertData(ctx context.Context, data *fftypes.Data, allowExi return err } - existing = dataRows.Next() - if existing && !allowHashUpdate { + existing := dataRows.Next() + if existing { var hash *fftypes.Bytes32 _ = dataRows.Scan(&hash) if !fftypes.SafeHashCompare(hash, data.Hash) { @@ -79,59 +143,15 @@ func (s *SQLCommon) UpsertData(ctx context.Context, data *fftypes.Data, allowExi } } dataRows.Close() - } - datatype := data.Datatype - if datatype == nil { - datatype = &fftypes.DatatypeRef{} - } - - blob := data.Blob - if blob == nil { - blob = &fftypes.BlobRef{} - } - - if existing { - // Update the data - if err = s.updateTx(ctx, tx, - sq.Update("data"). - Set("validator", string(data.Validator)). - Set("namespace", data.Namespace). - Set("datatype_name", datatype.Name). - Set("datatype_version", datatype.Version). - Set("hash", data.Hash). - Set("created", data.Created). - Set("blob_hash", blob.Hash). - Set("blob_public", blob.Public). - Set("value", data.Value). - Where(sq.Eq{"id": data.ID}), - func() { - s.callbacks.UUIDCollectionNSEvent(database.CollectionData, fftypes.ChangeEventTypeUpdated, data.Namespace, data.ID) - }, - ); err != nil { - return err - } - } else { - if _, err = s.insertTx(ctx, tx, - sq.Insert("data"). - Columns(dataColumnsWithValue...). - Values( - data.ID, - string(data.Validator), - data.Namespace, - datatype.Name, - datatype.Version, - data.Hash, - data.Created, - blob.Hash, - blob.Public, - data.Value, - ), - func() { - s.callbacks.UUIDCollectionNSEvent(database.CollectionData, fftypes.ChangeEventTypeCreated, data.Namespace, data.ID) - }, - ); err != nil { - return err + if existing { + if _, err = s.attemptDataUpdate(ctx, tx, data, datatype, blob); err != nil { + return err + } + } else { + if _, err = s.attemptDataInsert(ctx, tx, data, datatype, blob); err != nil { + return err + } } } @@ -271,7 +291,7 @@ func (s *SQLCommon) UpdateData(ctx context.Context, id *fftypes.UUID, update dat } query = query.Where(sq.Eq{"id": id}) - err = s.updateTx(ctx, tx, query, nil /* no change events for filter based updates */) + _, err = s.updateTx(ctx, tx, query, nil /* no change events for filter based updates */) if err != nil { return err } diff --git a/internal/database/sqlcommon/data_sql_test.go b/internal/database/sqlcommon/data_sql_test.go index 3291af2ed0..a19d6a68f4 100644 --- a/internal/database/sqlcommon/data_sql_test.go +++ b/internal/database/sqlcommon/data_sql_test.go @@ -57,7 +57,7 @@ func TestDataE2EWithDB(t *testing.T) { s.callbacks.On("UUIDCollectionNSEvent", database.CollectionData, fftypes.ChangeEventTypeCreated, "ns1", dataID, mock.Anything).Return() s.callbacks.On("UUIDCollectionNSEvent", database.CollectionData, fftypes.ChangeEventTypeUpdated, "ns1", dataID, mock.Anything).Return() - err := s.UpsertData(ctx, data, true, false) + err := s.UpsertData(ctx, data, database.UpsertOptimizationSkip) assert.NoError(t, err) // Check we get the exact same data back - we should not to return the value first @@ -99,11 +99,14 @@ func TestDataE2EWithDB(t *testing.T) { }, } - // Check disallows hash update - err = s.UpsertData(context.Background(), dataUpdated, true, false) + // Check disallows hash update, regardless of optimization + err = s.UpsertData(context.Background(), dataUpdated, database.UpsertOptimizationNew) + assert.Equal(t, database.HashMismatch, err) + err = s.UpsertData(context.Background(), dataUpdated, database.UpsertOptimizationExisting) assert.Equal(t, database.HashMismatch, err) - err = s.UpsertData(context.Background(), dataUpdated, true, true) + dataUpdated.Hash = data.Hash + err = s.UpsertData(context.Background(), dataUpdated, database.UpsertOptimizationSkip) assert.NoError(t, err) // Check we get the exact same message back - note the removal of one of the data elements @@ -162,7 +165,7 @@ func TestDataE2EWithDB(t *testing.T) { func TestUpsertDataFailBegin(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectBegin().WillReturnError(fmt.Errorf("pop")) - err := s.UpsertData(context.Background(), &fftypes.Data{}, true, true) + err := s.UpsertData(context.Background(), &fftypes.Data{}, database.UpsertOptimizationSkip) assert.Regexp(t, "FF10114", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -173,7 +176,7 @@ func TestUpsertDataFailSelect(t *testing.T) { mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) mock.ExpectRollback() dataID := fftypes.NewUUID() - err := s.UpsertData(context.Background(), &fftypes.Data{ID: dataID}, true, true) + err := s.UpsertData(context.Background(), &fftypes.Data{ID: dataID}, database.UpsertOptimizationSkip) assert.Regexp(t, "FF10115", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -185,7 +188,7 @@ func TestUpsertDataFailInsert(t *testing.T) { mock.ExpectExec("INSERT .*").WillReturnError(fmt.Errorf("pop")) mock.ExpectRollback() dataID := fftypes.NewUUID() - err := s.UpsertData(context.Background(), &fftypes.Data{ID: dataID}, true, true) + err := s.UpsertData(context.Background(), &fftypes.Data{ID: dataID}, database.UpsertOptimizationSkip) assert.Regexp(t, "FF10116", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -193,11 +196,12 @@ func TestUpsertDataFailInsert(t *testing.T) { func TestUpsertDataFailUpdate(t *testing.T) { s, mock := newMockProvider().init() dataID := fftypes.NewUUID() + dataHash := fftypes.NewRandB32() mock.ExpectBegin() - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(dataID.String())) + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"hash"}).AddRow(dataHash.String())) mock.ExpectExec("UPDATE .*").WillReturnError(fmt.Errorf("pop")) mock.ExpectRollback() - err := s.UpsertData(context.Background(), &fftypes.Data{ID: dataID}, true, true) + err := s.UpsertData(context.Background(), &fftypes.Data{ID: dataID, Hash: dataHash}, database.UpsertOptimizationSkip) assert.Regexp(t, "FF10117", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -209,7 +213,7 @@ func TestUpsertDataFailCommit(t *testing.T) { mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"})) mock.ExpectExec("INSERT .*").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit().WillReturnError(fmt.Errorf("pop")) - err := s.UpsertData(context.Background(), &fftypes.Data{ID: dataID}, true, true) + err := s.UpsertData(context.Background(), &fftypes.Data{ID: dataID}, database.UpsertOptimizationSkip) assert.Regexp(t, "FF10119", err) assert.NoError(t, mock.ExpectationsWereMet()) } diff --git a/internal/database/sqlcommon/datatype_sql.go b/internal/database/sqlcommon/datatype_sql.go index c2471ecf6f..d2a2d1332e 100644 --- a/internal/database/sqlcommon/datatype_sql.go +++ b/internal/database/sqlcommon/datatype_sql.go @@ -70,7 +70,7 @@ func (s *SQLCommon) UpsertDatatype(ctx context.Context, datatype *fftypes.Dataty if existing { // Update the datatype - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("datatypes"). Set("message_id", datatype.Message). Set("validator", string(datatype.Validator)). @@ -205,7 +205,7 @@ func (s *SQLCommon) UpdateDatatype(ctx context.Context, id *fftypes.UUID, update } query = query.Where(sq.Eq{"id": id}) - err = s.updateTx(ctx, tx, query, nil /* no change events for filter based updates */) + _, err = s.updateTx(ctx, tx, query, nil /* no change events for filter based updates */) if err != nil { return err } diff --git a/internal/database/sqlcommon/event_sql.go b/internal/database/sqlcommon/event_sql.go index 5df8dea9e9..a653e89b52 100644 --- a/internal/database/sqlcommon/event_sql.go +++ b/internal/database/sqlcommon/event_sql.go @@ -156,7 +156,7 @@ func (s *SQLCommon) UpdateEvent(ctx context.Context, id *fftypes.UUID, update da } query = query.Where(sq.Eq{"id": id}) - err = s.updateTx(ctx, tx, query, nil /* no change events on filter based update */) + _, err = s.updateTx(ctx, tx, query, nil /* no change events on filter based update */) if err != nil { return err } diff --git a/internal/database/sqlcommon/group_sql.go b/internal/database/sqlcommon/group_sql.go index daaeed32a8..b93e9b26e1 100644 --- a/internal/database/sqlcommon/group_sql.go +++ b/internal/database/sqlcommon/group_sql.go @@ -66,7 +66,7 @@ func (s *SQLCommon) UpsertGroup(ctx context.Context, group *fftypes.Group, allow if existing { // Update the group - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("groups"). Set("message_id", group.Message). Set("namespace", group.Namespace). @@ -303,7 +303,7 @@ func (s *SQLCommon) UpdateGroups(ctx context.Context, filter database.Filter, up return err } - err = s.updateTx(ctx, tx, query, nil /* no change event for filter based update */) + _, err = s.updateTx(ctx, tx, query, nil /* no change event for filter based update */) if err != nil { return err } diff --git a/internal/database/sqlcommon/message_sql.go b/internal/database/sqlcommon/message_sql.go index 5ff6a93617..da6f17fb72 100644 --- a/internal/database/sqlcommon/message_sql.go +++ b/internal/database/sqlcommon/message_sql.go @@ -91,7 +91,7 @@ func (s *SQLCommon) UpsertMessage(ctx context.Context, message *fftypes.Message, if existing { // Update the message - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("messages"). Set("cid", message.Header.CID). Set("mtype", string(message.Header.Type)). @@ -423,7 +423,7 @@ func (s *SQLCommon) UpdateMessages(ctx context.Context, filter database.Filter, return err } - err = s.updateTx(ctx, tx, query, nil /* no change events filter based update */) + _, err = s.updateTx(ctx, tx, query, nil /* no change events filter based update */) if err != nil { return err } diff --git a/internal/database/sqlcommon/namespace_sql.go b/internal/database/sqlcommon/namespace_sql.go index 1742ad487b..3a18d7dc5e 100644 --- a/internal/database/sqlcommon/namespace_sql.go +++ b/internal/database/sqlcommon/namespace_sql.go @@ -78,7 +78,7 @@ func (s *SQLCommon) UpsertNamespace(ctx context.Context, namespace *fftypes.Name if existing { // Update the namespace - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("namespaces"). // Note we do not update ID Set("message_id", namespace.Message). diff --git a/internal/database/sqlcommon/nextpin_sql.go b/internal/database/sqlcommon/nextpin_sql.go index e8ba506228..eccc8ff91b 100644 --- a/internal/database/sqlcommon/nextpin_sql.go +++ b/internal/database/sqlcommon/nextpin_sql.go @@ -161,7 +161,7 @@ func (s *SQLCommon) UpdateNextPin(ctx context.Context, sequence int64, update da } query = query.Where(sq.Eq{sequenceColumn: sequence}) - err = s.updateTx(ctx, tx, query, nil /* no change events for next pins */) + _, err = s.updateTx(ctx, tx, query, nil /* no change events for next pins */) if err != nil { return err } diff --git a/internal/database/sqlcommon/node_sql.go b/internal/database/sqlcommon/node_sql.go index 316452028a..8c6f218ac4 100644 --- a/internal/database/sqlcommon/node_sql.go +++ b/internal/database/sqlcommon/node_sql.go @@ -85,7 +85,7 @@ func (s *SQLCommon) UpsertNode(ctx context.Context, node *fftypes.Node, allowExi if existing { // Update the node - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("nodes"). // Note we do not update ID Set("message_id", node.Message). @@ -218,7 +218,7 @@ func (s *SQLCommon) UpdateNode(ctx context.Context, id *fftypes.UUID, update dat } query = query.Where(sq.Eq{"id": id}) - err = s.updateTx(ctx, tx, query, nil /* no change events for filter based updates */) + _, err = s.updateTx(ctx, tx, query, nil /* no change events for filter based updates */) if err != nil { return err } diff --git a/internal/database/sqlcommon/nonce_sql.go b/internal/database/sqlcommon/nonce_sql.go index ef7137ae66..c77422ac48 100644 --- a/internal/database/sqlcommon/nonce_sql.go +++ b/internal/database/sqlcommon/nonce_sql.go @@ -70,7 +70,7 @@ func (s *SQLCommon) UpsertNonceNext(ctx context.Context, nonce *fftypes.Nonce) ( nonceRows.Close() // Update the nonce - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("nonces"). Set("nonce", nonce.Nonce). Where(sq.Eq{sequenceColumn: sequence}), diff --git a/internal/database/sqlcommon/offset_sql.go b/internal/database/sqlcommon/offset_sql.go index 99bb78fdf8..5aa9087d5f 100644 --- a/internal/database/sqlcommon/offset_sql.go +++ b/internal/database/sqlcommon/offset_sql.go @@ -72,7 +72,7 @@ func (s *SQLCommon) UpsertOffset(ctx context.Context, offset *fftypes.Offset, al if existing { // Update the offset - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("offsets"). Set("otype", string(offset.Type)). Set("name", offset.Name). @@ -186,7 +186,7 @@ func (s *SQLCommon) UpdateOffset(ctx context.Context, rowID int64, update databa } query = query.Where(sq.Eq{sequenceColumn: rowID}) - err = s.updateTx(ctx, tx, query, nil /* offsets do not have change events */) + _, err = s.updateTx(ctx, tx, query, nil /* offsets do not have change events */) if err != nil { return err } diff --git a/internal/database/sqlcommon/operation_sql.go b/internal/database/sqlcommon/operation_sql.go index d55969b1e7..349c858185 100644 --- a/internal/database/sqlcommon/operation_sql.go +++ b/internal/database/sqlcommon/operation_sql.go @@ -76,7 +76,7 @@ func (s *SQLCommon) UpsertOperation(ctx context.Context, operation *fftypes.Oper if existing { // Update the operation - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("operations"). Set("namespace", operation.Namespace). Set("tx_id", operation.Transaction). @@ -212,7 +212,7 @@ func (s *SQLCommon) UpdateOperation(ctx context.Context, id *fftypes.UUID, updat query = query.Set("updated", fftypes.Now()) query = query.Where(sq.Eq{"id": id}) - err = s.updateTx(ctx, tx, query, nil /* no change events for filter based updates */) + _, err = s.updateTx(ctx, tx, query, nil /* no change events for filter based updates */) if err != nil { return err } diff --git a/internal/database/sqlcommon/organization_sql.go b/internal/database/sqlcommon/organization_sql.go index df285acf8a..230d822bf0 100644 --- a/internal/database/sqlcommon/organization_sql.go +++ b/internal/database/sqlcommon/organization_sql.go @@ -79,7 +79,7 @@ func (s *SQLCommon) UpsertOrganization(ctx context.Context, organization *fftype if existing { // Update the organization - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("orgs"). // Note we do not update ID Set("message_id", organization.Message). @@ -215,7 +215,7 @@ func (s *SQLCommon) UpdateOrganization(ctx context.Context, id *fftypes.UUID, up } query = query.Where(sq.Eq{"id": id}) - err = s.updateTx(ctx, tx, query, nil /* no change events for filter based updates */) + _, err = s.updateTx(ctx, tx, query, nil /* no change events for filter based updates */) if err != nil { return err } diff --git a/internal/database/sqlcommon/pin_sql.go b/internal/database/sqlcommon/pin_sql.go index eff00c7353..cb85028da2 100644 --- a/internal/database/sqlcommon/pin_sql.go +++ b/internal/database/sqlcommon/pin_sql.go @@ -149,7 +149,7 @@ func (s *SQLCommon) SetPinDispatched(ctx context.Context, sequence int64) (err e } defer s.rollbackTx(ctx, tx, autoCommit) - err = s.updateTx(ctx, tx, sq. + _, err = s.updateTx(ctx, tx, sq. Update("pins"). Set("dispatched", true). Where(sq.Eq{ diff --git a/internal/database/sqlcommon/sqlcommon.go b/internal/database/sqlcommon/sqlcommon.go index 19e1ce11c7..09c7fb8509 100644 --- a/internal/database/sqlcommon/sqlcommon.go +++ b/internal/database/sqlcommon/sqlcommon.go @@ -284,26 +284,26 @@ func (s *SQLCommon) deleteTx(ctx context.Context, tx *txWrapper, q sq.DeleteBuil return nil } -func (s *SQLCommon) updateTx(ctx context.Context, tx *txWrapper, q sq.UpdateBuilder, postCommit func()) error { +func (s *SQLCommon) updateTx(ctx context.Context, tx *txWrapper, q sq.UpdateBuilder, postCommit func()) (int64, error) { l := log.L(ctx) sqlQuery, args, err := q.PlaceholderFormat(s.provider.PlaceholderFormat()).ToSql() if err != nil { - return i18n.WrapError(ctx, err, i18n.MsgDBQueryBuildFailed) + return -1, i18n.WrapError(ctx, err, i18n.MsgDBQueryBuildFailed) } l.Debugf(`SQL-> update: %s`, sqlQuery) l.Tracef(`SQL-> update args: %+v`, args) res, err := tx.sqlTX.ExecContext(ctx, sqlQuery, args...) if err != nil { l.Errorf(`SQL update failed: %s sql=[ %s ]`, err, sqlQuery) - return i18n.WrapError(ctx, err, i18n.MsgDBUpdateFailed) + return -1, i18n.WrapError(ctx, err, i18n.MsgDBUpdateFailed) } - ra, _ := res.RowsAffected() // currently only used for debugging + ra, _ := res.RowsAffected() l.Debugf(`SQL<- update affected=%d`, ra) if postCommit != nil { s.postCommitEvent(tx, postCommit) } - return nil + return ra, nil } func (s *SQLCommon) postCommitEvent(tx *txWrapper, fn func()) { diff --git a/internal/database/sqlcommon/sqlcommon_test.go b/internal/database/sqlcommon/sqlcommon_test.go index 5d8fdd5ce9..e1fef823ba 100644 --- a/internal/database/sqlcommon/sqlcommon_test.go +++ b/internal/database/sqlcommon/sqlcommon_test.go @@ -112,7 +112,7 @@ func TestInsertTxBadSQL(t *testing.T) { func TestUpdateTxBadSQL(t *testing.T) { s, _ := newMockProvider().init() - err := s.updateTx(context.Background(), nil, sq.UpdateBuilder{}, nil) + _, err := s.updateTx(context.Background(), nil, sq.UpdateBuilder{}, nil) assert.Regexp(t, "FF10113", err) } diff --git a/internal/database/sqlcommon/subscription_sql.go b/internal/database/sqlcommon/subscription_sql.go index 21c84fcfaf..ec50fb785a 100644 --- a/internal/database/sqlcommon/subscription_sql.go +++ b/internal/database/sqlcommon/subscription_sql.go @@ -89,7 +89,7 @@ func (s *SQLCommon) UpsertSubscription(ctx context.Context, subscription *fftype if existing { // Update the subscription - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("subscriptions"). // Note we do not update ID Set("namespace", subscription.Namespace). @@ -247,7 +247,7 @@ func (s *SQLCommon) UpdateSubscription(ctx context.Context, namespace, name stri } query = query.Where(sq.Eq{"id": subscription.ID}) - err = s.updateTx(ctx, tx, query, + _, err = s.updateTx(ctx, tx, query, func() { s.callbacks.UUIDCollectionNSEvent(database.CollectionSubscriptions, fftypes.ChangeEventTypeUpdated, subscription.Namespace, subscription.ID) }) diff --git a/internal/database/sqlcommon/tokenbalance_sql.go b/internal/database/sqlcommon/tokenbalance_sql.go index b9cd08132d..b13ab548d6 100644 --- a/internal/database/sqlcommon/tokenbalance_sql.go +++ b/internal/database/sqlcommon/tokenbalance_sql.go @@ -62,7 +62,7 @@ func (s *SQLCommon) addTokenBalance(ctx context.Context, tx *txWrapper, transfer } if account != nil { - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("tokenbalance"). Set("balance", balance). Set("updated", fftypes.Now()). diff --git a/internal/database/sqlcommon/tokenpool_sql.go b/internal/database/sqlcommon/tokenpool_sql.go index 90d2bf1331..efc0265e3d 100644 --- a/internal/database/sqlcommon/tokenpool_sql.go +++ b/internal/database/sqlcommon/tokenpool_sql.go @@ -80,7 +80,7 @@ func (s *SQLCommon) UpsertTokenPool(ctx context.Context, pool *fftypes.TokenPool rows.Close() if existing { - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("tokenpool"). Set("namespace", pool.Namespace). Set("name", pool.Name). diff --git a/internal/database/sqlcommon/tokentransfer_sql.go b/internal/database/sqlcommon/tokentransfer_sql.go index ee8f9a1349..cdb2c442bd 100644 --- a/internal/database/sqlcommon/tokentransfer_sql.go +++ b/internal/database/sqlcommon/tokentransfer_sql.go @@ -77,7 +77,7 @@ func (s *SQLCommon) UpsertTokenTransfer(ctx context.Context, transfer *fftypes.T rows.Close() if existing { - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("tokentransfer"). Set("type", transfer.Type). Set("local_id", transfer.LocalID). diff --git a/internal/database/sqlcommon/transaction_sql.go b/internal/database/sqlcommon/transaction_sql.go index 8d22605aca..8c227fcae9 100644 --- a/internal/database/sqlcommon/transaction_sql.go +++ b/internal/database/sqlcommon/transaction_sql.go @@ -79,7 +79,7 @@ func (s *SQLCommon) UpsertTransaction(ctx context.Context, transaction *fftypes. if existing { // Update the transaction - if err = s.updateTx(ctx, tx, + if _, err = s.updateTx(ctx, tx, sq.Update("transactions"). Set("ttype", string(transaction.Subject.Type)). Set("namespace", transaction.Subject.Namespace). @@ -210,7 +210,7 @@ func (s *SQLCommon) UpdateTransaction(ctx context.Context, id *fftypes.UUID, upd } query = query.Where(sq.Eq{"id": id}) - err = s.updateTx(ctx, tx, query, nil /* no change evnents for filter based updates */) + _, err = s.updateTx(ctx, tx, query, nil /* no change evnents for filter based updates */) if err != nil { return err } diff --git a/pkg/database/plugin.go b/pkg/database/plugin.go index 7c96f9465d..335ba00b16 100644 --- a/pkg/database/plugin.go +++ b/pkg/database/plugin.go @@ -33,6 +33,14 @@ var ( DeleteRecordNotFound = i18n.NewError(context.Background(), i18n.Msg404NotFound) ) +type UpsertOptimization int + +const ( + UpsertOptimizationSkip UpsertOptimization = iota + UpsertOptimizationNew + UpsertOptimizationExisting +) + // Plugin is the interface implemented by each plugin type Plugin interface { PeristenceInterface // Split out to aid pluggability the next level down (SQL provider etc.) @@ -88,9 +96,10 @@ type iMessageCollection interface { } type iDataCollection interface { - // UpsertData - Upsert a data record - // allowHashUpdate=false throws HashMismatch error if the updated message has a different hash - UpsertData(ctx context.Context, data *fftypes.Data, allowExisting, allowHashUpdate bool) (err error) + // UpsertData - Upsert a data record. A hint can be supplied to whether the data already exists. + // The database will ensure that if a record already exists, the hash of that existing record + // must match the hash of the record that is being inserted. + UpsertData(ctx context.Context, data *fftypes.Data, optimizeForExisting bool) (err error) // UpdateData - Update data UpdateData(ctx context.Context, id *fftypes.UUID, update Update) (err error) From aafe36a7956336f73fc19de904b9dc9fbee0ee53 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 16 Nov 2021 16:28:26 -0500 Subject: [PATCH 2/7] Add optimization to messages, noting we no longer get sequence ret/emit on update Signed-off-by: Peter Broadhurst --- Makefile | 1 + .../postgres/000044_add_batch_node.down.sql | 3 + .../postgres/000044_add_batch_node.up.sql | 3 + .../sqlite/000044_add_batch_node.down.sql | 1 + .../sqlite/000044_add_batch_node.up.sql | 1 + docs/swagger/swagger.yaml | 7 + go.mod | 14 +- go.sum | 36 ++-- internal/assets/token_transfer.go | 2 +- internal/assets/token_transfer_test.go | 6 +- internal/batch/batch_manager.go | 6 +- internal/batch/batch_manager_test.go | 61 ++++--- internal/batch/batch_processor.go | 6 +- internal/batch/batch_processor_test.go | 5 +- internal/blockchain/ethereum/ethereum_test.go | 2 +- internal/blockchain/fabric/fabric_test.go | 2 +- internal/broadcast/datatype_test.go | 13 +- internal/broadcast/definition.go | 3 +- internal/broadcast/definition_test.go | 7 +- internal/broadcast/manager_test.go | 3 +- internal/broadcast/message.go | 3 +- internal/broadcast/message_test.go | 9 +- internal/broadcast/namespace_test.go | 5 +- internal/broadcast/tokenpool_test.go | 9 +- internal/data/blobstore.go | 2 +- internal/data/blobstore_test.go | 5 +- internal/data/data_manager.go | 2 +- internal/data/data_manager_test.go | 7 +- internal/database/sqlcommon/batch_sql.go | 5 + internal/database/sqlcommon/batch_sql_test.go | 2 + internal/database/sqlcommon/message_sql.go | 161 +++++++++++------- .../database/sqlcommon/message_sql_test.go | 35 ++-- internal/database/sqlcommon/provider.go | 2 +- internal/events/batch_pin_complete_test.go | 72 +++++--- internal/events/dx_callbacks.go | 4 +- internal/events/dx_callbacks_test.go | 9 +- internal/events/event_manager.go | 6 +- internal/events/event_manager_test.go | 12 +- internal/events/persist_batch.go | 35 ++-- internal/events/tokens_transferred.go | 2 +- internal/events/tokens_transferred_test.go | 4 +- internal/orchestrator/orchestrator.go | 5 +- internal/orchestrator/status.go | 18 ++ internal/privatemessaging/groupmanager.go | 4 +- .../privatemessaging/groupmanager_test.go | 2 +- internal/privatemessaging/message.go | 3 +- internal/privatemessaging/message_test.go | 15 +- internal/privatemessaging/recipients_test.go | 5 +- internal/restclient/ffresty.go | 2 +- internal/sysmessaging/localnodeinfo.go | 29 ++++ mocks/databasemocks/plugin.go | 20 +-- mocks/sysmessagingmocks/local_node_info.go | 31 ++++ pkg/database/plugin.go | 11 +- pkg/fftypes/batch.go | 1 + test/e2e/e2e_test.go | 4 +- 55 files changed, 484 insertions(+), 239 deletions(-) create mode 100644 db/migrations/postgres/000044_add_batch_node.down.sql create mode 100644 db/migrations/postgres/000044_add_batch_node.up.sql create mode 100644 db/migrations/sqlite/000044_add_batch_node.down.sql create mode 100644 db/migrations/sqlite/000044_add_batch_node.up.sql create mode 100644 internal/sysmessaging/localnodeinfo.go create mode 100644 mocks/sysmessagingmocks/local_node_info.go diff --git a/Makefile b/Makefile index 1494f3bf43..6c867b9798 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,7 @@ $(eval $(call makemock, internal/identity, Manager, identitymanag $(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/sysmessaging, LocalNodeInfo, sysmessagingmocks)) $(eval $(call makemock, internal/syncasync, Bridge, syncasyncmocks)) $(eval $(call makemock, internal/data, Manager, datamocks)) $(eval $(call makemock, internal/batch, Manager, batchmocks)) diff --git a/db/migrations/postgres/000044_add_batch_node.down.sql b/db/migrations/postgres/000044_add_batch_node.down.sql new file mode 100644 index 0000000000..835c80a440 --- /dev/null +++ b/db/migrations/postgres/000044_add_batch_node.down.sql @@ -0,0 +1,3 @@ +BEGIN; +ALTER TABLE batches DROP COLUMN node_id; +COMMIT; diff --git a/db/migrations/postgres/000044_add_batch_node.up.sql b/db/migrations/postgres/000044_add_batch_node.up.sql new file mode 100644 index 0000000000..9dffd6331e --- /dev/null +++ b/db/migrations/postgres/000044_add_batch_node.up.sql @@ -0,0 +1,3 @@ +BEGIN; +ALTER TABLE batches ADD COLUMN node_id UUID; +COMMIT; diff --git a/db/migrations/sqlite/000044_add_batch_node.down.sql b/db/migrations/sqlite/000044_add_batch_node.down.sql new file mode 100644 index 0000000000..e70c7dde4e --- /dev/null +++ b/db/migrations/sqlite/000044_add_batch_node.down.sql @@ -0,0 +1 @@ +ALTER TABLE batches DROP COLUMN node_id; diff --git a/db/migrations/sqlite/000044_add_batch_node.up.sql b/db/migrations/sqlite/000044_add_batch_node.up.sql new file mode 100644 index 0000000000..c2aa988194 --- /dev/null +++ b/db/migrations/sqlite/000044_add_batch_node.up.sql @@ -0,0 +1 @@ +ALTER TABLE batches ADD COLUMN node_id UUID; diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 45b8ba2b75..d9dbd68b13 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -339,6 +339,11 @@ paths: name: namespace schema: type: string + - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' + in: query + name: node + schema: + type: string - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^' in: query name: payloadref @@ -413,6 +418,7 @@ paths: type: string namespace: type: string + node: {} payload: properties: data: @@ -548,6 +554,7 @@ paths: type: string namespace: type: string + node: {} payload: properties: data: diff --git a/go.mod b/go.mod index 936feeb392..21ad584e2a 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,12 @@ require ( github.com/Masterminds/squirrel v1.5.1 github.com/aidarkhanov/nanoid v1.0.8 github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/docker/go-units v0.4.0 - github.com/getkin/kin-openapi v0.80.0 + github.com/getkin/kin-openapi v0.81.0 github.com/ghodss/yaml v1.0.0 github.com/go-openapi/swag v0.19.15 // indirect - github.com/go-resty/resty/v2 v2.6.0 + github.com/go-resty/resty/v2 v2.7.0 github.com/golang-migrate/migrate/v4 v4.15.1 github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 @@ -21,7 +22,7 @@ require ( github.com/jarcoal/httpmock v1.0.8 github.com/karlseguin/ccache v2.0.3+incompatible github.com/karlseguin/expect v1.0.8 // indirect - github.com/lib/pq v1.10.3 + github.com/lib/pq v1.10.4 github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.11 // indirect github.com/mattn/go-sqlite3 v1.14.9 @@ -44,10 +45,11 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 gitlab.com/msvechla/mux-prometheus v0.0.2 go.uber.org/atomic v1.9.0 // indirect - golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect - golang.org/x/sys v0.0.0-20211025112917-711f33c9992c // indirect + golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8 // indirect + golang.org/x/net v0.0.0-20211116231205-47ca1ff31462 // indirect + golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 - golang.org/x/tools v0.1.7 // indirect + gopkg.in/ini.v1 v1.64.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) diff --git a/go.sum b/go.sum index 422a9977ce..8a6290fe7f 100644 --- a/go.sum +++ b/go.sum @@ -186,8 +186,9 @@ github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -387,8 +388,8 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXt github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/getkin/kin-openapi v0.80.0 h1:W/s5/DNnDCR8P+pYyafEWlGk4S7/AfQUWXgrRSSAzf8= -github.com/getkin/kin-openapi v0.80.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getkin/kin-openapi v0.81.0 h1:avwkWFYWMZBvSMzewNA9ZdQiAPmLkRALYKdU6xC6uyw= +github.com/getkin/kin-openapi v0.81.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -424,8 +425,8 @@ github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyr github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= -github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4= -github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -760,8 +761,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg= -github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -1134,7 +1135,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= @@ -1201,8 +1201,9 @@ golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8 h1:5QRxNnVsaJP6NAse0UdkRgL3zHMvCRRkrDVLNdNpdy4= +golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1248,7 +1249,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1309,11 +1309,11 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211013171255-e13a2654a71e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI= -golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211116231205-47ca1ff31462 h1:2vmJlzGKvQ7e/X9XT0XydeWDxmqx8DnegiIMRT+5ssI= +golang.org/x/net v0.0.0-20211116231205-47ca1ff31462/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1449,13 +1449,12 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210818153620-00dd8d7831e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025112917-711f33c9992c h1:i4MLwL3EbCgobekQtkVW94UBSPLMadfEGtKq+CAFsEU= -golang.org/x/sys v0.0.0-20211025112917-711f33c9992c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c h1:DHcbWVXeY+0Y8HHKR+rbLwnoh2F4tNCY7rTiHJ30RmA= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1549,8 +1548,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1735,8 +1732,9 @@ gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWd gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.64.0 h1:Mj2zXEXcNb5joEiSA0zc3HZpTst/iyjNiR4CN8tDzOg= +gopkg.in/ini.v1 v1.64.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/internal/assets/token_transfer.go b/internal/assets/token_transfer.go index 06df03d436..e5a178e675 100644 --- a/internal/assets/token_transfer.go +++ b/internal/assets/token_transfer.go @@ -284,7 +284,7 @@ func (s *transferSender) sendInternal(ctx context.Context, method sendMethod) er } if s.transfer.Message != nil { s.transfer.Message.State = fftypes.MessageStateStaged - err = s.mgr.database.UpsertMessage(ctx, &s.transfer.Message.Message, false, false) + err = s.mgr.database.UpsertMessage(ctx, &s.transfer.Message.Message, database.UpsertOptimizationNew) } return err }) diff --git a/internal/assets/token_transfer_test.go b/internal/assets/token_transfer_test.go index d9f00ac301..f19849b6c5 100644 --- a/internal/assets/token_transfer_test.go +++ b/internal/assets/token_transfer_test.go @@ -857,7 +857,7 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { mms.On("Prepare", context.Background()).Return(nil) mdi.On("UpsertMessage", context.Background(), mock.MatchedBy(func(msg *fftypes.Message) bool { return msg.State == fftypes.MessageStateStaged - }), false, false).Return(nil) + }), database.UpsertOptimizationNew).Return(nil) _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) assert.NoError(t, err) @@ -951,7 +951,7 @@ func TestTransferTokensWithPrivateMessage(t *testing.T) { mms.On("Prepare", context.Background()).Return(nil) mdi.On("UpsertMessage", context.Background(), mock.MatchedBy(func(msg *fftypes.Message) bool { return msg.State == fftypes.MessageStateStaged - }), false, false).Return(nil) + }), database.UpsertOptimizationNew).Return(nil) _, err := am.TransferTokens(context.Background(), "ns1", transfer, false) assert.NoError(t, err) @@ -1086,7 +1086,7 @@ func TestTransferTokensWithBroadcastConfirm(t *testing.T) { mms.On("Prepare", context.Background()).Return(nil) mdi.On("UpsertMessage", context.Background(), mock.MatchedBy(func(msg *fftypes.Message) bool { return msg.State == fftypes.MessageStateStaged - }), false, false).Return(nil) + }), database.UpsertOptimizationNew).Return(nil) msa.On("WaitForMessage", context.Background(), "ns1", mock.Anything, mock.Anything). Run(func(args mock.Arguments) { send := args[3].(syncasync.RequestSender) diff --git a/internal/batch/batch_manager.go b/internal/batch/batch_manager.go index 08033383e4..79f1c8db4c 100644 --- a/internal/batch/batch_manager.go +++ b/internal/batch/batch_manager.go @@ -27,6 +27,7 @@ import ( "github.com/hyperledger/firefly/internal/i18n" "github.com/hyperledger/firefly/internal/log" "github.com/hyperledger/firefly/internal/retry" + "github.com/hyperledger/firefly/internal/sysmessaging" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" ) @@ -35,13 +36,14 @@ const ( msgBatchOffsetName = "ff_msgbatch" ) -func NewBatchManager(ctx context.Context, di database.Plugin, dm data.Manager) (Manager, error) { +func NewBatchManager(ctx context.Context, ni sysmessaging.LocalNodeInfo, di database.Plugin, dm data.Manager) (Manager, error) { if di == nil || dm == nil { return nil, i18n.NewError(ctx, i18n.MsgInitializationNilDepError) } readPageSize := config.GetUint(config.BatchManagerReadPageSize) bm := &batchManager{ ctx: log.WithLogField(ctx, "role", "batchmgr"), + ni: ni, database: di, data: dm, readPageSize: uint64(readPageSize), @@ -70,6 +72,7 @@ type Manager interface { type batchManager struct { ctx context.Context + ni sysmessaging.LocalNodeInfo database database.Plugin data data.Manager dispatchers map[fftypes.MessageType]*dispatcher @@ -162,6 +165,7 @@ func (bm *batchManager) getProcessor(batchType fftypes.MessageType, group *fftyp if !ok { processor = newBatchProcessor( bm.ctx, // Background context, not the call context + bm.ni, bm.database, &batchProcessorConf{ Options: dispatcher.batchOptions, diff --git a/internal/batch/batch_manager_test.go b/internal/batch/batch_manager_test.go index d939f1fa44..f98195b2d9 100644 --- a/internal/batch/batch_manager_test.go +++ b/internal/batch/batch_manager_test.go @@ -26,6 +26,7 @@ import ( "github.com/hyperledger/firefly/internal/log" "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/datamocks" + "github.com/hyperledger/firefly/mocks/sysmessagingmocks" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" @@ -37,6 +38,8 @@ func TestE2EDispatchBroadcast(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} + mni := &sysmessagingmocks.LocalNodeInfo{} + mni.On("GetNodeUUID", mock.Anything).Return(fftypes.NewUUID()) mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, msgBatchOffsetName).Return(nil, nil).Once() mdi.On("UpsertOffset", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) mdi.On("UpdateOffset", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) @@ -71,7 +74,7 @@ func TestE2EDispatchBroadcast(t *testing.T) { return nil } ctx, cancel := context.WithCancel(context.Background()) - bmi, _ := NewBatchManager(ctx, mdi, mdm) + bmi, _ := NewBatchManager(ctx, mni, mdi, mdm) bm := bmi.(*batchManager) bm.RegisterDispatcher([]fftypes.MessageType{fftypes.MessageTypeBroadcast}, handler, Options{ @@ -138,6 +141,8 @@ func TestE2EDispatchPrivate(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} + mni := &sysmessagingmocks.LocalNodeInfo{} + mni.On("GetNodeUUID", mock.Anything).Return(fftypes.NewUUID()) mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, msgBatchOffsetName).Return(nil, nil).Once() mdi.On("UpsertOffset", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) mdi.On("UpdateOffset", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) @@ -175,7 +180,7 @@ func TestE2EDispatchPrivate(t *testing.T) { return nil } ctx, cancel := context.WithCancel(context.Background()) - bmi, _ := NewBatchManager(ctx, mdi, mdm) + bmi, _ := NewBatchManager(ctx, mni, mdi, mdm) bm := bmi.(*batchManager) bm.RegisterDispatcher([]fftypes.MessageType{fftypes.MessageTypePrivate}, handler, Options{ @@ -244,19 +249,20 @@ func TestE2EDispatchPrivate(t *testing.T) { } func TestInitFailNoPersistence(t *testing.T) { - _, err := NewBatchManager(context.Background(), nil, nil) + _, err := NewBatchManager(context.Background(), nil, nil, nil) assert.Error(t, err) } func TestInitRestoreExistingOffset(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} + mni := &sysmessagingmocks.LocalNodeInfo{} mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, msgBatchOffsetName).Return(&fftypes.Offset{ Type: fftypes.OffsetTypeBatch, Name: msgBatchOffsetName, Current: 12345, }, nil) - bm, err := NewBatchManager(context.Background(), mdi, mdm) + bm, err := NewBatchManager(context.Background(), mni, mdi, mdm) assert.NoError(t, err) defer bm.Close() err = bm.Start() @@ -267,8 +273,9 @@ func TestInitRestoreExistingOffset(t *testing.T) { func TestInitFailCannotRestoreOffset(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} + mni := &sysmessagingmocks.LocalNodeInfo{} mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, msgBatchOffsetName).Return(nil, fmt.Errorf("pop")) - bm, err := NewBatchManager(context.Background(), mdi, mdm) + bm, err := NewBatchManager(context.Background(), mni, mdi, mdm) assert.NoError(t, err) defer bm.Close() bm.(*batchManager).retry.MaximumDelay = 1 * time.Microsecond @@ -279,10 +286,11 @@ func TestInitFailCannotRestoreOffset(t *testing.T) { func TestInitFailCannotCreateOffset(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} + mni := &sysmessagingmocks.LocalNodeInfo{} mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, msgBatchOffsetName).Return(nil, nil).Once() mdi.On("UpsertOffset", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, msgBatchOffsetName).Return(nil, fmt.Errorf("pop")) - bm, err := NewBatchManager(context.Background(), mdi, mdm) + bm, err := NewBatchManager(context.Background(), mni, mdi, mdm) assert.NoError(t, err) defer bm.Close() bm.(*batchManager).retry.MaximumDelay = 1 * time.Microsecond @@ -294,10 +302,11 @@ func TestGetInvalidBatchTypeMsg(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} + mni := &sysmessagingmocks.LocalNodeInfo{} mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, msgBatchOffsetName).Return(&fftypes.Offset{ Current: 12345, }, nil) - bm, _ := NewBatchManager(context.Background(), mdi, mdm) + bm, _ := NewBatchManager(context.Background(), mni, mdi, mdm) defer bm.Close() msg := &fftypes.Message{Header: fftypes.MessageHeader{}} err := bm.(*batchManager).dispatchMessage(nil, msg) @@ -307,8 +316,9 @@ func TestGetInvalidBatchTypeMsg(t *testing.T) { func TestMessageSequencerCancelledContext(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} + mni := &sysmessagingmocks.LocalNodeInfo{} mdi.On("GetMessages", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil, fmt.Errorf("pop")) - bm, _ := NewBatchManager(context.Background(), mdi, mdm) + bm, _ := NewBatchManager(context.Background(), mni, mdi, mdm) defer bm.Close() ctx, cancel := context.WithCancel(context.Background()) cancel() @@ -320,7 +330,8 @@ func TestMessageSequencerCancelledContext(t *testing.T) { func TestMessageSequencerMissingMessageData(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - bm, _ := NewBatchManager(context.Background(), mdi, mdm) + mni := &sysmessagingmocks.LocalNodeInfo{} + bm, _ := NewBatchManager(context.Background(), mni, mdi, mdm) dataID := fftypes.NewUUID() gmMock := mdi.On("GetMessages", mock.Anything, mock.Anything, mock.Anything).Return([]*fftypes.Message{ @@ -346,7 +357,8 @@ func TestMessageSequencerMissingMessageData(t *testing.T) { func TestMessageSequencerDispatchFail(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - bm, _ := NewBatchManager(context.Background(), mdi, mdm) + mni := &sysmessagingmocks.LocalNodeInfo{} + bm, _ := NewBatchManager(context.Background(), mni, mdi, mdm) dataID := fftypes.NewUUID() gmMock := mdi.On("GetMessages", mock.Anything, mock.Anything, mock.Anything).Return([]*fftypes.Message{ @@ -373,8 +385,10 @@ func TestMessageSequencerDispatchFail(t *testing.T) { func TestMessageSequencerUpdateMessagesFail(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} + mni := &sysmessagingmocks.LocalNodeInfo{} + mni.On("GetNodeUUID", mock.Anything).Return(fftypes.NewUUID()) ctx, cancelCtx := context.WithCancel(context.Background()) - bm, _ := NewBatchManager(ctx, mdi, mdm) + bm, _ := NewBatchManager(ctx, mni, mdi, mdm) bm.RegisterDispatcher([]fftypes.MessageType{fftypes.MessageTypeBroadcast}, func(c context.Context, b *fftypes.Batch, s []*fftypes.Bytes32) error { return nil }, Options{BatchMaxSize: 1, DisposeTimeout: 0}) @@ -413,8 +427,10 @@ func TestMessageSequencerUpdateMessagesFail(t *testing.T) { func TestMessageSequencerUpdateBatchFail(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} + mni := &sysmessagingmocks.LocalNodeInfo{} + mni.On("GetNodeUUID", mock.Anything).Return(fftypes.NewUUID()) ctx, cancelCtx := context.WithCancel(context.Background()) - bm, _ := NewBatchManager(ctx, mdi, mdm) + bm, _ := NewBatchManager(ctx, mni, mdi, mdm) bm.RegisterDispatcher([]fftypes.MessageType{fftypes.MessageTypeBroadcast}, func(c context.Context, b *fftypes.Batch, s []*fftypes.Bytes32) error { return nil }, Options{BatchMaxSize: 1, DisposeTimeout: 0}) @@ -454,7 +470,8 @@ func TestMessageSequencerUpdateBatchFail(t *testing.T) { func TestWaitForPollTimeout(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - bm, _ := NewBatchManager(context.Background(), mdi, mdm) + mni := &sysmessagingmocks.LocalNodeInfo{} + bm, _ := NewBatchManager(context.Background(), mni, mdi, mdm) bm.(*batchManager).messagePollTimeout = 1 * time.Microsecond bm.(*batchManager).waitForShoulderTapOrPollTimeout() } @@ -463,7 +480,8 @@ func TestWaitConsumesMessagesAndDoesNotBlock(t *testing.T) { config.Reset() mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - bm, _ := NewBatchManager(context.Background(), mdi, mdm) + mni := &sysmessagingmocks.LocalNodeInfo{} + bm, _ := NewBatchManager(context.Background(), mni, mdi, mdm) go bm.(*batchManager).newEventNotifications() for i := 0; i < int(bm.(*batchManager).readPageSize); i++ { bm.NewMessages() <- 12345 @@ -476,7 +494,8 @@ func TestWaitConsumesMessagesAndDoesNotBlock(t *testing.T) { func TestAssembleMessageDataNilData(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - bm, _ := NewBatchManager(context.Background(), mdi, mdm) + mni := &sysmessagingmocks.LocalNodeInfo{} + bm, _ := NewBatchManager(context.Background(), mni, mdi, mdm) bm.Close() mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return(nil, false, nil) _, err := bm.(*batchManager).assembleMessageData(&fftypes.Message{ @@ -491,7 +510,8 @@ func TestAssembleMessageDataNilData(t *testing.T) { func TestAssembleMessageDataClosed(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - bm, _ := NewBatchManager(context.Background(), mdi, mdm) + mni := &sysmessagingmocks.LocalNodeInfo{} + bm, _ := NewBatchManager(context.Background(), mni, mdi, mdm) bm.(*batchManager).retry.MaximumDelay = 1 * time.Microsecond mdi.On("UpdateOffset", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) err := bm.(*batchManager).updateOffset(false, 10) @@ -501,7 +521,8 @@ func TestAssembleMessageDataClosed(t *testing.T) { func TestGetMessageDataFail(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - bm, _ := NewBatchManager(context.Background(), mdi, mdm) + mni := &sysmessagingmocks.LocalNodeInfo{} + bm, _ := NewBatchManager(context.Background(), mni, mdi, mdm) mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return(nil, false, fmt.Errorf("pop")) bm.Close() _, err := bm.(*batchManager).assembleMessageData(&fftypes.Message{ @@ -518,7 +539,8 @@ func TestGetMessageDataFail(t *testing.T) { func TestGetMessageNotFound(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - bm, _ := NewBatchManager(context.Background(), mdi, mdm) + mni := &sysmessagingmocks.LocalNodeInfo{} + bm, _ := NewBatchManager(context.Background(), mni, mdi, mdm) mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return(nil, false, nil) bm.Close() _, err := bm.(*batchManager).assembleMessageData(&fftypes.Message{ @@ -535,7 +557,8 @@ func TestGetMessageNotFound(t *testing.T) { func TestWaitForShoulderTap(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - bm, _ := NewBatchManager(context.Background(), mdi, mdm) + mni := &sysmessagingmocks.LocalNodeInfo{} + bm, _ := NewBatchManager(context.Background(), mni, mdi, mdm) bm.(*batchManager).shoulderTap <- true bm.(*batchManager).waitForShoulderTapOrPollTimeout() } diff --git a/internal/batch/batch_processor.go b/internal/batch/batch_processor.go index 86dbee3804..b9dae1c72b 100644 --- a/internal/batch/batch_processor.go +++ b/internal/batch/batch_processor.go @@ -26,6 +26,7 @@ import ( "github.com/hyperledger/firefly/internal/log" "github.com/hyperledger/firefly/internal/retry" + "github.com/hyperledger/firefly/internal/sysmessaging" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" ) @@ -53,6 +54,7 @@ type batchProcessorConf struct { type batchProcessor struct { ctx context.Context + ni sysmessaging.LocalNodeInfo database database.Plugin name string cancelCtx func() @@ -65,12 +67,13 @@ type batchProcessor struct { conf *batchProcessorConf } -func newBatchProcessor(ctx context.Context, di database.Plugin, conf *batchProcessorConf, retry *retry.Retry) *batchProcessor { +func newBatchProcessor(ctx context.Context, ni sysmessaging.LocalNodeInfo, di database.Plugin, conf *batchProcessorConf, retry *retry.Retry) *batchProcessor { pCtx := log.WithLogField(ctx, "role", fmt.Sprintf("batchproc-%s:%s:%s", conf.namespace, conf.identity.Author, conf.identity.Key)) pCtx, cancelCtx := context.WithCancel(pCtx) bp := &batchProcessor{ ctx: pCtx, cancelCtx: cancelCtx, + ni: ni, database: di, name: fmt.Sprintf("%s:%s:%s", conf.namespace, conf.identity.Author, conf.identity.Key), newWork: make(chan *batchWork), @@ -161,6 +164,7 @@ func (bp *batchProcessor) createOrAddToBatch(batch *fftypes.Batch, newWork []*ba Group: bp.conf.group, Payload: fftypes.BatchPayload{}, Created: fftypes.Now(), + Node: bp.ni.GetNodeUUID(bp.ctx), } } for _, w := range newWork { diff --git a/internal/batch/batch_processor_test.go b/internal/batch/batch_processor_test.go index fb02babaf3..e7010a116f 100644 --- a/internal/batch/batch_processor_test.go +++ b/internal/batch/batch_processor_test.go @@ -24,6 +24,7 @@ import ( "github.com/hyperledger/firefly/internal/log" "github.com/hyperledger/firefly/internal/retry" "github.com/hyperledger/firefly/mocks/databasemocks" + "github.com/hyperledger/firefly/mocks/sysmessagingmocks" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -31,7 +32,9 @@ import ( func newTestBatchProcessor(dispatch DispatchHandler) (*databasemocks.Plugin, *batchProcessor) { mdi := &databasemocks.Plugin{} - bp := newBatchProcessor(context.Background(), mdi, &batchProcessorConf{ + mni := &sysmessagingmocks.LocalNodeInfo{} + mni.On("GetNodeUUID", mock.Anything).Return(fftypes.NewUUID()).Maybe() + bp := newBatchProcessor(context.Background(), mni, mdi, &batchProcessorConf{ namespace: "ns1", identity: fftypes.Identity{Author: "did:firefly:org/abcd", Key: "0x12345"}, dispatch: dispatch, diff --git a/internal/blockchain/ethereum/ethereum_test.go b/internal/blockchain/ethereum/ethereum_test.go index 01781f9559..13bd17d7d2 100644 --- a/internal/blockchain/ethereum/ethereum_test.go +++ b/internal/blockchain/ethereum/ethereum_test.go @@ -53,7 +53,7 @@ func newTestEthereum() (*Ethereum, func()) { wsm := &wsmocks.WSClient{} e := &Ethereum{ ctx: ctx, - client: resty.New().SetHostURL("http://localhost:12345"), + client: resty.New().SetBaseURL("http://localhost:12345"), instancePath: "/instances/0x12345", topic: "topic1", prefixShort: defaultPrefixShort, diff --git a/internal/blockchain/fabric/fabric_test.go b/internal/blockchain/fabric/fabric_test.go index 05dd3c4a17..86470a1865 100644 --- a/internal/blockchain/fabric/fabric_test.go +++ b/internal/blockchain/fabric/fabric_test.go @@ -55,7 +55,7 @@ func newTestFabric() (*Fabric, func()) { wsm := &wsmocks.WSClient{} e := &Fabric{ ctx: ctx, - client: resty.New().SetHostURL("http://localhost:12345"), + client: resty.New().SetBaseURL("http://localhost:12345"), defaultChannel: "firefly", chaincode: "firefly", topic: "topic1", diff --git a/internal/broadcast/datatype_test.go b/internal/broadcast/datatype_test.go index 5677f1fc4c..9f6edaf283 100644 --- a/internal/broadcast/datatype_test.go +++ b/internal/broadcast/datatype_test.go @@ -24,6 +24,7 @@ import ( "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/datamocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -77,7 +78,7 @@ func TestBroadcastUpsertFail(t *testing.T) { mim := bm.identity.(*identitymanagermocks.Manager) mim.On("ResolveInputIdentity", mock.Anything, mock.Anything).Return(nil) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(fmt.Errorf("pop")) mdm.On("VerifyNamespaceExists", mock.Anything, "ns1").Return(nil) mdm.On("CheckDatatype", mock.Anything, "ns1", mock.Anything).Return(nil) @@ -98,7 +99,7 @@ func TestBroadcastDatatypeInvalid(t *testing.T) { mim := bm.identity.(*identitymanagermocks.Manager) mim.On("ResolveInputIdentity", mock.Anything, mock.Anything).Return(nil) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(nil) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) mdm.On("VerifyNamespaceExists", mock.Anything, "ns1").Return(nil) mdm.On("CheckDatatype", mock.Anything, "ns1", mock.Anything).Return(fmt.Errorf("pop")) @@ -119,10 +120,10 @@ func TestBroadcastBroadcastFail(t *testing.T) { mim := bm.identity.(*identitymanagermocks.Manager) mim.On("ResolveInputIdentity", mock.Anything, mock.Anything).Return(nil) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(nil) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) mdm.On("VerifyNamespaceExists", mock.Anything, "ns1").Return(nil) mdm.On("CheckDatatype", mock.Anything, "ns1", mock.Anything).Return(nil) - mdi.On("UpsertMessage", mock.Anything, mock.Anything, false, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertMessage", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(fmt.Errorf("pop")) _, err := bm.BroadcastDatatype(context.Background(), "ns1", &fftypes.Datatype{ Namespace: "ns1", @@ -141,10 +142,10 @@ func TestBroadcastOk(t *testing.T) { mim := bm.identity.(*identitymanagermocks.Manager) mim.On("ResolveInputIdentity", mock.Anything, mock.Anything).Return(nil) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(nil) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) mdm.On("VerifyNamespaceExists", mock.Anything, "ns1").Return(nil) mdm.On("CheckDatatype", mock.Anything, "ns1", mock.Anything).Return(nil) - mdi.On("UpsertMessage", mock.Anything, mock.Anything, false, false).Return(nil) + mdi.On("UpsertMessage", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) _, err := bm.BroadcastDatatype(context.Background(), "ns1", &fftypes.Datatype{ Namespace: "ns1", diff --git a/internal/broadcast/definition.go b/internal/broadcast/definition.go index 4f3c22be13..23aa669631 100644 --- a/internal/broadcast/definition.go +++ b/internal/broadcast/definition.go @@ -21,6 +21,7 @@ import ( "encoding/json" "github.com/hyperledger/firefly/internal/i18n" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" ) @@ -63,7 +64,7 @@ func (bm *broadcastManager) broadcastDefinitionCommon(ctx context.Context, def f } // Write as data to the local store - if err = bm.database.UpsertData(ctx, data, true, false /* we just generated the ID, so it is new */); err != nil { + if err = bm.database.UpsertData(ctx, data, database.UpsertOptimizationNew); err != nil { return nil, err } diff --git a/internal/broadcast/definition_test.go b/internal/broadcast/definition_test.go index ab57c9a7e7..c88e752175 100644 --- a/internal/broadcast/definition_test.go +++ b/internal/broadcast/definition_test.go @@ -23,6 +23,7 @@ import ( "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" "github.com/hyperledger/firefly/mocks/syncasyncmocks" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -36,7 +37,7 @@ func TestBroadcastDefinitionAsNodeConfirm(t *testing.T) { msa := bm.syncasync.(*syncasyncmocks.Bridge) mim := bm.identity.(*identitymanagermocks.Manager) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(nil) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) mim.On("ResolveInputIdentity", mock.Anything, mock.Anything).Return(nil) msa.On("WaitForMessage", bm.ctx, "ff_system", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop")) @@ -53,7 +54,7 @@ func TestBroadcastDefinitionAsNodeUpsertFail(t *testing.T) { defer cancel() mdi := bm.database.(*databasemocks.Plugin) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(fmt.Errorf("pop")) mim := bm.identity.(*identitymanagermocks.Manager) mim.On("ResolveInputIdentity", mock.Anything, mock.Anything).Return(nil) _, err := bm.BroadcastDefinitionAsNode(bm.ctx, &fftypes.Namespace{}, fftypes.SystemTagDefineNamespace, false) @@ -81,7 +82,7 @@ func TestBroadcastRootOrgDefinitionPassedThroughAnyIdentity(t *testing.T) { mim.On("OrgDID", mock.Anything, mock.Anything).Return("did:firefly:org/12345", nil) // Should call through to upsert data, stop test there mdi := bm.database.(*databasemocks.Plugin) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(fmt.Errorf("pop")) _, err := bm.BroadcastRootOrgDefinition(bm.ctx, &fftypes.Organization{ ID: fftypes.NewUUID(), diff --git a/internal/broadcast/manager_test.go b/internal/broadcast/manager_test.go index 95555f7596..f1d574380d 100644 --- a/internal/broadcast/manager_test.go +++ b/internal/broadcast/manager_test.go @@ -34,6 +34,7 @@ import ( "github.com/hyperledger/firefly/mocks/identitymanagermocks" "github.com/hyperledger/firefly/mocks/publicstoragemocks" "github.com/hyperledger/firefly/mocks/syncasyncmocks" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -81,7 +82,7 @@ func TestBroadcastMessageGood(t *testing.T) { defer cancel() msg := &fftypes.MessageInOut{} - bm.database.(*databasemocks.Plugin).On("UpsertMessage", mock.Anything, &msg.Message, false, false).Return(nil) + bm.database.(*databasemocks.Plugin).On("UpsertMessage", mock.Anything, &msg.Message, database.UpsertOptimizationNew).Return(nil) broadcast := broadcastSender{ mgr: bm, diff --git a/internal/broadcast/message.go b/internal/broadcast/message.go index 52016e1822..ba49fc461b 100644 --- a/internal/broadcast/message.go +++ b/internal/broadcast/message.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger/firefly/internal/i18n" "github.com/hyperledger/firefly/internal/sysmessaging" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" ) @@ -157,7 +158,7 @@ func (s *broadcastSender) sendInternal(ctx context.Context, method sendMethod) ( } // Store the message - this asynchronously triggers the next step in process - return s.mgr.database.UpsertMessage(ctx, &s.msg.Message, false, false) + return s.mgr.database.UpsertMessage(ctx, &s.msg.Message, database.UpsertOptimizationNew) } func (s *broadcastSender) isRootOrgBroadcast(ctx context.Context) bool { diff --git a/internal/broadcast/message_test.go b/internal/broadcast/message_test.go index 9d683431ce..da193aa297 100644 --- a/internal/broadcast/message_test.go +++ b/internal/broadcast/message_test.go @@ -33,6 +33,7 @@ import ( "github.com/hyperledger/firefly/mocks/identitymanagermocks" "github.com/hyperledger/firefly/mocks/publicstoragemocks" "github.com/hyperledger/firefly/mocks/syncasyncmocks" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -54,7 +55,7 @@ func TestBroadcastMessageOk(t *testing.T) { mdm.On("ResolveInlineDataBroadcast", ctx, "ns1", mock.Anything).Return(fftypes.DataRefs{ {ID: fftypes.NewUUID(), Hash: fftypes.NewRandB32()}, }, []*fftypes.DataAndBlob{}, nil) - mdi.On("UpsertMessage", ctx, mock.Anything, false, false).Return(nil) + mdi.On("UpsertMessage", ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil) mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) msg, err := bm.BroadcastMessage(ctx, "ns1", &fftypes.MessageInOut{ @@ -111,7 +112,7 @@ func TestBroadcastRootOrg(t *testing.T) { mdm.On("ResolveInlineDataBroadcast", ctx, "ns1", mock.Anything).Return(fftypes.DataRefs{ {ID: fftypes.NewUUID(), Hash: fftypes.NewRandB32()}, }, []*fftypes.DataAndBlob{}, nil) - mdi.On("UpsertMessage", ctx, mock.Anything, false, false).Return(nil) + mdi.On("UpsertMessage", ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil) mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) msg, err := bm.BroadcastMessage(ctx, "ns1", &fftypes.MessageInOut{ @@ -213,7 +214,7 @@ func TestBroadcastMessageWaitConfirmOk(t *testing.T) { send(ctx) }). Return(replyMsg, nil) - mdi.On("UpsertMessage", ctx, mock.Anything, false, false).Return(nil) + mdi.On("UpsertMessage", ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil) msg, err := bm.BroadcastMessage(ctx, "ns1", &fftypes.MessageInOut{ Message: fftypes.Message{ @@ -278,7 +279,7 @@ func TestBroadcastMessageWithBlobsOk(t *testing.T) { return true })).Return("payload-ref", nil) mdi.On("UpdateData", ctx, mock.Anything, mock.Anything).Return(nil) - mdi.On("UpsertMessage", ctx, mock.Anything, false, false).Return(nil) + mdi.On("UpsertMessage", ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil) mim.On("ResolveInputIdentity", ctx, mock.Anything).Return(nil) msg, err := bm.BroadcastMessage(ctx, "ns1", &fftypes.MessageInOut{ diff --git a/internal/broadcast/namespace_test.go b/internal/broadcast/namespace_test.go index 361b77c5af..eabbe539cb 100644 --- a/internal/broadcast/namespace_test.go +++ b/internal/broadcast/namespace_test.go @@ -24,6 +24,7 @@ import ( "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/datamocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -68,9 +69,9 @@ func TestBroadcastNamespaceBroadcastOk(t *testing.T) { mim.On("ResolveInputIdentity", mock.Anything, mock.Anything).Return(nil) mdi.On("GetNamespace", mock.Anything, mock.Anything).Return(&fftypes.Namespace{Name: "ns1"}, nil) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(nil) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) mdm.On("CheckDatatype", mock.Anything, "ns1", mock.Anything).Return(nil) - mdi.On("UpsertMessage", mock.Anything, mock.Anything, false, false).Return(nil) + mdi.On("UpsertMessage", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) buff := strings.Builder{} buff.Grow(4097) for i := 0; i < 4097; i++ { diff --git a/internal/broadcast/tokenpool_test.go b/internal/broadcast/tokenpool_test.go index 6ad2e242b2..72945e5afc 100644 --- a/internal/broadcast/tokenpool_test.go +++ b/internal/broadcast/tokenpool_test.go @@ -24,6 +24,7 @@ import ( "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/datamocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -100,8 +101,8 @@ func TestBroadcastTokenPoolBroadcastFail(t *testing.T) { mim.On("ResolveInputIdentity", mock.Anything, mock.Anything).Return(nil) mdm.On("VerifyNamespaceExists", mock.Anything, "ns1").Return(nil) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(nil) - mdi.On("UpsertMessage", mock.Anything, mock.Anything, false, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) + mdi.On("UpsertMessage", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(fmt.Errorf("pop")) _, err := bm.BroadcastTokenPool(context.Background(), "ns1", pool, false) assert.EqualError(t, err, "pop") @@ -132,8 +133,8 @@ func TestBroadcastTokenPoolOk(t *testing.T) { mim.On("ResolveInputIdentity", mock.Anything, mock.Anything).Return(nil) mdm.On("VerifyNamespaceExists", mock.Anything, "ns1").Return(nil) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(nil) - mdi.On("UpsertMessage", mock.Anything, mock.Anything, false, false).Return(nil) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) + mdi.On("UpsertMessage", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) _, err := bm.BroadcastTokenPool(context.Background(), "ns1", pool, false) assert.NoError(t, err) diff --git a/internal/data/blobstore.go b/internal/data/blobstore.go index 24b43b5ac0..8a9724cc59 100644 --- a/internal/data/blobstore.go +++ b/internal/data/blobstore.go @@ -122,7 +122,7 @@ func (bs *blobStore) UploadBLOB(ctx context.Context, ns string, inData *fftypes. log.L(ctx).Infof("Uploaded BLOB %.2fkb blobhash=%s hash=%s", float64(written)/1024, data.Blob.Hash, data.Hash) err = bs.database.RunAsGroup(ctx, func(ctx context.Context) error { - err := bs.database.UpsertData(ctx, data, false, false) + err := bs.database.UpsertData(ctx, data, database.UpsertOptimizationNew) if err == nil { err = bs.database.InsertBlob(ctx, &fftypes.Blob{ Hash: hash, diff --git a/internal/data/blobstore_test.go b/internal/data/blobstore_test.go index 714706eb6f..64d5ae1fdd 100644 --- a/internal/data/blobstore_test.go +++ b/internal/data/blobstore_test.go @@ -30,6 +30,7 @@ import ( "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/dataexchangemocks" "github.com/hyperledger/firefly/mocks/publicstoragemocks" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -52,7 +53,7 @@ func TestUploadBlobOk(t *testing.T) { a[1].(func(context.Context) error)(a[0].(context.Context)), } } - mdi.On("UpsertData", mock.Anything, mock.Anything, false, false).Return(nil) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) mdi.On("InsertBlob", mock.Anything, mock.Anything).Return(nil) dxID := make(chan fftypes.UUID, 1) @@ -94,7 +95,7 @@ func TestUploadBlobAutoMetaOk(t *testing.T) { a[1].(func(context.Context) error)(a[0].(context.Context)), } } - mdi.On("UpsertData", mock.Anything, mock.Anything, false, false).Return(nil) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) mdi.On("InsertBlob", mock.Anything, mock.Anything).Return(nil) dxID := make(chan fftypes.UUID, 1) diff --git a/internal/data/data_manager.go b/internal/data/data_manager.go index 39f0e61bd9..57772af1c1 100644 --- a/internal/data/data_manager.go +++ b/internal/data/data_manager.go @@ -260,7 +260,7 @@ func (dm *dataManager) validateAndStore(ctx context.Context, ns string, validato } err = data.Seal(ctx) if err == nil { - err = dm.database.UpsertData(ctx, data, false, false) + err = dm.database.UpsertData(ctx, data, database.UpsertOptimizationNew) } if err != nil { return nil, nil, err diff --git a/internal/data/data_manager_test.go b/internal/data/data_manager_test.go index 097030d726..ffd33cdb2c 100644 --- a/internal/data/data_manager_test.go +++ b/internal/data/data_manager_test.go @@ -25,6 +25,7 @@ import ( "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/dataexchangemocks" "github.com/hyperledger/firefly/mocks/publicstoragemocks" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -405,7 +406,7 @@ func TestResolveInlineDataValueNoValidatorOK(t *testing.T) { defer cancel() mdi := dm.database.(*databasemocks.Plugin) - mdi.On("UpsertData", ctx, mock.Anything, false, false).Return(nil) + mdi.On("UpsertData", ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil) refs, err := dm.ResolveInlineDataPrivate(ctx, "ns1", fftypes.InlineData{ {Value: fftypes.Byteable(`{"some":"json"}`)}, @@ -421,7 +422,7 @@ func TestResolveInlineDataValueNoValidatorStoreFail(t *testing.T) { defer cancel() mdi := dm.database.(*databasemocks.Plugin) - mdi.On("UpsertData", ctx, mock.Anything, false, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertData", ctx, mock.Anything, database.UpsertOptimizationNew).Return(fmt.Errorf("pop")) _, err := dm.ResolveInlineDataPrivate(ctx, "ns1", fftypes.InlineData{ {Value: fftypes.Byteable(`{"some":"json"}`)}, @@ -434,7 +435,7 @@ func TestResolveInlineDataValueWithValidation(t *testing.T) { defer cancel() mdi := dm.database.(*databasemocks.Plugin) - mdi.On("UpsertData", ctx, mock.Anything, false, false).Return(nil) + mdi.On("UpsertData", ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil) mdi.On("GetDatatypeByName", ctx, "ns1", "customer", "0.0.1").Return(&fftypes.Datatype{ ID: fftypes.NewUUID(), Validator: fftypes.ValidatorTypeJSON, diff --git a/internal/database/sqlcommon/batch_sql.go b/internal/database/sqlcommon/batch_sql.go index 8c491b4768..8acb6da7d9 100644 --- a/internal/database/sqlcommon/batch_sql.go +++ b/internal/database/sqlcommon/batch_sql.go @@ -42,6 +42,7 @@ var ( "confirmed", "tx_type", "tx_id", + "node_id", } batchFilterFieldMap = map[string]string{ "type": "btype", @@ -49,6 +50,7 @@ var ( "transaction.type": "tx_type", "transaction.id": "tx_id", "group": "group_hash", + "node": "node_id", } ) @@ -98,6 +100,7 @@ func (s *SQLCommon) UpsertBatch(ctx context.Context, batch *fftypes.Batch, allow Set("confirmed", batch.Confirmed). Set("tx_type", batch.Payload.TX.Type). Set("tx_id", batch.Payload.TX.ID). + Set("node_id", batch.Node). Where(sq.Eq{"id": batch.ID}), func() { s.callbacks.UUIDCollectionNSEvent(database.CollectionBatches, fftypes.ChangeEventTypeUpdated, batch.Namespace, batch.ID) @@ -124,6 +127,7 @@ func (s *SQLCommon) UpsertBatch(ctx context.Context, batch *fftypes.Batch, allow batch.Confirmed, batch.Payload.TX.Type, batch.Payload.TX.ID, + batch.Node, ), func() { s.callbacks.UUIDCollectionNSEvent(database.CollectionBatches, fftypes.ChangeEventTypeCreated, batch.Namespace, batch.ID) @@ -152,6 +156,7 @@ func (s *SQLCommon) batchResult(ctx context.Context, row *sql.Rows) (*fftypes.Ba &batch.Confirmed, &batch.Payload.TX.Type, &batch.Payload.TX.ID, + &batch.Node, ) if err != nil { return nil, i18n.WrapError(ctx, err, i18n.MsgDBReadErr, "batches") diff --git a/internal/database/sqlcommon/batch_sql_test.go b/internal/database/sqlcommon/batch_sql_test.go index d172842b97..c39ecd6003 100644 --- a/internal/database/sqlcommon/batch_sql_test.go +++ b/internal/database/sqlcommon/batch_sql_test.go @@ -48,6 +48,7 @@ func TestBatch2EWithDB(t *testing.T) { Namespace: "ns1", Hash: fftypes.NewRandB32(), Created: fftypes.Now(), + Node: fftypes.NewUUID(), Payload: fftypes.BatchPayload{ Messages: []*fftypes.Message{ {Header: fftypes.MessageHeader{ID: msgID1}}, @@ -87,6 +88,7 @@ func TestBatch2EWithDB(t *testing.T) { Namespace: "ns1", Hash: fftypes.NewRandB32(), Created: fftypes.Now(), + Node: fftypes.NewUUID(), Payload: fftypes.BatchPayload{ TX: fftypes.TransactionRef{ ID: txid, diff --git a/internal/database/sqlcommon/message_sql.go b/internal/database/sqlcommon/message_sql.go index da6f17fb72..96ad301957 100644 --- a/internal/database/sqlcommon/message_sql.go +++ b/internal/database/sqlcommon/message_sql.go @@ -56,15 +56,85 @@ var ( } ) -func (s *SQLCommon) UpsertMessage(ctx context.Context, message *fftypes.Message, allowExisting, allowHashUpdate bool) (err error) { +func (s *SQLCommon) attemptMessageUpdate(ctx context.Context, tx *txWrapper, message *fftypes.Message) (int64, error) { + return s.updateTx(ctx, tx, + sq.Update("messages"). + Set("cid", message.Header.CID). + Set("mtype", string(message.Header.Type)). + Set("author", message.Header.Author). + Set("key", message.Header.Key). + Set("created", message.Header.Created). + Set("namespace", message.Header.Namespace). + Set("topics", message.Header.Topics). + Set("tag", message.Header.Tag). + Set("group_hash", message.Header.Group). + Set("datahash", message.Header.DataHash). + Set("hash", message.Hash). + Set("pins", message.Pins). + Set("state", message.State). + Set("confirmed", message.Confirmed). + Set("tx_type", message.Header.TxType). + Set("batch_id", message.BatchID). + Where(sq.Eq{ + "id": message.Header.ID, + "hash": message.Hash, + }), + func() { + s.callbacks.OrderedUUIDCollectionNSEvent(database.CollectionMessages, fftypes.ChangeEventTypeUpdated, message.Header.Namespace, message.Header.ID, -1) + }) +} + +func (s *SQLCommon) attemptMessageInsert(ctx context.Context, tx *txWrapper, message *fftypes.Message) (int64, error) { + return s.insertTx(ctx, tx, + sq.Insert("messages"). + Columns(msgColumns...). + Values( + message.Header.ID, + message.Header.CID, + string(message.Header.Type), + message.Header.Author, + message.Header.Key, + message.Header.Created, + message.Header.Namespace, + message.Header.Topics, + message.Header.Tag, + message.Header.Group, + message.Header.DataHash, + message.Hash, + message.Pins, + message.State, + message.Confirmed, + message.Header.TxType, + message.BatchID, + ), + func() { + s.callbacks.OrderedUUIDCollectionNSEvent(database.CollectionMessages, fftypes.ChangeEventTypeCreated, message.Header.Namespace, message.Header.ID, message.Sequence) + }) +} + +func (s *SQLCommon) UpsertMessage(ctx context.Context, message *fftypes.Message, optimization database.UpsertOptimization) (err error) { ctx, tx, autoCommit, err := s.beginOrUseTx(ctx) if err != nil { return err } defer s.rollbackTx(ctx, tx, autoCommit) - existing := false - if allowExisting { + // This is a performance critical function, as we stream data into the database for every message, in every batch. + // + // First attempt the operation based on the optimization passed in. + // The expectation is that this will practically hit of the time, as only recovery paths + // require us to go down the un-optimized route. + optimized := false + recreateDatarefs := false + if optimization == database.UpsertOptimizationNew { + _, opErr := s.attemptMessageInsert(ctx, tx, message) + optimized = opErr == nil + } else if optimization == database.UpsertOptimizationExisting { + rowsAffected, opErr := s.attemptMessageUpdate(ctx, tx, message) + optimized = opErr == nil && rowsAffected == 1 + } + + if !optimized { // Do a select within the transaction to detemine if the UUID already exists msgRows, _, err := s.queryTx(ctx, tx, sq.Select("hash", sequenceColumn). @@ -75,8 +145,8 @@ func (s *SQLCommon) UpsertMessage(ctx context.Context, message *fftypes.Message, return err } - existing = msgRows.Next() - if existing && !allowHashUpdate { + existing := msgRows.Next() + if existing { var hash *fftypes.Bytes32 _ = msgRows.Scan(&hash, &message.Sequence) if !fftypes.SafeHashCompare(hash, message.Hash) { @@ -84,80 +154,41 @@ func (s *SQLCommon) UpsertMessage(ctx context.Context, message *fftypes.Message, log.L(ctx).Errorf("Existing=%s New=%s", hash, message.Hash) return database.HashMismatch } + recreateDatarefs = true // non-optimized update path } msgRows.Close() - } - if existing { - - // Update the message - if _, err = s.updateTx(ctx, tx, - sq.Update("messages"). - Set("cid", message.Header.CID). - Set("mtype", string(message.Header.Type)). - Set("author", message.Header.Author). - Set("key", message.Header.Key). - Set("created", message.Header.Created). - Set("namespace", message.Header.Namespace). - Set("topics", message.Header.Topics). - Set("tag", message.Header.Tag). - Set("group_hash", message.Header.Group). - Set("datahash", message.Header.DataHash). - Set("hash", message.Hash). - Set("pins", message.Pins). - Set("state", message.State). - Set("confirmed", message.Confirmed). - Set("tx_type", message.Header.TxType). - Set("batch_id", message.BatchID). - Where(sq.Eq{"id": message.Header.ID}), - func() { - s.callbacks.OrderedUUIDCollectionNSEvent(database.CollectionMessages, fftypes.ChangeEventTypeUpdated, message.Header.Namespace, message.Header.ID, message.Sequence) - }, - ); err != nil { - return err + if existing { + // Update the message + if _, err = s.attemptMessageUpdate(ctx, tx, message); err != nil { + return err + } + } else { + if message.Sequence, err = s.attemptMessageInsert(ctx, tx, message); err != nil { + return err + } } - } else { - message.Sequence, err = s.insertTx(ctx, tx, - sq.Insert("messages"). - Columns(msgColumns...). - Values( - message.Header.ID, - message.Header.CID, - string(message.Header.Type), - message.Header.Author, - message.Header.Key, - message.Header.Created, - message.Header.Namespace, - message.Header.Topics, - message.Header.Tag, - message.Header.Group, - message.Header.DataHash, - message.Hash, - message.Pins, - message.State, - message.Confirmed, - message.Header.TxType, - message.BatchID, - ), - func() { - s.callbacks.OrderedUUIDCollectionNSEvent(database.CollectionMessages, fftypes.ChangeEventTypeCreated, message.Header.Namespace, message.Header.ID, message.Sequence) - }, - ) - if err != nil { + } + + // Note the message data refs are not allowed to change, as they are part of the hash. + // So the optimization above relies on the fact these are in a transaction, so the + // whole message (with datarefs) will have been inserted + if !optimized || optimization == database.UpsertOptimizationNew { + if err = s.updateMessageDataRefs(ctx, tx, message, recreateDatarefs); err != nil { return err } } - if err = s.updateMessageDataRefs(ctx, tx, message, existing); err != nil { - return err + if optimization != database.UpsertOptimizationSkip { + message.Sequence = -1 // code that allows the optimization, MUST not rely on a sequence being returned. } return s.commitTx(ctx, tx, autoCommit) } -func (s *SQLCommon) updateMessageDataRefs(ctx context.Context, tx *txWrapper, message *fftypes.Message, existing bool) error { +func (s *SQLCommon) updateMessageDataRefs(ctx context.Context, tx *txWrapper, message *fftypes.Message, recreateDatarefs bool) error { - if existing { + if recreateDatarefs { if err := s.deleteTx(ctx, tx, sq.Delete("messages_data"). Where(sq.And{ diff --git a/internal/database/sqlcommon/message_sql_test.go b/internal/database/sqlcommon/message_sql_test.go index bc800a2929..af258ef29b 100644 --- a/internal/database/sqlcommon/message_sql_test.go +++ b/internal/database/sqlcommon/message_sql_test.go @@ -72,7 +72,7 @@ func TestUpsertE2EWithDB(t *testing.T) { s.callbacks.On("OrderedUUIDCollectionNSEvent", database.CollectionMessages, fftypes.ChangeEventTypeCreated, "ns12345", msgID, mock.Anything).Return() s.callbacks.On("OrderedUUIDCollectionNSEvent", database.CollectionMessages, fftypes.ChangeEventTypeUpdated, "ns12345", msgID, mock.Anything).Return() - err := s.UpsertMessage(ctx, msg, false, false) + err := s.UpsertMessage(ctx, msg, database.UpsertOptimizationNew) assert.NoError(t, err) // Check we get the exact same message back @@ -87,8 +87,6 @@ func TestUpsertE2EWithDB(t *testing.T) { // Update the message (this is testing what's possible at the database layer, // and does not account for the verification that happens at the higher level) - dataID3 := fftypes.NewUUID() - rand3 := fftypes.NewRandB32() cid := fftypes.NewUUID() gid := fftypes.NewRandB32() bid := fftypes.NewUUID() @@ -115,16 +113,21 @@ func TestUpsertE2EWithDB(t *testing.T) { Confirmed: fftypes.Now(), BatchID: bid, Data: []*fftypes.DataRef{ - {ID: dataID2, Hash: rand2}, - {ID: dataID3, Hash: rand3}, + {ID: dataID1, Hash: rand1}, + {ID: dataID2, Hash: rand2}, // Note the data refs cannot change, as it would affect the hash, and the hash is immutable }, } - // Ensure hash change rejected - err = s.UpsertMessage(context.Background(), msgUpdated, true, false) + // Ensure hash change rejected, on any optimization + err = s.UpsertMessage(context.Background(), msgUpdated, database.UpsertOptimizationSkip) + assert.Equal(t, database.HashMismatch, err) + err = s.UpsertMessage(context.Background(), msgUpdated, database.UpsertOptimizationNew) + assert.Equal(t, database.HashMismatch, err) + err = s.UpsertMessage(context.Background(), msgUpdated, database.UpsertOptimizationExisting) assert.Equal(t, database.HashMismatch, err) - err = s.UpsertMessage(context.Background(), msgUpdated, true, true) + msgUpdated.Hash = msg.Hash + err = s.UpsertMessage(context.Background(), msgUpdated, database.UpsertOptimizationExisting) assert.NoError(t, err) // Check we get the exact same message back - note the removal of one of the data elements @@ -166,9 +169,9 @@ func TestUpsertE2EWithDB(t *testing.T) { assert.Equal(t, msgUpdated.Sequence, msgRefs[0].Sequence) // Check we can get it with a filter on only mesasges with a particular data ref - msgs, _, err = s.GetMessagesForData(ctx, dataID3, filter.Count(true)) + msgs, _, err = s.GetMessagesForData(ctx, dataID2, filter.Count(true)) assert.Regexp(t, "FF10267", err) // The left join means it will take non-trivial extra work to support this. So not supported for now - msgs, _, err = s.GetMessagesForData(ctx, dataID3, filter.Count(false)) + msgs, _, err = s.GetMessagesForData(ctx, dataID2, filter.Count(false)) assert.NoError(t, err) assert.Equal(t, 1, len(msgs)) msgReadJson, _ = json.Marshal(msgs[0]) @@ -208,7 +211,7 @@ func TestUpsertE2EWithDB(t *testing.T) { func TestUpsertMessageFailBegin(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectBegin().WillReturnError(fmt.Errorf("pop")) - err := s.UpsertMessage(context.Background(), &fftypes.Message{}, true, true) + err := s.UpsertMessage(context.Background(), &fftypes.Message{}, database.UpsertOptimizationSkip) assert.Regexp(t, "FF10114", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -219,7 +222,7 @@ func TestUpsertMessageFailSelect(t *testing.T) { mock.ExpectQuery("SELECT .*").WillReturnError(fmt.Errorf("pop")) mock.ExpectRollback() msgID := fftypes.NewUUID() - err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, true, true) + err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, database.UpsertOptimizationSkip) assert.Regexp(t, "FF10115", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -231,7 +234,7 @@ func TestUpsertMessageFailInsert(t *testing.T) { mock.ExpectExec("INSERT .*").WillReturnError(fmt.Errorf("pop")) mock.ExpectRollback() msgID := fftypes.NewUUID() - err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, true, true) + err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, database.UpsertOptimizationSkip) assert.Regexp(t, "FF10116", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -243,7 +246,7 @@ func TestUpsertMessageFailUpdate(t *testing.T) { mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(msgID.String())) mock.ExpectExec("UPDATE .*").WillReturnError(fmt.Errorf("pop")) mock.ExpectRollback() - err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, true, true) + err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, database.UpsertOptimizationSkip) assert.Regexp(t, "FF10117", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -256,7 +259,7 @@ func TestUpsertMessageFailUpdateRefs(t *testing.T) { mock.ExpectExec("UPDATE .*").WillReturnResult(driver.ResultNoRows) mock.ExpectExec("DELETE .*").WillReturnError(fmt.Errorf("pop")) mock.ExpectRollback() - err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, true, true) + err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, database.UpsertOptimizationSkip) assert.Regexp(t, "FF10118", err) assert.NoError(t, mock.ExpectationsWereMet()) } @@ -268,7 +271,7 @@ func TestUpsertMessageFailCommit(t *testing.T) { mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"id"})) mock.ExpectExec("INSERT .*").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit().WillReturnError(fmt.Errorf("pop")) - err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, true, true) + err := s.UpsertMessage(context.Background(), &fftypes.Message{Header: fftypes.MessageHeader{ID: msgID}}, database.UpsertOptimizationSkip) assert.Regexp(t, "FF10119", err) assert.NoError(t, mock.ExpectationsWereMet()) } diff --git a/internal/database/sqlcommon/provider.go b/internal/database/sqlcommon/provider.go index d00e77f6a2..674cb0e0bc 100644 --- a/internal/database/sqlcommon/provider.go +++ b/internal/database/sqlcommon/provider.go @@ -45,6 +45,6 @@ type Provider interface { // PlaceholderFormat gets the Squirrel placeholder format PlaceholderFormat() sq.PlaceholderFormat - // UpdateInsertForReturn updates the insert query for returning the Sequenc, and returns whether it needs to be run as a query to return the Sequence field + // UpdateInsertForSequenceReturn updates the INSERT query for returning the Sequence, and returns whether it needs to be run as a query to return the Sequence field UpdateInsertForSequenceReturn(insert sq.InsertBuilder) (updatedInsert sq.InsertBuilder, runAsQuery bool) } diff --git a/internal/events/batch_pin_complete_test.go b/internal/events/batch_pin_complete_test.go index d3838e9930..0ebabf39cf 100644 --- a/internal/events/batch_pin_complete_test.go +++ b/internal/events/batch_pin_complete_test.go @@ -380,11 +380,45 @@ func TestPersistBatchSwallowBadData(t *testing.T) { mdi.AssertExpectations(t) } -func TestPersistBatchGoodDataUpsertFail(t *testing.T) { +func TestPersistBatchGoodDataUpsertOptimizeExistingFail(t *testing.T) { em, cancel := newTestEventManager(t) defer cancel() batch := &fftypes.Batch{ - ID: fftypes.NewUUID(), + ID: fftypes.NewUUID(), + Node: testNodeID, + Identity: fftypes.Identity{ + Author: "author1", + Key: "0x12345", + }, + Namespace: "ns1", + Payload: fftypes.BatchPayload{ + TX: fftypes.TransactionRef{ + Type: fftypes.TransactionTypeBatchPin, + ID: fftypes.NewUUID(), + }, + Data: []*fftypes.Data{ + {ID: fftypes.NewUUID(), Value: fftypes.Byteable(`"test"`)}, + }, + }, + } + batch.Payload.Data[0].Hash = batch.Payload.Data[0].Value.Hash() + batch.Hash = batch.Payload.Hash() + + mdi := em.database.(*databasemocks.Plugin) + mdi.On("UpsertBatch", mock.Anything, mock.Anything, false).Return(nil) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationExisting).Return(fmt.Errorf("pop")) + + valid, err := em.persistBatch(context.Background(), batch) + assert.False(t, valid) + assert.EqualError(t, err, "pop") +} + +func TestPersistBatchGoodDataUpsertOptimizeNewFail(t *testing.T) { + em, cancel := newTestEventManager(t) + defer cancel() + batch := &fftypes.Batch{ + ID: fftypes.NewUUID(), + Node: fftypes.NewUUID(), Identity: fftypes.Identity{ Author: "author1", Key: "0x12345", @@ -405,7 +439,7 @@ func TestPersistBatchGoodDataUpsertFail(t *testing.T) { mdi := em.database.(*databasemocks.Plugin) mdi.On("UpsertBatch", mock.Anything, mock.Anything, false).Return(nil) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(fmt.Errorf("pop")) valid, err := em.persistBatch(context.Background(), batch) assert.False(t, valid) @@ -444,7 +478,7 @@ func TestPersistBatchGoodDataMessageFail(t *testing.T) { mdi := em.database.(*databasemocks.Plugin) mdi.On("UpsertBatch", mock.Anything, mock.Anything, false).Return(nil) - mdi.On("UpsertMessage", mock.Anything, mock.Anything, true, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertMessage", mock.Anything, mock.Anything, database.UpsertOptimizationSkip).Return(fmt.Errorf("pop")) valid, err := em.persistBatch(context.Background(), batch) assert.False(t, valid) @@ -498,7 +532,7 @@ func TestPersistBatchDataNilData(t *testing.T) { data := &fftypes.Data{ ID: fftypes.NewUUID(), } - err := em.persistBatchData(context.Background(), batch, 0, data) + err := em.persistBatchData(context.Background(), batch, 0, data, database.UpsertOptimizationSkip) assert.NoError(t, err) } @@ -513,7 +547,7 @@ func TestPersistBatchDataBadHash(t *testing.T) { Value: fftypes.Byteable(`"test"`), Hash: fftypes.NewRandB32(), } - err := em.persistBatchData(context.Background(), batch, 0, data) + err := em.persistBatchData(context.Background(), batch, 0, data, database.UpsertOptimizationSkip) assert.NoError(t, err) } @@ -528,9 +562,9 @@ func TestPersistBatchDataUpsertHashMismatch(t *testing.T) { data.Hash = data.Value.Hash() mdi := em.database.(*databasemocks.Plugin) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(database.HashMismatch) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationSkip).Return(database.HashMismatch) - err := em.persistBatchData(context.Background(), batch, 0, data) + err := em.persistBatchData(context.Background(), batch, 0, data, database.UpsertOptimizationSkip) assert.NoError(t, err) mdi.AssertExpectations(t) } @@ -546,9 +580,9 @@ func TestPersistBatchDataUpsertDataError(t *testing.T) { data.Hash = data.Value.Hash() mdi := em.database.(*databasemocks.Plugin) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationSkip).Return(fmt.Errorf("pop")) - err := em.persistBatchData(context.Background(), batch, 0, data) + err := em.persistBatchData(context.Background(), batch, 0, data, database.UpsertOptimizationSkip) assert.EqualError(t, err, "pop") } @@ -563,9 +597,9 @@ func TestPersistBatchDataOk(t *testing.T) { data.Hash = data.Value.Hash() mdi := em.database.(*databasemocks.Plugin) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(nil) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationSkip).Return(nil) - err := em.persistBatchData(context.Background(), batch, 0, data) + err := em.persistBatchData(context.Background(), batch, 0, data, database.UpsertOptimizationSkip) assert.NoError(t, err) mdi.AssertExpectations(t) } @@ -581,7 +615,7 @@ func TestPersistBatchMessageNilData(t *testing.T) { ID: fftypes.NewUUID(), }, } - err := em.persistBatchMessage(context.Background(), batch, 0, msg) + err := em.persistBatchMessage(context.Background(), batch, 0, msg, database.UpsertOptimizationSkip) assert.NoError(t, err) } @@ -601,9 +635,9 @@ func TestPersistBatchMessageUpsertHashMismatch(t *testing.T) { assert.NoError(t, msg.Verify(context.Background())) mdi := em.database.(*databasemocks.Plugin) - mdi.On("UpsertMessage", mock.Anything, mock.Anything, true, false).Return(database.HashMismatch) + mdi.On("UpsertMessage", mock.Anything, mock.Anything, database.UpsertOptimizationSkip).Return(database.HashMismatch) - err := em.persistBatchMessage(context.Background(), batch, 0, msg) + err := em.persistBatchMessage(context.Background(), batch, 0, msg, database.UpsertOptimizationSkip) assert.NoError(t, err) mdi.AssertExpectations(t) } @@ -624,9 +658,9 @@ func TestPersistBatchMessageUpsertMessageFail(t *testing.T) { assert.NoError(t, msg.Verify(context.Background())) mdi := em.database.(*databasemocks.Plugin) - mdi.On("UpsertMessage", mock.Anything, mock.Anything, true, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertMessage", mock.Anything, mock.Anything, database.UpsertOptimizationSkip).Return(fmt.Errorf("pop")) - err := em.persistBatchMessage(context.Background(), batch, 0, msg) + err := em.persistBatchMessage(context.Background(), batch, 0, msg, database.UpsertOptimizationSkip) assert.EqualError(t, err, "pop") } @@ -646,9 +680,9 @@ func TestPersistBatchMessageOK(t *testing.T) { assert.NoError(t, msg.Verify(context.Background())) mdi := em.database.(*databasemocks.Plugin) - mdi.On("UpsertMessage", mock.Anything, mock.Anything, true, false).Return(nil) + mdi.On("UpsertMessage", mock.Anything, mock.Anything, database.UpsertOptimizationSkip).Return(nil) - err := em.persistBatchMessage(context.Background(), batch, 0, msg) + err := em.persistBatchMessage(context.Background(), batch, 0, msg, database.UpsertOptimizationSkip) assert.NoError(t, err) mdi.AssertExpectations(t) } diff --git a/internal/events/dx_callbacks.go b/internal/events/dx_callbacks.go index b6e5dcef63..0878a8d628 100644 --- a/internal/events/dx_callbacks.go +++ b/internal/events/dx_callbacks.go @@ -290,14 +290,14 @@ func (em *eventManager) unpinnedMessageReceived(peerID string, message *fftypes. // Persist the data for i, d := range data { - if ok, err := em.persistReceivedData(ctx, i, d, "message", message.Header.ID); err != nil || !ok { + if ok, err := em.persistReceivedData(ctx, i, d, "message", message.Header.ID, database.UpsertOptimizationSkip); err != nil || !ok { return err } } // Persist the message - immediately considered confirmed as this is an unpinned receive message.Confirmed = fftypes.Now() - if ok, err := em.persistReceivedMessage(ctx, 0, message, "message", message.Header.ID); err != nil || !ok { + if ok, err := em.persistReceivedMessage(ctx, 0, message, "message", message.Header.ID, database.UpsertOptimizationSkip); err != nil || !ok { return err } diff --git a/internal/events/dx_callbacks_test.go b/internal/events/dx_callbacks_test.go index 6dea5bad66..01182db389 100644 --- a/internal/events/dx_callbacks_test.go +++ b/internal/events/dx_callbacks_test.go @@ -24,6 +24,7 @@ import ( "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/dataexchangemocks" "github.com/hyperledger/firefly/mocks/syshandlersmocks" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -681,7 +682,7 @@ func TestMessageReceiveMessagePersistMessageFail(t *testing.T) { mdi.On("GetOrganizationByIdentity", em.ctx, "0x12345").Return(&fftypes.Organization{ Identity: "0x12345", }, nil) - mdi.On("UpsertMessage", em.ctx, mock.Anything, true, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertMessage", em.ctx, mock.Anything, database.UpsertOptimizationSkip).Return(fmt.Errorf("pop")) err = em.MessageReceived(mdx, "peer1", b) assert.Regexp(t, "FF10158", err) @@ -731,7 +732,7 @@ func TestMessageReceiveMessagePersistDataFail(t *testing.T) { mdi.On("GetOrganizationByIdentity", em.ctx, "0x12345").Return(&fftypes.Organization{ Identity: "0x12345", }, nil) - mdi.On("UpsertData", em.ctx, mock.Anything, true, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertData", em.ctx, mock.Anything, database.UpsertOptimizationSkip).Return(fmt.Errorf("pop")) err = em.MessageReceived(mdx, "peer1", b) assert.Regexp(t, "FF10158", err) @@ -781,8 +782,8 @@ func TestMessageReceiveMessagePersistEventFail(t *testing.T) { mdi.On("GetOrganizationByIdentity", em.ctx, "0x12345").Return(&fftypes.Organization{ Identity: "0x12345", }, nil) - mdi.On("UpsertData", em.ctx, mock.Anything, true, false).Return(nil) - mdi.On("UpsertMessage", em.ctx, mock.Anything, true, false).Return(nil) + mdi.On("UpsertData", em.ctx, mock.Anything, database.UpsertOptimizationSkip).Return(nil) + mdi.On("UpsertMessage", em.ctx, mock.Anything, database.UpsertOptimizationSkip).Return(nil) mdi.On("InsertEvent", em.ctx, mock.Anything).Return(fmt.Errorf("pop")) err = em.MessageReceived(mdx, "peer1", b) diff --git a/internal/events/event_manager.go b/internal/events/event_manager.go index 30f04c88be..62aa65a29a 100644 --- a/internal/events/event_manager.go +++ b/internal/events/event_manager.go @@ -73,6 +73,7 @@ type EventManager interface { type eventManager struct { ctx context.Context + ni sysmessaging.LocalNodeInfo publicstorage publicstorage.Plugin database database.Plugin identity identity.Manager @@ -91,14 +92,15 @@ type eventManager struct { internalEvents *system.Events } -func NewEventManager(ctx context.Context, pi publicstorage.Plugin, di database.Plugin, im identity.Manager, sh syshandlers.SystemHandlers, dm data.Manager, bm broadcast.Manager, pm privatemessaging.Manager) (EventManager, error) { - if pi == nil || di == nil || im == nil || dm == nil || bm == nil || pm == nil { +func NewEventManager(ctx context.Context, ni sysmessaging.LocalNodeInfo, pi publicstorage.Plugin, di database.Plugin, im identity.Manager, sh syshandlers.SystemHandlers, dm data.Manager, bm broadcast.Manager, pm privatemessaging.Manager) (EventManager, error) { + if ni == nil || pi == nil || di == nil || im == nil || dm == nil || bm == nil || pm == nil { return nil, i18n.NewError(ctx, i18n.MsgInitializationNilDepError) } newPinNotifier := newEventNotifier(ctx, "pins") newEventNotifier := newEventNotifier(ctx, "events") em := &eventManager{ ctx: log.WithLogField(ctx, "role", "event-manager"), + ni: ni, publicstorage: pi, database: di, identity: im, diff --git a/internal/events/event_manager_test.go b/internal/events/event_manager_test.go index 9d512b1300..c1523f46e1 100644 --- a/internal/events/event_manager_test.go +++ b/internal/events/event_manager_test.go @@ -31,11 +31,14 @@ import ( "github.com/hyperledger/firefly/mocks/privatemessagingmocks" "github.com/hyperledger/firefly/mocks/publicstoragemocks" "github.com/hyperledger/firefly/mocks/syshandlersmocks" + "github.com/hyperledger/firefly/mocks/sysmessagingmocks" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) +var testNodeID = fftypes.NewUUID() + func newTestEventManager(t *testing.T) (*eventManager, func()) { config.Reset() ctx, cancel := context.WithCancel(context.Background()) @@ -47,8 +50,10 @@ func newTestEventManager(t *testing.T) (*eventManager, func()) { msh := &syshandlersmocks.SystemHandlers{} mbm := &broadcastmocks.Manager{} mpm := &privatemessagingmocks.Manager{} + mni := &sysmessagingmocks.LocalNodeInfo{} + mni.On("GetNodeUUID", mock.Anything).Return(testNodeID).Maybe() met.On("Name").Return("ut").Maybe() - emi, err := NewEventManager(ctx, mpi, mdi, mim, msh, mdm, mbm, mpm) + emi, err := NewEventManager(ctx, mni, mpi, mdi, mim, msh, mdm, mbm, mpm) em := emi.(*eventManager) rag := mdi.On("RunAsGroup", em.ctx, mock.Anything).Maybe() rag.RunFn = func(a mock.Arguments) { @@ -78,7 +83,7 @@ func TestStartStop(t *testing.T) { } func TestStartStopBadDependencies(t *testing.T) { - _, err := NewEventManager(context.Background(), nil, nil, nil, nil, nil, nil, nil) + _, err := NewEventManager(context.Background(), nil, nil, nil, nil, nil, nil, nil, nil) assert.Regexp(t, "FF10128", err) } @@ -93,7 +98,8 @@ func TestStartStopBadTransports(t *testing.T) { msh := &syshandlersmocks.SystemHandlers{} mbm := &broadcastmocks.Manager{} mpm := &privatemessagingmocks.Manager{} - _, err := NewEventManager(context.Background(), mpi, mdi, mim, msh, mdm, mbm, mpm) + mni := &sysmessagingmocks.LocalNodeInfo{} + _, err := NewEventManager(context.Background(), mni, mpi, mdi, mim, msh, mdm, mbm, mpm) assert.Regexp(t, "FF10172", err) } diff --git a/internal/events/persist_batch.go b/internal/events/persist_batch.go index e9d51eb734..9783bf93ff 100644 --- a/internal/events/persist_batch.go +++ b/internal/events/persist_batch.go @@ -121,16 +121,18 @@ func (em *eventManager) persistBatch(ctx context.Context /* db TX context*/, bat return false, err // a persistence failure here is considered retryable (so returned) } + optimization := em.getOptimization(ctx, batch) + // Insert the data entries for i, data := range batch.Payload.Data { - if err = em.persistBatchData(ctx, batch, i, data); err != nil { + if err = em.persistBatchData(ctx, batch, i, data, optimization); err != nil { return false, err } } // Insert the message entries for i, msg := range batch.Payload.Messages { - if err = em.persistBatchMessage(ctx, batch, i, msg); err != nil { + if err = em.persistBatchMessage(ctx, batch, i, msg, optimization); err != nil { return false, err } } @@ -138,12 +140,25 @@ func (em *eventManager) persistBatch(ctx context.Context /* db TX context*/, bat return true, nil } -func (em *eventManager) persistBatchData(ctx context.Context /* db TX context*/, batch *fftypes.Batch, i int, data *fftypes.Data) error { - _, err := em.persistReceivedData(ctx, i, data, "batch", batch.ID) +func (em *eventManager) getOptimization(ctx context.Context, batch *fftypes.Batch) database.UpsertOptimization { + localNode := em.ni.GetNodeUUID(ctx) + if batch.Node == nil { + // This is from a node that hasn't yet completed registration, so we can't optimize + return database.UpsertOptimizationSkip + } else if localNode != nil && localNode.Equals(batch.Node) { + // We sent the batch, so we should already have all the messages and data locally - optimize the DB operations for that + return database.UpsertOptimizationExisting + } + // We didn't send the batch, so all the data should be new - optimize the DB operations for that + return database.UpsertOptimizationNew +} + +func (em *eventManager) persistBatchData(ctx context.Context /* db TX context*/, batch *fftypes.Batch, i int, data *fftypes.Data, optimization database.UpsertOptimization) error { + _, err := em.persistReceivedData(ctx, i, data, "batch", batch.ID, optimization) return err } -func (em *eventManager) persistReceivedData(ctx context.Context /* db TX context*/, i int, data *fftypes.Data, mType string, mID *fftypes.UUID) (bool, error) { +func (em *eventManager) persistReceivedData(ctx context.Context /* db TX context*/, i int, data *fftypes.Data, mType string, mID *fftypes.UUID, optimization database.UpsertOptimization) (bool, error) { l := log.L(ctx) l.Tracef("%s '%s' data %d: %+v", mType, mID, i, data) @@ -164,7 +179,7 @@ func (em *eventManager) persistReceivedData(ctx context.Context /* db TX context } // Insert the data, ensuring the hash doesn't change - if err := em.database.UpsertData(ctx, data, true, false); err != nil { + if err := em.database.UpsertData(ctx, data, optimization); err != nil { if err == database.HashMismatch { log.L(ctx).Errorf("Invalid data entry %d in %s '%s'. Hash mismatch with existing record with same UUID '%s' Hash=%s", i, mType, mID, data.ID, data.Hash) return false, nil // This is not retryable. skip this data entry @@ -176,17 +191,17 @@ func (em *eventManager) persistReceivedData(ctx context.Context /* db TX context return true, nil } -func (em *eventManager) persistBatchMessage(ctx context.Context /* db TX context*/, batch *fftypes.Batch, i int, msg *fftypes.Message) error { +func (em *eventManager) persistBatchMessage(ctx context.Context /* db TX context*/, batch *fftypes.Batch, i int, msg *fftypes.Message, optimization database.UpsertOptimization) error { if msg != nil && (msg.Header.Author != batch.Author || msg.Header.Key != batch.Key) { log.L(ctx).Errorf("Mismatched key/author '%s'/'%s' on message entry %d in batch '%s'", msg.Header.Key, msg.Header.Author, i, batch.ID) return nil // skip entry } - _, err := em.persistReceivedMessage(ctx, i, msg, "batch", batch.ID) + _, err := em.persistReceivedMessage(ctx, i, msg, "batch", batch.ID, optimization) return err } -func (em *eventManager) persistReceivedMessage(ctx context.Context /* db TX context*/, i int, msg *fftypes.Message, mType string, mID *fftypes.UUID) (bool, error) { +func (em *eventManager) persistReceivedMessage(ctx context.Context /* db TX context*/, i int, msg *fftypes.Message, mType string, mID *fftypes.UUID, optimization database.UpsertOptimization) (bool, error) { l := log.L(ctx) l.Tracef("%s '%s' message %d: %+v", mType, mID, i, msg) @@ -204,7 +219,7 @@ func (em *eventManager) persistReceivedMessage(ctx context.Context /* db TX cont // Insert the message, ensuring the hash doesn't change. // We do not mark it as confirmed at this point, that's the job of the aggregator. msg.State = fftypes.MessageStatePending - if err = em.database.UpsertMessage(ctx, msg, true, false); err != nil { + if err = em.database.UpsertMessage(ctx, msg, optimization); err != nil { if err == database.HashMismatch { l.Errorf("Invalid message entry %d in %s '%s'. Hash mismatch with existing record with same UUID '%s' Hash=%s", i, mType, mID, msg.Header.ID, msg.Hash) return false, nil // This is not retryable. skip this data entry diff --git a/internal/events/tokens_transferred.go b/internal/events/tokens_transferred.go index acb651b3e4..06e8509870 100644 --- a/internal/events/tokens_transferred.go +++ b/internal/events/tokens_transferred.go @@ -128,7 +128,7 @@ func (em *eventManager) TokensTransferred(tk tokens.Plugin, poolProtocolID strin if msg.State == fftypes.MessageStateStaged { // Message can now be sent msg.State = fftypes.MessageStateReady - if err := em.database.UpsertMessage(ctx, msg, true, false); err != nil { + if err := em.database.UpsertMessage(ctx, msg, database.UpsertOptimizationExisting); err != nil { return err } } else { diff --git a/internal/events/tokens_transferred_test.go b/internal/events/tokens_transferred_test.go index 597222a608..b6999af0df 100644 --- a/internal/events/tokens_transferred_test.go +++ b/internal/events/tokens_transferred_test.go @@ -214,10 +214,10 @@ func TestTokensTransferredWithMessageSend(t *testing.T) { mdi.On("UpsertTokenTransfer", em.ctx, transfer).Return(nil).Times(2) mdi.On("UpdateTokenBalances", em.ctx, transfer).Return(nil).Times(2) mdi.On("GetMessages", em.ctx, mock.Anything).Return(messages, nil, nil).Times(2) - mdi.On("UpsertMessage", em.ctx, mock.Anything, true, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertMessage", em.ctx, mock.Anything, database.UpsertOptimizationExisting).Return(fmt.Errorf("pop")) mdi.On("UpsertMessage", em.ctx, mock.MatchedBy(func(msg *fftypes.Message) bool { return msg.State == fftypes.MessageStateReady - }), true, false).Return(nil) + }), database.UpsertOptimizationExisting).Return(nil) mdi.On("InsertEvent", em.ctx, mock.MatchedBy(func(ev *fftypes.Event) bool { return ev.Type == fftypes.EventTypeTransferConfirmed && ev.Reference == transfer.LocalID && ev.Namespace == pool.Namespace })).Return(nil).Once() diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index d45edbad2e..e32311a6bb 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -143,6 +143,7 @@ type orchestrator struct { tokens map[string]tokens.Plugin bc boundCallbacks preInitMode bool + node *fftypes.UUID } func NewOrchestrator() Orchestrator { @@ -385,7 +386,7 @@ func (or *orchestrator) initComponents(ctx context.Context) (err error) { } if or.batch == nil { - or.batch, err = batch.NewBatchManager(ctx, or.database, or.data) + or.batch, err = batch.NewBatchManager(ctx, or, or.database, or.data) if err != nil { return err } @@ -416,7 +417,7 @@ func (or *orchestrator) initComponents(ctx context.Context) (err error) { or.syshandlers = syshandlers.NewSystemHandlers(or.database, or.dataexchange, or.data, or.broadcast, or.messaging, or.assets) if or.events == nil { - or.events, err = events.NewEventManager(ctx, or.publicstorage, or.database, or.identity, or.syshandlers, or.data, or.broadcast, or.messaging) + or.events, err = events.NewEventManager(ctx, or, or.publicstorage, or.database, or.identity, or.syshandlers, or.data, or.broadcast, or.messaging) if err != nil { return err } diff --git a/internal/orchestrator/status.go b/internal/orchestrator/status.go index 6476c05d71..cdcd6d69eb 100644 --- a/internal/orchestrator/status.go +++ b/internal/orchestrator/status.go @@ -20,9 +20,27 @@ import ( "context" "github.com/hyperledger/firefly/internal/config" + "github.com/hyperledger/firefly/internal/log" "github.com/hyperledger/firefly/pkg/fftypes" ) +func (or *orchestrator) GetNodeUUID(ctx context.Context) (node *fftypes.UUID) { + if or.node != nil { + return or.node + } + status, err := or.GetStatus(ctx) + if err != nil { + log.L(or.ctx).Warnf("Failed to query local node UUID: %s", err) + return nil + } + if status.Node.Registered { + log.L(or.ctx).Infof("Node not yet registered") + } else { + or.node = status.Node.ID + } + return or.node +} + func (or *orchestrator) GetStatus(ctx context.Context) (status *fftypes.NodeStatus, err error) { orgKey := or.identity.GetOrgKey(ctx) diff --git a/internal/privatemessaging/groupmanager.go b/internal/privatemessaging/groupmanager.go index f7e0af2aff..9e1edd500a 100644 --- a/internal/privatemessaging/groupmanager.go +++ b/internal/privatemessaging/groupmanager.go @@ -102,7 +102,7 @@ func (gm *groupManager) groupInit(ctx context.Context, signer *fftypes.Identity, } // Write as data to the local store - if err = gm.database.UpsertData(ctx, data, true, false /* we just generated the ID, so it is new */); err != nil { + if err = gm.database.UpsertData(ctx, data, database.UpsertOptimizationNew); err != nil { return err } @@ -127,7 +127,7 @@ func (gm *groupManager) groupInit(ctx context.Context, signer *fftypes.Identity, err = msg.Seal(ctx) if err == nil { // Store the message - this asynchronously triggers the next step in process - err = gm.database.UpsertMessage(ctx, msg, false, false) + err = gm.database.UpsertMessage(ctx, msg, database.UpsertOptimizationNew) } if err == nil { log.L(ctx).Infof("Created new group %s", group.Hash) diff --git a/internal/privatemessaging/groupmanager_test.go b/internal/privatemessaging/groupmanager_test.go index 7098bbab7e..df1615b23d 100644 --- a/internal/privatemessaging/groupmanager_test.go +++ b/internal/privatemessaging/groupmanager_test.go @@ -66,7 +66,7 @@ func TestGroupInitWriteDataFail(t *testing.T) { mdi := pm.database.(*databasemocks.Plugin) mdi.On("UpsertGroup", mock.Anything, mock.Anything, true).Return(nil) - mdi.On("UpsertData", mock.Anything, mock.Anything, true, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(fmt.Errorf("pop")) group := &fftypes.Group{ GroupIdentity: fftypes.GroupIdentity{ diff --git a/internal/privatemessaging/message.go b/internal/privatemessaging/message.go index 5bdf1ad047..b06ebf6179 100644 --- a/internal/privatemessaging/message.go +++ b/internal/privatemessaging/message.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger/firefly/internal/i18n" "github.com/hyperledger/firefly/internal/sysmessaging" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" ) @@ -177,7 +178,7 @@ func (s *messageSender) sendInternal(ctx context.Context, method sendMethod) err } // Store the message - this asynchronously triggers the next step in process - if err := s.mgr.database.UpsertMessage(ctx, &s.msg.Message, false, false); err != nil { + if err := s.mgr.database.UpsertMessage(ctx, &s.msg.Message, database.UpsertOptimizationNew); err != nil { return err } diff --git a/internal/privatemessaging/message_test.go b/internal/privatemessaging/message_test.go index 36652f1c94..4e3d9ec654 100644 --- a/internal/privatemessaging/message_test.go +++ b/internal/privatemessaging/message_test.go @@ -26,6 +26,7 @@ import ( "github.com/hyperledger/firefly/mocks/datamocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" "github.com/hyperledger/firefly/mocks/syncasyncmocks" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -76,7 +77,7 @@ func TestSendConfirmMessageE2EOk(t *testing.T) { send(pm.ctx) }). Return(retMsg, nil).Once() - mdi.On("UpsertMessage", pm.ctx, mock.Anything, false, false).Return(nil).Once() + mdi.On("UpsertMessage", pm.ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil).Once() msg, err := pm.SendMessage(pm.ctx, "ns1", &fftypes.MessageInOut{ InlineData: fftypes.InlineData{ @@ -134,7 +135,7 @@ func TestSendUnpinnedMessageE2EOk(t *testing.T) { mdi.On("GetNodeByID", pm.ctx, nodeID2).Return(&fftypes.Node{ ID: nodeID2, Name: "node2", Owner: "org1", DX: fftypes.DXInfo{Peer: "peer2-remote"}, }, nil).Once() - mdi.On("UpsertMessage", pm.ctx, mock.Anything, false, false).Return(nil).Once() + mdi.On("UpsertMessage", pm.ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil).Once() mdi.On("InsertEvent", pm.ctx, mock.Anything).Return(nil).Once() mdx := pm.exchange.(*dataexchangemocks.Plugin) @@ -234,7 +235,7 @@ func TestSendMessageFail(t *testing.T) { mdi.On("GetGroups", pm.ctx, mock.Anything).Return([]*fftypes.Group{ {Hash: fftypes.NewRandB32()}, }, nil, nil) - mdi.On("UpsertMessage", pm.ctx, mock.Anything, false, false).Return(fmt.Errorf("pop")) + mdi.On("UpsertMessage", pm.ctx, mock.Anything, database.UpsertOptimizationNew).Return(fmt.Errorf("pop")) dataID := fftypes.NewUUID() mdm := pm.data.(*datamocks.Manager) @@ -544,7 +545,7 @@ func TestSendUnpinnedMessageInsertFail(t *testing.T) { }, nil) mdi := pm.database.(*databasemocks.Plugin) - mdi.On("UpsertMessage", pm.ctx, mock.Anything, false, false).Return(fmt.Errorf("pop")).Once() + mdi.On("UpsertMessage", pm.ctx, mock.Anything, database.UpsertOptimizationNew).Return(fmt.Errorf("pop")).Once() _, err := pm.SendMessage(pm.ctx, "ns1", &fftypes.MessageInOut{ Message: fftypes.Message{ @@ -614,7 +615,7 @@ func TestSendUnpinnedMessageResolveGroupFail(t *testing.T) { mdi := pm.database.(*databasemocks.Plugin) mdi.On("GetGroupByHash", pm.ctx, groupID).Return(nil, fmt.Errorf("pop")).Once() - mdi.On("UpsertMessage", pm.ctx, mock.Anything, false, false).Return(nil).Once() + mdi.On("UpsertMessage", pm.ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil).Once() mdx := pm.exchange.(*dataexchangemocks.Plugin) mdx.On("SendMessage", pm.ctx, "peer2-remote", mock.Anything).Return("tracking1", nil).Once() @@ -680,7 +681,7 @@ func TestSendUnpinnedMessageEventFail(t *testing.T) { mdi.On("GetNodeByID", pm.ctx, nodeID2).Return(&fftypes.Node{ ID: nodeID2, Name: "node2", Owner: "org1", DX: fftypes.DXInfo{Peer: "peer2-remote"}, }, nil).Once() - mdi.On("UpsertMessage", pm.ctx, mock.Anything, false, false).Return(nil).Once() + mdi.On("UpsertMessage", pm.ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil).Once() mdi.On("InsertEvent", pm.ctx, mock.Anything).Return(fmt.Errorf("pop")).Once() mdx := pm.exchange.(*dataexchangemocks.Plugin) @@ -761,7 +762,7 @@ func TestRequestReplySuccess(t *testing.T) { }, nil) mdi := pm.database.(*databasemocks.Plugin) - mdi.On("UpsertMessage", pm.ctx, mock.Anything, false, false).Return(nil).Once() + mdi.On("UpsertMessage", pm.ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil).Once() _, err := pm.RequestReply(pm.ctx, "ns1", &fftypes.MessageInOut{ Message: fftypes.Message{ diff --git a/internal/privatemessaging/recipients_test.go b/internal/privatemessaging/recipients_test.go index cee98859c2..03a64f956d 100644 --- a/internal/privatemessaging/recipients_test.go +++ b/internal/privatemessaging/recipients_test.go @@ -23,6 +23,7 @@ import ( "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" + "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/fftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -56,7 +57,7 @@ func TestResolveMemberListNewGroupE2E(t *testing.T) { mim := pm.identity.(*identitymanagermocks.Manager) mim.On("ResolveLocalOrgDID", pm.ctx).Return(orgDIDLocal, nil) mim.On("GetLocalOrganization", pm.ctx).Return(&fftypes.Organization{Identity: signingKeyLocal}, nil) - ud := mdi.On("UpsertData", pm.ctx, mock.Anything, true, false).Return(nil) + ud := mdi.On("UpsertData", pm.ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil) ud.RunFn = func(a mock.Arguments) { data := a[1].(*fftypes.Data) assert.Equal(t, fftypes.ValidatorTypeSystemDefinition, data.Validator) @@ -82,7 +83,7 @@ func TestResolveMemberListNewGroupE2E(t *testing.T) { dataID = data.ID } - um := mdi.On("UpsertMessage", pm.ctx, mock.Anything, false, false).Return(nil).Once() + um := mdi.On("UpsertMessage", pm.ctx, mock.Anything, database.UpsertOptimizationNew).Return(nil).Once() um.RunFn = func(a mock.Arguments) { msg := a[1].(*fftypes.Message) assert.Equal(t, fftypes.MessageTypeGroupInit, msg.Header.Type) diff --git a/internal/restclient/ffresty.go b/internal/restclient/ffresty.go index c42bc341c0..0536a9982e 100644 --- a/internal/restclient/ffresty.go +++ b/internal/restclient/ffresty.go @@ -75,7 +75,7 @@ func New(ctx context.Context, staticConfig config.Prefix) *resty.Client { url := strings.TrimSuffix(staticConfig.GetString(HTTPConfigURL), "/") if url != "" { - client.SetHostURL(url) + client.SetBaseURL(url) log.L(ctx).Debugf("Created REST client to %s", url) } diff --git a/internal/sysmessaging/localnodeinfo.go b/internal/sysmessaging/localnodeinfo.go new file mode 100644 index 0000000000..49cfd7d814 --- /dev/null +++ b/internal/sysmessaging/localnodeinfo.go @@ -0,0 +1,29 @@ +// 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" + + "github.com/hyperledger/firefly/pkg/fftypes" +) + +// LocalNodeInfo provides an interface to query the local node info +type LocalNodeInfo interface { + // GetNodeUUID returns the local node UUID, or nil if the node is not yet registered. It is cached for fast access + GetNodeUUID(ctx context.Context) *fftypes.UUID +} diff --git a/mocks/databasemocks/plugin.go b/mocks/databasemocks/plugin.go index ab23f31801..97e6df1bfd 100644 --- a/mocks/databasemocks/plugin.go +++ b/mocks/databasemocks/plugin.go @@ -1964,13 +1964,13 @@ func (_m *Plugin) UpsertConfigRecord(ctx context.Context, data *fftypes.ConfigRe return r0 } -// UpsertData provides a mock function with given fields: ctx, data, allowExisting, allowHashUpdate -func (_m *Plugin) UpsertData(ctx context.Context, data *fftypes.Data, allowExisting bool, allowHashUpdate bool) error { - ret := _m.Called(ctx, data, allowExisting, allowHashUpdate) +// UpsertData provides a mock function with given fields: ctx, data, optimization +func (_m *Plugin) UpsertData(ctx context.Context, data *fftypes.Data, optimization database.UpsertOptimization) error { + ret := _m.Called(ctx, data, optimization) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.Data, bool, bool) error); ok { - r0 = rf(ctx, data, allowExisting, allowHashUpdate) + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.Data, database.UpsertOptimization) error); ok { + r0 = rf(ctx, data, optimization) } else { r0 = ret.Error(0) } @@ -2006,13 +2006,13 @@ func (_m *Plugin) UpsertGroup(ctx context.Context, data *fftypes.Group, allowExi return r0 } -// UpsertMessage provides a mock function with given fields: ctx, message, allowExisting, allowHashUpdate -func (_m *Plugin) UpsertMessage(ctx context.Context, message *fftypes.Message, allowExisting bool, allowHashUpdate bool) error { - ret := _m.Called(ctx, message, allowExisting, allowHashUpdate) +// UpsertMessage provides a mock function with given fields: ctx, message, optimization +func (_m *Plugin) UpsertMessage(ctx context.Context, message *fftypes.Message, optimization database.UpsertOptimization) error { + ret := _m.Called(ctx, message, optimization) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.Message, bool, bool) error); ok { - r0 = rf(ctx, message, allowExisting, allowHashUpdate) + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.Message, database.UpsertOptimization) error); ok { + r0 = rf(ctx, message, optimization) } else { r0 = ret.Error(0) } diff --git a/mocks/sysmessagingmocks/local_node_info.go b/mocks/sysmessagingmocks/local_node_info.go new file mode 100644 index 0000000000..196d6d4d71 --- /dev/null +++ b/mocks/sysmessagingmocks/local_node_info.go @@ -0,0 +1,31 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package sysmessagingmocks + +import ( + context "context" + + fftypes "github.com/hyperledger/firefly/pkg/fftypes" + mock "github.com/stretchr/testify/mock" +) + +// LocalNodeInfo is an autogenerated mock type for the LocalNodeInfo type +type LocalNodeInfo struct { + mock.Mock +} + +// GetNodeUUID provides a mock function with given fields: ctx +func (_m *LocalNodeInfo) GetNodeUUID(ctx context.Context) *fftypes.UUID { + ret := _m.Called(ctx) + + var r0 *fftypes.UUID + if rf, ok := ret.Get(0).(func(context.Context) *fftypes.UUID); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*fftypes.UUID) + } + } + + return r0 +} diff --git a/pkg/database/plugin.go b/pkg/database/plugin.go index 335ba00b16..78c6aba5dd 100644 --- a/pkg/database/plugin.go +++ b/pkg/database/plugin.go @@ -73,8 +73,9 @@ type iNamespaceCollection interface { type iMessageCollection interface { // UpsertMessage - Upsert a message, with all the embedded data references. - // allowHashUpdate=false throws HashMismatch error if the updated message has a different hash - UpsertMessage(ctx context.Context, message *fftypes.Message, allowExisting, allowHashUpdate bool) (err error) + // The database layer must ensure that if a record already exists, the hash of that existing record + // must match the hash of the record that is being inserted. + UpsertMessage(ctx context.Context, message *fftypes.Message, optimization UpsertOptimization) (err error) // UpdateMessage - Update message UpdateMessage(ctx context.Context, id *fftypes.UUID, update Update) (err error) @@ -97,9 +98,9 @@ type iMessageCollection interface { type iDataCollection interface { // UpsertData - Upsert a data record. A hint can be supplied to whether the data already exists. - // The database will ensure that if a record already exists, the hash of that existing record + // The database layer must ensure that if a record already exists, the hash of that existing record // must match the hash of the record that is being inserted. - UpsertData(ctx context.Context, data *fftypes.Data, optimizeForExisting bool) (err error) + UpsertData(ctx context.Context, data *fftypes.Data, optimization UpsertOptimization) (err error) // UpdateData - Update data UpdateData(ctx context.Context, id *fftypes.UUID, update Update) (err error) @@ -547,6 +548,7 @@ const ( // providing a building block for a cluster of FireFly servers to directly propgate events to each other. // type Callbacks interface { + // OrderedUUIDCollectionNSEvent emits the sequence on insert, but it will be -1 on update OrderedUUIDCollectionNSEvent(resType OrderedUUIDCollectionNS, eventType fftypes.ChangeEventType, ns string, id *fftypes.UUID, sequence int64) OrderedCollectionEvent(resType OrderedCollection, eventType fftypes.ChangeEventType, sequence int64) UUIDCollectionNSEvent(resType UUIDCollectionNS, eventType fftypes.ChangeEventType, ns string, id *fftypes.UUID) @@ -605,6 +607,7 @@ var BatchQueryFactory = &queryFields{ "confirmed": &TimeField{}, "tx.type": &StringField{}, "tx.id": &UUIDField{}, + "node": &UUIDField{}, } // TransactionQueryFactory filter fields for transactions diff --git a/pkg/fftypes/batch.go b/pkg/fftypes/batch.go index 7fa96eeaf7..5325cdbfd0 100644 --- a/pkg/fftypes/batch.go +++ b/pkg/fftypes/batch.go @@ -29,6 +29,7 @@ type Batch struct { ID *UUID `json:"id"` Namespace string `json:"namespace"` Type MessageType `json:"type"` + Node *UUID `json:"node,omitempty"` Identity Group *Bytes32 `jdon:"group,omitempty"` Hash *Bytes32 `json:"hash"` diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 69c2a79be2..0c7a5305e7 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -161,8 +161,8 @@ func beforeE2ETest(t *testing.T) *testState { httpProtocolClient2 = "https" websocketProtocolClient2 = "wss" } - ts.client1.SetHostURL(fmt.Sprintf("%s://%s:%d/api/v1", httpProtocolClient1, stack.Members[0].FireflyHostname, stack.Members[0].ExposedFireflyPort)) - ts.client2.SetHostURL(fmt.Sprintf("%s://%s:%d/api/v1", httpProtocolClient2, stack.Members[1].FireflyHostname, stack.Members[1].ExposedFireflyPort)) + ts.client1.SetBaseURL(fmt.Sprintf("%s://%s:%d/api/v1", httpProtocolClient1, stack.Members[0].FireflyHostname, stack.Members[0].ExposedFireflyPort)) + ts.client2.SetBaseURL(fmt.Sprintf("%s://%s:%d/api/v1", httpProtocolClient2, stack.Members[1].FireflyHostname, stack.Members[1].ExposedFireflyPort)) if stack.Members[0].Username != "" && stack.Members[0].Password != "" { t.Log("Setting auth for user 1") From 81429dd724fae1f5826bb812ddb8eb09e607b24c Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 16 Nov 2021 21:03:29 -0500 Subject: [PATCH 3/7] GetNodeUUID tests Signed-off-by: Peter Broadhurst --- internal/orchestrator/status.go | 4 ++-- internal/orchestrator/status_test.go | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/orchestrator/status.go b/internal/orchestrator/status.go index cdcd6d69eb..0a2951e73c 100644 --- a/internal/orchestrator/status.go +++ b/internal/orchestrator/status.go @@ -34,9 +34,9 @@ func (or *orchestrator) GetNodeUUID(ctx context.Context) (node *fftypes.UUID) { return nil } if status.Node.Registered { - log.L(or.ctx).Infof("Node not yet registered") - } else { or.node = status.Node.ID + } else { + log.L(or.ctx).Infof("Node not yet registered") } return or.node } diff --git a/internal/orchestrator/status_test.go b/internal/orchestrator/status_test.go index 3caec04c10..1331b732f7 100644 --- a/internal/orchestrator/status_test.go +++ b/internal/orchestrator/status_test.go @@ -67,6 +67,9 @@ func TestGetStatusRegistered(t *testing.T) { assert.True(t, status.Node.Registered) assert.Equal(t, *nodeID, *status.Node.ID) + assert.True(t, or.GetNodeUUID(or.ctx).Equals(nodeID)) + assert.True(t, or.GetNodeUUID(or.ctx).Equals(nodeID)) // cached + } func TestGetStatusUnregistered(t *testing.T) { @@ -93,6 +96,8 @@ func TestGetStatusUnregistered(t *testing.T) { assert.Equal(t, "node1", status.Node.Name) assert.False(t, status.Node.Registered) + assert.Nil(t, or.GetNodeUUID(or.ctx)) + } func TestGetStatusOrgOnlyRegistered(t *testing.T) { @@ -128,6 +133,7 @@ func TestGetStatusOrgOnlyRegistered(t *testing.T) { assert.Equal(t, "node1", status.Node.Name) assert.False(t, status.Node.Registered) + assert.Nil(t, or.GetNodeUUID(or.ctx)) } func TestGetStatuOrgError(t *testing.T) { @@ -169,4 +175,7 @@ func TestGetStatusNodeError(t *testing.T) { _, err := or.GetStatus(or.ctx) assert.EqualError(t, err, "pop") + + assert.Nil(t, or.GetNodeUUID(or.ctx)) + } From 102f8be276ae16cb53354c4011455ba4d6d16f64 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 16 Nov 2021 21:42:16 -0500 Subject: [PATCH 4/7] Re-instate coverage Signed-off-by: Peter Broadhurst --- internal/apiserver/server_test.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/internal/apiserver/server_test.go b/internal/apiserver/server_test.go index 03ae1fca80..36d6b1f367 100644 --- a/internal/apiserver/server_test.go +++ b/internal/apiserver/server_test.go @@ -21,13 +21,14 @@ import ( "context" "encoding/json" "fmt" - "github.com/hyperledger/firefly/internal/metrics" "io/ioutil" "net/http" "net/http/httptest" "testing" "time" + "github.com/hyperledger/firefly/internal/metrics" + "github.com/getkin/kin-openapi/openapi3" "github.com/gorilla/mux" "github.com/hyperledger/firefly/internal/config" @@ -106,6 +107,21 @@ func TestStartAdminFail(t *testing.T) { assert.Regexp(t, "FF10104", err) } +func TestStartMetricsFail(t *testing.T) { + config.Reset() + metrics.Clear() + InitConfig() + metricsConfigPrefix.Set(HTTPConfAddress, "...://") + config.Set(config.MetricsEnabled, true) + ctx, cancel := context.WithCancel(context.Background()) + cancel() // server will immediately shut down + as := NewAPIServer() + mor := &orchestratormocks.Orchestrator{} + mor.On("IsPreInit").Return(true) + err := as.Serve(ctx, mor) + assert.Regexp(t, "FF10104", err) +} + func TestJSONHTTPServePOST201(t *testing.T) { mo, as := newTestServer() handler := as.routeHandler(mo, &oapispec.Route{ From 5ab61317e40fe01f2e5e14857cadfe6d47258afd Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 16 Nov 2021 22:02:15 -0500 Subject: [PATCH 5/7] Optimized insert sequence must make it to event Signed-off-by: Peter Broadhurst --- internal/database/sqlcommon/message_sql.go | 15 ++++++--------- internal/orchestrator/persistence_events.go | 7 ++++++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/internal/database/sqlcommon/message_sql.go b/internal/database/sqlcommon/message_sql.go index 96ad301957..16cadafa76 100644 --- a/internal/database/sqlcommon/message_sql.go +++ b/internal/database/sqlcommon/message_sql.go @@ -80,12 +80,12 @@ func (s *SQLCommon) attemptMessageUpdate(ctx context.Context, tx *txWrapper, mes "hash": message.Hash, }), func() { - s.callbacks.OrderedUUIDCollectionNSEvent(database.CollectionMessages, fftypes.ChangeEventTypeUpdated, message.Header.Namespace, message.Header.ID, -1) + s.callbacks.OrderedUUIDCollectionNSEvent(database.CollectionMessages, fftypes.ChangeEventTypeUpdated, message.Header.Namespace, message.Header.ID, -1 /* not applicable on update */) }) } -func (s *SQLCommon) attemptMessageInsert(ctx context.Context, tx *txWrapper, message *fftypes.Message) (int64, error) { - return s.insertTx(ctx, tx, +func (s *SQLCommon) attemptMessageInsert(ctx context.Context, tx *txWrapper, message *fftypes.Message) (err error) { + message.Sequence, err = s.insertTx(ctx, tx, sq.Insert("messages"). Columns(msgColumns...). Values( @@ -110,6 +110,7 @@ func (s *SQLCommon) attemptMessageInsert(ctx context.Context, tx *txWrapper, mes func() { s.callbacks.OrderedUUIDCollectionNSEvent(database.CollectionMessages, fftypes.ChangeEventTypeCreated, message.Header.Namespace, message.Header.ID, message.Sequence) }) + return err } func (s *SQLCommon) UpsertMessage(ctx context.Context, message *fftypes.Message, optimization database.UpsertOptimization) (err error) { @@ -127,7 +128,7 @@ func (s *SQLCommon) UpsertMessage(ctx context.Context, message *fftypes.Message, optimized := false recreateDatarefs := false if optimization == database.UpsertOptimizationNew { - _, opErr := s.attemptMessageInsert(ctx, tx, message) + opErr := s.attemptMessageInsert(ctx, tx, message) optimized = opErr == nil } else if optimization == database.UpsertOptimizationExisting { rowsAffected, opErr := s.attemptMessageUpdate(ctx, tx, message) @@ -164,7 +165,7 @@ func (s *SQLCommon) UpsertMessage(ctx context.Context, message *fftypes.Message, return err } } else { - if message.Sequence, err = s.attemptMessageInsert(ctx, tx, message); err != nil { + if err = s.attemptMessageInsert(ctx, tx, message); err != nil { return err } } @@ -179,10 +180,6 @@ func (s *SQLCommon) UpsertMessage(ctx context.Context, message *fftypes.Message, } } - if optimization != database.UpsertOptimizationSkip { - message.Sequence = -1 // code that allows the optimization, MUST not rely on a sequence being returned. - } - return s.commitTx(ctx, tx, autoCommit) } diff --git a/internal/orchestrator/persistence_events.go b/internal/orchestrator/persistence_events.go index a7e27d4f7c..eb255a977e 100644 --- a/internal/orchestrator/persistence_events.go +++ b/internal/orchestrator/persistence_events.go @@ -39,12 +39,17 @@ func (or *orchestrator) OrderedUUIDCollectionNSEvent(resType database.OrderedUUI case eventType == fftypes.ChangeEventTypeCreated && resType == database.CollectionEvents: or.events.NewEvents() <- sequence } + var ces *int64 + if eventType == fftypes.ChangeEventTypeCreated { + // Sequence is only provided on create events + ces = &sequence + } or.attemptChangeEventDispatch(&fftypes.ChangeEvent{ Collection: string(resType), Type: eventType, Namespace: ns, ID: id, - Sequence: &sequence, + Sequence: ces, }) } From 9d81d39d0bc7e5f4da66e066ac57aff57215bdbd Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 16 Nov 2021 22:19:31 -0500 Subject: [PATCH 6/7] Include batch in logging on BatchPin Signed-off-by: Peter Broadhurst --- internal/events/batch_pin_complete.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/events/batch_pin_complete.go b/internal/events/batch_pin_complete.go index ee7def7dfe..cc77762045 100644 --- a/internal/events/batch_pin_complete.go +++ b/internal/events/batch_pin_complete.go @@ -34,11 +34,11 @@ import ( // sequence, and also persist all the data. func (em *eventManager) BatchPinComplete(bi blockchain.Plugin, batchPin *blockchain.BatchPin, signingIdentity string, protocolTxID string, additionalInfo fftypes.JSONObject) error { - log.L(em.ctx).Infof("-> BatchPinComplete txn=%s signingIdentity=%s", protocolTxID, signingIdentity) + log.L(em.ctx).Infof("-> BatchPinComplete batch=%s txn=%s signingIdentity=%s", batchPin.BatchID, protocolTxID, signingIdentity) defer func() { - log.L(em.ctx).Infof("<- BatchPinComplete txn=%s signingIdentity=%s", protocolTxID, signingIdentity) + log.L(em.ctx).Infof("<- BatchPinComplete batch=%s txn=%s signingIdentity=%s", batchPin.BatchID, protocolTxID, signingIdentity) }() - log.L(em.ctx).Tracef("BatchPinComplete info: %+v", additionalInfo) + log.L(em.ctx).Tracef("BatchPinComplete batch=%s info: %+v", batchPin.BatchID, additionalInfo) if batchPin.BatchPaylodRef != "" { return em.handleBroadcastPinComplete(batchPin, signingIdentity, protocolTxID, additionalInfo) From 512b86323316b3b3e054eafcd2bd53dd58b14d3f Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Fri, 19 Nov 2021 11:13:11 -0500 Subject: [PATCH 7/7] Re-sequence migrations and review comments Signed-off-by: Peter Broadhurst --- ...d_batch_node.down.sql => 000046_add_batch_node.down.sql} | 0 ...4_add_batch_node.up.sql => 000046_add_batch_node.up.sql} | 0 ...d_batch_node.down.sql => 000046_add_batch_node.down.sql} | 0 ...4_add_batch_node.up.sql => 000046_add_batch_node.up.sql} | 0 internal/database/sqlcommon/data_sql.go | 4 ++-- internal/database/sqlcommon/message_sql.go | 6 +++--- 6 files changed, 5 insertions(+), 5 deletions(-) rename db/migrations/postgres/{000044_add_batch_node.down.sql => 000046_add_batch_node.down.sql} (100%) rename db/migrations/postgres/{000044_add_batch_node.up.sql => 000046_add_batch_node.up.sql} (100%) rename db/migrations/sqlite/{000044_add_batch_node.down.sql => 000046_add_batch_node.down.sql} (100%) rename db/migrations/sqlite/{000044_add_batch_node.up.sql => 000046_add_batch_node.up.sql} (100%) diff --git a/db/migrations/postgres/000044_add_batch_node.down.sql b/db/migrations/postgres/000046_add_batch_node.down.sql similarity index 100% rename from db/migrations/postgres/000044_add_batch_node.down.sql rename to db/migrations/postgres/000046_add_batch_node.down.sql diff --git a/db/migrations/postgres/000044_add_batch_node.up.sql b/db/migrations/postgres/000046_add_batch_node.up.sql similarity index 100% rename from db/migrations/postgres/000044_add_batch_node.up.sql rename to db/migrations/postgres/000046_add_batch_node.up.sql diff --git a/db/migrations/sqlite/000044_add_batch_node.down.sql b/db/migrations/sqlite/000046_add_batch_node.down.sql similarity index 100% rename from db/migrations/sqlite/000044_add_batch_node.down.sql rename to db/migrations/sqlite/000046_add_batch_node.down.sql diff --git a/db/migrations/sqlite/000044_add_batch_node.up.sql b/db/migrations/sqlite/000046_add_batch_node.up.sql similarity index 100% rename from db/migrations/sqlite/000044_add_batch_node.up.sql rename to db/migrations/sqlite/000046_add_batch_node.up.sql diff --git a/internal/database/sqlcommon/data_sql.go b/internal/database/sqlcommon/data_sql.go index afd67f358e..55663c3aae 100644 --- a/internal/database/sqlcommon/data_sql.go +++ b/internal/database/sqlcommon/data_sql.go @@ -110,8 +110,8 @@ func (s *SQLCommon) UpsertData(ctx context.Context, data *fftypes.Data, optimiza // This is a performance critical function, as we stream data into the database for every message, in every batch. // // First attempt the operation based on the optimization passed in. - // The expectation is that this will practically hit of the time, as only recovery paths - // require us to go down the un-optimized route. + // The expectation is that the optimization will hit almost all of the time, + // as only recovery paths require us to go down the un-optimized route. optimized := false if optimization == database.UpsertOptimizationNew { _, opErr := s.attemptDataInsert(ctx, tx, data, datatype, blob) diff --git a/internal/database/sqlcommon/message_sql.go b/internal/database/sqlcommon/message_sql.go index 16cadafa76..2c62326d67 100644 --- a/internal/database/sqlcommon/message_sql.go +++ b/internal/database/sqlcommon/message_sql.go @@ -123,8 +123,8 @@ func (s *SQLCommon) UpsertMessage(ctx context.Context, message *fftypes.Message, // This is a performance critical function, as we stream data into the database for every message, in every batch. // // First attempt the operation based on the optimization passed in. - // The expectation is that this will practically hit of the time, as only recovery paths - // require us to go down the un-optimized route. + // The expectation is that the optimization will hit almost all of the time, + // as only recovery paths require us to go down the un-optimized route. optimized := false recreateDatarefs := false if optimization == database.UpsertOptimizationNew { @@ -186,6 +186,7 @@ func (s *SQLCommon) UpsertMessage(ctx context.Context, message *fftypes.Message, func (s *SQLCommon) updateMessageDataRefs(ctx context.Context, tx *txWrapper, message *fftypes.Message, recreateDatarefs bool) error { if recreateDatarefs { + // Delete all the existing references, to replace them with new ones below if err := s.deleteTx(ctx, tx, sq.Delete("messages_data"). Where(sq.And{ @@ -197,7 +198,6 @@ func (s *SQLCommon) updateMessageDataRefs(ctx context.Context, tx *txWrapper, me } } - // Run through the ones in the message, finding ones that already exist, and ones that need to be created for msgDataRefIDx, msgDataRef := range message.Data { if msgDataRef.ID == nil { return i18n.NewError(ctx, i18n.MsgNullDataReferenceID, msgDataRefIDx)