You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

514 lines
16 KiB

  1. // Copyright 2017-2018 DERO Project. All rights reserved.
  2. // Use of this source code in any form is governed by RESEARCH license.
  3. // license can be found in the LICENSE file.
  4. // GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
  5. //
  6. //
  7. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  8. // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  9. // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  10. // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  11. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  12. // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  13. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  14. // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
  15. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. package main
  17. import "io"
  18. import "os"
  19. import "time"
  20. import "fmt"
  21. import "bytes"
  22. import "bufio"
  23. import "strings"
  24. import "strconv"
  25. import "runtime"
  26. import "encoding/hex"
  27. import "encoding/json"
  28. import "path/filepath"
  29. import "github.com/chzyer/readline"
  30. import "github.com/docopt/docopt-go"
  31. import log "github.com/sirupsen/logrus"
  32. import "github.com/arnaucode/derosuite/p2p"
  33. import "github.com/arnaucode/derosuite/globals"
  34. import "github.com/arnaucode/derosuite/blockchain"
  35. //import "github.com/arnaucode/derosuite/checkpoints"
  36. import "github.com/arnaucode/derosuite/crypto"
  37. import "github.com/arnaucode/derosuite/crypto/ringct"
  38. import "github.com/arnaucode/derosuite/blockchain/rpcserver"
  39. //import "github.com/arnaucode/derosuite/address"
  40. var command_line string = `derod
  41. DERO : A secure, private blockchain with smart-contracts
  42. Usage:
  43. derod [--help] [--version] [--testnet] [--debug] [--disable-checkpoints] [--socks-proxy=<socks_ip:port>] [--p2p-bind-port=<18090>] [--add-exclusive-node=<ip:port>]...
  44. derod -h | --help
  45. derod --version
  46. Options:
  47. -h --help Show this screen.
  48. --version Show version.
  49. --testnet Run in testnet mode.
  50. --debug Debug mode enabled, print log messages
  51. --disable-checkpoints Disable checkpoints, work in truly async, slow mode 1 block at a time
  52. --socks-proxy=<socks_ip:port> Use a proxy to connect to network.
  53. --p2p-bind-port=<18090> p2p server listens on this port.
  54. --add-exclusive-node=<ip:port> Connect to this peer only (disabled for this version)`
  55. func main() {
  56. var err error
  57. globals.Arguments, err = docopt.Parse(command_line, nil, true, "DERO daemon : work in progress", false)
  58. if err != nil {
  59. log.Fatalf("Error while parsing options err: %s\n", err)
  60. }
  61. // We need to initialize readline first, so it changes stderr to ansi processor on windows
  62. l, err := readline.NewEx(&readline.Config{
  63. //Prompt: "\033[92mDERO:\033[32m»\033[0m",
  64. Prompt: "\033[92mDERO:\033[32m>>>\033[0m ",
  65. HistoryFile: filepath.Join(os.TempDir(), "derod_readline.tmp"),
  66. AutoComplete: completer,
  67. InterruptPrompt: "^C",
  68. EOFPrompt: "exit",
  69. HistorySearchFold: true,
  70. FuncFilterInputRune: filterInput,
  71. })
  72. if err != nil {
  73. panic(err)
  74. }
  75. defer l.Close()
  76. // parse arguments and setup testnet mainnet
  77. globals.Initialize() // setup network and proxy
  78. globals.Logger.Infof("") // a dummy write is required to fully activate logrus
  79. // all screen output must go through the readline
  80. globals.Logger.Out = l.Stdout()
  81. globals.Logger.Debugf("Arguments %+v", globals.Arguments)
  82. globals.Logger.Infof("DERO daemon : This version is under heavy development, use it for testing/evaluations purpose only")
  83. globals.Logger.Infof("Copyright 2017-2018 DERO Project. All rights reserved.")
  84. globals.Logger.Infof("OS:%s ARCH:%s GOMAXPROCS:%d", runtime.GOOS, runtime.GOARCH, runtime.GOMAXPROCS(0))
  85. globals.Logger.Infof("Daemon in %s mode", globals.Config.Name)
  86. params := map[string]interface{}{}
  87. params["--disable-checkpoints"] = globals.Arguments["--disable-checkpoints"].(bool)
  88. chain, _ := blockchain.Blockchain_Start(params)
  89. params["chain"] = chain
  90. p2p.P2P_Init(params)
  91. rpc, _ := rpcserver.RPCServer_Start(params)
  92. // This tiny goroutine continuously updates status as required
  93. go func() {
  94. last_our_height := uint64(0)
  95. last_best_height := uint64(0)
  96. last_peer_count := uint64(0)
  97. last_mempool_tx_count := 0
  98. for {
  99. if globals.Exit_In_Progress {
  100. return
  101. }
  102. our_height := chain.Get_Height()
  103. best_height := p2p.Best_Peer_Height()
  104. peer_count := p2p.Peer_Count()
  105. mempool_tx_count := len(chain.Mempool.Mempool_List_TX())
  106. // only update prompt if needed
  107. if last_our_height != our_height || last_best_height != best_height || last_peer_count != peer_count || last_mempool_tx_count != mempool_tx_count {
  108. // choose color based on urgency
  109. color := "\033[32m" // default is green color
  110. if our_height < best_height {
  111. color = "\033[33m" // make prompt yellow
  112. } else if our_height > best_height {
  113. color = "\033[31m" // make prompt red
  114. }
  115. pcolor := "\033[32m" // default is green color
  116. if peer_count < 1 {
  117. pcolor = "\033[31m" // make prompt red
  118. } else if peer_count <= 8 {
  119. pcolor = "\033[33m" // make prompt yellow
  120. }
  121. l.SetPrompt(fmt.Sprintf("\033[1m\033[32mDERO: \033[0m"+color+"%d/%d "+pcolor+"P %d TXp %d\033[32m>>>\033[0m ", our_height, best_height, peer_count, mempool_tx_count))
  122. l.Refresh()
  123. last_our_height = our_height
  124. last_best_height = best_height
  125. last_peer_count = peer_count
  126. last_mempool_tx_count = mempool_tx_count
  127. }
  128. time.Sleep(1 * time.Second)
  129. }
  130. }()
  131. setPasswordCfg := l.GenPasswordConfig()
  132. setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
  133. l.SetPrompt(fmt.Sprintf("Enter password(%v): ", len(line)))
  134. l.Refresh()
  135. return nil, 0, false
  136. })
  137. l.Refresh() // refresh the prompt
  138. for {
  139. line, err := l.Readline()
  140. if err == readline.ErrInterrupt {
  141. if len(line) == 0 {
  142. fmt.Print("Ctrl-C received, Exit in progress\n")
  143. globals.Exit_In_Progress = true
  144. break
  145. } else {
  146. continue
  147. }
  148. } else if err == io.EOF {
  149. break
  150. }
  151. line = strings.TrimSpace(line)
  152. line_parts := strings.Fields(line)
  153. command := ""
  154. if len(line_parts) >= 1 {
  155. command = strings.ToLower(line_parts[0])
  156. }
  157. switch {
  158. case strings.HasPrefix(line, "mode "):
  159. switch line[5:] {
  160. case "vi":
  161. l.SetVimMode(true)
  162. case "emacs":
  163. l.SetVimMode(false)
  164. default:
  165. println("invalid mode:", line[5:])
  166. }
  167. case line == "mode":
  168. if l.IsVimMode() {
  169. println("current mode: vim")
  170. } else {
  171. println("current mode: emacs")
  172. }
  173. case line == "login":
  174. pswd, err := l.ReadPassword("please enter your password: ")
  175. if err != nil {
  176. break
  177. }
  178. println("you enter:", strconv.Quote(string(pswd)))
  179. case line == "help":
  180. usage(l.Stderr())
  181. case line == "setpassword":
  182. pswd, err := l.ReadPasswordWithConfig(setPasswordCfg)
  183. if err == nil {
  184. println("you set:", strconv.Quote(string(pswd)))
  185. }
  186. case strings.HasPrefix(line, "setprompt"):
  187. if len(line) <= 10 {
  188. log.Println("setprompt <prompt>")
  189. break
  190. }
  191. l.SetPrompt(line[10:])
  192. case strings.HasPrefix(line, "say"):
  193. line := strings.TrimSpace(line[3:])
  194. if len(line) == 0 {
  195. log.Println("say what?")
  196. break
  197. }
  198. go func() {
  199. for range time.Tick(time.Second) {
  200. log.Println(line)
  201. }
  202. }()
  203. case command == "print_bc":
  204. log.Info("printing block chain")
  205. // first is starting point, second is ending point
  206. start := int64(0)
  207. stop := int64(0)
  208. if len(line_parts) != 3 {
  209. fmt.Printf("This function requires 2 parameters, start and endpoint\n")
  210. continue
  211. }
  212. if s, err := strconv.ParseInt(line_parts[1], 10, 64); err == nil {
  213. start = s
  214. } else {
  215. fmt.Printf("Invalid start value")
  216. continue
  217. }
  218. if s, err := strconv.ParseInt(line_parts[2], 10, 64); err == nil {
  219. stop = s
  220. } else {
  221. fmt.Printf("Invalid stop value")
  222. continue
  223. }
  224. if start < 0 || start >= int64(chain.Get_Height()) {
  225. fmt.Printf("Start value should be be between 0 and current height\n")
  226. continue
  227. }
  228. if start > stop && stop >= int64(chain.Get_Height()) {
  229. fmt.Printf("Stop value should be > start and current height\n")
  230. continue
  231. }
  232. fmt.Printf("Printing block chain from %d to %d\n", start, stop)
  233. for i := start; i < stop; i++ {
  234. // get block id at height
  235. current_block_id, err := chain.Load_BL_ID_at_Height(uint64(i))
  236. if err != nil {
  237. fmt.Printf("Skipping block at height %d due to error %s\n", i, err)
  238. continue
  239. }
  240. timestamp := chain.Load_Block_Timestamp(current_block_id)
  241. parent_block_id := chain.Load_Block_Parent_ID(current_block_id)
  242. // calculate difficulty
  243. //parent_cdiff := chain.Load_Block_Cumulative_Difficulty(parent_block_id)
  244. //block_cdiff := chain.Load_Block_Cumulative_Difficulty(current_block_id)
  245. diff := chain.Get_Difficulty_At_Block(parent_block_id)
  246. //size := chain.
  247. fmt.Printf("height: %10d, timestamp: %10d, difficulty: %12d\n", i, timestamp, diff)
  248. fmt.Printf("Block Id: %s , prev block id:%s\n", current_block_id, parent_block_id)
  249. fmt.Printf("\n")
  250. }
  251. case command == "print_block":
  252. fmt.Printf("printing block\n")
  253. if len(line_parts) == 2 && len(line_parts[1]) == 64 {
  254. txid, err := hex.DecodeString(strings.ToLower(line_parts[1]))
  255. if err != nil {
  256. fmt.Printf("err while decoding txid err %s\n", err)
  257. continue
  258. }
  259. var hash crypto.Hash
  260. copy(hash[:32], []byte(txid))
  261. fmt.Printf("block id: %s\n", hash[:])
  262. bl, err := chain.Load_BL_FROM_ID(hash)
  263. if err == nil {
  264. fmt.Printf("Block : %s\n", bl.Serialize())
  265. } else {
  266. fmt.Printf("Err %s\n", err)
  267. }
  268. } else if len(line_parts) == 2 {
  269. if s, err := strconv.ParseInt(line_parts[1], 10, 64); err == nil {
  270. // first load block id from height
  271. hash, err := chain.Load_BL_ID_at_Height(uint64(s))
  272. if err == nil {
  273. bl, err := chain.Load_BL_FROM_ID(hash)
  274. if err == nil {
  275. fmt.Printf("block id: %s\n", hash[:])
  276. fmt.Printf("Block : %s\n", bl.Serialize())
  277. json_bytes, err := json.Marshal(bl)
  278. fmt.Printf("%s err : %s\n", string(prettyprint_json(json_bytes)), err)
  279. } else {
  280. fmt.Printf("Err %s\n", err)
  281. }
  282. } else {
  283. fmt.Printf("err %s\n", err)
  284. }
  285. }
  286. } else {
  287. fmt.Printf("print_tx needs a single transaction id as arugument\n")
  288. }
  289. case command == "print_tx":
  290. if len(line_parts) == 2 && len(line_parts[1]) == 64 {
  291. txid, err := hex.DecodeString(strings.ToLower(line_parts[1]))
  292. if err != nil {
  293. fmt.Printf("err while decoding txid err %s\n", err)
  294. continue
  295. }
  296. var hash crypto.Hash
  297. copy(hash[:32], []byte(txid))
  298. tx, err := chain.Load_TX_FROM_ID(hash)
  299. if err == nil {
  300. s_bytes := tx.Serialize()
  301. fmt.Printf("tx : %x\n", s_bytes)
  302. json_bytes, err := json.MarshalIndent(tx, "", " ")
  303. _ = err
  304. fmt.Printf("%s\n", string(json_bytes))
  305. tx.RctSignature.Message = ringct.Key(tx.GetPrefixHash())
  306. ringct.Get_pre_mlsag_hash(tx.RctSignature)
  307. chain.Expand_Transaction_v2(tx)
  308. } else {
  309. fmt.Printf("Err %s\n", err)
  310. }
  311. } else {
  312. fmt.Printf("print_tx needs a single transaction id as arugument\n")
  313. }
  314. case strings.ToLower(line) == "diff":
  315. fmt.Printf("Network %s BH %d, Diff %d, NW Hashrate %0.03f MH/sec TH %s\n", globals.Config.Name, chain.Get_Height(), chain.Get_Difficulty(), float64(chain.Get_Network_HashRate())/1000000.0, chain.Get_Top_ID())
  316. case strings.ToLower(line) == "status":
  317. // fmt.Printf("chain diff %d\n",chain.Get_Difficulty_At_Block(chain.Top_ID))
  318. //fmt.Printf("chain nw rate %d\n", chain.Get_Network_HashRate())
  319. inc, out := p2p.Peer_Direction_Count()
  320. supply := chain.Load_Already_Generated_Coins_for_BL_ID(chain.Get_Top_ID())
  321. supply -= (2000000 * 1000000000000) // remove premine
  322. fmt.Printf("Network %s Height %d NW Hashrate %0.03f MH/sec TH %s Peers %d inc, %d out MEMPOOL size %d Total Circulating Supply %s DERO \n", globals.Config.Name, chain.Get_Height(), float64(chain.Get_Network_HashRate())/1000000.0, chain.Get_Top_ID(), inc, out, len(chain.Mempool.Mempool_List_TX()), globals.FormatMoney(supply))
  323. case strings.ToLower(line) == "sync_info":
  324. p2p.Connection_Print()
  325. case strings.ToLower(line) == "bye":
  326. fallthrough
  327. case strings.ToLower(line) == "exit":
  328. fallthrough
  329. case strings.ToLower(line) == "quit":
  330. goto exit
  331. case strings.ToLower(line) == "checkpoints": // save all knowns block id
  332. var block_id crypto.Hash
  333. name := "mainnet_checkpoints.dat"
  334. if !globals.IsMainnet() {
  335. name = "testnet_checkpoints.dat"
  336. }
  337. filename := filepath.Join(os.TempDir(), name)
  338. // create output filen
  339. f, err := os.Create(filename)
  340. if err != nil {
  341. globals.Logger.Warnf("error creating new file %s", err)
  342. continue
  343. }
  344. w := bufio.NewWriter(f)
  345. chain.Lock() // we do not want any reorgs during this op
  346. height := chain.Get_Height()
  347. for i := uint64(0); i < height; i++ {
  348. block_id, err = chain.Load_BL_ID_at_Height(i)
  349. if err != nil {
  350. break
  351. }
  352. w.Write(block_id[:])
  353. }
  354. if err != nil {
  355. globals.Logger.Warnf("error writing checkpoints err: %s", err)
  356. } else {
  357. globals.Logger.Infof("Successfully wrote %d checkpoints to file %s", height, filename)
  358. }
  359. w.Flush() // flush everything
  360. f.Close()
  361. chain.Unlock()
  362. case line == "sleep":
  363. log.Println("sleep 4 second")
  364. time.Sleep(4 * time.Second)
  365. case line == "":
  366. default:
  367. log.Println("you said:", strconv.Quote(line))
  368. }
  369. }
  370. exit:
  371. globals.Logger.Infof("Exit in Progress, Please wait")
  372. time.Sleep(100 * time.Millisecond) // give prompt update time to finish
  373. rpc.RPCServer_Stop()
  374. p2p.P2P_Shutdown() // shutdown p2p subsystem
  375. chain.Shutdown() // shutdown chain subsysem
  376. for globals.Subsystem_Active > 0 {
  377. time.Sleep(100 * time.Millisecond)
  378. }
  379. }
  380. func prettyprint_json(b []byte) []byte {
  381. var out bytes.Buffer
  382. err := json.Indent(&out, b, "", " ")
  383. _ = err
  384. return out.Bytes()
  385. }
  386. func usage(w io.Writer) {
  387. io.WriteString(w, "commands:\n")
  388. //io.WriteString(w, completer.Tree(" "))
  389. io.WriteString(w, "\t\033[1mhelp\033[0m\t\tthis help\n")
  390. io.WriteString(w, "\t\033[1mdiff\033[0m\t\tShow difficulty\n")
  391. io.WriteString(w, "\t\033[1mprint_bc\033[0m\tPrint blockchain info in a given blocks range, print_bc <begin_height> <end_height>\n")
  392. io.WriteString(w, "\t\033[1mprint_block\033[0m\tPrint block, print_block <block_hash> or <block_height>\n")
  393. io.WriteString(w, "\t\033[1mprint_height\033[0m\tPrint local blockchain height\n")
  394. io.WriteString(w, "\t\033[1mprint_tx\033[0m\tPrint transaction, print_tx <transaction_hash>\n")
  395. io.WriteString(w, "\t\033[1mstatus\033[0m\t\tShow genereal information\n")
  396. io.WriteString(w, "\t\033[1msync_info\033[0m\tPrint information about connected peers and their state\n")
  397. io.WriteString(w, "\t\033[1mbye\033[0m\t\tQuit the daemon\n")
  398. io.WriteString(w, "\t\033[1mexit\033[0m\t\tQuit the daemon\n")
  399. io.WriteString(w, "\t\033[1mquit\033[0m\t\tQuit the daemon\n")
  400. }
  401. var completer = readline.NewPrefixCompleter(
  402. /* readline.PcItem("mode",
  403. readline.PcItem("vi"),
  404. readline.PcItem("emacs"),
  405. ),
  406. readline.PcItem("login"),
  407. readline.PcItem("say",
  408. readline.PcItem("hello"),
  409. readline.PcItem("bye"),
  410. ),
  411. readline.PcItem("setprompt"),
  412. readline.PcItem("setpassword"),
  413. readline.PcItem("bye"),
  414. */
  415. readline.PcItem("help"),
  416. /* readline.PcItem("go",
  417. readline.PcItem("build", readline.PcItem("-o"), readline.PcItem("-v")),
  418. readline.PcItem("install",
  419. readline.PcItem("-v"),
  420. readline.PcItem("-vv"),
  421. readline.PcItem("-vvv"),
  422. ),
  423. readline.PcItem("test"),
  424. ),
  425. readline.PcItem("sleep"),
  426. */
  427. readline.PcItem("diff"),
  428. readline.PcItem("print_bc"),
  429. readline.PcItem("print_block"),
  430. readline.PcItem("print_height"),
  431. readline.PcItem("print_tx"),
  432. readline.PcItem("status"),
  433. readline.PcItem("sync_info"),
  434. readline.PcItem("bye"),
  435. readline.PcItem("exit"),
  436. readline.PcItem("quit"),
  437. )
  438. func filterInput(r rune) (rune, bool) {
  439. switch r {
  440. // block CtrlZ feature
  441. case readline.CharCtrlZ:
  442. return r, false
  443. }
  444. return r, true
  445. }