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