github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/events/aggregator_test.go (about)

     1  // Copyright © 2021 Kaleido, Inc.
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package events
    18  
    19  import (
    20  	"context"
    21  	"crypto/sha256"
    22  	"fmt"
    23  	"testing"
    24  
    25  	"github.com/kaleido-io/firefly/internal/config"
    26  	"github.com/kaleido-io/firefly/mocks/broadcastmocks"
    27  	"github.com/kaleido-io/firefly/mocks/databasemocks"
    28  	"github.com/kaleido-io/firefly/mocks/datamocks"
    29  	"github.com/kaleido-io/firefly/mocks/privatemessagingmocks"
    30  	"github.com/kaleido-io/firefly/pkg/database"
    31  	"github.com/kaleido-io/firefly/pkg/fftypes"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/mock"
    34  )
    35  
    36  func uuidMatches(id1 *fftypes.UUID) interface{} {
    37  	return mock.MatchedBy(func(id2 *fftypes.UUID) bool { return id1.Equals(id2) })
    38  }
    39  
    40  func newTestAggregator() (*aggregator, func()) {
    41  	mdi := &databasemocks.Plugin{}
    42  	mbm := &broadcastmocks.Manager{}
    43  	mdm := &datamocks.Manager{}
    44  	mpm := &privatemessagingmocks.Manager{}
    45  	ctx, cancel := context.WithCancel(context.Background())
    46  	ag := newAggregator(ctx, mdi, mbm, mpm, mdm, newEventNotifier(ctx, "ut"))
    47  	return ag, cancel
    48  }
    49  
    50  func TestAggregationMaskedZeroNonceMatch(t *testing.T) {
    51  
    52  	ag, cancel := newTestAggregator()
    53  	defer cancel()
    54  
    55  	// Generate some pin data
    56  	member1 := "0x12345"
    57  	member2 := "0x23456"
    58  	topic := "some-topic"
    59  	batchID := fftypes.NewUUID()
    60  	groupID := fftypes.NewRandB32()
    61  	msgID := fftypes.NewUUID()
    62  	h := sha256.New()
    63  	h.Write([]byte(topic))
    64  	h.Write((*groupID)[:])
    65  	contextUnmasked := fftypes.HashResult(h)
    66  	member1NonceZero := ag.calcHash(topic, groupID, member1, 0)
    67  	member2NonceZero := ag.calcHash(topic, groupID, member2, 0)
    68  	member2NonceOne := ag.calcHash(topic, groupID, member2, 1)
    69  
    70  	mdi := ag.database.(*databasemocks.Plugin)
    71  	mdm := ag.data.(*datamocks.Manager)
    72  	mpm := ag.messaging.(*privatemessagingmocks.Manager)
    73  
    74  	// Get the batch
    75  	mdi.On("GetBatchByID", ag.ctx, uuidMatches(batchID)).Return(&fftypes.Batch{
    76  		ID: batchID,
    77  		Payload: fftypes.BatchPayload{
    78  			Messages: []*fftypes.Message{
    79  				{
    80  					Header: fftypes.MessageHeader{
    81  						ID:     msgID,
    82  						Group:  groupID,
    83  						Topics: []string{topic},
    84  						Author: member2,
    85  					},
    86  					Pins: []string{member2NonceZero.String()},
    87  					Data: fftypes.DataRefs{
    88  						{ID: fftypes.NewUUID()},
    89  					},
    90  				},
    91  			},
    92  		},
    93  	}, nil)
    94  	// Look for existing nextpins - none found, first on context
    95  	mdi.On("GetNextPins", ag.ctx, mock.Anything).Return([]*fftypes.NextPin{}, nil).Once()
    96  	// Get the group members
    97  	mpm.On("ResolveInitGroup", ag.ctx, mock.Anything).Return(&fftypes.Group{
    98  		GroupIdentity: fftypes.GroupIdentity{
    99  			Members: fftypes.Members{
   100  				{Identity: member1},
   101  				{Identity: member2},
   102  			},
   103  		},
   104  	}, nil)
   105  	// Look for any earlier pins - none found
   106  	mdi.On("GetPins", ag.ctx, mock.Anything).Return([]*fftypes.Pin{}, nil).Once()
   107  	// Insert all the zero pins
   108  	mdi.On("InsertNextPin", ag.ctx, mock.MatchedBy(func(np *fftypes.NextPin) bool {
   109  		assert.Equal(t, *np.Context, *contextUnmasked)
   110  		np.Sequence = 10011
   111  		return *np.Hash == *member1NonceZero && np.Nonce == 0
   112  	})).Return(nil).Once()
   113  	mdi.On("InsertNextPin", ag.ctx, mock.MatchedBy(func(np *fftypes.NextPin) bool {
   114  		assert.Equal(t, *np.Context, *contextUnmasked)
   115  		np.Sequence = 10012
   116  		return *np.Hash == *member2NonceZero && np.Nonce == 0
   117  	})).Return(nil).Once()
   118  	// Validate the message is ok
   119  	mdm.On("GetMessageData", ag.ctx, mock.Anything, true).Return([]*fftypes.Data{}, true, nil)
   120  	mdm.On("ValidateAll", ag.ctx, mock.Anything).Return(true, nil)
   121  	// Insert the confirmed event
   122  	mdi.On("UpsertEvent", ag.ctx, mock.MatchedBy(func(e *fftypes.Event) bool {
   123  		return *e.Reference == *msgID && e.Type == fftypes.EventTypeMessageConfirmed
   124  	}), false).Return(nil)
   125  	// Update member2 to nonce 1
   126  	mdi.On("UpdateNextPin", ag.ctx, mock.MatchedBy(func(seq int64) bool {
   127  		return seq == 10012
   128  	}), mock.MatchedBy(func(update database.Update) bool {
   129  		ui, _ := update.Finalize()
   130  		assert.Equal(t, "nonce", ui.SetOperations[0].Field)
   131  		v, _ := ui.SetOperations[0].Value.Value()
   132  		assert.Equal(t, int64(1), v.(int64))
   133  		assert.Equal(t, "hash", ui.SetOperations[1].Field)
   134  		v, _ = ui.SetOperations[1].Value.Value()
   135  		assert.Equal(t, member2NonceOne.String(), v)
   136  		return true
   137  	})).Return(nil)
   138  	// Set the pin to dispatched
   139  	mdi.On("SetPinDispatched", ag.ctx, int64(10001)).Return(nil)
   140  	// Update the message
   141  	mdi.On("UpdateMessage", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   142  	// Confirm the offset
   143  	mdi.On("UpdateOffset", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   144  
   145  	err := ag.processPins(ag.ctx, []*fftypes.Pin{
   146  		{
   147  			Sequence:   10001,
   148  			Masked:     true,
   149  			Hash:       member2NonceZero,
   150  			Batch:      batchID,
   151  			Index:      0,
   152  			Dispatched: false,
   153  		},
   154  	})
   155  	assert.NoError(t, err)
   156  
   157  	mdi.AssertExpectations(t)
   158  	mdm.AssertExpectations(t)
   159  }
   160  
   161  func TestAggregationMaskedNextSequenceMatch(t *testing.T) {
   162  
   163  	ag, cancel := newTestAggregator()
   164  	defer cancel()
   165  
   166  	// Generate some pin data
   167  	member1 := "0x12345"
   168  	member2 := "0x23456"
   169  	topic := "some-topic"
   170  	batchID := fftypes.NewUUID()
   171  	groupID := fftypes.NewRandB32()
   172  	msgID := fftypes.NewUUID()
   173  	h := sha256.New()
   174  	h.Write([]byte(topic))
   175  	h.Write((*groupID)[:])
   176  	contextUnmasked := fftypes.HashResult(h)
   177  	member1Nonce100 := ag.calcHash(topic, groupID, member1, 100)
   178  	member2Nonce500 := ag.calcHash(topic, groupID, member2, 500)
   179  	member2Nonce501 := ag.calcHash(topic, groupID, member2, 501)
   180  
   181  	mdi := ag.database.(*databasemocks.Plugin)
   182  	mdm := ag.data.(*datamocks.Manager)
   183  
   184  	// Get the batch
   185  	mdi.On("GetBatchByID", ag.ctx, uuidMatches(batchID)).Return(&fftypes.Batch{
   186  		ID: batchID,
   187  		Payload: fftypes.BatchPayload{
   188  			Messages: []*fftypes.Message{
   189  				{
   190  					Header: fftypes.MessageHeader{
   191  						ID:     msgID,
   192  						Group:  groupID,
   193  						Topics: []string{topic},
   194  						Author: member2,
   195  					},
   196  					Pins: []string{member2Nonce500.String()},
   197  					Data: fftypes.DataRefs{
   198  						{ID: fftypes.NewUUID()},
   199  					},
   200  				},
   201  			},
   202  		},
   203  	}, nil)
   204  	// Look for existing nextpins - none found, first on context
   205  	mdi.On("GetNextPins", ag.ctx, mock.Anything).Return([]*fftypes.NextPin{
   206  		{Context: contextUnmasked, Identity: member1, Hash: member1Nonce100, Nonce: 100, Sequence: 929},
   207  		{Context: contextUnmasked, Identity: member2, Hash: member2Nonce500, Nonce: 500, Sequence: 424},
   208  	}, nil).Once()
   209  	// Validate the message is ok
   210  	mdm.On("GetMessageData", ag.ctx, mock.Anything, true).Return([]*fftypes.Data{}, true, nil)
   211  	mdm.On("ValidateAll", ag.ctx, mock.Anything).Return(true, nil)
   212  	// Insert the confirmed event
   213  	mdi.On("UpsertEvent", ag.ctx, mock.MatchedBy(func(e *fftypes.Event) bool {
   214  		return *e.Reference == *msgID && e.Type == fftypes.EventTypeMessageConfirmed
   215  	}), false).Return(nil)
   216  	// Update member2 to nonce 1
   217  	mdi.On("UpdateNextPin", ag.ctx, mock.MatchedBy(func(seq int64) bool {
   218  		return seq == 424
   219  	}), mock.MatchedBy(func(update database.Update) bool {
   220  		ui, _ := update.Finalize()
   221  		assert.Equal(t, "nonce", ui.SetOperations[0].Field)
   222  		v, _ := ui.SetOperations[0].Value.Value()
   223  		assert.Equal(t, int64(501), v.(int64))
   224  		assert.Equal(t, "hash", ui.SetOperations[1].Field)
   225  		v, _ = ui.SetOperations[1].Value.Value()
   226  		assert.Equal(t, member2Nonce501.String(), v)
   227  		return true
   228  	})).Return(nil)
   229  	// Set the pin to dispatched
   230  	mdi.On("SetPinDispatched", ag.ctx, int64(10001)).Return(nil)
   231  	// Update the message
   232  	mdi.On("UpdateMessage", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   233  	// Confirm the offset
   234  	mdi.On("UpdateOffset", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   235  
   236  	err := ag.processPins(ag.ctx, []*fftypes.Pin{
   237  		{
   238  			Sequence:   10001,
   239  			Masked:     true,
   240  			Hash:       member2Nonce500,
   241  			Batch:      batchID,
   242  			Index:      0,
   243  			Dispatched: false,
   244  		},
   245  	})
   246  	assert.NoError(t, err)
   247  
   248  	mdi.AssertExpectations(t)
   249  	mdm.AssertExpectations(t)
   250  }
   251  
   252  func TestAggregationBroadcast(t *testing.T) {
   253  
   254  	ag, cancel := newTestAggregator()
   255  	defer cancel()
   256  
   257  	// Generate some pin data
   258  	topic := "some-topic"
   259  	batchID := fftypes.NewUUID()
   260  	msgID := fftypes.NewUUID()
   261  	h := sha256.New()
   262  	h.Write([]byte(topic))
   263  	contextUnmasked := fftypes.HashResult(h)
   264  
   265  	mdi := ag.database.(*databasemocks.Plugin)
   266  	mdm := ag.data.(*datamocks.Manager)
   267  
   268  	// Get the batch
   269  	member1 := "0x12345"
   270  	mdi.On("GetBatchByID", ag.ctx, uuidMatches(batchID)).Return(&fftypes.Batch{
   271  		ID: batchID,
   272  		Payload: fftypes.BatchPayload{
   273  			Messages: []*fftypes.Message{
   274  				{
   275  					Header: fftypes.MessageHeader{
   276  						ID:     msgID,
   277  						Topics: []string{topic},
   278  						Author: member1,
   279  					},
   280  					Data: fftypes.DataRefs{
   281  						{ID: fftypes.NewUUID()},
   282  					},
   283  				},
   284  			},
   285  		},
   286  	}, nil)
   287  	// Do not resolve any pins earlier
   288  	mdi.On("GetPins", mock.Anything, mock.Anything).Return([]*fftypes.Pin{}, nil)
   289  	// Validate the message is ok
   290  	mdm.On("GetMessageData", ag.ctx, mock.Anything, true).Return([]*fftypes.Data{}, true, nil)
   291  	mdm.On("ValidateAll", ag.ctx, mock.Anything).Return(true, nil)
   292  	// Insert the confirmed event
   293  	mdi.On("UpsertEvent", ag.ctx, mock.MatchedBy(func(e *fftypes.Event) bool {
   294  		return *e.Reference == *msgID && e.Type == fftypes.EventTypeMessageConfirmed
   295  	}), false).Return(nil)
   296  	// Set the pin to dispatched
   297  	mdi.On("SetPinDispatched", ag.ctx, int64(10001)).Return(nil)
   298  	// Update the message
   299  	mdi.On("UpdateMessage", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   300  	// Confirm the offset
   301  	mdi.On("UpdateOffset", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   302  
   303  	err := ag.processPins(ag.ctx, []*fftypes.Pin{
   304  		{
   305  			Sequence:   10001,
   306  			Hash:       contextUnmasked,
   307  			Batch:      batchID,
   308  			Index:      0,
   309  			Dispatched: false,
   310  		},
   311  	})
   312  	assert.NoError(t, err)
   313  
   314  	mdi.AssertExpectations(t)
   315  	mdm.AssertExpectations(t)
   316  }
   317  func TestShutdownOnCancel(t *testing.T) {
   318  	ag, cancel := newTestAggregator()
   319  	mdi := ag.database.(*databasemocks.Plugin)
   320  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeAggregator, fftypes.SystemNamespace, aggregatorOffsetName).Return(&fftypes.Offset{
   321  		Type:      fftypes.OffsetTypeAggregator,
   322  		Namespace: fftypes.SystemNamespace,
   323  		Name:      aggregatorOffsetName,
   324  		Current:   12345,
   325  	}, nil)
   326  	mdi.On("GetPins", mock.Anything, mock.Anything, mock.Anything).Return([]*fftypes.Pin{}, nil)
   327  	err := ag.start()
   328  	assert.NoError(t, err)
   329  	assert.Equal(t, int64(12345), ag.eventPoller.pollingOffset)
   330  	ag.eventPoller.eventNotifier.newEvents <- 12345
   331  	cancel()
   332  	<-ag.eventPoller.closed
   333  }
   334  
   335  func TestProcessPinsDBGroupFail(t *testing.T) {
   336  	ag, cancel := newTestAggregator()
   337  	defer cancel()
   338  
   339  	mdi := ag.database.(*databasemocks.Plugin)
   340  	rag := mdi.On("RunAsGroup", ag.ctx, mock.Anything)
   341  	rag.RunFn = func(a mock.Arguments) {
   342  		rag.ReturnArguments = mock.Arguments{
   343  			a[1].(func(context.Context) error)(a[0].(context.Context)),
   344  		}
   345  	}
   346  	mdi.On("GetBatchByID", ag.ctx, mock.Anything).Return(nil, fmt.Errorf("pop"))
   347  
   348  	_, err := ag.processPinsDBGroup([]fftypes.LocallySequenced{
   349  		&fftypes.Pin{
   350  			Batch: fftypes.NewUUID(),
   351  		},
   352  	})
   353  	assert.Regexp(t, "pop", err)
   354  }
   355  
   356  func TestGetPins(t *testing.T) {
   357  	ag, cancel := newTestAggregator()
   358  	defer cancel()
   359  
   360  	mdi := ag.database.(*databasemocks.Plugin)
   361  	mdi.On("GetPins", ag.ctx, mock.Anything).Return([]*fftypes.Pin{
   362  		{Sequence: 12345},
   363  	}, nil)
   364  
   365  	lc, err := ag.getPins(ag.ctx, database.EventQueryFactory.NewFilter(ag.ctx).Gte("sequence", 12345))
   366  	assert.NoError(t, err)
   367  	assert.Equal(t, int64(12345), lc[0].LocalSequence())
   368  }
   369  
   370  func TestProcessPinsMissingBatch(t *testing.T) {
   371  	ag, cancel := newTestAggregator()
   372  	defer cancel()
   373  
   374  	mdi := ag.database.(*databasemocks.Plugin)
   375  	mdi.On("GetBatchByID", ag.ctx, mock.Anything).Return(nil, nil)
   376  	mdi.On("UpdateOffset", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   377  
   378  	err := ag.processPins(ag.ctx, []*fftypes.Pin{
   379  		{Sequence: 12345, Batch: fftypes.NewUUID()},
   380  	})
   381  	assert.NoError(t, err)
   382  
   383  }
   384  
   385  func TestProcessPinsMissingNoMsg(t *testing.T) {
   386  	ag, cancel := newTestAggregator()
   387  	defer cancel()
   388  
   389  	mdi := ag.database.(*databasemocks.Plugin)
   390  	mdi.On("GetBatchByID", ag.ctx, mock.Anything).Return(&fftypes.Batch{
   391  		ID: fftypes.NewUUID(),
   392  		Payload: fftypes.BatchPayload{
   393  			Messages: []*fftypes.Message{
   394  				{Header: fftypes.MessageHeader{ID: fftypes.NewUUID()}},
   395  			},
   396  		},
   397  	}, nil)
   398  	mdi.On("UpdateOffset", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   399  
   400  	err := ag.processPins(ag.ctx, []*fftypes.Pin{
   401  		{Sequence: 12345, Batch: fftypes.NewUUID(), Index: 25},
   402  	})
   403  	assert.NoError(t, err)
   404  	mdi.AssertExpectations(t)
   405  
   406  }
   407  
   408  func TestProcessPinsBadMsgHeader(t *testing.T) {
   409  	ag, cancel := newTestAggregator()
   410  	defer cancel()
   411  
   412  	mdi := ag.database.(*databasemocks.Plugin)
   413  	mdi.On("GetBatchByID", ag.ctx, mock.Anything).Return(&fftypes.Batch{
   414  		ID: fftypes.NewUUID(),
   415  		Payload: fftypes.BatchPayload{
   416  			Messages: []*fftypes.Message{
   417  				{Header: fftypes.MessageHeader{
   418  					ID:     nil, /* missing */
   419  					Topics: fftypes.FFNameArray{"topic1"},
   420  				}},
   421  			},
   422  		},
   423  	}, nil)
   424  	mdi.On("UpdateOffset", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   425  
   426  	err := ag.processPins(ag.ctx, []*fftypes.Pin{
   427  		{Sequence: 12345, Batch: fftypes.NewUUID(), Index: 0},
   428  	})
   429  	assert.NoError(t, err)
   430  	mdi.AssertExpectations(t)
   431  
   432  }
   433  
   434  func TestProcessSkipDupMsg(t *testing.T) {
   435  	ag, cancel := newTestAggregator()
   436  	defer cancel()
   437  
   438  	batchID := fftypes.NewUUID()
   439  	mdi := ag.database.(*databasemocks.Plugin)
   440  	mdi.On("GetBatchByID", ag.ctx, mock.Anything).Return(&fftypes.Batch{
   441  		ID: batchID,
   442  		Payload: fftypes.BatchPayload{
   443  			Messages: []*fftypes.Message{
   444  				{Header: fftypes.MessageHeader{
   445  					ID:     fftypes.NewUUID(),
   446  					Topics: fftypes.FFNameArray{"topic1", "topic2"},
   447  				}},
   448  			},
   449  		},
   450  	}, nil).Once()
   451  	mdi.On("GetPins", mock.Anything, mock.Anything).Return([]*fftypes.Pin{
   452  		{Sequence: 1111}, // blocks the context
   453  	}, nil)
   454  	mdi.On("UpdateOffset", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   455  
   456  	err := ag.processPins(ag.ctx, []*fftypes.Pin{
   457  		{Sequence: 12345, Batch: batchID, Index: 0, Hash: fftypes.NewRandB32()},
   458  		{Sequence: 12345, Batch: batchID, Index: 1, Hash: fftypes.NewRandB32()},
   459  	})
   460  	assert.NoError(t, err)
   461  	mdi.AssertExpectations(t)
   462  
   463  }
   464  
   465  func TestProcessMsgFailGetPins(t *testing.T) {
   466  	ag, cancel := newTestAggregator()
   467  	defer cancel()
   468  
   469  	batchID := fftypes.NewUUID()
   470  	mdi := ag.database.(*databasemocks.Plugin)
   471  	mdi.On("GetBatchByID", ag.ctx, mock.Anything).Return(&fftypes.Batch{
   472  		ID: batchID,
   473  		Payload: fftypes.BatchPayload{
   474  			Messages: []*fftypes.Message{
   475  				{Header: fftypes.MessageHeader{
   476  					ID:     fftypes.NewUUID(),
   477  					Topics: fftypes.FFNameArray{"topic1"},
   478  				}},
   479  			},
   480  		},
   481  	}, nil).Once()
   482  	mdi.On("GetPins", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop"))
   483  
   484  	err := ag.processPins(ag.ctx, []*fftypes.Pin{
   485  		{Sequence: 12345, Batch: batchID, Index: 0, Hash: fftypes.NewRandB32()},
   486  	})
   487  	assert.EqualError(t, err, "pop")
   488  	mdi.AssertExpectations(t)
   489  }
   490  
   491  func TestProcessMsgFailMissingGroup(t *testing.T) {
   492  	ag, cancel := newTestAggregator()
   493  	defer cancel()
   494  
   495  	err := ag.processMessage(ag.ctx, &fftypes.Batch{}, true, 12345, &fftypes.Message{})
   496  	assert.NoError(t, err)
   497  
   498  }
   499  
   500  func TestProcessMsgFailBadPin(t *testing.T) {
   501  	ag, cancel := newTestAggregator()
   502  	defer cancel()
   503  
   504  	err := ag.processMessage(ag.ctx, &fftypes.Batch{}, true, 12345, &fftypes.Message{
   505  		Header: fftypes.MessageHeader{
   506  			ID:     fftypes.NewUUID(),
   507  			Group:  fftypes.NewRandB32(),
   508  			Topics: fftypes.FFNameArray{"topic1"},
   509  		},
   510  		Pins: fftypes.FFNameArray{"!Wrong"},
   511  	})
   512  	assert.NoError(t, err)
   513  
   514  }
   515  
   516  func TestProcessMsgFailGetNextPins(t *testing.T) {
   517  	ag, cancel := newTestAggregator()
   518  	defer cancel()
   519  
   520  	mdi := ag.database.(*databasemocks.Plugin)
   521  	mdi.On("GetNextPins", ag.ctx, mock.Anything).Return(nil, fmt.Errorf("pop"))
   522  
   523  	err := ag.processMessage(ag.ctx, &fftypes.Batch{}, true, 12345, &fftypes.Message{
   524  		Header: fftypes.MessageHeader{
   525  			ID:     fftypes.NewUUID(),
   526  			Group:  fftypes.NewRandB32(),
   527  			Topics: fftypes.FFNameArray{"topic1"},
   528  		},
   529  		Pins: fftypes.FFNameArray{fftypes.NewRandB32().String()},
   530  	})
   531  	assert.EqualError(t, err, "pop")
   532  
   533  }
   534  
   535  func TestProcessMsgFailDispatch(t *testing.T) {
   536  	ag, cancel := newTestAggregator()
   537  	defer cancel()
   538  
   539  	mdi := ag.database.(*databasemocks.Plugin)
   540  	mdi.On("GetPins", ag.ctx, mock.Anything).Return([]*fftypes.Pin{}, nil)
   541  	mdm := ag.data.(*datamocks.Manager)
   542  	mdm.On("GetMessageData", ag.ctx, mock.Anything, true).Return(nil, false, fmt.Errorf("pop"))
   543  
   544  	err := ag.processMessage(ag.ctx, &fftypes.Batch{}, false, 12345, &fftypes.Message{
   545  		Header: fftypes.MessageHeader{
   546  			ID:     fftypes.NewUUID(),
   547  			Topics: fftypes.FFNameArray{"topic1"},
   548  		},
   549  		Pins: fftypes.FFNameArray{fftypes.NewRandB32().String()},
   550  	})
   551  	assert.EqualError(t, err, "pop")
   552  
   553  }
   554  
   555  func TestProcessMsgFailPinUpdate(t *testing.T) {
   556  	ag, cancel := newTestAggregator()
   557  	defer cancel()
   558  	pin := fftypes.NewRandB32()
   559  
   560  	mdi := ag.database.(*databasemocks.Plugin)
   561  	mdm := ag.data.(*datamocks.Manager)
   562  	mdi.On("GetNextPins", ag.ctx, mock.Anything).Return([]*fftypes.NextPin{
   563  		{Context: fftypes.NewRandB32(), Hash: pin},
   564  	}, nil)
   565  	mdm.On("GetMessageData", ag.ctx, mock.Anything, true).Return([]*fftypes.Data{}, true, nil)
   566  	mdm.On("ValidateAll", ag.ctx, mock.Anything).Return(false, nil)
   567  	mdi.On("UpsertEvent", ag.ctx, mock.Anything, false).Return(nil)
   568  	mdi.On("UpdateMessage", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   569  	mdi.On("UpdateNextPin", ag.ctx, mock.Anything, mock.Anything).Return(fmt.Errorf("pop"))
   570  
   571  	err := ag.processMessage(ag.ctx, &fftypes.Batch{}, true, 12345, &fftypes.Message{
   572  		Header: fftypes.MessageHeader{
   573  			ID:     fftypes.NewUUID(),
   574  			Group:  fftypes.NewRandB32(),
   575  			Topics: fftypes.FFNameArray{"topic1"},
   576  		},
   577  		Pins: fftypes.FFNameArray{pin.String()},
   578  	})
   579  	assert.EqualError(t, err, "pop")
   580  
   581  }
   582  
   583  func TestCheckMaskedContextReadyMismatchedAuthor(t *testing.T) {
   584  	ag, cancel := newTestAggregator()
   585  	defer cancel()
   586  	pin := fftypes.NewRandB32()
   587  
   588  	mdi := ag.database.(*databasemocks.Plugin)
   589  	mdi.On("GetNextPins", ag.ctx, mock.Anything).Return([]*fftypes.NextPin{
   590  		{Context: fftypes.NewRandB32(), Hash: pin},
   591  	}, nil)
   592  
   593  	_, err := ag.checkMaskedContextReady(ag.ctx, &fftypes.Message{
   594  		Header: fftypes.MessageHeader{
   595  			ID:     fftypes.NewUUID(),
   596  			Group:  fftypes.NewRandB32(),
   597  			Author: "author1",
   598  		},
   599  	}, "topic1", 12345, fftypes.NewRandB32())
   600  	assert.NoError(t, err)
   601  
   602  }
   603  
   604  func TestAttemptContextInitGetGroupByIDFail(t *testing.T) {
   605  	ag, cancel := newTestAggregator()
   606  	defer cancel()
   607  
   608  	mpm := ag.messaging.(*privatemessagingmocks.Manager)
   609  	mpm.On("ResolveInitGroup", ag.ctx, mock.Anything).Return(nil, fmt.Errorf("pop"))
   610  
   611  	_, err := ag.attemptContextInit(ag.ctx, &fftypes.Message{
   612  		Header: fftypes.MessageHeader{
   613  			ID:     fftypes.NewUUID(),
   614  			Group:  fftypes.NewRandB32(),
   615  			Author: "author1",
   616  		},
   617  	}, "topic1", 12345, fftypes.NewRandB32(), fftypes.NewRandB32())
   618  	assert.EqualError(t, err, "pop")
   619  
   620  }
   621  
   622  func TestAttemptContextInitGroupNotFound(t *testing.T) {
   623  	ag, cancel := newTestAggregator()
   624  	defer cancel()
   625  
   626  	mpm := ag.messaging.(*privatemessagingmocks.Manager)
   627  	mpm.On("ResolveInitGroup", ag.ctx, mock.Anything).Return(nil, nil)
   628  
   629  	_, err := ag.attemptContextInit(ag.ctx, &fftypes.Message{
   630  		Header: fftypes.MessageHeader{
   631  			ID:     fftypes.NewUUID(),
   632  			Group:  fftypes.NewRandB32(),
   633  			Author: "author1",
   634  		},
   635  	}, "topic1", 12345, fftypes.NewRandB32(), fftypes.NewRandB32())
   636  	assert.NoError(t, err)
   637  
   638  }
   639  
   640  func TestAttemptContextInitAuthorMismatch(t *testing.T) {
   641  	ag, cancel := newTestAggregator()
   642  	defer cancel()
   643  
   644  	groupID := fftypes.NewRandB32()
   645  	zeroHash := ag.calcHash("topic1", groupID, "author2", 0)
   646  	mpm := ag.messaging.(*privatemessagingmocks.Manager)
   647  	mpm.On("ResolveInitGroup", ag.ctx, mock.Anything).Return(&fftypes.Group{
   648  		GroupIdentity: fftypes.GroupIdentity{
   649  			Members: fftypes.Members{
   650  				{Identity: "author2"},
   651  			},
   652  		},
   653  	}, nil)
   654  
   655  	_, err := ag.attemptContextInit(ag.ctx, &fftypes.Message{
   656  		Header: fftypes.MessageHeader{
   657  			ID:     fftypes.NewUUID(),
   658  			Group:  groupID,
   659  			Author: "author1",
   660  		},
   661  	}, "topic1", 12345, fftypes.NewRandB32(), zeroHash)
   662  	assert.NoError(t, err)
   663  
   664  }
   665  
   666  func TestAttemptContextInitNoMatch(t *testing.T) {
   667  	ag, cancel := newTestAggregator()
   668  	defer cancel()
   669  
   670  	groupID := fftypes.NewRandB32()
   671  	mpm := ag.messaging.(*privatemessagingmocks.Manager)
   672  	mpm.On("ResolveInitGroup", ag.ctx, mock.Anything).Return(&fftypes.Group{
   673  		GroupIdentity: fftypes.GroupIdentity{
   674  			Members: fftypes.Members{
   675  				{Identity: "author2"},
   676  			},
   677  		},
   678  	}, nil)
   679  
   680  	_, err := ag.attemptContextInit(ag.ctx, &fftypes.Message{
   681  		Header: fftypes.MessageHeader{
   682  			ID:     fftypes.NewUUID(),
   683  			Group:  groupID,
   684  			Author: "author1",
   685  		},
   686  	}, "topic1", 12345, fftypes.NewRandB32(), fftypes.NewRandB32())
   687  	assert.NoError(t, err)
   688  
   689  }
   690  
   691  func TestAttemptContextInitGetPinsFail(t *testing.T) {
   692  	ag, cancel := newTestAggregator()
   693  	defer cancel()
   694  
   695  	groupID := fftypes.NewRandB32()
   696  	zeroHash := ag.calcHash("topic1", groupID, "author1", 0)
   697  	mpm := ag.messaging.(*privatemessagingmocks.Manager)
   698  	mdi := ag.database.(*databasemocks.Plugin)
   699  	mpm.On("ResolveInitGroup", ag.ctx, mock.Anything).Return(&fftypes.Group{
   700  		GroupIdentity: fftypes.GroupIdentity{
   701  			Members: fftypes.Members{
   702  				{Identity: "author1"},
   703  			},
   704  		},
   705  	}, nil)
   706  	mdi.On("GetPins", ag.ctx, mock.Anything).Return(nil, fmt.Errorf("pop"))
   707  
   708  	_, err := ag.attemptContextInit(ag.ctx, &fftypes.Message{
   709  		Header: fftypes.MessageHeader{
   710  			ID:     fftypes.NewUUID(),
   711  			Group:  groupID,
   712  			Author: "author1",
   713  		},
   714  	}, "topic1", 12345, fftypes.NewRandB32(), zeroHash)
   715  	assert.EqualError(t, err, "pop")
   716  
   717  }
   718  
   719  func TestAttemptContextInitGetPinsBlocked(t *testing.T) {
   720  	ag, cancel := newTestAggregator()
   721  	defer cancel()
   722  
   723  	groupID := fftypes.NewRandB32()
   724  	zeroHash := ag.calcHash("topic1", groupID, "author1", 0)
   725  	mdi := ag.database.(*databasemocks.Plugin)
   726  	mpm := ag.messaging.(*privatemessagingmocks.Manager)
   727  	mpm.On("ResolveInitGroup", ag.ctx, mock.Anything).Return(&fftypes.Group{
   728  		GroupIdentity: fftypes.GroupIdentity{
   729  			Members: fftypes.Members{
   730  				{Identity: "author1"},
   731  			},
   732  		},
   733  	}, nil)
   734  	mdi.On("GetPins", ag.ctx, mock.Anything).Return([]*fftypes.Pin{
   735  		{Sequence: 12345},
   736  	}, nil)
   737  
   738  	np, err := ag.attemptContextInit(ag.ctx, &fftypes.Message{
   739  		Header: fftypes.MessageHeader{
   740  			ID:     fftypes.NewUUID(),
   741  			Group:  groupID,
   742  			Author: "author1",
   743  		},
   744  	}, "topic1", 12345, fftypes.NewRandB32(), zeroHash)
   745  	assert.NoError(t, err)
   746  	assert.Nil(t, np)
   747  
   748  }
   749  
   750  func TestAttemptContextInitInsertPinsFail(t *testing.T) {
   751  	ag, cancel := newTestAggregator()
   752  	defer cancel()
   753  
   754  	groupID := fftypes.NewRandB32()
   755  	zeroHash := ag.calcHash("topic1", groupID, "author1", 0)
   756  	mdi := ag.database.(*databasemocks.Plugin)
   757  	mpm := ag.messaging.(*privatemessagingmocks.Manager)
   758  	mpm.On("ResolveInitGroup", ag.ctx, mock.Anything).Return(&fftypes.Group{
   759  		GroupIdentity: fftypes.GroupIdentity{
   760  			Members: fftypes.Members{
   761  				{Identity: "author1"},
   762  			},
   763  		},
   764  	}, nil)
   765  	mdi.On("GetPins", ag.ctx, mock.Anything).Return([]*fftypes.Pin{}, nil)
   766  	mdi.On("InsertNextPin", ag.ctx, mock.Anything).Return(fmt.Errorf("pop"))
   767  
   768  	np, err := ag.attemptContextInit(ag.ctx, &fftypes.Message{
   769  		Header: fftypes.MessageHeader{
   770  			ID:     fftypes.NewUUID(),
   771  			Group:  groupID,
   772  			Author: "author1",
   773  		},
   774  	}, "topic1", 12345, fftypes.NewRandB32(), zeroHash)
   775  	assert.Nil(t, np)
   776  	assert.EqualError(t, err, "pop")
   777  
   778  }
   779  
   780  func TestAttemptMessageDispatchFailGetData(t *testing.T) {
   781  	ag, cancel := newTestAggregator()
   782  	defer cancel()
   783  
   784  	mdm := ag.data.(*datamocks.Manager)
   785  	mdm.On("GetMessageData", ag.ctx, mock.Anything, true).Return(nil, false, fmt.Errorf("pop"))
   786  
   787  	_, err := ag.attemptMessageDispatch(ag.ctx, &fftypes.Message{
   788  		Header: fftypes.MessageHeader{ID: fftypes.NewUUID()},
   789  	})
   790  	assert.EqualError(t, err, "pop")
   791  
   792  }
   793  
   794  func TestAttemptMessageDispatchFailValidateData(t *testing.T) {
   795  	ag, cancel := newTestAggregator()
   796  	defer cancel()
   797  
   798  	mdm := ag.data.(*datamocks.Manager)
   799  	mdm.On("GetMessageData", ag.ctx, mock.Anything, true).Return([]*fftypes.Data{}, true, nil)
   800  	mdm.On("ValidateAll", ag.ctx, mock.Anything).Return(false, fmt.Errorf("pop"))
   801  
   802  	_, err := ag.attemptMessageDispatch(ag.ctx, &fftypes.Message{
   803  		Header: fftypes.MessageHeader{ID: fftypes.NewUUID()},
   804  		Data: fftypes.DataRefs{
   805  			{ID: fftypes.NewUUID()},
   806  		},
   807  	})
   808  	assert.EqualError(t, err, "pop")
   809  
   810  }
   811  
   812  func TestAttemptMessageDispatchFailValidateBadSystem(t *testing.T) {
   813  	ag, cancel := newTestAggregator()
   814  	defer cancel()
   815  
   816  	mbm := ag.broadcast.(*broadcastmocks.Manager)
   817  	mbm.On("HandleSystemBroadcast", mock.Anything, mock.Anything, mock.Anything).Return(false, nil)
   818  
   819  	mdm := ag.data.(*datamocks.Manager)
   820  	mdm.On("GetMessageData", ag.ctx, mock.Anything, true).Return([]*fftypes.Data{}, true, nil)
   821  
   822  	mdi := ag.database.(*databasemocks.Plugin)
   823  	mdi.On("UpsertEvent", ag.ctx, mock.Anything, false).Return(nil)
   824  
   825  	_, err := ag.attemptMessageDispatch(ag.ctx, &fftypes.Message{
   826  		Header: fftypes.MessageHeader{
   827  			ID:        fftypes.NewUUID(),
   828  			Namespace: fftypes.SystemNamespace,
   829  		},
   830  		Data: fftypes.DataRefs{
   831  			{ID: fftypes.NewUUID()},
   832  		},
   833  	})
   834  	assert.NoError(t, err)
   835  
   836  }
   837  
   838  func TestAttemptMessageDispatchFailValidateSystemFail(t *testing.T) {
   839  	ag, cancel := newTestAggregator()
   840  	defer cancel()
   841  
   842  	mbm := ag.broadcast.(*broadcastmocks.Manager)
   843  	mbm.On("HandleSystemBroadcast", mock.Anything, mock.Anything, mock.Anything).Return(false, fmt.Errorf("pop"))
   844  
   845  	mdm := ag.data.(*datamocks.Manager)
   846  	mdm.On("GetMessageData", ag.ctx, mock.Anything, true).Return([]*fftypes.Data{}, true, nil)
   847  
   848  	_, err := ag.attemptMessageDispatch(ag.ctx, &fftypes.Message{
   849  		Header: fftypes.MessageHeader{
   850  			ID:        fftypes.NewUUID(),
   851  			Namespace: fftypes.SystemNamespace,
   852  		},
   853  		Data: fftypes.DataRefs{
   854  			{ID: fftypes.NewUUID()},
   855  		},
   856  	})
   857  	assert.EqualError(t, err, "pop")
   858  
   859  }
   860  
   861  func TestAttemptMessageDispatchEventFail(t *testing.T) {
   862  	ag, cancel := newTestAggregator()
   863  	defer cancel()
   864  
   865  	mdi := ag.database.(*databasemocks.Plugin)
   866  	mdm := ag.data.(*datamocks.Manager)
   867  	mdm.On("GetMessageData", ag.ctx, mock.Anything, true).Return([]*fftypes.Data{}, true, nil)
   868  	mdm.On("ValidateAll", ag.ctx, mock.Anything).Return(true, nil)
   869  	mdi.On("UpdateMessage", ag.ctx, mock.Anything, mock.Anything).Return(nil)
   870  	mdi.On("UpsertEvent", ag.ctx, mock.Anything, false).Return(fmt.Errorf("pop"))
   871  
   872  	_, err := ag.attemptMessageDispatch(ag.ctx, &fftypes.Message{
   873  		Header: fftypes.MessageHeader{ID: fftypes.NewUUID()},
   874  	})
   875  	assert.EqualError(t, err, "pop")
   876  
   877  }
   878  
   879  func TestAttemptMessageUpdateMessageFail(t *testing.T) {
   880  	ag, cancel := newTestAggregator()
   881  	defer cancel()
   882  
   883  	mdi := ag.database.(*databasemocks.Plugin)
   884  	mdm := ag.data.(*datamocks.Manager)
   885  	mdm.On("GetMessageData", ag.ctx, mock.Anything, true).Return([]*fftypes.Data{}, true, nil)
   886  	mdm.On("ValidateAll", ag.ctx, mock.Anything).Return(true, nil)
   887  	mdi.On("UpdateMessage", ag.ctx, mock.Anything, mock.Anything).Return(fmt.Errorf("pop"))
   888  
   889  	_, err := ag.attemptMessageDispatch(ag.ctx, &fftypes.Message{
   890  		Header: fftypes.MessageHeader{ID: fftypes.NewUUID()},
   891  	})
   892  	assert.EqualError(t, err, "pop")
   893  
   894  }
   895  
   896  func TestRewindOffchainBatchesNoBatches(t *testing.T) {
   897  	ag, cancel := newTestAggregator()
   898  	defer cancel()
   899  
   900  	mdi := ag.database.(*databasemocks.Plugin)
   901  	mdi.On("UpdateMessage", ag.ctx, mock.Anything, mock.Anything).Return(fmt.Errorf("pop"))
   902  
   903  	rewind, offset := ag.rewindOffchainBatches()
   904  	assert.False(t, rewind)
   905  	assert.Equal(t, int64(0), offset)
   906  }
   907  
   908  func TestRewindOffchainBatchesBatchesNoRewind(t *testing.T) {
   909  	config.Set(config.EventAggregatorBatchSize, 10)
   910  
   911  	ag, cancel := newTestAggregator()
   912  	defer cancel()
   913  
   914  	ag.offchainBatches <- fftypes.NewUUID()
   915  	ag.offchainBatches <- fftypes.NewUUID()
   916  	ag.offchainBatches <- fftypes.NewUUID()
   917  	ag.offchainBatches <- fftypes.NewUUID()
   918  
   919  	mdi := ag.database.(*databasemocks.Plugin)
   920  	mdi.On("GetPins", ag.ctx, mock.Anything, mock.Anything).Return([]*fftypes.Pin{}, nil)
   921  
   922  	rewind, offset := ag.rewindOffchainBatches()
   923  	assert.False(t, rewind)
   924  	assert.Equal(t, int64(0), offset)
   925  }
   926  
   927  func TestRewindOffchainBatchesBatchesRewind(t *testing.T) {
   928  	config.Set(config.EventAggregatorBatchSize, 10)
   929  
   930  	ag, cancel := newTestAggregator()
   931  	defer cancel()
   932  
   933  	ag.offchainBatches <- fftypes.NewUUID()
   934  	ag.offchainBatches <- fftypes.NewUUID()
   935  	ag.offchainBatches <- fftypes.NewUUID()
   936  	ag.offchainBatches <- fftypes.NewUUID()
   937  
   938  	mdi := ag.database.(*databasemocks.Plugin)
   939  	mdi.On("GetPins", ag.ctx, mock.Anything, mock.Anything).Return([]*fftypes.Pin{
   940  		{Sequence: 12345},
   941  	}, nil)
   942  
   943  	rewind, offset := ag.rewindOffchainBatches()
   944  	assert.True(t, rewind)
   945  	assert.Equal(t, int64(12345), offset)
   946  }
   947  
   948  func TestRewindOffchainBatchesBatchesError(t *testing.T) {
   949  	config.Set(config.EventAggregatorBatchSize, 10)
   950  
   951  	ag, cancel := newTestAggregator()
   952  	cancel()
   953  
   954  	ag.offchainBatches <- fftypes.NewUUID()
   955  
   956  	mdi := ag.database.(*databasemocks.Plugin)
   957  	mdi.On("GetPins", ag.ctx, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop"))
   958  
   959  	rewind, _ := ag.rewindOffchainBatches()
   960  	assert.False(t, rewind)
   961  }