Add support for Out Of Round txs (#359)

* [common] rework address encoding

* new address encoding

* replace offchain address by vtxo output key in DB

* merge migrations files into init one

* fix txbuilder fixtures

* fix transaction events

* OOR scheme

* fix conflicts

* [sdk] OOR

* update WASM wrappers

* revert renaming

* revert API changes

* update parser.go

* fix vtxosToTxsCovenantless

* add settled and spent in Utxo and Transaction

* Fixes (#5)

* Revert unneeded changes and rename claim to settle

* Revert changes to wasm and rename claim to settle

---------

Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com>
This commit is contained in:
Louis Singer
2024-10-24 17:43:27 +02:00
committed by GitHub
parent b536a9e652
commit bcb2b2075f
41 changed files with 1103 additions and 1390 deletions

View File

@@ -14,7 +14,6 @@ import (
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/pkg/client-sdk/client"
"github.com/ark-network/ark/pkg/client-sdk/explorer"
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
"github.com/ark-network/ark/pkg/client-sdk/redemption"
"github.com/ark-network/ark/pkg/client-sdk/types"
@@ -251,26 +250,6 @@ func (a *covenantArkClient) listenForBoardingUtxos(
//are multiple boarding inputs + spent vtxo with change in spendable + received in the same round
}
func (a *covenantArkClient) ListVtxos(
ctx context.Context,
) (spendableVtxos, spentVtxos []client.Vtxo, err error) {
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
if err != nil {
return
}
for _, addr := range offchainAddrs {
spendable, spent, err := a.client.ListVtxos(ctx, addr.Address)
if err != nil {
return nil, nil, err
}
spendableVtxos = append(spendableVtxos, spendable...)
spentVtxos = append(spentVtxos, spent...)
}
return
}
func (a *covenantArkClient) Balance(
ctx context.Context, computeVtxoExpiration bool,
) (*Balance, error) {
@@ -285,14 +264,13 @@ func (a *covenantArkClient) Balance(
chRes := make(chan balanceRes, nbWorkers*len(offchainAddrs))
for i := range offchainAddrs {
offchainAddr := offchainAddrs[i]
boardingAddr := boardingAddrs[i]
redeemAddr := redeemAddrs[i]
go func(addr string) {
go func() {
defer wg.Done()
balance, amountByExpiration, err := a.getOffchainBalance(
ctx, addr, computeVtxoExpiration,
ctx, computeVtxoExpiration,
)
if err != nil {
chRes <- balanceRes{err: err}
@@ -303,7 +281,7 @@ func (a *covenantArkClient) Balance(
offchainBalance: balance,
offchainBalanceByExpiration: amountByExpiration,
}
}(offchainAddr.Address)
}()
getDelayedBalance := func(addr string) {
defer wg.Done()
@@ -448,20 +426,11 @@ func (a *covenantArkClient) UnilateralRedeem(ctx context.Context) error {
return fmt.Errorf("wallet is locked")
}
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
vtxos, err := a.getVtxos(ctx, false, nil)
if err != nil {
return err
}
vtxos := make([]client.Vtxo, 0)
for _, offchainAddr := range offchainAddrs {
fetchedVtxos, _, err := a.client.ListVtxos(ctx, offchainAddr.Address)
if err != nil {
return err
}
vtxos = append(vtxos, fetchedVtxos...)
}
totalVtxosAmount := uint64(0)
for _, vtxo := range vtxos {
totalVtxosAmount += vtxo.Amount
@@ -519,22 +488,13 @@ func (a *covenantArkClient) CollaborativeRedeem(
return "", fmt.Errorf("wallet is locked")
}
// validate liquid address
if _, err := address.ToOutputScript(addr); err != nil {
return "", fmt.Errorf("invalid onchain address")
}
addrNet, err := address.NetworkForAddress(addr)
if err != nil {
return "", fmt.Errorf("invalid onchain address: unknown network")
}
net := utils.ToElementsNetwork(a.Network)
if net.Name != addrNet.Name {
return "", fmt.Errorf("invalid onchain address: must be for %s network", net.Name)
}
if isConf, _ := address.IsConfidential(addr); isConf {
info, _ := address.FromConfidential(addr)
addr = info.Address
if isConf, err := address.IsConfidential(addr); err != nil || isConf {
return "", fmt.Errorf("confidential onchain address not supported")
}
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
@@ -550,22 +510,34 @@ func (a *covenantArkClient) CollaborativeRedeem(
}
vtxos := make([]client.DescriptorVtxo, 0)
for _, offchainAddr := range offchainAddrs {
spendableVtxos, err := a.getVtxos(ctx, offchainAddr.Address, withExpiryCoinselect)
if err != nil {
return "", err
}
spendableVtxos, err := a.getVtxos(ctx, false, nil)
if err != nil {
return "", err
}
for _, vtxo := range spendableVtxos {
vtxos = append(vtxos, client.DescriptorVtxo{
Vtxo: vtxo,
Descriptor: offchainAddr.Descriptor,
})
for _, offchainAddr := range offchainAddrs {
for _, v := range spendableVtxos {
vtxoAddr, err := v.Address(a.AspPubkey, a.Network)
if err != nil {
return "", err
}
if vtxoAddr == offchainAddr.Address {
vtxos = append(vtxos, client.DescriptorVtxo{
Vtxo: v,
Descriptor: offchainAddr.Descriptor,
})
}
}
}
selectedCoins, changeAmount, err := utils.CoinSelect(
vtxos, amount, a.Dust, withExpiryCoinselect,
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx, nil)
if err != nil {
return "", err
}
selectedBoardingUtxos, selectedCoins, changeAmount, err := utils.CoinSelect(
boardingUtxos, vtxos, amount, a.Dust, withExpiryCoinselect,
)
if err != nil {
return "", err
@@ -583,7 +555,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
})
}
inputs := make([]client.Input, 0, len(selectedCoins))
inputs := make([]client.Input, 0, len(selectedCoins)+len(selectedBoardingUtxos))
for _, coin := range selectedCoins {
inputs = append(inputs, client.Input{
@@ -594,8 +566,17 @@ func (a *covenantArkClient) CollaborativeRedeem(
Descriptor: coin.Descriptor,
})
}
for _, coin := range selectedBoardingUtxos {
inputs = append(inputs, client.Input{
Outpoint: client.Outpoint{
Txid: coin.Txid,
VOut: coin.VOut,
},
Descriptor: coin.Descriptor,
})
}
paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "") // ephemeralPublicKey is not required for covenant
paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "")
if err != nil {
return "", err
}
@@ -604,9 +585,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
return "", err
}
poolTxID, err := a.handleRoundStream(
ctx, paymentID, selectedCoins, nil, "", receivers,
)
poolTxID, err := a.handleRoundStream(ctx, paymentID, selectedCoins, selectedBoardingUtxos, receivers)
if err != nil {
return "", err
}
@@ -621,36 +600,10 @@ func (a *covenantArkClient) SendAsync(
return "", fmt.Errorf("not implemented")
}
func (a *covenantArkClient) Claim(ctx context.Context) (string, error) {
myselfOffchain, boardingAddr, err := a.wallet.NewAddress(ctx, false)
if err != nil {
return "", err
}
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx)
if err != nil {
return "", err
}
var pendingBalance uint64
for _, vtxo := range boardingUtxos {
pendingBalance += vtxo.Amount
}
if pendingBalance == 0 {
return "", fmt.Errorf("no funds to claim")
}
receiver := client.Output{
Address: myselfOffchain.Address,
Amount: pendingBalance,
}
return a.selfTransferAllPendingPayments(
ctx,
boardingUtxos,
receiver,
boardingAddr.Descriptor,
)
func (a *covenantArkClient) Settle(
ctx context.Context,
) (string, error) {
return a.sendOffchain(ctx, false, nil)
}
func (a *covenantArkClient) GetTransactionHistory(
@@ -675,13 +628,13 @@ func (a *covenantArkClient) GetTransactionHistory(
return vtxosToTxsCovenant(config.RoundLifetime, spendableVtxos, spentVtxos, boardingTxs)
}
func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]types.Utxo, error) {
_, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
if err != nil {
return nil, err
}
utxos := []explorer.Utxo{}
utxos := make([]types.Utxo, 0)
for _, addr := range boardingAddrs {
txs, err := a.explorer.GetTxs(addr.Address)
if err != nil {
@@ -694,11 +647,12 @@ func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]explorer
if tx.Status.Confirmed {
createdAt = time.Unix(tx.Status.Blocktime, 0)
}
utxos = append(utxos, explorer.Utxo{
Txid: tx.Txid,
Vout: uint32(i),
Amount: vout.Amount,
CreatedAt: createdAt,
utxos = append(utxos, types.Utxo{
Txid: tx.Txid,
VOut: uint32(i),
Amount: vout.Amount,
CreatedAt: createdAt,
Descriptor: addr.Descriptor,
})
}
}
@@ -708,13 +662,13 @@ func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]explorer
return utxos, nil
}
func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context, opts *CoinSelectOptions) ([]types.Utxo, error) {
_, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
if err != nil {
return nil, err
}
claimable := make([]explorer.Utxo, 0)
claimable := make([]types.Utxo, 0)
for _, addr := range boardingAddrs {
boardingScript, err := tree.ParseVtxoScript(addr.Descriptor)
@@ -738,8 +692,25 @@ func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]ex
now := time.Now()
for _, utxo := range boardingUtxos {
u := utxo.ToUtxo(boardingTimeout)
if opts != nil && len(opts.OutpointsFilter) > 0 {
utxoOutpoint := client.Outpoint{
Txid: utxo.Txid,
VOut: utxo.Vout,
}
found := false
for _, outpoint := range opts.OutpointsFilter {
if outpoint.Equals(utxoOutpoint) {
found = true
break
}
}
if !found {
continue
}
}
u := utxo.ToUtxo(boardingTimeout, addr.Descriptor)
if u.SpendableAt.Before(now) {
continue
}
@@ -906,84 +877,123 @@ func (a *covenantArkClient) sendOnchain(
}
func (a *covenantArkClient) sendOffchain(
ctx context.Context, withExpiryCoinselect bool, receivers []Receiver,
ctx context.Context,
withExpiryCoinselect bool, receivers []Receiver,
) (string, error) {
if a.wallet.IsLocked() {
return "", fmt.Errorf("wallet is locked")
}
expectedAspPubKey := schnorr.SerializePubKey(a.AspPubkey)
outputs := make([]client.Output, 0)
sumOfReceivers := uint64(0)
// validate receivers and create outputs
for _, receiver := range receivers {
rcvAddr, err := common.DecodeAddress(receiver.To())
if err != nil {
return "", fmt.Errorf("invalid receiver address: %s", err)
}
rcvAspPubKey := schnorr.SerializePubKey(rcvAddr.Asp)
if !bytes.Equal(expectedAspPubKey, rcvAspPubKey) {
return "", fmt.Errorf("invalid receiver address '%s': expected ASP %s, got %s", receiver.To(), hex.EncodeToString(expectedAspPubKey), hex.EncodeToString(rcvAspPubKey))
}
if receiver.Amount() < a.Dust {
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust)
}
outputs = append(outputs, client.Output{
Address: receiver.To(),
Amount: receiver.Amount(),
})
sumOfReceivers += receiver.Amount()
}
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
if err != nil {
return "", err
}
if len(offchainAddrs) <= 0 {
return "", fmt.Errorf("no funds detected")
}
expectedAspPubkey := schnorr.SerializePubKey(a.AspPubkey)
receiversOutput := make([]client.Output, 0)
sumOfReceivers := uint64(0)
for _, receiver := range receivers {
rcvAddr, err := common.DecodeAddress(receiver.To())
if err != nil {
return "", fmt.Errorf("invalid receiver address: %s", err)
}
rcvAspPubkey := schnorr.SerializePubKey(rcvAddr.Asp)
if !bytes.Equal(rcvAspPubkey, expectedAspPubkey) {
return "", fmt.Errorf("invalid receiver address '%s': expected ASP %s, got %s", receiver.To(), hex.EncodeToString(expectedAspPubkey), hex.EncodeToString(rcvAspPubkey))
}
if receiver.Amount() < a.Dust {
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust)
}
receiversOutput = append(receiversOutput, client.Output{
Address: receiver.To(),
Amount: receiver.Amount(),
})
sumOfReceivers += receiver.Amount()
return "", fmt.Errorf("no offchain addresses found")
}
vtxos := make([]client.DescriptorVtxo, 0)
for _, offchainAddr := range offchainAddrs {
spendableVtxos, err := a.getVtxos(ctx, offchainAddr.Address, withExpiryCoinselect)
if err != nil {
return "", err
}
for _, vtxo := range spendableVtxos {
vtxos = append(vtxos, client.DescriptorVtxo{
Vtxo: vtxo,
Descriptor: offchainAddr.Descriptor,
})
spendableVtxos, err := a.getVtxos(ctx, withExpiryCoinselect, nil)
if err != nil {
return "", err
}
for _, offchainAddr := range offchainAddrs {
for _, v := range spendableVtxos {
vtxoAddr, err := v.Address(a.AspPubkey, a.Network)
if err != nil {
return "", err
}
if vtxoAddr == offchainAddr.Address {
vtxos = append(vtxos, client.DescriptorVtxo{
Vtxo: v,
Descriptor: offchainAddr.Descriptor,
})
}
}
}
selectedCoins, changeAmount, err := utils.CoinSelect(
vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect,
)
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx, nil)
if err != nil {
return "", err
}
var selectedBoardingCoins []types.Utxo
var selectedCoins []client.DescriptorVtxo
var changeAmount uint64
// if no receivers, self send all selected coins
if len(outputs) <= 0 {
selectedBoardingCoins = boardingUtxos
selectedCoins = vtxos
amount := uint64(0)
for _, utxo := range boardingUtxos {
amount += utxo.Amount
}
for _, utxo := range vtxos {
amount += utxo.Amount
}
outputs = append(outputs, client.Output{
Address: offchainAddrs[0].Address,
Amount: amount,
})
changeAmount = 0
} else {
selectedBoardingCoins, selectedCoins, changeAmount, err = utils.CoinSelect(
boardingUtxos, vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect,
)
if err != nil {
return "", err
}
}
if changeAmount > 0 {
offchainAddr, _, err := a.wallet.NewAddress(ctx, true)
if err != nil {
return "", err
}
changeReceiver := client.Output{
outputs = append(outputs, client.Output{
Address: offchainAddr.Address,
Amount: changeAmount,
}
receiversOutput = append(receiversOutput, changeReceiver)
})
}
inputs := make([]client.Input, 0, len(selectedCoins))
inputs := make([]client.Input, 0, len(selectedCoins)+len(selectedBoardingCoins))
for _, coin := range selectedCoins {
inputs = append(inputs, client.Input{
Outpoint: client.Outpoint{
@@ -993,16 +1003,23 @@ func (a *covenantArkClient) sendOffchain(
Descriptor: coin.Descriptor,
})
}
for _, coin := range selectedBoardingCoins {
inputs = append(inputs, client.Input{
Outpoint: client.Outpoint{
Txid: coin.Txid,
VOut: coin.VOut,
},
Descriptor: coin.Descriptor,
})
}
paymentID, err := a.client.RegisterInputsForNextRound(
ctx, inputs, "", // ephemeralPublicKey is not required for covenant
)
paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "")
if err != nil {
return "", err
}
if err := a.client.RegisterOutputsForNextRound(
ctx, paymentID, receiversOutput,
ctx, paymentID, outputs,
); err != nil {
return "", err
}
@@ -1010,7 +1027,7 @@ func (a *covenantArkClient) sendOffchain(
log.Infof("payment registered with id: %s", paymentID)
poolTxID, err := a.handleRoundStream(
ctx, paymentID, selectedCoins, nil, "", receiversOutput,
ctx, paymentID, selectedCoins, boardingUtxos, outputs,
)
if err != nil {
return "", err
@@ -1023,7 +1040,7 @@ func (a *covenantArkClient) sendOffchain(
func (a *covenantArkClient) addInputs(
ctx context.Context,
updater *psetv2.Updater,
utxos []explorer.Utxo,
utxos []types.Utxo,
) error {
// TODO works only with single-key wallet
offchain, _, err := a.wallet.NewAddress(ctx, false)
@@ -1055,7 +1072,7 @@ func (a *covenantArkClient) addInputs(
if err := updater.AddInputs([]psetv2.InputArgs{
{
Txid: utxo.Txid,
TxIndex: utxo.Vout,
TxIndex: utxo.VOut,
Sequence: sequence,
},
}); err != nil {
@@ -1113,8 +1130,7 @@ func (a *covenantArkClient) handleRoundStream(
ctx context.Context,
paymentID string,
vtxosToSign []client.DescriptorVtxo,
boardingUtxos []explorer.Utxo,
boardingDescriptor string,
boardingUtxos []types.Utxo,
receivers []client.Output,
) (string, error) {
eventsCh, close, err := a.client.GetEventStream(ctx, paymentID)
@@ -1151,7 +1167,7 @@ func (a *covenantArkClient) handleRoundStream(
log.Info("a round finalization started")
signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization(
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, boardingDescriptor, receivers,
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, receivers,
)
if err != nil {
return "", err
@@ -1178,8 +1194,7 @@ func (a *covenantArkClient) handleRoundFinalization(
ctx context.Context,
event client.RoundFinalizationEvent,
vtxos []client.DescriptorVtxo,
boardingUtxos []explorer.Utxo,
boardingDescriptor string,
boardingUtxos []types.Utxo,
receivers []client.Output,
) (signedForfeits []string, signedRoundTx string, err error) {
if err = a.validateCongestionTree(event, receivers); err != nil {
@@ -1193,13 +1208,23 @@ func (a *covenantArkClient) handleRoundFinalization(
}
}
if len(boardingUtxos) > 0 {
boardingVtxoScript, err := tree.ParseVtxoScript(boardingDescriptor)
if err != nil {
return nil, "", err
}
// if no boarding utxos inputs, we don't need to sign the round transaction
if len(boardingUtxos) <= 0 {
return
}
roundPtx, err := psetv2.NewPsetFromBase64(event.Tx)
roundPtx, err := psetv2.NewPsetFromBase64(event.Tx)
if err != nil {
return nil, "", err
}
updater, err := psetv2.NewUpdater(roundPtx)
if err != nil {
return nil, "", err
}
for _, boardingUtxo := range boardingUtxos {
boardingVtxoScript, err := tree.ParseVtxoScript(boardingUtxo.Descriptor)
if err != nil {
return nil, "", err
}
@@ -1213,7 +1238,7 @@ func (a *covenantArkClient) handleRoundFinalization(
AspPubkey: a.AspPubkey,
}
default:
return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingDescriptor)
return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingUtxo.Descriptor)
}
forfeitLeaf, err := forfeitClosure.Leaf()
@@ -1241,31 +1266,25 @@ func (a *covenantArkClient) handleRoundFinalization(
ControlBlock: *ctrlBlock,
}
updater, err := psetv2.NewUpdater(roundPtx)
if err != nil {
return nil, "", err
}
for i, input := range updater.Pset.Inputs {
for _, boardingUtxo := range boardingUtxos {
if chainhash.Hash(input.PreviousTxid).String() == boardingUtxo.Txid && boardingUtxo.Vout == input.PreviousTxIndex {
if err := updater.AddInTapLeafScript(i, tapscript); err != nil {
return nil, "", err
}
break
if chainhash.Hash(input.PreviousTxid).String() == boardingUtxo.Txid && boardingUtxo.VOut == input.PreviousTxIndex {
if err := updater.AddInTapLeafScript(i, tapscript); err != nil {
return nil, "", err
}
break
}
}
b64, err := updater.Pset.ToBase64()
if err != nil {
return nil, "", err
}
}
signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, b64)
if err != nil {
return nil, "", err
}
b64, err := updater.Pset.ToBase64()
if err != nil {
return nil, "", err
}
signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, b64)
if err != nil {
return nil, "", err
}
return signedForfeits, signedRoundTx, nil
@@ -1518,8 +1537,8 @@ func (a *covenantArkClient) createAndSignForfeits(
}
func (a *covenantArkClient) coinSelectOnchain(
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
) ([]explorer.Utxo, uint64, error) {
ctx context.Context, targetAmount uint64, exclude []types.Utxo,
) ([]types.Utxo, uint64, error) {
_, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx)
if err != nil {
return nil, 0, err
@@ -1527,7 +1546,7 @@ func (a *covenantArkClient) coinSelectOnchain(
now := time.Now()
fetchedUtxos := make([]explorer.Utxo, 0)
fetchedUtxos := make([]types.Utxo, 0)
for _, addr := range boardingAddrs {
boardingDescriptor := addr.Descriptor
@@ -1550,14 +1569,14 @@ func (a *covenantArkClient) coinSelectOnchain(
}
for _, utxo := range utxos {
u := utxo.ToUtxo(boardingTimeout)
u := utxo.ToUtxo(boardingTimeout, addr.Descriptor)
if u.SpendableAt.Before(now) {
fetchedUtxos = append(fetchedUtxos, u)
}
}
}
selected := make([]explorer.Utxo, 0)
selected := make([]types.Utxo, 0)
selectedAmount := uint64(0)
for _, utxo := range fetchedUtxos {
if selectedAmount >= targetAmount {
@@ -1565,7 +1584,7 @@ func (a *covenantArkClient) coinSelectOnchain(
}
for _, excluded := range exclude {
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
if utxo.Txid == excluded.Txid && utxo.VOut == excluded.VOut {
continue
}
}
@@ -1578,7 +1597,7 @@ func (a *covenantArkClient) coinSelectOnchain(
return selected, selectedAmount - targetAmount, nil
}
fetchedUtxos = make([]explorer.Utxo, 0)
fetchedUtxos = make([]types.Utxo, 0)
for _, addr := range redemptionAddrs {
utxos, err := a.explorer.GetUtxos(addr.Address)
if err != nil {
@@ -1586,7 +1605,7 @@ func (a *covenantArkClient) coinSelectOnchain(
}
for _, utxo := range utxos {
u := utxo.ToUtxo(uint(a.UnilateralExitDelay))
u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Descriptor)
if u.SpendableAt.Before(now) {
fetchedUtxos = append(fetchedUtxos, u)
}
@@ -1599,7 +1618,7 @@ func (a *covenantArkClient) coinSelectOnchain(
}
for _, excluded := range exclude {
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
if utxo.Txid == excluded.Txid && utxo.VOut == excluded.VOut {
continue
}
}
@@ -1648,11 +1667,11 @@ func (a *covenantArkClient) getRedeemBranches(
}
func (a *covenantArkClient) getOffchainBalance(
ctx context.Context, addr string, computeVtxoExpiration bool,
ctx context.Context, computeVtxoExpiration bool,
) (uint64, map[int64]uint64, error) {
amountByExpiration := make(map[int64]uint64, 0)
vtxos, err := a.getVtxos(ctx, addr, computeVtxoExpiration)
vtxos, err := a.getVtxos(ctx, computeVtxoExpiration, nil)
if err != nil {
return 0, nil, err
}
@@ -1675,18 +1694,24 @@ func (a *covenantArkClient) getOffchainBalance(
}
func (a *covenantArkClient) getVtxos(
ctx context.Context, addr string, computeVtxoExpiration bool,
ctx context.Context,
withExpiryCoinselect bool, opts *CoinSelectOptions,
) ([]client.Vtxo, error) {
vtxos, _, err := a.client.ListVtxos(ctx, addr)
spendableVtxos, _, err := a.ListVtxos(ctx)
if err != nil {
return nil, err
}
if !computeVtxoExpiration {
return vtxos, nil
if opts != nil && len(opts.OutpointsFilter) > 0 {
spendableVtxos = filterByOutpoints(spendableVtxos, opts.OutpointsFilter)
}
redeemBranches, err := a.getRedeemBranches(ctx, vtxos)
if opts == nil || !opts.WithExpirySorting {
return spendableVtxos, nil
}
// if sorting by expiry is required, we need to get the expiration date of each vtxo
redeemBranches, err := a.getRedeemBranches(ctx, spendableVtxos)
if err != nil {
return nil, err
}
@@ -1697,75 +1722,24 @@ func (a *covenantArkClient) getVtxos(
return nil, err
}
for i, vtxo := range vtxos {
for i, vtxo := range spendableVtxos {
if vtxo.Txid == vtxoTxid {
vtxos[i].ExpiresAt = expiration
spendableVtxos[i].ExpiresAt = expiration
break
}
}
}
return vtxos, nil
}
func (a *covenantArkClient) selfTransferAllPendingPayments(
ctx context.Context, boardingUtxos []explorer.Utxo, myself client.Output, boardingDescriptor string,
) (string, error) {
inputs := make([]client.Input, 0, len(boardingUtxos))
for _, utxo := range boardingUtxos {
inputs = append(inputs, client.Input{
Outpoint: client.Outpoint{
Txid: utxo.Txid,
VOut: utxo.Vout,
},
Descriptor: boardingDescriptor,
})
}
outputs := []client.Output{myself}
paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "") // ephemeralPublicKey is not required for covenant
if err != nil {
return "", err
}
if err := a.client.RegisterOutputsForNextRound(ctx, paymentID, outputs); err != nil {
return "", err
}
roundTxid, err := a.handleRoundStream(
ctx, paymentID, make([]client.DescriptorVtxo, 0), boardingUtxos, boardingDescriptor, outputs,
)
if err != nil {
return "", err
}
return roundTxid, nil
return spendableVtxos, nil
}
func (a *covenantArkClient) getBoardingTxs(ctx context.Context) (transactions []types.Transaction) {
utxos, err := a.getClaimableBoardingUtxos(ctx)
if err != nil {
return nil
}
isPending := make(map[string]bool)
for _, u := range utxos {
isPending[u.Txid] = true
}
allUtxos, err := a.getAllBoardingUtxos(ctx)
if err != nil {
return nil
}
for _, u := range allUtxos {
pending := false
if isPending[u.Txid] {
pending = true
}
transactions = append(transactions, types.Transaction{
TransactionKey: types.TransactionKey{
BoardingTxid: u.Txid,
@@ -1773,7 +1747,6 @@ func (a *covenantArkClient) getBoardingTxs(ctx context.Context) (transactions []
Amount: u.Amount,
Type: types.TxReceived,
CreatedAt: u.CreatedAt,
IsPending: pending,
})
}
return
@@ -1796,9 +1769,7 @@ func vtxosToTxsCovenant(
for _, v := range append(spendable, spent...) {
// get vtxo amount
amount := int(v.Amount)
if !v.Pending {
continue
}
// find other spent vtxos that spent this one
relatedVtxos := findVtxosBySpentBy(spent, v.Txid)
for _, r := range relatedVtxos {