mirror of
https://github.com/tendermint/tendermint.git
synced 2026-08-21 22:56:22 +00:00
@@ -242,9 +242,6 @@ func (mem *CListMempool) CheckTx(tx types.Tx, cb func(*abci.Response), txInfo Tx
|
||||
return err
|
||||
}
|
||||
|
||||
// The size of the corresponding TxMessage
|
||||
// can't be larger than the maxMsgSize, otherwise we can't
|
||||
// relay it to peers.
|
||||
if txSize > mem.config.MaxTxBytes {
|
||||
return ErrTxTooLarge{mem.config.MaxTxBytes, txSize}
|
||||
}
|
||||
|
||||
@@ -420,52 +420,43 @@ func TestMempoolCloseWAL(t *testing.T) {
|
||||
require.Equal(t, 1, len(m3), "expecting the wal match in")
|
||||
}
|
||||
|
||||
func TestMempoolMaxMsgSize(t *testing.T) {
|
||||
func TestMempool_CheckTxChecksTxSize(t *testing.T) {
|
||||
app := kvstore.NewApplication()
|
||||
cc := proxy.NewLocalClientCreator(app)
|
||||
mempl, cleanup := newMempoolWithApp(cc)
|
||||
defer cleanup()
|
||||
|
||||
maxTxSize := mempl.config.MaxTxBytes
|
||||
maxMsgSize := calcMaxMsgSize(maxTxSize)
|
||||
|
||||
testCases := []struct {
|
||||
len int
|
||||
err bool
|
||||
}{
|
||||
// check small txs. no error
|
||||
{10, false},
|
||||
{1000, false},
|
||||
{1000000, false},
|
||||
0: {10, false},
|
||||
1: {1000, false},
|
||||
2: {1000000, false},
|
||||
|
||||
// check around maxTxSize
|
||||
// changes from no error to error
|
||||
{maxTxSize - 2, false},
|
||||
{maxTxSize - 1, false},
|
||||
{maxTxSize, false},
|
||||
{maxTxSize + 1, true},
|
||||
{maxTxSize + 2, true},
|
||||
|
||||
// check around maxMsgSize. all error
|
||||
{maxMsgSize - 1, true},
|
||||
{maxMsgSize, true},
|
||||
{maxMsgSize + 1, true},
|
||||
3: {maxTxSize - 1, false},
|
||||
4: {maxTxSize, false},
|
||||
5: {maxTxSize + 1, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
caseString := fmt.Sprintf("case %d, len %d", i, testCase.len)
|
||||
|
||||
tx := tmrand.Bytes(testCase.len)
|
||||
|
||||
err := mempl.CheckTx(tx, nil, TxInfo{})
|
||||
bv := gogotypes.BytesValue{Value: tx}
|
||||
bz, err2 := bv.Marshal()
|
||||
require.NoError(t, err2)
|
||||
require.Equal(t, len(bz), proto.Size(&bv), caseString)
|
||||
|
||||
if !testCase.err {
|
||||
require.True(t, len(bz) <= maxMsgSize, caseString)
|
||||
require.NoError(t, err, caseString)
|
||||
} else {
|
||||
require.True(t, len(bz) > maxMsgSize, caseString)
|
||||
require.Equal(t, err, ErrTxTooLarge{maxTxSize, testCase.len}, caseString)
|
||||
}
|
||||
}
|
||||
|
||||
+75
-37
@@ -1,6 +1,7 @@
|
||||
package mempool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
@@ -17,8 +18,6 @@ import (
|
||||
const (
|
||||
MempoolChannel = byte(0x30)
|
||||
|
||||
protoOverheadForTxMessage = 4
|
||||
|
||||
peerCatchupSleepIntervalMS = 100 // If peer is behind, sleep this amount
|
||||
|
||||
// UnknownPeerID is the peer ID to use when running CheckTx when there is
|
||||
@@ -132,10 +131,10 @@ func (memR *Reactor) OnStart() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChannels implements Reactor.
|
||||
// It returns the list of channels for this reactor.
|
||||
// GetChannels implements Reactor by returning the list of channels for this
|
||||
// reactor.
|
||||
func (memR *Reactor) GetChannels() []*p2p.ChannelDescriptor {
|
||||
maxMsgSize := calcMaxMsgSize(memR.config.MaxTxBytes)
|
||||
maxMsgSize := memR.config.MaxBatchBytes
|
||||
return []*p2p.ChannelDescriptor{
|
||||
{
|
||||
ID: MempoolChannel,
|
||||
@@ -174,9 +173,11 @@ func (memR *Reactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
|
||||
if src != nil {
|
||||
txInfo.SenderP2PID = src.ID()
|
||||
}
|
||||
err = memR.mempool.CheckTx(msg.Tx, nil, txInfo)
|
||||
if err != nil {
|
||||
memR.Logger.Info("Could not check tx", "tx", txID(msg.Tx), "err", err)
|
||||
for _, tx := range msg.Txs {
|
||||
err = memR.mempool.CheckTx(tx, nil, txInfo)
|
||||
if err != nil {
|
||||
memR.Logger.Info("Could not check tx", "tx", txID(tx), "err", err)
|
||||
}
|
||||
}
|
||||
// broadcasting happens from go routines per peer
|
||||
}
|
||||
@@ -190,6 +191,7 @@ type PeerState interface {
|
||||
func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) {
|
||||
peerID := memR.ids.GetForPeer(peer)
|
||||
var next *clist.CElement
|
||||
|
||||
for {
|
||||
// In case of both next.NextWaitChan() and peer.Quit() are variable at the same time
|
||||
if !memR.IsRunning() || !peer.IsRunning() {
|
||||
@@ -211,9 +213,7 @@ func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) {
|
||||
}
|
||||
}
|
||||
|
||||
memTx := next.Value.(*mempoolTx)
|
||||
|
||||
// make sure the peer is up to date
|
||||
// Make sure the peer is up to date.
|
||||
peerState, ok := peer.Get(types.PeerStateKey).(PeerState)
|
||||
if !ok {
|
||||
// Peer does not have a state yet. We set it in the consensus reactor, but
|
||||
@@ -224,25 +224,28 @@ func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) {
|
||||
time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
if peerState.GetHeight() < memTx.Height()-1 { // Allow for a lag of 1 block
|
||||
|
||||
// Allow for a lag of 1 block.
|
||||
memTx := next.Value.(*mempoolTx)
|
||||
if peerState.GetHeight() < memTx.Height()-1 {
|
||||
time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
// ensure peer hasn't already sent us this tx
|
||||
if _, ok := memTx.senders.Load(peerID); !ok {
|
||||
txs := memR.txs(next, peerID, peerState.GetHeight()) // WARNING: mutates next!
|
||||
|
||||
// send txs
|
||||
if len(txs) > 0 {
|
||||
msg := protomem.Message{
|
||||
Sum: &protomem.Message_Tx{
|
||||
Tx: &protomem.Tx{
|
||||
Tx: []byte(memTx.tx),
|
||||
},
|
||||
Sum: &protomem.Message_Txs{
|
||||
Txs: &protomem.Txs{Txs: txs},
|
||||
},
|
||||
}
|
||||
|
||||
bz, err := msg.Marshal()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
memR.Logger.Debug("Sending N txs to peer", "N", len(txs), "peer", peer)
|
||||
success := peer.Send(MempoolChannel, bz)
|
||||
if !success {
|
||||
time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond)
|
||||
@@ -262,21 +265,62 @@ func (memR *Reactor) broadcastTxRoutine(peer p2p.Peer) {
|
||||
}
|
||||
}
|
||||
|
||||
// txs iterates over the transaction list and builds a batch of txs. next is
|
||||
// included.
|
||||
// WARNING: mutates next!
|
||||
func (memR *Reactor) txs(next *clist.CElement, peerID uint16, peerHeight int64) [][]byte {
|
||||
batch := make([][]byte, 0)
|
||||
|
||||
for {
|
||||
memTx := next.Value.(*mempoolTx)
|
||||
|
||||
if _, ok := memTx.senders.Load(peerID); !ok {
|
||||
// If current batch + this tx size is greater than max => return.
|
||||
batchMsg := protomem.Message{
|
||||
Sum: &protomem.Message_Txs{
|
||||
Txs: &protomem.Txs{Txs: append(batch, memTx.tx)},
|
||||
},
|
||||
}
|
||||
if batchMsg.Size() > memR.config.MaxBatchBytes {
|
||||
return batch
|
||||
}
|
||||
|
||||
batch = append(batch, memTx.tx)
|
||||
}
|
||||
|
||||
if next.Next() == nil {
|
||||
return batch
|
||||
}
|
||||
next = next.Next()
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Messages
|
||||
|
||||
func (memR *Reactor) decodeMsg(bz []byte) (TxMessage, error) {
|
||||
func (memR *Reactor) decodeMsg(bz []byte) (TxsMessage, error) {
|
||||
msg := protomem.Message{}
|
||||
err := msg.Unmarshal(bz)
|
||||
if err != nil {
|
||||
return TxMessage{}, err
|
||||
return TxsMessage{}, err
|
||||
}
|
||||
|
||||
var message TxMessage
|
||||
var message TxsMessage
|
||||
|
||||
if i, ok := msg.Sum.(*protomem.Message_Tx); ok {
|
||||
message = TxMessage{
|
||||
Tx: types.Tx(i.Tx.GetTx()),
|
||||
if i, ok := msg.Sum.(*protomem.Message_Txs); ok {
|
||||
txs := i.Txs.GetTxs()
|
||||
|
||||
if len(txs) == 0 {
|
||||
return message, errors.New("empty TxsMessage")
|
||||
}
|
||||
|
||||
decoded := make([]types.Tx, len(txs))
|
||||
for j, tx := range txs {
|
||||
decoded[j] = types.Tx(tx)
|
||||
}
|
||||
|
||||
message = TxsMessage{
|
||||
Txs: decoded,
|
||||
}
|
||||
return message, nil
|
||||
}
|
||||
@@ -285,18 +329,12 @@ func (memR *Reactor) decodeMsg(bz []byte) (TxMessage, error) {
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
// TxMessage is a Message containing a transaction.
|
||||
type TxMessage struct {
|
||||
Tx types.Tx
|
||||
// TxsMessage is a Message containing transactions.
|
||||
type TxsMessage struct {
|
||||
Txs []types.Tx
|
||||
}
|
||||
|
||||
// String returns a string representation of the TxMessage.
|
||||
func (m *TxMessage) String() string {
|
||||
return fmt.Sprintf("[TxMessage %v]", m.Tx)
|
||||
}
|
||||
|
||||
// calcMaxMsgSize returns the max size of TxMessage
|
||||
// account for proto overhead of bytesValue
|
||||
func calcMaxMsgSize(maxTxSize int) int {
|
||||
return maxTxSize + protoOverheadForTxMessage
|
||||
// String returns a string representation of the TxsMessage.
|
||||
func (m *TxsMessage) String() string {
|
||||
return fmt.Sprintf("[TxsMessage %v]", m.Txs)
|
||||
}
|
||||
|
||||
+44
-5
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmrand "github.com/tendermint/tendermint/libs/rand"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/p2p/mock"
|
||||
memproto "github.com/tendermint/tendermint/proto/tendermint/mempool"
|
||||
@@ -38,7 +39,7 @@ func (ps peerState) GetHeight() int64 {
|
||||
|
||||
// Send a bunch of txs to the first reactor's mempool and wait for them all to
|
||||
// be received in the others.
|
||||
func TestReactorBroadcastTxMessage(t *testing.T) {
|
||||
func TestReactorBroadcastTxsMessage(t *testing.T) {
|
||||
config := cfg.TestConfig()
|
||||
// if there were more than two reactors, the order of transactions could not be
|
||||
// asserted in waitForTxsOnReactors (due to transactions gossiping). If we
|
||||
@@ -87,6 +88,46 @@ func TestReactorNoBroadcastToSender(t *testing.T) {
|
||||
ensureNoTxs(t, reactors[peerID], 100*time.Millisecond)
|
||||
}
|
||||
|
||||
func TestReactor_MaxBatchBytes(t *testing.T) {
|
||||
config := cfg.TestConfig()
|
||||
config.Mempool.MaxBatchBytes = 1024
|
||||
|
||||
const N = 2
|
||||
reactors := makeAndConnectReactors(config, N)
|
||||
defer func() {
|
||||
for _, r := range reactors {
|
||||
if err := r.Stop(); err != nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
for _, r := range reactors {
|
||||
for _, peer := range r.Switch.Peers().List() {
|
||||
peer.Set(types.PeerStateKey, peerState{1})
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast a tx, which has the max size (minus proto overhead)
|
||||
// => ensure it's received by the second reactor.
|
||||
tx1 := tmrand.Bytes(1018)
|
||||
err := reactors[0].mempool.CheckTx(tx1, nil, TxInfo{SenderID: UnknownPeerID})
|
||||
require.NoError(t, err)
|
||||
waitForTxsOnReactors(t, []types.Tx{tx1}, reactors)
|
||||
|
||||
reactors[0].mempool.Flush()
|
||||
reactors[1].mempool.Flush()
|
||||
|
||||
// Broadcast a tx, which is beyond the max size
|
||||
// => ensure it's not sent
|
||||
tx2 := tmrand.Bytes(1020)
|
||||
err = reactors[0].mempool.CheckTx(tx2, nil, TxInfo{SenderID: UnknownPeerID})
|
||||
require.NoError(t, err)
|
||||
ensureNoTxs(t, reactors[1], 100*time.Millisecond)
|
||||
// => ensure the second reactor did not disconnect from us
|
||||
out, in, _ := reactors[1].Switch.NumPeers()
|
||||
assert.Equal(t, 1, out+in)
|
||||
}
|
||||
|
||||
func TestBroadcastTxForPeerStopsWhenPeerStops(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
@@ -266,7 +307,6 @@ func ensureNoTxs(t *testing.T, reactor *Reactor, timeout time.Duration) {
|
||||
}
|
||||
|
||||
func TestMempoolVectors(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
testName string
|
||||
tx []byte
|
||||
@@ -280,8 +320,8 @@ func TestMempoolVectors(t *testing.T) {
|
||||
tc := tc
|
||||
|
||||
msg := memproto.Message{
|
||||
Sum: &memproto.Message_Tx{
|
||||
Tx: &memproto.Tx{Tx: tc.tx},
|
||||
Sum: &memproto.Message_Txs{
|
||||
Txs: &memproto.Txs{Txs: [][]byte{tc.tx}},
|
||||
},
|
||||
}
|
||||
bz, err := msg.Marshal()
|
||||
@@ -289,5 +329,4 @@ func TestMempoolVectors(t *testing.T) {
|
||||
|
||||
require.Equal(t, tc.expBytes, hex.EncodeToString(bz), tc.testName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user