code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/blocks.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package sqlstore
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/datanode/entities"
    26  	"code.vegaprotocol.io/vega/datanode/metrics"
    27  
    28  	"github.com/georgysavva/scany/pgxscan"
    29  	"github.com/jackc/pgx/v4"
    30  )
    31  
    32  var (
    33  	ErrBlockWaitTimedout = errors.New("Timed out waiting for TimeUpdate event")
    34  	BlockWaitTimeout     = 5 * time.Second
    35  )
    36  
    37  type Blocks struct {
    38  	*ConnectionSource
    39  	lastBlock *entities.Block
    40  	mu        sync.Mutex
    41  }
    42  
    43  func NewBlocks(connectionSource *ConnectionSource) *Blocks {
    44  	b := &Blocks{
    45  		ConnectionSource: connectionSource,
    46  	}
    47  	return b
    48  }
    49  
    50  func (bs *Blocks) Add(ctx context.Context, b entities.Block) error {
    51  	defer metrics.StartSQLQuery("Blocks", "Add")()
    52  
    53  	_, err := bs.Exec(ctx,
    54  		`insert into blocks(vega_time, height, hash) values ($1, $2, $3)`,
    55  		b.VegaTime, b.Height, b.Hash)
    56  	if err != nil {
    57  		return fmt.Errorf("adding block: %w", err)
    58  	}
    59  
    60  	bs.setLastBlock(b)
    61  	return nil
    62  }
    63  
    64  func (bs *Blocks) GetAll(ctx context.Context) ([]entities.Block, error) {
    65  	defer metrics.StartSQLQuery("Blocks", "GetAll")()
    66  	blocks := []entities.Block{}
    67  	err := pgxscan.Select(ctx, bs.ConnectionSource, &blocks,
    68  		`SELECT vega_time, height, hash
    69  		FROM blocks
    70  		ORDER BY vega_time desc`)
    71  	return blocks, err
    72  }
    73  
    74  func (bs *Blocks) GetAtHeight(ctx context.Context, height int64) (entities.Block, error) {
    75  	connection := bs.ConnectionSource
    76  	defer metrics.StartSQLQuery("Blocks", "GetAtHeight")()
    77  
    78  	// Check if it's in our cache first
    79  	block, err := bs.GetLastBlock(ctx)
    80  	if err == nil && block.Height == height {
    81  		return block, nil
    82  	}
    83  
    84  	return GetAtHeightUsingConnection(ctx, connection, height)
    85  }
    86  
    87  func GetAtHeightUsingConnection(ctx context.Context, connection Connection, height int64) (entities.Block, error) {
    88  	block := entities.Block{}
    89  
    90  	return block, wrapE(pgxscan.Get(ctx, connection, &block,
    91  		`SELECT vega_time, height, hash
    92  		FROM blocks
    93  		WHERE height=$1`, height))
    94  }
    95  
    96  // GetLastBlock return the last block or ErrNoLastBlock if no block is found.
    97  func (bs *Blocks) GetLastBlock(ctx context.Context) (entities.Block, error) {
    98  	bs.mu.Lock()
    99  	defer bs.mu.Unlock()
   100  	if bs.lastBlock != nil {
   101  		return *bs.lastBlock, nil
   102  	}
   103  	defer metrics.StartSQLQuery("Blocks", "GetLastBlock")()
   104  
   105  	lastBlock, err := bs.getLastBlockUsingConnection(ctx, bs.ConnectionSource)
   106  	// FIXME(woot?): why do we set that before checking for error, that would clearly fuckup the cache or something innit?
   107  	bs.lastBlock = lastBlock
   108  	if err != nil {
   109  		return entities.Block{}, err
   110  	}
   111  
   112  	return *lastBlock, err
   113  }
   114  
   115  func (bs *Blocks) setLastBlock(b entities.Block) {
   116  	bs.mu.Lock()
   117  	defer bs.mu.Unlock()
   118  	bs.lastBlock = &b
   119  }
   120  
   121  func (bs *Blocks) GetOldestHistoryBlock(ctx context.Context) (entities.Block, error) {
   122  	defer metrics.StartSQLQuery("Blocks", "GetOldestHistoryBlock")()
   123  
   124  	return bs.getOldestHistoryBlockUsingConnection(ctx, bs.ConnectionSource)
   125  }
   126  
   127  func (bs *Blocks) getOldestHistoryBlockUsingConnection(ctx context.Context, connection Connection) (entities.Block, error) {
   128  	block := &entities.Block{}
   129  	if err := pgxscan.Get(ctx, connection, block, `SELECT vega_time, height, hash
   130  		FROM blocks order by height asc limit 1`); err != nil {
   131  		return entities.Block{}, bs.wrapE(err)
   132  	}
   133  
   134  	return *block, nil
   135  }
   136  
   137  func GetOldestHistoryBlockUsingConnection(ctx context.Context, connection Connection) (entities.Block, error) {
   138  	block := &entities.Block{}
   139  	err := pgxscan.Get(ctx, connection, block, `SELECT vega_time, height, hash
   140  		FROM blocks order by height asc limit 1`)
   141  
   142  	if errors.Is(err, pgx.ErrNoRows) {
   143  		return entities.Block{}, entities.ErrNotFound
   144  	}
   145  
   146  	return *block, nil
   147  }
   148  
   149  func (bs *Blocks) getLastBlockUsingConnection(ctx context.Context, connection Connection) (*entities.Block, error) {
   150  	block := &entities.Block{}
   151  	if err := pgxscan.Get(ctx, connection, block,
   152  		`SELECT vega_time, height, hash
   153  		FROM last_block`); err != nil {
   154  		return nil, bs.wrapE(err)
   155  	}
   156  
   157  	return block, nil
   158  }
   159  
   160  func GetLastBlockUsingConnection(ctx context.Context, connection Connection) (*entities.Block, error) {
   161  	block := &entities.Block{}
   162  	err := pgxscan.Get(ctx, connection, block,
   163  		`SELECT vega_time, height, hash
   164  		FROM last_block`)
   165  
   166  	if errors.Is(err, pgx.ErrNoRows) {
   167  		return nil, entities.ErrNotFound
   168  	}
   169  
   170  	return block, err
   171  }