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 }