github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/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  	"errors"
     8  	"sort"
     9  	"sync"
    10  
    11  	"github.com/NebulousLabs/Sia/crypto"
    12  	"github.com/NebulousLabs/Sia/modules"
    13  	"github.com/NebulousLabs/Sia/persist"
    14  	"github.com/NebulousLabs/Sia/types"
    15  )
    16  
    17  const (
    18  	// RespendTimeout records the number of blocks that the wallet will wait
    19  	// before spending an output that has been spent in the past. If the
    20  	// transaction spending the output has not made it to the transaction pool
    21  	// after the limit, the assumption is that it never will.
    22  	RespendTimeout = 40
    23  )
    24  
    25  var (
    26  	errNilConsensusSet = errors.New("wallet cannot initialize with a nil consensus set")
    27  	errNilTpool        = errors.New("wallet cannot initialize with a nil transaction pool")
    28  )
    29  
    30  // spendableKey is a set of secret keys plus the corresponding unlock
    31  // conditions.  The public key can be derived from the secret key and then
    32  // matched to the corresponding public keys in the unlock conditions. All
    33  // addresses that are to be used in 'FundSiacoins' or 'FundSiafunds' in the
    34  // transaction builder must conform to this form of spendable key.
    35  type spendableKey struct {
    36  	UnlockConditions types.UnlockConditions
    37  	SecretKeys       []crypto.SecretKey
    38  }
    39  
    40  // Wallet is an object that tracks balances, creates keys and addresses,
    41  // manages building and sending transactions.
    42  type Wallet struct {
    43  	// unlocked indicates whether the wallet is currently storing secret keys
    44  	// in memory. subscribed indicates whether the wallet has subscribed to the
    45  	// consensus set yet - the wallet is unable to subscribe to the consensus
    46  	// set until it has been unlocked for the first time. The primary seed is
    47  	// used to generate new addresses for the wallet.
    48  	unlocked    bool
    49  	subscribed  bool
    50  	persist     WalletPersist
    51  	primarySeed modules.Seed
    52  
    53  	// The wallet's dependencies. The items 'consensusSetHeight' and
    54  	// 'siafundPool' are tracked separately from the consensus set to minimize
    55  	// the number of queries that the wallet needs to make to the consensus
    56  	// set; queries to the consensus set are very slow.
    57  	cs                 modules.ConsensusSet
    58  	tpool              modules.TransactionPool
    59  	consensusSetHeight types.BlockHeight
    60  	siafundPool        types.Currency
    61  
    62  	// The following set of fields are responsible for tracking the confirmed
    63  	// outputs, and for being able to spend them. The seeds are used to derive
    64  	// the keys that are tracked on the blockchain. All keys are pregenerated
    65  	// from the seeds, when checking new outputs or spending outputs, the seeds
    66  	// are not referenced at all. The seeds are only stored so that the user
    67  	// may access them.
    68  	//
    69  	// siacoinOutptus, siafundOutputs, and spentOutputs are kept so that they
    70  	// can be scanned when trying to fund transactions.
    71  	seeds          []modules.Seed
    72  	keys           map[types.UnlockHash]spendableKey
    73  	siacoinOutputs map[types.SiacoinOutputID]types.SiacoinOutput
    74  	siafundOutputs map[types.SiafundOutputID]types.SiafundOutput
    75  	spentOutputs   map[types.OutputID]types.BlockHeight
    76  
    77  	// The following fields are kept to track transaction history.
    78  	// walletTransactions are stored in chronological order, and have a map for
    79  	// constant time random access. The set of full transactions is kept as
    80  	// well, ordering can be determined by the walletTransactions slice.
    81  	//
    82  	// The unconfirmed transactions are kept the same way, except without the
    83  	// random access. It is assumed that the list of unconfirmed transactions
    84  	// will be small enough that this will not be a problem.
    85  	//
    86  	// historicOutputs is kept so that the values of transaction inputs can be
    87  	// determined. historicOutputs is never cleared, but in general should be
    88  	// small compared to the list of transactions.
    89  	processedTransactions            []modules.ProcessedTransaction
    90  	processedTransactionMap          map[types.TransactionID]*modules.ProcessedTransaction
    91  	unconfirmedProcessedTransactions []modules.ProcessedTransaction
    92  
    93  	// TODO: Storing the whole set of historic outputs is expensive and
    94  	// unnecessary. There's a better way to do it.
    95  	historicOutputs     map[types.OutputID]types.Currency
    96  	historicClaimStarts map[types.SiafundOutputID]types.Currency
    97  
    98  	persistDir string
    99  	log        *persist.Logger
   100  	mu         sync.RWMutex
   101  }
   102  
   103  // New creates a new wallet, loading any known addresses from the input file
   104  // name and then using the file to save in the future. Keys and addresses are
   105  // not loaded into the wallet during the call to 'new', but rather during the
   106  // call to 'Unlock'.
   107  func New(cs modules.ConsensusSet, tpool modules.TransactionPool, persistDir string) (*Wallet, error) {
   108  	// Check for nil dependencies.
   109  	if cs == nil {
   110  		return nil, errNilConsensusSet
   111  	}
   112  	if tpool == nil {
   113  		return nil, errNilTpool
   114  	}
   115  
   116  	// Initialize the data structure.
   117  	w := &Wallet{
   118  		cs:    cs,
   119  		tpool: tpool,
   120  
   121  		keys:           make(map[types.UnlockHash]spendableKey),
   122  		siacoinOutputs: make(map[types.SiacoinOutputID]types.SiacoinOutput),
   123  		siafundOutputs: make(map[types.SiafundOutputID]types.SiafundOutput),
   124  		spentOutputs:   make(map[types.OutputID]types.BlockHeight),
   125  
   126  		processedTransactionMap: make(map[types.TransactionID]*modules.ProcessedTransaction),
   127  
   128  		historicOutputs:     make(map[types.OutputID]types.Currency),
   129  		historicClaimStarts: make(map[types.SiafundOutputID]types.Currency),
   130  
   131  		persistDir: persistDir,
   132  	}
   133  	err := w.initPersist()
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	return w, nil
   138  }
   139  
   140  // AllAddresses returns all addresses that the wallet is able to spend from,
   141  // including unseeded addresses. Addresses are returned sorted in byte-order.
   142  func (w *Wallet) AllAddresses() []types.UnlockHash {
   143  	w.mu.RLock()
   144  	defer w.mu.RUnlock()
   145  
   146  	addrs := make(types.UnlockHashSlice, 0, len(w.keys))
   147  	for addr := range w.keys {
   148  		addrs = append(addrs, addr)
   149  	}
   150  	sort.Sort(addrs)
   151  	return addrs
   152  }