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