forked from dcb9/janus
-
Notifications
You must be signed in to change notification settings - Fork 27
/
eth_getTransactionByHash.go
399 lines (354 loc) · 13.5 KB
/
eth_getTransactionByHash.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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
package transformer
import (
"context"
"encoding/json"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/labstack/echo"
"github.com/pkg/errors"
"github.com/qtumproject/janus/pkg/eth"
"github.com/qtumproject/janus/pkg/qtum"
"github.com/qtumproject/janus/pkg/utils"
)
// ProxyETHGetTransactionByHash implements ETHProxy
type ProxyETHGetTransactionByHash struct {
*qtum.Qtum
}
func (p *ProxyETHGetTransactionByHash) Method() string {
return "eth_getTransactionByHash"
}
func (p *ProxyETHGetTransactionByHash) Request(req *eth.JSONRPCRequest, c echo.Context) (interface{}, eth.JSONRPCError) {
var txHash eth.GetTransactionByHashRequest
if err := json.Unmarshal(req.Params, &txHash); err != nil {
// TODO: Correct error code?
return nil, eth.NewInvalidParamsError("couldn't unmarshal request")
}
if txHash == "" {
// TODO: Correct error code?
return nil, eth.NewInvalidParamsError("transaction hash is empty")
}
qtumReq := &qtum.GetTransactionRequest{
TxID: utils.RemoveHexPrefix(string(txHash)),
}
return p.request(c.Request().Context(), qtumReq)
}
func (p *ProxyETHGetTransactionByHash) request(ctx context.Context, req *qtum.GetTransactionRequest) (*eth.GetTransactionByHashResponse, eth.JSONRPCError) {
ethTx, err := getTransactionByHash(ctx, p.Qtum, req.TxID)
if err != nil {
return nil, err
}
return ethTx, nil
}
// TODO: think of returning flag if it's a reward transaction for miner
//
// FUTURE WORK: It might be possible to simplify this (and other?) translation by using a single verbose getblock qtum RPC command,
// since it returns a lot of data including the equivalent of calling GetRawTransaction on every transaction in block.
// The last point is of particular interest because GetRawTransaction doesn't by default work for every transaction.
// This would mean fetching a lot of probably unnecessary data, but in this setup query response delay is reasonably the biggest bottleneck anyway
func getTransactionByHash(ctx context.Context, p *qtum.Qtum, hash string) (*eth.GetTransactionByHashResponse, eth.JSONRPCError) {
qtumTx, err := p.GetTransaction(ctx, hash)
var ethTx *eth.GetTransactionByHashResponse
if err != nil {
if errors.Cause(err) != qtum.ErrInvalidAddress {
p.GetDebugLogger().Log("msg", "Failed to GetTransaction", "hash", hash, "err", err)
return nil, eth.NewCallbackError(err.Error())
}
var rawQtumTx *qtum.GetRawTransactionResponse
ethTx, rawQtumTx, err = getRewardTransactionByHash(ctx, p, hash)
if err != nil {
if errors.Cause(err) == qtum.ErrInvalidAddress {
return nil, nil
}
rawTx, err := p.GetRawTransaction(ctx, hash, false)
if err != nil {
if errors.Cause(err) == qtum.ErrInvalidAddress {
return nil, nil
}
p.GetDebugLogger().Log("msg", "Failed to GetRawTransaction", "hash", hash, "err", err)
return nil, eth.NewCallbackError(err.Error())
} else {
p.GetDebugLogger().Log("msg", "Got raw transaction by hash")
qtumTx = &qtum.GetTransactionResponse{
BlockHash: rawTx.BlockHash,
BlockIndex: 1, // TODO: Possible to get this somewhere?
Hex: rawTx.Hex,
}
}
} else {
p.GetDebugLogger().Log("msg", "Got reward transaction by hash")
qtumTx = &qtum.GetTransactionResponse{
Hex: rawQtumTx.Hex,
BlockHash: rawQtumTx.BlockHash,
}
}
}
qtumDecodedRawTx, err := p.DecodeRawTransaction(ctx, qtumTx.Hex)
if err != nil {
p.GetDebugLogger().Log("msg", "Failed to DecodeRawTransaction", "hex", qtumTx.Hex, "err", err)
return nil, eth.NewCallbackError("couldn't get raw transaction")
}
if ethTx == nil {
ethTx = ð.GetTransactionByHashResponse{
Hash: utils.AddHexPrefix(qtumDecodedRawTx.ID),
Nonce: "0x0",
TransactionIndex: "",
BlockHash: "",
BlockNumber: "",
// Added for go-ethereum client and graph-node support
R: "0xf000000000000000000000000000000000000000000000000000000000000000",
S: "0xf000000000000000000000000000000000000000000000000000000000000000",
V: "0x25",
Gas: "0x0",
GasPrice: "0x0",
}
}
if !qtumTx.IsPending() { // otherwise, the following values must be nulls
blockNumber, err := getBlockNumberByHash(ctx, p, qtumTx.BlockHash)
if err != nil {
p.GetDebugLogger().Log("msg", "Failed to get block number by hash", "hash", qtumTx.BlockHash, "err", err)
return nil, eth.NewCallbackError("couldn't get block number by hash")
}
ethTx.BlockNumber = hexutil.EncodeUint64(blockNumber)
ethTx.BlockHash = utils.AddHexPrefix(qtumTx.BlockHash)
if ethTx.TransactionIndex == "" {
ethTx.TransactionIndex = hexutil.EncodeUint64(uint64(qtumTx.BlockIndex))
} else {
// Already set in getRewardTransactionByHash
}
}
if ethTx.Value == "" {
// TODO: This CalcAmount() func needs improvement
ethAmount, err := formatQtumAmount(qtumDecodedRawTx.CalcAmount())
if err != nil {
// TODO: Correct error code?
p.GetDebugLogger().Log("msg", "Couldn't format qtum amount", "qtum", qtumDecodedRawTx.CalcAmount().String(), "err", err)
return nil, eth.NewInvalidParamsError("couldn't format amount")
}
ethTx.Value = ethAmount
}
qtumTxContractInfo, isContractTx, _ := qtumDecodedRawTx.ExtractContractInfo()
// parsing err is discarded because it's not an error if the transaction is not a valid contract call
// https://testnet.qtum.info/tx/24ed3749022ed21e53d8924764bb0303a4b6fa469f26922bfa64ba44507c4c4a
// if err != nil {
// p.GetDebugLogger().Log("msg", "Couldn't extract contract info", "err", err)
// return nil, eth.NewCallbackError(qtumTx.Hex /*"couldn't extract contract info"*/)
// }
if isContractTx {
// TODO: research is this allowed? ethTx.Input = utils.AddHexPrefix(qtumTxContractInfo.UserInput)
if qtumTxContractInfo.UserInput == "" {
ethTx.Input = "0x"
} else {
ethTx.Input = utils.AddHexPrefix(qtumTxContractInfo.UserInput)
}
if qtumTxContractInfo.From != "" {
ethTx.From = utils.AddHexPrefix(qtumTxContractInfo.From)
} else {
// It seems that ExtractContractInfo only looks for OP_SENDER address when assigning From field, so if none is present we handle it like for a non-contract TX
ethTx.From, err = getNonContractTxSenderAddress(ctx, p, qtumDecodedRawTx)
if err != nil {
p.GetDebugLogger().Log("msg", "Contract tx parsing found no sender address", "tx", qtumDecodedRawTx, "err", err)
return nil, eth.NewCallbackError("Contract tx parsing found no sender address, and the fallback function also failed: " + err.Error())
}
}
//TODO: research if 'To' adress could be other than zero address when 'isContractTx == TRUE'
if len(qtumTxContractInfo.To) == 0 {
ethTx.To = utils.AddHexPrefix(qtum.ZeroAddress)
} else {
ethTx.To = utils.AddHexPrefix(qtumTxContractInfo.To)
}
// gasLimit
if len(qtumTxContractInfo.GasLimit) == 0 {
qtumTxContractInfo.GasLimit = "0"
}
ethTx.Gas = utils.AddHexPrefix(qtumTxContractInfo.GasLimit)
// trim leading zeros from gasPrice
qtumTxContractInfo.GasPrice = strings.TrimLeft(qtumTxContractInfo.GasPrice, "0")
if len(qtumTxContractInfo.GasPrice) == 0 {
qtumTxContractInfo.GasPrice = "0"
}
// Gas price is in hex satoshis, convert to wei
gasPriceInSatoshis, err := utils.DecodeBig(qtumTxContractInfo.GasPrice)
if err != nil {
p.GetErrorLogger().Log("msg", "Failed to parse gasPrice: "+qtumTxContractInfo.GasPrice, "error", err.Error())
return ethTx, eth.NewCallbackError("Failed to parse gasPrice")
}
gasPriceInWei := convertFromSatoshiToWei(gasPriceInSatoshis)
ethTx.GasPrice = hexutil.EncodeBig(gasPriceInWei)
return ethTx, nil
}
if qtumTx.Generated {
ethTx.From = utils.AddHexPrefix(qtum.ZeroAddress)
} else {
// TODO: Figure out if following code still cause issues in some cases, see next comment
// causes issues on coinbase txs, coinbase will not have a sender and so this should be able to fail
ethTx.From, _ = getNonContractTxSenderAddress(ctx, p, qtumDecodedRawTx)
// TODO: discuss
// ? Does func above return incorrect address for graph-node (len is < 40)
// ! Temporary solution
if ethTx.From == "" {
ethTx.From = utils.AddHexPrefix(qtum.ZeroAddress)
}
}
if ethTx.To == "" {
ethTx.To, err = findNonContractTxReceiverAddress(qtumDecodedRawTx.Vouts)
if err != nil {
// TODO: discuss, research
// ? Some vouts doesn't have `receive` category at all
ethTx.To = utils.AddHexPrefix(qtum.ZeroAddress)
// TODO: uncomment, after todo above will be resolved
// return nil, errors.WithMessage(err, "couldn't get non contract transaction receiver address")
}
}
// TODO: discuss
// ? Does func above return incorrect address for graph-node (len is < 40)
// ! Temporary solution
if ethTx.To == "" {
ethTx.To = utils.AddHexPrefix(qtum.ZeroAddress)
}
// TODO: researching
// ! Temporary solution
// if len(qtumTx.Hex) == 0 {
// ethTx.Input = "0x0"
// } else {
// ethTx.Input = utils.AddHexPrefix(qtumTx.Hex)
// }
ethTx.Input = utils.AddHexPrefix(qtumTx.Hex)
return ethTx, nil
}
// TODO: Does this need to return eth.JSONRPCError
// TODO: discuss
// ? There are `witness` transactions, that is not acquireable nither via `gettransaction`, nor `getrawtransaction`
func getRewardTransactionByHash(ctx context.Context, p *qtum.Qtum, hash string) (*eth.GetTransactionByHashResponse, *qtum.GetRawTransactionResponse, error) {
rawQtumTx, err := p.GetRawTransaction(ctx, hash, false)
if err != nil {
return nil, nil, errors.WithMessage(err, "couldn't get raw reward transaction")
}
ethTx := ð.GetTransactionByHashResponse{
Hash: utils.AddHexPrefix(hash),
Nonce: "0x0",
// TODO: discuss
// ? Expect this value to be always zero
// Geth returns 0x if there is no input data for a transaction
Input: "0x",
// TODO: discuss
// ? Are zero values applicable
Gas: "0x0",
GasPrice: "0x0",
R: "0xf000000000000000000000000000000000000000000000000000000000000000",
S: "0xf000000000000000000000000000000000000000000000000000000000000000",
V: "0x25",
}
if rawQtumTx.IsPending() {
// geth returns null if the tx is pending
return nil, rawQtumTx, nil
} else {
blockIndex, err := getTransactionIndexInBlock(ctx, p, hash, rawQtumTx.BlockHash)
if err != nil {
return nil, nil, errors.WithMessage(err, "couldn't get transaction index in block")
}
ethTx.TransactionIndex = hexutil.EncodeUint64(uint64(blockIndex))
blockNumber, err := getBlockNumberByHash(ctx, p, rawQtumTx.BlockHash)
if err != nil {
return nil, nil, errors.WithMessage(err, "couldn't get block number by hash")
}
ethTx.BlockNumber = hexutil.EncodeUint64(blockNumber)
ethTx.BlockHash = utils.AddHexPrefix(rawQtumTx.BlockHash)
}
for i := range rawQtumTx.Vouts {
// TODO: discuss
// ! The response may be null, even if txout is presented
_, err := p.GetTransactionOut(ctx, hash, i, rawQtumTx.IsPending())
if err != nil {
return nil, nil, errors.WithMessage(err, "couldn't get transaction out")
}
// TODO: discuss, researching
// ? Where is a reward amount
ethTx.Value = "0x0"
}
// TODO: discuss
// ? Do we have to set `from` == `0x00..00`
ethTx.From = utils.AddHexPrefix(qtum.ZeroAddress)
// I used Base58AddressToHex at the moment
// because convertQtumAddress functions causes error for
// P2Sh address(such as MUrenj2sPqEVTiNbHQ2RARiZYyTAAeKiDX) and BECH32 address (such as qc1qkt33x6hkrrlwlr6v59wptwy6zskyrjfe40y0lx)
if rawQtumTx.OP_SENDER != "" {
// addr, err := convertQtumAddress(rawQtumTx.OP_SENDER)
addr, err := p.Base58AddressToHex(rawQtumTx.OP_SENDER)
if err == nil {
ethTx.From = utils.AddHexPrefix(addr)
}
} else if len(rawQtumTx.Vins) > 0 && rawQtumTx.Vins[0].Address != "" {
// addr, err := convertQtumAddress(rawQtumTx.Vins[0].Address)
addr, err := p.Base58AddressToHex(rawQtumTx.Vins[0].Address)
if err == nil {
ethTx.From = utils.AddHexPrefix(addr)
}
}
// TODO: discuss
// ? Where is a `to`
ethTx.To = utils.AddHexPrefix(qtum.ZeroAddress)
// when sending QTUM, the first vout will be the target
// the second will be change from the vin, it will be returned to the same account
if len(rawQtumTx.Vouts) >= 2 {
from := ""
if len(rawQtumTx.Vins) > 0 {
from = rawQtumTx.Vins[0].Address
}
var valueIn int64
var valueOut int64
var refund int64
var sent int64
var sentTo int64
for _, vin := range rawQtumTx.Vins {
valueIn += vin.AmountSatoshi
}
var to string
for _, vout := range rawQtumTx.Vouts {
valueOut += vout.AmountSatoshi
addresses := vout.Details.GetAddresses()
addressesCount := len(addresses)
if addressesCount > 0 && addresses[0] == from {
refund += vout.AmountSatoshi
} else {
if addressesCount > 0 && addresses[0] != "" {
if to == "" {
to = addresses[0]
}
if to == addresses[0] {
sentTo += vout.AmountSatoshi
}
}
sent += vout.AmountSatoshi
}
}
fee := valueIn - valueOut
if fee < 0 {
// coinbase/coinstake txs have no fees since they are a part of making a block
fee = 0
}
if refund == 0 && sent == 0 {
// entire tx was burnt
} else if refund == 0 {
// no refund, entire vin was consumed
// subtract fee from sent coins
sent -= fee
sentTo -= fee
} else {
// no coins sent to anybody
// subtract fee from refund
refund -= fee
}
sentToInWei := convertFromSatoshiToWei(big.NewInt(sentTo))
ethTx.Value = hexutil.EncodeUint64(sentToInWei.Uint64())
if to != "" {
toAddress, err := p.Base58AddressToHex(to)
if err == nil {
ethTx.To = utils.AddHexPrefix(toAddress)
}
}
// TODO: compute gasPrice based on fee, guess a gas amount based on vin/vout
// gas price is set in the OP_CALL/OP_CREATE script
}
return ethTx, rawQtumTx, nil
}