Compare commits

...

5 commits

Author SHA1 Message Date
Roy Lee 04763e36f0 doc: update README.md 2022-10-31 21:21:45 -07:00
Roy Lee ca56a420ee rpc: implement rescanblockchain 2022-10-31 19:53:16 -07:00
Roy Lee 67c9c48940 wallet: break recovery() to recovery() and rescanblockchain()
Now the recovery, which runs at startup, only scans for known
addresses that were generated and recorded by this wallet.

The coming rescanblockchain RPC implementation, which requires the
wallet to be unlocked, does account discovery.
2022-10-31 19:53:16 -07:00
Roy Lee c6842ef6fb go mod: update lbcd to v0.22.118 2022-10-31 19:53:16 -07:00
Roy Lee 1fa143fa0e wallet: update passphrase user experience.
For users don't want to set/manage a passphrase a default passphrase
"passphrase" will be used during wallet creation.

At startup, the wallet tries to unlock itself using the default
passphrase, or a user provided one (using -p).

Users that prefer a passphrase can override the default one at wallet
creation time using -p, or use the walletpassphrase rpc when
the wallet is running. This will prevent the wallet from auto-unlock,
and preserve the lock-by-default behavior.
2022-10-31 19:53:16 -07:00
11 changed files with 136 additions and 181 deletions

167
README.md
View file

