Process ETH transaction failure reasons
This commit is contained in:
parent
91031715f7
commit
45a53e41a1
@ -4,6 +4,7 @@ import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang/protobuf/proto"
|
||||
@ -88,7 +89,7 @@ func (p *EthereumParser) ethTxToTx(tx *bchain.RpcTransaction, receipt *bchain.Rp
|
||||
}
|
||||
if internalData != nil {
|
||||
// ignore empty internal data
|
||||
if internalData.Type == bchain.CALL && len(internalData.Transfers) == 0 {
|
||||
if internalData.Type == bchain.CALL && len(internalData.Transfers) == 0 && len(internalData.Error) == 0 {
|
||||
internalData = nil
|
||||
} else {
|
||||
if fixEIP55 {
|
||||
@ -505,3 +506,45 @@ func GetEthereumTxDataFromSpecificData(coinSpecificData interface{}) *EthereumTx
|
||||
}
|
||||
return &etd
|
||||
}
|
||||
|
||||
const errorOutputSignature = "08c379a0"
|
||||
|
||||
// ParseErrorFromOutput takes output field from internal transaction data and extracts an error message from it
|
||||
// the output must have errorOutputSignature to be parsed
|
||||
func ParseErrorFromOutput(output string) string {
|
||||
if has0xPrefix(output) {
|
||||
output = output[2:]
|
||||
}
|
||||
if len(output) < 8+64+64+64 || output[:8] != errorOutputSignature {
|
||||
return ""
|
||||
}
|
||||
return parseErc20StringProperty(nil, output[8:])
|
||||
}
|
||||
|
||||
// PackInternalTransactionError packs common error messages to single byte to save DB space
|
||||
func PackInternalTransactionError(e string) string {
|
||||
if e == "execution reverted" {
|
||||
return "\x01"
|
||||
}
|
||||
if e == "out of gas" {
|
||||
return "\x02"
|
||||
}
|
||||
if e == "contract creation code storage out of gas" {
|
||||
return "\x03"
|
||||
}
|
||||
if e == "max code size exceeded" {
|
||||
return "\x04"
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// UnpackInternalTransactionError unpacks common error messages packed by PackInternalTransactionError
|
||||
func UnpackInternalTransactionError(data []byte) string {
|
||||
e := string(data)
|
||||
e = strings.ReplaceAll(e, "\x01", "Reverted. ")
|
||||
e = strings.ReplaceAll(e, "\x02", "Out of gas. ")
|
||||
e = strings.ReplaceAll(e, "\x03", "Contract creation code storage out of gas. ")
|
||||
e = strings.ReplaceAll(e, "\x04", "Max code size exceeded. ")
|
||||
return strings.TrimSpace(e)
|
||||
}
|
||||
|
||||
@ -400,3 +400,97 @@ func TestEthereumParser_GetEthereumTxData(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEthereumParser_ParseErrorFromOutput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
output string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "ParseErrorFromOutput 1",
|
||||
output: "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000031546f74616c206e756d626572206f662067726f757073206d7573742062652067726561746572207468616e207a65726f2e000000000000000000000000000000",
|
||||
want: "Total number of groups must be greater than zero.",
|
||||
},
|
||||
{
|
||||
name: "ParseErrorFromOutput 2",
|
||||
output: "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000126e6f7420656e6f7567682062616c616e63650000000000000000000000000000",
|
||||
want: "not enough balance",
|
||||
},
|
||||
{
|
||||
name: "ParseErrorFromOutput empty",
|
||||
output: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "ParseErrorFromOutput short",
|
||||
output: "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "ParseErrorFromOutput invalid signature",
|
||||
output: "0x08c379b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000126e6f7420656e6f7567682062616c616e63650000000000000000000000000000",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ParseErrorFromOutput(tt.output)
|
||||
if got != tt.want {
|
||||
t.Errorf("EthereumParser.ParseErrorFromOutput() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEthereumParser_PackInternalTransactionError_UnpackInternalTransactionError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
original string
|
||||
packed string
|
||||
unpacked string
|
||||
}{
|
||||
{
|
||||
name: "execution reverted",
|
||||
original: "execution reverted",
|
||||
packed: "\x01",
|
||||
unpacked: "Reverted.",
|
||||
},
|
||||
{
|
||||
name: "out of gas",
|
||||
original: "out of gas",
|
||||
packed: "\x02",
|
||||
unpacked: "Out of gas.",
|
||||
},
|
||||
{
|
||||
name: "contract creation code storage out of gas",
|
||||
original: "contract creation code storage out of gas",
|
||||
packed: "\x03",
|
||||
unpacked: "Contract creation code storage out of gas.",
|
||||
},
|
||||
{
|
||||
name: "max code size exceeded",
|
||||
original: "max code size exceeded",
|
||||
packed: "\x04",
|
||||
unpacked: "Max code size exceeded.",
|
||||
},
|
||||
{
|
||||
name: "unknown error",
|
||||
original: "unknown error",
|
||||
packed: "unknown error",
|
||||
unpacked: "unknown error",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
packed := PackInternalTransactionError(tt.original)
|
||||
if packed != tt.packed {
|
||||
t.Errorf("EthereumParser.PackInternalTransactionError() = %v, want %v", packed, tt.packed)
|
||||
}
|
||||
unpacked := UnpackInternalTransactionError([]byte(packed))
|
||||
if unpacked != tt.unpacked {
|
||||
t.Errorf("EthereumParser.UnpackInternalTransactionError() = %v, want %v", unpacked, tt.unpacked)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -512,19 +513,20 @@ func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]*
|
||||
|
||||
type rpcCallTrace struct {
|
||||
// CREATE, CREATE2, SELFDESTRUCT, CALL, CALLCODE, DELEGATECALL, STATICCALL
|
||||
Type string `json:"type"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
Value string `json:"value"`
|
||||
Error string `json:"error"`
|
||||
Calls []rpcCallTrace `json:"calls"`
|
||||
Type string `json:"type"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
Value string `json:"value"`
|
||||
Error string `json:"error"`
|
||||
Output string `json:"output"`
|
||||
Calls []rpcCallTrace `json:"calls"`
|
||||
}
|
||||
|
||||
type rpcTraceResult struct {
|
||||
Result rpcCallTrace `json:"result"`
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) processCallTrace(call rpcCallTrace, d *bchain.EthereumInternalData) {
|
||||
func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInternalData) {
|
||||
value, err := hexutil.DecodeBig(call.Value)
|
||||
if call.Type == "CREATE" {
|
||||
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
|
||||
@ -548,8 +550,11 @@ func (b *EthereumRPC) processCallTrace(call rpcCallTrace, d *bchain.EthereumInte
|
||||
To: call.To,
|
||||
})
|
||||
}
|
||||
if call.Error != "" {
|
||||
d.Error = call.Error
|
||||
}
|
||||
for i := range call.Calls {
|
||||
b.processCallTrace(call.Calls[i], d)
|
||||
b.processCallTrace(&call.Calls[i], d)
|
||||
}
|
||||
}
|
||||
|
||||
@ -579,7 +584,28 @@ func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []b
|
||||
d.Type = bchain.SELFDESTRUCT
|
||||
}
|
||||
for j := range r.Calls {
|
||||
b.processCallTrace(r.Calls[j], d)
|
||||
b.processCallTrace(&r.Calls[j], d)
|
||||
}
|
||||
if r.Error != "" {
|
||||
baseError := PackInternalTransactionError(r.Error)
|
||||
if len(baseError) > 1 {
|
||||
// n, _ := ethNumber(transactions[i].BlockNumber)
|
||||
// glog.Infof("Internal Data Error %d %s: unknown base error %s", n, transactions[i].Hash, baseError)
|
||||
baseError = strings.ToUpper(baseError[:1]) + baseError[1:] + ". "
|
||||
}
|
||||
outputError := ParseErrorFromOutput(r.Output)
|
||||
if len(outputError) > 0 {
|
||||
d.Error = baseError + strings.ToUpper(outputError[:1]) + outputError[1:]
|
||||
} else {
|
||||
traceError := PackInternalTransactionError(d.Error)
|
||||
if traceError == baseError {
|
||||
d.Error = baseError
|
||||
} else {
|
||||
d.Error = baseError + traceError
|
||||
}
|
||||
}
|
||||
// n, _ := ethNumber(transactions[i].BlockNumber)
|
||||
// glog.Infof("Internal Data Error %d %s: %s", n, transactions[i].Hash, UnpackInternalTransactionError([]byte(d.Error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -719,7 +745,6 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
// TODO - handle internal tx
|
||||
btx, err = b.Parser.ethTxToTx(tx, &receipt, nil, time, confirmations, true)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
|
||||
@ -27,6 +27,7 @@ type EthereumInternalData struct {
|
||||
Type EthereumInternalTransactionType `json:"type"`
|
||||
Contract string `json:"contract,omitempty"`
|
||||
Transfers []EthereumInternalTransfer `json:"transfers,omitempty"`
|
||||
Error string
|
||||
}
|
||||
|
||||
// Erc20Contract contains info about ERC20 contract
|
||||
|
||||
@ -189,6 +189,7 @@ type ethInternalData struct {
|
||||
internalType bchain.EthereumInternalTransactionType
|
||||
contract bchain.AddressDescriptor
|
||||
transfers []ethInternalTransfer
|
||||
errorMsg string
|
||||
}
|
||||
|
||||
type ethBlockTx struct {
|
||||
@ -242,6 +243,7 @@ func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses ad
|
||||
if eid.InternalData != nil {
|
||||
blockTx.internalData = ðInternalData{
|
||||
internalType: eid.InternalData.Type,
|
||||
errorMsg: eid.InternalData.Error,
|
||||
}
|
||||
// index contract creation
|
||||
if eid.InternalData.Type == bchain.CREATE {
|
||||
@ -365,6 +367,9 @@ func packEthInternalData(data *ethInternalData) []byte {
|
||||
l = packBigint(&t.value, varBuf)
|
||||
buf = append(buf, varBuf[:l]...)
|
||||
}
|
||||
if len(data.errorMsg) > 0 {
|
||||
buf = append(buf, []byte(data.errorMsg)...)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
@ -398,6 +403,7 @@ func (d *RocksDB) unpackEthInternalData(buf []byte) (*bchain.EthereumInternalDat
|
||||
t.Value, ll = unpackBigint(buf[l:])
|
||||
l += ll
|
||||
}
|
||||
id.Error = eth.UnpackInternalTransactionError(buf[l:])
|
||||
return &id, nil
|
||||
}
|
||||
|
||||
@ -423,7 +429,7 @@ func (d *RocksDB) storeInternalDataEthereumType(wb *gorocksdb.WriteBatch, blockT
|
||||
for i := range blockTxs {
|
||||
blockTx := &blockTxs[i]
|
||||
if blockTx.internalData != nil {
|
||||
wb.PutCF(d.cfh[cfInternalData], blockTx.btxID, packEthInternalData(blockTx.internalData))
|
||||
wb.PutCF(d.cfh[cfInternalData], blockTx.btxID, packEthInternalData(blockTx.internalData))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -163,6 +163,11 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
|
||||
"00" + dbtestdata.EthAddr3e + dbtestdata.EthAddr3e + "030f4242",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
dbtestdata.EthTxidB2T1,
|
||||
"00" + hex.EncodeToString([]byte(dbtestdata.EthTx3InternalData.Error)),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
dbtestdata.EthTxidB2T2,
|
||||
"05" + dbtestdata.EthAddrContract0d +
|
||||
@ -231,6 +236,7 @@ func formatInternalData(in *bchain.EthereumInternalData) *bchain.EthereumInterna
|
||||
t.From = eth.EIP55AddressFromAddress(t.From)
|
||||
t.To = eth.EIP55AddressFromAddress(t.To)
|
||||
}
|
||||
out.Error = eth.UnpackInternalTransactionError([]byte(in.Error))
|
||||
return &out
|
||||
}
|
||||
|
||||
@ -295,6 +301,10 @@ func TestRocksDB_Index_EthereumType(t *testing.T) {
|
||||
if err != nil || !reflect.DeepEqual(id, formatInternalData(dbtestdata.EthTx2InternalData)) {
|
||||
t.Errorf("GetEthereumInternalData(%s) = %+v, want %+v, err %v", dbtestdata.EthTxidB1T2, id, formatInternalData(dbtestdata.EthTx2InternalData), err)
|
||||
}
|
||||
id, err = d.GetEthereumInternalData(dbtestdata.EthTxidB2T1)
|
||||
if err != nil || !reflect.DeepEqual(id, formatInternalData(dbtestdata.EthTx3InternalData)) {
|
||||
t.Errorf("GetEthereumInternalData(%s) = %+v, want %+v, err %v", dbtestdata.EthTxidB2T1, id, formatInternalData(dbtestdata.EthTx3InternalData), err)
|
||||
}
|
||||
id, err = d.GetEthereumInternalData(dbtestdata.EthTxidB2T2)
|
||||
if err != nil || !reflect.DeepEqual(id, formatInternalData(dbtestdata.EthTx4InternalData)) {
|
||||
t.Errorf("GetEthereumInternalData(%s) = %+v, want %+v, err %v", dbtestdata.EthTxidB2T2, id, formatInternalData(dbtestdata.EthTx4InternalData), err)
|
||||
@ -348,7 +358,15 @@ func TestRocksDB_Index_EthereumType(t *testing.T) {
|
||||
|
||||
// Test tx caching functionality, leave one tx in db to test cleanup in DisconnectBlock
|
||||
testTxCache(t, d, block1, &block1.Txs[0])
|
||||
// InternalData are not packed and stored in DB, remove them so that the test does not fail
|
||||
esd, _ := block2.Txs[0].CoinSpecificData.(bchain.EthereumSpecificData)
|
||||
eid := esd.InternalData
|
||||
esd.InternalData = nil
|
||||
block2.Txs[0].CoinSpecificData = esd
|
||||
testTxCache(t, d, block2, &block2.Txs[0])
|
||||
// restore InternalData
|
||||
esd.InternalData = eid
|
||||
block2.Txs[0].CoinSpecificData = esd
|
||||
if err = d.PutTx(&block2.Txs[1], block2.Height, block2.Txs[1].Blocktime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@ -515,7 +515,7 @@ func testTxCache(t *testing.T, d *RocksDB, b *bchain.Block, tx *bchain.Tx) {
|
||||
// Confirmations are not stored in the DB, set them from input tx
|
||||
gtx.Confirmations = tx.Confirmations
|
||||
if !reflect.DeepEqual(gtx, tx) {
|
||||
t.Errorf("GetTx: %v, want %v", gtx, tx)
|
||||
t.Errorf("GetTx: %+v, want %+v", gtx, tx)
|
||||
}
|
||||
if err := d.DeleteTx(tx.Txid); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@ -54,6 +54,12 @@ var EthTx2InternalData = &bchain.EthereumInternalData{
|
||||
},
|
||||
}
|
||||
|
||||
var EthTx3InternalData = &bchain.EthereumInternalData{
|
||||
Type: bchain.CALL,
|
||||
Transfers: []bchain.EthereumInternalTransfer{},
|
||||
Error: "\x01Something wrong",
|
||||
}
|
||||
|
||||
var EthTx4InternalData = &bchain.EthereumInternalData{
|
||||
Type: bchain.CREATE,
|
||||
Contract: EthAddrContract0d,
|
||||
@ -127,7 +133,8 @@ func GetTestEthereumTypeBlock2(parser bchain.BlockChainParser) *bchain.Block {
|
||||
Confirmations: 1,
|
||||
},
|
||||
Txs: unpackTxs([]packedAndInternal{{
|
||||
packed: EthTx3Packed,
|
||||
packed: EthTx3Packed,
|
||||
internal: EthTx3InternalData,
|
||||
}, {
|
||||
packed: EthTx4Packed,
|
||||
internal: EthTx4InternalData,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user