-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathethTx.go
303 lines (267 loc) · 8.19 KB
/
ethTx.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package main
import (
"bytes"
"encoding/hex"
"fmt"
"strings"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
)
type EthField int
const (
NONCE EthField = iota
GAS_PRICE
GAS_LIMIT
RECIPIENT
VALUE
DATA
SIG_V
SIG_R
SIG_S
)
// TODO: move this to own package and export
type ethTxParser struct{}
func (e *ethTxParser) understands(s string) bool {
tx := &types.Transaction{}
// hacky we got a txID and need to look up the raw txn
if len(s) == len("0xc45367afb97f4e79fe6cccfed0bea22a8c63d6fbd7ec4f85aa2541d05075f8af") {
raw := etherscanCrawlRaw(s)
if len(raw) > 20 {
return true
}
return false
}
rawTx := strings.TrimPrefix(s, "0x")
buf, err := hex.DecodeString(rawTx)
if err != nil {
return false
}
r := bytes.NewBuffer(buf)
stream := rlp.NewStream(r, 0)
err = tx.DecodeRLP(stream)
if err != nil {
return false
}
return true
}
func (e *ethTxParser) parse(s string) ([]token, error) {
tx := &types.Transaction{}
var rawTx string
// hacky we got a txID and need to look up the raw txn
// TODO: need to deal with network failure for etherscan
if len(s) == len("0xc45367afb97f4e79fe6cccfed0bea22a8c63d6fbd7ec4f85aa2541d05075f8af") {
rawTx = strings.TrimPrefix(etherscanCrawlRaw(s), "0x")
} else {
rawTx = strings.TrimPrefix(s, "0x")
}
buf, err := hex.DecodeString(rawTx)
if err != nil {
return nil, err
}
r := bytes.NewBuffer(buf)
stream := rlp.NewStream(r, 0)
err = tx.DecodeRLP(stream)
if err != nil {
return nil, err
}
var toks []token
// first rlp node pre-nonce
// TODO: probaly some edge case i'm missing
prefix := buf[0]
l := prefix - 0xf7
llen := buf[1 : 1+l]
pre := token{
Token: hex.EncodeToString(buf[0 : 1+l]),
Title: "RLP Prefix",
Description: fmt.Sprintf("RLP is an encoding/decoding algorithm that helps Ethereum to serialize data.\nThis is an RLP 'list' with total length > 55 bytes.\n The first byte (0x%x - 0xF7) tells us the length of the length (%d bytes).\nThe actual length of the list in bytes is %s bytes (0x%x).", prefix, l, bytesToInt(llen), llen),
Value: "0x" + hex.EncodeToString(buf[0:1+l]),
}
toks = append(toks, pre)
/*
tok := &token{
Token: hex.EncodeToString(enc[:1+l]),
Title: "RLP Length Prefix",
Description: fmt.Sprintf("This is an RLP 'string' with length > 55 bytes.\nThe first byte (0x%x-0xB7) tells us the length of the length (%d bytes).\nThe actual field length is %s bytes (0x%x)", prefix, l, bytesToInt(fieldLen).String(), fieldLen),
Value: hex.EncodeToString(enc[:1+l]),
}
*/
// add the other fields and their rlp prefixes
toks = append(toks, genToken(tx.Nonce(), NONCE)...)
toks = append(toks, genToken(tx.GasPrice(), GAS_PRICE)...)
toks = append(toks, genToken(tx.Gas(), GAS_LIMIT)...)
// empty slice if contract creation
if tx.To() != nil {
toks = append(toks, genToken(tx.To().Bytes(), RECIPIENT)...)
} else {
toks = append(toks, genToken([]byte{}, RECIPIENT)...)
}
toks = append(toks, genToken(tx.Value(), VALUE)...)
toks = append(toks, genToken(tx.Data(), DATA)...)
sigV, sigR, sigS := tx.RawSignatureValues()
toks = append(toks, genToken(sigV.Bytes(), SIG_V)...)
toks = append(toks, genToken(sigR.Bytes(), SIG_R)...)
toks = append(toks, genToken(sigS.Bytes(), SIG_S)...)
return toks, nil
}
func genToken(val interface{}, f EthField) []token {
enc, err := rlp.EncodeToBytes(val)
if err != nil {
panic(err)
}
var toks []token
// Add RLP tokens
rlpTok, prefixLen := addRLPToken(enc)
if rlpTok != nil {
toks = append(toks, *rlpTok)
}
// strip bytes that were part of the RLP prefix
body := enc[prefixLen:]
//fmt.Printf("prefixLen: %d, enc: %s, res: %s\n", prefixLen, hex.EncodeToString(enc), hex.EncodeToString(body))
// Add token for actual field
var (
title string
desc string
longDesc string
value string
)
switch f {
case NONCE:
title = "Nonce"
desc = "The nonce is an incrementing sequence number used to prevent message replay."
longDesc = ""
if rlpTok == nil && body[0] == 0x80 {
value = "0 (0x80) is the RLP encoded version of zero."
} else {
value = fmt.Sprintf("%s (0x%x)", bytesToInt(body).String(), body)
}
case GAS_PRICE:
title = "Gas Price"
desc = "The price of gas (in wei) that the sender is willing to pay."
longDesc = ""
value = fmt.Sprintf("%s (0x%x)", bytesToInt(body).String(), body)
case GAS_LIMIT:
title = "Gas Limit"
desc = "The maximum amount of gas the originator is willing to pay for this transaction."
longDesc = ""
value = fmt.Sprintf("%s (0x%x)", bytesToInt(body).String(), body)
case RECIPIENT:
// TODO: edgecase for contract create
title = "Recipient"
if rlpTok == nil && body[0] == 0x80 {
desc = "The recipient field is empty. This signifies that this is a special call to Create a contract."
value = ""
} else {
desc = "The address of the user account or contract to interact with."
value = "0x" + hex.EncodeToString(body[:20])
}
case VALUE:
title = "Value"
desc = "Amount of Eth in wei"
longDesc = "The amount of ether (in wei) to send to the recipient address."
if rlpTok == nil && body[0] == 0x80 {
value = "0 Wei"
} else {
value = bytesToInt(body).String() + " Wei"
}
case DATA:
title = "Data"
desc = "Data being sent to a contract function. The first 4 bytes are known as the 'function selector'."
longDesc = ""
if rlpTok == nil {
value = "No Data"
} else {
if len(body) > 20 {
value = "0x" + hex.EncodeToString(body[:20]) + "..."
} else {
value = "0x" + hex.EncodeToString(body)
}
}
case SIG_V:
title = "Signature V"
desc = "Indicates both the chainID of the transaction and the parity (odd or even) of the y component of the public key."
longDesc = ""
value = "0x" + hex.EncodeToString(body)
case SIG_R:
title = "Signature R"
desc = "(r) part of the signature pair (r,s)."
longDesc = "Represents the X-coordinate of an ephemeral public key created during the ECDSA signing process."
value = "0x" + hex.EncodeToString(body)
case SIG_S:
title = "Signature S"
desc = "(s) part of the signature pair (r,s)."
longDesc = "Generated using the ECDSA signing algorithm."
value = "0x" + hex.EncodeToString(body)
}
toks = append(toks, token{
Token: hex.EncodeToString(body),
Description: desc,
FlavorText: longDesc,
Value: value,
Title: title,
})
return toks
}
// create a token for the rlp prefix and return the size of the prefix
func addRLPToken(enc []byte) (*token, int) {
length := len(enc)
if length == 0 {
panic("encoded value shouldn't be 0 length")
}
prefix := enc[0]
switch {
// single byte value that would already have been added in previous step
// technically not correct to include 0x80 but makes other code cleaner
case prefix <= 0x80:
return nil, 0
// rlp "string" with length 0-55 bytes
case prefix < 0xB8:
tok := &token{
Token: hex.EncodeToString([]byte{prefix}),
Title: "RLP Length Prefix",
Description: fmt.Sprintf("RLP Length Prefix. The next field is an RLP 'string' of length %d (0x%x - 0x80).", int(prefix)-0x80, prefix),
Value: "0x" + hex.EncodeToString([]byte{prefix}),
}
return tok, len(enc) - (int(prefix) - 0x80)
// rlp "string" with length > 55 bytes
case prefix < 0xC0:
// prefix is Length of the length field + 0xB7
l := prefix - 0xB7
fieldLen := enc[1 : 1+l]
tok := &token{
Token: hex.EncodeToString(enc[:1+l]),
Title: "RLP Length Prefix",
Description: fmt.Sprintf("This is an RLP 'string' with length > 55 bytes.\nThe first byte (0x%x-0xB7) tells us the length of the length (%d bytes).\nThe actual field length is %s bytes (0x%x).", prefix, l, bytesToInt(fieldLen).String(), fieldLen),
Value: "0x" + hex.EncodeToString(enc[:1+l]),
}
return tok, 1 + len(fieldLen)
default:
panic("RLP prefix outside of expected range for ETH tx")
}
}
//field := NONCE
//idx := 0
//for {
//nonce := addToken(tx.Nonce(), NONCE)
/*
switch field {
var val interface{}
case NONCE:
val = tx.Nonce()
case GAS_PRICE:
val = tx.GasPrice()
case GAS:
val = tx.Gas()
case RECIPIENT:
// empty array if contract creation
if tx.To() != nil {
val = tx.To()
} else {
val = []byte{}
}
case VALUE:
val = tx.Value()
case DATA:
val = tx.Data()
case
}
*/