code.vegaprotocol.io/vega@v0.79.0/core/txcache/cache.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 txcache 17 18 import ( 19 "context" 20 "encoding/hex" 21 "fmt" 22 "sort" 23 "sync" 24 25 "code.vegaprotocol.io/vega/core/nodewallets" 26 "code.vegaprotocol.io/vega/core/txn" 27 "code.vegaprotocol.io/vega/core/types" 28 "code.vegaprotocol.io/vega/libs/num" 29 "code.vegaprotocol.io/vega/libs/proto" 30 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 31 snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1" 32 33 "github.com/cometbft/cometbft/crypto/tmhash" 34 ) 35 36 func NewTxCache(commander *nodewallets.Commander) *TxCache { 37 return &TxCache{ 38 commander: commander, 39 marketToDelayRequired: map[string]bool{}, 40 heightToTxs: map[uint64][][]byte{}, 41 cachedTxs: map[string]struct{}{}, 42 } 43 } 44 45 type TxCache struct { 46 commander *nodewallets.Commander 47 heightToTxs map[uint64][][]byte 48 // network param 49 numBlocksToDelay uint64 50 // no need to include is snapshot - is updated when markets are created/updated/loaded from snapshot 51 marketToDelayRequired map[string]bool 52 // map of transactions that have been picked up for delay 53 cachedTxs map[string]struct{} 54 lock sync.RWMutex 55 } 56 57 func (t *TxCache) IsTxInCache(txHash []byte) bool { 58 t.lock.RLock() 59 defer t.lock.RUnlock() 60 _, ok := t.cachedTxs[hex.EncodeToString(txHash)] 61 return ok 62 } 63 64 func (t *TxCache) removeHeightFromCache(height uint64) { 65 t.lock.Lock() 66 defer t.lock.Unlock() 67 if txs, ok := t.heightToTxs[height]; ok { 68 for _, tx := range txs { 69 delete(t.cachedTxs, hex.EncodeToString(tmhash.Sum(tx))) 70 } 71 } 72 } 73 74 func (t *TxCache) addHeightToCache(txs [][]byte) { 75 t.lock.Lock() 76 defer t.lock.Unlock() 77 for _, tx := range txs { 78 t.cachedTxs[hex.EncodeToString(tmhash.Sum(tx))] = struct{}{} 79 } 80 } 81 82 // MarketDelayRequiredUpdated is called when the market configuration is created/updated with support for 83 // transaction reordering. 84 func (t *TxCache) MarketDelayRequiredUpdated(marketID string, required bool) { 85 t.marketToDelayRequired[marketID] = required 86 } 87 88 // IsDelayRequired returns true if the market supports transaction reordering. 89 func (t *TxCache) IsDelayRequired(marketID string) bool { 90 delay, ok := t.marketToDelayRequired[marketID] 91 return ok && delay 92 } 93 94 // IsDelayRequiredAnyMarket returns true of there is any market that supports transaction reordering. 95 func (t *TxCache) IsDelayRequiredAnyMarket() bool { 96 return len(t.marketToDelayRequired) > 0 97 } 98 99 // OnNumBlocksToDelayUpdated is called when the network parameter for the number of blocks to delay 100 // transactions is updated. 101 func (t *TxCache) OnNumBlocksToDelayUpdated(_ context.Context, blocks *num.Uint) error { 102 t.numBlocksToDelay = blocks.Uint64() 103 return nil 104 } 105 106 // NewDelayedTransaction creates a new delayed transaction with a target block height being the current 107 // block being proposed + the configured network param indicating the target delay. 108 func (t *TxCache) NewDelayedTransaction(ctx context.Context, delayed [][]byte, currentHeight uint64) []byte { 109 height := currentHeight + t.numBlocksToDelay 110 payload := &commandspb.DelayedTransactionsWrapper{Transactions: delayed, Height: height} 111 tx, err := t.commander.NewTransaction(ctx, txn.DelayedTransactionsWrapper, payload) 112 if err != nil { 113 panic(err.Error()) 114 } 115 return tx 116 } 117 118 func (t *TxCache) SetRawTxs(rtx [][]byte, height uint64) { 119 if rtx == nil { 120 t.removeHeightFromCache(height) 121 delete(t.heightToTxs, height) 122 } else { 123 t.heightToTxs[height] = rtx 124 t.addHeightToCache(rtx) 125 } 126 } 127 128 func (t *TxCache) GetRawTxs(height uint64) [][]byte { 129 return t.heightToTxs[height] 130 } 131 132 func (t *TxCache) Namespace() types.SnapshotNamespace { 133 return types.TxCacheSnapshot 134 } 135 136 func (t *TxCache) Keys() []string { 137 return []string{(&types.PayloadTxCache{}).Key()} 138 } 139 140 func (t *TxCache) GetState(k string) ([]byte, []types.StateProvider, error) { 141 delays := make([]*snapshotpb.DelayedTx, 0, len(t.heightToTxs)) 142 for delay, txs := range t.heightToTxs { 143 delays = append(delays, &snapshotpb.DelayedTx{ 144 Height: delay, 145 Tx: txs, 146 }) 147 } 148 sort.Slice(delays, func(i, j int) bool { 149 return delays[i].Height < delays[j].Height 150 }) 151 152 payload := &snapshotpb.Payload{ 153 Data: &snapshotpb.Payload_TxCache{ 154 TxCache: &snapshotpb.TxCache{ 155 Txs: delays, 156 }, 157 }, 158 } 159 160 serialised, err := proto.Marshal(payload) 161 if err != nil { 162 return nil, nil, fmt.Errorf("could not serialize tx cache payload: %w", err) 163 } 164 return serialised, nil, err 165 } 166 167 func (t *TxCache) LoadState(_ context.Context, p *types.Payload) ([]types.StateProvider, error) { 168 if t.Namespace() != p.Data.Namespace() { 169 return nil, types.ErrInvalidSnapshotNamespace 170 } 171 172 switch data := p.Data.(type) { 173 case *types.PayloadTxCache: 174 t.heightToTxs = map[uint64][][]byte{} 175 for _, tx := range data.TxCache.Txs { 176 t.heightToTxs[tx.Height] = tx.Tx 177 t.addHeightToCache(tx.Tx) 178 } 179 return nil, nil 180 default: 181 return nil, types.ErrUnknownSnapshotType 182 } 183 } 184 185 func (t *TxCache) Stopped() bool { 186 return false 187 } 188 189 func (e *TxCache) StopSnapshots() {}