// 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 }
// 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() }
// 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 }
// 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 } } }
// 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 }
// 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 }
// 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 }
// 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 }