Skip to content

Commit

Permalink
Merge pull request #57 from blocknative/feature/multi-bundle
Browse files Browse the repository at this point in the history
Feature/multi bundle
  • Loading branch information
zeroecco authored Apr 11, 2022
2 parents 9c324a5 + 9218f14 commit f2b3a79
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 46 deletions.
211 changes: 196 additions & 15 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,20 @@ package core
import (
"fmt"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
"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/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)

// StateProcessor is a basic Processor, which takes care of transitioning
Expand Down Expand Up @@ -142,7 +146,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
return receipt, err
}

func applyTransactionWithResult(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, msgTx types.Message, usedGas *uint64, evm *vm.EVM, tracer *logger.StructLogger) (*types.Receipt, *ExecutionResult, []StructLogRes, error) {
func applyTransactionWithResult(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, msgTx types.Message, usedGas *uint64, evm *vm.EVM, tracer TracerResult) (*types.Receipt, *ExecutionResult, interface{}, error) {
// Create a new context to be used in the EVM environment.
txContext := NewEVMTxContext(msg)
evm.Reset(txContext, statedb)
Expand All @@ -153,15 +157,12 @@ func applyTransactionWithResult(msg types.Message, config *params.ChainConfig, b
return nil, nil, nil, err
}

traceResult := FormatLogs(tracer.StructLogs())

if err != nil {
return nil, nil, nil, err
}
traceResult, err := tracer.GetResult()
// Update the state with pending changes.
var root []byte
if config.IsByzantium(header.Number) {
statedb.Finalise(true)
// statedb.GetRefund()

} else {
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
}
Expand All @@ -178,11 +179,6 @@ func applyTransactionWithResult(msg types.Message, config *params.ChainConfig, b
// receipt.TxHash = tx.Hash()
receipt.GasUsed = result.UsedGas

// If the transaction created a contract, store the creation address in the receipt.
// if msg.To() == nil {
// receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
// }

// Set the receipt logs and create the bloom filter.
receipt.BlockHash = header.Hash()
receipt.BlockNumber = header.Number
Expand All @@ -205,9 +201,9 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
}

func ApplyUnsignedTransactionWithResult(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, msg types.Message, usedGas *uint64, cfg vm.Config) (*types.Receipt, *ExecutionResult, []StructLogRes, error) {
// Create struct logger to get JSON stack traces
tracer := logger.NewStructLogger(nil)
func ApplyUnsignedTransactionWithResult(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, msg types.Message, usedGas *uint64, cfg vm.Config) (*types.Receipt, *ExecutionResult, interface{}, error) {
// Create call tracer to get JSON stack traces
tracer := NewCallTracer(statedb)

// Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, author)
Expand Down Expand Up @@ -265,3 +261,188 @@ func FormatLogs(logs []logger.StructLog) []StructLogRes {
}
return formatted
}

type call struct {
Type string `json:"type"`
From common.Address `json:"from"`
To common.Address `json:"to"`
Value *hexutil.Big `json:"value,omitempty"`
Gas hexutil.Uint64 `json:"gas"`
GasUsed hexutil.Uint64 `json:"gasUsed"`
Input hexutil.Bytes `json:"input"`
Output hexutil.Bytes `json:"output"`
Time string `json:"time,omitempty"`
Calls []*call `json:"calls,omitempty"`
Error string `json:"error,omitempty"`
startTime time.Time
outOff uint64
outLen uint64
gasIn uint64
gasCost uint64
}

type TracerResult interface {
vm.EVMLogger
GetResult() (interface{}, error)
}

type CallTracer struct {
callStack []*call
descended bool
statedb *state.StateDB
}

func NewCallTracer(statedb *state.StateDB) TracerResult {
return &CallTracer{
callStack: []*call{},
descended: false,
statedb: statedb,
}
}

func (tracer *CallTracer) i() int {
return len(tracer.callStack) - 1
}

func (tracer *CallTracer) GetResult() (interface{}, error) {
return tracer.callStack[0], nil
}

func (tracer *CallTracer) CaptureStart(evm *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
hvalue := hexutil.Big(*value)
tracer.callStack = []*call{&call{
From: from,
To: to,
Value: &hvalue,
Gas: hexutil.Uint64(gas),
Input: hexutil.Bytes(input),
Calls: []*call{},
}}
}
func (tracer *CallTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
tracer.callStack[tracer.i()].GasUsed = hexutil.Uint64(gasUsed)
tracer.callStack[tracer.i()].Time = fmt.Sprintf("%v", t)
tracer.callStack[tracer.i()].Output = hexutil.Bytes(output)
}

func (tracer *CallTracer) descend(newCall *call) {
tracer.callStack[tracer.i()].Calls = append(tracer.callStack[tracer.i()].Calls, newCall)
tracer.callStack = append(tracer.callStack, newCall)
tracer.descended = true
}

func toAddress(value *uint256.Int) common.Address {
return common.BytesToAddress(value.Bytes())
}

func (tracer *CallTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
// for depth < len(tracer.callStack) {
// c := tracer.callStack[tracer.i()]
// c.GasUsed = c.Gas - gas
// tracer.callStack = tracer.callStack[:tracer.i()]
// }
defer func() {
if r := recover(); r != nil {
tracer.callStack[tracer.i()].Error = "internal failure"
log.Warn("Panic during trace. Recovered.", "err", r)
}
}()
if op == vm.CREATE || op == vm.CREATE2 {
inOff := scope.Stack.Back(1).Uint64()
inLen := scope.Stack.Back(2).Uint64()
hvalue := hexutil.Big(*scope.Contract.Value())
tracer.descend(&call{
Type: op.String(),
From: scope.Contract.Caller(),
Input: scope.Memory.GetCopy(int64(inOff), int64(inLen)),
gasIn: gas,
gasCost: cost,
Value: &hvalue,
startTime: time.Now(),
})
return
}
if op == vm.SELFDESTRUCT {
hvalue := hexutil.Big(*tracer.statedb.GetBalance(scope.Contract.Caller()))
tracer.descend(&call{
Type: op.String(),
From: scope.Contract.Caller(),
To: toAddress(scope.Stack.Back(0)),
// TODO: Is this input correct?
Input: scope.Contract.Input,
Value: &hvalue,
gasIn: gas,
gasCost: cost,
startTime: time.Now(),
})
return
}
if op == vm.CALL || op == vm.CALLCODE || op == vm.DELEGATECALL || op == vm.STATICCALL {
toAddress := toAddress(scope.Stack.Back(1))
if _, isPrecompile := vm.PrecompiledContractsIstanbul[toAddress]; isPrecompile {
return
}
off := 1
if op == vm.DELEGATECALL || op == vm.STATICCALL {
off = 0
}
inOff := scope.Stack.Back(2 + off).Uint64()
inLength := scope.Stack.Back(3 + off).Uint64()
newCall := &call{
Type: op.String(),
From: scope.Contract.Address(),
To: toAddress,
Input: scope.Memory.GetCopy(int64(inOff), int64(inLength)),
gasIn: gas,
gasCost: cost,
outOff: scope.Stack.Back(4 + off).Uint64(),
outLen: scope.Stack.Back(5 + off).Uint64(),
startTime: time.Now(),
}
if off == 1 {
value := hexutil.Big(*new(big.Int).SetBytes(scope.Stack.Back(2).Bytes()))
newCall.Value = &value
}
tracer.descend(newCall)
return
}
if tracer.descended {
if depth >= len(tracer.callStack) {
tracer.callStack[tracer.i()].Gas = hexutil.Uint64(gas)
}
tracer.descended = false
}
if op == vm.REVERT {
tracer.callStack[tracer.i()].Error = "execution reverted"
return
}
if depth == len(tracer.callStack)-1 {
c := tracer.callStack[tracer.i()]
// c.Time = fmt.Sprintf("%v", time.Since(c.startTime))
tracer.callStack = tracer.callStack[:len(tracer.callStack)-1]
if vm.StringToOp(c.Type) == vm.CREATE || vm.StringToOp(c.Type) == vm.CREATE2 {
c.GasUsed = hexutil.Uint64(c.gasIn - c.gasCost - gas)
ret := scope.Stack.Back(0)
if ret.Uint64() != 0 {
c.To = common.BytesToAddress(ret.Bytes())
c.Output = tracer.statedb.GetCode(c.To)
} else if c.Error == "" {
c.Error = "internal failure"
}
} else {
c.GasUsed = hexutil.Uint64(c.gasIn - c.gasCost + uint64(c.Gas) - gas)
ret := scope.Stack.Back(0)
if ret.Uint64() != 0 {
c.Output = hexutil.Bytes(scope.Memory.GetCopy(int64(c.outOff), int64(c.outLen)))
} else if c.Error == "" {
c.Error = "internal failure"
}
}
}
return
}
func (tracer *CallTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.ScopeContext, depth int, err error) {
}
func (tracer *CallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
func (tracer *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}
51 changes: 20 additions & 31 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2085,8 +2085,6 @@ func toHexSlice(b [][]byte) []string {
return r
}

// ---------------------------------------------------------------- FlashBots inspired code ----------------------------------------------------------------

// PrivateTxBundleAPI offers an API for accepting bundled transactions
type PrivateTxBundleAPI struct {
b Backend
Expand All @@ -2108,32 +2106,24 @@ func NewBundleAPI(b Backend, chain *core.BlockChain) *BundleAPI {
return &BundleAPI{b, chain}
}

// CallBundleArgs represents the arguments for a call.
// CallBundleArgs represents the arguments for a bundle of calls.
type CallBundleArgs struct {
Txs []TransactionArgs `json:"txs"`
BlockNumber rpc.BlockNumber `json:"blockNumber"`
StateBlockNumberOrHash rpc.BlockNumberOrHash `json:"stateBlockNumber"`
Coinbase *string `json:"coinbase"`
Timestamp *uint64 `json:"timestamp"`
Timeout *int64 `json:"timeout"`
GasLimit *uint64 `json:"gasLimit"`
Difficulty *big.Int `json:"difficulty"`
BaseFee *big.Int `json:"baseFee"`
}

// CallBundle will simulate a bundle of transactions at the top of a given block
// number with the state of another (or the same) block. This can be used to
// simulate future blocks with the current state, or it can be used to simulate
// a past block.
// The sender is responsible for signing the transactions and using the correct
// nonce and ensuring validity
Txs []TransactionArgs `json:"txs"`
Coinbase *string `json:"coinbase"`
Timestamp *uint64 `json:"timestamp"`
Timeout *int64 `json:"timeout"`
GasLimit *uint64 `json:"gasLimit"`
Difficulty *big.Int `json:"difficulty"`
BaseFee *big.Int `json:"baseFee"`
}

//
// CallBundle will simulate a bundle of transactions on top of
// the most recent block. Partially follows flashbots spec v0.5.
func (s *BundleAPI) CallBundle(ctx context.Context, args CallBundleArgs) (map[string]interface{}, error) {
if len(args.Txs) == 0 {
return nil, errors.New("bundle missing unsigned txs")
}
if args.BlockNumber == 0 {
return nil, errors.New("bundle missing blockNumber")
}

defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())

Expand All @@ -2142,11 +2132,12 @@ func (s *BundleAPI) CallBundle(ctx context.Context, args CallBundleArgs) (map[st
timeoutMilliSeconds = *args.Timeout
}
timeout := time.Millisecond * time.Duration(timeoutMilliSeconds)
state, parent, err := s.b.StateAndHeaderByNumberOrHash(ctx, args.StateBlockNumberOrHash)
blockNumberRPC := rpc.BlockNumber(-1)
state, parent, err := s.b.StateAndHeaderByNumber(ctx, blockNumberRPC)

if state == nil || err != nil {
return nil, err
}
blockNumber := big.NewInt(int64(args.BlockNumber))

timestamp := parent.Time + 1
if args.Timestamp != nil {
Expand All @@ -2165,14 +2156,12 @@ func (s *BundleAPI) CallBundle(ctx context.Context, args CallBundleArgs) (map[st
gasLimit = *args.GasLimit
}
var baseFee *big.Int
if args.BaseFee != nil {
baseFee = args.BaseFee
} else if s.b.ChainConfig().IsLondon(big.NewInt(args.BlockNumber.Int64())) {
baseFee = misc.CalcBaseFee(s.b.ChainConfig(), parent)
}
// Assume bn simulaton occur after london hardfork

baseFee = misc.CalcBaseFee(s.b.ChainConfig(), parent)
header := &types.Header{
ParentHash: parent.Hash(),
Number: blockNumber,
Number: big.NewInt(parent.Number.Int64()),
GasLimit: gasLimit,
Time: timestamp,
Difficulty: difficulty,
Expand Down

0 comments on commit f2b3a79

Please sign in to comment.