mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-18 12:44:19 +01:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user