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() {}