Example #1
0
// ticketsRevokedInBlock fetches a list of tickets that were revoked in the
// block.
func ticketsRevokedInBlock(bl *dcrutil.Block) []chainhash.Hash {
	var tickets []chainhash.Hash
	for _, stx := range bl.MsgBlock().STransactions {
		if stake.DetermineTxType(stx) == stake.TxTypeSSRtx {
			tickets = append(tickets, stx.TxIn[0].PreviousOutPoint.Hash)
		}
	}

	return tickets
}
Example #2
0
// DebugBlockString dumps a verbose message containing information about
// the transactions of a block.
func DebugBlockString(block *dcrutil.Block) string {
	if block == nil {
		return "block pointer nil"
	}

	var buffer bytes.Buffer

	hash := block.Sha()

	str := fmt.Sprintf("Block Header: %v Height: %v \n",
		hash, block.Height())
	buffer.WriteString(str)

	str = fmt.Sprintf("Block contains %v regular transactions "+
		"and %v stake transactions \n",
		len(block.Transactions()),
		len(block.STransactions()))
	buffer.WriteString(str)

	str = fmt.Sprintf("List of regular transactions \n")
	buffer.WriteString(str)

	for i, tx := range block.Transactions() {
		str = fmt.Sprintf("Index: %v, Hash: %v \n", i, tx.Sha())
		buffer.WriteString(str)
	}

	if len(block.STransactions()) == 0 {
		return buffer.String()
	}

	str = fmt.Sprintf("List of stake transactions \n")
	buffer.WriteString(str)

	for i, stx := range block.STransactions() {
		txTypeStr := ""
		txType := stake.DetermineTxType(stx)
		switch txType {
		case stake.TxTypeSStx:
			txTypeStr = "SStx"
		case stake.TxTypeSSGen:
			txTypeStr = "SSGen"
		case stake.TxTypeSSRtx:
			txTypeStr = "SSRtx"
		default:
			txTypeStr = "Error"
		}

		str = fmt.Sprintf("Index: %v, Type: %v, Hash: %v \n",
			i, txTypeStr, stx.Sha())
		buffer.WriteString(str)
	}

	return buffer.String()
}
Example #3
0
// AddTxOuts adds all outputs in the passed transaction which are not provably
// unspendable to the view.  When the view already has entries for any of the
// outputs, they are simply marked unspent.  All fields will be updated for
// existing entries since it's possible it has changed during a reorg.
func (view *UtxoViewpoint) AddTxOuts(tx *dcrutil.Tx, blockHeight int64,
	blockIndex uint32) {
	msgTx := tx.MsgTx()
	// When there are not already any utxos associated with the transaction,
	// add a new entry for it to the view.
	entry := view.LookupEntry(tx.Sha())
	if entry == nil {
		txType := stake.DetermineTxType(msgTx)
		entry = newUtxoEntry(msgTx.Version, uint32(blockHeight),
			blockIndex, IsCoinBaseTx(msgTx), msgTx.Expiry != 0, txType)
		if txType == stake.TxTypeSStx {
			stakeExtra := make([]byte, serializeSizeForMinimalOutputs(tx))
			putTxToMinimalOutputs(stakeExtra, tx)
			entry.stakeExtra = stakeExtra
		}
		view.entries[*tx.Sha()] = entry
	} else {
		entry.height = uint32(blockHeight)
		entry.index = uint32(blockIndex)
	}
	entry.modified = true

	// Loop all of the transaction outputs and add those which are not
	// provably unspendable.
	for txOutIdx, txOut := range tx.MsgTx().TxOut {
		// TODO allow pruning of stake utxs after all other outputs are spent
		if txscript.IsUnspendable(txOut.Value, txOut.PkScript) {
			continue
		}

		// Update existing entries.  All fields are updated because it's
		// possible (although extremely unlikely) that the existing
		// entry is being replaced by a different transaction with the
		// same hash.  This is allowed so long as the previous
		// transaction is fully spent.
		if output, ok := entry.sparseOutputs[uint32(txOutIdx)]; ok {
			output.spent = false
			output.amount = txOut.Value
			output.scriptVersion = txOut.Version
			output.pkScript = txOut.PkScript
			output.compressed = false
			continue
		}

		// Add the unspent transaction output.
		entry.sparseOutputs[uint32(txOutIdx)] = &utxoOutput{
			spent:         false,
			amount:        txOut.Value,
			scriptVersion: txOut.Version,
			pkScript:      txOut.PkScript,
			compressed:    false,
		}
	}
	return
}
Example #4
0
// TestCheckTransactionStandard tests the checkTransactionStandard API.
func TestCheckTransactionStandard(t *testing.T) {
	// Create some dummy, but otherwise standard, data for transactions.
	prevOutHash, err := chainhash.NewHashFromStr("01")
	if err != nil {
		t.Fatalf("NewShaHashFromStr: unexpected error: %v", err)
	}
	dummyPrevOut := wire.OutPoint{Hash: *prevOutHash, Index: 1, Tree: 0}
	dummySigScript := bytes.Repeat([]byte{0x00}, 65)
	dummyTxIn := wire.TxIn{
		PreviousOutPoint: dummyPrevOut,
		Sequence:         wire.MaxTxInSequenceNum,
		ValueIn:          0,
		BlockHeight:      0,
		BlockIndex:       0,
		SignatureScript:  dummySigScript,
	}
	addrHash := [20]byte{0x01}
	addr, err := dcrutil.NewAddressPubKeyHash(addrHash[:],
		&chaincfg.TestNetParams, chainec.ECTypeSecp256k1)
	if err != nil {
		t.Fatalf("NewAddressPubKeyHash: unexpected error: %v", err)
	}
	dummyPkScript, err := txscript.PayToAddrScript(addr)
	if err != nil {
		t.Fatalf("PayToAddrScript: unexpected error: %v", err)
	}
	dummyTxOut := wire.TxOut{
		Value:    100000000, // 1 BTC
		Version:  0,
		PkScript: dummyPkScript,
	}

	tests := []struct {
		name       string
		tx         wire.MsgTx
		height     int64
		isStandard bool
		code       wire.RejectCode
	}{
		{
			name: "Typical pay-to-pubkey-hash transaction",
			tx: wire.MsgTx{
				Version:  1,
				TxIn:     []*wire.TxIn{&dummyTxIn},
				TxOut:    []*wire.TxOut{&dummyTxOut},
				LockTime: 0,
			},
			height:     300000,
			isStandard: true,
		},
		{
			name: "Transaction version too high",
			tx: wire.MsgTx{
				Version:  int32(wire.TxVersion + 1),
				TxIn:     []*wire.TxIn{&dummyTxIn},
				TxOut:    []*wire.TxOut{&dummyTxOut},
				LockTime: 0,
			},
			height:     300000,
			isStandard: false,
			code:       wire.RejectNonstandard,
		},
		{
			name: "Transaction is not finalized",
			tx: wire.MsgTx{
				Version: 1,
				TxIn: []*wire.TxIn{{
					PreviousOutPoint: dummyPrevOut,
					SignatureScript:  dummySigScript,
					Sequence:         0,
				}},
				TxOut:    []*wire.TxOut{&dummyTxOut},
				LockTime: 300001,
			},
			height:     300000,
			isStandard: false,
			code:       wire.RejectNonstandard,
		},
		{
			name: "Transaction size is too large",
			tx: wire.MsgTx{
				Version: 1,
				TxIn:    []*wire.TxIn{&dummyTxIn},
				TxOut: []*wire.TxOut{{
					Value: 0,
					PkScript: bytes.Repeat([]byte{0x00},
						maxStandardTxSize+1),
				}},
				LockTime: 0,
			},
			height:     300000,
			isStandard: false,
			code:       wire.RejectNonstandard,
		},
		{
			name: "Signature script size is too large",
			tx: wire.MsgTx{
				Version: 1,
				TxIn: []*wire.TxIn{{
					PreviousOutPoint: dummyPrevOut,
					SignatureScript: bytes.Repeat([]byte{0x00},
						maxStandardSigScriptSize+1),
					Sequence: wire.MaxTxInSequenceNum,
				}},
				TxOut:    []*wire.TxOut{&dummyTxOut},
				LockTime: 0,
			},
			height:     300000,
			isStandard: false,
			code:       wire.RejectNonstandard,
		},
		{
			name: "Signature script that does more than push data",
			tx: wire.MsgTx{
				Version: 1,
				TxIn: []*wire.TxIn{{
					PreviousOutPoint: dummyPrevOut,
					SignatureScript: []byte{
						txscript.OP_CHECKSIGVERIFY},
					Sequence: wire.MaxTxInSequenceNum,
				}},
				TxOut:    []*wire.TxOut{&dummyTxOut},
				LockTime: 0,
			},
			height:     300000,
			isStandard: false,
			code:       wire.RejectNonstandard,
		},
		{
			name: "Valid but non standard public key script",
			tx: wire.MsgTx{
				Version: 1,
				TxIn:    []*wire.TxIn{&dummyTxIn},
				TxOut: []*wire.TxOut{{
					Value:    100000000,
					PkScript: []byte{txscript.OP_TRUE},
				}},
				LockTime: 0,
			},
			height:     300000,
			isStandard: false,
			code:       wire.RejectNonstandard,
		},
		{
			name: "More than four nulldata outputs",
			tx: wire.MsgTx{
				Version: 1,
				TxIn:    []*wire.TxIn{&dummyTxIn},
				TxOut: []*wire.TxOut{{
					Value:    0,
					PkScript: []byte{txscript.OP_RETURN},
				}, {
					Value:    0,
					PkScript: []byte{txscript.OP_RETURN},
				}, {
					Value:    0,
					PkScript: []byte{txscript.OP_RETURN},
				}, {
					Value:    0,
					PkScript: []byte{txscript.OP_RETURN},
				}, {
					Value:    0,
					PkScript: []byte{txscript.OP_RETURN},
				}},
				LockTime: 0,
			},
			height:     300000,
			isStandard: false,
			code:       wire.RejectNonstandard,
		},
		{
			name: "Dust output",
			tx: wire.MsgTx{
				Version: 1,
				TxIn:    []*wire.TxIn{&dummyTxIn},
				TxOut: []*wire.TxOut{{
					Value:    0,
					PkScript: dummyPkScript,
				}},
				LockTime: 0,
			},
			height:     300000,
			isStandard: false,
			code:       wire.RejectDust,
		},
		{
			name: "One nulldata output with 0 amount (standard)",
			tx: wire.MsgTx{
				Version: 1,
				TxIn:    []*wire.TxIn{&dummyTxIn},
				TxOut: []*wire.TxOut{{
					Value:    0,
					PkScript: []byte{txscript.OP_RETURN},
				}},
				LockTime: 0,
			},
			height:     300000,
			isStandard: true,
		},
	}

	timeSource := blockchain.NewMedianTime()
	for _, test := range tests {
		// Ensure standardness is as expected.
		tx := dcrutil.NewTx(&test.tx)
		err := checkTransactionStandard(tx, stake.DetermineTxType(tx),
			test.height, timeSource, defaultMinRelayTxFee)
		if err == nil && test.isStandard {
			// Test passes since function returned standard for a
			// transaction which is intended to be standard.
			continue
		}
		if err == nil && !test.isStandard {
			t.Errorf("checkTransactionStandard (%s): standard when "+
				"it should not be", test.name)
			continue
		}
		if err != nil && test.isStandard {
			t.Errorf("checkTransactionStandard (%s): nonstandard "+
				"when it should not be: %v", test.name, err)
			continue
		}

		// Ensure error type is a TxRuleError inside of a RuleError.
		rerr, ok := err.(RuleError)
		if !ok {
			t.Errorf("checkTransactionStandard (%s): unexpected "+
				"error type - got %T", test.name, err)
			continue
		}
		txrerr, ok := rerr.Err.(TxRuleError)
		if !ok {
			t.Errorf("checkTransactionStandard (%s): unexpected "+
				"error type - got %T", test.name, rerr.Err)
			continue
		}

		// Ensure the reject code is the expected one.
		if txrerr.RejectCode != test.code {
			t.Errorf("checkTransactionStandard (%s): unexpected "+
				"error code - got %v, want %v", test.name,
				txrerr.RejectCode, test.code)
			continue
		}
	}
}
Example #5
0
// disconnectTransactionSlice updates the view by removing all of the transactions
// created by the passed slice of transactions, restoring all utxos the
// transactions spent by using the provided spent txo information, and setting
// the best hash for the view to the block before the passed block.
func (view *UtxoViewpoint) disconnectTransactionSlice(transactions []*dcrutil.Tx,
	height int64, stxosPtr *[]spentTxOut) (int, error) {
	if stxosPtr == nil {
		return 0, AssertError("passed pointer to non-existing stxos slice")
	}

	stxos := *stxosPtr
	stxoIdx := len(stxos) - 1
	if stxoIdx == -1 {
		return 0, nil
	}
	for txIdx := len(transactions) - 1; txIdx > -1; txIdx-- {
		tx := transactions[txIdx]
		msgTx := tx.MsgTx()
		txType := stake.DetermineTxType(msgTx)

		// Clear this transaction from the view if it already exists or
		// create a new empty entry for when it does not.  This is done
		// because the code relies on its existence in the view in order
		// to signal modifications have happened.
		isCoinbase := txIdx == 0
		entry := view.entries[*tx.Sha()]
		if entry == nil {
			entry = newUtxoEntry(msgTx.Version, uint32(height),
				uint32(txIdx), IsCoinBaseTx(msgTx), msgTx.Expiry != 0, txType)
			if txType == stake.TxTypeSStx {
				stakeExtra := make([]byte, serializeSizeForMinimalOutputs(tx))
				putTxToMinimalOutputs(stakeExtra, tx)
				entry.stakeExtra = stakeExtra
			}
			view.entries[*tx.Sha()] = entry
		}
		entry.modified = true
		entry.sparseOutputs = make(map[uint32]*utxoOutput)

		// Loop backwards through all of the transaction inputs (except
		// for the coinbase which has no inputs) and unspend the
		// referenced txos.  This is necessary to match the order of the
		// spent txout entries.
		if isCoinbase {
			continue
		}
		for txInIdx := len(msgTx.TxIn) - 1; txInIdx > -1; txInIdx-- {
			// Ensure the spent txout index is decremented to stay
			// in sync with the transaction input.
			stxo := &stxos[stxoIdx]
			stxoIdx--

			// When there is not already an entry for the referenced
			// transaction in the view, it means it was fully spent,
			// so create a new utxo entry in order to resurrect it.
			txIn := msgTx.TxIn[txInIdx]
			originHash := &txIn.PreviousOutPoint.Hash
			originInIndex := txIn.PreviousOutPoint.Index
			//originHeight := txIn.BlockHeight
			// originIndex := txIn.BlockIndex
			entry := view.entries[*originHash]
			if entry == nil {
				entry = newUtxoEntry(stxo.txVersion, stxo.height,
					stxo.index, stxo.isCoinBase, stxo.hasExpiry,
					stxo.txType)
				if txType == stake.TxTypeSStx {
					//stakeExtra := make([]byte,
					//	serializeSizeForMinimalOutputs(tx))
					// putTxToMinimalOutputs(stakeExtra, tx)
					// entry.stakeExtra = stakeExtra
					entry.stakeExtra = stxo.stakeExtra
				}
				view.entries[*originHash] = entry
			}

			// Mark the entry as modified since it is either new
			// or will be changed below.
			entry.modified = true

			// Restore the specific utxo using the stxo data from
			// the spend journal if it doesn't already exist in the
			// view.
			output, ok := entry.sparseOutputs[originInIndex]
			if !ok {
				// Add the unspent transaction output.
				entry.sparseOutputs[originInIndex] = &utxoOutput{
					compressed:    stxo.compressed,
					spent:         false,
					amount:        txIn.ValueIn,
					scriptVersion: stxo.scriptVersion,
					pkScript:      stxo.pkScript,
				}
				continue
			}

			// Mark the existing referenced transaction output as
			// unspent.
			output.spent = false
		}
	}

	return stxoIdx + 1, nil
}
Example #6
0
// disconnectTransactions updates the view by removing all of the transactions
// created by the passed block, restoring all utxos the transactions spent by
// using the provided spent txo information, and setting the best hash for the
// view to the block before the passed block.
//
// This function will ONLY work correctly for a single transaction tree at a
// time because of index tracking.
func (b *BlockChain) disconnectTransactions(view *UtxoViewpoint,
	block *dcrutil.Block, parent *dcrutil.Block, stxos []spentTxOut) error {
	// Sanity check the correct number of stxos are provided.
	if len(stxos) != countSpentOutputs(block, parent) {
		return AssertError(fmt.Sprintf("disconnectTransactions "+
			"called with bad spent transaction out information "+
			"(len stxos %v, count is %v)", len(stxos),
			countSpentOutputs(block, parent)))
	}

	// Loop backwards through all transactions so everything is unspent in
	// reverse order.  This is necessary since transactions later in a block
	// can spend from previous ones.
	regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits,
		dcrutil.BlockValid)
	thisNodeStakeViewpoint := ViewpointPrevInvalidStake
	if regularTxTreeValid {
		thisNodeStakeViewpoint = ViewpointPrevValidStake
	}
	view.SetStakeViewpoint(thisNodeStakeViewpoint)
	err := view.fetchInputUtxos(b.db, block, parent)
	if err != nil {
		return err
	}
	stxoIdx := len(stxos) - 1
	transactions := block.STransactions()
	for txIdx := len(transactions) - 1; txIdx > -1; txIdx-- {
		tx := transactions[txIdx]
		msgTx := tx.MsgTx()
		tt := stake.DetermineTxType(msgTx)

		// Clear this transaction from the view if it already exists or
		// create a new empty entry for when it does not.  This is done
		// because the code relies on its existence in the view in order
		// to signal modifications have happened.
		entry := view.entries[*tx.Sha()]
		if entry == nil {
			entry = newUtxoEntry(msgTx.Version, uint32(block.Height()),
				uint32(txIdx), IsCoinBaseTx(msgTx), msgTx.Expiry != 0, tt)
			if tt == stake.TxTypeSStx {
				stakeExtra := make([]byte, serializeSizeForMinimalOutputs(tx))
				putTxToMinimalOutputs(stakeExtra, tx)
				entry.stakeExtra = stakeExtra
			}
			view.entries[*tx.Sha()] = entry
		}
		entry.modified = true
		entry.sparseOutputs = make(map[uint32]*utxoOutput)

		// Loop backwards through all of the transaction inputs (except
		// for the coinbase which has no inputs) and unspend the
		// referenced txos.  This is necessary to match the order of the
		// spent txout entries.
		for txInIdx := len(tx.MsgTx().TxIn) - 1; txInIdx > -1; txInIdx-- {
			// Skip empty vote stakebases.
			if txInIdx == 0 && (tt == stake.TxTypeSSGen) {
				continue
			}

			// Ensure the spent txout index is decremented to stay
			// in sync with the transaction input.
			stxo := &stxos[stxoIdx]
			stxoIdx--

			// When there is not already an entry for the referenced
			// transaction in the view, it means it was fully spent,
			// so create a new utxo entry in order to resurrect it.
			txIn := tx.MsgTx().TxIn[txInIdx]
			originHash := &txIn.PreviousOutPoint.Hash
			originIndex := txIn.PreviousOutPoint.Index
			entry := view.LookupEntry(originHash)
			if entry == nil {
				if !stxo.txFullySpent {
					return AssertError(fmt.Sprintf("tried to revive utx %v from "+
						"non-fully spent stx entry", originHash))
				}

				entry = newUtxoEntry(tx.MsgTx().Version, stxo.height,
					stxo.index, stxo.isCoinBase, stxo.hasExpiry,
					stxo.txType)
				if stxo.txType == stake.TxTypeSStx {
					entry.stakeExtra = stxo.stakeExtra
				}
				view.entries[*originHash] = entry
			}

			// Mark the entry as modified since it is either new
			// or will be changed below.
			entry.modified = true

			// Restore the specific utxo using the stxo data from
			// the spend journal if it doesn't already exist in the
			// view.
			output, ok := entry.sparseOutputs[originIndex]
			if !ok {
				// Add the unspent transaction output.
				entry.sparseOutputs[originIndex] = &utxoOutput{
					compressed:    stxo.compressed,
					spent:         false,
					amount:        txIn.ValueIn,
					scriptVersion: stxo.scriptVersion,
					pkScript:      stxo.pkScript,
				}
				continue
			}

			// Mark the existing referenced transaction output as
			// unspent.
			output.spent = false
		}
	}

	// There is no regular tx from before the genesis block, so ignore the genesis
	// block for the next step.
	if parent != nil && block.Height() != 0 {
		// Only bother to unspend transactions if the parent's tx tree was
		// validated. Otherwise, these transactions were never in the blockchain's
		// history in the first place.
		if regularTxTreeValid {
			view.SetStakeViewpoint(ViewpointPrevValidInitial)
			err = view.fetchInputUtxos(b.db, block, parent)
			if err != nil {
				return err
			}

			transactions := parent.Transactions()
			for txIdx := len(transactions) - 1; txIdx > -1; txIdx-- {
				tx := transactions[txIdx]

				// Clear this transaction from the view if it already exists or
				// create a new empty entry for when it does not.  This is done
				// because the code relies on its existence in the view in order
				// to signal modifications have happened.
				isCoinbase := txIdx == 0
				entry := view.entries[*tx.Sha()]
				if entry == nil {
					entry = newUtxoEntry(tx.MsgTx().Version,
						uint32(parent.Height()), uint32(txIdx), isCoinbase,
						tx.MsgTx().Expiry != 0, stake.TxTypeRegular)
					view.entries[*tx.Sha()] = entry
				}
				entry.modified = true
				entry.sparseOutputs = make(map[uint32]*utxoOutput)

				// Loop backwards through all of the transaction inputs (except
				// for the coinbase which has no inputs) and unspend the
				// referenced txos.  This is necessary to match the order of the
				// spent txout entries.
				if isCoinbase {
					continue
				}
				for txInIdx := len(tx.MsgTx().TxIn) - 1; txInIdx > -1; txInIdx-- {
					// Ensure the spent txout index is decremented to stay
					// in sync with the transaction input.
					stxo := &stxos[stxoIdx]
					stxoIdx--

					// When there is not already an entry for the referenced
					// transaction in the view, it means it was fully spent,
					// so create a new utxo entry in order to resurrect it.
					txIn := tx.MsgTx().TxIn[txInIdx]
					originHash := &txIn.PreviousOutPoint.Hash
					originIndex := txIn.PreviousOutPoint.Index
					entry := view.entries[*originHash]
					if entry == nil {
						if !stxo.txFullySpent {
							return AssertError(fmt.Sprintf("tried to "+
								"revive utx %v from non-fully spent stx entry",
								originHash))
						}
						entry = newUtxoEntry(tx.MsgTx().Version,
							stxo.height, stxo.index, stxo.isCoinBase,
							stxo.hasExpiry, stxo.txType)
						if stxo.txType == stake.TxTypeSStx {
							entry.stakeExtra = stxo.stakeExtra
						}
						view.entries[*originHash] = entry
					}

					// Mark the entry as modified since it is either new
					// or will be changed below.
					entry.modified = true

					// Restore the specific utxo using the stxo data from
					// the spend journal if it doesn't already exist in the
					// view.
					output, ok := entry.sparseOutputs[originIndex]
					if !ok {
						// Add the unspent transaction output.
						entry.sparseOutputs[originIndex] = &utxoOutput{
							compressed:    stxo.compressed,
							spent:         false,
							amount:        txIn.ValueIn,
							scriptVersion: stxo.scriptVersion,
							pkScript:      stxo.pkScript,
						}
						continue
					}

					// Mark the existing referenced transaction output as
					// unspent.
					output.spent = false
				}
			}
		}
	}

	// Update the best hash for view to the previous block since all of the
	// transactions for the current block have been disconnected.
	view.SetBestHash(parent.Sha())
	return nil
}
Example #7
0
// connectTransaction updates the view by adding all new utxos created by the
// passed transaction and marking all utxos that the transactions spend as
// spent.  In addition, when the 'stxos' argument is not nil, it will be updated
// to append an entry for each spent txout.  An error will be returned if the
// view does not contain the required utxos.
func (view *UtxoViewpoint) connectTransaction(tx *dcrutil.Tx, blockHeight int64,
	blockIndex uint32, stxos *[]spentTxOut) error {
	msgTx := tx.MsgTx()
	// Coinbase transactions don't have any inputs to spend.
	if IsCoinBaseTx(msgTx) {
		// Add the transaction's outputs as available utxos.
		view.AddTxOuts(tx, blockHeight, blockIndex)
		return nil
	}

	// Spend the referenced utxos by marking them spent in the view and,
	// if a slice was provided for the spent txout details, append an entry
	// to it.
	txType := stake.DetermineTxType(msgTx)
	for i, txIn := range msgTx.TxIn {
		if i == 0 && (txType == stake.TxTypeSSGen) {
			continue
		}

		originIndex := txIn.PreviousOutPoint.Index
		entry := view.entries[txIn.PreviousOutPoint.Hash]

		// Ensure the referenced utxo exists in the view.  This should
		// never happen unless there is a bug is introduced in the code.
		if entry == nil {
			return AssertError(fmt.Sprintf("view missing input %v",
				txIn.PreviousOutPoint))
		}
		entry.SpendOutput(originIndex)

		// Don't create the stxo details if not requested.
		if stxos == nil {
			continue
		}

		// Populate the stxo details using the utxo entry.  When the
		// transaction is fully spent, set the additional stxo fields
		// accordingly since those details will no longer be available
		// in the utxo set.
		var stxo = spentTxOut{
			compressed:    false,
			amount:        txIn.ValueIn,
			scriptVersion: entry.ScriptVersionByIndex(originIndex),
			pkScript:      entry.PkScriptByIndex(originIndex),
		}
		if entry.IsFullySpent() {
			stxo.txVersion = entry.TxVersion()
			stxo.height = uint32(entry.BlockHeight())
			stxo.index = entry.BlockIndex()
			stxo.isCoinBase = entry.IsCoinBase()
			stxo.hasExpiry = entry.HasExpiry()
			stxo.txType = entry.txType
			stxo.txFullySpent = true

			if entry.txType == stake.TxTypeSStx {
				stxo.stakeExtra = entry.stakeExtra
			}
		}

		// Append the entry to the provided spent txouts slice.
		*stxos = append(*stxos, stxo)
	}

	// Add the transaction's outputs as available utxos.
	view.AddTxOuts(tx, blockHeight, blockIndex)

	return nil
}
Example #8
0
// indexBlockAddrs returns a populated index of the all the transactions in the
// passed block based on the addresses involved in each transaction.
func (a *addrIndexer) indexBlockAddrs(blk *dcrutil.Block,
	parent *dcrutil.Block) (database.BlockAddrIndex, error) {
	var addrIndex database.BlockAddrIndex
	_, stxLocs, err := blk.TxLoc()
	if err != nil {
		return nil, err
	}

	txTreeRegularValid := dcrutil.IsFlagSet16(blk.MsgBlock().Header.VoteBits,
		dcrutil.BlockValid)

	// Add regular transactions iff the block was validated.
	if txTreeRegularValid {
		txLocs, _, err := parent.TxLoc()
		if err != nil {
			return nil, err
		}
		for txIdx, tx := range parent.Transactions() {
			// Tx's offset and length in the block.
			locInBlock := &txLocs[txIdx]

			// Coinbases don't have any inputs.
			if !blockchain.IsCoinBase(tx) {
				// Index the SPK's of each input's previous outpoint
				// transaction.
				for _, txIn := range tx.MsgTx().TxIn {
					prevOutTx, err := a.lookupTransaction(
						txIn.PreviousOutPoint.Hash,
						blk,
						parent)
					inputOutPoint := prevOutTx.TxOut[txIn.PreviousOutPoint.Index]

					toAppend, err := convertToAddrIndex(inputOutPoint.Version,
						inputOutPoint.PkScript, parent.Height(), locInBlock,
						stake.TxTypeRegular)
					if err != nil {
						adxrLog.Tracef("Error converting tx txin %v: %v",
							txIn.PreviousOutPoint.Hash, err)
						continue
					}
					addrIndex = append(addrIndex, toAppend...)
				}
			}

			for _, txOut := range tx.MsgTx().TxOut {
				toAppend, err := convertToAddrIndex(txOut.Version, txOut.PkScript,
					parent.Height(), locInBlock, stake.TxTypeRegular)
				if err != nil {
					adxrLog.Tracef("Error converting tx txout %v: %v",
						tx.MsgTx().TxSha(), err)
					continue
				}
				addrIndex = append(addrIndex, toAppend...)
			}
		}
	}

	// Add stake transactions.
	for stxIdx, stx := range blk.STransactions() {
		// Tx's offset and length in the block.
		locInBlock := &stxLocs[stxIdx]

		txType := stake.DetermineTxType(stx)

		// Index the SPK's of each input's previous outpoint
		// transaction.
		for i, txIn := range stx.MsgTx().TxIn {
			// Stakebases don't have any inputs.
			if txType == stake.TxTypeSSGen && i == 0 {
				continue
			}

			// Lookup and fetch the referenced output's tx.
			prevOutTx, err := a.lookupTransaction(
				txIn.PreviousOutPoint.Hash,
				blk,
				parent)
			inputOutPoint := prevOutTx.TxOut[txIn.PreviousOutPoint.Index]

			toAppend, err := convertToAddrIndex(inputOutPoint.Version,
				inputOutPoint.PkScript, blk.Height(), locInBlock,
				txType)
			if err != nil {
				adxrLog.Tracef("Error converting stx txin %v: %v",
					txIn.PreviousOutPoint.Hash, err)
				continue
			}
			addrIndex = append(addrIndex, toAppend...)
		}

		for _, txOut := range stx.MsgTx().TxOut {
			toAppend, err := convertToAddrIndex(txOut.Version, txOut.PkScript,
				blk.Height(), locInBlock, txType)
			if err != nil {
				adxrLog.Tracef("Error converting stx txout %v: %v",
					stx.MsgTx().TxSha(), err)
				continue
			}
			addrIndex = append(addrIndex, toAppend...)
		}
	}

	return addrIndex, nil
}