github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/wallet/wallet.go (about)

     1  package wallet
     2  
     3  // TODO: Theoretically, the transaction builder in this wallet supports
     4  // multisig, but there are no automated tests to verify that.
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"sort"
    10  	"sync"
    11  
    12  	"gitlab.com/NebulousLabs/bolt"
    13  
    14  	"SiaPrime/build"
    15  	"SiaPrime/crypto"
    16  	"SiaPrime/encoding"
    17  	"SiaPrime/modules"
    18  	"SiaPrime/persist"
    19  	siasync "SiaPrime/sync"
    20  	"SiaPrime/types"
    21  	"gitlab.com/NebulousLabs/errors"
    22  	"gitlab.com/NebulousLabs/threadgroup"
    23  )
    24  
    25  const (
    26  	// RespendTimeout records the number of blocks that the wallet will wait
    27  	// before spending an output that has been spent in the past. If the
    28  	// transaction spending the output has not made it to the transaction pool
    29  	// after the limit, the assumption is that it never will.
    30  	RespendTimeout = 40
    31  )
    32  
    33  var (
    34  	errNilConsensusSet = errors.New("wallet cannot initialize with a nil consensus set")
    35  	errNilTpool        = errors.New("wallet cannot initialize with a nil transaction pool")
    36  )
    37  
    38  // spendableKey is a set of secret keys plus the corresponding unlock
    39  // conditions.  The public key can be derived from the secret key and then
    40  // matched to the corresponding public keys in the unlock conditions. All
    41  // addresses that are to be used in 'FundSiacoins' or 'FundSiafunds' in the
    42  // transaction builder must conform to this form of spendable key.
    43  type spendableKey struct {
    44  	UnlockConditions types.UnlockConditions
    45  	SecretKeys       []crypto.SecretKey
    46  }
    47  
    48  // Wallet is an object that tracks balances, creates keys and addresses,
    49  // manages building and sending transactions.
    50  type Wallet struct {
    51  	// encrypted indicates whether the wallet has been encrypted (i.e.
    52  	// initialized). unlocked indicates whether the wallet is currently
    53  	// storing secret keys in memory. subscribed indicates whether the wallet
    54  	// has subscribed to the consensus set yet - the wallet is unable to
    55  	// subscribe to the consensus set until it has been unlocked for the first
    56  	// time. The primary seed is used to generate new addresses for the
    57  	// wallet.
    58  	encrypted   bool
    59  	unlocked    bool
    60  	subscribed  bool
    61  	primarySeed modules.Seed
    62  
    63  	// The wallet's dependencies.
    64  	cs    modules.ConsensusSet
    65  	tpool modules.TransactionPool
    66  	deps  modules.Dependencies
    67  
    68  	// The following set of fields are responsible for tracking the confirmed
    69  	// outputs, and for being able to spend them. The seeds are used to derive
    70  	// the keys that are tracked on the blockchain. All keys are pregenerated
    71  	// from the seeds, when checking new outputs or spending outputs, the seeds
    72  	// are not referenced at all. The seeds are only stored so that the user
    73  	// may access them.
    74  	seeds        []modules.Seed
    75  	keys         map[types.UnlockHash]spendableKey
    76  	lookahead    map[types.UnlockHash]uint64
    77  	watchedAddrs map[types.UnlockHash]struct{}
    78  
    79  	// unconfirmedProcessedTransactions tracks unconfirmed transactions.
    80  	//
    81  	// TODO: Replace this field with a linked list. Currently when a new
    82  	// transaction set diff is provided, the entire array needs to be
    83  	// reallocated. Since this can happen tens of times per second, and the
    84  	// array can have tens of thousands of elements, it's a performance issue.
    85  	unconfirmedSets                  map[modules.TransactionSetID][]types.TransactionID
    86  	unconfirmedProcessedTransactions []modules.ProcessedTransaction
    87  
    88  	// The wallet's database tracks its seeds, keys, outputs, and
    89  	// transactions. A global db transaction is maintained in memory to avoid
    90  	// excessive disk writes. Any operations involving dbTx must hold an
    91  	// exclusive lock.
    92  	//
    93  	// If dbRollback is set, then when the database syncs it will perform a
    94  	// rollback instead of a commit. For safety reasons, the db will close and
    95  	// the wallet will close if a rollback is performed.
    96  	db         *persist.BoltDatabase
    97  	dbRollback bool
    98  	dbTx       *bolt.Tx
    99  
   100  	persistDir string
   101  	log        *persist.Logger
   102  	mu         sync.RWMutex
   103  
   104  	// A separate TryMutex is used to protect against concurrent unlocking or
   105  	// initialization.
   106  	scanLock siasync.TryMutex
   107  
   108  	// The wallet's ThreadGroup tells tracked functions to shut down and
   109  	// blocks until they have all exited before returning from Close.
   110  	tg threadgroup.ThreadGroup
   111  
   112  	// defragDisabled determines if the wallet is set to defrag outputs once it
   113  	// reaches a certain threshold
   114  	defragDisabled bool
   115  }
   116  
   117  // Height return the internal processed consensus height of the wallet
   118  func (w *Wallet) Height() (types.BlockHeight, error) {
   119  	if err := w.tg.Add(); err != nil {
   120  		return types.BlockHeight(0), modules.ErrWalletShutdown
   121  	}
   122  	defer w.tg.Done()
   123  
   124  	w.mu.Lock()
   125  	defer w.mu.Unlock()
   126  	w.syncDB()
   127  
   128  	var height uint64
   129  	err := w.db.View(func(tx *bolt.Tx) error {
   130  		return encoding.Unmarshal(tx.Bucket(bucketWallet).Get(keyConsensusHeight), &height)
   131  	})
   132  	if err != nil {
   133  		return types.BlockHeight(0), err
   134  	}
   135  	return types.BlockHeight(height), nil
   136  }
   137  
   138  // New creates a new wallet, loading any known addresses from the input file
   139  // name and then using the file to save in the future. Keys and addresses are
   140  // not loaded into the wallet during the call to 'new', but rather during the
   141  // call to 'Unlock'.
   142  func New(cs modules.ConsensusSet, tpool modules.TransactionPool, persistDir string) (*Wallet, error) {
   143  	return NewCustomWallet(cs, tpool, persistDir, modules.ProdDependencies)
   144  }
   145  
   146  // NewCustomWallet creates a new wallet using custom dependencies.
   147  func NewCustomWallet(cs modules.ConsensusSet, tpool modules.TransactionPool, persistDir string, deps modules.Dependencies) (*Wallet, error) {
   148  	// Check for nil dependencies.
   149  	if cs == nil {
   150  		return nil, errNilConsensusSet
   151  	}
   152  	if tpool == nil {
   153  		return nil, errNilTpool
   154  	}
   155  
   156  	// Initialize the data structure.
   157  	w := &Wallet{
   158  		cs:    cs,
   159  		tpool: tpool,
   160  
   161  		keys:         make(map[types.UnlockHash]spendableKey),
   162  		lookahead:    make(map[types.UnlockHash]uint64),
   163  		watchedAddrs: make(map[types.UnlockHash]struct{}),
   164  
   165  		unconfirmedSets: make(map[modules.TransactionSetID][]types.TransactionID),
   166  
   167  		persistDir: persistDir,
   168  
   169  		deps: deps,
   170  	}
   171  	err := w.initPersist()
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	return w, nil
   176  }
   177  
   178  // Close terminates all ongoing processes involving the wallet, enabling
   179  // garbage collection.
   180  func (w *Wallet) Close() error {
   181  	if err := w.tg.Stop(); err != nil {
   182  		return err
   183  	}
   184  	var errs []error
   185  	// Lock the wallet outside of mu.Lock because Lock uses its own mu.Lock.
   186  	// Once the wallet is locked it cannot be unlocked except using the
   187  	// unexported unlock method (w.Unlock returns an error if the wallet's
   188  	// ThreadGroup is stopped).
   189  	if w.managedUnlocked() {
   190  		if err := w.managedLock(); err != nil {
   191  			errs = append(errs, err)
   192  		}
   193  	}
   194  
   195  	w.cs.Unsubscribe(w)
   196  	w.tpool.Unsubscribe(w)
   197  
   198  	if err := w.log.Close(); err != nil {
   199  		errs = append(errs, fmt.Errorf("log.Close failed: %v", err))
   200  	}
   201  	return build.JoinErrors(errs, "; ")
   202  }
   203  
   204  // AllAddresses returns all addresses that the wallet is able to spend from,
   205  // including unseeded addresses. Addresses are returned sorted in byte-order.
   206  func (w *Wallet) AllAddresses() ([]types.UnlockHash, error) {
   207  	if err := w.tg.Add(); err != nil {
   208  		return []types.UnlockHash{}, modules.ErrWalletShutdown
   209  	}
   210  	defer w.tg.Done()
   211  
   212  	w.mu.RLock()
   213  	defer w.mu.RUnlock()
   214  
   215  	addrs := make([]types.UnlockHash, 0, len(w.keys))
   216  	for addr := range w.keys {
   217  		addrs = append(addrs, addr)
   218  	}
   219  	sort.Slice(addrs, func(i, j int) bool {
   220  		return bytes.Compare(addrs[i][:], addrs[j][:]) < 0
   221  	})
   222  	return addrs, nil
   223  }
   224  
   225  // Rescanning reports whether the wallet is currently rescanning the
   226  // blockchain.
   227  func (w *Wallet) Rescanning() (bool, error) {
   228  	if err := w.tg.Add(); err != nil {
   229  		return false, modules.ErrWalletShutdown
   230  	}
   231  	defer w.tg.Done()
   232  
   233  	rescanning := !w.scanLock.TryLock()
   234  	if !rescanning {
   235  		w.scanLock.Unlock()
   236  	}
   237  	return rescanning, nil
   238  }
   239  
   240  // Settings returns the wallet's current settings
   241  func (w *Wallet) Settings() (modules.WalletSettings, error) {
   242  	if err := w.tg.Add(); err != nil {
   243  		return modules.WalletSettings{}, modules.ErrWalletShutdown
   244  	}
   245  	defer w.tg.Done()
   246  	return modules.WalletSettings{
   247  		NoDefrag: w.defragDisabled,
   248  	}, nil
   249  }
   250  
   251  // SetSettings will update the settings for the wallet.
   252  func (w *Wallet) SetSettings(s modules.WalletSettings) error {
   253  	if err := w.tg.Add(); err != nil {
   254  		return modules.ErrWalletShutdown
   255  	}
   256  	defer w.tg.Done()
   257  
   258  	w.mu.Lock()
   259  	w.defragDisabled = s.NoDefrag
   260  	w.mu.Unlock()
   261  	return nil
   262  }