github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/client/usif/textui/commands.go (about)

     1  package textui
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"runtime"
    10  	"runtime/debug"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"sync/atomic"
    15  	"time"
    16  
    17  	"github.com/piotrnar/gocoin"
    18  	"github.com/piotrnar/gocoin/client/common"
    19  	"github.com/piotrnar/gocoin/client/network"
    20  	"github.com/piotrnar/gocoin/client/network/peersdb"
    21  	"github.com/piotrnar/gocoin/client/usif"
    22  	"github.com/piotrnar/gocoin/lib/btc"
    23  	"github.com/piotrnar/gocoin/lib/others/sys"
    24  )
    25  
    26  type oneUiCmd struct {
    27  	cmds    []string // command name
    28  	help    string   // a helf for this command
    29  	sync    bool     // shall be executed in the blochcina therad
    30  	handler func(pars string)
    31  }
    32  
    33  var (
    34  	uiCmds      []*oneUiCmd
    35  	show_prompt bool = true
    36  )
    37  
    38  // newUi adds a new UI commend handler.
    39  func newUi(cmds string, sync bool, hn func(string), help string) {
    40  	cs := strings.Split(cmds, " ")
    41  	if len(cs[0]) > 0 {
    42  		var c = new(oneUiCmd)
    43  		c.cmds = append(c.cmds, cs...)
    44  		c.sync = sync
    45  		c.help = help
    46  		c.handler = hn
    47  		if len(uiCmds) > 0 {
    48  			var i int
    49  			for i = 0; i < len(uiCmds); i++ {
    50  				if uiCmds[i].cmds[0] > c.cmds[0] {
    51  					break // lets have them sorted
    52  				}
    53  			}
    54  			tmp := make([]*oneUiCmd, len(uiCmds)+1)
    55  			copy(tmp[:i], uiCmds[:i])
    56  			tmp[i] = c
    57  			copy(tmp[i+1:], uiCmds[i:])
    58  			uiCmds = tmp
    59  		} else {
    60  			uiCmds = []*oneUiCmd{c}
    61  		}
    62  	} else {
    63  		panic("empty command string")
    64  	}
    65  }
    66  
    67  func readline() string {
    68  	li, _, _ := bufio.NewReader(os.Stdin).ReadLine()
    69  	return string(li)
    70  }
    71  
    72  func AskYesNo(msg string) bool {
    73  	for {
    74  		fmt.Print(msg, " (y/n) : ")
    75  		l := strings.ToLower(readline())
    76  		if l == "y" {
    77  			return true
    78  		} else if l == "n" {
    79  			return false
    80  		}
    81  	}
    82  }
    83  
    84  func ShowPrompt() {
    85  	fmt.Print("> ")
    86  }
    87  
    88  func MainThread() {
    89  	time.Sleep(1e9) // hold on for 1 sencond before showing the show_prompt
    90  	for !usif.Exit_now.Get() {
    91  		if show_prompt {
    92  			ShowPrompt()
    93  		}
    94  		show_prompt = true
    95  		li := strings.Trim(readline(), " \n\t\r")
    96  		if len(li) > 0 {
    97  			cmdpar := strings.SplitN(li, " ", 2)
    98  			cmd := cmdpar[0]
    99  			param := ""
   100  			if len(cmdpar) == 2 {
   101  				param = cmdpar[1]
   102  			}
   103  			found := false
   104  			for i := range uiCmds {
   105  				for j := range uiCmds[i].cmds {
   106  					if cmd == uiCmds[i].cmds[j] {
   107  						found = true
   108  						if uiCmds[i].sync {
   109  							usif.ExecUiReq(&usif.OneUiReq{Param: param, Handler: uiCmds[i].handler})
   110  							show_prompt = false
   111  						} else {
   112  							uiCmds[i].handler(param)
   113  						}
   114  					}
   115  				}
   116  			}
   117  			if !found {
   118  				fmt.Printf("Unknown command '%s'. Type 'help' for help.\n", cmd)
   119  			}
   120  		}
   121  	}
   122  }
   123  
   124  func show_info(par string) {
   125  	fmt.Println("main.go last seen in line:", common.BusyIn())
   126  
   127  	network.MutexRcv.Lock()
   128  	discarded := len(network.DiscardedBlocks)
   129  	cached := network.CachedBlocksLen()
   130  	b2g_len := len(network.BlocksToGet)
   131  	b2g_idx_len := len(network.IndexToBlocksToGet)
   132  	network.MutexRcv.Unlock()
   133  
   134  	fmt.Printf("Gocoin: %s,  Synced: %t (%d),  Uptime %s,  Peers: %d,  ECDSAs: %d %d %d\n",
   135  		gocoin.Version, common.GetBool(&common.BlockChainSynchronized), network.HeadersReceived.Get(),
   136  		time.Since(common.StartTime).String(), peersdb.PeerDB.Count(),
   137  		btc.EcdsaVerifyCnt(), btc.SchnorrVerifyCnt(), btc.CheckPay2ContractCnt())
   138  
   139  	// Memory used
   140  	al, sy := sys.MemUsed()
   141  	cb, ca := common.MemUsed()
   142  	fmt.Printf("Heap_used: %d MB,  System_used: %d MB,  UTXO-X-mem: %d MB in %d recs,  Saving: %t\n", al>>20, sy>>20,
   143  		cb>>20, ca, common.BlockChain.Unspent.WritingInProgress.Get())
   144  
   145  	network.MutexRcv.Lock()
   146  	fmt.Println("Last Header:", network.LastCommitedHeader.BlockHash.String(), "@", network.LastCommitedHeader.Height)
   147  	network.MutexRcv.Unlock()
   148  
   149  	common.Last.Mutex.Lock()
   150  	fmt.Println("Last Block :", common.Last.Block.BlockHash.String(), "@", common.Last.Block.Height)
   151  	fmt.Printf(" Time: %s (~%s),  Diff: %.0f,  Rcvd: %s ago\n",
   152  		time.Unix(int64(common.Last.Block.Timestamp()), 0).Format("2006/01/02 15:04:05"),
   153  		time.Unix(int64(common.Last.Block.GetMedianTimePast()), 0).Format("15:04:05"),
   154  		btc.GetDifficulty(common.Last.Block.Bits()), time.Since(common.Last.Time).String())
   155  	common.Last.Mutex.Unlock()
   156  
   157  	network.Mutex_net.Lock()
   158  	fmt.Printf("Blocks Queued: %d,  Cached: %d,  Discarded: %d,  To Get: %d/%d,  UTXO.db on disk: %d\n",
   159  		len(network.NetBlocks), cached, discarded, b2g_len, b2g_idx_len,
   160  		atomic.LoadUint32(&common.BlockChain.Unspent.CurrentHeightOnDisk))
   161  	network.Mutex_net.Unlock()
   162  
   163  	network.TxMutex.Lock()
   164  	var sw_cnt, sw_bts uint64
   165  	for _, v := range network.TransactionsToSend {
   166  		if v.SegWit != nil {
   167  			sw_cnt++
   168  			sw_bts += uint64(v.Size)
   169  		}
   170  	}
   171  	fmt.Printf("Txs in mempool: %d (%sB),  Using SegWit: %d (%sB),  Rejected: %d (%sB)\n",
   172  		len(network.TransactionsToSend), common.UintToString(network.TransactionsToSendSize),
   173  		sw_cnt, common.UintToString(sw_bts),
   174  		len(network.TransactionsRejected), common.UintToString(network.TransactionsRejectedSize))
   175  	fmt.Printf(" Wait4Input: %d (%sB),  SpentOuts: %d,  AvgFee: %.1f SpB,  Pending:%d/%d,  ScrFlgs:0x%x\n",
   176  		len(network.WaitingForInputs), common.UintToString(network.WaitingForInputsSize),
   177  		len(network.SpentOutputs), usif.GetAverageFee(),
   178  		len(network.TransactionsPending), len(network.NetTxs), common.CurrentScriptFlags())
   179  	network.TxMutex.Unlock()
   180  
   181  	var gs debug.GCStats
   182  	debug.ReadGCStats(&gs)
   183  	usif.BlockFeesMutex.Lock()
   184  	fmt.Println("Go version:", runtime.Version(), "  LastGC:", time.Since(gs.LastGC).String(),
   185  		"  NumGC:", gs.NumGC,
   186  		"  PauseTotal:", gs.PauseTotal.String())
   187  	usif.BlockFeesMutex.Unlock()
   188  }
   189  
   190  func show_counters(par string) {
   191  	common.CounterMutex.Lock()
   192  	ck := make([]string, 0)
   193  	for k := range common.Counter {
   194  		if par == "" || strings.HasPrefix(k, par) {
   195  			ck = append(ck, k)
   196  		}
   197  	}
   198  	sort.Strings(ck)
   199  
   200  	var li string
   201  	for i := range ck {
   202  		k := ck[i]
   203  		v := common.Counter[k]
   204  		s := fmt.Sprint(k, ": ", v)
   205  		if len(li)+len(s) >= 80 {
   206  			fmt.Println(li)
   207  			li = ""
   208  		} else if li != "" {
   209  			li += ",   "
   210  		}
   211  		li += s
   212  	}
   213  	if li != "" {
   214  		fmt.Println(li)
   215  	}
   216  	common.CounterMutex.Unlock()
   217  }
   218  
   219  func show_pending(par string) {
   220  	network.MutexRcv.Lock()
   221  	out := make([]string, len(network.BlocksToGet))
   222  	var idx int
   223  	for _, v := range network.BlocksToGet {
   224  		out[idx] = fmt.Sprintf(" * %d / %s / %d in progress", v.Block.Height, v.Block.Hash.String(), v.InProgress)
   225  		idx++
   226  	}
   227  	network.MutexRcv.Unlock()
   228  	sort.Strings(out)
   229  	for _, s := range out {
   230  		fmt.Println(s)
   231  	}
   232  }
   233  
   234  func show_help(par string) {
   235  	fmt.Println("The following", len(uiCmds), "commands are supported:")
   236  	for i := range uiCmds {
   237  		fmt.Print("   ")
   238  		for j := range uiCmds[i].cmds {
   239  			if j > 0 {
   240  				fmt.Print(", ")
   241  			}
   242  			fmt.Print(uiCmds[i].cmds[j])
   243  		}
   244  		fmt.Println(" -", uiCmds[i].help)
   245  	}
   246  	fmt.Println("All the commands are case sensitive.")
   247  }
   248  
   249  func show_mem(p string) {
   250  	al, sy := sys.MemUsed()
   251  
   252  	fmt.Println("Allocated:", al>>20, "MB")
   253  	fmt.Println("SystemMem:", sy>>20, "MB")
   254  
   255  	if p == "" {
   256  		return
   257  	}
   258  	if p == "free" {
   259  		fmt.Println("Freeing the mem...")
   260  		sys.FreeMem()
   261  		show_mem("")
   262  		return
   263  	}
   264  	if p == "gc" {
   265  		fmt.Println("Running GC...")
   266  		runtime.GC()
   267  		fmt.Println("Done.")
   268  		return
   269  	}
   270  	i, e := strconv.ParseInt(p, 10, 64)
   271  	if e != nil {
   272  		println(e.Error())
   273  		return
   274  	}
   275  	debug.SetGCPercent(int(i))
   276  	fmt.Println("GC treshold set to", i, "percent")
   277  }
   278  
   279  func dump_block(s string) {
   280  	h := btc.NewUint256FromString(s)
   281  	if h == nil {
   282  		println("Specify block's hash")
   283  		return
   284  	}
   285  	crec, _, er := common.BlockChain.Blocks.BlockGetExt(btc.NewUint256(h.Hash[:]))
   286  	if er != nil {
   287  		println("BlockGetExt:", er.Error())
   288  		return
   289  	}
   290  
   291  	ioutil.WriteFile(h.String()+".bin", crec.Data, 0700)
   292  	fmt.Println("Block saved")
   293  }
   294  
   295  func ui_quit(par string) {
   296  	usif.Exit_now.Set()
   297  }
   298  
   299  func blchain_stats(par string) {
   300  	fmt.Println(common.BlockChain.Stats())
   301  }
   302  
   303  func blchain_utxodb(par string) {
   304  	fmt.Println(common.BlockChain.Unspent.UTXOStats())
   305  }
   306  
   307  func set_ulmax(par string) {
   308  	v, e := strconv.ParseUint(par, 10, 64)
   309  	if e == nil {
   310  		common.SetUploadLimit(v << 10)
   311  	}
   312  	if common.UploadLimit() != 0 {
   313  		fmt.Printf("Current upload limit is %d KB/s\n", common.UploadLimit()>>10)
   314  	} else {
   315  		fmt.Println("The upload speed is not limited")
   316  	}
   317  }
   318  
   319  func set_dlmax(par string) {
   320  	v, e := strconv.ParseUint(par, 10, 64)
   321  	if e == nil {
   322  		common.SetDownloadLimit(v << 10)
   323  	}
   324  	if common.DownloadLimit() != 0 {
   325  		fmt.Printf("Current download limit is %d KB/s\n", common.DownloadLimit()>>10)
   326  	} else {
   327  		fmt.Println("The download speed is not limited")
   328  	}
   329  }
   330  
   331  func set_config(s string) {
   332  	common.LockCfg()
   333  	defer common.UnlockCfg()
   334  	if s != "" {
   335  		new := common.CFG
   336  		e := json.Unmarshal([]byte("{"+s+"}"), &new)
   337  		if e != nil {
   338  			println(e.Error())
   339  		} else {
   340  			common.CFG = new
   341  			common.Reset()
   342  			fmt.Println("Config changed. Execute configsave, if you want to save it.")
   343  		}
   344  	}
   345  	dat, _ := json.MarshalIndent(&common.CFG, "", "    ")
   346  	fmt.Println(string(dat))
   347  }
   348  
   349  func load_config(s string) {
   350  	d, e := ioutil.ReadFile(common.ConfigFile)
   351  	if e != nil {
   352  		println(e.Error())
   353  		return
   354  	}
   355  	common.LockCfg()
   356  	defer common.UnlockCfg()
   357  	e = json.Unmarshal(d, &common.CFG)
   358  	if e != nil {
   359  		println(e.Error())
   360  		return
   361  	}
   362  	common.Reset()
   363  	fmt.Println("Config reloaded")
   364  }
   365  
   366  func save_config(s string) {
   367  	common.LockCfg()
   368  	if common.SaveConfig() {
   369  		fmt.Println("Current settings saved to", common.ConfigFile)
   370  	}
   371  	common.UnlockCfg()
   372  }
   373  
   374  func show_cached(par string) {
   375  	var hi, lo uint32
   376  	for _, v := range network.CachedBlocks {
   377  		//fmt.Printf(" * %s -> %s\n", v.Hash.String(), btc.NewUint256(v.ParentHash()).String())
   378  		if hi == 0 {
   379  			hi = v.Block.Height
   380  			lo = v.Block.Height
   381  		} else if v.Block.Height > hi {
   382  			hi = v.Block.Height
   383  		} else if v.Block.Height < lo {
   384  			lo = v.Block.Height
   385  		}
   386  	}
   387  	fmt.Println(len(network.CachedBlocks), "block cached with heights", lo, "to", hi, hi-lo)
   388  }
   389  
   390  func send_inv(par string) {
   391  	cs := strings.Split(par, " ")
   392  	if len(cs) != 2 {
   393  		println("Specify hash and type")
   394  		return
   395  	}
   396  	ha := btc.NewUint256FromString(cs[1])
   397  	if ha == nil {
   398  		println("Incorrect hash")
   399  		return
   400  	}
   401  	v, e := strconv.ParseInt(cs[0], 10, 32)
   402  	if e != nil {
   403  		println("Incorrect type:", e.Error())
   404  		return
   405  	}
   406  	network.NetRouteInv(uint32(v), ha, nil)
   407  	fmt.Println("Inv sent to all peers")
   408  }
   409  
   410  func analyze_bip9(par string) {
   411  	all := par == "all"
   412  	n := common.BlockChain.BlockTreeRoot
   413  	for n != nil {
   414  		var i uint
   415  		start_block := uint(n.Height)
   416  		start_time := n.Timestamp()
   417  		bits := make(map[byte]uint32)
   418  		for i = 0; i < 2016 && n != nil; i++ {
   419  			ver := n.BlockVersion()
   420  			if (ver & 0x20000000) != 0 {
   421  				for bit := byte(0); bit <= 28; bit++ {
   422  					if (ver & (1 << bit)) != 0 {
   423  						bits[bit]++
   424  					}
   425  				}
   426  			}
   427  			n = n.FindPathTo(common.BlockChain.LastBlock())
   428  		}
   429  		if len(bits) > 0 {
   430  			var s string
   431  			for k, v := range bits {
   432  				if all || v >= common.BlockChain.Consensus.BIP9_Treshold {
   433  					if s != "" {
   434  						s += " | "
   435  					}
   436  					s += fmt.Sprint(v, " x bit(", k, ")")
   437  				}
   438  			}
   439  			if s != "" {
   440  				fmt.Println("Period from", time.Unix(int64(start_time), 0).Format("2006/01/02 15:04"),
   441  					" block #", start_block, "-", start_block+i-1, ":", s, " - active from", start_block+2*2016)
   442  			}
   443  		}
   444  	}
   445  }
   446  
   447  func switch_trust(par string) {
   448  	if par == "0" {
   449  		common.FLAG.TrustAll = false
   450  	} else if par == "1" {
   451  		common.FLAG.TrustAll = true
   452  	}
   453  	fmt.Println("Assume blocks trusted:", common.FLAG.TrustAll)
   454  }
   455  
   456  func save_utxo(par string) {
   457  	//common.BlockChain.Unspent.HurryUp()
   458  	common.BlockChain.Unspent.Save()
   459  }
   460  
   461  func purge_utxo(par string) {
   462  	common.BlockChain.Unspent.PurgeUnspendable(true)
   463  	if !common.CFG.Memory.PurgeUnspendableUTXO {
   464  		fmt.Println("Save your config file (cs) to have all the futher records purged automatically.")
   465  		common.CFG.Memory.PurgeUnspendableUTXO = true
   466  	}
   467  }
   468  
   469  func undo_block(par string) {
   470  	common.BlockChain.UndoLastBlock()
   471  	common.Last.Mutex.Lock()
   472  	common.Last.Block = common.BlockChain.LastBlock()
   473  	common.UpdateScriptFlags(0)
   474  	common.Last.Mutex.Unlock()
   475  }
   476  
   477  func redo_block(par string) {
   478  	network.MutexRcv.Lock()
   479  	end := network.LastCommitedHeader
   480  	network.MutexRcv.Unlock()
   481  	last := common.BlockChain.LastBlock()
   482  	if last == end {
   483  		println("You already are at the last known block - nothing to redo")
   484  		return
   485  	}
   486  
   487  	sta := time.Now()
   488  	nxt := last.FindPathTo(end)
   489  	if nxt == nil {
   490  		println("ERROR: FindPathTo failed")
   491  		return
   492  	}
   493  
   494  	if nxt.BlockSize == 0 {
   495  		println("ERROR: BlockSize is zero - corrupt database")
   496  		return
   497  	}
   498  
   499  	pre := time.Now()
   500  	crec, _, _ := common.BlockChain.Blocks.BlockGetInternal(nxt.BlockHash, true)
   501  
   502  	bl, er := btc.NewBlock(crec.Data)
   503  	if er != nil {
   504  		println("btc.NewBlock() error - corrupt database")
   505  		return
   506  	}
   507  	bl.Height = nxt.Height
   508  
   509  	// Recover the flags to be used when verifying scripts for non-trusted blocks (stored orphaned blocks)
   510  	common.BlockChain.ApplyBlockFlags(bl)
   511  
   512  	er = bl.BuildTxList()
   513  	if er != nil {
   514  		println("bl.BuildTxList() error - corrupt database")
   515  		return
   516  	}
   517  
   518  	bl.Trusted.Clr() // assume not trusted
   519  
   520  	tdl := time.Now()
   521  	rb := &network.OneReceivedBlock{TmStart: sta, TmPreproc: pre, TmDownload: tdl}
   522  	network.MutexRcv.Lock()
   523  	network.ReceivedBlocks[bl.Hash.BIdx()] = rb
   524  	network.MutexRcv.Unlock()
   525  
   526  	fmt.Println("Putting block", bl.Height, "into net queue...")
   527  	network.NetBlocks <- &network.BlockRcvd{Conn: nil, Block: bl, BlockTreeNode: nxt,
   528  		OneReceivedBlock: rb, BlockExtraInfo: nil}
   529  }
   530  
   531  func kill_node(par string) {
   532  	os.Exit(1)
   533  }
   534  
   535  func init() {
   536  	newUi("bchain b", true, blchain_stats, "Display blockchain statistics")
   537  	newUi("bip9", true, analyze_bip9, "Analyze current blockchain for BIP9 bits (add 'all' to see more)")
   538  	newUi("cache", true, show_cached, "Show blocks cached in memory")
   539  	newUi("configload cl", false, load_config, "Re-load settings from the common file")
   540  	newUi("configsave cs", false, save_config, "Save current settings to a common file")
   541  	newUi("configset cfg", false, set_config, "Set a specific common value - use JSON, omit top {}")
   542  	newUi("counters c", false, show_counters, "Show all kind of debug counters")
   543  	newUi("dlimit dl", false, set_dlmax, "Set maximum download speed. The value is in KB/second - 0 for unlimited")
   544  	newUi("help h ?", false, show_help, "Shows this help")
   545  	newUi("info i", false, show_info, "Shows general info about the node")
   546  	newUi("inv", false, send_inv, "Send inv message to all the peers - specify type & hash")
   547  	newUi("kill", true, kill_node, "Kill the node. WARNING: not safe - use 'quit' instead")
   548  	newUi("mem", false, show_mem, "Show detailed memory stats (optionally free, gc or a numeric param)")
   549  	newUi("pend", false, show_pending, "Show pending blocks, to be fetched")
   550  	newUi("purge", true, purge_utxo, "Purge all unspendable outputs from UTXO database")
   551  	newUi("quit q", false, ui_quit, "Quit the node")
   552  	newUi("redo", true, redo_block, "Redo last block")
   553  	newUi("savebl", false, dump_block, "Saves a block with a given hash to a binary file")
   554  	newUi("saveutxo s", true, save_utxo, "Save UTXO database now")
   555  	newUi("trust t", true, switch_trust, "Assume all donwloaded blocks trusted (1) or un-trusted (0)")
   556  	newUi("ulimit ul", false, set_ulmax, "Set maximum upload speed. The value is in KB/second - 0 for unlimited")
   557  	newUi("undo", true, undo_block, "Undo last block")
   558  	newUi("utxo u", true, blchain_utxodb, "Display UTXO-db statistics")
   559  }