Skip to content

Commit

Permalink
wallet: Add birthday.
Browse files Browse the repository at this point in the history
Add two new values to the database. One shows the intent to save a
wallet birthday to the database and includes a time. The other is the
wallet's birthday block. While syncing, the wallet will check if there
is an intent to save the birthday and save the block header and height
of the block before the birthday to the database.
  • Loading branch information
JoeGruffins committed Apr 24, 2024
1 parent ad140d2 commit 3da2ec7
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 1 deletion.
31 changes: 31 additions & 0 deletions internal/prompt/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"io"
"os"
"strings"
"time"
"unicode"

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

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

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()) {
fmt.Println("Birthday cannot be in the future.")
continue
}
return &birthday, 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
37 changes: 36 additions & 1 deletion wallet/chainntfns.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func (w *Wallet) ChainSwitch(ctx context.Context, forest *SidechainForest, chain
}

if sideChainForkHeight <= tipHeight {
birthdayHash, birthdayHeight := w.txStore.Birthblock(dbtx)
chainTipChanges.DetachedBlocks = make([]*chainhash.Hash, tipHeight-sideChainForkHeight+1)
prevChain = make([]*BlockNode, tipHeight-sideChainForkHeight+1)
for i := tipHeight; i >= sideChainForkHeight; i-- {
Expand All @@ -136,6 +137,14 @@ func (w *Wallet) ChainSwitch(ctx context.Context, forest *SidechainForest, chain
return err
}

if birthdayHash != nil && hash == *birthdayHash {
if err := w.txStore.SetBirthdayTimeBool(dbtx, true); err != nil {
return err
}
log.Infof("Old birthday block %d (%v) was forked off the network. "+
"A new one will be set.", birthdayHeight, birthdayHash)
}

// DetachedBlocks and prevChain are sorted in order of increasing heights.
chainTipChanges.DetachedBlocks[i-sideChainForkHeight] = &hash
prevChain[i-sideChainForkHeight] = NewBlockNode(header, &hash, filter)
Expand All @@ -160,7 +169,9 @@ func (w *Wallet) ChainSwitch(ctx context.Context, forest *SidechainForest, chain
}
}

for _, n := range chain {
shouldSetBirthday, birthday := w.txStore.BirthdayTime(dbtx)

for i, n := range chain {
if voteVersion(w.chainParams) < n.Header.StakeVersion {
log.Warnf("Old vote version detected (v%v), please update your "+
"wallet to the latest version.", voteVersion(w.chainParams))
Expand All @@ -174,6 +185,30 @@ func (w *Wallet) ChainSwitch(ctx context.Context, forest *SidechainForest, chain

// Add the block hash to the notification.
chainTipChanges.AttachedBlocks = append(chainTipChanges.AttachedBlocks, n.Hash)

if shouldSetBirthday {
if n.Header.Timestamp.After(*birthday) {
bh := n.Header.PrevBlock
if err := w.txStore.SetBirthblock(dbtx, &bh, n.Header.Height-1); err != nil {
return err
}
shouldSetBirthday = false
if err := w.txStore.UpdateProcessedTxsBlockMarker(dbtx, &bh); err != nil {
return err
}
log.Infof("Set wallet birthday to block %d (%v).",
n.Header.Height-1, bh)
} else if i == len(chain)-1 {
// In the case that the birthday will not be reached,
// store a hash for initial sync to start from. Do not
// store the tip hash as that will cause w.rescanPoint
// to return nil.
bh := n.Header.PrevBlock
if err := w.txStore.UpdateProcessedTxsBlockMarker(dbtx, &bh); err != nil {
return err
}
}
}
}

if relevantTxs != nil {
Expand Down
14 changes: 14 additions & 0 deletions wallet/rescan.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,3 +493,17 @@ func (w *Wallet) rescanPoint(dbtx walletdb.ReadTx) (*chainhash.Hash, error) {
}
return &rescanPoint, nil
}

// SetBirthdayTime sets a time that the birthday block should be set before.
// The birthday will be set automatically when the wallet sees a block past the
// birthday time.
func (w *Wallet) SetBirthdayTime(ctx context.Context, birthday *time.Time) error {
const op errors.Op = "wallet.SetBirthdayTime"
err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
return w.txStore.SetBirthdayTime(dbtx, birthday)
})
if err != nil {
return errors.E(op, err)
}
return nil
}
2 changes: 2 additions & 0 deletions wallet/udb/txdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ var (
rootHaveCFilters = []byte("havecfilters")
rootLastTxsBlock = []byte("lasttxsblock")
rootVSPHostIndex = []byte("vsphostindex")
rootBirthdayTime = []byte("birthdaytime")
rootBirthblock = []byte("birthblock")
)

// The root bucket's mined balance k/v pair records the total balance for all
Expand Down
82 changes: 82 additions & 0 deletions wallet/udb/txmined.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,88 @@ func (s *Store) UpdateProcessedTxsBlockMarker(dbtx walletdb.ReadWriteTx, hash *c
return nil
}

// SetBirthblock sets a birthday block and hash. It also sets the birthday time
// should be set bool to false.
//
// [0:32] Block header hash (32 bytes)
// [32:36] Birthblock block height (4 bytes)
func (s *Store) SetBirthblock(dbtx walletdb.ReadWriteTx, blockHash *chainhash.Hash, height uint32) error {
ns := dbtx.ReadWriteBucket(wtxmgrBucketKey)
err := s.setBirthblock(ns, blockHash, height)
if err != nil {
return err
}
return s.setBirthdayTimeBool(ns, false)
}

func (s *Store) setBirthblock(ns walletdb.ReadWriteBucket, blockHash *chainhash.Hash, height uint32) error {
v := make([]byte, chainhash.HashSize+4)
copy(v[:], blockHash[:])
byteOrder.PutUint32(v[chainhash.HashSize:], uint32(height))
return ns.Put(rootBirthblock, v)
}

// Birthblock returns the currently set wallet birthday block hash and height. If
// not set a nil hash is returned.
func (s *Store) Birthblock(dbtx walletdb.ReadTx) (*chainhash.Hash, uint32) {
ns := dbtx.ReadBucket(wtxmgrBucketKey)
v := ns.Get(rootBirthblock)
if len(v) != chainhash.HashSize+4 {
return nil, 0
}
var h chainhash.Hash
copy(h[:], v[:chainhash.HashSize])
return &h, byteOrder.Uint32(v[chainhash.HashSize:])
}

// SetBirthdayTime sets a time that the birthday should be set from. Also sets
// the bool that decides if the birthday should be set to true.
//
// [0:1] should set bool (1 byte)
// [1:9] Birthday time (8 bytes)
func (s *Store) SetBirthdayTime(dbtx walletdb.ReadWriteTx, birthday *time.Time) error {
ns := dbtx.ReadWriteBucket(wtxmgrBucketKey)
v := make([]byte, 1+8)
v[0] = 1 // true
byteOrder.PutUint64(v[1:], uint64(birthday.Unix()))
return ns.Put(rootBirthdayTime, v)
}

// SetBirthdayTimeBool sets only the bool that decides if the birthday should
// be set. Will error if the birthday time was never set.
func (s *Store) SetBirthdayTimeBool(dbtx walletdb.ReadWriteTx, shouldSet bool) error {
ns := dbtx.ReadWriteBucket(wtxmgrBucketKey)
return s.setBirthdayTimeBool(ns, shouldSet)
}

func (s *Store) setBirthdayTimeBool(ns walletdb.ReadWriteBucket, shouldSet bool) error {
v := ns.Get(rootBirthdayTime)
if len(v) != 1+8 {
return errors.New("birthday time never set")
}
vcopy := make([]byte, 1+8)
copy(vcopy, v)
if shouldSet {
vcopy[0] = 1
} else {
vcopy[0] = 0
}
return ns.Put(rootBirthdayTime, vcopy)
}

// BirthdayTime returns whether the birthday should be set and what time it
// should be set before.
func (s *Store) BirthdayTime(dbtx walletdb.ReadTx) (shouldSet bool, birthday *time.Time) {
ns := dbtx.ReadBucket(wtxmgrBucketKey)
v := ns.Get(rootBirthdayTime)
if len(v) != 1+8 {
return false, nil
}
shouldSet = v[0] == 1
t := time.Unix(int64(byteOrder.Uint64(v[1:])), 0)
return shouldSet, &t
}

// IsMissingMainChainCFilters returns whether all compact filters for main chain
// blocks have been recorded to the database after the upgrade which began to
// require them to extend the main chain. If compact filters are missing, they
Expand Down
20 changes: 20 additions & 0 deletions walletsetup.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"os"
"path/filepath"
"strings"
"time"

"decred.org/dcrwallet/v4/errors"
"decred.org/dcrwallet/v4/internal/loader"
Expand Down Expand Up @@ -125,6 +126,7 @@ func createWallet(ctx context.Context, cfg *config) error {
var importedAccountNames []string
var importedAccountXpubs []*hdkeychain.ExtendedKey
var err error
var birthday *time.Time
c := make(chan struct{}, 1)
go func() {
defer func() { c <- struct{}{} }()
Expand Down Expand Up @@ -164,6 +166,18 @@ func createWallet(ctx context.Context, cfg *config) error {
// script which provide scripted input to create wallets to
// continue working.

// Ask for a birthday if the wallet was created from seed. If
// the wallet is new the birthday is now.
if imported {
birthday, err = prompt.Birthday(r)
if err != nil {
return
}
} else {
bd := time.Now()
birthday = &bd
}

// Prompt for any additional xpubs to import as watching-only accounts.
importedAccountNames, importedAccountXpubs, err = prompt.ImportedAccounts(
r, activeNet.Params)
Expand Down Expand Up @@ -213,6 +227,12 @@ func createWallet(ctx context.Context, cfg *config) error {
}
}

if birthday != nil {
if err := w.SetBirthdayTime(ctx, birthday); err != nil {
return err
}
}

err = loader.UnloadWallet()
if err != nil {
return err
Expand Down

0 comments on commit 3da2ec7

Please sign in to comment.