Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions sei-tendermint/internal/p2p/handshake_deadline_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package p2p

import (
"context"
"io"
"testing"
"time"

dbm "github.com/tendermint/tm-db"
"golang.org/x/time/rate"

"github.com/sei-protocol/sei-chain/sei-tendermint/crypto/ed25519"
"github.com/sei-protocol/sei-chain/sei-tendermint/internal/p2p/conn"
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils"
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/require"
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/scope"
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/tcp"
"github.com/sei-protocol/sei-chain/sei-tendermint/types"
)

// The node info exchange is part of the handshake and holds an accept-semaphore
// slot, so it has to run under the handshake deadline. A peer that goes quiet
// part way through must be hung up on rather than left holding the slot.
func TestRouter_InboundNodeInfoBoundedByHandshakeDeadline(t *testing.T) {
ctx := t.Context()

privKey := NodeSecretKey(ed25519.GenerateSecretKey())
endpoint := Endpoint{AddrPort: tcp.TestReserveAddr()}
nodeInfo := types.NodeInfo{
NodeID: privKey.Public().NodeID(),
ListenAddr: endpoint.String(),
Moniker: string(privKey.Public().NodeID()),
Network: "test",
}
router, err := NewRouter(
privKey,
func() *types.NodeInfo { return &nodeInfo },
dbm.NewMemDB(),
&RouterOptions{
Endpoint: endpoint,
Connection: conn.DefaultMConnConfig(),
IncomingConnectionWindow: utils.Some[time.Duration](0),
MaxAcceptRate: utils.Some(rate.Inf),
// Short, so the test fails fast rather than waiting out the 10s default.
HandshakeTimeout: utils.Some(100 * time.Millisecond),
},
)
require.NoError(t, err)
require.NoError(t, router.Start(ctx))
require.NoError(t, router.WaitForStart(ctx))
t.Cleanup(router.Stop)

err = scope.Run(ctx, func(ctx context.Context, s scope.Scope) error {
tcpConn, err := tcp.Dial(ctx, endpoint.AddrPort)
if err != nil {
return err
}
s.SpawnBg(func() error { return tcpConn.Run(ctx) })

// Complete the handshake, which authenticates us, then send no node info.
if _, err := handshake(ctx, tcpConn, NodeSecretKey(ed25519.GenerateSecretKey()), handshakeSpec{}); err != nil {
return err
}

// Keep a read outstanding so the connection pump observes the close. Without
// the deadline covering exchangeNodeInfo the router never hangs up and this
// never returns; the go test timeout is the backstop.
buf := make([]byte, 1)
for ctx.Err() == nil {
if err := tcpConn.Read(ctx, buf); err != nil {
break
}
}
return nil
})
// The router hanging up on us surfaces as EOF from the connection pump.
require.ErrorIs(t, err, io.EOF)
}
5 changes: 4 additions & 1 deletion sei-tendermint/internal/p2p/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,10 @@ func (r *Router) acceptPeersRoutine(ctx context.Context) error {
release()
return giga.RunInboundConn(ctx, hConn)
}
info, err := exchangeNodeInfo(ctx, hConn, *r.nodeInfoProducer())
// The node info exchange is part of the handshake, so it runs under
// the same deadline. Without it a peer that loses connectivity
// mid-exchange holds an accept slot for as long as its socket lives.
info, err := exchangeNodeInfo(handshakeCtx, hConn, *r.nodeInfoProducer())
if err != nil {
return fmt.Errorf("exchangeNodeInfo(): %w", err)
}
Expand Down
Loading