Dynamic min-relay-fee and dust amount (#280)

* [btc-embedded] add chainfee.Estimator and extraAPI interfaces

* dynamic fee amount

* dynamic dust amount

* [client] fix linter errors

* [domain] fix unit tests

* [server] return dust amount in GetInfo RPC

* [sdk] fix lnd dependencie

* go work sync

* fix witness stack size forfeit tx size estimator

* remove hardcoded fee values in covenant txbuilder

* lower liquid feerate

* fix after reviews

* go work sync
This commit is contained in:
Louis Singer
2024-09-10 17:22:09 +02:00
committed by GitHub
parent a8cf0ed204
commit 0fb34cb13d
75 changed files with 2061 additions and 594 deletions

View File

@@ -19,12 +19,9 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
const (
connectorAmount = uint64(1000)
dustLimit = uint64(1000)
"github.com/lightningnetwork/lnd/input"
)
type txBuilder struct {
@@ -155,19 +152,29 @@ func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx strin
}
func (b *txBuilder) BuildForfeitTxs(
aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment, minRelayFee uint64,
aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment,
) (connectors []string, forfeitTxs []string, err error) {
connectorPkScript, err := b.getConnectorPkScript(poolTx)
if err != nil {
return nil, nil, err
}
connectorTxs, err := b.createConnectors(poolTx, payments, connectorPkScript, minRelayFee)
minRelayFeeConnectorTx, err := b.minRelayFeeConnectorTx()
if err != nil {
return nil, nil, err
}
forfeitTxs, err = b.createForfeitTxs(aspPubkey, payments, connectorTxs, minRelayFee)
connectorTxs, err := b.createConnectors(poolTx, payments, connectorPkScript, minRelayFeeConnectorTx)
if err != nil {
return nil, nil, err
}
minRelayFeeForfeitTx, err := b.minRelayFeeForfeitTx()
if err != nil {
return nil, nil, err
}
forfeitTxs, err = b.createForfeitTxs(aspPubkey, payments, connectorTxs, minRelayFeeForfeitTx)
if err != nil {
return nil, nil, err
}
@@ -183,7 +190,6 @@ func (b *txBuilder) BuildPoolTx(
aspPubkey *secp256k1.PublicKey,
payments []domain.Payment,
boardingInputs []ports.BoardingInput,
minRelayFee uint64,
sweptRounds []domain.Round,
cosigners ...*secp256k1.PublicKey,
) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error) {
@@ -196,9 +202,14 @@ func (b *txBuilder) BuildPoolTx(
receivers := getOffchainReceivers(payments)
feeAmount, err := b.minRelayFeeTreeTx()
if err != nil {
return "", nil, "", err
}
if !isOnchainOnly(payments) {
sharedOutputScript, sharedOutputAmount, err = bitcointree.CraftSharedOutput(
cosigners, aspPubkey, receivers, minRelayFee, b.roundLifetime, b.exitDelay,
cosigners, aspPubkey, receivers, feeAmount, b.roundLifetime, b.exitDelay,
)
if err != nil {
return
@@ -211,7 +222,7 @@ func (b *txBuilder) BuildPoolTx(
}
ptx, err := b.createPoolTx(
aspPubkey, sharedOutputAmount, sharedOutputScript, payments, boardingInputs, connectorAddress, minRelayFee, sweptRounds,
aspPubkey, sharedOutputAmount, sharedOutputScript, payments, boardingInputs, connectorAddress, sweptRounds,
)
if err != nil {
return
@@ -229,7 +240,7 @@ func (b *txBuilder) BuildPoolTx(
}
congestionTree, err = bitcointree.CraftCongestionTree(
initialOutpoint, cosigners, aspPubkey, receivers, minRelayFee, b.roundLifetime, b.exitDelay,
initialOutpoint, cosigners, aspPubkey, receivers, feeAmount, b.roundLifetime, b.exitDelay,
)
if err != nil {
return
@@ -317,16 +328,22 @@ func (b *txBuilder) FindLeaves(congestionTree tree.CongestionTree, fromtxid stri
// TODO add locktimes to txs
func (b *txBuilder) BuildAsyncPaymentTransactions(
vtxos []domain.Vtxo, aspPubKey *secp256k1.PublicKey,
receivers []domain.Receiver, minRelayFee uint64,
vtxos []domain.Vtxo, aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver,
) (*domain.AsyncPaymentTxs, error) {
if len(vtxos) <= 0 {
return nil, fmt.Errorf("missing vtxos")
}
for _, vtxo := range vtxos {
if vtxo.AsyncPayment != nil {
return nil, fmt.Errorf("vtxo %s is an async payment", vtxo.Txid)
}
}
ins := make([]*wire.OutPoint, 0, len(vtxos))
outs := make([]*wire.TxOut, 0, len(receivers))
unconditionalForfeitTxs := make([]string, 0, len(vtxos))
redeemTxWeightEstimator := &input.TxWeightEstimator{}
for _, vtxo := range vtxos {
if vtxo.Spent {
return nil, fmt.Errorf("all vtxos must be unspent")
@@ -342,6 +359,7 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
return nil, err
}
// TODO generate a fresh new address to get the forfeit funds
aspScript, err := p2trScript(aspPubKey, b.onchainNetwork())
if err != nil {
return nil, err
@@ -362,11 +380,6 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
return nil, err
}
output := &wire.TxOut{
PkScript: aspScript,
Value: int64(vtxo.Amount - minRelayFee),
}
forfeitClosure := &bitcointree.MultisigClosure{
Pubkey: sender,
AspPubkey: aspPubKey,
@@ -384,6 +397,28 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
return nil, err
}
forfeitTxWeightEstimator := &input.TxWeightEstimator{}
tapscript := &waddrmgr.Tapscript{
RevealedScript: leafProof.Script,
ControlBlock: &ctrlBlock,
}
forfeitTxWeightEstimator.AddTapscriptInput(64*2, tapscript)
forfeitTxWeightEstimator.AddP2TROutput() // ASP output
forfeitTxFee, err := b.wallet.MinRelayFee(context.Background(), uint64(forfeitTxWeightEstimator.VSize()))
if err != nil {
return nil, err
}
if forfeitTxFee >= vtxo.Amount {
return nil, fmt.Errorf("forfeit tx fee is higher than the amount of the vtxo")
}
output := &wire.TxOut{
PkScript: aspScript,
Value: int64(vtxo.Amount - forfeitTxFee),
}
unconditionnalForfeitPtx, err := psbt.New(
[]*wire.OutPoint{vtxoOutpoint},
[]*wire.TxOut{output},
@@ -416,6 +451,20 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
unconditionalForfeitTxs = append(unconditionalForfeitTxs, forfeitTx)
ins = append(ins, vtxoOutpoint)
redeemTxWeightEstimator.AddTapscriptInput(64, tapscript)
}
for range receivers {
redeemTxWeightEstimator.AddP2TROutput()
}
redeemTxMinRelayFee, err := b.wallet.MinRelayFee(context.Background(), uint64(redeemTxWeightEstimator.VSize()))
if err != nil {
return nil, err
}
if redeemTxMinRelayFee >= receivers[len(receivers)-1].Amount {
return nil, fmt.Errorf("redeem tx fee is higher than the amount of the change receiver")
}
for i, receiver := range receivers {
@@ -437,7 +486,7 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
// to be the change in case it's not a send-all.
value := receiver.Amount
if i == len(receivers)-1 {
value -= minRelayFee
value -= redeemTxMinRelayFee
}
outs = append(outs, &wire.TxOut{
Value: int64(value),
@@ -459,7 +508,6 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
redeemPtx.Inputs[i].WitnessUtxo = unconditionnalForfeitPsbt.Inputs[0].WitnessUtxo
redeemPtx.Inputs[i].TaprootInternalKey = unconditionnalForfeitPsbt.Inputs[0].TaprootInternalKey
redeemPtx.Inputs[i].TaprootLeafScript = unconditionnalForfeitPsbt.Inputs[0].TaprootLeafScript
}
redeemTx, err := redeemPtx.B64Encode()
@@ -531,7 +579,7 @@ func (b *txBuilder) getLeafScriptAndTree(
func (b *txBuilder) createPoolTx(
aspPubKey *secp256k1.PublicKey,
sharedOutputAmount int64, sharedOutputScript []byte,
payments []domain.Payment, boardingInputs []ports.BoardingInput, connectorAddress string, minRelayFee uint64,
payments []domain.Payment, boardingInputs []ports.BoardingInput, connectorAddress string,
sweptRounds []domain.Round,
) (*psbt.Packet, error) {
connectorAddr, err := btcutil.DecodeAddress(connectorAddress, b.onchainNetwork())
@@ -544,11 +592,23 @@ func (b *txBuilder) createPoolTx(
return nil, err
}
connectorMinRelayFee, err := b.minRelayFeeConnectorTx()
if err != nil {
return nil, err
}
dustLimit, err := b.wallet.GetDustAmount(context.Background())
if err != nil {
return nil, err
}
connectorAmount := dustLimit
receivers := getOnchainReceivers(payments)
nbOfInputs := countSpentVtxos(payments)
connectorsAmount := (connectorAmount + minRelayFee) * nbOfInputs
connectorsAmount := (connectorAmount + connectorMinRelayFee) * nbOfInputs
if nbOfInputs > 1 {
connectorsAmount -= minRelayFee
connectorsAmount -= connectorMinRelayFee
}
targetAmount := connectorsAmount
@@ -855,6 +915,10 @@ func (b *txBuilder) createPoolTx(
return ptx, nil
}
func (b *txBuilder) minRelayFeeConnectorTx() (uint64, error) {
return b.wallet.MinRelayFee(context.Background(), uint64(common.ConnectorTxSize))
}
func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string, error) {
roundTx, err := psbt.NewFromRawBytes(strings.NewReader(dest), true)
if err != nil {
@@ -912,13 +976,18 @@ func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string,
}
func (b *txBuilder) createConnectors(
poolTx string, payments []domain.Payment, connectorScript []byte, minRelayFee uint64,
poolTx string, payments []domain.Payment, connectorScript []byte, feeAmount uint64,
) ([]*psbt.Packet, error) {
partialTx, err := psbt.NewFromRawBytes(strings.NewReader(poolTx), true)
if err != nil {
return nil, err
}
connectorAmount, err := b.wallet.GetDustAmount(context.Background())
if err != nil {
return nil, err
}
connectorOutput := &wire.TxOut{
PkScript: connectorScript,
Value: int64(connectorAmount),
@@ -933,7 +1002,7 @@ func (b *txBuilder) createConnectors(
if numberOfConnectors == 1 {
outputs := []*wire.TxOut{connectorOutput}
connectorTx, err := craftConnectorTx(previousInput, connectorScript, outputs, minRelayFee)
connectorTx, err := craftConnectorTx(previousInput, connectorScript, outputs, feeAmount)
if err != nil {
return nil, err
}
@@ -941,23 +1010,23 @@ func (b *txBuilder) createConnectors(
return []*psbt.Packet{connectorTx}, nil
}
totalConnectorAmount := (connectorAmount + minRelayFee) * numberOfConnectors
totalConnectorAmount := (connectorAmount + feeAmount) * numberOfConnectors
if numberOfConnectors > 1 {
totalConnectorAmount -= minRelayFee
totalConnectorAmount -= feeAmount
}
connectors := make([]*psbt.Packet, 0, numberOfConnectors-1)
for i := uint64(0); i < numberOfConnectors-1; i++ {
outputs := []*wire.TxOut{connectorOutput}
totalConnectorAmount -= connectorAmount
totalConnectorAmount -= minRelayFee
totalConnectorAmount -= feeAmount
if totalConnectorAmount > 0 {
outputs = append(outputs, &wire.TxOut{
PkScript: connectorScript,
Value: int64(totalConnectorAmount),
})
}
connectorTx, err := craftConnectorTx(previousInput, connectorScript, outputs, minRelayFee)
connectorTx, err := craftConnectorTx(previousInput, connectorScript, outputs, feeAmount)
if err != nil {
return nil, err
}
@@ -973,9 +1042,52 @@ func (b *txBuilder) createConnectors(
return connectors, nil
}
func (b *txBuilder) minRelayFeeTreeTx() (uint64, error) {
return b.wallet.MinRelayFee(context.Background(), uint64(common.TreeTxSize))
}
func (b *txBuilder) minRelayFeeForfeitTx() (uint64, error) {
// rebuild the forfeit leaf in order to estimate the input witness size
randomKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
return 0, err
}
pubkey := randomKey.PubKey()
forfeitClosure := &bitcointree.MultisigClosure{
Pubkey: pubkey,
AspPubkey: pubkey,
}
leaf, err := forfeitClosure.Leaf()
if err != nil {
return 0, err
}
_, vtxoTaprootTree, err := b.getLeafScriptAndTree(pubkey, pubkey)
if err != nil {
return 0, err
}
merkleProofIndex := vtxoTaprootTree.LeafProofIndex[leaf.TapHash()]
merkleProof := vtxoTaprootTree.LeafMerkleProofs[merkleProofIndex]
controlBlock := merkleProof.ToControlBlock(bitcointree.UnspendableKey())
weightEstimator := &input.TxWeightEstimator{}
weightEstimator.AddP2WKHInput() // connector input
weightEstimator.AddTapscriptInput(64*2, &waddrmgr.Tapscript{
RevealedScript: merkleProof.Script,
ControlBlock: &controlBlock,
}) // forfeit input
weightEstimator.AddP2TROutput() // the asp output
return b.wallet.MinRelayFee(context.Background(), uint64(weightEstimator.VSize()))
}
func (b *txBuilder) createForfeitTxs(
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, connectors []*psbt.Packet, minRelayFee uint64,
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, connectors []*psbt.Packet, feeAmount uint64,
) ([]string, error) {
// TODO generate a fresh new address to receive the forfeited funds
aspScript, err := p2trScript(aspPubkey, b.onchainNetwork())
if err != nil {
return nil, err
@@ -1021,6 +1133,11 @@ func (b *txBuilder) createForfeitTxs(
return nil, err
}
connectorAmount, err := b.wallet.GetDustAmount(context.Background())
if err != nil {
return nil, err
}
for _, connector := range connectors {
txs, err := craftForfeitTxs(
connector, vtxo,
@@ -1029,7 +1146,7 @@ func (b *txBuilder) createForfeitTxs(
Script: forfeitProof.Script,
LeafVersion: forfeitProof.LeafVersion,
},
vtxoScript, aspScript, minRelayFee,
vtxoScript, aspScript, feeAmount, int64(connectorAmount),
)
if err != nil {
return nil, err