github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/wallet/offline.go (about)

     1  package wallet
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"math"
     7  
     8  	"SiaPrime/crypto"
     9  	"SiaPrime/modules"
    10  	"SiaPrime/types"
    11  )
    12  
    13  // UnspentOutputs returns the unspent outputs tracked by the wallet.
    14  func (w *Wallet) UnspentOutputs() ([]modules.UnspentOutput, error) {
    15  	if err := w.tg.Add(); err != nil {
    16  		return nil, err
    17  	}
    18  	defer w.tg.Done()
    19  	w.mu.Lock()
    20  	defer w.mu.Unlock()
    21  
    22  	// ensure durability of reported outputs
    23  	if err := w.syncDB(); err != nil {
    24  		return nil, err
    25  	}
    26  
    27  	// build initial list of confirmed outputs
    28  	var outputs []modules.UnspentOutput
    29  	dbForEachSiacoinOutput(w.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) {
    30  		outputs = append(outputs, modules.UnspentOutput{
    31  			FundType:   types.SpecifierSiacoinOutput,
    32  			ID:         types.OutputID(scoid),
    33  			UnlockHash: sco.UnlockHash,
    34  			Value:      sco.Value,
    35  		})
    36  	})
    37  	dbForEachSiafundOutput(w.dbTx, func(sfoid types.SiafundOutputID, sfo types.SiafundOutput) {
    38  		outputs = append(outputs, modules.UnspentOutput{
    39  			FundType:   types.SpecifierSiafundOutput,
    40  			ID:         types.OutputID(sfoid),
    41  			UnlockHash: sfo.UnlockHash,
    42  			Value:      sfo.Value,
    43  		})
    44  	})
    45  
    46  	// don't include outputs marked as spent in pending transactions
    47  	pending := make(map[types.OutputID]struct{})
    48  	for _, pt := range w.unconfirmedProcessedTransactions {
    49  		for _, input := range pt.Inputs {
    50  			if input.WalletAddress {
    51  				pending[input.ParentID] = struct{}{}
    52  			}
    53  		}
    54  	}
    55  	filtered := outputs[:0]
    56  	for _, o := range outputs {
    57  		if _, ok := pending[o.ID]; !ok {
    58  			filtered = append(filtered, o)
    59  		}
    60  	}
    61  	outputs = filtered
    62  
    63  	// set the confirmation height for each output
    64  outer:
    65  	for i, o := range outputs {
    66  		txnIndices, err := dbGetAddrTransactions(w.dbTx, o.UnlockHash)
    67  		if err != nil {
    68  			return nil, err
    69  		}
    70  		for _, j := range txnIndices {
    71  			pt, err := dbGetProcessedTransaction(w.dbTx, j)
    72  			if err != nil {
    73  				return nil, err
    74  			}
    75  			for _, sco := range pt.Outputs {
    76  				if sco.ID == o.ID {
    77  					outputs[i].ConfirmationHeight = pt.ConfirmationHeight
    78  					continue outer
    79  				}
    80  			}
    81  		}
    82  	}
    83  
    84  	// add unconfirmed outputs, except those that are spent in pending
    85  	// transactions
    86  	for _, pt := range w.unconfirmedProcessedTransactions {
    87  		for _, o := range pt.Outputs {
    88  			if _, ok := pending[o.ID]; !ok && o.WalletAddress {
    89  				outputs = append(outputs, modules.UnspentOutput{
    90  					FundType:           types.SpecifierSiacoinOutput,
    91  					ID:                 o.ID,
    92  					UnlockHash:         o.RelatedAddress,
    93  					Value:              o.Value,
    94  					ConfirmationHeight: types.BlockHeight(math.MaxUint64), // unconfirmed
    95  				})
    96  			}
    97  		}
    98  	}
    99  
   100  	// mark the watch-only outputs
   101  	for i, o := range outputs {
   102  		_, ok := w.watchedAddrs[o.UnlockHash]
   103  		outputs[i].IsWatchOnly = ok
   104  	}
   105  
   106  	return outputs, nil
   107  }
   108  
   109  // UnlockConditions returns the UnlockConditions for the specified address, if
   110  // they are known to the wallet.
   111  func (w *Wallet) UnlockConditions(addr types.UnlockHash) (uc types.UnlockConditions, err error) {
   112  	if err := w.tg.Add(); err != nil {
   113  		return types.UnlockConditions{}, err
   114  	}
   115  	defer w.tg.Done()
   116  	w.mu.RLock()
   117  	defer w.mu.RUnlock()
   118  	if !w.unlocked {
   119  		return types.UnlockConditions{}, modules.ErrLockedWallet
   120  	}
   121  	if sk, ok := w.keys[addr]; ok {
   122  		uc = sk.UnlockConditions
   123  	} else {
   124  		// not in memory; try database
   125  		uc, err = dbGetUnlockConditions(w.dbTx, addr)
   126  		if err != nil {
   127  			return types.UnlockConditions{}, errors.New("no record of UnlockConditions for that UnlockHash")
   128  		}
   129  	}
   130  	// make a copy of the public key slice; otherwise the caller can modify it
   131  	uc.PublicKeys = append([]types.SiaPublicKey(nil), uc.PublicKeys...)
   132  	return uc, nil
   133  }
   134  
   135  // AddUnlockConditions adds a set of UnlockConditions to the wallet database.
   136  func (w *Wallet) AddUnlockConditions(uc types.UnlockConditions) error {
   137  	if err := w.tg.Add(); err != nil {
   138  		return err
   139  	}
   140  	defer w.tg.Done()
   141  	w.mu.RLock()
   142  	defer w.mu.RUnlock()
   143  	if !w.unlocked {
   144  		return modules.ErrLockedWallet
   145  	}
   146  	return dbPutUnlockConditions(w.dbTx, uc)
   147  }
   148  
   149  // SignTransaction signs txn using secret keys known to the wallet. The
   150  // transaction should be complete with the exception of the Signature fields
   151  // of each TransactionSignature referenced by toSign. For convenience, if
   152  // toSign is empty, SignTransaction signs everything that it can.
   153  func (w *Wallet) SignTransaction(txn *types.Transaction, toSign []crypto.Hash) error {
   154  	if err := w.tg.Add(); err != nil {
   155  		return err
   156  	}
   157  	defer w.tg.Done()
   158  
   159  	w.mu.Lock()
   160  	defer w.mu.Unlock()
   161  	if !w.unlocked {
   162  		return modules.ErrLockedWallet
   163  	}
   164  	consensusHeight, err := dbGetConsensusHeight(w.dbTx)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	// if toSign is empty, sign all inputs that we have keys for
   170  	if len(toSign) == 0 {
   171  		for _, sci := range txn.SiacoinInputs {
   172  			if _, ok := w.keys[sci.UnlockConditions.UnlockHash()]; ok {
   173  				toSign = append(toSign, crypto.Hash(sci.ParentID))
   174  			}
   175  		}
   176  		for _, sfi := range txn.SiafundInputs {
   177  			if _, ok := w.keys[sfi.UnlockConditions.UnlockHash()]; ok {
   178  				toSign = append(toSign, crypto.Hash(sfi.ParentID))
   179  			}
   180  		}
   181  	}
   182  	return signTransaction(txn, w.keys, toSign, consensusHeight)
   183  }
   184  
   185  // SignTransaction signs txn using secret keys derived from seed. The
   186  // transaction should be complete with the exception of the Signature fields
   187  // of each TransactionSignature referenced by toSign, which must not be empty.
   188  //
   189  // SignTransaction must derive all of the keys from scratch, so it is
   190  // appreciably slower than calling the Wallet.SignTransaction method. Only the
   191  // first 1 million keys are derived.
   192  func SignTransaction(txn *types.Transaction, seed modules.Seed, toSign []crypto.Hash, height types.BlockHeight) error {
   193  	if len(toSign) == 0 {
   194  		// unlike the wallet method, we can't simply "sign all inputs we have
   195  		// keys for," because without generating all of the keys up front, we
   196  		// don't know how many inputs we actually have keys for.
   197  		return errors.New("toSign cannot be empty")
   198  	}
   199  	// generate keys in batches up to 1e6 before giving up
   200  	keys := make(map[types.UnlockHash]spendableKey, 1e6)
   201  	var keyIndex uint64
   202  	const keysPerBatch = 1000
   203  	for len(keys) < 1e6 {
   204  		for _, sk := range generateKeys(seed, keyIndex, keyIndex+keysPerBatch) {
   205  			keys[sk.UnlockConditions.UnlockHash()] = sk
   206  		}
   207  		keyIndex += keysPerBatch
   208  		if err := signTransaction(txn, keys, toSign, height); err == nil {
   209  			return nil
   210  		}
   211  	}
   212  	return signTransaction(txn, keys, toSign, height)
   213  }
   214  
   215  // signTransaction signs the specified inputs of txn using the specified keys.
   216  // It returns an error if any of the specified inputs cannot be signed.
   217  func signTransaction(txn *types.Transaction, keys map[types.UnlockHash]spendableKey, toSign []crypto.Hash, height types.BlockHeight) error {
   218  	// helper function to lookup unlock conditions in the txn associated with
   219  	// a transaction signature's ParentID
   220  	findUnlockConditions := func(id crypto.Hash) (types.UnlockConditions, bool) {
   221  		for _, sci := range txn.SiacoinInputs {
   222  			if crypto.Hash(sci.ParentID) == id {
   223  				return sci.UnlockConditions, true
   224  			}
   225  		}
   226  		for _, sfi := range txn.SiafundInputs {
   227  			if crypto.Hash(sfi.ParentID) == id {
   228  				return sfi.UnlockConditions, true
   229  			}
   230  		}
   231  		return types.UnlockConditions{}, false
   232  	}
   233  	// helper function to lookup the secret key that can sign
   234  	findSigningKey := func(uc types.UnlockConditions, pubkeyIndex uint64) (crypto.SecretKey, bool) {
   235  		if pubkeyIndex >= uint64(len(uc.PublicKeys)) {
   236  			return crypto.SecretKey{}, false
   237  		}
   238  		pk := uc.PublicKeys[pubkeyIndex]
   239  		sk, ok := keys[uc.UnlockHash()]
   240  		if !ok {
   241  			return crypto.SecretKey{}, false
   242  		}
   243  		for _, key := range sk.SecretKeys {
   244  			pubKey := key.PublicKey()
   245  			if bytes.Equal(pk.Key, pubKey[:]) {
   246  				return key, true
   247  			}
   248  		}
   249  		return crypto.SecretKey{}, false
   250  	}
   251  
   252  	for _, id := range toSign {
   253  		// find associated txn signature
   254  		sigIndex := -1
   255  		for i, sig := range txn.TransactionSignatures {
   256  			if sig.ParentID == id {
   257  				sigIndex = i
   258  				break
   259  			}
   260  		}
   261  		if sigIndex == -1 {
   262  			return errors.New("toSign references signatures not present in transaction")
   263  		}
   264  		// find associated input
   265  		uc, ok := findUnlockConditions(id)
   266  		if !ok {
   267  			return errors.New("toSign references IDs not present in transaction")
   268  		}
   269  		// lookup the signing key
   270  		sk, ok := findSigningKey(uc, txn.TransactionSignatures[sigIndex].PublicKeyIndex)
   271  		if !ok {
   272  			return errors.New("could not locate signing key for " + id.String())
   273  		}
   274  		// add signature
   275  		//
   276  		// NOTE: it's possible that the Signature field will already be filled
   277  		// out. Although we could save a bit of work by not signing it, in
   278  		// practice it's probably best to overwrite any existing signatures,
   279  		// since we know that ours will be valid.
   280  		sigHash := txn.SigHash(sigIndex, height)
   281  		encodedSig := crypto.SignHash(sigHash, sk)
   282  		txn.TransactionSignatures[sigIndex].Signature = encodedSig[:]
   283  	}
   284  
   285  	return nil
   286  }
   287  
   288  // AddWatchAddresses instructs the wallet to begin tracking a set of
   289  // addresses, in addition to the addresses it was previously tracking. If none
   290  // of the addresses have appeared in the blockchain, the unused flag may be
   291  // set to true. Otherwise, the wallet must rescan the blockchain to search for
   292  // transactions containing the addresses.
   293  func (w *Wallet) AddWatchAddresses(addrs []types.UnlockHash, unused bool) error {
   294  	if err := w.tg.Add(); err != nil {
   295  		return modules.ErrWalletShutdown
   296  	}
   297  	defer w.tg.Done()
   298  
   299  	err := func() error {
   300  		w.mu.Lock()
   301  		defer w.mu.Unlock()
   302  		if !w.unlocked {
   303  			return modules.ErrLockedWallet
   304  		}
   305  
   306  		// update in-memory map
   307  		for _, addr := range addrs {
   308  			w.watchedAddrs[addr] = struct{}{}
   309  		}
   310  		// update db
   311  		alladdrs := make([]types.UnlockHash, 0, len(w.watchedAddrs))
   312  		for addr := range w.watchedAddrs {
   313  			alladdrs = append(alladdrs, addr)
   314  		}
   315  		if err := dbPutWatchedAddresses(w.dbTx, alladdrs); err != nil {
   316  			return err
   317  		}
   318  
   319  		if !unused {
   320  			// prepare to rescan
   321  			if err := w.dbTx.DeleteBucket(bucketProcessedTransactions); err != nil {
   322  				return err
   323  			}
   324  			if _, err := w.dbTx.CreateBucket(bucketProcessedTransactions); err != nil {
   325  				return err
   326  			}
   327  			w.unconfirmedProcessedTransactions = nil
   328  			if err := dbPutConsensusChangeID(w.dbTx, modules.ConsensusChangeBeginning); err != nil {
   329  				return err
   330  			}
   331  			if err := dbPutConsensusHeight(w.dbTx, 0); err != nil {
   332  				return err
   333  			}
   334  		}
   335  		return w.syncDB()
   336  	}()
   337  	if err != nil {
   338  		return err
   339  	}
   340  
   341  	if !unused {
   342  		// rescan the blockchain
   343  		w.cs.Unsubscribe(w)
   344  		w.tpool.Unsubscribe(w)
   345  
   346  		done := make(chan struct{})
   347  		go w.rescanMessage(done)
   348  		defer close(done)
   349  		if err := w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning, w.tg.StopChan()); err != nil {
   350  			return err
   351  		}
   352  		w.tpool.TransactionPoolSubscribe(w)
   353  	}
   354  
   355  	return nil
   356  }
   357  
   358  // RemoveWatchAddresses instructs the wallet to stop tracking a set of
   359  // addresses and delete their associated transactions. If none of the
   360  // addresses have appeared in the blockchain, the unused flag may be set to
   361  // true. Otherwise, the wallet must rescan the blockchain to rebuild its
   362  // transaction history.
   363  func (w *Wallet) RemoveWatchAddresses(addrs []types.UnlockHash, unused bool) error {
   364  	if err := w.tg.Add(); err != nil {
   365  		return modules.ErrWalletShutdown
   366  	}
   367  	defer w.tg.Done()
   368  
   369  	err := func() error {
   370  		w.mu.Lock()
   371  		defer w.mu.Unlock()
   372  		if !w.unlocked {
   373  			return modules.ErrLockedWallet
   374  		}
   375  
   376  		// update in-memory map
   377  		for _, addr := range addrs {
   378  			delete(w.watchedAddrs, addr)
   379  		}
   380  		// update db
   381  		alladdrs := make([]types.UnlockHash, 0, len(w.watchedAddrs))
   382  		for addr := range w.watchedAddrs {
   383  			alladdrs = append(alladdrs, addr)
   384  		}
   385  		if err := dbPutWatchedAddresses(w.dbTx, alladdrs); err != nil {
   386  			return err
   387  		}
   388  		if !unused {
   389  			// outputs associated with the addresses may be present in the
   390  			// SiacoinOutputs bucket. Iterate through the bucket and remove
   391  			// any outputs that we are no longer watching.
   392  			var outputIDs []types.SiacoinOutputID
   393  			dbForEachSiacoinOutput(w.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) {
   394  				if !w.isWalletAddress(sco.UnlockHash) {
   395  					outputIDs = append(outputIDs, scoid)
   396  				}
   397  			})
   398  			for _, scoid := range outputIDs {
   399  				if err := dbDeleteSiacoinOutput(w.dbTx, scoid); err != nil {
   400  					return err
   401  				}
   402  			}
   403  
   404  			// prepare to rescan
   405  			if err := w.dbTx.DeleteBucket(bucketProcessedTransactions); err != nil {
   406  				return err
   407  			}
   408  			if _, err := w.dbTx.CreateBucket(bucketProcessedTransactions); err != nil {
   409  				return err
   410  			}
   411  			w.unconfirmedProcessedTransactions = nil
   412  			if err := dbPutConsensusChangeID(w.dbTx, modules.ConsensusChangeBeginning); err != nil {
   413  				return err
   414  			}
   415  			if err := dbPutConsensusHeight(w.dbTx, 0); err != nil {
   416  				return err
   417  			}
   418  		}
   419  		return w.syncDB()
   420  	}()
   421  	if err != nil {
   422  		return err
   423  	}
   424  
   425  	if !unused {
   426  		// rescan the blockchain
   427  		w.cs.Unsubscribe(w)
   428  		w.tpool.Unsubscribe(w)
   429  
   430  		done := make(chan struct{})
   431  		go w.rescanMessage(done)
   432  		defer close(done)
   433  		if err := w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning, w.tg.StopChan()); err != nil {
   434  			return err
   435  		}
   436  		w.tpool.TransactionPoolSubscribe(w)
   437  	}
   438  
   439  	return nil
   440  }
   441  
   442  // WatchAddresses returns the set of addresses that the wallet is currently
   443  // watching.
   444  func (w *Wallet) WatchAddresses() ([]types.UnlockHash, error) {
   445  	if err := w.tg.Add(); err != nil {
   446  		return nil, modules.ErrWalletShutdown
   447  	}
   448  	defer w.tg.Done()
   449  
   450  	w.mu.RLock()
   451  	defer w.mu.RUnlock()
   452  
   453  	addrs := make([]types.UnlockHash, 0, len(w.watchedAddrs))
   454  	for addr := range w.watchedAddrs {
   455  		addrs = append(addrs, addr)
   456  	}
   457  	return addrs, nil
   458  }