Skip to content

Commit

Permalink
preliminary commit: allow to sweep all transactions.
Browse files Browse the repository at this point in the history
  • Loading branch information
ziggie1984 committed Jan 16, 2024
1 parent cda34bf commit 852ecc4
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 80 deletions.
22 changes: 18 additions & 4 deletions btc/explorer_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ type AddressStats struct {
MempoolStats *Stats `json:"mempool_stats"`
}

type Transactions struct {
Tx *TX
TxIndex int
}

func (a *ExplorerAPI) Transaction(txid string) (*TX, error) {
tx := &TX{}
err := fetchJSON(fmt.Sprintf("%s/tx/%s", a.BaseURL, txid), tx)
Expand All @@ -88,23 +93,32 @@ func (a *ExplorerAPI) Transaction(txid string) (*TX, error) {
return tx, nil
}

func (a *ExplorerAPI) Outpoint(addr string) (*TX, int, error) {
func (a *ExplorerAPI) Outpoints(addr string) ([]Transactions, error) {
var txs []*TX
var transactions []Transactions
err := fetchJSON(
fmt.Sprintf("%s/address/%s/txs", a.BaseURL, addr), &txs,
)
if err != nil {
return nil, 0, err
return nil, err
}
for _, tx := range txs {
for idx, vout := range tx.Vout {
if vout.ScriptPubkeyAddr == addr {
return tx, idx, nil
transactions = append(
transactions, Transactions{
Tx: tx,
TxIndex: idx,
})
}
}
}

return nil, 0, fmt.Errorf("no tx found")
if len(transactions) != 0 {
return transactions, nil
}

return nil, fmt.Errorf("no tx found")
}

func (a *ExplorerAPI) Spends(addr string) ([]*TX, error) {
Expand Down
5 changes: 4 additions & 1 deletion cmd/chantools/pullanchor.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,11 +320,14 @@ func addAnchorInputs(anchorAddrs []string, packet *psbt.Packet,
// Fetch the additional info we need for the anchor output as well.
results := make([]targetAnchor, len(anchorAddrs))
for idx, anchorAddr := range anchorAddrs {
anchorTx, anchorIndex, err := api.Outpoint(anchorAddr)
txs, err := api.Outpoints(anchorAddr)
if err != nil {
return nil, fmt.Errorf("error fetching anchor "+
"outpoint: %w", err)
}

anchorTx := txs[0].Tx
anchorIndex := txs[0].TxIndex
anchorTxHash, err := chainhash.NewHashFromStr(anchorTx.TXID)
if err != nil {
return nil, fmt.Errorf("error decoding anchor txid: %w",
Expand Down
161 changes: 86 additions & 75 deletions cmd/chantools/sweeptimelockmanual.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type sweepTimeLockManualCommand struct {
MaxNumChanUpdates uint64
StartNumChanUpdates uint64

Batch bool

ChannelBackup string
ChannelPoint string

Expand Down Expand Up @@ -148,6 +150,9 @@ chantools sweeptimelockmanual \
"specified in the --frombackup flag, "+
"format: txid:index",
)
cc.cmd.Flags().BoolVar(
&cc.Batch, "Batch", false, "batch sweeps",
)

cc.rootKey = newRootKey(cc.cmd, "deriving keys")
cc.inputs = newInputFlags(cc.cmd)
Expand Down Expand Up @@ -239,7 +244,7 @@ func (c *sweepTimeLockManualCommand) Execute(_ *cobra.Command, _ []string) error
return fmt.Errorf("error parsing multisigkey path: %w", err)
}
if len(multiSigKeyPath) != 5 {
return fmt.Errorf("invalid delay path '%v'", delayPath)
return fmt.Errorf("invalid delay path '%v'", multiSigKeyPath)
}
multiSigIdx = multiSigKeyPath[4]

Expand Down Expand Up @@ -285,8 +290,7 @@ func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,

// Create signer and transaction template.
var (
estimator input.TxWeightEstimator
signer = &lnd.Signer{
signer = &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
Expand Down Expand Up @@ -363,90 +367,97 @@ func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
// We now know everything we need to construct the sweep transaction,
// except for what outpoint to sweep. We'll ask the chain API to give
// us this information.
tx, txindex, err := api.Outpoint(timeLockAddr)
txs, err := api.Outpoints(timeLockAddr)
if err != nil {
return fmt.Errorf("error looking up lock address %s on chain: "+
"%v", timeLockAddr, err)
}

sweepTx := wire.NewMsgTx(2)
sweepValue := int64(tx.Vout[txindex].Value)
for _, transaction := range txs {
tx := transaction.Tx
txindex := transaction.TxIndex
sweepTx := wire.NewMsgTx(2)
sweepValue := int64(tx.Vout[txindex].Value)

// Create the transaction input.
txHash, err := chainhash.NewHashFromStr(tx.TXID)
if err != nil {
return fmt.Errorf("error parsing tx hash: %w", err)
}
sweepTx.TxIn = []*wire.TxIn{{
PreviousOutPoint: wire.OutPoint{
Hash: *txHash,
Index: uint32(txindex),
},
Sequence: input.LockTimeToSequence(
false, uint32(csvTimeout),
),
}}

// Calculate the fee based on the given fee rate and our weight
// estimation.
estimator.AddWitnessInput(input.ToLocalTimeoutWitnessSize)
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()
totalFee := feeRateKWeight.FeeForWeight(int64(estimator.Weight()))

// Add our sweep destination output.
sweepTx.TxOut = []*wire.TxOut{{
Value: sweepValue - int64(totalFee),
PkScript: sweepScript,
}}

log.Infof("Fee %d sats of %d total amount (estimated weight %d)",
totalFee, sweepValue, estimator.Weight())

// Create the sign descriptor for the input then sign the transaction.
prevOutFetcher := txscript.NewCannedPrevOutputFetcher(
scriptHash, sweepValue,
)
sigHashes := txscript.NewTxSigHashes(sweepTx, prevOutFetcher)
signDesc := &input.SignDescriptor{
KeyDesc: *delayDesc,
SingleTweak: input.SingleTweakBytes(
commitPoint, delayDesc.PubKey,
),
WitnessScript: script,
Output: &wire.TxOut{
PkScript: scriptHash,
Value: sweepValue,
},
InputIndex: 0,
SigHashes: sigHashes,
PrevOutputFetcher: prevOutFetcher,
HashType: txscript.SigHashAll,
}
witness, err := input.CommitSpendTimeout(signer, signDesc, sweepTx)
if err != nil {
return err
}
sweepTx.TxIn[0].Witness = witness

var buf bytes.Buffer
err = sweepTx.Serialize(&buf)
if err != nil {
return err
}

// Publish TX.
if publish {
response, err := api.PublishTx(
hex.EncodeToString(buf.Bytes()),
// Create the transaction input.
txHash, err := chainhash.NewHashFromStr(tx.TXID)
if err != nil {
return fmt.Errorf("error parsing tx hash: %w", err)
}
sweepTx.TxIn = []*wire.TxIn{{
PreviousOutPoint: wire.OutPoint{
Hash: *txHash,
Index: uint32(txindex),
},
Sequence: input.LockTimeToSequence(
false, uint32(csvTimeout),
),
}}

// Calculate the fee based on the given fee rate and our weight
// estimation.
estimator := input.TxWeightEstimator{}
estimator.AddWitnessInput(input.ToLocalTimeoutWitnessSize)
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()
totalFee := feeRateKWeight.FeeForWeight(int64(estimator.Weight()))

// Add our sweep destination output.
sweepTx.TxOut = []*wire.TxOut{{
Value: sweepValue - int64(totalFee),
PkScript: sweepScript,
}}

log.Infof("Fee %d sats of %d total amount (estimated weight %d)",
totalFee, sweepValue, estimator.Weight())

// Create the sign descriptor for the input then sign the transaction.
prevOutFetcher := txscript.NewCannedPrevOutputFetcher(
scriptHash, sweepValue,
)
sigHashes := txscript.NewTxSigHashes(sweepTx, prevOutFetcher)
signDesc := &input.SignDescriptor{
KeyDesc: *delayDesc,
SingleTweak: input.SingleTweakBytes(
commitPoint, delayDesc.PubKey,
),
WitnessScript: script,
Output: &wire.TxOut{
PkScript: scriptHash,
Value: sweepValue,
},
InputIndex: 0,
SigHashes: sigHashes,
PrevOutputFetcher: prevOutFetcher,
HashType: txscript.SigHashAll,
}
witness, err := input.CommitSpendTimeout(signer, signDesc, sweepTx)
if err != nil {
return err
}
sweepTx.TxIn[0].Witness = witness

var buf bytes.Buffer
err = sweepTx.Serialize(&buf)
if err != nil {
return err
}
log.Infof("Published TX %s, response: %s",
sweepTx.TxHash().String(), response)

// Publish TX.
if publish {
response, err := api.PublishTx(
hex.EncodeToString(buf.Bytes()),
)
if err != nil {
return err
}
log.Infof("Published TX %s, response: %s",
sweepTx.TxHash().String(), response)
}

log.Infof("Transaction: %x", buf.Bytes())

}

log.Infof("Transaction: %x", buf.Bytes())
return nil
}

Expand Down

0 comments on commit 852ecc4

Please sign in to comment.