fix race condition on proposal height for published txs (#2021)
* #1920 try to fix race condition on proposal height for published txs - related to create_empty_blocks=false - published height for accepted tx can be wrong (too low) - use the actual mempool height + 1 for the proposal - expose Height() on mempool * #1920 add initial test for mempool.Height() - not sure how to test the lock - can the mutex reference be of type Locker? -- this way, we can use a "mock" of the mutex to test triggering * #1920 use the ConsensusState height in favor of mempool - gets rid of indirections - doesn't need any "+1" magic * #1920 cosmetic - if we use cs.Height, it's enough to evaluate right before propose * #1920 cleanup TODO and non-needed code * #1920 add changelog entry
This commit is contained in:
parent
c5c1689591
commit
e36ce6f893
|
@ -11,6 +11,9 @@ IMPROVEMENTS:
|
||||||
- [config] Increase default send/recv rates to 5 mB/s
|
- [config] Increase default send/recv rates to 5 mB/s
|
||||||
- [libs/common] Generated gogoproto static marshaller methods
|
- [libs/common] Generated gogoproto static marshaller methods
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
- [mempool] fixed a race condition when create_empty_blocks=false where a transaction is published at an old height
|
||||||
|
|
||||||
|
|
||||||
## 0.22.4
|
## 0.22.4
|
||||||
|
|
||||||
|
|
|
@ -571,8 +571,10 @@ func (cs *ConsensusState) receiveRoutine(maxSteps int) {
|
||||||
var mi msgInfo
|
var mi msgInfo
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case height := <-cs.mempool.TxsAvailable():
|
case txAvailable := <-cs.mempool.TxsAvailable():
|
||||||
cs.handleTxsAvailable(height)
|
if txAvailable {
|
||||||
|
cs.handleTxsAvailable()
|
||||||
|
}
|
||||||
case mi = <-cs.peerMsgQueue:
|
case mi = <-cs.peerMsgQueue:
|
||||||
cs.wal.Write(mi)
|
cs.wal.Write(mi)
|
||||||
// handles proposals, block parts, votes
|
// handles proposals, block parts, votes
|
||||||
|
@ -683,11 +685,12 @@ func (cs *ConsensusState) handleTimeout(ti timeoutInfo, rs cstypes.RoundState) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *ConsensusState) handleTxsAvailable(height int64) {
|
func (cs *ConsensusState) handleTxsAvailable() {
|
||||||
cs.mtx.Lock()
|
cs.mtx.Lock()
|
||||||
defer cs.mtx.Unlock()
|
defer cs.mtx.Unlock()
|
||||||
// we only need to do this for round 0
|
// we only need to do this for round 0
|
||||||
cs.enterPropose(height, 0)
|
cs.Logger.Debug("handling available txs", "height to propose", cs.Height)
|
||||||
|
cs.enterPropose(cs.Height, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
|
@ -78,7 +78,7 @@ type Mempool struct {
|
||||||
recheckCursor *clist.CElement // next expected response
|
recheckCursor *clist.CElement // next expected response
|
||||||
recheckEnd *clist.CElement // re-checking stops here
|
recheckEnd *clist.CElement // re-checking stops here
|
||||||
notifiedTxsAvailable bool
|
notifiedTxsAvailable bool
|
||||||
txsAvailable chan int64 // fires the next height once for each height, when the mempool is not empty
|
txsAvailable chan bool // fires once for each height, when the mempool is not empty
|
||||||
|
|
||||||
// Keep a cache of already-seen txs.
|
// Keep a cache of already-seen txs.
|
||||||
// This reduces the pressure on the proxyApp.
|
// This reduces the pressure on the proxyApp.
|
||||||
|
@ -130,7 +130,7 @@ func NewMempool(
|
||||||
// ensuring it will trigger once every height when transactions are available.
|
// ensuring it will trigger once every height when transactions are available.
|
||||||
// NOTE: not thread safe - should only be called once, on startup
|
// NOTE: not thread safe - should only be called once, on startup
|
||||||
func (mem *Mempool) EnableTxsAvailable() {
|
func (mem *Mempool) EnableTxsAvailable() {
|
||||||
mem.txsAvailable = make(chan int64, 1)
|
mem.txsAvailable = make(chan bool, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogger sets the Logger.
|
// SetLogger sets the Logger.
|
||||||
|
@ -348,7 +348,7 @@ func (mem *Mempool) resCbRecheck(req *abci.Request, res *abci.Response) {
|
||||||
// TxsAvailable returns a channel which fires once for every height,
|
// TxsAvailable returns a channel which fires once for every height,
|
||||||
// and only when transactions are available in the mempool.
|
// and only when transactions are available in the mempool.
|
||||||
// NOTE: the returned channel may be nil if EnableTxsAvailable was not called.
|
// NOTE: the returned channel may be nil if EnableTxsAvailable was not called.
|
||||||
func (mem *Mempool) TxsAvailable() <-chan int64 {
|
func (mem *Mempool) TxsAvailable() <-chan bool {
|
||||||
return mem.txsAvailable
|
return mem.txsAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,11 +358,11 @@ func (mem *Mempool) notifyTxsAvailable() {
|
||||||
}
|
}
|
||||||
if mem.txsAvailable != nil && !mem.notifiedTxsAvailable {
|
if mem.txsAvailable != nil && !mem.notifiedTxsAvailable {
|
||||||
// channel cap is 1, so this will send once
|
// channel cap is 1, so this will send once
|
||||||
|
mem.notifiedTxsAvailable = true
|
||||||
select {
|
select {
|
||||||
case mem.txsAvailable <- mem.height + 1:
|
case mem.txsAvailable <- true:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
mem.notifiedTxsAvailable = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ func newMempoolWithApp(cc proxy.ClientCreator) *Mempool {
|
||||||
return mempool
|
return mempool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureNoFire(t *testing.T, ch <-chan int64, timeoutMS int) {
|
func ensureNoFire(t *testing.T, ch <-chan bool, timeoutMS int) {
|
||||||
timer := time.NewTimer(time.Duration(timeoutMS) * time.Millisecond)
|
timer := time.NewTimer(time.Duration(timeoutMS) * time.Millisecond)
|
||||||
select {
|
select {
|
||||||
case <-ch:
|
case <-ch:
|
||||||
|
@ -47,7 +47,7 @@ func ensureNoFire(t *testing.T, ch <-chan int64, timeoutMS int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureFire(t *testing.T, ch <-chan int64, timeoutMS int) {
|
func ensureFire(t *testing.T, ch <-chan bool, timeoutMS int) {
|
||||||
timer := time.NewTimer(time.Duration(timeoutMS) * time.Millisecond)
|
timer := time.NewTimer(time.Duration(timeoutMS) * time.Millisecond)
|
||||||
select {
|
select {
|
||||||
case <-ch:
|
case <-ch:
|
||||||
|
|
|
@ -27,7 +27,7 @@ type Mempool interface {
|
||||||
Flush()
|
Flush()
|
||||||
FlushAppConn() error
|
FlushAppConn() error
|
||||||
|
|
||||||
TxsAvailable() <-chan int64
|
TxsAvailable() <-chan bool
|
||||||
EnableTxsAvailable()
|
EnableTxsAvailable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ func (m MockMempool) Reap(n int) types.Txs { retur
|
||||||
func (m MockMempool) Update(height int64, txs types.Txs) error { return nil }
|
func (m MockMempool) Update(height int64, txs types.Txs) error { return nil }
|
||||||
func (m MockMempool) Flush() {}
|
func (m MockMempool) Flush() {}
|
||||||
func (m MockMempool) FlushAppConn() error { return nil }
|
func (m MockMempool) FlushAppConn() error { return nil }
|
||||||
func (m MockMempool) TxsAvailable() <-chan int64 { return make(chan int64) }
|
func (m MockMempool) TxsAvailable() <-chan bool { return make(chan bool) }
|
||||||
func (m MockMempool) EnableTxsAvailable() {}
|
func (m MockMempool) EnableTxsAvailable() {}
|
||||||
|
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
|
|
Loading…
Reference in New Issue