forked from lightninglabs/neutrino
/
error.go
152 lines (126 loc) · 4.53 KB
/
error.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
package pushtx
import (
"fmt"
"strings"
"github.com/ltcsuite/ltcd/wire"
)
// BroadcastErrorCode uniquely identifies the broadcast error.
type BroadcastErrorCode uint8
const (
// Unknown is the code used when a transaction has been rejected by some
// unknown reason by a peer.
Unknown BroadcastErrorCode = iota
// Invalid is the code used when a transaction has been deemed invalid
// by a peer.
Invalid
// InsufficientFee is the code used when a transaction has been deemed
// as having an insufficient fee by a peer.
InsufficientFee
// Mempool is the code used when a transaction already exists in a
// peer's mempool.
Mempool
// Confirmed is the code used when a transaction has been deemed as
// confirmed in the chain by a peer.
Confirmed
)
func (c BroadcastErrorCode) String() string {
switch c {
case Invalid:
return "Invalid"
case InsufficientFee:
return "InsufficientFee"
case Mempool:
return "Mempool"
case Confirmed:
return "Confirmed"
default:
return "Unknown"
}
}
// BroadcastError is an error type that encompasses the different possible
// broadcast errors returned by the network.
type BroadcastError struct {
// Code is the uniquely identifying code of the broadcast error.
Code BroadcastErrorCode
// Reason is the string detailing the reason as to why the transaction
// was rejected.
Reason string
}
// A compile-time constraint to ensure BroadcastError satisfies the error
// interface.
var _ error = (*BroadcastError)(nil)
// Error returns the reason of the broadcast error.
func (e *BroadcastError) Error() string {
return e.Reason
}
// IsBroadcastError is a helper function that can be used to determine whether
// an error is a BroadcastError that matches any of the specified codes.
func IsBroadcastError(err error, codes ...BroadcastErrorCode) bool {
broadcastErr, ok := err.(*BroadcastError)
if !ok {
return false
}
for _, code := range codes {
if broadcastErr.Code == code {
return true
}
}
return false
}
// ParseBroadcastError maps a peer's reject message for a transaction to a
// BroadcastError.
func ParseBroadcastError(msg *wire.MsgReject, peerAddr string) *BroadcastError {
// We'll determine the appropriate broadcast error code by looking at
// the reject's message code and reason. The only reject codes returned
// from peers (bitcoind and btcd) when attempting to accept a
// transaction into their mempool are:
// RejectInvalid, RejectNonstandard, RejectInsufficientFee,
// RejectDuplicate
var code BroadcastErrorCode
switch {
// The cases below apply for reject messages sent from any kind of peer.
case msg.Code == wire.RejectInvalid || msg.Code == wire.RejectNonstandard:
code = Invalid
case msg.Code == wire.RejectInsufficientFee:
code = InsufficientFee
// The cases below apply for reject messages sent from bitcoind peers.
//
// If the transaction double spends an unconfirmed transaction in the
// peer's mempool, then we'll deem it as invalid.
case msg.Code == wire.RejectDuplicate &&
strings.Contains(msg.Reason, "txn-mempool-conflict"):
code = Invalid
// If the transaction was rejected due to it already existing in the
// peer's mempool, then return an error signaling so.
case msg.Code == wire.RejectDuplicate &&
strings.Contains(msg.Reason, "txn-already-in-mempool"):
code = Mempool
// If the transaction was rejected due to it already existing in the
// chain according to our peer, then we'll return an error signaling so.
case msg.Code == wire.RejectDuplicate &&
strings.Contains(msg.Reason, "txn-already-known"):
code = Confirmed
// The cases below apply for reject messages sent from btcd peers.
//
// If the transaction double spends an unconfirmed transaction in the
// peer's mempool, then we'll deem it as invalid.
case msg.Code == wire.RejectDuplicate &&
strings.Contains(msg.Reason, "already spent"):
code = Invalid
// If the transaction was rejected due to it already existing in the
// peer's mempool, then return an error signaling so.
case msg.Code == wire.RejectDuplicate &&
strings.Contains(msg.Reason, "already have transaction"):
code = Mempool
// If the transaction was rejected due to it already existing in the
// chain according to our peer, then we'll return an error signaling so.
case msg.Code == wire.RejectDuplicate &&
strings.Contains(msg.Reason, "transaction already exists"):
code = Confirmed
// Any other reject messages will use the unknown code.
default:
code = Unknown
}
reason := fmt.Sprintf("rejected by %v: %v", peerAddr, msg.Reason)
return &BroadcastError{Code: code, Reason: reason}
}