Skip to content

Commit

Permalink
Merge branch 'develop' into 10238-fix-no-state-logic-for-spam-request
Browse files Browse the repository at this point in the history
  • Loading branch information
peterbarrow authored Dec 11, 2023
2 parents 069e2fd + 0d52f1a commit 8381b0a
Show file tree
Hide file tree
Showing 59 changed files with 6,015 additions and 3,002 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### 🚨 Breaking changes

- [9945](https://github.com/vegaprotocol/vega/issues/9945) - Add liquidation strategy.
- [10215](https://github.com/vegaprotocol/vega/issues/10215) - Listing transactions on block explorer does not support the field `limit` any more.

### 🗑️ Deprecation

Expand All @@ -22,14 +23,18 @@
- [9516](https://github.com/vegaprotocol/vega/issues/9516) - Add filter by transfer ID for ledger entries API.
- [9943](https://github.com/vegaprotocol/vega/issues/9943) - Support amending the order size by defining the target size.
- [9231](https://github.com/vegaprotocol/vega/issues/9231) - Add a `JoinTeam API`
- [10222](https://github.com/vegaprotocol/vega/issues/10222) - Supply bootstrap peers after starting the `IPFS` node to increase reliability.
- [10097](https://github.com/vegaprotocol/vega/issues/10097) - Add funding rate modifiers to perpetual product definition.
- [9981](https://github.com/vegaprotocol/vega/issues/9981) - Support filtering on epoch range on transfers.
- [9981](https://github.com/vegaprotocol/vega/issues/9981) - Support filtering on status on transfers.
- [10104](https://github.com/vegaprotocol/vega/issues/10104) - Add network position tracking.
- [9981](https://github.com/vegaprotocol/vega/issues/9981) - Support filtering on scope on transfers.
- [9983](https://github.com/vegaprotocol/vega/issues/9983) - Implement cap and discount for transfer fees.
- [9980](https://github.com/vegaprotocol/vega/issues/9980) - Add teams statistics API.
- [9257](https://github.com/vegaprotocol/vega/issues/9257) - Add games details API
- [9260](https://github.com/vegaprotocol/vega/issues/9260) - Enhance rewards API for competitions
- [10180](https://github.com/vegaprotocol/vega/issues/10180) - Additional candle intervals
- [10218](https://github.com/vegaprotocol/vega/issues/10218) - Volume discount stats shows volumes even if party doesn't qualify for a discount tier.

### 🐛 Fixes

Expand Down Expand Up @@ -65,6 +70,10 @@
- [10211](https://github.com/vegaprotocol/vega/issues/10211) - Ensure infra fees don't get counted for vesting.
- [10217](https://github.com/vegaprotocol/vega/issues/10217) - Game ID for reward entity should be optional
- [10238](https://github.com/vegaprotocol/vega/issues/10238) - Fix logic when a user firsts requests spam information
- [10227](https://github.com/vegaprotocol/vega/issues/10227) - Make the wallet errors on spam stats meaningful.
- [10193](https://github.com/vegaprotocol/vega/issues/10193) - Denormalize `tx_results` to avoid joins with blocks when queried.
- [10233](https://github.com/vegaprotocol/vega/issues/10233) - Fix expiring stop orders panic.
- [10215](https://github.com/vegaprotocol/vega/issues/10215) - Rework pagination to align with the natural reverse-chronological order of the block explorer.

## 0.73.0

Expand Down
56 changes: 36 additions & 20 deletions blockexplorer/api/grpc/implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,28 +81,12 @@ func (b *blockExplorerAPI) ListTransactions(ctx context.Context, req *pb.ListTra
var before, after *entities.TxCursor
var first, last uint32

if req.First > 0 && req.Last > 0 {
return nil, apiError(codes.InvalidArgument, errors.New("cannot specify both first and last"))
}

first = b.MaxPageSizeDefault
if req.First > 0 {
first = req.First
if req.After == nil && req.Before != nil {
return nil, apiError(codes.InvalidArgument, errors.New("cannot specify before when using first"))
}
if req.Before != nil && req.After != nil && (req.First > 0 || req.Last > 0) {
return nil, apiError(codes.InvalidArgument, errors.New("cannot use neither limits `first`, nor `last` when both cursors `before` and `after` are set"))
}

if req.Last > 0 {
last = req.Last
if req.Before == nil && req.After != nil {
return nil, apiError(codes.InvalidArgument, errors.New("cannot specify after when using last"))
}
}

// Temporary for now, until we have fully deprecated the limit field in the request.
if req.Limit > 0 && req.First == 0 && req.Last == 0 {
first = req.Limit
if req.First > 0 && req.Last > 0 {
return nil, apiError(codes.InvalidArgument, errors.New("cannot use both limits `first` and `last` within the same query"))
}

if req.Before != nil {
Expand All @@ -111,6 +95,7 @@ func (b *blockExplorerAPI) ListTransactions(ctx context.Context, req *pb.ListTra
return nil, apiError(codes.InvalidArgument, err)
}
before = &cursor
last = b.MaxPageSizeDefault
}

if req.After != nil {
Expand All @@ -119,6 +104,37 @@ func (b *blockExplorerAPI) ListTransactions(ctx context.Context, req *pb.ListTra
return nil, apiError(codes.InvalidArgument, err)
}
after = &cursor
first = b.MaxPageSizeDefault
}

if before != nil && after != nil {
// The order of the parameters may seem odd, but this is expected as we have
// to keep in mind the natural order of the block-explorer is reverse-chronological.
// so, given transactions 4.2, 4.1, 3.2, 3.1, 2.2, when applying the window between
// 3.1 and 4.2, then we have to set after to 3.1 and before to 4.2.
// So effectively, after is the start and before is the end of the set.
if entities.AreValidCursorBoundaries(after, before) {
return nil, apiError(codes.InvalidArgument, errors.New("cursors `before` and `after` do not create a valid window"))
}
}

if req.First > 0 {
if req.Before != nil {
return nil, apiError(codes.InvalidArgument, errors.New("cannot use cursor `before` when using limit `first`"))
}
first = req.First
} else if req.Last > 0 {
if req.After != nil {
return nil, apiError(codes.InvalidArgument, errors.New("cannot use cursor `after` when using limit `last`"))
}
last = req.Last
}

// Entering this condition means there is no pagination set, so it defaults
// to listing the MaxPageSizeDefault newest transactions.
// Note, setting limits on a cursor window is not supported.
if !(before != nil && after != nil) && first == 0 && last == 0 {
first = b.MaxPageSizeDefault
}

transactions, err := b.store.ListTransactions(ctx,
Expand Down
32 changes: 21 additions & 11 deletions blockexplorer/entities/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ import (
)

type TxResultRow struct {
RowID int64 `db:"rowid"`
BlockID int64 `db:"block_id"`
Index int64 `db:"index"`
CreatedAt time.Time `db:"created_at"`
TxHash string `db:"tx_hash"`
TxResult []byte `db:"tx_result"`
Submitter string `db:"submitter"`
CmdType string `db:"cmd_type"`
RowID int64 `db:"rowid"`
BlockHeight int64 `db:"block_height"`
Index int64 `db:"index"`
CreatedAt time.Time `db:"created_at"`
TxHash string `db:"tx_hash"`
TxResult []byte `db:"tx_result"`
Submitter string `db:"submitter"`
CmdType string `db:"cmd_type"`
}

func (t *TxResultRow) ToProto() (*pb.Transaction, error) {
Expand All @@ -65,7 +65,7 @@ func (t *TxResultRow) ToProto() (*pb.Transaction, error) {
}

return &pb.Transaction{
Block: uint64(t.BlockID),
Block: uint64(t.BlockHeight),
Index: uint32(t.Index),
Type: extractAttribute(&txResult, "command", "type"),
Submitter: extractAttribute(&txResult, "tx", "submitter"),
Expand All @@ -83,7 +83,7 @@ func (t *TxResultRow) ToProto() (*pb.Transaction, error) {

func (t *TxResultRow) Cursor() TxCursor {
return TxCursor{
BlockNumber: uint64(t.BlockID),
BlockNumber: uint64(t.BlockHeight),
TxIndex: uint32(t.Index),
}
}
Expand Down Expand Up @@ -123,11 +123,21 @@ func TxCursorFromString(s string) (TxCursor, error) {
}

return TxCursor{
BlockNumber: blockNumber, // increase by one again to make the behaviour consistent
BlockNumber: blockNumber,
TxIndex: uint32(txIndex),
}, nil
}

func (c *TxCursor) String() string {
return fmt.Sprintf("%d.%d", c.BlockNumber, c.TxIndex)
}

// AreValidCursorBoundaries checks if the start and end cursors creates valid
// set boundaries for cursors, as: [start, end].
func AreValidCursorBoundaries(start, end *TxCursor) bool {
if start.BlockNumber == end.BlockNumber {
return start.TxIndex < end.TxIndex
}

return start.BlockNumber < end.BlockNumber
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-- +goose Up

-- make the block height nullable so tendermint can still insert and the
-- trigger takes over to set the value.
ALTER TABLE tx_results
ADD COLUMN IF NOT EXISTS block_height BIGINT DEFAULT 0;

UPDATE tx_results
SET block_height=b.height
FROM blocks b
WHERE b.rowid = tx_results.block_id;

-- +goose StatementBegin
CREATE OR REPLACE FUNCTION add_block_height_to_tx_results()
RETURNS TRIGGER
LANGUAGE plpgsql AS
$$
BEGIN
UPDATE tx_results
SET block_height=b.height
FROM blocks b
WHERE b.rowid = NEW.block_id
AND tx_results.rowid = NEW.rowid;

RETURN NULL;
END;
$$;
-- +goose StatementEnd

CREATE TRIGGER add_block_height_to_tx_results
AFTER INSERT
ON tx_results
FOR EACH ROW
EXECUTE PROCEDURE add_block_height_to_tx_results();

-- +goose Down

DROP TRIGGER IF EXISTS add_block_height_to_tx_results ON tx_results;

ALTER TABLE tx_results
DROP COLUMN IF EXISTS block_height;

68 changes: 41 additions & 27 deletions blockexplorer/store/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ var (
func (s *Store) GetTransaction(ctx context.Context, txID string) (*pb.Transaction, error) {
txID = strings.ToUpper(txID)

query := `SELECT t.rowid, b.height as block_id, t.index, t.created_at, t.tx_hash, t.tx_result, t.cmd_type, t.submitter FROM tx_results t JOIN blocks b ON t.block_id = b.rowid WHERE t.tx_hash=$1`
query := `SELECT t.rowid, t.block_id, t.index, t.created_at, t.tx_hash, t.tx_result, t.cmd_type, t.submitter FROM tx_results t WHERE t.tx_hash=$1`
var rows []entities.TxResultRow

if err := pgxscan.Select(ctx, s.pool, &rows, query, txID); err != nil {
return nil, fmt.Errorf("querying tx_results:%w", err)
return nil, fmt.Errorf("querying tx_results: %w", err)
}

if len(rows) == 0 {
Expand All @@ -66,38 +66,48 @@ func (s *Store) ListTransactions(ctx context.Context,
last uint32,
before *entities.TxCursor,
) ([]*pb.Transaction, error) {
query := `SELECT t.rowid, b.height as block_id, t.index, t.created_at, t.tx_hash, t.tx_result, t.cmd_type, t.submitter FROM tx_results t JOIN blocks b ON t.block_id = b.rowid`
query := `SELECT t.rowid, t.block_height, t.index, t.created_at, t.tx_hash, t.tx_result, t.cmd_type, t.submitter FROM tx_results t`

args := []interface{}{}
predicates := []string{}

// by default we want the most recent transactions so we'll set the limit to first
// and sort order to desc
limit := first
limit := uint32(0)

sortOrder := "desc"

// if we have a before cursor we want the results ordered earliest to latest
// so the limit will be set to last and sort order to asc
if first > 0 {
// We want the N most recent transactions, descending on block height and block
// index: 4.1, 3.2, 3.1, 2.2...
// The resulting query should already sort the rows in the right order.
limit = first
sortOrder = "desc"
} else if last > 0 {
// We want the N oldest transactions, ascending on block height and block
// index: 1.1, 1.2, 2.1, 2.2...
// The resulting query should sort the rows in the chronological order. But
// that's necessary to apply the LIMIT clause. It will be sorted in the
// reverse chronological order later on.
limit = last
sortOrder = "asc"
}

if before != nil {
block := nextBindVar(&args, before.BlockNumber)
index := nextBindVar(&args, before.TxIndex)
predicate := fmt.Sprintf("(b.height, t.index) > (%s, %s)", block, index)
predicate := fmt.Sprintf("(t.block_height, t.index) < (%s, %s)", block, index)
predicates = append(predicates, predicate)
limit = last
sortOrder = "asc"
// We change the sorting order because we want the transactions right before
// the cursor, meaning older transactions.
sortOrder = "desc"
}

if after != nil {
block := nextBindVar(&args, after.BlockNumber)
index := nextBindVar(&args, after.TxIndex)
predicate := fmt.Sprintf("(b.height, t.index) < (%s, %s)", block, index)
predicate := fmt.Sprintf("(t.block_height, t.index) > (%s, %s)", block, index)
predicates = append(predicates, predicate)
}

// just in case we have no before cursor, but we want to have the last N transactions in the data set
// i.e. the earliest transactions, sorting ascending
if last > 0 && first == 0 && after == nil && before == nil {
limit = last
// We change the sorting order because we want the transactions right after
// the cursor, meaning newer transaction. That's necessary to apply the
// LIMIT clause.
sortOrder = "asc"
}

Expand All @@ -122,12 +132,12 @@ func (s *Store) ListTransactions(ctx context.Context,

if key == "tx.submitter" {
// tx.submitter is lifted out of attributes and into tx_results by a trigger for faster access
predicate = fmt.Sprintf("t.submitter=%s", nextBindVar(&args, value))
predicate = fmt.Sprintf("t.submitter= %s", nextBindVar(&args, value))
} else if key == "cmd.type" {
predicate = fmt.Sprintf("t.cmd_type=%s", nextBindVar(&args, value))
predicate = fmt.Sprintf("t.cmd_type= %s", nextBindVar(&args, value))
} else if key == "block.height" {
// much quicker to filter block height by joining to the block table than looking in attributes
predicate = fmt.Sprintf("b.height = %s", nextBindVar(&args, value))
predicate = fmt.Sprintf("t.block_height = %s", nextBindVar(&args, value))
} else {
predicate = fmt.Sprintf(`
EXISTS (SELECT 1 FROM events e JOIN attributes a ON e.rowid = a.event_id
Expand All @@ -142,12 +152,14 @@ func (s *Store) ListTransactions(ctx context.Context,
query = fmt.Sprintf("%s WHERE %s", query, strings.Join(predicates, " AND "))
}

query = fmt.Sprintf("%s ORDER BY t.block_id %s, t.index %s", query, sortOrder, sortOrder)
query = fmt.Sprintf("%s LIMIT %d", query, limit)
query = fmt.Sprintf("%s ORDER BY t.block_height %s, t.index %s", query, sortOrder, sortOrder)
if limit != 0 {
query = fmt.Sprintf("%s LIMIT %d", query, limit)
}

var rows []entities.TxResultRow
if err := pgxscan.Select(ctx, s.pool, &rows, query, args...); err != nil {
return nil, fmt.Errorf("querying tx_results:%w", err)
return nil, fmt.Errorf("querying tx_results: %w", err)
}

txs := make([]*pb.Transaction, 0, len(rows))
Expand All @@ -160,8 +172,10 @@ func (s *Store) ListTransactions(ctx context.Context,
txs = append(txs, tx)
}

// make sure the results are always order in the same direction, i.e. newest first, regardless of the order of the
// results from the database.
// Make sure the results are always order in the reverse chronological order,
// as required.
// This cannot be replaced by the `order by` in the request as it's used by the
// pagination system.
sort.Slice(txs, func(i, j int) bool {
if txs[i].Block == txs[j].Block {
return txs[i].Index > txs[j].Index
Expand Down
Loading

0 comments on commit 8381b0a

Please sign in to comment.