Skip to content

Commit a38eb3b

Browse files
authored
feat: Added methods to handle new transactions (#36)
* fix: Fix a parameter type casting * feat: Add a `newTransactions` event subscription * feat: Implement an RPC transaction filter * fix: Send events by event type * fix: Fix comments * feat: Add transaction filter options * fix: Remove reflect types * fix: Change transaction event handling * fix: Change exception conditions when getting tx * fix: Fix a lint error
1 parent ee4570d commit a38eb3b

File tree

18 files changed

+537
-181
lines changed

18 files changed

+537
-181
lines changed

fetch/fetch_test.go

+199-16
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,15 @@ func TestFetcher_FetchTransactions_Valid_FullBlocks(t *testing.T) {
7474

7575
savedTxs = make([]*types.TxResult, 0, txCount*blockNum)
7676
savedBlocks = make([]*types.Block, 0, blockNum)
77-
capturedEvents = make([]*indexerTypes.NewBlock, 0)
77+
capturedEvents = make([]events.Event, 0)
7878

7979
mockEvents = &mockEvents{
8080
signalEventFn: func(e events.Event) {
81-
blockEvent, ok := e.(*indexerTypes.NewBlock)
82-
require.True(t, ok)
83-
84-
capturedEvents = append(capturedEvents, blockEvent)
81+
if e.GetType() == indexerTypes.NewBlockEvent {
82+
_, ok := e.(*indexerTypes.NewBlock)
83+
require.True(t, ok)
84+
capturedEvents = append(capturedEvents, e)
85+
}
8586
},
8687
}
8788

@@ -201,13 +202,20 @@ func TestFetcher_FetchTransactions_Valid_FullBlocks(t *testing.T) {
201202
require.Len(t, capturedEvents, len(blocks)-1)
202203

203204
for index, event := range capturedEvents {
205+
if event.GetType() != indexerTypes.NewBlockEvent {
206+
continue
207+
}
208+
209+
eventData, ok := event.(*indexerTypes.NewBlock)
210+
require.True(t, ok)
211+
204212
// Make sure the block is valid
205-
assert.Equal(t, blocks[index+1], event.Block)
213+
assert.Equal(t, blocks[index+1], eventData.Block)
206214

207215
// Make sure the transaction results are valid
208-
require.Len(t, event.Results, txCount)
216+
require.Len(t, eventData.Results, txCount)
209217

210-
for txIndex, tx := range event.Results {
218+
for txIndex, tx := range eventData.Results {
211219
assert.EqualValues(t, blocks[index+1].Height, tx.Height)
212220
assert.EqualValues(t, txIndex, tx.Index)
213221
assert.Equal(t, serializedTxs[txIndex], tx.Tx)
@@ -229,14 +237,15 @@ func TestFetcher_FetchTransactions_Valid_FullBlocks(t *testing.T) {
229237

230238
savedTxs = make([]*types.TxResult, 0, txCount*blockNum)
231239
savedBlocks = make([]*types.Block, 0, blockNum)
232-
capturedEvents = make([]*indexerTypes.NewBlock, 0)
240+
capturedEvents = make([]events.Event, 0)
233241

234242
mockEvents = &mockEvents{
235243
signalEventFn: func(e events.Event) {
236-
blockEvent, ok := e.(*indexerTypes.NewBlock)
237-
require.True(t, ok)
238-
239-
capturedEvents = append(capturedEvents, blockEvent)
244+
if e.GetType() == indexerTypes.NewBlockEvent {
245+
_, ok := e.(*indexerTypes.NewBlock)
246+
require.True(t, ok)
247+
capturedEvents = append(capturedEvents, e)
248+
}
240249
},
241250
}
242251

@@ -378,12 +387,186 @@ func TestFetcher_FetchTransactions_Valid_FullBlocks(t *testing.T) {
378387

379388
for index, event := range capturedEvents {
380389
// Make sure the block is valid
381-
assert.Equal(t, blocks[index+1], event.Block)
390+
eventData := event.(*indexerTypes.NewBlock)
391+
assert.Equal(t, blocks[index+1], eventData.Block)
392+
393+
// Make sure the transaction results are valid
394+
require.Len(t, eventData.Results, txCount)
395+
396+
for txIndex, tx := range eventData.Results {
397+
assert.EqualValues(t, blocks[index+1].Height, tx.Height)
398+
assert.EqualValues(t, txIndex, tx.Index)
399+
assert.Equal(t, serializedTxs[txIndex], tx.Tx)
400+
}
401+
}
402+
})
403+
}
404+
405+
func TestFetcher_FetchTransactions_Valid_FullTransactions(t *testing.T) {
406+
t.Parallel()
407+
408+
t.Run("valid txs flow, sequential", func(t *testing.T) {
409+
t.Parallel()
410+
411+
var cancelFn context.CancelFunc
412+
413+
var (
414+
blockNum = 1000
415+
txCount = 10
416+
txs = generateTransactions(t, txCount)
417+
serializedTxs = serializeTxs(t, txs)
418+
blocks = generateBlocks(t, blockNum+1, txs)
419+
420+
savedTxs = make([]*types.TxResult, 0, txCount*blockNum)
421+
savedBlocks = make([]*types.Block, 0, blockNum)
422+
capturedEvents = make([]events.Event, 0)
423+
424+
mockEvents = &mockEvents{
425+
signalEventFn: func(e events.Event) {
426+
if e.GetType() == indexerTypes.NewBlockEvent {
427+
_, ok := e.(*indexerTypes.NewBlock)
428+
require.True(t, ok)
429+
capturedEvents = append(capturedEvents, e)
430+
}
431+
},
432+
}
433+
434+
latestSaved = uint64(0)
435+
436+
mockStorage = &mock.Storage{
437+
GetLatestSavedHeightFn: func() (uint64, error) {
438+
if latestSaved == 0 {
439+
return 0, storageErrors.ErrNotFound
440+
}
441+
442+
return latestSaved, nil
443+
},
444+
GetWriteBatchFn: func() storage.Batch {
445+
return &mock.WriteBatch{
446+
SetBlockFn: func(block *types.Block) error {
447+
savedBlocks = append(savedBlocks, block)
448+
449+
// Check if all blocks are saved
450+
if block.Height == int64(blockNum) {
451+
// At this point, we can cancel the process
452+
cancelFn()
453+
}
454+
455+
latestSaved = uint64(block.Height)
456+
457+
return nil
458+
},
459+
SetTxFn: func(result *types.TxResult) error {
460+
savedTxs = append(savedTxs, result)
461+
462+
return nil
463+
},
464+
}
465+
},
466+
}
467+
468+
mockClient = &mockClient{
469+
createBatchFn: func() clientTypes.Batch {
470+
return &mockBatch{
471+
executeFn: func(_ context.Context) ([]any, error) {
472+
// Force an error
473+
return nil, errors.New("something is flaky")
474+
},
475+
countFn: func() int {
476+
return 1 // to trigger execution
477+
},
478+
}
479+
},
480+
getLatestBlockNumberFn: func() (uint64, error) {
481+
return uint64(blockNum), nil
482+
},
483+
getBlockFn: func(num uint64) (*core_types.ResultBlock, error) {
484+
// Sanity check
485+
if num > uint64(blockNum) {
486+
t.Fatalf("invalid block requested, %d", num)
487+
}
488+
489+
if len(blocks[num].Txs) != txCount {
490+
t.Fatalf("invalid transactions, current size: %d", len(blocks[num].Txs))
491+
}
492+
493+
return &core_types.ResultBlock{
494+
Block: blocks[num],
495+
}, nil
496+
},
497+
getBlockResultsFn: func(num uint64) (*core_types.ResultBlockResults, error) {
498+
// Sanity check
499+
if num > uint64(blockNum) {
500+
t.Fatalf("invalid block requested, %d", num)
501+
}
502+
503+
return &core_types.ResultBlockResults{
504+
Height: int64(num),
505+
Results: &state.ABCIResponses{
506+
DeliverTxs: make([]abci.ResponseDeliverTx, txCount),
507+
},
508+
}, nil
509+
},
510+
}
511+
)
512+
513+
// Create the fetcher
514+
f := New(
515+
mockStorage,
516+
mockClient,
517+
mockEvents,
518+
WithMaxSlots(10),
519+
WithMaxChunkSize(50),
520+
)
521+
522+
// Short interval to force spawning
523+
f.queryInterval = 100 * time.Millisecond
524+
525+
// Create the context
526+
ctx, cancelFn := context.WithCancel(context.Background())
527+
defer cancelFn()
528+
529+
// Run the fetch
530+
require.NoError(t, f.FetchChainData(ctx))
531+
532+
// Verify the transactions are saved correctly
533+
require.Len(t, savedTxs, blockNum*txCount)
534+
535+
for blockIndex := 0; blockIndex < blockNum; blockIndex++ {
536+
assert.Equal(t, blocks[blockIndex+1], savedBlocks[blockIndex])
537+
538+
for txIndex := 0; txIndex < txCount; txIndex++ {
539+
// since this is a linearized array of transactions
540+
// we can access each item with: blockNum * length + txIndx
541+
// where blockNum is the y-axis, and txIndx is the x-axis
542+
tx := savedTxs[blockIndex*txCount+txIndex]
543+
544+
assert.EqualValues(t, blockIndex+1, tx.Height)
545+
assert.EqualValues(t, txIndex, tx.Index)
546+
assert.Equal(t, serializedTxs[txIndex], tx.Tx)
547+
}
548+
}
549+
550+
// Make sure proper events were emitted
551+
// Blocks each have as many transactions as txCount.
552+
txEventCount := (len(blocks) - 1)
553+
require.Len(t, capturedEvents, txEventCount)
554+
555+
for index, event := range capturedEvents {
556+
if event.GetType() != indexerTypes.NewBlockEvent {
557+
continue
558+
}
559+
560+
eventData, ok := event.(*indexerTypes.NewBlock)
561+
require.True(t, ok)
562+
563+
// Make sure the block is valid
564+
assert.Equal(t, blocks[index+1], eventData.Block)
382565

383566
// Make sure the transaction results are valid
384-
require.Len(t, event.Results, txCount)
567+
require.Len(t, eventData.Results, txCount)
385568

386-
for txIndex, tx := range event.Results {
569+
for txIndex, tx := range eventData.Results {
387570
assert.EqualValues(t, blocks[index+1].Height, tx.Height)
388571
assert.EqualValues(t, txIndex, tx.Index)
389572
assert.Equal(t, serializedTxs[txIndex], tx.Tx)

serve/filters/filter/base.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,6 @@ func (b *baseFilter) UpdateLastUsed() {
3939
b.lastUsed = time.Now()
4040
}
4141

42-
func (b *baseFilter) GetChanges() any {
42+
func (b *baseFilter) GetChanges() []any {
4343
return nil
4444
}

serve/filters/filter/block.go

+17-4
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,34 @@ func NewBlockFilter() *BlockFilter {
2020
}
2121

2222
// GetChanges returns all new block headers from the last query
23-
func (b *BlockFilter) GetChanges() any {
23+
func (b *BlockFilter) GetChanges() []any {
24+
return b.getBlockChanges()
25+
}
26+
27+
func (b *BlockFilter) UpdateWith(data any) {
28+
if block, ok := data.(*types.Block); ok {
29+
b.updateWithBlock(block)
30+
}
31+
}
32+
33+
// getBlockChanges returns all new block headers from the last query
34+
func (b *BlockFilter) getBlockChanges() []any {
2435
b.Lock()
2536
defer b.Unlock()
2637

2738
// Get hashes
28-
hashes := make([]types.Header, len(b.blockHeaders))
29-
copy(hashes, b.blockHeaders)
39+
hashes := make([]any, len(b.blockHeaders))
40+
for index, blockHeader := range b.blockHeaders {
41+
hashes[index] = blockHeader
42+
}
3043

3144
// Empty headers
3245
b.blockHeaders = b.blockHeaders[:0]
3346

3447
return hashes
3548
}
3649

37-
func (b *BlockFilter) UpdateWithBlock(block *types.Block) {
50+
func (b *BlockFilter) updateWithBlock(block *types.Block) {
3851
b.Lock()
3952
defer b.Unlock()
4053

serve/filters/filter/block_test.go

+2-5
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,11 @@ func TestBlockFilter_GetChanges(t *testing.T) {
4040
for _, block := range blocks {
4141
block := block
4242

43-
f.UpdateWithBlock(block)
43+
f.UpdateWith(block)
4444
}
4545

4646
// Get changes
47-
changesRaw := f.GetChanges()
48-
49-
changes, ok := changesRaw.([]types.Header)
50-
require.True(t, ok)
47+
changes := f.GetChanges()
5148

5249
// Make sure the headers match
5350
require.Len(t, changes, len(blocks))

0 commit comments

Comments
 (0)