-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Export functionality sends transaction data and contracts to tenderly…
… servers for rerunning and debugging purposes.
- Loading branch information
Showing
42 changed files
with
2,949 additions
and
2,063 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package evm | ||
|
||
import ( | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/consensus" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/tenderly/tenderly-cli/ethereum" | ||
) | ||
|
||
type Chain struct { | ||
Header *types.Header | ||
client *ethereum.Client | ||
|
||
engine consensus.Engine | ||
|
||
cachedHeaders map[int64]*types.Header | ||
} | ||
|
||
func newChain(header *types.Header, client *ethereum.Client, cachedHeaders map[int64]*types.Header, engine consensus.Engine) *Chain { | ||
h := &types.Header{ | ||
Number: header.Number, | ||
ParentHash: header.ParentHash, | ||
UncleHash: header.UncleHash, | ||
Coinbase: header.Coinbase, | ||
Root: header.Root, | ||
TxHash: header.TxHash, | ||
ReceiptHash: header.ReceiptHash, | ||
Bloom: header.Bloom, | ||
Difficulty: header.Difficulty, | ||
GasLimit: header.GasLimit, | ||
GasUsed: header.GasUsed, | ||
Time: header.Time, | ||
Extra: header.Extra, | ||
MixDigest: header.MixDigest, | ||
Nonce: header.Nonce, | ||
} | ||
if engine != nil { | ||
h.Coinbase, _ = engine.Author(header) | ||
} | ||
|
||
cachedHeaders[header.Number.Int64()] = h | ||
|
||
return &Chain{ | ||
Header: h, | ||
client: client, | ||
|
||
engine: engine, | ||
|
||
cachedHeaders: cachedHeaders, | ||
} | ||
} | ||
|
||
func (c *Chain) Engine() consensus.Engine { | ||
if c.engine == nil { | ||
panic("engine not implemented") | ||
} | ||
|
||
return c.engine | ||
} | ||
|
||
func (c *Chain) GetHeader(hash common.Hash, number uint64) *types.Header { | ||
if number == c.Header.Number.Uint64() { | ||
c.cachedHeaders[int64(number)] = c.Header | ||
return c.Header | ||
} | ||
|
||
if c.cachedHeaders[int64(number)] != nil { | ||
return c.cachedHeaders[int64(number)] | ||
} | ||
|
||
if c.client == nil { | ||
panic("client not initiated") | ||
} | ||
|
||
blockHeader, err := c.client.GetBlockByHash(hash.String()) | ||
if err != nil { | ||
return &types.Header{} | ||
} | ||
|
||
header := &types.Header{ | ||
ParentHash: blockHeader.ParentHash(), | ||
Root: blockHeader.StateRoot(), | ||
Number: blockHeader.Number().Big(), | ||
Time: blockHeader.Time().ToInt().Uint64(), | ||
Difficulty: blockHeader.Difficulty().ToInt(), | ||
GasLimit: blockHeader.GasLimit().ToInt().Uint64(), | ||
Coinbase: blockHeader.Coinbase(), | ||
} | ||
|
||
if c.engine != nil { | ||
header.Coinbase, _ = c.engine.Author(header) | ||
} | ||
|
||
c.cachedHeaders[int64(number)] = header | ||
return header | ||
} | ||
|
||
func (c *Chain) GetHeaders() map[int64]*types.Header { | ||
return c.cachedHeaders | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
package evm | ||
|
||
import ( | ||
"encoding/binary" | ||
"fmt" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/consensus" | ||
"github.com/ethereum/go-ethereum/consensus/clique" | ||
"github.com/ethereum/go-ethereum/core" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/ethereum/go-ethereum/core/vm" | ||
"github.com/ethereum/go-ethereum/params" | ||
"github.com/pkg/errors" | ||
"github.com/tenderly/tenderly-cli/commands/state" | ||
"github.com/tenderly/tenderly-cli/ethereum" | ||
tenderlyTypes "github.com/tenderly/tenderly-cli/ethereum/types" | ||
"github.com/tenderly/tenderly-cli/model" | ||
"github.com/tenderly/tenderly-cli/userError" | ||
) | ||
|
||
type Processor struct { | ||
client *ethereum.Client | ||
|
||
chainConfig *params.ChainConfig | ||
} | ||
|
||
func NewProcessor(client *ethereum.Client, chainConfig *params.ChainConfig) *Processor { | ||
return &Processor{ | ||
client: client, | ||
chainConfig: chainConfig, | ||
} | ||
} | ||
|
||
func (p *Processor) ProcessTransaction(hash string) (*model.TransactionState, error) { | ||
_, err := p.client.GetTransaction(hash) | ||
if err != nil { | ||
return nil, userError.NewUserError( | ||
errors.Wrap(err, "unable to find transaction"), | ||
fmt.Sprintf("Transaction with hash %s not found.", hash), | ||
) | ||
} | ||
|
||
receipt, err := p.client.GetTransactionReceipt(hash) | ||
if err != nil { | ||
return nil, userError.NewUserError( | ||
errors.Wrap(err, "unable to find transaction receipt"), | ||
fmt.Sprintf("Transaction receipt with hash %s not found.", hash), | ||
) | ||
} | ||
|
||
block, err := p.client.GetBlock(receipt.BlockNumber().Value()) | ||
if err != nil { | ||
return nil, userError.NewUserError( | ||
errors.Wrap(err, "unable to get block by number"), | ||
fmt.Sprintf("Block with number %d not found.", receipt.BlockNumber()), | ||
) | ||
} | ||
|
||
return p.processTransactions(block, receipt.TransactionIndex().Value()) | ||
} | ||
|
||
func (p *Processor) processTransactions(ethBlock tenderlyTypes.Block, ti int64) (*model.TransactionState, error) { | ||
stateDB := state.NewState(p.client, ethBlock.Number().Value()) | ||
|
||
blockHeader, err := p.client.GetBlockByHash(ethBlock.Hash().String()) | ||
if err != nil { | ||
return nil, userError.NewUserError( | ||
errors.Wrap(err, "unable to get block by hash"), | ||
fmt.Sprintf("Block with hash %s not found.", ethBlock.Hash()), | ||
) | ||
} | ||
|
||
var author *common.Address | ||
if p.chainConfig.Clique == nil || blockHeader.Coinbase() != common.BytesToAddress([]byte{}) { | ||
coinbase := blockHeader.Coinbase() | ||
author = &coinbase | ||
} | ||
|
||
header := types.Header{ | ||
Number: blockHeader.Number().Big(), | ||
ParentHash: blockHeader.ParentHash(), | ||
UncleHash: blockHeader.UncleHash(), | ||
Coinbase: blockHeader.Coinbase(), | ||
Root: blockHeader.StateRoot(), | ||
TxHash: blockHeader.TxHash(), | ||
ReceiptHash: blockHeader.ReceiptHash(), | ||
Bloom: blockHeader.Bloom(), | ||
Difficulty: blockHeader.Difficulty().ToInt(), | ||
GasLimit: blockHeader.GasLimit().ToInt().Uint64(), | ||
GasUsed: blockHeader.GasUsed().ToInt().Uint64(), | ||
Time: blockHeader.Time().ToInt().Uint64(), | ||
Extra: blockHeader.ExtraData(), | ||
MixDigest: blockHeader.MixDigest(), | ||
Nonce: blockHeader.Nonce(), | ||
} | ||
|
||
return p.applyTransactions(ethBlock.Hash(), ethBlock.Transactions()[:ti+1], stateDB, header, author) | ||
} | ||
|
||
func (p Processor) applyTransactions(blockHash common.Hash, txs []tenderlyTypes.Transaction, | ||
stateDB *state.StateDB, header types.Header, author *common.Address, | ||
) (*model.TransactionState, error) { | ||
var txState *model.TransactionState | ||
for ti := 0; ti < len(txs); ti++ { | ||
tx := txs[ti] | ||
|
||
receipt, err := p.client.GetTransactionReceipt(tx.Hash().String()) | ||
if err != nil { | ||
return nil, userError.NewUserError( | ||
errors.Wrap(err, "unable to find transaction receipt"), | ||
fmt.Sprintf("Transaction receipt with hash %s not found.", tx.Hash()), | ||
) | ||
} | ||
|
||
stateDB.Prepare(tx.Hash(), blockHash, ti) | ||
snapshotId := stateDB.Snapshot() | ||
txState, err = p.applyTransaction(tx, stateDB, header, author) | ||
if err := stateDB.GetDbErr(); err != nil { | ||
ti -= 1 | ||
stateDB.RevertToSnapshot(snapshotId) | ||
stateDB.CleanErr() | ||
continue | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if txState.GasUsed != receipt.GasUsed().ToInt().Uint64() { | ||
return nil, userError.NewUserError( | ||
errors.Wrap(err, "unable to find transaction receipt"), | ||
fmt.Sprintf("Rerun gas mismatch for transaction %s, make sure chain config is correct.", | ||
tx.Hash(), | ||
), | ||
) | ||
} | ||
|
||
stateDB.Finalise(true) | ||
} | ||
|
||
return txState, nil | ||
} | ||
|
||
func (p Processor) applyTransaction(tx tenderlyTypes.Transaction, stateDB *state.StateDB, | ||
header types.Header, author *common.Address, | ||
) (*model.TransactionState, error) { | ||
message := types.NewMessage(tx.From(), tx.To(), tx.Nonce().ToInt().Uint64(), | ||
tx.Value().ToInt(), tx.Gas().ToInt().Uint64(), | ||
tx.GasPrice().ToInt(), tx.Input(), false) | ||
|
||
var engine consensus.Engine | ||
if p.chainConfig.Clique != nil { | ||
engine = clique.New(p.chainConfig.Clique, nil) | ||
} | ||
chain := newChain(&header, p.client, make(map[int64]*types.Header), engine) | ||
context := core.NewEVMContext(message, &header, chain, author) | ||
|
||
evm := vm.NewEVM(context, stateDB, p.chainConfig, vm.Config{}) | ||
|
||
_, gasUsed, failed, err := core.ApplyMessage(evm, message, new(core.GasPool).AddGas(message.Gas())) | ||
if err != nil { | ||
return nil, userError.NewUserError( | ||
errors.Wrap(err, "unable to apply message"), | ||
fmt.Sprintf("Transaction applying error with hash %s.", tx.Hash()), | ||
) | ||
} | ||
|
||
return &model.TransactionState{ | ||
GasUsed: gasUsed, | ||
Status: !failed, | ||
|
||
StateObjects: stateObjects(stateDB), | ||
Headers: headers(chain), | ||
}, nil | ||
} | ||
|
||
func stateObjects(stateDB *state.StateDB) (stateObjects []*model.StateObject) { | ||
for _, stateObject := range stateDB.GetStateObjects() { | ||
if stateObject.Used() { | ||
stateObjects = append(stateObjects, &model.StateObject{ | ||
Address: stateObject.Address().String(), | ||
Data: &model.Data{ | ||
Nonce: stateObject.OriginalNonce(), | ||
Balance: stateObject.OriginalBalance().Bytes(), | ||
CodeHash: stateObject.OriginalCodeHash(), | ||
}, | ||
Code: stateObject.GetCode(), | ||
Storage: stateObject.GetStorage(), | ||
}) | ||
} | ||
} | ||
|
||
return stateObjects | ||
} | ||
|
||
func headers(chain *Chain) (headers []*model.Header) { | ||
for _, header := range chain.GetHeaders() { | ||
gasLimit := make([]byte, 8) | ||
binary.LittleEndian.PutUint64(gasLimit, header.GasLimit) | ||
|
||
headers = append(headers, &model.Header{ | ||
Number: header.Number.Int64(), | ||
Root: header.Root.Bytes(), | ||
ParentHash: header.ParentHash.Bytes(), | ||
Timestamp: int64(header.Time), | ||
Difficulty: header.Difficulty.Bytes(), | ||
Coinbase: header.Coinbase.Bytes(), | ||
GasLimit: gasLimit, | ||
}) | ||
} | ||
|
||
return headers | ||
} |
Oops, something went wrong.