github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/cmd/u2u/launcher/txtracer_cmd.go (about)

     1  package launcher
     2  
     3  import (
     4  	"compress/gzip"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"os/signal"
    10  	"strconv"
    11  	"strings"
    12  	"syscall"
    13  	"time"
    14  
    15  	"gopkg.in/urfave/cli.v1"
    16  
    17  	"github.com/unicornultrafoundation/go-u2u/cmd/utils"
    18  	"github.com/unicornultrafoundation/go-u2u/common"
    19  	"github.com/unicornultrafoundation/go-u2u/log"
    20  	"github.com/unicornultrafoundation/go-u2u/rlp"
    21  
    22  	"github.com/unicornultrafoundation/go-helios/native/idx"
    23  	"github.com/unicornultrafoundation/go-helios/u2udb"
    24  	"github.com/unicornultrafoundation/go-u2u/gossip"
    25  	"github.com/unicornultrafoundation/go-u2u/native"
    26  )
    27  
    28  type TracePayload struct {
    29  	Key    common.Hash
    30  	Traces []byte
    31  }
    32  
    33  // importTxTracer imports transaction traces from a specified file
    34  func importTxTracer(ctx *cli.Context) error {
    35  	if len(ctx.Args()) < 1 {
    36  		utils.Fatalf("This command requires an argument.")
    37  	}
    38  
    39  	// Watch for Ctrl-C while the import is running.
    40  	// If a signal is received, the import will stop.
    41  	interrupt := make(chan os.Signal, 1)
    42  	signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
    43  	defer signal.Stop(interrupt)
    44  
    45  	cfg := makeAllConfigs(ctx)
    46  
    47  	rawDbs := makeDirectDBsProducer(cfg)
    48  	gdb, err := makeRawGossipStoreTrace(rawDbs, cfg)
    49  	if err != nil {
    50  		log.Crit("DB opening error", "datadir", cfg.Node.DataDir, "err", err)
    51  	}
    52  	defer gdb.Close()
    53  
    54  	fn := ctx.Args().First()
    55  
    56  	// Open the file handle and potentially unwrap the gzip stream
    57  	fh, err := os.Open(fn)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	defer fh.Close()
    62  
    63  	var (
    64  		reader  io.Reader = fh
    65  		counter int
    66  	)
    67  	if strings.HasSuffix(fn, ".gz") {
    68  		if reader, err = gzip.NewReader(reader); err != nil {
    69  			return err
    70  		}
    71  		defer reader.(*gzip.Reader).Close()
    72  	}
    73  
    74  	log.Info("Importing transaction traces from file", "file", fn)
    75  	start, reported := time.Now(), time.Now()
    76  
    77  	stream := rlp.NewStream(reader, 0)
    78  	for {
    79  		select {
    80  		case <-interrupt:
    81  			return fmt.Errorf("interrupted")
    82  		default:
    83  		}
    84  		e := new(TracePayload)
    85  		err = stream.Decode(e)
    86  		if err == io.EOF {
    87  			break
    88  		}
    89  		if err != nil {
    90  			return err
    91  		} else {
    92  			gdb.TxTraceStore().SetTxTrace(e.Key, e.Traces)
    93  			counter++
    94  			if time.Since(reported) >= statsReportLimit {
    95  				log.Info("Importing transaction traces", "imported", counter, "elapsed", common.PrettyDuration(time.Since(start)))
    96  				reported = time.Now()
    97  			}
    98  		}
    99  	}
   100  	log.Info("Imported transaction traces", "imported", counter, "elapsed", common.PrettyDuration(time.Since(start)))
   101  
   102  	return nil
   103  }
   104  
   105  // deleteTxTracer removes transaction traces for specified block range
   106  func deleteTxTracer(ctx *cli.Context) error {
   107  
   108  	cfg := makeAllConfigs(ctx)
   109  
   110  	rawDbs := makeDirectDBsProducer(cfg)
   111  	gdb, err := makeRawGossipStoreTrace(rawDbs, cfg)
   112  	if err != nil {
   113  		log.Crit("DB opening error", "datadir", cfg.Node.DataDir, "err", err)
   114  	}
   115  	defer gdb.Close()
   116  
   117  	from := idx.Block(1)
   118  	if len(ctx.Args()) > 0 {
   119  		n, err := strconv.ParseUint(ctx.Args().Get(0), 10, 64)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		from = idx.Block(n)
   124  	}
   125  	to := gdb.GetLatestBlockIndex()
   126  	if len(ctx.Args()) > 1 {
   127  		n, err := strconv.ParseUint(ctx.Args().Get(1), 10, 64)
   128  		if err != nil {
   129  			return err
   130  		}
   131  		to = idx.Block(n)
   132  	}
   133  
   134  	log.Info("Deleting transaction traces", "from block", from, "to block", to)
   135  
   136  	err = deleteTraces(gdb, from, to)
   137  	if err != nil {
   138  		utils.Fatalf("Deleting traces error: %v\n", err)
   139  	}
   140  
   141  	return nil
   142  }
   143  
   144  // exportTxTracer exports transaction traces from specified block range
   145  func exportTxTracer(ctx *cli.Context) error {
   146  	if len(ctx.Args()) < 1 {
   147  		utils.Fatalf("This command requires an argument.")
   148  	}
   149  
   150  	cfg := makeAllConfigs(ctx)
   151  
   152  	rawDbs := makeDirectDBsProducer(cfg)
   153  	gdb, err := makeRawGossipStoreTrace(rawDbs, cfg)
   154  	if err != nil {
   155  		log.Crit("DB opening error", "datadir", cfg.Node.DataDir, "err", err)
   156  	}
   157  	defer gdb.Close()
   158  
   159  	fn := ctx.Args().First()
   160  
   161  	// Open the file handle and potentially wrap with a gzip stream
   162  	fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	defer fh.Close()
   167  
   168  	var writer io.Writer = fh
   169  	if strings.HasSuffix(fn, ".gz") {
   170  		writer = gzip.NewWriter(writer)
   171  		defer writer.(*gzip.Writer).Close()
   172  	}
   173  
   174  	from := idx.Block(1)
   175  	if len(ctx.Args()) > 1 {
   176  		n, err := strconv.ParseUint(ctx.Args().Get(1), 10, 64)
   177  		if err != nil {
   178  			return err
   179  		}
   180  		from = idx.Block(n)
   181  	}
   182  	to := gdb.GetLatestBlockIndex()
   183  	if len(ctx.Args()) > 2 {
   184  		n, err := strconv.ParseUint(ctx.Args().Get(2), 10, 64)
   185  		if err != nil {
   186  			return err
   187  		}
   188  		to = idx.Block(n)
   189  	}
   190  
   191  	log.Info("Exporting transaction traces to file", "file", fn)
   192  
   193  	err = exportTraceTo(writer, gdb, from, to)
   194  	if err != nil {
   195  		utils.Fatalf("Export error: %v\n", err)
   196  	}
   197  
   198  	return nil
   199  }
   200  
   201  func makeRawGossipStoreTrace(producer u2udb.FlushableDBProducer, cfg *config) (*gossip.Store, error) {
   202  	gdb := makeGossipStore(producer, cfg)
   203  
   204  	if gdb.TxTraceStore() == nil {
   205  		return nil, errors.New("transaction traces db store is not initialized")
   206  	}
   207  
   208  	return gdb, nil
   209  }
   210  
   211  // exportTraceTo writes the active chain
   212  func exportTraceTo(w io.Writer, gdb *gossip.Store, from, to idx.Block) (err error) {
   213  
   214  	if from == 1 && to == gdb.GetLatestBlockIndex() {
   215  		exportAllTraceTo(w, gdb)
   216  		return
   217  	}
   218  	start, reported := time.Now(), time.Now()
   219  
   220  	var (
   221  		counter int
   222  		block   *native.Block
   223  	)
   224  
   225  	for i := from; i <= to; i++ {
   226  		block = gdb.GetBlock(i)
   227  		for _, tx := range gdb.GetBlockTxs(i, block) {
   228  			traces := gdb.TxTraceStore().GetTx(tx.Hash())
   229  			if len(traces) > 0 {
   230  				counter++
   231  				rlp.Encode(w, TracePayload{tx.Hash(), traces})
   232  			}
   233  			if time.Since(reported) >= statsReportLimit {
   234  				log.Info("Exporting transaction traces", "at block", i, "exported", counter, "elapsed", common.PrettyDuration(time.Since(start)))
   235  				reported = time.Now()
   236  			}
   237  		}
   238  	}
   239  	log.Info("Exported transaction traces", "from block", from, "to block", to, "exported", counter, "elapsed", common.PrettyDuration(time.Since(start)))
   240  
   241  	return
   242  }
   243  
   244  // exportAllTraceTo writes all transaction traces of the active chain
   245  func exportAllTraceTo(w io.Writer, gdb *gossip.Store) (err error) {
   246  	start, reported := time.Now(), time.Now()
   247  	var counter int
   248  
   249  	gdb.TxTraceStore().ForEachTxtrace(func(key common.Hash, traces []byte) bool {
   250  		counter++
   251  		err = rlp.Encode(w, TracePayload{key, traces})
   252  		if err != nil {
   253  			return false
   254  		}
   255  		if time.Since(reported) >= statsReportLimit {
   256  			log.Info("Exporting all transaction traces", "exported", counter, "elapsed", common.PrettyDuration(time.Since(start)))
   257  			reported = time.Now()
   258  		}
   259  		return true
   260  	})
   261  	log.Info("Exported all transaction traces", "exported", counter, "elapsed", common.PrettyDuration(time.Since(start)))
   262  
   263  	return
   264  }
   265  
   266  // deleteTraces removes transaction traces for specified block range
   267  func deleteTraces(gdb *gossip.Store, from, to idx.Block) (err error) {
   268  	start, reported := time.Now(), time.Now()
   269  
   270  	var (
   271  		counter int
   272  	)
   273  
   274  	for i := from; i <= to; i++ {
   275  		for _, tx := range gdb.GetBlockTxs(i, gdb.GetBlock(i)) {
   276  			ok, err := gdb.TxTraceStore().HasTxTrace(tx.Hash())
   277  			if ok && err == nil {
   278  				counter++
   279  				gdb.TxTraceStore().RemoveTxTrace(tx.Hash())
   280  				if time.Since(reported) >= statsReportLimit {
   281  					log.Info("Deleting traces", "deleted", counter, "elapsed", common.PrettyDuration(time.Since(start)))
   282  					reported = time.Now()
   283  				}
   284  			}
   285  		}
   286  	}
   287  	log.Info("Deleting transaction traces done", "deleted", counter, "from block", from, "to block", to, "elapsed", common.PrettyDuration(time.Since(start)))
   288  	return
   289  }