// 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) } }
// GetSSGenVoteBits takes an SSGen tx as input and scans through its // outputs, returning the VoteBits of the index 1 output. func GetSSGenVoteBits(tx *dcrutil.Tx) uint16 { msgTx := tx.MsgTx() votebits := binary.LittleEndian.Uint16(msgTx.TxOut[1].PkScript[2:4]) return votebits }
// ValidateTransactionScripts validates the scripts for the passed transaction // using multiple goroutines. func ValidateTransactionScripts(tx *dcrutil.Tx, utxoView *UtxoViewpoint, flags txscript.ScriptFlags, sigCache *txscript.SigCache) error { // Collect all of the transaction inputs and required information for // validation. txIns := tx.MsgTx().TxIn txValItems := make([]*txValidateItem, 0, len(txIns)) for txInIdx, txIn := range txIns { // Skip coinbases. if txIn.PreviousOutPoint.Index == math.MaxUint32 { continue } txVI := &txValidateItem{ txInIndex: txInIdx, txIn: txIn, tx: tx, } txValItems = append(txValItems, txVI) } // Validate all of the inputs. validator := newTxValidator(utxoView, flags, sigCache) if err := validator.Validate(txValItems); err != nil { return err } return nil }
// IsFinalizedTransaction determines whether or not a transaction is finalized. func IsFinalizedTransaction(tx *dcrutil.Tx, blockHeight int64, blockTime time.Time) bool { msgTx := tx.MsgTx() // Lock time of zero means the transaction is finalized. lockTime := msgTx.LockTime if lockTime == 0 { return true } // The lock time field of a transaction is either a block height at // which the transaction is finalized or a timestamp depending on if the // value is before the txscript.LockTimeThreshold. When it is under the // threshold it is a block height. blockTimeOrHeight := int64(0) if lockTime < txscript.LockTimeThreshold { blockTimeOrHeight = blockHeight } else { blockTimeOrHeight = blockTime.Unix() } if int64(lockTime) < blockTimeOrHeight { return true } // At this point, the transaction's lock time hasn't occurred yet, but // the transaction might still be finalized if the sequence number // for all transaction inputs is maxed out. for _, txIn := range msgTx.TxIn { if txIn.Sequence != math.MaxUint32 { return false } } return true }
// FetchTransactionStore fetches the input transactions referenced by the // passed transaction from the point of view of the end of the main chain. It // also attempts to fetch the transaction itself so the returned TxStore can be // examined for duplicate transactions. // IsValid indicates if the current block on head has had its TxTreeRegular // validated by the stake voters. func (b *BlockChain) FetchTransactionStore(tx *dcrutil.Tx, isValid bool) (TxStore, error) { isSSGen, _ := stake.IsSSGen(tx) // Create a set of needed transactions from the transactions referenced // by the inputs of the passed transaction. Also, add the passed // transaction itself as a way for the caller to detect duplicates. txNeededSet := make(map[chainhash.Hash]struct{}) txNeededSet[*tx.Sha()] = struct{}{} for i, txIn := range tx.MsgTx().TxIn { // Skip all stakebase inputs. if isSSGen && (i == 0) { continue } txNeededSet[txIn.PreviousOutPoint.Hash] = struct{}{} } // Request the input transactions from the point of view of the end of // the main chain without including fully spent transactions in the // results. Fully spent transactions are only needed for chain // reorganization which does not apply here. txStore := fetchTxStoreMain(b.db, txNeededSet, false) topBlock, err := b.getBlockFromHash(b.bestChain.hash) if err != nil { return nil, err } if isValid { connectTxTree(txStore, topBlock, true) } return txStore, nil }
// calcPriority returns a transaction priority given a transaction and the sum // of each of its input values multiplied by their age (# of confirmations). // Thus, the final formula for the priority is: // sum(inputValue * inputAge) / adjustedTxSize func calcPriority(tx *dcrutil.Tx, inputValueAge float64) float64 { // In order to encourage spending multiple old unspent transaction // outputs thereby reducing the total set, don't count the constant // overhead for each input as well as enough bytes of the signature // script to cover a pay-to-script-hash redemption with a compressed // pubkey. This makes additional inputs free by boosting the priority // of the transaction accordingly. No more incentive is given to avoid // encouraging gaming future transactions through the use of junk // outputs. This is the same logic used in the reference // implementation. // // The constant overhead for a txin is 41 bytes since the previous // outpoint is 36 bytes + 4 bytes for the sequence + 1 byte the // signature script length. // // A compressed pubkey pay-to-script-hash redemption with a maximum len // signature is of the form: // [OP_DATA_73 <73-byte sig> + OP_DATA_35 + {OP_DATA_33 // <33 byte compresed pubkey> + OP_CHECKSIG}] // // Thus 1 + 73 + 1 + 1 + 33 + 1 = 110 overhead := 0 for _, txIn := range tx.MsgTx().TxIn { // Max inputs + size can't possibly overflow here. overhead += 41 + minInt(110, len(txIn.SignatureScript)) } serializedTxSize := tx.MsgTx().SerializeSize() if overhead >= serializedTxSize { return 0.0 } return inputValueAge / float64(serializedTxSize-overhead) }
// GetSStxStakeOutputsInfo takes an SStx as input and scans through its outputs, // returning the pubkeyhashs and amounts for any NullDataTy's (future commitments // to stake generation rewards). func GetSStxStakeOutputInfo(tx *dcrutil.Tx) ([]bool, [][]byte, []int64, []int64, [][]bool, [][]uint16) { msgTx := tx.MsgTx() isP2SH := make([]bool, len(msgTx.TxIn)) addresses := make([][]byte, len(msgTx.TxIn)) amounts := make([]int64, len(msgTx.TxIn)) changeAmounts := make([]int64, len(msgTx.TxIn)) allSpendRules := make([][]bool, len(msgTx.TxIn)) allSpendLimits := make([][]uint16, len(msgTx.TxIn)) // Cycle through the inputs and pull the proportional amounts // and commit to PKHs/SHs. for idx, out := range msgTx.TxOut { // We only care about the outputs where we get proportional // amounts and the PKHs/SHs to send rewards to, which is all // the odd numbered output indexes. if (idx > 0) && (idx%2 != 0) { // The MSB (sign), not used ever normally, encodes whether // or not it is a P2PKH or P2SH for the input. amtEncoded := make([]byte, 8, 8) copy(amtEncoded, out.PkScript[22:30]) isP2SH[idx/2] = !(amtEncoded[7]&(1<<7) == 0) // MSB set? amtEncoded[7] &= ^uint8(1 << 7) // Clear bit addresses[idx/2] = out.PkScript[2:22] amounts[idx/2] = int64(binary.LittleEndian.Uint64(amtEncoded)) // Get flags and restrictions for the outputs to be // make in either a vote or revocation. spendRules := make([]bool, 2, 2) spendLimits := make([]uint16, 2, 2) // This bitflag is true/false. feeLimitUint16 := binary.LittleEndian.Uint16(out.PkScript[30:32]) spendRules[0] = (feeLimitUint16 & SStxVoteFractionFlag) == SStxVoteFractionFlag spendRules[1] = (feeLimitUint16 & SStxRevFractionFlag) == SStxRevFractionFlag allSpendRules[idx/2] = spendRules // This is the fraction to use out of 64. spendLimits[0] = feeLimitUint16 & SStxVoteReturnFractionMask spendLimits[1] = feeLimitUint16 & SStxRevReturnFractionMask spendLimits[1] >>= 8 allSpendLimits[idx/2] = spendLimits } // Here we only care about the change amounts, so scan // the change outputs (even indices) and save their // amounts. if (idx > 0) && (idx%2 == 0) { changeAmounts[(idx/2)-1] = out.Value } } return isP2SH, addresses, amounts, changeAmounts, allSpendRules, allSpendLimits }
// matchTxAndUpdate returns true if the bloom filter matches data within the // passed transaction, otherwise false is returned. If the filter does match // the passed transaction, it will also update the filter depending on the bloom // update flags set via the loaded filter if needed. // // This function MUST be called with the filter lock held. func (bf *Filter) matchTxAndUpdate(tx *dcrutil.Tx) bool { // Check if the filter matches the hash of the transaction. // This is useful for finding transactions when they appear in a block. matched := bf.matches(tx.Sha().Bytes()) // Check if the filter matches any data elements in the public key // scripts of any of the outputs. When it does, add the outpoint that // matched so transactions which spend from the matched transaction are // also included in the filter. This removes the burden of updating the // filter for this scenario from the client. It is also more efficient // on the network since it avoids the need for another filteradd message // from the client and avoids some potential races that could otherwise // occur. for i, txOut := range tx.MsgTx().TxOut { pushedData, err := txscript.PushedData(txOut.PkScript) if err != nil { continue } for _, data := range pushedData { if !bf.matches(data) { continue } matched = true bf.maybeAddOutpoint(txOut.Version, txOut.PkScript, tx.Sha(), uint32(i), tx.Tree()) break } } // Nothing more to do if a match has already been made. if matched { return true } // At this point, the transaction and none of the data elements in the // public key scripts of its outputs matched. // Check if the filter matches any outpoints this transaction spends or // any any data elements in the signature scripts of any of the inputs. for _, txin := range tx.MsgTx().TxIn { if bf.matchesOutPoint(&txin.PreviousOutPoint) { return true } pushedData, err := txscript.PushedData(txin.SignatureScript) if err != nil { continue } for _, data := range pushedData { if bf.matches(data) { return true } } } return false }
// isNullOutpoint determines whether or not a previous transaction output point // is set. func isNullOutpoint(tx *dcrutil.Tx) bool { nullInOP := tx.MsgTx().TxIn[0].PreviousOutPoint if nullInOP.Index == math.MaxUint32 && nullInOP.Hash.IsEqual(zeroHash) && nullInOP.Tree == dcrutil.TxTreeRegular { return true } return false }
func serializeTx(tx *dcrutil.Tx) []byte { var buf bytes.Buffer err := tx.MsgTx().Serialize(&buf) if err != nil { panic(err) } return buf.Bytes() }
// isNonstandardTransaction determines whether a transaction contains any // scripts which are not one of the standard types. func isNonstandardTransaction(tx *dcrutil.Tx) bool { // Check all of the output public key scripts for non-standard scripts. for _, txOut := range tx.MsgTx().TxOut { scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript) if scriptClass == txscript.NonStandardTy { return true } } return false }
// SetTxTree analyzes the embedded MsgTx and sets the transaction tree // accordingly. func SetTxTree(tx *dcrutil.Tx) { txType := DetermineTxType(tx.MsgTx()) indicatedTree := wire.TxTreeRegular if txType != TxTypeRegular { indicatedTree = wire.TxTreeStake } tx.SetTree(indicatedTree) }
// isNullFraudProof determines whether or not a previous transaction fraud proof // is set. func isNullFraudProof(tx *dcrutil.Tx) bool { txIn := tx.MsgTx().TxIn[0] switch { case txIn.BlockHeight != wire.NullBlockHeight: return false case txIn.BlockIndex != wire.NullBlockIndex: return false } return true }
// FetchUtxoView loads utxo details about the input transactions referenced by // the passed transaction from the point of view of the end of the main chain. // It also attempts to fetch the utxo details for the transaction itself so the // returned view can be examined for duplicate unspent transaction outputs. // // This function is safe for concurrent access however the returned view is NOT. func (b *BlockChain) FetchUtxoView(tx *dcrutil.Tx, treeValid bool) (*UtxoViewpoint, error) { b.chainLock.RLock() defer b.chainLock.RUnlock() // Request the utxos from the point of view of the end of the main // chain. view := NewUtxoViewpoint() if treeValid { view.SetStakeViewpoint(ViewpointPrevValidRegular) block, err := b.fetchBlockFromHash(&b.bestNode.hash) if err != nil { return nil, err } parent, err := b.fetchBlockFromHash(&b.bestNode.header.PrevBlock) if err != nil { return nil, err } err = view.fetchInputUtxos(b.db, block, parent) if err != nil { return nil, err } for i, blockTx := range block.Transactions() { err := view.connectTransaction(blockTx, b.bestNode.height, uint32(i), nil) if err != nil { return nil, err } } } view.SetBestHash(&b.bestNode.hash) // Create a set of needed transactions based on those referenced by the // inputs of the passed transaction. Also, add the passed transaction // itself as a way for the caller to detect duplicates that are not // fully spent. txNeededSet := make(map[chainhash.Hash]struct{}) txNeededSet[*tx.Sha()] = struct{}{} msgTx := tx.MsgTx() isSSGen, _ := stake.IsSSGen(msgTx) if !IsCoinBaseTx(msgTx) { for i, txIn := range msgTx.TxIn { if isSSGen && i == 0 { continue } txNeededSet[txIn.PreviousOutPoint.Hash] = struct{}{} } } err := view.fetchUtxosMain(b.db, txNeededSet) return view, err }
// GetSSGenBlockVotedOn takes an SSGen tx and returns the block voted on in the // first OP_RETURN by hash and height. func GetSSGenBlockVotedOn(tx *dcrutil.Tx) (chainhash.Hash, uint32, error) { msgTx := tx.MsgTx() // Get the block header hash. blockSha, err := chainhash.NewHash(msgTx.TxOut[0].PkScript[2:34]) if err != nil { return chainhash.Hash{}, 0, err } // Get the block height. height := binary.LittleEndian.Uint32(msgTx.TxOut[0].PkScript[34:38]) return *blockSha, height, nil }
// checkInputsStandard performs a series of checks on a transaction's inputs // to ensure they are "standard". A standard transaction input is one that // that consumes the expected number of elements from the stack and that number // is the same as the output script pushes. This help prevent resource // exhaustion attacks by "creative" use of scripts that are super expensive to // process like OP_DUP OP_CHECKSIG OP_DROP repeated a large number of times // followed by a final OP_TRUE. // Decred TODO: I think this is okay, but we'll see with simnet. func checkInputsStandard(tx *dcrutil.Tx, txType stake.TxType, txStore blockchain.TxStore) error { // NOTE: The reference implementation also does a coinbase check here, // but coinbases have already been rejected prior to calling this // function so no need to recheck. for i, txIn := range tx.MsgTx().TxIn { if i == 0 && txType == stake.TxTypeSSGen { continue } // It is safe to elide existence and index checks here since // they have already been checked prior to calling this // function. prevOut := txIn.PreviousOutPoint originTx := txStore[prevOut.Hash].Tx.MsgTx() originPkScript := originTx.TxOut[prevOut.Index].PkScript // Calculate stats for the script pair. scriptInfo, err := txscript.CalcScriptInfo(txIn.SignatureScript, originPkScript, true) if err != nil { str := fmt.Sprintf("transaction input #%d script parse "+ "failure: %v", i, err) return txRuleError(wire.RejectNonstandard, str) } // A negative value for expected inputs indicates the script is // non-standard in some way. if scriptInfo.ExpectedInputs < 0 { str := fmt.Sprintf("transaction input #%d expects %d "+ "inputs", i, scriptInfo.ExpectedInputs) return txRuleError(wire.RejectNonstandard, str) } // The script pair is non-standard if the number of available // inputs does not match the number of expected inputs. if scriptInfo.NumInputs != scriptInfo.ExpectedInputs { str := fmt.Sprintf("transaction input #%d expects %d "+ "inputs, but referenced output script provides "+ "%d", i, scriptInfo.ExpectedInputs, scriptInfo.NumInputs) return txRuleError(wire.RejectNonstandard, str) } } return nil }
// 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 }
// GetSSGenStakeOutputInfo takes an SSGen tx as input and scans through its // outputs, returning the amount of the output and the PKH or SH that it was // sent to. func GetSSGenStakeOutputInfo(tx *dcrutil.Tx, params *chaincfg.Params) ([]bool, [][]byte, []int64, error) { msgTx := tx.MsgTx() numOutputsInSSGen := len(msgTx.TxOut) isP2SH := make([]bool, numOutputsInSSGen-2) addresses := make([][]byte, numOutputsInSSGen-2) amounts := make([]int64, numOutputsInSSGen-2) // Cycle through the inputs and generate for idx, out := range msgTx.TxOut { // We only care about the outputs where we get proportional // amounts and the PKHs they were sent to. if (idx > 1) && (idx < numOutputsInSSGen) { // Get the PKH or SH it's going to, and what type of // script it is. class, addr, _, err := txscript.ExtractPkScriptAddrs(out.Version, out.PkScript, params) if err != nil { return nil, nil, nil, err } if class != txscript.StakeGenTy { return nil, nil, nil, fmt.Errorf("ssgen output included non "+ "ssgen tagged output in idx %v", idx) } subClass, err := txscript.GetStakeOutSubclass(out.PkScript) if !(subClass == txscript.PubKeyHashTy || subClass == txscript.ScriptHashTy) { return nil, nil, nil, fmt.Errorf("bad script type") } isP2SH[idx-2] = false if subClass == txscript.ScriptHashTy { isP2SH[idx-2] = true } // Get the amount that was sent. amt := out.Value addresses[idx-2] = addr[0].ScriptAddress() amounts[idx-2] = amt } } return isP2SH, addresses, amounts, nil }
func (c *Client) onRecvTx(tx *dcrutil.Tx, block *dcrjson.BlockDetails) { blk, err := parseBlock(block) if err != nil { // Log and drop improper notification. log.Errorf("recvtx notification bad block: %v", err) return } rec, err := wtxmgr.NewTxRecordFromMsgTx(tx.MsgTx(), time.Now()) if err != nil { log.Errorf("Cannot create transaction record for relevant "+ "tx: %v", err) return } select { case c.enqueueNotification <- RelevantTx{rec, blk}: case <-c.quit: } }
// IsStakeBase returns whether or not a tx could be considered as having a // topically valid stake base present. func IsStakeBase(tx *dcrutil.Tx) bool { msgTx := tx.MsgTx() // A stake base (SSGen) must only have two transaction inputs. if len(msgTx.TxIn) != 2 { return false } // The previous output of a coin base must have a max value index and // a zero hash, as well as null fraud proofs. if !isNullOutpoint(tx) { return false } if !isNullFraudProof(tx) { return false } return true }
// insertSStx inserts an SStx into the store. func (s *StakeStore) insertSStx(ns walletdb.ReadWriteBucket, sstx *dcrutil.Tx, voteBits stake.VoteBits) error { // If we already have the SStx, no need to // try to include twice. exists := s.checkHashInStore(sstx.Sha()) if exists { log.Tracef("Attempted to insert SStx %v into the stake store, "+ "but the SStx already exists.", sstx.Sha()) return nil } record := &sstxRecord{ sstx, time.Now(), true, voteBits.Bits, voteBits.ExtendedBits, } // Add the SStx to the database. err := putSStxRecord(ns, record, voteBits) if err != nil { return err } // Add the SStx's hash to the internal list in the store. s.addHashToStore(sstx.Sha()) return nil }
// insertSStx inserts an SStx into the store. func (s *StakeStore) insertSStx(sstx *dcrutil.Tx) error { // If we already have the SStx, no need to // try to include twice. exists := s.checkHashInStore(sstx.Sha()) if exists { log.Tracef("Attempted to insert SStx %v into the stake store, "+ "but the SStx already exists.", sstx.Sha()) return nil } record := &sstxRecord{ sstx, time.Now(), } // Add the SStx to the database. err := s.namespace.Update(func(tx walletdb.Tx) error { if putErr := putSStxRecord(tx, record); putErr != nil { return putErr } return nil }) if err != nil { return err } // Add the SStx's hash to the internal list in the store. s.addHashToStore(sstx.Sha()) return nil }
// CoinbasePaysTax checks to see if a given block's coinbase correctly pays // tax to the developer organization. func CoinbasePaysTax(subsidyCache *SubsidyCache, tx *dcrutil.Tx, height uint32, voters uint16, params *chaincfg.Params) error { // Taxes only apply from block 2 onwards. if height <= 1 { return nil } // Tax is disabled. if params.BlockTaxProportion == 0 { return nil } if len(tx.MsgTx().TxOut) == 0 { errStr := fmt.Sprintf("invalid coinbase (no outputs)") return ruleError(ErrNoTxOutputs, errStr) } taxOutput := tx.MsgTx().TxOut[0] if taxOutput.Version != params.OrganizationPkScriptVersion { return ruleError(ErrNoTax, "coinbase tax output uses incorrect script version") } if !bytes.Equal(taxOutput.PkScript, params.OrganizationPkScript) { return ruleError(ErrNoTax, "coinbase tax output script does not match the "+ "required script") } // Get the amount of subsidy that should have been paid out to // the organization, then check it. orgSubsidy := CalcBlockTaxSubsidy(subsidyCache, int64(height), voters, params) if orgSubsidy != taxOutput.Value { errStr := fmt.Sprintf("amount in output 0 has non matching org "+ "calculated amount; got %v, want %v", taxOutput.Value, orgSubsidy) return ruleError(ErrNoTax, errStr) } return nil }
// 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() } }
// AddTicket adds a ticket transaction to the wallet. func (w *Wallet) AddTicket(ticket *dcrutil.Tx) error { return walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { stakemgrNs := tx.ReadWriteBucket(wstakemgrNamespaceKey) // Insert the ticket to be tracked and voted. err := w.StakeMgr.InsertSStx(stakemgrNs, ticket, w.VoteBits) if err != nil { return err } if w.stakePoolEnabled { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) // Pluck the ticketaddress to indentify the stakepool user. pkVersion := ticket.MsgTx().TxOut[0].Version pkScript := ticket.MsgTx().TxOut[0].PkScript _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkVersion, pkScript, w.ChainParams()) if err != nil { return err } ticketHash := ticket.MsgTx().TxSha() chainClient, err := w.requireChainClient() if err != nil { return err } rawTx, err := chainClient.GetRawTransactionVerbose(&ticketHash) if err != nil { return err } // Update the pool ticket stake. This will include removing it from the // invalid slice and adding a ImmatureOrLive ticket to the valid ones. err = w.updateStakePoolInvalidTicket(stakemgrNs, addrmgrNs, addrs[0], &ticketHash, rawTx.BlockHeight) if err != nil { return err } } return nil }) }
// IsSSGen returns whether or not a transaction is an SSRtx. It does some // simple validation steps to make sure the number of inputs, number of // outputs, and the input/output scripts are valid. // // SSRtx transactions are specified as below. // Inputs: // SStx-tagged output [index 0] // // Outputs: // SSGen-tagged output to address from SStx-tagged output's tx index output 1 // [index 0] // SSGen-tagged output to address from SStx-tagged output's tx index output 2 // [index 1] // ... // SSGen-tagged output to address from SStx-tagged output's tx index output // MaxInputsPerSStx [index MaxOutputsPerSSRtx - 1] // // The errors in this function can be ignored if you want to use it in to // identify SSRtx from a list of stake tx. func IsSSRtx(tx *dcrutil.Tx) (bool, error) { msgTx := tx.MsgTx() // Check to make sure there is the correct number of inputs. // CheckTransactionSanity already makes sure that number of inputs is // greater than 0, so no need to check that. if len(msgTx.TxIn) != NumInputsPerSSRtx { return false, stakeRuleError(ErrSSRtxWrongNumInputs, "SSRtx has an "+ " invalid number of inputs") } // Check to make sure there aren't too many outputs. if len(msgTx.TxOut) > MaxOutputsPerSSRtx { return false, stakeRuleError(ErrSSRtxTooManyOutputs, "SSRtx has too "+ "many outputs") } // Check to make sure there are some outputs. if len(msgTx.TxOut) == 0 { return false, stakeRuleError(ErrSSRtxNoOutputs, "SSRtx has no "+ "outputs") } // Check to make sure that all output scripts are the default version. for _, txOut := range msgTx.TxOut { if txOut.Version != txscript.DefaultScriptVersion { return false, stakeRuleError(ErrSSRtxBadOuts, "invalid "+ "script version found in txOut") } } // Check to make sure that the output used as input came from TxTreeStake. for _, txin := range msgTx.TxIn { if txin.PreviousOutPoint.Tree != dcrutil.TxTreeStake { return false, stakeRuleError(ErrSSRtxWrongTxTree, "SSRtx used "+ "a non-stake input") } } // Ensure that the first input is an SStx tagged output. // TODO: Do this in validate, needs a DB and chain. // Ensure that the tx height given in the last 8 bytes is StakeMaturity // many blocks ahead of the block in which that SStx appear, otherwise // this ticket has failed to mature and the SStx must be invalid. // TODO: Do this in validate, needs a DB and chain. // Ensure that the outputs are OP_SSRTX tagged. // Ensure that the tx height given in the last 8 bytes is StakeMaturity // many blocks ahead of the block in which that SStx appear, otherwise // this ticket has failed to mature and the SStx must be invalid. // TODO: This is validate level stuff, do this there. // Ensure that the outputs are OP_SSRTX tagged. for outTxIndex := 0; outTxIndex < len(msgTx.TxOut); outTxIndex++ { scrVersion := msgTx.TxOut[outTxIndex].Version rawScript := msgTx.TxOut[outTxIndex].PkScript // The script should be a OP_SSRTX tagged output. if txscript.GetScriptClass(scrVersion, rawScript) != txscript.StakeRevocationTy { str := fmt.Sprintf("SSRtx output at output index %d was not "+ "an OP_SSRTX tagged output", outTxIndex) return false, stakeRuleError(ErrSSRtxBadOuts, str) } } // Ensure the number of outputs is equal to the number of inputs found in // the original SStx. // TODO: Do this in validate, needs a DB and chain. return true, nil }
// checkTransactionStandard performs a series of checks on a transaction to // ensure it is a "standard" transaction. A standard transaction is one that // conforms to several additional limiting cases over what is considered a // "sane" transaction such as having a version in the supported range, being // finalized, conforming to more stringent size constraints, having scripts // of recognized forms, and not containing "dust" outputs (those that are // so small it costs more to process them than they are worth). func checkTransactionStandard(tx *dcrutil.Tx, txType stake.TxType, height int64, timeSource blockchain.MedianTimeSource, minRelayTxFee dcrutil.Amount) error { // The transaction must be a currently supported version. msgTx := tx.MsgTx() if !wire.IsSupportedMsgTxVersion(msgTx) { str := fmt.Sprintf("transaction version %d is not in the "+ "valid range of %d-%d", msgTx.Version, 1, wire.TxVersion) return txRuleError(wire.RejectNonstandard, str) } // The transaction must be finalized to be standard and therefore // considered for inclusion in a block. adjustedTime := timeSource.AdjustedTime() if !blockchain.IsFinalizedTransaction(tx, height, adjustedTime) { return txRuleError(wire.RejectNonstandard, "transaction is not finalized") } // Since extremely large transactions with a lot of inputs can cost // almost as much to process as the sender fees, limit the maximum // size of a transaction. This also helps mitigate CPU exhaustion // attacks. serializedLen := msgTx.SerializeSize() if serializedLen > maxStandardTxSize { str := fmt.Sprintf("transaction size of %v is larger than max "+ "allowed size of %v", serializedLen, maxStandardTxSize) return txRuleError(wire.RejectNonstandard, str) } for i, txIn := range msgTx.TxIn { // Each transaction input signature script must not exceed the // maximum size allowed for a standard transaction. See // the comment on maxStandardSigScriptSize for more details. sigScriptLen := len(txIn.SignatureScript) if sigScriptLen > maxStandardSigScriptSize { str := fmt.Sprintf("transaction input %d: signature "+ "script size of %d bytes is large than max "+ "allowed size of %d bytes", i, sigScriptLen, maxStandardSigScriptSize) return txRuleError(wire.RejectNonstandard, str) } // Each transaction input signature script must only contain // opcodes which push data onto the stack. if !txscript.IsPushOnlyScript(txIn.SignatureScript) { str := fmt.Sprintf("transaction input %d: signature "+ "script is not push only", i) return txRuleError(wire.RejectNonstandard, str) } } // None of the output public key scripts can be a non-standard script or // be "dust" (except when the script is a null data script). numNullDataOutputs := 0 for i, txOut := range msgTx.TxOut { scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript) err := checkPkScriptStandard(txOut.Version, txOut.PkScript, scriptClass) if err != nil { // Attempt to extract a reject code from the error so // it can be retained. When not possible, fall back to // a non standard error. rejectCode, found := extractRejectCode(err) if !found { rejectCode = wire.RejectNonstandard } str := fmt.Sprintf("transaction output %d: %v", i, err) return txRuleError(rejectCode, str) } // Accumulate the number of outputs which only carry data. For // all other script types, ensure the output value is not // "dust". if scriptClass == txscript.NullDataTy { numNullDataOutputs++ } else if isDust(txOut, minRelayTxFee) && txType != stake.TxTypeSStx { str := fmt.Sprintf("transaction output %d: payment "+ "of %d is dust", i, txOut.Value) return txRuleError(wire.RejectDust, str) } } // A standard transaction must not have more than one output script that // only carries data. However, certain types of standard stake transactions // are allowed to have multiple OP_RETURN outputs, so only throw an error here // if the tx is TxTypeRegular. if numNullDataOutputs > maxNullDataOutputs && txType == stake.TxTypeRegular { str := "more than one transaction output in a nulldata script for a " + "regular type tx" return txRuleError(wire.RejectNonstandard, str) } return nil }
// SignVRTransaction signs a vote (SSGen) or revocation (SSRtx) // transaction. isSSGen indicates if it is an SSGen; if it's not, // it's an SSRtx. func (s *StakeStore) SignVRTransaction(waddrmgrNs walletdb.ReadBucket, msgTx *wire.MsgTx, sstx *dcrutil.Tx, isSSGen bool) error { if s.isClosed { str := "stake store is closed" return stakeStoreError(ErrStoreClosed, str, nil) } txInNumToSign := 0 hashType := txscript.SigHashAll if isSSGen { // For an SSGen tx, skip the first input as it is a stake base // and doesn't need to be signed. msgTx.TxIn[0].SignatureScript = s.Params.StakeBaseSigScript txInNumToSign = 1 } // Get the script for the OP_SSTX tagged output that we need // to sign. sstxOutScript := sstx.MsgTx().TxOut[0].PkScript // Set up our callbacks that we pass to dcrscript so it can // look up the appropriate keys and scripts by address. getKey := txscript.KeyClosure(func(addr dcrutil.Address) ( chainec.PrivateKey, bool, error) { address, err := s.Manager.Address(waddrmgrNs, addr) if err != nil { return nil, false, err } pka, ok := address.(waddrmgr.ManagedPubKeyAddress) if !ok { return nil, false, fmt.Errorf("address is not " + "a pubkey address") } key, err := pka.PrivKey() if err != nil { return nil, false, err } return key, pka.Compressed(), nil }) getScript := txscript.ScriptClosure(func( addr dcrutil.Address) ([]byte, error) { address, err := s.Manager.Address(waddrmgrNs, addr) if err != nil { return nil, err } sa, ok := address.(waddrmgr.ManagedScriptAddress) if !ok { return nil, fmt.Errorf("address is not a script" + " address") } return sa.Script() }) // Attempt to generate the signed txin. signedScript, err := txscript.SignTxOutput(s.Params, msgTx, txInNumToSign, sstxOutScript, hashType, getKey, getScript, msgTx.TxIn[txInNumToSign].SignatureScript, chainec.ECTypeSecp256k1) if err != nil { return fmt.Errorf("failed to sign ssgen or "+ "ssrtx, error: %v", err.Error()) } msgTx.TxIn[txInNumToSign].SignatureScript = signedScript // Either it was already signed or we just signed it. // Find out if it is completely satisfied or still needs more. // Decred: Needed?? flags := txscript.ScriptBip16 engine, err := txscript.NewEngine(sstxOutScript, msgTx, txInNumToSign, flags, txscript.DefaultScriptVersion, nil) if err != nil { return fmt.Errorf("failed to generate signature script engine for "+ "ssgen or ssrtx, error: %v", err.Error()) } err = engine.Execute() if err != nil { return fmt.Errorf("failed to generate correct signature script for "+ "ssgen or ssrtx: %v", err.Error()) } return nil }
// BlockOneCoinbasePaysTokens checks to see if the first block coinbase pays // out to the network initial token ledger. func BlockOneCoinbasePaysTokens(tx *dcrutil.Tx, params *chaincfg.Params) error { // If no ledger is specified, just return true. if len(params.BlockOneLedger) == 0 { return nil } if tx.MsgTx().LockTime != 0 { errStr := fmt.Sprintf("block 1 coinbase has invalid locktime") return ruleError(ErrBlockOneTx, errStr) } if tx.MsgTx().Expiry != wire.NoExpiryValue { errStr := fmt.Sprintf("block 1 coinbase has invalid expiry") return ruleError(ErrBlockOneTx, errStr) } if tx.MsgTx().TxIn[0].Sequence != wire.MaxTxInSequenceNum { errStr := fmt.Sprintf("block 1 coinbase not finalized") return ruleError(ErrBlockOneInputs, errStr) } if len(tx.MsgTx().TxOut) == 0 { errStr := fmt.Sprintf("coinbase outputs empty in block 1") return ruleError(ErrBlockOneOutputs, errStr) } ledger := params.BlockOneLedger if len(ledger) != len(tx.MsgTx().TxOut) { errStr := fmt.Sprintf("wrong number of outputs in block 1 coinbase; "+ "got %v, expected %v", len(tx.MsgTx().TxOut), len(ledger)) return ruleError(ErrBlockOneOutputs, errStr) } // Check the addresses and output amounts against those in the ledger. for i, txout := range tx.MsgTx().TxOut { if txout.Version != txscript.DefaultScriptVersion { errStr := fmt.Sprintf("bad block one output version; want %v, got %v", txscript.DefaultScriptVersion, txout.Version) return ruleError(ErrBlockOneOutputs, errStr) } // There should only be one address. _, addrs, _, err := txscript.ExtractPkScriptAddrs(txout.Version, txout.PkScript, params) if len(addrs) != 1 { errStr := fmt.Sprintf("too many addresses in output") return ruleError(ErrBlockOneOutputs, errStr) } addrLedger, err := dcrutil.DecodeAddress(ledger[i].Address, params) if err != nil { return err } if !bytes.Equal(addrs[0].ScriptAddress(), addrLedger.ScriptAddress()) { errStr := fmt.Sprintf("address in output %v has non matching "+ "address; got %v (hash160 %x), want %v (hash160 %x)", i, addrs[0].EncodeAddress(), addrs[0].ScriptAddress(), addrLedger.EncodeAddress(), addrLedger.ScriptAddress()) return ruleError(ErrBlockOneOutputs, errStr) } if txout.Value != ledger[i].Amount { errStr := fmt.Sprintf("address in output %v has non matching "+ "amount; got %v, want %v", i, txout.Value, ledger[i].Amount) return ruleError(ErrBlockOneOutputs, errStr) } } return nil }
// CoinbasePaysTax checks to see if a given block's coinbase correctly pays // tax to the developer organization. func CoinbasePaysTax(tx *dcrutil.Tx, height uint32, voters uint16, params *chaincfg.Params) error { // Taxes only apply from block 2 onwards. if height <= 1 { return nil } // Tax is disabled. if params.BlockTaxProportion == 0 { return nil } if len(tx.MsgTx().TxOut) == 0 { errStr := fmt.Sprintf("invalid coinbase (no outputs)") return ruleError(ErrNoTxOutputs, errStr) } // Coinbase output 0 must be the subsidy to the dev organization. taxPkVersion := tx.MsgTx().TxOut[0].Version taxPkScript := tx.MsgTx().TxOut[0].PkScript class, addrs, _, err := txscript.ExtractPkScriptAddrs(taxPkVersion, taxPkScript, params) // The script can't be a weird class. if !(class == txscript.ScriptHashTy || class == txscript.PubKeyHashTy || class == txscript.PubKeyTy) { errStr := fmt.Sprintf("wrong script class for tax output") return ruleError(ErrNoTax, errStr) } // There should only be one address. if len(addrs) != 1 { errStr := fmt.Sprintf("no or too many addresses in output") return ruleError(ErrNoTax, errStr) } // Decode the organization address. addrOrg, err := dcrutil.DecodeAddress(params.OrganizationAddress, params) if err != nil { return err } if !bytes.Equal(addrs[0].ScriptAddress(), addrOrg.ScriptAddress()) { errStr := fmt.Sprintf("address in output 0 has non matching org "+ "address; got %v (hash160 %x), want %v (hash160 %x)", addrs[0].EncodeAddress(), addrs[0].ScriptAddress(), addrOrg.EncodeAddress(), addrOrg.ScriptAddress()) return ruleError(ErrNoTax, errStr) } // Get the amount of subsidy that should have been paid out to // the organization, then check it. orgSubsidy := CalcBlockTaxSubsidy(int64(height), voters, params) amountFound := tx.MsgTx().TxOut[0].Value if orgSubsidy != amountFound { errStr := fmt.Sprintf("amount in output 0 has non matching org "+ "calculated amount; got %v, want %v", amountFound, orgSubsidy) return ruleError(ErrNoTax, errStr) } return nil }