mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-24 07:24:19 +01:00
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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user