package mempool
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
acm "github.com/tendermint/tendermint/account"
|
|
_ "github.com/tendermint/tendermint/config/tendermint_test"
|
|
sm "github.com/tendermint/tendermint/state"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
var someAddr = []byte("ABCDEFGHIJABCDEFGHIJ")
|
|
|
|
// number of txs
|
|
var nTxs = 100
|
|
|
|
// what the ResetInfo should look like after ResetForBlockAndState
|
|
var TestResetInfoData = ResetInfo{
|
|
Included: []Range{
|
|
Range{0, 5},
|
|
Range{10, 10},
|
|
Range{30, 5},
|
|
},
|
|
Invalid: []Range{
|
|
Range{5, 5},
|
|
Range{20, 8}, // let 28 and 29 be valid
|
|
Range{35, 64}, // let 99 be valid
|
|
},
|
|
}
|
|
|
|
// inverse of the ResetInfo
|
|
var notInvalidNotIncluded = map[int]struct{}{
|
|
28: struct{}{},
|
|
29: struct{}{},
|
|
99: struct{}{},
|
|
}
|
|
|
|
func newSendTx(t *testing.T, mempool *Mempool, from *acm.PrivAccount, to []byte, amt int64) types.Tx {
|
|
tx := types.NewSendTx()
|
|
tx.AddInput(mempool.GetCache(), from.PubKey, amt)
|
|
tx.AddOutput(to, amt)
|
|
tx.SignInput(config.GetString("chain_id"), 0, from)
|
|
if err := mempool.AddTx(tx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return tx
|
|
}
|
|
|
|
func addTxs(t *testing.T, mempool *Mempool, lastAcc *acm.PrivAccount, privAccs []*acm.PrivAccount) []types.Tx {
|
|
txs := make([]types.Tx, nTxs)
|
|
for i := 0; i < nTxs; i++ {
|
|
if _, ok := notInvalidNotIncluded[i]; ok {
|
|
txs[i] = newSendTx(t, mempool, lastAcc, someAddr, 10)
|
|
} else {
|
|
txs[i] = newSendTx(t, mempool, privAccs[i%len(privAccs)], privAccs[(i+1)%len(privAccs)].Address, 5)
|
|
}
|
|
}
|
|
return txs
|
|
}
|
|
|
|
func makeBlock(mempool *Mempool) *types.Block {
|
|
txs := mempool.GetProposalTxs()
|
|
var includedTxs []types.Tx
|
|
for _, rid := range TestResetInfoData.Included {
|
|
includedTxs = append(includedTxs, txs[rid.Start:rid.Start+rid.Length]...)
|
|
}
|
|
|
|
mempool.mtx.Lock()
|
|
state := mempool.state
|
|
state.LastBlockHeight += 1
|
|
mempool.mtx.Unlock()
|
|
return &types.Block{
|
|
Header: &types.Header{
|
|
ChainID: state.ChainID,
|
|
Height: state.LastBlockHeight,
|
|
NumTxs: len(includedTxs),
|
|
},
|
|
Data: &types.Data{
|
|
Txs: includedTxs,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Add txs. Grab chunks to put in block. All the others become invalid because of nonce errors except those in notInvalidNotIncluded
|
|
func TestResetInfo(t *testing.T) {
|
|
amtPerAccount := int64(100000)
|
|
state, privAccs, _ := sm.RandGenesisState(6, false, amtPerAccount, 1, true, 100)
|
|
|
|
mempool := NewMempool(state)
|
|
|
|
lastAcc := privAccs[5] // we save him (his tx wont become invalid)
|
|
privAccs = privAccs[:5]
|
|
|
|
txs := addTxs(t, mempool, lastAcc, privAccs)
|
|
|
|
// its actually an invalid block since we're skipping nonces
|
|
// but all we care about is how the mempool responds after
|
|
block := makeBlock(mempool)
|
|
|
|
ri := mempool.ResetForBlockAndState(block, state)
|
|
|
|
if len(ri.Included) != len(TestResetInfoData.Included) {
|
|
t.Fatalf("invalid number of included ranges. Got %d, expected %d\n", len(ri.Included), len(TestResetInfoData.Included))
|
|
}
|
|
|
|
if len(ri.Invalid) != len(TestResetInfoData.Invalid) {
|
|
t.Fatalf("invalid number of invalid ranges. Got %d, expected %d\n", len(ri.Invalid), len(TestResetInfoData.Invalid))
|
|
}
|
|
|
|
for i, rid := range ri.Included {
|
|
inc := TestResetInfoData.Included[i]
|
|
if rid.Start != inc.Start {
|
|
t.Fatalf("Invalid start of range. Got %d, expected %d\n", inc.Start, rid.Start)
|
|
}
|
|
if rid.Length != inc.Length {
|
|
t.Fatalf("Invalid length of range. Got %d, expected %d\n", inc.Length, rid.Length)
|
|
}
|
|
}
|
|
|
|
txs = mempool.GetProposalTxs()
|
|
if len(txs) != len(notInvalidNotIncluded) {
|
|
t.Fatalf("Expected %d txs left in mempool. Got %d", len(notInvalidNotIncluded), len(txs))
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------------------
|
|
|
|
type TestPeer struct {
|
|
sync.Mutex
|
|
running bool
|
|
height int
|
|
|
|
t *testing.T
|
|
|
|
received int
|
|
txs map[string]int
|
|
|
|
timeoutFail int
|
|
|
|
done chan int
|
|
}
|
|
|
|
func newPeer(t *testing.T, state *sm.State) *TestPeer {
|
|
return &TestPeer{
|
|
running: true,
|
|
height: state.LastBlockHeight,
|
|
t: t,
|
|
txs: make(map[string]int),
|
|
done: make(chan int),
|
|
}
|
|
}
|
|
|
|
func (tp *TestPeer) IsRunning() bool {
|
|
tp.Lock()
|
|
defer tp.Unlock()
|
|
return tp.running
|
|
}
|
|
|
|
func (tp *TestPeer) SetRunning(running bool) {
|
|
tp.Lock()
|
|
defer tp.Unlock()
|
|
tp.running = running
|
|
}
|
|
|
|
func (tp *TestPeer) Send(chID byte, msg interface{}) bool {
|
|
if tp.timeoutFail > 0 {
|
|
time.Sleep(time.Second * time.Duration(tp.timeoutFail))
|
|
return false
|
|
}
|
|
tx := msg.(*TxMessage).Tx
|
|
id := types.TxID(config.GetString("chain_id"), tx)
|
|
if _, ok := tp.txs[string(id)]; ok {
|
|
tp.t.Fatal("received the same tx twice!")
|
|
}
|
|
tp.txs[string(id)] = tp.received
|
|
tp.received += 1
|
|
tp.done <- tp.received
|
|
return true
|
|
}
|
|
|
|
func (tp *TestPeer) Get(key string) interface{} {
|
|
return tp
|
|
}
|
|
|
|
func (tp *TestPeer) GetHeight() int {
|
|
return tp.height
|
|
}
|
|
|
|
func TestBroadcast(t *testing.T) {
|
|
state, privAccs, _ := sm.RandGenesisState(6, false, 10000, 1, true, 100)
|
|
mempool := NewMempool(state)
|
|
reactor := NewMempoolReactor(mempool)
|
|
reactor.Start()
|
|
|
|
lastAcc := privAccs[5] // we save him (his tx wont become invalid)
|
|
privAccs = privAccs[:5]
|
|
|
|
peer := newPeer(t, state)
|
|
newBlockChan := make(chan ResetInfo)
|
|
tickerChan := make(chan time.Time)
|
|
go reactor.broadcastTxRoutine(tickerChan, newBlockChan, peer)
|
|
|
|
// we don't broadcast any before updating
|
|
fmt.Println("dont broadcast any")
|
|
addTxs(t, mempool, lastAcc, privAccs)
|
|
block := makeBlock(mempool)
|
|
ri := mempool.ResetForBlockAndState(block, state)
|
|
newBlockChan <- ri
|
|
peer.height = ri.Height
|
|
tickerChan <- time.Now()
|
|
pullTxs(t, peer, len(mempool.txs)) // should have sent whatever txs are left (3)
|
|
|
|
toBroadcast := []int{1, 3, 7, 9, 11, 12, 18, 20, 21, 28, 29, 30, 31, 34, 35, 36, 50, 90, 99, 100}
|
|
for _, N := range toBroadcast {
|
|
peer = resetPeer(t, reactor, mempool, state, tickerChan, newBlockChan, peer)
|
|
|
|
// we broadcast N txs before updating
|
|
fmt.Println("broadcast", N)
|
|
addTxs(t, mempool, lastAcc, privAccs)
|
|
txsToSendPerCheck = N
|
|
tickerChan <- time.Now()
|
|
pullTxs(t, peer, txsToSendPerCheck) // should have sent N txs
|
|
block = makeBlock(mempool)
|
|
ri := mempool.ResetForBlockAndState(block, state)
|
|
newBlockChan <- ri
|
|
peer.height = ri.Height
|
|
txsToSendPerCheck = 100
|
|
tickerChan <- time.Now()
|
|
left := len(mempool.txs)
|
|
if N > 99 {
|
|
left -= 3
|
|
} else if N > 29 {
|
|
left -= 2
|
|
} else if N > 28 {
|
|
left -= 1
|
|
}
|
|
pullTxs(t, peer, left) // should have sent whatever txs are left that havent been sent
|
|
}
|
|
}
|
|
|
|
func pullTxs(t *testing.T, peer *TestPeer, N int) {
|
|
timer := time.NewTicker(time.Second * 2)
|
|
for i := 0; i < N; i++ {
|
|
select {
|
|
case <-peer.done:
|
|
case <-timer.C:
|
|
panic(fmt.Sprintf("invalid number of received messages. Got %d, expected %d\n", i, N))
|
|
}
|
|
}
|
|
|
|
if N == 0 {
|
|
select {
|
|
case <-peer.done:
|
|
t.Fatalf("should not have sent any more txs")
|
|
case <-timer.C:
|
|
}
|
|
}
|
|
}
|
|
|
|
func resetPeer(t *testing.T, reactor *MempoolReactor, mempool *Mempool, state *sm.State, tickerChan chan time.Time, newBlockChan chan ResetInfo, peer *TestPeer) *TestPeer {
|
|
// reset peer
|
|
mempool.txs = []types.Tx{}
|
|
mempool.state = state
|
|
mempool.cache = sm.NewBlockCache(state)
|
|
peer.SetRunning(false)
|
|
tickerChan <- time.Now()
|
|
peer = newPeer(t, state)
|
|
go reactor.broadcastTxRoutine(tickerChan, newBlockChan, peer)
|
|
return peer
|
|
}
|