-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
pendingtx.go
169 lines (143 loc) · 4.12 KB
/
pendingtx.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package soltxm
import (
"context"
"errors"
"sync"
"time"
"github.com/gagliardetto/solana-go"
"golang.org/x/exp/maps"
)
type PendingTxContext interface {
Add(sig solana.Signature, cancel context.CancelFunc) error
Remove(sig solana.Signature)
ListAll() []solana.Signature
Expired(sig solana.Signature, lifespan time.Duration) bool
// state change hooks
OnSuccess(sig solana.Signature)
OnError(sig solana.Signature, errType int) // match err type using enum
}
var _ PendingTxContext = &pendingTxContext{}
type pendingTxContext struct {
cancelBy map[solana.Signature]context.CancelFunc
timestamp map[solana.Signature]time.Time
lock sync.RWMutex
}
func newPendingTxContext() *pendingTxContext {
return &pendingTxContext{
cancelBy: map[solana.Signature]context.CancelFunc{},
timestamp: map[solana.Signature]time.Time{},
}
}
func (c *pendingTxContext) Add(sig solana.Signature, cancel context.CancelFunc) error {
// already exists
c.lock.RLock()
if c.cancelBy[sig] != nil {
c.lock.RUnlock()
return errors.New("signature already exists")
}
c.lock.RUnlock()
// upgrade to write lock if sig does not exist
c.lock.Lock()
defer c.lock.Unlock()
if c.cancelBy[sig] != nil {
return errors.New("signature already exists")
}
// save cancel func
c.cancelBy[sig] = cancel
c.timestamp[sig] = time.Now()
return nil
}
func (c *pendingTxContext) Remove(sig solana.Signature) {
// already cancelled
c.lock.RLock()
if c.cancelBy[sig] == nil {
c.lock.RUnlock()
return
}
c.lock.RUnlock()
// upgrade to write lock if sig does not exist
c.lock.Lock()
defer c.lock.Unlock()
if c.cancelBy[sig] == nil {
return
}
// call cancel func + remove from map
c.cancelBy[sig]() // cancel context
delete(c.cancelBy, sig)
delete(c.timestamp, sig)
}
func (c *pendingTxContext) ListAll() []solana.Signature {
c.lock.RLock()
defer c.lock.RUnlock()
return maps.Keys(c.cancelBy)
}
// Expired returns if the timeout for trying to confirm a signature has been reached
func (c *pendingTxContext) Expired(sig solana.Signature, lifespan time.Duration) bool {
c.lock.RLock()
timestamp, exists := c.timestamp[sig]
c.lock.RUnlock()
if !exists {
return true // return expired = true if timestamp doesn't exist
}
return time.Since(timestamp) > lifespan
}
func (c *pendingTxContext) OnSuccess(sig solana.Signature) {
c.Remove(sig)
}
func (c *pendingTxContext) OnError(sig solana.Signature, _ int) {
c.Remove(sig)
}
var _ PendingTxContext = &pendingTxContextWithProm{}
type pendingTxContextWithProm struct {
pendingTx *pendingTxContext
chainID string
}
const (
TxFailRevert = iota
TxFailReject
TxFailDrop
TxFailSimRevert
TxFailSimOther
)
func newPendingTxContextWithProm(id string) *pendingTxContextWithProm {
return &pendingTxContextWithProm{
chainID: id,
pendingTx: newPendingTxContext(),
}
}
func (c *pendingTxContextWithProm) Add(sig solana.Signature, cancel context.CancelFunc) error {
return c.pendingTx.Add(sig, cancel)
}
func (c *pendingTxContextWithProm) Remove(sig solana.Signature) {
c.pendingTx.Remove(sig)
}
func (c *pendingTxContextWithProm) ListAll() []solana.Signature {
sigs := c.pendingTx.ListAll()
promSolTxmPendingTxs.WithLabelValues(c.chainID).Set(float64(len(sigs)))
return sigs
}
func (c *pendingTxContextWithProm) Expired(sig solana.Signature, lifespan time.Duration) bool {
return c.pendingTx.Expired(sig, lifespan)
}
// Success - tx included in block and confirmed
func (c *pendingTxContextWithProm) OnSuccess(sig solana.Signature) {
promSolTxmSuccessTxs.WithLabelValues(c.chainID).Add(1)
c.pendingTx.OnSuccess(sig)
}
func (c *pendingTxContextWithProm) OnError(sig solana.Signature, errType int) {
switch errType {
case TxFailRevert:
promSolTxmRevertTxs.WithLabelValues(c.chainID).Add(1)
case TxFailReject:
promSolTxmRejectTxs.WithLabelValues(c.chainID).Add(1)
case TxFailDrop:
promSolTxmDropTxs.WithLabelValues(c.chainID).Add(1)
case TxFailSimRevert:
promSolTxmSimRevertTxs.WithLabelValues(c.chainID).Add(1)
case TxFailSimOther:
promSolTxmSimOtherTxs.WithLabelValues(c.chainID).Add(1)
}
// increment total errors
promSolTxmErrorTxs.WithLabelValues(c.chainID).Add(1)
c.pendingTx.OnError(sig, errType)
}