// indexUnconfirmedAddresses modifies the unconfirmed (memory-only) address // index to include mappings for the addresses encoded by the passed public key // script to the transaction. // // This function is safe for concurrent access. func (idx *AddrIndex) indexUnconfirmedAddresses(scriptVersion uint16, pkScript []byte, tx *dcrutil.Tx, isSStx bool) { // The error is ignored here since the only reason it can fail is if the // script fails to parse and it was already validated before being // admitted to the mempool. class, addresses, _, _ := txscript.ExtractPkScriptAddrs(scriptVersion, pkScript, idx.chainParams) if isSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(pkScript, idx.chainParams) if err != nil { // Fail if this fails to decode. It should. return } addresses = append(addresses, addr) } for _, addr := range addresses { // Ignore unsupported address types. addrKey, err := addrToKey(addr, idx.chainParams) if err != nil { continue } // Add a mapping from the address to the transaction. idx.unconfirmedLock.Lock() addrIndexEntry := idx.txnsByAddr[addrKey] if addrIndexEntry == nil { addrIndexEntry = make(map[chainhash.Hash]*dcrutil.Tx) idx.txnsByAddr[addrKey] = addrIndexEntry } addrIndexEntry[*tx.Sha()] = tx // Add a mapping from the transaction to the address. addrsByTxEntry := idx.addrsByTx[*tx.Sha()] if addrsByTxEntry == nil { addrsByTxEntry = make(map[[addrKeySize]byte]struct{}) idx.addrsByTx[*tx.Sha()] = addrsByTxEntry } addrsByTxEntry[addrKey] = struct{}{} idx.unconfirmedLock.Unlock() } }
// indexPkScript extracts all standard addresses from the passed public key // script and maps each of them to the associated transaction using the passed // map. func (idx *AddrIndex) indexPkScript(data writeIndexData, scriptVersion uint16, pkScript []byte, txIdx int, isSStx bool) { // Nothing to index if the script is non-standard or otherwise doesn't // contain any addresses. class, addrs, _, err := txscript.ExtractPkScriptAddrs(scriptVersion, pkScript, idx.chainParams) if err != nil { return } if isSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(pkScript, idx.chainParams) if err != nil { return } addrs = append(addrs, addr) } if len(addrs) == 0 { return } for _, addr := range addrs { addrKey, err := addrToKey(addr, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } // Avoid inserting the transaction more than once. Since the // transactions are indexed serially any duplicates will be // indexed in a row, so checking the most recent entry for the // address is enough to detect duplicates. indexedTxns := data[addrKey] numTxns := len(indexedTxns) if numTxns > 0 && indexedTxns[numTxns-1] == txIdx { continue } indexedTxns = append(indexedTxns, txIdx) data[addrKey] = indexedTxns } }
// evaluateStakePoolTicket evaluates a stake pool ticket to see if it's // acceptable to the stake pool. The ticket must pay out to the stake // pool cold wallet, and must have a sufficient fee. func (w *Wallet) evaluateStakePoolTicket(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta, poolUser dcrutil.Address) (bool, error) { tx := rec.MsgTx // Check the first commitment output (txOuts[1]) // and ensure that the address found there exists // in the list of approved addresses. Also ensure // that the fee exists and is of the amount // requested by the pool. commitmentOut := tx.TxOut[1] commitAddr, err := stake.AddrFromSStxPkScrCommitment( commitmentOut.PkScript, w.chainParams) if err != nil { return false, fmt.Errorf("Failed to parse commit out addr: %s", err.Error()) } // Extract the fee from the ticket. in := dcrutil.Amount(0) for i := range tx.TxOut { if i%2 != 0 { commitAmt, err := stake.AmountFromSStxPkScrCommitment( tx.TxOut[i].PkScript) if err != nil { return false, fmt.Errorf("Failed to parse commit "+ "out amt for commit in vout %v: %s", i, err.Error()) } in += dcrutil.Amount(commitAmt) } } out := dcrutil.Amount(0) for i := range tx.TxOut { out += dcrutil.Amount(tx.TxOut[i].Value) } fees := in - out _, exists := w.stakePoolColdAddrs[commitAddr.EncodeAddress()] if exists { commitAmt, err := stake.AmountFromSStxPkScrCommitment( commitmentOut.PkScript) if err != nil { return false, fmt.Errorf("failed to parse commit "+ "out amt: %s", err.Error()) } // Calculate the fee required based on the current // height and the required amount from the pool. feeNeeded := txrules.StakePoolTicketFee(dcrutil.Amount( tx.TxOut[0].Value), fees, block.Height, w.PoolFees(), w.ChainParams()) if commitAmt < feeNeeded { log.Warnf("User %s submitted ticket %v which "+ "has less fees than are required to use this "+ "stake pool and is being skipped (required: %v"+ ", found %v)", commitAddr.EncodeAddress(), tx.TxSha(), feeNeeded, commitAmt) // Reject the entire transaction if it didn't // pay the pool server fees. return false, nil } } else { log.Warnf("Unknown pool commitment address %s for ticket %v", commitAddr.EncodeAddress(), tx.TxSha()) return false, nil } log.Debugf("Accepted valid stake pool ticket %v committing %v in fees", tx.TxSha(), tx.TxOut[0].Value) return true, 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 }
// convertToAddrIndex indexes all data pushes greater than 8 bytes within the // passed SPK and returns a TxAddrIndex with the given data. Our "address" // index is actually a hash160 index, where in the ideal case the data push // is either the hash160 of a publicKey (P2PKH) or a Script (P2SH). func convertToAddrIndex(scrVersion uint16, scr []byte, height int64, locInBlock *wire.TxLoc, txType stake.TxType) ([]*database.TxAddrIndex, error) { var tais []*database.TxAddrIndex if scr == nil || locInBlock == nil { return nil, fmt.Errorf("passed nil pointer") } var indexKey [ripemd160.Size]byte // Get the script classes and extract the PKH if applicable. // If it's multisig, unknown, etc, just hash the script itself. class, addrs, _, err := txscript.ExtractPkScriptAddrs(scrVersion, scr, activeNetParams.Params) if err != nil { return nil, fmt.Errorf("script conversion error: %v", err.Error()) } knownType := false for _, addr := range addrs { switch { case class == txscript.PubKeyTy: copy(indexKey[:], addr.Hash160()[:]) case class == txscript.PubkeyAltTy: copy(indexKey[:], addr.Hash160()[:]) case class == txscript.PubKeyHashTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.PubkeyHashAltTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeSubmissionTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeGenTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeRevocationTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeSubChangeTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.MultiSigTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.ScriptHashTy: copy(indexKey[:], addr.ScriptAddress()[:]) } tai := &database.TxAddrIndex{ Hash160: indexKey, Height: uint32(height), TxOffset: uint32(locInBlock.TxStart), TxLen: uint32(locInBlock.TxLen), } tais = append(tais, tai) knownType = true } // This is a commitment for a future vote or // revocation. Extract the address data from // it and store it in the addrIndex. if txType == stake.TxTypeSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(scr, activeNetParams.Params) if err != nil { return nil, fmt.Errorf("ticket commit pkscr conversion error: %v", err.Error()) } copy(indexKey[:], addr.ScriptAddress()[:]) tai := &database.TxAddrIndex{ Hash160: indexKey, Height: uint32(height), TxOffset: uint32(locInBlock.TxStart), TxLen: uint32(locInBlock.TxLen), } tais = append(tais, tai) } else if !knownType { copy(indexKey[:], dcrutil.Hash160(scr)) tai := &database.TxAddrIndex{ Hash160: indexKey, Height: uint32(height), TxOffset: uint32(locInBlock.TxStart), TxLen: uint32(locInBlock.TxLen), } tais = append(tais, tai) } return tais, nil }