bitxhub/pkg/order/mempool/tx_store.go

234 lines
6.8 KiB
Go

package mempool
import (
"math"
"sync"
"github.com/google/btree"
"github.com/meshplus/bitxhub-model/pb"
)
type transactionStore struct {
// track all valid tx hashes cached in mempool
txHashMap map[string]*orderedIndexKey
// track all valid tx, mapping user' account to all related transactions.
allTxs map[string]*txSortedMap
// track the commit nonce and pending nonce of each account.
nonceCache *nonceCache
// keep track of the latest timestamp of ready txs in ttlIndex
earliestTimestamp int64
// keep track of the livetime of ready txs in priorityIndex
ttlIndex *txLiveTimeMap
// keeps track of "non-ready" txs (txs that can't be included in next block)
// only used to help remove some txs if pool is full.
parkingLotIndex *btreeIndex
// keeps track of "ready" txs
priorityIndex *btreeIndex
// cache all the batched txs which haven't executed.
batchedTxs map[orderedIndexKey]bool
// track the non-batch priority transaction.
priorityNonBatchSize uint64
}
func newTransactionStore() *transactionStore {
return &transactionStore{
txHashMap: make(map[string]*orderedIndexKey, 0),
allTxs: make(map[string]*txSortedMap),
batchedTxs: make(map[orderedIndexKey]bool),
parkingLotIndex: newBtreeIndex(),
priorityIndex: newBtreeIndex(),
ttlIndex: newTxLiveTimeMap(),
nonceCache: newNonceCache(),
}
}
func (txStore *transactionStore) insertTxs(txs map[string][]*pb.Transaction, isLocal bool) map[string]bool {
dirtyAccounts := make(map[string]bool)
for account, list := range txs {
for _, tx := range list {
txHash := tx.TransactionHash.String()
txPointer := &orderedIndexKey{
account: account,
nonce: tx.Nonce,
}
txStore.txHashMap[txHash] = txPointer
txList, ok := txStore.allTxs[account]
if !ok {
// if this is new account to send tx, create a new txSortedMap
txStore.allTxs[account] = newTxSortedMap()
}
txList = txStore.allTxs[account]
txItem := &txItem{
account: account,
tx: tx,
local: isLocal,
}
txList.items[tx.Nonce] = txItem
txList.index.insertBySortedNonceKey(tx)
if isLocal {
// no need to rebroadcast tx from other nodes to reduce network overhead
txStore.ttlIndex.insertByTtlKey(account, tx.Nonce, tx.Timestamp)
}
}
dirtyAccounts[account] = true
}
return dirtyAccounts
}
// Get transaction by account address + nonce
func (txStore *transactionStore) getTxByOrderKey(account string, seqNo uint64) *pb.Transaction {
if list, ok := txStore.allTxs[account]; ok {
res := list.items[seqNo]
if res == nil {
return nil
}
return res.tx
}
return nil
}
func (txStore *transactionStore) updateEarliestTimestamp() {
// find the earliest tx in ttlIndex
earliestTime := int64(math.MaxInt64)
latestItem := txStore.ttlIndex.index.Min()
if latestItem != nil {
earliestTime = latestItem.(*orderedTimeoutKey).timestamp
}
txStore.earliestTimestamp = earliestTime
}
type txSortedMap struct {
items map[uint64]*txItem // map nonce to transaction
index *btreeIndex // index for items
}
func newTxSortedMap() *txSortedMap {
return &txSortedMap{
items: make(map[uint64]*txItem),
index: newBtreeIndex(),
}
}
func (m *txSortedMap) filterReady(demandNonce uint64) ([]*pb.Transaction, []*pb.Transaction, uint64) {
var readyTxs, nonReadyTxs []*pb.Transaction
if m.index.data.Len() == 0 {
return nil, nil, demandNonce
}
demandKey := makeSortedNonceKey(demandNonce)
m.index.data.AscendGreaterOrEqual(demandKey, func(i btree.Item) bool {
nonce := i.(*sortedNonceKey).nonce
if nonce == demandNonce {
readyTxs = append(readyTxs, m.items[demandNonce].tx)
demandNonce++
} else {
nonReadyTxs = append(nonReadyTxs, m.items[nonce].tx)
}
return true
})
return readyTxs, nonReadyTxs, demandNonce
}
// forward removes all allTxs from the map with a nonce lower than the
// provided commitNonce.
func (m *txSortedMap) forward(commitNonce uint64) map[string][]*pb.Transaction {
removedTxs := make(map[string][]*pb.Transaction)
commitNonceKey := makeSortedNonceKey(commitNonce)
m.index.data.AscendLessThan(commitNonceKey, func(i btree.Item) bool {
// delete tx from map.
nonce := i.(*sortedNonceKey).nonce
txItem := m.items[nonce]
account := txItem.account
if _, ok := removedTxs[account]; !ok {
removedTxs[account] = make([]*pb.Transaction, 0)
}
removedTxs[account] = append(removedTxs[account], txItem.tx)
delete(m.items, nonce)
return true
})
return removedTxs
}
// TODO (YH): persist and restore commitNonce and pendingNonce from db.
type nonceCache struct {
// commitNonces records each account's latest committed nonce in ledger.
commitNonces map[string]uint64
// pendingNonces records each account's latest nonce which has been included in
// priority queue. Invariant: pendingNonces[account] >= commitNonces[account]
pendingNonces map[string]uint64
pendingMu sync.RWMutex
}
func newNonceCache() *nonceCache {
return &nonceCache{
commitNonces: make(map[string]uint64),
pendingNonces: make(map[string]uint64),
}
}
func (nc *nonceCache) getCommitNonce(account string) uint64 {
nonce, ok := nc.commitNonces[account]
if !ok {
return 1
}
return nonce
}
func (nc *nonceCache) setCommitNonce(account string, nonce uint64) {
nc.commitNonces[account] = nonce
}
func (nc *nonceCache) getPendingNonce(account string) uint64 {
nc.pendingMu.RLock()
defer nc.pendingMu.RUnlock()
nonce, ok := nc.pendingNonces[account]
if !ok {
return 1
}
return nonce
}
func (nc *nonceCache) setPendingNonce(account string, nonce uint64) {
nc.pendingMu.Lock()
defer nc.pendingMu.Unlock()
nc.pendingNonces[account] = nonce
}
// since the live time field in sortedTtlKey may vary during process
// we need to track the latest live time since its latest broadcast.
type txLiveTimeMap struct {
items map[string]int64 // map account to its latest live time
index *btree.BTree // index for txs
}
func newTxLiveTimeMap() *txLiveTimeMap {
return &txLiveTimeMap{
index: btree.New(btreeDegree),
items: make(map[string]int64),
}
}
func (tlm *txLiveTimeMap) insertByTtlKey(account string, nonce uint64, liveTime int64) {
tlm.index.ReplaceOrInsert(&orderedTimeoutKey{account, nonce, liveTime})
tlm.items[makeAccountNonceKey(account, nonce)] = liveTime
}
func (tlm *txLiveTimeMap) removeByTtlKey(txs map[string][]*pb.Transaction) {
for account, list := range txs {
for _, tx := range list {
liveTime, ok := tlm.items[makeAccountNonceKey(account, tx.Nonce)]
if !ok {
continue
}
tlm.index.Delete(&orderedTimeoutKey{account, tx.Nonce, liveTime})
delete(tlm.items, makeAccountNonceKey(account, tx.Nonce))
}
}
}
func (tlm *txLiveTimeMap) updateByTtlKey(originalKey *orderedTimeoutKey, newTime int64) {
tlm.index.Delete(originalKey)
delete(tlm.items, makeAccountNonceKey(originalKey.account, originalKey.nonce))
tlm.insertByTtlKey(originalKey.account, originalKey.nonce, newTime)
}