@@ -37,14 +37,13 @@ type TxInput struct {
3737type GetTxInfo interface {
3838 GetTimestamp () int64
3939 GetRecentBlockhash () solana.Hash
40- }
4140
42- // GetDurableNonceInfo is an interface to retrieve durable nonce information from a TxInput.
43- type GetDurableNonceInfo interface {
4441 GetDurableNonceAccount () solana.PublicKey
4542 GetDurableNonceValue () solana.Hash
4643 HasDurableNonce () bool
4744 IsCreatingDurableNonceAccount () bool
45+
46+ DoesTxUseDurableNonce (tx * solana.Transaction ) bool
4847}
4948
5049type TokenAccount struct {
@@ -54,7 +53,6 @@ type TokenAccount struct {
5453
5554var _ xc.TxInput = & TxInput {}
5655var _ GetTxInfo = & TxInput {}
57- var _ GetDurableNonceInfo = & TxInput {}
5856var _ xc.TxInputWithUnix = & TxInput {}
5957
6058func init () {
@@ -90,6 +88,25 @@ func (input *TxInput) IsCreatingDurableNonceAccount() bool {
9088 return input .ShouldCreateDurableNonce && ! input .DurableNonceAccount .IsZero ()
9189}
9290
91+ func (input * TxInput ) DoesTxUseDurableNonce (tx * solana.Transaction ) bool {
92+ if ! input .HasDurableNonce () {
93+ return false
94+ }
95+
96+ if tx .Message .RecentBlockhash .Equals (input .DurableNonce ) {
97+ return true
98+ }
99+ usingDurableNonce := false
100+ for _ , accountKey := range tx .Message .AccountKeys {
101+ if input .DurableNonceAccount .Equals (accountKey ) {
102+ usingDurableNonce = true
103+ break
104+ }
105+ }
106+
107+ return usingDurableNonce
108+ }
109+
93110// GetBlockhashForTx returns the blockhash to use for the transaction.
94111// If a durable nonce is set and initialized, the nonce value is used instead of a recent blockhash.
95112// When the nonce account needs to be created first, the recent blockhash is used.
@@ -155,50 +172,71 @@ func (input *TxInput) IsFeeLimitAccurate() bool {
155172 return true
156173}
157174
175+ func (input * TxInput ) MatchDurableNonce (otherNonce GetTxInfo ) (accountMatch , nonceMatch bool ) {
176+ sameAccount := input .DurableNonceAccount .Equals (otherNonce .GetDurableNonceAccount ())
177+ if sameAccount {
178+ // Both creating the same nonce account = conflict
179+ if input .IsCreatingDurableNonceAccount () && otherNonce .IsCreatingDurableNonceAccount () {
180+ return true , true
181+ }
182+ // Both using the same nonce value = conflict (only one can succeed)
183+ // Different nonce values = independent (each uses its own nonce)
184+ if input .HasDurableNonce () && otherNonce .HasDurableNonce () {
185+ return true , input .DurableNonce .Equals (otherNonce .GetDurableNonceValue ())
186+ }
187+ }
188+ // different accounts = independent
189+ return false , false
190+ }
191+
192+ func (input * TxInput ) DidTimeoutOccur (other GetTxInfo ) (timeout bool ) {
193+ diff := input .Timestamp - other .GetTimestamp ()
194+ if diff < int64 (SafetyTimeoutMargin .Seconds ()) || other .GetRecentBlockhash ().Equals (input .GetRecentBlockhash ()) {
195+ return false
196+ }
197+ return true
198+ }
199+
158200func (input * TxInput ) IndependentOf (other xc.TxInput ) (independent bool ) {
159- if otherNonce , ok := other .(GetDurableNonceInfo ); ok {
160- sameAccount := ! input .DurableNonceAccount .IsZero () &&
161- input .DurableNonceAccount .Equals (otherNonce .GetDurableNonceAccount ())
162- if sameAccount {
163- // Both creating the same nonce account = conflict
164- if input .IsCreatingDurableNonceAccount () && otherNonce .IsCreatingDurableNonceAccount () {
165- return false
166- }
167- // Both using the same nonce value = conflict (only one can succeed)
168- // Different nonce values = independent (each uses its own nonce)
169- if input .HasDurableNonce () && otherNonce .HasDurableNonce () {
170- return ! input .DurableNonce .Equals (otherNonce .GetDurableNonceValue ())
171- }
201+ if otherNonce , ok := other .(GetTxInfo ); ok {
202+ // if input.HasDurableNonce() {
203+ _ , sameNonce := input .MatchDurableNonce (otherNonce )
204+ if sameNonce {
205+ // one of the transactions will fail
206+ return false
207+ } else {
208+ // both work
209+ return true
172210 }
173211 }
212+ // solana transactions are always independent if no durable nonce
174213 return true
175214}
176215
177216func (input * TxInput ) SafeFromDoubleSend (other xc.TxInput ) (safe bool ) {
178- if otherNonce , ok := other .(GetDurableNonceInfo ); ok {
179- sameAccount := ! input .DurableNonceAccount .IsZero () &&
180- input .DurableNonceAccount .Equals (otherNonce .GetDurableNonceAccount ())
217+ otherInput , ok := other .(GetTxInfo )
218+ if ! ok {
219+ return false
220+ }
221+
222+ if input .HasDurableNonce () {
223+ sameAccount , sameNonce := input .MatchDurableNonce (otherInput )
181224 if sameAccount {
182- // Safe only when both have actual nonce values and they match
183- // (the nonce can only be consumed once, so only one tx can land).
184- // If either is missing a nonce (e.g. setup phase), not safe.
185- if input . HasDurableNonce () && otherNonce . HasDurableNonce () {
186- return input . DurableNonce . Equals ( otherNonce . GetDurableNonceValue ())
225+ if sameNonce {
226+ // safe
227+ return true
228+ } else {
229+ return false
187230 }
188- return false
189231 }
190232 }
191233
192234 // For recent blockhash (non-durable-nonce) transactions
193- oldInput , ok := other .(GetTxInfo )
194- if ! ok {
195- return false
235+ if input .DidTimeoutOccur (otherInput ) {
236+ return true
196237 }
197- diff := input .Timestamp - oldInput .GetTimestamp ()
198- if diff < int64 (SafetyTimeoutMargin .Seconds ()) || oldInput .GetRecentBlockhash ().Equals (input .GetRecentBlockhash ()) {
199- return false
200- }
201- return true
238+
239+ return false
202240}
203241
204242func (input * TxInput ) SetUnix (unix int64 ) {
0 commit comments