github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/client/main.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/signal" 9 "runtime/debug" 10 "syscall" 11 "time" 12 "unsafe" 13 14 "github.com/piotrnar/gocoin" 15 "github.com/piotrnar/gocoin/client/common" 16 "github.com/piotrnar/gocoin/client/network" 17 "github.com/piotrnar/gocoin/client/network/peersdb" 18 "github.com/piotrnar/gocoin/client/rpcapi" 19 "github.com/piotrnar/gocoin/client/usif" 20 "github.com/piotrnar/gocoin/client/usif/textui" 21 "github.com/piotrnar/gocoin/client/usif/webui" 22 "github.com/piotrnar/gocoin/client/wallet" 23 "github.com/piotrnar/gocoin/lib/btc" 24 "github.com/piotrnar/gocoin/lib/chain" 25 "github.com/piotrnar/gocoin/lib/others/qdb" 26 "github.com/piotrnar/gocoin/lib/others/sys" 27 ) 28 29 var ( 30 retryCachedBlocks bool 31 SaveBlockChain *time.Timer = time.NewTimer(24 * time.Hour) 32 33 NetBlocksSize sys.SyncInt 34 35 exitat *uint = flag.Uint("exitat", 0, "Auto exit node after comitting block with the given height") 36 ) 37 38 const ( 39 SaveBlockChainAfter = 2 * time.Second 40 SaveBlockChainAfterNoSync = 10 * time.Minute 41 ) 42 43 func reset_save_timer() { 44 SaveBlockChain.Stop() 45 for len(SaveBlockChain.C) > 0 { 46 <-SaveBlockChain.C 47 } 48 if common.BlockChainSynchronized { 49 SaveBlockChain.Reset(SaveBlockChainAfter) 50 } else { 51 SaveBlockChain.Reset(SaveBlockChainAfterNoSync) 52 } 53 } 54 55 func blockMined(bl *btc.Block) { 56 network.BlockMined(bl) 57 if int(bl.LastKnownHeight)-int(bl.Height) < 144 { // do not run it when syncing chain 58 usif.ProcessBlockFees(bl.Height, bl) 59 } 60 } 61 62 func blockUndone(bl *btc.Block) { 63 network.BlockUndone(bl) 64 } 65 66 func exit_now() { 67 al, sy := sys.MemUsed() 68 cb, _ := common.MemUsed() 69 fmt.Printf("Sync to %d took %s - %.0f min. Mem: %d %d %d MB - cachempty: %d\n", 70 common.Last.Block.Height, time.Since(common.StartTime).String(), 71 float64(time.Since(common.StartTime))/float64(time.Minute), al>>20, sy>>20, cb>>20, 72 network.Fetch.CacheEmpty) 73 fmt.Printf("Wasted %dMB from %d blocks. Max cache used: %d / %dMB\n", 74 network.Fetch.BlockBytesWasted>>20, network.Fetch.BlockSameRcvd, 75 network.MaxCachedBlocksSize.Get()>>20, common.SyncMaxCacheBytes.Get()>>20) 76 common.PrintBWStats() 77 fmt.Print("Reached given block ", *exitat, ". Now exiting....\n\n\n\n") 78 os.Exit(0) 79 } 80 81 func LocalAcceptBlock(newbl *network.BlockRcvd) (e error) { 82 bl := newbl.Block 83 if common.FLAG.TrustAll || newbl.BlockTreeNode.Trusted.Get() { 84 bl.Trusted.Set() 85 } 86 87 common.BlockChain.Unspent.AbortWriting() // abort saving of UTXO.db 88 common.BlockChain.Blocks.BlockAdd(newbl.BlockTreeNode.Height, bl) 89 newbl.TmQueue = time.Now() 90 91 if newbl.DoInvs { 92 common.Busy() 93 network.NetRouteInv(network.MSG_BLOCK, bl.Hash, newbl.Conn) 94 } 95 96 network.MutexRcv.Lock() 97 bl.LastKnownHeight = network.LastCommitedHeader.Height 98 network.MutexRcv.Unlock() 99 e = common.BlockChain.CommitBlock(bl, newbl.BlockTreeNode) 100 if bl.LastKnownHeight-bl.Height > common.GetUint32(&common.CFG.Memory.MaxCachedBlks) { 101 bl.Txs = nil // we won't be needing bl.Txs anymore, so might as well mark the memory as unused 102 } 103 104 if e == nil { 105 // new block accepted 106 newbl.TmAccepted = time.Now() 107 108 newbl.NonWitnessSize = bl.NoWitnessSize 109 newbl.TheWeight = bl.BlockWeight 110 newbl.ThePaidVSize = bl.PaidTxsVSize 111 newbl.TheOrdCnt = bl.OrbTxCnt 112 newbl.TheOrdSize = bl.OrbTxSize 113 newbl.TheOrdWeight = bl.OrbTxWeight 114 115 common.RecalcAverageBlockSize() 116 117 common.Last.Mutex.Lock() 118 common.Last.Time = time.Now() 119 common.Last.Block = common.BlockChain.LastBlock() 120 common.UpdateScriptFlags(bl.VerifyFlags) 121 122 if common.Last.ParseTill != nil && (common.Last.Block.Height%100e3) == 0 { 123 println("Parsing to", common.Last.Block.Height, "took", time.Since(newbl.TmStart).String()) 124 } 125 126 if common.Last.ParseTill != nil && common.Last.Block == common.Last.ParseTill { 127 println("Initial parsing finished in", time.Since(newbl.TmStart).String()) 128 common.Last.ParseTill = nil 129 } 130 if common.Last.ParseTill == nil && !common.BlockChainSynchronized && 131 ((common.Last.Block.Height%50e3) == 0 || common.Last.Block.Height == network.LastCommitedHeader.Height) { 132 al, sy := sys.MemUsed() 133 cb, _ := common.MemUsed() 134 fmt.Printf("Sync to %d took %s - %.1f min. Mem: %d %d %d MB - cachempty: %d\n", 135 common.Last.Block.Height, time.Since(common.StartTime).String(), 136 float64(time.Since(common.StartTime))/float64(time.Minute), al>>20, sy>>20, cb>>20, 137 network.Fetch.CacheEmpty) 138 if common.Last.Block.Height <= 200e3 { 139 // Cache underflow counter is not reliable at the beginning of chain sync,s o reset it here 140 network.Fetch.CacheEmpty = 0 141 } 142 } 143 if *exitat != 0 && uint(common.Last.Block.Height) == *exitat { 144 exit_now() 145 } 146 common.Last.Mutex.Unlock() 147 } else { 148 //fmt.Println("Warning: AcceptBlock failed. If the block was valid, you may need to rebuild the unspent DB (-r)") 149 new_end := common.BlockChain.LastBlock() 150 common.Last.Mutex.Lock() 151 common.Last.Block = new_end 152 common.UpdateScriptFlags(bl.VerifyFlags) 153 common.Last.Mutex.Unlock() 154 // update network.LastCommitedHeader 155 network.MutexRcv.Lock() 156 prev_last_header := network.LastCommitedHeader 157 network.DiscardBlock(newbl.BlockTreeNode) // this function can also modify network.LastCommitedHeader 158 if common.Last.Block.Height > network.LastCommitedHeader.Height { 159 network.LastCommitedHeader, _ = common.Last.Block.FindFarthestNode() 160 } 161 need_more_headers := prev_last_header != network.LastCommitedHeader 162 network.MutexRcv.Unlock() 163 if need_more_headers { 164 //println("LastCommitedHeader moved to", network.LastCommitedHeader.Height) 165 network.GetMoreHeaders() 166 } 167 } 168 reset_save_timer() 169 return 170 } 171 172 func retry_cached_blocks() bool { 173 var idx int 174 common.CountSafe("RedoCachedBlks") 175 for idx < len(network.CachedBlocks) { 176 newbl := network.CachedBlocks[idx] 177 if CheckParentDiscarded(newbl.BlockTreeNode) { 178 common.CountSafe("DiscardCachedBlock") 179 if newbl.Block == nil { 180 os.Remove(common.TempBlocksDir() + newbl.BlockTreeNode.BlockHash.String()) 181 } 182 network.CachedBlocksDel(idx) 183 return len(network.CachedBlocks) > 0 184 } 185 if common.BlockChain.HasAllParents(newbl.BlockTreeNode) { 186 common.Busy() 187 188 if newbl.Block == nil { 189 tmpfn := common.TempBlocksDir() + newbl.BlockTreeNode.BlockHash.String() 190 dat, e := ioutil.ReadFile(tmpfn) 191 os.Remove(tmpfn) 192 if e != nil { 193 panic(e.Error()) 194 } 195 if newbl.Block, e = btc.NewBlock(dat); e != nil { 196 panic(e.Error()) 197 } 198 if e = newbl.Block.BuildTxList(); e != nil { 199 panic(e.Error()) 200 } 201 newbl.Block.BlockExtraInfo = *newbl.BlockExtraInfo 202 } 203 204 e := LocalAcceptBlock(newbl) 205 if e != nil { 206 fmt.Println("AcceptBlock2", newbl.BlockTreeNode.BlockHash.String(), "-", e.Error()) 207 newbl.Conn.Misbehave("LocalAcceptBl2", 250) 208 } 209 if usif.Exit_now.Get() { 210 return false 211 } 212 // remove it from cache 213 network.CachedBlocksDel(idx) 214 return len(network.CachedBlocks) > 0 215 } else { 216 idx++ 217 } 218 } 219 return false 220 } 221 222 // CheckParentDiscarded returns true if the block's parent is on the DiscardedBlocks list. 223 // Add it to DiscardedBlocks, if returning true. 224 func CheckParentDiscarded(n *chain.BlockTreeNode) bool { 225 network.MutexRcv.Lock() 226 defer network.MutexRcv.Unlock() 227 if network.DiscardedBlocks[n.Parent.BlockHash.BIdx()] { 228 network.DiscardedBlocks[n.BlockHash.BIdx()] = true 229 return true 230 } 231 return false 232 } 233 234 // HandleNetBlock is called from the blockchain thread. 235 func HandleNetBlock(newbl *network.BlockRcvd) { 236 if common.Last.ParseTill != nil { 237 NetBlocksSize.Add(-len(newbl.Block.Raw)) 238 } 239 240 defer func() { 241 common.CountSafe("MainNetBlock") 242 if common.GetUint32(&common.WalletOnIn) > 0 { 243 common.SetUint32(&common.WalletOnIn, 5) // snooze the timer to 5 seconds from now 244 } 245 }() 246 247 if CheckParentDiscarded(newbl.BlockTreeNode) { 248 common.CountSafe("DiscardFreshBlockA") 249 if newbl.Block == nil { 250 os.Remove(common.TempBlocksDir() + newbl.BlockTreeNode.BlockHash.String()) 251 } 252 retryCachedBlocks = len(network.CachedBlocks) > 0 253 return 254 } 255 256 if !common.BlockChain.HasAllParents(newbl.BlockTreeNode) { 257 // it's not linking - keep it for later 258 network.CachedBlocksAdd(newbl) 259 common.CountSafe("BlockPostone") 260 return 261 } 262 263 if newbl.Block == nil { 264 tmpfn := common.TempBlocksDir() + newbl.BlockTreeNode.BlockHash.String() 265 dat, e := ioutil.ReadFile(tmpfn) 266 os.Remove(tmpfn) 267 if e != nil { 268 panic(e.Error()) 269 } 270 if newbl.Block, e = btc.NewBlock(dat); e != nil { 271 panic(e.Error()) 272 } 273 if e = newbl.Block.BuildTxList(); e != nil { 274 panic(e.Error()) 275 } 276 newbl.Block.BlockExtraInfo = *newbl.BlockExtraInfo 277 } 278 279 common.Busy() 280 if e := LocalAcceptBlock(newbl); e != nil { 281 common.CountSafe("DiscardFreshBlockB") 282 fmt.Println("AcceptBlock1", newbl.Block.Hash.String(), "-", e.Error()) 283 newbl.Conn.Misbehave("LocalAcceptBl1", 250) 284 } 285 retryCachedBlocks = retry_cached_blocks() 286 if !retryCachedBlocks && network.BlocksToGetCnt() != 0 { 287 now := time.Now() 288 if network.Fetch.LastCacheEmpty.IsZero() || now.Sub(network.Fetch.LastCacheEmpty) >= time.Second { 289 network.Fetch.CacheEmpty++ 290 network.Fetch.LastCacheEmpty = now 291 } 292 } 293 } 294 295 func HandleRpcBlock(msg *rpcapi.BlockSubmited) { 296 common.CountSafe("RPCNewBlock") 297 298 network.MutexRcv.Lock() 299 rb := network.ReceivedBlocks[msg.Block.Hash.BIdx()] 300 network.MutexRcv.Unlock() 301 if rb == nil { 302 panic("Block " + msg.Block.Hash.String() + " not in ReceivedBlocks map") 303 } 304 305 common.BlockChain.Unspent.AbortWriting() 306 rb.TmQueue = time.Now() 307 308 _, _, e := common.BlockChain.CheckBlock(msg.Block) 309 if e == nil { 310 e = common.BlockChain.AcceptBlock(msg.Block) 311 rb.TmAccepted = time.Now() 312 } 313 if e != nil { 314 common.CountSafe("RPCBlockError") 315 msg.Error = e.Error() 316 msg.Done.Done() 317 return 318 } 319 320 network.NetRouteInv(network.MSG_BLOCK, msg.Block.Hash, nil) 321 common.RecalcAverageBlockSize() 322 323 common.CountSafe("RPCBlockOK") 324 println("New mined block", msg.Block.Height, "accepted OK in", rb.TmAccepted.Sub(rb.TmQueue).String()) 325 326 common.Last.Mutex.Lock() 327 common.Last.Time = time.Now() 328 common.Last.Block = common.BlockChain.LastBlock() 329 common.UpdateScriptFlags(msg.VerifyFlags) 330 common.Last.Mutex.Unlock() 331 332 msg.Done.Done() 333 } 334 335 func do_the_blocks(end *chain.BlockTreeNode) { 336 sta := time.Now() 337 last := common.BlockChain.LastBlock() 338 for last != end { 339 nxt := last.FindPathTo(end) 340 if nxt == nil { 341 break 342 } 343 344 if nxt.BlockSize == 0 { 345 println("BlockSize is zero - corrupt database") 346 break 347 } 348 349 pre := time.Now() 350 crec, trusted, _ := common.BlockChain.Blocks.BlockGetInternal(nxt.BlockHash, true) 351 if crec == nil || crec.Data == nil { 352 panic(fmt.Sprint("No data for block #", nxt.Height, " ", nxt.BlockHash.String())) 353 } 354 355 bl, er := btc.NewBlock(crec.Data) 356 if er != nil { 357 println("btc.NewBlock() error - corrupt database") 358 break 359 } 360 bl.Height = nxt.Height 361 362 // Recover the flags to be used when verifying scripts for non-trusted blocks (stored orphaned blocks) 363 common.BlockChain.ApplyBlockFlags(bl) 364 365 er = bl.BuildTxList() 366 if er != nil { 367 println("bl.BuildTxList() error - corrupt database") 368 break 369 } 370 371 bl.Trusted.Store(trusted) 372 373 tdl := time.Now() 374 375 rb := &network.OneReceivedBlock{TmStart: sta, TmPreproc: pre, TmDownload: tdl} 376 network.MutexRcv.Lock() 377 network.ReceivedBlocks[bl.Hash.BIdx()] = rb 378 network.MutexRcv.Unlock() 379 380 network.NetBlocks <- &network.BlockRcvd{Conn: nil, Block: bl, BlockTreeNode: nxt, 381 OneReceivedBlock: rb, BlockExtraInfo: nil} 382 383 NetBlocksSize.Add(len(bl.Raw)) 384 for NetBlocksSize.Get() > 64*1024*1024 { 385 time.Sleep(100 * time.Millisecond) 386 } 387 388 last = nxt 389 390 } 391 //println("all blocks queued", len(network.NetBlocks)) 392 } 393 394 func main() { 395 var ptr *byte 396 if unsafe.Sizeof(ptr) < 8 { 397 fmt.Println("WARNING: Gocoin client shall be build for 64-bit arch. It will likely crash now.") 398 } 399 400 fmt.Println("Gocoin client version", gocoin.Version) 401 402 // Disable Ctrl+C 403 signal.Notify(common.KillChan, os.Interrupt, syscall.SIGTERM) 404 defer func() { 405 if r := recover(); r != nil { 406 err, ok := r.(error) 407 if !ok { 408 err = fmt.Errorf("pkg: %v", r) 409 } 410 fmt.Println("main panic recovered:", err.Error()) 411 fmt.Println(string(debug.Stack())) 412 network.NetCloseAll() 413 common.CloseBlockChain() 414 peersdb.ClosePeerDB() 415 sys.UnlockDatabaseDir() 416 os.Exit(1) 417 } 418 }() 419 420 common.InitConfig() 421 422 if common.FLAG.SaveConfig { 423 common.SaveConfig() 424 fmt.Println("Configuration file saved") 425 os.Exit(0) 426 } 427 428 if common.FLAG.VolatileUTXO { 429 fmt.Println("WARNING! Using UTXO database in a volatile mode. Make sure to close the client properly (do not kill it!)") 430 } 431 432 if common.FLAG.TrustAll { 433 fmt.Println("WARNING! Assuming all scripts inside new blocks to PASS. Verify the last block's hash when finished.") 434 } 435 436 host_init() // This will create the DB lock file and keep it open 437 438 os.RemoveAll(common.TempBlocksDir()) 439 common.MkTempBlocksDir() 440 441 if common.FLAG.UndoBlocks > 0 { 442 usif.Exit_now.Set() 443 } 444 445 if common.FLAG.Rescan && common.FLAG.VolatileUTXO { 446 447 fmt.Println("UTXO database rebuilt complete in the volatile mode, so flush DB to disk and exit...") 448 449 } else if !usif.Exit_now.Get() { 450 451 common.RecalcAverageBlockSize() 452 453 peersTick := time.Tick(peersdb.ExpirePeersPeriod) 454 netTick := time.Tick(time.Second) 455 456 reset_save_timer() // we wil do one save try after loading, in case if ther was a rescan 457 458 peersdb.Testnet = common.Testnet 459 peersdb.ConnectOnly = common.CFG.ConnectOnly 460 peersdb.Services = common.Services 461 peersdb.InitPeers(common.GocoinHomeDir) 462 if common.FLAG.UnbanAllPeers { 463 var keys []qdb.KeyType 464 var vals [][]byte 465 peersdb.PeerDB.Browse(func(k qdb.KeyType, v []byte) uint32 { 466 peer := peersdb.NewPeer(v) 467 if peer.Banned != 0 { 468 fmt.Println("Unban", peer.NetAddr.String()) 469 peer.Banned = 0 470 keys = append(keys, k) 471 vals = append(vals, peer.Bytes()) 472 } 473 return 0 474 }) 475 for i := range keys { 476 peersdb.PeerDB.Put(keys[i], vals[i]) 477 } 478 479 fmt.Println(len(keys), "peers un-baned") 480 } 481 482 for k, v := range common.BlockChain.BlockIndex { 483 network.ReceivedBlocks[k] = &network.OneReceivedBlock{TmStart: time.Unix(int64(v.Timestamp()), 0)} 484 } 485 486 if common.Last.ParseTill != nil { 487 network.LastCommitedHeader = common.Last.ParseTill 488 println("Hold on network for now as we have", 489 common.Last.ParseTill.Height-common.Last.Block.Height, "new blocks on disk.") 490 go do_the_blocks(common.Last.ParseTill) 491 } else { 492 network.LastCommitedHeader = common.Last.Block 493 } 494 495 if common.CFG.TXPool.SaveOnDisk { 496 network.MempoolLoad2() 497 } 498 499 if common.CFG.TextUI_Enabled { 500 go textui.MainThread() 501 } 502 503 if common.CFG.WebUI.Interface != "" { 504 go webui.ServerThread() 505 } 506 507 if common.CFG.RPC.Enabled { 508 go rpcapi.StartServer(common.RPCPort()) 509 } 510 511 usif.LoadBlockFees() 512 513 wallet.FetchingBalanceTick = func() bool { 514 select { 515 case rec := <-usif.LocksChan: 516 common.CountSafe("DoMainLocks") 517 rec.In.Done() 518 rec.Out.Wait() 519 520 case newtx := <-network.NetTxs: 521 common.CountSafe("DoMainNetTx") 522 network.HandleNetTx(newtx, false) 523 524 case <-netTick: 525 common.CountSafe("DoMainNetTick") 526 if common.Last.ParseTill != nil { 527 break 528 } 529 network.NetworkTick() 530 531 case on := <-wallet.OnOff: 532 if !on { 533 return true 534 } 535 536 default: 537 } 538 return usif.Exit_now.Get() 539 } 540 541 startup_ticks := 5 // give 5 seconds for finding out missing blocks 542 if !common.FLAG.NoWallet { 543 // snooze the timer to 10 seconds after startup_ticks goes down 544 common.SetUint32(&common.WalletOnIn, 10) 545 } 546 547 for !usif.Exit_now.Get() { 548 common.Busy() 549 550 common.CountSafe("MainThreadLoops") 551 for retryCachedBlocks { 552 retryCachedBlocks = retry_cached_blocks() 553 if !retryCachedBlocks && network.BlocksToGetCnt() != 0 { 554 now := time.Now() 555 if network.Fetch.LastCacheEmpty.IsZero() || now.Sub(network.Fetch.LastCacheEmpty) >= time.Second { 556 network.Fetch.CacheEmpty++ 557 network.Fetch.LastCacheEmpty = now 558 } 559 } 560 // We have done one per loop - now do something else if pending... 561 if len(network.NetBlocks) > 0 || len(usif.UiChannel) > 0 { 562 break 563 } 564 } 565 566 // first check for priority messages; kill signal or a new block 567 select { 568 case <-common.KillChan: 569 common.Busy() 570 usif.Exit_now.Set() 571 continue 572 573 case newbl := <-network.NetBlocks: 574 common.Busy() 575 HandleNetBlock(newbl) 576 577 case rpcbl := <-rpcapi.RpcBlocks: 578 common.Busy() 579 HandleRpcBlock(rpcbl) 580 581 default: // timeout immediatelly if no priority message 582 } 583 584 common.Busy() 585 586 select { 587 case <-common.KillChan: 588 common.Busy() 589 usif.Exit_now.Set() 590 continue 591 592 case newbl := <-network.NetBlocks: 593 common.Busy() 594 HandleNetBlock(newbl) 595 596 case rpcbl := <-rpcapi.RpcBlocks: 597 common.Busy() 598 HandleRpcBlock(rpcbl) 599 600 case rec := <-usif.LocksChan: 601 common.Busy() 602 common.CountSafe("MainLocks") 603 rec.In.Done() 604 rec.Out.Wait() 605 606 case <-SaveBlockChain.C: 607 common.Busy() 608 common.CountSafe("SaveBlockChain") 609 if common.BlockChain.Idle() { 610 common.CountSafe("ChainIdleUsed") 611 } 612 613 case newtx := <-network.NetTxs: 614 common.Busy() 615 common.CountSafe("MainNetTx") 616 network.HandleNetTx(newtx, false) 617 618 case <-netTick: 619 common.Busy() 620 common.CountSafe("MainNetTick") 621 if common.Last.ParseTill != nil { 622 break 623 } 624 network.NetworkTick() 625 626 if common.BlockChainSynchronized { 627 if common.WalletPendingTick() { 628 wallet.OnOff <- true 629 } 630 break // BlockChainSynchronized so never mind checking it 631 } 632 633 // Now check if the chain is synchronized... 634 if (network.HeadersReceived.Get() > int(common.GetUint32(&common.CFG.Net.MaxOutCons)/2) || 635 peersdb.ConnectOnly != "" && network.HeadersReceived.Get() >= 1) && 636 network.BlocksToGetCnt() == 0 && len(network.NetBlocks) == 0 && 637 len(network.CachedBlocks) == 0 { 638 // only when we have no pending blocks and rteceived header messages, startup_ticks can go down.. 639 if startup_ticks > 0 { 640 startup_ticks-- 641 break 642 } 643 common.SetBool(&common.BlockChainSynchronized, true) 644 if *exitat == 99999999 { 645 exit_now() 646 } 647 reset_save_timer() 648 } else { 649 startup_ticks = 5 // snooze by 5 seconds each time we're in here 650 } 651 652 case cmd := <-usif.UiChannel: 653 common.Busy() 654 common.CountSafe("MainUICmd") 655 cmd.Handler(cmd.Param) 656 cmd.Done.Done() 657 658 case <-peersTick: 659 common.Busy() 660 peersdb.ExpirePeers() 661 usif.ExpireBlockFees() 662 663 case on := <-wallet.OnOff: 664 common.Busy() 665 if on { 666 if common.BlockChainSynchronized { 667 usif.FetchingBalances.Set() 668 wallet.LoadBalance() 669 usif.FetchingBalances.Clr() 670 } else { 671 fmt.Println("Cannot enable wallet functionality with blockchain sync in progress") 672 } 673 } else { 674 wallet.Disable() 675 common.SetUint32(&common.WalletOnIn, 0) 676 } 677 } 678 } 679 680 common.BlockChain.Unspent.HurryUp() 681 wallet.UpdateMapSizes() 682 network.NetCloseAll() 683 } 684 685 sta := time.Now() 686 common.CloseBlockChain() 687 if common.FLAG.UndoBlocks == 0 { 688 network.MempoolSave(false) 689 } 690 fmt.Println("Blockchain closed in", time.Since(sta).String()) 691 peersdb.ClosePeerDB() 692 usif.SaveBlockFees() 693 sys.UnlockDatabaseDir() 694 os.RemoveAll(common.TempBlocksDir()) 695 }