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 }