New address encoding (#356)

* [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
This commit is contained in:
Louis Singer
2024-10-18 16:50:07 +02:00
committed by GitHub
parent b1c9261f14
commit b536a9e652
58 changed files with 2243 additions and 1896 deletions

View File

@@ -156,6 +156,7 @@ func (s *covenantService) GetBoardingAddress(ctx context.Context, userPubkey *se
func (s *covenantService) SpendVtxos(ctx context.Context, inputs []ports.Input) (string, error) {
vtxosInputs := make([]domain.Vtxo, 0)
boardingInputs := make([]ports.BoardingInput, 0)
descriptors := make(map[domain.VtxoKey]string)
now := time.Now().Unix()
@@ -218,13 +219,14 @@ func (s *covenantService) SpendVtxos(ctx context.Context, inputs []ports.Input)
}
vtxosInputs = append(vtxosInputs, vtxo)
descriptors[vtxo.VtxoKey] = input.Descriptor
}
payment, err := domain.NewPayment(vtxosInputs)
if err != nil {
return "", err
}
if err := s.paymentRequests.push(*payment, boardingInputs); err != nil {
if err := s.paymentRequests.push(*payment, boardingInputs, descriptors); err != nil {
return "", err
}
return payment.Id, nil
@@ -324,7 +326,7 @@ func (s *covenantService) CompleteAsyncPayment(ctx context.Context, redeemTx str
return fmt.Errorf("unimplemented")
}
func (s *covenantService) CreateAsyncPayment(ctx context.Context, inputs []ports.Input, receivers []domain.Receiver) (string, error) {
func (s *covenantService) CreateAsyncPayment(_ context.Context, _ []AsyncPaymentInput, _ []domain.Receiver) (string, error) {
return "", fmt.Errorf("unimplemented")
}
@@ -345,9 +347,14 @@ func (s *covenantService) SignRoundTx(ctx context.Context, signedRoundTx string)
return nil
}
func (s *covenantService) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, []domain.Vtxo, error) {
pk := hex.EncodeToString(pubkey.SerializeCompressed())
return s.repoManager.Vtxos().GetAllVtxos(ctx, pk)
func (s *covenantService) ListVtxos(ctx context.Context, address string) ([]domain.Vtxo, []domain.Vtxo, error) {
decodedAddress, err := common.DecodeAddress(address)
if err != nil {
return nil, nil, fmt.Errorf("failed to decode address: %s", err)
}
pubkey := hex.EncodeToString(schnorr.SerializePubKey(decodedAddress.VtxoTapKey))
return s.repoManager.Vtxos().GetAllVtxos(ctx, pubkey)
}
func (s *covenantService) GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent {
@@ -482,7 +489,7 @@ func (s *covenantService) startFinalization() {
if num > paymentsThreshold {
num = paymentsThreshold
}
payments, boardingInputs, _ := s.paymentRequests.pop(num)
payments, boardingInputs, descriptors, _ := s.paymentRequests.pop(num)
if _, err := round.RegisterPayments(payments); err != nil {
round.Fail(fmt.Errorf("failed to register payments: %s", err))
log.WithError(err).Warn("failed to register payments")
@@ -517,7 +524,7 @@ func (s *covenantService) startFinalization() {
minRelayFeeRate := s.wallet.MinRelayFeeRate(ctx)
if needForfeits {
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(unsignedPoolTx, payments, minRelayFeeRate)
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(unsignedPoolTx, payments, descriptors, minRelayFeeRate)
if err != nil {
round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
log.WithError(err).Warn("failed to create connectors and forfeit txs")
@@ -933,53 +940,20 @@ func (s *covenantService) getNewVtxos(round *domain.Round) []domain.Vtxo {
continue // skip fee outputs
}
desc := ""
found := false
for _, p := range round.Payments {
if found {
break
}
for _, r := range p.Receivers {
if r.IsOnchain() {
continue
}
vtxoScript, err := tree.ParseVtxoScript(r.Descriptor)
if err != nil {
log.WithError(err).Warn("failed to parse vtxo descriptor")
continue
}
tapKey, _, err := vtxoScript.TapTree()
if err != nil {
log.WithError(err).Warn("failed to compute vtxo tap key")
continue
}
script, err := common.P2TRScript(tapKey)
if err != nil {
log.WithError(err).Warn("failed to create vtxo scriptpubkey")
continue
}
if bytes.Equal(script, out.Script) {
found = true
desc = r.Descriptor
break
}
}
vtxoTapKey, err := schnorr.ParsePubKey(out.Script[2:])
if err != nil {
log.WithError(err).Warn("failed to parse vtxo tap key")
continue
}
if found {
vtxos = append(vtxos, domain.Vtxo{
VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)},
Receiver: domain.Receiver{Descriptor: desc, Amount: uint64(out.Value)},
RoundTxid: round.Txid,
})
break
}
vtxoPubkey := hex.EncodeToString(schnorr.SerializePubKey(vtxoTapKey))
vtxos = append(vtxos, domain.Vtxo{
VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)},
Pubkey: vtxoPubkey,
Amount: uint64(out.Value),
RoundTxid: round.Txid,
})
}
}
return vtxos
@@ -1050,17 +1024,17 @@ func (s *covenantService) restoreWatchingVtxos() error {
func (s *covenantService) extractVtxosScripts(vtxos []domain.Vtxo) ([]string, error) {
indexedScripts := make(map[string]struct{})
for _, vtxo := range vtxos {
vtxoScript, err := tree.ParseVtxoScript(vtxo.Receiver.Descriptor)
vtxoTapKeyBytes, err := hex.DecodeString(vtxo.Pubkey)
if err != nil {
return nil, err
}
tapKey, _, err := vtxoScript.TapTree()
vtxoTapKey, err := schnorr.ParsePubKey(vtxoTapKeyBytes)
if err != nil {
return nil, err
}
script, err := common.P2TRScript(tapKey)
script, err := common.P2TRScript(vtxoTapKey)
if err != nil {
return nil, err
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
@@ -177,37 +178,28 @@ func (s *covenantlessService) CompleteAsyncPayment(
// verify that the vtxo is spendable
vtxo, err := vtxoRepo.GetVtxos(ctx, []domain.VtxoKey{{Txid: vtxoOutpoint.Hash.String(), VOut: vtxoOutpoint.Index}})
vtxos, err := vtxoRepo.GetVtxos(ctx, []domain.VtxoKey{{Txid: vtxoOutpoint.Hash.String(), VOut: vtxoOutpoint.Index}})
if err != nil {
return fmt.Errorf("failed to get vtxo: %s", err)
}
if len(vtxo) == 0 {
if len(vtxos) == 0 {
return fmt.Errorf("vtxo not found")
}
if vtxo[0].Spent {
vtxo := vtxos[0]
if vtxo.Spent {
return fmt.Errorf("vtxo already spent")
}
if vtxo[0].Redeemed {
if vtxo.Redeemed {
return fmt.Errorf("vtxo already redeemed")
}
if vtxo[0].Swept {
if vtxo.Swept {
return fmt.Errorf("vtxo already swept")
}
vtxoScript, err := bitcointree.ParseVtxoScript(vtxo[0].Descriptor)
if err != nil {
return fmt.Errorf("failed to parse vtxo script: %s", err)
}
vtxoTapKey, _, err := vtxoScript.TapTree()
if err != nil {
return fmt.Errorf("failed to get taproot key: %s", err)
}
// verify that the user signs a forfeit closure
var userPubKey *secp256k1.PublicKey
@@ -228,6 +220,16 @@ func (s *covenantlessService) CompleteAsyncPayment(
return fmt.Errorf("redeem transaction is not signed")
}
vtxoPublicKeyBytes, err := hex.DecodeString(vtxo.Pubkey)
if err != nil {
return fmt.Errorf("failed to decode vtxo pubkey: %s", err)
}
vtxoTapKey, err := schnorr.ParsePubKey(vtxoPublicKeyBytes)
if err != nil {
return fmt.Errorf("failed to parse vtxo pubkey: %s", err)
}
// verify witness utxo
pkscript, err := common.P2TRScript(vtxoTapKey)
if err != nil {
@@ -238,7 +240,7 @@ func (s *covenantlessService) CompleteAsyncPayment(
return fmt.Errorf("witness utxo script mismatch")
}
if input.WitnessUtxo.Value != int64(vtxo[0].Amount) {
if input.WitnessUtxo.Value != int64(vtxo.Amount) {
return fmt.Errorf("witness utxo value mismatch")
}
}
@@ -260,19 +262,23 @@ func (s *covenantlessService) CompleteAsyncPayment(
vtxos := make([]domain.Vtxo, 0, len(asyncPayData.receivers))
for outIndex, out := range redeemPtx.UnsignedTx.TxOut {
desc := asyncPayData.receivers[outIndex].Descriptor
_, _, _, _, err := descriptor.ParseReversibleVtxoDescriptor(desc)
isPending := err == nil
vtxoTapKey, err := schnorr.ParsePubKey(out.PkScript[2:])
if err != nil {
return fmt.Errorf("failed to parse vtxo taproot key: %s", err)
}
vtxoPubkey := hex.EncodeToString(schnorr.SerializePubKey(vtxoTapKey))
// all pending except the last one
isPending := outIndex < len(asyncPayData.receivers)-1
vtxos = append(vtxos, domain.Vtxo{
VtxoKey: domain.VtxoKey{
Txid: redeemTxid,
VOut: uint32(outIndex),
},
Receiver: domain.Receiver{
Descriptor: desc,
Amount: uint64(out.Value),
},
Pubkey: vtxoPubkey,
Amount: uint64(out.Value),
ExpireAt: asyncPayData.expireAt,
RedeemTx: redeemTx,
Pending: isPending,
@@ -309,11 +315,16 @@ func (s *covenantlessService) CompleteAsyncPayment(
}
func (s *covenantlessService) CreateAsyncPayment(
ctx context.Context, inputs []ports.Input, receivers []domain.Receiver,
ctx context.Context, inputs []AsyncPaymentInput, receivers []domain.Receiver,
) (string, error) {
vtxosKeys := make([]domain.VtxoKey, 0, len(inputs))
descriptors := make(map[domain.VtxoKey]string)
forfeitLeaves := make(map[domain.VtxoKey]chainhash.Hash)
for _, in := range inputs {
vtxosKeys = append(vtxosKeys, in.VtxoKey)
descriptors[in.VtxoKey] = in.Descriptor
forfeitLeaves[in.VtxoKey] = in.ForfeitLeafHash
}
vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, vtxosKeys)
@@ -351,7 +362,7 @@ func (s *covenantlessService) CreateAsyncPayment(
}
redeemTx, err := s.builder.BuildAsyncPaymentTransactions(
vtxosInputs, s.pubkey, receivers,
vtxosInputs, descriptors, forfeitLeaves, receivers,
)
if err != nil {
return "", fmt.Errorf("failed to build async payment txs: %s", err)
@@ -404,7 +415,7 @@ func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Inp
now := time.Now().Unix()
boardingTxs := make(map[string]wire.MsgTx, 0) // txid -> txhex
descriptors := make(map[domain.VtxoKey]string)
for _, input := range inputs {
vtxosResult, err := s.repoManager.Vtxos().GetVtxos(ctx, []domain.VtxoKey{input.VtxoKey})
if err != nil || len(vtxosResult) == 0 {
@@ -461,6 +472,8 @@ func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Inp
return "", fmt.Errorf("input %s:%d already swept", vtxo.Txid, vtxo.VOut)
}
descriptors[vtxo.VtxoKey] = input.Descriptor
vtxosInputs = append(vtxosInputs, vtxo)
}
@@ -468,7 +481,7 @@ func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Inp
if err != nil {
return "", err
}
if err := s.paymentRequests.push(*payment, boardingInputs); err != nil {
if err := s.paymentRequests.push(*payment, boardingInputs, descriptors); err != nil {
return "", err
}
return payment.Id, nil
@@ -572,9 +585,14 @@ func (s *covenantlessService) SignRoundTx(ctx context.Context, signedRoundTx str
return nil
}
func (s *covenantlessService) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, []domain.Vtxo, error) {
pk := hex.EncodeToString(pubkey.SerializeCompressed())
return s.repoManager.Vtxos().GetAllVtxos(ctx, pk)
func (s *covenantlessService) ListVtxos(ctx context.Context, address string) ([]domain.Vtxo, []domain.Vtxo, error) {
decodedAddress, err := common.DecodeAddress(address)
if err != nil {
return nil, nil, fmt.Errorf("failed to decode address: %s", err)
}
pubkey := hex.EncodeToString(schnorr.SerializePubKey(decodedAddress.VtxoTapKey))
return s.repoManager.Vtxos().GetAllVtxos(ctx, pubkey)
}
func (s *covenantlessService) GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent {
@@ -771,7 +789,7 @@ func (s *covenantlessService) startFinalization() {
if num > paymentsThreshold {
num = paymentsThreshold
}
payments, boardingInputs, cosigners := s.paymentRequests.pop(num)
payments, boardingInputs, descriptors, cosigners := s.paymentRequests.pop(num)
if len(payments) > len(cosigners) {
err := fmt.Errorf("missing ephemeral key for payments")
round.Fail(fmt.Errorf("round aborted: %s", err))
@@ -973,7 +991,7 @@ func (s *covenantlessService) startFinalization() {
minRelayFeeRate := s.wallet.MinRelayFeeRate(ctx)
if needForfeits {
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(unsignedRoundTx, payments, minRelayFeeRate)
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(unsignedRoundTx, payments, descriptors, minRelayFeeRate)
if err != nil {
round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
log.WithError(err).Warn("failed to create connectors and forfeit txs")
@@ -1332,53 +1350,18 @@ func (s *covenantlessService) getNewVtxos(round *domain.Round) []domain.Vtxo {
continue
}
for i, out := range tx.UnsignedTx.TxOut {
desc := ""
found := false
for _, p := range round.Payments {
if found {
break
}
for _, r := range p.Receivers {
if r.IsOnchain() {
continue
}
vtxoScript, err := bitcointree.ParseVtxoScript(r.Descriptor)
if err != nil {
log.WithError(err).Warn("failed to parse vtxo descriptor")
continue
}
tapKey, _, err := vtxoScript.TapTree()
if err != nil {
log.WithError(err).Warn("failed to compute vtxo tap key")
continue
}
script, err := common.P2TRScript(tapKey)
if err != nil {
log.WithError(err).Warn("failed to create vtxo scriptpubkey")
continue
}
if bytes.Equal(script, out.PkScript) {
found = true
desc = r.Descriptor
break
}
}
vtxoTapKey, err := schnorr.ParsePubKey(out.PkScript[2:])
if err != nil {
log.WithError(err).Warn("failed to parse vtxo tap key")
continue
}
if found {
vtxos = append(vtxos, domain.Vtxo{
VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)},
Receiver: domain.Receiver{Descriptor: desc, Amount: uint64(out.Value)},
RoundTxid: round.Txid,
})
break
}
vtxos = append(vtxos, domain.Vtxo{
VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)},
Pubkey: hex.EncodeToString(schnorr.SerializePubKey(vtxoTapKey)),
Amount: uint64(out.Value),
RoundTxid: round.Txid,
})
}
}
return vtxos
@@ -1450,17 +1433,17 @@ func (s *covenantlessService) extractVtxosScripts(vtxos []domain.Vtxo) ([]string
indexedScripts := make(map[string]struct{})
for _, vtxo := range vtxos {
vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Receiver.Descriptor)
vtxoTapKeyBytes, err := hex.DecodeString(vtxo.Pubkey)
if err != nil {
return nil, err
}
tapKey, _, err := vtxoScript.TapTree()
vtxoTapKey, err := schnorr.ParsePubKey(vtxoTapKeyBytes)
if err != nil {
return nil, err
}
script, err := common.P2TRScript(tapKey)
script, err := common.P2TRScript(vtxoTapKey)
if err != nil {
return nil, err
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/ark-network/ark/server/internal/core/domain"
"github.com/ark-network/ark/server/internal/core/ports"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
@@ -12,6 +13,11 @@ var (
paymentsThreshold = int64(128)
)
type AsyncPaymentInput struct {
ports.Input
ForfeitLeafHash chainhash.Hash
}
type Service interface {
Start() error
Stop()
@@ -27,12 +33,12 @@ type Service interface {
ctx context.Context, paymentId string,
) (lastEvent domain.RoundEvent, err error)
ListVtxos(
ctx context.Context, pubkey *secp256k1.PublicKey,
ctx context.Context, address string,
) (spendableVtxos, spentVtxos []domain.Vtxo, err error)
GetInfo(ctx context.Context) (*ServiceInfo, error)
// Async payments
CreateAsyncPayment(
ctx context.Context, inputs []ports.Input, receivers []domain.Receiver,
ctx context.Context, inputs []AsyncPaymentInput, receivers []domain.Receiver,
) (string, error)
CompleteAsyncPayment(
ctx context.Context, redeemTx string,

View File

@@ -24,13 +24,14 @@ type timedPayment struct {
type paymentsMap struct {
lock *sync.RWMutex
payments map[string]*timedPayment
descriptors map[domain.VtxoKey]string
ephemeralKeys map[string]*secp256k1.PublicKey
}
func newPaymentsMap() *paymentsMap {
paymentsById := make(map[string]*timedPayment)
lock := &sync.RWMutex{}
return &paymentsMap{lock, paymentsById, make(map[string]*secp256k1.PublicKey)}
return &paymentsMap{lock, paymentsById, make(map[domain.VtxoKey]string), make(map[string]*secp256k1.PublicKey)}
}
func (m *paymentsMap) len() int64 {
@@ -58,7 +59,11 @@ func (m *paymentsMap) delete(id string) error {
return nil
}
func (m *paymentsMap) push(payment domain.Payment, boardingInputs []ports.BoardingInput) error {
func (m *paymentsMap) push(
payment domain.Payment,
boardingInputs []ports.BoardingInput,
descriptors map[domain.VtxoKey]string,
) error {
m.lock.Lock()
defer m.lock.Unlock()
@@ -86,6 +91,10 @@ func (m *paymentsMap) push(payment domain.Payment, boardingInputs []ports.Boardi
}
}
for key, desc := range descriptors {
m.descriptors[key] = desc
}
m.payments[payment.Id] = &timedPayment{payment, boardingInputs, time.Now(), time.Time{}}
return nil
}
@@ -102,7 +111,7 @@ func (m *paymentsMap) pushEphemeralKey(paymentId string, pubkey *secp256k1.Publi
return nil
}
func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, []*secp256k1.PublicKey) {
func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, map[domain.VtxoKey]string, []*secp256k1.PublicKey) {
m.lock.Lock()
defer m.lock.Unlock()
@@ -129,6 +138,7 @@ func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, [
payments := make([]domain.Payment, 0, num)
boardingInputs := make([]ports.BoardingInput, 0)
cosigners := make([]*secp256k1.PublicKey, 0, num)
descriptors := make(map[domain.VtxoKey]string)
for _, p := range paymentsByTime[:num] {
boardingInputs = append(boardingInputs, p.boardingInputs...)
payments = append(payments, p.Payment)
@@ -136,9 +146,15 @@ func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, [
cosigners = append(cosigners, pubkey)
delete(m.ephemeralKeys, p.Payment.Id)
}
for _, input := range payments {
for _, vtxo := range input.Inputs {
descriptors[vtxo.VtxoKey] = m.descriptors[vtxo.VtxoKey]
delete(m.descriptors, vtxo.VtxoKey)
}
}
delete(m.payments, p.Id)
}
return payments, boardingInputs, cosigners
return payments, boardingInputs, descriptors, cosigners
}
func (m *paymentsMap) update(payment domain.Payment) error {

View File

@@ -68,9 +68,12 @@ func (p Payment) validate(ignoreOuts bool) error {
return fmt.Errorf("missing outputs")
}
for _, r := range p.Receivers {
if len(r.OnchainAddress) <= 0 && len(r.Descriptor) <= 0 {
if len(r.OnchainAddress) <= 0 && len(r.Pubkey) <= 0 {
return fmt.Errorf("missing receiver destination")
}
if r.Amount == 0 {
return fmt.Errorf("missing receiver amount")
}
}
return nil
}
@@ -80,6 +83,10 @@ type VtxoKey struct {
VOut uint32
}
func (k VtxoKey) String() string {
return fmt.Sprintf("%s:%d", k.Txid, k.VOut)
}
func (k VtxoKey) Hash() string {
calcHash := func(buf []byte, hasher hash.Hash) []byte {
_, _ = hasher.Write(buf)
@@ -96,9 +103,9 @@ func (k VtxoKey) Hash() string {
}
type Receiver struct {
Descriptor string
Amount uint64
OnchainAddress string
OnchainAddress string // onchain
Pubkey string // offchain
}
func (r Receiver) IsOnchain() bool {
@@ -107,7 +114,8 @@ func (r Receiver) IsOnchain() bool {
type Vtxo struct {
VtxoKey
Receiver
Amount uint64
Pubkey string
RoundTxid string
SpentBy string // round txid or async redeem txid
Spent bool

View File

@@ -1,22 +1,14 @@
package domain_test
import (
"fmt"
"testing"
"github.com/ark-network/ark/common/descriptor"
"github.com/ark-network/ark/server/internal/core/domain"
"github.com/stretchr/testify/require"
)
var desc = fmt.Sprintf(
descriptor.DefaultVtxoDescriptorTemplate,
"030000000000000000000000000000000000000000000000000000000000000001",
"0000000000000000000000000000000000000000000000000000000000000001",
"0000000000000000000000000000000000000000000000000000000000000001",
512,
"0000000000000000000000000000000000000000000000000000000000000001",
)
// x-only pubkey
const pubkey = "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967"
var inputs = []domain.Vtxo{
{
@@ -24,10 +16,8 @@ var inputs = []domain.Vtxo{
Txid: "0000000000000000000000000000000000000000000000000000000000000000",
VOut: 0,
},
Receiver: domain.Receiver{
Descriptor: desc,
Amount: 1000,
},
Pubkey: pubkey,
Amount: 1000,
},
}
@@ -51,12 +41,12 @@ func TestPayment(t *testing.T) {
err = payment.AddReceivers([]domain.Receiver{
{
Descriptor: desc,
Amount: 450,
Pubkey: pubkey,
Amount: 450,
},
{
Descriptor: desc,
Amount: 550,
Pubkey: pubkey,
Amount: 550,
},
})
require.NoError(t, err)

View File

@@ -20,24 +20,22 @@ var (
Txid: txid,
VOut: 0,
},
Receiver: domain.Receiver{
Descriptor: desc,
Amount: 2000,
},
Pubkey: pubkey,
Amount: 2000,
},
},
Receivers: []domain.Receiver{
{
Descriptor: desc,
Amount: 700,
Pubkey: pubkey,
Amount: 700,
},
{
Descriptor: desc,
Amount: 700,
Pubkey: pubkey,
Amount: 700,
},
{
Descriptor: desc,
Amount: 600,
Pubkey: pubkey,
Amount: 600,
},
},
},
@@ -49,25 +47,21 @@ var (
Txid: txid,
VOut: 0,
},
Receiver: domain.Receiver{
Descriptor: desc,
Amount: 1000,
},
Pubkey: pubkey,
Amount: 1000,
},
{
VtxoKey: domain.VtxoKey{
Txid: txid,
VOut: 0,
},
Receiver: domain.Receiver{
Descriptor: desc,
Amount: 1000,
},
Pubkey: pubkey,
Amount: 1000,
},
},
Receivers: []domain.Receiver{{
Descriptor: desc,
Amount: 2000,
Pubkey: pubkey,
Amount: 2000,
}},
},
}

View File

@@ -32,7 +32,12 @@ type TxBuilder interface {
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, boardingInputs []BoardingInput, sweptRounds []domain.Round,
cosigners ...*secp256k1.PublicKey,
) (roundTx string, congestionTree tree.CongestionTree, connectorAddress string, err error)
BuildForfeitTxs(poolTx string, payments []domain.Payment, minRelayFeeRate chainfee.SatPerKVByte) (connectors []string, forfeitTxs []string, err error)
BuildForfeitTxs(
roundTx string,
payments []domain.Payment,
descriptors map[domain.VtxoKey]string,
minRelayFeeRate chainfee.SatPerKVByte,
) (connectors []string, forfeitTxs []string, err error)
BuildSweepTx(inputs []SweepInput) (signedSweepTx string, err error)
GetSweepInput(node tree.Node) (lifetime int64, sweepInput SweepInput, err error)
FinalizeAndExtract(tx string) (txhex string, err error)
@@ -41,7 +46,9 @@ type TxBuilder interface {
FindLeaves(congestionTree tree.CongestionTree, fromtxid string, vout uint32) (leaves []tree.Node, err error)
BuildAsyncPaymentTransactions(
vtxosToSpend []domain.Vtxo,
aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver,
descriptors map[domain.VtxoKey]string,
forfeitsLeaves map[domain.VtxoKey]chainhash.Hash,
receivers []domain.Receiver,
) (string, error)
VerifyAndCombinePartialTx(dest string, src string) (string, error)
GetTxID(tx string) (string, error)

View File

@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"path/filepath"
"regexp"
"strings"
"github.com/ark-network/ark/server/internal/core/domain"
@@ -101,13 +100,7 @@ func (r *vtxoRepository) GetAllVtxos(
) ([]domain.Vtxo, []domain.Vtxo, error) {
query := badgerhold.Where("Redeemed").Eq(false)
if len(pubkey) > 0 {
if len(pubkey) == 66 {
pubkey = pubkey[2:]
}
query = query.And("Descriptor").RegExp(
regexp.MustCompile(fmt.Sprintf(".*%s.*", pubkey)),
)
query = query.And("Pubkey").Eq(pubkey)
}
vtxos, err := r.findVtxos(ctx, query)
if err != nil {

View File

@@ -4,14 +4,12 @@ import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"reflect"
"sort"
"testing"
"time"
"github.com/ark-network/ark/common/descriptor"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/server/internal/core/domain"
"github.com/ark-network/ark/server/internal/core/ports"
@@ -24,26 +22,8 @@ import (
const (
emptyPtx = "cHNldP8BAgQCAAAAAQQBAAEFAQABBgEDAfsEAgAAAAA="
emptyTx = "0200000000000000000000"
pubkey1 = "00000000000000000000000000000000000000000000000000000000000000001"
pubkey2 = "00000000000000000000000000000000000000000000000000000000000000002"
)
var desc1 = fmt.Sprintf(
descriptor.DefaultVtxoDescriptorTemplate,
randomString(66),
pubkey1,
pubkey1,
512,
pubkey1,
)
var desc2 = fmt.Sprintf(
descriptor.DefaultVtxoDescriptorTemplate,
randomString(66),
pubkey2,
pubkey2,
512,
pubkey2,
pubkey = "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967"
pubkey2 = "33ffb3dee353b1a9ebe4ced64b946238d0a4ac364f275d771da6ad2445d07ae0"
)
var congestionTree = [][]tree.Node{
@@ -270,15 +250,13 @@ func testRoundRepository(t *testing.T, svc ports.RepoManager) {
},
RoundTxid: randomString(32),
ExpireAt: 7980322,
Receiver: domain.Receiver{
Descriptor: randomString(120),
Amount: 300,
},
Pubkey: randomString(32),
Amount: 300,
},
},
Receivers: []domain.Receiver{{
Descriptor: randomString(120),
Amount: 300,
Pubkey: randomString(32),
Amount: 300,
}},
},
{
@@ -292,20 +270,18 @@ func testRoundRepository(t *testing.T, svc ports.RepoManager) {
},
RoundTxid: randomString(32),
ExpireAt: 7980322,
Receiver: domain.Receiver{
Descriptor: randomString(120),
Amount: 600,
},
Pubkey: randomString(32),
Amount: 600,
},
},
Receivers: []domain.Receiver{
{
Descriptor: randomString(120),
Amount: 400,
Pubkey: randomString(32),
Amount: 400,
},
{
Descriptor: randomString(120),
Amount: 200,
Pubkey: randomString(32),
Amount: 200,
},
},
},
@@ -370,20 +346,16 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
Txid: randomString(32),
VOut: 0,
},
Receiver: domain.Receiver{
Descriptor: desc1,
Amount: 1000,
},
Pubkey: pubkey,
Amount: 1000,
},
{
VtxoKey: domain.VtxoKey{
Txid: randomString(32),
VOut: 1,
},
Receiver: domain.Receiver{
Descriptor: desc1,
Amount: 2000,
},
Pubkey: pubkey,
Amount: 2000,
},
}
newVtxos := append(userVtxos, domain.Vtxo{
@@ -391,10 +363,8 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
Txid: randomString(32),
VOut: 1,
},
Receiver: domain.Receiver{
Descriptor: desc2,
Amount: 2000,
},
Pubkey: pubkey2,
Amount: 2000,
})
vtxoKeys := make([]domain.VtxoKey, 0, len(userVtxos))
@@ -406,7 +376,7 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
require.Error(t, err)
require.Empty(t, vtxos)
spendableVtxos, spentVtxos, err := svc.Vtxos().GetAllVtxos(ctx, pubkey1)
spendableVtxos, spentVtxos, err := svc.Vtxos().GetAllVtxos(ctx, pubkey)
require.NoError(t, err)
require.Empty(t, spendableVtxos)
require.Empty(t, spentVtxos)
@@ -423,7 +393,7 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
require.NoError(t, err)
require.Exactly(t, userVtxos, vtxos)
spendableVtxos, spentVtxos, err = svc.Vtxos().GetAllVtxos(ctx, pubkey1)
spendableVtxos, spentVtxos, err = svc.Vtxos().GetAllVtxos(ctx, pubkey)
require.NoError(t, err)
sortedVtxos := sortVtxos(userVtxos)
@@ -449,7 +419,7 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
require.True(t, v.Spent)
}
spendableVtxos, spentVtxos, err = svc.Vtxos().GetAllVtxos(ctx, pubkey1)
spendableVtxos, spentVtxos, err = svc.Vtxos().GetAllVtxos(ctx, pubkey)
require.NoError(t, err)
require.Exactly(t, vtxos[1:], spendableVtxos)
require.Len(t, spentVtxos, len(vtxoKeys[:1]))

View File

@@ -21,11 +21,11 @@ CREATE TABLE IF NOT EXISTS payment (
CREATE TABLE IF NOT EXISTS receiver (
payment_id TEXT NOT NULL,
pubkey TEXT NOT NULL,
pubkey TEXT,
onchain_address TEXT,
amount INTEGER NOT NULL,
onchain_address TEXT NOT NULL,
FOREIGN KEY (payment_id) REFERENCES payment(id),
PRIMARY KEY (payment_id, pubkey)
PRIMARY KEY (payment_id, pubkey, onchain_address)
);
CREATE TABLE IF NOT EXISTS tx (
@@ -54,6 +54,7 @@ CREATE TABLE IF NOT EXISTS vtxo (
expire_at INTEGER NOT NULL,
payment_id TEXT,
redeem_tx TEXT,
pending BOOLEAN NOT NULL,
PRIMARY KEY (txid, vout),
FOREIGN KEY (payment_id) REFERENCES payment(id)
);

View File

@@ -1,17 +0,0 @@
CREATE TABLE IF NOT EXISTS old_receiver (
payment_id TEXT NOT NULL,
pubkey TEXT NOT NULL,
amount INTEGER NOT NULL,
onchain_address TEXT NOT NULL,
FOREIGN KEY (payment_id) REFERENCES payment(id),
PRIMARY KEY (payment_id, pubkey)
);
INSERT INTO old_receiver SELECT * FROM receiver;
DROP TABLE receiver;
ALTER TABLE old_receiver RENAME TO receiver;
ALTER TABLE vtxo DROP COLUMN descriptor;
ALTER TABLE vtxo ADD COLUMN pubkey TEXT NOT NULL;

View File

@@ -1,28 +0,0 @@
CREATE TABLE IF NOT EXISTS new_receiver (
payment_id TEXT NOT NULL,
descriptor TEXT NOT NULL,
amount INTEGER NOT NULL,
onchain_address TEXT NOT NULL,
FOREIGN KEY (payment_id) REFERENCES payment(id),
PRIMARY KEY (payment_id, descriptor)
);
INSERT INTO new_receiver SELECT * FROM receiver;
DROP VIEW payment_vtxo_vw;
DROP VIEW payment_receiver_vw;
DROP TABLE receiver;
ALTER TABLE new_receiver RENAME TO receiver;
ALTER TABLE vtxo ADD COLUMN descriptor TEXT;
ALTER TABLE vtxo DROP COLUMN pubkey;
CREATE VIEW payment_vtxo_vw AS SELECT vtxo.*
FROM payment
LEFT OUTER JOIN vtxo
ON payment.id=vtxo.payment_id;
CREATE VIEW payment_receiver_vw AS SELECT receiver.*
FROM payment
LEFT OUTER JOIN receiver
ON payment.id=receiver.payment_id;

View File

@@ -1 +0,0 @@
ALTER TABLE vtxo DROP COLUMN pending;

View File

@@ -1,14 +0,0 @@
ALTER TABLE vtxo ADD COLUMN pending BOOLEAN NOT NULL;
DROP VIEW payment_vtxo_vw;
DROP VIEW payment_receiver_vw;
CREATE VIEW payment_vtxo_vw AS SELECT vtxo.*
FROM payment
LEFT OUTER JOIN vtxo
ON payment.id=vtxo.payment_id;
CREATE VIEW payment_receiver_vw AS SELECT receiver.*
FROM payment
LEFT OUTER JOIN receiver
ON payment.id=receiver.payment_id;

View File

@@ -164,10 +164,16 @@ func (r *roundRepository) AddOrUpdateRound(ctx context.Context, round domain.Rou
if err := querierWithTx.UpsertReceiver(
ctx,
queries.UpsertReceiverParams{
PaymentID: payment.Id,
Descriptor: receiver.Descriptor,
Amount: int64(receiver.Amount),
OnchainAddress: receiver.OnchainAddress,
PaymentID: payment.Id,
Amount: int64(receiver.Amount),
Pubkey: sql.NullString{
String: receiver.Pubkey,
Valid: len(receiver.Pubkey) > 0,
},
OnchainAddress: sql.NullString{
String: receiver.OnchainAddress,
Valid: len(receiver.OnchainAddress) > 0,
},
},
); err != nil {
return fmt.Errorf("failed to upsert receiver: %w", err)
@@ -320,8 +326,8 @@ func (r *roundRepository) GetSweptRounds(ctx context.Context) ([]domain.Round, e
func rowToReceiver(row queries.PaymentReceiverVw) domain.Receiver {
return domain.Receiver{
Descriptor: row.Descriptor.String,
Amount: uint64(row.Amount.Int64),
Pubkey: row.Pubkey.String,
OnchainAddress: row.OnchainAddress.String,
}
}
@@ -413,8 +419,8 @@ func readRoundRows(rows []roundPaymentTxReceiverVtxoRow) ([]*domain.Round, error
found := false
for _, rcv := range payment.Receivers {
if v.receiver.Descriptor.Valid && v.receiver.Amount.Valid {
if rcv.Descriptor == v.receiver.Descriptor.String && int64(rcv.Amount) == v.receiver.Amount.Int64 {
if (v.receiver.Pubkey.Valid || v.receiver.OnchainAddress.Valid) && v.receiver.Amount.Valid {
if rcv.Pubkey == v.receiver.Pubkey.String && rcv.OnchainAddress == v.receiver.OnchainAddress.String && int64(rcv.Amount) == v.receiver.Amount.Int64 {
found = true
break
}
@@ -469,10 +475,8 @@ func rowToPaymentVtxoVw(row queries.PaymentVtxoVw) domain.Vtxo {
Txid: row.Txid.String,
VOut: uint32(row.Vout.Int64),
},
Receiver: domain.Receiver{
Descriptor: row.Descriptor.String,
Amount: uint64(row.Amount.Int64),
},
Amount: uint64(row.Amount.Int64),
Pubkey: row.Pubkey.String,
RoundTxid: row.PoolTx.String,
SpentBy: row.SpentBy.String,
Spent: row.Spent.Bool,

View File

@@ -15,32 +15,32 @@ type Payment struct {
type PaymentReceiverVw struct {
PaymentID sql.NullString
Descriptor sql.NullString
Amount sql.NullInt64
Pubkey sql.NullString
OnchainAddress sql.NullString
Amount sql.NullInt64
}
type PaymentVtxoVw struct {
Txid sql.NullString
Vout sql.NullInt64
Amount sql.NullInt64
PoolTx sql.NullString
SpentBy sql.NullString
Spent sql.NullBool
Redeemed sql.NullBool
Swept sql.NullBool
ExpireAt sql.NullInt64
PaymentID sql.NullString
RedeemTx sql.NullString
Descriptor sql.NullString
Pending sql.NullBool
Txid sql.NullString
Vout sql.NullInt64
Pubkey sql.NullString
Amount sql.NullInt64
PoolTx sql.NullString
SpentBy sql.NullString
Spent sql.NullBool
Redeemed sql.NullBool
Swept sql.NullBool
ExpireAt sql.NullInt64
PaymentID sql.NullString
RedeemTx sql.NullString
Pending sql.NullBool
}
type Receiver struct {
PaymentID string
Descriptor string
Pubkey sql.NullString
OnchainAddress sql.NullString
Amount int64
OnchainAddress string
}
type Round struct {
@@ -88,17 +88,17 @@ type Tx struct {
}
type Vtxo struct {
Txid string
Vout int64
Amount int64
PoolTx string
SpentBy string
Spent bool
Redeemed bool
Swept bool
ExpireAt int64
PaymentID sql.NullString
RedeemTx sql.NullString
Descriptor sql.NullString
Pending bool
Txid string
Vout int64
Pubkey string
Amount int64
PoolTx string
SpentBy string
Spent bool
Redeemed bool
Swept bool
ExpireAt int64
PaymentID sql.NullString
RedeemTx sql.NullString
Pending bool
}

View File

@@ -54,7 +54,7 @@ func (q *Queries) MarkVtxoAsSwept(ctx context.Context, arg MarkVtxoAsSweptParams
}
const selectNotRedeemedVtxos = `-- name: SelectNotRedeemedVtxos :many
SELECT vtxo.txid, vtxo.vout, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx, vtxo.descriptor, vtxo.pending FROM vtxo
SELECT vtxo.txid, vtxo.vout, vtxo.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx, vtxo.pending FROM vtxo
WHERE redeemed = false
`
@@ -74,6 +74,7 @@ func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]SelectNotRedeem
if err := rows.Scan(
&i.Vtxo.Txid,
&i.Vtxo.Vout,
&i.Vtxo.Pubkey,
&i.Vtxo.Amount,
&i.Vtxo.PoolTx,
&i.Vtxo.SpentBy,
@@ -83,7 +84,6 @@ func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]SelectNotRedeem
&i.Vtxo.ExpireAt,
&i.Vtxo.PaymentID,
&i.Vtxo.RedeemTx,
&i.Vtxo.Descriptor,
&i.Vtxo.Pending,
); err != nil {
return nil, err
@@ -100,16 +100,16 @@ func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]SelectNotRedeem
}
const selectNotRedeemedVtxosWithPubkey = `-- name: SelectNotRedeemedVtxosWithPubkey :many
SELECT vtxo.txid, vtxo.vout, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx, vtxo.descriptor, vtxo.pending FROM vtxo
WHERE redeemed = false AND INSTR(descriptor, ?) > 0
SELECT vtxo.txid, vtxo.vout, vtxo.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx, vtxo.pending FROM vtxo
WHERE redeemed = false AND pubkey = ?
`
type SelectNotRedeemedVtxosWithPubkeyRow struct {
Vtxo Vtxo
}
func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, instr string) ([]SelectNotRedeemedVtxosWithPubkeyRow, error) {
rows, err := q.db.QueryContext(ctx, selectNotRedeemedVtxosWithPubkey, instr)
func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, pubkey string) ([]SelectNotRedeemedVtxosWithPubkeyRow, error) {
rows, err := q.db.QueryContext(ctx, selectNotRedeemedVtxosWithPubkey, pubkey)
if err != nil {
return nil, err
}
@@ -120,6 +120,7 @@ func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, instr st
if err := rows.Scan(
&i.Vtxo.Txid,
&i.Vtxo.Vout,
&i.Vtxo.Pubkey,
&i.Vtxo.Amount,
&i.Vtxo.PoolTx,
&i.Vtxo.SpentBy,
@@ -129,7 +130,6 @@ func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, instr st
&i.Vtxo.ExpireAt,
&i.Vtxo.PaymentID,
&i.Vtxo.RedeemTx,
&i.Vtxo.Descriptor,
&i.Vtxo.Pending,
); err != nil {
return nil, err
@@ -208,8 +208,8 @@ const selectRoundWithRoundId = `-- name: SelectRoundWithRoundId :many
SELECT round.id, round.starting_timestamp, round.ending_timestamp, round.ended, round.failed, round.stage_code, round.txid, round.unsigned_tx, round.connector_address, round.dust_amount, round.version, round.swept,
round_payment_vw.id, round_payment_vw.round_id,
round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf,
payment_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.descriptor, payment_vtxo_vw.pending
payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, payment_receiver_vw.onchain_address, payment_receiver_vw.amount,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.pending
FROM round
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id
@@ -260,11 +260,12 @@ func (q *Queries) SelectRoundWithRoundId(ctx context.Context, id string) ([]Sele
&i.RoundTxVw.ParentTxid,
&i.RoundTxVw.IsLeaf,
&i.PaymentReceiverVw.PaymentID,
&i.PaymentReceiverVw.Descriptor,
&i.PaymentReceiverVw.Amount,
&i.PaymentReceiverVw.Pubkey,
&i.PaymentReceiverVw.OnchainAddress,
&i.PaymentReceiverVw.Amount,
&i.PaymentVtxoVw.Txid,
&i.PaymentVtxoVw.Vout,
&i.PaymentVtxoVw.Pubkey,
&i.PaymentVtxoVw.Amount,
&i.PaymentVtxoVw.PoolTx,
&i.PaymentVtxoVw.SpentBy,
@@ -274,7 +275,6 @@ func (q *Queries) SelectRoundWithRoundId(ctx context.Context, id string) ([]Sele
&i.PaymentVtxoVw.ExpireAt,
&i.PaymentVtxoVw.PaymentID,
&i.PaymentVtxoVw.RedeemTx,
&i.PaymentVtxoVw.Descriptor,
&i.PaymentVtxoVw.Pending,
); err != nil {
return nil, err
@@ -294,8 +294,8 @@ const selectRoundWithRoundTxId = `-- name: SelectRoundWithRoundTxId :many
SELECT round.id, round.starting_timestamp, round.ending_timestamp, round.ended, round.failed, round.stage_code, round.txid, round.unsigned_tx, round.connector_address, round.dust_amount, round.version, round.swept,
round_payment_vw.id, round_payment_vw.round_id,
round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf,
payment_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.descriptor, payment_vtxo_vw.pending
payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, payment_receiver_vw.onchain_address, payment_receiver_vw.amount,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.pending
FROM round
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id
@@ -346,11 +346,12 @@ func (q *Queries) SelectRoundWithRoundTxId(ctx context.Context, txid string) ([]
&i.RoundTxVw.ParentTxid,
&i.RoundTxVw.IsLeaf,
&i.PaymentReceiverVw.PaymentID,
&i.PaymentReceiverVw.Descriptor,
&i.PaymentReceiverVw.Amount,
&i.PaymentReceiverVw.Pubkey,
&i.PaymentReceiverVw.OnchainAddress,
&i.PaymentReceiverVw.Amount,
&i.PaymentVtxoVw.Txid,
&i.PaymentVtxoVw.Vout,
&i.PaymentVtxoVw.Pubkey,
&i.PaymentVtxoVw.Amount,
&i.PaymentVtxoVw.PoolTx,
&i.PaymentVtxoVw.SpentBy,
@@ -360,7 +361,6 @@ func (q *Queries) SelectRoundWithRoundTxId(ctx context.Context, txid string) ([]
&i.PaymentVtxoVw.ExpireAt,
&i.PaymentVtxoVw.PaymentID,
&i.PaymentVtxoVw.RedeemTx,
&i.PaymentVtxoVw.Descriptor,
&i.PaymentVtxoVw.Pending,
); err != nil {
return nil, err
@@ -380,8 +380,8 @@ const selectSweepableRounds = `-- name: SelectSweepableRounds :many
SELECT round.id, round.starting_timestamp, round.ending_timestamp, round.ended, round.failed, round.stage_code, round.txid, round.unsigned_tx, round.connector_address, round.dust_amount, round.version, round.swept,
round_payment_vw.id, round_payment_vw.round_id,
round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf,
payment_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.descriptor, payment_vtxo_vw.pending
payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, payment_receiver_vw.onchain_address, payment_receiver_vw.amount,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.pending
FROM round
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id
@@ -432,11 +432,12 @@ func (q *Queries) SelectSweepableRounds(ctx context.Context) ([]SelectSweepableR
&i.RoundTxVw.ParentTxid,
&i.RoundTxVw.IsLeaf,
&i.PaymentReceiverVw.PaymentID,
&i.PaymentReceiverVw.Descriptor,
&i.PaymentReceiverVw.Amount,
&i.PaymentReceiverVw.Pubkey,
&i.PaymentReceiverVw.OnchainAddress,
&i.PaymentReceiverVw.Amount,
&i.PaymentVtxoVw.Txid,
&i.PaymentVtxoVw.Vout,
&i.PaymentVtxoVw.Pubkey,
&i.PaymentVtxoVw.Amount,
&i.PaymentVtxoVw.PoolTx,
&i.PaymentVtxoVw.SpentBy,
@@ -446,7 +447,6 @@ func (q *Queries) SelectSweepableRounds(ctx context.Context) ([]SelectSweepableR
&i.PaymentVtxoVw.ExpireAt,
&i.PaymentVtxoVw.PaymentID,
&i.PaymentVtxoVw.RedeemTx,
&i.PaymentVtxoVw.Descriptor,
&i.PaymentVtxoVw.Pending,
); err != nil {
return nil, err
@@ -463,7 +463,7 @@ func (q *Queries) SelectSweepableRounds(ctx context.Context) ([]SelectSweepableR
}
const selectSweepableVtxos = `-- name: SelectSweepableVtxos :many
SELECT vtxo.txid, vtxo.vout, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx, vtxo.descriptor, vtxo.pending FROM vtxo
SELECT vtxo.txid, vtxo.vout, vtxo.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx, vtxo.pending FROM vtxo
WHERE redeemed = false AND swept = false
`
@@ -483,6 +483,7 @@ func (q *Queries) SelectSweepableVtxos(ctx context.Context) ([]SelectSweepableVt
if err := rows.Scan(
&i.Vtxo.Txid,
&i.Vtxo.Vout,
&i.Vtxo.Pubkey,
&i.Vtxo.Amount,
&i.Vtxo.PoolTx,
&i.Vtxo.SpentBy,
@@ -492,7 +493,6 @@ func (q *Queries) SelectSweepableVtxos(ctx context.Context) ([]SelectSweepableVt
&i.Vtxo.ExpireAt,
&i.Vtxo.PaymentID,
&i.Vtxo.RedeemTx,
&i.Vtxo.Descriptor,
&i.Vtxo.Pending,
); err != nil {
return nil, err
@@ -512,8 +512,8 @@ const selectSweptRounds = `-- name: SelectSweptRounds :many
SELECT round.id, round.starting_timestamp, round.ending_timestamp, round.ended, round.failed, round.stage_code, round.txid, round.unsigned_tx, round.connector_address, round.dust_amount, round.version, round.swept,
round_payment_vw.id, round_payment_vw.round_id,
round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf,
payment_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.descriptor, payment_vtxo_vw.pending
payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, payment_receiver_vw.onchain_address, payment_receiver_vw.amount,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.pending
FROM round
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id
@@ -564,11 +564,12 @@ func (q *Queries) SelectSweptRounds(ctx context.Context) ([]SelectSweptRoundsRow
&i.RoundTxVw.ParentTxid,
&i.RoundTxVw.IsLeaf,
&i.PaymentReceiverVw.PaymentID,
&i.PaymentReceiverVw.Descriptor,
&i.PaymentReceiverVw.Amount,
&i.PaymentReceiverVw.Pubkey,
&i.PaymentReceiverVw.OnchainAddress,
&i.PaymentReceiverVw.Amount,
&i.PaymentVtxoVw.Txid,
&i.PaymentVtxoVw.Vout,
&i.PaymentVtxoVw.Pubkey,
&i.PaymentVtxoVw.Amount,
&i.PaymentVtxoVw.PoolTx,
&i.PaymentVtxoVw.SpentBy,
@@ -578,7 +579,6 @@ func (q *Queries) SelectSweptRounds(ctx context.Context) ([]SelectSweptRoundsRow
&i.PaymentVtxoVw.ExpireAt,
&i.PaymentVtxoVw.PaymentID,
&i.PaymentVtxoVw.RedeemTx,
&i.PaymentVtxoVw.Descriptor,
&i.PaymentVtxoVw.Pending,
); err != nil {
return nil, err
@@ -595,7 +595,7 @@ func (q *Queries) SelectSweptRounds(ctx context.Context) ([]SelectSweptRoundsRow
}
const selectVtxoByOutpoint = `-- name: SelectVtxoByOutpoint :one
SELECT vtxo.txid, vtxo.vout, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx, vtxo.descriptor, vtxo.pending FROM vtxo
SELECT vtxo.txid, vtxo.vout, vtxo.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx, vtxo.pending FROM vtxo
WHERE txid = ? AND vout = ?
`
@@ -614,6 +614,7 @@ func (q *Queries) SelectVtxoByOutpoint(ctx context.Context, arg SelectVtxoByOutp
err := row.Scan(
&i.Vtxo.Txid,
&i.Vtxo.Vout,
&i.Vtxo.Pubkey,
&i.Vtxo.Amount,
&i.Vtxo.PoolTx,
&i.Vtxo.SpentBy,
@@ -623,14 +624,13 @@ func (q *Queries) SelectVtxoByOutpoint(ctx context.Context, arg SelectVtxoByOutp
&i.Vtxo.ExpireAt,
&i.Vtxo.PaymentID,
&i.Vtxo.RedeemTx,
&i.Vtxo.Descriptor,
&i.Vtxo.Pending,
)
return i, err
}
const selectVtxosByPoolTxid = `-- name: SelectVtxosByPoolTxid :many
SELECT vtxo.txid, vtxo.vout, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx, vtxo.descriptor, vtxo.pending FROM vtxo
SELECT vtxo.txid, vtxo.vout, vtxo.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx, vtxo.pending FROM vtxo
WHERE pool_tx = ?
`
@@ -650,6 +650,7 @@ func (q *Queries) SelectVtxosByPoolTxid(ctx context.Context, poolTx string) ([]S
if err := rows.Scan(
&i.Vtxo.Txid,
&i.Vtxo.Vout,
&i.Vtxo.Pubkey,
&i.Vtxo.Amount,
&i.Vtxo.PoolTx,
&i.Vtxo.SpentBy,
@@ -659,7 +660,6 @@ func (q *Queries) SelectVtxosByPoolTxid(ctx context.Context, poolTx string) ([]S
&i.Vtxo.ExpireAt,
&i.Vtxo.PaymentID,
&i.Vtxo.RedeemTx,
&i.Vtxo.Descriptor,
&i.Vtxo.Pending,
); err != nil {
return nil, err
@@ -721,26 +721,26 @@ func (q *Queries) UpsertPayment(ctx context.Context, arg UpsertPaymentParams) er
}
const upsertReceiver = `-- name: UpsertReceiver :exec
INSERT INTO receiver (payment_id, descriptor, amount, onchain_address) VALUES (?, ?, ?, ?)
ON CONFLICT(payment_id, descriptor) DO UPDATE SET
INSERT INTO receiver (payment_id, pubkey, onchain_address, amount) VALUES (?, ?, ?, ?)
ON CONFLICT(payment_id, pubkey, onchain_address) DO UPDATE SET
amount = EXCLUDED.amount,
onchain_address = EXCLUDED.onchain_address,
descriptor = EXCLUDED.descriptor
pubkey = EXCLUDED.pubkey,
onchain_address = EXCLUDED.onchain_address
`
type UpsertReceiverParams struct {
PaymentID string
Descriptor string
Pubkey sql.NullString
OnchainAddress sql.NullString
Amount int64
OnchainAddress string
}
func (q *Queries) UpsertReceiver(ctx context.Context, arg UpsertReceiverParams) error {
_, err := q.db.ExecContext(ctx, upsertReceiver,
arg.PaymentID,
arg.Descriptor,
arg.Amount,
arg.Pubkey,
arg.OnchainAddress,
arg.Amount,
)
return err
}
@@ -847,9 +847,9 @@ func (q *Queries) UpsertTransaction(ctx context.Context, arg UpsertTransactionPa
}
const upsertVtxo = `-- name: UpsertVtxo :exec
INSERT INTO vtxo (txid, vout, descriptor, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, redeem_tx, pending)
INSERT INTO vtxo (txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, redeem_tx, pending)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET
descriptor = EXCLUDED.descriptor,
pubkey = EXCLUDED.pubkey,
amount = EXCLUDED.amount,
pool_tx = EXCLUDED.pool_tx,
spent_by = EXCLUDED.spent_by,
@@ -862,25 +862,25 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SE
`
type UpsertVtxoParams struct {
Txid string
Vout int64
Descriptor sql.NullString
Amount int64
PoolTx string
SpentBy string
Spent bool
Redeemed bool
Swept bool
ExpireAt int64
RedeemTx sql.NullString
Pending bool
Txid string
Vout int64
Pubkey string
Amount int64
PoolTx string
SpentBy string
Spent bool
Redeemed bool
Swept bool
ExpireAt int64
RedeemTx sql.NullString
Pending bool
}
func (q *Queries) UpsertVtxo(ctx context.Context, arg UpsertVtxoParams) error {
_, err := q.db.ExecContext(ctx, upsertVtxo,
arg.Txid,
arg.Vout,
arg.Descriptor,
arg.Pubkey,
arg.Amount,
arg.PoolTx,
arg.SpentBy,

View File

@@ -44,11 +44,11 @@ INSERT INTO payment (id, round_id) VALUES (?, ?)
ON CONFLICT(id) DO UPDATE SET round_id = EXCLUDED.round_id;
-- name: UpsertReceiver :exec
INSERT INTO receiver (payment_id, descriptor, amount, onchain_address) VALUES (?, ?, ?, ?)
ON CONFLICT(payment_id, descriptor) DO UPDATE SET
INSERT INTO receiver (payment_id, pubkey, onchain_address, amount) VALUES (?, ?, ?, ?)
ON CONFLICT(payment_id, pubkey, onchain_address) DO UPDATE SET
amount = EXCLUDED.amount,
onchain_address = EXCLUDED.onchain_address,
descriptor = EXCLUDED.descriptor;
pubkey = EXCLUDED.pubkey,
onchain_address = EXCLUDED.onchain_address;
-- name: UpdateVtxoPaymentId :exec
UPDATE vtxo SET payment_id = ? WHERE txid = ? AND vout = ?;
@@ -112,9 +112,9 @@ SELECT id FROM round WHERE starting_timestamp > ? AND starting_timestamp < ?;
SELECT id FROM round;
-- name: UpsertVtxo :exec
INSERT INTO vtxo (txid, vout, descriptor, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, redeem_tx, pending)
INSERT INTO vtxo (txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, redeem_tx, pending)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET
descriptor = EXCLUDED.descriptor,
pubkey = EXCLUDED.pubkey,
amount = EXCLUDED.amount,
pool_tx = EXCLUDED.pool_tx,
spent_by = EXCLUDED.spent_by,
@@ -135,7 +135,7 @@ WHERE redeemed = false;
-- name: SelectNotRedeemedVtxosWithPubkey :many
SELECT sqlc.embed(vtxo) FROM vtxo
WHERE redeemed = false AND INSTR(descriptor, ?) > 0;
WHERE redeemed = false AND pubkey = ?;
-- name: SelectVtxoByOutpoint :one
SELECT sqlc.embed(vtxo) FROM vtxo

View File

@@ -37,20 +37,21 @@ func (v *vxtoRepository) AddVtxos(ctx context.Context, vtxos []domain.Vtxo) erro
txBody := func(querierWithTx *queries.Queries) error {
for i := range vtxos {
vtxo := vtxos[i]
if err := querierWithTx.UpsertVtxo(
ctx, queries.UpsertVtxoParams{
Txid: vtxo.Txid,
Vout: int64(vtxo.VOut),
Descriptor: sql.NullString{String: vtxo.Descriptor, Valid: true},
Amount: int64(vtxo.Amount),
PoolTx: vtxo.RoundTxid,
SpentBy: vtxo.SpentBy,
Spent: vtxo.Spent,
Redeemed: vtxo.Redeemed,
Swept: vtxo.Swept,
ExpireAt: vtxo.ExpireAt,
RedeemTx: sql.NullString{String: vtxo.RedeemTx, Valid: true},
Pending: vtxo.Pending,
Txid: vtxo.Txid,
Vout: int64(vtxo.VOut),
Pubkey: vtxo.Pubkey,
Amount: int64(vtxo.Amount),
PoolTx: vtxo.RoundTxid,
SpentBy: vtxo.SpentBy,
Spent: vtxo.Spent,
Redeemed: vtxo.Redeemed,
Swept: vtxo.Swept,
ExpireAt: vtxo.ExpireAt,
RedeemTx: sql.NullString{String: vtxo.RedeemTx, Valid: true},
Pending: vtxo.Pending,
},
); err != nil {
return err
@@ -81,10 +82,6 @@ func (v *vxtoRepository) GetAllVtxos(ctx context.Context, pubkey string) ([]doma
var rows []queries.Vtxo
if withPubkey {
if len(pubkey) == 66 {
pubkey = pubkey[2:]
}
res, err := v.querier.SelectNotRedeemedVtxosWithPubkey(ctx, pubkey)
if err != nil {
return nil, nil, err
@@ -253,10 +250,8 @@ func rowToVtxo(row queries.Vtxo) domain.Vtxo {
Txid: row.Txid,
VOut: uint32(row.Vout),
},
Receiver: domain.Receiver{
Descriptor: row.Descriptor.String,
Amount: uint64(row.Amount),
},
Amount: uint64(row.Amount),
Pubkey: row.Pubkey,
RoundTxid: row.PoolTx,
SpentBy: row.SpentBy,
Spent: row.Spent,

View File

@@ -84,6 +84,7 @@ func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx strin
func (b *txBuilder) BuildForfeitTxs(
poolTx string,
payments []domain.Payment,
descriptors map[domain.VtxoKey]string,
minRelayFeeRate chainfee.SatPerKVByte,
) (connectors []string, forfeitTxs []string, err error) {
connectorAddress, err := b.getConnectorAddress(poolTx)
@@ -106,7 +107,7 @@ func (b *txBuilder) BuildForfeitTxs(
return nil, nil, err
}
forfeitTxs, err = b.createForfeitTxs(payments, connectorTxs, connectorAmount, minRelayFeeRate)
forfeitTxs, err = b.createForfeitTxs(payments, descriptors, connectorTxs, connectorAmount, minRelayFeeRate)
if err != nil {
return nil, nil, err
}
@@ -147,13 +148,13 @@ func (b *txBuilder) BuildRoundTx(
return "", nil, "", err
}
receivers, err := getOffchainReceivers(payments)
vtxosLeaves, err := getOutputVtxosLeaves(payments)
if err != nil {
return "", nil, "", err
}
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err = tree.CraftCongestionTree(
b.onchainNetwork().AssetID, aspPubkey, receivers, feeSatsPerNode, b.roundLifetime,
b.onchainNetwork().AssetID, aspPubkey, vtxosLeaves, feeSatsPerNode, b.roundLifetime,
)
if err != nil {
return "", nil, "", err
@@ -362,7 +363,10 @@ func (b *txBuilder) FindLeaves(
}
func (b *txBuilder) BuildAsyncPaymentTransactions(
_ []domain.Vtxo, _ *secp256k1.PublicKey, _ []domain.Receiver,
_ []domain.Vtxo,
_ map[domain.VtxoKey]string,
_ map[domain.VtxoKey]chainhash.Hash,
_ []domain.Receiver,
) (string, error) {
return "", fmt.Errorf("not implemented")
}
@@ -396,7 +400,6 @@ func (b *txBuilder) createPoolTx(
return nil, err
}
receivers := getOnchainReceivers(payments)
nbOfInputs := countSpentVtxos(payments)
connectorsAmount := (dustAmount + connectorMinRelayFee) * nbOfInputs
if nbOfInputs > 1 {
@@ -424,21 +427,17 @@ func (b *txBuilder) createPoolTx(
})
}
for _, receiver := range receivers {
targetAmount += receiver.Amount
receiverScript, err := address.ToOutputScript(receiver.OnchainAddress)
if err != nil {
return nil, err
}
outputs = append(outputs, psetv2.OutputArgs{
Asset: b.onchainNetwork().AssetID,
Amount: receiver.Amount,
Script: receiverScript,
})
onchainOutputs, err := getOnchainOutputs(payments, b.onchainNetwork())
if err != nil {
return nil, err
}
for _, out := range onchainOutputs {
targetAmount += out.Amount
}
outputs = append(outputs, onchainOutputs...)
for _, in := range boardingInputs {
targetAmount -= in.Amount
}
@@ -786,6 +785,7 @@ func (b *txBuilder) createConnectors(
func (b *txBuilder) createForfeitTxs(
payments []domain.Payment,
descriptors map[domain.VtxoKey]string,
connectors []*psetv2.Pset,
connectorAmount uint64,
minRelayFeeRate chainfee.SatPerKVByte,
@@ -803,7 +803,12 @@ func (b *txBuilder) createForfeitTxs(
forfeitTxs := make([]string, 0)
for _, payment := range payments {
for _, vtxo := range payment.Inputs {
offchainScript, err := tree.ParseVtxoScript(vtxo.Descriptor)
desc, ok := descriptors[vtxo.VtxoKey]
if !ok {
return nil, fmt.Errorf("descriptor not found for vtxo %s:%d", vtxo.VtxoKey.Txid, vtxo.VtxoKey.VOut)
}
offchainScript, err := tree.ParseVtxoScript(desc)
if err != nil {
return nil, err
}

View File

@@ -113,7 +113,7 @@ func TestBuildForfeitTxs(t *testing.T) {
t.Run("valid", func(t *testing.T) {
for _, f := range fixtures.Valid {
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
f.PoolTx, f.Payments, minRelayFeeRate,
f.PoolTx, f.Payments, f.Descriptors, minRelayFeeRate,
)
require.NoError(t, err)
require.Len(t, connectors, f.ExpectedNumOfConnectors)
@@ -151,7 +151,7 @@ func TestBuildForfeitTxs(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
for _, f := range fixtures.Invalid {
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
f.PoolTx, f.Payments, minRelayFeeRate,
f.PoolTx, f.Payments, f.Descriptors, minRelayFeeRate,
)
require.EqualError(t, err, f.ExpectedErr)
require.Empty(t, connectors)
@@ -215,6 +215,7 @@ func parsePoolTxFixtures() (*poolTxFixtures, error) {
type forfeitTxsFixtures struct {
Valid []struct {
Payments []domain.Payment
Descriptors map[domain.VtxoKey]string
ExpectedNumOfConnectors int
ExpectedNumOfForfeitTxs int
PoolTx string
@@ -222,6 +223,7 @@ type forfeitTxsFixtures struct {
}
Invalid []struct {
Payments []domain.Payment
Descriptors map[domain.VtxoKey]string
ExpectedErr string
PoolTx string
}
@@ -244,6 +246,42 @@ func parseForfeitTxsFixtures() (*forfeitTxsFixtures, error) {
return nil, err
}
valid := vv["valid"].([]interface{})
for i, v := range valid {
val := v.(map[string]interface{})
payments := val["payments"].([]interface{})
descriptors := make(map[domain.VtxoKey]string)
for _, p := range payments {
inputs := p.(map[string]interface{})["inputs"].([]interface{})
for _, in := range inputs {
inMap := in.(map[string]interface{})
descriptors[domain.VtxoKey{
Txid: inMap["txid"].(string),
VOut: uint32(inMap["vout"].(float64)),
}] = inMap["descriptor"].(string)
}
}
fixtures.Valid[i].Descriptors = descriptors
}
invalid := vv["invalid"].([]interface{})
for i, v := range invalid {
val := v.(map[string]interface{})
payments := val["payments"].([]interface{})
descriptors := make(map[domain.VtxoKey]string)
for _, p := range payments {
inputs := p.(map[string]interface{})["inputs"].([]interface{})
for _, in := range inputs {
inMap := in.(map[string]interface{})
descriptors[domain.VtxoKey{
Txid: inMap["txid"].(string),
VOut: uint32(inMap["vout"].(float64)),
}] = inMap["descriptor"].(string)
}
}
fixtures.Invalid[i].Descriptors = descriptors
}
return &fixtures, nil
}

View File

@@ -9,14 +9,14 @@
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 1100
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1100
}
]
@@ -33,18 +33,18 @@
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 1100
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 600
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]
@@ -61,18 +61,18 @@
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 1100
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 600
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]
@@ -83,18 +83,18 @@
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 1100
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 600
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]
@@ -105,18 +105,18 @@
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 1100
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 600
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]
@@ -133,58 +133,58 @@
{
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 1000
},
{
"txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 1000
},
{
"txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909",
"vout": 1,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 500
},
{
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
"vout": 0,
"pubkey":"0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 1000
},
{
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
"vout": 1,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 1000
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]
@@ -206,25 +206,25 @@
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 600
},
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 1,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
"amount": 500
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 600
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]

View File

@@ -45,34 +45,38 @@ func getPsetId(pset *psetv2.Pset) (string, error) {
return utx.TxHash().String(), nil
}
func getOnchainReceivers(
payments []domain.Payment,
) []domain.Receiver {
receivers := make([]domain.Receiver, 0)
func getOnchainOutputs(
payments []domain.Payment, net *network.Network,
) ([]psetv2.OutputArgs, error) {
outputs := make([]psetv2.OutputArgs, 0)
for _, payment := range payments {
for _, receiver := range payment.Receivers {
if receiver.IsOnchain() {
receivers = append(receivers, receiver)
}
}
}
return receivers
}
func getOffchainReceivers(
payments []domain.Payment,
) ([]tree.Receiver, error) {
receivers := make([]tree.Receiver, 0)
for _, payment := range payments {
for _, receiver := range payment.Receivers {
if !receiver.IsOnchain() {
vtxoScript, err := tree.ParseVtxoScript(receiver.Descriptor)
receiverScript, err := address.ToOutputScript(receiver.OnchainAddress)
if err != nil {
return nil, err
}
receivers = append(receivers, tree.Receiver{
Script: vtxoScript,
outputs = append(outputs, psetv2.OutputArgs{
Script: receiverScript,
Amount: receiver.Amount,
Asset: net.AssetID,
})
}
}
}
return outputs, nil
}
func getOutputVtxosLeaves(
payments []domain.Payment,
) ([]tree.VtxoLeaf, error) {
receivers := make([]tree.VtxoLeaf, 0)
for _, payment := range payments {
for _, receiver := range payment.Receivers {
if !receiver.IsOnchain() {
receivers = append(receivers, tree.VtxoLeaf{
Pubkey: receiver.Pubkey,
Amount: receiver.Amount,
})
}

View File

@@ -228,7 +228,10 @@ func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx strin
}
func (b *txBuilder) BuildForfeitTxs(
poolTx string, payments []domain.Payment, minRelayFeeRate chainfee.SatPerKVByte,
poolTx string,
payments []domain.Payment,
descriptors map[domain.VtxoKey]string,
minRelayFeeRate chainfee.SatPerKVByte,
) (connectors []string, forfeitTxs []string, err error) {
connectorPkScript, err := b.getConnectorPkScript(poolTx)
if err != nil {
@@ -245,7 +248,7 @@ func (b *txBuilder) BuildForfeitTxs(
return nil, nil, err
}
forfeitTxs, err = b.createForfeitTxs(payments, connectorTxs, minRelayFeeRate)
forfeitTxs, err = b.createForfeitTxs(payments, descriptors, connectorTxs, minRelayFeeRate)
if err != nil {
return nil, nil, err
}
@@ -271,7 +274,7 @@ func (b *txBuilder) BuildRoundTx(
return "", nil, "", fmt.Errorf("missing cosigners")
}
receivers, err := getOffchainReceivers(payments)
receivers, err := getOutputVtxosLeaves(payments)
if err != nil {
return "", nil, "", err
}
@@ -399,7 +402,10 @@ func (b *txBuilder) FindLeaves(congestionTree tree.CongestionTree, fromtxid stri
}
func (b *txBuilder) BuildAsyncPaymentTransactions(
vtxos []domain.Vtxo, aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver,
vtxos []domain.Vtxo,
descriptors map[domain.VtxoKey]string,
forfeitsLeaves map[domain.VtxoKey]chainhash.Hash,
receivers []domain.Receiver,
) (string, error) {
if len(vtxos) <= 0 {
return "", fmt.Errorf("missing vtxos")
@@ -412,6 +418,16 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
redeemTxWeightEstimator := &input.TxWeightEstimator{}
for index, vtxo := range vtxos {
desc, ok := descriptors[vtxo.VtxoKey]
if !ok {
return "", fmt.Errorf("missing descriptor for vtxo %s", vtxo.VtxoKey)
}
forfeitLeafHash, ok := forfeitsLeaves[vtxo.VtxoKey]
if !ok {
return "", fmt.Errorf("missing forfeit leaf hash for vtxo %s", vtxo.VtxoKey)
}
if vtxo.Spent || vtxo.Redeemed || vtxo.Swept {
return "", fmt.Errorf("all vtxos must be unspent")
}
@@ -426,7 +442,7 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
Index: vtxo.VOut,
}
vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Descriptor)
vtxoScript, err := bitcointree.ParseVtxoScript(desc)
if err != nil {
return "", err
}
@@ -446,41 +462,27 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
PkScript: vtxoOutputScript,
}
if defaultVtxoScript, ok := vtxoScript.(*bitcointree.DefaultVtxoScript); ok {
forfeitLeaf := bitcointree.MultisigClosure{
Pubkey: defaultVtxoScript.Owner,
AspPubkey: defaultVtxoScript.Asp,
}
tapLeaf, err := forfeitLeaf.Leaf()
if err != nil {
return "", err
}
leafProof, err := vtxoTree.GetTaprootMerkleProof(tapLeaf.TapHash())
if err != nil {
return "", err
}
tapscripts[index] = &psbt.TaprootTapLeafScript{
ControlBlock: leafProof.ControlBlock,
Script: leafProof.Script,
LeafVersion: txscript.BaseLeafVersion,
}
ctrlBlock, err := txscript.ParseControlBlock(leafProof.ControlBlock)
if err != nil {
return "", err
}
redeemTxWeightEstimator.AddTapscriptInput(64*2, &waddrmgr.Tapscript{
RevealedScript: leafProof.Script,
ControlBlock: ctrlBlock,
})
} else {
return "", fmt.Errorf("vtxo %s:%d script is not default script, can't be async spent", vtxo.Txid, vtxo.VOut)
leafProof, err := vtxoTree.GetTaprootMerkleProof(forfeitLeafHash)
if err != nil {
return "", err
}
tapscripts[index] = &psbt.TaprootTapLeafScript{
ControlBlock: leafProof.ControlBlock,
Script: leafProof.Script,
LeafVersion: txscript.BaseLeafVersion,
}
ctrlBlock, err := txscript.ParseControlBlock(leafProof.ControlBlock)
if err != nil {
return "", err
}
redeemTxWeightEstimator.AddTapscriptInput(64*2+40, &waddrmgr.Tapscript{
RevealedScript: leafProof.Script,
ControlBlock: ctrlBlock,
})
ins = append(ins, vtxoOutpoint)
}
@@ -498,17 +500,21 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
}
for i, receiver := range receivers {
offchainScript, err := bitcointree.ParseVtxoScript(receiver.Descriptor)
if receiver.IsOnchain() {
return "", fmt.Errorf("receiver %d is onchain", i)
}
pubkeyBytes, err := hex.DecodeString(receiver.Pubkey)
if err != nil {
return "", err
}
receiverVtxoTaprootKey, _, err := offchainScript.TapTree()
pubkey, err := schnorr.ParsePubKey(pubkeyBytes)
if err != nil {
return "", err
}
newVtxoScript, err := common.P2TRScript(receiverVtxoTaprootKey)
newVtxoScript, err := common.P2TRScript(pubkey)
if err != nil {
return "", err
}
@@ -588,7 +594,6 @@ func (b *txBuilder) createRoundTx(
connectorAmount := dustLimit
receivers := getOnchainReceivers(payments)
nbOfInputs := countSpentVtxos(payments)
connectorsAmount := (connectorAmount + connectorMinRelayFee) * nbOfInputs
if nbOfInputs > 1 {
@@ -614,25 +619,17 @@ func (b *txBuilder) createRoundTx(
})
}
for _, receiver := range receivers {
targetAmount += receiver.Amount
receiverAddr, err := btcutil.DecodeAddress(receiver.OnchainAddress, b.onchainNetwork())
if err != nil {
return nil, err
}
receiverScript, err := txscript.PayToAddrScript(receiverAddr)
if err != nil {
return nil, err
}
outputs = append(outputs, &wire.TxOut{
Value: int64(receiver.Amount),
PkScript: receiverScript,
})
onchainOutputs, err := getOnchainOutputs(payments, b.onchainNetwork())
if err != nil {
return nil, err
}
for _, output := range onchainOutputs {
targetAmount += uint64(output.Value)
}
outputs = append(outputs, onchainOutputs...)
for _, input := range boardingInputs {
targetAmount -= input.Amount
}
@@ -1016,6 +1013,7 @@ func (b *txBuilder) minRelayFeeTreeTx() (uint64, error) {
func (b *txBuilder) createForfeitTxs(
payments []domain.Payment,
descriptors map[domain.VtxoKey]string,
connectors []*psbt.Packet,
minRelayFeeRate chainfee.SatPerKVByte,
) ([]string, error) {
@@ -1042,7 +1040,12 @@ func (b *txBuilder) createForfeitTxs(
forfeitTxs := make([]string, 0)
for _, payment := range payments {
for _, vtxo := range payment.Inputs {
offchainscript, err := bitcointree.ParseVtxoScript(vtxo.Descriptor)
desc, ok := descriptors[vtxo.VtxoKey]
if !ok {
return nil, err
}
offchainscript, err := bitcointree.ParseVtxoScript(desc)
if err != nil {
return nil, err
}

View File

@@ -123,7 +123,7 @@ func TestBuildForfeitTxs(t *testing.T) {
t.Run("valid", func(t *testing.T) {
for _, f := range fixtures.Valid {
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
f.PoolTx, f.Payments, minRelayFeeRate,
f.PoolTx, f.Payments, f.Descriptors, minRelayFeeRate,
)
require.NoError(t, err)
require.Len(t, connectors, f.ExpectedNumOfConnectors)
@@ -161,7 +161,7 @@ func TestBuildForfeitTxs(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
for _, f := range fixtures.Invalid {
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
f.PoolTx, f.Payments, minRelayFeeRate,
f.PoolTx, f.Payments, f.Descriptors, minRelayFeeRate,
)
require.EqualError(t, err, f.ExpectedErr)
require.Empty(t, connectors)
@@ -225,6 +225,7 @@ func parsePoolTxFixtures() (*poolTxFixtures, error) {
type forfeitTxsFixtures struct {
Valid []struct {
Payments []domain.Payment
Descriptors map[domain.VtxoKey]string
ExpectedNumOfConnectors int
ExpectedNumOfForfeitTxs int
PoolTx string
@@ -232,6 +233,7 @@ type forfeitTxsFixtures struct {
}
Invalid []struct {
Payments []domain.Payment
Descriptors map[domain.VtxoKey]string
ExpectedErr string
PoolTx string
}
@@ -254,5 +256,41 @@ func parseForfeitTxsFixtures() (*forfeitTxsFixtures, error) {
return nil, err
}
valid := vv["valid"].([]interface{})
for i, v := range valid {
val := v.(map[string]interface{})
payments := val["payments"].([]interface{})
descriptors := make(map[domain.VtxoKey]string)
for _, p := range payments {
inputs := p.(map[string]interface{})["inputs"].([]interface{})
for _, in := range inputs {
inMap := in.(map[string]interface{})
descriptors[domain.VtxoKey{
Txid: inMap["txid"].(string),
VOut: uint32(inMap["vout"].(float64)),
}] = inMap["descriptor"].(string)
}
}
fixtures.Valid[i].Descriptors = descriptors
}
invalid := vv["invalid"].([]interface{})
for i, v := range invalid {
val := v.(map[string]interface{})
payments := val["payments"].([]interface{})
descriptors := make(map[domain.VtxoKey]string)
for _, p := range payments {
inputs := p.(map[string]interface{})["inputs"].([]interface{})
for _, in := range inputs {
inMap := in.(map[string]interface{})
descriptors[domain.VtxoKey{
Txid: inMap["txid"].(string),
VOut: uint32(inMap["vout"].(float64)),
}] = inMap["descriptor"].(string)
}
}
fixtures.Invalid[i].Descriptors = descriptors
}
return &fixtures, nil
}

View File

@@ -9,13 +9,14 @@
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1100
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1100
}
]
@@ -32,17 +33,18 @@
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1100
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 600
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]
@@ -59,17 +61,18 @@
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1100
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 600
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]
@@ -80,17 +83,18 @@
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1100
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 600
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]
@@ -101,17 +105,18 @@
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1100
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 600
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]
@@ -128,54 +133,58 @@
{
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1000
},
{
"txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1000
},
{
"txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909",
"vout": 1,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 500
},
{
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1000
},
{
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
"vout": 1,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1000
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]
@@ -197,53 +206,58 @@
{
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1000
},
{
"txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1000
},
{
"txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909",
"vout": 1,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 500
},
{
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
"vout": 0,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1000
},
{
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
"vout": 1,
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"amount": 1000
}
],
"receivers": [
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 1000
},
{
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
"amount": 500
}
]
@@ -252,7 +266,7 @@
"poolTx": "cHNidP8BALICAAAAAnonOnsJBkHUUaKf/7fdS0/sVyBCgDPusYzGSZZiXPbtAAAAAAD/////VLtr81ZII3QJnXgrIwgcnbsq3aa4L3qdHOAn2evlFtEAAAAAAP////8CohIAAAAAAAAiUSBZarBUuSIHnlkuIoel9MmvexqTGK8jCZaRjt8L+Pb3s+gDAAAAAAAAIlEgI95L4kHEn2fAA+vysD+RIR4eD3AIQwc+FyCInJ8HivYAAAAAAAEBIOgDAAAAAAAAF6kU6p9IboLvs92Dpp/Zbj8BE3V9oDyHAAEBIOgDAAAAAAAAF6kU6p9IboLvs92Dpp/Zbj8BE3V9oDyHAAAA",
"poolTxid": "7c0c10756cdb9ab8e605f1c82e25989761308cf4c60e6a6f42b72d46144c4ce0",
"expectedNumOfForfeitTxs": 25,
"expectedNumOfConnectors": 4
"expectedNumOfConnectors": 4
}
],
"invalid": []

View File

@@ -1,47 +1,58 @@
package txbuilder
import (
"github.com/ark-network/ark/common/bitcointree"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/server/internal/core/domain"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
func getOnchainReceivers(
payments []domain.Payment,
) []domain.Receiver {
receivers := make([]domain.Receiver, 0)
func getOnchainOutputs(
payments []domain.Payment, network *chaincfg.Params,
) ([]*wire.TxOut, error) {
outputs := make([]*wire.TxOut, 0)
for _, payment := range payments {
for _, receiver := range payment.Receivers {
if receiver.IsOnchain() {
receivers = append(receivers, receiver)
}
}
}
return receivers
}
func getOffchainReceivers(
payments []domain.Payment,
) ([]bitcointree.Receiver, error) {
receivers := make([]bitcointree.Receiver, 0)
for _, payment := range payments {
for _, receiver := range payment.Receivers {
if !receiver.IsOnchain() {
vtxoScript, err := bitcointree.ParseVtxoScript(receiver.Descriptor)
receiverAddr, err := btcutil.DecodeAddress(receiver.OnchainAddress, network)
if err != nil {
return nil, err
}
receivers = append(receivers, bitcointree.Receiver{
Script: vtxoScript,
receiverScript, err := txscript.PayToAddrScript(receiverAddr)
if err != nil {
return nil, err
}
outputs = append(outputs, &wire.TxOut{
Value: int64(receiver.Amount),
PkScript: receiverScript,
})
}
}
}
return outputs, nil
}
func getOutputVtxosLeaves(
payments []domain.Payment,
) ([]tree.VtxoLeaf, error) {
leaves := make([]tree.VtxoLeaf, 0)
for _, payment := range payments {
for _, receiver := range payment.Receivers {
if !receiver.IsOnchain() {
leaves = append(leaves, tree.VtxoLeaf{
Pubkey: receiver.Pubkey,
Amount: receiver.Amount,
})
}
}
}
return receivers, nil
return leaves, nil
}
func countSpentVtxos(payments []domain.Payment) uint64 {

View File

@@ -338,7 +338,7 @@ func (h *handler) Ping(
func (h *handler) CreatePayment(
ctx context.Context, req *arkv1.CreatePaymentRequest,
) (*arkv1.CreatePaymentResponse, error) {
inputs, err := parseInputs(req.GetInputs())
inputs, err := parseAsyncPaymentInputs(req.GetInputs())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
@@ -353,12 +353,12 @@ func (h *handler) CreatePayment(
return nil, status.Error(codes.InvalidArgument, "output amount must be greater than 0")
}
if len(receiver.OnchainAddress) > 0 {
return nil, status.Error(codes.InvalidArgument, "onchain address is not supported as async payment destination")
if len(receiver.OnchainAddress) <= 0 && len(receiver.Pubkey) <= 0 {
return nil, status.Error(codes.InvalidArgument, "missing address")
}
if len(receiver.Descriptor) <= 0 {
return nil, status.Error(codes.InvalidArgument, "missing output descriptor")
if receiver.IsOnchain() {
return nil, status.Error(codes.InvalidArgument, "onchain outputs are not supported as async payment destination")
}
}
@@ -462,12 +462,12 @@ func (h *handler) GetRoundById(
func (h *handler) ListVtxos(
ctx context.Context, req *arkv1.ListVtxosRequest,
) (*arkv1.ListVtxosResponse, error) {
_, userPubkey, _, err := parseAddress(req.GetAddress())
_, err := parseAddress(req.GetAddress())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
spendableVtxos, spentVtxos, err := h.svc.ListVtxos(ctx, userPubkey)
spendableVtxos, spentVtxos, err := h.svc.ListVtxos(ctx, req.GetAddress())
if err != nil {
return nil, err
}

View File

@@ -1,25 +1,55 @@
package handlers
import (
"encoding/hex"
"fmt"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/server/internal/core/application"
"github.com/ark-network/ark/server/internal/core/domain"
"github.com/ark-network/ark/server/internal/core/ports"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// From interface type to app type
func parseAddress(addr string) (string, *secp256k1.PublicKey, *secp256k1.PublicKey, error) {
func parseAddress(addr string) (*common.Address, error) {
if len(addr) <= 0 {
return "", nil, nil, fmt.Errorf("missing address")
return nil, fmt.Errorf("missing address")
}
return common.DecodeAddress(addr)
}
func parseAsyncPaymentInputs(ins []*arkv1.AsyncPaymentInput) ([]application.AsyncPaymentInput, error) {
if len(ins) <= 0 {
return nil, fmt.Errorf("missing inputs")
}
inputs := make([]application.AsyncPaymentInput, 0, len(ins))
for _, input := range ins {
forfeitLeafHash, err := chainhash.NewHashFromStr(input.GetForfeitLeafHash())
if err != nil {
return nil, fmt.Errorf("invalid forfeit leaf hash: %s", err)
}
inputs = append(inputs, application.AsyncPaymentInput{
Input: ports.Input{
VtxoKey: domain.VtxoKey{
Txid: input.GetInput().GetOutpoint().GetTxid(),
VOut: input.GetInput().GetOutpoint().GetVout(),
},
Descriptor: input.GetInput().GetDescriptor_(),
},
ForfeitLeafHash: *forfeitLeafHash,
})
}
return inputs, nil
}
func parseInputs(ins []*arkv1.Input) ([]ports.Input, error) {
if len(ins) <= 0 {
return nil, fmt.Errorf("missing inputs")
@@ -39,26 +69,43 @@ func parseInputs(ins []*arkv1.Input) ([]ports.Input, error) {
return inputs, nil
}
func parseReceiver(out *arkv1.Output) (domain.Receiver, error) {
decodedAddr, err := common.DecodeAddress(out.GetAddress())
if err != nil {
// onchain address
return domain.Receiver{
Amount: out.GetAmount(),
OnchainAddress: out.GetAddress(),
}, nil
}
return domain.Receiver{
Amount: out.GetAmount(),
Pubkey: hex.EncodeToString(schnorr.SerializePubKey(decodedAddr.VtxoTapKey)),
}, nil
}
func parseReceivers(outs []*arkv1.Output) ([]domain.Receiver, error) {
receivers := make([]domain.Receiver, 0, len(outs))
for _, out := range outs {
if out.GetAmount() == 0 {
return nil, fmt.Errorf("missing output amount")
}
if len(out.GetAddress()) <= 0 && len(out.GetDescriptor_()) <= 0 {
if len(out.GetAddress()) <= 0 {
return nil, fmt.Errorf("missing output destination")
}
receivers = append(receivers, domain.Receiver{
Descriptor: out.GetDescriptor_(),
Amount: out.GetAmount(),
OnchainAddress: out.GetAddress(),
})
rcv, err := parseReceiver(out)
if err != nil {
return nil, err
}
receivers = append(receivers, rcv)
}
return receivers, nil
}
// From app typeto interface type
// From app type to interface type
type vtxoList []domain.Vtxo
@@ -70,15 +117,15 @@ func (v vtxoList) toProto() []*arkv1.Vtxo {
Txid: vv.Txid,
Vout: vv.VOut,
},
Descriptor_: vv.Descriptor,
Amount: vv.Amount,
RoundTxid: vv.RoundTxid,
Spent: vv.Spent,
ExpireAt: vv.ExpireAt,
SpentBy: vv.SpentBy,
Swept: vv.Swept,
RedeemTx: vv.RedeemTx,
Pending: vv.Pending,
Amount: vv.Amount,
RoundTxid: vv.RoundTxid,
Spent: vv.Spent,
ExpireAt: vv.ExpireAt,
SpentBy: vv.SpentBy,
Swept: vv.Swept,
RedeemTx: vv.RedeemTx,
Pending: vv.Pending,
Pubkey: vv.Pubkey,
})
}