// AddUnconfirmedTx adds all addresses related to the transaction to the // unconfirmed (memory-only) address index. // // NOTE: This transaction MUST have already been validated by the memory pool // before calling this function with it and have all of the inputs available in // the provided utxo view. Failure to do so could result in some or all // addresses not being indexed. // // This function is safe for concurrent access. func (idx *AddrIndex) AddUnconfirmedTx(tx *dcrutil.Tx, utxoView *blockchain.UtxoViewpoint) { // Index addresses of all referenced previous transaction outputs. // // The existence checks are elided since this is only called after the // transaction has already been validated and thus all inputs are // already known to exist. msgTx := tx.MsgTx() isSSGen, _ := stake.IsSSGen(msgTx) for i, txIn := range msgTx.TxIn { // Skip stakebase. if i == 0 && isSSGen { continue } entry := utxoView.LookupEntry(&txIn.PreviousOutPoint.Hash) if entry == nil { // Ignore missing entries. This should never happen // in practice since the function comments specifically // call out all inputs must be available. continue } version := entry.ScriptVersionByIndex(txIn.PreviousOutPoint.Index) pkScript := entry.PkScriptByIndex(txIn.PreviousOutPoint.Index) txType := entry.TransactionType() idx.indexUnconfirmedAddresses(version, pkScript, tx, txType == stake.TxTypeSStx) } // Index addresses of all created outputs. isSStx, _ := stake.IsSStx(msgTx) for _, txOut := range msgTx.TxOut { idx.indexUnconfirmedAddresses(txOut.Version, txOut.PkScript, tx, isSStx) } }
// fetchNewTicketsForNode fetches the list of newly maturing tickets for a // given node by traversing backwards through its parents until it finds the // block that contains the original tickets to mature. // // This function is NOT safe for concurrent access and must be called with // the chainLock held for writes. func (b *BlockChain) fetchNewTicketsForNode(node *blockNode) ([]chainhash.Hash, error) { // If we're before the stake enabled height, there can be no // tickets in the live ticket pool. if node.height < b.chainParams.StakeEnabledHeight { return []chainhash.Hash{}, nil } // If we already cached the tickets, simply return the cached list. // It's important to make the distinction here that nil means the // value was never looked up, while an empty slice of pointers means // that there were no new tickets at this height. if node.newTickets != nil { return node.newTickets, nil } // Calculate block number for where new tickets matured from and retrieve // this block from DB or in memory if it's a sidechain. matureNode, err := b.nodeAtHeightFromTopNode(node, int64(b.chainParams.TicketMaturity)) if err != nil { return nil, err } matureBlock, errBlock := b.fetchBlockFromHash(&matureNode.hash) if errBlock != nil { return nil, errBlock } tickets := []chainhash.Hash{} for _, stx := range matureBlock.MsgBlock().STransactions { if is, _ := stake.IsSStx(stx); is { h := stx.TxSha() tickets = append(tickets, h) } } // Set the new tickets in memory so that they exist for future // reference in the node. node.newTickets = tickets return tickets, nil }
// DebugMsgTxString dumps a verbose message containing information about the // contents of a transaction. func DebugMsgTxString(msgTx *wire.MsgTx) string { tx := dcrutil.NewTx(msgTx) isSStx, _ := stake.IsSStx(tx) isSSGen, _ := stake.IsSSGen(tx) var sstxType []bool var sstxPkhs [][]byte var sstxAmts []int64 var sstxRules [][]bool var sstxLimits [][]uint16 if isSStx { sstxType, sstxPkhs, sstxAmts, _, sstxRules, sstxLimits = stake.GetSStxStakeOutputInfo(tx) } var buffer bytes.Buffer hash := msgTx.TxSha() str := fmt.Sprintf("Transaction hash: %v, Version %v, Locktime: %v, "+ "Expiry %v\n\n", hash, msgTx.Version, msgTx.LockTime, msgTx.Expiry) buffer.WriteString(str) str = fmt.Sprintf("==INPUTS==\nNumber of inputs: %v\n\n", len(msgTx.TxIn)) buffer.WriteString(str) for i, input := range msgTx.TxIn { str = fmt.Sprintf("Input number: %v\n", i) buffer.WriteString(str) str = fmt.Sprintf("Previous outpoint hash: %v, ", input.PreviousOutPoint.Hash) buffer.WriteString(str) str = fmt.Sprintf("Previous outpoint index: %v, ", input.PreviousOutPoint.Index) buffer.WriteString(str) str = fmt.Sprintf("Previous outpoint tree: %v \n", input.PreviousOutPoint.Tree) buffer.WriteString(str) str = fmt.Sprintf("Sequence: %v \n", input.Sequence) buffer.WriteString(str) str = fmt.Sprintf("ValueIn: %v \n", input.ValueIn) buffer.WriteString(str) str = fmt.Sprintf("BlockHeight: %v \n", input.BlockHeight) buffer.WriteString(str) str = fmt.Sprintf("BlockIndex: %v \n", input.BlockIndex) buffer.WriteString(str) str = fmt.Sprintf("Raw signature script: %x \n", input.SignatureScript) buffer.WriteString(str) sigScr, _ := txscript.DisasmString(input.SignatureScript) str = fmt.Sprintf("Disasmed signature script: %v \n\n", sigScr) buffer.WriteString(str) } str = fmt.Sprintf("==OUTPUTS==\nNumber of outputs: %v\n\n", len(msgTx.TxOut)) buffer.WriteString(str) for i, output := range msgTx.TxOut { str = fmt.Sprintf("Output number: %v\n", i) buffer.WriteString(str) coins := float64(output.Value) / 1e8 str = fmt.Sprintf("Output amount: %v atoms or %v coins\n", output.Value, coins) buffer.WriteString(str) // SStx OP_RETURNs, dump pkhs and amts committed if isSStx && i != 0 && i%2 == 1 { coins := float64(sstxAmts[i/2]) / 1e8 str = fmt.Sprintf("SStx commit amount: %v atoms or %v coins\n", sstxAmts[i/2], coins) buffer.WriteString(str) str = fmt.Sprintf("SStx commit address: %x\n", sstxPkhs[i/2]) buffer.WriteString(str) str = fmt.Sprintf("SStx address type is P2SH: %v\n", sstxType[i/2]) buffer.WriteString(str) str = fmt.Sprintf("SStx all address types is P2SH: %v\n", sstxType) buffer.WriteString(str) str = fmt.Sprintf("Voting is fee limited: %v\n", sstxLimits[i/2][0]) buffer.WriteString(str) if sstxRules[i/2][0] { str = fmt.Sprintf("Voting limit imposed: %v\n", sstxLimits[i/2][0]) buffer.WriteString(str) } str = fmt.Sprintf("Revoking is fee limited: %v\n", sstxRules[i/2][1]) buffer.WriteString(str) if sstxRules[i/2][1] { str = fmt.Sprintf("Voting limit imposed: %v\n", sstxLimits[i/2][1]) buffer.WriteString(str) } } // SSGen block/block height OP_RETURN. if isSSGen && i == 0 { blkHash, blkHeight, _ := stake.GetSSGenBlockVotedOn(tx) str = fmt.Sprintf("SSGen block hash voted on: %v, height: %v\n", blkHash, blkHeight) buffer.WriteString(str) } if isSSGen && i == 1 { vb := stake.GetSSGenVoteBits(tx) str = fmt.Sprintf("SSGen vote bits: %v\n", vb) buffer.WriteString(str) } str = fmt.Sprintf("Raw script: %x \n", output.PkScript) buffer.WriteString(str) scr, _ := txscript.DisasmString(output.PkScript) str = fmt.Sprintf("Disasmed script: %v \n\n", scr) buffer.WriteString(str) } return buffer.String() }
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 }
// addUnconfirmedTx adds all addresses related to the transaction to the // unconfirmed (memory-only) exists address index. func (idx *ExistsAddrIndex) addUnconfirmedTx(tx *wire.MsgTx) { isSStx, _ := stake.IsSStx(tx) for _, txIn := range tx.TxIn { if txscript.IsMultisigSigScript(txIn.SignatureScript) { rs, err := txscript.MultisigRedeemScriptFromScriptSig( txIn.SignatureScript) if err != nil { continue } class, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, rs, idx.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if class != txscript.MultiSigTy { // This should never happen, but be paranoid. continue } for _, addr := range addrs { k, err := addrToKey(addr, idx.chainParams) if err != nil { continue } if _, exists := idx.mpExistsAddr[k]; !exists { idx.mpExistsAddr[k] = struct{}{} } } } } for _, txOut := range tx.TxOut { class, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.Version, txOut.PkScript, idx.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if isSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(txOut.PkScript, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } addrs = append(addrs, addr) } for _, addr := range addrs { k, err := addrToKey(addr, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } if _, exists := idx.mpExistsAddr[k]; !exists { idx.mpExistsAddr[k] = struct{}{} } } } }
// ConnectBlock is invoked by the index manager when a new block has been // connected to the main chain. This indexer adds a key for each address // the transactions in the block involve. // // This is part of the Indexer interface. func (idx *ExistsAddrIndex) ConnectBlock(dbTx database.Tx, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error { regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) var parentTxs []*dcrutil.Tx if regularTxTreeValid && block.Height() > 1 { parentTxs = parent.Transactions() } blockTxns := block.STransactions() allTxns := append(parentTxs, blockTxns...) usedAddrs := make(map[[addrKeySize]byte]struct{}) for _, tx := range allTxns { msgTx := tx.MsgTx() isSStx, _ := stake.IsSStx(msgTx) for _, txIn := range msgTx.TxIn { if txscript.IsMultisigSigScript(txIn.SignatureScript) { rs, err := txscript.MultisigRedeemScriptFromScriptSig( txIn.SignatureScript) if err != nil { continue } class, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, rs, idx.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if class != txscript.MultiSigTy { // This should never happen, but be paranoid. continue } for _, addr := range addrs { k, err := addrToKey(addr, idx.chainParams) if err != nil { continue } usedAddrs[k] = struct{}{} } } } for _, txOut := range tx.MsgTx().TxOut { class, addrs, _, err := txscript.ExtractPkScriptAddrs( txOut.Version, txOut.PkScript, idx.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if isSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(txOut.PkScript, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } addrs = append(addrs, addr) } for _, addr := range addrs { k, err := addrToKey(addr, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } usedAddrs[k] = struct{}{} } } } // Write all the newly used addresses to the database, // skipping any keys that already exist. Write any // addresses we see in mempool at this time, too, // then remove them from the unconfirmed map drop // dropping the old map and reassigning a new map. idx.unconfirmedLock.Lock() for k := range idx.mpExistsAddr { usedAddrs[k] = struct{}{} } idx.mpExistsAddr = make(map[[addrKeySize]byte]struct{}) idx.unconfirmedLock.Unlock() meta := dbTx.Metadata() existsAddrIndex := meta.Bucket(existsAddrIndexKey) newUsedAddrs := make(map[[addrKeySize]byte]struct{}) for k := range usedAddrs { if !idx.existsAddress(existsAddrIndex, k) { newUsedAddrs[k] = struct{}{} } } for k := range newUsedAddrs { err := dbPutExistsAddr(existsAddrIndex, k) if err != nil { return err } } return nil }
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 }
// 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 }
// 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 }
// upgradeToVersion2 upgrades a version 1 blockchain to version 2, allowing // use of the new on-disk ticket database. func (b *BlockChain) upgradeToVersion2() error { log.Infof("Initializing upgrade to database version 2") best := b.BestSnapshot() progressLogger := progresslog.NewBlockProgressLogger("Upgraded", log) // The upgrade is atomic, so there is no need to set the flag that // the database is undergoing an upgrade here. Get the stake node // for the genesis block, and then begin connecting stake nodes // incrementally. err := b.db.Update(func(dbTx database.Tx) error { bestStakeNode, errLocal := stake.InitDatabaseState(dbTx, b.chainParams) if errLocal != nil { return errLocal } parent, errLocal := dbFetchBlockByHeight(dbTx, 0) if errLocal != nil { return errLocal } for i := int64(1); i <= best.Height; i++ { block, errLocal := dbFetchBlockByHeight(dbTx, i) if errLocal != nil { return errLocal } // If we need the tickets, fetch them too. var newTickets []chainhash.Hash if i >= b.chainParams.StakeEnabledHeight { matureHeight := i - int64(b.chainParams.TicketMaturity) matureBlock, errLocal := dbFetchBlockByHeight(dbTx, matureHeight) if errLocal != nil { return errLocal } for _, stx := range matureBlock.MsgBlock().STransactions { if is, _ := stake.IsSStx(stx); is { h := stx.TxSha() newTickets = append(newTickets, h) } } } // Iteratively connect the stake nodes in memory. header := block.MsgBlock().Header bestStakeNode, errLocal = bestStakeNode.ConnectNode(header, ticketsSpentInBlock(block), ticketsRevokedInBlock(block), newTickets) if errLocal != nil { return errLocal } // Write the top block stake node to the database. errLocal = stake.WriteConnectedBestNode(dbTx, bestStakeNode, *best.Hash) if errLocal != nil { return errLocal } // Write the best block node when we reach it. if i == best.Height { b.bestNode.stakeNode = bestStakeNode b.bestNode.stakeUndoData = bestStakeNode.UndoData() b.bestNode.newTickets = newTickets b.bestNode.ticketsSpent = ticketsSpentInBlock(block) b.bestNode.ticketsRevoked = ticketsRevokedInBlock(block) } progressLogger.LogBlockHeight(block, parent) parent = block } // Write the new database version. b.dbInfo.version = 2 errLocal = dbPutDatabaseInfo(dbTx, b.dbInfo) if errLocal != nil { return errLocal } return nil }) if err != nil { return err } log.Infof("Upgrade to new stake database was successful!") return nil }
// indexBlock extract all of the standard addresses from all of the transactions // in the passed block and maps each of them to the assocaited transaction using // the passed map. func (idx *AddrIndex) indexBlock(data writeIndexData, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) { regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) var stakeStartIdx int if regularTxTreeValid { for txIdx, tx := range parent.Transactions() { // Coinbases do not reference any inputs. Since the block is // required to have already gone through full validation, it has // already been proven on the first transaction in the block is // a coinbase. if txIdx != 0 { for _, txIn := range tx.MsgTx().TxIn { // The view should always have the input since // the index contract requires it, however, be // safe and simply ignore any missing entries. origin := &txIn.PreviousOutPoint entry := view.LookupEntry(&origin.Hash) if entry == nil { log.Warnf("Missing input %v for tx %v while "+ "indexing block %v (height %v)\n", origin.Hash, tx.Sha(), block.Sha(), block.Height()) continue } version := entry.ScriptVersionByIndex(origin.Index) pkScript := entry.PkScriptByIndex(origin.Index) txType := entry.TransactionType() idx.indexPkScript(data, version, pkScript, txIdx, txType == stake.TxTypeSStx) } } for _, txOut := range tx.MsgTx().TxOut { idx.indexPkScript(data, txOut.Version, txOut.PkScript, txIdx, false) } } stakeStartIdx = len(parent.Transactions()) } for txIdx, tx := range block.STransactions() { msgTx := tx.MsgTx() thisTxOffset := txIdx + stakeStartIdx isSSGen, _ := stake.IsSSGen(msgTx) for i, txIn := range msgTx.TxIn { // Skip stakebases. if isSSGen && i == 0 { continue } // The view should always have the input since // the index contract requires it, however, be // safe and simply ignore any missing entries. origin := &txIn.PreviousOutPoint entry := view.LookupEntry(&origin.Hash) if entry == nil { log.Warnf("Missing input %v for tx %v while "+ "indexing block %v (height %v)\n", origin.Hash, tx.Sha(), block.Sha(), block.Height()) continue } version := entry.ScriptVersionByIndex(origin.Index) pkScript := entry.PkScriptByIndex(origin.Index) txType := entry.TransactionType() idx.indexPkScript(data, version, pkScript, thisTxOffset, txType == stake.TxTypeSStx) } isSStx, _ := stake.IsSStx(msgTx) for _, txOut := range msgTx.TxOut { idx.indexPkScript(data, txOut.Version, txOut.PkScript, thisTxOffset, isSStx) } } }