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 }