From 51d28507d62b59272b885c4828632c9452f7be39 Mon Sep 17 00:00:00 2001 From: jholdstock Date: Fri, 10 Jul 2026 12:52:11 +0800 Subject: [PATCH] mixclient: Make jitter configurable. Accept the message and peer jitter durations as parameters to NewClient and Dicemix respectively, rather than hardcoding them as package constants. Allowing consumers to set jitter however they please makes the package more flexible. It also enables jitter to be set to zero in tests, which has a very dramatic impact. On my system with a i7-10750H CPU this reduces the total runtime for the mixclient package tests from ~143.4s to ~13.1s. --- mixing/mixclient/client.go | 27 ++++++++++++++++++--------- mixing/mixclient/client_test.go | 12 ++++++++---- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/mixing/mixclient/client.go b/mixing/mixclient/client.go index 17458bddf7..ca980d68e0 100644 --- a/mixing/mixclient/client.go +++ b/mixing/mixclient/client.go @@ -43,8 +43,6 @@ const pairingVersion byte = 2 const ( timeoutDuration = 30 * time.Second maxJitter = timeoutDuration / 10 - msgJitter = 300 * time.Millisecond - peerJitter = maxJitter - msgJitter ) // expiredPRErr indicates that a dicemix session failed to complete due to the @@ -397,10 +395,12 @@ type Client struct { testWaiting chan struct{} testTickC chan time.Time testHooks map[hook]hookFunc + + msgJitter time.Duration } // NewClient creates a wallet's mixing client manager. -func NewClient(w Wallet) *Client { +func NewClient(w Wallet, msgJitter time.Duration) *Client { var prFlags byte err := solverrpc.StartSolver() if err == nil { @@ -421,6 +421,7 @@ func NewClient(w Wallet) *Client { blake256Hasher: blake256.NewHasher256(), epoch: w.Mixpool().Epoch(), stopping: make(chan struct{}), + msgJitter: msgJitter, } } @@ -567,7 +568,7 @@ func (c *Client) sendLocalPeerMsgs(ctx context.Context, deadline time.Time, s *s continue } msg := delayedMsg{ - sendTime: now.Add(p.msgJitter()), + sendTime: now.Add(p.msgJitter(c.msgJitter)), deadline: deadline, m: nil, p: p, @@ -694,7 +695,10 @@ func (c *Client) waitForEpoch(ctx context.Context) (time.Time, error) { } } -func (p *peer) msgJitter() time.Duration { +func (p *peer) msgJitter(msgJitter time.Duration) time.Duration { + if msgJitter == 0 { + return p.jitter + } return p.jitter + rand.Duration(msgJitter) } @@ -717,7 +721,7 @@ func (c *Client) prDelay(ctx context.Context, p *peer) error { wait = sendAfter.Sub(now) sendBefore = sendBefore.Add(c.epoch) } - wait += p.msgJitter() + rand.Duration(sendBefore.Sub(now)) + wait += p.msgJitter(c.msgJitter) + rand.Duration(sendBefore.Sub(now)) timer := time.NewTimer(wait) select { case <-ctx.Done(): @@ -963,7 +967,7 @@ func (c *Client) epochTicker(ctx context.Context) error { } // Dicemix performs a new mixing session for a coinjoin mix transaction. -func (c *Client) Dicemix(ctx context.Context, cj *CoinJoin) error { +func (c *Client) Dicemix(ctx context.Context, cj *CoinJoin, peerJitter time.Duration) error { select { case <-c.warming: case <-ctx.Done(): @@ -977,7 +981,7 @@ func (c *Client) Dicemix(ctx context.Context, cj *CoinJoin) error { p := &peer{ client: c, - jitter: rand.Duration(peerJitter), + jitter: peerJitter, res: make(chan error, 1), pub: pub, priv: priv, @@ -2063,7 +2067,12 @@ DCs: return sesRun, err } - time.Sleep(lowestJitter + rand.Duration(msgJitter)) + var msgJitter time.Duration + if c.msgJitter > 0 { + msgJitter = rand.Duration(c.msgJitter) + } + time.Sleep(lowestJitter + msgJitter) + err = c.wallet.PublishTransaction(context.Background(), cj.tx) if err != nil { return sesRun, err diff --git a/mixing/mixclient/client_test.go b/mixing/mixclient/client_test.go index 012dece7ab..e76c03c494 100644 --- a/mixing/mixclient/client_test.go +++ b/mixing/mixclient/client_test.go @@ -36,6 +36,10 @@ func requireCsppsolver(t *testing.T) { var ( testStartingHeight uint32 = 100 testStartingBlock = chainhash.Hash{100} + + // Zero jitter so tests can run as fast as possible. + msgJitter = time.Duration(0) + peerJitter = time.Duration(0) ) const ( @@ -44,7 +48,7 @@ const ( ) func newTestClient(w *testWallet, logger slog.Logger) *Client { - c := NewClient(w) + c := NewClient(w, msgJitter) c.testWaiting = make(chan struct{}) c.testTickC = make(chan time.Time) c.SetLogger(logger) @@ -231,7 +235,7 @@ func TestHonest(t *testing.T) { for i := range peers { p := peers[i] g.Go(func() error { - return c.Dicemix(ctx, p.cj) + return c.Dicemix(ctx, p.cj, peerJitter) }) } @@ -346,7 +350,7 @@ func testDisruption(t *testing.T, misbehavingID *identity, h hook, f hookFunc) { for _, p := range peers[:len(peers)/2] { p := p g.Go(func() error { - err := c.Dicemix(ctx, p.cj) + err := c.Dicemix(ctx, p.cj, peerJitter) var e *testPeerBlamedError if errors.As(err, &e) { blameErrC <- e @@ -359,7 +363,7 @@ func testDisruption(t *testing.T, misbehavingID *identity, h hook, f hookFunc) { for _, p := range peers[len(peers)/2:] { p := p g.Go(func() error { - err := c2.Dicemix(ctx, p.cj) + err := c2.Dicemix(ctx, p.cj, peerJitter) var e *testPeerBlamedError if errors.As(err, &e) { blameErrC <- e