forked from ethereum-optimism/optimism
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsend_state.go
147 lines (124 loc) · 4.89 KB
/
send_state.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package txmgr
import (
"errors"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool"
)
var (
// Returned by CriticalError when the system is unable to get the tx into the mempool in the
// allotted time
ErrMempoolDeadlineExpired = errors.New("failed to get tx into the mempool")
)
// SendState tracks information about the publication state of a given txn. In
// this context, a txn may correspond to multiple different txn hashes due to
// varying gas prices, though we treat them all as the same logical txn. This
// struct is primarily used to determine whether or not the txmgr should abort a
// given txn.
type SendState struct {
minedTxs map[common.Hash]struct{}
mu sync.RWMutex
now func() time.Time
// Config
nonceTooLowCount uint64
txInMempoolDeadline time.Time // deadline to abort at if no transactions are in the mempool
// Counts of the different types of errors
successFullPublishCount uint64 // nil error => tx made it to the mempool
safeAbortNonceTooLowCount uint64 // nonce too low error
// Whether any attempt to send the tx resulted in ErrAlreadyReserved
alreadyReserved bool
// Whether we should bump fees before trying to publish the tx again
bumpFees bool
// Miscellaneous tracking
bumpCount int // number of times we have bumped the gas price
}
// NewSendStateWithNow creates a new send state with the provided clock.
func NewSendStateWithNow(safeAbortNonceTooLowCount uint64, unableToSendTimeout time.Duration, now func() time.Time) *SendState {
if safeAbortNonceTooLowCount == 0 {
panic("txmgr: safeAbortNonceTooLowCount cannot be zero")
}
return &SendState{
minedTxs: make(map[common.Hash]struct{}),
safeAbortNonceTooLowCount: safeAbortNonceTooLowCount,
txInMempoolDeadline: now().Add(unableToSendTimeout),
now: now,
}
}
// NewSendState creates a new send state
func NewSendState(safeAbortNonceTooLowCount uint64, unableToSendTimeout time.Duration) *SendState {
return NewSendStateWithNow(safeAbortNonceTooLowCount, unableToSendTimeout, time.Now)
}
// ProcessSendError should be invoked with the error returned for each
// publication. It is safe to call this method with nil or arbitrary errors.
func (s *SendState) ProcessSendError(err error) {
s.mu.Lock()
defer s.mu.Unlock()
// Record the type of error
switch {
case err == nil:
s.successFullPublishCount++
case errStringMatch(err, core.ErrNonceTooLow):
s.nonceTooLowCount++
case errStringMatch(err, txpool.ErrAlreadyReserved):
s.alreadyReserved = true
}
}
// TxMined records that the txn with txnHash has been mined and is await
// confirmation. It is safe to call this function multiple times.
func (s *SendState) TxMined(txHash common.Hash) {
s.mu.Lock()
defer s.mu.Unlock()
s.minedTxs[txHash] = struct{}{}
}
// TxNotMined records that the txn with txnHash has not been mined or has been
// reorg'd out. It is safe to call this function multiple times.
func (s *SendState) TxNotMined(txHash common.Hash) {
s.mu.Lock()
defer s.mu.Unlock()
_, wasMined := s.minedTxs[txHash]
delete(s.minedTxs, txHash)
// If the txn got reorged and left us with no mined txns, reset the nonce
// too low count, otherwise we might abort too soon when processing the next
// error. If the nonce too low errors persist, we want to ensure we wait out
// the full safe abort count to ensure we have a sufficient number of
// observations.
if len(s.minedTxs) == 0 && wasMined {
s.nonceTooLowCount = 0
}
}
// CriticalError returns a non-nil error if the txmgr should give up on trying a given txn with the
// target nonce. This occurs when the set of errors recorded indicates that no further progress
// can be made on this transaction, or if there is an incompatible tx type currently in the
// mempool.
func (s *SendState) CriticalError() error {
s.mu.RLock()
defer s.mu.RUnlock()
switch {
case len(s.minedTxs) > 0:
// Never abort if our latest sample reports having at least one mined txn.
return nil
case s.nonceTooLowCount >= s.safeAbortNonceTooLowCount:
// we have exceeded the nonce too low count
return core.ErrNonceTooLow
case s.successFullPublishCount == 0 && s.nonceTooLowCount > 0:
// A nonce too low error before successfully publishing any transaction means the tx will
// need a different nonce, which we can force by returning error.
return core.ErrNonceTooLow
case s.successFullPublishCount == 0 && s.now().After(s.txInMempoolDeadline):
// unable to get the tx into the mempool in the allotted time
return ErrMempoolDeadlineExpired
case s.alreadyReserved:
// incompatible tx type in mempool
return txpool.ErrAlreadyReserved
}
return nil
}
// IsWaitingForConfirmation returns true if we have at least one confirmation on
// one of our txs.
func (s *SendState) IsWaitingForConfirmation() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return len(s.minedTxs) > 0
}