github.com/ZuluSpl0it/Sia@v1.3.7/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  	"github.com/coreos/bbolt"
    13  
    14  	"github.com/NebulousLabs/Sia/build"
    15  	"github.com/NebulousLabs/Sia/crypto"
    16  	"github.com/NebulousLabs/Sia/encoding"
    17  	"github.com/NebulousLabs/Sia/modules"
    18  	"github.com/NebulousLabs/Sia/persist"
    19  	siasync "github.com/NebulousLabs/Sia/sync"
    20  	"github.com/NebulousLabs/Sia/types"
    21  	"github.com/NebulousLabs/errors"
    22  	"github.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  
    78  	// unconfirmedProcessedTransactions tracks unconfirmed transactions.
    79  	//
    80  	// TODO: Replace this field with a linked list. Currently when a new
    81  	// transaction set diff is provided, the entire array needs to be
    82  	// reallocated. Since this can happen tens of times per second, and the
    83  	// array can have tens of thousands of elements, it's a performance issue.
    84  	unconfirmedSets                  map[modules.TransactionSetID][]types.TransactionID
    85  	unconfirmedProcessedTransactions []modules.ProcessedTransaction
    86  
    87  	// The wallet's database tracks its seeds, keys, outputs, and
    88  	// transactions. A global db transaction is maintained in memory to avoid
    89  	// excessive disk writes. Any operations involving dbTx must hold an
    90  	// exclusive lock.
    91  	//
    92  	// If dbRollback is set, then when the database syncs it will perform a
    93  	// rollback instead of a commit. For safety reasons, the db will close and
    94  	// the wallet will close if a rollback is performed.
    95  	db         *persist.BoltDatabase
    96  	dbRollback bool
    97  	dbTx       *bolt.Tx
    98  
    99  	persistDir string
   100  	log        *persist.Logger
   101  	mu         sync.RWMutex
   102  
   103  	// A separate TryMutex is used to protect against concurrent unlocking or
   104  	// initialization.
   105  	scanLock siasync.TryMutex
   106  
   107  	// The wallet's ThreadGroup tells tracked functions to shut down and
   108  	// blocks until they have all exited before returning from Close.
   109  	tg threadgroup.ThreadGroup
   110  
   111  	// defragDisabled determines if the wallet is set to defrag outputs once it
   112  	// reaches a certain threshold
   113  	defragDisabled bool
   114  }
   115  
   116  // Height return the internal processed consensus height of the wallet
   117  func (w *Wallet) Height() (types.BlockHeight, error) {
   118  	if err := w.tg.Add(); err != nil {
   119  		return types.BlockHeight(0), modules.ErrWalletShutdown
   120  	}
   121  	defer w.tg.Done()
   122  
   123  	w.mu.Lock()
   124  	defer w.mu.Unlock()
   125  
   126  	var height uint64
   127  	err := w.db.View(func(tx *bolt.Tx) error {
   128  		return encoding.Unmarshal(tx.Bucket(bucketWallet).Get(keyConsensusHeight), &height)
   129  	})
   130  	if err != nil {
   131  		return types.BlockHeight(0), err
   132  	}
   133  	return types.BlockHeight(height), nil
   134  }
   135  
   136  // New creates a new wallet, loading any known addresses from the input file
   137  // name and then using the file to save in the future. Keys and addresses are
   138  // not loaded into the wallet during the call to 'new', but rather during the
   139  // call to 'Unlock'.
   140  func New(cs modules.ConsensusSet, tpool modules.TransactionPool, persistDir string) (*Wallet, error) {
   141  	return NewCustomWallet(cs, tpool, persistDir, modules.ProdDependencies)
   142  }
   143  
   144  // NewCustomWallet creates a new wallet using custom dependencies.
   145  func NewCustomWallet(cs modules.ConsensusSet, tpool modules.TransactionPool, persistDir string, deps modules.Dependencies) (*Wallet, error) {
   146  	// Check for nil dependencies.
   147  	if cs == nil {
   148  		return nil, errNilConsensusSet
   149  	}
   150  	if tpool == nil {
   151  		return nil, errNilTpool
   152  	}
   153  
   154  	// Initialize the data structure.
   155  	w := &Wallet{
   156  		cs:    cs,
   157  		tpool: tpool,
   158  
   159  		keys:      make(map[types.UnlockHash]spendableKey),
   160  		lookahead: make(map[types.UnlockHash]uint64),
   161  
   162  		unconfirmedSets: make(map[modules.TransactionSetID][]types.TransactionID),
   163  
   164  		persistDir: persistDir,
   165  
   166  		deps: deps,
   167  	}
   168  	err := w.initPersist()
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	// begin the initial transaction
   174  	w.dbTx, err = w.db.Begin(true)
   175  	if err != nil {
   176  		w.log.Critical("ERROR: failed to start database update:", err)
   177  	}
   178  
   179  	// COMPATv131 we need to create the bucketProcessedTxnIndex if it doesn't exist
   180  	if w.dbTx.Bucket(bucketProcessedTransactions).Stats().KeyN > 0 &&
   181  		w.dbTx.Bucket(bucketProcessedTxnIndex).Stats().KeyN == 0 {
   182  		err = initProcessedTxnIndex(w.dbTx)
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  		// Save changes to disk
   187  		if err = w.syncDB(); err != nil {
   188  			return nil, err
   189  		}
   190  	}
   191  	return w, nil
   192  }
   193  
   194  // Close terminates all ongoing processes involving the wallet, enabling
   195  // garbage collection.
   196  func (w *Wallet) Close() error {
   197  	if err := w.tg.Stop(); err != nil {
   198  		return err
   199  	}
   200  	var errs []error
   201  	// Lock the wallet outside of mu.Lock because Lock uses its own mu.Lock.
   202  	// Once the wallet is locked it cannot be unlocked except using the
   203  	// unexported unlock method (w.Unlock returns an error if the wallet's
   204  	// ThreadGroup is stopped).
   205  	if w.managedUnlocked() {
   206  		if err := w.managedLock(); err != nil {
   207  			errs = append(errs, err)
   208  		}
   209  	}
   210  
   211  	w.cs.Unsubscribe(w)
   212  	w.tpool.Unsubscribe(w)
   213  
   214  	if err := w.log.Close(); err != nil {
   215  		errs = append(errs, fmt.Errorf("log.Close failed: %v", err))
   216  	}
   217  	return build.JoinErrors(errs, "; ")
   218  }
   219  
   220  // AllAddresses returns all addresses that the wallet is able to spend from,
   221  // including unseeded addresses. Addresses are returned sorted in byte-order.
   222  func (w *Wallet) AllAddresses() ([]types.UnlockHash, error) {
   223  	if err := w.tg.Add(); err != nil {
   224  		return []types.UnlockHash{}, modules.ErrWalletShutdown
   225  	}
   226  	defer w.tg.Done()
   227  
   228  	w.mu.RLock()
   229  	defer w.mu.RUnlock()
   230  
   231  	addrs := make([]types.UnlockHash, 0, len(w.keys))
   232  	for addr := range w.keys {
   233  		addrs = append(addrs, addr)
   234  	}
   235  	sort.Slice(addrs, func(i, j int) bool {
   236  		return bytes.Compare(addrs[i][:], addrs[j][:]) < 0
   237  	})
   238  	return addrs, nil
   239  }
   240  
   241  // Rescanning reports whether the wallet is currently rescanning the
   242  // blockchain.
   243  func (w *Wallet) Rescanning() (bool, error) {
   244  	if err := w.tg.Add(); err != nil {
   245  		return false, modules.ErrWalletShutdown
   246  	}
   247  	defer w.tg.Done()
   248  
   249  	rescanning := !w.scanLock.TryLock()
   250  	if !rescanning {
   251  		w.scanLock.Unlock()
   252  	}
   253  	return rescanning, nil
   254  }
   255  
   256  // Settings returns the wallet's current settings
   257  func (w *Wallet) Settings() (modules.WalletSettings, error) {
   258  	if err := w.tg.Add(); err != nil {
   259  		return modules.WalletSettings{}, modules.ErrWalletShutdown
   260  	}
   261  	defer w.tg.Done()
   262  	return modules.WalletSettings{
   263  		NoDefrag: w.defragDisabled,
   264  	}, nil
   265  }
   266  
   267  // SetSettings will update the settings for the wallet.
   268  func (w *Wallet) SetSettings(s modules.WalletSettings) error {
   269  	if err := w.tg.Add(); err != nil {
   270  		return modules.ErrWalletShutdown
   271  	}
   272  	defer w.tg.Done()
   273  
   274  	w.mu.Lock()
   275  	w.defragDisabled = s.NoDefrag
   276  	w.mu.Unlock()
   277  	return nil
   278  }