diff --git a/docs/content/chifra/accounts.md b/docs/content/chifra/accounts.md index fb971e987d..04d19696c1 100644 --- a/docs/content/chifra/accounts.md +++ b/docs/content/chifra/accounts.md @@ -239,30 +239,21 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). ``` Data models produced by this tool: diff --git a/khedra b/khedra index da355bafa6..164b2ab800 160000 --- a/khedra +++ b/khedra @@ -1 +1 @@ -Subproject commit da355bafa6d340bc75425317eb68714b5d380971 +Subproject commit 164b2ab800f0b8f2bef2fa7385ef8934b402e342 diff --git a/sdk b/sdk index d6fbfc4cec..657c3ac495 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit d6fbfc4cecdf3ddaa80a2b361d0c8a10e18e21d8 +Subproject commit 657c3ac495e540bc8926b34a8ca4530f42b18037 diff --git a/src/apps/chifra/cmd/monitors.go b/src/apps/chifra/cmd/monitors.go index d924a89446..1ba86336aa 100644 --- a/src/apps/chifra/cmd/monitors.go +++ b/src/apps/chifra/cmd/monitors.go @@ -47,10 +47,7 @@ const longMonitors = `Purpose: const notesMonitors = ` Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list).` + - If no address is presented to the --clean command, all existing monitors will be cleaned.` func init() { var capabilities caps.Capability // capabilities for chifra monitors @@ -67,12 +64,6 @@ func init() { monitorsCmd.Flags().BoolVarP(&monitorsPkg.GetOptions().List, "list", "l", false, `list monitors in the cache (--verbose for more detail)`) monitorsCmd.Flags().BoolVarP(&monitorsPkg.GetOptions().Count, "count", "c", false, `show the number of active monitors (included deleted but not removed monitors)`) monitorsCmd.Flags().BoolVarP(&monitorsPkg.GetOptions().Staged, "staged", "S", false, `for --clean, --list, and --count options only, include staged monitors`) - monitorsCmd.Flags().BoolVarP(&monitorsPkg.GetOptions().Watch, "watch", "w", false, `continually scan for new blocks and extract data as per the command file`) - monitorsCmd.Flags().StringVarP(&monitorsPkg.GetOptions().Watchlist, "watchlist", "a", "", `available with --watch option only, a file containing the addresses to watch`) - monitorsCmd.Flags().StringVarP(&monitorsPkg.GetOptions().Commands, "commands", "d", "", `available with --watch option only, the file containing the list of commands to apply to each watched address`) - monitorsCmd.Flags().Uint64VarP(&monitorsPkg.GetOptions().BatchSize, "batch_size", "b", 8, `available with --watch option only, the number of monitors to process in each batch`) - monitorsCmd.Flags().Uint64VarP(&monitorsPkg.GetOptions().RunCount, "run_count", "u", 0, `available with --watch option only, run the monitor this many times, then quit`) - monitorsCmd.Flags().Float64VarP(&monitorsPkg.GetOptions().Sleep, "sleep", "s", 14, `available with --watch option only, the number of seconds to sleep between runs`) globals.InitGlobals("monitors", monitorsCmd, &monitorsPkg.GetOptions().Globals, capabilities) monitorsCmd.SetUsageTemplate(UsageWithNotes(notesMonitors)) diff --git a/src/apps/chifra/internal/daemon/output.go b/src/apps/chifra/internal/daemon/output.go index ecebed7ce4..248501b073 100644 --- a/src/apps/chifra/internal/daemon/output.go +++ b/src/apps/chifra/internal/daemon/output.go @@ -89,6 +89,7 @@ func (opts *DaemonOptions) DaemonInternal(rCtx *output.RenderCtx) error { // Start listening to the web sockets RunWebsocketPool() + // Start listening for requests logger.Fatal(http.ListenAndServe(opts.Url, NewRouter(opts.Silent))) diff --git a/src/apps/chifra/internal/export/handle_accounting.go b/src/apps/chifra/internal/export/handle_accounting.go index afd7f8e07e..796a4e6bc8 100644 --- a/src/apps/chifra/internal/export/handle_accounting.go +++ b/src/apps/chifra/internal/export/handle_accounting.go @@ -21,7 +21,6 @@ func (opts *ExportOptions) HandleAccounting(rCtx *output.RenderCtx, monitorArray opts.Articulate = true ledgers := &ledger.Ledger{} - chain := opts.Globals.Chain abiCache := articulate.NewAbiCache(opts.Conn, opts.Articulate) testMode := opts.Globals.TestMode filter := filter.NewFilter( @@ -68,6 +67,7 @@ func (opts *ExportOptions) HandleAccounting(rCtx *output.RenderCtx, monitorArray } else if !opts.NoZero || cnt > 0 { ledgers = ledger.NewLedger( opts.Conn, + apps, mon.Address, opts.FirstBlock, opts.LastBlock, @@ -78,7 +78,6 @@ func (opts *ExportOptions) HandleAccounting(rCtx *output.RenderCtx, monitorArray opts.Reversed, &opts.Asset, ) - _ = ledgers.SetContexts(chain, apps) for _, app := range apps { if err := visitAppearance(&app); err != nil { diff --git a/src/apps/chifra/internal/export/handle_statements.go b/src/apps/chifra/internal/export/handle_statements.go index a0126a9f97..ab293e77c5 100644 --- a/src/apps/chifra/internal/export/handle_statements.go +++ b/src/apps/chifra/internal/export/handle_statements.go @@ -20,7 +20,6 @@ import ( ) func (opts *ExportOptions) HandleStatements(rCtx *output.RenderCtx, monitorArray []monitor.Monitor) error { - chain := opts.Globals.Chain testMode := opts.Globals.TestMode filter := filter.NewFilter( opts.Reversed, @@ -115,6 +114,7 @@ func (opts *ExportOptions) HandleStatements(rCtx *output.RenderCtx, monitorArray ledgers := ledger.NewLedger( opts.Conn, + apps, mon.Address, opts.FirstBlock, opts.LastBlock, @@ -125,7 +125,6 @@ func (opts *ExportOptions) HandleStatements(rCtx *output.RenderCtx, monitorArray opts.Reversed, &opts.Asset, ) - _ = ledgers.SetContexts(chain, apps) items := make([]types.Statement, 0, len(thisMap)) for _, tx := range txArray { diff --git a/src/apps/chifra/internal/export/validate.go b/src/apps/chifra/internal/export/validate.go index 9f22efc8d2..a8f197720d 100644 --- a/src/apps/chifra/internal/export/validate.go +++ b/src/apps/chifra/internal/export/validate.go @@ -153,7 +153,7 @@ func (opts *ExportOptions) validateExport() error { if opts.Accounting { if len(opts.Addrs) != 1 { - return validate.Usage("The {0} option is allows with only a single address.", "--accounting") + return validate.Usage("The {0} option allows only a single address.", "--accounting") } if !opts.Conn.IsNodeArchive() { diff --git a/src/apps/chifra/internal/monitors/README.md b/src/apps/chifra/internal/monitors/README.md index d70556302f..2a84e6aa18 100644 --- a/src/apps/chifra/internal/monitors/README.md +++ b/src/apps/chifra/internal/monitors/README.md @@ -53,30 +53,21 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). ``` Data models produced by this tool: diff --git a/src/apps/chifra/internal/monitors/handle_watch.go b/src/apps/chifra/internal/monitors/handle_watch.go deleted file mode 100644 index f73453b5f4..0000000000 --- a/src/apps/chifra/internal/monitors/handle_watch.go +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright 2021 The TrueBlocks Authors. All rights reserved. -// Use of this source code is governed by a license that can -// be found in the LICENSE file. - -package monitorsPkg - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/monitor" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/output" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/utils" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/validate" -) - -// HandleWatch starts the monitor watcher -func (opts *MonitorsOptions) HandleWatch(rCtx *output.RenderCtx) error { - opts.Globals.Cache = true - scraper := NewScraper(colors.Magenta, "MonitorScraper", opts.Sleep, 0) - - var wg sync.WaitGroup - wg.Add(1) - // Note that this never returns in normal operation - go opts.RunMonitorScraper(&wg, &scraper) - wg.Wait() - - return nil -} - -// RunMonitorScraper runs continually, never stopping and freshens any existing monitors -func (opts *MonitorsOptions) RunMonitorScraper(wg *sync.WaitGroup, s *Scraper) { - defer wg.Done() - - chain := opts.Globals.Chain - tmpPath := filepath.Join(config.PathToCache(chain), "tmp") - - s.ChangeState(true, tmpPath) - - runCount := uint64(0) - for { - if !s.Running { - s.Pause() - - } else { - monitorList := opts.getMonitorList() - if len(monitorList) == 0 { - logger.Error(validate.Usage("No monitors found. Use 'chifra list' to initialize a monitor.").Error()) - return - } - - if canceled, err := opts.Refresh(monitorList); err != nil { - logger.Error(err) - return - } else { - if canceled { - return - } - } - - runCount++ - if opts.RunCount != 0 && runCount >= opts.RunCount { - return - } - - sleep := opts.Sleep - if sleep > 0 { - ms := time.Duration(sleep*1000) * time.Millisecond - if !opts.Globals.TestMode { - logger.Info(fmt.Sprintf("Sleeping for %g seconds", sleep)) - } - time.Sleep(ms) - } - } - } -} - -type Command struct { - Fmt string `json:"fmt"` - Folder string `json:"folder"` - Cmd string `json:"cmd"` - Cache bool `json:"cache"` -} - -func (c *Command) fileName(addr base.Address) string { - return filepath.Join(c.Folder, addr.Hex()+"."+c.Fmt) -} - -func (c *Command) resolve(addr base.Address, before, after int64) string { - fn := c.fileName(addr) - if file.FileExists(fn) { - if strings.Contains(c.Cmd, "export") { - c.Cmd += fmt.Sprintf(" --first_record %d", uint64(before+1)) - c.Cmd += fmt.Sprintf(" --max_records %d", uint64(after-before+1)) // extra space won't hurt - } else { - c.Cmd += fmt.Sprintf(" %d-%d", before+1, after) - } - c.Cmd += " --append --no_header" - } - c.Cmd = strings.Replace(c.Cmd, " ", " ", -1) - ret := c.Cmd + " --fmt " + c.Fmt + " --output " + c.fileName(addr) + " " + addr.Hex() - if c.Cache { - ret += " --cache" - } - return ret -} - -func (c *Command) String() string { - b, _ := json.MarshalIndent(c, "", " ") - return string(b) -} - -func (opts *MonitorsOptions) Refresh(monitors []monitor.Monitor) (bool, error) { - theCmds, err := opts.getCommands() - if err != nil { - return false, err - } - - batches := batchSlice[monitor.Monitor](monitors, opts.BatchSize) - for i := 0; i < len(batches); i++ { - addrs := []base.Address{} - countsBefore := []int64{} - for _, mon := range batches[i] { - addrs = append(addrs, mon.Address) - countsBefore = append(countsBefore, mon.Count()) - } - - batchSize := int(opts.BatchSize) - fmt.Printf("%s%d-%d of %d:%s chifra export --freshen", - colors.BrightBlue, - i*batchSize, - base.Min(((i+1)*batchSize)-1, len(monitors)), - len(monitors), - colors.Green) - for _, addr := range addrs { - fmt.Printf(" %s", addr.Hex()) - } - fmt.Println(colors.Off) - - canceled, err := opts.FreshenMonitorsForWatch(addrs) - if canceled || err != nil { - return canceled, err - } - - for j := 0; j < len(batches[i]); j++ { - mon := batches[i][j] - countAfter := mon.Count() - - if countAfter > 1000000 { - // TODO: Make this value configurable - fmt.Println(colors.Red, "Too many transactions for address", mon.Address, colors.Off) - continue - } - - if countAfter == 0 { - continue - } - - logger.Info(fmt.Sprintf("Processing item %d in batch %d: %d %d\n", j, i, countsBefore[j], countAfter)) - - for _, cmd := range theCmds { - countBefore := countsBefore[j] - if countBefore == 0 || countAfter > countBefore { - utils.System(cmd.resolve(mon.Address, countBefore, countAfter)) - // o := opts - // o.Globals.File = "" - // _ = o.Globals.PassItOn("acctExport", chain, cmd, []string{}) - } else if opts.Globals.Verbose { - fmt.Println("No new transactions for", mon.Address.Hex(), "since last run.") - } - } - } - } - return false, nil -} - -func batchSlice[T any](slice []T, batchSize uint64) [][]T { - var batches [][]T - for i := 0; i < len(slice); i += int(batchSize) { - end := i + int(batchSize) - if end > len(slice) { - end = len(slice) - } - batches = append(batches, slice[i:end]) - } - return batches -} - -func GetExportFormat(cmd, def string) string { - if strings.Contains(cmd, "json") { - return "json" - } else if strings.Contains(cmd, "txt") { - return "txt" - } else if strings.Contains(cmd, "csv") { - return "csv" - } - if len(def) > 0 { - return def - } - return "csv" -} - -func (opts *MonitorsOptions) cleanLine(lineIn string) (cmd Command, err error) { - line := strings.Replace(lineIn, "[{ADDRESS}]", "", -1) - if strings.Contains(line, "--fmt") { - line = strings.Replace(line, "--fmt", "", -1) - line = strings.Replace(line, "json", "", -1) - line = strings.Replace(line, "csv", "", -1) - line = strings.Replace(line, "txt", "", -1) - } - line = utils.StripComments(line) - if len(line) == 0 { - return Command{}, nil - } - - folder, err := opts.getOutputFolder(line) - if err != nil { - return Command{}, err - } - - _ = file.EstablishFolder(folder) - return Command{Cmd: line, Folder: folder, Fmt: GetExportFormat(lineIn, "csv"), Cache: opts.Globals.Cache}, nil -} - -func (opts *MonitorsOptions) getCommands() (ret []Command, err error) { - lines := file.AsciiFileToLines(opts.Commands) - for _, line := range lines { - // orig := line - if cmd, err := opts.cleanLine(line); err != nil { - return nil, err - } else if len(cmd.Cmd) == 0 { - continue - } else { - ret = append(ret, cmd) - } - } - return ret, nil -} - -func (opts *MonitorsOptions) getOutputFolder(orig string) (string, error) { - okMap := map[string]bool{ - "export": true, - "list": true, - "state": true, - "tokens": true, - } - - cmdLine := orig - parts := strings.Split(strings.Replace(cmdLine, " ", " ", -1), " ") - if len(parts) < 1 || parts[0] != "chifra" { - s := fmt.Sprintf("Invalid command: %s. Must start with 'chifra'.", strings.Trim(orig, " \t\n\r")) - logger.Fatal(s) - } - if len(parts) < 2 || !okMap[parts[1]] { - s := fmt.Sprintf("Invalid command: %s. Must start with 'chifra export', 'chifra list', 'chifra state', or 'chifra tokens'.", orig) - logger.Fatal(s) - } - - cwd, _ := os.Getwd() - cmdLine += " " - folder := "unknown" - if parts[1] == "export" { - if strings.Contains(cmdLine, "-p ") || strings.Contains(cmdLine, "--appearances ") { - folder = filepath.Join(cwd, parts[1], "appearances") - } else if strings.Contains(cmdLine, "-r ") || strings.Contains(cmdLine, "--receipts ") { - folder = filepath.Join(cwd, parts[1], "receipts") - } else if strings.Contains(cmdLine, "-l ") || strings.Contains(cmdLine, "--logs ") { - folder = filepath.Join(cwd, parts[1], "logs") - } else if strings.Contains(cmdLine, "-t ") || strings.Contains(cmdLine, "--traces ") { - folder = filepath.Join(cwd, parts[1], "traces") - } else if strings.Contains(cmdLine, "-n ") || strings.Contains(cmdLine, "--neighbors ") { - folder = filepath.Join(cwd, parts[1], "neighbors") - } else if strings.Contains(cmdLine, "-C ") || strings.Contains(cmdLine, "--accounting ") { - folder = filepath.Join(cwd, parts[1], "accounting") - } else if strings.Contains(cmdLine, "-A ") || strings.Contains(cmdLine, "--statements ") { - folder = filepath.Join(cwd, parts[1], "statements") - } else if strings.Contains(cmdLine, "-b ") || strings.Contains(cmdLine, "--balances ") { - folder = filepath.Join(cwd, parts[1], "balances") - } else { - folder = filepath.Join(cwd, parts[1], "transactions") - } - - } else if parts[1] == "list" { - folder = filepath.Join(cwd, parts[1], "appearances") - - } else if parts[1] == "state" { - if strings.Contains(cmdLine, "-l ") || strings.Contains(cmdLine, "--call ") { - folder = filepath.Join(cwd, parts[1], "calls") - } else { - folder = filepath.Join(cwd, parts[1], "blocks") - } - - } else if parts[1] == "tokens" { - if strings.Contains(cmdLine, "-b ") || strings.Contains(cmdLine, "--by_acct ") { - folder = filepath.Join(cwd, parts[1], "by_acct") - } else { - folder = filepath.Join(cwd, parts[1], "blocks") - } - } - - if strings.Contains(folder, "unknown") { - return "", fmt.Errorf("unable to determine output folder for command: %s", cmdLine) - } - - if abs, err := filepath.Abs(filepath.Join(opts.Globals.Chain, folder)); err != nil { - return "", err - } else { - return abs, nil - } -} - -func (opts *MonitorsOptions) getMonitorList() []monitor.Monitor { - var monitors []monitor.Monitor - - monitorChan := make(chan monitor.Monitor) - go monitor.ListWatchedMonitors(opts.Globals.Chain, opts.Watchlist, monitorChan) - - for result := range monitorChan { - switch result.Address { - case base.NotAMonitor: - logger.Info(fmt.Sprintf("Loaded %d monitors", len(monitors))) - close(monitorChan) - default: - if result.Count() > 500000 { - logger.Warn("Ignoring too-large address", result.Address) - continue - } - monitors = append(monitors, result) - } - } - - return monitors -} diff --git a/src/apps/chifra/internal/monitors/options.go b/src/apps/chifra/internal/monitors/options.go index f240ca529e..1c19c47df8 100644 --- a/src/apps/chifra/internal/monitors/options.go +++ b/src/apps/chifra/internal/monitors/options.go @@ -28,31 +28,22 @@ import ( // MonitorsOptions provides all command options for the chifra monitors command. type MonitorsOptions struct { - Addrs []string `json:"addrs,omitempty"` // One or more addresses (0x...) to process - Delete bool `json:"delete,omitempty"` // Delete a monitor, but do not remove it - Undelete bool `json:"undelete,omitempty"` // Undelete a previously deleted monitor - Remove bool `json:"remove,omitempty"` // Remove a previously deleted monitor - Clean bool `json:"clean,omitempty"` // Clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - List bool `json:"list,omitempty"` // List monitors in the cache (--verbose for more detail) - Count bool `json:"count,omitempty"` // Show the number of active monitors (included deleted but not removed monitors) - Staged bool `json:"staged,omitempty"` // For --clean, --list, and --count options only, include staged monitors - Watch bool `json:"watch,omitempty"` // Continually scan for new blocks and extract data as per the command file - Watchlist string `json:"watchlist,omitempty"` // Available with --watch option only, a file containing the addresses to watch - Commands string `json:"commands,omitempty"` // Available with --watch option only, the file containing the list of commands to apply to each watched address - BatchSize uint64 `json:"batchSize,omitempty"` // Available with --watch option only, the number of monitors to process in each batch - RunCount uint64 `json:"runCount,omitempty"` // Available with --watch option only, run the monitor this many times, then quit - Sleep float64 `json:"sleep,omitempty"` // Available with --watch option only, the number of seconds to sleep between runs - Globals globals.GlobalOptions `json:"globals,omitempty"` // The global options - Conn *rpc.Connection `json:"conn,omitempty"` // The connection to the RPC server - BadFlag error `json:"badFlag,omitempty"` // An error flag if needed + Addrs []string `json:"addrs,omitempty"` // One or more addresses (0x...) to process + Delete bool `json:"delete,omitempty"` // Delete a monitor, but do not remove it + Undelete bool `json:"undelete,omitempty"` // Undelete a previously deleted monitor + Remove bool `json:"remove,omitempty"` // Remove a previously deleted monitor + Clean bool `json:"clean,omitempty"` // Clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + List bool `json:"list,omitempty"` // List monitors in the cache (--verbose for more detail) + Count bool `json:"count,omitempty"` // Show the number of active monitors (included deleted but not removed monitors) + Staged bool `json:"staged,omitempty"` // For --clean, --list, and --count options only, include staged monitors + Globals globals.GlobalOptions `json:"globals,omitempty"` // The global options + Conn *rpc.Connection `json:"conn,omitempty"` // The connection to the RPC server + BadFlag error `json:"badFlag,omitempty"` // An error flag if needed // EXISTING_CODE // EXISTING_CODE } -var defaultMonitorsOptions = MonitorsOptions{ - BatchSize: 8, - Sleep: 14, -} +var defaultMonitorsOptions = MonitorsOptions{} // testLog is used only during testing to export the options for this test case. func (opts *MonitorsOptions) testLog() { @@ -64,12 +55,6 @@ func (opts *MonitorsOptions) testLog() { logger.TestLog(opts.List, "List: ", opts.List) logger.TestLog(opts.Count, "Count: ", opts.Count) logger.TestLog(opts.Staged, "Staged: ", opts.Staged) - logger.TestLog(opts.Watch, "Watch: ", opts.Watch) - logger.TestLog(len(opts.Watchlist) > 0, "Watchlist: ", opts.Watchlist) - logger.TestLog(len(opts.Commands) > 0, "Commands: ", opts.Commands) - logger.TestLog(opts.BatchSize != 8, "BatchSize: ", opts.BatchSize) - logger.TestLog(opts.RunCount != 0, "RunCount: ", opts.RunCount) - logger.TestLog(opts.Sleep != float64(14), "Sleep: ", opts.Sleep) opts.Conn.TestLog(opts.getCaches()) opts.Globals.TestLog() } @@ -93,8 +78,6 @@ func MonitorsFinishParseInternal(w io.Writer, values url.Values) *MonitorsOption copy := defaultMonitorsOptions copy.Globals.Caps = getCaps() opts := © - opts.BatchSize = 8 - opts.Sleep = 14 for key, value := range values { switch key { case "addrs": @@ -116,18 +99,6 @@ func MonitorsFinishParseInternal(w io.Writer, values url.Values) *MonitorsOption opts.Count = true case "staged": opts.Staged = true - case "watch": - opts.Watch = true - case "watchlist": - opts.Watchlist = value[0] - case "commands": - opts.Commands = value[0] - case "batchSize": - opts.BatchSize = base.MustParseUint64(value[0]) - case "runCount": - opts.RunCount = base.MustParseUint64(value[0]) - case "sleep": - opts.Sleep = base.MustParseFloat64(value[0]) default: if !copy.Globals.Caps.HasKey(key) { err := validate.Usage("Invalid key ({0}) in {1} route.", key, "monitors") @@ -204,8 +175,6 @@ func ResetOptions(testMode bool) { opts.Globals.TestMode = testMode opts.Globals.Writer = w opts.Globals.Caps = getCaps() - opts.BatchSize = 8 - opts.Sleep = 14 defaultMonitorsOptions = opts } diff --git a/src/apps/chifra/internal/monitors/output.go b/src/apps/chifra/internal/monitors/output.go index f9086e2a3f..8cd36503f6 100644 --- a/src/apps/chifra/internal/monitors/output.go +++ b/src/apps/chifra/internal/monitors/output.go @@ -82,8 +82,6 @@ func (opts *MonitorsOptions) MonitorsInternal(rCtx *output.RenderCtx) error { err = opts.HandleClean(rCtx) } else if opts.List { err = opts.HandleList(rCtx) - } else if opts.Watch { - err = opts.HandleWatch(rCtx) } else if opts.anyCrud() { err = opts.HandleCrud(rCtx) } else { diff --git a/src/apps/chifra/internal/monitors/validate.go b/src/apps/chifra/internal/monitors/validate.go index f4cbe0af9a..a09c9a6749 100644 --- a/src/apps/chifra/internal/monitors/validate.go +++ b/src/apps/chifra/internal/monitors/validate.go @@ -5,13 +5,7 @@ package monitorsPkg import ( - "errors" - "path/filepath" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/index" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/validate" ) @@ -38,89 +32,28 @@ func (opts *MonitorsOptions) validateMonitors() error { } else { // Count dominates if present if !opts.Count { - if opts.Watch { - if opts.Globals.IsApiMode() { - return validate.Usage("The {0} options is not available from the API", "--watch") - } - - if len(opts.Globals.File) > 0 { - return validate.Usage("The {0} option is not allowed with the {1} option. Use {2} instead.", "--file", "--watch", "--commands") - } - - if len(opts.Commands) == 0 { - return validate.Usage("The {0} option requires {1}.", "--watch", "a --commands file") - } else { - cmdFile, err := filepath.Abs(opts.Commands) - if err != nil || !file.FileExists(cmdFile) { - return validate.Usage("The {0} option requires {1} to exist.", "--watch", opts.Commands) - } - if file.FileSize(cmdFile) == 0 { - logger.Fatal(validate.Usage("The file you specified ({0}) was found but contained no commands.", cmdFile).Error()) - } + // We validate some of the simpler curd commands here and the rest in HandleCrud + if opts.Undelete { + if opts.Delete || opts.Remove { + return validate.Usage("The --undelete option may not be used with --delete or --remove.") } + } - if len(opts.Watchlist) == 0 { - return validate.Usage("The {0} option requires {1}.", "--watch", "a --watchlist file") - } else { - if opts.Watchlist != "existing" { - watchList, err := filepath.Abs(opts.Watchlist) - if err != nil || !file.FileExists(watchList) { - return validate.Usage("The {0} option requires {1} to exist.", "--watch", opts.Watchlist) - } - if file.FileSize(watchList) == 0 { - logger.Fatal(validate.Usage("The file you specified ({0}) was found but contained no addresses.", watchList).Error()) - } - } - } + if !opts.Clean && len(opts.Addrs) == 0 { + return validate.Usage("You must provide at least one Ethereum address for this command.") + } - if err := index.IsInitialized(chain, config.ExpectedVersion()); err != nil { - if (errors.Is(err, index.ErrNotInitialized) || errors.Is(err, index.ErrIncorrectHash)) && !opts.Globals.IsApiMode() { - logger.Fatal(err) - } - return err - } + if !opts.Clean && !opts.Delete && !opts.Undelete && !opts.Remove && !opts.Globals.Decache { + return validate.Usage("Please provide either --clean or one of the CRUD commands.") + } - if opts.BatchSize < 1 { - return validate.Usage("The {0} option must be greater than zero.", "--batch_size") - } - } else { - if opts.BatchSize != 8 { - return validate.Usage("The {0} option is not available{1}.", "--batch_size", " without --watch") + if !opts.Globals.IsApiMode() && !opts.Clean { + if len(opts.Globals.File) > 0 { + // Do nothing } else { - opts.BatchSize = 0 - } - - if opts.RunCount > 0 { - return validate.Usage("The {0} option is not available{1}.", "--run_count", " without --watch") - } - - if opts.Sleep != 14 { - return validate.Usage("The {0} option is not available{1}.", "--sleep", " without --watch") - } - - // We validate some of the simpler curd commands here and the rest in HandleCrud - if opts.Undelete { - if opts.Delete || opts.Remove { - return validate.Usage("The --undelete option may not be used with --delete or --remove.") - } - } - - if !opts.Clean && len(opts.Addrs) == 0 { - return validate.Usage("You must provide at least one Ethereum address for this command.") - } - - if !opts.Clean && !opts.Delete && !opts.Undelete && !opts.Remove && !opts.Globals.Decache { - return validate.Usage("Please provide either --clean or one of the CRUD commands.") - } - - if !opts.Globals.IsApiMode() && !opts.Clean { - if len(opts.Globals.File) > 0 { - // Do nothing - } else { - err := validate.ValidateAtLeastOneNonSentinal(opts.Addrs) - if err != nil { - return err - } + err := validate.ValidateAtLeastOneNonSentinal(opts.Addrs) + if err != nil { + return err } } } diff --git a/src/apps/chifra/internal/scrape/notify.go b/src/apps/chifra/internal/scrape/notify.go index 761577dad8..210c7ac9a9 100644 --- a/src/apps/chifra/internal/scrape/notify.go +++ b/src/apps/chifra/internal/scrape/notify.go @@ -14,6 +14,8 @@ import ( "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/notify" ) +// NOTIFY CODE + var ErrConfiguredButNotRunning = fmt.Errorf("listener is configured but not running") // GetNotifyEndpoint returns the notification endpoint diff --git a/src/apps/chifra/internal/scrape/scrape_blaze.go b/src/apps/chifra/internal/scrape/scrape_blaze.go index 362912db8a..bb8a668463 100644 --- a/src/apps/chifra/internal/scrape/scrape_blaze.go +++ b/src/apps/chifra/internal/scrape/scrape_blaze.go @@ -199,6 +199,7 @@ func (bm *BlazeManager) WriteAppearances(bn base.Blknum, addrMap uniq.AddressBoo } } + // NOTIFY CODE if bm.opts.Notify && bn <= bm.ripeBlock { err = Notify(notify.Notification[[]notify.NotificationPayloadAppearance]{ Msg: notify.MessageAppearance, diff --git a/src/apps/chifra/internal/scrape/scrape_chunk_notify.go b/src/apps/chifra/internal/scrape/scrape_chunk_notify.go index 08ff7dcff0..072fa53dd6 100644 --- a/src/apps/chifra/internal/scrape/scrape_chunk_notify.go +++ b/src/apps/chifra/internal/scrape/scrape_chunk_notify.go @@ -7,6 +7,7 @@ import ( "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/notify" ) +// NOTIFY CODE func (opts *ScrapeOptions) NotifyChunkWritten(chunk index.Chunk, chunkPath string) (err error) { if !opts.Notify { return nil @@ -22,6 +23,7 @@ func (opts *ScrapeOptions) NotifyChunkWritten(chunk index.Chunk, chunkPath strin // Generate range from path, as chunks sometimes don't have Range set chunkRange := base.RangeFromFilename(index.ToIndexPath(chunkPath)) + // NOTIFY CODE return Notify(notify.Notification[[]notify.NotificationPayloadChunkWritten]{ Msg: notify.MessageChunkWritten, Meta: nil, diff --git a/src/apps/chifra/internal/scrape/scrape_consolidate.go b/src/apps/chifra/internal/scrape/scrape_consolidate.go index eaa9715a30..aecadccaa0 100644 --- a/src/apps/chifra/internal/scrape/scrape_consolidate.go +++ b/src/apps/chifra/internal/scrape/scrape_consolidate.go @@ -106,6 +106,7 @@ func (bm *BlazeManager) Consolidate(ctx context.Context, blocks []base.Blknum) e } else { logger.Info(report.Report(isSnap, file.FileSize(chunkPath))) } + // NOTIFY CODE if err = bm.opts.NotifyChunkWritten(chunk, chunkPath); err != nil { return err } @@ -152,6 +153,7 @@ func (bm *BlazeManager) Consolidate(ctx context.Context, blocks []base.Blknum) e nAppsNow := int(file.FileSize(stageFn) / asciiAppearanceSize) bm.report(len(blocks), int(bm.PerChunk()), nChunks, nAppsNow, nAppsFound, nAddrsFound) + // NOTIFY CODE if bm.opts.Notify { if err := Notify(notify.Notification[string]{ Msg: notify.MessageStageUpdated, diff --git a/src/apps/chifra/internal/scrape/scrape_prepare.go b/src/apps/chifra/internal/scrape/scrape_prepare.go index d5c4f5abd4..60b17cf6ac 100644 --- a/src/apps/chifra/internal/scrape/scrape_prepare.go +++ b/src/apps/chifra/internal/scrape/scrape_prepare.go @@ -65,6 +65,7 @@ func (opts *ScrapeOptions) Prepare() (ok bool, err error) { } else { logger.Info(report.Report(true /* isSnapped */, file.FileSize(indexPath))) } + // NOTIFY CODE if err = opts.NotifyChunkWritten(chunk, indexPath); err != nil { return false, err } diff --git a/src/apps/chifra/pkg/articulate/events.go b/src/apps/chifra/pkg/articulate/events.go index f22f0ee8aa..6bee53c982 100644 --- a/src/apps/chifra/pkg/articulate/events.go +++ b/src/apps/chifra/pkg/articulate/events.go @@ -1,25 +1,12 @@ package articulate import ( - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/topics" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" ) -// transferTopic is here because these three topics make up almost all of the logs in the entire history -// of the chain, we get significant speed-ups if we handle these items without -// regular processing. -var transferTopic = base.HexToHash( - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", -) -var ensTransferTopic = base.HexToHash( - "0xd4735d920b0f87494915f556dd9b54c8f309026070caea5c737245152564d266", -) -var approvalTopic = base.HexToHash( - "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", -) - func parseTransferEvent(log *types.Log) (function *types.Function) { - if len(log.Topics) < 3 || log.Topics[0] != transferTopic { + if len(log.Topics) < 3 || log.Topics[0] != topics.TransferTopic { // TODO: Too short topics happens (sometimes) because the ABI says that the data is not // TODO: index, but it is or visa versa. In either case, we get the same topic0. We need to // TODO: attempt both with and without indexed parameters. See issues/1366. @@ -29,7 +16,7 @@ func parseTransferEvent(log *types.Log) (function *types.Function) { function = &types.Function{} function.Name = "Transfer" function.FunctionType = "event" - function.Encoding = transferTopic.Hex() + function.Encoding = topics.TransferTopic.Hex() function.Inputs = []types.Parameter{ { Name: "_from", @@ -51,7 +38,7 @@ func parseTransferEvent(log *types.Log) (function *types.Function) { } func parseEnsTransferEvent(log *types.Log) (function *types.Function) { - if len(log.Topics) < 2 || log.Topics[0] != ensTransferTopic { + if len(log.Topics) < 2 || log.Topics[0] != topics.EnsTransferTopic { // TODO: Too short topics happens (sometimes) because the ABI says that the data is not // TODO: index, but it is or visa versa. In either case, we get the same topic0. We need to // TODO: attempt both with and without indexed parameters. See issues/1366. @@ -61,7 +48,7 @@ func parseEnsTransferEvent(log *types.Log) (function *types.Function) { function = &types.Function{} function.Name = "Transfer" function.FunctionType = "event" - function.Encoding = ensTransferTopic.Hex() + function.Encoding = topics.EnsTransferTopic.Hex() function.Inputs = []types.Parameter{ { Name: "_node", @@ -78,7 +65,7 @@ func parseEnsTransferEvent(log *types.Log) (function *types.Function) { } func parseApprovalEvent(log *types.Log) (function *types.Function) { - if len(log.Topics) < 3 || log.Topics[0] != approvalTopic { + if len(log.Topics) < 3 || log.Topics[0] != topics.ApprovalTopic { // TODO: Too short topics happens (sometimes) because the ABI says that the data is not // TODO: index, but it is or visa versa. In either case, we get the same topic0. We need to // TODO: attempt both with and without indexed parameters. See issues/1366. @@ -88,7 +75,7 @@ func parseApprovalEvent(log *types.Log) (function *types.Function) { function = &types.Function{} function.Name = "Approval" function.FunctionType = "event" - function.Encoding = approvalTopic.Hex() + function.Encoding = topics.ApprovalTopic.Hex() function.Inputs = []types.Parameter{ { Name: "_owner", diff --git a/src/apps/chifra/pkg/base/types_wei.go b/src/apps/chifra/pkg/base/types_wei.go index 26867ffb86..1d7bc0a8e6 100644 --- a/src/apps/chifra/pkg/base/types_wei.go +++ b/src/apps/chifra/pkg/base/types_wei.go @@ -17,6 +17,12 @@ func NewWei(x int64) *Wei { return (*Wei)(big.NewInt(x)) } +func NewWeiStr(x string) *Wei { + val := big.NewInt(0) + val.SetString(x, 10) + return (*Wei)(val) +} + func (b *Wei) ToInt() *big.Int { return (*big.Int)(b) } @@ -70,7 +76,8 @@ func (x *Wei) Text(base int) string { } func (w *Wei) Add(x, y *Wei) *Wei { - return (*Wei)((*big.Int)(w).Add((*big.Int)(x), (*big.Int)(y))) + result := new(big.Int).Add((*big.Int)(x), (*big.Int)(y)) + return (*Wei)(result) } func (w *Wei) Sub(x, y *Wei) *Wei { diff --git a/src/apps/chifra/pkg/ledger/app_context.go b/src/apps/chifra/pkg/ledger/app_context.go new file mode 100644 index 0000000000..ec24421f5d --- /dev/null +++ b/src/apps/chifra/pkg/ledger/app_context.go @@ -0,0 +1,87 @@ +package ledger + +import ( + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" +) + +// appContextKey is a string type used as a key in the Ledger's map of appearance contexts. +// It is formed by formatting the block number and transaction index. +type appContextKey string + +// appContext provides context for a transaction appearance. It tracks the previous, +// current, and next block numbers to help with determining how the balances should be +// reconciled. Additionally, it holds a reconciliation type that describes the differences +// between these block values, and a flag indicating if the ordering is reversed. +type appContext struct { + address base.Address + prvBlk base.Blknum + curBlk base.Blknum + nxtBlk base.Blknum + reconType types.ReconType + reversed bool +} + +func newAppContext(prev, cur, next base.Blknum, isFirst, isLast, reversed bool) *appContext { + if prev > cur || cur > next { + return &appContext{reconType: types.Invalid, reversed: reversed} + } + + reconType := types.Invalid + if cur == 0 { + reconType = types.Genesis + } else { + prevDiff := prev != cur + nextDiff := cur != next + + if prevDiff && nextDiff { + reconType = types.DiffDiff + } else if !prevDiff && !nextDiff { + reconType = types.SameSame + } else if prevDiff { + reconType = types.DiffSame + } else if nextDiff { + reconType = types.SameDiff + } else { + reconType = types.Invalid + logger.Panic("should not happen") + } + } + + if isFirst { + reconType |= types.First + } + + if isLast { + reconType |= types.Last + } + + return &appContext{ + prvBlk: prev, + curBlk: cur, + nxtBlk: next, + reconType: reconType, + reversed: reversed, + } +} + +func (c *appContext) Prev() base.Blknum { + return c.prvBlk +} + +func (c *appContext) Cur() base.Blknum { + return c.curBlk +} + +func (c *appContext) Next() base.Blknum { + return c.nxtBlk +} + +func (c *appContext) Recon() types.ReconType { + return c.reconType +} + +func (c *appContext) Address() base.Address { + return c.address +} diff --git a/src/apps/chifra/pkg/ledger/app_context_test.go b/src/apps/chifra/pkg/ledger/app_context_test.go new file mode 100644 index 0000000000..f852c993ba --- /dev/null +++ b/src/apps/chifra/pkg/ledger/app_context_test.go @@ -0,0 +1,88 @@ +package ledger + +import ( + "testing" + + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" +) + +func TestNewAppContext(t *testing.T) { + cases := []struct { + name string + prev base.Blknum + cur base.Blknum + next base.Blknum + isFirst bool + isLast bool + reversed bool + expectRecon types.ReconType + }{ + { + name: "Genesis Block", + prev: 0, cur: 0, next: 1, + isFirst: true, isLast: false, reversed: false, + expectRecon: types.Genesis | types.First, + }, + { + name: "SameSame", + prev: 1, cur: 1, next: 1, + isFirst: false, isLast: false, reversed: false, + expectRecon: types.SameSame, + }, + { + name: "DiffDiff", + prev: 1, cur: 2, next: 3, + isFirst: false, isLast: false, reversed: false, + expectRecon: types.DiffDiff, + }, + { + name: "DiffSame", + prev: 1, cur: 2, next: 2, + isFirst: false, isLast: false, reversed: false, + expectRecon: types.DiffSame, + }, + { + name: "SameDiff", + prev: 2, cur: 2, next: 3, + isFirst: false, isLast: false, reversed: false, + expectRecon: types.SameDiff, + }, + { + name: "Invalid Block Order", + prev: 3, cur: 2, next: 1, + isFirst: false, isLast: false, reversed: false, + expectRecon: types.Invalid, + }, + // { + // name: "Reversed DiffDiff", + // prev: 3, cur: 2, next: 1, + // isFirst: false, isLast: false, reversed: true, + // expectRecon: types.DiffDiff, + // }, + // { + // name: "Reversed DiffSame", + // prev: 2, cur: 2, next: 1, + // isFirst: false, isLast: false, reversed: true, + // expectRecon: types.DiffSame, + // }, + // { + // name: "Reversed SameDiff", + // prev: 3, cur: 3, next: 2, + // isFirst: false, isLast: false, reversed: true, + // expectRecon: types.DiffSame, + // }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ctx := newAppContext(tc.prev, tc.cur, tc.next, tc.isFirst, tc.isLast, tc.reversed) + if ctx.Recon() != tc.expectRecon { + t.Errorf("expected %v, got %v", tc.expectRecon, ctx.Recon()) + } + if ctx.reversed != tc.reversed { + t.Errorf("expected reversed %v, got %v", tc.reversed, ctx.reversed) + } + }) + } +} diff --git a/src/apps/chifra/pkg/ledger/asset_context.go b/src/apps/chifra/pkg/ledger/asset_context.go new file mode 100644 index 0000000000..98328e9b6d --- /dev/null +++ b/src/apps/chifra/pkg/ledger/asset_context.go @@ -0,0 +1,48 @@ +package ledger + +import ( + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" +) + +// assetContextKey is a string type used as a key in the Ledger's map of asset contexts. +// It includes the asset address along with block and transaction identifiers to uniquely +// identify an asset's context within a transaction. +type assetContextKey string + +// assetContext represents the context for a specific asset within a transaction. It is defined +// as an alias to appContext and carries the same block boundary and reconciliation information, +// but it is used to track balance changes and other details at the asset level. +type assetContext appContext + +func newAssetContext(prev, cur, next base.Blknum, isFirst, isLast, reversed bool, addr base.Address) *assetContext { + appCtx := newAppContext(prev, cur, next, isFirst, isLast, reversed) + return &assetContext{ + address: addr, + prvBlk: appCtx.Prev(), + curBlk: appCtx.Cur(), + nxtBlk: appCtx.Next(), + reconType: appCtx.Recon(), + reversed: appCtx.reversed, + } +} + +func (c *assetContext) Prev() base.Blknum { + return c.prvBlk +} + +func (c *assetContext) Cur() base.Blknum { + return c.curBlk +} + +func (c *assetContext) Next() base.Blknum { + return c.nxtBlk +} + +func (c *assetContext) Recon() types.ReconType { + return c.reconType +} + +func (c *assetContext) Address() base.Address { + return c.address +} diff --git a/src/apps/chifra/pkg/ledger/asset_context_test.go b/src/apps/chifra/pkg/ledger/asset_context_test.go new file mode 100644 index 0000000000..f798ceb73a --- /dev/null +++ b/src/apps/chifra/pkg/ledger/asset_context_test.go @@ -0,0 +1,20 @@ +package ledger + +import ( + "fmt" + "testing" + + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" +) + +func TestGetAssetContextKey(t *testing.T) { + addr := base.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd") + bn := base.Blknum(123) + txid := base.Txnum(45) + expected := fmt.Sprintf("%s-%09d-%05d", addr.Hex(), bn, txid) + var l Ledger + key := l.getAssetContextKey(bn, txid, addr) + if string(key) != expected { + t.Errorf("Expected %s, got %s", expected, key) + } +} diff --git a/src/apps/chifra/pkg/ledger/context.go b/src/apps/chifra/pkg/ledger/context.go deleted file mode 100644 index cdcc5656fb..0000000000 --- a/src/apps/chifra/pkg/ledger/context.go +++ /dev/null @@ -1,144 +0,0 @@ -package ledger - -import ( - "fmt" - "sort" - "strings" - - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" -) - -type ledgerContextKey string - -// ledgerContext is a struct to hold the context of a reconciliation (i.e., its -// previous and next blocks and whether they are different) -type ledgerContext struct { - PrevBlock base.Blknum - CurBlock base.Blknum - NextBlock base.Blknum - ReconType types.ReconType -} - -func newLedgerContext(prev, cur, next base.Blknum, isFirst, isLast, reversed bool) *ledgerContext { - _ = reversed // Silences unused parameter warning - - if prev > cur || cur > next { - return &ledgerContext{ - ReconType: types.Invalid, - } - } - - reconType := types.Invalid - if cur == 0 { - reconType = types.Genesis - } else { - prevDiff := prev != cur - nextDiff := cur != next - if prevDiff && nextDiff { - reconType = types.DiffDiff - } else if !prevDiff && !nextDiff { - reconType = types.SameSame - } else if prevDiff { - reconType = types.DiffSame - } else if nextDiff { - reconType = types.SameDiff - } else { - reconType = types.Invalid - logger.Panic("should not happen") - } - } - - if isFirst { - reconType |= types.First - } - - if isLast { - reconType |= types.Last - } - - return &ledgerContext{ - PrevBlock: prev, - CurBlock: cur, - NextBlock: next, - ReconType: reconType, - // Reversed: reversed, - } -} - -func (c *ledgerContext) Prev() base.Blknum { - return c.PrevBlock -} - -func (c *ledgerContext) Cur() base.Blknum { - return c.CurBlock -} - -func (c *ledgerContext) Next() base.Blknum { - return c.NextBlock -} - -func (l *Ledger) ctxKey(bn base.Blknum, txid base.Txnum) ledgerContextKey { - // TODO: Is having the context per asset necessary? Can we use Locator? - // return fmt.Sprintf("%s-%09d-%05d", l.AccountFor.Hex(), bn, txid) - return ledgerContextKey(fmt.Sprintf("%09d-%05d", bn, txid)) -} - -const maxTestingBlock = 17000000 - -// SetContexts visits the list of appearances and notes the block numbers of the next and previous -// appearance's and if they are the same or different. Because balances are only available per block, -// we must know this information to be able to calculate the correct post-tx balance. -func (l *Ledger) SetContexts(chain string, apps []types.Appearance) error { - for i := 0; i < len(apps); i++ { - cur := base.Blknum(apps[i].BlockNumber) - prev := base.Blknum(apps[base.Max(1, i)-1].BlockNumber) - next := base.Blknum(apps[base.Min(i+1, len(apps)-1)].BlockNumber) - key := l.ctxKey(base.Blknum(apps[i].BlockNumber), base.Txnum(apps[i].TransactionIndex)) - l.Contexts[key] = newLedgerContext(base.Blknum(prev), base.Blknum(cur), base.Blknum(next), i == 0, i == (len(apps)-1), l.Reversed) - } - l.debugContext() - return nil -} - -func (l *Ledger) debugContext() { - if !l.TestMode { - return - } - - keys := make([]ledgerContextKey, 0, len(l.Contexts)) - for key := range l.Contexts { - keys = append(keys, key) - } - - sort.Slice(keys, func(i, j int) bool { - return string(keys[i]) < string(keys[j]) - }) - - logger.Info(strings.Repeat("-", 60)) - logger.Info(fmt.Sprintf("Contexts (%d)", len(keys))) - for _, key := range keys { - c := l.Contexts[key] - if c.CurBlock > maxTestingBlock { - continue - } - msg := "" - rr := c.ReconType &^ (types.First | types.Last) - switch rr { - case types.Genesis: - msg = fmt.Sprintf(" %s", c.ReconType.String()+"-diff") - case types.DiffDiff: - msg = fmt.Sprintf(" %s", c.ReconType.String()) - case types.SameSame: - msg = fmt.Sprintf(" %s", c.ReconType.String()) - case types.DiffSame: - msg = fmt.Sprintf(" %s", c.ReconType.String()) - case types.SameDiff: - msg = fmt.Sprintf(" %s", c.ReconType.String()) - default: - msg = fmt.Sprintf(" %s should not happen!", c.ReconType.String()) - } - logger.Info(fmt.Sprintf("%s: % 10d % 10d % 11d%s", key, c.PrevBlock, c.CurBlock, c.NextBlock, msg)) - } -} diff --git a/src/apps/chifra/pkg/ledger/context_test.go b/src/apps/chifra/pkg/ledger/context_test.go deleted file mode 100644 index 489c2e203d..0000000000 --- a/src/apps/chifra/pkg/ledger/context_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package ledger - -import ( - "testing" -) - -func TestLedgerContext(t *testing.T) { - // tests := []struct { - // prev base.Blknum - // cur base.Blknum - // next base.Blknum - // reversed bool - // expected reconType - // }{ - // {0, 0, 0, false, genesis}, - // {0, 0, 1, false, genesis}, - // {1, 0, 1, false, invalid}, - // {12, 13, 14, false, diffDiff}, - // {12, 12, 13, false, sameDiff}, - // {12, 13, 13, false, diffSame}, - // {12, 12, 12, false, sameSame}, - // {10, 9, 9, false, invalid}, - // {10, 10, 9, false, invalid}, - // {10, 9, 8, false, invalid}, - // } - - // for i, test := range tests { - // // got := newLedgerContext(test.prev, test.cur, test.next, i == 0, false, test.reversed) - // got := newLedgerContext(test.prev, test.cur, test.next, false, false, test.reversed) - // if got.ReconType != test.expected { - // t.Error("expected:", test.expected, "got:", got.ReconType) - // } - // } -} diff --git a/src/apps/chifra/pkg/ledger/ledger.go b/src/apps/chifra/pkg/ledger/ledger.go index de8eedf310..9b9517191a 100644 --- a/src/apps/chifra/pkg/ledger/ledger.go +++ b/src/apps/chifra/pkg/ledger/ledger.go @@ -1,7 +1,12 @@ package ledger import ( + "fmt" + "sort" + "strings" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/names" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" @@ -11,37 +16,40 @@ import ( // TODO: balances in a concurrent way before spinning through the appearances. And (2) if we did that // TODO: prior to doing the accounting, we could easily travers in reverse order. -// Ledger is a structure that carries enough information to complate a reconciliation +// Ledger represents the ledger state and provides methods to process and reconcile +// transactions and their associated logs. It holds configuration details such as the +// account being tracked, block ranges for processing, connection to an RPC endpoint, +// asset filters, and maps for both application-level and asset-level contexts. type Ledger struct { - Chain string - AccountFor base.Address - FirstBlock base.Blknum - LastBlock base.Blknum - Names map[base.Address]types.Name - TestMode bool - Contexts map[ledgerContextKey]*ledgerContext - AsEther bool - NoZero bool - Reversed bool - UseTraces bool - Conn *rpc.Connection - assetFilter []base.Address - theTx *types.Transaction + accountFor base.Address + firstBlock base.Blknum + lastBlock base.Blknum + names map[base.Address]types.Name + testMode bool + asEther bool + noZero bool + reversed bool + useTraces bool + connection *rpc.Connection + assetFilter []base.Address + theTx *types.Transaction + appContexts map[appContextKey]*appContext + assetContexts map[assetContextKey]*assetContext } -// NewLedger returns a new empty Ledger struct -func NewLedger(conn *rpc.Connection, acctFor base.Address, fb, lb base.Blknum, asEther, testMode, noZero, useTraces, reversed bool, assetFilters *[]string) *Ledger { +func NewLedger(conn *rpc.Connection, apps []types.Appearance, acctFor base.Address, fb, lb base.Blknum, asEther, testMode, noZero, useTraces, reversed bool, assetFilters *[]string) *Ledger { l := &Ledger{ - Conn: conn, - AccountFor: acctFor, - FirstBlock: fb, - LastBlock: lb, - Contexts: make(map[ledgerContextKey]*ledgerContext), - AsEther: asEther, - TestMode: testMode, - NoZero: noZero, - Reversed: reversed, - UseTraces: useTraces, + connection: conn, + accountFor: acctFor, + firstBlock: fb, + lastBlock: lb, + asEther: asEther, + testMode: testMode, + noZero: noZero, + reversed: reversed, + useTraces: useTraces, + appContexts: make(map[appContextKey]*appContext), + assetContexts: make(map[assetContextKey]*assetContext), } if assetFilters != nil { @@ -54,12 +62,21 @@ func NewLedger(conn *rpc.Connection, acctFor base.Address, fb, lb base.Blknum, a } parts := types.Custom | types.Prefund | types.Regular - l.Names, _ = names.LoadNamesMap(conn.Chain, parts, []string{}) + l.names, _ = names.LoadNamesMap(conn.Chain, parts, []string{}) + + for index := 0; index < len(apps); index++ { + curApp := apps[index] + cur := base.Blknum(curApp.BlockNumber) + prev := base.Blknum(apps[base.Max(1, index)-1].BlockNumber) + next := base.Blknum(apps[base.Min(index+1, len(apps)-1)].BlockNumber) + appKey := l.getAppContextKey(base.Blknum(curApp.BlockNumber), base.Txnum(curApp.TransactionIndex)) + l.appContexts[appKey] = newAppContext(base.Blknum(prev), base.Blknum(cur), base.Blknum(next), index == 0, index == (len(apps)-1), l.reversed) + } + debugLedgerContexts(l.testMode, l.appContexts) return l } -// assetOfInterest returns true if the asset filter is empty or the asset matches func (l *Ledger) assetOfInterest(needle base.Address) bool { if len(l.assetFilter) == 0 { return true @@ -74,66 +91,72 @@ func (l *Ledger) assetOfInterest(needle base.Address) bool { return false } -// See issue #2791 - This is the code that used to generate extra traces to make reconcilation work -// (or, at least, similar code in `chifra export` generated these traces. -// bool isSuicide = trace.action.selfDestructed != ""; -// bool isCreation = trace.result.address != ""; -// // do not colapse -// if (isCreation) { -// displayAsTrace(opt, trace); -// displayAsCreation(opt, trace); -// } -// // do not colapse -// if (isSuicide) { -// displayAsSuicide(opt, trace); -// } -// // do not colapse -// if (!isCreation && !isSuicide) { -// displayAsTrace(opt, trace); -// } -//-------------------------------------------------------------- -// bool displayAsCreation(COptions* opt, const CTrace& trace) { -// if (trace.result.address == "") -// return false; -// CTrace copy = trace; -// copy.action.from = "0x0"; -// copy.action.to = trace.result.address; -// copy.action.callType = "creation"; -// copy.action.value = trace.action.value; -// if (copy.traceAddress.size() == 0) -// copy.traceAddress.push_back("null"); -// copy.traceAddress.push_back("s"); -// copy.transactionHash = uint_2_Hex(trace.blockNumber * 100000 + trace.transactionIndex); -// copy.action.input = trace.action.input; -// return displayAsTrace(opt, copy); -// } -// //-------------------------------------------------------------- -// bool displayAsSuicide(COptions* opt, const CTrace& trace) { -// if (trace.action.refundAddress == "") -// return false; -// CTrace copy = trace; -// copy.action.from = trace.action.selfDestructed; -// copy.action.to = trace.action.refundAddress; -// copy.action.callType = "suicide"; -// copy.action.value = trace.action.balance; -// copy.traceAddress.push_back("s"); -// copy.transactionHash = uint_2_Hex(trace.blockNumber * 100000 + trace.transactionIndex); -// copy.action.input = "0x"; -// return displayAsTrace(opt, copy); -// } -// //-------------------------------------------------------------- -// bool displayAsTrace(COptions* opt, const CTrace& trace) { -// bool isText = (expContext().exportFmt & (TXT1 | CSV1)); -// if (isText) { -// cout << trim(trace.Format(expContext().fmtMap["format"]), '\t') << endl; -// } else { -// if (!opt->firstOut) -// cout << ","; -// cout << " "; -// indent(); -// trace.toJson(cout); -// unindent(); -// opt->firstOut = false; -// } -// return true; -// } +func (l *Ledger) getAppContextKey(bn base.Blknum, txid base.Txnum) appContextKey { + return appContextKey(fmt.Sprintf("%09d-%05d", bn, txid)) +} + +func (l *Ledger) getAssetContextKey(bn base.Blknum, txid base.Txnum, assetAddr base.Address) assetContextKey { + return assetContextKey(fmt.Sprintf("%s-%09d-%05d", assetAddr.Hex(), bn, txid)) +} + +func (l *Ledger) getOrCreateAssetContext(bn base.Blknum, txid base.Txnum, assetAddr base.Address) *assetContext { + assetKey := l.getAssetContextKey(bn, txid, assetAddr) + if ctx, exists := l.assetContexts[assetKey]; exists { + return ctx + } + + appKey := l.getAppContextKey(bn, txid) + appCtx, exists := l.appContexts[appKey] + if !exists { + logger.Warn("This should never happen in getOrCreateAssetContext") + appCtx = newAppContext(bn, bn, bn, false, false, l.reversed) + l.appContexts[appKey] = appCtx + } + + assetCtx := newAssetContext(appCtx.Prev(), appCtx.Cur(), appCtx.Next(), false, false, l.reversed, assetAddr) + l.assetContexts[assetKey] = assetCtx + return assetCtx +} + +const maxTestingBlock = 17000000 + +func debugLedgerContexts[K ~string, T types.LedgerContexter](testMode bool, ctxs map[K]T) { + if !testMode { + return + } + + keys := make([]K, 0, len(ctxs)) + for key := range ctxs { + keys = append(keys, key) + } + + sort.Slice(keys, func(i, j int) bool { + return string(keys[i]) < string(keys[j]) + }) + + logger.Info(strings.Repeat("-", 60)) + logger.Info(fmt.Sprintf("Contexts (%d)", len(keys))) + for _, key := range keys { + c := ctxs[key] + if c.Cur() > maxTestingBlock { + continue + } + msg := "" + rr := c.Recon() &^ (types.First | types.Last) + switch rr { + case types.Genesis: + msg = fmt.Sprintf(" %s", c.Recon().String()+"-diff") + case types.DiffDiff: + msg = fmt.Sprintf(" %s", c.Recon().String()) + case types.SameSame: + msg = fmt.Sprintf(" %s", c.Recon().String()) + case types.DiffSame: + msg = fmt.Sprintf(" %s", c.Recon().String()) + case types.SameDiff: + msg = fmt.Sprintf(" %s", c.Recon().String()) + default: + msg = fmt.Sprintf(" %s should not happen!", c.Recon().String()) + } + logger.Info(fmt.Sprintf("%s: % 10d % 10d % 11d%s", key, c.Prev(), c.Cur(), c.Next(), msg)) + } +} diff --git a/src/apps/chifra/pkg/ledger/ledger_test.go b/src/apps/chifra/pkg/ledger/ledger_test.go new file mode 100644 index 0000000000..17e6b53225 --- /dev/null +++ b/src/apps/chifra/pkg/ledger/ledger_test.go @@ -0,0 +1,217 @@ +package ledger + +import ( + "fmt" + "testing" + + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" +) + +func TestGetOrCreateAssetContext_New(t *testing.T) { + l := &Ledger{ + reversed: false, + appContexts: make(map[appContextKey]*appContext), + assetContexts: make(map[assetContextKey]*assetContext), + } + + bn := base.Blknum(100) + txid := base.Txnum(1) + assetAddr := base.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd") + + assetCtx := l.getOrCreateAssetContext(bn, txid, assetAddr) + + if assetCtx == nil { + t.Fatal("Expected asset context to be non-nil") + } + + if assetCtx.address != assetAddr { + t.Errorf("Expected asset address %s, got %s", assetAddr.Hex(), assetCtx.address.Hex()) + } + + appKey := l.getAppContextKey(bn, txid) + if _, exists := l.appContexts[appKey]; !exists { + t.Error("Expected app context to be created and stored in ledger.appContexts") + } + + assetKey := l.getAssetContextKey(bn, txid, assetAddr) + storedAssetCtx, exists := l.assetContexts[assetKey] + if !exists { + t.Error("Expected asset context to be stored in ledger.assetContexts") + } + if storedAssetCtx != assetCtx { + t.Error("Stored asset context does not match returned asset context") + } +} + +func TestGetOrCreateAssetContext_Existing(t *testing.T) { + l := &Ledger{ + reversed: false, + appContexts: make(map[appContextKey]*appContext), + assetContexts: make(map[assetContextKey]*assetContext), + } + + bn := base.Blknum(200) + txid := base.Txnum(2) + assetAddr := base.HexToAddress("0x1111111111111111111111111111111111111111") + + appKey := l.getAppContextKey(bn, txid) + fakeAppCtx := newAppContext(bn-1, bn, bn+1, false, false, l.reversed) + l.appContexts[appKey] = fakeAppCtx + + assetCtx1 := l.getOrCreateAssetContext(bn, txid, assetAddr) + if assetCtx1 == nil { + t.Fatal("Expected asset context to be non-nil") + } + + assetCtx2 := l.getOrCreateAssetContext(bn, txid, assetAddr) + if assetCtx1 != assetCtx2 { + t.Error("Expected subsequent calls to return the same asset context instance") + } + + if assetCtx1.Prev() != fakeAppCtx.Prev() || + assetCtx1.Cur() != fakeAppCtx.Cur() || + assetCtx1.Next() != fakeAppCtx.Next() { + t.Error("Asset context values do not match those of the underlying app context") + } +} + +func TestAssetOfInterest(t *testing.T) { + // Case 1: Empty assetFilter should always return true. + l1 := &Ledger{ + assetFilter: []base.Address{}, + } + needle := base.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + if !l1.assetOfInterest(needle) { + t.Error("Expected assetOfInterest to return true when assetFilter is empty") + } + + // Case 2: Non-empty assetFilter where needle is present. + l2 := &Ledger{ + assetFilter: []base.Address{ + base.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"), + base.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), // needle below + base.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + }, + } + needle2 := base.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + if !l2.assetOfInterest(needle2) { + t.Error("Expected assetOfInterest to return true when needle is present in assetFilter") + } + + // Case 3: Non-empty assetFilter where needle is not present. + l3 := &Ledger{ + assetFilter: []base.Address{ + base.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"), + base.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + }, + } + needle3 := base.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + if l3.assetOfInterest(needle3) { + t.Error("Expected assetOfInterest to return false when needle is not present in assetFilter") + } + + // Case 4: Multiple entries with mixed ordering. (This is similar to Case 2.) + l4 := &Ledger{ + assetFilter: []base.Address{ + base.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + base.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + base.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"), + }, + } + if !l4.assetOfInterest(needle2) { + t.Error("Expected assetOfInterest to return true when needle is present among multiple addresses") + } +} + +func TestNewLedger_WithAssetFiltersNil(t *testing.T) { + // Create a slice of dummy appearances. + apps := []types.Appearance{ + {BlockNumber: 10, TransactionIndex: 1}, + {BlockNumber: 20, TransactionIndex: 2}, + {BlockNumber: 30, TransactionIndex: 3}, + } + conn := rpc.TempConnection("mainnet") + l := NewLedger(conn, apps, base.HexToAddress("0xAAA"), 10, 30, false, true, false, false, false, nil) + // Expect that assetFilter is an empty slice. + if len(l.assetFilter) != 0 { + t.Errorf("Expected assetFilter to be empty when assetFilters is nil, got length %d", len(l.assetFilter)) + } + // Verify appContexts for each appearance. + if len(l.appContexts) != len(apps) { + t.Errorf("Expected %d appContexts, got %d", len(apps), len(l.appContexts)) + } + // For each appearance, check the computed prev, cur, and next block numbers. + for index, app := range apps { + expectedKey := fmt.Sprintf("%09d-%05d", app.BlockNumber, app.TransactionIndex) + ctx, exists := l.appContexts[appContextKey(expectedKey)] + if !exists { + t.Errorf("Expected appContext with key %s to exist", expectedKey) + continue + } + // For the first appearance, prev and cur should equal its block number, + // and next should equal the block number of the second appearance. + if index == 0 { + if ctx.Prev() != base.Blknum(app.BlockNumber) { + t.Errorf("For first app, expected Prev() to be %d, got %d", app.BlockNumber, ctx.Prev()) + } + if ctx.Cur() != base.Blknum(app.BlockNumber) { + t.Errorf("For first app, expected Cur() to be %d, got %d", app.BlockNumber, ctx.Cur()) + } + if ctx.Next() != base.Blknum(apps[1].BlockNumber) { + t.Errorf("For first app, expected Next() to be %d, got %d", apps[1].BlockNumber, ctx.Next()) + } + } + // For a middle appearance, prev should be first app's block number, + // cur equal to its block, and next equal to the third app's block number. + if index == 1 { + if ctx.Prev() != base.Blknum(apps[0].BlockNumber) { + t.Errorf("For middle app, expected Prev() to be %d, got %d", apps[0].BlockNumber, ctx.Prev()) + } + if ctx.Cur() != base.Blknum(app.BlockNumber) { + t.Errorf("For middle app, expected Cur() to be %d, got %d", app.BlockNumber, ctx.Cur()) + } + if ctx.Next() != base.Blknum(apps[2].BlockNumber) { + t.Errorf("For middle app, expected Next() to be %d, got %d", apps[2].BlockNumber, ctx.Next()) + } + } + // For the last appearance, prev should be the second app's block number, + // cur equal to its block, and next equal to its block. + if index == 2 { + if ctx.Prev() != base.Blknum(apps[1].BlockNumber) { + t.Errorf("For last app, expected Prev() to be %d, got %d", apps[1].BlockNumber, ctx.Prev()) + } + if ctx.Cur() != base.Blknum(app.BlockNumber) { + t.Errorf("For last app, expected Cur() to be %d, got %d", app.BlockNumber, ctx.Cur()) + } + if ctx.Next() != base.Blknum(app.BlockNumber) { + t.Errorf("For last app, expected Next() to be %d, got %d", app.BlockNumber, ctx.Next()) + } + } + } +} + +func TestNewLedger_WithAssetFiltersProvided(t *testing.T) { + apps := []types.Appearance{ + {BlockNumber: 100, TransactionIndex: 1}, + {BlockNumber: 200, TransactionIndex: 2}, + } + filters := []string{"0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"} + conn := rpc.TempConnection("mainnet") + l := NewLedger(conn, apps, base.HexToAddress("0xAAA"), 100, 200, false, true, false, false, false, &filters) + // Check that assetFilter in Ledger is populated correctly. + if len(l.assetFilter) != len(filters) { + t.Errorf("Expected assetFilter length %d, got %d", len(filters), len(l.assetFilter)) + } + for i, addrStr := range filters { + expectedAddr := base.HexToAddress(addrStr) + if l.assetFilter[i] != expectedAddr { + t.Errorf("At index %d, expected assetFilter %s, got %s", i, expectedAddr.Hex(), l.assetFilter[i].Hex()) + } + } + // Also verify that appContexts are created. + if len(l.appContexts) != len(apps) { + t.Errorf("Expected %d appContexts, got %d", len(apps), len(l.appContexts)) + } +} diff --git a/src/apps/chifra/pkg/ledger/normalize_log.go b/src/apps/chifra/pkg/ledger/normalize_log.go new file mode 100644 index 0000000000..2f0c7ec2b2 --- /dev/null +++ b/src/apps/chifra/pkg/ledger/normalize_log.go @@ -0,0 +1,160 @@ +package ledger + +import ( + "encoding/hex" + "errors" + "math/big" + "strings" + + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/topics" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" +) + +func (l *Ledger) normalizeLog(log *types.Log) (types.Log, error) { + var from, to *string + var value *big.Int + var err error + + // Case 1: Standard event with 3 topics and matching signature. + if len(log.Topics) == 3 && log.Topics[0] == topics.TransferTopic { + from, err = normalizeTopicAddress(log.Topics[1].Hex()) + if err != nil { + return types.Log{}, err + } + to, err = normalizeTopicAddress(log.Topics[2].Hex()) + if err != nil { + return types.Log{}, err + } + value, err = decodeUint256(log.Data) + if err != nil { + return types.Log{}, err + } + } else if len(log.Topics) == 2 { + // Case 2: Two-topic variant: + // Assume topics[1] is the indexed 'from'. + from, err = normalizeTopicAddress(log.Topics[1].Hex()) + if err != nil { + return types.Log{}, err + } + // In this case, the data should contain two 32-byte values: + // first 32 bytes: padded 'to' address, + // next 32 bytes: uint256 value. + toDecoded, valueDecoded, err := decodeTwoValues(log.Data) + if err != nil { + return types.Log{}, err + } + to, err = normalizeTopicAddress(toDecoded) + if err != nil { + return types.Log{}, err + } + value = valueDecoded + } else if len(log.Topics) == 1 || len(log.Topics) == 0 { + // Case 3: Non-standard variant: + // No indexed addresses; all parameters are in data. + fromDecoded, toDecoded, valueDecoded, err := decodeThreeValues(log.Data) + if err != nil { + return types.Log{}, err + } + from, err = normalizeTopicAddress(fromDecoded) + if err != nil { + return types.Log{}, err + } + to, err = normalizeTopicAddress(toDecoded) + if err != nil { + return types.Log{}, err + } + value = valueDecoded + } else { + return types.Log{}, errors.New("unrecognized Transfer event format") + } + + // Build a new normalized log: three topics and a data field containing only the value. + newLog := types.Log{ + Topics: []base.Hash{ + topics.TransferTopic, // normalized event signature + base.HexToHash(*from), // normalized "from" address + base.HexToHash(*to), // normalized "to" address + }, + Data: encodeUint256(value), + } + return newLog, nil +} + +// normalizeTopicAddress converts a topic (or decoded address string) into a normalized 32-byte padded address. +// It assumes the input is a hex string that represents a 20-byte address (with or without the "0x" prefix). +func normalizeTopicAddress(input string) (*string, error) { + // Remove any "0x" prefix. + s := strings.TrimPrefix(input, "0x") + // For a padded address, we expect 64 hex characters. + if len(s) == 64 { + // We want to verify that the last 40 characters represent the actual address. + addr := s[24:] + // Rebuild the normalized topic value. + normalized := "0x" + "000000000000000000000000" + addr + return &normalized, nil + } + // If the input is 40 hex characters, assume it's an unpadded address. + if len(s) == 40 { + normalized := "0x" + "000000000000000000000000" + s + return &normalized, nil + } + return nil, errors.New("invalid address length in topic") +} + +// decodeUint256 decodes a 32-byte (64 hex characters) uint256 value from the beginning of the data. +func decodeUint256(data string) (*big.Int, error) { + data = strings.TrimPrefix(data, "0x") + if len(data) < 64 { + return nil, errors.New("data length too short for uint256") + } + bytes, err := hex.DecodeString(data[:64]) + if err != nil { + return nil, err + } + value := new(big.Int).SetBytes(bytes) + return value, nil +} + +// encodeUint256 encodes a *big.Int into a 32-byte hex string with "0x" prefix. +func encodeUint256(value *big.Int) string { + bytes := value.Bytes() + padded := make([]byte, 32) + copy(padded[32-len(bytes):], bytes) + return "0x" + hex.EncodeToString(padded) +} + +// decodeTwoValues decodes a data payload assumed to contain two 32-byte values: +// first for a padded address and second for a uint256 value. +func decodeTwoValues(data string) (string, *big.Int, error) { + data = strings.TrimPrefix(data, "0x") + if len(data) < 128 { + return "", nil, errors.New("data length too short for two values") + } + // Extract the first 32 bytes for the 'to' address. + toPart := "0x" + data[:64] + // Next 32 bytes for the value. + valueBytes, err := hex.DecodeString(data[64:128]) + if err != nil { + return "", nil, err + } + value := new(big.Int).SetBytes(valueBytes) + return toPart, value, nil +} + +// decodeThreeValues decodes a data payload assumed to contain three 32-byte values: +// from, to, and value. +func decodeThreeValues(data string) (string, string, *big.Int, error) { + data = strings.TrimPrefix(data, "0x") + if len(data) < 192 { + return "", "", nil, errors.New("data length too short for three values") + } + fromPart := "0x" + data[:64] + toPart := "0x" + data[64:128] + valueBytes, err := hex.DecodeString(data[128:192]) + if err != nil { + return "", "", nil, err + } + value := new(big.Int).SetBytes(valueBytes) + return fromPart, toPart, value, nil +} diff --git a/src/apps/chifra/pkg/ledger/normalize_log_test.go b/src/apps/chifra/pkg/ledger/normalize_log_test.go new file mode 100644 index 0000000000..de61c6e2c6 --- /dev/null +++ b/src/apps/chifra/pkg/ledger/normalize_log_test.go @@ -0,0 +1,163 @@ +package ledger + +import ( + "math/big" + "strings" + "testing" + + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/topics" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" +) + +func TestNormalizedLog_Standard(t *testing.T) { + // Standard event with 3 topics. + logStandard := types.Log{ + Topics: []base.Hash{ + topics.TransferTopic, // event signature + base.HexToHash("0x0000000000000000000000001111111111111111111111111111111111111111"), + base.HexToHash("0x0000000000000000000000002222222222222222222222222222222222222222"), + }, + Data: "0x00000000000000000000000000000000000000000000000000000000000003e8", // value = 1000 + } + + var l Ledger + normLog, err := l.normalizeLog(&logStandard) + if err != nil { + t.Fatalf("unexpected error in standard case: %v", err) + } + + // Expected normalized topics. + expectedFrom := "0x0000000000000000000000001111111111111111111111111111111111111111" + expectedTo := "0x0000000000000000000000002222222222222222222222222222222222222222" + + if !strings.EqualFold(normLog.Topics[1].Hex(), expectedFrom) { + t.Errorf("expected normalized from topic %s, got %s", expectedFrom, normLog.Topics[1]) + } + if !strings.EqualFold(normLog.Topics[2].Hex(), expectedTo) { + t.Errorf("expected normalized to topic %s, got %s", expectedTo, normLog.Topics[2]) + } + + // Verify the data encodes the value 1000. + expectedValue := big.NewInt(1000) + val, err := decodeUint256(normLog.Data) + if err != nil { + t.Fatalf("failed to decode normalized data: %v", err) + } + if val.Cmp(expectedValue) != 0 { + t.Errorf("expected value %s, got %s", expectedValue.String(), val.String()) + } +} + +func TestNormalizedLog_TwoTopics(t *testing.T) { + // Two-topic variant: topics[1] is the "from" address; + // the data field holds two 32-byte values: first the "to" address and then the value. + fromTopic := "0x000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + // Create the "to" address. + // Use an unpadded 40-character address, which our helper will normalize. + toRaw := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + // Expected normalized to topic: padded to 32 bytes. + expectedTo := "0x000000000000000000000000" + toRaw + + // Encode the value (5000) as a 32-byte hex string. + value := big.NewInt(5000) + valueHex := encodeUint256(value) + + // Build the data: concatenation of the padded "to" address and the value. + data := "0x" + strings.TrimPrefix(expectedTo, "0x") + strings.TrimPrefix(valueHex, "0x") + + logTwoTopics := types.Log{ + Topics: []base.Hash{ + topics.TransferTopic, // event signature + base.HexToHash(fromTopic), + }, + Data: data, + } + + var l Ledger + normLog, err := l.normalizeLog(&logTwoTopics) + if err != nil { + t.Fatalf("unexpected error in two-topic case: %v", err) + } + + if !strings.EqualFold(normLog.Topics[1].Hex(), fromTopic) { + t.Errorf("expected normalized from topic %s, got %s", fromTopic, normLog.Topics[1]) + } + if !strings.EqualFold(normLog.Topics[2].Hex(), expectedTo) { + t.Errorf("expected normalized to topic %s, got %s", expectedTo, normLog.Topics[2]) + } + + expectedValue := value + val, err := decodeUint256(normLog.Data) + if err != nil { + t.Fatalf("failed to decode normalized data: %v", err) + } + if val.Cmp(expectedValue) != 0 { + t.Errorf("expected value %s, got %s", expectedValue.String(), val.String()) + } +} + +func TestNormalizedLog_NonStandard(t *testing.T) { + // Non-standard variant: No addresses are indexed; all parameters are in the data. + // Data encodes three 32-byte values: from, to, and value. + fromRaw := "cccccccccccccccccccccccccccccccccccccccc" + toRaw := "dddddddddddddddddddddddddddddddddddddddd" + expectedFrom := "0x000000000000000000000000" + fromRaw + expectedTo := "0x000000000000000000000000" + toRaw + value := big.NewInt(9999) + valueHex := encodeUint256(value) + + data := "0x" + + "000000000000000000000000" + fromRaw + + "000000000000000000000000" + toRaw + + strings.TrimPrefix(valueHex, "0x") + + logNonStandard := types.Log{ + Topics: []base.Hash{ + topics.TransferTopic, + }, + Data: data, + } + + var l Ledger + normLog, err := l.normalizeLog(&logNonStandard) + if err != nil { + t.Fatalf("unexpected error in non-standard case: %v", err) + } + + if !strings.EqualFold(normLog.Topics[1].Hex(), expectedFrom) { + t.Errorf("expected normalized from topic %s, got %s", expectedFrom, normLog.Topics[1]) + } + if !strings.EqualFold(normLog.Topics[2].Hex(), expectedTo) { + t.Errorf("expected normalized to topic %s, got %s", expectedTo, normLog.Topics[2]) + } + + expectedValue := value + val, err := decodeUint256(normLog.Data) + if err != nil { + t.Fatalf("failed to decode normalized data: %v", err) + } + if val.Cmp(expectedValue) != 0 { + t.Errorf("expected value %s, got %s", expectedValue.String(), val.String()) + } +} + +func TestNormalizedLog_UnrecognizedFormat(t *testing.T) { + // Unrecognized format: extra topics. + logInvalid := types.Log{ + Topics: []base.Hash{ + topics.TransferTopic, + base.HexToHash("0x000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + base.HexToHash("0x000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), + base.HexToHash("0x000000000000000000000000cccccccccccccccccccccccccccccccccccccccc"), // extra topic + }, + Data: "0x00000000000000000000000000000000000000000000000000000000000003e8", + } + + var l Ledger + _, err := l.normalizeLog(&logInvalid) + if err == nil { + t.Fatal("expected error for unrecognized event format, got nil") + } +} diff --git a/src/apps/chifra/pkg/ledger/stmnt_from_log.go b/src/apps/chifra/pkg/ledger/stmnt_from_log.go index eab487d421..8819e462a8 100644 --- a/src/apps/chifra/pkg/ledger/stmnt_from_log.go +++ b/src/apps/chifra/pkg/ledger/stmnt_from_log.go @@ -8,30 +8,27 @@ import ( "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/topics" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/utils" ) -var transferTopic = base.HexToHash( - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", -) - var ErrNonIndexedTransfer = fmt.Errorf("non-indexed transfer") // getStatementsFromLog returns a statement from a given log func (l *Ledger) getStatementsFromLog(conn *rpc.Connection, logIn *types.Log) (types.Statement, error) { - if logIn.Topics[0] != transferTopic { + if logIn.Topics[0] != topics.TransferTopic { // Not a transfer return types.Statement{}, nil } - if log, err := l.normalizeTransfer(logIn); err != nil { + if log, err := l.normalizeLog(logIn); err != nil { return types.Statement{}, err } else { sym := log.Address.Prefix(6) decimals := base.Value(18) - name := l.Names[log.Address] + name := l.names[log.Address] if name.Address == log.Address { if name.Symbol != "" { sym = name.Symbol @@ -52,19 +49,19 @@ func (l *Ledger) getStatementsFromLog(conn *rpc.Connection, logIn *types.Log) (t ofInterest := false // Do not collapse, may be both - if l.AccountFor == sender { + if l.accountFor == sender { amountOut = *amt ofInterest = true } // Do not collapse, may be both - if l.AccountFor == recipient { + if l.accountFor == recipient { amountIn = *amt ofInterest = true } s := types.Statement{ - AccountedFor: l.AccountFor, + AccountedFor: l.accountFor, Sender: sender, Recipient: recipient, BlockNumber: log.BlockNumber, @@ -81,33 +78,51 @@ func (l *Ledger) getStatementsFromLog(conn *rpc.Connection, logIn *types.Log) (t AmountOut: amountOut, } - // TODO: BOGUS PERF - WE HIT GETBALANCE THREE TIMES FOR EACH APPEARANCE. SPIN THROUGH ONCE - // TODO: AND CACHE RESULTS IN MEMORY, BUT BE CAREFUL OF MULTIPLE LOGS PER BLOCK (OR TRANSACTION) - key := l.ctxKey(log.BlockNumber, log.TransactionIndex) - ctx := l.Contexts[key] + key := l.getAssetContextKey(log.BlockNumber, log.TransactionIndex, s.AccountedFor) + var ctx *assetContext + var exists bool + if ctx, exists = l.assetContexts[key]; !exists { + return s, fmt.Errorf("no context for %s", key) + } if ofInterest { var err error - pBal := new(base.Wei) - if pBal, err = conn.GetBalanceAtToken(log.Address, l.AccountFor, fmt.Sprintf("0x%x", ctx.PrevBlock)); pBal == nil { - return s, err + + // Previous balance: + var pBal *base.Wei + if (ctx.Recon() & (types.SameSame | types.SameDiff | types.DiffSame)) != 0 { + pBal, err = conn.GetBalanceAtToken(log.Address, l.accountFor, fmt.Sprintf("0x%x", ctx.Prev())) + if err != nil || pBal == nil { + return s, err + } + } else { + pBal, err = conn.GetBalanceAtToken(log.Address, l.accountFor, fmt.Sprintf("0x%x", ctx.Prev())) + if err != nil || pBal == nil { + return s, err + } } s.PrevBal = *pBal - bBal := new(base.Wei) - if bBal, err = conn.GetBalanceAtToken(log.Address, l.AccountFor, fmt.Sprintf("0x%x", ctx.CurBlock-1)); bBal == nil { - return s, err + var bBal *base.Wei + if (ctx.Recon() & (types.SameSame | types.SameDiff | types.DiffSame)) != 0 { + bBal = pBal + } else { + bBal, err = conn.GetBalanceAtToken(log.Address, l.accountFor, fmt.Sprintf("0x%x", ctx.Cur()-1)) + if err != nil || bBal == nil { + return s, err + } } s.BegBal = *bBal eBal := new(base.Wei) - if eBal, err = conn.GetBalanceAtToken(log.Address, l.AccountFor, fmt.Sprintf("0x%x", ctx.CurBlock)); eBal == nil { + eBal, err = conn.GetBalanceAtToken(log.Address, l.accountFor, fmt.Sprintf("0x%x", ctx.Cur())) + if err != nil || eBal == nil { return s, err } s.EndBal = *eBal id := fmt.Sprintf(" %d.%d.%d", s.BlockNumber, s.TransactionIndex, s.LogIndex) - if !l.trialBalance("token", &s) { + if !l.trialBalance(trialBalToken, &s) { if !utils.IsFuzzing() { logger.Warn(colors.Yellow+"Log statement at ", id, " does not reconcile."+colors.Off) } @@ -121,20 +136,3 @@ func (l *Ledger) getStatementsFromLog(conn *rpc.Connection, logIn *types.Log) (t return s, nil } } - -func (l *Ledger) normalizeTransfer(log *types.Log) (*types.Log, error) { - if len(log.Topics) < 3 { - // Transfer(address _from, address _to, uint256 _tokenId) - no indexed topics - // Transfer(address indexed _from, address indexed _to, uint256 _value) - two indexed topics - // Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId) - three indexed topics - // TODO: This may be a transfer. Returning here is wrong. What this means is that - // TODO:the some of the transfer's data is not indexed. Too short topics happens - // TODO: (sometimes) because the ABI says that the data is not index, but it is - // TODO: or visa versa. In either case, we get the same topic0. We need to - // TODO: attempt both with and without indexed parameters. See issues/1366. - // TODO: We could fix this and call back in recursively... - return nil, ErrNonIndexedTransfer - } - - return log, nil -} diff --git a/src/apps/chifra/pkg/ledger/stmnt_from_log_integration_test.go b/src/apps/chifra/pkg/ledger/stmnt_from_log_integration_test.go deleted file mode 100644 index aee4d9e70c..0000000000 --- a/src/apps/chifra/pkg/ledger/stmnt_from_log_integration_test.go +++ /dev/null @@ -1,60 +0,0 @@ -//go:build integration -// +build integration - -package ledger - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/utils" -) - -func TestGetStatementFromLog(t *testing.T) { - bn := base.Blknum(9279453) - txid := base.Txnum(208) - log := types.Log{ - Address: base.HexToAddress("0x6b175474e89094c44da98b954eedeac495271d0f"), - Topics: []base.Hash{ - transferTopic, - base.HexToHash("0xf503017d7baf7fbc0fff7492b751025c6a78179b"), - base.HexToHash("0x1212121212121212121212121212121212121212"), - }, - Data: "0xa", - BlockNumber: bn, - TransactionIndex: txid, - } - conn := rpc.TempConnection(utils.GetTestChain()) - l := NewLedger( - conn, - base.HexToAddress("0xf503017d7baf7fbc0fff7492b751025c6a78179b"), - 0, - base.NOPOSN, - true, - false, - false, - false, - false, - nil, - ) - tx := types.Transaction{ - BlockNumber: bn, - TransactionIndex: txid, - Receipt: &types.Receipt{}, - } - l.theTx = &tx - apps := make([]types.Appearance, 0, 100) - apps = append(apps, types.Appearance{ - BlockNumber: uint32(bn), - TransactionIndex: uint32(txid), - }) - l.SetContexts("mainnet", apps) - s, _ := l.getStatementsFromLog(conn, &log) - b, _ := json.MarshalIndent(s, "", " ") - fmt.Println(string(b)) - fmt.Println("reconciled:", s.Reconciled()) -} diff --git a/src/apps/chifra/pkg/ledger/stmnt_from_receipt.go b/src/apps/chifra/pkg/ledger/stmnt_from_receipt.go index f80a94a7b0..501c21998e 100644 --- a/src/apps/chifra/pkg/ledger/stmnt_from_receipt.go +++ b/src/apps/chifra/pkg/ledger/stmnt_from_receipt.go @@ -15,13 +15,13 @@ func (l *Ledger) getStatementsFromReceipt(conn *rpc.Connection, filter *filter.A statements := make([]types.Statement, 0, 20) // a high estimate of the number of statements we'll need for _, log := range receipt.Logs { - addrArray := []base.Address{l.AccountFor} + addrArray := []base.Address{l.accountFor} if filter.ApplyLogFilter(&log, addrArray) && l.assetOfInterest(log.Address) { if statement, err := l.getStatementsFromLog(conn, &log); err != nil { return statements, err } else { - if statement.Sender == l.AccountFor || statement.Recipient == l.AccountFor { - add := !l.NoZero || statement.IsMaterial() + if statement.Sender == l.accountFor || statement.Recipient == l.accountFor { + add := !l.noZero || statement.IsMaterial() if add { statements = append(statements, statement) } diff --git a/src/apps/chifra/pkg/ledger/stmnt_from_traces.go b/src/apps/chifra/pkg/ledger/stmnt_from_traces.go index 0804689e75..958d4a890d 100644 --- a/src/apps/chifra/pkg/ledger/stmnt_from_traces.go +++ b/src/apps/chifra/pkg/ledger/stmnt_from_traces.go @@ -103,7 +103,7 @@ func (l *Ledger) getStatementsFromTraces(conn *rpc.Connection, trans *types.Tran } } - if l.trialBalance("trace-eth", &ret) { + if l.trialBalance(trialBalTraceEth, &ret) { if ret.IsMaterial() { statements = append(statements, ret) } else { diff --git a/src/apps/chifra/pkg/ledger/stmnt_from_tx.go b/src/apps/chifra/pkg/ledger/stmnt_from_tx.go index 687e7b85b0..f4b1b08999 100644 --- a/src/apps/chifra/pkg/ledger/stmnt_from_tx.go +++ b/src/apps/chifra/pkg/ledger/stmnt_from_tx.go @@ -15,39 +15,53 @@ import ( // GetStatements returns a statement from a given transaction func (l *Ledger) GetStatements(conn *rpc.Connection, filter *filter.AppearanceFilter, trans *types.Transaction) ([]types.Statement, error) { + // We need this below... l.theTx = trans - if false && conn.StoreReadable() { + if conn.StoreReadable() { // walk.Cache_Statements statementGroup := &types.StatementGroup{ BlockNumber: trans.BlockNumber, TransactionIndex: trans.TransactionIndex, - Address: l.AccountFor, + Address: l.accountFor, } if err := conn.Store.Read(statementGroup, nil); err == nil { return statementGroup.Statements, nil } } + _ = l.getOrCreateAssetContext(trans.BlockNumber, trans.TransactionIndex, l.accountFor) + _ = l.getOrCreateAssetContext(trans.BlockNumber, trans.TransactionIndex, base.FAKE_ETH_ADDRESS) + if trans.Receipt != nil { + for _, log := range trans.Receipt.Logs { + _ = l.getOrCreateAssetContext(trans.BlockNumber, trans.TransactionIndex, log.Address) + } + } + // make room for our results statements := make([]types.Statement, 0, 20) // a high estimate of the number of statements we'll need - key := l.ctxKey(trans.BlockNumber, trans.TransactionIndex) - ctx := l.Contexts[key] + key := l.getAssetContextKey(trans.BlockNumber, trans.TransactionIndex, l.accountFor) + var ctx *assetContext + var exists bool + if ctx, exists = l.assetContexts[key]; !exists { + debugLedgerContexts(l.testMode, l.assetContexts) + return statements, fmt.Errorf("no context for %s", key) + } if l.assetOfInterest(base.FAKE_ETH_ADDRESS) { // TODO: We ignore errors in the next few lines, but we should not // TODO: BOGUS PERF - This greatly increases the number of times we call into eth_getBalance which is quite slow - prevBal, _ := conn.GetBalanceAt(l.AccountFor, ctx.PrevBlock) + prevBal, _ := conn.GetBalanceAt(l.accountFor, ctx.Prev()) if trans.BlockNumber == 0 { prevBal = new(base.Wei) } - begBal, _ := conn.GetBalanceAt(l.AccountFor, ctx.CurBlock-1) - endBal, _ := conn.GetBalanceAt(l.AccountFor, ctx.CurBlock) + begBal, _ := conn.GetBalanceAt(l.accountFor, ctx.Cur()-1) + endBal, _ := conn.GetBalanceAt(l.accountFor, ctx.Cur()) ret := types.Statement{ - AccountedFor: l.AccountFor, + AccountedFor: l.accountFor, Sender: trans.From, Recipient: trans.To, BlockNumber: trans.BlockNumber, @@ -63,7 +77,7 @@ func (l *Ledger) GetStatements(conn *rpc.Connection, filter *filter.AppearanceFi PrevBal: *prevBal, BegBal: *begBal, EndBal: *endBal, - ReconType: ctx.ReconType, + ReconType: ctx.Recon(), } if trans.To.IsZero() && trans.Receipt != nil && !trans.Receipt.ContractAddress.IsZero() { @@ -71,7 +85,7 @@ func (l *Ledger) GetStatements(conn *rpc.Connection, filter *filter.AppearanceFi } // Do not collapse. A single transaction may have many movements of money - if l.AccountFor == ret.Sender { + if l.accountFor == ret.Sender { gasUsed := new(base.Wei) if trans.Receipt != nil { gasUsed.SetUint64(uint64(trans.Receipt.GasUsed)) @@ -84,7 +98,7 @@ func (l *Ledger) GetStatements(conn *rpc.Connection, filter *filter.AppearanceFi } // Do not collapse. A single transaction may have many movements of money - if l.AccountFor == ret.Recipient { + if l.accountFor == ret.Recipient { if ret.BlockNumber == 0 { ret.PrefundIn = trans.Value } else { @@ -100,19 +114,19 @@ func (l *Ledger) GetStatements(conn *rpc.Connection, filter *filter.AppearanceFi } } - if l.AsEther { + if l.asEther { ret.AssetSymbol = "ETH" } - if !l.UseTraces && l.trialBalance("eth", &ret) { + if !l.useTraces && l.trialBalance(trialBalEth, &ret) { if ret.IsMaterial() { statements = append(statements, ret) } else { logger.TestLog(true, "Tx reconciled with a zero value net amount. It's okay.") } } else { - if !l.UseTraces { - logger.TestLog(!l.UseTraces, "Trial balance failed for ", ret.TransactionHash.Hex(), "need to decend into traces") + if !l.useTraces { + logger.TestLog(!l.useTraces, "Trial balance failed for ", ret.TransactionHash.Hex(), "need to decend into traces") } if traceStatements, err := l.getStatementsFromTraces(conn, trans, &ret); err != nil { if !utils.IsFuzzing() { @@ -125,21 +139,30 @@ func (l *Ledger) GetStatements(conn *rpc.Connection, filter *filter.AppearanceFi } if receiptStatements, err := l.getStatementsFromReceipt(conn, filter, trans.Receipt); err != nil { - logger.Warn(l.TestMode, "Error getting statement from receipt") + // logger.Warn("Error getting statement from receipt", err) } else { statements = append(statements, receiptStatements...) } isFinal := base.IsFinal(conn.LatestBlockTimestamp, trans.Timestamp) - if false && isFinal && conn.StoreWritable() && conn.EnabledMap[walk.Cache_Statements] { + isWritable := conn.StoreWritable() + isEnabled := conn.EnabledMap[walk.Cache_Statements] + if isFinal && isWritable && isEnabled { + for _, statement := range statements { + if statement.IsMaterial() && !statement.Reconciled() { + debugLedgerContexts(l.testMode, l.assetContexts) + return statements, nil + } + } statementGroup := &types.StatementGroup{ BlockNumber: trans.BlockNumber, TransactionIndex: trans.TransactionIndex, - Address: l.AccountFor, + Address: l.accountFor, Statements: statements, } _ = conn.Store.Write(statementGroup, nil) } + debugLedgerContexts(l.testMode, l.assetContexts) return statements, nil } diff --git a/src/apps/chifra/pkg/ledger/todo.go b/src/apps/chifra/pkg/ledger/todo.go new file mode 100644 index 0000000000..e6bfb3ec06 --- /dev/null +++ b/src/apps/chifra/pkg/ledger/todo.go @@ -0,0 +1,68 @@ +package ledger + +// TODO: Is this comment relavant any longer? + +// See issue #2791 - This is the code that used to generate extra traces to make reconcilation work +// (or, at least, similar code in `chifra export` generated these traces. +// bool isSuicide = trace.action.selfDestructed != ""; +// bool isCreation = trace.result.address != ""; +// // do not colapse +// if (isCreation) { +// displayAsTrace(opt, trace); +// displayAsCreation(opt, trace); +// } +// // do not colapse +// if (isSuicide) { +// displayAsSuicide(opt, trace); +// } +// // do not colapse +// if (!isCreation && !isSuicide) { +// displayAsTrace(opt, trace); +// } +//-------------------------------------------------------------- +// bool displayAsCreation(COptions* opt, const CTrace& trace) { +// if (trace.result.address == "") +// return false; +// CTrace copy = trace; +// copy.action.from = "0x0"; +// copy.action.to = trace.result.address; +// copy.action.callType = "creation"; +// copy.action.value = trace.action.value; +// if (copy.traceAddress.size() == 0) +// copy.traceAddress.push_back("null"); +// copy.traceAddress.push_back("s"); +// copy.transactionHash = uint_2_Hex(trace.blockNumber * 100000 + trace.transactionIndex); +// copy.action.input = trace.action.input; +// return displayAsTrace(opt, copy); +// } +// //-------------------------------------------------------------- +// bool displayAsSuicide(COptions* opt, const CTrace& trace) { +// if (trace.action.refundAddress == "") +// return false; +// CTrace copy = trace; +// copy.action.from = trace.action.selfDestructed; +// copy.action.to = trace.action.refundAddress; +// copy.action.callType = "suicide"; +// copy.action.value = trace.action.balance; +// copy.traceAddress.push_back("s"); +// copy.transactionHash = uint_2_Hex(trace.blockNumber * 100000 + trace.transactionIndex); +// copy.action.input = "0x"; +// return displayAsTrace(opt, copy); +// } +// //-------------------------------------------------------------- +// bool displayAsTrace(COptions* opt, const CTrace& trace) { +// bool isText = (expContext().exportFmt & (TXT1 | CSV1)); +// if (isText) { +// cout << trim(trace.Format(expContext().fmtMap["format"]), '\t') << endl; +// } else { +// if (!opt->firstOut) +// cout << ","; +// cout << " "; +// indent(); +// trace.toJson(cout); +// unindent(); +// opt->firstOut = false; +// } +// return true; +// } + diff --git a/src/apps/chifra/pkg/ledger/trail_balance.go b/src/apps/chifra/pkg/ledger/trail_balance.go index cd50465126..a53b518213 100644 --- a/src/apps/chifra/pkg/ledger/trail_balance.go +++ b/src/apps/chifra/pkg/ledger/trail_balance.go @@ -1,37 +1,179 @@ package ledger import ( + "fmt" + "strings" + + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/pricing" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" ) -// trialBalance returns true of the reconciliation balances, false otherwise. It also prints the trial balance to the console. -func (l *Ledger) trialBalance(reason string, s *types.Statement) bool { - key := l.ctxKey(s.BlockNumber, s.TransactionIndex) - ctx := l.Contexts[key] +type trialBalType string + +const ( + trialBalEth trialBalType = "eth" + trialBalTraceEth trialBalType = "trace-eth" + trialBalToken trialBalType = "token" +) + +// trialBalance returns true of the reconciliation balances, false otherwise. If the statement +// does not reconcile, it tries to repair it in two ways (a) for null transfers and (b) for +// any other reason. If that works and the statement is material (money moved in some way), the +// function tries to price the asset. it then prints optional debugging information. Note that +// the statement may be modified in this function. +func (l *Ledger) trialBalance(reason trialBalType, s *types.Statement) bool { + key := l.getAssetContextKey(s.BlockNumber, s.TransactionIndex, s.AssetAddr) + if ctx, exists := l.assetContexts[key]; !exists { + logger.Fatal(fmt.Sprintf("should never happen - no context for %s", key)) + + } else { + logger.TestLog(l.testMode, "Start of trial balance report") - s.ReconType = ctx.ReconType - s.AssetType = reason + s.ReconType = ctx.Recon() + s.AssetType = string(reason) - logger.TestLog(l.TestMode, "Start of trial balance report") + var okay bool + if okay = s.Reconciled(); !okay { + if okay = l.correctForNullTransfer(s, l.theTx); !okay { + if s.IsEth() { + _ = l.correctForSomethingElseEth(s) + } else { + _ = l.correctForSomethingElseToken(s) + } + } + } + + if s.IsMaterial() { + s.SpotPrice, s.PriceSource, _ = pricing.PriceUsd(l.connection, s) + } - // TODO: BOGUS PERF - var okay bool - if okay = s.Reconciled(); !okay { - if okay = s.CorrectForNullTransfer(l.theTx); !okay { - _ = s.CorrectForSomethingElse(l.theTx) + if l.testMode { + s.DebugStatement(ctx) } } - // TODO: BOGUS PERF - if s.IsMaterial() { - s.SpotPrice, s.PriceSource, _ = pricing.PriceUsd(l.Conn, s) + return s.Reconciled() +} + +func isNullTransfer(s *types.Statement, tx *types.Transaction) bool { + lotsOfLogs := len(tx.Receipt.Logs) > 10 + mayBeAirdrop := s.Sender.IsZero() || s.Sender == tx.To + noBalanceChange := s.EndBal.Cmp(&s.BegBal) == 0 && s.IsMaterial() + ret := (lotsOfLogs || mayBeAirdrop) && noBalanceChange + + logger.TestLog(true, "A possible nullTransfer") + logger.TestLog(true, " nLogs: ", len(tx.Receipt.Logs)) + logger.TestLog(true, " lotsOfLogs: -->", lotsOfLogs) + + logger.TestLog(true, " Sender.IsZero: ", s.Sender, s.Sender.IsZero()) + logger.TestLog(true, " or Sender == To: ", s.Sender == tx.To) + logger.TestLog(true, " mayBeAirdrop: -->", mayBeAirdrop) + + logger.TestLog(true, " EndBal-BegBal: ", s.EndBal.Cmp(&s.BegBal)) + logger.TestLog(true, " material: ", s.IsMaterial()) + logger.TestLog(true, " noBalanceChange: -->", noBalanceChange) + + if !ret { + logger.TestLog(true, " ---> Not a nullTransfer") + } + + return ret +} + +func (l *Ledger) correctForNullTransfer(s *types.Statement, tx *types.Transaction) bool { + if s.IsEth() { + return s.Reconciled() } - if l.TestMode { - s.DebugStatement(ctx) + if isNullTransfer(s, tx) { + logger.TestLog(true, "Correcting token transfer for a null transfer") + amt := s.TotalIn() // use totalIn since this is the amount that was faked + s.AmountOut = *new(base.Wei) + s.AmountIn = *new(base.Wei) + s.CorrectingIn = *amt + s.CorrectingOut = *amt + s.CorrectingReason = "null-transfer" + } else { + logger.TestLog(true, "Needs correction for token transfer") } return s.Reconciled() } + +func (l *Ledger) correctForSomethingElseEth(s *types.Statement) bool { + if s.AssetType == "trace-eth" && s.ReconType&types.First != 0 && s.ReconType&types.Last != 0 { + if s.EndBalCalc().Cmp(&s.EndBal) != 0 { + s.EndBal = *s.EndBalCalc() + s.CorrectingReason = "per-block-balance" + } + } else { + logger.TestLog(true, "Needs correction for eth") + } + return s.Reconciled() +} + +func (l *Ledger) correctForSomethingElseToken(s *types.Statement) bool { + logger.TestLog(true, "Correcting token transfer for unknown income or outflow") + + // We assume that the discrepancy is due to an error in the beginning balance. + // Compute the difference at the beginning: + deltaBeg := new(base.Wei).Sub(&s.BegBal, &s.PrevBal) + // Compute the computed ending balance: + endCalc := s.EndBalCalc() + // Compute the difference at the end: + deltaEnd := new(base.Wei).Sub(endCalc, &s.EndBal) + + // For reconciliation, we want both deltaBeg and deltaEnd to be zero. + // One simple approach is to assume that the correcting entry should cancel the beginning discrepancy. + // That is, if BegBal is too high relative to PrevBal (deltaBeg positive), + // we record a correcting entry (as a debit) equal to deltaBeg. + + // Reset correcting fields. + s.CorrectingIn.SetUint64(0) + s.CorrectingOut.SetUint64(0) + s.CorrectingReason = "" + + // If the beginning balance is too high, record a debit correction. + if deltaBeg.Cmp(new(base.Wei).SetInt64(0)) > 0 { + s.CorrectingIn = *deltaBeg + s.CorrectingReason = "begbal" + } else if deltaBeg.Cmp(new(base.Wei).SetInt64(0)) < 0 { + s.CorrectingOut = *deltaBeg + s.CorrectingReason = "begbal" + } + + // Now adjust the ending balance so that the computed ending balance matches. + // We want the effective ending balance (EndBal plus correction) to equal EndBalCalc. + // Let’s define the net correction as the sum of the beginning and ending discrepancies. + // One option is to add the ending discrepancy to the correcting entry. + if deltaEnd.Cmp(new(base.Wei).SetInt64(0)) > 0 { + n := new(base.Wei).Add(&s.CorrectingIn, deltaEnd) + s.CorrectingIn = *n + s.CorrectingReason += "endbal" + } else if deltaEnd.Cmp(new(base.Wei).SetInt64(0)) < 0 { + n := new(base.Wei).Add(&s.CorrectingOut, deltaEnd) + s.CorrectingOut = *n + s.CorrectingReason += "endbal" + } + + // Finally, adjust EndBal so that the effective ending balance is correct. + // One way is to subtract the net correcting entry from EndBalCalc. + // (This assumes that the correction is recorded on the beginning side.) + // In other words, set EndBal = EndBalCalc - (net correcting entry). + var netCorrection *base.Wei + if s.CorrectingIn.Cmp(new(base.Wei).SetInt64(0)) != 0 { + netCorrection = &s.CorrectingIn + } else { + netCorrection = &s.CorrectingOut + } + + adjustedEndBal := new(base.Wei).Sub(endCalc, netCorrection) + s.EndBal = *adjustedEndBal + + // For logging clarity, also replace the reason string formatting. + s.CorrectingReason = strings.Replace(s.CorrectingReason, "begbalendbal", "begbal-endbal", -1) + + return s.Reconciled() // Now s.Reconciled() should be true. +} diff --git a/src/apps/chifra/pkg/ledger/trail_balance_test.go b/src/apps/chifra/pkg/ledger/trail_balance_test.go new file mode 100644 index 0000000000..f98804a5ff --- /dev/null +++ b/src/apps/chifra/pkg/ledger/trail_balance_test.go @@ -0,0 +1,482 @@ +package ledger + +import ( + "fmt" + "os" + "testing" + + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" +) + +// TestCorrectForNullTransfer tests the correctForNullTransfer function. +func TestCorrectForNullTransfer(t *testing.T) { + // --- ETH branch --- + t.Run("ETH branch", func(t *testing.T) { + // Create a Transaction with any minimal required fields. + tx := &types.Transaction{ + To: base.HexToAddress("0xAAA"), + } + tx.Receipt = new(types.Receipt) + // (For ETH branch the logs and other fields are irrelevant.) + + // Create a statement that is considered ETH. + // s.IsEth() returns true when AssetAddr equals base.FAKE_ETH_ADDRESS. + stmt := new(types.Statement) + stmt.AssetAddr = base.FAKE_ETH_ADDRESS // This makes it ETH. + // Set balances to any values; they won't be modified by correctForNullTransfer. + stmt.BegBal = *base.NewWei(1000) + stmt.EndBal = *base.NewWei(1050) + // Set a nonzero amount so that the statement is material. + stmt.AmountIn = *base.NewWei(100) + + // Call correctForNullTransfer; for ETH, it should simply return s.Reconciled(). + // Note: It will not modify the token fields. + prevAmountIn := &stmt.AmountIn + prevCorrectingIn := &stmt.CorrectingIn + var l Ledger + result := l.correctForNullTransfer(stmt, tx) + // For ETH branch, no correction is applied; the return is s.Reconciled(). + // We don't expect any modifications to AmountIn, AmountOut, etc. + if stmt.AmountIn.Cmp(prevAmountIn) != 0 { + t.Error("ETH branch: AmountIn should remain unchanged") + } + if stmt.CorrectingIn.Cmp(prevCorrectingIn) != 0 { + t.Error("ETH branch: CorrectingIn should remain unchanged") + } + // We can also check that the function simply returns s.Reconciled(). + if result != stmt.Reconciled() { + t.Error("ETH branch: Function should return s.Reconciled()") + } + }) + + // --- Token branch: isNullTransfer returns true --- + t.Run("Token branch (null transfer detected)", func(t *testing.T) { + // Create a Transaction that meets the "lotsOfLogs" condition. + tx := &types.Transaction{ + To: base.HexToAddress("0xBBB"), + } + tx.Receipt = new(types.Receipt) + // Set Logs to a slice of 11 Logs to trigger lotsOfLogs. + tx.Receipt.Logs = make([]types.Log, 11) + + // Create a token statement (non-ETH). + stmt := new(types.Statement) + // Use a non-ETH address. + stmt.AssetAddr = base.HexToAddress("0xCCC") + stmt.AssetType = "token" + // To trigger the airdrop condition, we can set Sender to zero address. + stmt.Sender = base.HexToAddress("0x0") + // For isNullTransfer, noBalanceChange must be true: + // set BegBal equal to EndBal. + stmt.BegBal = *base.NewWei(100) + stmt.EndBal = *base.NewWei(100) + // Set a nonzero AmountIn so that the statement is material. + stmt.AmountIn = *base.NewWei(50) + // Optionally, ensure other incoming fields are zero. + stmt.InternalIn = *base.NewWei(0) + stmt.SelfDestructIn = *base.NewWei(0) + stmt.MinerBaseRewardIn = *base.NewWei(0) + stmt.MinerNephewRewardIn = *base.NewWei(0) + stmt.MinerTxFeeIn = *base.NewWei(0) + stmt.MinerUncleRewardIn = *base.NewWei(0) + stmt.PrefundIn = *base.NewWei(0) + + // Pre-set outgoing and correcting fields to nonzero to verify they are reset. + stmt.AmountOut = *base.NewWei(123) + stmt.CorrectingIn = *base.NewWei(456) + stmt.CorrectingOut = *base.NewWei(789) + stmt.CorrectingReason = "oldreason" + + // Record the TotalIn before correction. + originalTotalIn := stmt.TotalIn() + + // Call correctForNullTransfer. + var l Ledger + result := l.correctForNullTransfer(stmt, tx) + // After correction, since isNullTransfer is true, + // AmountIn and AmountOut should be zero. + if stmt.AmountIn.Cmp(base.NewWei(0)) != 0 { + t.Errorf("Token branch (null transfer): Expected AmountIn to be 0, got %s", stmt.AmountIn.Text(10)) + } + if stmt.AmountOut.Cmp(base.NewWei(0)) != 0 { + t.Errorf("Token branch (null transfer): Expected AmountOut to be 0, got %s", stmt.AmountOut.Text(10)) + } + // CorrectingIn and CorrectingOut should be set to the original TotalIn. + if stmt.CorrectingIn.Cmp(originalTotalIn) != 0 { + t.Errorf("Token branch (null transfer): Expected CorrectingIn to be %s, got %s", + originalTotalIn.Text(10), stmt.CorrectingIn.Text(10)) + } + if stmt.CorrectingOut.Cmp(originalTotalIn) != 0 { + t.Errorf("Token branch (null transfer): Expected CorrectingOut to be %s, got %s", + originalTotalIn.Text(10), stmt.CorrectingOut.Text(10)) + } + // CorrectingReason should be "null-transfer". + if stmt.CorrectingReason != "null-transfer" { + t.Errorf("Token branch (null transfer): Expected CorrectingReason to be 'null-transfer', got %q", stmt.CorrectingReason) + } + // Finally, check the reconciliation status. It depends on the rest of the statement. + // For this test, likely Reconciled() remains false because BegBalDiff and EndBalDiff + // are computed from BegBal, PrevBal, EndBal, etc., which haven't been changed. + // We simply check that the function returns what Reconciled() returns. + if result != stmt.Reconciled() { + t.Error("Token branch (null transfer): Function return should equal s.Reconciled()") + } + }) + + // --- Token branch: isNullTransfer returns false --- + t.Run("Token branch (null transfer not detected)", func(t *testing.T) { + // Create a Transaction that does NOT meet the null transfer condition. + // For example, set Logs to fewer than 10 logs. + tx := &types.Transaction{ + To: base.HexToAddress("0xBBB"), + } + tx.Receipt = new(types.Receipt) + tx.Receipt.Logs = make([]types.Log, 5) // not enough logs for lotsOfLogs + + // Create a token statement. + stmt := new(types.Statement) + stmt.AssetAddr = base.HexToAddress("0xCCC") + stmt.AssetType = "token" + // Set Sender to nonzero and not equal to tx.To, so airdrop condition is false. + stmt.Sender = base.HexToAddress("0xABC") + // Set balances so that noBalanceChange is true. + stmt.BegBal = *base.NewWei(100) + stmt.EndBal = *base.NewWei(100) + // Set AmountIn so that the statement is material. + stmt.AmountIn = *base.NewWei(50) + + // Pre-set outgoing and correcting fields. + stmt.AmountOut = *base.NewWei(123) + stmt.CorrectingIn = *base.NewWei(456) + stmt.CorrectingOut = *base.NewWei(789) + stmt.CorrectingReason = "oldreason" + + // Record the original values. + origAmountIn := &stmt.AmountIn + origAmountOut := &stmt.AmountOut + origCorrectingIn := &stmt.CorrectingIn + origCorrectingOut := &stmt.CorrectingOut + origReason := stmt.CorrectingReason + + // Call correctForNullTransfer. + var l Ledger + result := l.correctForNullTransfer(stmt, tx) + // Since isNullTransfer is false, no corrections should be applied. + if stmt.AmountIn.Cmp(origAmountIn) != 0 { + t.Error("Token branch (non-null transfer): AmountIn should remain unchanged") + } + if stmt.AmountOut.Cmp(origAmountOut) != 0 { + t.Error("Token branch (non-null transfer): AmountOut should remain unchanged") + } + if stmt.CorrectingIn.Cmp(origCorrectingIn) != 0 { + t.Error("Token branch (non-null transfer): CorrectingIn should remain unchanged") + } + if stmt.CorrectingOut.Cmp(origCorrectingOut) != 0 { + t.Error("Token branch (non-null transfer): CorrectingOut should remain unchanged") + } + if stmt.CorrectingReason != origReason { + t.Error("Token branch (non-null transfer): CorrectingReason should remain unchanged") + } + if result != stmt.Reconciled() { + t.Error("Token branch (non-null transfer): Function return should equal s.Reconciled()") + } + }) +} + +func TestIsNullTransfer(t *testing.T) { + // // Case 1: lotsOfLogs true, airdrop true, noBalanceChange true. + t.Run("Case 1: lotsOfLogs true, airdrop true, noBalanceChange true", func(t *testing.T) { + tx := &types.Transaction{ + To: base.HexToAddress("0xABC"), // arbitrary nonzero address + } + // Initialize Receipt and Logs. + tx.Receipt = new(types.Receipt) + tx.Receipt.Logs = make([]types.Log, 11) // >10 logs + + stmt := new(types.Statement) + // Set Sender to zero address to trigger airdrop condition. + stmt.Sender = base.HexToAddress("0x0") + // Ensure no balance change: BegBal equals EndBal. + stmt.BegBal = *base.NewWei(100) + stmt.EndBal = *base.NewWei(100) + // Make the statement material by setting a nonzero incoming amount. + stmt.AmountIn = *base.NewWei(50) + + if !isNullTransfer(stmt, tx) { + t.Error("Case 1: Expected isNullTransfer to return true") + } + }) + + // Case 2: lotsOfLogs false, airdrop true, noBalanceChange true. + t.Run("Case 2: lotsOfLogs false, airdrop true, noBalanceChange true", func(t *testing.T) { + tx := &types.Transaction{ + To: base.HexToAddress("0xABC"), + } + // Initialize Receipt and Logs. + tx.Receipt = new(types.Receipt) + tx.Receipt.Logs = make([]types.Log, 5) // ≤10 logs + + stmt := new(types.Statement) + stmt.Sender = base.HexToAddress("0x0") // triggers airdrop condition. + stmt.BegBal = *base.NewWei(100) + stmt.EndBal = *base.NewWei(100) + stmt.AmountIn = *base.NewWei(50) + + if !isNullTransfer(stmt, tx) { + t.Error("Case 2: Expected isNullTransfer to return true") + } + }) + + // Case 3: lotsOfLogs true, airdrop false, noBalanceChange true. + t.Run("Case 3: lotsOfLogs true, airdrop false, noBalanceChange true", func(t *testing.T) { + tx := &types.Transaction{ + To: base.HexToAddress("0xDEF"), // Different from Sender. + } + // Initialize Receipt and Logs. + tx.Receipt = new(types.Receipt) + tx.Receipt.Logs = make([]types.Log, 11) // >10 logs + + stmt := new(types.Statement) + // Set Sender to a nonzero address different from tx.To. + stmt.Sender = base.HexToAddress("0xABC") + stmt.BegBal = *base.NewWei(100) + stmt.EndBal = *base.NewWei(100) + stmt.AmountIn = *base.NewWei(50) + + if !isNullTransfer(stmt, tx) { + t.Error("Case 3: Expected isNullTransfer to return true") + } + }) + + // Case 4: lotsOfLogs false, airdrop false, noBalanceChange true. + t.Run("Case 4: lotsOfLogs false, airdrop false, noBalanceChange true", func(t *testing.T) { + tx := &types.Transaction{ + To: base.HexToAddress("0xDEF"), + } + // Initialize Receipt and Logs. + tx.Receipt = new(types.Receipt) + tx.Receipt.Logs = make([]types.Log, 5) // fewer logs + + stmt := new(types.Statement) + // Set Sender to a nonzero address different from tx.To. + stmt.Sender = base.HexToAddress("0xABC") + stmt.BegBal = *base.NewWei(100) + stmt.EndBal = *base.NewWei(100) + stmt.AmountIn = *base.NewWei(50) + + if isNullTransfer(stmt, tx) { + t.Error("Case 4: Expected isNullTransfer to return false") + } + }) + + // Case 5: Conditions met for logs/airdrop, but balance changes (noBalanceChange false). + t.Run("Case 5: Balance change present", func(t *testing.T) { + tx := &types.Transaction{ + To: base.HexToAddress("0x0"), // may trigger airdrop condition. + } + // Initialize Receipt and Logs. + tx.Receipt = new(types.Receipt) + tx.Receipt.Logs = make([]types.Log, 11) + + stmt := new(types.Statement) + stmt.Sender = base.HexToAddress("0x0") + stmt.BegBal = *base.NewWei(100) + stmt.EndBal = *base.NewWei(110) // Balance changes: difference of 10. + stmt.AmountIn = *base.NewWei(50) + + if isNullTransfer(stmt, tx) { + t.Error("Case 5: Expected isNullTransfer to return false due to balance change") + } + }) + + // Case 6: Conditions met for logs/airdrop, but the statement is not material. + t.Run("Case 6: Not material", func(t *testing.T) { + tx := &types.Transaction{ + To: base.HexToAddress("0x0"), + } + // Initialize Receipt and Logs. + tx.Receipt = new(types.Receipt) + tx.Receipt.Logs = make([]types.Log, 11) + + stmt := new(types.Statement) + stmt.Sender = base.HexToAddress("0x0") // zero address triggers airdrop condition. + stmt.BegBal = *base.NewWei(100) + stmt.EndBal = *base.NewWei(100) + // No AmountIn set, so the statement is not material. + if isNullTransfer(stmt, tx) { + t.Error("Case 6: Expected isNullTransfer to return false because statement is not material") + } + }) +} + +// capturedLogs will store the logs produced by our fakeTestLog. +var capturedLogs []string + +// fakeTestLog is our test stub that captures log messages. +// It mimics logger.TestLog by appending a formatted string to capturedLogs. +func fakeTestLog(notDefault bool, args ...interface{}) { + // We'll capture logs only if notDefault is true (as per original logic). + if notDefault { + // Concatenate all arguments with a space. + logMsg := fmt.Sprint(args...) + capturedLogs = append(capturedLogs, logMsg) + } +} + +// resetLogger overrides logger.TestLog with our fakeTestLog, +// clears capturedLogs, and returns a function to restore the original. +func resetLogger() func() { + // Ensure that the test mode is enabled for our tests. + os.Setenv("TEST_MODE", "true") + // Save the original TestLog. + orig := logger.TestLog + // Replace TestLog with our fake. + logger.TestLog = fakeTestLog + // Clear any previously captured logs. + capturedLogs = nil + // Return a restore function. + return func() { + logger.TestLog = orig + } +} + +func TestCorrectForSomethingElseEth(t *testing.T) { + restore := resetLogger() + defer restore() + + t.Run("ETH branch: per-block-balance correction", func(t *testing.T) { + // For the ETH branch, s.IsEth() must return true. + // Assume that s.IsEth() checks whether AssetAddr equals base.FAKE_ETH_ADDRESS. + // Also, for this branch we want AssetType == "trace-eth" and both First and Last flags set. + stmt := &types.Statement{ + AssetAddr: base.FAKE_ETH_ADDRESS, // so that IsEth() returns true + AssetType: "trace-eth", + ReconType: types.First | types.Last, + BlockNumber: 100, // arbitrary block number + TransactionIndex: 1, + // Set balances: + // Let BegBal be 1000 and AmountNet (computed via AmountIn - AmountOut, with others zero) be 100. + // Then EndBalCalc() should be 1100. + BegBal: *base.NewWei(1000), + AmountIn: *base.NewWei(100), + AmountOut: *base.NewWei(0), + // Set EndBal to a value different from EndBalCalc() so that correction is needed. + EndBal: *base.NewWei(1050), + } + // When CorrectForSomethingElse is called, it should update EndBal to 1100 and set CorrectingReason. + var l Ledger + reconciled := l.correctForSomethingElseEth(stmt) + // Verify EndBal is corrected. + expectedEndBal := base.NewWei(1100) + if stmt.EndBal.Cmp(expectedEndBal) != 0 { + t.Errorf("ETH branch: Expected EndBal to be corrected to %s; got %s", expectedEndBal.Text(10), stmt.EndBal.Text(10)) + } + // Verify CorrectingReason is set appropriately. + if stmt.CorrectingReason != "per-block-balance" { + t.Errorf("ETH branch: Expected CorrectingReason to be 'per-block-balance'; got %q", stmt.CorrectingReason) + } + // (Optionally, you could inspect reconciled, but here we focus on the side effects.) + _ = reconciled + }) +} + +func TestCorrectForSomethingElseToken(t *testing.T) { + restore := resetLogger() + defer restore() + + t.Run("Token branch: adjusting correcting values", func(t *testing.T) { + // Create a fresh Statement with all fields initialized to their zero values. + stmt := new(types.Statement) + + // Set only the fields required for the test. + stmt.AssetAddr = base.HexToAddress("0x1111111111111111111111111111111111111111") // Not ETH. + stmt.AssetType = "token" + stmt.BlockNumber = 200 + + // Set up balance fields. + // Let PrevBal = 90, BegBal = 100, so BegBalDiff() should be 10. + stmt.PrevBal = *base.NewWei(90) + stmt.BegBal = *base.NewWei(100) + + // Set AmountIn = 50, AmountOut = 0, so that AmountNet() = 50. + stmt.AmountIn = *base.NewWei(50) + stmt.AmountOut = *base.NewWei(0) + + // Then EndBalCalc() should be BegBal + AmountNet() = 100 + 50 = 150. + // Set EndBal to 140, so EndBalDiff() = 150 - 140 = 10. + stmt.EndBal = *base.NewWei(140) + + // Pre-populate correcting fields to non-zero values to ensure they are reset. + stmt.CorrectingIn = *base.NewWei(999) + stmt.CorrectingOut = *base.NewWei(999) + stmt.CorrectingReason = "oldreason" + + // Call the correction method. + var l Ledger + reconciled := l.correctForSomethingElseToken(stmt) + + // Expected behavior: + // - BegBalDiff() is 10. + // - EndBalDiff() is 10. + // Thus, CorrectingIn should be set to 10 + 10 = 20, + // CorrectingOut should be reset to 0, + // And CorrectingReason should become "begbal-endbal". + expectedCorrectingIn := base.NewWei(20) + if stmt.CorrectingIn.Cmp(expectedCorrectingIn) != 0 { + t.Errorf("Token branch: Expected CorrectingIn to be %s; got %s", + expectedCorrectingIn.Text(10), stmt.CorrectingIn.Text(10)) + } + if stmt.CorrectingOut.Cmp(base.NewWei(0)) != 0 { + t.Errorf("Token branch: Expected CorrectingOut to be 0; got %s", stmt.CorrectingOut.Text(10)) + } + if stmt.CorrectingReason != "begbal-endbal" { + t.Errorf("Token branch: Expected CorrectingReason to be 'begbal-endbal'; got %q", stmt.CorrectingReason) + } + if reconciled { + t.Error("Token branch: Expected statement not to be reconciled") + } + }) +} + +func TestTrialBalance_Reconciled(t *testing.T) { + l := &Ledger{ + testMode: true, + assetContexts: make(map[assetContextKey]*assetContext), + } + l.theTx = &types.Transaction{ + Receipt: new(types.Receipt), + To: base.HexToAddress("0xDummy"), + } + l.theTx.Receipt.Logs = []types.Log{} + + bn := base.Blknum(100) + txid := base.Txnum(1) + assetAddr := base.HexToAddress("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee") + appCtx := newAppContext(bn-1, bn, bn+1, false, false, l.reversed) + ac := newAssetContext(appCtx.Prev(), appCtx.Cur(), appCtx.Next(), false, false, l.reversed, assetAddr) + key := l.getAssetContextKey(bn, txid, assetAddr) + l.assetContexts[key] = ac + + s := new(types.Statement) + s.BlockNumber = bn + s.TransactionIndex = txid + s.AssetAddr = assetAddr + s.PrevBal = *base.NewWei(100) + s.BegBal = *base.NewWei(100) + s.EndBal = *base.NewWei(100) + s.AmountIn = *base.NewWei(10) + s.AmountOut = *base.NewWei(10) + + result := l.trialBalance(trialBalEth, s) + if !result { + t.Error("Expected trialBalance to return true for a reconciled statement") + } + if s.AssetType != string(trialBalEth) { + t.Errorf("Expected AssetType to be %s, got %s", trialBalEth, s.AssetType) + } + if s.ReconType != ac.Recon() { + t.Errorf("Expected ReconType to be %v, got %v", ac.Recon(), s.ReconType) + } +} diff --git a/src/apps/chifra/pkg/logger/logger.go b/src/apps/chifra/pkg/logger/logger.go index a52358e32f..160477cb1b 100644 --- a/src/apps/chifra/pkg/logger/logger.go +++ b/src/apps/chifra/pkg/logger/logger.go @@ -47,7 +47,7 @@ func SetTestMode(onOff bool) { } // TestLog is used to print log lines during testing only -func TestLog(notDefault bool, a ...interface{}) { +var TestLog = func(notDefault bool, a ...interface{}) { if !testModeSet { testModeSet = true testMode = os.Getenv("TEST_MODE") == "true" diff --git a/src/apps/chifra/pkg/notify/notification.go b/src/apps/chifra/pkg/notify/notification.go index f3b67fc6e8..55a1a5f273 100644 --- a/src/apps/chifra/pkg/notify/notification.go +++ b/src/apps/chifra/pkg/notify/notification.go @@ -2,6 +2,7 @@ package notify import "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" +// NOTIFY CODE type Message string type Notification[T NotificationPayload] struct { diff --git a/src/apps/chifra/pkg/notify/scraper.go b/src/apps/chifra/pkg/notify/scraper.go index ced3112d19..5d1c6fdccc 100644 --- a/src/apps/chifra/pkg/notify/scraper.go +++ b/src/apps/chifra/pkg/notify/scraper.go @@ -7,6 +7,7 @@ import ( "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/uniq" ) +// NOTIFY CODE const ( MessageChunkWritten Message = "chunkWritten" MessageStageUpdated Message = "stageUpdated" diff --git a/src/apps/chifra/pkg/rpc/connection.go b/src/apps/chifra/pkg/rpc/connection.go index c5ae065d2c..39e9af6cf9 100644 --- a/src/apps/chifra/pkg/rpc/connection.go +++ b/src/apps/chifra/pkg/rpc/connection.go @@ -16,6 +16,9 @@ type Connection struct { Store *cache.Store // Cache Store to use for read/write. Write can be disabled by setting Store to read-only mode LatestBlockTimestamp base.Timestamp EnabledMap map[walk.CacheType]bool + balanceCache map[string]*base.Wei + tokenBalanceCache map[string]*base.Wei + // cacheMutex sync.Mutex } // settings allows every command has its own options type, we have to @@ -33,7 +36,10 @@ func NewConnection(chain string, cacheEnabled bool, caches map[walk.CacheType]bo CacheEnabled: cacheEnabled, EnabledMap: caches, } - return settings.GetRpcConnection() + conn := settings.GetRpcConnection() + conn.balanceCache = make(map[string]*base.Wei) + conn.tokenBalanceCache = make(map[string]*base.Wei) + return conn } func TempConnection(chain string) *Connection { diff --git a/src/apps/chifra/pkg/rpc/get_state.go b/src/apps/chifra/pkg/rpc/get_state.go index b433780604..2eea011210 100644 --- a/src/apps/chifra/pkg/rpc/get_state.go +++ b/src/apps/chifra/pkg/rpc/get_state.go @@ -157,14 +157,34 @@ func (conn *Connection) GetState(fieldBits types.StatePart, address base.Address // GetBalanceAt returns a balance for an address at a block func (conn *Connection) GetBalanceAt(addr base.Address, bn base.Blknum) (*base.Wei, error) { + // var ok bool + var balance *base.Wei + + // key := fmt.Sprintf("%s|%d", addr.Hex(), bn) + // conn.cacheMutex.Lock() + // if balance, ok = conn.balanceCache[key]; ok { + // conn.cacheMutex.Unlock() + // return balance, nil + // } + // conn.cacheMutex.Unlock() + if ec, err := conn.getClient(); err != nil { var zero base.Wei return &zero, err } else { defer ec.Close() - ret, err := ec.BalanceAt(context.Background(), addr.Common(), base.BiFromBn(bn)) - return (*base.Wei)(ret), err + if ret, err := ec.BalanceAt(context.Background(), addr.Common(), base.BiFromBn(bn)); err != nil { + return (*base.Wei)(ret), err + } else { + balance = (*base.Wei)(ret) + } } + + // conn.cacheMutex.Lock() + // conn.balanceCache[key] = balance + // conn.cacheMutex.Unlock() + + return balance, nil } func (conn *Connection) getTypeNonProxy(address base.Address, bn base.Blknum) string { diff --git a/src/apps/chifra/pkg/rpc/get_token.go b/src/apps/chifra/pkg/rpc/get_token.go index 1693f34dc2..79849a3280 100644 --- a/src/apps/chifra/pkg/rpc/get_token.go +++ b/src/apps/chifra/pkg/rpc/get_token.go @@ -150,6 +150,13 @@ func (conn *Connection) GetBalanceAtToken(token, holder base.Address, hexBlockNo if hexBlockNo != "" && hexBlockNo != "latest" && !strings.HasPrefix(hexBlockNo, "0x") { hexBlockNo = fmt.Sprintf("0x%x", base.MustParseUint64(hexBlockNo)) } + // key := fmt.Sprintf("%s|%s|%s", token.Hex(), holder.Hex(), hexBlockNo) + // conn.cacheMutex.Lock() + // if balance, ok := conn.tokenBalanceCache[key]; ok { + // conn.cacheMutex.Unlock() + // return balance, nil + // } + // conn.cacheMutex.Unlock() payloads := []query.BatchPayload{{ Key: "balance", @@ -170,9 +177,16 @@ func (conn *Connection) GetBalanceAtToken(token, holder base.Address, hexBlockNo return nil, err } + var balance *base.Wei if output["balance"] == nil { - return base.NewWei(0), nil + balance = base.NewWei(0) + } else { + balance = base.HexToWei(*output["balance"]) } - return base.HexToWei(*output["balance"]), nil + // conn.cacheMutex.Lock() + // conn.tokenBalanceCache[key] = balance + // conn.cacheMutex.Unlock() + + return balance, nil } diff --git a/src/apps/chifra/pkg/topics/topics.go b/src/apps/chifra/pkg/topics/topics.go new file mode 100644 index 0000000000..d550dc2600 --- /dev/null +++ b/src/apps/chifra/pkg/topics/topics.go @@ -0,0 +1,15 @@ +package topics + +import "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + +// transferTopic is here because these three topics make up almost all of the logs in the entire history +// of the chain, we get significant speed-ups if we handle these items without regular processing. +var TransferTopic = base.HexToHash( + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", +) +var ApprovalTopic = base.HexToHash( + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", +) +var EnsTransferTopic = base.HexToHash( + "0xd4735d920b0f87494915f556dd9b54c8f309026070caea5c737245152564d266", +) diff --git a/src/apps/chifra/pkg/types/types_reconType_test.go b/src/apps/chifra/pkg/types/types_reconType_test.go new file mode 100644 index 0000000000..26530dc14a --- /dev/null +++ b/src/apps/chifra/pkg/types/types_reconType_test.go @@ -0,0 +1,64 @@ +package types + +import ( + "testing" +) + +func TestReconTypeString(t *testing.T) { + tests := []struct { + input ReconType + expected string + }{ + {Genesis, "genesis"}, + {DiffDiff, "diff-diff"}, + {SameSame, "same-same"}, + {DiffSame, "diff-same"}, + {SameDiff, "same-diff"}, + {Invalid, "invalid"}, + {ShouldNotHappen, "invalid"}, + {First | DiffDiff, "first-diff-diff"}, + {Last | SameSame, "same-last"}, + {First | Last | DiffSame, "first-diff-last"}, + } + + for _, tt := range tests { + result := tt.input.String() + if result != tt.expected { + t.Errorf("ReconType(%d).String() = %q; want %q", tt.input, result, tt.expected) + } + } +} + +func TestReconTypeBitwiseOperations(t *testing.T) { + if (DiffDiff & First) != 0 { + t.Errorf("DiffDiff should not have First flag set") + } + + if (First|DiffDiff)&First == 0 { + t.Errorf("First flag should be detected in First | DiffDiff") + } + + if (First|Last|DiffSame)&Last == 0 { + t.Errorf("Last flag should be detected in First | Last | DiffSame") + } +} + +func TestReconTypeCombinationStrings(t *testing.T) { + tests := []struct { + input ReconType + expected string + }{ + {First | Genesis, "genesis"}, + {First | Last | Genesis, "genesis"}, + {First | Last | DiffDiff, "first-diff-diff-last"}, + {First | Last | SameSame, "first-last"}, + {First | Last | DiffSame, "first-diff-last"}, + } + + for _, tt := range tests { + result := tt.input.String() + if result != tt.expected { + t.Errorf("ReconType(%d).String() = %q; want %q", tt.input, result, tt.expected) + } + } +} diff --git a/src/apps/chifra/pkg/types/types_statement.go b/src/apps/chifra/pkg/types/types_statement.go index c49264dea1..097b31c75f 100644 --- a/src/apps/chifra/pkg/types/types_statement.go +++ b/src/apps/chifra/pkg/types/types_statement.go @@ -598,7 +598,7 @@ func (s *Statement) BegBalDiff() *base.Wei { if s.BlockNumber == 0 { val = new(base.Wei).SetInt64(0) } else { - new(base.Wei).Sub(&s.BegBal, &s.PrevBal) + val = new(base.Wei).Sub(&s.BegBal, &s.PrevBal) } return val @@ -638,101 +638,15 @@ func (s *Statement) IsStableCoin() bool { return stables[s.AssetAddr] } -func (s *Statement) isNullTransfer(tx *Transaction) bool { - lotsOfLogs := len(tx.Receipt.Logs) > 10 - mayBeAirdrop := s.Sender.IsZero() || s.Sender == tx.To - noBalanceChange := s.EndBal.Cmp(&s.BegBal) == 0 && s.IsMaterial() - ret := (lotsOfLogs || mayBeAirdrop) && noBalanceChange - - // TODO: BOGUS PERF - // logger.Warn("Statement is not reconciled", s.AssetSymbol, "at", s.BlockNumber, s.TransactionIndex, s.LogIndex) - logger.TestLog(true, "A possible nullTransfer") - logger.TestLog(true, " nLogs: ", len(tx.Receipt.Logs)) - logger.TestLog(true, " lotsOfLogs: -->", lotsOfLogs) - - logger.TestLog(true, " Sender.IsZero: ", s.Sender, s.Sender.IsZero()) - logger.TestLog(true, " or Sender == To: ", s.Sender == tx.To) - logger.TestLog(true, " mayBeAirdrop: -->", mayBeAirdrop) - - logger.TestLog(true, " EndBal-BegBal: ", s.EndBal.Cmp(&s.BegBal)) - logger.TestLog(true, " material: ", s.IsMaterial()) - logger.TestLog(true, " noBalanceChange: -->", noBalanceChange) - - if !ret { - logger.TestLog(true, " ---> Not a nullTransfer") - } - return ret -} - -func (s *Statement) CorrectForNullTransfer(tx *Transaction) bool { - if !s.IsEth() { - if s.isNullTransfer(tx) { - logger.TestLog(true, "Correcting token transfer for a null transfer") - amt := s.TotalIn() // use totalIn since this is the amount that was faked - s.AmountOut = *new(base.Wei) - s.AmountIn = *new(base.Wei) - s.CorrectingIn = *amt - s.CorrectingOut = *amt - s.CorrectingReason = "null-transfer" - } else { - logger.TestLog(true, "Needs correction for token transfer") - } - } else { - logger.TestLog(true, "Needs correction for eth") - } - return s.Reconciled() -} - -func (s *Statement) CorrectForSomethingElse(tx *Transaction) bool { - if s.IsEth() { - if s.AssetType == "trace-eth" && s.ReconType&First != 0 && s.ReconType&Last != 0 { - if s.EndBalCalc().Cmp(&s.EndBal) != 0 { - s.EndBal = *s.EndBalCalc() - s.CorrectingReason = "per-block-balance" - } - } else { - logger.TestLog(true, "Needs correction for eth") - } - } else { - logger.TestLog(true, "Correcting token transfer for unknown income or outflow") - - s.CorrectingIn.SetUint64(0) - s.CorrectingOut.SetUint64(0) - s.CorrectingReason = "" - zero := new(base.Wei).SetInt64(0) - cmpBegBal := s.BegBalDiff().Cmp(zero) - cmpEndBal := s.EndBalDiff().Cmp(zero) - - if cmpBegBal > 0 { - s.CorrectingIn = *s.BegBalDiff() - s.CorrectingReason = "begbal" - } else if cmpBegBal < 0 { - s.CorrectingOut = *s.BegBalDiff() - s.CorrectingReason = "begbal" - } - - if cmpEndBal > 0 { - n := new(base.Wei).Add(&s.CorrectingIn, s.EndBalDiff()) - s.CorrectingIn = *n - s.CorrectingReason += "endbal" - } else if cmpEndBal < 0 { - n := new(base.Wei).Add(&s.CorrectingOut, s.EndBalDiff()) - s.CorrectingOut = *n - s.CorrectingReason += "endbal" - } - s.CorrectingReason = strings.Replace(s.CorrectingReason, "begbalendbal", "begbal-endbal", -1) - } - - return s.Reconciled() -} - -type Ledgerer interface { +type LedgerContexter interface { Prev() base.Blknum Cur() base.Blknum Next() base.Blknum + Recon() ReconType + Address() base.Address } -func (s *Statement) DebugStatement(ctx Ledgerer) { +func (s *Statement) DebugStatement(ctx LedgerContexter) { logger.TestLog(true, "===================================================") logger.TestLog(true, fmt.Sprintf("====> %s", s.AssetType)) logger.TestLog(true, "===================================================") @@ -787,11 +701,10 @@ func (s *Statement) DebugStatement(ctx Ledgerer) { logger.TestLog(true, "End of trial balance report") } -func isZero(val *base.Wei) bool { - return val.Cmp(base.NewWei(0)) == 0 -} - func reportE(msg string, val *base.Wei) { + isZero := func(val *base.Wei) bool { + return val.Cmp(base.NewWei(0)) == 0 + } logger.TestLog(!isZero(val), msg, val.ToEtherStr(18)) } diff --git a/src/apps/chifra/pkg/types/types_statement_test.go b/src/apps/chifra/pkg/types/types_statement_test.go new file mode 100644 index 0000000000..59b8f1e484 --- /dev/null +++ b/src/apps/chifra/pkg/types/types_statement_test.go @@ -0,0 +1,586 @@ +package types + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" +) + +// TestIsMaterial verifies that IsMaterial returns false when both TotalIn and TotalOut are zero, +// and returns true when either is non-zero. +func TestIsMaterial(t *testing.T) { + // Case 1: All incoming and outgoing fields are zero. + stmt1 := new(Statement) + // With a fresh statement, all amounts should be zero. + if stmt1.IsMaterial() { + t.Error("Expected IsMaterial to return false for a statement with all zero amounts") + } + + // Case 2: Set one incoming field to a nonzero value. + stmt2 := new(Statement) + stmt2.AmountIn = *base.NewWei(10) // nonzero incoming amount + if !stmt2.IsMaterial() { + t.Error("Expected IsMaterial to return true for a statement with nonzero AmountIn") + } + + // Case 3: Alternatively, set one outgoing field to a nonzero value. + stmt3 := new(Statement) + stmt3.AmountOut = *base.NewWei(20) // nonzero outgoing amount + if !stmt3.IsMaterial() { + t.Error("Expected IsMaterial to return true for a statement with nonzero AmountOut") + } +} + +// TestIsStableCoin verifies that IsStableCoin returns true when AssetAddr matches one of the stable coin addresses +// and false otherwise. +func TestIsStableCoin(t *testing.T) { + // Assuming the stable coin addresses are defined as: + // sai := base.HexToAddress("0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359") + dai := base.HexToAddress("0x6b175474e89094c44da98b954eedeac495271d0f") + // usdc := base.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48") + // usdt := base.HexToAddress("0xdac17f958d2ee523a2206206994597c13d831ec7") + + // Case 1: Create a statement with a stable coin address. + stmtStable := new(Statement) + stmtStable.AssetAddr = dai // using DAI as an example + if !stmtStable.IsStableCoin() { + t.Errorf("Expected IsStableCoin to return true for address %s", dai.Hex()) + } + + // Case 2: Create a statement with a non-stable coin address. + stmtNonStable := new(Statement) + stmtNonStable.AssetAddr = base.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + if stmtNonStable.IsStableCoin() { + t.Errorf("Expected IsStableCoin to return false for address %s", stmtNonStable.AssetAddr.Hex()) + } +} + +// TestReconciled verifies that Reconciled returns true when both BegBalDiff and EndBalDiff are zero. +func TestReconciled(t *testing.T) { + // Case 1: A fully reconciled statement. + // For reconciliation, we want: + // PrevBal == BegBal, and EndBal == EndBalCalc() + stmt1 := new(Statement) + // Set PrevBal and BegBal to the same value. + stmt1.PrevBal = *base.NewWei(1000) + stmt1.BegBal = *base.NewWei(1000) + // Set AmountIn and AmountOut so that AmountNet is 50. + stmt1.AmountIn = *base.NewWei(50) + stmt1.AmountOut = *base.NewWei(0) + // EndBalCalc() should then be BegBal + AmountNet = 1000 + 50 = 1050. + // Set EndBal to 1050. + stmt1.EndBal = *base.NewWei(1050) + if !stmt1.Reconciled() { + t.Error("Expected Reconciled to return true for a reconciled statement") + } + + // Case 2: A non-reconciled statement. + // For example, if BegBal != PrevBal or EndBal != EndBalCalc(). + stmt2 := new(Statement) + stmt2.PrevBal = *base.NewWei(900) + stmt2.BegBal = *base.NewWei(1000) // BegBalDiff = 100 + stmt2.AmountIn = *base.NewWei(50) + stmt2.AmountOut = *base.NewWei(0) + // EndBalCalc() = 1000 + 50 = 1050. + // Set EndBal to 1040 so that EndBalDiff = 10. + stmt2.EndBal = *base.NewWei(1040) + if stmt2.Reconciled() { + t.Error("Expected Reconciled to return false for a non-reconciled statement") + } +} + +// TestIsEth verifies that IsEth returns true when the AssetAddr equals base.FAKE_ETH_ADDRESS, +// and false otherwise. +func TestIsEth(t *testing.T) { + // Create a statement that should be recognized as ETH. + ethStmt := &Statement{ + AssetAddr: base.FAKE_ETH_ADDRESS, + } + if !ethStmt.IsEth() { + t.Error("Expected statement to be ETH") + } + + // Create a statement that should not be recognized as ETH. + nonEthStmt := &Statement{ + AssetAddr: base.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + } + if nonEthStmt.IsEth() { + t.Error("Expected statement not to be ETH") + } +} + +// TestTotalIn verifies that TotalIn returns the sum of all incoming value fields. +func TestTotalIn(t *testing.T) { + stmt := new(Statement) + // Set the incoming fields: + // AmountIn = 10, InternalIn = 20, SelfDestructIn = 30, + // MinerBaseRewardIn = 40, MinerNephewRewardIn = 50, MinerTxFeeIn = 60, + // MinerUncleRewardIn = 70, CorrectingIn = 80, PrefundIn = 90. + // Expected total: 10+20+30+40+50+60+70+80+90 = 450. + stmt.AmountIn = *base.NewWei(10) + stmt.InternalIn = *base.NewWei(20) + stmt.SelfDestructIn = *base.NewWei(30) + stmt.MinerBaseRewardIn = *base.NewWei(40) + stmt.MinerNephewRewardIn = *base.NewWei(50) + stmt.MinerTxFeeIn = *base.NewWei(60) + stmt.MinerUncleRewardIn = *base.NewWei(70) + stmt.CorrectingIn = *base.NewWei(80) + stmt.PrefundIn = *base.NewWei(90) + + totalIn := stmt.TotalIn() + expected := base.NewWei(450) + if totalIn.Cmp(expected) != 0 { + t.Errorf("TotalIn: expected %s, got %s", expected.Text(10), totalIn.Text(10)) + } +} + +// TestTotalOut verifies that TotalOut returns the sum of all outgoing value fields. +func TestTotalOut(t *testing.T) { + stmt := new(Statement) + // Set the outgoing fields: + // AmountOut = 15, InternalOut = 25, CorrectingOut = 35, + // SelfDestructOut = 45, GasOut = 55. + // Expected total: 15+25+35+45+55 = 175. + stmt.AmountOut = *base.NewWei(15) + stmt.InternalOut = *base.NewWei(25) + stmt.CorrectingOut = *base.NewWei(35) + stmt.SelfDestructOut = *base.NewWei(45) + stmt.GasOut = *base.NewWei(55) + + totalOut := stmt.TotalOut() + expected := base.NewWei(175) + if totalOut.Cmp(expected) != 0 { + t.Errorf("TotalOut: expected %s, got %s", expected.Text(10), totalOut.Text(10)) + } +} + +// TestAmountNet verifies that AmountNet equals TotalIn minus TotalOut. +func TestAmountNet(t *testing.T) { + stmt := new(Statement) + // Set the same values as in TotalIn and TotalOut tests: + // TotalIn: 10+20+30+40+50+60+70+80+90 = 450. + stmt.AmountIn = *base.NewWei(10) + stmt.InternalIn = *base.NewWei(20) + stmt.SelfDestructIn = *base.NewWei(30) + stmt.MinerBaseRewardIn = *base.NewWei(40) + stmt.MinerNephewRewardIn = *base.NewWei(50) + stmt.MinerTxFeeIn = *base.NewWei(60) + stmt.MinerUncleRewardIn = *base.NewWei(70) + stmt.CorrectingIn = *base.NewWei(80) + stmt.PrefundIn = *base.NewWei(90) + // TotalOut: 15+25+35+45+55 = 175. + stmt.AmountOut = *base.NewWei(15) + stmt.InternalOut = *base.NewWei(25) + stmt.CorrectingOut = *base.NewWei(35) + stmt.SelfDestructOut = *base.NewWei(45) + stmt.GasOut = *base.NewWei(55) + + // Expected AmountNet = 450 - 175 = 275. + amountNet := stmt.AmountNet() + expected := base.NewWei(275) + if amountNet.Cmp(expected) != 0 { + t.Errorf("AmountNet: expected %s, got %s", expected.Text(10), amountNet.Text(10)) + } +} + +// TestTotalOutLessGas verifies that TotalOutLessGas equals TotalOut minus GasOut. +func TestTotalOutLessGas(t *testing.T) { + stmt := new(Statement) + // Use the same outgoing values as before: TotalOut = 175. + stmt.AmountOut = *base.NewWei(15) + stmt.InternalOut = *base.NewWei(25) + stmt.CorrectingOut = *base.NewWei(35) + stmt.SelfDestructOut = *base.NewWei(45) + stmt.GasOut = *base.NewWei(55) + + totalOutLessGas := stmt.TotalOutLessGas() // should be 175 - 55 = 120. + expected := base.NewWei(120) + if totalOutLessGas.Cmp(expected) != 0 { + t.Errorf("TotalOutLessGas: expected %s, got %s", expected.Text(10), totalOutLessGas.Text(10)) + } +} + +// TestBegBalDiff tests the BegBalDiff() method. +func TestBegBalDiff(t *testing.T) { + // Case 1: BlockNumber == 0. Should return 0 regardless of BegBal and PrevBal. + stmt := Statement{ + BlockNumber: 0, + PrevBal: *base.NewWei(90), // e.g., 90 + BegBal: *base.NewWei(100), // e.g., 100 + } + diff := stmt.BegBalDiff() + expected := base.NewWei(0) + if diff.Cmp(expected) != 0 { + t.Errorf("BegBalDiff() with BlockNumber==0: expected %s, got %s", expected.Text(10), diff.Text(10)) + } + + // Case 2: BlockNumber != 0. + // Let PrevBal = 90 and BegBal = 100, then BegBalDiff should be 10. + stmt.BlockNumber = 1 + stmt.PrevBal = *base.NewWei(90) + stmt.BegBal = *base.NewWei(100) + diff = stmt.BegBalDiff() + expected = base.NewWei(10) + if diff.Cmp(expected) != 0 { + t.Errorf("BegBalDiff() with BlockNumber!=0: expected %s, got %s", expected.Text(10), diff.Text(10)) + } +} + +// TestEndBalCalc tests the EndBalCalc() method. +func TestEndBalCalc(t *testing.T) { + // Set up a Statement with: + // BegBal = 100, + // AmountIn = 50, AmountOut = 0 (and assume other TotalIn/TotalOut contributions are zero), + // so that AmountNet() = 50 and EndBalCalc() = BegBal + 50 = 150. + stmt := Statement{ + BegBal: *base.NewWei(100), + AmountIn: *base.NewWei(50), + AmountOut: *base.NewWei(0), + InternalIn: *base.NewWei(0), + InternalOut: *base.NewWei(0), + // All other fields that TotalIn and TotalOut consider are assumed to be zero. + } + endCalc := stmt.EndBalCalc() + expected := base.NewWei(150) + if endCalc.Cmp(expected) != 0 { + t.Errorf("EndBalCalc(): expected %s, got %s", expected.Text(10), endCalc.Text(10)) + } +} + +// TestEndBalDiff tests the EndBalDiff() method. +func TestEndBalDiff(t *testing.T) { + // Use the same setup as in TestEndBalCalc, and then set EndBal to a value below EndBalCalc. + // For example, let: + // BegBal = 100, + // AmountIn = 50, AmountOut = 0 --> EndBalCalc() = 150, + // EndBal = 140, + // so that EndBalDiff() = 150 - 140 = 10. + stmt := Statement{ + BegBal: *base.NewWei(100), + AmountIn: *base.NewWei(50), + AmountOut: *base.NewWei(0), + EndBal: *base.NewWei(140), + InternalIn: *base.NewWei(0), + InternalOut: *base.NewWei(0), + // Other contributing fields are assumed zero. + } + diff := stmt.EndBalDiff() + expected := base.NewWei(10) + if diff.Cmp(expected) != 0 { + t.Errorf("EndBalDiff(): expected %s, got %s", expected.Text(10), diff.Text(10)) + } +} + +type dummyLedgerer struct{} + +func (d dummyLedgerer) Prev() base.Blknum { return 99 } +func (d dummyLedgerer) Cur() base.Blknum { return 100 } +func (d dummyLedgerer) Next() base.Blknum { return 101 } +func (d dummyLedgerer) Recon() ReconType { return 0 } +func (d dummyLedgerer) Address() base.Address { return base.ZeroAddr } + +func TestDebugStatement(t *testing.T) { + restore := resetLogger() + defer restore() + + ledger := dummyLedgerer{} + + // Create a test Statement with some sample values. + stmt := &Statement{ + AssetType: "eth", + BlockNumber: 100, + TransactionIndex: 1, + LogIndex: 2, + ReconType: 0, // assume 0 for simplicity + AccountedFor: base.HexToAddress("0xAAAABBBBCCCCDDDDEEEEFFFF0000111122223333"), + Sender: base.HexToAddress("0x1111222233334444555566667777888899990000"), + Recipient: base.HexToAddress("0x0000999988887777666655554444333322221111"), + AssetAddr: base.HexToAddress("0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"), + AssetSymbol: "ETH", + Decimals: 18, + TransactionHash: base.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), + Timestamp: 1610000000, + SpotPrice: 2000, // sample value + PriceSource: "TestSource", + PrevBal: *base.NewWei(1000000000000000000), + BegBal: *base.NewWei(1500000000000000000), + EndBal: *base.NewWei(1600000000000000000), + CorrectingIn: *base.NewWei(0), + CorrectingOut: *base.NewWei(0), + AmountIn: *base.NewWei(500000000000000000), + InternalIn: *base.NewWei(0), + MinerBaseRewardIn: *base.NewWei(0), + MinerNephewRewardIn: *base.NewWei(0), + MinerTxFeeIn: *base.NewWei(0), + MinerUncleRewardIn: *base.NewWei(0), + PrefundIn: *base.NewWei(0), + AmountOut: *base.NewWei(400000000000000000), + InternalOut: *base.NewWei(0), + SelfDestructIn: *base.NewWei(0), + SelfDestructOut: *base.NewWei(0), + GasOut: *base.NewWei(100000000000000000), + } + + // Call DebugStatement with the dummy ledgerer. + stmt.DebugStatement(ledger) + + // Verify that certain key substrings appear in the captured logs. + foundHeader := false + foundBlockLine := false + foundTrialBalance := false + + for _, logLine := range capturedLogs { + if strings.Contains(logLine, "====> eth") { + foundHeader = true + } + if strings.Contains(logLine, fmt.Sprintf("Current: %d", stmt.BlockNumber)) { + foundBlockLine = true + } + if strings.Contains(logLine, "Trial balance:") { + foundTrialBalance = true + } + } + + if !foundHeader { + t.Error("DebugStatement output missing header with asset type") + } + if !foundBlockLine { + t.Error("DebugStatement output missing current block number information") + } + if !foundTrialBalance { + t.Error("DebugStatement output missing 'Trial balance:' section") + } +} + +// TestCacheLocations verifies that CacheLocations() produces the expected directory, +// padded ID, and extension. +func TestCacheLocations(t *testing.T) { + // Use a dummy address. The address string should be a valid hex string. + // For example, use base.HexToAddress to convert a hex literal. + addr := base.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + // Define BlockNumber and TransactionIndex. + var blockNumber base.Blknum = 123 + var txIndex base.Txnum = 45 + + // Create a StatementGroup. + sg := StatementGroup{ + BlockNumber: blockNumber, + TransactionIndex: txIndex, + Address: addr, + } + + // Call CacheLocations. + directory, paddedId, ext := sg.CacheLocations() + + // Compute the expected paddedId. + // Format: "%s-%09d-%05d" where the first value is the address without the "0x" prefix. + expectedPaddedId := fmt.Sprintf("%s-%09d-%05d", addr.Hex()[2:], blockNumber, txIndex) + if paddedId != expectedPaddedId { + t.Errorf("Expected paddedId %q; got %q", expectedPaddedId, paddedId) + } + + // Compute the expected directory. + // parts are the first 6 characters of paddedId split as: [0:2], [2:4], [4:6] + parts := []string{paddedId[:2], paddedId[2:4], paddedId[4:6]} + expectedDirectory := filepath.Join("statements", filepath.Join(parts...)) + if directory != expectedDirectory { + t.Errorf("Expected directory %q; got %q", expectedDirectory, directory) + } + + // The expected extension is "bin" + if ext != "bin" { + t.Errorf("Expected extension %q; got %q", "bin", ext) + } +} + +// TestStatementGroupCache tests that a StatementGroup can be marshaled to a cache +// (i.e. written to a buffer) and then unmarshaled back with the same content. +func TestStatementGroupCache(t *testing.T) { + // Create a dummy Statement. + // For simplicity, only populate a few fields. + stmt := Statement{ + AccountedFor: base.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"), + AmountIn: *base.NewWei(1000000000000000000), // 1 Ether in Wei + AmountOut: *base.NewWei(500000000000000000), // 0.5 Ether in Wei + AssetAddr: base.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + AssetSymbol: "TKN", + BegBal: *base.NewWei(2000000000000000000), + BlockNumber: 100, + LogIndex: 1, + TransactionHash: base.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111"), + TransactionIndex: 2, + // (populate additional fields as necessary) + } + + // Create a StatementGroup with one Statement. + origGroup := StatementGroup{ + BlockNumber: 100, + TransactionIndex: 2, + Address: base.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"), + Statements: []Statement{stmt}, + } + + // Marshal the StatementGroup to an in-memory buffer. + var buf bytes.Buffer + if err := origGroup.MarshalCache(&buf); err != nil { + t.Fatalf("MarshalCache failed: %v", err) + } + + // Now create a new StatementGroup and unmarshal from the same buffer. + var newGroup StatementGroup + // Pass a dummy version number (e.g., 1) + if err := newGroup.UnmarshalCache(1, &buf); err != nil { + t.Fatalf("UnmarshalCache failed: %v", err) + } + + // Compare the original and new Statement slices. + // Here we use reflect.DeepEqual; adjust if your types require a different comparison. + if !reflect.DeepEqual(origGroup.Statements, newGroup.Statements) { + t.Errorf("Unmarshaled statements do not match original.\nOriginal: %+v\nNew: %+v", + origGroup.Statements, newGroup.Statements) + } +} + +// capturedLogs will store the logs produced by our fakeTestLog. +var capturedLogs []string + +// fakeTestLog is our test stub that captures log messages. +// It mimics logger.TestLog by appending a formatted string to capturedLogs. +func fakeTestLog(notDefault bool, args ...interface{}) { + // We'll capture logs only if notDefault is true (as per original logic). + if notDefault { + // Concatenate all arguments with a space. + logMsg := fmt.Sprint(args...) + capturedLogs = append(capturedLogs, logMsg) + } +} + +// resetLogger overrides logger.TestLog with our fakeTestLog, +// clears capturedLogs, and returns a function to restore the original. +func resetLogger() func() { + // Ensure that the test mode is enabled for our tests. + os.Setenv("TEST_MODE", "true") + // Save the original TestLog. + orig := logger.TestLog + // Replace TestLog with our fake. + logger.TestLog = fakeTestLog + // Clear any previously captured logs. + capturedLogs = nil + // Return a restore function. + return func() { + logger.TestLog = orig + } +} + +// TestReportE tests reportE: it should log the message along with the Wei value formatted as Ether. +func TestReportE(t *testing.T) { + restore := resetLogger() + defer restore() + + // Create a Wei value corresponding to 1 Ether. + oneEther := base.NewWei(1000000000000000000) // 1e18 wei + reportE("ReportE:", oneEther) + + if len(capturedLogs) != 1 { + t.Fatalf("Expected 1 log entry; got %d", len(capturedLogs)) + } + + // Assuming ToEtherStr(18) for 1e18 returns "1.0" + expected := "ReportE:1" + got := capturedLogs[0] + if got != expected { + t.Errorf("Expected log %q; got %q", expected, capturedLogs[0]) + } +} + +// TestReport2 tests report2 with various combinations of v1 and v2. +func TestReport2(t *testing.T) { + restore := resetLogger() + defer restore() + + // Create two Wei values. + val1 := base.NewWei(500000000000000000) // 0.5 Ether + val2 := base.NewWei(2000000000000000000) // 2 Ether + + // Case 1: Both v1 and v2 non-nil. + report2("TestReport2:", val1, val2) + if len(capturedLogs) != 1 { + t.Fatalf("Case 1: Expected 1 log entry; got %d", len(capturedLogs)) + } + // Expected string: "TestReport2:" followed by "0.5 (2.0)" (assuming ToEtherStr conversion) + expected1 := "TestReport2:0.5 (2)" + got1 := capturedLogs[0] + if got1 != expected1 { + t.Errorf("Case 1: Expected %q; got %q", expected1, capturedLogs[0]) + } + + // Reset for next case. + capturedLogs = nil + + // Case 2: v1 is nil, v2 non-nil. + report2("TestReport2NilV1:", nil, val2) + if len(capturedLogs) != 1 { + t.Fatalf("Case 2: Expected 1 log entry; got %d", len(capturedLogs)) + } + // When v1 is nil, the string will be empty plus v2 formatted in parentheses. + expected2 := "TestReport2NilV1: (2)" + got2 := capturedLogs[0] + if got2 != expected2 { + t.Errorf("Case 2: Expected %q; got %q", expected2, capturedLogs[0]) + } + + // Reset for next case. + capturedLogs = nil + + // Case 3: Both v1 and v2 are nil. + report2("TestReport2BothNil:", nil, nil) + if len(capturedLogs) != 1 { + t.Fatalf("Case 3: Expected 1 log entry; got %d", len(capturedLogs)) + } + // With both nil, the output should simply be the message. + expected3 := "TestReport2BothNil:" + got3 := capturedLogs[0] + if got3 != expected3 { + t.Errorf("Case 3: Expected %q; got %q", expected3, capturedLogs[0]) + } +} + +// TestReportL tests reportL, which is a thin wrapper around report2 with nil values. +func TestReportL(t *testing.T) { + restore := resetLogger() + defer restore() + + reportL("--------------------") + if len(capturedLogs) != 1 { + t.Fatalf("Expected 1 log entry; got %d", len(capturedLogs)) + } + expected := "--------------------" + got := capturedLogs[0] + if got != expected { + t.Errorf("Expected log %q; got %q", expected, capturedLogs[0]) + } +} + +// TestReport1 tests report1 which calls report2 with a non-nil value for v1. +func TestReport1(t *testing.T) { + restore := resetLogger() + defer restore() + + val := base.NewWei(750000000000000000) // 0.75 Ether + report1("TestReport1:", val) + if len(capturedLogs) != 1 { + t.Fatalf("Expected 1 log entry; got %d", len(capturedLogs)) + } + expected := "TestReport1:0.75" + got := capturedLogs[0] + if got != expected { + t.Errorf("Expected log %q; got %q", expected, capturedLogs[0]) + } +} diff --git a/src/dev_tools/goMaker/templates/cmd-line-options.csv b/src/dev_tools/goMaker/templates/cmd-line-options.csv index 0e922400ed..105bc1b837 100644 --- a/src/dev_tools/goMaker/templates/cmd-line-options.csv +++ b/src/dev_tools/goMaker/templates/cmd-line-options.csv @@ -69,17 +69,8 @@ num,folder,group,route,tool,longName,hotKey,def_val,attributes,handler,option_ty 14070,apps,Accounts,monitors,acctExport,list,l,,visible|docs,3,switch,,monitor,,,,list monitors in the cache (--verbose for more detail) 14075,apps,Accounts,monitors,acctExport,count,c,,visible|docs,1,switch,,count,,,,show the number of active monitors (included deleted but not removed monitors) 14065,apps,Accounts,monitors,acctExport,staged,S,,visible|docs,,switch,,,,,,for --clean, --list, and --count options only, include staged monitors -14080,apps,Accounts,monitors,acctExport,watch,w,,visible|docs|notApi,4,switch,,,,,,continually scan for new blocks and extract data as per the command file -14090,apps,Accounts,monitors,acctExport,watchlist,a,,visible|docs|notApi,,flag,,,,,,available with --watch option only, a file containing the addresses to watch -14100,apps,Accounts,monitors,acctExport,commands,d,,visible|docs|notApi,,flag,,,,,,available with --watch option only, the file containing the list of commands to apply to each watched address -14110,apps,Accounts,monitors,acctExport,batch_size,b,8,visible|docs|notApi,,flag,,,,,,available with --watch option only, the number of monitors to process in each batch -14120,apps,Accounts,monitors,acctExport,run_count,u,,visible|docs|notApi,,flag,,,,,,available with --watch option only, run the monitor this many times, then quit -14130,apps,Accounts,monitors,acctExport,sleep,s,14,visible|docs|notApi,,flag,,,,,,available with --watch option only, the number of seconds to sleep between runs 14140,apps,Accounts,monitors,acctExport,n1,,,,,note,,,,,,An `address` must be either an ENS name or start with '0x' and be forty-two characters long. 14150,apps,Accounts,monitors,acctExport,n2,,,,,note,,,,,,If no address is presented to the --clean command, all existing monitors will be cleaned. -14160,apps,Accounts,monitors,acctExport,n3,,,,,note,,,,,,The --watch option requires two additional parameters to be specified: `--watchlist` and `--commands`. -14170,apps,Accounts,monitors,acctExport,n4,,,,,note,,,,,,Addresses provided on the command line are ignored in `--watch` mode. -14180,apps,Accounts,monitors,acctExport,n5,,,,,note,,,,,,Providing the value `existing` to the `--watchlist` monitors all existing monitor files (see --list). # 15000,tools,Accounts,names,ethNames,,,,visible|docs,,command,,,Manage names,[flags] [term...],default|,Query addresses or names of well-known accounts. 15020,tools,Accounts,names,ethNames,terms,,,required|visible|docs,4,positional,list,name,,,,a space separated list of one or more search terms diff --git a/src/dev_tools/sdkFuzzer/monitors.go b/src/dev_tools/sdkFuzzer/monitors.go index 9d339c1e94..5cd3eff423 100644 --- a/src/dev_tools/sdkFuzzer/monitors.go +++ b/src/dev_tools/sdkFuzzer/monitors.go @@ -31,19 +31,12 @@ func DoMonitors() { undelete := []bool{false, true} remove := []bool{false, true} staged := []bool{false, true} - watch := []bool{false, true} - // watchlist is not fuzzed - // commands is not fuzzed - // batchSize is not fuzzed - // runCount is not fuzzed - // sleep is not fuzzed // Fuzz Loop // EXISTING_CODE _ = staged _ = delete _ = undelete _ = remove - _ = watch _ = globs opts = sdk.MonitorsOptions{ Addrs: []string{fuzzAddresses[0]}, diff --git a/src/dev_tools/testRunner/testCases/chunks.csv b/src/dev_tools/testRunner/testCases/chunks.csv index 0df5e4bdd7..30325b63f8 100644 --- a/src/dev_tools/testRunner/testCases/chunks.csv +++ b/src/dev_tools/testRunner/testCases/chunks.csv @@ -58,7 +58,7 @@ on ,both ,fast ,chunks ,apps ,chunkMan ,belongs2 ,y ,mod on ,both ,fast ,chunks ,apps ,chunkMan ,pin_chunks_bad ,y ,mode = addresses & pin on ,both ,fast ,chunks ,apps ,chunkMan ,pin_data_bad ,y ,mode = manifest & publish -on ,both ,fast ,chunks ,apps ,chunkMan ,pin_rewrite_not_deep ,y ,mode = manifest & pin & rewrite +local ,both ,fast ,chunks ,apps ,chunkMan ,pin_rewrite_not_deep ,y ,mode = manifest & pin & rewrite on ,both ,fast ,chunks ,apps ,chunkMan ,pin_rewrite_not_pin ,y ,mode = manifest & rewrite on ,both ,fast ,chunks ,apps ,chunkMan ,check_bad ,y ,mode = addresses & check diff --git a/tests/gold/apps/acctExport/acctExport_caps_allowed_m.txt b/tests/gold/apps/acctExport/acctExport_caps_allowed_m.txt index 6f40715d29..c34d6f05c1 100644 --- a/tests/gold/apps/acctExport/acctExport_caps_allowed_m.txt +++ b/tests/gold/apps/acctExport/acctExport_caps_allowed_m.txt @@ -9,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_caps_disallowed_1_m.txt b/tests/gold/apps/acctExport/acctExport_caps_disallowed_1_m.txt index 49b21f29de..7e9eb70be7 100644 --- a/tests/gold/apps/acctExport/acctExport_caps_disallowed_1_m.txt +++ b/tests/gold/apps/acctExport/acctExport_caps_disallowed_1_m.txt @@ -9,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_caps_disallowed_3_m.txt b/tests/gold/apps/acctExport/acctExport_caps_disallowed_3_m.txt index 84f4e1d87a..652616f7cd 100644 --- a/tests/gold/apps/acctExport/acctExport_caps_disallowed_3_m.txt +++ b/tests/gold/apps/acctExport/acctExport_caps_disallowed_3_m.txt @@ -9,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_caps_disallowed_4_m.txt b/tests/gold/apps/acctExport/acctExport_caps_disallowed_4_m.txt index 5d8e9b0b76..f5891846d6 100644 --- a/tests/gold/apps/acctExport/acctExport_caps_disallowed_4_m.txt +++ b/tests/gold/apps/acctExport/acctExport_caps_disallowed_4_m.txt @@ -9,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_delete_1_fail.txt b/tests/gold/apps/acctExport/acctExport_monitors_delete_1_fail.txt index 357d8e90c0..35f132acd5 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_delete_1_fail.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_delete_1_fail.txt @@ -11,27 +11,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_err_batch_size2.txt b/tests/gold/apps/acctExport/acctExport_monitors_err_batch_size2.txt index 65c3d2e0b4..bf9ed4ba35 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_err_batch_size2.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_err_batch_size2.txt @@ -1,9 +1,7 @@ chifra monitors 0xf503017d7baf7fbc0fff7492b751025c6a78179b --fmt json --batch_size 1 -TEST[DATE|TIME] Addrs: [0xf503017d7baf7fbc0fff7492b751025c6a78179b] -TEST[DATE|TIME] BatchSize: 1 -TEST[DATE|TIME] Caps: cache,decache -TEST[DATE|TIME] Format: json -Error: The --batch_size option is not available without --watch. +Error: + unknown flag: --batch_size + Usage: chifra monitors [flags]
[address...] @@ -11,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_err_not_both1.txt b/tests/gold/apps/acctExport/acctExport_monitors_err_not_both1.txt index 73a56481ae..d48fb2c262 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_err_not_both1.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_err_not_both1.txt @@ -12,27 +12,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_err_not_both2.txt b/tests/gold/apps/acctExport/acctExport_monitors_err_not_both2.txt index c3c31d8e29..5af0a15141 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_err_not_both2.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_err_not_both2.txt @@ -12,27 +12,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_err_run_once.txt b/tests/gold/apps/acctExport/acctExport_monitors_err_run_once.txt index f8aefa3aea..21ffe7f71f 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_err_run_once.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_err_run_once.txt @@ -1,9 +1,7 @@ chifra monitors 0xf503017d7baf7fbc0fff7492b751025c6a78179b --fmt json --run_count 1 -TEST[DATE|TIME] Addrs: [0xf503017d7baf7fbc0fff7492b751025c6a78179b] -TEST[DATE|TIME] RunCount: 1 -TEST[DATE|TIME] Caps: cache,decache -TEST[DATE|TIME] Format: json -Error: The --run_count option is not available without --watch. +Error: + unknown flag: --run_count + Usage: chifra monitors [flags]
[address...] @@ -11,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_err_sleep.txt b/tests/gold/apps/acctExport/acctExport_monitors_err_sleep.txt index e027b0fa93..cf66ffaec6 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_err_sleep.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_err_sleep.txt @@ -1,6 +1,6 @@ chifra monitors 0xf503017d7baf7fbc0fff7492b751025c6a78179b --fmt json --sleep Error: - flag needs an argument: --sleep + unknown flag: --sleep Usage: chifra monitors [flags]
[address...] @@ -9,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_err_watch.txt b/tests/gold/apps/acctExport/acctExport_monitors_err_watch.txt index 4346571978..7cbf4c203a 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_err_watch.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_err_watch.txt @@ -1,9 +1,7 @@ chifra monitors --watch --batch_size 0 --fmt json -TEST[DATE|TIME] Watch: true -TEST[DATE|TIME] BatchSize: 0 -TEST[DATE|TIME] Caps: cache,decache -TEST[DATE|TIME] Format: json -Error: The --watch option requires a --commands file. +Error: + unknown flag: --watch + Usage: chifra monitors [flags]
[address...] @@ -11,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_help.txt b/tests/gold/apps/acctExport/acctExport_monitors_help.txt index de146c4589..cdfc89fdd0 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_help.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_help.txt @@ -9,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_list1.txt b/tests/gold/apps/acctExport/acctExport_monitors_list1.txt index 51a43a85cd..5f182be47d 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_list1.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_list1.txt @@ -11,27 +11,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_list1_v.txt b/tests/gold/apps/acctExport/acctExport_monitors_list1_v.txt index d034d079d3..73429a0dbb 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_list1_v.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_list1_v.txt @@ -12,27 +12,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_list2.txt b/tests/gold/apps/acctExport/acctExport_monitors_list2.txt index 7661a296a6..4e89523982 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_list2.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_list2.txt @@ -11,27 +11,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_list2_v.txt b/tests/gold/apps/acctExport/acctExport_monitors_list2_v.txt index ee5049f3e7..e9cca34d7f 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_list2_v.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_list2_v.txt @@ -12,27 +12,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_list_not_in.txt b/tests/gold/apps/acctExport/acctExport_monitors_list_not_in.txt index ad8780a9bd..1edfde2da1 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_list_not_in.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_list_not_in.txt @@ -11,27 +11,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_long_help.txt b/tests/gold/apps/acctExport/acctExport_monitors_long_help.txt index ccc1d1fc10..4a0e89dbbd 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_long_help.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_long_help.txt @@ -9,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_no_params.txt b/tests/gold/apps/acctExport/acctExport_monitors_no_params.txt index a401eaa018..d49fc32abd 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_no_params.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_no_params.txt @@ -9,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_remove_fail.txt b/tests/gold/apps/acctExport/acctExport_monitors_remove_fail.txt index 26ce64749a..ac6aee684b 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_remove_fail.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_remove_fail.txt @@ -11,27 +11,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_undelete2.txt b/tests/gold/apps/acctExport/acctExport_monitors_undelete2.txt index cd3f9d0177..93ce103784 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_undelete2.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_undelete2.txt @@ -11,27 +11,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_monitors_undelete_fail.txt b/tests/gold/apps/acctExport/acctExport_monitors_undelete_fail.txt index fc68638d8d..33b2eca1e7 100644 --- a/tests/gold/apps/acctExport/acctExport_monitors_undelete_fail.txt +++ b/tests/gold/apps/acctExport/acctExport_monitors_undelete_fail.txt @@ -11,27 +11,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list). diff --git a/tests/gold/apps/acctExport/acctExport_statement_2.txt b/tests/gold/apps/acctExport/acctExport_statement_2.txt index 641d5a5e64..6338915d0a 100644 --- a/tests/gold/apps/acctExport/acctExport_statement_2.txt +++ b/tests/gold/apps/acctExport/acctExport_statement_2.txt @@ -14,7 +14,6 @@ INFO[DATE|TIME] 000046685-00000: 0 46685 46709 diff-diff INFO[DATE|TIME] 000046709-00000: 46685 46709 46709 diff-last TEST[DATE|TIME] Start of trial balance report TEST[DATE|TIME] Needs correction for eth -TEST[DATE|TIME] Needs correction for eth TEST[DATE|TIME] Block 0 is prior to deployment (3684349) of Maker. No fallback pricing method TEST[DATE|TIME] =================================================== TEST[DATE|TIME] ====> eth @@ -51,7 +50,6 @@ TEST[DATE|TIME] End of trial balance report TEST[DATE|TIME] Trial balance failed for 0x0000000000000000000000000000000000000000000000000000000000000000 need to decend into traces TEST[DATE|TIME] Start of trial balance report TEST[DATE|TIME] Needs correction for eth -TEST[DATE|TIME] Needs correction for eth TEST[DATE|TIME] =================================================== TEST[DATE|TIME] ====> trace-eth TEST[DATE|TIME] =================================================== @@ -83,6 +81,10 @@ TEST[DATE|TIME] reconciled: false TEST[DATE|TIME] ^^ we need to fix this ^^ TEST[DATE|TIME] --------------------------------------------------- TEST[DATE|TIME] End of trial balance report +INFO[DATE|TIME] ------------------------------------------------------------ +INFO[DATE|TIME] Contexts (2) +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff TEST[DATE|TIME] Start of trial balance report TEST[DATE|TIME] Block 46685 is prior to deployment (3684349) of Maker. No fallback pricing method TEST[DATE|TIME] =================================================== @@ -117,6 +119,12 @@ TEST[DATE|TIME] material: true TEST[DATE|TIME] reconciled: true TEST[DATE|TIME] --------------------------------------------------- TEST[DATE|TIME] End of trial balance report +INFO[DATE|TIME] ------------------------------------------------------------ +INFO[DATE|TIME] Contexts (4) +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000046685-00000: 0 46685 46709 diff-diff +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046685-00000: 0 46685 46709 diff-diff TEST[DATE|TIME] Start of trial balance report TEST[DATE|TIME] Block 46709 is prior to deployment (3684349) of Maker. No fallback pricing method TEST[DATE|TIME] =================================================== @@ -125,7 +133,7 @@ TEST[DATE|TIME] =================================================== TEST[DATE|TIME] Previous: 46685 TEST[DATE|TIME] Current: 46709 TEST[DATE|TIME] Next: 46709 -TEST[DATE|TIME] reconciliationType: diff-last +TEST[DATE|TIME] reconciliationType: diff-same TEST[DATE|TIME] assetType: eth TEST[DATE|TIME] accountedFor: 0x08166f02313feae18bb044e7877c808b55b5bf58 TEST[DATE|TIME] sender: 0x08166f02313feae18bb044e7877c808b55b5bf58 ==> 0xad00b7a324f31351d397408c8c3952ea198317eb @@ -151,6 +159,14 @@ TEST[DATE|TIME] material: true TEST[DATE|TIME] reconciled: true TEST[DATE|TIME] --------------------------------------------------- TEST[DATE|TIME] End of trial balance report +INFO[DATE|TIME] ------------------------------------------------------------ +INFO[DATE|TIME] Contexts (6) +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000046685-00000: 0 46685 46709 diff-diff +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000046709-00000: 46685 46709 46709 diff-same +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046685-00000: 0 46685 46709 diff-diff +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046709-00000: 46685 46709 46709 diff-same ---- Results in ./statement_2_out.file { @@ -183,6 +199,7 @@ Results in ./statement_2_out.file "minerTxFeeIn": "0", "minerUncleRewardIn": "0", "prefundIn": "0", + "prevBal": "0", "priceSource": "eth-not-priced-pre-maker", "recipient": "0x08166f02313feae18bb044e7877c808b55b5bf58", "reconciled": false, @@ -274,7 +291,7 @@ Results in ./statement_2_out.file "priceSource": "eth-not-priced-pre-maker", "recipient": "0xad00b7a324f31351d397408c8c3952ea198317eb", "reconciled": true, - "reconciliationType": "diff-last", + "reconciliationType": "diff-same", "selfDestructIn": "0", "selfDestructOut": "0", "sender": "0x08166f02313feae18bb044e7877c808b55b5bf58", diff --git a/tests/gold/apps/acctExport/acctExport_statement_2_ether.txt b/tests/gold/apps/acctExport/acctExport_statement_2_ether.txt index f490b78bdb..e5d125bfa6 100644 --- a/tests/gold/apps/acctExport/acctExport_statement_2_ether.txt +++ b/tests/gold/apps/acctExport/acctExport_statement_2_ether.txt @@ -15,7 +15,6 @@ INFO[DATE|TIME] 000046685-00000: 0 46685 46709 diff-diff INFO[DATE|TIME] 000046709-00000: 46685 46709 46709 diff-last TEST[DATE|TIME] Start of trial balance report TEST[DATE|TIME] Needs correction for eth -TEST[DATE|TIME] Needs correction for eth TEST[DATE|TIME] Block 0 is prior to deployment (3684349) of Maker. No fallback pricing method TEST[DATE|TIME] =================================================== TEST[DATE|TIME] ====> eth @@ -52,7 +51,6 @@ TEST[DATE|TIME] End of trial balance report TEST[DATE|TIME] Trial balance failed for 0x0000000000000000000000000000000000000000000000000000000000000000 need to decend into traces TEST[DATE|TIME] Start of trial balance report TEST[DATE|TIME] Needs correction for eth -TEST[DATE|TIME] Needs correction for eth TEST[DATE|TIME] =================================================== TEST[DATE|TIME] ====> trace-eth TEST[DATE|TIME] =================================================== @@ -84,6 +82,10 @@ TEST[DATE|TIME] reconciled: false TEST[DATE|TIME] ^^ we need to fix this ^^ TEST[DATE|TIME] --------------------------------------------------- TEST[DATE|TIME] End of trial balance report +INFO[DATE|TIME] ------------------------------------------------------------ +INFO[DATE|TIME] Contexts (2) +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff TEST[DATE|TIME] Start of trial balance report TEST[DATE|TIME] Block 46685 is prior to deployment (3684349) of Maker. No fallback pricing method TEST[DATE|TIME] =================================================== @@ -118,6 +120,12 @@ TEST[DATE|TIME] material: true TEST[DATE|TIME] reconciled: true TEST[DATE|TIME] --------------------------------------------------- TEST[DATE|TIME] End of trial balance report +INFO[DATE|TIME] ------------------------------------------------------------ +INFO[DATE|TIME] Contexts (4) +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000046685-00000: 0 46685 46709 diff-diff +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046685-00000: 0 46685 46709 diff-diff TEST[DATE|TIME] Start of trial balance report TEST[DATE|TIME] Block 46709 is prior to deployment (3684349) of Maker. No fallback pricing method TEST[DATE|TIME] =================================================== @@ -126,7 +134,7 @@ TEST[DATE|TIME] =================================================== TEST[DATE|TIME] Previous: 46685 TEST[DATE|TIME] Current: 46709 TEST[DATE|TIME] Next: 46709 -TEST[DATE|TIME] reconciliationType: diff-last +TEST[DATE|TIME] reconciliationType: diff-same TEST[DATE|TIME] assetType: eth TEST[DATE|TIME] accountedFor: 0x08166f02313feae18bb044e7877c808b55b5bf58 TEST[DATE|TIME] sender: 0x08166f02313feae18bb044e7877c808b55b5bf58 ==> 0xad00b7a324f31351d397408c8c3952ea198317eb @@ -152,6 +160,14 @@ TEST[DATE|TIME] material: true TEST[DATE|TIME] reconciled: true TEST[DATE|TIME] --------------------------------------------------- TEST[DATE|TIME] End of trial balance report +INFO[DATE|TIME] ------------------------------------------------------------ +INFO[DATE|TIME] Contexts (6) +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000046685-00000: 0 46685 46709 diff-diff +INFO[DATE|TIME] 0x08166f02313feae18bb044e7877c808b55b5bf58-000046709-00000: 46685 46709 46709 diff-same +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046685-00000: 0 46685 46709 diff-diff +INFO[DATE|TIME] 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046709-00000: 46685 46709 46709 diff-same ---- Results in ./statement_2_ether_out.file { @@ -202,6 +218,8 @@ Results in ./statement_2_ether_out.file "minerUncleRewardInEth": "0", "prefundIn": "0", "prefundInEth": "0", + "prevBal": "0", + "prevBalEth": "0", "priceSource": "eth-not-priced-pre-maker", "recipient": "0x08166f02313feae18bb044e7877c808b55b5bf58", "reconciled": false, @@ -341,7 +359,7 @@ Results in ./statement_2_ether_out.file "priceSource": "eth-not-priced-pre-maker", "recipient": "0xad00b7a324f31351d397408c8c3952ea198317eb", "reconciled": true, - "reconciliationType": "diff-last", + "reconciliationType": "diff-same", "selfDestructIn": "0", "selfDestructInEth": "0", "selfDestructOut": "0", diff --git a/tests/gold/apps/acctExport/api_tests/acctExport_statement_2.txt b/tests/gold/apps/acctExport/api_tests/acctExport_statement_2.txt index ac70cd73be..16e5526cee 100644 --- a/tests/gold/apps/acctExport/api_tests/acctExport_statement_2.txt +++ b/tests/gold/apps/acctExport/api_tests/acctExport_statement_2.txt @@ -29,6 +29,7 @@ export?addrs=0x08166f02313feae18bb044e7877c808b55b5bf58&accounting&statements&la "minerTxFeeIn": "0", "minerUncleRewardIn": "0", "prefundIn": "0", + "prevBal": "0", "priceSource": "eth-not-priced-pre-maker", "recipient": "0x08166f02313feae18bb044e7877c808b55b5bf58", "reconciled": false, @@ -120,7 +121,7 @@ export?addrs=0x08166f02313feae18bb044e7877c808b55b5bf58&accounting&statements&la "priceSource": "eth-not-priced-pre-maker", "recipient": "0xad00b7a324f31351d397408c8c3952ea198317eb", "reconciled": true, - "reconciliationType": "diff-last", + "reconciliationType": "diff-same", "selfDestructIn": "0", "selfDestructOut": "0", "sender": "0x08166f02313feae18bb044e7877c808b55b5bf58", diff --git a/tests/gold/apps/acctExport/api_tests/acctExport_statement_2_ether.txt b/tests/gold/apps/acctExport/api_tests/acctExport_statement_2_ether.txt index 50dd1834e0..e242a53291 100644 --- a/tests/gold/apps/acctExport/api_tests/acctExport_statement_2_ether.txt +++ b/tests/gold/apps/acctExport/api_tests/acctExport_statement_2_ether.txt @@ -47,6 +47,8 @@ export?addrs=0x08166f02313feae18bb044e7877c808b55b5bf58&accounting&statements&la "minerUncleRewardInEth": "0", "prefundIn": "0", "prefundInEth": "0", + "prevBal": "0", + "prevBalEth": "0", "priceSource": "eth-not-priced-pre-maker", "recipient": "0x08166f02313feae18bb044e7877c808b55b5bf58", "reconciled": false, @@ -186,7 +188,7 @@ export?addrs=0x08166f02313feae18bb044e7877c808b55b5bf58&accounting&statements&la "priceSource": "eth-not-priced-pre-maker", "recipient": "0xad00b7a324f31351d397408c8c3952ea198317eb", "reconciled": true, - "reconciliationType": "diff-last", + "reconciliationType": "diff-same", "selfDestructIn": "0", "selfDestructInEth": "0", "selfDestructOut": "0", diff --git a/tests/gold/apps/acctExport/sdk_tests/acctExport_statement_2.txt b/tests/gold/apps/acctExport/sdk_tests/acctExport_statement_2.txt index 46fce6332b..43823c6aac 100644 --- a/tests/gold/apps/acctExport/sdk_tests/acctExport_statement_2.txt +++ b/tests/gold/apps/acctExport/sdk_tests/acctExport_statement_2.txt @@ -14,7 +14,6 @@ Contexts (3) 000046709-00000: 46685 46709 46709 diff-last Start of trial balance report Needs correction for eth -Needs correction for eth Block 0 is prior to deployment (3684349) of Maker. No fallback pricing method =================================================== ====> eth @@ -51,7 +50,6 @@ End of trial balance report Trial balance failed for 0x0000000000000000000000000000000000000000000000000000000000000000 need to decend into traces Start of trial balance report Needs correction for eth -Needs correction for eth =================================================== ====> trace-eth =================================================== @@ -83,6 +81,10 @@ Trial balance: ^^ we need to fix this ^^ --------------------------------------------------- End of trial balance report +------------------------------------------------------------ +Contexts (2) +0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff Start of trial balance report Block 46685 is prior to deployment (3684349) of Maker. No fallback pricing method =================================================== @@ -117,6 +119,12 @@ Trial balance: reconciled: true --------------------------------------------------- End of trial balance report +------------------------------------------------------------ +Contexts (4) +0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +0x08166f02313feae18bb044e7877c808b55b5bf58-000046685-00000: 0 46685 46709 diff-diff +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046685-00000: 0 46685 46709 diff-diff Start of trial balance report Block 46709 is prior to deployment (3684349) of Maker. No fallback pricing method =================================================== @@ -125,7 +133,7 @@ Block 46709 is prior to deployment (3684349) of Maker. No fallback pricing metho Previous: 46685 Current: 46709 Next: 46709 -reconciliationType: diff-last +reconciliationType: diff-same assetType: eth accountedFor: 0x08166f02313feae18bb044e7877c808b55b5bf58 sender: 0x08166f02313feae18bb044e7877c808b55b5bf58 ==> 0xad00b7a324f31351d397408c8c3952ea198317eb @@ -151,6 +159,14 @@ Trial balance: reconciled: true --------------------------------------------------- End of trial balance report +------------------------------------------------------------ +Contexts (6) +0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +0x08166f02313feae18bb044e7877c808b55b5bf58-000046685-00000: 0 46685 46709 diff-diff +0x08166f02313feae18bb044e7877c808b55b5bf58-000046709-00000: 46685 46709 46709 diff-same +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046685-00000: 0 46685 46709 diff-diff +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046709-00000: 46685 46709 46709 diff-same { "data": [ { @@ -181,6 +197,7 @@ End of trial balance report "minerTxFeeIn": "0", "minerUncleRewardIn": "0", "prefundIn": "0", + "prevBal": "0", "priceSource": "eth-not-priced-pre-maker", "recipient": "0x08166f02313feae18bb044e7877c808b55b5bf58", "reconciled": false, @@ -272,7 +289,7 @@ End of trial balance report "priceSource": "eth-not-priced-pre-maker", "recipient": "0xad00b7a324f31351d397408c8c3952ea198317eb", "reconciled": true, - "reconciliationType": "diff-last", + "reconciliationType": "diff-same", "selfDestructIn": "0", "selfDestructOut": "0", "sender": "0x08166f02313feae18bb044e7877c808b55b5bf58", diff --git a/tests/gold/apps/acctExport/sdk_tests/acctExport_statement_2_ether.txt b/tests/gold/apps/acctExport/sdk_tests/acctExport_statement_2_ether.txt index 6b7f650282..f38fc0e745 100644 --- a/tests/gold/apps/acctExport/sdk_tests/acctExport_statement_2_ether.txt +++ b/tests/gold/apps/acctExport/sdk_tests/acctExport_statement_2_ether.txt @@ -15,7 +15,6 @@ Contexts (3) 000046709-00000: 46685 46709 46709 diff-last Start of trial balance report Needs correction for eth -Needs correction for eth Block 0 is prior to deployment (3684349) of Maker. No fallback pricing method =================================================== ====> eth @@ -52,7 +51,6 @@ End of trial balance report Trial balance failed for 0x0000000000000000000000000000000000000000000000000000000000000000 need to decend into traces Start of trial balance report Needs correction for eth -Needs correction for eth =================================================== ====> trace-eth =================================================== @@ -84,6 +82,10 @@ Trial balance: ^^ we need to fix this ^^ --------------------------------------------------- End of trial balance report +------------------------------------------------------------ +Contexts (2) +0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff Start of trial balance report Block 46685 is prior to deployment (3684349) of Maker. No fallback pricing method =================================================== @@ -118,6 +120,12 @@ Trial balance: reconciled: true --------------------------------------------------- End of trial balance report +------------------------------------------------------------ +Contexts (4) +0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +0x08166f02313feae18bb044e7877c808b55b5bf58-000046685-00000: 0 46685 46709 diff-diff +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046685-00000: 0 46685 46709 diff-diff Start of trial balance report Block 46709 is prior to deployment (3684349) of Maker. No fallback pricing method =================================================== @@ -126,7 +134,7 @@ Block 46709 is prior to deployment (3684349) of Maker. No fallback pricing metho Previous: 46685 Current: 46709 Next: 46709 -reconciliationType: diff-last +reconciliationType: diff-same assetType: eth accountedFor: 0x08166f02313feae18bb044e7877c808b55b5bf58 sender: 0x08166f02313feae18bb044e7877c808b55b5bf58 ==> 0xad00b7a324f31351d397408c8c3952ea198317eb @@ -152,6 +160,14 @@ Trial balance: reconciled: true --------------------------------------------------- End of trial balance report +------------------------------------------------------------ +Contexts (6) +0x08166f02313feae18bb044e7877c808b55b5bf58-000000000-00270: 0 0 46685 genesis-diff +0x08166f02313feae18bb044e7877c808b55b5bf58-000046685-00000: 0 46685 46709 diff-diff +0x08166f02313feae18bb044e7877c808b55b5bf58-000046709-00000: 46685 46709 46709 diff-same +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000000000-00270: 0 0 46685 genesis-diff +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046685-00000: 0 46685 46709 diff-diff +0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-000046709-00000: 46685 46709 46709 diff-same { "data": [ { @@ -200,6 +216,8 @@ End of trial balance report "minerUncleRewardInEth": "0", "prefundIn": "0", "prefundInEth": "0", + "prevBal": "0", + "prevBalEth": "0", "priceSource": "eth-not-priced-pre-maker", "recipient": "0x08166f02313feae18bb044e7877c808b55b5bf58", "reconciled": false, @@ -339,7 +357,7 @@ End of trial balance report "priceSource": "eth-not-priced-pre-maker", "recipient": "0xad00b7a324f31351d397408c8c3952ea198317eb", "reconciled": true, - "reconciliationType": "diff-last", + "reconciliationType": "diff-same", "selfDestructIn": "0", "selfDestructInEth": "0", "selfDestructOut": "0", diff --git a/tests/gold/apps/acctExport/statement_2_ether_out.file b/tests/gold/apps/acctExport/statement_2_ether_out.file index 028d709bc0..1933a65ab3 100644 --- a/tests/gold/apps/acctExport/statement_2_ether_out.file +++ b/tests/gold/apps/acctExport/statement_2_ether_out.file @@ -46,6 +46,8 @@ "minerUncleRewardInEth": "0", "prefundIn": "0", "prefundInEth": "0", + "prevBal": "0", + "prevBalEth": "0", "priceSource": "eth-not-priced-pre-maker", "recipient": "0x08166f02313feae18bb044e7877c808b55b5bf58", "reconciled": false, @@ -185,7 +187,7 @@ "priceSource": "eth-not-priced-pre-maker", "recipient": "0xad00b7a324f31351d397408c8c3952ea198317eb", "reconciled": true, - "reconciliationType": "diff-last", + "reconciliationType": "diff-same", "selfDestructIn": "0", "selfDestructInEth": "0", "selfDestructOut": "0", diff --git a/tests/gold/apps/acctExport/statement_2_out.file b/tests/gold/apps/acctExport/statement_2_out.file index 4b3c425972..35c091be87 100644 --- a/tests/gold/apps/acctExport/statement_2_out.file +++ b/tests/gold/apps/acctExport/statement_2_out.file @@ -28,6 +28,7 @@ "minerTxFeeIn": "0", "minerUncleRewardIn": "0", "prefundIn": "0", + "prevBal": "0", "priceSource": "eth-not-priced-pre-maker", "recipient": "0x08166f02313feae18bb044e7877c808b55b5bf58", "reconciled": false, @@ -119,7 +120,7 @@ "priceSource": "eth-not-priced-pre-maker", "recipient": "0xad00b7a324f31351d397408c8c3952ea198317eb", "reconciled": true, - "reconciliationType": "diff-last", + "reconciliationType": "diff-same", "selfDestructIn": "0", "selfDestructOut": "0", "sender": "0x08166f02313feae18bb044e7877c808b55b5bf58", diff --git a/tests/gold/apps/chifra/chifra_help_rm.txt b/tests/gold/apps/chifra/chifra_help_rm.txt index 694a31dffd..daff2cb792 100644 --- a/tests/gold/apps/chifra/chifra_help_rm.txt +++ b/tests/gold/apps/chifra/chifra_help_rm.txt @@ -9,27 +9,18 @@ Arguments: addrs - one or more addresses (0x...) to process Flags: - --delete delete a monitor, but do not remove it - --undelete undelete a previously deleted monitor - --remove remove a previously deleted monitor - -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage - -l, --list list monitors in the cache (--verbose for more detail) - -c, --count show the number of active monitors (included deleted but not removed monitors) - -S, --staged for --clean, --list, and --count options only, include staged monitors - -w, --watch continually scan for new blocks and extract data as per the command file - -a, --watchlist string available with --watch option only, a file containing the addresses to watch - -d, --commands string available with --watch option only, the file containing the list of commands to apply to each watched address - -b, --batch_size uint available with --watch option only, the number of monitors to process in each batch (default 8) - -u, --run_count uint available with --watch option only, run the monitor this many times, then quit - -s, --sleep float available with --watch option only, the number of seconds to sleep between runs (default 14) - -D, --decache removes related items from the cache - -x, --fmt string export format, one of [none|json*|txt|csv] - -v, --verbose enable verbose output - -h, --help display this help screen + --delete delete a monitor, but do not remove it + --undelete undelete a previously deleted monitor + --remove remove a previously deleted monitor + -C, --clean clean (i.e. remove duplicate appearances) from monitors, optionally clear stage + -l, --list list monitors in the cache (--verbose for more detail) + -c, --count show the number of active monitors (included deleted but not removed monitors) + -S, --staged for --clean, --list, and --count options only, include staged monitors + -D, --decache removes related items from the cache + -x, --fmt string export format, one of [none|json*|txt|csv] + -v, --verbose enable verbose output + -h, --help display this help screen Notes: - An address must be either an ENS name or start with '0x' and be forty-two characters long. - If no address is presented to the --clean command, all existing monitors will be cleaned. - - The --watch option requires two additional parameters to be specified: --watchlist and --commands. - - Addresses provided on the command line are ignored in --watch mode. - - Providing the value existing to the --watchlist monitors all existing monitor files (see --list).