2019-05-04 10:41:31 +04:00
|
|
|
package mempool
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"container/list"
|
|
|
|
"crypto/sha256"
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
|
|
|
abci "github.com/tendermint/tendermint/abci/types"
|
|
|
|
cfg "github.com/tendermint/tendermint/config"
|
|
|
|
auto "github.com/tendermint/tendermint/libs/autofile"
|
|
|
|
"github.com/tendermint/tendermint/libs/clist"
|
|
|
|
cmn "github.com/tendermint/tendermint/libs/common"
|
|
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
|
|
"github.com/tendermint/tendermint/proxy"
|
|
|
|
"github.com/tendermint/tendermint/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// CListMempool is an ordered in-memory pool for transactions before they are
|
|
|
|
// proposed in a consensus round. Transaction validity is checked using the
|
|
|
|
// CheckTx abci message before the transaction is added to the pool. The
|
|
|
|
// mempool uses a concurrent list structure for storing transactions that can
|
|
|
|
// be efficiently accessed by multiple concurrent readers.
|
|
|
|
type CListMempool struct {
|
|
|
|
config *cfg.MempoolConfig
|
|
|
|
|
|
|
|
proxyMtx sync.Mutex
|
|
|
|
proxyAppConn proxy.AppConnMempool
|
|
|
|
txs *clist.CList // concurrent linked-list of good txs
|
|
|
|
preCheck PreCheckFunc
|
|
|
|
postCheck PostCheckFunc
|
|
|
|
|
|
|
|
// Track whether we're rechecking txs.
|
|
|
|
// These are not protected by a mutex and are expected to be mutated
|
|
|
|
// in serial (ie. by abci responses which are called in serial).
|
|
|
|
recheckCursor *clist.CElement // next expected response
|
|
|
|
recheckEnd *clist.CElement // re-checking stops here
|
|
|
|
|
|
|
|
// notify listeners (ie. consensus) when txs are available
|
|
|
|
notifiedTxsAvailable bool
|
|
|
|
txsAvailable chan struct{} // fires once for each height, when the mempool is not empty
|
|
|
|
|
|
|
|
// Map for quick access to txs to record sender in CheckTx.
|
|
|
|
// txsMap: txKey -> CElement
|
|
|
|
txsMap sync.Map
|
|
|
|
|
|
|
|
// Atomic integers
|
|
|
|
height int64 // the last block Update()'d to
|
|
|
|
rechecking int32 // for re-checking filtered txs on Update()
|
|
|
|
txsBytes int64 // total size of mempool, in bytes
|
|
|
|
|
|
|
|
// Keep a cache of already-seen txs.
|
|
|
|
// This reduces the pressure on the proxyApp.
|
|
|
|
cache txCache
|
|
|
|
|
|
|
|
// A log of mempool txs
|
|
|
|
wal *auto.AutoFile
|
|
|
|
|
|
|
|
logger log.Logger
|
|
|
|
|
|
|
|
metrics *Metrics
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ Mempool = &CListMempool{}
|
|
|
|
|
|
|
|
// CListMempoolOption sets an optional parameter on the mempool.
|
|
|
|
type CListMempoolOption func(*CListMempool)
|
|
|
|
|
|
|
|
// NewCListMempool returns a new mempool with the given configuration and connection to an application.
|
|
|
|
func NewCListMempool(
|
|
|
|
config *cfg.MempoolConfig,
|
|
|
|
proxyAppConn proxy.AppConnMempool,
|
|
|
|
height int64,
|
|
|
|
options ...CListMempoolOption,
|
|
|
|
) *CListMempool {
|
|
|
|
mempool := &CListMempool{
|
|
|
|
config: config,
|
|
|
|
proxyAppConn: proxyAppConn,
|
|
|
|
txs: clist.New(),
|
|
|
|
height: height,
|
|
|
|
rechecking: 0,
|
|
|
|
recheckCursor: nil,
|
|
|
|
recheckEnd: nil,
|
|
|
|
logger: log.NewNopLogger(),
|
|
|
|
metrics: NopMetrics(),
|
|
|
|
}
|
|
|
|
if config.CacheSize > 0 {
|
|
|
|
mempool.cache = newMapTxCache(config.CacheSize)
|
|
|
|
} else {
|
|
|
|
mempool.cache = nopTxCache{}
|
|
|
|
}
|
|
|
|
proxyAppConn.SetResponseCallback(mempool.globalCb)
|
|
|
|
for _, option := range options {
|
|
|
|
option(mempool)
|
|
|
|
}
|
|
|
|
return mempool
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: not thread safe - should only be called once, on startup
|
|
|
|
func (mem *CListMempool) EnableTxsAvailable() {
|
|
|
|
mem.txsAvailable = make(chan struct{}, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetLogger sets the Logger.
|
|
|
|
func (mem *CListMempool) SetLogger(l log.Logger) {
|
|
|
|
mem.logger = l
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithPreCheck sets a filter for the mempool to reject a tx if f(tx) returns
|
|
|
|
// false. This is ran before CheckTx.
|
|
|
|
func WithPreCheck(f PreCheckFunc) CListMempoolOption {
|
|
|
|
return func(mem *CListMempool) { mem.preCheck = f }
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithPostCheck sets a filter for the mempool to reject a tx if f(tx) returns
|
|
|
|
// false. This is ran after CheckTx.
|
|
|
|
func WithPostCheck(f PostCheckFunc) CListMempoolOption {
|
|
|
|
return func(mem *CListMempool) { mem.postCheck = f }
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMetrics sets the metrics.
|
|
|
|
func WithMetrics(metrics *Metrics) CListMempoolOption {
|
|
|
|
return func(mem *CListMempool) { mem.metrics = metrics }
|
|
|
|
}
|
|
|
|
|
|
|
|
// *panics* if can't create directory or open file.
|
|
|
|
// *not thread safe*
|
|
|
|
func (mem *CListMempool) InitWAL() {
|
|
|
|
walDir := mem.config.WalDir()
|
|
|
|
err := cmn.EnsureDir(walDir, 0700)
|
|
|
|
if err != nil {
|
|
|
|
panic(errors.Wrap(err, "Error ensuring WAL dir"))
|
|
|
|
}
|
|
|
|
af, err := auto.OpenAutoFile(walDir + "/wal")
|
|
|
|
if err != nil {
|
|
|
|
panic(errors.Wrap(err, "Error opening WAL file"))
|
|
|
|
}
|
|
|
|
mem.wal = af
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) CloseWAL() {
|
|
|
|
mem.proxyMtx.Lock()
|
|
|
|
defer mem.proxyMtx.Unlock()
|
|
|
|
|
|
|
|
if err := mem.wal.Close(); err != nil {
|
|
|
|
mem.logger.Error("Error closing WAL", "err", err)
|
|
|
|
}
|
|
|
|
mem.wal = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) Lock() {
|
|
|
|
mem.proxyMtx.Lock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) Unlock() {
|
|
|
|
mem.proxyMtx.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) Size() int {
|
|
|
|
return mem.txs.Len()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) TxsBytes() int64 {
|
|
|
|
return atomic.LoadInt64(&mem.txsBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) FlushAppConn() error {
|
|
|
|
return mem.proxyAppConn.FlushSync()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) Flush() {
|
|
|
|
mem.proxyMtx.Lock()
|
|
|
|
defer mem.proxyMtx.Unlock()
|
|
|
|
|
|
|
|
mem.cache.Reset()
|
|
|
|
|
|
|
|
for e := mem.txs.Front(); e != nil; e = e.Next() {
|
|
|
|
mem.txs.Remove(e)
|
|
|
|
e.DetachPrev()
|
|
|
|
}
|
|
|
|
|
|
|
|
mem.txsMap = sync.Map{}
|
|
|
|
_ = atomic.SwapInt64(&mem.txsBytes, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TxsFront returns the first transaction in the ordered list for peer
|
|
|
|
// goroutines to call .NextWait() on.
|
|
|
|
// FIXME: leaking implementation details!
|
|
|
|
func (mem *CListMempool) TxsFront() *clist.CElement {
|
|
|
|
return mem.txs.Front()
|
|
|
|
}
|
|
|
|
|
|
|
|
// TxsWaitChan returns a channel to wait on transactions. It will be closed
|
|
|
|
// once the mempool is not empty (ie. the internal `mem.txs` has at least one
|
|
|
|
// element)
|
|
|
|
func (mem *CListMempool) TxsWaitChan() <-chan struct{} {
|
|
|
|
return mem.txs.WaitChan()
|
|
|
|
}
|
|
|
|
|
|
|
|
// It blocks if we're waiting on Update() or Reap().
|
|
|
|
// cb: A callback from the CheckTx command.
|
|
|
|
// It gets called from another goroutine.
|
|
|
|
// CONTRACT: Either cb will get called, or err returned.
|
|
|
|
func (mem *CListMempool) CheckTx(tx types.Tx, cb func(*abci.Response)) (err error) {
|
|
|
|
return mem.CheckTxWithInfo(tx, cb, TxInfo{SenderID: UnknownPeerID})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) CheckTxWithInfo(tx types.Tx, cb func(*abci.Response), txInfo TxInfo) (err error) {
|
|
|
|
mem.proxyMtx.Lock()
|
|
|
|
// use defer to unlock mutex because application (*local client*) might panic
|
|
|
|
defer mem.proxyMtx.Unlock()
|
|
|
|
|
|
|
|
var (
|
|
|
|
memSize = mem.Size()
|
|
|
|
txsBytes = mem.TxsBytes()
|
|
|
|
)
|
|
|
|
if memSize >= mem.config.Size ||
|
|
|
|
int64(len(tx))+txsBytes > mem.config.MaxTxsBytes {
|
|
|
|
return ErrMempoolIsFull{
|
|
|
|
memSize, mem.config.Size,
|
|
|
|
txsBytes, mem.config.MaxTxsBytes}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The size of the corresponding amino-encoded TxMessage
|
|
|
|
// can't be larger than the maxMsgSize, otherwise we can't
|
|
|
|
// relay it to peers.
|
|
|
|
if len(tx) > maxTxSize {
|
|
|
|
return ErrTxTooLarge
|
|
|
|
}
|
|
|
|
|
|
|
|
if mem.preCheck != nil {
|
|
|
|
if err := mem.preCheck(tx); err != nil {
|
|
|
|
return ErrPreCheck{err}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CACHE
|
|
|
|
if !mem.cache.Push(tx) {
|
|
|
|
// Record a new sender for a tx we've already seen.
|
|
|
|
// Note it's possible a tx is still in the cache but no longer in the mempool
|
|
|
|
// (eg. after committing a block, txs are removed from mempool but not cache),
|
|
|
|
// so we only record the sender for txs still in the mempool.
|
|
|
|
if e, ok := mem.txsMap.Load(txKey(tx)); ok {
|
|
|
|
memTx := e.(*clist.CElement).Value.(*mempoolTx)
|
|
|
|
if _, loaded := memTx.senders.LoadOrStore(txInfo.SenderID, true); loaded {
|
|
|
|
// TODO: consider punishing peer for dups,
|
|
|
|
// its non-trivial since invalid txs can become valid,
|
|
|
|
// but they can spam the same tx with little cost to them atm.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ErrTxInCache
|
|
|
|
}
|
|
|
|
// END CACHE
|
|
|
|
|
|
|
|
// WAL
|
|
|
|
if mem.wal != nil {
|
|
|
|
// TODO: Notify administrators when WAL fails
|
|
|
|
_, err := mem.wal.Write([]byte(tx))
|
|
|
|
if err != nil {
|
|
|
|
mem.logger.Error("Error writing to WAL", "err", err)
|
|
|
|
}
|
|
|
|
_, err = mem.wal.Write([]byte("\n"))
|
|
|
|
if err != nil {
|
|
|
|
mem.logger.Error("Error writing to WAL", "err", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// END WAL
|
|
|
|
|
|
|
|
// NOTE: proxyAppConn may error if tx buffer is full
|
|
|
|
if err = mem.proxyAppConn.Error(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
abci: Refactor CheckTx to notify of recheck (#3744)
As per #2127, this refactors the RequestCheckTx ProtoBuf struct to allow for a flag indicating whether a query is a recheck or not (and allows for possible future, more nuanced states).
In order to pass this extended information through to the ABCI app, the proxy.AppConnMempool (and, for consistency, the proxy.AppConnConsensus) interface seems to need to be refactored along with abcicli.Client.
And, as per this comment, I've made the following modification to the protobuf definition for the RequestCheckTx structure:
enum CheckTxType {
New = 0;
Recheck = 1;
}
message RequestCheckTx {
bytes tx = 1;
CheckTxType type = 2;
}
* Refactor ABCI CheckTx to notify of recheck
As per #2127, this refactors the `RequestCheckTx` ProtoBuf struct to allow for:
1. a flag indicating whether a query is a recheck or not (and allows for
possible future, more nuanced states)
2. an `additional_data` bytes array to provide information for those more
nuanced states.
In order to pass this extended information through to the ABCI app, the
`proxy.AppConnMempool` (and, for consistency, the
`proxy.AppConnConsensus`) interface seems to need to be refactored.
Commits:
* Fix linting issue
* Add CHANGELOG_PENDING entry
* Remove extraneous explicit initialization
* Update ABCI spec doc to include new CheckTx params
* Rename method param for consistency
* Rename CheckTxType enum values and remove additional_data param
2019-07-02 10:14:53 -04:00
|
|
|
reqRes := mem.proxyAppConn.CheckTxAsync(abci.RequestCheckTx{Tx: tx})
|
2019-05-04 10:41:31 +04:00
|
|
|
reqRes.SetCallback(mem.reqResCb(tx, txInfo.SenderID, cb))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Global callback that will be called after every ABCI response.
|
|
|
|
// Having a single global callback avoids needing to set a callback for each request.
|
|
|
|
// However, processing the checkTx response requires the peerID (so we can track which txs we heard from who),
|
|
|
|
// and peerID is not included in the ABCI request, so we have to set request-specific callbacks that
|
|
|
|
// include this information. If we're not in the midst of a recheck, this function will just return,
|
|
|
|
// so the request specific callback can do the work.
|
|
|
|
// When rechecking, we don't need the peerID, so the recheck callback happens here.
|
|
|
|
func (mem *CListMempool) globalCb(req *abci.Request, res *abci.Response) {
|
|
|
|
if mem.recheckCursor == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
mem.metrics.RecheckTimes.Add(1)
|
|
|
|
mem.resCbRecheck(req, res)
|
|
|
|
|
|
|
|
// update metrics
|
|
|
|
mem.metrics.Size.Set(float64(mem.Size()))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Request specific callback that should be set on individual reqRes objects
|
|
|
|
// to incorporate local information when processing the response.
|
|
|
|
// This allows us to track the peer that sent us this tx, so we can avoid sending it back to them.
|
|
|
|
// NOTE: alternatively, we could include this information in the ABCI request itself.
|
|
|
|
//
|
|
|
|
// External callers of CheckTx, like the RPC, can also pass an externalCb through here that is called
|
|
|
|
// when all other response processing is complete.
|
|
|
|
//
|
|
|
|
// Used in CheckTxWithInfo to record PeerID who sent us the tx.
|
|
|
|
func (mem *CListMempool) reqResCb(tx []byte, peerID uint16, externalCb func(*abci.Response)) func(res *abci.Response) {
|
|
|
|
return func(res *abci.Response) {
|
|
|
|
if mem.recheckCursor != nil {
|
|
|
|
// this should never happen
|
|
|
|
panic("recheck cursor is not nil in reqResCb")
|
|
|
|
}
|
|
|
|
|
|
|
|
mem.resCbFirstTime(tx, peerID, res)
|
|
|
|
|
|
|
|
// update metrics
|
|
|
|
mem.metrics.Size.Set(float64(mem.Size()))
|
|
|
|
|
|
|
|
// passed in by the caller of CheckTx, eg. the RPC
|
|
|
|
if externalCb != nil {
|
|
|
|
externalCb(res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called from:
|
|
|
|
// - resCbFirstTime (lock not held) if tx is valid
|
|
|
|
func (mem *CListMempool) addTx(memTx *mempoolTx) {
|
|
|
|
e := mem.txs.PushBack(memTx)
|
|
|
|
mem.txsMap.Store(txKey(memTx.tx), e)
|
|
|
|
atomic.AddInt64(&mem.txsBytes, int64(len(memTx.tx)))
|
|
|
|
mem.metrics.TxSizeBytes.Observe(float64(len(memTx.tx)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called from:
|
|
|
|
// - Update (lock held) if tx was committed
|
|
|
|
// - resCbRecheck (lock not held) if tx was invalidated
|
|
|
|
func (mem *CListMempool) removeTx(tx types.Tx, elem *clist.CElement, removeFromCache bool) {
|
|
|
|
mem.txs.Remove(elem)
|
|
|
|
elem.DetachPrev()
|
|
|
|
mem.txsMap.Delete(txKey(tx))
|
|
|
|
atomic.AddInt64(&mem.txsBytes, int64(-len(tx)))
|
|
|
|
|
|
|
|
if removeFromCache {
|
|
|
|
mem.cache.Remove(tx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// callback, which is called after the app checked the tx for the first time.
|
|
|
|
//
|
|
|
|
// The case where the app checks the tx for the second and subsequent times is
|
|
|
|
// handled by the resCbRecheck callback.
|
|
|
|
func (mem *CListMempool) resCbFirstTime(tx []byte, peerID uint16, res *abci.Response) {
|
|
|
|
switch r := res.Value.(type) {
|
|
|
|
case *abci.Response_CheckTx:
|
|
|
|
var postCheckErr error
|
|
|
|
if mem.postCheck != nil {
|
|
|
|
postCheckErr = mem.postCheck(tx, r.CheckTx)
|
|
|
|
}
|
|
|
|
if (r.CheckTx.Code == abci.CodeTypeOK) && postCheckErr == nil {
|
|
|
|
memTx := &mempoolTx{
|
|
|
|
height: mem.height,
|
|
|
|
gasWanted: r.CheckTx.GasWanted,
|
|
|
|
tx: tx,
|
|
|
|
}
|
|
|
|
memTx.senders.Store(peerID, true)
|
|
|
|
mem.addTx(memTx)
|
|
|
|
mem.logger.Info("Added good transaction",
|
|
|
|
"tx", txID(tx),
|
|
|
|
"res", r,
|
|
|
|
"height", memTx.height,
|
|
|
|
"total", mem.Size(),
|
|
|
|
)
|
|
|
|
mem.notifyTxsAvailable()
|
|
|
|
} else {
|
|
|
|
// ignore bad transaction
|
|
|
|
mem.logger.Info("Rejected bad transaction", "tx", txID(tx), "res", r, "err", postCheckErr)
|
|
|
|
mem.metrics.FailedTxs.Add(1)
|
|
|
|
// remove from cache (it might be good later)
|
|
|
|
mem.cache.Remove(tx)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// ignore other messages
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// callback, which is called after the app rechecked the tx.
|
|
|
|
//
|
|
|
|
// The case where the app checks the tx for the first time is handled by the
|
|
|
|
// resCbFirstTime callback.
|
|
|
|
func (mem *CListMempool) resCbRecheck(req *abci.Request, res *abci.Response) {
|
|
|
|
switch r := res.Value.(type) {
|
|
|
|
case *abci.Response_CheckTx:
|
|
|
|
tx := req.GetCheckTx().Tx
|
|
|
|
memTx := mem.recheckCursor.Value.(*mempoolTx)
|
|
|
|
if !bytes.Equal(tx, memTx.tx) {
|
|
|
|
panic(fmt.Sprintf(
|
|
|
|
"Unexpected tx response from proxy during recheck\nExpected %X, got %X",
|
|
|
|
memTx.tx,
|
|
|
|
tx))
|
|
|
|
}
|
|
|
|
var postCheckErr error
|
|
|
|
if mem.postCheck != nil {
|
|
|
|
postCheckErr = mem.postCheck(tx, r.CheckTx)
|
|
|
|
}
|
|
|
|
if (r.CheckTx.Code == abci.CodeTypeOK) && postCheckErr == nil {
|
|
|
|
// Good, nothing to do.
|
|
|
|
} else {
|
|
|
|
// Tx became invalidated due to newly committed block.
|
|
|
|
mem.logger.Info("Tx is no longer valid", "tx", txID(tx), "res", r, "err", postCheckErr)
|
|
|
|
// NOTE: we remove tx from the cache because it might be good later
|
|
|
|
mem.removeTx(tx, mem.recheckCursor, true)
|
|
|
|
}
|
|
|
|
if mem.recheckCursor == mem.recheckEnd {
|
|
|
|
mem.recheckCursor = nil
|
|
|
|
} else {
|
|
|
|
mem.recheckCursor = mem.recheckCursor.Next()
|
|
|
|
}
|
|
|
|
if mem.recheckCursor == nil {
|
|
|
|
// Done!
|
|
|
|
atomic.StoreInt32(&mem.rechecking, 0)
|
|
|
|
mem.logger.Info("Done rechecking txs")
|
|
|
|
|
|
|
|
// incase the recheck removed all txs
|
|
|
|
if mem.Size() > 0 {
|
|
|
|
mem.notifyTxsAvailable()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// ignore other messages
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) TxsAvailable() <-chan struct{} {
|
|
|
|
return mem.txsAvailable
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) notifyTxsAvailable() {
|
|
|
|
if mem.Size() == 0 {
|
|
|
|
panic("notified txs available but mempool is empty!")
|
|
|
|
}
|
|
|
|
if mem.txsAvailable != nil && !mem.notifiedTxsAvailable {
|
|
|
|
// channel cap is 1, so this will send once
|
|
|
|
mem.notifiedTxsAvailable = true
|
|
|
|
select {
|
|
|
|
case mem.txsAvailable <- struct{}{}:
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs {
|
|
|
|
mem.proxyMtx.Lock()
|
|
|
|
defer mem.proxyMtx.Unlock()
|
|
|
|
|
|
|
|
for atomic.LoadInt32(&mem.rechecking) > 0 {
|
|
|
|
// TODO: Something better?
|
|
|
|
time.Sleep(time.Millisecond * 10)
|
|
|
|
}
|
|
|
|
|
|
|
|
var totalBytes int64
|
|
|
|
var totalGas int64
|
|
|
|
// TODO: we will get a performance boost if we have a good estimate of avg
|
|
|
|
// size per tx, and set the initial capacity based off of that.
|
|
|
|
// txs := make([]types.Tx, 0, cmn.MinInt(mem.txs.Len(), max/mem.avgTxSize))
|
|
|
|
txs := make([]types.Tx, 0, mem.txs.Len())
|
|
|
|
for e := mem.txs.Front(); e != nil; e = e.Next() {
|
|
|
|
memTx := e.Value.(*mempoolTx)
|
|
|
|
// Check total size requirement
|
|
|
|
aminoOverhead := types.ComputeAminoOverhead(memTx.tx, 1)
|
|
|
|
if maxBytes > -1 && totalBytes+int64(len(memTx.tx))+aminoOverhead > maxBytes {
|
|
|
|
return txs
|
|
|
|
}
|
|
|
|
totalBytes += int64(len(memTx.tx)) + aminoOverhead
|
|
|
|
// Check total gas requirement.
|
|
|
|
// If maxGas is negative, skip this check.
|
|
|
|
// Since newTotalGas < masGas, which
|
|
|
|
// must be non-negative, it follows that this won't overflow.
|
|
|
|
newTotalGas := totalGas + memTx.gasWanted
|
|
|
|
if maxGas > -1 && newTotalGas > maxGas {
|
|
|
|
return txs
|
|
|
|
}
|
|
|
|
totalGas = newTotalGas
|
|
|
|
txs = append(txs, memTx.tx)
|
|
|
|
}
|
|
|
|
return txs
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) ReapMaxTxs(max int) types.Txs {
|
|
|
|
mem.proxyMtx.Lock()
|
|
|
|
defer mem.proxyMtx.Unlock()
|
|
|
|
|
|
|
|
if max < 0 {
|
|
|
|
max = mem.txs.Len()
|
|
|
|
}
|
|
|
|
|
|
|
|
for atomic.LoadInt32(&mem.rechecking) > 0 {
|
|
|
|
// TODO: Something better?
|
|
|
|
time.Sleep(time.Millisecond * 10)
|
|
|
|
}
|
|
|
|
|
|
|
|
txs := make([]types.Tx, 0, cmn.MinInt(mem.txs.Len(), max))
|
|
|
|
for e := mem.txs.Front(); e != nil && len(txs) <= max; e = e.Next() {
|
|
|
|
memTx := e.Value.(*mempoolTx)
|
|
|
|
txs = append(txs, memTx.tx)
|
|
|
|
}
|
|
|
|
return txs
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mem *CListMempool) Update(
|
|
|
|
height int64,
|
|
|
|
txs types.Txs,
|
mempool: remove only valid (Code==0) txs on Update (#3625)
* mempool: remove only valid (Code==0) txs on Update
so evil proposers can't drop valid txs in Commit stage.
Also remove invalid (Code!=0) txs from the cache so they can be
resubmitted.
Fixes #3322
@rickyyangz:
In the end of commit stage, we will update mempool to remove all the txs
in current block.
// Update mempool.
err = blockExec.mempool.Update(
block.Height,
block.Txs,
TxPreCheck(state),
TxPostCheck(state),
)
Assum an account has 3 transactions in the mempool, the sequences are
100, 101 and 102 separately, So an evil proposal can only package the
101 and 102 transactions into its proposal block, and leave 100 still in
mempool, then the two txs will be removed from all validators' mempool
when commit. So the account lost the two valid txs.
@ebuchman:
In the longer term we may want to do something like #2639 so we can
validate txs before we commit the block. But even in this case we'd only
want to run the equivalent of CheckTx, which means the DeliverTx could
still fail even if the CheckTx passes depending on how the app handles
the ABCI Code semantics. So more work will be required around the ABCI
code. See also #2185
* add changelog entry and tests
* improve changelog message
* reformat code
2019-05-07 12:25:35 +04:00
|
|
|
deliverTxResponses []*abci.ResponseDeliverTx,
|
2019-05-04 10:41:31 +04:00
|
|
|
preCheck PreCheckFunc,
|
|
|
|
postCheck PostCheckFunc,
|
|
|
|
) error {
|
|
|
|
// Set height
|
|
|
|
mem.height = height
|
|
|
|
mem.notifiedTxsAvailable = false
|
|
|
|
|
|
|
|
if preCheck != nil {
|
|
|
|
mem.preCheck = preCheck
|
|
|
|
}
|
|
|
|
if postCheck != nil {
|
|
|
|
mem.postCheck = postCheck
|
|
|
|
}
|
|
|
|
|
mempool: remove only valid (Code==0) txs on Update (#3625)
* mempool: remove only valid (Code==0) txs on Update
so evil proposers can't drop valid txs in Commit stage.
Also remove invalid (Code!=0) txs from the cache so they can be
resubmitted.
Fixes #3322
@rickyyangz:
In the end of commit stage, we will update mempool to remove all the txs
in current block.
// Update mempool.
err = blockExec.mempool.Update(
block.Height,
block.Txs,
TxPreCheck(state),
TxPostCheck(state),
)
Assum an account has 3 transactions in the mempool, the sequences are
100, 101 and 102 separately, So an evil proposal can only package the
101 and 102 transactions into its proposal block, and leave 100 still in
mempool, then the two txs will be removed from all validators' mempool
when commit. So the account lost the two valid txs.
@ebuchman:
In the longer term we may want to do something like #2639 so we can
validate txs before we commit the block. But even in this case we'd only
want to run the equivalent of CheckTx, which means the DeliverTx could
still fail even if the CheckTx passes depending on how the app handles
the ABCI Code semantics. So more work will be required around the ABCI
code. See also #2185
* add changelog entry and tests
* improve changelog message
* reformat code
2019-05-07 12:25:35 +04:00
|
|
|
for i, tx := range txs {
|
|
|
|
if deliverTxResponses[i].Code == abci.CodeTypeOK {
|
|
|
|
// Add valid committed tx to the cache (if missing).
|
|
|
|
_ = mem.cache.Push(tx)
|
|
|
|
} else {
|
|
|
|
// Allow invalid transactions to be resubmitted.
|
|
|
|
mem.cache.Remove(tx)
|
2019-06-03 21:46:02 +09:00
|
|
|
}
|
2019-05-04 10:41:31 +04:00
|
|
|
|
2019-06-03 21:46:02 +09:00
|
|
|
// Remove committed tx from the mempool.
|
|
|
|
//
|
|
|
|
// Note an evil proposer can drop valid txs!
|
|
|
|
// Mempool before:
|
|
|
|
// 100 -> 101 -> 102
|
|
|
|
// Block, proposed by an evil proposer:
|
|
|
|
// 101 -> 102
|
|
|
|
// Mempool after:
|
|
|
|
// 100
|
|
|
|
// https://github.com/tendermint/tendermint/issues/3322.
|
|
|
|
if e, ok := mem.txsMap.Load(txKey(tx)); ok {
|
|
|
|
mem.removeTx(tx, e.(*clist.CElement), false)
|
mempool: remove only valid (Code==0) txs on Update (#3625)
* mempool: remove only valid (Code==0) txs on Update
so evil proposers can't drop valid txs in Commit stage.
Also remove invalid (Code!=0) txs from the cache so they can be
resubmitted.
Fixes #3322
@rickyyangz:
In the end of commit stage, we will update mempool to remove all the txs
in current block.
// Update mempool.
err = blockExec.mempool.Update(
block.Height,
block.Txs,
TxPreCheck(state),
TxPostCheck(state),
)
Assum an account has 3 transactions in the mempool, the sequences are
100, 101 and 102 separately, So an evil proposal can only package the
101 and 102 transactions into its proposal block, and leave 100 still in
mempool, then the two txs will be removed from all validators' mempool
when commit. So the account lost the two valid txs.
@ebuchman:
In the longer term we may want to do something like #2639 so we can
validate txs before we commit the block. But even in this case we'd only
want to run the equivalent of CheckTx, which means the DeliverTx could
still fail even if the CheckTx passes depending on how the app handles
the ABCI Code semantics. So more work will be required around the ABCI
code. See also #2185
* add changelog entry and tests
* improve changelog message
* reformat code
2019-05-07 12:25:35 +04:00
|
|
|
}
|
|
|
|
}
|
2019-05-04 10:41:31 +04:00
|
|
|
|
|
|
|
// Either recheck non-committed txs to see if they became invalid
|
|
|
|
// or just notify there're some txs left.
|
mempool: remove only valid (Code==0) txs on Update (#3625)
* mempool: remove only valid (Code==0) txs on Update
so evil proposers can't drop valid txs in Commit stage.
Also remove invalid (Code!=0) txs from the cache so they can be
resubmitted.
Fixes #3322
@rickyyangz:
In the end of commit stage, we will update mempool to remove all the txs
in current block.
// Update mempool.
err = blockExec.mempool.Update(
block.Height,
block.Txs,
TxPreCheck(state),
TxPostCheck(state),
)
Assum an account has 3 transactions in the mempool, the sequences are
100, 101 and 102 separately, So an evil proposal can only package the
101 and 102 transactions into its proposal block, and leave 100 still in
mempool, then the two txs will be removed from all validators' mempool
when commit. So the account lost the two valid txs.
@ebuchman:
In the longer term we may want to do something like #2639 so we can
validate txs before we commit the block. But even in this case we'd only
want to run the equivalent of CheckTx, which means the DeliverTx could
still fail even if the CheckTx passes depending on how the app handles
the ABCI Code semantics. So more work will be required around the ABCI
code. See also #2185
* add changelog entry and tests
* improve changelog message
* reformat code
2019-05-07 12:25:35 +04:00
|
|
|
if mem.Size() > 0 {
|
2019-05-04 10:41:31 +04:00
|
|
|
if mem.config.Recheck {
|
mempool: remove only valid (Code==0) txs on Update (#3625)
* mempool: remove only valid (Code==0) txs on Update
so evil proposers can't drop valid txs in Commit stage.
Also remove invalid (Code!=0) txs from the cache so they can be
resubmitted.
Fixes #3322
@rickyyangz:
In the end of commit stage, we will update mempool to remove all the txs
in current block.
// Update mempool.
err = blockExec.mempool.Update(
block.Height,
block.Txs,
TxPreCheck(state),
TxPostCheck(state),
)
Assum an account has 3 transactions in the mempool, the sequences are
100, 101 and 102 separately, So an evil proposal can only package the
101 and 102 transactions into its proposal block, and leave 100 still in
mempool, then the two txs will be removed from all validators' mempool
when commit. So the account lost the two valid txs.
@ebuchman:
In the longer term we may want to do something like #2639 so we can
validate txs before we commit the block. But even in this case we'd only
want to run the equivalent of CheckTx, which means the DeliverTx could
still fail even if the CheckTx passes depending on how the app handles
the ABCI Code semantics. So more work will be required around the ABCI
code. See also #2185
* add changelog entry and tests
* improve changelog message
* reformat code
2019-05-07 12:25:35 +04:00
|
|
|
mem.logger.Info("Recheck txs", "numtxs", mem.Size(), "height", height)
|
|
|
|
mem.recheckTxs()
|
2019-05-04 10:41:31 +04:00
|
|
|
// At this point, mem.txs are being rechecked.
|
|
|
|
// mem.recheckCursor re-scans mem.txs and possibly removes some txs.
|
|
|
|
// Before mem.Reap(), we should wait for mem.recheckCursor to be nil.
|
|
|
|
} else {
|
|
|
|
mem.notifyTxsAvailable()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update metrics
|
|
|
|
mem.metrics.Size.Set(float64(mem.Size()))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
mempool: remove only valid (Code==0) txs on Update (#3625)
* mempool: remove only valid (Code==0) txs on Update
so evil proposers can't drop valid txs in Commit stage.
Also remove invalid (Code!=0) txs from the cache so they can be
resubmitted.
Fixes #3322
@rickyyangz:
In the end of commit stage, we will update mempool to remove all the txs
in current block.
// Update mempool.
err = blockExec.mempool.Update(
block.Height,
block.Txs,
TxPreCheck(state),
TxPostCheck(state),
)
Assum an account has 3 transactions in the mempool, the sequences are
100, 101 and 102 separately, So an evil proposal can only package the
101 and 102 transactions into its proposal block, and leave 100 still in
mempool, then the two txs will be removed from all validators' mempool
when commit. So the account lost the two valid txs.
@ebuchman:
In the longer term we may want to do something like #2639 so we can
validate txs before we commit the block. But even in this case we'd only
want to run the equivalent of CheckTx, which means the DeliverTx could
still fail even if the CheckTx passes depending on how the app handles
the ABCI Code semantics. So more work will be required around the ABCI
code. See also #2185
* add changelog entry and tests
* improve changelog message
* reformat code
2019-05-07 12:25:35 +04:00
|
|
|
func (mem *CListMempool) recheckTxs() {
|
|
|
|
if mem.Size() == 0 {
|
|
|
|
panic("recheckTxs is called, but the mempool is empty")
|
2019-05-04 10:41:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
atomic.StoreInt32(&mem.rechecking, 1)
|
|
|
|
mem.recheckCursor = mem.txs.Front()
|
|
|
|
mem.recheckEnd = mem.txs.Back()
|
|
|
|
|
|
|
|
// Push txs to proxyAppConn
|
|
|
|
// NOTE: globalCb may be called concurrently.
|
mempool: remove only valid (Code==0) txs on Update (#3625)
* mempool: remove only valid (Code==0) txs on Update
so evil proposers can't drop valid txs in Commit stage.
Also remove invalid (Code!=0) txs from the cache so they can be
resubmitted.
Fixes #3322
@rickyyangz:
In the end of commit stage, we will update mempool to remove all the txs
in current block.
// Update mempool.
err = blockExec.mempool.Update(
block.Height,
block.Txs,
TxPreCheck(state),
TxPostCheck(state),
)
Assum an account has 3 transactions in the mempool, the sequences are
100, 101 and 102 separately, So an evil proposal can only package the
101 and 102 transactions into its proposal block, and leave 100 still in
mempool, then the two txs will be removed from all validators' mempool
when commit. So the account lost the two valid txs.
@ebuchman:
In the longer term we may want to do something like #2639 so we can
validate txs before we commit the block. But even in this case we'd only
want to run the equivalent of CheckTx, which means the DeliverTx could
still fail even if the CheckTx passes depending on how the app handles
the ABCI Code semantics. So more work will be required around the ABCI
code. See also #2185
* add changelog entry and tests
* improve changelog message
* reformat code
2019-05-07 12:25:35 +04:00
|
|
|
for e := mem.txs.Front(); e != nil; e = e.Next() {
|
|
|
|
memTx := e.Value.(*mempoolTx)
|
abci: Refactor CheckTx to notify of recheck (#3744)
As per #2127, this refactors the RequestCheckTx ProtoBuf struct to allow for a flag indicating whether a query is a recheck or not (and allows for possible future, more nuanced states).
In order to pass this extended information through to the ABCI app, the proxy.AppConnMempool (and, for consistency, the proxy.AppConnConsensus) interface seems to need to be refactored along with abcicli.Client.
And, as per this comment, I've made the following modification to the protobuf definition for the RequestCheckTx structure:
enum CheckTxType {
New = 0;
Recheck = 1;
}
message RequestCheckTx {
bytes tx = 1;
CheckTxType type = 2;
}
* Refactor ABCI CheckTx to notify of recheck
As per #2127, this refactors the `RequestCheckTx` ProtoBuf struct to allow for:
1. a flag indicating whether a query is a recheck or not (and allows for
possible future, more nuanced states)
2. an `additional_data` bytes array to provide information for those more
nuanced states.
In order to pass this extended information through to the ABCI app, the
`proxy.AppConnMempool` (and, for consistency, the
`proxy.AppConnConsensus`) interface seems to need to be refactored.
Commits:
* Fix linting issue
* Add CHANGELOG_PENDING entry
* Remove extraneous explicit initialization
* Update ABCI spec doc to include new CheckTx params
* Rename method param for consistency
* Rename CheckTxType enum values and remove additional_data param
2019-07-02 10:14:53 -04:00
|
|
|
mem.proxyAppConn.CheckTxAsync(abci.RequestCheckTx{
|
|
|
|
Tx: memTx.tx,
|
|
|
|
Type: abci.CheckTxType_Recheck,
|
|
|
|
})
|
2019-05-04 10:41:31 +04:00
|
|
|
}
|
mempool: remove only valid (Code==0) txs on Update (#3625)
* mempool: remove only valid (Code==0) txs on Update
so evil proposers can't drop valid txs in Commit stage.
Also remove invalid (Code!=0) txs from the cache so they can be
resubmitted.
Fixes #3322
@rickyyangz:
In the end of commit stage, we will update mempool to remove all the txs
in current block.
// Update mempool.
err = blockExec.mempool.Update(
block.Height,
block.Txs,
TxPreCheck(state),
TxPostCheck(state),
)
Assum an account has 3 transactions in the mempool, the sequences are
100, 101 and 102 separately, So an evil proposal can only package the
101 and 102 transactions into its proposal block, and leave 100 still in
mempool, then the two txs will be removed from all validators' mempool
when commit. So the account lost the two valid txs.
@ebuchman:
In the longer term we may want to do something like #2639 so we can
validate txs before we commit the block. But even in this case we'd only
want to run the equivalent of CheckTx, which means the DeliverTx could
still fail even if the CheckTx passes depending on how the app handles
the ABCI Code semantics. So more work will be required around the ABCI
code. See also #2185
* add changelog entry and tests
* improve changelog message
* reformat code
2019-05-07 12:25:35 +04:00
|
|
|
|
2019-05-04 10:41:31 +04:00
|
|
|
mem.proxyAppConn.FlushAsync()
|
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// mempoolTx is a transaction that successfully ran
|
|
|
|
type mempoolTx struct {
|
|
|
|
height int64 // height that this tx had been validated in
|
|
|
|
gasWanted int64 // amount of gas this tx states it will require
|
|
|
|
tx types.Tx //
|
|
|
|
|
|
|
|
// ids of peers who've sent us this tx (as a map for quick lookups).
|
|
|
|
// senders: PeerID -> bool
|
|
|
|
senders sync.Map
|
|
|
|
}
|
|
|
|
|
|
|
|
// Height returns the height for this transaction
|
|
|
|
func (memTx *mempoolTx) Height() int64 {
|
|
|
|
return atomic.LoadInt64(&memTx.height)
|
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
type txCache interface {
|
|
|
|
Reset()
|
|
|
|
Push(tx types.Tx) bool
|
|
|
|
Remove(tx types.Tx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// mapTxCache maintains a LRU cache of transactions. This only stores the hash
|
|
|
|
// of the tx, due to memory concerns.
|
|
|
|
type mapTxCache struct {
|
|
|
|
mtx sync.Mutex
|
|
|
|
size int
|
|
|
|
map_ map[[sha256.Size]byte]*list.Element
|
|
|
|
list *list.List
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ txCache = (*mapTxCache)(nil)
|
|
|
|
|
|
|
|
// newMapTxCache returns a new mapTxCache.
|
|
|
|
func newMapTxCache(cacheSize int) *mapTxCache {
|
|
|
|
return &mapTxCache{
|
|
|
|
size: cacheSize,
|
|
|
|
map_: make(map[[sha256.Size]byte]*list.Element, cacheSize),
|
|
|
|
list: list.New(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset resets the cache to an empty state.
|
|
|
|
func (cache *mapTxCache) Reset() {
|
|
|
|
cache.mtx.Lock()
|
|
|
|
cache.map_ = make(map[[sha256.Size]byte]*list.Element, cache.size)
|
|
|
|
cache.list.Init()
|
|
|
|
cache.mtx.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Push adds the given tx to the cache and returns true. It returns
|
|
|
|
// false if tx is already in the cache.
|
|
|
|
func (cache *mapTxCache) Push(tx types.Tx) bool {
|
|
|
|
cache.mtx.Lock()
|
|
|
|
defer cache.mtx.Unlock()
|
|
|
|
|
|
|
|
// Use the tx hash in the cache
|
|
|
|
txHash := txKey(tx)
|
|
|
|
if moved, exists := cache.map_[txHash]; exists {
|
|
|
|
cache.list.MoveToBack(moved)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if cache.list.Len() >= cache.size {
|
|
|
|
popped := cache.list.Front()
|
|
|
|
poppedTxHash := popped.Value.([sha256.Size]byte)
|
|
|
|
delete(cache.map_, poppedTxHash)
|
|
|
|
if popped != nil {
|
|
|
|
cache.list.Remove(popped)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
e := cache.list.PushBack(txHash)
|
|
|
|
cache.map_[txHash] = e
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove removes the given tx from the cache.
|
|
|
|
func (cache *mapTxCache) Remove(tx types.Tx) {
|
|
|
|
cache.mtx.Lock()
|
|
|
|
txHash := txKey(tx)
|
|
|
|
popped := cache.map_[txHash]
|
|
|
|
delete(cache.map_, txHash)
|
|
|
|
if popped != nil {
|
|
|
|
cache.list.Remove(popped)
|
|
|
|
}
|
|
|
|
|
|
|
|
cache.mtx.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
type nopTxCache struct{}
|
|
|
|
|
|
|
|
var _ txCache = (*nopTxCache)(nil)
|
|
|
|
|
|
|
|
func (nopTxCache) Reset() {}
|
|
|
|
func (nopTxCache) Push(types.Tx) bool { return true }
|
|
|
|
func (nopTxCache) Remove(types.Tx) {}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// txKey is the fixed length array sha256 hash used as the key in maps.
|
|
|
|
func txKey(tx types.Tx) [sha256.Size]byte {
|
|
|
|
return sha256.Sum256(tx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// txID is the hex encoded hash of the bytes as a types.Tx.
|
|
|
|
func txID(tx []byte) string {
|
|
|
|
return fmt.Sprintf("%X", types.Tx(tx).Hash())
|
|
|
|
}
|