diff --git a/backend/pkg/commons/db2/data/data_test.go b/backend/pkg/commons/db2/data/data_test.go index 18f477f9f..376c34be1 100644 --- a/backend/pkg/commons/db2/data/data_test.go +++ b/backend/pkg/commons/db2/data/data_test.go @@ -3,6 +3,7 @@ package data import ( "context" "encoding/hex" + "fmt" "testing" "time" @@ -19,6 +20,7 @@ var ( bob = common.HexToAddress("0x000000000000000000000000000000000000beef") carl = common.HexToAddress("0x000000000000000000000000000000000000cafe") usdc = common.HexToAddress("0x000000000000000000000000000000000000dead") + dai = common.HexToAddress("0x000000000000000000000000000000000000eeee") ) func TestStore(t *testing.T) { @@ -28,15 +30,13 @@ func TestStore(t *testing.T) { if err != nil { t.Fatal(err) } - store := Store{ - db: database.Wrap(s, Table), - } + + store := NewStore(database.Wrap(s, Table)) tests := []struct { name string txs map[string][][]*types.Eth1TransactionIndexed // map[chainID][block][txPosition]*types.Eth1TransactionIndexed transfers map[string][][]TransferWithIndexes - limit int64 opts []Option addresses []common.Address expectedHashes []string @@ -50,7 +50,6 @@ func TestStore(t *testing.T) { {newTx("hash3", alice, bob, "", 2)}, }, }, - limit: 1, addresses: []common.Address{alice}, expectedHashes: []string{"hash3", "hash2", "hash1"}, }, @@ -62,7 +61,6 @@ func TestStore(t *testing.T) { {newTx("hash2", carl, bob, "", 1)}, }, }, - limit: 2, addresses: []common.Address{alice, carl}, expectedHashes: []string{"hash2", "hash1"}, }, @@ -76,7 +74,6 @@ func TestStore(t *testing.T) { {newTx("hash4", carl, bob, "", 3)}, }, }, - limit: 2, addresses: []common.Address{alice, carl}, expectedHashes: []string{"hash4", "hash3", "hash2", "hash1"}, }, @@ -92,7 +89,6 @@ func TestStore(t *testing.T) { {newTx("hash4", carl, bob, "", 3)}, }, }, - limit: 2, addresses: []common.Address{alice, carl}, expectedHashes: []string{"hash4", "hash3", "hash2", "hash1"}, }, @@ -108,7 +104,6 @@ func TestStore(t *testing.T) { {newTx("hash4", alice, bob, "", 3)}, }, }, - limit: 2, addresses: []common.Address{alice, carl}, expectedHashes: []string{"hash4", "hash3", "hash2", "hash1"}, }, @@ -121,7 +116,6 @@ func TestStore(t *testing.T) { {newTx("hash3", carl, bob, "foo", 2)}, }, }, - limit: 1, opts: []Option{OnlyTransactions(), ByMethod(hex.EncodeToString([]byte("foo")))}, addresses: []common.Address{alice}, expectedHashes: []string{"hash1"}, @@ -135,7 +129,6 @@ func TestStore(t *testing.T) { {newTx("hash3", alice, bob, "", 2)}, }, }, - limit: 1, opts: []Option{WithTimeRange(timestamppb.New(t0), timestamppb.New(t1))}, addresses: []common.Address{alice}, expectedHashes: []string{"hash2", "hash1"}, @@ -149,7 +142,6 @@ func TestStore(t *testing.T) { {newTx("hash3", alice, bob, "", 2)}, }, }, - limit: 1, opts: []Option{OnlySent()}, addresses: []common.Address{alice}, expectedHashes: []string{"hash3", "hash1"}, @@ -163,7 +155,6 @@ func TestStore(t *testing.T) { {newTx("hash3", alice, bob, "", 2)}, }, }, - limit: 1, opts: []Option{OnlyReceived()}, addresses: []common.Address{bob}, expectedHashes: []string{"hash3", "hash1"}, @@ -181,7 +172,6 @@ func TestStore(t *testing.T) { {newTransfer("hash3", alice, bob, common.Address{}, 2)}, }, }, - limit: 1, opts: []Option{OnlyTransfers()}, addresses: []common.Address{alice}, expectedHashes: []string{"hash3", "hash1"}, @@ -199,7 +189,6 @@ func TestStore(t *testing.T) { {newTransfer("hash2", alice, bob, common.Address{}, 1)}, }, }, - limit: 1, opts: []Option{OnlyTransactions()}, addresses: []common.Address{alice}, expectedHashes: []string{"hash3", "hash1"}, @@ -224,7 +213,6 @@ func TestStore(t *testing.T) { {newTransfer("hash10", alice, bob, common.Address{}, 9)}, }, }, - limit: 2, addresses: []common.Address{alice}, expectedHashes: []string{"hash10", "hash9", "hash8", "hash7", "hash6", "hash5", "hash4", "hash3", "hash2", "hash1"}, }, @@ -234,31 +222,28 @@ func TestStore(t *testing.T) { "1": { {newTransfer("hash1", alice, bob, usdc, 0)}, {newTransfer("hash2", alice, bob, usdc, 1)}, - {newTransfer("hash3", alice, bob, usdc, 2)}, + {newTransfer("hash3", alice, bob, dai, 2)}, {newTransfer("hash4", alice, bob, usdc, 3)}, {newTransfer("hash5", alice, bob, usdc, 4)}, }, }, - limit: 2, opts: []Option{OnlyTransfers(), ByAsset(usdc), WithTimeRange(timestamppb.New(t1), timestamppb.New(t0.Add(3*time.Second)))}, addresses: []common.Address{alice}, - expectedHashes: []string{"hash4", "hash3", "hash2"}, + expectedHashes: []string{"hash4", "hash2"}, }, { - name: "by asset and sender with time range", + name: "by asset and sender", transfers: map[string][][]TransferWithIndexes{ "1": { - {newTransfer("hash1", alice, bob, usdc, 0)}, - {newTransfer("hash2", bob, alice, usdc, 1)}, - {newTransfer("hash3", alice, bob, usdc, 2)}, - {newTransfer("hash4", bob, alice, usdc, 3)}, - {newTransfer("hash5", alice, bob, usdc, 4)}, + {newTransfer("hash1", bob, alice, usdc, 0)}, + {newTransfer("hash2", bob, alice, dai, 1)}, + {newTransfer("hash3", alice, bob, dai, 2)}, + {newTransfer("hash4", alice, bob, usdc, 3)}, }, }, - limit: 2, - opts: []Option{OnlyTransfers(), OnlySent(), ByAsset(usdc), WithTimeRange(timestamppb.New(t1), timestamppb.New(t0.Add(3*time.Second)))}, + opts: []Option{OnlyTransfers(), ByAsset(usdc), OnlySent()}, addresses: []common.Address{alice}, - expectedHashes: []string{"hash3"}, + expectedHashes: []string{"hash4"}, }, { name: "by asset and receiver with time range", @@ -271,7 +256,6 @@ func TestStore(t *testing.T) { {newTransfer("hash5", bob, alice, usdc, 4)}, }, }, - limit: 2, opts: []Option{OnlyTransfers(), OnlyReceived(), ByAsset(usdc), WithTimeRange(timestamppb.New(t1), timestamppb.New(t0.Add(3*time.Second)))}, addresses: []common.Address{alice}, expectedHashes: []string{"hash3"}, @@ -295,28 +279,55 @@ func TestStore(t *testing.T) { } } var suffix map[string]string - for i := int64(0); i < int64(len(tt.expectedHashes))/tt.limit; i++ { - txs, newSuffix, err := store.Get(tt.addresses, suffix, tt.limit, tt.opts...) - if err != nil { - t.Fatalf("tx %d: %v", i, err) - } - if len(txs) == 0 { - t.Fatalf("tx %d: no transactions found", i) - } - if got, want := int64(len(txs)), tt.limit; got != want { + txs, _, err := store.Get(tt.addresses, suffix, 25, tt.opts...) + if err != nil { + t.Fatal(err) + } + if len(txs) == 0 { + t.Fatalf("no transactions found") + } + if got, want := len(txs), len(tt.expectedHashes); got != want { + t.Errorf("got %v, want %v", got, want) + } + for i := int64(0); i < int64(len(tt.expectedHashes)); i++ { + if got, want := string(txs[i].Hash), tt.expectedHashes[i]; got != want { t.Errorf("got %v, want %v", got, want) } - for j := int64(0); j < tt.limit; j++ { - if got, want := string(txs[j].Hash), tt.expectedHashes[i*tt.limit+j]; got != want { - t.Errorf("got %v, want %v", got, want) - } - } - suffix = newSuffix } }) } } +func TestStoreLimitAndPagination(t *testing.T) { + client, admin := databasetest.NewBigTable(t) + + s, err := database.NewBigTableWithClient(context.Background(), client, admin, Schema) + if err != nil { + t.Fatal(err) + } + + store := NewStore(database.Wrap(s, Table)) + + for i := 0; i < 10; i++ { + err := store.AddBlockTransactions("1", []*types.Eth1TransactionIndexed{newTx(fmt.Sprintf("%d", i), common.Address{}, common.Address{}, "", int64(i))}) + if err != nil { + t.Fatal(err) + } + } + + var prefix map[string]string + var txs []*Interaction + for i := 9; i >= 0; i-- { + txs, prefix, err = store.Get([]common.Address{{}}, prefix, 1) + if len(txs) != 1 { + t.Errorf("got %v, want 1", len(txs)) + } + if got, want := string(txs[0].Hash), fmt.Sprintf("%d", i); got != want { + t.Errorf("got %v, want %v", got, want) + } + } +} + var ( t0 = time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC) t1 = time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC).Add(1 * time.Second) diff --git a/backend/pkg/commons/db2/data/filter.go b/backend/pkg/commons/db2/data/filter.go index ce3ba9f55..648085f59 100644 --- a/backend/pkg/commons/db2/data/filter.go +++ b/backend/pkg/commons/db2/data/filter.go @@ -51,6 +51,10 @@ func newQueryFilter(options options) (*queryFilter, error) { query[0] = "out" params[0] = "
" } + if options.with != nil { + query = append(query, "with") + params = append(params, toHex(options.with.Bytes())) + } if options.chainID != nil { query = append(query, "chainID") params = append(params, *options.chainID) @@ -65,6 +69,10 @@ func newQueryFilter(options options) (*queryFilter, error) { query = append(query, "method") params = append(params, *options.method) } + if options.asset != nil { + query = append(query, "asset") + params = append(params, toHex(options.asset.Bytes())) + } if options.from == nil || options.to == nil { options.from = nil options.to = nil diff --git a/backend/pkg/commons/db2/data/filter_test.go b/backend/pkg/commons/db2/data/filter_test.go index a31358bfe..06485a0aa 100644 --- a/backend/pkg/commons/db2/data/filter_test.go +++ b/backend/pkg/commons/db2/data/filter_test.go @@ -48,6 +48,14 @@ func TestQueryFilter(t *testing.T) { }, want: "out:
", }, + { + name: "sent to", + options: []Option{ + OnlySent(), + With(common.Address{}), + }, + want: "out:with:
:0000000000000000000000000000000000000000", + }, { name: "on a chain ID", options: []Option{ @@ -89,6 +97,15 @@ func TestQueryFilter(t *testing.T) { }, want: "in:chainID:TX:method:
:1234:bar", }, + { + name: "received method on a chain ID", + options: []Option{ + ByAsset(common.Address{}), + OnlyReceived(), + ByChainID("1234"), + }, + want: "in:chainID:ERC20:asset:
:1234:0000000000000000000000000000000000000000", + }, { name: "with time range", options: []Option{ diff --git a/backend/pkg/commons/db2/data/keys.go b/backend/pkg/commons/db2/data/keys.go index f6be0a643..f86722e77 100644 --- a/backend/pkg/commons/db2/data/keys.go +++ b/backend/pkg/commons/db2/data/keys.go @@ -44,46 +44,52 @@ func reversePaddedTimestamp(timestamp *timestamppb.Timestamp) string { //} // lost keyTxBlock, keyTxError, keyTxContractCreation -// key are sorted side (+optional other address), chainID, type, method +// key are sorted side, with, chainID, type, method func transactionKeys(chainID string, transaction *types.Eth1TransactionIndexed, index int) (string, []string) { main := "TX::" baseKeys := []string{ "all:
", + "all:with:
:", "all:chainID:
:", + "all:with:chainID:
::", } fromToKeys := []string{ "in:", "in:chainID::", - "in:out::", - "in:out:chainID:::", + "in:with::", + "in:with:chainID:::", "out:", "out:chainID::", - "out:in::", - "out:in:chainID:::", + "out:with::", + "out:with:chainID:::", } baseTxKeys := []string{ "all:TX:
", "all:TX:method:
:", "all:chainID:TX:
:", "all:chainID:TX:method:
::", + "all:with:TX:
:", + "all:with:TX:method:
:", + "all:with:chainID:TX:
::", + "all:with:chainID:TX:method:
::", } fromToTxKeys := []string{ "in:TX:", "in:TX:method::", "in:chainID:TX::", "in:chainID:TX:method:::", - "in:out:TX::", - "in:out:TX:method:::", - "in:out:chainID:TX:::", - "in:out:chainID:TX:method::::", + "in:with:TX::", + "in:with:TX:method:::", + "in:with:chainID:TX:::", + "in:with:chainID:TX:method::::", "out:TX:", "out:TX:method::", "out:chainID:TX::", "out:chainID:TX:method:::", - "out:in:TX::", - "out:in:TX:method:::", - "out:in:chainID:TX:::", - "out:in:chainID:TX:method::::", + "out:with:TX::", + "out:with:TX:method:::", + "out:with:chainID:TX:::", + "out:with:chainID:TX:method::::", } replacer := strings.NewReplacer( "", toHex(transaction.Hash), @@ -97,8 +103,8 @@ func transactionKeys(chainID string, transaction *types.Eth1TransactionIndexed, keys := append(fromToKeys, fromToTxKeys...) for _, format := range append(baseKeys, baseTxKeys...) { keys = append(keys, - strings.ReplaceAll(format, "
", ""), - strings.ReplaceAll(format, "
", ""), + strings.ReplaceAll(strings.ReplaceAll(format, "
", ""), "", ""), + strings.ReplaceAll(strings.ReplaceAll(format, "
", ""), "", ""), ) } id := ":