gitlab.com/jokerrs1/Sia@v1.3.2/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/NebulousLabs/Sia/build"
    16  	"github.com/NebulousLabs/Sia/crypto"
    17  	"github.com/NebulousLabs/Sia/encoding"
    18  	"github.com/NebulousLabs/Sia/modules"
    19  	"github.com/NebulousLabs/Sia/persist"
    20  	siasync "github.com/NebulousLabs/Sia/sync"
    21  	"github.com/NebulousLabs/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  	db   *persist.BoltDatabase
    91  	dbTx *bolt.Tx
    92  
    93  	persistDir string
    94  	log        *persist.Logger
    95  	mu         sync.RWMutex
    96  
    97  	// A separate TryMutex is used to protect against concurrent unlocking or
    98  	// initialization.
    99  	scanLock siasync.TryMutex
   100  
   101  	// The wallet's ThreadGroup tells tracked functions to shut down and
   102  	// blocks until they have all exited before returning from Close.
   103  	tg siasync.ThreadGroup
   104  
   105  	// defragDisabled determines if the wallet is set to defrag outputs once it
   106  	// reaches a certain threshold
   107  	defragDisabled bool
   108  }
   109  
   110  // Height return the internal processed consensus height of the wallet
   111  func (w *Wallet) Height() types.BlockHeight {
   112  	w.mu.Lock()
   113  	defer w.mu.Unlock()
   114  
   115  	var height uint64
   116  	err := w.db.View(func(tx *bolt.Tx) error {
   117  		return encoding.Unmarshal(tx.Bucket(bucketWallet).Get(keyConsensusHeight), &height)
   118  	})
   119  	if err != nil {
   120  		return types.BlockHeight(0)
   121  	}
   122  	return types.BlockHeight(height)
   123  }
   124  
   125  // New creates a new wallet, loading any known addresses from the input file
   126  // name and then using the file to save in the future. Keys and addresses are
   127  // not loaded into the wallet during the call to 'new', but rather during the
   128  // call to 'Unlock'.
   129  func New(cs modules.ConsensusSet, tpool modules.TransactionPool, persistDir string) (*Wallet, error) {
   130  	return newWallet(cs, tpool, persistDir, &modules.ProductionDependencies{})
   131  }
   132  
   133  func newWallet(cs modules.ConsensusSet, tpool modules.TransactionPool, persistDir string, deps modules.Dependencies) (*Wallet, error) {
   134  	// Check for nil dependencies.
   135  	if cs == nil {
   136  		return nil, errNilConsensusSet
   137  	}
   138  	if tpool == nil {
   139  		return nil, errNilTpool
   140  	}
   141  
   142  	// Initialize the data structure.
   143  	w := &Wallet{
   144  		cs:    cs,
   145  		tpool: tpool,
   146  
   147  		keys:      make(map[types.UnlockHash]spendableKey),
   148  		lookahead: make(map[types.UnlockHash]uint64),
   149  
   150  		unconfirmedSets: make(map[modules.TransactionSetID][]types.TransactionID),
   151  
   152  		persistDir: persistDir,
   153  
   154  		deps: deps,
   155  	}
   156  	err := w.initPersist()
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	// begin the initial transaction
   162  	w.dbTx, err = w.db.Begin(true)
   163  	if err != nil {
   164  		w.log.Critical("ERROR: failed to start database update:", err)
   165  	}
   166  
   167  	// COMPATv131 we need to create the bucketProcessedTxnIndex if it doesn't exist
   168  	if w.dbTx.Bucket(bucketProcessedTransactions).Stats().KeyN > 0 &&
   169  		w.dbTx.Bucket(bucketProcessedTxnIndex).Stats().KeyN == 0 {
   170  		err = initProcessedTxnIndex(w.dbTx)
   171  		if err != nil {
   172  			return nil, err
   173  		}
   174  		// Save changes to disk
   175  		w.syncDB()
   176  	}
   177  
   178  	// make sure we commit on shutdown
   179  	w.tg.AfterStop(func() {
   180  		err := w.dbTx.Commit()
   181  		if err != nil {
   182  			w.log.Println("ERROR: failed to apply database update:", err)
   183  			w.dbTx.Rollback()
   184  		}
   185  	})
   186  	go w.threadedDBUpdate()
   187  
   188  	return w, nil
   189  }
   190  
   191  // Close terminates all ongoing processes involving the wallet, enabling
   192  // garbage collection.
   193  func (w *Wallet) Close() error {
   194  	if err := w.tg.Stop(); err != nil {
   195  		return err
   196  	}
   197  	var errs []error
   198  	// Lock the wallet outside of mu.Lock because Lock uses its own mu.Lock.
   199  	// Once the wallet is locked it cannot be unlocked except using the
   200  	// unexported unlock method (w.Unlock returns an error if the wallet's
   201  	// ThreadGroup is stopped).
   202  	if w.Unlocked() {
   203  		if err := w.Lock(); err != nil {
   204  			errs = append(errs, err)
   205  		}
   206  	}
   207  
   208  	w.cs.Unsubscribe(w)
   209  	w.tpool.Unsubscribe(w)
   210  
   211  	if err := w.log.Close(); err != nil {
   212  		errs = append(errs, fmt.Errorf("log.Close failed: %v", err))
   213  	}
   214  	return build.JoinErrors(errs, "; ")
   215  }
   216  
   217  // AllAddresses returns all addresses that the wallet is able to spend from,
   218  // including unseeded addresses. Addresses are returned sorted in byte-order.
   219  func (w *Wallet) AllAddresses() []types.UnlockHash {
   220  	w.mu.RLock()
   221  	defer w.mu.RUnlock()
   222  
   223  	addrs := make([]types.UnlockHash, 0, len(w.keys))
   224  	for addr := range w.keys {
   225  		addrs = append(addrs, addr)
   226  	}
   227  	sort.Slice(addrs, func(i, j int) bool {
   228  		return bytes.Compare(addrs[i][:], addrs[j][:]) < 0
   229  	})
   230  	return addrs
   231  }
   232  
   233  // Rescanning reports whether the wallet is currently rescanning the
   234  // blockchain.
   235  func (w *Wallet) Rescanning() bool {
   236  	rescanning := !w.scanLock.TryLock()
   237  	if !rescanning {
   238  		w.scanLock.Unlock()
   239  	}
   240  	return rescanning
   241  }
   242  
   243  // Settings returns the wallet's current settings
   244  func (w *Wallet) Settings() modules.WalletSettings {
   245  	return modules.WalletSettings{
   246  		NoDefrag: w.defragDisabled,
   247  	}
   248  }
   249  
   250  // SetSettings will update the settings for the wallet.
   251  func (w *Wallet) SetSettings(s modules.WalletSettings) {
   252  	w.defragDisabled = s.NoDefrag
   253  }