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 }