github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/neatptc/api_tracer.go (about)

     1  package neatptc
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"runtime"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/neatlab/neatio/chain/core"
    14  	"github.com/neatlab/neatio/chain/core/rawdb"
    15  	"github.com/neatlab/neatio/chain/core/state"
    16  	"github.com/neatlab/neatio/chain/core/types"
    17  	"github.com/neatlab/neatio/chain/core/vm"
    18  	"github.com/neatlab/neatio/chain/log"
    19  	"github.com/neatlab/neatio/chain/trie"
    20  	"github.com/neatlab/neatio/internal/neatapi"
    21  	"github.com/neatlab/neatio/neatptc/tracers"
    22  	"github.com/neatlab/neatio/network/rpc"
    23  	"github.com/neatlab/neatio/utilities/common"
    24  	"github.com/neatlab/neatio/utilities/common/hexutil"
    25  	"github.com/neatlab/neatio/utilities/rlp"
    26  )
    27  
    28  const (
    29  	defaultTraceTimeout = 5 * time.Second
    30  
    31  	defaultTraceReexec = uint64(128)
    32  )
    33  
    34  type TraceConfig struct {
    35  	*vm.LogConfig
    36  	Tracer  *string
    37  	Timeout *string
    38  	Reexec  *uint64
    39  }
    40  
    41  type StdTraceConfig struct {
    42  	*vm.LogConfig
    43  	Reexec *uint64
    44  	TxHash common.Hash
    45  }
    46  
    47  type txTraceResult struct {
    48  	Result interface{} `json:"result,omitempty"`
    49  	Error  string      `json:"error,omitempty"`
    50  }
    51  
    52  type blockTraceTask struct {
    53  	statedb *state.StateDB
    54  	block   *types.Block
    55  	rootref common.Hash
    56  	results []*txTraceResult
    57  }
    58  
    59  type blockTraceResult struct {
    60  	Block  hexutil.Uint64   `json:"block"`
    61  	Hash   common.Hash      `json:"hash"`
    62  	Traces []*txTraceResult `json:"traces"`
    63  }
    64  
    65  type txTraceTask struct {
    66  	statedb *state.StateDB
    67  	index   int
    68  }
    69  
    70  func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) {
    71  
    72  	var from, to *types.Block
    73  
    74  	switch start {
    75  	case rpc.PendingBlockNumber:
    76  		from = api.eth.miner.PendingBlock()
    77  	case rpc.LatestBlockNumber:
    78  		from = api.eth.blockchain.CurrentBlock()
    79  	default:
    80  		from = api.eth.blockchain.GetBlockByNumber(uint64(start))
    81  	}
    82  	switch end {
    83  	case rpc.PendingBlockNumber:
    84  		to = api.eth.miner.PendingBlock()
    85  	case rpc.LatestBlockNumber:
    86  		to = api.eth.blockchain.CurrentBlock()
    87  	default:
    88  		to = api.eth.blockchain.GetBlockByNumber(uint64(end))
    89  	}
    90  
    91  	if from == nil {
    92  		return nil, fmt.Errorf("starting block #%d not found", start)
    93  	}
    94  	if to == nil {
    95  		return nil, fmt.Errorf("end block #%d not found", end)
    96  	}
    97  	if from.Number().Cmp(to.Number()) >= 0 {
    98  		return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start)
    99  	}
   100  	return api.traceChain(ctx, from, to, config)
   101  }
   102  
   103  func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) {
   104  
   105  	notifier, supported := rpc.NotifierFromContext(ctx)
   106  	if !supported {
   107  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
   108  	}
   109  	sub := notifier.CreateSubscription()
   110  
   111  	origin := start.NumberU64()
   112  	database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16)
   113  
   114  	if number := start.NumberU64(); number > 0 {
   115  		start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1)
   116  		if start == nil {
   117  			return nil, fmt.Errorf("parent block #%d not found", number-1)
   118  		}
   119  	}
   120  	statedb, err := state.New(start.Root(), database)
   121  	if err != nil {
   122  
   123  		reexec := defaultTraceReexec
   124  		if config != nil && config.Reexec != nil {
   125  			reexec = *config.Reexec
   126  		}
   127  
   128  		for i := uint64(0); i < reexec; i++ {
   129  			start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1)
   130  			if start == nil {
   131  				break
   132  			}
   133  			if statedb, err = state.New(start.Root(), database); err == nil {
   134  				break
   135  			}
   136  		}
   137  
   138  		if err != nil {
   139  			switch err.(type) {
   140  			case *trie.MissingNodeError:
   141  				return nil, errors.New("required historical state unavailable")
   142  			default:
   143  				return nil, err
   144  			}
   145  		}
   146  	}
   147  
   148  	blocks := int(end.NumberU64() - origin)
   149  
   150  	threads := runtime.NumCPU()
   151  	if threads > blocks {
   152  		threads = blocks
   153  	}
   154  	var (
   155  		pend    = new(sync.WaitGroup)
   156  		tasks   = make(chan *blockTraceTask, threads)
   157  		results = make(chan *blockTraceTask, threads)
   158  	)
   159  	for th := 0; th < threads; th++ {
   160  		pend.Add(1)
   161  		go func() {
   162  			defer pend.Done()
   163  
   164  			for task := range tasks {
   165  				signer := types.MakeSigner(api.eth.blockchain.Config(), task.block.Number())
   166  
   167  				for i, tx := range task.block.Transactions() {
   168  					msg, _ := tx.AsMessage(signer)
   169  					vmctx := core.NewEVMContext(msg, task.block.Header(), api.eth.blockchain, nil)
   170  
   171  					res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config)
   172  					if err != nil {
   173  						task.results[i] = &txTraceResult{Error: err.Error()}
   174  						log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err)
   175  						break
   176  					}
   177  
   178  					task.statedb.Finalise(api.eth.blockchain.Config().IsEIP158(task.block.Number()))
   179  					task.results[i] = &txTraceResult{Result: res}
   180  				}
   181  
   182  				select {
   183  				case results <- task:
   184  				case <-notifier.Closed():
   185  					return
   186  				}
   187  			}
   188  		}()
   189  	}
   190  
   191  	begin := time.Now()
   192  
   193  	go func() {
   194  		var (
   195  			logged time.Time
   196  			number uint64
   197  			traced uint64
   198  			failed error
   199  			proot  common.Hash
   200  		)
   201  
   202  		defer func() {
   203  			close(tasks)
   204  			pend.Wait()
   205  
   206  			switch {
   207  			case failed != nil:
   208  				log.Warn("Chain tracing failed", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin), "err", failed)
   209  			case number < end.NumberU64():
   210  				log.Warn("Chain tracing aborted", "start", start.NumberU64(), "end", end.NumberU64(), "abort", number, "transactions", traced, "elapsed", time.Since(begin))
   211  			default:
   212  				log.Info("Chain tracing finished", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin))
   213  			}
   214  			close(results)
   215  		}()
   216  
   217  		for number = start.NumberU64() + 1; number <= end.NumberU64(); number++ {
   218  
   219  			select {
   220  			case <-notifier.Closed():
   221  				return
   222  			default:
   223  			}
   224  
   225  			if time.Since(logged) > 8*time.Second {
   226  				if number > origin {
   227  					nodes, imgs := database.TrieDB().Size()
   228  					log.Info("Tracing chain segment", "start", origin, "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin), "memory", nodes+imgs)
   229  				} else {
   230  					log.Info("Preparing state for chain trace", "block", number, "start", origin, "elapsed", time.Since(begin))
   231  				}
   232  				logged = time.Now()
   233  			}
   234  
   235  			block := api.eth.blockchain.GetBlockByNumber(number)
   236  			if block == nil {
   237  				failed = fmt.Errorf("block #%d not found", number)
   238  				break
   239  			}
   240  
   241  			if number > origin {
   242  				txs := block.Transactions()
   243  
   244  				select {
   245  				case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: block, rootref: proot, results: make([]*txTraceResult, len(txs))}:
   246  				case <-notifier.Closed():
   247  					return
   248  				}
   249  				traced += uint64(len(txs))
   250  			}
   251  
   252  			_, _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
   253  			if err != nil {
   254  				failed = err
   255  				break
   256  			}
   257  
   258  			root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
   259  			if err != nil {
   260  				failed = err
   261  				break
   262  			}
   263  			if err := statedb.Reset(root); err != nil {
   264  				failed = err
   265  				break
   266  			}
   267  
   268  			database.TrieDB().Reference(root, common.Hash{})
   269  			if number >= origin {
   270  				database.TrieDB().Reference(root, common.Hash{})
   271  			}
   272  
   273  			if proot != (common.Hash{}) {
   274  				database.TrieDB().Dereference(proot)
   275  			}
   276  			proot = root
   277  
   278  		}
   279  	}()
   280  
   281  	go func() {
   282  		var (
   283  			done = make(map[uint64]*blockTraceResult)
   284  			next = origin + 1
   285  		)
   286  		for res := range results {
   287  
   288  			result := &blockTraceResult{
   289  				Block:  hexutil.Uint64(res.block.NumberU64()),
   290  				Hash:   res.block.Hash(),
   291  				Traces: res.results,
   292  			}
   293  			done[uint64(result.Block)] = result
   294  
   295  			database.TrieDB().Dereference(res.rootref)
   296  
   297  			for result, ok := done[next]; ok; result, ok = done[next] {
   298  				if len(result.Traces) > 0 || next == end.NumberU64() {
   299  					notifier.Notify(sub.ID, result)
   300  				}
   301  				delete(done, next)
   302  				next++
   303  			}
   304  		}
   305  	}()
   306  	return sub, nil
   307  }
   308  
   309  func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) {
   310  
   311  	var block *types.Block
   312  
   313  	switch number {
   314  	case rpc.PendingBlockNumber:
   315  		block = api.eth.miner.PendingBlock()
   316  	case rpc.LatestBlockNumber:
   317  		block = api.eth.blockchain.CurrentBlock()
   318  	default:
   319  		block = api.eth.blockchain.GetBlockByNumber(uint64(number))
   320  	}
   321  
   322  	if block == nil {
   323  		return nil, fmt.Errorf("block #%d not found", number)
   324  	}
   325  	return api.traceBlock(ctx, block, config)
   326  }
   327  
   328  func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
   329  	block := api.eth.blockchain.GetBlockByHash(hash)
   330  	if block == nil {
   331  		return nil, fmt.Errorf("block #%x not found", hash)
   332  	}
   333  	return api.traceBlock(ctx, block, config)
   334  }
   335  
   336  func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) {
   337  	block := new(types.Block)
   338  	if err := rlp.Decode(bytes.NewReader(blob), block); err != nil {
   339  		return nil, fmt.Errorf("could not decode block: %v", err)
   340  	}
   341  	return api.traceBlock(ctx, block, config)
   342  }
   343  
   344  func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) {
   345  	blob, err := ioutil.ReadFile(file)
   346  	if err != nil {
   347  		return nil, fmt.Errorf("could not read file: %v", err)
   348  	}
   349  	return api.TraceBlock(ctx, blob, config)
   350  }
   351  
   352  func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) {
   353  
   354  	if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil {
   355  		return nil, err
   356  	}
   357  	parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
   358  	if parent == nil {
   359  		return nil, fmt.Errorf("parent #%x not found", block.ParentHash())
   360  	}
   361  	reexec := defaultTraceReexec
   362  	if config != nil && config.Reexec != nil {
   363  		reexec = *config.Reexec
   364  	}
   365  	statedb, err := api.computeStateDB(parent, reexec)
   366  	if err != nil {
   367  		return nil, err
   368  	}
   369  
   370  	var (
   371  		signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number())
   372  
   373  		txs     = block.Transactions()
   374  		results = make([]*txTraceResult, len(txs))
   375  
   376  		pend = new(sync.WaitGroup)
   377  		jobs = make(chan *txTraceTask, len(txs))
   378  	)
   379  	threads := runtime.NumCPU()
   380  	if threads > len(txs) {
   381  		threads = len(txs)
   382  	}
   383  	for th := 0; th < threads; th++ {
   384  		pend.Add(1)
   385  		go func() {
   386  			defer pend.Done()
   387  
   388  			for task := range jobs {
   389  				msg, _ := txs[task.index].AsMessage(signer)
   390  				vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
   391  
   392  				res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config)
   393  				if err != nil {
   394  					results[task.index] = &txTraceResult{Error: err.Error()}
   395  					continue
   396  				}
   397  				results[task.index] = &txTraceResult{Result: res}
   398  			}
   399  		}()
   400  	}
   401  
   402  	var failed error
   403  	for i, tx := range txs {
   404  
   405  		jobs <- &txTraceTask{statedb: statedb.Copy(), index: i}
   406  
   407  		msg, _ := tx.AsMessage(signer)
   408  		vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
   409  
   410  		vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{})
   411  		if _, _, err := core.ApplyMessageEx(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
   412  			failed = err
   413  			break
   414  		}
   415  
   416  		statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
   417  	}
   418  	close(jobs)
   419  	pend.Wait()
   420  
   421  	if failed != nil {
   422  		return nil, failed
   423  	}
   424  	return results, nil
   425  }
   426  
   427  func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*state.StateDB, error) {
   428  
   429  	statedb, err := api.eth.blockchain.StateAt(block.Root())
   430  	if err == nil {
   431  		return statedb, nil
   432  	}
   433  
   434  	origin := block.NumberU64()
   435  	database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16)
   436  
   437  	for i := uint64(0); i < reexec; i++ {
   438  		block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
   439  		if block == nil {
   440  			break
   441  		}
   442  		if statedb, err = state.New(block.Root(), database); err == nil {
   443  			break
   444  		}
   445  	}
   446  	if err != nil {
   447  		switch err.(type) {
   448  		case *trie.MissingNodeError:
   449  			return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec)
   450  		default:
   451  			return nil, err
   452  		}
   453  	}
   454  
   455  	var (
   456  		start  = time.Now()
   457  		logged time.Time
   458  		proot  common.Hash
   459  	)
   460  	for block.NumberU64() < origin {
   461  
   462  		if time.Since(logged) > 8*time.Second {
   463  			log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start))
   464  			logged = time.Now()
   465  		}
   466  
   467  		if block = api.eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil {
   468  			return nil, fmt.Errorf("block #%d not found", block.NumberU64()+1)
   469  		}
   470  		_, _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
   471  		if err != nil {
   472  			return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err)
   473  		}
   474  
   475  		root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
   476  		if err != nil {
   477  			return nil, err
   478  		}
   479  		if err := statedb.Reset(root); err != nil {
   480  			return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err)
   481  		}
   482  		database.TrieDB().Reference(root, common.Hash{})
   483  		if proot != (common.Hash{}) {
   484  			database.TrieDB().Dereference(proot)
   485  		}
   486  		proot = root
   487  	}
   488  	nodes, imgs := database.TrieDB().Size()
   489  	log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs)
   490  	return statedb, nil
   491  }
   492  
   493  func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) {
   494  
   495  	tx, blockHash, _, index := rawdb.ReadTransaction(api.eth.ChainDb(), hash)
   496  	if tx == nil {
   497  		return nil, fmt.Errorf("transaction #%x not found", hash)
   498  	}
   499  	reexec := defaultTraceReexec
   500  	if config != nil && config.Reexec != nil {
   501  		reexec = *config.Reexec
   502  	}
   503  	msg, vmctx, statedb, err := api.computeTxEnv(blockHash, int(index), reexec)
   504  	if err != nil {
   505  		return nil, err
   506  	}
   507  
   508  	return api.traceTx(ctx, msg, vmctx, statedb, config)
   509  }
   510  
   511  func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
   512  
   513  	var (
   514  		tracer vm.Tracer
   515  		err    error
   516  	)
   517  	switch {
   518  	case config != nil && config.Tracer != nil:
   519  
   520  		timeout := defaultTraceTimeout
   521  		if config.Timeout != nil {
   522  			if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
   523  				return nil, err
   524  			}
   525  		}
   526  
   527  		if tracer, err = tracers.New(*config.Tracer); err != nil {
   528  			return nil, err
   529  		}
   530  
   531  		deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
   532  		go func() {
   533  			<-deadlineCtx.Done()
   534  			tracer.(*tracers.Tracer).Stop(errors.New("execution timeout"))
   535  		}()
   536  		defer cancel()
   537  
   538  	case config == nil:
   539  		tracer = vm.NewStructLogger(nil)
   540  
   541  	default:
   542  		tracer = vm.NewStructLogger(config.LogConfig)
   543  	}
   544  
   545  	vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer})
   546  
   547  	result, _, err := core.ApplyMessageEx(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
   548  	if err != nil {
   549  		return nil, fmt.Errorf("tracing failed: %v", err)
   550  	}
   551  
   552  	switch tracer := tracer.(type) {
   553  	case *vm.StructLogger:
   554  		return &neatapi.ExecutionResult{
   555  			Gas:         result.UsedGas,
   556  			Failed:      result.Failed(),
   557  			ReturnValue: fmt.Sprintf("%x", result.Revert()),
   558  			StructLogs:  neatapi.FormatLogs(tracer.StructLogs()),
   559  		}, nil
   560  
   561  	case *tracers.Tracer:
   562  		return tracer.GetResult()
   563  
   564  	default:
   565  		panic(fmt.Sprintf("bad tracer type %T", tracer))
   566  	}
   567  }
   568  
   569  func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) {
   570  
   571  	block := api.eth.blockchain.GetBlockByHash(blockHash)
   572  	if block == nil {
   573  		return nil, vm.Context{}, nil, fmt.Errorf("block %#x not found", blockHash)
   574  	}
   575  	parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
   576  	if parent == nil {
   577  		return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash())
   578  	}
   579  	statedb, err := api.computeStateDB(parent, reexec)
   580  	if err != nil {
   581  		return nil, vm.Context{}, nil, err
   582  	}
   583  
   584  	if txIndex == 0 && len(block.Transactions()) == 0 {
   585  		return nil, vm.Context{}, statedb, nil
   586  	}
   587  
   588  	signer := types.MakeSigner(api.eth.blockchain.Config(), block.Number())
   589  
   590  	for idx, tx := range block.Transactions() {
   591  
   592  		msg, _ := tx.AsMessage(signer)
   593  		context := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
   594  		if idx == txIndex {
   595  			return msg, context, statedb, nil
   596  		}
   597  
   598  		vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{})
   599  		if _, _, err := core.ApplyMessageEx(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
   600  			return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
   601  		}
   602  
   603  		statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
   604  	}
   605  	return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, blockHash)
   606  }