// 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 }
// IsDustOutput determines whether a transaction output is considered dust. // Transactions with dust outputs are not standard and are rejected by mempools // with default policies. func IsDustOutput(output *wire.TxOut, relayFeePerKb dcrutil.Amount) bool { // Unspendable outputs which solely carry data are not checked for dust. if txscript.GetScriptClass(output.Version, output.PkScript) == txscript.NullDataTy { return false } // All other unspendable outputs are considered dust. if txscript.IsUnspendable(output.Value, output.PkScript) { return true } return IsDustAmount(dcrutil.Amount(output.Value), len(output.PkScript), relayFeePerKb) }
// TestIsUnspendable ensures the IsUnspendable function returns the expected // results. func TestIsUnspendable(t *testing.T) { t.Parallel() tests := []struct { name string amount int64 pkScript []byte expected bool }{ { // Unspendable amount: 100, pkScript: []byte{0x6a, 0x04, 0x74, 0x65, 0x73, 0x74}, expected: true, }, { // Unspendable amount: 0, pkScript: []byte{0x76, 0xa9, 0x14, 0x29, 0x95, 0xa0, 0xfe, 0x68, 0x43, 0xfa, 0x9b, 0x95, 0x45, 0x97, 0xf0, 0xdc, 0xa7, 0xa4, 0x4d, 0xf6, 0xfa, 0x0b, 0x5c, 0x88, 0xac}, expected: true, }, { // Spendable amount: 100, pkScript: []byte{0x76, 0xa9, 0x14, 0x29, 0x95, 0xa0, 0xfe, 0x68, 0x43, 0xfa, 0x9b, 0x95, 0x45, 0x97, 0xf0, 0xdc, 0xa7, 0xa4, 0x4d, 0xf6, 0xfa, 0x0b, 0x5c, 0x88, 0xac}, expected: false, }, } for i, test := range tests { res := txscript.IsUnspendable(test.amount, test.pkScript) if res != test.expected { t.Errorf("TestIsUnspendable #%d failed: got %v want %v", i, res, test.expected) continue } } }
// isDust returns whether or not the passed transaction output amount is // considered dust or not based on the passed minimum transaction relay fee. // Dust is defined in terms of the minimum transaction relay fee. In // particular, if the cost to the network to spend coins is more than 1/3 of the // minimum transaction relay fee, it is considered dust. func isDust(txOut *wire.TxOut, minRelayTxFee dcrutil.Amount) bool { // Unspendable outputs are considered dust. if txscript.IsUnspendable(txOut.PkScript) { return true } // The total serialized size consists of the output and the associated // input script to redeem it. Since there is no input script // to redeem it yet, use the minimum size of a typical input script. // // Pay-to-pubkey-hash bytes breakdown: // // Output to hash (38 bytes): // 2 script version, 8 value, 1 script len, 25 script // [1 OP_DUP, 1 OP_HASH_160, 1 OP_DATA_20, 20 hash, // 1 OP_EQUALVERIFY, 1 OP_CHECKSIG] // // Input with compressed pubkey (165 bytes): // 37 prev outpoint, 16 fraud proof, 1 script len, // 107 script [1 OP_DATA_72, 72 sig, 1 OP_DATA_33, // 33 compressed pubkey], 4 sequence // // Input with uncompressed pubkey (198 bytes): // 37 prev outpoint, 16 fraud proof, 1 script len, // 139 script [1 OP_DATA_72, 72 sig, 1 OP_DATA_65, // 65 compressed pubkey], 4 sequence, 1 witness // append // // Pay-to-pubkey bytes breakdown: // // Output to compressed pubkey (46 bytes): // 2 script version, 8 value, 1 script len, 35 script // [1 OP_DATA_33, 33 compressed pubkey, 1 OP_CHECKSIG] // // Output to uncompressed pubkey (76 bytes): // 2 script version, 8 value, 1 script len, 67 script // [1 OP_DATA_65, 65 pubkey, 1 OP_CHECKSIG] // // Input (133 bytes): // 37 prev outpoint, 16 fraud proof, 1 script len, 73 // script [1 OP_DATA_72, 72 sig], 4 sequence, 1 witness // append // // Theoretically this could examine the script type of the output script // and use a different size for the typical input script size for // pay-to-pubkey vs pay-to-pubkey-hash inputs per the above breakdowns, // but the only combinination which is less than the value chosen is // a pay-to-pubkey script with a compressed pubkey, which is not very // common. // // The most common scripts are pay-to-pubkey-hash, and as per the above // breakdown, the minimum size of a p2pkh input script is 165 bytes. So // that figure is used. totalSize := txOut.SerializeSize() + 165 // The output is considered dust if the cost to the network to spend the // coins is more than 1/3 of the minimum free transaction relay fee. // minFreeTxRelayFee is in Atom/KB, so multiply by 1000 to // convert to bytes. // // Using the typical values for a pay-to-pubkey-hash transaction from // the breakdown above and the default minimum free transaction relay // fee of 5000000, this equates to values less than 546 atoms being // considered dust. // // The following is equivalent to (value/totalSize) * (1/3) * 1000 // without needing to do floating point math. return txOut.Value*1000/(3*int64(totalSize)) < int64(minRelayTxFee) }