1 // Package txrules provides transaction rules that should be followed by
2 // transaction authors for wide mempool acceptance and quick mining.
3 package txrules
4 5 import (
6 "errors"
7 "github.com/p9c/p9/pkg/amt"
8 9 "github.com/p9c/p9/pkg/txscript"
10 "github.com/p9c/p9/pkg/wire"
11 )
12 13 // DefaultRelayFeePerKb is the default minimum relay fee policy for a mempool.
14 const DefaultRelayFeePerKb amt.Amount = 1e3
15 16 // Transaction rule violations
17 var (
18 ErrAmountNegative = errors.New("transaction output amount is negative")
19 ErrAmountExceedsMax = errors.New("transaction output amount exceeds maximum value")
20 ErrOutputIsDust = errors.New("transaction output is dust")
21 )
22 23 // GetDustThreshold is used to define the amount below which output will be determined as dust. Threshold is determined
24 // as 3 times the relay fee.
25 func GetDustThreshold(scriptSize int, relayFeePerKb amt.Amount) amt.Amount {
26 // Calculate the total (estimated) cost to the network. This is calculated using the serialize size of the output
27 // plus the serial size of a transaction input which redeems it. The output is assumed to be compressed P2PKH as
28 // this is the most common script type. Use the average size of a compressed P2PKH redeem input (148) rather than
29 // the largest possible (txsizes.RedeemP2PKHInputSize).
30 totalSize := 8 + wire.VarIntSerializeSize(uint64(scriptSize)) +
31 scriptSize + 148
32 byteFee := relayFeePerKb / 1000
33 relayFee := amt.Amount(totalSize) * byteFee
34 return 3 * relayFee
35 }
36 37 // IsDustAmount determines whether a transaction output value and script length would cause the output to be considered
38 // dust. Transactions with dust outputs are not standard and are rejected by mempools with default policies.
39 func IsDustAmount(amount amt.Amount, scriptSize int, relayFeePerKb amt.Amount) bool {
40 return amount < GetDustThreshold(scriptSize, relayFeePerKb)
41 }
42 43 // IsDustOutput determines whether a transaction output is considered dust. Transactions with dust outputs are not
44 // standard and are rejected by mempools with default policies.
45 func IsDustOutput(output *wire.TxOut, relayFeePerKb amt.Amount) bool {
46 // Unspendable outputs which solely carry data are not checked for dust.
47 if txscript.GetScriptClass(output.PkScript) == txscript.NullDataTy {
48 return false
49 }
50 // All other unspendable outputs are considered dust.
51 if txscript.IsUnspendable(output.PkScript) {
52 return true
53 }
54 return IsDustAmount(
55 amt.Amount(output.Value), len(output.PkScript),
56 relayFeePerKb,
57 )
58 }
59 60 // CheckOutput performs simple consensus and policy tests on a transaction output.
61 func CheckOutput(output *wire.TxOut, relayFeePerKb amt.Amount) (e error) {
62 if output.Value < 0 {
63 return ErrAmountNegative
64 }
65 if output.Value > int64(amt.MaxSatoshi) {
66 return ErrAmountExceedsMax
67 }
68 if IsDustOutput(output, relayFeePerKb) {
69 return ErrOutputIsDust
70 }
71 return nil
72 }
73 74 // FeeForSerializeSize calculates the required fee for a transaction of some arbitrary size given a mempool's relay fee
75 // policy.
76 func FeeForSerializeSize(relayFeePerKb amt.Amount, txSerializeSize int) amt.Amount {
77 fee := relayFeePerKb * amt.Amount(txSerializeSize) / 1000
78 if fee == 0 && relayFeePerKb > 0 {
79 fee = relayFeePerKb
80 }
81 if fee < 0 || fee > amt.MaxSatoshi {
82 fee = amt.MaxSatoshi
83 }
84 return fee
85 }
86