Skip to content

Commit 27f3cc1

Browse files
authored
update the way we manage nonce (#812)
* update the way we manage nonce * uncomment code
1 parent a01b1c8 commit 27f3cc1

File tree

5 files changed

+52
-22
lines changed

5 files changed

+52
-22
lines changed

build/devenv/cciptestinterfaces/interface.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66
"fmt"
77
"math/big"
8-
"sync/atomic"
98
"time"
109

1110
"github.com/ethereum/go-ethereum/accounts/abi/bind"
@@ -153,7 +152,8 @@ type Chain interface {
153152
// SendMessage sends a CCIP message to the specified destination chain with the specified message options.
154153
SendMessage(ctx context.Context, dest uint64, fields MessageFields, opts MessageOptions) (MessageSentEvent, error)
155154
// SendMessageWithNonce sends a CCIP message to the specified destination chain with the specified message options and nonce.
156-
SendMessageWithNonce(ctx context.Context, dest uint64, fields MessageFields, opts MessageOptions, sender *bind.TransactOpts, nonce *atomic.Uint64, disableTokenAmountCheck bool) (MessageSentEvent, error)
155+
// A nil nonce instructs the client to use the pending nonce from the RPC node.
156+
SendMessageWithNonce(ctx context.Context, dest uint64, fields MessageFields, opts MessageOptions, sender *bind.TransactOpts, nonce *uint64, disableTokenAmountCheck bool) (MessageSentEvent, error)
157157
// GetUserNonce returns the nonce for the given user address on this chain.
158158
GetUserNonce(ctx context.Context, userAddress protocol.UnknownAddress) (uint64, error)
159159
// GetExpectedNextSequenceNumber gets an expected sequence number for message to the specified destination chain.

build/devenv/evm/impl.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ func (m *CCIP17EVM) SendMessage(ctx context.Context, dest uint64, fields cciptes
576576
return m.SendMessageWithNonce(ctx, dest, fields, opts, nil, nil, false)
577577
}
578578

579-
func (m *CCIP17EVM) SendMessageWithNonce(ctx context.Context, dest uint64, fields cciptestinterfaces.MessageFields, opts cciptestinterfaces.MessageOptions, sender *bind.TransactOpts, nonce *atomic.Uint64, disableTokenAmountCheck bool) (cciptestinterfaces.MessageSentEvent, error) {
579+
func (m *CCIP17EVM) SendMessageWithNonce(ctx context.Context, dest uint64, fields cciptestinterfaces.MessageFields, opts cciptestinterfaces.MessageOptions, sender *bind.TransactOpts, nonce *uint64, disableTokenAmountCheck bool) (cciptestinterfaces.MessageSentEvent, error) {
580580
l := m.logger
581581
srcChain := m.chain
582582
if sender == nil {
@@ -640,24 +640,20 @@ func (m *CCIP17EVM) SendMessageWithNonce(ctx context.Context, dest uint64, field
640640
return cciptestinterfaces.MessageSentEvent{}, err
641641
}
642642

643-
var loadNonce *big.Int = nil
643+
var loadNonce *big.Int
644644
if nonce != nil {
645-
loadNonce = big.NewInt(int64(nonce.Load()))
645+
loadNonce = new(big.Int).SetUint64(*nonce)
646646
}
647647
senderKeyCopy := &bind.TransactOpts{
648648
From: sender.From,
649649
Signer: sender.Signer,
650650
Nonce: loadNonce,
651651
Value: msgValue,
652652
}
653-
fmt.Printf("sender: %s, srcChain: %d, nonce: %s\n", senderKeyCopy.From.String(), srcChain.Selector, loadNonce.String())
654653
tx, err := rout.CcipSend(senderKeyCopy, dest, msg)
655654
if err != nil {
656655
return cciptestinterfaces.MessageSentEvent{}, fmt.Errorf("failed to send CCIP message: %w, extraArgs: %x", err, extraArgs)
657656
}
658-
if nonce != nil {
659-
nonce.Add(1)
660-
}
661657
txHash := tx.Hash()
662658

663659
_, err = srcChain.Confirm(tx)

build/devenv/staging-load.toml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,30 @@ message_profiles=[
33
{name="PTT", hasData=true, hasToken=true},
44
{name="token only", hasToken=true},
55
]
6-
76
[[test_profiles]]
87
enabled=true
8+
chains_as_source=[
9+
{selector="16281711391670634445", ratio=1}, # polygon amoy
10+
]
11+
chains_as_dest=[
12+
{selector="14767482510784806043", ratio=1}, # avalanche fuji
13+
{selector="16015286601757825753", ratio=1}, # ethereum sepolia
14+
{selector="10344971235874465080", ratio=1}, # arbitrum sepolia
15+
{selector="3478487238524512106", ratio=1}, # base sepolia
16+
]
17+
messages=[
18+
{ratio=100, message_profile="data only"},
19+
# {ratio=35, message_profile="PTT"},
20+
# {ratio=53, message_profile="token only"}
21+
]
22+
test_duration = "2m"
23+
# Should be "integer/duration" like "1/1s" or "10/5m"
24+
message_rate = "4/1s"
25+
load_duration = "30m"
26+
27+
28+
[[test_profiles]]
29+
enabled=false
930
chains_as_source=[
1031
{selector="16015286601757825753", ratio=1}, # ethereum sepolia
1132
{selector="3478487238524512106", ratio=1}, # arbitrum sepolia
@@ -28,7 +49,7 @@ message_profiles=[
2849

2950

3051
[[test_profiles]]
31-
enabled=true
52+
enabled=false
3253
chains_as_source=[
3354
{selector="16281711391670634445", ratio=1}, # polygon amoy
3455
{selector="14767482510784806043", ratio=1}, # avalanche-fuji

build/devenv/tests/e2e/gun.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ type EVMTXGun struct {
6363
seqNosMu sync.Mutex
6464
sentMsgCh chan SentMessage // Channel for real-time message notifications
6565
closeOnce sync.Once // Ensure channel is closed only once
66-
nonceMu sync.Mutex
67-
nonce map[NonceKey]*atomic.Uint64
66+
nonce sync.Map // map[NonceKey]*uint64
6867
messageProfiles []load.MessageProfileConfig
6968
userSelector map[uint64]func() *bind.TransactOpts
7069
}
@@ -88,7 +87,6 @@ func NewEVMTransactionGun(cfg *ccv.Cfg, e *deployment.Environment, selectors []u
8887
impl: impls,
8988
sentMsgSet: make(map[SentMessage]struct{}),
9089
sentMsgCh: make(chan SentMessage, sentMessageChannelBufferSize),
91-
nonce: make(map[NonceKey]*atomic.Uint64),
9290
srcSelectors: srcSelectors,
9391
destSelectors: destSelectors,
9492
userSelector: userSelector,
@@ -123,7 +121,6 @@ func NewEVMTransactionGunFromTestConfig(cfg *ccv.Cfg, testProfile *load.TestProf
123121
impl: impls,
124122
sentMsgSet: make(map[SentMessage]struct{}),
125123
sentMsgCh: make(chan SentMessage, sentMessageChannelBufferSize),
126-
nonce: make(map[NonceKey]*atomic.Uint64),
127124
srcSelectors: srcSelectors,
128125
destSelectors: destSelectors,
129126
messageProfiles: messageProfiles,
@@ -132,10 +129,7 @@ func NewEVMTransactionGunFromTestConfig(cfg *ccv.Cfg, testProfile *load.TestProf
132129
}
133130

134131
func (m *EVMTXGun) initNonce(key NonceKey, userAddress common.Address) error {
135-
m.nonceMu.Lock()
136-
defer m.nonceMu.Unlock()
137-
138-
if m.nonce[key] != nil {
132+
if _, loaded := m.nonce.Load(key); loaded {
139133
return nil
140134
}
141135

@@ -144,8 +138,12 @@ func (m *EVMTXGun) initNonce(key NonceKey, userAddress common.Address) error {
144138
return fmt.Errorf("failed to get pending nonce for selector %d: %w", key.Selector, err)
145139
}
146140

147-
m.nonce[key] = &atomic.Uint64{}
148-
m.nonce[key].Store(n)
141+
// Allocate a pointer so the stored value can be incremented atomically across
142+
// goroutines without replacing the map entry. LoadOrStore ensures exactly one
143+
// pointer wins even if multiple goroutines race through initialization.
144+
ptr := new(uint64)
145+
*ptr = n
146+
m.nonce.LoadOrStore(key, ptr)
149147
return nil
150148
}
151149

@@ -174,6 +172,15 @@ func (m *EVMTXGun) Call(_ *wasp.Generator) *wasp.Response {
174172
return &wasp.Response{Error: err.Error(), Failed: true}
175173
}
176174

175+
nonceVal, ok := m.nonce.Load(nonceKey)
176+
if !ok {
177+
return &wasp.Response{Error: fmt.Sprintf("nonce not initialized for key %+v", nonceKey), Failed: true}
178+
}
179+
noncePtr := nonceVal.(*uint64)
180+
// Atomically claim the next nonce. AddUint64 returns the new value, so
181+
// subtracting 1 gives us the nonce we own exclusively for this send.
182+
currentNonce := atomic.AddUint64(noncePtr, 1) - 1
183+
177184
b := ccv.NewDefaultCLDFBundle(m.e)
178185
m.e.OperationsBundle = b
179186

@@ -182,7 +189,7 @@ func (m *EVMTXGun) Call(_ *wasp.Generator) *wasp.Response {
182189
return &wasp.Response{Error: "impl is not CCIP17EVM", Failed: true}
183190
}
184191

185-
sentEvent, err := c.SendMessageWithNonce(ctx, destSelector, fields, opts, sender, m.nonce[nonceKey], true)
192+
sentEvent, err := c.SendMessageWithNonce(ctx, destSelector, fields, opts, sender, &currentNonce, true)
186193
if err != nil {
187194
return &wasp.Response{Error: fmt.Errorf("failed to send message: %w", err).Error(), Failed: true}
188195
}

build/devenv/tests/e2e/load_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,9 @@ func TestStaging(t *testing.T) {
755755

756756
var wg sync.WaitGroup
757757
for _, testProfile := range testConfig.TestProfiles {
758+
if !testProfile.Enabled {
759+
continue
760+
}
758761
for _, chainInfo := range testProfile.ChainsAsSource {
759762
wg.Add(1)
760763
go func(chainInfo load.ChainProfileConfig) {
@@ -771,6 +774,9 @@ func TestStaging(t *testing.T) {
771774
}
772775
wg.Wait()
773776

777+
// Wait for old txns and nonces to settled before we start the load test
778+
time.Sleep(30 * time.Second)
779+
774780
for idx, testProfile := range testConfig.TestProfiles {
775781
if !testProfile.Enabled {
776782
continue

0 commit comments

Comments
 (0)