Ethereum: process call trace to extract internal transactions
This commit is contained in:
parent
6acf8cc38a
commit
5818ce8aa2
@ -77,8 +77,9 @@ type rpcReceipt struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type completeTransaction struct {
|
type completeTransaction struct {
|
||||||
Tx *rpcTransaction `json:"tx"`
|
Tx *rpcTransaction `json:"tx"`
|
||||||
Receipt *rpcReceipt `json:"receipt,omitempty"`
|
InternalData *bchain.EthereumInternalData `json:"internalData,omitempty"`
|
||||||
|
Receipt *rpcReceipt `json:"receipt,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type rpcBlockTransactions struct {
|
type rpcBlockTransactions struct {
|
||||||
@ -96,7 +97,7 @@ func ethNumber(n string) (int64, error) {
|
|||||||
return 0, errors.Errorf("Not a number: '%v'", n)
|
return 0, errors.Errorf("Not a number: '%v'", n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32, fixEIP55 bool) (*bchain.Tx, error) {
|
func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, internalData *bchain.EthereumInternalData, blocktime int64, confirmations uint32, fixEIP55 bool) (*bchain.Tx, error) {
|
||||||
txid := tx.Hash
|
txid := tx.Hash
|
||||||
var (
|
var (
|
||||||
fa, ta []string
|
fa, ta []string
|
||||||
@ -121,9 +122,24 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, bloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if internalData != nil {
|
||||||
|
// ignore empty internal data
|
||||||
|
if internalData.Type == bchain.CALL && len(internalData.Transfers) == 0 {
|
||||||
|
internalData = nil
|
||||||
|
} else {
|
||||||
|
if fixEIP55 {
|
||||||
|
for i := range internalData.Transfers {
|
||||||
|
it := &internalData.Transfers[i]
|
||||||
|
it.From = EIP55AddressFromAddress(it.From)
|
||||||
|
it.To = EIP55AddressFromAddress(it.To)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ct := completeTransaction{
|
ct := completeTransaction{
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
Receipt: receipt,
|
InternalData: internalData,
|
||||||
|
Receipt: receipt,
|
||||||
}
|
}
|
||||||
vs, err := hexutil.DecodeBig(tx.Value)
|
vs, err := hexutil.DecodeBig(tx.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -254,6 +270,7 @@ func hexEncodeBig(b []byte) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PackTx packs transaction to byte array
|
// PackTx packs transaction to byte array
|
||||||
|
// completeTransaction.InternalData are not packed, they are stored in a different table
|
||||||
func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||||
var err error
|
var err error
|
||||||
var n uint64
|
var n uint64
|
||||||
@ -396,7 +413,8 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
|||||||
Logs: logs,
|
Logs: logs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0, false)
|
// TODO handle internal transactions
|
||||||
|
tx, err := p.ethTxToTx(&rt, rr, nil, int64(pt.BlockTime), 0, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,6 +42,7 @@ type Configuration struct {
|
|||||||
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
||||||
MempoolTxTimeoutHours int `json:"mempoolTxTimeoutHours"`
|
MempoolTxTimeoutHours int `json:"mempoolTxTimeoutHours"`
|
||||||
QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"`
|
QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"`
|
||||||
|
ProcessInternalTransactions bool `json:"processInternalTransactions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EthereumRPC is an interface to JSON-RPC eth service.
|
// EthereumRPC is an interface to JSON-RPC eth service.
|
||||||
@ -157,11 +158,9 @@ func (b *EthereumRPC) Initialize() error {
|
|||||||
case MainNet:
|
case MainNet:
|
||||||
b.Testnet = false
|
b.Testnet = false
|
||||||
b.Network = "livenet"
|
b.Network = "livenet"
|
||||||
break
|
|
||||||
case TestNet:
|
case TestNet:
|
||||||
b.Testnet = true
|
b.Testnet = true
|
||||||
b.Network = "testnet"
|
b.Network = "testnet"
|
||||||
break
|
|
||||||
case TestNetGoerli:
|
case TestNetGoerli:
|
||||||
b.Testnet = true
|
b.Testnet = true
|
||||||
b.Network = "goerli"
|
b.Network = "goerli"
|
||||||
@ -511,6 +510,83 @@ func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]*
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 rpcTraceResult struct {
|
||||||
|
Result rpcCallTrace `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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{
|
||||||
|
Type: bchain.CREATE,
|
||||||
|
Value: *value,
|
||||||
|
From: call.From,
|
||||||
|
To: call.To,
|
||||||
|
})
|
||||||
|
|
||||||
|
} else if call.Type == "SELFDESTRUCT" {
|
||||||
|
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
|
||||||
|
Type: bchain.SELFDESTRUCT,
|
||||||
|
Value: *value,
|
||||||
|
From: call.From,
|
||||||
|
To: call.To,
|
||||||
|
})
|
||||||
|
} else if err == nil && value.BitLen() > 0 {
|
||||||
|
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
|
||||||
|
Value: *value,
|
||||||
|
From: call.From,
|
||||||
|
To: call.To,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for i := range call.Calls {
|
||||||
|
b.processCallTrace(call.Calls[i], d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInternalDataForBlock fetches debug trace using callTracer, extracts internal transfers and creations and destructions of contracts
|
||||||
|
// by design, it never returns error so that missing internal transactions do not stop the rest of the blockchain import
|
||||||
|
func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []rpcTransaction) ([]bchain.EthereumInternalData, error) {
|
||||||
|
data := make([]bchain.EthereumInternalData, len(transactions))
|
||||||
|
if b.ChainConfig.ProcessInternalTransactions {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
var trace []rpcTraceResult
|
||||||
|
err := b.rpc.CallContext(ctx, &trace, "debug_traceBlockByHash", blockHash, map[string]interface{}{"tracer": "callTracer"})
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("debug_traceBlockByHash block ", blockHash, ", error ", err)
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
if len(trace) != len(data) {
|
||||||
|
glog.Error("debug_traceBlockByHash block ", blockHash, ", error: trace length does not match block length ", len(trace), "!=", len(data))
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
for i, result := range trace {
|
||||||
|
r := &result.Result
|
||||||
|
d := &data[i]
|
||||||
|
if r.Type == "CREATE" {
|
||||||
|
d.Type = bchain.CREATE
|
||||||
|
d.Contract = r.To
|
||||||
|
} else if r.Type == "SELFDESTRUCT" {
|
||||||
|
d.Type = bchain.SELFDESTRUCT
|
||||||
|
}
|
||||||
|
for j := range r.Calls {
|
||||||
|
b.processCallTrace(r.Calls[j], d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlock returns block with given hash or height, hash has precedence if both passed
|
// GetBlock returns block with given hash or height, hash has precedence if both passed
|
||||||
func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||||
raw, err := b.getBlockRaw(hash, height, true)
|
raw, err := b.getBlockRaw(hash, height, true)
|
||||||
@ -534,10 +610,16 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internalData, err := b.getInternalDataForBlock(head.Hash, body.Transactions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
btxs := make([]bchain.Tx, len(body.Transactions))
|
btxs := make([]bchain.Tx, len(body.Transactions))
|
||||||
for i := range body.Transactions {
|
for i := range body.Transactions {
|
||||||
tx := &body.Transactions[i]
|
tx := &body.Transactions[i]
|
||||||
btx, err := b.Parser.ethTxToTx(tx, &rpcReceipt{Logs: logs[tx.Hash]}, bbh.Time, uint32(bbh.Confirmations), true)
|
btx, err := b.Parser.ethTxToTx(tx, &rpcReceipt{Logs: logs[tx.Hash]}, &internalData[i], bbh.Time, uint32(bbh.Confirmations), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash)
|
return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash)
|
||||||
}
|
}
|
||||||
@ -603,7 +685,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
|||||||
var btx *bchain.Tx
|
var btx *bchain.Tx
|
||||||
if tx.BlockNumber == "" {
|
if tx.BlockNumber == "" {
|
||||||
// mempool tx
|
// mempool tx
|
||||||
btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0, true)
|
btx, err = b.Parser.ethTxToTx(tx, nil, nil, 0, 0, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||||
}
|
}
|
||||||
@ -636,7 +718,8 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||||
}
|
}
|
||||||
btx, err = b.Parser.ethTxToTx(tx, &receipt, time, confirmations, true)
|
// TODO - handle internal tx
|
||||||
|
btx, err = b.Parser.ethTxToTx(tx, &receipt, nil, time, confirmations, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -202,6 +202,31 @@ func AddressDescriptorFromString(s string) (AddressDescriptor, error) {
|
|||||||
|
|
||||||
// EthereumType specific
|
// EthereumType specific
|
||||||
|
|
||||||
|
// EthereumInternalTransfer contains data about internal transfer
|
||||||
|
type EthereumInternalTransfer struct {
|
||||||
|
Type EthereumInternalTransactionType `json:"type"`
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
Value big.Int `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EthereumInternalTransactionType - type of ethereum transaction from internal data
|
||||||
|
type EthereumInternalTransactionType int
|
||||||
|
|
||||||
|
// EthereumInternalTransactionType enumeration
|
||||||
|
const (
|
||||||
|
CALL = EthereumInternalTransactionType(iota)
|
||||||
|
CREATE
|
||||||
|
SELFDESTRUCT
|
||||||
|
)
|
||||||
|
|
||||||
|
// EthereumInternalTransaction contains internal transfers
|
||||||
|
type EthereumInternalData struct {
|
||||||
|
Type EthereumInternalTransactionType `json:"type"`
|
||||||
|
Contract string `json:"contract,omitempty"`
|
||||||
|
Transfers []EthereumInternalTransfer `json:"transfers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Erc20Contract contains info about ERC20 contract
|
// Erc20Contract contains info about ERC20 contract
|
||||||
type Erc20Contract struct {
|
type Erc20Contract struct {
|
||||||
Contract string `json:"contract"`
|
Contract string `json:"contract"`
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user