From 88e82bcbf07adcf1b8102e1a5242bbb1c9251026 Mon Sep 17 00:00:00 2001 From: "Leo Zhang (zhangchiqing)" Date: Tue, 28 May 2024 11:51:13 -0700 Subject: [PATCH] add get full collection --- adapters/access.go | 14 +++++++++++ emulator/blockchain.go | 18 ++++++++++++++ emulator/emulator.go | 1 + emulator/mocks/emulator.go | 15 ++++++++++++ storage/memstore/memstore.go | 26 ++++++++++++++++++++ storage/mocks/store.go | 15 ++++++++++++ storage/store.go | 41 ++++++++++++++++++++++++++++++++ storage/store_test.go | 46 +++++++++++++++++++++++++++--------- utils/unittest/fixtures.go | 12 ++++++++++ 9 files changed, 177 insertions(+), 11 deletions(-) diff --git a/adapters/access.go b/adapters/access.go index 339476a6..1f534fda 100644 --- a/adapters/access.go +++ b/adapters/access.go @@ -172,6 +172,20 @@ func (a *AccessAdapter) GetCollectionByID(_ context.Context, id flowgo.Identifie return collection, nil } +func (a *AccessAdapter) GetFullCollectionByID(_ context.Context, id flowgo.Identifier) (*flowgo.Collection, error) { + collection, err := a.emulator.GetFullCollectionByID(id) + if err != nil { + return nil, convertError(err, codes.Internal) + } + + a.logger.Debug(). + Str("colID", id.String()). + Msg("📚 GetFullCollectionByID called") + + return collection, nil + +} + func (a *AccessAdapter) GetTransaction(_ context.Context, id flowgo.Identifier) (*flowgo.TransactionBody, error) { tx, err := a.emulator.GetTransaction(id) if err != nil { diff --git a/emulator/blockchain.go b/emulator/blockchain.go index fdc8bccc..fa432563 100644 --- a/emulator/blockchain.go +++ b/emulator/blockchain.go @@ -948,6 +948,24 @@ func (b *Blockchain) getCollectionByID(colID flowgo.Identifier) (*flowgo.LightCo return &col, nil } +func (b *Blockchain) GetFullCollectionByID(colID flowgo.Identifier) (*flowgo.Collection, error) { + b.mu.RLock() + defer b.mu.RUnlock() + return b.getFullCollectionByID(colID) +} + +func (b *Blockchain) getFullCollectionByID(colID flowgo.Identifier) (*flowgo.Collection, error) { + col, err := b.storage.FullCollectionByID(context.Background(), colID) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + return nil, &types.CollectionNotFoundError{ID: colID} + } + return nil, err + } + + return &col, nil +} + // GetTransaction gets an existing transaction by ID. // // The function first looks in the pending block, then the current emulator state. diff --git a/emulator/emulator.go b/emulator/emulator.go index 1679b92e..4da06984 100644 --- a/emulator/emulator.go +++ b/emulator/emulator.go @@ -131,6 +131,7 @@ type AccessProvider interface { GetBlockByHeight(height uint64) (*flowgo.Block, error) GetCollectionByID(colID flowgo.Identifier) (*flowgo.LightCollection, error) + GetFullCollectionByID(colID flowgo.Identifier) (*flowgo.Collection, error) GetTransaction(txID flowgo.Identifier) (*flowgo.TransactionBody, error) GetTransactionResult(txID flowgo.Identifier) (*access.TransactionResult, error) diff --git a/emulator/mocks/emulator.go b/emulator/mocks/emulator.go index 9521fc62..7d8891f6 100644 --- a/emulator/mocks/emulator.go +++ b/emulator/mocks/emulator.go @@ -393,6 +393,21 @@ func (mr *MockEmulatorMockRecorder) GetEventsForHeightRange(arg0, arg1, arg2 any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEventsForHeightRange", reflect.TypeOf((*MockEmulator)(nil).GetEventsForHeightRange), arg0, arg1, arg2) } +// GetFullCollectionByID mocks base method. +func (m *MockEmulator) GetFullCollectionByID(arg0 flow.Identifier) (*flow.Collection, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFullCollectionByID", arg0) + ret0, _ := ret[0].(*flow.Collection) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFullCollectionByID indicates an expected call of GetFullCollectionByID. +func (mr *MockEmulatorMockRecorder) GetFullCollectionByID(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFullCollectionByID", reflect.TypeOf((*MockEmulator)(nil).GetFullCollectionByID), arg0) +} + // GetLatestBlock mocks base method. func (m *MockEmulator) GetLatestBlock() (*flow.Block, error) { m.ctrl.T.Helper() diff --git a/storage/memstore/memstore.go b/storage/memstore/memstore.go index 284a4ed3..ff8b09c6 100644 --- a/storage/memstore/memstore.go +++ b/storage/memstore/memstore.go @@ -217,6 +217,32 @@ func (s *Store) CollectionByID( return tx, nil } +func (s *Store) FullCollectionByID( + _ context.Context, + collectionID flowgo.Identifier, +) (flowgo.Collection, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + light, ok := s.collections[collectionID] + if !ok { + return flowgo.Collection{}, storage.ErrNotFound + } + + txs := make([]*flowgo.TransactionBody, len(light.Transactions)) + for i, txID := range light.Transactions { + tx, ok := s.transactions[txID] + if !ok { + return flowgo.Collection{}, storage.ErrNotFound + } + txs[i] = &tx + } + + return flowgo.Collection{ + Transactions: txs, + }, nil +} + func (s *Store) TransactionByID( _ context.Context, transactionID flowgo.Identifier, diff --git a/storage/mocks/store.go b/storage/mocks/store.go index 9cac2709..a3263bc8 100644 --- a/storage/mocks/store.go +++ b/storage/mocks/store.go @@ -116,6 +116,21 @@ func (mr *MockStoreMockRecorder) EventsByHeight(arg0, arg1, arg2 any) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventsByHeight", reflect.TypeOf((*MockStore)(nil).EventsByHeight), arg0, arg1, arg2) } +// FullCollectionByID mocks base method. +func (m *MockStore) FullCollectionByID(arg0 context.Context, arg1 flow.Identifier) (flow.Collection, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FullCollectionByID", arg0, arg1) + ret0, _ := ret[0].(flow.Collection) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FullCollectionByID indicates an expected call of FullCollectionByID. +func (mr *MockStoreMockRecorder) FullCollectionByID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullCollectionByID", reflect.TypeOf((*MockStore)(nil).FullCollectionByID), arg0, arg1) +} + // LatestBlock mocks base method. func (m *MockStore) LatestBlock(arg0 context.Context) (flow.Block, error) { m.ctrl.T.Helper() diff --git a/storage/store.go b/storage/store.go index 8414b375..b4135c8e 100644 --- a/storage/store.go +++ b/storage/store.go @@ -86,6 +86,9 @@ type Store interface { // CollectionByID gets the collection (transaction IDs only) with the given ID. CollectionByID(ctx context.Context, collectionID flowgo.Identifier) (flowgo.LightCollection, error) + // FullCollectionByID gets the full collection (including transaction bodies) with the given ID. + FullCollectionByID(ctx context.Context, collectionID flowgo.Identifier) (flowgo.Collection, error) + // TransactionByID gets the transaction with the given ID. TransactionByID(ctx context.Context, transactionID flowgo.Identifier) (flowgo.TransactionBody, error) @@ -327,6 +330,44 @@ func (s *DefaultStore) CollectionByID( return } +func (s *DefaultStore) FullCollectionByID( + ctx context.Context, + colID flowgo.Identifier, +) ( + col flowgo.Collection, + err error, +) { + light := flowgo.LightCollection{} + encCol, err := s.DataGetter.GetBytes( + ctx, + s.KeyGenerator.Storage(CollectionStoreName), + s.KeyGenerator.Identifier(colID), + ) + if err != nil { + return + } + + err = decodeCollection(&light, encCol) + if err != nil { + return + } + + txs := make([]*flowgo.TransactionBody, len(light.Transactions)) + for i, txID := range light.Transactions { + tx, err := s.TransactionByID(ctx, txID) + if err != nil { + return col, err + } + txs[i] = &tx + } + + col = flowgo.Collection{ + Transactions: txs, + } + + return +} + func (s *DefaultStore) InsertCollection(ctx context.Context, col flowgo.LightCollection) error { encCol, err := encodeCollection(col) if err != nil { diff --git a/storage/store_test.go b/storage/store_test.go index 53e7649f..ac06d664 100644 --- a/storage/store_test.go +++ b/storage/store_test.go @@ -133,16 +133,8 @@ func TestCollections(t *testing.T) { require.NoError(t, os.RemoveAll(dir)) }() - ids := test.IdentifierGenerator() - // collection with 3 transactions - col := flowgo.LightCollection{ - Transactions: []flowgo.Identifier{ - flowgo.Identifier(ids.New()), - flowgo.Identifier(ids.New()), - flowgo.Identifier(ids.New()), - }, - } + col := unittest.FullCollectionFixture(3) t.Run("should return error for not found", func(t *testing.T) { _, err := store.CollectionByID(context.Background(), col.ID()) @@ -152,13 +144,13 @@ func TestCollections(t *testing.T) { }) t.Run("should be able to insert collection", func(t *testing.T) { - err := store.InsertCollection(context.Background(), col) + err := store.InsertCollection(context.Background(), col.Light()) assert.NoError(t, err) t.Run("should be able to get inserted collection", func(t *testing.T) { storedCol, err := store.CollectionByID(context.Background(), col.ID()) require.NoError(t, err) - assert.Equal(t, col, storedCol) + assert.Equal(t, col.Light(), storedCol) }) }) } @@ -194,6 +186,38 @@ func TestTransactions(t *testing.T) { }) } +func TestFullCollection(t *testing.T) { + t.Parallel() + store, dir := setupStore(t) + defer func() { + require.NoError(t, store.Close()) + require.NoError(t, os.RemoveAll(dir)) + }() + + col := unittest.FullCollectionFixture(3) + + t.Run("should be able to insert full collection", func(t *testing.T) { + _, err := store.CollectionByID(context.Background(), col.ID()) + require.Error(t, storage.ErrNotFound, err) + + _, err = store.FullCollectionByID(context.Background(), col.ID()) + require.Error(t, storage.ErrNotFound, err) + + err = store.InsertCollection(context.Background(), col.Light()) + require.NoError(t, err) + + for _, tx := range col.Transactions { + err = store.InsertTransaction(context.Background(), *tx) + require.NoError(t, err) + } + + c, err := store.FullCollectionByID(context.Background(), col.ID()) + require.NoError(t, err) + require.Equal(t, col, c) + }) + +} + func TestTransactionResults(t *testing.T) { t.Parallel() diff --git a/utils/unittest/fixtures.go b/utils/unittest/fixtures.go index 23903782..f40eb5f6 100644 --- a/utils/unittest/fixtures.go +++ b/utils/unittest/fixtures.go @@ -46,3 +46,15 @@ func StorableTransactionResultFixture() types.StorableTransactionResult { }, } } + +func FullCollectionFixture(n int) flowgo.Collection { + transactions := make([]*flowgo.TransactionBody, n) + for i := 0; i < n; i++ { + tx := TransactionFixture() + transactions[i] = &tx + } + + return flowgo.Collection{ + Transactions: transactions, + } +}