Skip to content

Commit

Permalink
Merge pull request #109 from blocknative/TS_nbc_stable
Browse files Browse the repository at this point in the history
Native Balance Changes
  • Loading branch information
tyler-smith authored Sep 15, 2023
2 parents 9df49b9 + bb6339d commit a802766
Show file tree
Hide file tree
Showing 25 changed files with 5,477 additions and 62 deletions.
2 changes: 1 addition & 1 deletion accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis
}

filterBackend := &filterBackend{database, blockchain, backend}
backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{})
backend.filterSystem = filters.NewFilterSystem(filterBackend, nil, filters.Config{})
backend.events = filters.NewEventSystem(backend.filterSystem, false)

header := backend.blockchain.CurrentBlock()
Expand Down
2 changes: 1 addition & 1 deletion cmd/geth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)

// Configure log filter RPC API.
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth, eth.BlockChain())

// Configure GraphQL if requested.
if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {
Expand Down
4 changes: 2 additions & 2 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -1912,9 +1912,9 @@ func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, filterSyst
}

// RegisterFilterAPI adds the eth log filtering RPC API to the node.
func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config) *filters.FilterSystem {
func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config, chain *core.BlockChain) *filters.FilterSystem {
isLightClient := ethcfg.SyncMode == downloader.LightSync
filterSystem := filters.NewFilterSystem(backend, filters.Config{
filterSystem := filters.NewFilterSystem(backend, chain, filters.Config{
LogCacheSize: ethcfg.FilterLogCacheSize,
})
stack.RegisterAPIs([]rpc.API{{
Expand Down
7 changes: 5 additions & 2 deletions eth/filters/dropped_tx_subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package filters

import (
"context"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers/blocknative"
"github.com/ethereum/go-ethereum/rpc"
lru "github.com/hashicorp/golang-lru"
"sync"
Expand Down Expand Up @@ -51,6 +52,8 @@ type RPCTransaction struct {
V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`

Trace *blocknative.Trace `json:"trace,omitempty"`
}

// newRPCTransaction returns a transaction that will serialize to the RPC
Expand Down
4 changes: 3 additions & 1 deletion eth/filters/filter_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,17 @@ type Backend interface {
// FilterSystem holds resources shared by all filters.
type FilterSystem struct {
backend Backend
chain *core.BlockChain
logsCache *lru.Cache[common.Hash, *logCacheElem]
cfg *Config
}

// NewFilterSystem creates a filter system.
func NewFilterSystem(backend Backend, config Config) *FilterSystem {
func NewFilterSystem(backend Backend, chain *core.BlockChain, config Config) *FilterSystem {
config = config.withDefaults()
return &FilterSystem{
backend: backend,
chain: chain,
logsCache: lru.NewCache[common.Hash, *logCacheElem](config.LogCacheSize),
cfg: &config,
}
Expand Down
288 changes: 288 additions & 0 deletions eth/filters/trace_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
package filters

import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/blocknative"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"math/big"
)

var defaultTxTraceOpts = blocknative.TracerOpts{
BalanceChanges: true,
}

var defaultBlockTraceOpts = blocknative.TracerOpts{
BalanceChanges: true,
DisableBlockContext: true,
}

// TraceNewPendingTransactions creates a subscription that is triggered each time a
// transaction enters the transaction pool. The tx is traced and sent to the client.
func (api *FilterAPI) NewPendingTransactionsWithTrace(ctx context.Context, tracerOptsJSON *[]byte) (*rpc.Subscription, error) {
notifier, supported := rpc.NotifierFromContext(ctx)
if !supported {
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
}

rpcSub := notifier.CreateSubscription()

go func() {
chainConfig := api.sys.backend.ChainConfig()
txs := make(chan []*types.Transaction, 128)
pendingTxSub := api.events.SubscribePendingTxs(txs)
defer pendingTxSub.Unsubscribe()

tracerOpts, err := getTracerOpts(tracerOptsJSON, defaultTxTraceOpts)
if err != nil {
log.Error("failed to parse tracer options", "err", err)
return
}

for {
select {
case txs := <-txs:
var (
currentHeader = api.sys.backend.CurrentHeader()
header = &types.Header{
ParentHash: currentHeader.Hash(),
Coinbase: currentHeader.Coinbase,
Difficulty: currentHeader.Difficulty,
GasLimit: currentHeader.GasLimit,
Time: currentHeader.Time + 12,
BaseFee: eip1559.CalcBaseFee(chainConfig, currentHeader),
Number: new(big.Int).Add(currentHeader.Number, common.Big1),
}
signer = types.MakeSigner(chainConfig, header.Number, header.Time)

blockCtx = core.NewEVMBlockContext(header, api.sys.chain, nil)
traceCtx = &tracers.Context{
BlockHash: header.Hash(),
BlockNumber: header.Number,
}

msg *core.Message
tracedTxs = make([]*RPCTransaction, 0, len(txs))
blockNumber = hexutil.Big(*header.Number)
blockHash = header.Hash()
)

statedb, err := api.sys.chain.State()
if err != nil {
log.Error("NewPendingTransactionsWithTrace failed to get state", "err", err)
return
}

for _, tx := range txs {
msg, _ = core.TransactionToMessage(tx, signer, header.BaseFee)
if err != nil {
log.Error("NewPendingTransactionsWithTrace failed to create tx message", "err", err, "tx", tx.Hash())
continue
}

traceCtx.TxHash = tx.Hash()
trace, err := traceTx(msg, traceCtx, blockCtx, chainConfig, statedb, tracerOpts)
if err != nil {
log.Error("NewPendingTransactionsWithTrace failed to trace tx", "err", err, "tx", tx.Hash())
continue
}

rpcTx := newRPCPendingTransaction(tx)
rpcTx.BlockHash = &blockHash
rpcTx.BlockNumber = &blockNumber
rpcTx.Trace = trace
tracedTxs = append(tracedTxs, rpcTx)
}
notifier.Notify(rpcSub.ID, tracedTxs)
case <-rpcSub.Err():
return
case <-notifier.Closed():
return
}
}
}()

return rpcSub, nil
}

// TraceNewFullBlocks creates a subscription that is triggered each time a
// block is added to the chain. The block is traced and sent to the client.
func (api *FilterAPI) NewFullBlocksWithTrace(ctx context.Context, tracerOptsJSON *[]byte) (*rpc.Subscription, error) {
notifier, supported := rpc.NotifierFromContext(ctx)
if !supported {
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
}

rpcSub := notifier.CreateSubscription()

go func() {
headers := make(chan *types.Header, 1024)
headersSub := api.events.SubscribeNewHeads(headers)
reorgs := make(chan *core.Reorg, 1024)
reorgSub := core.SubscribeReorgs(reorgs)
defer headersSub.Unsubscribe()
defer reorgSub.Unsubscribe()
chainConfig := api.sys.backend.ChainConfig()

tracerOpts, err := getTracerOpts(tracerOptsJSON, defaultBlockTraceOpts)
if err != nil {
log.Error("failed to parse tracer options", "err", err)
return
}

var hashes []common.Hash
select {
case r := <-reorgs:
// Reverse the added blocks in the reorgs, excluding the latest block
// as it will be emitted on the newHeads channels.
hashes = make([]common.Hash, 0, len(r.Added)-1)
for i := len(r.Added) - 1; i > 0; i-- {
hashes = append(hashes, r.Added[i])
}
case h := <-headers:
hashes = []common.Hash{h.Hash()}
case <-headersSub.Err():
return
case <-reorgSub.Err():
return
case <-notifier.Closed():
return
}

for _, hash := range hashes {
block, err := api.sys.backend.BlockByHash(ctx, hash)
if err != nil {
log.Error("failed to get block", "err", err, "hash", hash)
continue
}

marshalBlock, err := RPCMarshalBlock(block, true, true, api.sys.backend.ChainConfig())
if err != nil {
continue
}

trace, err := traceBlock(block, chainConfig, api.sys.chain, tracerOpts)
if err != nil {
log.Error("failed to trace block", "err", err, "block", block.Number())
continue
}
marshalBlock["trace"] = trace

marshalReceipts := make(map[common.Hash]map[string]interface{})
receipts, err := api.sys.backend.GetReceipts(ctx, hash)
if err != nil {
continue
}
for index, receipt := range receipts {
fields := map[string]interface{}{
"transactionIndex": hexutil.Uint64(index),
"gasUsed": hexutil.Uint64(receipt.GasUsed),
"cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
"contractAddress": nil,
"logs": receipt.Logs,
"logsBloom": receipt.Bloom,
"status": hexutil.Uint64(receipt.Status),
}
if receipt.Logs == nil {
fields["logs"] = [][]*types.Log{}
}
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
if receipt.ContractAddress != (common.Address{}) {
fields["contractAddress"] = receipt.ContractAddress
}
if reason, ok := core.GetRevertReason(receipt.TxHash, hash); ok {
fields["revertReason"] = reason
}
marshalReceipts[receipt.TxHash] = fields
}
marshalBlock["receipts"] = marshalReceipts

notifier.Notify(rpcSub.ID, marshalBlock)
}
}()

return rpcSub, nil
}

// traceTx traces a transaction with the given contexts.
func traceTx(message *core.Message, txCtx *tracers.Context, vmctx vm.BlockContext, chainConfig *params.ChainConfig, statedb *state.StateDB, tracerOpts blocknative.TracerOpts) (*blocknative.Trace, error) {
tracer, err := blocknative.NewTxnOpCodeTracerWithOpts(tracerOpts)
if err != nil {
return nil, err
}

txContext := core.NewEVMTxContext(message)
vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{Tracer: tracer})
statedb.SetTxContext(txCtx.TxHash, txCtx.TxIndex)

if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit)); err != nil {
return nil, fmt.Errorf("tracing failed: %w", err)
}
return tracer.GetTrace()
}

// traceBlock traces all transactions in a block.
func traceBlock(block *types.Block, chainConfig *params.ChainConfig, chain *core.BlockChain, tracerOpts blocknative.TracerOpts) ([]*blocknative.Trace, error) {
parent := chain.GetBlockByHash(block.ParentHash())
if parent == nil {
return nil, errors.New("parent block not found")
}

statedb, err := chain.StateAt(parent.Root())
if err != nil {
return nil, err
}

var (
txs = block.Transactions()
blockHash = block.Hash()
is158 = chainConfig.IsEIP158(block.Number())
blockCtx = core.NewEVMBlockContext(block.Header(), chain, nil)
signer = types.MakeSigner(chainConfig, block.Number(), block.Time())
results = make([]*blocknative.Trace, len(txs))
)

for i, tx := range txs {
msg, err := core.TransactionToMessage(tx, signer, block.BaseFee())
if err != nil {
return nil, err
}
txCtx := &tracers.Context{
BlockHash: blockHash,
BlockNumber: block.Number(),
TxIndex: i,
TxHash: tx.Hash(),
}
results[i], err = traceTx(msg, txCtx, blockCtx, chainConfig, statedb, tracerOpts)
if err != nil {
return nil, err
}
statedb.Finalise(is158)
}

return results, nil
}

// getTracerOpts parses the tracer options from the given JSON and applies them
// on top of the default options.
func getTracerOpts(optsJSON *[]byte, defaults blocknative.TracerOpts) (blocknative.TracerOpts, error) {
opts := defaults
if optsJSON != nil {
if err := json.Unmarshal(*optsJSON, &opts); err != nil {
return blocknative.TracerOpts{}, err
}
}
return opts, nil
}
Loading

0 comments on commit a802766

Please sign in to comment.