Commit b2b873e0 authored by David Vorick's avatar David Vorick

Merge branch '3261-add-ability-to-query-for-the-latest-addresses' into 'master'

Resolve "Add ability to query for the latest addresses"

Closes #3261

See merge request !3403
parents fded0ebe 48437bbb
Pipeline #48339206 failed with stages
in 123 minutes and 33 seconds
......@@ -2107,6 +2107,35 @@ Fetches the list of addresses from the wallet. If the wallet has not been create
**addresses**
Array of wallet addresses owned by the wallet.
## /wallet/seedaddrs [GET]
Fetches addresses generated by the wallet in reverse order. The last address
generated by the wallet will be the first returned. This also means that
addresses which weren't generated using the wallet's seed can't be retrieved
with this endpoint.
### Query String Parameters
#### OPTIONAL
**count**
Number of addresses that should be returned. If count is not specified or if
count is bigger than the number of addresses generated by the wallet, all
addresses will be returned.
### JSON Response
> JSON Response Example
```go
{
"addresses": [
"1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
]
}
```
**addresses**
Array of wallet addresses previously generated by the wallet.
## /wallet/backup [GET]
Creates a backup of the wallet settings file. Though this can easily be done manually, the settings file is often in an unknown or difficult to find location. The /wallet/backup call can spare users the trouble of needing to find their wallet file.
......
......@@ -307,6 +307,10 @@ type (
// filepath. The backup will have all seeds and keys.
CreateBackup(string) error
// LastAddresses returns the last n addresses starting at the last seedProgress
// for which an address was generated.
LastAddresses(n uint64) ([]types.UnlockHash, error)
// LoadBackup will load a backup of the wallet from the provided
// address. The backup wallet will be added as an auxiliary seed, not
// as a primary seed.
......
......@@ -135,6 +135,42 @@ func (w *Wallet) Height() (types.BlockHeight, error) {
return types.BlockHeight(height), nil
}
// LastAddresses returns the last n addresses starting at the last seedProgress
// for which an address was generated. If n is greater than the current
// progress, fewer than n keys will be returned. That means all addresses can
// be retrieved in reverse order by simply supplying math.MaxUint64 for n.
func (w *Wallet) LastAddresses(n uint64) ([]types.UnlockHash, error) {
if err := w.tg.Add(); err != nil {
return nil, modules.ErrWalletShutdown
}
defer w.tg.Done()
w.mu.Lock()
defer w.mu.Unlock()
// Get the current seed progress from disk.
var seedProgress uint64
err := w.db.View(func(tx *bolt.Tx) (err error) {
seedProgress, err = dbGetPrimarySeedProgress(tx)
return
})
if err != nil {
return []types.UnlockHash{}, err
}
// At most seedProgess addresses can be requested.
if n > seedProgress {
n = seedProgress
}
start := seedProgress - n
// Generate the keys.
keys := generateKeys(w.primarySeed, start, n)
uhs := make([]types.UnlockHash, 0, len(keys))
for i := len(keys) - 1; i >= 0; i-- {
uhs = append(uhs, keys[i].UnlockConditions.UnlockHash())
}
return uhs, nil
}
// New creates a new wallet, loading any known addresses from the input file
// name and then using the file to save in the future. Keys and addresses are
// not loaded into the wallet during the call to 'new', but rather during the
......
......@@ -61,6 +61,14 @@ func (c *Client) WalletGet() (wg api.WalletGET, err error) {
return
}
// WalletLastAddressesGet returns the count last addresses generated by the
// wallet in reverse order. That means the last generated address will be the
// first one in the slice.
func (c *Client) WalletLastAddressesGet(count uint64) (wag api.WalletAddressesGET, err error) {
err = c.get(fmt.Sprintf("/wallet/seedaddrs?count=%v", count), &wag)
return
}
// WalletLockPost uses the /wallet/lock endpoint to lock the wallet.
func (c *Client) WalletLockPost() (err error) {
err = c.post("/wallet/lock", "", nil)
......@@ -194,7 +202,7 @@ func (c *Client) WalletWatchGet() (wwg api.WalletWatchGET, err error) {
return
}
// WalletWatchPost uses the /wallet/watch endpoint to add a set of addresses
// WalletWatchAddPost uses the /wallet/watch endpoint to add a set of addresses
// to the watch set. The unused flag should be set to true if the addresses
// have never appeared in the blockchain.
func (c *Client) WalletWatchAddPost(addrs []types.UnlockHash, unused bool) error {
......@@ -209,7 +217,7 @@ func (c *Client) WalletWatchAddPost(addrs []types.UnlockHash, unused bool) error
return c.post("/wallet/watch", string(json), nil)
}
// WalletWatchPost uses the /wallet/watch endpoint to remove a set of
// WalletWatchRemovePost uses the /wallet/watch endpoint to remove a set of
// addresses from the watch set. The unused flag should be set to true if the
// addresses have never appeared in the blockchain.
func (c *Client) WalletWatchRemovePost(addrs []types.UnlockHash, unused bool) error {
......
......@@ -118,6 +118,7 @@ func (api *API) buildHTTPRoutes(requiredUserAgent string, requiredPassword strin
router.POST("/wallet/033x", RequirePassword(api.wallet033xHandler, requiredPassword))
router.GET("/wallet/address", RequirePassword(api.walletAddressHandler, requiredPassword))
router.GET("/wallet/addresses", api.walletAddressesHandler)
router.GET("/wallet/seedaddrs", api.walletSeedAddressesHandler)
router.GET("/wallet/backup", RequirePassword(api.walletBackupHandler, requiredPassword))
router.POST("/wallet/init", RequirePassword(api.walletInitHandler, requiredPassword))
router.POST("/wallet/init/seed", RequirePassword(api.walletInitSeedHandler, requiredPassword))
......
......@@ -254,6 +254,31 @@ func (api *API) walletAddressHandler(w http.ResponseWriter, req *http.Request, _
})
}
// walletSeedAddressesHandler handles the requests to /wallet/seedaddrs.
func (api *API) walletSeedAddressesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
// Parse the count argument. If it isn't specified we return as many
// addresses as possible.
count := uint64(math.MaxUint64)
c := req.FormValue("count")
if c != "" {
_, err := fmt.Sscan(c, &count)
if err != nil {
WriteError(w, Error{"Failed to parse count: " + err.Error()}, http.StatusBadRequest)
return
}
}
// Get the last count addresses.
addresses, err := api.wallet.LastAddresses(count)
if err != nil {
WriteError(w, Error{fmt.Sprintf("Error when calling /wallet/addresses: %v", err)}, http.StatusBadRequest)
return
}
// Send the response.
WriteJSON(w, WalletAddressesGET{
Addresses: addresses,
})
}
// walletAddressHandler handles API calls to /wallet/addresses.
func (api *API) walletAddressesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
addresses, err := api.wallet.AllAddresses()
......
......@@ -2,6 +2,7 @@ package wallet
import (
"errors"
"math"
"path/filepath"
"testing"
"time"
......@@ -481,3 +482,78 @@ func TestFileContractUnspentOutputs(t *testing.T) {
t.Fatal("wallet's spendable outputs did not contain file contract output")
}
}
// TestWalletLastAddresses tests the /wallet/addresses endpoint with a
// specified count.
func TestWalletLastAddresses(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
// Create a new server
testNode, err := siatest.NewCleanNode(node.AllModules(siatest.TestDir(t.Name())))
if err != nil {
t.Fatal(err)
}
defer func() {
if err := testNode.Close(); err != nil {
t.Fatal(err)
}
}()
// The wallet should have 0 addresses.
wag, err := testNode.WalletAddressesGet()
if err != nil {
t.Fatal(err)
}
if len(wag.Addresses) != 0 {
t.Fatal("Wallet should have 0 addresses but had", len(wag.Addresses))
}
// Generate n addresses.
n := 10
addresses := make([]types.UnlockHash, 0, n)
for i := 0; i < n; i++ {
wag, err := testNode.WalletAddressGet()
if err != nil {
t.Fatal(err)
}
addresses = append(addresses, wag.Address)
}
// The wallet should have n addresses now.
wag, err = testNode.WalletAddressesGet()
if err != nil {
t.Fatal(err)
}
if len(wag.Addresses) != n {
t.Fatal("Wallet should have 100 addresses but had", len(wag.Addresses))
}
// Get the n addresses in reverse order.
wlag, err := testNode.WalletLastAddressesGet(uint64(n))
if err != nil {
t.Fatal(err)
}
if len(addresses) != len(wlag.Addresses) {
t.Fatalf("Expected %v addresses but got %v",
len(addresses), len(wlag.Addresses))
}
// Make sure the returned addresses are the same and have the reversed
// order of the created ones.
for i := range wag.Addresses {
if addresses[i] != wlag.Addresses[len(wlag.Addresses)-1-i] {
t.Fatal("addresses don't match for i =", i)
}
}
// Get MaxUint64 addresses in reverse order. This should still only return
// n addresses.
wlag, err = testNode.WalletLastAddressesGet(math.MaxUint64)
if err != nil {
t.Fatal(err)
}
// Make sure the returned addresses are the same and have the reversed
// order of the created ones.
for i := range wag.Addresses {
if addresses[i] != wlag.Addresses[len(wlag.Addresses)-1-i] {
t.Fatal("addresses don't match for i =", i)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment