Skip to content

Commit

Permalink
Draft Approach
Browse files Browse the repository at this point in the history
  • Loading branch information
ziggie1984 committed Feb 13, 2023
1 parent 9398358 commit 16ea904
Show file tree
Hide file tree
Showing 7 changed files with 786 additions and 17 deletions.
60 changes: 57 additions & 3 deletions cmd/sweepaccount/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/btcsuite/btcwallet/wallet/txsizes"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/jessevdk/go-flags"
)

Expand Down Expand Up @@ -187,6 +188,55 @@ func makeInputSource(outputs []btcjson.ListUnspentResult) txauthor.InputSource {
}
}

func fetchInputs(outputs []btcjson.ListUnspentResult) ([]wtxmgr.Credit, error) {
var (
totalInputValue btcutil.Amount
inputs = make([]wtxmgr.Credit, 0, len(outputs))
sourceErr error
)
for _, output := range outputs {
output := output

outputAmount, err := btcutil.NewAmount(output.Amount)
if err != nil {
sourceErr = fmt.Errorf(
"invalid amount `%v` in listunspent result",
output.Amount)
break
}
if outputAmount == 0 {
continue
}
if !saneOutputValue(outputAmount) {
sourceErr = fmt.Errorf(
"impossible output amount `%v` in listunspent result",
outputAmount)
break
}
totalInputValue += outputAmount

previousOutPoint, err := parseOutPoint(&output)
if err != nil {
sourceErr = fmt.Errorf(
"invalid data in listunspent result: %v",
err)
break
}

inputs = append(inputs, wtxmgr.Credit{
OutPoint: previousOutPoint,
Amount: outputAmount,
})
}

if sourceErr == nil && totalInputValue == 0 {
sourceErr = noInputValue{}
}

return inputs, sourceErr

}

// makeDestinationScriptSource creates a ChangeSource which is used to receive
// all correlated previous input value. A non-change address is created by this
// function.
Expand Down Expand Up @@ -277,10 +327,14 @@ func sweep() error {
numErrors++
}
for _, previousOutputs := range sourceOutputs {
inputSource := makeInputSource(previousOutputs)
// inputSource := makeInputSource(previousOutputs)
inputs, err := fetchInputs(previousOutputs)
if err != nil {
return err
}
destinationSource := makeDestinationScriptSource(rpcClient, opts.DestinationAccount)
tx, err := txauthor.NewUnsignedTransaction(nil, opts.FeeRate.Amount,
inputSource, destinationSource)
tx, err := txauthor.NewUnsignedTransaction2(nil, opts.FeeRate.Amount,
inputs, destinationSource)
if err != nil {
if err != (noInputValue{}) {
reportError("Failed to create unsigned transaction: %v", err)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ require (
)

go 1.16

replace github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 => /Users/ziggie-pro/working_freetime/btcwallet/wallet/txauthor
14 changes: 9 additions & 5 deletions wallet/createtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,16 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut,
return err
}

var inputSource txauthor.InputSource
// var inputSource txauthor.InputSource
// We need to define the Selection Strategy
var inputSelectionStrategy txauthor.InputSelectionStrategy = txauthor.ConstantSelection

switch coinSelectionStrategy {
// Pick largest outputs first.
case CoinSelectionLargest:
sort.Sort(sort.Reverse(byAmount(eligible)))
inputSource = makeInputSource(eligible)
// inputSource = makeInputSource(eligible)
inputSelectionStrategy = txauthor.PositiveYieldingSelection

// Select coins at random. This prevents the creation of ever
// smaller utxos over time that may never become economical to
Expand All @@ -170,12 +173,13 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut,
positivelyYielding[i], positivelyYielding[j] =
positivelyYielding[j], positivelyYielding[i]
})
inputSelectionStrategy = txauthor.RandomSelection

inputSource = makeInputSource(positivelyYielding)
// inputSource = makeInputSource(positivelyYielding)
}

tx, err = txauthor.NewUnsignedTransaction(
outputs, feeSatPerKb, inputSource, changeSource,
tx, err = txauthor.NewUnsignedTransaction2(
outputs, feeSatPerKb, eligible, inputSelectionStrategy, changeSource,
)
if err != nil {
return err
Expand Down
14 changes: 7 additions & 7 deletions wallet/psbt.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func (w *Wallet) FundPsbt(packet *psbt.Packet, keyScope *waddrmgr.KeyScope,
PkScript: utxo.PkScript,
}
}
inputSource := constantInputSource(credits)
// inputSource := constantInputSource(credits)

// Build the TxCreateOption to retrieve the change scope.
opts := defaultTxCreateOptions()
Expand All @@ -197,25 +197,25 @@ func (w *Wallet) FundPsbt(packet *psbt.Packet, keyScope *waddrmgr.KeyScope,
dbtx, opts.changeKeyScope, account,
)
if err != nil {
return err
return fmt.Errorf("could not add change address to "+
"database: %v", err)
}

// Ask the txauthor to create a transaction with our
// selected coins. This will perform fee estimation and
// add a change output if necessary.
tx, err = txauthor.NewUnsignedTransaction(
txOut, feeSatPerKB, inputSource, changeSource,
tx, err = txauthor.NewUnsignedTransaction2(
txOut, feeSatPerKB, credits, txauthor.ConstantSelection, changeSource,
)
if err != nil {
return fmt.Errorf("fee estimation not "+
"successful: %v", err)
"successful: %w", err)
}

return nil
})
if err != nil {
return 0, fmt.Errorf("could not add change address to "+
"database: %v", err)
return 0, err
}
}

Expand Down
122 changes: 120 additions & 2 deletions wallet/txauthor/author.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/btcsuite/btcwallet/wallet/txsizes"
"github.com/btcsuite/btcwallet/wtxmgr"
)

// SumOutputValues sums up the list of TxOuts and returns an Amount.
Expand Down Expand Up @@ -43,9 +44,12 @@ type InputSourceError interface {
}

// Default implementation of InputSourceError.
type insufficientFundsError struct{}
type insufficientFundsError struct {
}

func (insufficientFundsError) InputSourceError() {

func (insufficientFundsError) InputSourceError() {}
}
func (insufficientFundsError) Error() string {
return "insufficient funds available to construct transaction"
}
Expand Down Expand Up @@ -168,6 +172,120 @@ func NewUnsignedTransaction(outputs []*wire.TxOut, feeRatePerKb btcutil.Amount,
}
}

// NewUnsignedTransaction2 is an experimental function to solve the fee-calculation problem which is appareant
// in NewUnsignedTransaction.
func NewUnsignedTransaction2(outputs []*wire.TxOut, feeRatePerKb btcutil.Amount,
inputs []wtxmgr.Credit, selectionStrategy InputSelectionStrategy, changeSource *ChangeSource) (*AuthoredTx, error) {

changeScript, err := changeSource.NewScript()
if err != nil {
return nil, err
}

targetAmount := SumOutputValues(outputs)

inputSelection := txSelector{
inputState: &inputState{
feeRatePerKb: feeRatePerKb,
targetAmount: targetAmount,
outputs: outputs,
selectionStrategy: selectionStrategy,
changeOutpoint: wire.TxOut{
PkScript: changeScript,
},
},
}

switch selectionStrategy {

case PositiveYieldingSelection, RandomSelection:

// We look through all our inputs and add an input until
// the amount is enough to pay the target amount and the
// transaction fees.
for _, input := range inputs {
if !inputSelection.inputState.enoughInput() {

// We try to add a new input but we only consider
// positive yielding inputs. Therefore when the input
// is not added we check if the inputs where ordered
// according to their size, which is the case when the
// inputselection strategy is positive yielding. Then we
// abort quickly because all follwing outputs will be
// negative yielding as well.
if !inputSelection.add(input) &&
selectionStrategy == PositiveYieldingSelection {

return nil, txSelectionError{
targetAmount: inputSelection.inputState.targetAmount,
txFee: inputSelection.inputState.txFee,
availableAmt: inputSelection.inputState.inputTotal,
}
}

continue
}

// We stop obviously considering inputs when the input amount
// is enough to fund the transaction.
break
}

case ConstantSelection:
// In case of a constant selection all inputs are added
// although they might be negative yielding so we do not
// check for the return value.
inputSelection.add(inputs...)
}

// This check is needed to make sure we have enough inputs
// after going trough all eligable inputs.
if !inputSelection.inputState.enoughInput() {
return nil, txSelectionError{
targetAmount: inputSelection.inputState.targetAmount,
txFee: inputSelection.inputState.txFee,
availableAmt: inputSelection.inputState.inputTotal,
}
}

// We need the inputs in the right format for the followup functions.
txIn := make([]*wire.TxIn, 0, len(inputSelection.inputState.inputs))
inputValues := make([]btcutil.Amount, 0, len(inputSelection.inputState.inputs))

for _, input := range inputSelection.inputState.inputs {
txIn = append(txIn, wire.NewTxIn(&input.OutPoint, nil, nil))
inputValues = append(inputValues, input.Amount)
}

unsignedTransaction := &wire.MsgTx{
Version: wire.TxVersion,
TxIn: txIn,
TxOut: outputs,
LockTime: 0,
}

// Default is no change output. We check if changeOutpoint in the
// input state is above dust, and add the changeoutput to the
// transaction.
changeIndex := -1
if !txrules.IsDustOutput(&inputSelection.inputState.changeOutpoint,
txrules.DefaultRelayFeePerKb) {

l := len(outputs)
unsignedTransaction.TxOut = append(outputs[:l:l], &inputSelection.inputState.changeOutpoint)
changeIndex = l
}

return &AuthoredTx{
Tx: unsignedTransaction,
PrevScripts: inputSelection.inputState.scripts,
PrevInputValues: inputValues,
TotalInput: inputSelection.inputState.inputTotal,
ChangeIndex: changeIndex,
}, nil

}

// RandomizeOutputPosition randomizes the position of a transaction's output by
// swapping it with a random output. The new index is returned. This should be
// done before signing.
Expand Down
Loading

0 comments on commit 16ea904

Please sign in to comment.