Skip to content

Commit

Permalink
wallet: Add birthday.
Browse files Browse the repository at this point in the history
Add a birthday state to the database than can be used when creating a
new wallet to restore from a certain date or time.
  • Loading branch information
JoeGruffins committed Apr 30, 2024
1 parent ad140d2 commit c1e6035
Show file tree
Hide file tree
Showing 15 changed files with 3,947 additions and 3,514 deletions.
41 changes: 41 additions & 0 deletions internal/prompt/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
"fmt"
"io"
"os"
"strconv"
"strings"
"time"
"unicode"

"decred.org/dcrwallet/v4/errors"
Expand Down Expand Up @@ -367,6 +369,45 @@ func Seed(reader *bufio.Reader) (seed []byte, imported bool, err error) {
}
}

// Birthday prompts for a wallet birthday. Return values may be nil with no error.
func Birthday(reader *bufio.Reader) (*time.Time, *uint32, error) {
for {
fmt.Printf("Do you have a wallet birthday we should rescan from? (enter date as YYYY-MM-DD, or a block number, or 'no') [no]: ")
reply, err := reader.ReadString('\n')
if err != nil {
return nil, nil, err
}
reply = strings.TrimSpace(reply)
switch strings.ToLower(reply) {
case "", "n", "no":
return nil, nil, nil
case "y", "yes":
continue
default:
}

// If just a uint assume this is a block number and return that.
// Ignoring errors and parsing as a birthday below.
if n, err := strconv.ParseUint(reply, 10, 32); err == nil {
birthdayBlock := uint32(n)
fmt.Printf("Using birthday block %d.\n", birthdayBlock)
return nil, &birthdayBlock, nil
}

birthday, err := time.Parse("2006-01-02", reply)
if err != nil {
fmt.Printf("Unable to parse date: %v\n", err)
continue
}
if birthday.After(time.Now().Add(time.Hour * -24)) {
fmt.Println("Birthday cannot be in the future or too close (one day) to the present.")
continue
}
fmt.Printf("Using birthday time %s.\n", birthday)
return &birthday, nil, nil
}
}

