From f49d7edf6330bcb06ed423d9a463da357dddc9da Mon Sep 17 00:00:00 2001 From: Antonio Navarro Date: Tue, 22 Oct 2024 11:12:24 -0500 Subject: [PATCH 1/7] feat: Add ordering on new filtered methods. Signed-off-by: Antonio Navarro --- internal/mock/storage.go | 13 ++ serve/graph/all.resolvers.go | 63 +++++-- serve/graph/gen/generate.go | 4 +- serve/graph/generated.go | 161 ++++++++++++++-- serve/graph/model/models_gen.go | 49 +++++ serve/graph/schema/schema.graphql | 8 + serve/graph/schema/types/block.graphql | 4 + serve/graph/schema/types/transaction.graphql | 4 + storage/pebble.go | 186 ++++++++++++++++--- storage/types.go | 7 + 10 files changed, 441 insertions(+), 58 deletions(-) diff --git a/internal/mock/storage.go b/internal/mock/storage.go index 23ae5a1f..8cc698ab 100644 --- a/internal/mock/storage.go +++ b/internal/mock/storage.go @@ -66,6 +66,19 @@ func (m *Storage) TxIterator( panic("not implemented") // TODO: Implement } +func (m *Storage) BlockReverseIterator(_, _ uint64) (storage.Iterator[*types.Block], error) { + panic("not implemented") // TODO: Implement +} + +func (m *Storage) TxReverseIterator( + _, + _ uint64, + _, + _ uint32, +) (storage.Iterator[*types.TxResult], error) { + panic("not implemented") // TODO: Implement +} + // WriteBatch provides a batch intended to do a write action that // can be cancelled or committed all at the same time func (m *Storage) WriteBatch() storage.Batch { diff --git a/serve/graph/all.resolvers.go b/serve/graph/all.resolvers.go index ec84c24b..75ea69ea 100644 --- a/serve/graph/all.resolvers.go +++ b/serve/graph/all.resolvers.go @@ -8,9 +8,12 @@ import ( "context" "github.com/99designs/gqlgen/graphql" + bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/vektah/gqlparser/v2/gqlerror" + "github.com/gnolang/tx-indexer/serve/graph/model" + "github.com/gnolang/tx-indexer/storage" "github.com/gnolang/tx-indexer/types" - "github.com/vektah/gqlparser/v2/gqlerror" ) // Transactions is the resolver for the transactions field. @@ -124,7 +127,7 @@ func (r *queryResolver) LatestBlockHeight(ctx context.Context) (int, error) { } // GetBlocks is the resolver for the getBlocks field. -func (r *queryResolver) GetBlocks(ctx context.Context, where model.FilterBlock) ([]*model.Block, error) { +func (r *queryResolver) GetBlocks(ctx context.Context, where model.FilterBlock, order *model.TransactionOrder) ([]*model.Block, error) { fromh, toh := where.MinMaxHeight() dfromh := uint64(deref(fromh)) dtoh := uint64(deref(toh)) @@ -134,12 +137,24 @@ func (r *queryResolver) GetBlocks(ctx context.Context, where model.FilterBlock) dtoh++ } - it, err := r. - store. - BlockIterator( - dfromh, - dtoh, - ) + var err error + var it storage.Iterator[*bfttypes.Block] + if order != nil && order.HeightAndIndex == model.OrderDesc { + it, err = r. + store. + BlockReverseIterator( + dfromh, + dtoh, + ) + } else { + it, err = r. + store. + BlockIterator( + dfromh, + dtoh, + ) + } + if err != nil { return nil, gqlerror.Wrap(err) } @@ -182,7 +197,7 @@ func (r *queryResolver) GetBlocks(ctx context.Context, where model.FilterBlock) } // GetTransactions is the resolver for the getTransactions field. -func (r *queryResolver) GetTransactions(ctx context.Context, where model.FilterTransaction) ([]*model.Transaction, error) { +func (r *queryResolver) GetTransactions(ctx context.Context, where model.FilterTransaction, order *model.TransactionOrder) ([]*model.Transaction, error) { // corner case if where.Hash != nil && where.Hash.Eq != nil && @@ -220,14 +235,28 @@ func (r *queryResolver) GetTransactions(ctx context.Context, where model.FilterT dtoi++ } - it, err := r. - store. - TxIterator( - dfromh, - dtoh, - dfromi, - dtoi, - ) + var err error + var it storage.Iterator[*bfttypes.TxResult] + if order != nil && order.HeightAndIndex == model.OrderDesc { + it, err = r. + store. + TxReverseIterator( + dfromh, + dtoh, + dfromi, + dtoi, + ) + } else { + it, err = r. + store. + TxIterator( + dfromh, + dtoh, + dfromi, + dtoi, + ) + } + if err != nil { return nil, gqlerror.Wrap(err) } diff --git a/serve/graph/gen/generate.go b/serve/graph/gen/generate.go index 7764204a..6b2cfe41 100644 --- a/serve/graph/gen/generate.go +++ b/serve/graph/gen/generate.go @@ -18,14 +18,14 @@ type Query { Incomplete results due to errors return both the partial Blocks and the associated errors. """ - getBlocks(where: FilterBlock!): [Block!] + getBlocks(where: FilterBlock!, order: TransactionOrder): [Block!] """ EXPERIMENTAL: Retrieves a list of Transactions that match the given where criteria. If the result is incomplete due to errors, both partial results and errors are returned. """ - getTransactions(where: FilterTransaction!): [Transaction!] + getTransactions(where: FilterTransaction!, order: TransactionOrder): [Transaction!] } type Subscription { diff --git a/serve/graph/generated.go b/serve/graph/generated.go index 156d01da..6aa978d2 100644 --- a/serve/graph/generated.go +++ b/serve/graph/generated.go @@ -15,9 +15,10 @@ import ( "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/introspection" - "github.com/gnolang/tx-indexer/serve/graph/model" gqlparser "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" + + "github.com/gnolang/tx-indexer/serve/graph/model" ) // region ************************** generated!.gotpl ************************** @@ -132,8 +133,8 @@ type ComplexityRoot struct { Query struct { Blocks func(childComplexity int, filter model.BlockFilter) int - GetBlocks func(childComplexity int, where model.FilterBlock) int - GetTransactions func(childComplexity int, where model.FilterTransaction) int + GetBlocks func(childComplexity int, where model.FilterBlock, order *model.TransactionOrder) int + GetTransactions func(childComplexity int, where model.FilterTransaction, order *model.TransactionOrder) int LatestBlockHeight func(childComplexity int) int Transactions func(childComplexity int, filter model.TransactionFilter) int } @@ -191,8 +192,8 @@ type QueryResolver interface { Transactions(ctx context.Context, filter model.TransactionFilter) ([]*model.Transaction, error) Blocks(ctx context.Context, filter model.BlockFilter) ([]*model.Block, error) LatestBlockHeight(ctx context.Context) (int, error) - GetBlocks(ctx context.Context, where model.FilterBlock) ([]*model.Block, error) - GetTransactions(ctx context.Context, where model.FilterTransaction) ([]*model.Transaction, error) + GetBlocks(ctx context.Context, where model.FilterBlock, order *model.TransactionOrder) ([]*model.Block, error) + GetTransactions(ctx context.Context, where model.FilterTransaction, order *model.TransactionOrder) ([]*model.Transaction, error) } type SubscriptionResolver interface { Transactions(ctx context.Context, filter model.TransactionFilter) (<-chan *model.Transaction, error) @@ -578,7 +579,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.GetBlocks(childComplexity, args["where"].(model.FilterBlock)), true + return e.complexity.Query.GetBlocks(childComplexity, args["where"].(model.FilterBlock), args["order"].(*model.TransactionOrder)), true case "Query.getTransactions": if e.complexity.Query.GetTransactions == nil { @@ -590,7 +591,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.GetTransactions(childComplexity, args["where"].(model.FilterTransaction)), true + return e.complexity.Query.GetTransactions(childComplexity, args["where"].(model.FilterTransaction), args["order"].(*model.TransactionOrder)), true case "Query.latestBlockHeight": if e.complexity.Query.LatestBlockHeight == nil { @@ -831,6 +832,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputAmountInput, ec.unmarshalInputBankMsgSendInput, ec.unmarshalInputBlockFilter, + ec.unmarshalInputBlockOrder, ec.unmarshalInputEventAttributeInput, ec.unmarshalInputEventInput, ec.unmarshalInputFilterBankMsgSend, @@ -879,6 +881,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputTransactionBankMessageInput, ec.unmarshalInputTransactionFilter, ec.unmarshalInputTransactionMessageInput, + ec.unmarshalInputTransactionOrder, ec.unmarshalInputTransactionVmMessageInput, ) first := true @@ -1146,6 +1149,9 @@ input BlockFilter { """ to_time: Time } +input BlockOrder { + height: Order! +} """ Defines a transaction within a block, its execution specifics and content. """ @@ -2632,6 +2638,10 @@ input NestedFilterUnknownEvent { """ value: FilterString } +enum Order { + ASC + DESC +} """ Root Query type to fetch data about Blocks and Transactions based on filters or retrieve the latest block height. """ @@ -2653,13 +2663,13 @@ type Query { Incomplete results due to errors return both the partial Blocks and the associated errors. """ - getBlocks(where: FilterBlock!): [Block!] + getBlocks(where: FilterBlock!, order: TransactionOrder): [Block!] """ EXPERIMENTAL: Retrieves a list of Transactions that match the given where criteria. If the result is incomplete due to errors, both partial results and errors are returned. """ - getTransactions(where: FilterTransaction!): [Transaction!] + getTransactions(where: FilterTransaction!, order: TransactionOrder): [Transaction!] } """ Subscriptions provide a way for clients to receive real-time updates about Transactions and Blocks based on specified filter criteria. @@ -2893,6 +2903,9 @@ input TransactionMessageInput { """ vm_param: TransactionVmMessageInput } +input TransactionOrder { + heightAndIndex: Order! +} """ ` + "`" + `TransactionResponse` + "`" + ` is the processing result of the transaction. It has ` + "`" + `log` + "`" + `, ` + "`" + `info` + "`" + `, ` + "`" + `error` + "`" + `, and ` + "`" + `data` + "`" + `. @@ -3077,6 +3090,11 @@ func (ec *executionContext) field_Query_getBlocks_args(ctx context.Context, rawA return nil, err } args["where"] = arg0 + arg1, err := ec.field_Query_getBlocks_argsOrder(ctx, rawArgs) + if err != nil { + return nil, err + } + args["order"] = arg1 return args, nil } func (ec *executionContext) field_Query_getBlocks_argsWhere( @@ -3101,6 +3119,28 @@ func (ec *executionContext) field_Query_getBlocks_argsWhere( return zeroVal, nil } +func (ec *executionContext) field_Query_getBlocks_argsOrder( + ctx context.Context, + rawArgs map[string]interface{}, +) (*model.TransactionOrder, error) { + // We won't call the directive if the argument is null. + // Set call_argument_directives_with_null to true to call directives + // even if the argument is null. + _, ok := rawArgs["order"] + if !ok { + var zeroVal *model.TransactionOrder + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("order")) + if tmp, ok := rawArgs["order"]; ok { + return ec.unmarshalOTransactionOrder2ᚖgithubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐTransactionOrder(ctx, tmp) + } + + var zeroVal *model.TransactionOrder + return zeroVal, nil +} + func (ec *executionContext) field_Query_getTransactions_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -3109,6 +3149,11 @@ func (ec *executionContext) field_Query_getTransactions_args(ctx context.Context return nil, err } args["where"] = arg0 + arg1, err := ec.field_Query_getTransactions_argsOrder(ctx, rawArgs) + if err != nil { + return nil, err + } + args["order"] = arg1 return args, nil } func (ec *executionContext) field_Query_getTransactions_argsWhere( @@ -3133,6 +3178,28 @@ func (ec *executionContext) field_Query_getTransactions_argsWhere( return zeroVal, nil } +func (ec *executionContext) field_Query_getTransactions_argsOrder( + ctx context.Context, + rawArgs map[string]interface{}, +) (*model.TransactionOrder, error) { + // We won't call the directive if the argument is null. + // Set call_argument_directives_with_null to true to call directives + // even if the argument is null. + _, ok := rawArgs["order"] + if !ok { + var zeroVal *model.TransactionOrder + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("order")) + if tmp, ok := rawArgs["order"]; ok { + return ec.unmarshalOTransactionOrder2ᚖgithubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐTransactionOrder(ctx, tmp) + } + + var zeroVal *model.TransactionOrder + return zeroVal, nil +} + func (ec *executionContext) field_Query_transactions_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -6773,7 +6840,7 @@ func (ec *executionContext) _Query_getBlocks(ctx context.Context, field graphql. }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().GetBlocks(rctx, fc.Args["where"].(model.FilterBlock)) + return ec.resolvers.Query().GetBlocks(rctx, fc.Args["where"].(model.FilterBlock), fc.Args["order"].(*model.TransactionOrder)) }) if err != nil { ec.Error(ctx, err) @@ -6861,7 +6928,7 @@ func (ec *executionContext) _Query_getTransactions(ctx context.Context, field gr }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().GetTransactions(rctx, fc.Args["where"].(model.FilterTransaction)) + return ec.resolvers.Query().GetTransactions(rctx, fc.Args["where"].(model.FilterTransaction), fc.Args["order"].(*model.TransactionOrder)) }) if err != nil { ec.Error(ctx, err) @@ -10861,6 +10928,33 @@ func (ec *executionContext) unmarshalInputBlockFilter(ctx context.Context, obj i return it, nil } +func (ec *executionContext) unmarshalInputBlockOrder(ctx context.Context, obj interface{}) (model.BlockOrder, error) { + var it model.BlockOrder + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"height"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "height": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("height")) + data, err := ec.unmarshalNOrder2githubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐOrder(ctx, v) + if err != nil { + return it, err + } + it.Height = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputEventAttributeInput(ctx context.Context, obj interface{}) (model.EventAttributeInput, error) { var it model.EventAttributeInput asMap := map[string]interface{}{} @@ -13753,6 +13847,33 @@ func (ec *executionContext) unmarshalInputTransactionMessageInput(ctx context.Co return it, nil } +func (ec *executionContext) unmarshalInputTransactionOrder(ctx context.Context, obj interface{}) (model.TransactionOrder, error) { + var it model.TransactionOrder + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"heightAndIndex"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "heightAndIndex": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("heightAndIndex")) + data, err := ec.unmarshalNOrder2githubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐOrder(ctx, v) + if err != nil { + return it, err + } + it.HeightAndIndex = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputTransactionVmMessageInput(ctx context.Context, obj interface{}) (model.TransactionVMMessageInput, error) { var it model.TransactionVMMessageInput asMap := map[string]interface{}{} @@ -15469,6 +15590,16 @@ func (ec *executionContext) marshalNMessageValue2githubᚗcomᚋgnolangᚋtxᚑi return ec._MessageValue(ctx, sel, v) } +func (ec *executionContext) unmarshalNOrder2githubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐOrder(ctx context.Context, v interface{}) (model.Order, error) { + var res model.Order + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNOrder2githubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐOrder(ctx context.Context, sel ast.SelectionSet, v model.Order) graphql.Marshaler { + return v +} + func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) @@ -17430,6 +17561,14 @@ func (ec *executionContext) unmarshalOTransactionMessageInput2ᚕᚖgithubᚗcom return res, nil } +func (ec *executionContext) unmarshalOTransactionOrder2ᚖgithubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐTransactionOrder(ctx context.Context, v interface{}) (*model.TransactionOrder, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputTransactionOrder(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOTransactionVmMessageInput2ᚖgithubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐTransactionVMMessageInput(ctx context.Context, v interface{}) (*model.TransactionVMMessageInput, error) { if v == nil { return nil, nil diff --git a/serve/graph/model/models_gen.go b/serve/graph/model/models_gen.go index 66e24e1b..a47c87af 100644 --- a/serve/graph/model/models_gen.go +++ b/serve/graph/model/models_gen.go @@ -71,6 +71,10 @@ type BlockFilter struct { ToTime *time.Time `json:"to_time,omitempty"` } +type BlockOrder struct { + Height Order `json:"height"` +} + // Defines a transaction within a block, its execution specifics and content. type BlockTransaction struct { // Hash computes the TMHASH hash of the wire encoded transaction. @@ -965,6 +969,10 @@ type TransactionMessageInput struct { VMParam *TransactionVMMessageInput `json:"vm_param,omitempty"` } +type TransactionOrder struct { + HeightAndIndex Order `json:"heightAndIndex"` +} + // `TransactionVmMessageInput` represents input parameters required when the message router is `vm`. type TransactionVMMessageInput struct { // `MsgCallInput` represents input parameters required when the message type is `exec`. @@ -1137,3 +1145,44 @@ func (e *MessageType) UnmarshalGQL(v interface{}) error { func (e MessageType) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } + +type Order string + +const ( + OrderAsc Order = "ASC" + OrderDesc Order = "DESC" +) + +var AllOrder = []Order{ + OrderAsc, + OrderDesc, +} + +func (e Order) IsValid() bool { + switch e { + case OrderAsc, OrderDesc: + return true + } + return false +} + +func (e Order) String() string { + return string(e) +} + +func (e *Order) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = Order(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid Order", str) + } + return nil +} + +func (e Order) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/serve/graph/schema/schema.graphql b/serve/graph/schema/schema.graphql index 2b8bc3ff..554757f4 100644 --- a/serve/graph/schema/schema.graphql +++ b/serve/graph/schema/schema.graphql @@ -7,3 +7,11 @@ schema { Field representing a point on time. It is following the RFC3339Nano format ("2006-01-02T15:04:05.999999999Z07:00") """ scalar Time + +""" +Order defines the output order for hte method, It can be in DESC (descending) or ASC (ascending) order. +""" +enum Order { + ASC + DESC +} \ No newline at end of file diff --git a/serve/graph/schema/types/block.graphql b/serve/graph/schema/types/block.graphql index 41b12fa4..e087524f 100644 --- a/serve/graph/schema/types/block.graphql +++ b/serve/graph/schema/types/block.graphql @@ -121,3 +121,7 @@ type BlockTransaction { """ content_raw: String! } + +input BlockOrder { + height: Order! +} \ No newline at end of file diff --git a/serve/graph/schema/types/transaction.graphql b/serve/graph/schema/types/transaction.graphql index ad31ac07..42478d78 100644 --- a/serve/graph/schema/types/transaction.graphql +++ b/serve/graph/schema/types/transaction.graphql @@ -384,3 +384,7 @@ type UnknownEvent { """ value: String! @filterable } + +input TransactionOrder { + heightAndIndex: Order! +} \ No newline at end of file diff --git a/storage/pebble.go b/storage/pebble.go index 3ae7fe83..8d0df753 100644 --- a/storage/pebble.go +++ b/storage/pebble.go @@ -153,7 +153,38 @@ func (s *Pebble) GetTxByHash(txHash string) (*types.TxResult, error) { return decodeTx(tx) } +func (s *Pebble) loadBlockIterator(fromBlockNum, toBlockNum uint64) (*pebble.Iterator, *pebble.Snapshot, error) { + fromKey := keyBlock(fromBlockNum) + + if toBlockNum == 0 { + toBlockNum = math.MaxInt64 + } + + toKey := keyBlock(toBlockNum) + + snap := s.db.NewSnapshot() + + it, err := snap.NewIter(&pebble.IterOptions{ + LowerBound: fromKey, + UpperBound: toKey, + }) + if err != nil { + return nil, nil, multierr.Append(snap.Close(), err) + } + + return it, snap, nil +} + func (s *Pebble) BlockIterator(fromBlockNum, toBlockNum uint64) (Iterator[*types.Block], error) { + it, snap, err := s.loadBlockIterator(fromBlockNum, toBlockNum) + if err != nil { + return nil, err + } + + return &PebbleBlockIter{pebbleBaseBlockIter: pebbleBaseBlockIter{i: it, s: snap}}, nil +} + +func (s *Pebble) BlockReverseIterator(fromBlockNum, toBlockNum uint64) (Iterator[*types.Block], error) { fromKey := keyBlock(fromBlockNum) if toBlockNum == 0 { @@ -172,15 +203,15 @@ func (s *Pebble) BlockIterator(fromBlockNum, toBlockNum uint64) (Iterator[*types return nil, multierr.Append(snap.Close(), err) } - return &PebbleBlockIter{i: it, s: snap}, nil + return &PebbleReverseBlockIter{pebbleBaseBlockIter: pebbleBaseBlockIter{i: it, s: snap}}, nil } -func (s *Pebble) TxIterator( +func (s *Pebble) loadTxIterator( fromBlockNum, toBlockNum uint64, fromTxIndex, toTxIndex uint32, -) (Iterator[*types.TxResult], error) { +) (*pebble.Iterator, *pebble.Snapshot, error) { fromKey := keyTx(fromBlockNum, fromTxIndex) if toBlockNum == 0 { @@ -200,10 +231,37 @@ func (s *Pebble) TxIterator( UpperBound: toKey, }) if err != nil { - return nil, multierr.Append(snap.Close(), err) + return nil, nil, multierr.Append(snap.Close(), err) + } + return it, snap, nil +} + +func (s *Pebble) TxIterator( + fromBlockNum, + toBlockNum uint64, + fromTxIndex, + toTxIndex uint32, +) (Iterator[*types.TxResult], error) { + it, snap, err := s.loadTxIterator(fromBlockNum, toBlockNum, fromTxIndex, toTxIndex) + if err != nil { + return nil, err + } + + return &PebbleTxIter{pebbleBaseTxIter: pebbleBaseTxIter{i: it, s: snap, fromIndex: fromTxIndex, toIndex: toTxIndex}}, nil +} + +func (s *Pebble) TxReverseIterator( + fromBlockNum, + toBlockNum uint64, + fromTxIndex, + toTxIndex uint32, +) (Iterator[*types.TxResult], error) { + it, snap, err := s.loadTxIterator(fromBlockNum, toBlockNum, fromTxIndex, toTxIndex) + if err != nil { + return nil, err } - return &PebbleTxIter{i: it, s: snap, fromIndex: fromTxIndex, toIndex: toTxIndex}, nil + return &PebbleReverseTxIter{pebbleBaseTxIter: pebbleBaseTxIter{i: it, s: snap, fromIndex: fromTxIndex, toIndex: toTxIndex}}, nil } func (s *Pebble) WriteBatch() Batch { @@ -216,15 +274,31 @@ func (s *Pebble) Close() error { return s.db.Close() } -var _ Iterator[*types.Block] = &PebbleBlockIter{} - -type PebbleBlockIter struct { +type pebbleBaseBlockIter struct { i *pebble.Iterator s *pebble.Snapshot init bool } +func (pi *pebbleBaseBlockIter) Error() error { + return pi.i.Error() +} + +func (pi *pebbleBaseBlockIter) Value() (*types.Block, error) { + return decodeBlock(pi.i.Value()) +} + +func (pi *pebbleBaseBlockIter) Close() error { + return multierr.Append(pi.i.Close(), pi.s.Close()) +} + +var _ Iterator[*types.Block] = &PebbleBlockIter{} + +type PebbleBlockIter struct { + pebbleBaseBlockIter +} + func (pi *PebbleBlockIter) Next() bool { if !pi.init { pi.init = true @@ -235,21 +309,23 @@ func (pi *PebbleBlockIter) Next() bool { return pi.i.Valid() && pi.i.Next() } -func (pi *PebbleBlockIter) Error() error { - return pi.i.Error() -} +var _ Iterator[*types.Block] = &PebbleReverseBlockIter{} -func (pi *PebbleBlockIter) Value() (*types.Block, error) { - return decodeBlock(pi.i.Value()) +type PebbleReverseBlockIter struct { + pebbleBaseBlockIter } -func (pi *PebbleBlockIter) Close() error { - return multierr.Append(pi.i.Close(), pi.s.Close()) -} +func (pi *PebbleReverseBlockIter) Next() bool { + if !pi.init { + pi.init = true -var _ Iterator[*types.TxResult] = &PebbleTxIter{} + return pi.i.Last() + } -type PebbleTxIter struct { + return pi.i.Valid() && pi.i.Prev() +} + +type pebbleBaseTxIter struct { nextError error i *pebble.Iterator s *pebble.Snapshot @@ -258,6 +334,28 @@ type PebbleTxIter struct { init bool } +func (pi *pebbleBaseTxIter) Error() error { + if pi.nextError != nil { + return pi.nextError + } + + return pi.i.Error() +} + +func (pi *pebbleBaseTxIter) Value() (*types.TxResult, error) { + return decodeTx(pi.i.Value()) +} + +func (pi *pebbleBaseTxIter) Close() error { + return multierr.Append(pi.i.Close(), pi.s.Close()) +} + +var _ Iterator[*types.TxResult] = &PebbleTxIter{} + +type PebbleTxIter struct { + pebbleBaseTxIter +} + func (pi *PebbleTxIter) Next() bool { for { if !pi.init { @@ -299,20 +397,52 @@ func (pi *PebbleTxIter) Next() bool { } } -func (pi *PebbleTxIter) Error() error { - if pi.nextError != nil { - return pi.nextError - } +var _ Iterator[*types.TxResult] = &PebbleReverseTxIter{} - return pi.i.Error() +type PebbleReverseTxIter struct { + pebbleBaseTxIter } -func (pi *PebbleTxIter) Value() (*types.TxResult, error) { - return decodeTx(pi.i.Value()) -} +func (pi *PebbleReverseTxIter) Next() bool { + for { + if !pi.init { + if !pi.i.Last() { + return false + } -func (pi *PebbleTxIter) Close() error { - return multierr.Append(pi.i.Close(), pi.s.Close()) + pi.init = true + } else if !pi.i.Prev() { + return false + } + + var buf []byte + + key, _, err := decodeUnsafeStringAscending(pi.i.Key(), buf) + if err != nil { + pi.nextError = err + + return false + } + + key, _, err = decodeUint64Ascending(key) + if err != nil { + pi.nextError = err + + return false + } + + _, txIdx, err := decodeUint32Ascending(key) + if err != nil { + pi.nextError = err + + return false + } + + // TODO check if this is correct + if txIdx >= pi.fromIndex && txIdx < pi.toIndex { + return true + } + } } var _ Batch = &PebbleBatch{} diff --git a/storage/types.go b/storage/types.go index 3fff1241..1c98e224 100644 --- a/storage/types.go +++ b/storage/types.go @@ -31,9 +31,16 @@ type Reader interface { // BlockIterator iterates over Blocks, limiting the results to be between the provided block numbers BlockIterator(fromBlockNum, toBlockNum uint64) (Iterator[*types.Block], error) + // BlockReverseIterator iterates over Blocks in reverse order, limiting the results to be between the provided block numbers + BlockReverseIterator(fromBlockNum, toBlockNum uint64) (Iterator[*types.Block], error) + // TxIterator iterates over transactions, limiting the results to be between the provided block numbers // and transaction indexes TxIterator(fromBlockNum, toBlockNum uint64, fromTxIndex, toTxIndex uint32) (Iterator[*types.TxResult], error) + + // TxReverseIterator iterates over transactions in reverse order, limiting the results to be between the provided block numbers + // and transaction indexes + TxReverseIterator(fromBlockNum, toBlockNum uint64, fromTxIndex, toTxIndex uint32) (Iterator[*types.TxResult], error) } type Iterator[T any] interface { From e28c51722ecdd267542ef3d6c3408adf774ad0b6 Mon Sep 17 00:00:00 2001 From: Antonio Navarro Date: Tue, 22 Oct 2024 11:14:08 -0500 Subject: [PATCH 2/7] fix spacing Signed-off-by: Antonio Navarro --- serve/graph/schema/schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serve/graph/schema/schema.graphql b/serve/graph/schema/schema.graphql index 554757f4..ed7ecf30 100644 --- a/serve/graph/schema/schema.graphql +++ b/serve/graph/schema/schema.graphql @@ -12,6 +12,6 @@ scalar Time Order defines the output order for hte method, It can be in DESC (descending) or ASC (ascending) order. """ enum Order { - ASC + ASC DESC } \ No newline at end of file From 65ce1e4196c319dbb8e295b4f7cfb956937672f3 Mon Sep 17 00:00:00 2001 From: Antonio Navarro Date: Tue, 22 Oct 2024 11:33:27 -0500 Subject: [PATCH 3/7] Linter Signed-off-by: Antonio Navarro --- .github/golangci.yaml | 1 - serve/graph/all.resolvers.go | 6 ++++-- storage/pebble.go | 19 +++++++++++++++++-- storage/types.go | 7 ++++--- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/.github/golangci.yaml b/.github/golangci.yaml index a1661657..1c19cdff 100644 --- a/.github/golangci.yaml +++ b/.github/golangci.yaml @@ -51,7 +51,6 @@ linters: - tagliatelle # Checks struct tags - tenv # Detects using os.Setenv instead of t.Setenv - testableexamples # Checks if examples are testable (have expected output) - - unparam # Finds unused params - usestdlibvars # Detects the possibility to use variables/constants from stdlib - wastedassign # Finds wasted assignment statements - loggercheck # Checks the odd number of key and value pairs for common logger libraries diff --git a/serve/graph/all.resolvers.go b/serve/graph/all.resolvers.go index 75ea69ea..75915021 100644 --- a/serve/graph/all.resolvers.go +++ b/serve/graph/all.resolvers.go @@ -346,5 +346,7 @@ func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } // Subscription returns SubscriptionResolver implementation. func (r *Resolver) Subscription() SubscriptionResolver { return &subscriptionResolver{r} } -type queryResolver struct{ *Resolver } -type subscriptionResolver struct{ *Resolver } +type ( + queryResolver struct{ *Resolver } + subscriptionResolver struct{ *Resolver } +) diff --git a/storage/pebble.go b/storage/pebble.go index 8d0df753..1190afbd 100644 --- a/storage/pebble.go +++ b/storage/pebble.go @@ -233,6 +233,7 @@ func (s *Pebble) loadTxIterator( if err != nil { return nil, nil, multierr.Append(snap.Close(), err) } + return it, snap, nil } @@ -247,7 +248,14 @@ func (s *Pebble) TxIterator( return nil, err } - return &PebbleTxIter{pebbleBaseTxIter: pebbleBaseTxIter{i: it, s: snap, fromIndex: fromTxIndex, toIndex: toTxIndex}}, nil + return &PebbleTxIter{ + pebbleBaseTxIter: pebbleBaseTxIter{ + i: it, + s: snap, + fromIndex: fromTxIndex, + toIndex: toTxIndex, + }, + }, nil } func (s *Pebble) TxReverseIterator( @@ -261,7 +269,14 @@ func (s *Pebble) TxReverseIterator( return nil, err } - return &PebbleReverseTxIter{pebbleBaseTxIter: pebbleBaseTxIter{i: it, s: snap, fromIndex: fromTxIndex, toIndex: toTxIndex}}, nil + return &PebbleReverseTxIter{ + pebbleBaseTxIter: pebbleBaseTxIter{ + i: it, + s: snap, + fromIndex: fromTxIndex, + toIndex: toTxIndex, + }, + }, nil } func (s *Pebble) WriteBatch() Batch { diff --git a/storage/types.go b/storage/types.go index 1c98e224..945522a2 100644 --- a/storage/types.go +++ b/storage/types.go @@ -31,15 +31,16 @@ type Reader interface { // BlockIterator iterates over Blocks, limiting the results to be between the provided block numbers BlockIterator(fromBlockNum, toBlockNum uint64) (Iterator[*types.Block], error) - // BlockReverseIterator iterates over Blocks in reverse order, limiting the results to be between the provided block numbers + // BlockReverseIterator iterates over Blocks in reverse order, + // limiting the results to be between the provided block numbers BlockReverseIterator(fromBlockNum, toBlockNum uint64) (Iterator[*types.Block], error) // TxIterator iterates over transactions, limiting the results to be between the provided block numbers // and transaction indexes TxIterator(fromBlockNum, toBlockNum uint64, fromTxIndex, toTxIndex uint32) (Iterator[*types.TxResult], error) - // TxReverseIterator iterates over transactions in reverse order, limiting the results to be between the provided block numbers - // and transaction indexes + // TxReverseIterator iterates over transactions in reverse order, + // limiting the results to be between the provided block numbers and transaction indexes TxReverseIterator(fromBlockNum, toBlockNum uint64, fromTxIndex, toTxIndex uint32) (Iterator[*types.TxResult], error) } From 752c1a8a37c9a1d14d9438ef33e8a44f359d3922 Mon Sep 17 00:00:00 2001 From: Antonio Navarro Date: Tue, 22 Oct 2024 11:36:31 -0500 Subject: [PATCH 4/7] Go generate Signed-off-by: Antonio Navarro --- serve/graph/all.resolvers.go | 9 +++------ serve/graph/generated.go | 6 ++++-- serve/graph/model/models_gen.go | 1 + 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/serve/graph/all.resolvers.go b/serve/graph/all.resolvers.go index 75915021..df055529 100644 --- a/serve/graph/all.resolvers.go +++ b/serve/graph/all.resolvers.go @@ -9,11 +9,10 @@ import ( "github.com/99designs/gqlgen/graphql" bfttypes "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/vektah/gqlparser/v2/gqlerror" - "github.com/gnolang/tx-indexer/serve/graph/model" "github.com/gnolang/tx-indexer/storage" "github.com/gnolang/tx-indexer/types" + "github.com/vektah/gqlparser/v2/gqlerror" ) // Transactions is the resolver for the transactions field. @@ -346,7 +345,5 @@ func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } // Subscription returns SubscriptionResolver implementation. func (r *Resolver) Subscription() SubscriptionResolver { return &subscriptionResolver{r} } -type ( - queryResolver struct{ *Resolver } - subscriptionResolver struct{ *Resolver } -) +type queryResolver struct{ *Resolver } +type subscriptionResolver struct{ *Resolver } diff --git a/serve/graph/generated.go b/serve/graph/generated.go index 6aa978d2..607ef1af 100644 --- a/serve/graph/generated.go +++ b/serve/graph/generated.go @@ -15,10 +15,9 @@ import ( "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/introspection" + "github.com/gnolang/tx-indexer/serve/graph/model" gqlparser "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" - - "github.com/gnolang/tx-indexer/serve/graph/model" ) // region ************************** generated!.gotpl ************************** @@ -2638,6 +2637,9 @@ input NestedFilterUnknownEvent { """ value: FilterString } +""" +Order defines the output order for hte method, It can be in DESC (descending) or ASC (ascending) order. +""" enum Order { ASC DESC diff --git a/serve/graph/model/models_gen.go b/serve/graph/model/models_gen.go index a47c87af..83623cc5 100644 --- a/serve/graph/model/models_gen.go +++ b/serve/graph/model/models_gen.go @@ -1146,6 +1146,7 @@ func (e MessageType) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } +// Order defines the output order for hte method, It can be in DESC (descending) or ASC (ascending) order. type Order string const ( From dd0faa8632231fbc6f10a4395eecd3b8545e9ba7 Mon Sep 17 00:00:00 2001 From: Antonio Navarro Date: Tue, 22 Oct 2024 13:25:27 -0500 Subject: [PATCH 5/7] go generate Signed-off-by: Antonio Navarro --- serve/graph/all.resolvers.go | 4 ++-- serve/graph/gen/generate.go | 2 +- serve/graph/generated.go | 26 +++++++++++++++++--------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/serve/graph/all.resolvers.go b/serve/graph/all.resolvers.go index df055529..dbfdb54f 100644 --- a/serve/graph/all.resolvers.go +++ b/serve/graph/all.resolvers.go @@ -126,7 +126,7 @@ func (r *queryResolver) LatestBlockHeight(ctx context.Context) (int, error) { } // GetBlocks is the resolver for the getBlocks field. -func (r *queryResolver) GetBlocks(ctx context.Context, where model.FilterBlock, order *model.TransactionOrder) ([]*model.Block, error) { +func (r *queryResolver) GetBlocks(ctx context.Context, where model.FilterBlock, order *model.BlockOrder) ([]*model.Block, error) { fromh, toh := where.MinMaxHeight() dfromh := uint64(deref(fromh)) dtoh := uint64(deref(toh)) @@ -138,7 +138,7 @@ func (r *queryResolver) GetBlocks(ctx context.Context, where model.FilterBlock, var err error var it storage.Iterator[*bfttypes.Block] - if order != nil && order.HeightAndIndex == model.OrderDesc { + if order != nil && order.Height == model.OrderDesc { it, err = r. store. BlockReverseIterator( diff --git a/serve/graph/gen/generate.go b/serve/graph/gen/generate.go index 6b2cfe41..b77da12a 100644 --- a/serve/graph/gen/generate.go +++ b/serve/graph/gen/generate.go @@ -18,7 +18,7 @@ type Query { Incomplete results due to errors return both the partial Blocks and the associated errors. """ - getBlocks(where: FilterBlock!, order: TransactionOrder): [Block!] + getBlocks(where: FilterBlock!, order: BlockOrder): [Block!] """ EXPERIMENTAL: Retrieves a list of Transactions that match the given diff --git a/serve/graph/generated.go b/serve/graph/generated.go index 607ef1af..28714050 100644 --- a/serve/graph/generated.go +++ b/serve/graph/generated.go @@ -132,7 +132,7 @@ type ComplexityRoot struct { Query struct { Blocks func(childComplexity int, filter model.BlockFilter) int - GetBlocks func(childComplexity int, where model.FilterBlock, order *model.TransactionOrder) int + GetBlocks func(childComplexity int, where model.FilterBlock, order *model.BlockOrder) int GetTransactions func(childComplexity int, where model.FilterTransaction, order *model.TransactionOrder) int LatestBlockHeight func(childComplexity int) int Transactions func(childComplexity int, filter model.TransactionFilter) int @@ -191,7 +191,7 @@ type QueryResolver interface { Transactions(ctx context.Context, filter model.TransactionFilter) ([]*model.Transaction, error) Blocks(ctx context.Context, filter model.BlockFilter) ([]*model.Block, error) LatestBlockHeight(ctx context.Context) (int, error) - GetBlocks(ctx context.Context, where model.FilterBlock, order *model.TransactionOrder) ([]*model.Block, error) + GetBlocks(ctx context.Context, where model.FilterBlock, order *model.BlockOrder) ([]*model.Block, error) GetTransactions(ctx context.Context, where model.FilterTransaction, order *model.TransactionOrder) ([]*model.Transaction, error) } type SubscriptionResolver interface { @@ -578,7 +578,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.GetBlocks(childComplexity, args["where"].(model.FilterBlock), args["order"].(*model.TransactionOrder)), true + return e.complexity.Query.GetBlocks(childComplexity, args["where"].(model.FilterBlock), args["order"].(*model.BlockOrder)), true case "Query.getTransactions": if e.complexity.Query.GetTransactions == nil { @@ -2665,7 +2665,7 @@ type Query { Incomplete results due to errors return both the partial Blocks and the associated errors. """ - getBlocks(where: FilterBlock!, order: TransactionOrder): [Block!] + getBlocks(where: FilterBlock!, order: BlockOrder): [Block!] """ EXPERIMENTAL: Retrieves a list of Transactions that match the given where criteria. If the result is incomplete due to errors, both partial @@ -3124,22 +3124,22 @@ func (ec *executionContext) field_Query_getBlocks_argsWhere( func (ec *executionContext) field_Query_getBlocks_argsOrder( ctx context.Context, rawArgs map[string]interface{}, -) (*model.TransactionOrder, error) { +) (*model.BlockOrder, error) { // We won't call the directive if the argument is null. // Set call_argument_directives_with_null to true to call directives // even if the argument is null. _, ok := rawArgs["order"] if !ok { - var zeroVal *model.TransactionOrder + var zeroVal *model.BlockOrder return zeroVal, nil } ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("order")) if tmp, ok := rawArgs["order"]; ok { - return ec.unmarshalOTransactionOrder2ᚖgithubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐTransactionOrder(ctx, tmp) + return ec.unmarshalOBlockOrder2ᚖgithubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐBlockOrder(ctx, tmp) } - var zeroVal *model.TransactionOrder + var zeroVal *model.BlockOrder return zeroVal, nil } @@ -6842,7 +6842,7 @@ func (ec *executionContext) _Query_getBlocks(ctx context.Context, field graphql. }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().GetBlocks(rctx, fc.Args["where"].(model.FilterBlock), fc.Args["order"].(*model.TransactionOrder)) + return ec.resolvers.Query().GetBlocks(rctx, fc.Args["where"].(model.FilterBlock), fc.Args["order"].(*model.BlockOrder)) }) if err != nil { ec.Error(ctx, err) @@ -16030,6 +16030,14 @@ func (ec *executionContext) marshalOBlock2ᚕᚖgithubᚗcomᚋgnolangᚋtxᚑin return ret } +func (ec *executionContext) unmarshalOBlockOrder2ᚖgithubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐBlockOrder(ctx context.Context, v interface{}) (*model.BlockOrder, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputBlockOrder(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalOBlockTransaction2ᚖgithubᚗcomᚋgnolangᚋtxᚑindexerᚋserveᚋgraphᚋmodelᚐBlockTransaction(ctx context.Context, sel ast.SelectionSet, v *model.BlockTransaction) graphql.Marshaler { if v == nil { return graphql.Null From 20e667e88886dd6e6912a45be8bd49ef2a195feb Mon Sep 17 00:00:00 2001 From: Antonio Navarro Perez Date: Thu, 28 Nov 2024 11:25:38 +0100 Subject: [PATCH 6/7] Remove TODO comment Signed-off-by: Antonio Navarro Perez --- storage/pebble.go | 1 - 1 file changed, 1 deletion(-) diff --git a/storage/pebble.go b/storage/pebble.go index 1190afbd..122677f9 100644 --- a/storage/pebble.go +++ b/storage/pebble.go @@ -453,7 +453,6 @@ func (pi *PebbleReverseTxIter) Next() bool { return false } - // TODO check if this is correct if txIdx >= pi.fromIndex && txIdx < pi.toIndex { return true } From 8bed8de9d0792f98dd67b4c56d86c187bf5a4d17 Mon Sep 17 00:00:00 2001 From: Antonio Navarro Perez Date: Thu, 28 Nov 2024 11:52:38 +0100 Subject: [PATCH 7/7] Keep removed linter Signed-off-by: Antonio Navarro Perez --- .github/golangci.yaml | 1 + storage/encode.go | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.github/golangci.yaml b/.github/golangci.yaml index 1c19cdff..a1661657 100644 --- a/.github/golangci.yaml +++ b/.github/golangci.yaml @@ -51,6 +51,7 @@ linters: - tagliatelle # Checks struct tags - tenv # Detects using os.Setenv instead of t.Setenv - testableexamples # Checks if examples are testable (have expected output) + - unparam # Finds unused params - usestdlibvars # Detects the possibility to use variables/constants from stdlib - wastedassign # Finds wasted assignment statements - loggercheck # Checks the odd number of key and value pairs for common logger libraries diff --git a/storage/encode.go b/storage/encode.go index 9de6c241..5adf47b5 100644 --- a/storage/encode.go +++ b/storage/encode.go @@ -42,6 +42,8 @@ func encodeUint32Ascending(b []byte, v uint32) []byte { // decodeUint32Ascending decodes a uint32 from the input buffer, treating // the input as a big-endian 4 byte uint32 representation. The remainder // of the input buffer and the decoded uint32 are returned. +// +//nolint:unparam // We want to keep all returning params func decodeUint32Ascending(b []byte) ([]byte, uint32, error) { if len(b) < 4 { return nil, 0, fmt.Errorf("insufficient bytes to decode uint32 int value") @@ -156,6 +158,8 @@ func unsafeConvertStringToBytes(s string) []byte { // temporary buffer in order to avoid memory allocations. The remainder of the // input buffer and the decoded string are returned. Note that the returned // string may share storage with the input buffer. +// +//nolint:unparam // We want to keep all returning params func decodeUnsafeStringAscending(b, r []byte) ([]byte, string, error) { b, r, err := decodeBytesAscending(b, r)