Exemple #1
0
func (w *Wallet) addRelevantTx(dbtx walletdb.ReadWriteTx, rec *wtxmgr.TxRecord,
	block *wtxmgr.BlockMeta) error {

	addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
	stakemgrNs := dbtx.ReadWriteBucket(wstakemgrNamespaceKey)
	txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey)

	// At the moment all notified transactions are assumed to actually be
	// relevant.  This assumption will not hold true when SPV support is
	// added, but until then, simply insert the transaction because there
	// should either be one or more relevant inputs or outputs.
	//
	// TODO This function is pretty bad corruption wise, it's very easy
	// to corrupt the wallet if you ctrl+c while in this function. This
	// needs desperate refactoring.

	tx := dcrutil.NewTx(&rec.MsgTx)
	txHash := rec.Hash

	// Handle incoming SStx; store them in the stake manager if we own
	// the OP_SSTX tagged out, except if we're operating as a stake pool
	// server. In that case, additionally consider the first commitment
	// output as well.
	if is, _ := stake.IsSStx(&rec.MsgTx); is {
		// Errors don't matter here.  If addrs is nil, the range below
		// does nothing.
		txOut := tx.MsgTx().TxOut[0]

		_, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.Version,
			txOut.PkScript, w.chainParams)
		insert := false
		for _, addr := range addrs {
			_, err := w.Manager.Address(addrmgrNs, addr)
			if err == nil {
				// We own the voting output pubkey or script and we're
				// not operating as a stake pool, so simply insert this
				// ticket now.
				if !w.stakePoolEnabled {
					insert = true
					break
				} else {
					// We are operating as a stake pool. The below
					// function will ONLY add the ticket into the
					// stake pool if it has been found within a
					// block.
					if block == nil {
						break
					}

					valid, errEval := w.evaluateStakePoolTicket(rec, block,
						addr)
					if valid {
						// Be sure to insert this into the user's stake
						// pool entry into the stake manager.
						poolTicket := &wstakemgr.PoolTicket{
							Ticket:       txHash,
							HeightTicket: uint32(block.Height),
							Status:       wstakemgr.TSImmatureOrLive,
						}
						errUpdate := w.StakeMgr.UpdateStakePoolUserTickets(
							stakemgrNs, addrmgrNs, addr, poolTicket)
						if errUpdate != nil {
							log.Warnf("Failed to insert stake pool "+
								"user ticket: %s", err.Error())
						}
						log.Debugf("Inserted stake pool ticket %v for user %v "+
							"into the stake store database", txHash, addr)

						insert = true
						break
					}

					// Log errors if there were any. At this point the ticket
					// must be invalid, so insert it into the list of invalid
					// user tickets.
					if errEval != nil {
						log.Warnf("Ticket %v failed ticket evaluation for "+
							"the stake pool: %s", rec.Hash, err.Error())
					}
					errUpdate := w.StakeMgr.UpdateStakePoolUserInvalTickets(
						stakemgrNs, addr, &rec.Hash)
					if errUpdate != nil {
						log.Warnf("Failed to update pool user %v with "+
							"invalid ticket %v", addr.EncodeAddress(),
							rec.Hash)
					}
				}
			}
		}

		if insert {
			err := w.StakeMgr.InsertSStx(stakemgrNs, tx, w.VoteBits)
			if err != nil {
				log.Errorf("Failed to insert SStx %v"+
					"into the stake store.", tx.Sha())
			}
		}
	}

	// Handle incoming SSGen; store them if we own
	// the ticket used to purchase them.
	if is, _ := stake.IsSSGen(&rec.MsgTx); is {
		if block != nil {
			txInHash := tx.MsgTx().TxIn[1].PreviousOutPoint.Hash
			if w.StakeMgr.CheckHashInStore(&txInHash) {
				w.StakeMgr.InsertSSGen(stakemgrNs, &block.Hash,
					int64(block.Height),
					&txHash,
					w.VoteBits.Bits,
					&txInHash)
			}

			// If we're running as a stake pool, insert
			// the stake pool user ticket update too.
			if w.stakePoolEnabled {
				txInHeight := tx.MsgTx().TxIn[1].BlockHeight
				poolTicket := &wstakemgr.PoolTicket{
					Ticket:       txInHash,
					HeightTicket: txInHeight,
					Status:       wstakemgr.TSVoted,
					SpentBy:      txHash,
					HeightSpent:  uint32(block.Height),
				}

				poolUser, err := w.StakeMgr.SStxAddress(stakemgrNs, &txInHash)
				if err != nil {
					log.Warnf("Failed to fetch stake pool user for "+
						"ticket %v (voted ticket)", txInHash)
				} else {
					err = w.StakeMgr.UpdateStakePoolUserTickets(
						stakemgrNs, addrmgrNs, poolUser, poolTicket)
					if err != nil {
						log.Warnf("Failed to update stake pool ticket for "+
							"stake pool user %s after voting",
							poolUser.EncodeAddress())
					} else {
						log.Debugf("Updated voted stake pool ticket %v "+
							"for user %v into the stake store database ("+
							"vote hash: %v)", txInHash, poolUser, txHash)
					}
				}
			}
		} else {
			// If there's no associated block, it's potentially a
			// doublespent SSGen. Just ignore it and wait for it
			// to later get into a block.
			return nil
		}
	}

	// Handle incoming SSRtx; store them if we own
	// the ticket used to purchase them.
	if is, _ := stake.IsSSRtx(&rec.MsgTx); is {
		if block != nil {
			txInHash := tx.MsgTx().TxIn[0].PreviousOutPoint.Hash

			if w.StakeMgr.CheckHashInStore(&txInHash) {
				w.StakeMgr.InsertSSRtx(stakemgrNs, &block.Hash,
					int64(block.Height),
					&txHash,
					&txInHash)
			}

			// If we're running as a stake pool, insert
			// the stake pool user ticket update too.
			if w.stakePoolEnabled {
				txInHeight := tx.MsgTx().TxIn[0].BlockHeight
				poolTicket := &wstakemgr.PoolTicket{
					Ticket:       txInHash,
					HeightTicket: txInHeight,
					Status:       wstakemgr.TSMissed,
					SpentBy:      txHash,
					HeightSpent:  uint32(block.Height),
				}

				poolUser, err := w.StakeMgr.SStxAddress(stakemgrNs, &txInHash)
				if err != nil {
					log.Warnf("failed to fetch stake pool user for "+
						"ticket %v (missed ticket)", txInHash)
				} else {
					err = w.StakeMgr.UpdateStakePoolUserTickets(
						stakemgrNs, addrmgrNs, poolUser, poolTicket)
					if err != nil {
						log.Warnf("failed to update stake pool ticket for "+
							"stake pool user %s after revoking",
							poolUser.EncodeAddress())
					} else {
						log.Debugf("Updated missed stake pool ticket %v "+
							"for user %v into the stake store database ("+
							"revocation hash: %v)", txInHash, poolUser, txHash)
					}
				}
			}
		}
	}

	err := w.TxStore.InsertTx(txmgrNs, addrmgrNs, rec, block)
	if err != nil {
		return err
	}

	// Handle input scripts that contain P2PKs that we care about.
	for i, input := range rec.MsgTx.TxIn {
		if txscript.IsMultisigSigScript(input.SignatureScript) {
			rs, err :=
				txscript.MultisigRedeemScriptFromScriptSig(
					input.SignatureScript)
			if err != nil {
				return err
			}

			class, addrs, _, err := txscript.ExtractPkScriptAddrs(
				txscript.DefaultScriptVersion, rs, w.chainParams)
			if err != nil {
				// Non-standard outputs are skipped.
				continue
			}
			if class != txscript.MultiSigTy {
				// This should never happen, but be paranoid.
				continue
			}

			isRelevant := false
			for _, addr := range addrs {
				_, err := w.Manager.Address(addrmgrNs, addr)
				if err == nil {
					isRelevant = true
					err = w.Manager.MarkUsed(addrmgrNs, addr)
					if err != nil {
						return err
					}
					log.Debugf("Marked address %v used", addr)
				} else {
					// Missing addresses are skipped.  Other errors should
					// be propagated.
					if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) {
						return err
					}
				}
			}

			// Add the script to the script databases.
			// TODO Markused script address? cj
			if isRelevant {
				err = w.TxStore.InsertTxScript(txmgrNs, rs)
				if err != nil {
					return err
				}
				var blockToUse *waddrmgr.BlockStamp
				if block != nil {
					blockToUse = &waddrmgr.BlockStamp{
						Height: block.Height,
						Hash:   block.Hash,
					}
				}
				mscriptaddr, err := w.Manager.ImportScript(addrmgrNs, rs, blockToUse)
				if err != nil {
					switch {
					// Don't care if it's already there.
					case waddrmgr.IsError(err, waddrmgr.ErrDuplicateAddress):
						break
					case waddrmgr.IsError(err, waddrmgr.ErrLocked):
						log.Warnf("failed to attempt script importation "+
							"of incoming tx script %x because addrmgr "+
							"was locked", rs)
						break
					default:
						return err
					}
				} else {
					// This is the first time seeing this script address
					// belongs to us, so do a rescan and see if there are
					// any other outputs to this address.
					job := &RescanJob{
						Addrs:     []dcrutil.Address{mscriptaddr.Address()},
						OutPoints: nil,
						BlockStamp: waddrmgr.BlockStamp{
							Height: 0,
							Hash:   *w.chainParams.GenesisHash,
						},
					}

					// Submit rescan job and log when the import has completed.
					// Do not block on finishing the rescan.  The rescan success
					// or failure is logged elsewhere, and the channel is not
					// required to be read, so discard the return value.
					_ = w.SubmitRescan(job)
				}
			}

			// If we're spending a multisig outpoint we know about,
			// update the outpoint. Inefficient because you deserialize
			// the entire multisig output info. Consider a specific
			// exists function in wtxmgr. The error here is skipped
			// because the absence of an multisignature output for
			// some script can not always be considered an error. For
			// example, the wallet might be rescanning as called from
			// the above function and so does not have the output
			// included yet.
			mso, err := w.TxStore.GetMultisigOutput(txmgrNs, &input.PreviousOutPoint)
			if mso != nil && err == nil {
				w.TxStore.SpendMultisigOut(txmgrNs, &input.PreviousOutPoint,
					rec.Hash,
					uint32(i))
			}
		}
	}

	// Check every output to determine whether it is controlled by a wallet
	// key.  If so, mark the output as a credit.
	for i, output := range rec.MsgTx.TxOut {
		// Ignore unspendable outputs.
		if output.Value == 0 {
			continue
		}

		class, addrs, _, err := txscript.ExtractPkScriptAddrs(output.Version,
			output.PkScript, w.chainParams)
		if err != nil {
			// Non-standard outputs are skipped.
			continue
		}
		isStakeType := class == txscript.StakeSubmissionTy ||
			class == txscript.StakeSubChangeTy ||
			class == txscript.StakeGenTy ||
			class == txscript.StakeRevocationTy
		if isStakeType {
			class, err = txscript.GetStakeOutSubclass(output.PkScript)
			if err != nil {
				log.Errorf("Unknown stake output subclass parse error "+
					"encountered: %v", err)
				continue
			}
		}

		for _, addr := range addrs {
			ma, err := w.Manager.Address(addrmgrNs, addr)
			if err == nil {
				// TODO: Credits should be added with the
				// account they belong to, so wtxmgr is able to
				// track per-account balances.
				err = w.TxStore.AddCredit(txmgrNs, rec, block,
					uint32(i), ma.Internal(), ma.Account())
				if err != nil {
					return err
				}
				err = w.Manager.MarkUsed(addrmgrNs, addr)
				if err != nil {
					return err
				}
				log.Debugf("Marked address %v used", addr)
				continue
			}

			// Missing addresses are skipped.  Other errors should
			// be propagated.
			if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) {
				return err
			}
		}

		// Handle P2SH addresses that are multisignature scripts
		// with keys that we own.
		if class == txscript.ScriptHashTy {
			var expandedScript []byte
			for _, addr := range addrs {
				// Search both the script store in the tx store
				// and the address manager for the redeem script.
				var err error
				expandedScript, err =
					w.TxStore.GetTxScript(txmgrNs,
						addr.ScriptAddress())
				if err != nil {
					return err
				}

				if expandedScript == nil {
					scrAddr, err := w.Manager.Address(addrmgrNs, addr)
					if err == nil {
						sa, ok := scrAddr.(waddrmgr.ManagedScriptAddress)
						if !ok {
							log.Warnf("address %v is not a script"+
								" address (type %T)",
								scrAddr.Address().EncodeAddress(),
								scrAddr.Address())
							continue
						}
						retrievedScript, err := sa.Script()
						if err != nil {
							log.Errorf("failed to decode redeemscript for "+
								"address %v: %v", addr.EncodeAddress(),
								err.Error())
							continue
						}
						expandedScript = retrievedScript

					} else {
						// We can't find this redeem script anywhere.
						// Skip this output.
						log.Debugf("failed to find redeemscript for "+
							"address %v in address manager: %v",
							addr.EncodeAddress(), err.Error())
						continue
					}
				}
			}

			// Otherwise, extract the actual addresses and
			// see if any belong to us.
			expClass, multisigAddrs, _, err := txscript.ExtractPkScriptAddrs(
				txscript.DefaultScriptVersion,
				expandedScript,
				w.chainParams)
			if err != nil {
				return err
			}

			// Skip non-multisig scripts.
			if expClass != txscript.MultiSigTy {
				continue
			}

			for _, maddr := range multisigAddrs {
				_, err := w.Manager.Address(addrmgrNs, maddr)
				// An address we own; handle accordingly.
				if err == nil {
					errStore := w.TxStore.AddMultisigOut(
						txmgrNs, rec, block, uint32(i))
					if errStore != nil {
						// This will throw if there are multiple private keys
						// for this multisignature output owned by the wallet,
						// so it's routed to debug.
						log.Debugf("unable to add multisignature output: %v",
							errStore.Error())
					}
				}
			}
		}
	}

	// Send notification of mined or unmined transaction to any interested
	// clients.
	//
	// TODO: Avoid the extra db hits.
	if block == nil {
		details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, nil)
		if err != nil {
			log.Errorf("Cannot query transaction details for notifiation: %v",
				err)
		} else {
			w.NtfnServer.notifyUnminedTransaction(dbtx, details)
		}
	} else {
		details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, &block.Block)
		if err != nil {
			log.Errorf("Cannot query transaction details for notifiation: %v",
				err)
		} else {
			w.NtfnServer.notifyMinedTransaction(dbtx, details, block)
		}
	}

	return nil
}
Exemple #2
0
// GenerateRevocation generates a revocation (SSRtx), signs it, and
// submits it by SendRawTransaction. It also stores a record of it
// in the local database.
func (s *StakeStore) generateRevocation(ns walletdb.ReadWriteBucket, waddrmgrNs walletdb.ReadBucket, blockHash *chainhash.Hash, height int64, sstxHash *chainhash.Hash,
	allowHighFees bool) (*StakeNotification, error) {

	// 1. Fetch the SStx, then calculate all the values we'll need later for
	// the generation of the SSRtx tx outputs.
	sstxRecord, err := s.getSStx(ns, sstxHash)
	if err != nil {
		return nil, err
	}
	sstx := sstxRecord.tx

	// Store the sstx pubkeyhashes and amounts as found in the transaction
	// outputs.
	// TODO Get information on the allowable fee range for the revocation
	// and check to make sure we don't overflow that.
	sstxPayTypes, sstxPkhs, sstxAmts, _, _, _ :=
		stake.TxSStxStakeOutputInfo(sstx.MsgTx())
	ssrtxCalcAmts := stake.CalculateRewards(sstxAmts, sstx.MsgTx().TxOut[0].Value,
		int64(0))

	// Calculate the fee to use for this revocation based on the fee
	// per KB that is standard for mainnet.
	revocationSizeEst := estimateSSRtxTxSize(1, len(sstxPkhs))
	revocationFee := txrules.FeeForSerializeSize(revocationFeePerKB,
		revocationSizeEst)

	// 2. Add the only input.
	msgTx := wire.NewMsgTx()

	// SStx tagged output as an OutPoint; reference this as
	// the only input.
	prevOut := wire.NewOutPoint(sstxHash,
		0, // Index 0
		1) // Tree stake
	txIn := wire.NewTxIn(prevOut, []byte{})
	msgTx.AddTxIn(txIn)

	// 3. Add all the OP_SSRTX tagged outputs.

	// Add all the SSRtx-tagged transaction outputs to the transaction after
	// performing some validity checks.
	feeAdded := false
	for i, sstxPkh := range sstxPkhs {
		// Create a new script which pays to the provided address specified in
		// the original ticket tx.
		var ssrtxOutScript []byte
		switch sstxPayTypes[i] {
		case false: // P2PKH
			ssrtxOutScript, err = txscript.PayToSSRtxPKHDirect(sstxPkh)
			if err != nil {
				return nil, err
			}
		case true: // P2SH
			ssrtxOutScript, err = txscript.PayToSSRtxSHDirect(sstxPkh)
			if err != nil {
				return nil, err
			}
		}

		// Add a fee from an output that has enough.
		amt := ssrtxCalcAmts[i]
		if !feeAdded && ssrtxCalcAmts[i] >= int64(revocationFee) {
			amt -= int64(revocationFee)
			feeAdded = true
		}

		// Add the txout to our SSRtx tx.
		txOut := wire.NewTxOut(amt, ssrtxOutScript)
		msgTx.AddTxOut(txOut)
	}

	// Check to make sure our SSRtx was created correctly.
	_, err = stake.IsSSRtx(msgTx)
	if err != nil {
		return nil, err
	}

	// Sign the transaction.
	err = s.SignVRTransaction(waddrmgrNs, msgTx, sstx, false)
	if err != nil {
		return nil, err
	}

	// Store the information about the SSRtx.
	hash := msgTx.TxSha()
	err = s.insertSSRtx(ns,
		blockHash,
		height,
		&hash,
		sstx.Sha())
	if err != nil {
		return nil, err
	}

	// Send the transaction.
	ssrtxSha, err := s.chainSvr.SendRawTransaction(msgTx, allowHighFees)
	if err != nil {
		return nil, err
	}

	log.Debugf("Generated SSRtx %v. The ticket used to "+
		"generate the SSRtx was %v.", ssrtxSha, sstx.Sha())

	// Generate a notification to return.
	ntfn := &StakeNotification{
		TxType:    int8(stake.TxTypeSSRtx),
		TxHash:    *ssrtxSha,
		BlockHash: chainhash.Hash{},
		Height:    0,
		Amount:    0,
		SStxIn:    *sstx.Sha(),
		VoteBits:  0,
	}

	return ntfn, nil
}
Exemple #3
0
func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord,
	block *wtxmgr.BlockMeta) error {
	// TODO: The transaction store and address manager need to be updated
	// together, but each operate under different namespaces and are changed
	// under new transactions.  This is not error safe as we lose
	// transaction semantics.
	//
	// I'm unsure of the best way to solve this.  Some possible solutions
	// and drawbacks:
	//
	//   1. Open write transactions here and pass the handle to every
	//      waddrmr and wtxmgr method.  This complicates the caller code
	//      everywhere, however.
	//
	//   2. Move the wtxmgr namespace into the waddrmgr namespace, likely
	//      under its own bucket.  This entire function can then be moved
	//      into the waddrmgr package, which updates the nested wtxmgr.
	//      This removes some of separation between the components.
	//
	//   3. Use multiple wtxmgrs, one for each account, nested in the
	//      waddrmgr namespace.  This still provides some sort of logical
	//      separation (transaction handling remains in another package, and
	//      is simply used by waddrmgr), but may result in duplicate
	//      transactions being saved if they are relevant to multiple
	//      accounts.
	//
	//   4. Store wtxmgr-related details under the waddrmgr namespace, but
	//      solve the drawback of #3 by splitting wtxmgr to save entire
	//      transaction records globally for all accounts, with
	//      credit/debit/balance tracking per account.  Each account would
	//      also save the relevant transaction hashes and block incidence so
	//      the full transaction can be loaded from the waddrmgr
	//      transactions bucket.  This currently seems like the best
	//      solution.

	// At the moment all notified transactions are assumed to actually be
	// relevant.  This assumption will not hold true when SPV support is
	// added, but until then, simply insert the transaction because there
	// should either be one or more relevant inputs or outputs.
	//
	// TODO This function is pretty bad corruption wise, it's very easy
	// to corrupt the wallet if you ctrl+c while in this function. This
	// needs desperate refactoring.

	tx := dcrutil.NewTx(&rec.MsgTx)

	// Handle incoming SStx; store them in the stake manager if we own
	// the OP_SSTX tagged out.
	if is, _ := stake.IsSStx(tx); is {
		// Errors don't matter here.  If addrs is nil, the range below
		// does nothing.
		txOut := tx.MsgTx().TxOut[0]

		_, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.Version,
			txOut.PkScript, w.chainParams)
		insert := false
		for _, addr := range addrs {
			_, err := w.Manager.Address(addr)
			if err == nil {
				insert = true
				break
			}
		}

		if insert {
			err := w.StakeMgr.InsertSStx(tx)
			if err != nil {
				log.Errorf("Failed to insert SStx %v"+
					"into the stake store.", tx.Sha())
			}
		}
	}

	// Handle incoming SSGen; store them if we own
	// the ticket used to purchase them.
	if is, _ := stake.IsSSGen(tx); is {
		if block != nil {
			txInHash := tx.MsgTx().TxIn[1].PreviousOutPoint.Hash
			if w.StakeMgr.CheckHashInStore(&txInHash) {
				w.StakeMgr.InsertSSGen(&block.Hash,
					int64(block.Height),
					tx.Sha(),
					w.VoteBits,
					&txInHash)
			}
		} else {
			// If there's no associated block, it's potentially a
			// doublespent SSGen. Just ignore it and wait for it
			// to later get into a block.
			return nil
		}
	}

	// Handle incoming SSRtx; store them if we own
	// the ticket used to purchase them.
	if is, _ := stake.IsSSRtx(tx); is {
		if block != nil {
			txInHash := tx.MsgTx().TxIn[0].PreviousOutPoint.Hash

			if w.StakeMgr.CheckHashInStore(&txInHash) {
				w.StakeMgr.InsertSSRtx(&block.Hash,
					int64(block.Height),
					tx.Sha(),
					&txInHash)
			}
		}
	}

	err := w.TxStore.InsertTx(rec, block)
	if err != nil {
		return err
	}

	// Handle input scripts that contain P2PKs that we care about.
	for i, input := range rec.MsgTx.TxIn {
		if txscript.IsMultisigSigScript(input.SignatureScript) {
			rs, err :=
				txscript.MultisigRedeemScriptFromScriptSig(
					input.SignatureScript)
			if err != nil {
				return err
			}

			class, addrs, _, err := txscript.ExtractPkScriptAddrs(
				txscript.DefaultScriptVersion, rs, w.chainParams)
			if err != nil {
				// Non-standard outputs are skipped.
				continue
			}
			if class != txscript.MultiSigTy {
				// This should never happen, but be paranoid.
				continue
			}

			isRelevant := false
			for _, addr := range addrs {
				_, err := w.Manager.Address(addr)
				if err == nil {
					isRelevant = true
					err = w.Manager.MarkUsed(addr)
					if err != nil {
						return err
					}
					log.Debugf("Marked address %v used", addr)
				} else {
					// Missing addresses are skipped.  Other errors should
					// be propagated.
					if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) {
						return err
					}
				}
			}

			// Add the script to the script databases.
			// TODO Markused script address? cj
			if isRelevant {
				err = w.TxStore.InsertTxScript(rs)
				if err != nil {
					return err
				}
				var blockToUse *waddrmgr.BlockStamp
				if block != nil {
					blockToUse = &waddrmgr.BlockStamp{block.Height, block.Hash}
				}
				mscriptaddr, err := w.Manager.ImportScript(rs, blockToUse)
				if err != nil {
					switch {
					// Don't care if it's already there.
					case waddrmgr.IsError(err, waddrmgr.ErrDuplicateAddress):
						break
					case waddrmgr.IsError(err, waddrmgr.ErrLocked):
						log.Debugf("failed to attempt script importation " +
							"of incoming tx because addrmgr was locked")
						break
					default:
						return err
					}
				} else {
					// This is the first time seeing this script address
					// belongs to us, so do a rescan and see if there are
					// any other outputs to this address.
					job := &RescanJob{
						Addrs:     []dcrutil.Address{mscriptaddr.Address()},
						OutPoints: nil,
						BlockStamp: waddrmgr.BlockStamp{
							0,
							*w.chainParams.GenesisHash,
						},
					}

					// Submit rescan job and log when the import has completed.
					// Do not block on finishing the rescan.  The rescan success
					// or failure is logged elsewhere, and the channel is not
					// required to be read, so discard the return value.
					_ = w.SubmitRescan(job)
				}
			}

			// If we're spending a multisig outpoint we
			// know about, update the outpoint.
			// Inefficient because you deserialize the
			// entire multisig output info, consider
			// a specific exists function in wtxmgr. cj
			mso, err := w.TxStore.GetMultisigOutput(&input.PreviousOutPoint)
			if err != nil {
				return err
			}
			if mso != nil {
				w.TxStore.SpendMultisigOut(&input.PreviousOutPoint,
					rec.Hash,
					uint32(i))
			}
		}
	}

	// Check every output to determine whether it is controlled by a wallet
	// key.  If so, mark the output as a credit.
	for i, output := range rec.MsgTx.TxOut {
		class, addrs, _, err := txscript.ExtractPkScriptAddrs(output.Version,
			output.PkScript, w.chainParams)
		if err != nil {
			// Non-standard outputs are skipped.
			continue
		}
		isStakeType := class == txscript.StakeSubmissionTy ||
			class == txscript.StakeSubChangeTy ||
			class == txscript.StakeGenTy ||
			class == txscript.StakeRevocationTy
		if isStakeType {
			class, err = txscript.GetStakeOutSubclass(output.PkScript)
			if err != nil {
				log.Errorf("Unknown stake output subclass encountered")
				continue
			}
		}
		switch {
		case class == txscript.PubKeyHashTy:
			for _, addr := range addrs {
				ma, err := w.Manager.Address(addr)
				if err == nil {
					// TODO: Credits should be added with the
					// account they belong to, so wtxmgr is able to
					// track per-account balances.
					err = w.TxStore.AddCredit(rec, block, uint32(i),
						ma.Internal())
					if err != nil {
						return err
					}
					err = w.Manager.MarkUsed(addr)
					if err != nil {
						return err
					}
					log.Debugf("Marked address %v used", addr)
					continue
				}

				// Missing addresses are skipped.  Other errors should
				// be propagated.
				if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) {
					return err
				}
			}
		// Handle P2SH addresses that are multisignature scripts
		// with keys that we own.
		case class == txscript.ScriptHashTy:
			var expandedScript []byte
			for _, addr := range addrs {
				var err error
				expandedScript, err =
					w.TxStore.GetTxScript(addr.ScriptAddress())
				if err != nil {
					return err
				}

				// TODO make this work, the type conversion is broken cj
				//scrAddr, err := w.Manager.Address(addr)
				//if err == nil {
				//	addrTyped := scrAddr.(*waddrmgr.ManagedScriptAddress)
				//	retrievedScript, err := addrTyped.Script()
				//	if err == nil {
				//		expandedScript = retrievedScript
				//	}
				//}
			}

			// We don't have the script for this hash, skip.
			if expandedScript == nil {
				continue
			}

			// Otherwise, extract the actual addresses and
			// see if any belong to us.
			expClass, multisigAddrs, _, err := txscript.ExtractPkScriptAddrs(
				txscript.DefaultScriptVersion,
				expandedScript,
				w.chainParams)
			if err != nil {
				return err
			}

			// Skip non-multisig scripts.
			if expClass != txscript.MultiSigTy {
				continue
			}

			for _, maddr := range multisigAddrs {
				_, err := w.Manager.Address(maddr)
				// An address we own; handle accordingly.
				if err == nil {
					errStore := w.TxStore.AddMultisigOut(rec, block, uint32(i))
					if errStore != nil {
						// This will throw if there are multiple private keys
						// for this multisignature output owned by the wallet,
						// so it's routed to debug.
						log.Debugf("unable to add multisignature output: %v",
							errStore.Error())
					}
				}
			}
		}
	}

	// TODO: Notify connected clients of the added transaction.

	bs, err := w.chainSvr.BlockStamp()
	if err == nil {
		w.notifyBalances(bs.Height, wtxmgr.BFBalanceSpendable)
	}

	return nil
}
Exemple #4
0
// GenerateRevocation generates a revocation (SSRtx), signs it, and
// submits it by SendRawTransaction. It also stores a record of it
// in the local database.
func (s *StakeStore) generateRevocation(blockHash *chainhash.Hash, height int64,
	sstxHash *chainhash.Hash) (*StakeNotification, error) {
	var revocationFee int64
	switch {
	case s.Params == &chaincfg.MainNetParams:
		revocationFee = revocationFeeMainNet
	case s.Params == &chaincfg.TestNetParams:
		revocationFee = revocationFeeTestNet
	default:
		revocationFee = revocationFeeTestNet
	}

	// 1. Fetch the SStx, then calculate all the values we'll need later for
	// the generation of the SSRtx tx outputs.
	sstxRecord, err := s.getSStx(sstxHash)
	if err != nil {
		return nil, err
	}
	sstx := sstxRecord.tx

	// Store the sstx pubkeyhashes and amounts as found in the transaction
	// outputs.
	// TODO Get information on the allowable fee range for the revocation
	// and check to make sure we don't overflow that.
	sstxPayTypes, sstxPkhs, sstxAmts, _, _, _ :=
		stake.GetSStxStakeOutputInfo(sstx)

	ssrtxCalcAmts := stake.GetStakeRewards(sstxAmts, sstx.MsgTx().TxOut[0].Value,
		int64(0))

	// 2. Add the only input.
	msgTx := wire.NewMsgTx()

	// SStx tagged output as an OutPoint; reference this as
	// the only input.
	prevOut := wire.NewOutPoint(sstxHash,
		0, // Index 0
		1) // Tree stake
	txIn := wire.NewTxIn(prevOut, []byte{})
	msgTx.AddTxIn(txIn)

	// 3. Add all the OP_SSRTX tagged outputs.

	// Add all the SSRtx-tagged transaction outputs to the transaction after
	// performing some validity checks.
	feeAdded := false
	for i, sstxPkh := range sstxPkhs {
		// Create a new script which pays to the provided address specified in
		// the original ticket tx.
		var ssrtxOutScript []byte
		switch sstxPayTypes[i] {
		case false: // P2PKH
			ssrtxOutScript, err = txscript.PayToSSRtxPKHDirect(sstxPkh)
			if err != nil {
				return nil, err
			}
		case true: // P2SH
			ssrtxOutScript, err = txscript.PayToSSRtxSHDirect(sstxPkh)
			if err != nil {
				return nil, err
			}
		}

		// Add a fee from an output that has enough.
		amt := ssrtxCalcAmts[i]
		if !feeAdded && ssrtxCalcAmts[i] >= revocationFee {
			amt -= revocationFee
			feeAdded = true
		}

		// Add the txout to our SSRtx tx.
		txOut := wire.NewTxOut(amt, ssrtxOutScript)
		msgTx.AddTxOut(txOut)
	}

	// Check to make sure our SSRtx was created correctly.
	ssrtxTx := dcrutil.NewTx(msgTx)
	ssrtxTx.SetTree(dcrutil.TxTreeStake)
	_, err = stake.IsSSRtx(ssrtxTx)
	if err != nil {
		return nil, err
	}

	// Sign the transaction.
	err = s.SignVRTransaction(msgTx, sstx, false)
	if err != nil {
		return nil, err
	}

	// Send the transaction.
	ssrtxSha, err := s.chainSvr.SendRawTransaction(msgTx, false)
	if err != nil {
		return nil, err
	}

	// Store the information about the SSRtx.
	err = s.insertSSRtx(blockHash,
		height,
		ssrtxSha,
		sstx.Sha())
	if err != nil {
		return nil, err
	}

	log.Debugf("Generated SSRtx %v. "+
		"The ticket used to generate the SSRtx was %v.",
		ssrtxSha, sstx.Sha())

	// Generate a notification to return.
	ntfn := &StakeNotification{
		TxType:    int8(stake.TxTypeSSRtx),
		TxHash:    *ssrtxSha,
		BlockHash: chainhash.Hash{},
		Height:    0,
		Amount:    0,
		SStxIn:    *sstx.Sha(),
		VoteBits:  0,
	}

	return ntfn, nil
}
Exemple #5
0
// disconnectTransactions updates the passed map by undoing transaction and
// spend information for all transactions in the passed block.  Only
// transactions in the passed map are updated.
// This function should only ever have to disconnect transactions from the main
// chain, so most of the calls are directly the the tmdb which contains all this
// data in an organized bucket.
func (b *BlockChain) disconnectTickets(tixStore TicketStore,
	node *blockNode,
	block *dcrutil.Block) error {

	tM := int64(b.chainParams.TicketMaturity)
	height := node.height

	// Nothing to do if tickets haven't yet possibly matured.
	if height < b.chainParams.StakeEnabledHeight {
		return nil
	}

	// PART 1: Remove newly maturing tickets

	// Calculate block number for where new tickets matured from and retrieve
	// this block from db.
	matureNode, err := b.getNodeAtHeightFromTopNode(node, tM)
	if err != nil {
		return err
	}

	matureBlock, errBlock := b.getBlockFromHash(matureNode.hash)
	if errBlock != nil {
		return errBlock
	}

	// Store pointers to empty ticket data in the ticket store and mark them as
	// non-existing.
	for _, stx := range matureBlock.STransactions() {
		if is, _ := stake.IsSStx(stx); is {
			// Leave this pointing to nothing, as the ticket technically does not
			// exist. It may exist when we add blocks later, but we can fill it
			// out then.
			td := &stake.TicketData{}

			tpd := NewTicketPatchData(td,
				TiNonexisting,
				nil)

			tixStore[*stx.Sha()] = tpd
		}
	}

	// PART 2: Unrevoke any SSRtx in this block and restore them as
	// missed tickets.
	for _, stx := range block.STransactions() {
		if is, _ := stake.IsSSRtx(stx); is {
			// Move the revoked ticket to missed tickets. Obtain the
			// revoked ticket data from the ticket database.
			msgTx := stx.MsgTx()
			sstxIn := msgTx.TxIn[0] // sstx input
			sstxHash := sstxIn.PreviousOutPoint.Hash

			td := b.tmdb.GetRevokedTicket(sstxHash)
			if td == nil {
				return fmt.Errorf("Failed to find revoked ticket %v in tmdb",
					sstxHash)
			}

			tpd := NewTicketPatchData(td,
				TiMissed,
				nil)

			tixStore[sstxHash] = tpd
		}
	}

	// PART 3: Unspend or unmiss all tickets spent/missed/expired at this block.

	// Query the stake db for used tickets (spentTicketDb), which includes all of
	// the spent and missed tickets.
	spentTickets, errDump := b.tmdb.DumpSpentTickets(height)
	if errDump != nil {
		return errDump
	}

	// Move all of these tickets into the ticket store as available tickets.
	for hash, td := range spentTickets {
		tpd := NewTicketPatchData(td,
			TiAvailable,
			nil)

		tixStore[hash] = tpd
	}

	return nil
}
Exemple #6
0
// connectTickets updates the passed map by removing removing any tickets
// from the ticket pool that have been considered spent or missed in this block
// according to the block header. Then, it connects all the newly mature tickets
// to the passed map.
func (b *BlockChain) connectTickets(tixStore TicketStore,
	node *blockNode,
	block *dcrutil.Block) error {
	if tixStore == nil {
		return fmt.Errorf("nil ticket store!")
	}

	// Nothing to do if tickets haven't yet possibly matured.
	height := node.height
	if height < b.chainParams.StakeEnabledHeight {
		return nil
	}

	parentBlock, err := b.GetBlockFromHash(node.parentHash)
	if err != nil {
		return err
	}

	revocations := node.header.Revocations

	tM := int64(b.chainParams.TicketMaturity)

	// Skip a number of validation steps before we requiring chain
	// voting.
	if node.height >= b.chainParams.StakeValidationHeight {
		regularTxTreeValid := dcrutil.IsFlagSet16(node.header.VoteBits,
			dcrutil.BlockValid)
		thisNodeStakeViewpoint := ViewpointPrevInvalidStake
		if regularTxTreeValid {
			thisNodeStakeViewpoint = ViewpointPrevValidStake
		}

		// We need the missed tickets bucket from the original perspective of
		// the node.
		missedTickets, err := b.GenerateMissedTickets(tixStore)
		if err != nil {
			return err
		}

		// TxStore at blockchain HEAD + TxTreeRegular of prevBlock (if
		// validated) for this node.
		txInputStoreStake, err := b.fetchInputTransactions(node, block,
			thisNodeStakeViewpoint)
		if err != nil {
			errStr := fmt.Sprintf("fetchInputTransactions failed for incoming "+
				"node %v; error given: %v", node.hash, err)
			return errors.New(errStr)
		}

		// PART 1: Spend/miss winner tickets

		// Iterate through all the SSGen (vote) tx in the block and add them to
		// a map of tickets that were actually used.
		spentTicketsFromBlock := make(map[chainhash.Hash]bool)
		numberOfSSgen := 0
		for _, staketx := range block.STransactions() {
			if is, _ := stake.IsSSGen(staketx); is {
				msgTx := staketx.MsgTx()
				sstxIn := msgTx.TxIn[1] // sstx input
				sstxHash := sstxIn.PreviousOutPoint.Hash

				originTx, exists := txInputStoreStake[sstxHash]
				if !exists {
					str := fmt.Sprintf("unable to find input transaction "+
						"%v for transaction %v", sstxHash, staketx.Sha())
					return ruleError(ErrMissingTx, str)
				}

				sstxHeight := originTx.BlockHeight

				// Check maturity of ticket; we can only spend the ticket after it
				// hits maturity at height + tM + 1.
				if (height - sstxHeight) < (tM + 1) {
					blockSha := block.Sha()
					errStr := fmt.Sprintf("Error: A ticket spend as an SSGen in "+
						"block height %v was immature! Block sha %v",
						height,
						blockSha)
					return errors.New(errStr)
				}

				// Fill out the ticket data.
				spentTicketsFromBlock[sstxHash] = true
				numberOfSSgen++
			}
		}

		// Obtain the TicketsPerBlock many tickets that were selected this round,
		// then check these against the tickets that were actually used to make
		// sure that any SSGen actually match the selected tickets. Commit the
		// spent or missed tickets to the ticket store after.
		spentAndMissedTickets := make(TicketStore)
		tixSpent := 0
		tixMissed := 0

		// Sort the entire list of tickets lexicographically by sorting
		// each bucket and then appending it to the list. Start by generating
		// a prefix matched map of tickets to speed up the lookup.
		tpdBucketMap := make(map[uint8][]*TicketPatchData)
		for _, tpd := range tixStore {
			// Bucket does not exist.
			if _, ok := tpdBucketMap[tpd.td.Prefix]; !ok {
				tpdBucketMap[tpd.td.Prefix] = make([]*TicketPatchData, 1)
				tpdBucketMap[tpd.td.Prefix][0] = tpd
			} else {
				// Bucket exists.
				data := tpdBucketMap[tpd.td.Prefix]
				tpdBucketMap[tpd.td.Prefix] = append(data, tpd)
			}
		}
		totalTickets := 0
		sortedSlice := make([]*stake.TicketData, 0)
		for i := 0; i < stake.BucketsSize; i++ {
			ltb, err := b.GenerateLiveTicketBucket(tixStore, tpdBucketMap,
				uint8(i))
			if err != nil {
				h := node.hash
				str := fmt.Sprintf("Failed to generate live ticket bucket "+
					"%v for node %v, height %v! Error: %v",
					i,
					h,
					node.height,
					err.Error())
				return fmt.Errorf(str)
			}
			mapLen := len(ltb)

			tempTdSlice := stake.NewTicketDataSlice(mapLen)
			itr := 0 // Iterator
			for _, td := range ltb {
				tempTdSlice[itr] = td
				itr++
				totalTickets++
			}
			sort.Sort(tempTdSlice)
			sortedSlice = append(sortedSlice, tempTdSlice...)
		}

		// Use the parent block's header to seed a PRNG that picks the
		// lottery winners.
		ticketsPerBlock := int(b.chainParams.TicketsPerBlock)
		pbhB, err := parentBlock.MsgBlock().Header.Bytes()
		if err != nil {
			return err
		}
		prng := stake.NewHash256PRNG(pbhB)
		ts, err := stake.FindTicketIdxs(int64(totalTickets), ticketsPerBlock, prng)
		if err != nil {
			return err
		}

		ticketsToSpendOrMiss := make([]*stake.TicketData, ticketsPerBlock,
			ticketsPerBlock)
		for i, idx := range ts {
			ticketsToSpendOrMiss[i] = sortedSlice[idx]
		}

		// Spend or miss these tickets by checking for their existence in the
		// passed spentTicketsFromBlock map.
		for _, ticket := range ticketsToSpendOrMiss {
			// Move the ticket from active tickets map into the used tickets
			// map if the ticket was spent.
			wasSpent, _ := spentTicketsFromBlock[ticket.SStxHash]

			if wasSpent {
				tpd := NewTicketPatchData(ticket, TiSpent, nil)
				spentAndMissedTickets[ticket.SStxHash] = tpd
				tixSpent++
			} else { // Ticket missed being spent and --> false or nil
				tpd := NewTicketPatchData(ticket, TiMissed, nil)
				spentAndMissedTickets[ticket.SStxHash] = tpd
				tixMissed++
			}
		}

		// This error is thrown if for some reason there exists an SSGen in
		// the block that doesn't spend a ticket from the eligible list of
		// tickets, thus making it invalid.
		if tixSpent != numberOfSSgen {
			errStr := fmt.Sprintf("an invalid number %v "+
				"tickets was spent, but %v many tickets should "+
				"have been spent!", tixSpent, numberOfSSgen)
			return errors.New(errStr)
		}

		if tixMissed != (ticketsPerBlock - numberOfSSgen) {
			errStr := fmt.Sprintf("an invalid number %v "+
				"tickets was missed, but %v many tickets should "+
				"have been missed!", tixMissed,
				ticketsPerBlock-numberOfSSgen)
			return errors.New(errStr)
		}

		if (tixSpent + tixMissed) != int(b.chainParams.TicketsPerBlock) {
			errStr := fmt.Sprintf("an invalid number %v "+
				"tickets was spent and missed, but TicketsPerBlock %v many "+
				"tickets should have been spent!", tixSpent,
				ticketsPerBlock)
			return errors.New(errStr)
		}

		// Calculate all the tickets expiring this block and mark them as missed.
		tpdBucketMap = make(map[uint8][]*TicketPatchData)
		for _, tpd := range tixStore {
			// Bucket does not exist.
			if _, ok := tpdBucketMap[tpd.td.Prefix]; !ok {
				tpdBucketMap[tpd.td.Prefix] = make([]*TicketPatchData, 1)
				tpdBucketMap[tpd.td.Prefix][0] = tpd
			} else {
				// Bucket exists.
				data := tpdBucketMap[tpd.td.Prefix]
				tpdBucketMap[tpd.td.Prefix] = append(data, tpd)
			}
		}
		toExpireHeight := node.height - int64(b.chainParams.TicketExpiry)
		if !(toExpireHeight < int64(b.chainParams.StakeEnabledHeight)) {
			for i := 0; i < stake.BucketsSize; i++ {
				// Generate the live ticket bucket.
				ltb, err := b.GenerateLiveTicketBucket(tixStore,
					tpdBucketMap, uint8(i))
				if err != nil {
					return err
				}

				for _, ticket := range ltb {
					if ticket.BlockHeight == toExpireHeight {
						tpd := NewTicketPatchData(ticket, TiMissed, nil)
						spentAndMissedTickets[ticket.SStxHash] = tpd
					}
				}
			}
		}

		// Merge the ticket store patch containing the spent and missed tickets
		// with the ticket store.
		for hash, tpd := range spentAndMissedTickets {
			tixStore[hash] = tpd
		}

		// At this point our tixStore now contains all the spent and missed tx
		// as per this block.

		// PART 2: Remove tickets that were missed and are now revoked.

		// Iterate through all the SSGen (vote) tx in the block and add them to
		// a map of tickets that were actually used.
		revocationsFromBlock := make(map[chainhash.Hash]struct{})
		numberOfSSRtx := 0
		for _, staketx := range block.STransactions() {
			if is, _ := stake.IsSSRtx(staketx); is {
				msgTx := staketx.MsgTx()
				sstxIn := msgTx.TxIn[0] // sstx input
				sstxHash := sstxIn.PreviousOutPoint.Hash

				// Fill out the ticket data.
				revocationsFromBlock[sstxHash] = struct{}{}
				numberOfSSRtx++
			}
		}

		if numberOfSSRtx != int(revocations) {
			errStr := fmt.Sprintf("an invalid revocations %v was calculated "+
				"the block header indicates %v instead", numberOfSSRtx,
				revocations)
			return errors.New(errStr)
		}

		// Lookup the missed ticket. If we find it in the patch data,
		// modify the patch data so that it doesn't exist.
		// Otherwise, just modify load the missed ticket data from
		// the ticket db and create patch data based on that.
		for hash, _ := range revocationsFromBlock {
			ticketWasMissed := false
			if td, is := missedTickets[hash]; is {
				maturedHeight := td.BlockHeight

				// Check maturity of ticket; we can only spend the ticket after it
				// hits maturity at height + tM + 2.
				if height < maturedHeight+2 {
					blockSha := block.Sha()
					errStr := fmt.Sprintf("Error: A ticket spend as an "+
						"SSRtx in block height %v was immature! Block sha %v",
						height,
						blockSha)
					return errors.New(errStr)
				}

				ticketWasMissed = true
			}

			if !ticketWasMissed {
				errStr := fmt.Sprintf("SSRtx spent missed sstx %v, "+
					"but that missed sstx could not be found!",
					hash)
				return errors.New(errStr)
			}
		}
	}

	// PART 3: Add newly maturing tickets
	// This is the only chunk we need to do for blocks appearing before
	// stake validation height.

	// Calculate block number for where new tickets are maturing from and retrieve
	// this block from db.

	// Get the block that is maturing.
	matureNode, err := b.getNodeAtHeightFromTopNode(node, tM)
	if err != nil {
		return err
	}

	matureBlock, errBlock := b.getBlockFromHash(matureNode.hash)
	if errBlock != nil {
		return errBlock
	}

	// Maturing tickets are from the maturingBlock; fill out the ticket patch data
	// and then push them to the tixStore.
	for _, stx := range matureBlock.STransactions() {
		if is, _ := stake.IsSStx(stx); is {
			// Calculate the prefix for pre-sort.
			sstxHash := *stx.Sha()
			prefix := uint8(sstxHash[0])

			// Fill out the ticket data.
			td := stake.NewTicketData(sstxHash,
				prefix,
				chainhash.Hash{},
				height,
				false, // not missed
				false) // not expired

			tpd := NewTicketPatchData(td,
				TiAvailable,
				nil)
			tixStore[*stx.Sha()] = tpd
		}
	}

	return nil
}