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 }