mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 12:14:21 +01:00
Use connectors utxos from swept rounds (#167)
* use connectors utxos from swept rounds * revert docker-compose.regtest.yml * add gitignore * fix integration tests
This commit is contained in:
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/build/
|
/build/
|
||||||
|
.vscode/
|
||||||
@@ -38,6 +38,9 @@ func getVtxos(
|
|||||||
t := time.Unix(v.ExpireAt, 0)
|
t := time.Unix(v.ExpireAt, 0)
|
||||||
expireAt = &t
|
expireAt = &t
|
||||||
}
|
}
|
||||||
|
if v.Swept {
|
||||||
|
continue
|
||||||
|
}
|
||||||
vtxos = append(vtxos, vtxo{
|
vtxos = append(vtxos, vtxo{
|
||||||
amount: v.Receiver.Amount,
|
amount: v.Receiver.Amount,
|
||||||
txid: v.Outpoint.Txid,
|
txid: v.Outpoint.Txid,
|
||||||
|
|||||||
1
common/.gitignore
vendored
Normal file
1
common/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.vscode/
|
||||||
@@ -29,6 +29,7 @@ services:
|
|||||||
- ARK_ROUND_INTERVAL=10
|
- ARK_ROUND_INTERVAL=10
|
||||||
- ARK_NETWORK=regtest
|
- ARK_NETWORK=regtest
|
||||||
- ARK_LOG_LEVEL=5
|
- ARK_LOG_LEVEL=5
|
||||||
|
- ARK_ROUND_LIFETIME=512
|
||||||
ports:
|
ports:
|
||||||
- "6000:6000"
|
- "6000:6000"
|
||||||
|
|
||||||
|
|||||||
@@ -382,7 +382,14 @@ func (s *service) startFinalization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, s.minRelayFee)
|
sweptRounds, err := s.repoManager.Rounds().GetSweptRounds(ctx)
|
||||||
|
if err != nil {
|
||||||
|
changes = round.Fail(fmt.Errorf("failed to retrieve swept rounds: %s", err))
|
||||||
|
log.WithError(err).Warn("failed to retrieve swept rounds")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, s.minRelayFee, sweptRounds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
changes = round.Fail(fmt.Errorf("failed to create pool tx: %s", err))
|
changes = round.Fail(fmt.Errorf("failed to create pool tx: %s", err))
|
||||||
log.WithError(err).Warn("failed to create pool tx")
|
log.WithError(err).Warn("failed to create pool tx")
|
||||||
@@ -619,7 +626,12 @@ func (s *service) listenToScannerNotifications() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
signedForfeitTx, err := s.wallet.SignConnectorInput(ctx, forfeitTx, []int{0}, false)
|
if err := s.wallet.LockConnectorUtxos(ctx, []ports.TxOutpoint{txOutpoint{connectorTxid, connectorVout}}); err != nil {
|
||||||
|
log.WithError(err).Warn("failed to lock connector utxos")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
signedForfeitTx, err := s.wallet.SignPset(ctx, forfeitTx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Warn("failed to sign connector input in forfeit tx")
|
log.WithError(err).Warn("failed to sign connector input in forfeit tx")
|
||||||
continue
|
continue
|
||||||
@@ -697,8 +709,14 @@ func (s *service) getNextConnector(
|
|||||||
|
|
||||||
for _, i := range pset.Inputs {
|
for _, i := range pset.Inputs {
|
||||||
if chainhash.Hash(i.PreviousTxid).String() == u.GetTxid() && i.PreviousTxIndex == u.GetIndex() {
|
if chainhash.Hash(i.PreviousTxid).String() == u.GetTxid() && i.PreviousTxIndex == u.GetIndex() {
|
||||||
|
connectorOutpoint := newOutpointFromPsetInput(pset.Inputs[0])
|
||||||
|
|
||||||
|
if err := s.wallet.LockConnectorUtxos(ctx, []ports.TxOutpoint{connectorOutpoint}); err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
|
||||||
// sign & broadcast the connector tx
|
// sign & broadcast the connector tx
|
||||||
signedConnectorTx, err := s.wallet.SignConnectorInput(ctx, b64, []int{0}, true)
|
signedConnectorTx, err := s.wallet.SignPset(ctx, b64, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, err
|
return "", 0, err
|
||||||
}
|
}
|
||||||
@@ -1015,3 +1033,23 @@ func findForfeitTx(
|
|||||||
|
|
||||||
return "", fmt.Errorf("forfeit tx not found")
|
return "", fmt.Errorf("forfeit tx not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type txOutpoint struct {
|
||||||
|
txid string
|
||||||
|
vout uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOutpointFromPsetInput(input psetv2.Input) txOutpoint {
|
||||||
|
return txOutpoint{
|
||||||
|
txid: chainhash.Hash(input.PreviousTxid).String(),
|
||||||
|
vout: input.PreviousTxIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (outpoint txOutpoint) GetTxid() string {
|
||||||
|
return outpoint.txid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (outpoint txOutpoint) GetIndex() uint32 {
|
||||||
|
return outpoint.vout
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package application
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
@@ -227,7 +228,7 @@ func (s *sweeper) createTask(
|
|||||||
err = nil
|
err = nil
|
||||||
txid := ""
|
txid := ""
|
||||||
// retry until the tx is broadcasted or the error is not BIP68 final
|
// retry until the tx is broadcasted or the error is not BIP68 final
|
||||||
for len(txid) == 0 && (err == nil || err == fmt.Errorf("non-BIP68-final")) {
|
for len(txid) == 0 && (err == nil || strings.Contains(err.Error(), "non-BIP68-final")) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugln("sweep tx not BIP68 final, retrying in 5 seconds")
|
log.Debugln("sweep tx not BIP68 final, retrying in 5 seconds")
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type RoundRepository interface {
|
|||||||
GetRoundWithId(ctx context.Context, id string) (*Round, error)
|
GetRoundWithId(ctx context.Context, id string) (*Round, error)
|
||||||
GetRoundWithTxid(ctx context.Context, txid string) (*Round, error)
|
GetRoundWithTxid(ctx context.Context, txid string) (*Round, error)
|
||||||
GetSweepableRounds(ctx context.Context) ([]Round, error)
|
GetSweepableRounds(ctx context.Context) ([]Round, error)
|
||||||
|
GetSweptRounds(ctx context.Context) ([]Round, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type VtxoRepository interface {
|
type VtxoRepository interface {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type SweepInput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TxBuilder interface {
|
type TxBuilder interface {
|
||||||
BuildPoolTx(aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error)
|
BuildPoolTx(aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64, sweptRounds []domain.Round) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error)
|
||||||
BuildForfeitTxs(aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment, minRelayFee uint64) (connectors []string, forfeitTxs []string, err error)
|
BuildForfeitTxs(aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment, minRelayFee uint64) (connectors []string, forfeitTxs []string, err error)
|
||||||
BuildSweepTx(inputs []SweepInput) (signedSweepTx string, err error)
|
BuildSweepTx(inputs []SweepInput) (signedSweepTx string, err error)
|
||||||
GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error)
|
GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error)
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ type WalletService interface {
|
|||||||
IsTransactionConfirmed(ctx context.Context, txid string) (isConfirmed bool, blocktime int64, err error)
|
IsTransactionConfirmed(ctx context.Context, txid string) (isConfirmed bool, blocktime int64, err error)
|
||||||
WaitForSync(ctx context.Context, txid string) error
|
WaitForSync(ctx context.Context, txid string) error
|
||||||
EstimateFees(ctx context.Context, pset string) (uint64, error)
|
EstimateFees(ctx context.Context, pset string) (uint64, error)
|
||||||
SignConnectorInput(ctx context.Context, pset string, inputIndexes []int, extract bool) (string, error)
|
|
||||||
ListConnectorUtxos(ctx context.Context, connectorAddress string) ([]TxInput, error)
|
ListConnectorUtxos(ctx context.Context, connectorAddress string) ([]TxInput, error)
|
||||||
|
LockConnectorUtxos(ctx context.Context, utxos []TxOutpoint) error
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,3 +39,8 @@ type TxInput interface {
|
|||||||
GetAsset() string
|
GetAsset() string
|
||||||
GetValue() uint64
|
GetValue() uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TxOutpoint interface {
|
||||||
|
GetTxid() string
|
||||||
|
GetIndex() uint32
|
||||||
|
}
|
||||||
|
|||||||
@@ -100,11 +100,13 @@ func (r *roundRepository) GetSweepableRounds(
|
|||||||
) ([]domain.Round, error) {
|
) ([]domain.Round, error) {
|
||||||
query := badgerhold.Where("Stage.Code").Eq(domain.FinalizationStage).
|
query := badgerhold.Where("Stage.Code").Eq(domain.FinalizationStage).
|
||||||
And("Stage.Ended").Eq(true).And("Swept").Eq(false)
|
And("Stage.Ended").Eq(true).And("Swept").Eq(false)
|
||||||
rounds, err := r.findRound(ctx, query)
|
return r.findRound(ctx, query)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return rounds, nil
|
|
||||||
|
func (r *roundRepository) GetSweptRounds(ctx context.Context) ([]domain.Round, error) {
|
||||||
|
query := badgerhold.Where("Stage.Code").Eq(domain.FinalizationStage).
|
||||||
|
And("Stage.Ended").Eq(true).And("Swept").Eq(true).And("ConnectorAddress").Ne("")
|
||||||
|
return r.findRound(ctx, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *roundRepository) Close() {
|
func (r *roundRepository) Close() {
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ func (s *service) SignPset(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) SelectUtxos(ctx context.Context, asset string, amount uint64) ([]ports.TxInput, uint64, error) {
|
func (s *service) SelectUtxos(ctx context.Context, asset string, amount uint64) ([]ports.TxInput, uint64, error) {
|
||||||
// TODO: select coins from the connector account IF the round is swept
|
|
||||||
res, err := s.txClient.SelectUtxos(ctx, &pb.SelectUtxosRequest{
|
res, err := s.txClient.SelectUtxos(ctx, &pb.SelectUtxosRequest{
|
||||||
AccountName: arkAccount,
|
AccountName: arkAccount,
|
||||||
TargetAsset: asset,
|
TargetAsset: asset,
|
||||||
@@ -82,24 +81,6 @@ func (s *service) SelectUtxos(ctx context.Context, asset string, amount uint64)
|
|||||||
return inputs, res.GetChange(), nil
|
return inputs, res.GetChange(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) getTransaction(
|
|
||||||
ctx context.Context, txid string,
|
|
||||||
) (string, bool, int64, error) {
|
|
||||||
res, err := s.txClient.GetTransaction(ctx, &pb.GetTransactionRequest{
|
|
||||||
Txid: txid,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", false, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.GetBlockDetails().GetTimestamp() > 0 {
|
|
||||||
return res.GetTxHex(), true, res.BlockDetails.GetTimestamp(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not confirmed, we return now + 1 min to estimate the next blocktime
|
|
||||||
return res.GetTxHex(), false, time.Now().Add(time.Minute).Unix(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *service) BroadcastTransaction(
|
func (s *service) BroadcastTransaction(
|
||||||
ctx context.Context, txHex string,
|
ctx context.Context, txHex string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
@@ -221,36 +202,20 @@ func (s *service) SignPsetWithKey(ctx context.Context, b64 string, indexes []int
|
|||||||
return signedPset.GetSignedTx(), nil
|
return signedPset.GetSignedTx(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) SignConnectorInput(ctx context.Context, pset string, inputIndexes []int, extract bool) (string, error) {
|
func (s *service) LockConnectorUtxos(ctx context.Context, utxos []ports.TxOutpoint) error {
|
||||||
decodedTx, err := psetv2.NewPsetFromBase64(pset)
|
pbUtxos := make([]*pb.Input, 0, len(utxos))
|
||||||
if err != nil {
|
for _, utxo := range utxos {
|
||||||
return "", err
|
pbUtxos = append(pbUtxos, &pb.Input{
|
||||||
}
|
Txid: utxo.GetTxid(),
|
||||||
|
Index: utxo.GetIndex(),
|
||||||
utxos := make([]*pb.Input, 0, len(decodedTx.Inputs))
|
|
||||||
|
|
||||||
for i := range inputIndexes {
|
|
||||||
if i >= len(decodedTx.Inputs) {
|
|
||||||
return "", fmt.Errorf("input index %d out of range", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
input := decodedTx.Inputs[i]
|
|
||||||
|
|
||||||
utxos = append(utxos, &pb.Input{
|
|
||||||
Txid: chainhash.Hash(input.PreviousTxid).String(),
|
|
||||||
Index: input.PreviousTxIndex,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.txClient.LockUtxos(ctx, &pb.LockUtxosRequest{
|
_, err := s.txClient.LockUtxos(ctx, &pb.LockUtxosRequest{
|
||||||
AccountName: connectorAccount,
|
AccountName: connectorAccount,
|
||||||
Utxos: utxos,
|
Utxos: pbUtxos,
|
||||||
})
|
})
|
||||||
if err != nil {
|
return err
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.SignPset(ctx, pset, extract)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) EstimateFees(
|
func (s *service) EstimateFees(
|
||||||
@@ -315,3 +280,21 @@ func (s *service) EstimateFees(
|
|||||||
// we add 5 sats in order to avoid min-relay-fee not met errors
|
// we add 5 sats in order to avoid min-relay-fee not met errors
|
||||||
return fee.GetFeeAmount() + 5, nil
|
return fee.GetFeeAmount() + 5, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *service) getTransaction(
|
||||||
|
ctx context.Context, txid string,
|
||||||
|
) (string, bool, int64, error) {
|
||||||
|
res, err := s.txClient.GetTransaction(ctx, &pb.GetTransactionRequest{
|
||||||
|
Txid: txid,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", false, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.GetBlockDetails().GetTimestamp() > 0 {
|
||||||
|
return res.GetTxHex(), true, res.BlockDetails.GetTimestamp(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not confirmed, we return now + 1 min to estimate the next blocktime
|
||||||
|
return res.GetTxHex(), false, time.Now().Add(time.Minute).Unix(), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ type txBuilder struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewTxBuilder(
|
func NewTxBuilder(
|
||||||
wallet ports.WalletService, net network.Network, roundLifetime int64, exitDelay int64,
|
wallet ports.WalletService,
|
||||||
|
net network.Network,
|
||||||
|
roundLifetime int64,
|
||||||
|
exitDelay int64,
|
||||||
) ports.TxBuilder {
|
) ports.TxBuilder {
|
||||||
return &txBuilder{wallet, &net, roundLifetime, exitDelay}
|
return &txBuilder{wallet, &net, roundLifetime, exitDelay}
|
||||||
}
|
}
|
||||||
@@ -106,7 +109,7 @@ func (b *txBuilder) BuildForfeitTxs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) BuildPoolTx(
|
func (b *txBuilder) BuildPoolTx(
|
||||||
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64,
|
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64, sweptRounds []domain.Round,
|
||||||
) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error) {
|
) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error) {
|
||||||
// The creation of the tree and the pool tx are tightly coupled:
|
// The creation of the tree and the pool tx are tightly coupled:
|
||||||
// - building the tree requires knowing the shared outpoint (txid:vout)
|
// - building the tree requires knowing the shared outpoint (txid:vout)
|
||||||
@@ -139,7 +142,7 @@ func (b *txBuilder) BuildPoolTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ptx, err := b.createPoolTx(
|
ptx, err := b.createPoolTx(
|
||||||
sharedOutputAmount, sharedOutputScript, payments, aspPubkey, connectorAddress, minRelayFee,
|
sharedOutputAmount, sharedOutputScript, payments, aspPubkey, connectorAddress, minRelayFee, sweptRounds,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -210,6 +213,7 @@ func (b *txBuilder) getLeafScriptAndTree(
|
|||||||
func (b *txBuilder) createPoolTx(
|
func (b *txBuilder) createPoolTx(
|
||||||
sharedOutputAmount uint64, sharedOutputScript []byte,
|
sharedOutputAmount uint64, sharedOutputScript []byte,
|
||||||
payments []domain.Payment, aspPubKey *secp256k1.PublicKey, connectorAddress string, minRelayFee uint64,
|
payments []domain.Payment, aspPubKey *secp256k1.PublicKey, connectorAddress string, minRelayFee uint64,
|
||||||
|
sweptRounds []domain.Round,
|
||||||
) (*psetv2.Pset, error) {
|
) (*psetv2.Pset, error) {
|
||||||
aspScript, err := p2wpkhScript(aspPubKey, b.net)
|
aspScript, err := p2wpkhScript(aspPubKey, b.net)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -263,7 +267,7 @@ func (b *txBuilder) createPoolTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
utxos, change, err := b.wallet.SelectUtxos(ctx, b.net.AssetID, targetAmount)
|
utxos, change, err := b.selectUtxos(ctx, sweptRounds, targetAmount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -325,7 +329,7 @@ func (b *txBuilder) createPoolTx(
|
|||||||
// remove change output if present
|
// remove change output if present
|
||||||
ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1]
|
ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1]
|
||||||
}
|
}
|
||||||
newUtxos, change, err := b.wallet.SelectUtxos(ctx, b.net.AssetID, feeAmount-change)
|
newUtxos, change, err := b.selectUtxos(ctx, sweptRounds, feeAmount-change)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -347,7 +351,7 @@ func (b *txBuilder) createPoolTx(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if feeAmount-dust > 0 {
|
} else if feeAmount-dust > 0 {
|
||||||
newUtxos, change, err := b.wallet.SelectUtxos(ctx, b.net.AssetID, feeAmount-dust)
|
newUtxos, change, err := b.selectUtxos(ctx, sweptRounds, feeAmount-dust)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func TestBuildPoolTx(t *testing.T) {
|
|||||||
t.Run("valid", func(t *testing.T) {
|
t.Run("valid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Valid {
|
for _, f := range fixtures.Valid {
|
||||||
poolTx, congestionTree, connAddr, err := builder.BuildPoolTx(
|
poolTx, congestionTree, connAddr, err := builder.BuildPoolTx(
|
||||||
pubkey, f.Payments, minRelayFee,
|
pubkey, f.Payments, minRelayFee, []domain.Round{},
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, poolTx)
|
require.NotEmpty(t, poolTx)
|
||||||
@@ -81,7 +81,7 @@ func TestBuildPoolTx(t *testing.T) {
|
|||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Invalid {
|
for _, f := range fixtures.Invalid {
|
||||||
poolTx, congestionTree, connAddr, err := builder.BuildPoolTx(
|
poolTx, congestionTree, connAddr, err := builder.BuildPoolTx(
|
||||||
pubkey, f.Payments, minRelayFee,
|
pubkey, f.Payments, minRelayFee, []domain.Round{},
|
||||||
)
|
)
|
||||||
require.EqualError(t, err, f.ExpectedErr)
|
require.EqualError(t, err, f.ExpectedErr)
|
||||||
require.Empty(t, poolTx)
|
require.Empty(t, poolTx)
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package txbuilder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/internal/core/domain"
|
||||||
|
"github.com/ark-network/ark/internal/core/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *txBuilder) selectUtxos(ctx context.Context, sweptRounds []domain.Round, amount uint64) ([]ports.TxInput, uint64, error) {
|
||||||
|
selectedConnectorsUtxos := make([]ports.TxInput, 0)
|
||||||
|
selectedConnectorsAmount := uint64(0)
|
||||||
|
|
||||||
|
for _, round := range sweptRounds {
|
||||||
|
if selectedConnectorsAmount >= amount {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
connectors, err := b.wallet.ListConnectorUtxos(ctx, round.ConnectorAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, connector := range connectors {
|
||||||
|
if selectedConnectorsAmount >= amount {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedConnectorsUtxos = append(selectedConnectorsUtxos, connector)
|
||||||
|
selectedConnectorsAmount += connector.GetValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(selectedConnectorsUtxos) > 0 {
|
||||||
|
if err := b.wallet.LockConnectorUtxos(ctx, castToOutpoints(selectedConnectorsUtxos)); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectedConnectorsAmount >= amount {
|
||||||
|
return selectedConnectorsUtxos, selectedConnectorsAmount - amount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
utxos, change, err := b.wallet.SelectUtxos(ctx, b.net.AssetID, amount-selectedConnectorsAmount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(selectedConnectorsUtxos, utxos...), change, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func castToOutpoints(inputs []ports.TxInput) []ports.TxOutpoint {
|
||||||
|
outpoints := make([]ports.TxOutpoint, 0, len(inputs))
|
||||||
|
for _, input := range inputs {
|
||||||
|
outpoints = append(outpoints, input)
|
||||||
|
}
|
||||||
|
return outpoints
|
||||||
|
}
|
||||||
@@ -133,17 +133,6 @@ func (m *mockedWallet) SignPsetWithKey(ctx context.Context, pset string, inputIn
|
|||||||
return res, args.Error(1)
|
return res, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockedWallet) SignConnectorInput(ctx context.Context, pset string, inputIndexes []int, extract bool) (string, error) {
|
|
||||||
args := m.Called(ctx, pset, inputIndexes, extract)
|
|
||||||
|
|
||||||
var res string
|
|
||||||
if a := args.Get(0); a != nil {
|
|
||||||
res = a.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, args.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockedWallet) WatchScripts(
|
func (m *mockedWallet) WatchScripts(
|
||||||
ctx context.Context, scripts []string,
|
ctx context.Context, scripts []string,
|
||||||
) error {
|
) error {
|
||||||
@@ -179,6 +168,11 @@ func (m *mockedWallet) ListConnectorUtxos(ctx context.Context, addr string) ([]p
|
|||||||
return res, args.Error(1)
|
return res, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockedWallet) LockConnectorUtxos(ctx context.Context, utxos []ports.TxOutpoint) error {
|
||||||
|
args := m.Called(ctx, utxos)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockedWallet) WaitForSync(ctx context.Context, txid string) error {
|
func (m *mockedWallet) WaitForSync(ctx context.Context, txid string) error {
|
||||||
args := m.Called(ctx, txid)
|
args := m.Called(ctx, txid)
|
||||||
return args.Error(0)
|
return args.Error(0)
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ func TestUnilateralExit(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance))
|
require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance))
|
||||||
require.Zero(t, balance.Offchain.Total)
|
require.Zero(t, balance.Offchain.Total)
|
||||||
require.Len(t, balance.Onchain.Locked, 1)
|
require.Greater(t, len(balance.Onchain.Locked), 0)
|
||||||
|
|
||||||
lockedBalance := balance.Onchain.Locked[0].Amount
|
lockedBalance := balance.Onchain.Locked[0].Amount
|
||||||
require.NotZero(t, lockedBalance)
|
require.NotZero(t, lockedBalance)
|
||||||
|
|||||||
Reference in New Issue
Block a user