@ -1,53 +1,18 @@
# lbcwallet
lbcwallet is a daemon, which provides lbry wallet functionality for a
single user.
Public and private keys are derived using the hierarchical
deterministic format described by
[BIP0032](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki).
Unencrypted private keys are not supported and are never written to disk.
lbcwallet uses the `m/44'/<coin type>'/<account>'/<branch>/<address index>`
HD path for all derived addresses, as described by
[BIP0044](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki).
Due to the sensitive nature of public data in a BIP0032 wallet,
lbcwallet provides the option of encrypting not just private keys, but
public data as well. This is intended to thwart privacy risks where a
wallet file is compromised without exposing all current and future
addresses (public keys) managed by the wallet. While access to this
information would not allow an attacker to spend or steal coins, it
does mean they could track all transactions involving your addresses
and therefore know your exact balance. In a future release, public data
encryption will extend to transactions as well.
The JSON-RPC server exists to ease the migration of wallet applications
from Core, but complete compatibility is not guaranteed. Some portions of
the API (and especially accounts) have to work differently due to other
design decisions (mostly due to BIP0044). However, if you find a
compatibility issue and feel that it could be reasonably supported, please
report an issue. This server is enabled by default.
lbcwallet implements HD Wallet functionality which conforms to
[BIP0032](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki),
[BIP0043](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki),
and [BIP0044](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki).
## Security
We take security seriously. Please contact [security](mailto:security@lbry.com) regarding any security issues.
Our PGP key is [here](https://lbry.com/faq/pgp-key) if you need it.
## Requirements
## Build from Source Code
- [Go](http://golang.org) 1.16 or newer.
- `lbcwallet` is not an SPV client and requires connecting to a local or remote
`lbcd` for asynchronous blockchain queries and notifications over websockets.
Full installation instructions can be found [here](https://github.com/lbryio/lbcd).
## To Build lbcwallet, lbcd, and lbcctl from Source
Install Go according to its [installation instructions](http://golang.org/doc/install).
Build `lbcwallet`
Requires [Go](http://golang.org) 1.19 or newer. Install Go according to its [installation instructions](http://golang.org/doc/install).
``` sh
git clone https://github.com/lbryio/lbcwallet
@ -55,112 +20,62 @@ cd lbcwallet
go build .
```
To make the quick start guide self-contained, here's how we can build the `lbcd` and `lbcctl`
## **lbcd** & **lbcwallet**
``` sh
git clone https://github.com/lbryio/lbcd
cd lbcd
`lbcwallet` is not an SPV client and requires connecting to a `lbcd` node for asynchronous blockchain queries and notifications over websockets.
# build lbcd
go build .
lbcwallet can serve wallet related RPCs and proxy lbcd RPCs to the assocated lbcd. It's sufficient for a user to connect just the **lbcwallet** instead of both.
# build lbcctl
go build ./cmd/lbcctl
``` mermaid
sequenceDiagram
actor C as lbcctl
participant W as lbcwallet (port: 9244)
participant D as lbcd (port: 9245)
rect rgb(200,200,200)
Note over C,W: lbcctl --wallet balance
C ->>+ W: getbalance
W -->>- C: response
end
rect rgb(200,200,200)
Note over C,D: lbcctl --wallet getblockcount (lbcd RPC service proxied by lbcwallet)
C ->>+ W: getblockcount
W ->>+ D: getblockcount
D -->>- W: response
W -->>- C: response
end
```
## Getting Started
The first time running the `lbcwallet` we need to create a new wallet.
Create a new wallet with a randomly generated seed or an existing one.
``` sh
./lbcwallet --create
lbcwallet --create
Do you have an existing wallet seed you want to use? (n/no/y/yes) [no]: no
Your wallet generation seed is: 3d005498ad5e9b7439b857249e328ec34e21845b7d1a7d2a5641d4050c02d0da
```
Start a local instance of `lbcd` and have the `lbcwallet` connecting to it.
The created wallet protects the seed with a default passphrase (`"passphrase"`), which can be override with `-p` option:
``` sh
# Start a lbcd with its RPC credentials
./lbcd --txindex --rpcuser=rpcuser --rpcpass=rpcpass
# Start a lbcwallet with its RPC credentials along with the lbcd's RPC credentials
# The default lbcd instance to conect to is already localhost:9245 so we don't need to specify it explicitly here.
./lbcwallet --rpcuser=rpcuser --rpcpass=rpcpass # --rpcconnect=localhost:9245
#
# rpcuser/rpcpass rpcuser/rpcpass
# lbcctl <-------------------> lbcwallet <--------------------> lbcd
# RPC port 9244 RPC port 9245
#
lbcwallet --create -p my-passphrase
```
Start wallet server, and connect it to a lbcd instance.
``` sh
./lbcd --txindex --rpcuser=rpcuser --rpcpass=rpcpass
./lbcwallet --rpcuser=rpcuser --rpcpass=rpcpass
#
# rpcuser/rpcpass rpcuser/rpcpass
# lbcctl <-------------------> lbcwallet <--------------------> lbcd
# RPC port 9244 RPC port 9245
#
lbcwallet --rpcuser=rpcuser --rpcpass=rpcpass # --rpcconnect=localhost:9245
```
Note:
At startup, the wallet will try to unlock itself with the default passphrase (`passphrase`) or an user provided one (using `-p` option).
- `lbcd` and `lbcwallet` implements two disjoint sets of RPCs.
- `lbcd` serves RPC on port 9245 while `lbcwallet` on port 9244.
- `lbcwallet` can proxy non-wallet RPCs to its associated `lbcd`.
Examples of using `lbcctl` to interact with the setup via RPCs:
1. Calling non-wallet RPC directly on lbcd:
``` sh
./lbcctl --rpcuser=rpcuser --rpcpass=rpcpass getblockcount
#
# lbcctl <-- getblockcount() --> lbcd
# RPC port 9245 (handled)
#
```
2. Calling wallet RPC on lbcwallet (using `--wallet`)
``` sh
./lbcctl --rpcuser=rpcuser --rpcpass=rpcpass --wallet getbalance
#
# lbcctl <-- getbalance() --> lbcwallet
# RPC port 9244 (handled)
#
```
3. Calling non-wallet RPC on lbcwallet, which proxies it to lbcd:
``` sh
./lbcctl --rpcuser=rpcuser --rpcpass=rpcpass --wallet getblockcount
#
# lbcctl <-- getblockcount() --> lbcwallet <-- getblockcount() --> lbcd
# RPC port 9244 (proxied) RPC port 9245
#
```
## Default Network and RPC Ports
| Instance | mainnet | testet | regtest |
| ------------- | ------- | ------ | ------- |
| lbcd Network | 9246 | 19246 | 29246 |
| lbcd RPC | 9245 | 19245 | 29245 |
| lbcwallet RPC | 9244 | 19244 | 29244 |
Examples
If the passphrase does not match, the wallet remains locked. User can lock/unlock the wallet using `walletlock` and `walletpassphrase` RPCs.
``` sh
./lbcctl getblockcount # port 9245
./lbcctl --wallet getblockcount # port 9244
./lbcctl --testnet getblockcount # port 19245
./lbcctl --wallet --regtest getblockcount # port 29244
lbcwallet --rpcuser=rpcuser --rpcpass=rpcpass -p my_passphrase
```
## Contributing

View file

@ -58,7 +58,7 @@ type config struct {
DBTimeout time.Duration `long:"dbtimeout" description:"The timeout value to use when opening the wallet database."`
// Passphrase options
Passphrase string `short:"p" long:"passphrase" default-mask:"-" description:"The wallet passphrase (default: \"insecurepassphrase\")"`
Passphrase string `short:"p" long:"passphrase" default-mask:"-" description:"The wallet passphrase (default: \"passphrase\")"`
// RPC client options
RPCConnect string `short:"c" long:"rpcconnect" description:"Hostname/IP and port of lbcd RPC server to connect to (default localhost:9245, testnet: localhost:19245, regtest: localhost:29245)"`

2
go.mod
View file

@ -6,7 +6,7 @@ require (
github.com/davecgh/go-spew v1.1.1
github.com/jessevdk/go-flags v1.5.0
github.com/jrick/logrotate v1.0.0
github.com/lbryio/lbcd v0.22.115
github.com/lbryio/lbcd v0.22.118
github.com/lbryio/lbcutil v1.0.202
github.com/lightningnetwork/lnd/clock v1.1.0
github.com/stretchr/testify v1.7.1

4
go.sum
View file

@ -187,8 +187,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/lbryio/lbcd v0.22.115 h1:C8Jy9UYeU5rONfxKZHwgDoEdCpJVWcLf+kCJLx7UiHU=
github.com/lbryio/lbcd v0.22.115/go.mod h1:YZ2Vi4khEheO7hllkWhDdScXmHhXCBzK4xIQcVDcozs=
github.com/lbryio/lbcd v0.22.118 h1:q3HAwCKdINJE2Tj5FrjmSfltSuiqSB5gnuSDAAQVt8A=
github.com/lbryio/lbcd v0.22.118/go.mod h1:YZ2Vi4khEheO7hllkWhDdScXmHhXCBzK4xIQcVDcozs=
github.com/lbryio/lbcutil v1.0.202 h1:L0aRMs2bdCUAicD8Xe4NmUEvevDDea3qkIpCSACnftI=
github.com/lbryio/lbcutil v1.0.202/go.mod h1:LGPtVBBzh4cFXfLFb8ginlFcbA2QwumLNFd0yk/as2o=
github.com/lightningnetwork/lnd/clock v1.1.0 h1:/yfVAwtPmdx45aQBoXQImeY7sOIEr7IXlImRMBOZ7GQ=

View file

@ -348,6 +348,15 @@ var helpDescsEnUS = map[string]string{
"renameaccount-oldaccount": "The old account name to rename.",
"renameaccount-newaccount": "The new name for the account.",
// RescanBlockchainCmd help.
"rescanblockchain--synopsis": "Renames an account.",
"rescanblockchain-startheight": "Block height where the rescan should start.",
"rescanblockchain-stopheight": "The last block height that should be scanned. If none is provided it will rescan up to the tip at return time of this call.",
// RescanblockchainResult help.
"rescanblockchainresult-start_height": "The block height where the rescan started (the requested height or 0)",
"rescanblockchainresult-stop_height": "The height of the last rescanned block.",
// SendFromCmd help.
"sendfrom--synopsis": "Authors, signs, and sends a transaction that outputs some amount to a payment address.\n" +
"A change output is automatically included to send extra output value back to the original account.",

View file

@ -70,6 +70,7 @@ var Methods = []struct {
{"listaddresstransactions", returnsLTRArray},
{"listalltransactions", returnsLTRArray},
{"renameaccount", nil},
{"rescanblockchain", []interface{}{(*btcjson.RescanBlockchainResult)(nil)}},
{"walletislocked", returnsBool},
}

View file

@ -85,6 +85,11 @@ func walletMain() error {
loader.RunAfterLoad(func(w *wallet.Wallet) {
startWalletRPCServices(w, legacyRPCServer)
log.Infof("Unlocking wallet with the default or specified passphrase...")
err = w.Unlock([]byte(cfg.Passphrase), nil)
if err != nil {
log.Infof("Unable to unlock wallet: %v", err)
}
})
_, err = loader.OpenExistingWallet()

View file

@ -102,6 +102,7 @@ var rpcHandlers = map[string]struct {
"listunspent": {handler: listUnspent},
"lockunspent": {handler: lockUnspent},
"sendfrom": {handlerWithChain: sendFrom},
"rescanblockchain": {handlerWithChain: rescanBlockchain},
"sendmany": {handler: sendMany},
"sendtoaddress": {handler: sendToAddress},
"settxfee": {handler: setTxFee},
@ -1585,6 +1586,49 @@ func makeOutputs(pairs map[string]btcutil.Amount, chainParams *chaincfg.Params)
return outputs, nil
}
// rescanBlockchain handles a rescanblockhain RPC request.
func rescanBlockchain(icmd interface{}, w *wallet.Wallet,
chainClient *chain.RPCClient) (interface{}, error) {
cmd := icmd.(*btcjson.RescanBlockchainCmd)
_, bestHeight, err := chainClient.GetBestBlock()
if err != nil {
return nil, err
}
startHeight := *cmd.StartHeight
if startHeight < 0 || startHeight > bestHeight {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: "Invalid start height",
}
}
// Scan to the best block if no stopHeight is specified.
stopHeight := bestHeight
if cmd.StopHeight != nil {
stopHeight = *cmd.StopHeight
}
if stopHeight < 0 || stopHeight > bestHeight {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: "Invalid stop height",
}
}
startHeight, stopHeight, err = w.RescanBlockchain(chainClient,
startHeight, stopHeight)
if err != nil {
return nil, fmt.Errorf("rescanblockchain: %w", err)
}
ret := btcjson.RescanBlockchainResult{
StartHeight: startHeight,
StoptHeight: stopHeight,
}
return ret, nil
}
// sendPairs creates and sends payment transactions.
// It returns the transaction hash in string format upon success
// All errors are returned in btcjson.RPCError format

View file

@ -48,6 +48,7 @@ func helpDescsEnUS() map[string]string {
"listaddresstransactions": "listaddresstransactions [\"address\",...] (account=\"default\")\n\nReturns a JSON array of objects containing verbose details for wallet transactions pertaining some addresses.\n\nArguments:\n1. addresses (array of string, required) Addresses to filter transaction results by.\n2. account (string, optional, default=\"default\") Account to filter transactions results by. Defaults to 'default'.\n\nResult:\n[{\n \"abandoned\": true|false, (boolean) Unset.\n \"account\": \"value\", (string) The account name associated with the transaction.\n \"address\": \"value\", (string) Payment address for a transaction output.\n \"amount\": n.nnn, (numeric) The value of the transaction output valued in LBC.\n \"bip125-replaceable\": \"value\", (string) Unset.\n \"blockhash\": \"value\", (string) The hash of the block this transaction is mined in, or the empty string if unmined.\n \"blockheight\": n, (numeric) The block height containing the transaction.\n \"blockindex\": n, (numeric) Unset.\n \"blocktime\": n, (numeric) The Unix time of the block header this transaction is mined in, or 0 if unmined.\n \"category\": \"value\", (string) The kind of transaction: \"send\" for sent transactions, \"immature\" for immature coinbase outputs, \"generate\" for mature coinbase outputs, or \"recv\" for all other received outputs. Note: A single output may be included multiple times under different categories\n \"confirmations\": n, (numeric) The number of block confirmations of the transaction.\n \"fee\": n.nnn, (numeric) The total input value minus the total output value for sent transactions.\n \"generated\": true|false, (boolean) Whether the transaction output is a coinbase output.\n \"involveswatchonly\": true|false, (boolean) Unset.\n \"label\": \"value\", (string) A comment for the address/transaction, if any.\n \"time\": n, (numeric) The earliest Unix time this transaction was known to exist.\n \"timereceived\": n, (numeric) The earliest Unix time this transaction was known to exist.\n \"trusted\": true|false, (boolean) Unset.\n \"txid\": \"value\", (string) The hash of the transaction.\n \"vout\": n, (numeric) The transaction output index.\n \"walletconflicts\": [\"value\",...], (array of string) Unset.\n \"comment\": \"value\", (string) Unset.\n \"otheraccount\": \"value\", (string) Unset.\n},...]\n",
"listalltransactions": "listalltransactions (account=\"default\")\n\nReturns a JSON array of objects in the same format as 'listtransactions' without limiting the number of returned objects.\n\nArguments:\n1. account (string, optional, default=\"default\") Account to filter transactions results by. Defaults to 'default'.\n\nResult:\n[{\n \"abandoned\": true|false, (boolean) Unset.\n \"account\": \"value\", (string) The account name associated with the transaction.\n \"address\": \"value\", (string) Payment address for a transaction output.\n \"amount\": n.nnn, (numeric) The value of the transaction output valued in LBC.\n \"bip125-replaceable\": \"value\", (string) Unset.\n \"blockhash\": \"value\", (string) The hash of the block this transaction is mined in, or the empty string if unmined.\n \"blockheight\": n, (numeric) The block height containing the transaction.\n \"blockindex\": n, (numeric) Unset.\n \"blocktime\": n, (numeric) The Unix time of the block header this transaction is mined in, or 0 if unmined.\n \"category\": \"value\", (string) The kind of transaction: \"send\" for sent transactions, \"immature\" for immature coinbase outputs, \"generate\" for mature coinbase outputs, or \"recv\" for all other received outputs. Note: A single output may be included multiple times under different categories\n \"confirmations\": n, (numeric) The number of block confirmations of the transaction.\n \"fee\": n.nnn, (numeric) The total input value minus the total output value for sent transactions.\n \"generated\": true|false, (boolean) Whether the transaction output is a coinbase output.\n \"involveswatchonly\": true|false, (boolean) Unset.\n \"label\": \"value\", (string) A comment for the address/transaction, if any.\n \"time\": n, (numeric) The earliest Unix time this transaction was known to exist.\n \"timereceived\": n, (numeric) The earliest Unix time this transaction was known to exist.\n \"trusted\": true|false, (boolean) Unset.\n \"txid\": \"value\", (string) The hash of the transaction.\n \"vout\": n, (numeric) The transaction output index.\n \"walletconflicts\": [\"value\",...], (array of string) Unset.\n \"comment\": \"value\", (string) Unset.\n \"otheraccount\": \"value\", (string) Unset.\n},...]\n",
"renameaccount": "renameaccount \"oldaccount\" \"newaccount\"\n\nRenames an account.\n\nArguments:\n1. oldaccount (string, required) The old account name to rename.\n2. newaccount (string, required) The new name for the account.\n\nResult:\nNothing\n",
"rescanblockchain": "rescanblockchain (startheight=0 stopheight)\n\nRenames an account.\n\nArguments:\n1. startheight (numeric, optional, default=0) Block height where the rescan should start.\n2. stopheight (numeric, optional) The last block height that should be scanned. If none is provided it will rescan up to the tip at return time of this call.\n\nResult:\n{\n \"start_height\": n, (numeric) The block height where the rescan started (the requested height or 0)\n \"stop_height\": n, (numeric) The height of the last rescanned block.\n} \n",
"walletislocked": "walletislocked\n\nReturns whether or not the wallet is locked.\n\nArguments:\nNone\n\nResult:\ntrue|false (boolean) Whether the wallet is locked.\n",
}
}
@ -56,4 +57,4 @@ var localeHelpDescs = map[string]func() map[string]string{
"en_US": helpDescsEnUS,
}
var requestUsages = "addmultisigaddress nrequired [\"key\",...] (\"account\")\ncreatemultisig nrequired [\"key\",...]\ndumpprivkey \"address\"\ngetaccount \"address\"\ngetaccountaddress (account=\"default\" addresstype=\"legacy\")\ngetaddressesbyaccount (account=\"default\" addresstype=\"*\")\ngetaddressinfo \"address\"\ngetbalance (account=\"default\" minconf=1 addresstype=\"*\")\ngetbestblockhash\ngetblockcount\ngetinfo\ngetnewaddress (account=\"default\" addresstype=\"legacy\")\ngetrawchangeaddress (account=\"default\" addresstype=\"legacy\")\ngetreceivedbyaccount (account=\"default\" minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngettransaction \"txid\" (includewatchonly=false)\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true)\nkeypoolrefill (newsize=100)\nlistaccounts (minconf=1 addresstype=\"*\")\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (account=\"default\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"txid\":\"value\",\"vout\":n},...]\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 addresstype=\"*\" \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 addresstype=\"*\" \"comment\")\nsendtoaddress \"address\" amount (addresstype=\"*\" \"comment\" \"commentto\")\nsettxfee amount\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\ncreatenewaccount \"account\"\ngetbestblock\ngetunconfirmedbalance (account=\"default\")\nlistaddresstransactions [\"address\",...] (account=\"default\")\nlistalltransactions (account=\"default\")\nrenameaccount \"oldaccount\" \"newaccount\"\nwalletislocked"
var requestUsages = "addmultisigaddress nrequired [\"key\",...] (\"account\")\ncreatemultisig nrequired [\"key\",...]\ndumpprivkey \"address\"\ngetaccount \"address\"\ngetaccountaddress (account=\"default\" addresstype=\"legacy\")\ngetaddressesbyaccount (account=\"default\" addresstype=\"*\")\ngetaddressinfo \"address\"\ngetbalance (account=\"default\" minconf=1 addresstype=\"*\")\ngetbestblockhash\ngetblockcount\ngetinfo\ngetnewaddress (account=\"default\" addresstype=\"legacy\")\ngetrawchangeaddress (account=\"default\" addresstype=\"legacy\")\ngetreceivedbyaccount (account=\"default\" minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngettransaction \"txid\" (includewatchonly=false)\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true)\nkeypoolrefill (newsize=100)\nlistaccounts (minconf=1 addresstype=\"*\")\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (account=\"default\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"txid\":\"value\",\"vout\":n},...]\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 addresstype=\"*\" \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 addresstype=\"*\" \"comment\")\nsendtoaddress \"address\" amount (addresstype=\"*\" \"comment\" \"commentto\")\nsettxfee amount\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\ncreatenewaccount \"account\"\ngetbestblock\ngetunconfirmedbalance (account=\"default\")\nlistaddresstransactions [\"address\",...] (account=\"default\")\nlistalltransactions (account=\"default\")\nrenameaccount \"oldaccount\" \"newaccount\"\nrescanblockchain (startheight=0 stopheight)\nwalletislocked"

View file

@ -26,7 +26,6 @@ import (
btcutil "github.com/lbryio/lbcutil"
"github.com/lbryio/lbcutil/hdkeychain"
"github.com/lbryio/lbcwallet/chain"
"github.com/lbryio/lbcwallet/internal/prompt"
"github.com/lbryio/lbcwallet/waddrmgr"
"github.com/lbryio/lbcwallet/wallet/txauthor"
"github.com/lbryio/lbcwallet/wallet/txrules"
@ -407,7 +406,7 @@ func (w *Wallet) syncWithChain(birthdayStamp *waddrmgr.BlockStamp) error {
// If the wallet requested an on-chain recovery of its funds, we'll do
// so now.
if w.recoveryWindow > 0 {
if err := w.recovery(chainClient, birthdayStamp); err != nil {
if err := w.Recovery(chainClient); err != nil {
return fmt.Errorf("unable to perform wallet recovery: "+
"%v", err)
}
@ -638,13 +637,12 @@ func locateBirthdayBlock(chainClient chainConn,
return birthdayBlock, nil
}
// recovery attempts to recover any unspent outputs that pay to any of our
// Recovery attempts to recover any unspent outputs that pay to any of our
// addresses starting from our birthday, or the wallet's tip (if higher), which
// would indicate resuming a recovery after a restart.
func (w *Wallet) recovery(chainClient chain.Interface,
birthdayBlock *waddrmgr.BlockStamp) error {
func (w *Wallet) Recovery(chainClient chain.Interface) error {
log.Infof("RECOVERY MODE ENABLED -- rescanning for used addresses "+
log.Infof("Recovery for used addresses "+
"with recovery_window=%d", w.recoveryWindow)
// We'll initialize the recovery manager with a default batch size of
@ -672,44 +670,36 @@ func (w *Wallet) recovery(chainClient chain.Interface,
return err
}
// Fetch the best height from the backend to determine when we should
// stop.
_, bestHeight, err := chainClient.GetBestBlock()
if err != nil {
return err
}
return nil
}
// Now we can begin scanning the chain from the wallet's current tip to
// ensure we properly handle restarts. Since the recovery process itself
// acts as rescan, we'll also update our wallet's synced state along the
// way to reflect the blocks we process and prevent rescanning them
// later on.
//
// NOTE: We purposefully don't update our best height since we assume
// that a wallet rescan will be performed from the wallet's tip, which
// will be of bestHeight after completing the recovery process.
func (w *Wallet) RescanBlockchain(chainClient chain.Interface,
startHeight int32, stopHeight int32) (int32, int32, error) {
pass, err := prompt.Passphrase(false)
if err != nil {
return err
}
log.Infof("Rescanning blockchain from block %d to %d "+
"with recovery_window=%d", startHeight, stopHeight,
w.recoveryWindow)
err = w.Unlock(pass, nil)
if err != nil {
return err
defer log.Infof("Rescan blockchain done")
recoveryMgr := NewRecoveryManager(
w.recoveryWindow, recoveryBatchSize, w.chainParams,
)
scopedMgrs := make(map[waddrmgr.KeyScope]*waddrmgr.ScopedKeyManager)
for _, scopedMgr := range w.Manager.ActiveScopedKeyManagers() {
scopedMgrs[scopedMgr.Scope()] = scopedMgr
}
defer w.Lock()
var blocks []*waddrmgr.BlockStamp
startHeight := w.Manager.SyncedTo().Height + 1
for height := startHeight; height <= bestHeight; height++ {
for height := startHeight; height <= stopHeight; height++ {
hash, err := chainClient.GetBlockHash(int64(height))
if err != nil {
return err
return startHeight, stopHeight, err
}
header, err := chainClient.GetBlockHeader(hash)
if err != nil {
return err
return startHeight, stopHeight, err
}
blocks = append(blocks, &waddrmgr.BlockStamp{
Hash: *hash,
@ -720,7 +710,7 @@ func (w *Wallet) recovery(chainClient chain.Interface,
// It's possible for us to run into blocks before our birthday
// if our birthday is after our reorg safe height, so we'll make
// sure to not add those to the batch.
if height >= birthdayBlock.Height {
if height >= startHeight {
recoveryMgr.AddToBlockBatch(
hash, height, header.Timestamp,
)
@ -731,18 +721,12 @@ func (w *Wallet) recovery(chainClient chain.Interface,
// the recovery batch size, so we can proceed to commit our
// state to disk.
recoveryBatch := recoveryMgr.BlockBatch()
if len(recoveryBatch) != recoveryBatchSize && height != bestHeight {
if len(recoveryBatch) != recoveryBatchSize && height != stopHeight {
continue
}
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
for _, block := range blocks {
err = w.Manager.SetSyncedTo(ns, block)
if err != nil {
return err
}
}
for scope, scopedMgr := range scopedMgrs {
scopeState := recoveryMgr.State().StateForScope(scope)
err = expandScopeHorizons(ns, scopedMgr, scopeState)
@ -755,7 +739,7 @@ func (w *Wallet) recovery(chainClient chain.Interface,
)
})
if err != nil {
return err
return startHeight, stopHeight, err
}
if len(recoveryBatch) > 0 {
@ -769,8 +753,7 @@ func (w *Wallet) recovery(chainClient chain.Interface,
blocks = blocks[:0]
recoveryMgr.ResetBlockBatch()
}
return nil
return startHeight, stopHeight, nil
}
// recoverScopedAddresses scans a range of blocks in attempts to recover any

View file

@ -46,12 +46,9 @@ func createWallet(cfg *config) error {
)
// Start by prompting for the passphrase.
reader := bufio.NewReader(os.Stdin)
privPass, err := prompt.Passphrase(true)
if err != nil {
return err
}
passphrase := []byte(cfg.Passphrase)
reader := bufio.NewReader(os.Stdin)
// Ascertain the wallet generation seed. This will either be an
// automatically generated value the user has already confirmed or a
// value the user has entered which has already been validated.
@ -61,7 +58,7 @@ func createWallet(cfg *config) error {
}
fmt.Println("Creating the wallet...")
w, err := loader.CreateNewWallet(privPass, seed, bday)
w, err := loader.CreateNewWallet(passphrase, seed, bday)
if err != nil {
return err
}