-
Notifications
You must be signed in to change notification settings - Fork 534
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add draft EVM tracer * Add draft block tracer * Add draft debug_traceCall endpoint * Add helper struct for common methods among jsonrpc endpoints * Format struct logs in EVM tracer * Fix lint error * Fix lint error * Revert super struct of jsonrpc endpoint and add helper methods to debug endpoint * Remove Tracer argument from Apply method and add SetTracer method in Transition object * Merge CallStart & InnerCallStart, CallEnd & InnerCallEnd Fix lint error * Merge CpatureMemory, CaptureStack, and CaptureStorage into CaptureState * Cleanup the flow with tracer in VM loop * Clean up the flow with Tracer in Transition * Reconstruct tracer package * Fix comment * Create helper functions in jsonrpc package * Remove dependency of TracerConfig in debug endpoint * Add TestDebugTraceConfigDecode * Rename tracing methods in store * Add timeout config in tracer * Remove Limit from Tracer configuration * [WIP] Reverting some missing changes from previous merge commit * Fix build & test error due to wrong resolving of merge conflicts * Add unit tests in JSON-RPC layer * Add hash or number to error message in debug endpoint * Remove inline instanizing error object * Add unit tests of StructTracer * Add unit tests of EVM with Tracer * Fix minor issue that passing nil to captureCallEnd * Fix lint error * Remove comment * Add cancelLock instead of sync.Atomic to protect reason against concurrent accesses * Fix typo * Remove context argument from newTracer * Fix error message of ErrNegativeBlockNumber * Export cancel func to terminate context from newTracer * Add explain for ErrTraceGenesisBlock * Add nil check for latest header in GetNumericBlockNumber * Fix lint error * Remove resolveNum from getLogsFromBlocks * Update go.sum * Fix comment * Removed unused local variable Revert gasCopy * Set empty array in struct tracer result to adjust some tools (Hardhat, Truffle) * Ran go mod tidy Co-authored-by: Lazar Travica <lazar.travica@mvpworkshop.co>
- Loading branch information
1 parent
e22c827
commit 316ce9b
Showing
26 changed files
with
4,076 additions
and
249 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
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,230 @@ | ||
package jsonrpc | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/0xPolygon/polygon-edge/helper/hex" | ||
"github.com/0xPolygon/polygon-edge/state/runtime/tracer" | ||
"github.com/0xPolygon/polygon-edge/state/runtime/tracer/structtracer" | ||
"github.com/0xPolygon/polygon-edge/types" | ||
) | ||
|
||
var ( | ||
defaultTraceTimeout = 5 * time.Second | ||
|
||
// ErrExecutionTimeout indicates the execution was terminated due to timeout | ||
ErrExecutionTimeout = errors.New("execution timeout") | ||
// ErrTraceGenesisBlock is an error returned when tracing genesis block which can't be traced | ||
ErrTraceGenesisBlock = errors.New("genesis is not traceable") | ||
) | ||
|
||
type debugBlockchainStore interface { | ||
// Header returns the current header of the chain (genesis if empty) | ||
Header() *types.Header | ||
|
||
// GetHeaderByNumber gets a header using the provided number | ||
GetHeaderByNumber(uint64) (*types.Header, bool) | ||
|
||
// ReadTxLookup returns a block hash in which a given txn was mined | ||
ReadTxLookup(txnHash types.Hash) (types.Hash, bool) | ||
|
||
// GetBlockByHash gets a block using the provided hash | ||
GetBlockByHash(hash types.Hash, full bool) (*types.Block, bool) | ||
|
||
// GetBlockByNumber gets a block using the provided height | ||
GetBlockByNumber(num uint64, full bool) (*types.Block, bool) | ||
|
||
// TraceBlock traces all transactions in the given block | ||
TraceBlock(*types.Block, tracer.Tracer) ([]interface{}, error) | ||
|
||
// TraceTxn traces a transaction in the block, associated with the given hash | ||
TraceTxn(*types.Block, types.Hash, tracer.Tracer) (interface{}, error) | ||
|
||
// TraceCall traces a single call at the point when the given header is mined | ||
TraceCall(*types.Transaction, *types.Header, tracer.Tracer) (interface{}, error) | ||
} | ||
|
||
type debugTxPoolStore interface { | ||
GetNonce(types.Address) uint64 | ||
} | ||
|
||
type debugStateStore interface { | ||
GetAccount(root types.Hash, addr types.Address) (*Account, error) | ||
} | ||
|
||
type debugStore interface { | ||
debugBlockchainStore | ||
debugTxPoolStore | ||
debugStateStore | ||
} | ||
|
||
// Debug is the debug jsonrpc endpoint | ||
type Debug struct { | ||
store debugStore | ||
} | ||
|
||
type TraceConfig struct { | ||
EnableMemory bool `json:"enableMemory"` | ||
DisableStack bool `json:"disableStack"` | ||
DisableStorage bool `json:"disableStorage"` | ||
EnableReturnData bool `json:"enableReturnData"` | ||
Timeout *string `json:"timeout"` | ||
} | ||
|
||
func (d *Debug) TraceBlockByNumber( | ||
blockNumber BlockNumber, | ||
config *TraceConfig, | ||
) (interface{}, error) { | ||
num, err := GetNumericBlockNumber(blockNumber, d.store) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
block, ok := d.store.GetBlockByNumber(num, true) | ||
if !ok { | ||
return nil, fmt.Errorf("block %d not found", num) | ||
} | ||
|
||
return d.traceBlock(block, config) | ||
} | ||
|
||
func (d *Debug) TraceBlockByHash( | ||
blockHash types.Hash, | ||
config *TraceConfig, | ||
) (interface{}, error) { | ||
block, ok := d.store.GetBlockByHash(blockHash, true) | ||
if !ok { | ||
return nil, fmt.Errorf("block %s not found", blockHash) | ||
} | ||
|
||
return d.traceBlock(block, config) | ||
} | ||
|
||
func (d *Debug) TraceBlock( | ||
input string, | ||
config *TraceConfig, | ||
) (interface{}, error) { | ||
blockByte, decodeErr := hex.DecodeHex(input) | ||
if decodeErr != nil { | ||
return nil, fmt.Errorf("unable to decode block, %w", decodeErr) | ||
} | ||
|
||
block := &types.Block{} | ||
if err := block.UnmarshalRLP(blockByte); err != nil { | ||
return nil, err | ||
} | ||
|
||
return d.traceBlock(block, config) | ||
} | ||
|
||
func (d *Debug) TraceTransaction( | ||
txHash types.Hash, | ||
config *TraceConfig, | ||
) (interface{}, error) { | ||
tx, block := GetTxAndBlockByTxHash(txHash, d.store) | ||
if tx == nil { | ||
return nil, fmt.Errorf("tx %s not found", txHash.String()) | ||
} | ||
|
||
if block.Number() == 0 { | ||
return nil, ErrTraceGenesisBlock | ||
} | ||
|
||
tracer, cancel, err := newTracer(config) | ||
defer cancel() | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return d.store.TraceTxn(block, tx.Hash, tracer) | ||
} | ||
|
||
func (d *Debug) TraceCall( | ||
arg *txnArgs, | ||
filter BlockNumberOrHash, | ||
config *TraceConfig, | ||
) (interface{}, error) { | ||
header, err := GetHeaderFromBlockNumberOrHash(filter, d.store) | ||
if err != nil { | ||
return nil, ErrHeaderNotFound | ||
} | ||
|
||
tx, err := DecodeTxn(arg, d.store) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// If the caller didn't supply the gas limit in the message, then we set it to maximum possible => block gas limit | ||
if tx.Gas == 0 { | ||
tx.Gas = header.GasLimit | ||
} | ||
|
||
tracer, cancel, err := newTracer(config) | ||
defer cancel() | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return d.store.TraceCall(tx, header, tracer) | ||
} | ||
|
||
func (d *Debug) traceBlock( | ||
block *types.Block, | ||
config *TraceConfig, | ||
) (interface{}, error) { | ||
if block.Number() == 0 { | ||
return nil, ErrTraceGenesisBlock | ||
} | ||
|
||
tracer, cancel, err := newTracer(config) | ||
defer cancel() | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return d.store.TraceBlock(block, tracer) | ||
} | ||
|
||
// newTracer creates new tracer by config | ||
func newTracer(config *TraceConfig) ( | ||
tracer.Tracer, | ||
context.CancelFunc, | ||
error, | ||
) { | ||
var ( | ||
timeout = defaultTraceTimeout | ||
err error | ||
) | ||
|
||
if config.Timeout != nil { | ||
if timeout, err = time.ParseDuration(*config.Timeout); err != nil { | ||
return nil, nil, err | ||
} | ||
} | ||
|
||
tracer := structtracer.NewStructTracer(structtracer.Config{ | ||
EnableMemory: config.EnableMemory, | ||
EnableStack: !config.DisableStack, | ||
EnableStorage: !config.DisableStorage, | ||
EnableReturnData: config.EnableReturnData, | ||
}) | ||
|
||
timeoutCtx, cancel := context.WithTimeout(context.Background(), timeout) | ||
|
||
go func() { | ||
<-timeoutCtx.Done() | ||
|
||
if errors.Is(timeoutCtx.Err(), context.DeadlineExceeded) { | ||
tracer.Cancel(ErrExecutionTimeout) | ||
} | ||
}() | ||
|
||
// cancellation of context is done by caller | ||
return tracer, cancel, nil | ||
} |
Oops, something went wrong.