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 }