// ImportedAccounts prompts for any additional account names and xpubs to
// import at wallet creation.
func ImportedAccounts(reader *bufio.Reader, params *chaincfg.Params) (names []string, xpubs []*hdkeychain.ExtendedKey, err error) {
Expand Down
15 changes: 13 additions & 2 deletions internal/rpc/jsonrpc/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -5497,7 +5497,7 @@ func (s *Server) walletInfo(ctx context.Context, icmd any) (any, error) {
_ = binary.Read(bytes.NewBuffer(voteBits.ExtendedBits[0:4]), binary.LittleEndian, &voteVersion)
voting := w.VotingEnabled()

return &types.WalletInfoResult{
wi := &types.WalletInfoResult{
DaemonConnected: connected,
SPV: spvMode,
Unlocked: unlocked,
Expand All @@ -5509,7 +5509,18 @@ func (s *Server) walletInfo(ctx context.Context, icmd any) (any, error) {
Voting: voting,
VSP: s.cfg.VSPHost,
ManualTickets: w.ManualTickets(),
}, nil
}

birthState, err := w.BirthState(ctx)
if err != nil {
log.Errorf("Failed to get birth state: %v", err)
} else if birthState != nil &&
!(birthState.SetFromTime || birthState.SetFromHeight) {
wi.BirthHash = birthState.Hash.String()
wi.BirthHeight = birthState.Height
}

return wi, nil
}

// walletIsLocked handles the walletislocked extension request by
Expand Down
2 changes: 1 addition & 1 deletion internal/rpc/jsonrpc/rpcserverhelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func helpDescsEnUS() map[string]string {
"validatepredcp0005cf": "validatepredcp0005cf\n\nValidate whether all stored cfilters from before DCP0005 activation are correct according to the expected hardcoded hash\n\nArguments:\nNone\n\nResult:\ntrue|false (boolean) Whether the cfilters are valid\n",
"verifymessage": "verifymessage \"address\" \"signature\" \"message\"\n\nVerify a message was signed with the associated private key of some address.\n\nArguments:\n1. address (string, required) Address used to sign message\n2. signature (string, required) The signature to verify\n3. message (string, required) The message to verify\n\nResult:\ntrue|false (boolean) Whether the message was signed with the private key of 'address'\n",
"version": "version\n\nReturns application and API versions (semver) keyed by their names\n\nArguments:\nNone\n\nResult:\n{\n \"Program or API name\": Object containing the semantic version, (object) Version objects keyed by the program or API name\n ...\n}\n",
"walletinfo": "walletinfo\n\nReturns global information about the wallet\n\nArguments:\nNone\n\nResult:\n{\n \"daemonconnected\": true|false, (boolean) Whether or not the wallet is currently connected to the daemon RPC\n \"spv\": true|false, (boolean) Whether or not wallet is syncing in SPV mode\n \"unlocked\": true|false, (boolean) Whether or not the wallet is unlocked\n \"cointype\": n, (numeric) Active coin type. Not available for watching-only wallets.\n \"txfee\": n.nnn, (numeric) Transaction fee per kB of the serialized tx size in coins\n \"votebits\": n, (numeric) Vote bits setting\n \"votebitsextended\": \"value\", (string) Extended vote bits setting\n \"voteversion\": n, (numeric) Version of votes that will be generated\n \"voting\": true|false, (boolean) Whether or not the wallet is currently voting tickets\n \"vsp\": \"value\", (string) VSP URL used when purchasing tickets\n \"manualtickets\": true|false, (boolean) Whether or not the wallet is only accepting tickets manually\n} \n",
"walletinfo": "walletinfo\n\nReturns global information about the wallet\n\nArguments:\nNone\n\nResult:\n{\n \"daemonconnected\": true|false, (boolean) Whether or not the wallet is currently connected to the daemon RPC\n \"spv\": true|false, (boolean) Whether or not wallet is syncing in SPV mode\n \"unlocked\": true|false, (boolean) Whether or not the wallet is unlocked\n \"cointype\": n, (numeric) Active coin type. Not available for watching-only wallets.\n \"txfee\": n.nnn, (numeric) Transaction fee per kB of the serialized tx size in coins\n \"votebits\": n, (numeric) Vote bits setting\n \"votebitsextended\": \"value\", (string) Extended vote bits setting\n \"voteversion\": n, (numeric) Version of votes that will be generated\n \"voting\": true|false, (boolean) Whether or not the wallet is currently voting tickets\n \"vsp\": \"value\", (string) VSP URL used when purchasing tickets\n \"manualtickets\": true|false, (boolean) Whether or not the wallet is only accepting tickets manually\n \"birthhash\": \"value\", (string) The wallet birth hash.\n \"birthheight\": n, (numeric) The wallet birth height.\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",
"walletlock": "walletlock\n\nLock the wallet.\n\nArguments:\nNone\n\nResult:\nNothing\n",
"walletpassphrase": "walletpassphrase \"passphrase\" timeout\n\nUnlock the wallet.\n\nArguments:\n1. passphrase (string, required) The wallet passphrase\n2. timeout (numeric, required) The number of seconds to wait before the wallet automatically locks. 0 leaves the wallet unlocked indefinitely.\n\nResult:\nNothing\n",
Expand Down
22 changes: 22 additions & 0 deletions internal/rpc/rpcserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,28 @@ func (s *walletServer) DumpPrivateKey(ctx context.Context, req *pb.DumpPrivateKe
return &pb.DumpPrivateKeyResponse{PrivateKeyWif: key}, nil
}

func (s *walletServer) BirthBlock(ctx context.Context, req *pb.BirthBlockRequest) (
*pb.BirthBlockResponse, error) {

birthState, err := s.wallet.BirthState(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal,
"failed to get birth state: %v", err)
}
if birthState == nil || birthState.SetFromTime || birthState.SetFromHeight {
errMsg := "birth block not set"
if birthState.SetFromTime || birthState.SetFromHeight {
errMsg = "birth block is pending..."
}
return nil, status.Errorf(codes.NotFound, errMsg)
}

return &pb.BirthBlockResponse{
Hash: birthState.Hash.String(),
Height: birthState.Height,
}, nil
}

func (s *walletServer) ImportPrivateKey(ctx context.Context, req *pb.ImportPrivateKeyRequest) (
*pb.ImportPrivateKeyResponse, error) {

Expand Down
2 changes: 2 additions & 0 deletions internal/rpchelp/helpdescs_en_US.go
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,8 @@ var helpDescsEnUS = map[string]string{
"walletinforesult-voting": "Whether or not the wallet is currently voting tickets",
"walletinforesult-vsp": "VSP URL used when purchasing tickets",
"walletinforesult-manualtickets": "Whether or not the wallet is only accepting tickets manually",
"walletinforesult-birthhash": "The wallet birth hash.",
"walletinforesult-birthheight": "The wallet birth height.",

// WalletIsLockedCmd help.
"walletislocked--synopsis": "Returns whether or not the wallet is locked.",
Expand Down
8 changes: 8 additions & 0 deletions rpc/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ service WalletService {
rpc GetCFilters (GetCFiltersRequest) returns (stream GetCFiltersResponse);
rpc GetPeerInfo(GetPeerInfoRequest) returns (GetPeerInfoResponse);
rpc DumpPrivateKey (DumpPrivateKeyRequest) returns (DumpPrivateKeyResponse);
rpc BirthBlock (BirthBlockRequest) returns (BirthBlockResponse);

// Notifications
rpc TransactionNotifications (TransactionNotificationsRequest) returns (stream TransactionNotificationsResponse);
Expand Down Expand Up @@ -321,6 +322,13 @@ message DumpPrivateKeyResponse {
string private_key_wif = 1;
}

message BirthBlockRequest {
}
message BirthBlockResponse {
string hash = 1;
uint32 height = 2;
}

message ImportVotingAccountFromSeedRequest {
bytes seed = 1;
string name = 2;
Expand Down
18 changes: 18 additions & 0 deletions rpc/documentation/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ The service provides the following methods:
- [`GetTrackedVSPTickets`](#GetTrackedVSPTickets)
- [`Address`](#Address)
- [`DumpPrivateKey`](#DumpPrivateKey)
- [`BirthBlock`](#BirthBlock)

#### `Ping`

Expand Down Expand Up @@ -2695,6 +2696,23 @@ or account must be unlocked.

- `InvalidArgument`: Watching only wallet.

#### `BirthBlock`

The `BirthBlock` method returns the wallets birthday block if set. Rescans
should generally be started from after this block.

**Request:** `BirthBlockRequest`

**Response:** `BirthBlockResponse`

- `string hash`: The birth block hash.

- `uint32 height`: The birth block height.

**Expected errors:**

- `NotFound`: Birth block never set or pending being found.

## `SeedService`

The `SeedService` service provides RPC clients with the ability to generate
Expand Down
2 changes: 2 additions & 0 deletions rpc/jsonrpc/types/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,8 @@ type WalletInfoResult struct {
Voting bool `json:"voting"`
VSP string `json:"vsp"`
ManualTickets bool `json:"manualtickets"`
BirthHash string `json:"birthhash"`
BirthHeight uint32 `json:"birthheight"`
}

// AccountUnlockedResult models the data returned by the accountunlocked
Expand Down

0 comments on commit c1e6035

Please sign in to comment.