From f7d082615eee910ebce59ad974b20b824131c70c Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 15 Apr 2021 18:24:12 +0100 Subject: [PATCH] rpc: implement syncstatus JSON-RPC method --- internal/rpc/jsonrpc/methods.go | 54 ++++++++++++++++++++++++++- internal/rpc/jsonrpc/rpcserverhelp.go | 3 +- internal/rpchelp/helpdescs_en_US.go | 10 ++++- internal/rpchelp/methods.go | 3 +- rpc/jsonrpc/types/methods.go | 6 ++- rpc/jsonrpc/types/results.go | 9 ++++- spv/sync.go | 22 ++++++++++- wallet/wallet.go | 15 +++++++- 8 files changed, 114 insertions(+), 8 deletions(-) diff --git a/internal/rpc/jsonrpc/methods.go b/internal/rpc/jsonrpc/methods.go index cf5e7c02b..ece9cde45 100644 --- a/internal/rpc/jsonrpc/methods.go +++ b/internal/rpc/jsonrpc/methods.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -150,6 +150,7 @@ var handlers = map[string]handler{ "signrawtransactions": {fn: (*Server).signRawTransactions}, "stakepooluserinfo": {fn: (*Server).stakePoolUserInfo}, "sweepaccount": {fn: (*Server).sweepAccount}, + "syncstatus": {fn: (*Server).syncStatus}, "ticketinfo": {fn: (*Server).ticketInfo}, "ticketsforaddress": {fn: (*Server).ticketsForAddress}, "treasurypolicy": {fn: (*Server).treasuryPolicy}, @@ -1223,6 +1224,57 @@ func difficultyRatio(bits uint32, params *chaincfg.Params) float64 { return ratio } +// syncStatus handles a syncstatus request. +func (s *Server) syncStatus(ctx context.Context, icmd interface{}) (interface{}, error) { + w, ok := s.walletLoader.LoadedWallet() + if !ok { + return nil, errUnloadedWallet + } + n, err := w.NetworkBackend() + if err != nil { + return nil, err + } + + walletBestHash, walletBestHeight := w.MainChainTip(ctx) + bestBlock, err := w.BlockInfo(ctx, wallet.NewBlockIdentifierFromHash(&walletBestHash)) + if err != nil { + return nil, err + } + _24HoursAgo := time.Now().UTC().Add(-24 * time.Hour).Unix() + walletBestBlockTooOld := bestBlock.Timestamp < _24HoursAgo + + var synced bool + var targetHeight int32 + + if syncer, ok := n.(*spv.Syncer); ok { + synced = syncer.Synced() + targetHeight = syncer.EstimateMainChainTip() + } else if rpc, ok := n.(*dcrd.RPC); ok { + var chainInfo *dcrdtypes.GetBlockChainInfoResult + err := rpc.Call(ctx, "getblockchaininfo", &chainInfo) + if err != nil { + return nil, err + } + synced = chainInfo.Headers == int64(walletBestHeight) + targetHeight = int32(chainInfo.Headers) + } + + var headersFetchProgress float32 + blocksToFetch := targetHeight - walletBestHeight + if blocksToFetch <= 0 { + headersFetchProgress = 1 + } else { + totalHeadersToFetch := targetHeight - w.InitialHeight() + headersFetchProgress = 1 - (float32(blocksToFetch) / float32(totalHeadersToFetch)) + } + + return &types.SyncStatusResult{ + Synced: synced, + InitialBlockDownload: walletBestBlockTooOld, + HeadersFetchProgress: headersFetchProgress, + }, nil +} + // getInfo handles a getinfo request by returning a structure containing // information about the current state of the wallet. func (s *Server) getInfo(ctx context.Context, icmd interface{}) (interface{}, error) { diff --git a/internal/rpc/jsonrpc/rpcserverhelp.go b/internal/rpc/jsonrpc/rpcserverhelp.go index ec3dd5441..ab92e4932 100644 --- a/internal/rpc/jsonrpc/rpcserverhelp.go +++ b/internal/rpc/jsonrpc/rpcserverhelp.go @@ -84,6 +84,7 @@ func helpDescsEnUS() map[string]string { "signrawtransactions": "signrawtransactions [\"rawtx\",...] (send=true)\n\nSigns transaction inputs using private keys from this wallet and request for a list of transactions.\n\n\nArguments:\n1. rawtxs (array of string, required) A list of transactions to sign (and optionally send).\n2. send (boolean, optional, default=true) Set true to send the transactions after signing.\n\nResult:\n{\n \"results\": [{ (array of object) Returned values from the signrawtransactions command.\n \"signingresult\": { (object) Success or failure of signing.\n \"hex\": \"value\", (string) The resulting transaction encoded as a hexadecimal string\n \"complete\": true|false, (boolean) Whether all input signatures have been created\n \"errors\": [{ (array of object) Script verification errors (if exists)\n \"txid\": \"value\", (string) The transaction hash of the referenced previous output\n \"vout\": n, (numeric) The output index of the referenced previous output\n \"scriptSig\": \"value\", (string) The hex-encoded signature script\n \"sequence\": n, (numeric) Script sequence number\n \"error\": \"value\", (string) Verification or signing error related to the input\n },...], \n }, \n \"sent\": true|false, (boolean) Tells if the transaction was sent.\n \"txhash\": \"value\", (string) The hash of the signed tx.\n },...], \n} \n", "stakepooluserinfo": "stakepooluserinfo \"user\"\n\nGet user info for stakepool\n\nArguments:\n1. user (string, required) The id of the user to be looked up\n\nResult:\n{\n \"tickets\": [{ (array of object) A list of valid tickets that the user has added\n \"status\": \"value\", (string) The current status of the added ticket\n \"ticket\": \"value\", (string) The hash of the added ticket\n \"ticketheight\": n, (numeric) The height in which the ticket was added\n \"spentby\": \"value\", (string) The vote in which the ticket was spent\n \"spentbyheight\": n, (numeric) The height in which the ticket was spent\n },...], \n \"invalid\": [\"value\",...], (array of string) A list of invalid tickets that the user has added\n} \n", "sweepaccount": "sweepaccount \"sourceaccount\" \"destinationaddress\" (requiredconfirmations feeperkb)\n\nMoves as much value as possible in a transaction from an account.\n\n\nArguments:\n1. sourceaccount (string, required) The account to be swept.\n2. destinationaddress (string, required) The destination address to pay to.\n3. requiredconfirmations (numeric, optional) The minimum utxo confirmation requirement (optional).\n4. feeperkb (numeric, optional) The minimum relay fee policy (optional).\n\nResult:\n{\n \"unsignedtransaction\": \"value\", (string) The hex encoded string of the unsigned transaction.\n \"totalpreviousoutputamount\": n.nnn, (numeric) The total transaction input amount.\n \"totaloutputamount\": n.nnn, (numeric) The total transaction output amount.\n \"estimatedsignedsize\": n, (numeric) The estimated size of the transaction when signed.\n} \n", + "syncstatus": "syncstatus\n\nReturns information about this wallet's synchronization to the network.\n\nArguments:\nNone\n\nResult:\n{\n \"synced\": true|false, (boolean) Whether or not the wallet is fully caught up to the network.\n \"initialblockdownload\": true|false, (boolean) Best guess of whether this wallet is in the initial block download mode used to catch up the blockchain when it is far behind.\n \"headersfetchprogress\": n.nnn, (numeric) Estimated progress of the headers fetching stage of the current sync process.\n} \n", "ticketinfo": "ticketinfo (startheight=0)\n\nReturns details of each wallet ticket transaction\n\nArguments:\n1. startheight (numeric, optional, default=0) Specify the starting block height to scan from\n\nResult:\n[{\n \"hash\": \"value\", (string) Transaction hash of the ticket\n \"cost\": n.nnn, (numeric) Amount paid to purchase the ticket; this may be greater than the ticket price at time of purchase\n \"votingaddress\": \"value\", (string) Address of 0th output, which describes the requirements to spend the ticket\n \"status\": \"value\", (string) Description of ticket status (unknown, unmined, immature, mature, live, voted, missed, expired, unspent, revoked)\n \"blockhash\": \"value\", (string) Hash of block ticket is mined in\n \"blockheight\": n, (numeric) Height of block ticket is mined in\n \"vote\": \"value\", (string) Transaction hash of vote which spends the ticket\n \"revocation\": \"value\", (string) Transaction hash of revocation which spends the ticket\n \"choices\": [{ (array of object) Vote preferences set for the ticket\n \"agendaid\": \"value\", (string) The ID for the agenda the choice concerns\n \"agendadescription\": \"value\", (string) A description of the agenda the choice concerns\n \"choiceid\": \"value\", (string) The ID of the current choice for this agenda\n \"choicedescription\": \"value\", (string) A description of the current choice for this agenda\n },...], \n},...]\n", "ticketsforaddress": "ticketsforaddress \"address\"\n\nRequest all the tickets for an address.\n\nArguments:\n1. address (string, required) Address to look for.\n\nResult:\ntrue|false (boolean) Tickets owned by the specified address.\n", "treasurypolicy": "treasurypolicy (\"key\")\n\nReturn voting policies for treasury spend transactions by key\n\nArguments:\n1. key (string, optional) Return the policy for a particular key\n\nResult (no key provided):\n[{\n \"key\": \"value\", (string) Treasury key associated with a policy\n \"policy\": \"value\", (string) Voting policy description (abstain, yes, or no)\n},...]\n\nResult (key specified):\n{\n \"key\": \"value\", (string) Treasury key associated with a policy\n \"policy\": \"value\", (string) Voting policy description (abstain, yes, or no)\n} \n", @@ -106,4 +107,4 @@ var localeHelpDescs = map[string]func() map[string]string{ "en_US": helpDescsEnUS, } -var requestUsages = "abandontransaction \"hash\"\naccountaddressindex \"account\" branch\naccountsyncaddressindex \"account\" branch index\naccountunlocked \"account\"\naddmultisigaddress nrequired [\"key\",...] (\"account\")\naddtransaction \"blockhash\" \"transaction\"\nauditreuse (since)\nconsolidate inputs (\"account\" \"address\")\ncreatemultisig nrequired [\"key\",...]\ncreatenewaccount \"account\"\ncreaterawtransaction [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...] {\"address\":amount,...} (locktime expiry)\ncreatesignature \"address\" inputindex hashtype \"previouspkscript\" \"serializedtransaction\"\ndiscoverusage (\"startblock\" discoveraccounts gaplimit)\ndumpprivkey \"address\"\nfundrawtransaction \"hexstring\" \"fundaccount\" ({\"changeaddress\":changeaddress,\"feerate\":feerate,\"conftarget\":conftarget})\ngeneratevote \"blockhash\" height \"tickethash\" votebits \"votebitsext\"\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblock\ngetbestblockhash\ngetblockcount\ngetblockhash index\ngetcoinjoinsbyacct\ngetinfo\ngetmasterpubkey (\"account\")\ngetmultisigoutinfo \"hash\" index\ngetnewaddress (\"account\" \"gappolicy\")\ngetpeerinfo\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngetstakeinfo\ngettickets includeimmature\ngettransaction \"txid\" (includewatchonly=false)\ngetunconfirmedbalance (\"account\")\ngetvotechoices (\"tickethash\")\ngetwalletfee\nhelp (\"command\")\nimportcfiltersv2 startheight [\"filter\",...]\nimportprivkey \"privkey\" (\"label\" rescan=true scanfrom)\nimportscript \"hex\" (rescan=true scanfrom)\nimportxpub \"name\" \"xpub\"\nlistaccounts (minconf=1)\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nlistlockunspent (\"account\")\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...] \"account\")\nlockaccount \"account\"\nlockunspent unlock [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\nmixaccount\nmixoutput \"outpoint\"\npurchaseticket \"fromaccount\" spendlimit (minconf=1 \"ticketaddress\" numtickets=1 \"pooladdress\" poolfees expiry \"comment\" dontsigntx)\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nrenameaccount \"oldaccount\" \"newaccount\"\nrescanwallet (beginheight=0)\nrevoketickets\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendfromtreasury \"key\" amounts\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendrawtransaction \"hextx\" (allowhighfees=false)\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsendtotreasury amount\nsetaccountpassphrase \"account\" \"passphrase\"\nsettreasurypolicy \"key\" \"policy\"\nsettspendpolicy \"hash\" \"policy\"\nsettxfee amount\nsetvotechoice \"agendaid\" \"choiceid\" (\"tickethash\")\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nstakepooluserinfo \"user\"\nsweepaccount \"sourceaccount\" \"destinationaddress\" (requiredconfirmations feeperkb)\nticketinfo (startheight=0)\nticketsforaddress \"address\"\ntreasurypolicy (\"key\")\ntspendpolicy (\"hash\")\nunlockaccount \"account\" \"passphrase\"\nvalidateaddress \"address\"\nvalidatepredcp0005cf\nverifymessage \"address\" \"signature\" \"message\"\nversion\nwalletinfo\nwalletislocked\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\nwalletpubpassphrasechange \"oldpassphrase\" \"newpassphrase\"" +var requestUsages = "abandontransaction \"hash\"\naccountaddressindex \"account\" branch\naccountsyncaddressindex \"account\" branch index\naccountunlocked \"account\"\naddmultisigaddress nrequired [\"key\",...] (\"account\")\naddtransaction \"blockhash\" \"transaction\"\nauditreuse (since)\nconsolidate inputs (\"account\" \"address\")\ncreatemultisig nrequired [\"key\",...]\ncreatenewaccount \"account\"\ncreaterawtransaction [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...] {\"address\":amount,...} (locktime expiry)\ncreatesignature \"address\" inputindex hashtype \"previouspkscript\" \"serializedtransaction\"\ndiscoverusage (\"startblock\" discoveraccounts gaplimit)\ndumpprivkey \"address\"\nfundrawtransaction \"hexstring\" \"fundaccount\" ({\"changeaddress\":changeaddress,\"feerate\":feerate,\"conftarget\":conftarget})\ngeneratevote \"blockhash\" height \"tickethash\" votebits \"votebitsext\"\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblock\ngetbestblockhash\ngetblockcount\ngetblockhash index\ngetcoinjoinsbyacct\ngetinfo\ngetmasterpubkey (\"account\")\ngetmultisigoutinfo \"hash\" index\ngetnewaddress (\"account\" \"gappolicy\")\ngetpeerinfo\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngetstakeinfo\ngettickets includeimmature\ngettransaction \"txid\" (includewatchonly=false)\ngetunconfirmedbalance (\"account\")\ngetvotechoices (\"tickethash\")\ngetwalletfee\nhelp (\"command\")\nimportcfiltersv2 startheight [\"filter\",...]\nimportprivkey \"privkey\" (\"label\" rescan=true scanfrom)\nimportscript \"hex\" (rescan=true scanfrom)\nimportxpub \"name\" \"xpub\"\nlistaccounts (minconf=1)\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nlistlockunspent (\"account\")\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...] \"account\")\nlockaccount \"account\"\nlockunspent unlock [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\nmixaccount\nmixoutput \"outpoint\"\npurchaseticket \"fromaccount\" spendlimit (minconf=1 \"ticketaddress\" numtickets=1 \"pooladdress\" poolfees expiry \"comment\" dontsigntx)\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nrenameaccount \"oldaccount\" \"newaccount\"\nrescanwallet (beginheight=0)\nrevoketickets\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendfromtreasury \"key\" amounts\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendrawtransaction \"hextx\" (allowhighfees=false)\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsendtotreasury amount\nsetaccountpassphrase \"account\" \"passphrase\"\nsettreasurypolicy \"key\" \"policy\"\nsettspendpolicy \"hash\" \"policy\"\nsettxfee amount\nsetvotechoice \"agendaid\" \"choiceid\" (\"tickethash\")\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nstakepooluserinfo \"user\"\nsweepaccount \"sourceaccount\" \"destinationaddress\" (requiredconfirmations feeperkb)\nsyncstatus\nticketinfo (startheight=0)\nticketsforaddress \"address\"\ntreasurypolicy (\"key\")\ntspendpolicy (\"hash\")\nunlockaccount \"account\" \"passphrase\"\nvalidateaddress \"address\"\nvalidatepredcp0005cf\nverifymessage \"address\" \"signature\" \"message\"\nversion\nwalletinfo\nwalletislocked\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\nwalletpubpassphrasechange \"oldpassphrase\" \"newpassphrase\"" diff --git a/internal/rpchelp/helpdescs_en_US.go b/internal/rpchelp/helpdescs_en_US.go index bd500ecaf..4af499b20 100644 --- a/internal/rpchelp/helpdescs_en_US.go +++ b/internal/rpchelp/helpdescs_en_US.go @@ -1,5 +1,5 @@ // Copyright (c) 2015 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -191,6 +191,14 @@ var helpDescsEnUS = map[string]string{ "getblockhash-index": "The block height", "getblockhash--result0": "The main chain block hash", + // SyncStatusCmd help. + "syncstatus--synopsis": "Returns information about this wallet's synchronization to the network.", + + // SyncStatusResult help. + "syncstatusresult-synced": "Whether or not the wallet is fully caught up to the network.", + "syncstatusresult-initialblockdownload": "Best guess of whether this wallet is in the initial block download mode used to catch up the blockchain when it is far behind.", + "syncstatusresult-headersfetchprogress": "Estimated progress of the headers fetching stage of the current sync process.", + // GetInfoCmd help. "getinfo--synopsis": "Returns a JSON object containing various state info.", diff --git a/internal/rpchelp/methods.go b/internal/rpchelp/methods.go index c4b0dc57a..35dc9e889 100644 --- a/internal/rpchelp/methods.go +++ b/internal/rpchelp/methods.go @@ -1,5 +1,5 @@ // Copyright (c) 2015 The btcsuite developers -// Copyright (c) 2015-2017 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -107,6 +107,7 @@ var Methods = []struct { {"signrawtransactions", []interface{}{(*types.SignRawTransactionsResult)(nil)}}, {"stakepooluserinfo", []interface{}{(*types.StakePoolUserInfoResult)(nil)}}, {"sweepaccount", []interface{}{(*types.SweepAccountResult)(nil)}}, + {"syncstatus", []interface{}{(*types.SyncStatusResult)(nil)}}, {"ticketinfo", []interface{}{(*[]types.TicketInfoResult)(nil)}}, {"ticketsforaddress", returnsBool}, {"treasurypolicy", []interface{}{(*[]types.TreasuryPolicyResult)(nil), (*types.TreasuryPolicyResult)(nil)}}, diff --git a/rpc/jsonrpc/types/methods.go b/rpc/jsonrpc/types/methods.go index 0f6f3f1eb..392783915 100644 --- a/rpc/jsonrpc/types/methods.go +++ b/rpc/jsonrpc/types/methods.go @@ -1,5 +1,5 @@ // Copyright (c) 2014 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -1048,6 +1048,9 @@ func NewSweepAccountCmd(sourceAccount string, destinationAddress string, require } } +// SyncStatusCmd defines the syncstatus JSON-RPC command. +type SyncStatusCmd struct{} + // WalletInfoCmd defines the walletinfo JSON-RPC command. type WalletInfoCmd struct { } @@ -1246,6 +1249,7 @@ func init() { {"signrawtransactions", (*SignRawTransactionsCmd)(nil)}, {"stakepooluserinfo", (*StakePoolUserInfoCmd)(nil)}, {"sweepaccount", (*SweepAccountCmd)(nil)}, + {"syncstatus", (*SyncStatusCmd)(nil)}, {"ticketinfo", (*TicketInfoCmd)(nil)}, {"treasurypolicy", (*TreasuryPolicyCmd)(nil)}, {"tspendpolicy", (*TSpendPolicyCmd)(nil)}, diff --git a/rpc/jsonrpc/types/results.go b/rpc/jsonrpc/types/results.go index d63c7bbe1..15222c9a6 100644 --- a/rpc/jsonrpc/types/results.go +++ b/rpc/jsonrpc/types/results.go @@ -1,5 +1,5 @@ // Copyright (c) 2014 The btcsuite developers -// Copyright (c) 2015-2019 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -161,6 +161,13 @@ type GetVoteChoicesResult struct { Choices []VoteChoice `json:"choices"` } +// SyncStatusResult models the data returned by the syncstatus command. +type SyncStatusResult struct { + Synced bool `json:"synced"` + InitialBlockDownload bool `json:"initialblockdownload"` + HeadersFetchProgress float32 `json:"headersfetchprogress"` +} + // InfoResult models the data returned by the wallet server getinfo // command. type InfoResult struct { diff --git a/spv/sync.go b/spv/sync.go index 417aaafee..7136d9fd9 100644 --- a/spv/sync.go +++ b/spv/sync.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2020 The Decred developers +// Copyright (c) 2018-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -149,6 +149,26 @@ func (s *Syncer) synced() { } } +// Synced returns whether this wallet is completely synced to the network. +func (s *Syncer) Synced() bool { + return atomic.LoadUint32(&s.atomicWalletSynced) == 1 +} + +// EstimateMainChainTip returns an estimated height for the current tip of the +// blockchain. The estimate is made by comparing the initial height reported by +// all connected peers and the wallet's current tip. The highest of these values +// is estimated to be the mainchain's tip height. +func (s *Syncer) EstimateMainChainTip() int32 { + _, chainTip := s.wallet.MainChainTip(context.Background()) + s.forRemotes(func(rp *p2p.RemotePeer) error { + if rp.InitialHeight() > chainTip { + chainTip = rp.InitialHeight() + } + return nil + }) + return chainTip +} + // GetRemotePeers returns a map of connected remote peers. func (s *Syncer) GetRemotePeers() map[string]*p2p.RemotePeer { s.remotesMu.Lock() diff --git a/wallet/wallet.go b/wallet/wallet.go index 2ac477945..e5db49b97 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1,5 +1,5 @@ // Copyright (c) 2013-2016 The btcsuite developers -// Copyright (c) 2015-2020 The Decred developers +// Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -113,6 +113,11 @@ type Wallet struct { gapLimit uint32 accountGapLimit int + // initialHeight is the wallet's tip height prior to syncing with the + // network. Useful for calculating or estimating headers fetch progress + // during sync if the target header height is known or can be estimated. + initialHeight int32 + networkBackend NetworkBackend networkBackendMu sync.Mutex @@ -638,6 +643,11 @@ func (w *Wallet) SetRelayFee(relayFee dcrutil.Amount) { w.relayFeeMu.Unlock() } +// InitialHeight is the wallet's tip height prior to syncing with the network. +func (w *Wallet) InitialHeight() int32 { + return w.initialHeight +} + // MainChainTip returns the hash and height of the tip-most block in the main // chain that the wallet is synchronized to. func (w *Wallet) MainChainTip(ctx context.Context) (hash chainhash.Hash, height int32) { @@ -5343,6 +5353,9 @@ func Open(ctx context.Context, cfg *Config) (*Wallet, error) { // Amounts w.relayFee = cfg.RelayFee + // Record current tip as initialHeight. + _, w.initialHeight = w.MainChainTip(ctx) + return w, nil }