mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 12:14:21 +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:
@@ -506,6 +506,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"v1AsyncPaymentInput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"input": {
|
||||||
|
"$ref": "#/definitions/v1Input"
|
||||||
|
},
|
||||||
|
"forfeitLeafHash": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"v1CompletePaymentRequest": {
|
"v1CompletePaymentRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -524,7 +535,7 @@
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"$ref": "#/definitions/v1Input"
|
"$ref": "#/definitions/v1AsyncPaymentInput"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": {
|
"outputs": {
|
||||||
@@ -717,9 +728,6 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uint64",
|
"format": "uint64",
|
||||||
"description": "Amount to send in satoshis."
|
"description": "Amount to send in satoshis."
|
||||||
},
|
|
||||||
"descriptor": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1046,9 +1054,6 @@
|
|||||||
"outpoint": {
|
"outpoint": {
|
||||||
"$ref": "#/definitions/v1Outpoint"
|
"$ref": "#/definitions/v1Outpoint"
|
||||||
},
|
},
|
||||||
"descriptor": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"spent": {
|
"spent": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@@ -1074,6 +1079,9 @@
|
|||||||
"amount": {
|
"amount": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uint64"
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
"pubkey": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,8 +187,13 @@ message PingResponse {
|
|||||||
|
|
||||||
/* Async Payment API messages */
|
/* Async Payment API messages */
|
||||||
|
|
||||||
|
message AsyncPaymentInput {
|
||||||
|
Input input = 1;
|
||||||
|
string forfeit_leaf_hash = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message CreatePaymentRequest {
|
message CreatePaymentRequest {
|
||||||
repeated Input inputs = 1;
|
repeated AsyncPaymentInput inputs = 1;
|
||||||
repeated Output outputs = 2;
|
repeated Output outputs = 2;
|
||||||
}
|
}
|
||||||
message CreatePaymentResponse {
|
message CreatePaymentResponse {
|
||||||
@@ -290,7 +295,6 @@ message Input {
|
|||||||
message Output {
|
message Output {
|
||||||
string address = 1; // onchain or off-chain
|
string address = 1; // onchain or off-chain
|
||||||
uint64 amount = 2; // Amount to send in satoshis.
|
uint64 amount = 2; // Amount to send in satoshis.
|
||||||
string descriptor = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Tree {
|
message Tree {
|
||||||
@@ -309,18 +313,17 @@ message Node {
|
|||||||
|
|
||||||
message Vtxo {
|
message Vtxo {
|
||||||
Outpoint outpoint = 1;
|
Outpoint outpoint = 1;
|
||||||
string descriptor = 2;
|
bool spent = 2;
|
||||||
bool spent = 3;
|
string round_txid = 3;
|
||||||
string round_txid = 4;
|
string spent_by = 4;
|
||||||
string spent_by = 5;
|
int64 expire_at = 5;
|
||||||
int64 expire_at = 6;
|
bool swept = 6;
|
||||||
bool swept = 7;
|
bool pending = 7;
|
||||||
bool pending = 8;
|
string redeem_tx = 8;
|
||||||
string redeem_tx = 9;
|
uint64 amount = 9;
|
||||||
uint64 amount = 10;
|
string pubkey = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
message GetTransactionsStreamRequest {}
|
message GetTransactionsStreamRequest {}
|
||||||
message GetTransactionsStreamResponse {
|
message GetTransactionsStreamResponse {
|
||||||
oneof tx {
|
oneof tx {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,10 @@
|
|||||||
package bitcointree
|
package bitcointree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common"
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||||
@@ -15,7 +17,7 @@ import (
|
|||||||
|
|
||||||
// CraftSharedOutput returns the taproot script and the amount of the initial root output
|
// CraftSharedOutput returns the taproot script and the amount of the initial root output
|
||||||
func CraftSharedOutput(
|
func CraftSharedOutput(
|
||||||
cosigners []*secp256k1.PublicKey, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
|
cosigners []*secp256k1.PublicKey, aspPubkey *secp256k1.PublicKey, receivers []tree.VtxoLeaf,
|
||||||
feeSatsPerNode uint64, roundLifetime int64,
|
feeSatsPerNode uint64, roundLifetime int64,
|
||||||
) ([]byte, int64, error) {
|
) ([]byte, int64, error) {
|
||||||
aggregatedKey, _, err := createAggregatedKeyWithSweep(
|
aggregatedKey, _, err := createAggregatedKeyWithSweep(
|
||||||
@@ -32,7 +34,7 @@ func CraftSharedOutput(
|
|||||||
|
|
||||||
amount := root.getAmount() + int64(feeSatsPerNode)
|
amount := root.getAmount() + int64(feeSatsPerNode)
|
||||||
|
|
||||||
scriptPubKey, err := taprootOutputScript(aggregatedKey.FinalKey)
|
scriptPubKey, err := common.P2TRScript(aggregatedKey.FinalKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -42,7 +44,7 @@ func CraftSharedOutput(
|
|||||||
|
|
||||||
// CraftCongestionTree creates all the tree's transactions
|
// CraftCongestionTree creates all the tree's transactions
|
||||||
func CraftCongestionTree(
|
func CraftCongestionTree(
|
||||||
initialInput *wire.OutPoint, cosigners []*secp256k1.PublicKey, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
|
initialInput *wire.OutPoint, cosigners []*secp256k1.PublicKey, aspPubkey *secp256k1.PublicKey, receivers []tree.VtxoLeaf,
|
||||||
feeSatsPerNode uint64, roundLifetime int64,
|
feeSatsPerNode uint64, roundLifetime int64,
|
||||||
) (tree.CongestionTree, error) {
|
) (tree.CongestionTree, error) {
|
||||||
aggregatedKey, sweepTapLeaf, err := createAggregatedKeyWithSweep(
|
aggregatedKey, sweepTapLeaf, err := createAggregatedKeyWithSweep(
|
||||||
@@ -108,8 +110,8 @@ type node interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type leaf struct {
|
type leaf struct {
|
||||||
vtxoScript VtxoScript
|
|
||||||
amount int64
|
amount int64
|
||||||
|
pubkey *secp256k1.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
type branch struct {
|
type branch struct {
|
||||||
@@ -142,12 +144,7 @@ func (l *leaf) getAmount() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *leaf) getOutputs() ([]*wire.TxOut, error) {
|
func (l *leaf) getOutputs() ([]*wire.TxOut, error) {
|
||||||
taprootKey, _, err := l.vtxoScript.TapTree()
|
script, err := common.P2TRScript(l.pubkey)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
script, err := taprootOutputScript(taprootKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -161,7 +158,7 @@ func (l *leaf) getOutputs() ([]*wire.TxOut, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *branch) getOutputs() ([]*wire.TxOut, error) {
|
func (b *branch) getOutputs() ([]*wire.TxOut, error) {
|
||||||
sharedOutputScript, err := taprootOutputScript(b.aggregatedKey.FinalKey)
|
sharedOutputScript, err := common.P2TRScript(b.aggregatedKey.FinalKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -246,7 +243,7 @@ func getTx(
|
|||||||
func createRootNode(
|
func createRootNode(
|
||||||
aggregatedKey *musig2.AggregateKey,
|
aggregatedKey *musig2.AggregateKey,
|
||||||
cosigners []*secp256k1.PublicKey,
|
cosigners []*secp256k1.PublicKey,
|
||||||
receivers []Receiver,
|
receivers []tree.VtxoLeaf,
|
||||||
feeSatsPerNode uint64,
|
feeSatsPerNode uint64,
|
||||||
) (root node, err error) {
|
) (root node, err error) {
|
||||||
if len(receivers) == 0 {
|
if len(receivers) == 0 {
|
||||||
@@ -255,9 +252,19 @@ func createRootNode(
|
|||||||
|
|
||||||
nodes := make([]node, 0, len(receivers))
|
nodes := make([]node, 0, len(receivers))
|
||||||
for _, r := range receivers {
|
for _, r := range receivers {
|
||||||
|
pubkeyBytes, err := hex.DecodeString(r.Pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey, err := schnorr.ParsePubKey(pubkeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
leafNode := &leaf{
|
leafNode := &leaf{
|
||||||
vtxoScript: r.Script,
|
|
||||||
amount: int64(r.Amount),
|
amount: int64(r.Amount),
|
||||||
|
pubkey: pubkey,
|
||||||
}
|
}
|
||||||
nodes = append(nodes, leafNode)
|
nodes = append(nodes, leafNode)
|
||||||
}
|
}
|
||||||
@@ -339,9 +346,3 @@ func createUpperLevel(nodes []node, aggregatedKey *musig2.AggregateKey, cosigner
|
|||||||
}
|
}
|
||||||
return pairs, nil
|
return pairs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func taprootOutputScript(taprootKey *secp256k1.PublicKey) ([]byte, error) {
|
|
||||||
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(
|
|
||||||
schnorr.SerializePubKey(taprootKey),
|
|
||||||
).Script()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common"
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
@@ -512,7 +513,7 @@ func prevOutFetcherFactory(
|
|||||||
func(partial *psbt.Packet) (txscript.PrevOutputFetcher, error),
|
func(partial *psbt.Packet) (txscript.PrevOutputFetcher, error),
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
pkscript, err := taprootOutputScript(finalAggregatedKey)
|
pkscript, err := common.P2TRScript(finalAggregatedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package bitcointree_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common/bitcointree"
|
"github.com/ark-network/ark/common/bitcointree"
|
||||||
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
@@ -44,7 +44,7 @@ func TestRoundTripSignTree(t *testing.T) {
|
|||||||
_, sharedOutputAmount, err := bitcointree.CraftSharedOutput(
|
_, sharedOutputAmount, err := bitcointree.CraftSharedOutput(
|
||||||
cosigners,
|
cosigners,
|
||||||
asp.PubKey(),
|
asp.PubKey(),
|
||||||
castReceivers(f.Receivers, asp.PubKey()),
|
castReceivers(f.Receivers),
|
||||||
minRelayFee,
|
minRelayFee,
|
||||||
lifetime,
|
lifetime,
|
||||||
)
|
)
|
||||||
@@ -58,7 +58,7 @@ func TestRoundTripSignTree(t *testing.T) {
|
|||||||
},
|
},
|
||||||
cosigners,
|
cosigners,
|
||||||
asp.PubKey(),
|
asp.PubKey(),
|
||||||
castReceivers(f.Receivers, asp.PubKey()),
|
castReceivers(f.Receivers),
|
||||||
minRelayFee,
|
minRelayFee,
|
||||||
lifetime,
|
lifetime,
|
||||||
)
|
)
|
||||||
@@ -222,29 +222,11 @@ type receiverFixture struct {
|
|||||||
Pubkey string `json:"pubkey"`
|
Pubkey string `json:"pubkey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r receiverFixture) toVtxoScript(asp *secp256k1.PublicKey) bitcointree.VtxoScript {
|
func castReceivers(receivers []receiverFixture) []tree.VtxoLeaf {
|
||||||
bytesKey, err := hex.DecodeString(r.Pubkey)
|
receiversOut := make([]tree.VtxoLeaf, 0, len(receivers))
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pubkey, err := secp256k1.ParsePubKey(bytesKey)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bitcointree.DefaultVtxoScript{
|
|
||||||
Owner: pubkey,
|
|
||||||
Asp: asp,
|
|
||||||
ExitDelay: exitDelay,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func castReceivers(receivers []receiverFixture, asp *secp256k1.PublicKey) []bitcointree.Receiver {
|
|
||||||
receiversOut := make([]bitcointree.Receiver, 0, len(receivers))
|
|
||||||
for _, r := range receivers {
|
for _, r := range receivers {
|
||||||
receiversOut = append(receiversOut, bitcointree.Receiver{
|
receiversOut = append(receiversOut, tree.VtxoLeaf{
|
||||||
Script: r.toVtxoScript(asp),
|
Pubkey: r.Pubkey,
|
||||||
Amount: uint64(r.Amount),
|
Amount: uint64(r.Amount),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
16
common/bitcointree/testdata/musig2.json
vendored
16
common/bitcointree/testdata/musig2.json
vendored
@@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -12,11 +12,11 @@
|
|||||||
{
|
{
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
"amount": 8000
|
"amount": 8000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -24,23 +24,23 @@
|
|||||||
{
|
{
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package bitcointree
|
|
||||||
|
|
||||||
type Receiver struct {
|
|
||||||
Script VtxoScript
|
|
||||||
Amount uint64
|
|
||||||
}
|
|
||||||
@@ -3,63 +3,68 @@ package common
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcutil/bech32"
|
"github.com/btcsuite/btcd/btcutil/bech32"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func EncodeAddress(
|
// Address represents an Ark address with HRP, ASP public key, and VTXO Taproot public key
|
||||||
hrp string, userKey, aspKey *secp256k1.PublicKey,
|
type Address struct {
|
||||||
) (addr string, err error) {
|
HRP string
|
||||||
if userKey == nil {
|
Asp *secp256k1.PublicKey
|
||||||
err = fmt.Errorf("missing public key")
|
VtxoTapKey *secp256k1.PublicKey
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if aspKey == nil {
|
|
||||||
err = fmt.Errorf("missing asp public key")
|
// Encode converts the address to its bech32m string representation
|
||||||
return
|
func (a *Address) Encode() (string, error) {
|
||||||
|
if a.Asp == nil {
|
||||||
|
return "", fmt.Errorf("missing asp public key")
|
||||||
}
|
}
|
||||||
if hrp != Liquid.Addr && hrp != LiquidTestNet.Addr {
|
if a.VtxoTapKey == nil {
|
||||||
err = fmt.Errorf("invalid prefix")
|
return "", fmt.Errorf("missing vtxo tap public key")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
combinedKey := append(
|
combinedKey := append(
|
||||||
aspKey.SerializeCompressed(), userKey.SerializeCompressed()...,
|
schnorr.SerializePubKey(a.Asp), schnorr.SerializePubKey(a.VtxoTapKey)...,
|
||||||
)
|
)
|
||||||
grp, err := bech32.ConvertBits(combinedKey, 8, 5, true)
|
grp, err := bech32.ConvertBits(combinedKey, 8, 5, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return "", err
|
||||||
}
|
}
|
||||||
addr, err = bech32.EncodeM(hrp, grp)
|
return bech32.EncodeM(a.HRP, grp)
|
||||||
return
|
}
|
||||||
|
|
||||||
|
// DecodeAddress parses a bech32m encoded address string and returns an Address object
|
||||||
|
func DecodeAddress(addr string) (*Address, error) {
|
||||||
|
if len(addr) == 0 {
|
||||||
|
return nil, fmt.Errorf("address is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeAddress(
|
|
||||||
addr string,
|
|
||||||
) (hrp string, userKey, aspKey *secp256k1.PublicKey, err error) {
|
|
||||||
prefix, buf, err := bech32.DecodeNoLimit(addr)
|
prefix, buf, err := bech32.DecodeNoLimit(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
if prefix != Liquid.Addr && prefix != LiquidTestNet.Addr && prefix != LiquidRegTest.Addr {
|
if prefix != Liquid.Addr && prefix != LiquidTestNet.Addr && prefix != LiquidRegTest.Addr {
|
||||||
err = fmt.Errorf("invalid prefix")
|
return nil, fmt.Errorf("invalid prefix")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
grp, err := bech32.ConvertBits(buf, 5, 8, false)
|
grp, err := bech32.ConvertBits(buf, 5, 8, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
aKey, err := secp256k1.ParsePubKey(grp[:33])
|
|
||||||
|
aKey, err := schnorr.ParsePubKey(grp[:32])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("failed to parse public key: %s", err)
|
return nil, fmt.Errorf("failed to parse public key: %s", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
uKey, err := secp256k1.ParsePubKey(grp[33:])
|
|
||||||
|
vtxoKey, err := schnorr.ParsePubKey(grp[32:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("failed to parse asp public key: %s", err)
|
return nil, fmt.Errorf("failed to parse asp public key: %s", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
hrp = prefix
|
|
||||||
userKey = uKey
|
return &Address{
|
||||||
aspKey = aKey
|
HRP: prefix,
|
||||||
return
|
Asp: aKey,
|
||||||
|
VtxoTapKey: vtxoKey,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,31 +40,29 @@ func TestAddressEncoding(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("valid", func(t *testing.T) {
|
t.Run("valid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Address.Valid {
|
for _, f := range fixtures.Address.Valid {
|
||||||
hrp, userKey, aspKey, err := common.DecodeAddress(f.Addr)
|
addr, err := common.DecodeAddress(f.Addr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, hrp)
|
require.NotEmpty(t, addr.HRP)
|
||||||
require.NotNil(t, userKey)
|
require.NotNil(t, addr.Asp)
|
||||||
require.NotNil(t, aspKey)
|
require.NotNil(t, addr.VtxoTapKey)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, f.ExpectedUserKey, hex.EncodeToString(userKey.SerializeCompressed()))
|
require.Equal(t, f.ExpectedUserKey, hex.EncodeToString(addr.VtxoTapKey.SerializeCompressed()))
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, f.ExpectedAspKey, hex.EncodeToString(aspKey.SerializeCompressed()))
|
require.Equal(t, f.ExpectedAspKey, hex.EncodeToString(addr.Asp.SerializeCompressed()))
|
||||||
|
|
||||||
addr, err := common.EncodeAddress(hrp, userKey, aspKey)
|
encoded, err := addr.Encode()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, f.Addr, addr)
|
require.Equal(t, f.Addr, encoded)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Address.Invalid {
|
for _, f := range fixtures.Address.Invalid {
|
||||||
hrp, userKey, aspKey, err := common.DecodeAddress(f.Addr)
|
addr, err := common.DecodeAddress(f.Addr)
|
||||||
require.EqualError(t, err, f.ExpectedError)
|
require.EqualError(t, err, f.ExpectedError)
|
||||||
require.Empty(t, hrp)
|
require.Nil(t, addr)
|
||||||
require.Nil(t, userKey)
|
|
||||||
require.Nil(t, aspKey)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
"address": {
|
"address": {
|
||||||
"valid": [
|
"valid": [
|
||||||
{
|
{
|
||||||
"addr": "ark1qgvdtj5ttpuhkldavhq8thtm5auyk0ec4dcmrfdgu0u5hgp9we22vqa7mdkrrulzu48law4zzvzz8k59hul0ayl2urt905we5wf6gee68sfrfj35",
|
"addr": "tark1x0lm8hhr2wc6n6lyemtyh9rz8rg2ftpkfun46aca56kjg3ws0tsztfpuanaquxc6faedvjk3tax0575y6perapg3e95654pk8r4fjecs5fyd2",
|
||||||
"expectedUserKey": "03bedb6c31f3e2e54ffebaa2130423da85bf3efe93eae0d657d1d9a393a4673a3c",
|
"expectedUserKey": "0225a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"expectedAspKey": "0218d5ca8b58797b7dbd65c075dd7ba7784b3f38ab71b1a5a8e3f94ba0257654a6"
|
"expectedAspKey": "0233ffb3dee353b1a9ebe4ced64b946238d0a4ac364f275d771da6ad2445d07ae0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"invalid": [
|
"invalid": [
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package tree
|
package tree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func CraftCongestionTree(
|
func CraftCongestionTree(
|
||||||
asset string, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
|
asset string, aspPubkey *secp256k1.PublicKey, receivers []VtxoLeaf,
|
||||||
feeSatsPerNode uint64, roundLifetime int64,
|
feeSatsPerNode uint64, roundLifetime int64,
|
||||||
) (
|
) (
|
||||||
buildCongestionTree TreeFactory,
|
buildCongestionTree TreeFactory,
|
||||||
@@ -41,9 +41,14 @@ func CraftCongestionTree(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type vtxoOutput struct {
|
||||||
|
pubkey *secp256k1.PublicKey
|
||||||
|
amount uint64
|
||||||
|
}
|
||||||
|
|
||||||
type node struct {
|
type node struct {
|
||||||
sweepKey *secp256k1.PublicKey
|
sweepKey *secp256k1.PublicKey
|
||||||
receivers []Receiver
|
receivers []vtxoOutput
|
||||||
left *node
|
left *node
|
||||||
right *node
|
right *node
|
||||||
asset string
|
asset string
|
||||||
@@ -61,7 +66,7 @@ func (n *node) isLeaf() bool {
|
|||||||
func (n *node) getAmount() uint64 {
|
func (n *node) getAmount() uint64 {
|
||||||
var amount uint64
|
var amount uint64
|
||||||
for _, r := range n.receivers {
|
for _, r := range n.receivers {
|
||||||
amount += r.Amount
|
amount += r.amount
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.isLeaf() {
|
if n.isLeaf() {
|
||||||
@@ -107,7 +112,7 @@ func (n *node) getChildren() []*node {
|
|||||||
|
|
||||||
func (n *node) getOutputs() ([]psetv2.OutputArgs, error) {
|
func (n *node) getOutputs() ([]psetv2.OutputArgs, error) {
|
||||||
if n.isLeaf() {
|
if n.isLeaf() {
|
||||||
taprootKey, _, err := n.getVtxoWitnessData()
|
taprootKey, err := n.getVtxoWitnessData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -168,7 +173,7 @@ func (n *node) getWitnessData() (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if n.isLeaf() {
|
if n.isLeaf() {
|
||||||
taprootKey, _, err := n.getVtxoWitnessData()
|
taprootKey, err := n.getVtxoWitnessData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -241,15 +246,14 @@ func (n *node) getWitnessData() (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) getVtxoWitnessData() (
|
func (n *node) getVtxoWitnessData() (
|
||||||
*secp256k1.PublicKey, common.TaprootTree, error,
|
*secp256k1.PublicKey, error,
|
||||||
) {
|
) {
|
||||||
if !n.isLeaf() {
|
if !n.isLeaf() {
|
||||||
return nil, nil, fmt.Errorf("cannot call vtxoWitness on a non-leaf node")
|
return nil, fmt.Errorf("cannot call vtxoWitness on a non-leaf node")
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver := n.receivers[0]
|
receiver := n.receivers[0]
|
||||||
|
return receiver.pubkey, nil
|
||||||
return receiver.Script.TapTree()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) getTreeNode(
|
func (n *node) getTreeNode(
|
||||||
@@ -373,7 +377,7 @@ func (n *node) createFinalCongestionTree() TreeFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createPartialCongestionTree(
|
func createPartialCongestionTree(
|
||||||
asset string, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
|
asset string, aspPubkey *secp256k1.PublicKey, receivers []VtxoLeaf,
|
||||||
feeSatsPerNode uint64, roundLifetime int64,
|
feeSatsPerNode uint64, roundLifetime int64,
|
||||||
) (root *node, err error) {
|
) (root *node, err error) {
|
||||||
if len(receivers) == 0 {
|
if len(receivers) == 0 {
|
||||||
@@ -382,9 +386,19 @@ func createPartialCongestionTree(
|
|||||||
|
|
||||||
nodes := make([]*node, 0, len(receivers))
|
nodes := make([]*node, 0, len(receivers))
|
||||||
for _, r := range receivers {
|
for _, r := range receivers {
|
||||||
|
pubkeyBytes, err := hex.DecodeString(r.Pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey, err := schnorr.ParsePubKey(pubkeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
leafNode := &node{
|
leafNode := &node{
|
||||||
sweepKey: aspPubkey,
|
sweepKey: aspPubkey,
|
||||||
receivers: []Receiver{r},
|
receivers: []vtxoOutput{{pubkey, r.Amount}},
|
||||||
asset: asset,
|
asset: asset,
|
||||||
feeSats: feeSatsPerNode,
|
feeSats: feeSatsPerNode,
|
||||||
roundLifetime: roundLifetime,
|
roundLifetime: roundLifetime,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
type TreeFactory func(outpoint psetv2.InputArgs) (CongestionTree, error)
|
type TreeFactory func(outpoint psetv2.InputArgs) (CongestionTree, error)
|
||||||
|
|
||||||
type Receiver struct {
|
type VtxoLeaf struct {
|
||||||
Script VtxoScript
|
Pubkey string
|
||||||
Amount uint64
|
Amount uint64
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func (a *arkClient) Receive(ctx context.Context) (string, string, error) {
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return offchainAddr, boardingAddr, nil
|
return offchainAddr.Address, boardingAddr.Address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *arkClient) GetTransactionEventChannel() chan types.TransactionEvent {
|
func (a *arkClient) GetTransactionEventChannel() chan types.TransactionEvent {
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common"
|
||||||
"github.com/ark-network/ark/common/bitcointree"
|
"github.com/ark-network/ark/common/bitcointree"
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
)
|
)
|
||||||
@@ -41,7 +45,7 @@ type ASPClient interface {
|
|||||||
) (<-chan RoundEventChannel, func(), error)
|
) (<-chan RoundEventChannel, func(), error)
|
||||||
Ping(ctx context.Context, paymentID string) (RoundEvent, error)
|
Ping(ctx context.Context, paymentID string) (RoundEvent, error)
|
||||||
CreatePayment(
|
CreatePayment(
|
||||||
ctx context.Context, inputs []Input, outputs []Output,
|
ctx context.Context, inputs []AsyncPaymentInput, outputs []Output,
|
||||||
) (string, error)
|
) (string, error)
|
||||||
CompletePayment(
|
CompletePayment(
|
||||||
ctx context.Context, signedRedeemTx string,
|
ctx context.Context, signedRedeemTx string,
|
||||||
@@ -79,9 +83,14 @@ type Input struct {
|
|||||||
Descriptor string
|
Descriptor string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AsyncPaymentInput struct {
|
||||||
|
Input
|
||||||
|
ForfeitLeafHash chainhash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
type Vtxo struct {
|
type Vtxo struct {
|
||||||
Outpoint
|
Outpoint
|
||||||
Descriptor string
|
Pubkey string
|
||||||
Amount uint64
|
Amount uint64
|
||||||
RoundTxid string
|
RoundTxid string
|
||||||
ExpiresAt *time.Time
|
ExpiresAt *time.Time
|
||||||
@@ -90,9 +99,33 @@ type Vtxo struct {
|
|||||||
SpentBy string
|
SpentBy string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v Vtxo) Address(asp *secp256k1.PublicKey, net common.Network) (string, error) {
|
||||||
|
pubkeyBytes, err := hex.DecodeString(v.Pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey, err := schnorr.ParsePubKey(pubkeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
a := &common.Address{
|
||||||
|
HRP: net.Addr,
|
||||||
|
Asp: asp,
|
||||||
|
VtxoTapKey: pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type DescriptorVtxo struct {
|
||||||
|
Vtxo
|
||||||
|
Descriptor string
|
||||||
|
}
|
||||||
|
|
||||||
type Output struct {
|
type Output struct {
|
||||||
Address string // onchain output address
|
Address string // onchain or offchain address
|
||||||
Descriptor string // offchain vtxo descriptor
|
|
||||||
Amount uint64
|
Amount uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,10 +238,10 @@ func (a *grpcClient) Ping(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *grpcClient) CreatePayment(
|
func (a *grpcClient) CreatePayment(
|
||||||
ctx context.Context, inputs []client.Input, outputs []client.Output,
|
ctx context.Context, inputs []client.AsyncPaymentInput, outputs []client.Output,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
req := &arkv1.CreatePaymentRequest{
|
req := &arkv1.CreatePaymentRequest{
|
||||||
Inputs: ins(inputs).toProto(),
|
Inputs: asyncIns(inputs).toProto(),
|
||||||
Outputs: outs(outputs).toProto(),
|
Outputs: outs(outputs).toProto(),
|
||||||
}
|
}
|
||||||
resp, err := a.svc.CreatePayment(ctx, req)
|
resp, err := a.svc.CreatePayment(ctx, req)
|
||||||
@@ -404,7 +404,7 @@ func vtxosFromProto(protoVtxos []*arkv1.Vtxo) []client.Vtxo {
|
|||||||
Txid: v.Outpoint.Txid,
|
Txid: v.Outpoint.Txid,
|
||||||
VOut: v.Outpoint.Vout,
|
VOut: v.Outpoint.Vout,
|
||||||
},
|
},
|
||||||
Descriptor: v.Descriptor_,
|
Pubkey: v.Pubkey,
|
||||||
Amount: v.Amount,
|
Amount: v.Amount,
|
||||||
RoundTxid: v.RoundTxid,
|
RoundTxid: v.RoundTxid,
|
||||||
ExpiresAt: &expiresAt,
|
ExpiresAt: &expiresAt,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ type out client.Output
|
|||||||
func (o out) toProto() *arkv1.Output {
|
func (o out) toProto() *arkv1.Output {
|
||||||
return &arkv1.Output{
|
return &arkv1.Output{
|
||||||
Address: o.Address,
|
Address: o.Address,
|
||||||
Descriptor_: o.Descriptor,
|
|
||||||
Amount: o.Amount,
|
Amount: o.Amount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +128,7 @@ func (v vtxo) toVtxo() client.Vtxo {
|
|||||||
Pending: v.GetPending(),
|
Pending: v.GetPending(),
|
||||||
RedeemTx: v.GetRedeemTx(),
|
RedeemTx: v.GetRedeemTx(),
|
||||||
SpentBy: v.GetSpentBy(),
|
SpentBy: v.GetSpentBy(),
|
||||||
Descriptor: v.GetDescriptor_(),
|
Pubkey: v.GetPubkey(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +152,23 @@ func toProtoInput(i client.Input) *arkv1.Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toAsyncProtoInput(i client.AsyncPaymentInput) *arkv1.AsyncPaymentInput {
|
||||||
|
return &arkv1.AsyncPaymentInput{
|
||||||
|
Input: toProtoInput(i.Input),
|
||||||
|
ForfeitLeafHash: i.ForfeitLeafHash.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type asyncIns []client.AsyncPaymentInput
|
||||||
|
|
||||||
|
func (i asyncIns) toProto() []*arkv1.AsyncPaymentInput {
|
||||||
|
list := make([]*arkv1.AsyncPaymentInput, 0, len(i))
|
||||||
|
for _, ii := range i {
|
||||||
|
list = append(list, toAsyncProtoInput(ii))
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
type ins []client.Input
|
type ins []client.Input
|
||||||
|
|
||||||
func (i ins) toProto() []*arkv1.Input {
|
func (i ins) toProto() []*arkv1.Input {
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ func (a *restClient) RegisterOutputsForNextRound(
|
|||||||
for _, o := range outputs {
|
for _, o := range outputs {
|
||||||
outs = append(outs, &models.V1Output{
|
outs = append(outs, &models.V1Output{
|
||||||
Address: o.Address,
|
Address: o.Address,
|
||||||
Descriptor: o.Descriptor,
|
|
||||||
Amount: strconv.Itoa(int(o.Amount)),
|
Amount: strconv.Itoa(int(o.Amount)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -337,16 +336,19 @@ func (a *restClient) Ping(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *restClient) CreatePayment(
|
func (a *restClient) CreatePayment(
|
||||||
ctx context.Context, inputs []client.Input, outputs []client.Output,
|
ctx context.Context, inputs []client.AsyncPaymentInput, outputs []client.Output,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
ins := make([]*models.V1Input, 0, len(inputs))
|
ins := make([]*models.V1AsyncPaymentInput, 0, len(inputs))
|
||||||
for _, i := range inputs {
|
for _, i := range inputs {
|
||||||
ins = append(ins, &models.V1Input{
|
ins = append(ins, &models.V1AsyncPaymentInput{
|
||||||
|
Input: &models.V1Input{
|
||||||
Outpoint: &models.V1Outpoint{
|
Outpoint: &models.V1Outpoint{
|
||||||
Txid: i.Txid,
|
Txid: i.Input.Txid,
|
||||||
Vout: int64(i.VOut),
|
Vout: int64(i.VOut),
|
||||||
},
|
},
|
||||||
Descriptor: i.Descriptor,
|
Descriptor: i.Input.Descriptor,
|
||||||
|
},
|
||||||
|
ForfeitLeafHash: i.ForfeitLeafHash.String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
outs := make([]*models.V1Output, 0, len(outputs))
|
outs := make([]*models.V1Output, 0, len(outputs))
|
||||||
@@ -354,7 +356,6 @@ func (a *restClient) CreatePayment(
|
|||||||
outs = append(outs, &models.V1Output{
|
outs = append(outs, &models.V1Output{
|
||||||
Address: o.Address,
|
Address: o.Address,
|
||||||
Amount: strconv.Itoa(int(o.Amount)),
|
Amount: strconv.Itoa(int(o.Amount)),
|
||||||
Descriptor: o.Descriptor,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
body := models.V1CreatePaymentRequest{
|
body := models.V1CreatePaymentRequest{
|
||||||
@@ -501,7 +502,7 @@ func (a *restClient) ListVtxos(
|
|||||||
Pending: v.Pending,
|
Pending: v.Pending,
|
||||||
RedeemTx: v.RedeemTx,
|
RedeemTx: v.RedeemTx,
|
||||||
SpentBy: v.SpentBy,
|
SpentBy: v.SpentBy,
|
||||||
Descriptor: v.Descriptor,
|
Pubkey: v.Pubkey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +532,7 @@ func (a *restClient) ListVtxos(
|
|||||||
RoundTxid: v.RoundTxid,
|
RoundTxid: v.RoundTxid,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
SpentBy: v.SpentBy,
|
SpentBy: v.SpentBy,
|
||||||
Descriptor: v.Descriptor,
|
Pubkey: v.Pubkey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,7 +696,7 @@ func vtxosFromRest(restVtxos []*models.V1Vtxo) []client.Vtxo {
|
|||||||
Txid: v.Outpoint.Txid,
|
Txid: v.Outpoint.Txid,
|
||||||
VOut: uint32(v.Outpoint.Vout),
|
VOut: uint32(v.Outpoint.Vout),
|
||||||
},
|
},
|
||||||
Descriptor: v.Descriptor,
|
Pubkey: v.Pubkey,
|
||||||
Amount: uint64(amount),
|
Amount: uint64(amount),
|
||||||
RoundTxid: v.RoundTxid,
|
RoundTxid: v.RoundTxid,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-openapi/errors"
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// V1AsyncPaymentInput v1 async payment input
|
||||||
|
//
|
||||||
|
// swagger:model v1AsyncPaymentInput
|
||||||
|
type V1AsyncPaymentInput struct {
|
||||||
|
|
||||||
|
// forfeit leaf hash
|
||||||
|
ForfeitLeafHash string `json:"forfeitLeafHash,omitempty"`
|
||||||
|
|
||||||
|
// input
|
||||||
|
Input *V1Input `json:"input,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates this v1 async payment input
|
||||||
|
func (m *V1AsyncPaymentInput) Validate(formats strfmt.Registry) error {
|
||||||
|
var res []error
|
||||||
|
|
||||||
|
if err := m.validateInput(formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *V1AsyncPaymentInput) validateInput(formats strfmt.Registry) error {
|
||||||
|
if swag.IsZero(m.Input) { // not required
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Input != nil {
|
||||||
|
if err := m.Input.Validate(formats); err != nil {
|
||||||
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
|
return ve.ValidateName("input")
|
||||||
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
|
return ce.ValidateName("input")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validate this v1 async payment input based on the context it is used
|
||||||
|
func (m *V1AsyncPaymentInput) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
var res []error
|
||||||
|
|
||||||
|
if err := m.contextValidateInput(ctx, formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *V1AsyncPaymentInput) contextValidateInput(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
|
||||||
|
if m.Input != nil {
|
||||||
|
|
||||||
|
if swag.IsZero(m.Input) { // not required
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.Input.ContextValidate(ctx, formats); err != nil {
|
||||||
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
|
return ve.ValidateName("input")
|
||||||
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
|
return ce.ValidateName("input")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary interface implementation
|
||||||
|
func (m *V1AsyncPaymentInput) MarshalBinary() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return swag.WriteJSON(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary interface implementation
|
||||||
|
func (m *V1AsyncPaymentInput) UnmarshalBinary(b []byte) error {
|
||||||
|
var res V1AsyncPaymentInput
|
||||||
|
if err := swag.ReadJSON(b, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = res
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
type V1CreatePaymentRequest struct {
|
type V1CreatePaymentRequest struct {
|
||||||
|
|
||||||
// inputs
|
// inputs
|
||||||
Inputs []*V1Input `json:"inputs"`
|
Inputs []*V1AsyncPaymentInput `json:"inputs"`
|
||||||
|
|
||||||
// outputs
|
// outputs
|
||||||
Outputs []*V1Output `json:"outputs"`
|
Outputs []*V1Output `json:"outputs"`
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ type V1Output struct {
|
|||||||
|
|
||||||
// Amount to send in satoshis.
|
// Amount to send in satoshis.
|
||||||
Amount string `json:"amount,omitempty"`
|
Amount string `json:"amount,omitempty"`
|
||||||
|
|
||||||
// descriptor
|
|
||||||
Descriptor string `json:"descriptor,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates this v1 output
|
// Validate validates this v1 output
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ type V1Vtxo struct {
|
|||||||
// amount
|
// amount
|
||||||
Amount string `json:"amount,omitempty"`
|
Amount string `json:"amount,omitempty"`
|
||||||
|
|
||||||
// descriptor
|
|
||||||
Descriptor string `json:"descriptor,omitempty"`
|
|
||||||
|
|
||||||
// expire at
|
// expire at
|
||||||
ExpireAt string `json:"expireAt,omitempty"`
|
ExpireAt string `json:"expireAt,omitempty"`
|
||||||
|
|
||||||
@@ -33,6 +30,9 @@ type V1Vtxo struct {
|
|||||||
// pending
|
// pending
|
||||||
Pending bool `json:"pending,omitempty"`
|
Pending bool `json:"pending,omitempty"`
|
||||||
|
|
||||||
|
// pubkey
|
||||||
|
Pubkey string `json:"pubkey,omitempty"`
|
||||||
|
|
||||||
// redeem tx
|
// redeem tx
|
||||||
RedeemTx string `json:"redeemTx,omitempty"`
|
RedeemTx string `json:"redeemTx,omitempty"`
|
||||||
|
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ func (a *covenantArkClient) ListVtxos(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, addr := range offchainAddrs {
|
for _, addr := range offchainAddrs {
|
||||||
spendable, spent, err := a.client.ListVtxos(ctx, addr)
|
spendable, spent, err := a.client.ListVtxos(ctx, addr.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -303,7 +303,7 @@ func (a *covenantArkClient) Balance(
|
|||||||
offchainBalance: balance,
|
offchainBalance: balance,
|
||||||
offchainBalanceByExpiration: amountByExpiration,
|
offchainBalanceByExpiration: amountByExpiration,
|
||||||
}
|
}
|
||||||
}(offchainAddr)
|
}(offchainAddr.Address)
|
||||||
|
|
||||||
getDelayedBalance := func(addr string) {
|
getDelayedBalance := func(addr string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@@ -323,8 +323,8 @@ func (a *covenantArkClient) Balance(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go getDelayedBalance(boardingAddr)
|
go getDelayedBalance(boardingAddr.Address)
|
||||||
go getDelayedBalance(redeemAddr)
|
go getDelayedBalance(redeemAddr.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -455,7 +455,7 @@ func (a *covenantArkClient) UnilateralRedeem(ctx context.Context) error {
|
|||||||
|
|
||||||
vtxos := make([]client.Vtxo, 0)
|
vtxos := make([]client.Vtxo, 0)
|
||||||
for _, offchainAddr := range offchainAddrs {
|
for _, offchainAddr := range offchainAddrs {
|
||||||
fetchedVtxos, _, err := a.client.ListVtxos(ctx, offchainAddr)
|
fetchedVtxos, _, err := a.client.ListVtxos(ctx, offchainAddr.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -549,13 +549,19 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxos := make([]client.Vtxo, 0)
|
vtxos := make([]client.DescriptorVtxo, 0)
|
||||||
for _, offchainAddr := range offchainAddrs {
|
for _, offchainAddr := range offchainAddrs {
|
||||||
spendableVtxos, err := a.getVtxos(ctx, offchainAddr, withExpiryCoinselect)
|
spendableVtxos, err := a.getVtxos(ctx, offchainAddr.Address, withExpiryCoinselect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
vtxos = append(vtxos, spendableVtxos...)
|
|
||||||
|
for _, vtxo := range spendableVtxos {
|
||||||
|
vtxos = append(vtxos, client.DescriptorVtxo{
|
||||||
|
Vtxo: vtxo,
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedCoins, changeAmount, err := utils.CoinSelect(
|
selectedCoins, changeAmount, err := utils.CoinSelect(
|
||||||
@@ -571,13 +577,8 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
desc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
receivers = append(receivers, client.Output{
|
receivers = append(receivers, client.Output{
|
||||||
Descriptor: desc,
|
Address: offchainAddr.Address,
|
||||||
Amount: changeAmount,
|
Amount: changeAmount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -621,12 +622,7 @@ func (a *covenantArkClient) SendAsync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) Claim(ctx context.Context) (string, error) {
|
func (a *covenantArkClient) Claim(ctx context.Context) (string, error) {
|
||||||
myselfOffchain, _, err := a.wallet.NewAddress(ctx, false)
|
myselfOffchain, boardingAddr, err := a.wallet.NewAddress(ctx, false)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, mypubkey, _, err := common.DecodeAddress(myselfOffchain)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -644,13 +640,8 @@ func (a *covenantArkClient) Claim(ctx context.Context) (string, error) {
|
|||||||
return "", fmt.Errorf("no funds to claim")
|
return "", fmt.Errorf("no funds to claim")
|
||||||
}
|
}
|
||||||
|
|
||||||
desc, err := a.offchainAddressToDefaultVtxoDescriptor(myselfOffchain)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
receiver := client.Output{
|
receiver := client.Output{
|
||||||
Descriptor: desc,
|
Address: myselfOffchain.Address,
|
||||||
Amount: pendingBalance,
|
Amount: pendingBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,7 +649,7 @@ func (a *covenantArkClient) Claim(ctx context.Context) (string, error) {
|
|||||||
ctx,
|
ctx,
|
||||||
boardingUtxos,
|
boardingUtxos,
|
||||||
receiver,
|
receiver,
|
||||||
hex.EncodeToString(mypubkey.SerializeCompressed()),
|
boardingAddr.Descriptor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,13 +683,13 @@ func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]explorer
|
|||||||
|
|
||||||
utxos := []explorer.Utxo{}
|
utxos := []explorer.Utxo{}
|
||||||
for _, addr := range boardingAddrs {
|
for _, addr := range boardingAddrs {
|
||||||
txs, err := a.explorer.GetTxs(addr)
|
txs, err := a.explorer.GetTxs(addr.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
for i, vout := range tx.Vout {
|
for i, vout := range tx.Vout {
|
||||||
if vout.Address == addr {
|
if vout.Address == addr.Address {
|
||||||
createdAt := time.Time{}
|
createdAt := time.Time{}
|
||||||
if tx.Status.Confirmed {
|
if tx.Status.Confirmed {
|
||||||
createdAt = time.Unix(tx.Status.Blocktime, 0)
|
createdAt = time.Unix(tx.Status.Blocktime, 0)
|
||||||
@@ -718,25 +709,15 @@ func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]explorer
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
|
func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
|
||||||
offchainAddrs, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
|
_, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
claimable := make([]explorer.Utxo, 0)
|
claimable := make([]explorer.Utxo, 0)
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
|
for _, addr := range boardingAddrs {
|
||||||
if err != nil {
|
boardingScript, err := tree.ParseVtxoScript(addr.Descriptor)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
|
|
||||||
descriptorStr := strings.ReplaceAll(
|
|
||||||
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
|
||||||
)
|
|
||||||
|
|
||||||
boardingScript, err := tree.ParseVtxoScript(descriptorStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -746,15 +727,16 @@ func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]ex
|
|||||||
if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok {
|
if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok {
|
||||||
boardingTimeout = defaultVtxo.ExitDelay
|
boardingTimeout = defaultVtxo.ExitDelay
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("unsupported boarding descriptor: %s", descriptorStr)
|
return nil, fmt.Errorf("unsupported boarding descriptor: %s", addr.Descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, addr := range boardingAddrs {
|
boardingUtxos, err := a.explorer.GetUtxos(addr.Address)
|
||||||
boardingUtxos, err := a.explorer.GetUtxos(addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
for _, utxo := range boardingUtxos {
|
for _, utxo := range boardingUtxos {
|
||||||
u := utxo.ToUtxo(boardingTimeout)
|
u := utxo.ToUtxo(boardingTimeout)
|
||||||
|
|
||||||
@@ -826,7 +808,7 @@ func (a *covenantArkClient) sendOnchain(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
changeScript, err := address.ToOutputScript(changeAddr)
|
changeScript, err := address.ToOutputScript(changeAddr.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -876,7 +858,7 @@ func (a *covenantArkClient) sendOnchain(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
changeScript, err := address.ToOutputScript(changeAddr)
|
changeScript, err := address.ToOutputScript(changeAddr.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -938,49 +920,47 @@ func (a *covenantArkClient) sendOffchain(
|
|||||||
return "", fmt.Errorf("no funds detected")
|
return "", fmt.Errorf("no funds detected")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, aspPubKey, err := common.DecodeAddress(offchainAddrs[0])
|
expectedAspPubkey := schnorr.SerializePubKey(a.AspPubkey)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
receiversOutput := make([]client.Output, 0)
|
receiversOutput := make([]client.Output, 0)
|
||||||
sumOfReceivers := uint64(0)
|
sumOfReceivers := uint64(0)
|
||||||
|
|
||||||
for _, receiver := range receivers {
|
for _, receiver := range receivers {
|
||||||
_, _, aspKey, err := common.DecodeAddress(receiver.To())
|
rcvAddr, err := common.DecodeAddress(receiver.To())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("invalid receiver address: %s", err)
|
return "", fmt.Errorf("invalid receiver address: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(
|
rcvAspPubkey := schnorr.SerializePubKey(rcvAddr.Asp)
|
||||||
aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed(),
|
|
||||||
) {
|
if !bytes.Equal(rcvAspPubkey, expectedAspPubkey) {
|
||||||
return "", fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiver.To())
|
return "", fmt.Errorf("invalid receiver address '%s': expected ASP %s, got %s", receiver.To(), hex.EncodeToString(expectedAspPubkey), hex.EncodeToString(rcvAspPubkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
if receiver.Amount() < a.Dust {
|
if receiver.Amount() < a.Dust {
|
||||||
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust)
|
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust)
|
||||||
}
|
}
|
||||||
|
|
||||||
desc, err := a.offchainAddressToDefaultVtxoDescriptor(receiver.To())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
receiversOutput = append(receiversOutput, client.Output{
|
receiversOutput = append(receiversOutput, client.Output{
|
||||||
Descriptor: desc,
|
Address: receiver.To(),
|
||||||
Amount: receiver.Amount(),
|
Amount: receiver.Amount(),
|
||||||
})
|
})
|
||||||
sumOfReceivers += receiver.Amount()
|
sumOfReceivers += receiver.Amount()
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxos := make([]client.Vtxo, 0)
|
vtxos := make([]client.DescriptorVtxo, 0)
|
||||||
for _, offchainAddr := range offchainAddrs {
|
for _, offchainAddr := range offchainAddrs {
|
||||||
spendableVtxos, err := a.getVtxos(ctx, offchainAddr, withExpiryCoinselect)
|
spendableVtxos, err := a.getVtxos(ctx, offchainAddr.Address, withExpiryCoinselect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
vtxos = append(vtxos, spendableVtxos...)
|
|
||||||
|
for _, vtxo := range spendableVtxos {
|
||||||
|
vtxos = append(vtxos, client.DescriptorVtxo{
|
||||||
|
Vtxo: vtxo,
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedCoins, changeAmount, err := utils.CoinSelect(
|
selectedCoins, changeAmount, err := utils.CoinSelect(
|
||||||
@@ -996,13 +976,8 @@ func (a *covenantArkClient) sendOffchain(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
desc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
changeReceiver := client.Output{
|
changeReceiver := client.Output{
|
||||||
Descriptor: desc,
|
Address: offchainAddr.Address,
|
||||||
Amount: changeAmount,
|
Amount: changeAmount,
|
||||||
}
|
}
|
||||||
receiversOutput = append(receiversOutput, changeReceiver)
|
receiversOutput = append(receiversOutput, changeReceiver)
|
||||||
@@ -1044,6 +1019,7 @@ func (a *covenantArkClient) sendOffchain(
|
|||||||
return poolTxID, nil
|
return poolTxID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addInputs adds the inputs to the pset for send onchain
|
||||||
func (a *covenantArkClient) addInputs(
|
func (a *covenantArkClient) addInputs(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
updater *psetv2.Updater,
|
updater *psetv2.Updater,
|
||||||
@@ -1055,11 +1031,21 @@ func (a *covenantArkClient) addInputs(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, userPubkey, aspPubkey, err := common.DecodeAddress(offchain)
|
vtxoScript, err := tree.ParseVtxoScript(offchain.Descriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var userPubkey, aspPubkey *secp256k1.PublicKey
|
||||||
|
|
||||||
|
switch s := vtxoScript.(type) {
|
||||||
|
case *tree.DefaultVtxoScript:
|
||||||
|
userPubkey = s.Owner
|
||||||
|
aspPubkey = s.Asp
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported vtxo script: %T", s)
|
||||||
|
}
|
||||||
|
|
||||||
for _, utxo := range utxos {
|
for _, utxo := range utxos {
|
||||||
sequence, err := utxo.Sequence()
|
sequence, err := utxo.Sequence()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1126,7 +1112,7 @@ func (a *covenantArkClient) addInputs(
|
|||||||
func (a *covenantArkClient) handleRoundStream(
|
func (a *covenantArkClient) handleRoundStream(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
paymentID string,
|
paymentID string,
|
||||||
vtxosToSign []client.Vtxo,
|
vtxosToSign []client.DescriptorVtxo,
|
||||||
boardingUtxos []explorer.Utxo,
|
boardingUtxos []explorer.Utxo,
|
||||||
boardingDescriptor string,
|
boardingDescriptor string,
|
||||||
receivers []client.Output,
|
receivers []client.Output,
|
||||||
@@ -1191,7 +1177,7 @@ func (a *covenantArkClient) handleRoundStream(
|
|||||||
func (a *covenantArkClient) handleRoundFinalization(
|
func (a *covenantArkClient) handleRoundFinalization(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
event client.RoundFinalizationEvent,
|
event client.RoundFinalizationEvent,
|
||||||
vtxos []client.Vtxo,
|
vtxos []client.DescriptorVtxo,
|
||||||
boardingUtxos []explorer.Utxo,
|
boardingUtxos []explorer.Utxo,
|
||||||
boardingDescriptor string,
|
boardingDescriptor string,
|
||||||
receivers []client.Output,
|
receivers []client.Output,
|
||||||
@@ -1200,18 +1186,8 @@ func (a *covenantArkClient) handleRoundFinalization(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddr, _, err := a.wallet.NewAddress(ctx, false)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, myPubkey, _, err := common.DecodeAddress(offchainAddr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(vtxos) > 0 {
|
if len(vtxos) > 0 {
|
||||||
signedForfeits, err = a.createAndSignForfeits(ctx, vtxos, event.Connectors, event.MinRelayFeeRate, myPubkey)
|
signedForfeits, err = a.createAndSignForfeits(ctx, vtxos, event.Connectors, event.MinRelayFeeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1228,11 +1204,17 @@ func (a *covenantArkClient) handleRoundFinalization(
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// add tapscript leaf
|
var forfeitClosure tree.Closure
|
||||||
forfeitClosure := &tree.MultisigClosure{
|
|
||||||
Pubkey: myPubkey,
|
switch s := boardingVtxoScript.(type) {
|
||||||
|
case *tree.DefaultVtxoScript:
|
||||||
|
forfeitClosure = &tree.MultisigClosure{
|
||||||
|
Pubkey: s.Owner,
|
||||||
AspPubkey: a.AspPubkey,
|
AspPubkey: a.AspPubkey,
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
forfeitLeaf, err := forfeitClosure.Leaf()
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1381,15 +1363,12 @@ func (a *covenantArkClient) validateOffChainReceiver(
|
|||||||
) error {
|
) error {
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
receiverVtxoScript, err := tree.ParseVtxoScript(receiver.Descriptor)
|
addr, err := common.DecodeAddress(receiver.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
outputTapKey, _, err := receiverVtxoScript.TapTree()
|
vtxoTapKey := schnorr.SerializePubKey(addr.VtxoTapKey)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
leaves := congestionTree.Leaves()
|
leaves := congestionTree.Leaves()
|
||||||
for _, leaf := range leaves {
|
for _, leaf := range leaves {
|
||||||
@@ -1402,7 +1381,7 @@ func (a *covenantArkClient) validateOffChainReceiver(
|
|||||||
if len(output.Script) == 0 {
|
if len(output.Script) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if bytes.Equal(output.Script[2:], schnorr.SerializePubKey(outputTapKey)) {
|
if bytes.Equal(output.Script[2:], vtxoTapKey) {
|
||||||
if output.Value == receiver.Amount {
|
if output.Value == receiver.Amount {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
@@ -1423,10 +1402,9 @@ func (a *covenantArkClient) validateOffChainReceiver(
|
|||||||
|
|
||||||
func (a *covenantArkClient) createAndSignForfeits(
|
func (a *covenantArkClient) createAndSignForfeits(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
vtxosToSign []client.Vtxo,
|
vtxosToSign []client.DescriptorVtxo,
|
||||||
connectors []string,
|
connectors []string,
|
||||||
feeRate chainfee.SatPerKVByte,
|
feeRate chainfee.SatPerKVByte,
|
||||||
myPubKey *secp256k1.PublicKey,
|
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
signedForfeits := make([]string, 0)
|
signedForfeits := make([]string, 0)
|
||||||
connectorsPsets := make([]*psetv2.Pset, 0, len(connectors))
|
connectorsPsets := make([]*psetv2.Pset, 0, len(connectors))
|
||||||
@@ -1471,10 +1449,17 @@ func (a *covenantArkClient) createAndSignForfeits(
|
|||||||
TxIndex: vtxo.VOut,
|
TxIndex: vtxo.VOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitClosure := &tree.MultisigClosure{
|
var forfeitClosure tree.Closure
|
||||||
Pubkey: myPubKey,
|
|
||||||
|
switch s := vtxoScript.(type) {
|
||||||
|
case *tree.DefaultVtxoScript:
|
||||||
|
forfeitClosure = &tree.MultisigClosure{
|
||||||
|
Pubkey: s.Owner,
|
||||||
AspPubkey: a.AspPubkey,
|
AspPubkey: a.AspPubkey,
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported vtxo script: %T", s)
|
||||||
|
}
|
||||||
|
|
||||||
forfeitLeaf, err := forfeitClosure.Leaf()
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1535,21 +1520,18 @@ func (a *covenantArkClient) createAndSignForfeits(
|
|||||||
func (a *covenantArkClient) coinSelectOnchain(
|
func (a *covenantArkClient) coinSelectOnchain(
|
||||||
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
|
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
|
||||||
) ([]explorer.Utxo, uint64, error) {
|
) ([]explorer.Utxo, uint64, error) {
|
||||||
offchainAddrs, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx)
|
_, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
|
now := time.Now()
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
|
fetchedUtxos := make([]explorer.Utxo, 0)
|
||||||
descriptorStr := strings.ReplaceAll(
|
for _, addr := range boardingAddrs {
|
||||||
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
boardingDescriptor := addr.Descriptor
|
||||||
)
|
|
||||||
boardingScript, err := tree.ParseVtxoScript(descriptorStr)
|
boardingScript, err := tree.ParseVtxoScript(boardingDescriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -1559,14 +1541,10 @@ func (a *covenantArkClient) coinSelectOnchain(
|
|||||||
if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok {
|
if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok {
|
||||||
boardingTimeout = defaultVtxo.ExitDelay
|
boardingTimeout = defaultVtxo.ExitDelay
|
||||||
} else {
|
} else {
|
||||||
return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", descriptorStr)
|
return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", boardingDescriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
utxos, err := a.explorer.GetUtxos(addr.Address)
|
||||||
|
|
||||||
fetchedUtxos := make([]explorer.Utxo, 0)
|
|
||||||
for _, addr := range boardingAddrs {
|
|
||||||
utxos, err := a.explorer.GetUtxos(addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -1602,7 +1580,7 @@ func (a *covenantArkClient) coinSelectOnchain(
|
|||||||
|
|
||||||
fetchedUtxos = make([]explorer.Utxo, 0)
|
fetchedUtxos = make([]explorer.Utxo, 0)
|
||||||
for _, addr := range redemptionAddrs {
|
for _, addr := range redemptionAddrs {
|
||||||
utxos, err := a.explorer.GetUtxos(addr)
|
utxos, err := a.explorer.GetUtxos(addr.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -1731,14 +1709,10 @@ func (a *covenantArkClient) getVtxos(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) selfTransferAllPendingPayments(
|
func (a *covenantArkClient) selfTransferAllPendingPayments(
|
||||||
ctx context.Context, boardingUtxos []explorer.Utxo, myself client.Output, mypubkey string,
|
ctx context.Context, boardingUtxos []explorer.Utxo, myself client.Output, boardingDescriptor string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
inputs := make([]client.Input, 0, len(boardingUtxos))
|
inputs := make([]client.Input, 0, len(boardingUtxos))
|
||||||
|
|
||||||
boardingDescriptor := strings.ReplaceAll(
|
|
||||||
a.BoardingDescriptorTemplate, "USER", mypubkey[2:],
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, utxo := range boardingUtxos {
|
for _, utxo := range boardingUtxos {
|
||||||
inputs = append(inputs, client.Input{
|
inputs = append(inputs, client.Input{
|
||||||
Outpoint: client.Outpoint{
|
Outpoint: client.Outpoint{
|
||||||
@@ -1761,7 +1735,7 @@ func (a *covenantArkClient) selfTransferAllPendingPayments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
roundTxid, err := a.handleRoundStream(
|
roundTxid, err := a.handleRoundStream(
|
||||||
ctx, paymentID, make([]client.Vtxo, 0), boardingUtxos, boardingDescriptor, outputs,
|
ctx, paymentID, make([]client.DescriptorVtxo, 0), boardingUtxos, boardingDescriptor, outputs,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -1770,24 +1744,7 @@ func (a *covenantArkClient) selfTransferAllPendingPayments(
|
|||||||
return roundTxid, nil
|
return roundTxid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) offchainAddressToDefaultVtxoDescriptor(addr string) (string, error) {
|
func (a *covenantArkClient) getBoardingTxs(ctx context.Context) (transactions []types.Transaction) {
|
||||||
_, userPubKey, aspPubkey, err := common.DecodeAddress(addr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoScript := tree.DefaultVtxoScript{
|
|
||||||
Owner: userPubKey,
|
|
||||||
Asp: aspPubkey,
|
|
||||||
ExitDelay: uint(a.UnilateralExitDelay),
|
|
||||||
}
|
|
||||||
|
|
||||||
return vtxoScript.ToDescriptor(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *covenantArkClient) getBoardingTxs(
|
|
||||||
ctx context.Context,
|
|
||||||
) (transactions []types.Transaction) {
|
|
||||||
utxos, err := a.getClaimableBoardingUtxos(ctx)
|
utxos, err := a.getClaimableBoardingUtxos(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -223,12 +223,14 @@ func (a *covenantlessArkClient) listenForTransactions(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
desc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddr)
|
addr, err := common.DecodeAddress(offchainAddr.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("Failed to get descriptor for new address")
|
log.WithError(err).Error("Failed to decode address")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addrPubkey := hex.EncodeToString(schnorr.SerializePubKey(addr.VtxoTapKey))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event, ok := <-eventChan:
|
case event, ok := <-eventChan:
|
||||||
@@ -253,7 +255,7 @@ func (a *covenantlessArkClient) listenForTransactions(ctx context.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
a.processTransactionEvent(desc, event, pendingBoardingTxsMap)
|
a.processTransactionEvent(addrPubkey, event, pendingBoardingTxsMap)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -319,7 +321,7 @@ func (a *covenantlessArkClient) getBoardingPendingTransactions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) processTransactionEvent(
|
func (a *covenantlessArkClient) processTransactionEvent(
|
||||||
descriptor string,
|
pubkey string,
|
||||||
event client.TransactionEvent,
|
event client.TransactionEvent,
|
||||||
pendingBoardingTxsMap map[string]types.Transaction,
|
pendingBoardingTxsMap map[string]types.Transaction,
|
||||||
) {
|
) {
|
||||||
@@ -372,7 +374,7 @@ func (a *covenantlessArkClient) processTransactionEvent(
|
|||||||
vtxosToInsert := make([]types.Vtxo, 0)
|
vtxosToInsert := make([]types.Vtxo, 0)
|
||||||
txsToInsert := make([]types.Transaction, 0)
|
txsToInsert := make([]types.Transaction, 0)
|
||||||
for _, v := range event.Round.SpendableVtxos {
|
for _, v := range event.Round.SpendableVtxos {
|
||||||
if v.Descriptor == descriptor {
|
if v.Pubkey == pubkey {
|
||||||
vtxosToInsert = append(vtxosToInsert, types.Vtxo{
|
vtxosToInsert = append(vtxosToInsert, types.Vtxo{
|
||||||
VtxoKey: types.VtxoKey{
|
VtxoKey: types.VtxoKey{
|
||||||
Txid: v.Txid,
|
Txid: v.Txid,
|
||||||
@@ -449,7 +451,7 @@ func (a *covenantlessArkClient) processTransactionEvent(
|
|||||||
|
|
||||||
outputAmount := uint64(0)
|
outputAmount := uint64(0)
|
||||||
for _, v := range event.Redeem.SpendableVtxos {
|
for _, v := range event.Redeem.SpendableVtxos {
|
||||||
if v.Descriptor == descriptor {
|
if v.Pubkey == pubkey {
|
||||||
vtxosToInsert = append(vtxosToInsert, types.Vtxo{
|
vtxosToInsert = append(vtxosToInsert, types.Vtxo{
|
||||||
VtxoKey: types.VtxoKey{
|
VtxoKey: types.VtxoKey{
|
||||||
Txid: v.Txid,
|
Txid: v.Txid,
|
||||||
@@ -482,7 +484,7 @@ func (a *covenantlessArkClient) processTransactionEvent(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, v := range event.Redeem.SpendableVtxos {
|
for _, v := range event.Redeem.SpendableVtxos {
|
||||||
if v.Descriptor == descriptor {
|
if v.Pubkey == pubkey {
|
||||||
vtxosToInsert = append(vtxosToInsert, types.Vtxo{
|
vtxosToInsert = append(vtxosToInsert, types.Vtxo{
|
||||||
VtxoKey: types.VtxoKey{
|
VtxoKey: types.VtxoKey{
|
||||||
Txid: v.Txid,
|
Txid: v.Txid,
|
||||||
@@ -524,17 +526,25 @@ func (a *covenantlessArkClient) processTransactionEvent(
|
|||||||
func (a *covenantlessArkClient) ListVtxos(
|
func (a *covenantlessArkClient) ListVtxos(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (spendableVtxos, spentVtxos []client.Vtxo, err error) {
|
) (spendableVtxos, spentVtxos []client.Vtxo, err error) {
|
||||||
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
|
offchainAddrs, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, pubkey, _, err := common.DecodeAddress(offchainAddrs[0])
|
boardingAddrScript, err := bitcointree.ParseVtxoScript(boardingAddrs[0].Descriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
myPubkey := schnorr.SerializePubKey(pubkey)
|
var myPubkey []byte
|
||||||
|
|
||||||
|
if boardingScript, ok := boardingAddrScript.(*bitcointree.DefaultVtxoScript); ok {
|
||||||
|
myPubkey = schnorr.SerializePubKey(boardingScript.Owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
if myPubkey == nil {
|
||||||
|
return nil, nil, fmt.Errorf("invalid boarding address descriptor")
|
||||||
|
}
|
||||||
|
|
||||||
// The ASP returns the vtxos sent to others via async payments as spendable
|
// The ASP returns the vtxos sent to others via async payments as spendable
|
||||||
// because they are actually revertable. Since we do not provide any revert
|
// because they are actually revertable. Since we do not provide any revert
|
||||||
@@ -544,22 +554,25 @@ func (a *covenantlessArkClient) ListVtxos(
|
|||||||
// The auxiliary variables below are used to make these checks in an
|
// The auxiliary variables below are used to make these checks in an
|
||||||
// efficient way.
|
// efficient way.
|
||||||
for _, addr := range offchainAddrs {
|
for _, addr := range offchainAddrs {
|
||||||
spendable, spent, err := a.client.ListVtxos(ctx, addr)
|
spendable, spent, err := a.client.ListVtxos(ctx, addr.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
script, err := bitcointree.ParseVtxoScript(addr.Descriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reversibleVtxo, isReversible := script.(*bitcointree.ReversibleVtxoScript)
|
||||||
|
|
||||||
for _, v := range spendable {
|
for _, v := range spendable {
|
||||||
if !v.Pending {
|
if !v.Pending {
|
||||||
spendableVtxos = append(spendableVtxos, v)
|
spendableVtxos = append(spendableVtxos, v)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
script, err := bitcointree.ParseVtxoScript(v.Descriptor)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reversibleVtxo, ok := script.(*bitcointree.ReversibleVtxoScript)
|
if !isReversible {
|
||||||
if !ok {
|
|
||||||
spendableVtxos = append(spendableVtxos, v)
|
spendableVtxos = append(spendableVtxos, v)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -568,17 +581,13 @@ func (a *covenantlessArkClient) ListVtxos(
|
|||||||
spendableVtxos = append(spendableVtxos, v)
|
spendableVtxos = append(spendableVtxos, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, v := range spent {
|
|
||||||
script, err := bitcointree.ParseVtxoScript(v.Descriptor)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reversibleVtxo, ok := script.(*bitcointree.ReversibleVtxoScript)
|
for _, v := range spent {
|
||||||
if !ok {
|
if !isReversible {
|
||||||
spentVtxos = append(spentVtxos, v)
|
spentVtxos = append(spentVtxos, v)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(schnorr.SerializePubKey(reversibleVtxo.Sender), myPubkey) {
|
if !bytes.Equal(schnorr.SerializePubKey(reversibleVtxo.Sender), myPubkey) {
|
||||||
spentVtxos = append(spentVtxos, v)
|
spentVtxos = append(spentVtxos, v)
|
||||||
}
|
}
|
||||||
@@ -602,14 +611,13 @@ func (a *covenantlessArkClient) Balance(
|
|||||||
|
|
||||||
chRes := make(chan balanceRes, nbWorkers*len(offchainAddrs))
|
chRes := make(chan balanceRes, nbWorkers*len(offchainAddrs))
|
||||||
for i := range offchainAddrs {
|
for i := range offchainAddrs {
|
||||||
offchainAddr := offchainAddrs[i]
|
|
||||||
boardingAddr := boardingAddrs[i]
|
boardingAddr := boardingAddrs[i]
|
||||||
redeemAddr := redeemAddrs[i]
|
redeemAddr := redeemAddrs[i]
|
||||||
|
|
||||||
go func(addr string) {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
balance, amountByExpiration, err := a.getOffchainBalance(
|
balance, amountByExpiration, err := a.getOffchainBalance(
|
||||||
ctx, addr, computeVtxoExpiration,
|
ctx, computeVtxoExpiration,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chRes <- balanceRes{err: err}
|
chRes <- balanceRes{err: err}
|
||||||
@@ -620,7 +628,7 @@ func (a *covenantlessArkClient) Balance(
|
|||||||
offchainBalance: balance,
|
offchainBalance: balance,
|
||||||
offchainBalanceByExpiration: amountByExpiration,
|
offchainBalanceByExpiration: amountByExpiration,
|
||||||
}
|
}
|
||||||
}(offchainAddr)
|
}()
|
||||||
|
|
||||||
getDelayedBalance := func(addr string) {
|
getDelayedBalance := func(addr string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@@ -640,8 +648,8 @@ func (a *covenantlessArkClient) Balance(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go getDelayedBalance(boardingAddr)
|
go getDelayedBalance(boardingAddr.Address)
|
||||||
go getDelayedBalance(redeemAddr)
|
go getDelayedBalance(redeemAddr.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -765,20 +773,11 @@ func (a *covenantlessArkClient) UnilateralRedeem(ctx context.Context) error {
|
|||||||
return fmt.Errorf("wallet is locked")
|
return fmt.Errorf("wallet is locked")
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
|
vtxos, _, err := a.getVtxos(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxos := make([]client.Vtxo, 0)
|
|
||||||
for _, offchainAddr := range offchainAddrs {
|
|
||||||
spendableVtxos, _, err := a.getVtxos(ctx, offchainAddr, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
vtxos = append(vtxos, spendableVtxos...)
|
|
||||||
}
|
|
||||||
|
|
||||||
totalVtxosAmount := uint64(0)
|
totalVtxosAmount := uint64(0)
|
||||||
for _, vtxo := range vtxos {
|
for _, vtxo := range vtxos {
|
||||||
totalVtxosAmount += vtxo.Amount
|
totalVtxosAmount += vtxo.Amount
|
||||||
@@ -853,13 +852,26 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxos := make([]client.Vtxo, 0)
|
vtxos := make([]client.DescriptorVtxo, 0)
|
||||||
for _, offchainAddr := range offchainAddrs {
|
spendableVtxos, _, err := a.getVtxos(ctx, withExpiryCoinselect)
|
||||||
spendableVtxos, _, err := a.getVtxos(ctx, offchainAddr, withExpiryCoinselect)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
vtxos = append(vtxos, spendableVtxos...)
|
|
||||||
|
for _, offchainAddr := range offchainAddrs {
|
||||||
|
for _, v := range spendableVtxos {
|
||||||
|
vtxoAddr, err := v.Address(a.AspPubkey, a.Network)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if vtxoAddr == offchainAddr.Address {
|
||||||
|
vtxos = append(vtxos, client.DescriptorVtxo{
|
||||||
|
Vtxo: v,
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedCoins, changeAmount, err := utils.CoinSelect(
|
selectedCoins, changeAmount, err := utils.CoinSelect(
|
||||||
@@ -875,13 +887,8 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
desc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
receivers = append(receivers, client.Output{
|
receivers = append(receivers, client.Output{
|
||||||
Descriptor: desc,
|
Address: offchainAddr.Address,
|
||||||
Amount: changeAmount,
|
Amount: changeAmount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -950,59 +957,57 @@ func (a *covenantlessArkClient) SendAsync(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, aspPubKey, err := common.DecodeAddress(offchainAddrs[0])
|
expectedAspPubKey := schnorr.SerializePubKey(a.AspPubkey)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
receiversOutput := make([]client.Output, 0)
|
receiversOutput := make([]client.Output, 0)
|
||||||
sumOfReceivers := uint64(0)
|
sumOfReceivers := uint64(0)
|
||||||
|
|
||||||
for _, receiver := range receivers {
|
for _, receiver := range receivers {
|
||||||
_, _, aspKey, err := common.DecodeAddress(receiver.To())
|
rcvAddr, err := common.DecodeAddress(receiver.To())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("invalid receiver address: %s", err)
|
return "", fmt.Errorf("invalid receiver address: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(
|
rcvAspPubKey := schnorr.SerializePubKey(rcvAddr.Asp)
|
||||||
aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed(),
|
|
||||||
) {
|
if !bytes.Equal(expectedAspPubKey, rcvAspPubKey) {
|
||||||
return "", fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiver)
|
return "", fmt.Errorf("invalid receiver address '%s': expected ASP %s, got %s", receiver.To(), hex.EncodeToString(expectedAspPubKey), hex.EncodeToString(rcvAspPubKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
if receiver.Amount() < a.Dust {
|
if receiver.Amount() < a.Dust {
|
||||||
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust)
|
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust)
|
||||||
}
|
}
|
||||||
|
|
||||||
isSelfTransfer := offchainAddrs[0] == receiver.To()
|
|
||||||
|
|
||||||
var desc string
|
|
||||||
|
|
||||||
// reversible vtxo does not make sense for self transfer
|
|
||||||
// if the receiver is the same as the sender, handle the output like the change
|
|
||||||
if !isSelfTransfer {
|
|
||||||
desc, err = a.offchainAddressToReversibleVtxoDescriptor(offchainAddrs[0], receiver.To())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
desc, err = a.offchainAddressToDefaultVtxoDescriptor(receiver.To())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
receiversOutput = append(receiversOutput, client.Output{
|
receiversOutput = append(receiversOutput, client.Output{
|
||||||
Descriptor: desc,
|
Address: receiver.To(),
|
||||||
Amount: receiver.Amount(),
|
Amount: receiver.Amount(),
|
||||||
})
|
})
|
||||||
sumOfReceivers += receiver.Amount()
|
sumOfReceivers += receiver.Amount()
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxos, _, err := a.getVtxos(ctx, offchainAddrs[0], withExpiryCoinselect)
|
vtxos := make([]client.DescriptorVtxo, 0)
|
||||||
|
|
||||||
|
spendableVtxos, _, err := a.getVtxos(ctx, withExpiryCoinselect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, offchainAddr := range offchainAddrs {
|
||||||
|
for _, v := range spendableVtxos {
|
||||||
|
vtxoAddr, err := v.Address(a.AspPubkey, a.Network)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if vtxoAddr == offchainAddr.Address {
|
||||||
|
vtxos = append(vtxos, client.DescriptorVtxo{
|
||||||
|
Vtxo: v,
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
selectedCoins, changeAmount, err := utils.CoinSelect(
|
selectedCoins, changeAmount, err := utils.CoinSelect(
|
||||||
vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect,
|
vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect,
|
||||||
)
|
)
|
||||||
@@ -1011,27 +1016,52 @@ func (a *covenantlessArkClient) SendAsync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if changeAmount > 0 {
|
if changeAmount > 0 {
|
||||||
changeDesc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddrs[0])
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
changeReceiver := client.Output{
|
changeReceiver := client.Output{
|
||||||
Descriptor: changeDesc,
|
Address: offchainAddrs[0].Address,
|
||||||
Amount: changeAmount,
|
Amount: changeAmount,
|
||||||
}
|
}
|
||||||
receiversOutput = append(receiversOutput, changeReceiver)
|
receiversOutput = append(receiversOutput, changeReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs := make([]client.Input, 0, len(selectedCoins))
|
inputs := make([]client.AsyncPaymentInput, 0, len(selectedCoins))
|
||||||
|
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, client.Input{
|
vtxoScript, err := bitcointree.ParseVtxoScript(coin.Descriptor)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var forfeitClosure bitcointree.Closure
|
||||||
|
|
||||||
|
switch s := vtxoScript.(type) {
|
||||||
|
case *bitcointree.DefaultVtxoScript:
|
||||||
|
forfeitClosure = &bitcointree.MultisigClosure{
|
||||||
|
Pubkey: s.Owner,
|
||||||
|
AspPubkey: s.Asp,
|
||||||
|
}
|
||||||
|
case *bitcointree.ReversibleVtxoScript:
|
||||||
|
forfeitClosure = &bitcointree.MultisigClosure{
|
||||||
|
Pubkey: s.Owner,
|
||||||
|
AspPubkey: s.Asp,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported vtxo script: %T", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs = append(inputs, client.AsyncPaymentInput{
|
||||||
|
Input: client.Input{
|
||||||
Outpoint: client.Outpoint{
|
Outpoint: client.Outpoint{
|
||||||
Txid: coin.Txid,
|
Txid: coin.Txid,
|
||||||
VOut: coin.VOut,
|
VOut: coin.VOut,
|
||||||
},
|
},
|
||||||
Descriptor: coin.Descriptor,
|
Descriptor: coin.Descriptor,
|
||||||
|
},
|
||||||
|
ForfeitLeafHash: forfeitLeaf.TapHash(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1057,19 +1087,22 @@ func (a *covenantlessArkClient) SendAsync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) Claim(ctx context.Context) (string, error) {
|
func (a *covenantlessArkClient) Claim(ctx context.Context) (string, error) {
|
||||||
myselfOffchain, _, err := a.wallet.NewAddress(ctx, false)
|
myselfOffchain, boardingAddr, err := a.wallet.NewAddress(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, pendingVtxos, err := a.getVtxos(ctx, myselfOffchain, false)
|
_, pendingVtxos, err := a.getVtxos(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, mypubkey, _, err := common.DecodeAddress(myselfOffchain)
|
pendingVtxosWithDescriptor := make([]client.DescriptorVtxo, 0)
|
||||||
if err != nil {
|
for _, vtxo := range pendingVtxos {
|
||||||
return "", err
|
pendingVtxosWithDescriptor = append(pendingVtxosWithDescriptor, client.DescriptorVtxo{
|
||||||
|
Vtxo: vtxo,
|
||||||
|
Descriptor: myselfOffchain.Descriptor,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx)
|
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx)
|
||||||
@@ -1088,22 +1121,17 @@ func (a *covenantlessArkClient) Claim(ctx context.Context) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
desc, err := a.offchainAddressToDefaultVtxoDescriptor(myselfOffchain)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
receiver := client.Output{
|
receiver := client.Output{
|
||||||
Descriptor: desc,
|
Address: myselfOffchain.Address,
|
||||||
Amount: pendingBalance,
|
Amount: pendingBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.selfTransferAllPendingPayments(
|
return a.selfTransferAllPendingPayments(
|
||||||
ctx,
|
ctx,
|
||||||
pendingVtxos,
|
pendingVtxosWithDescriptor,
|
||||||
boardingUtxos,
|
boardingUtxos,
|
||||||
receiver,
|
receiver,
|
||||||
hex.EncodeToString(mypubkey.SerializeCompressed()),
|
boardingAddr.Descriptor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1208,7 +1236,7 @@ func (a *covenantlessArkClient) sendOnchain(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
addr, _ := btcutil.DecodeAddress(changeAddr, &netParams)
|
addr, _ := btcutil.DecodeAddress(changeAddr.Address, &netParams)
|
||||||
|
|
||||||
pkscript, err := txscript.PayToAddrScript(addr)
|
pkscript, err := txscript.PayToAddrScript(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1255,7 +1283,7 @@ func (a *covenantlessArkClient) sendOnchain(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
addr, _ := btcutil.DecodeAddress(changeAddr, &netParams)
|
addr, _ := btcutil.DecodeAddress(changeAddr.Address, &netParams)
|
||||||
|
|
||||||
pkscript, err := txscript.PayToAddrScript(addr)
|
pkscript, err := txscript.PayToAddrScript(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1306,49 +1334,55 @@ func (a *covenantlessArkClient) sendOffchain(
|
|||||||
return "", fmt.Errorf("no funds detected")
|
return "", fmt.Errorf("no funds detected")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, aspPubKey, err := common.DecodeAddress(offchainAddrs[0])
|
expectedAspPubKey := schnorr.SerializePubKey(a.AspPubkey)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
receiversOutput := make([]client.Output, 0)
|
receiversOutput := make([]client.Output, 0)
|
||||||
sumOfReceivers := uint64(0)
|
sumOfReceivers := uint64(0)
|
||||||
|
|
||||||
for _, receiver := range receivers {
|
for _, receiver := range receivers {
|
||||||
_, _, aspKey, err := common.DecodeAddress(receiver.To())
|
rcvAddr, err := common.DecodeAddress(receiver.To())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("invalid receiver address: %s", err)
|
return "", fmt.Errorf("invalid receiver address: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(
|
rcvAspPubKey := schnorr.SerializePubKey(rcvAddr.Asp)
|
||||||
aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed(),
|
|
||||||
) {
|
if !bytes.Equal(expectedAspPubKey, rcvAspPubKey) {
|
||||||
return "", fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiver.To())
|
return "", fmt.Errorf("invalid receiver address '%s': expected ASP %s, got %s", receiver.To(), hex.EncodeToString(expectedAspPubKey), hex.EncodeToString(rcvAspPubKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
if receiver.Amount() < a.Dust {
|
if receiver.Amount() < a.Dust {
|
||||||
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust)
|
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust)
|
||||||
}
|
}
|
||||||
|
|
||||||
desc, err := a.offchainAddressToDefaultVtxoDescriptor(receiver.To())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
receiversOutput = append(receiversOutput, client.Output{
|
receiversOutput = append(receiversOutput, client.Output{
|
||||||
Descriptor: desc,
|
Address: receiver.To(),
|
||||||
Amount: receiver.Amount(),
|
Amount: receiver.Amount(),
|
||||||
})
|
})
|
||||||
sumOfReceivers += receiver.Amount()
|
sumOfReceivers += receiver.Amount()
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxos := make([]client.Vtxo, 0)
|
vtxos := make([]client.DescriptorVtxo, 0)
|
||||||
for _, offchainAddr := range offchainAddrs {
|
|
||||||
spendableVtxos, _, err := a.getVtxos(ctx, offchainAddr, withExpiryCoinselect)
|
spendableVtxos, _, err := a.getVtxos(ctx, withExpiryCoinselect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
vtxos = append(vtxos, spendableVtxos...)
|
|
||||||
|
for _, offchainAddr := range offchainAddrs {
|
||||||
|
for _, v := range spendableVtxos {
|
||||||
|
vtxoAddr, err := v.Address(a.AspPubkey, a.Network)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if vtxoAddr == offchainAddr.Address {
|
||||||
|
vtxos = append(vtxos, client.DescriptorVtxo{
|
||||||
|
Vtxo: v,
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedCoins, changeAmount, err := utils.CoinSelect(
|
selectedCoins, changeAmount, err := utils.CoinSelect(
|
||||||
@@ -1363,14 +1397,8 @@ func (a *covenantlessArkClient) sendOffchain(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
desc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
changeReceiver := client.Output{
|
changeReceiver := client.Output{
|
||||||
Descriptor: desc,
|
Address: offchainAddr.Address,
|
||||||
Amount: changeAmount,
|
Amount: changeAmount,
|
||||||
}
|
}
|
||||||
receiversOutput = append(receiversOutput, changeReceiver)
|
receiversOutput = append(receiversOutput, changeReceiver)
|
||||||
@@ -1428,11 +1456,21 @@ func (a *covenantlessArkClient) addInputs(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, userPubkey, aspPubkey, err := common.DecodeAddress(offchain)
|
vtxoScript, err := tree.ParseVtxoScript(offchain.Descriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var userPubkey, aspPubkey *secp256k1.PublicKey
|
||||||
|
|
||||||
|
switch s := vtxoScript.(type) {
|
||||||
|
case *tree.DefaultVtxoScript:
|
||||||
|
userPubkey = s.Owner
|
||||||
|
aspPubkey = s.Asp
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported vtxo script: %T", s)
|
||||||
|
}
|
||||||
|
|
||||||
for _, utxo := range utxos {
|
for _, utxo := range utxos {
|
||||||
previousHash, err := chainhash.NewHashFromStr(utxo.Txid)
|
previousHash, err := chainhash.NewHashFromStr(utxo.Txid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1495,7 +1533,7 @@ func (a *covenantlessArkClient) addInputs(
|
|||||||
func (a *covenantlessArkClient) handleRoundStream(
|
func (a *covenantlessArkClient) handleRoundStream(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
paymentID string,
|
paymentID string,
|
||||||
vtxosToSign []client.Vtxo,
|
vtxosToSign []client.DescriptorVtxo,
|
||||||
boardingUtxos []explorer.Utxo,
|
boardingUtxos []explorer.Utxo,
|
||||||
boardingDescriptor string,
|
boardingDescriptor string,
|
||||||
receivers []client.Output,
|
receivers []client.Output,
|
||||||
@@ -1681,7 +1719,7 @@ func (a *covenantlessArkClient) handleRoundSigningNoncesGenerated(
|
|||||||
func (a *covenantlessArkClient) handleRoundFinalization(
|
func (a *covenantlessArkClient) handleRoundFinalization(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
event client.RoundFinalizationEvent,
|
event client.RoundFinalizationEvent,
|
||||||
vtxos []client.Vtxo,
|
vtxos []client.DescriptorVtxo,
|
||||||
boardingUtxos []explorer.Utxo,
|
boardingUtxos []explorer.Utxo,
|
||||||
boardingDescriptor string,
|
boardingDescriptor string,
|
||||||
receivers []client.Output,
|
receivers []client.Output,
|
||||||
@@ -1690,21 +1728,11 @@ func (a *covenantlessArkClient) handleRoundFinalization(
|
|||||||
return nil, "", fmt.Errorf("failed to verify congestion tree: %s", err)
|
return nil, "", fmt.Errorf("failed to verify congestion tree: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddr, _, err := a.wallet.NewAddress(ctx, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, myPubkey, _, err := common.DecodeAddress(offchainAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var forfeits []string
|
var forfeits []string
|
||||||
|
|
||||||
if len(vtxos) > 0 {
|
if len(vtxos) > 0 {
|
||||||
signedForfeits, err := a.createAndSignForfeits(
|
signedForfeits, err := a.createAndSignForfeits(
|
||||||
ctx, vtxos, event.Connectors, event.MinRelayFeeRate, myPubkey,
|
ctx, vtxos, event.Connectors, event.MinRelayFeeRate,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
@@ -1724,6 +1752,15 @@ func (a *covenantlessArkClient) handleRoundFinalization(
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var myPubkey *secp256k1.PublicKey
|
||||||
|
|
||||||
|
switch v := boardingVtxoScript.(type) {
|
||||||
|
case *bitcointree.DefaultVtxoScript:
|
||||||
|
myPubkey = v.Owner
|
||||||
|
default:
|
||||||
|
return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
// add tapscript leaf
|
// add tapscript leaf
|
||||||
forfeitClosure := &bitcointree.MultisigClosure{
|
forfeitClosure := &bitcointree.MultisigClosure{
|
||||||
Pubkey: myPubkey,
|
Pubkey: myPubkey,
|
||||||
@@ -1869,15 +1906,12 @@ func (a *covenantlessArkClient) validateOffChainReceiver(
|
|||||||
) error {
|
) error {
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
receiverVtxoScript, err := bitcointree.ParseVtxoScript(receiver.Descriptor)
|
rcvAddr, err := common.DecodeAddress(receiver.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
outputTapKey, _, err := receiverVtxoScript.TapTree()
|
vtxoTapKey := schnorr.SerializePubKey(rcvAddr.VtxoTapKey)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
leaves := congestionTree.Leaves()
|
leaves := congestionTree.Leaves()
|
||||||
for _, leaf := range leaves {
|
for _, leaf := range leaves {
|
||||||
@@ -1891,9 +1925,7 @@ func (a *covenantlessArkClient) validateOffChainReceiver(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Equal(
|
if bytes.Equal(output.PkScript[2:], vtxoTapKey) {
|
||||||
output.PkScript[2:], schnorr.SerializePubKey(outputTapKey),
|
|
||||||
) {
|
|
||||||
if output.Value != int64(receiver.Amount) {
|
if output.Value != int64(receiver.Amount) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1919,10 +1951,9 @@ func (a *covenantlessArkClient) validateOffChainReceiver(
|
|||||||
|
|
||||||
func (a *covenantlessArkClient) createAndSignForfeits(
|
func (a *covenantlessArkClient) createAndSignForfeits(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
vtxosToSign []client.Vtxo,
|
vtxosToSign []client.DescriptorVtxo,
|
||||||
connectors []string,
|
connectors []string,
|
||||||
feeRate chainfee.SatPerKVByte,
|
feeRate chainfee.SatPerKVByte,
|
||||||
myPubkey *secp256k1.PublicKey,
|
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
parsedForfeitAddr, err := btcutil.DecodeAddress(a.ForfeitAddress, nil)
|
parsedForfeitAddr, err := btcutil.DecodeAddress(a.ForfeitAddress, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1982,10 +2013,22 @@ func (a *covenantlessArkClient) createAndSignForfeits(
|
|||||||
Index: vtxo.VOut,
|
Index: vtxo.VOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitClosure := &bitcointree.MultisigClosure{
|
var forfeitClosure bitcointree.Closure
|
||||||
Pubkey: myPubkey,
|
|
||||||
|
switch v := vtxoScript.(type) {
|
||||||
|
case *bitcointree.DefaultVtxoScript:
|
||||||
|
forfeitClosure = &bitcointree.MultisigClosure{
|
||||||
|
Pubkey: v.Owner,
|
||||||
AspPubkey: a.AspPubkey,
|
AspPubkey: a.AspPubkey,
|
||||||
}
|
}
|
||||||
|
case *bitcointree.ReversibleVtxoScript:
|
||||||
|
forfeitClosure = &bitcointree.MultisigClosure{
|
||||||
|
Pubkey: v.Owner,
|
||||||
|
AspPubkey: a.AspPubkey,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported vtxo script: %T", vtxoScript)
|
||||||
|
}
|
||||||
|
|
||||||
forfeitLeaf, err := forfeitClosure.Leaf()
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2040,22 +2083,16 @@ func (a *covenantlessArkClient) createAndSignForfeits(
|
|||||||
func (a *covenantlessArkClient) coinSelectOnchain(
|
func (a *covenantlessArkClient) coinSelectOnchain(
|
||||||
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
|
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
|
||||||
) ([]explorer.Utxo, uint64, error) {
|
) ([]explorer.Utxo, uint64, error) {
|
||||||
offchainAddrs, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx)
|
_, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
|
now := time.Now()
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
|
fetchedUtxos := make([]explorer.Utxo, 0)
|
||||||
descriptorStr := strings.ReplaceAll(
|
for _, addr := range boardingAddrs {
|
||||||
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
boardingScript, err := bitcointree.ParseVtxoScript(addr.Descriptor)
|
||||||
)
|
|
||||||
|
|
||||||
boardingScript, err := bitcointree.ParseVtxoScript(descriptorStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -2065,14 +2102,9 @@ func (a *covenantlessArkClient) coinSelectOnchain(
|
|||||||
if defaultVtxo, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok {
|
if defaultVtxo, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok {
|
||||||
boardingTimeout = defaultVtxo.ExitDelay
|
boardingTimeout = defaultVtxo.ExitDelay
|
||||||
} else {
|
} else {
|
||||||
return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", descriptorStr)
|
return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", addr.Descriptor)
|
||||||
}
|
}
|
||||||
|
utxos, err := a.explorer.GetUtxos(addr.Address)
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
fetchedUtxos := make([]explorer.Utxo, 0)
|
|
||||||
for _, addr := range boardingAddrs {
|
|
||||||
utxos, err := a.explorer.GetUtxos(addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -2108,7 +2140,7 @@ func (a *covenantlessArkClient) coinSelectOnchain(
|
|||||||
|
|
||||||
fetchedUtxos = make([]explorer.Utxo, 0)
|
fetchedUtxos = make([]explorer.Utxo, 0)
|
||||||
for _, addr := range redemptionAddrs {
|
for _, addr := range redemptionAddrs {
|
||||||
utxos, err := a.explorer.GetUtxos(addr)
|
utxos, err := a.explorer.GetUtxos(addr.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -2185,11 +2217,11 @@ func (a *covenantlessArkClient) getRedeemBranches(
|
|||||||
// Currently, the returned balance is calculated from both spendable and
|
// Currently, the returned balance is calculated from both spendable and
|
||||||
// pending vtxos.
|
// pending vtxos.
|
||||||
func (a *covenantlessArkClient) getOffchainBalance(
|
func (a *covenantlessArkClient) getOffchainBalance(
|
||||||
ctx context.Context, addr string, computeVtxoExpiration bool,
|
ctx context.Context, computeVtxoExpiration bool,
|
||||||
) (uint64, map[int64]uint64, error) {
|
) (uint64, map[int64]uint64, error) {
|
||||||
amountByExpiration := make(map[int64]uint64, 0)
|
amountByExpiration := make(map[int64]uint64, 0)
|
||||||
|
|
||||||
vtxos, _, err := a.getVtxos(ctx, addr, computeVtxoExpiration)
|
vtxos, _, err := a.getVtxos(ctx, computeVtxoExpiration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
@@ -2222,13 +2254,13 @@ func (a *covenantlessArkClient) getAllBoardingUtxos(
|
|||||||
utxos := []explorer.Utxo{}
|
utxos := []explorer.Utxo{}
|
||||||
ignoreVtxos := make(map[string]struct{}, 0)
|
ignoreVtxos := make(map[string]struct{}, 0)
|
||||||
for _, addr := range boardingAddrs {
|
for _, addr := range boardingAddrs {
|
||||||
txs, err := a.explorer.GetTxs(addr)
|
txs, err := a.explorer.GetTxs(addr.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
for i, vout := range tx.Vout {
|
for i, vout := range tx.Vout {
|
||||||
if vout.Address == addr {
|
if vout.Address == addr.Address {
|
||||||
spentStatuses, err := a.explorer.GetTxOutspends(tx.Txid)
|
spentStatuses, err := a.explorer.GetTxOutspends(tx.Txid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -2255,22 +2287,16 @@ func (a *covenantlessArkClient) getAllBoardingUtxos(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
|
func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
|
||||||
offchainAddrs, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
|
_, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
|
claimable := make([]explorer.Utxo, 0)
|
||||||
if err != nil {
|
now := time.Now()
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
|
for _, addr := range boardingAddrs {
|
||||||
descriptorStr := strings.ReplaceAll(
|
boardingScript, err := bitcointree.ParseVtxoScript(addr.Descriptor)
|
||||||
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
|
||||||
)
|
|
||||||
|
|
||||||
boardingScript, err := bitcointree.ParseVtxoScript(descriptorStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -2280,14 +2306,10 @@ func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context) (
|
|||||||
if defaultVtxo, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok {
|
if defaultVtxo, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok {
|
||||||
boardingTimeout = defaultVtxo.ExitDelay
|
boardingTimeout = defaultVtxo.ExitDelay
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("unsupported boarding descriptor: %s", descriptorStr)
|
return nil, fmt.Errorf("unsupported boarding descriptor: %s", addr.Descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
claimable := make([]explorer.Utxo, 0)
|
boardingUtxos, err := a.explorer.GetUtxos(addr.Address)
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
for _, addr := range boardingAddrs {
|
|
||||||
boardingUtxos, err := a.explorer.GetUtxos(addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -2306,7 +2328,7 @@ func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) getVtxos(
|
func (a *covenantlessArkClient) getVtxos(
|
||||||
ctx context.Context, _ string, computeVtxoExpiration bool,
|
ctx context.Context, computeVtxoExpiration bool,
|
||||||
) ([]client.Vtxo, []client.Vtxo, error) {
|
) ([]client.Vtxo, []client.Vtxo, error) {
|
||||||
spendableVtxos, _, err := a.ListVtxos(ctx)
|
spendableVtxos, _, err := a.ListVtxos(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2347,14 +2369,10 @@ func (a *covenantlessArkClient) getVtxos(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) selfTransferAllPendingPayments(
|
func (a *covenantlessArkClient) selfTransferAllPendingPayments(
|
||||||
ctx context.Context, pendingVtxos []client.Vtxo, boardingUtxos []explorer.Utxo, myself client.Output, mypubkey string,
|
ctx context.Context, pendingVtxos []client.DescriptorVtxo, boardingUtxos []explorer.Utxo, myself client.Output, boardingDescriptor string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
inputs := make([]client.Input, 0, len(pendingVtxos)+len(boardingUtxos))
|
inputs := make([]client.Input, 0, len(pendingVtxos)+len(boardingUtxos))
|
||||||
|
|
||||||
boardingDescriptor := strings.ReplaceAll(
|
|
||||||
a.BoardingDescriptorTemplate, "USER", mypubkey[2:],
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, coin := range pendingVtxos {
|
for _, coin := range pendingVtxos {
|
||||||
inputs = append(inputs, client.Input{
|
inputs = append(inputs, client.Input{
|
||||||
Outpoint: client.Outpoint{
|
Outpoint: client.Outpoint{
|
||||||
@@ -2404,42 +2422,6 @@ func (a *covenantlessArkClient) selfTransferAllPendingPayments(
|
|||||||
return roundTxid, nil
|
return roundTxid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) offchainAddressToReversibleVtxoDescriptor(myaddr string, receiveraddr string) (string, error) {
|
|
||||||
_, receiverPubkey, aspPubkey, err := common.DecodeAddress(receiveraddr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, userPubKey, _, err := common.DecodeAddress(myaddr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoScript := bitcointree.ReversibleVtxoScript{
|
|
||||||
Owner: receiverPubkey,
|
|
||||||
Sender: userPubKey,
|
|
||||||
Asp: aspPubkey,
|
|
||||||
ExitDelay: uint(a.UnilateralExitDelay),
|
|
||||||
}
|
|
||||||
|
|
||||||
return vtxoScript.ToDescriptor(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *covenantlessArkClient) offchainAddressToDefaultVtxoDescriptor(addr string) (string, error) {
|
|
||||||
_, userPubKey, aspPubkey, err := common.DecodeAddress(addr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoScript := bitcointree.DefaultVtxoScript{
|
|
||||||
Owner: userPubKey,
|
|
||||||
Asp: aspPubkey,
|
|
||||||
ExitDelay: uint(a.UnilateralExitDelay),
|
|
||||||
}
|
|
||||||
|
|
||||||
return vtxoScript.ToDescriptor(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getBoardingTxs builds the boarding tx history from onchain utxos:
|
// getBoardingTxs builds the boarding tx history from onchain utxos:
|
||||||
// - unspent utxo => pending boarding tx
|
// - unspent utxo => pending boarding tx
|
||||||
// - spent utxo => claimed boarding tx
|
// - spent utxo => claimed boarding tx
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func CoinSelect(
|
func CoinSelect(
|
||||||
vtxos []client.Vtxo, amount, dust uint64, sortByExpirationTime bool,
|
vtxos []client.DescriptorVtxo, amount, dust uint64, sortByExpirationTime bool,
|
||||||
) ([]client.Vtxo, uint64, error) {
|
) ([]client.DescriptorVtxo, uint64, error) {
|
||||||
selected := make([]client.Vtxo, 0)
|
selected := make([]client.DescriptorVtxo, 0)
|
||||||
notSelected := make([]client.Vtxo, 0)
|
notSelected := make([]client.DescriptorVtxo, 0)
|
||||||
selectedAmount := uint64(0)
|
selectedAmount := uint64(0)
|
||||||
|
|
||||||
if sortByExpirationTime {
|
if sortByExpirationTime {
|
||||||
|
|||||||
@@ -43,41 +43,96 @@ func NewBitcoinWallet(
|
|||||||
|
|
||||||
func (w *bitcoinWallet) GetAddresses(
|
func (w *bitcoinWallet) GetAddresses(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) ([]string, []string, []string, error) {
|
) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, []wallet.DescriptorAddress, error) {
|
||||||
offchainAddr, boardingAddr, redemptionAddr, err := w.getAddress(ctx)
|
offchainAddr, boardingAddr, err := w.getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddrs := []string{offchainAddr}
|
encodedOffchainAddr, err := offchainAddr.Address.Encode()
|
||||||
boardingAddrs := []string{boardingAddr}
|
if err != nil {
|
||||||
redemptionAddrs := []string{redemptionAddr}
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := w.configStore.GetData(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
netParams := utils.ToBitcoinNetwork(data.Network)
|
||||||
|
|
||||||
|
redemptionAddr, err := btcutil.NewAddressTaproot(
|
||||||
|
schnorr.SerializePubKey(offchainAddr.Address.VtxoTapKey),
|
||||||
|
&netParams,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offchainAddrs := []wallet.DescriptorAddress{
|
||||||
|
{
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
Address: encodedOffchainAddr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
boardingAddrs := []wallet.DescriptorAddress{
|
||||||
|
{
|
||||||
|
Descriptor: boardingAddr.Descriptor,
|
||||||
|
Address: boardingAddr.Address,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
redemptionAddrs := []wallet.DescriptorAddress{
|
||||||
|
{
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
Address: redemptionAddr.EncodeAddress(),
|
||||||
|
},
|
||||||
|
}
|
||||||
return offchainAddrs, boardingAddrs, redemptionAddrs, nil
|
return offchainAddrs, boardingAddrs, redemptionAddrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *bitcoinWallet) NewAddress(
|
func (w *bitcoinWallet) NewAddress(
|
||||||
ctx context.Context, _ bool,
|
ctx context.Context, _ bool,
|
||||||
) (string, string, error) {
|
) (*wallet.DescriptorAddress, *wallet.DescriptorAddress, error) {
|
||||||
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
|
offchainAddr, boardingAddr, err := w.getAddress(ctx)
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return offchainAddr, boardingAddr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *bitcoinWallet) NewAddresses(
|
|
||||||
ctx context.Context, _ bool, num int,
|
|
||||||
) ([]string, []string, error) {
|
|
||||||
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddrs := make([]string, 0, num)
|
encodedOffchainAddr, err := offchainAddr.Address.Encode()
|
||||||
boardingAddrs := make([]string, 0, num)
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &wallet.DescriptorAddress{
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
Address: encodedOffchainAddr,
|
||||||
|
}, boardingAddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *bitcoinWallet) NewAddresses(
|
||||||
|
ctx context.Context, _ bool, num int,
|
||||||
|
) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, error) {
|
||||||
|
offchainAddr, boardingAddr, err := w.getAddress(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offchainAddrs := make([]wallet.DescriptorAddress, 0, num)
|
||||||
|
boardingAddrs := make([]wallet.DescriptorAddress, 0, num)
|
||||||
for i := 0; i < num; i++ {
|
for i := 0; i < num; i++ {
|
||||||
offchainAddrs = append(offchainAddrs, offchainAddr)
|
encodedOffchainAddr, err := offchainAddr.Address.Encode()
|
||||||
boardingAddrs = append(boardingAddrs, boardingAddr)
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offchainAddrs = append(offchainAddrs, wallet.DescriptorAddress{
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
Address: encodedOffchainAddr,
|
||||||
|
})
|
||||||
|
boardingAddrs = append(boardingAddrs, wallet.DescriptorAddress{
|
||||||
|
Descriptor: boardingAddr.Descriptor,
|
||||||
|
Address: boardingAddr.Address,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return offchainAddrs, boardingAddrs, nil
|
return offchainAddrs, boardingAddrs, nil
|
||||||
}
|
}
|
||||||
@@ -205,19 +260,21 @@ func (s *bitcoinWallet) SignTransaction(
|
|||||||
|
|
||||||
func (w *bitcoinWallet) getAddress(
|
func (w *bitcoinWallet) getAddress(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (string, string, string, error) {
|
) (
|
||||||
|
*struct {
|
||||||
|
Address common.Address
|
||||||
|
Descriptor string
|
||||||
|
},
|
||||||
|
*wallet.DescriptorAddress,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
if w.walletData == nil {
|
if w.walletData == nil {
|
||||||
return "", "", "", fmt.Errorf("wallet not initialized")
|
return nil, nil, fmt.Errorf("wallet not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := w.configStore.GetData(ctx)
|
data, err := w.configStore.GetData(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, nil, err
|
||||||
}
|
|
||||||
|
|
||||||
offchainAddr, err := common.EncodeAddress(data.Network.Addr, w.walletData.Pubkey, data.AspPubkey)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
netParams := utils.ToBitcoinNetwork(data.Network)
|
netParams := utils.ToBitcoinNetwork(data.Network)
|
||||||
@@ -230,15 +287,13 @@ func (w *bitcoinWallet) getAddress(
|
|||||||
|
|
||||||
vtxoTapKey, _, err := defaultVtxoScript.TapTree()
|
vtxoTapKey, _, err := defaultVtxoScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
redemptionAddr, err := btcutil.NewAddressTaproot(
|
offchainAddress := &common.Address{
|
||||||
schnorr.SerializePubKey(vtxoTapKey),
|
HRP: data.Network.Addr,
|
||||||
&netParams,
|
Asp: data.AspPubkey,
|
||||||
)
|
VtxoTapKey: vtxoTapKey,
|
||||||
if err != nil {
|
|
||||||
return "", "", "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
|
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
|
||||||
@@ -248,12 +303,12 @@ func (w *bitcoinWallet) getAddress(
|
|||||||
|
|
||||||
boardingVtxoScript, err := bitcointree.ParseVtxoScript(descriptorStr)
|
boardingVtxoScript, err := bitcointree.ParseVtxoScript(descriptorStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
boardingTapKey, _, err := boardingVtxoScript.TapTree()
|
boardingTapKey, _, err := boardingVtxoScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
boardingAddr, err := btcutil.NewAddressTaproot(
|
boardingAddr, err := btcutil.NewAddressTaproot(
|
||||||
@@ -261,8 +316,18 @@ func (w *bitcoinWallet) getAddress(
|
|||||||
&netParams,
|
&netParams,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return offchainAddr, boardingAddr.EncodeAddress(), redemptionAddr.EncodeAddress(), nil
|
return &struct {
|
||||||
|
Address common.Address
|
||||||
|
Descriptor string
|
||||||
|
}{
|
||||||
|
*offchainAddress, defaultVtxoScript.ToDescriptor(),
|
||||||
|
},
|
||||||
|
&wallet.DescriptorAddress{
|
||||||
|
Descriptor: descriptorStr,
|
||||||
|
Address: boardingAddr.EncodeAddress(),
|
||||||
|
},
|
||||||
|
nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,41 +44,103 @@ func NewLiquidWallet(
|
|||||||
|
|
||||||
func (w *liquidWallet) GetAddresses(
|
func (w *liquidWallet) GetAddresses(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) ([]string, []string, []string, error) {
|
) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, []wallet.DescriptorAddress, error) {
|
||||||
offchainAddr, boardingAddr, redemptionAddr, err := w.getAddress(ctx)
|
offchainAddr, boardingAddr, err := w.getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddrs := []string{offchainAddr}
|
encodedOffchainAddr, err := offchainAddr.Address.Encode()
|
||||||
boardingAddrs := []string{boardingAddr}
|
if err != nil {
|
||||||
redemptionAddrs := []string{redemptionAddr}
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := w.configStore.GetData(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
liquidNet := utils.ToElementsNetwork(data.Network)
|
||||||
|
|
||||||
|
vtxoP2TR, err := payment.FromTweakedKey(offchainAddr.Address.VtxoTapKey, &liquidNet, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
redemptionAddr, err := vtxoP2TR.TaprootAddress()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offchainAddrs := []wallet.DescriptorAddress{
|
||||||
|
{
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
Address: encodedOffchainAddr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
boardingAddrs := []wallet.DescriptorAddress{
|
||||||
|
{
|
||||||
|
Descriptor: boardingAddr.Descriptor,
|
||||||
|
Address: boardingAddr.Address,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
redemptionAddrs := []wallet.DescriptorAddress{
|
||||||
|
{
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
Address: redemptionAddr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
return offchainAddrs, boardingAddrs, redemptionAddrs, nil
|
return offchainAddrs, boardingAddrs, redemptionAddrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *liquidWallet) NewAddress(
|
func (w *liquidWallet) NewAddress(
|
||||||
ctx context.Context, _ bool,
|
ctx context.Context, _ bool,
|
||||||
) (string, string, error) {
|
) (*wallet.DescriptorAddress, *wallet.DescriptorAddress, error) {
|
||||||
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
|
offchainAddr, boardingAddr, err := w.getAddress(ctx)
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return offchainAddr, boardingAddr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *liquidWallet) NewAddresses(
|
|
||||||
ctx context.Context, _ bool, num int,
|
|
||||||
) ([]string, []string, error) {
|
|
||||||
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddrs := make([]string, 0, num)
|
encodedOffchainAddr, err := offchainAddr.Address.Encode()
|
||||||
boardingAddrs := make([]string, 0, num)
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &wallet.DescriptorAddress{
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
Address: encodedOffchainAddr,
|
||||||
|
}, &wallet.DescriptorAddress{
|
||||||
|
Descriptor: boardingAddr.Descriptor,
|
||||||
|
Address: boardingAddr.Address,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *liquidWallet) NewAddresses(
|
||||||
|
ctx context.Context, _ bool, num int,
|
||||||
|
) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, error) {
|
||||||
|
offchainAddr, boardingAddr, err := w.getAddress(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offchainAddrs := make([]wallet.DescriptorAddress, 0, num)
|
||||||
|
boardingAddrs := make([]wallet.DescriptorAddress, 0, num)
|
||||||
for i := 0; i < num; i++ {
|
for i := 0; i < num; i++ {
|
||||||
offchainAddrs = append(offchainAddrs, offchainAddr)
|
encodedOffchainAddr, err := offchainAddr.Address.Encode()
|
||||||
boardingAddrs = append(boardingAddrs, boardingAddr)
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offchainAddrs = append(offchainAddrs, wallet.DescriptorAddress{
|
||||||
|
Descriptor: offchainAddr.Descriptor,
|
||||||
|
Address: encodedOffchainAddr,
|
||||||
|
})
|
||||||
|
boardingAddrs = append(boardingAddrs, wallet.DescriptorAddress{
|
||||||
|
Descriptor: boardingAddr.Descriptor,
|
||||||
|
Address: boardingAddr.Address,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return offchainAddrs, boardingAddrs, nil
|
return offchainAddrs, boardingAddrs, nil
|
||||||
}
|
}
|
||||||
@@ -227,21 +289,21 @@ func (s *liquidWallet) SignTransaction(
|
|||||||
|
|
||||||
func (w *liquidWallet) getAddress(
|
func (w *liquidWallet) getAddress(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (string, string, string, error) {
|
) (
|
||||||
|
*struct {
|
||||||
|
Address common.Address
|
||||||
|
Descriptor string
|
||||||
|
},
|
||||||
|
*wallet.DescriptorAddress,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
if w.walletData == nil {
|
if w.walletData == nil {
|
||||||
return "", "", "", fmt.Errorf("wallet not initialized")
|
return nil, nil, fmt.Errorf("wallet not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := w.configStore.GetData(ctx)
|
data, err := w.configStore.GetData(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, nil, err
|
||||||
}
|
|
||||||
|
|
||||||
offchainAddr, err := common.EncodeAddress(
|
|
||||||
data.Network.Addr, w.walletData.Pubkey, data.AspPubkey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
liquidNet := utils.ToElementsNetwork(data.Network)
|
liquidNet := utils.ToElementsNetwork(data.Network)
|
||||||
@@ -254,17 +316,13 @@ func (w *liquidWallet) getAddress(
|
|||||||
|
|
||||||
vtxoTapKey, _, err := vtxoScript.TapTree()
|
vtxoTapKey, _, err := vtxoScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxoP2tr, err := payment.FromTweakedKey(vtxoTapKey, &liquidNet, nil)
|
offchainAddr := &common.Address{
|
||||||
if err != nil {
|
HRP: data.Network.Addr,
|
||||||
return "", "", "", err
|
Asp: data.AspPubkey,
|
||||||
}
|
VtxoTapKey: vtxoTapKey,
|
||||||
|
|
||||||
redemptionAddr, err := vtxoP2tr.TaprootAddress()
|
|
||||||
if err != nil {
|
|
||||||
return "", "", "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
|
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
|
||||||
@@ -274,23 +332,32 @@ func (w *liquidWallet) getAddress(
|
|||||||
|
|
||||||
onboardingScript, err := tree.ParseVtxoScript(descriptorStr)
|
onboardingScript, err := tree.ParseVtxoScript(descriptorStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tapKey, _, err := onboardingScript.TapTree()
|
tapKey, _, err := onboardingScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
p2tr, err := payment.FromTweakedKey(tapKey, &liquidNet, nil)
|
p2tr, err := payment.FromTweakedKey(tapKey, &liquidNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
boardingAddr, err := p2tr.TaprootAddress()
|
boardingAddr, err := p2tr.TaprootAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return offchainAddr, boardingAddr, redemptionAddr, nil
|
return &struct {
|
||||||
|
Address common.Address
|
||||||
|
Descriptor string
|
||||||
|
}{
|
||||||
|
Address: *offchainAddr,
|
||||||
|
Descriptor: vtxoScript.ToDescriptor(),
|
||||||
|
}, &wallet.DescriptorAddress{
|
||||||
|
Descriptor: descriptorStr,
|
||||||
|
Address: boardingAddr,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ const (
|
|||||||
SingleKeyWallet = "singlekey"
|
SingleKeyWallet = "singlekey"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DescriptorAddress struct {
|
||||||
|
Descriptor string
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
type WalletService interface {
|
type WalletService interface {
|
||||||
GetType() string
|
GetType() string
|
||||||
Create(
|
Create(
|
||||||
@@ -20,13 +25,13 @@ type WalletService interface {
|
|||||||
IsLocked() bool
|
IsLocked() bool
|
||||||
GetAddresses(
|
GetAddresses(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (offchainAddresses, boardingAddresses, redemptionAddresses []string, err error)
|
) (offchainAddresses, boardingAddresses, redemptionAddresses []DescriptorAddress, err error)
|
||||||
NewAddress(
|
NewAddress(
|
||||||
ctx context.Context, change bool,
|
ctx context.Context, change bool,
|
||||||
) (offchainAddr, onchainAddr string, err error)
|
) (offchainAddr, onchainAddr *DescriptorAddress, err error)
|
||||||
NewAddresses(
|
NewAddresses(
|
||||||
ctx context.Context, change bool, num int,
|
ctx context.Context, change bool, num int,
|
||||||
) (offchainAddresses, onchainAddresses []string, err error)
|
) (offchainAddresses, onchainAddresses []DescriptorAddress, err error)
|
||||||
SignTransaction(
|
SignTransaction(
|
||||||
ctx context.Context, explorerSvc explorer.Explorer, tx string,
|
ctx context.Context, explorerSvc explorer.Explorer, tx string,
|
||||||
) (signedTx string, err error)
|
) (signedTx string, err error)
|
||||||
|
|||||||
@@ -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) {
|
func (s *covenantService) SpendVtxos(ctx context.Context, inputs []ports.Input) (string, error) {
|
||||||
vtxosInputs := make([]domain.Vtxo, 0)
|
vtxosInputs := make([]domain.Vtxo, 0)
|
||||||
boardingInputs := make([]ports.BoardingInput, 0)
|
boardingInputs := make([]ports.BoardingInput, 0)
|
||||||
|
descriptors := make(map[domain.VtxoKey]string)
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
|
|
||||||
@@ -218,13 +219,14 @@ func (s *covenantService) SpendVtxos(ctx context.Context, inputs []ports.Input)
|
|||||||
}
|
}
|
||||||
|
|
||||||
vtxosInputs = append(vtxosInputs, vtxo)
|
vtxosInputs = append(vtxosInputs, vtxo)
|
||||||
|
descriptors[vtxo.VtxoKey] = input.Descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
payment, err := domain.NewPayment(vtxosInputs)
|
payment, err := domain.NewPayment(vtxosInputs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := s.paymentRequests.push(*payment, boardingInputs); err != nil {
|
if err := s.paymentRequests.push(*payment, boardingInputs, descriptors); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return payment.Id, nil
|
return payment.Id, nil
|
||||||
@@ -324,7 +326,7 @@ func (s *covenantService) CompleteAsyncPayment(ctx context.Context, redeemTx str
|
|||||||
return fmt.Errorf("unimplemented")
|
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")
|
return "", fmt.Errorf("unimplemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,9 +347,14 @@ func (s *covenantService) SignRoundTx(ctx context.Context, signedRoundTx string)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantService) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, []domain.Vtxo, error) {
|
func (s *covenantService) ListVtxos(ctx context.Context, address string) ([]domain.Vtxo, []domain.Vtxo, error) {
|
||||||
pk := hex.EncodeToString(pubkey.SerializeCompressed())
|
decodedAddress, err := common.DecodeAddress(address)
|
||||||
return s.repoManager.Vtxos().GetAllVtxos(ctx, pk)
|
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 {
|
func (s *covenantService) GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent {
|
||||||
@@ -482,7 +489,7 @@ func (s *covenantService) startFinalization() {
|
|||||||
if num > paymentsThreshold {
|
if num > paymentsThreshold {
|
||||||
num = paymentsThreshold
|
num = paymentsThreshold
|
||||||
}
|
}
|
||||||
payments, boardingInputs, _ := s.paymentRequests.pop(num)
|
payments, boardingInputs, descriptors, _ := s.paymentRequests.pop(num)
|
||||||
if _, err := round.RegisterPayments(payments); err != nil {
|
if _, err := round.RegisterPayments(payments); err != nil {
|
||||||
round.Fail(fmt.Errorf("failed to register payments: %s", err))
|
round.Fail(fmt.Errorf("failed to register payments: %s", err))
|
||||||
log.WithError(err).Warn("failed to register payments")
|
log.WithError(err).Warn("failed to register payments")
|
||||||
@@ -517,7 +524,7 @@ func (s *covenantService) startFinalization() {
|
|||||||
minRelayFeeRate := s.wallet.MinRelayFeeRate(ctx)
|
minRelayFeeRate := s.wallet.MinRelayFeeRate(ctx)
|
||||||
|
|
||||||
if needForfeits {
|
if needForfeits {
|
||||||
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(unsignedPoolTx, payments, minRelayFeeRate)
|
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(unsignedPoolTx, payments, descriptors, minRelayFeeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
|
round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
|
||||||
log.WithError(err).Warn("failed to create connectors and forfeit txs")
|
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
|
continue // skip fee outputs
|
||||||
}
|
}
|
||||||
|
|
||||||
desc := ""
|
vtxoTapKey, err := schnorr.ParsePubKey(out.Script[2:])
|
||||||
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 {
|
if err != nil {
|
||||||
log.WithError(err).Warn("failed to parse vtxo descriptor")
|
log.WithError(err).Warn("failed to parse vtxo tap key")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tapKey, _, err := vtxoScript.TapTree()
|
vtxoPubkey := hex.EncodeToString(schnorr.SerializePubKey(vtxoTapKey))
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if found {
|
|
||||||
vtxos = append(vtxos, domain.Vtxo{
|
vtxos = append(vtxos, domain.Vtxo{
|
||||||
VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)},
|
VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)},
|
||||||
Receiver: domain.Receiver{Descriptor: desc, Amount: uint64(out.Value)},
|
Pubkey: vtxoPubkey,
|
||||||
|
Amount: uint64(out.Value),
|
||||||
RoundTxid: round.Txid,
|
RoundTxid: round.Txid,
|
||||||
})
|
})
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vtxos
|
return vtxos
|
||||||
@@ -1050,17 +1024,17 @@ func (s *covenantService) restoreWatchingVtxos() error {
|
|||||||
func (s *covenantService) extractVtxosScripts(vtxos []domain.Vtxo) ([]string, error) {
|
func (s *covenantService) extractVtxosScripts(vtxos []domain.Vtxo) ([]string, error) {
|
||||||
indexedScripts := make(map[string]struct{})
|
indexedScripts := make(map[string]struct{})
|
||||||
for _, vtxo := range vtxos {
|
for _, vtxo := range vtxos {
|
||||||
vtxoScript, err := tree.ParseVtxoScript(vtxo.Receiver.Descriptor)
|
vtxoTapKeyBytes, err := hex.DecodeString(vtxo.Pubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tapKey, _, err := vtxoScript.TapTree()
|
vtxoTapKey, err := schnorr.ParsePubKey(vtxoTapKeyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
script, err := common.P2TRScript(tapKey)
|
script, err := common.P2TRScript(vtxoTapKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
@@ -177,37 +178,28 @@ func (s *covenantlessService) CompleteAsyncPayment(
|
|||||||
|
|
||||||
// verify that the vtxo is spendable
|
// 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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get vtxo: %s", err)
|
return fmt.Errorf("failed to get vtxo: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(vtxo) == 0 {
|
if len(vtxos) == 0 {
|
||||||
return fmt.Errorf("vtxo not found")
|
return fmt.Errorf("vtxo not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if vtxo[0].Spent {
|
vtxo := vtxos[0]
|
||||||
|
if vtxo.Spent {
|
||||||
return fmt.Errorf("vtxo already spent")
|
return fmt.Errorf("vtxo already spent")
|
||||||
}
|
}
|
||||||
|
|
||||||
if vtxo[0].Redeemed {
|
if vtxo.Redeemed {
|
||||||
return fmt.Errorf("vtxo already redeemed")
|
return fmt.Errorf("vtxo already redeemed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if vtxo[0].Swept {
|
if vtxo.Swept {
|
||||||
return fmt.Errorf("vtxo already 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
|
// verify that the user signs a forfeit closure
|
||||||
var userPubKey *secp256k1.PublicKey
|
var userPubKey *secp256k1.PublicKey
|
||||||
|
|
||||||
@@ -228,6 +220,16 @@ func (s *covenantlessService) CompleteAsyncPayment(
|
|||||||
return fmt.Errorf("redeem transaction is not signed")
|
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
|
// verify witness utxo
|
||||||
pkscript, err := common.P2TRScript(vtxoTapKey)
|
pkscript, err := common.P2TRScript(vtxoTapKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -238,7 +240,7 @@ func (s *covenantlessService) CompleteAsyncPayment(
|
|||||||
return fmt.Errorf("witness utxo script mismatch")
|
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")
|
return fmt.Errorf("witness utxo value mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,19 +262,23 @@ func (s *covenantlessService) CompleteAsyncPayment(
|
|||||||
vtxos := make([]domain.Vtxo, 0, len(asyncPayData.receivers))
|
vtxos := make([]domain.Vtxo, 0, len(asyncPayData.receivers))
|
||||||
|
|
||||||
for outIndex, out := range redeemPtx.UnsignedTx.TxOut {
|
for outIndex, out := range redeemPtx.UnsignedTx.TxOut {
|
||||||
desc := asyncPayData.receivers[outIndex].Descriptor
|
vtxoTapKey, err := schnorr.ParsePubKey(out.PkScript[2:])
|
||||||
_, _, _, _, err := descriptor.ParseReversibleVtxoDescriptor(desc)
|
if err != nil {
|
||||||
isPending := 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{
|
vtxos = append(vtxos, domain.Vtxo{
|
||||||
VtxoKey: domain.VtxoKey{
|
VtxoKey: domain.VtxoKey{
|
||||||
Txid: redeemTxid,
|
Txid: redeemTxid,
|
||||||
VOut: uint32(outIndex),
|
VOut: uint32(outIndex),
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Pubkey: vtxoPubkey,
|
||||||
Descriptor: desc,
|
|
||||||
Amount: uint64(out.Value),
|
Amount: uint64(out.Value),
|
||||||
},
|
|
||||||
ExpireAt: asyncPayData.expireAt,
|
ExpireAt: asyncPayData.expireAt,
|
||||||
RedeemTx: redeemTx,
|
RedeemTx: redeemTx,
|
||||||
Pending: isPending,
|
Pending: isPending,
|
||||||
@@ -309,11 +315,16 @@ func (s *covenantlessService) CompleteAsyncPayment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) CreateAsyncPayment(
|
func (s *covenantlessService) CreateAsyncPayment(
|
||||||
ctx context.Context, inputs []ports.Input, receivers []domain.Receiver,
|
ctx context.Context, inputs []AsyncPaymentInput, receivers []domain.Receiver,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
vtxosKeys := make([]domain.VtxoKey, 0, len(inputs))
|
vtxosKeys := make([]domain.VtxoKey, 0, len(inputs))
|
||||||
|
descriptors := make(map[domain.VtxoKey]string)
|
||||||
|
forfeitLeaves := make(map[domain.VtxoKey]chainhash.Hash)
|
||||||
|
|
||||||
for _, in := range inputs {
|
for _, in := range inputs {
|
||||||
vtxosKeys = append(vtxosKeys, in.VtxoKey)
|
vtxosKeys = append(vtxosKeys, in.VtxoKey)
|
||||||
|
descriptors[in.VtxoKey] = in.Descriptor
|
||||||
|
forfeitLeaves[in.VtxoKey] = in.ForfeitLeafHash
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, vtxosKeys)
|
vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, vtxosKeys)
|
||||||
@@ -351,7 +362,7 @@ func (s *covenantlessService) CreateAsyncPayment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
redeemTx, err := s.builder.BuildAsyncPaymentTransactions(
|
redeemTx, err := s.builder.BuildAsyncPaymentTransactions(
|
||||||
vtxosInputs, s.pubkey, receivers,
|
vtxosInputs, descriptors, forfeitLeaves, receivers,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to build async payment txs: %s", err)
|
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()
|
now := time.Now().Unix()
|
||||||
|
|
||||||
boardingTxs := make(map[string]wire.MsgTx, 0) // txid -> txhex
|
boardingTxs := make(map[string]wire.MsgTx, 0) // txid -> txhex
|
||||||
|
descriptors := make(map[domain.VtxoKey]string)
|
||||||
for _, input := range inputs {
|
for _, input := range inputs {
|
||||||
vtxosResult, err := s.repoManager.Vtxos().GetVtxos(ctx, []domain.VtxoKey{input.VtxoKey})
|
vtxosResult, err := s.repoManager.Vtxos().GetVtxos(ctx, []domain.VtxoKey{input.VtxoKey})
|
||||||
if err != nil || len(vtxosResult) == 0 {
|
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)
|
return "", fmt.Errorf("input %s:%d already swept", vtxo.Txid, vtxo.VOut)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
descriptors[vtxo.VtxoKey] = input.Descriptor
|
||||||
|
|
||||||
vtxosInputs = append(vtxosInputs, vtxo)
|
vtxosInputs = append(vtxosInputs, vtxo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,7 +481,7 @@ func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Inp
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := s.paymentRequests.push(*payment, boardingInputs); err != nil {
|
if err := s.paymentRequests.push(*payment, boardingInputs, descriptors); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return payment.Id, nil
|
return payment.Id, nil
|
||||||
@@ -572,9 +585,14 @@ func (s *covenantlessService) SignRoundTx(ctx context.Context, signedRoundTx str
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, []domain.Vtxo, error) {
|
func (s *covenantlessService) ListVtxos(ctx context.Context, address string) ([]domain.Vtxo, []domain.Vtxo, error) {
|
||||||
pk := hex.EncodeToString(pubkey.SerializeCompressed())
|
decodedAddress, err := common.DecodeAddress(address)
|
||||||
return s.repoManager.Vtxos().GetAllVtxos(ctx, pk)
|
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 {
|
func (s *covenantlessService) GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent {
|
||||||
@@ -771,7 +789,7 @@ func (s *covenantlessService) startFinalization() {
|
|||||||
if num > paymentsThreshold {
|
if num > paymentsThreshold {
|
||||||
num = paymentsThreshold
|
num = paymentsThreshold
|
||||||
}
|
}
|
||||||
payments, boardingInputs, cosigners := s.paymentRequests.pop(num)
|
payments, boardingInputs, descriptors, cosigners := s.paymentRequests.pop(num)
|
||||||
if len(payments) > len(cosigners) {
|
if len(payments) > len(cosigners) {
|
||||||
err := fmt.Errorf("missing ephemeral key for payments")
|
err := fmt.Errorf("missing ephemeral key for payments")
|
||||||
round.Fail(fmt.Errorf("round aborted: %s", err))
|
round.Fail(fmt.Errorf("round aborted: %s", err))
|
||||||
@@ -973,7 +991,7 @@ func (s *covenantlessService) startFinalization() {
|
|||||||
minRelayFeeRate := s.wallet.MinRelayFeeRate(ctx)
|
minRelayFeeRate := s.wallet.MinRelayFeeRate(ctx)
|
||||||
|
|
||||||
if needForfeits {
|
if needForfeits {
|
||||||
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(unsignedRoundTx, payments, minRelayFeeRate)
|
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(unsignedRoundTx, payments, descriptors, minRelayFeeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
|
round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
|
||||||
log.WithError(err).Warn("failed to create connectors and forfeit txs")
|
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
|
continue
|
||||||
}
|
}
|
||||||
for i, out := range tx.UnsignedTx.TxOut {
|
for i, out := range tx.UnsignedTx.TxOut {
|
||||||
desc := ""
|
vtxoTapKey, err := schnorr.ParsePubKey(out.PkScript[2:])
|
||||||
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 {
|
if err != nil {
|
||||||
log.WithError(err).Warn("failed to parse vtxo descriptor")
|
log.WithError(err).Warn("failed to parse vtxo tap key")
|
||||||
continue
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if found {
|
|
||||||
vtxos = append(vtxos, domain.Vtxo{
|
vtxos = append(vtxos, domain.Vtxo{
|
||||||
VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)},
|
VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)},
|
||||||
Receiver: domain.Receiver{Descriptor: desc, Amount: uint64(out.Value)},
|
Pubkey: hex.EncodeToString(schnorr.SerializePubKey(vtxoTapKey)),
|
||||||
|
Amount: uint64(out.Value),
|
||||||
RoundTxid: round.Txid,
|
RoundTxid: round.Txid,
|
||||||
})
|
})
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vtxos
|
return vtxos
|
||||||
@@ -1450,17 +1433,17 @@ func (s *covenantlessService) extractVtxosScripts(vtxos []domain.Vtxo) ([]string
|
|||||||
indexedScripts := make(map[string]struct{})
|
indexedScripts := make(map[string]struct{})
|
||||||
|
|
||||||
for _, vtxo := range vtxos {
|
for _, vtxo := range vtxos {
|
||||||
vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Receiver.Descriptor)
|
vtxoTapKeyBytes, err := hex.DecodeString(vtxo.Pubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tapKey, _, err := vtxoScript.TapTree()
|
vtxoTapKey, err := schnorr.ParsePubKey(vtxoTapKeyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
script, err := common.P2TRScript(tapKey)
|
script, err := common.P2TRScript(vtxoTapKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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/domain"
|
||||||
"github.com/ark-network/ark/server/internal/core/ports"
|
"github.com/ark-network/ark/server/internal/core/ports"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,6 +13,11 @@ var (
|
|||||||
paymentsThreshold = int64(128)
|
paymentsThreshold = int64(128)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AsyncPaymentInput struct {
|
||||||
|
ports.Input
|
||||||
|
ForfeitLeafHash chainhash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Start() error
|
Start() error
|
||||||
Stop()
|
Stop()
|
||||||
@@ -27,12 +33,12 @@ type Service interface {
|
|||||||
ctx context.Context, paymentId string,
|
ctx context.Context, paymentId string,
|
||||||
) (lastEvent domain.RoundEvent, err error)
|
) (lastEvent domain.RoundEvent, err error)
|
||||||
ListVtxos(
|
ListVtxos(
|
||||||
ctx context.Context, pubkey *secp256k1.PublicKey,
|
ctx context.Context, address string,
|
||||||
) (spendableVtxos, spentVtxos []domain.Vtxo, err error)
|
) (spendableVtxos, spentVtxos []domain.Vtxo, err error)
|
||||||
GetInfo(ctx context.Context) (*ServiceInfo, error)
|
GetInfo(ctx context.Context) (*ServiceInfo, error)
|
||||||
// Async payments
|
// Async payments
|
||||||
CreateAsyncPayment(
|
CreateAsyncPayment(
|
||||||
ctx context.Context, inputs []ports.Input, receivers []domain.Receiver,
|
ctx context.Context, inputs []AsyncPaymentInput, receivers []domain.Receiver,
|
||||||
) (string, error)
|
) (string, error)
|
||||||
CompleteAsyncPayment(
|
CompleteAsyncPayment(
|
||||||
ctx context.Context, redeemTx string,
|
ctx context.Context, redeemTx string,
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ type timedPayment struct {
|
|||||||
type paymentsMap struct {
|
type paymentsMap struct {
|
||||||
lock *sync.RWMutex
|
lock *sync.RWMutex
|
||||||
payments map[string]*timedPayment
|
payments map[string]*timedPayment
|
||||||
|
descriptors map[domain.VtxoKey]string
|
||||||
ephemeralKeys map[string]*secp256k1.PublicKey
|
ephemeralKeys map[string]*secp256k1.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPaymentsMap() *paymentsMap {
|
func newPaymentsMap() *paymentsMap {
|
||||||
paymentsById := make(map[string]*timedPayment)
|
paymentsById := make(map[string]*timedPayment)
|
||||||
lock := &sync.RWMutex{}
|
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 {
|
func (m *paymentsMap) len() int64 {
|
||||||
@@ -58,7 +59,11 @@ func (m *paymentsMap) delete(id string) error {
|
|||||||
return nil
|
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()
|
m.lock.Lock()
|
||||||
defer m.lock.Unlock()
|
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{}}
|
m.payments[payment.Id] = &timedPayment{payment, boardingInputs, time.Now(), time.Time{}}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -102,7 +111,7 @@ func (m *paymentsMap) pushEphemeralKey(paymentId string, pubkey *secp256k1.Publi
|
|||||||
return nil
|
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()
|
m.lock.Lock()
|
||||||
defer m.lock.Unlock()
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
@@ -129,6 +138,7 @@ func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, [
|
|||||||
payments := make([]domain.Payment, 0, num)
|
payments := make([]domain.Payment, 0, num)
|
||||||
boardingInputs := make([]ports.BoardingInput, 0)
|
boardingInputs := make([]ports.BoardingInput, 0)
|
||||||
cosigners := make([]*secp256k1.PublicKey, 0, num)
|
cosigners := make([]*secp256k1.PublicKey, 0, num)
|
||||||
|
descriptors := make(map[domain.VtxoKey]string)
|
||||||
for _, p := range paymentsByTime[:num] {
|
for _, p := range paymentsByTime[:num] {
|
||||||
boardingInputs = append(boardingInputs, p.boardingInputs...)
|
boardingInputs = append(boardingInputs, p.boardingInputs...)
|
||||||
payments = append(payments, p.Payment)
|
payments = append(payments, p.Payment)
|
||||||
@@ -136,9 +146,15 @@ func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, [
|
|||||||
cosigners = append(cosigners, pubkey)
|
cosigners = append(cosigners, pubkey)
|
||||||
delete(m.ephemeralKeys, p.Payment.Id)
|
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)
|
delete(m.payments, p.Id)
|
||||||
}
|
}
|
||||||
return payments, boardingInputs, cosigners
|
return payments, boardingInputs, descriptors, cosigners
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *paymentsMap) update(payment domain.Payment) error {
|
func (m *paymentsMap) update(payment domain.Payment) error {
|
||||||
|
|||||||
@@ -68,9 +68,12 @@ func (p Payment) validate(ignoreOuts bool) error {
|
|||||||
return fmt.Errorf("missing outputs")
|
return fmt.Errorf("missing outputs")
|
||||||
}
|
}
|
||||||
for _, r := range p.Receivers {
|
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")
|
return fmt.Errorf("missing receiver destination")
|
||||||
}
|
}
|
||||||
|
if r.Amount == 0 {
|
||||||
|
return fmt.Errorf("missing receiver amount")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -80,6 +83,10 @@ type VtxoKey struct {
|
|||||||
VOut uint32
|
VOut uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k VtxoKey) String() string {
|
||||||
|
return fmt.Sprintf("%s:%d", k.Txid, k.VOut)
|
||||||
|
}
|
||||||
|
|
||||||
func (k VtxoKey) Hash() string {
|
func (k VtxoKey) Hash() string {
|
||||||
calcHash := func(buf []byte, hasher hash.Hash) []byte {
|
calcHash := func(buf []byte, hasher hash.Hash) []byte {
|
||||||
_, _ = hasher.Write(buf)
|
_, _ = hasher.Write(buf)
|
||||||
@@ -96,9 +103,9 @@ func (k VtxoKey) Hash() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Receiver struct {
|
type Receiver struct {
|
||||||
Descriptor string
|
|
||||||
Amount uint64
|
Amount uint64
|
||||||
OnchainAddress string
|
OnchainAddress string // onchain
|
||||||
|
Pubkey string // offchain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Receiver) IsOnchain() bool {
|
func (r Receiver) IsOnchain() bool {
|
||||||
@@ -107,7 +114,8 @@ func (r Receiver) IsOnchain() bool {
|
|||||||
|
|
||||||
type Vtxo struct {
|
type Vtxo struct {
|
||||||
VtxoKey
|
VtxoKey
|
||||||
Receiver
|
Amount uint64
|
||||||
|
Pubkey string
|
||||||
RoundTxid string
|
RoundTxid string
|
||||||
SpentBy string // round txid or async redeem txid
|
SpentBy string // round txid or async redeem txid
|
||||||
Spent bool
|
Spent bool
|
||||||
|
|||||||
@@ -1,22 +1,14 @@
|
|||||||
package domain_test
|
package domain_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common/descriptor"
|
|
||||||
"github.com/ark-network/ark/server/internal/core/domain"
|
"github.com/ark-network/ark/server/internal/core/domain"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var desc = fmt.Sprintf(
|
// x-only pubkey
|
||||||
descriptor.DefaultVtxoDescriptorTemplate,
|
const pubkey = "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967"
|
||||||
"030000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
512,
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
)
|
|
||||||
|
|
||||||
var inputs = []domain.Vtxo{
|
var inputs = []domain.Vtxo{
|
||||||
{
|
{
|
||||||
@@ -24,11 +16,9 @@ var inputs = []domain.Vtxo{
|
|||||||
Txid: "0000000000000000000000000000000000000000000000000000000000000000",
|
Txid: "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
VOut: 0,
|
VOut: 0,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Pubkey: pubkey,
|
||||||
Descriptor: desc,
|
|
||||||
Amount: 1000,
|
Amount: 1000,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPayment(t *testing.T) {
|
func TestPayment(t *testing.T) {
|
||||||
@@ -51,11 +41,11 @@ func TestPayment(t *testing.T) {
|
|||||||
|
|
||||||
err = payment.AddReceivers([]domain.Receiver{
|
err = payment.AddReceivers([]domain.Receiver{
|
||||||
{
|
{
|
||||||
Descriptor: desc,
|
Pubkey: pubkey,
|
||||||
Amount: 450,
|
Amount: 450,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Descriptor: desc,
|
Pubkey: pubkey,
|
||||||
Amount: 550,
|
Amount: 550,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,23 +20,21 @@ var (
|
|||||||
Txid: txid,
|
Txid: txid,
|
||||||
VOut: 0,
|
VOut: 0,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Pubkey: pubkey,
|
||||||
Descriptor: desc,
|
|
||||||
Amount: 2000,
|
Amount: 2000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
Receivers: []domain.Receiver{
|
Receivers: []domain.Receiver{
|
||||||
{
|
{
|
||||||
Descriptor: desc,
|
Pubkey: pubkey,
|
||||||
Amount: 700,
|
Amount: 700,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Descriptor: desc,
|
Pubkey: pubkey,
|
||||||
Amount: 700,
|
Amount: 700,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Descriptor: desc,
|
Pubkey: pubkey,
|
||||||
Amount: 600,
|
Amount: 600,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -49,24 +47,20 @@ var (
|
|||||||
Txid: txid,
|
Txid: txid,
|
||||||
VOut: 0,
|
VOut: 0,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Pubkey: pubkey,
|
||||||
Descriptor: desc,
|
|
||||||
Amount: 1000,
|
Amount: 1000,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
{
|
{
|
||||||
VtxoKey: domain.VtxoKey{
|
VtxoKey: domain.VtxoKey{
|
||||||
Txid: txid,
|
Txid: txid,
|
||||||
VOut: 0,
|
VOut: 0,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Pubkey: pubkey,
|
||||||
Descriptor: desc,
|
|
||||||
Amount: 1000,
|
Amount: 1000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
Receivers: []domain.Receiver{{
|
Receivers: []domain.Receiver{{
|
||||||
Descriptor: desc,
|
Pubkey: pubkey,
|
||||||
Amount: 2000,
|
Amount: 2000,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,7 +32,12 @@ type TxBuilder interface {
|
|||||||
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, boardingInputs []BoardingInput, sweptRounds []domain.Round,
|
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, boardingInputs []BoardingInput, sweptRounds []domain.Round,
|
||||||
cosigners ...*secp256k1.PublicKey,
|
cosigners ...*secp256k1.PublicKey,
|
||||||
) (roundTx string, congestionTree tree.CongestionTree, connectorAddress string, err error)
|
) (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)
|
BuildSweepTx(inputs []SweepInput) (signedSweepTx string, err error)
|
||||||
GetSweepInput(node tree.Node) (lifetime int64, sweepInput SweepInput, err error)
|
GetSweepInput(node tree.Node) (lifetime int64, sweepInput SweepInput, err error)
|
||||||
FinalizeAndExtract(tx string) (txhex string, 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)
|
FindLeaves(congestionTree tree.CongestionTree, fromtxid string, vout uint32) (leaves []tree.Node, err error)
|
||||||
BuildAsyncPaymentTransactions(
|
BuildAsyncPaymentTransactions(
|
||||||
vtxosToSpend []domain.Vtxo,
|
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)
|
) (string, error)
|
||||||
VerifyAndCombinePartialTx(dest string, src string) (string, error)
|
VerifyAndCombinePartialTx(dest string, src string) (string, error)
|
||||||
GetTxID(tx string) (string, error)
|
GetTxID(tx string) (string, error)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ark-network/ark/server/internal/core/domain"
|
"github.com/ark-network/ark/server/internal/core/domain"
|
||||||
@@ -101,13 +100,7 @@ func (r *vtxoRepository) GetAllVtxos(
|
|||||||
) ([]domain.Vtxo, []domain.Vtxo, error) {
|
) ([]domain.Vtxo, []domain.Vtxo, error) {
|
||||||
query := badgerhold.Where("Redeemed").Eq(false)
|
query := badgerhold.Where("Redeemed").Eq(false)
|
||||||
if len(pubkey) > 0 {
|
if len(pubkey) > 0 {
|
||||||
if len(pubkey) == 66 {
|
query = query.And("Pubkey").Eq(pubkey)
|
||||||
pubkey = pubkey[2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
query = query.And("Descriptor").RegExp(
|
|
||||||
regexp.MustCompile(fmt.Sprintf(".*%s.*", pubkey)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
vtxos, err := r.findVtxos(ctx, query)
|
vtxos, err := r.findVtxos(ctx, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,14 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common/descriptor"
|
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/ark-network/ark/server/internal/core/domain"
|
"github.com/ark-network/ark/server/internal/core/domain"
|
||||||
"github.com/ark-network/ark/server/internal/core/ports"
|
"github.com/ark-network/ark/server/internal/core/ports"
|
||||||
@@ -24,26 +22,8 @@ import (
|
|||||||
const (
|
const (
|
||||||
emptyPtx = "cHNldP8BAgQCAAAAAQQBAAEFAQABBgEDAfsEAgAAAAA="
|
emptyPtx = "cHNldP8BAgQCAAAAAQQBAAEFAQABBgEDAfsEAgAAAAA="
|
||||||
emptyTx = "0200000000000000000000"
|
emptyTx = "0200000000000000000000"
|
||||||
pubkey1 = "00000000000000000000000000000000000000000000000000000000000000001"
|
pubkey = "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967"
|
||||||
pubkey2 = "00000000000000000000000000000000000000000000000000000000000000002"
|
pubkey2 = "33ffb3dee353b1a9ebe4ced64b946238d0a4ac364f275d771da6ad2445d07ae0"
|
||||||
)
|
|
||||||
|
|
||||||
var desc1 = fmt.Sprintf(
|
|
||||||
descriptor.DefaultVtxoDescriptorTemplate,
|
|
||||||
randomString(66),
|
|
||||||
pubkey1,
|
|
||||||
pubkey1,
|
|
||||||
512,
|
|
||||||
pubkey1,
|
|
||||||
)
|
|
||||||
|
|
||||||
var desc2 = fmt.Sprintf(
|
|
||||||
descriptor.DefaultVtxoDescriptorTemplate,
|
|
||||||
randomString(66),
|
|
||||||
pubkey2,
|
|
||||||
pubkey2,
|
|
||||||
512,
|
|
||||||
pubkey2,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var congestionTree = [][]tree.Node{
|
var congestionTree = [][]tree.Node{
|
||||||
@@ -270,14 +250,12 @@ func testRoundRepository(t *testing.T, svc ports.RepoManager) {
|
|||||||
},
|
},
|
||||||
RoundTxid: randomString(32),
|
RoundTxid: randomString(32),
|
||||||
ExpireAt: 7980322,
|
ExpireAt: 7980322,
|
||||||
Receiver: domain.Receiver{
|
Pubkey: randomString(32),
|
||||||
Descriptor: randomString(120),
|
|
||||||
Amount: 300,
|
Amount: 300,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
Receivers: []domain.Receiver{{
|
Receivers: []domain.Receiver{{
|
||||||
Descriptor: randomString(120),
|
Pubkey: randomString(32),
|
||||||
Amount: 300,
|
Amount: 300,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
@@ -292,19 +270,17 @@ func testRoundRepository(t *testing.T, svc ports.RepoManager) {
|
|||||||
},
|
},
|
||||||
RoundTxid: randomString(32),
|
RoundTxid: randomString(32),
|
||||||
ExpireAt: 7980322,
|
ExpireAt: 7980322,
|
||||||
Receiver: domain.Receiver{
|
Pubkey: randomString(32),
|
||||||
Descriptor: randomString(120),
|
|
||||||
Amount: 600,
|
Amount: 600,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
Receivers: []domain.Receiver{
|
Receivers: []domain.Receiver{
|
||||||
{
|
{
|
||||||
Descriptor: randomString(120),
|
Pubkey: randomString(32),
|
||||||
Amount: 400,
|
Amount: 400,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Descriptor: randomString(120),
|
Pubkey: randomString(32),
|
||||||
Amount: 200,
|
Amount: 200,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -370,31 +346,25 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
|
|||||||
Txid: randomString(32),
|
Txid: randomString(32),
|
||||||
VOut: 0,
|
VOut: 0,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Pubkey: pubkey,
|
||||||
Descriptor: desc1,
|
|
||||||
Amount: 1000,
|
Amount: 1000,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
{
|
{
|
||||||
VtxoKey: domain.VtxoKey{
|
VtxoKey: domain.VtxoKey{
|
||||||
Txid: randomString(32),
|
Txid: randomString(32),
|
||||||
VOut: 1,
|
VOut: 1,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Pubkey: pubkey,
|
||||||
Descriptor: desc1,
|
|
||||||
Amount: 2000,
|
Amount: 2000,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
newVtxos := append(userVtxos, domain.Vtxo{
|
newVtxos := append(userVtxos, domain.Vtxo{
|
||||||
VtxoKey: domain.VtxoKey{
|
VtxoKey: domain.VtxoKey{
|
||||||
Txid: randomString(32),
|
Txid: randomString(32),
|
||||||
VOut: 1,
|
VOut: 1,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Pubkey: pubkey2,
|
||||||
Descriptor: desc2,
|
|
||||||
Amount: 2000,
|
Amount: 2000,
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
vtxoKeys := make([]domain.VtxoKey, 0, len(userVtxos))
|
vtxoKeys := make([]domain.VtxoKey, 0, len(userVtxos))
|
||||||
@@ -406,7 +376,7 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Empty(t, vtxos)
|
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.NoError(t, err)
|
||||||
require.Empty(t, spendableVtxos)
|
require.Empty(t, spendableVtxos)
|
||||||
require.Empty(t, spentVtxos)
|
require.Empty(t, spentVtxos)
|
||||||
@@ -423,7 +393,7 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Exactly(t, userVtxos, vtxos)
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
sortedVtxos := sortVtxos(userVtxos)
|
sortedVtxos := sortVtxos(userVtxos)
|
||||||
@@ -449,7 +419,7 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
|
|||||||
require.True(t, v.Spent)
|
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.NoError(t, err)
|
||||||
require.Exactly(t, vtxos[1:], spendableVtxos)
|
require.Exactly(t, vtxos[1:], spendableVtxos)
|
||||||
require.Len(t, spentVtxos, len(vtxoKeys[:1]))
|
require.Len(t, spentVtxos, len(vtxoKeys[:1]))
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ CREATE TABLE IF NOT EXISTS payment (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS receiver (
|
CREATE TABLE IF NOT EXISTS receiver (
|
||||||
payment_id TEXT NOT NULL,
|
payment_id TEXT NOT NULL,
|
||||||
pubkey TEXT NOT NULL,
|
pubkey TEXT,
|
||||||
|
onchain_address TEXT,
|
||||||
amount INTEGER NOT NULL,
|
amount INTEGER NOT NULL,
|
||||||
onchain_address TEXT NOT NULL,
|
|
||||||
FOREIGN KEY (payment_id) REFERENCES payment(id),
|
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 (
|
CREATE TABLE IF NOT EXISTS tx (
|
||||||
@@ -54,6 +54,7 @@ CREATE TABLE IF NOT EXISTS vtxo (
|
|||||||
expire_at INTEGER NOT NULL,
|
expire_at INTEGER NOT NULL,
|
||||||
payment_id TEXT,
|
payment_id TEXT,
|
||||||
redeem_tx TEXT,
|
redeem_tx TEXT,
|
||||||
|
pending BOOLEAN NOT NULL,
|
||||||
PRIMARY KEY (txid, vout),
|
PRIMARY KEY (txid, vout),
|
||||||
FOREIGN KEY (payment_id) REFERENCES payment(id)
|
FOREIGN KEY (payment_id) REFERENCES payment(id)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE vtxo DROP COLUMN pending;
|
|
||||||
@@ -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;
|
|
||||||
@@ -165,9 +165,15 @@ func (r *roundRepository) AddOrUpdateRound(ctx context.Context, round domain.Rou
|
|||||||
ctx,
|
ctx,
|
||||||
queries.UpsertReceiverParams{
|
queries.UpsertReceiverParams{
|
||||||
PaymentID: payment.Id,
|
PaymentID: payment.Id,
|
||||||
Descriptor: receiver.Descriptor,
|
|
||||||
Amount: int64(receiver.Amount),
|
Amount: int64(receiver.Amount),
|
||||||
OnchainAddress: receiver.OnchainAddress,
|
Pubkey: sql.NullString{
|
||||||
|
String: receiver.Pubkey,
|
||||||
|
Valid: len(receiver.Pubkey) > 0,
|
||||||
|
},
|
||||||
|
OnchainAddress: sql.NullString{
|
||||||
|
String: receiver.OnchainAddress,
|
||||||
|
Valid: len(receiver.OnchainAddress) > 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("failed to upsert receiver: %w", err)
|
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 {
|
func rowToReceiver(row queries.PaymentReceiverVw) domain.Receiver {
|
||||||
return domain.Receiver{
|
return domain.Receiver{
|
||||||
Descriptor: row.Descriptor.String,
|
|
||||||
Amount: uint64(row.Amount.Int64),
|
Amount: uint64(row.Amount.Int64),
|
||||||
|
Pubkey: row.Pubkey.String,
|
||||||
OnchainAddress: row.OnchainAddress.String,
|
OnchainAddress: row.OnchainAddress.String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,8 +419,8 @@ func readRoundRows(rows []roundPaymentTxReceiverVtxoRow) ([]*domain.Round, error
|
|||||||
|
|
||||||
found := false
|
found := false
|
||||||
for _, rcv := range payment.Receivers {
|
for _, rcv := range payment.Receivers {
|
||||||
if v.receiver.Descriptor.Valid && v.receiver.Amount.Valid {
|
if (v.receiver.Pubkey.Valid || v.receiver.OnchainAddress.Valid) && v.receiver.Amount.Valid {
|
||||||
if rcv.Descriptor == v.receiver.Descriptor.String && int64(rcv.Amount) == v.receiver.Amount.Int64 {
|
if rcv.Pubkey == v.receiver.Pubkey.String && rcv.OnchainAddress == v.receiver.OnchainAddress.String && int64(rcv.Amount) == v.receiver.Amount.Int64 {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -469,10 +475,8 @@ func rowToPaymentVtxoVw(row queries.PaymentVtxoVw) domain.Vtxo {
|
|||||||
Txid: row.Txid.String,
|
Txid: row.Txid.String,
|
||||||
VOut: uint32(row.Vout.Int64),
|
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,
|
RoundTxid: row.PoolTx.String,
|
||||||
SpentBy: row.SpentBy.String,
|
SpentBy: row.SpentBy.String,
|
||||||
Spent: row.Spent.Bool,
|
Spent: row.Spent.Bool,
|
||||||
|
|||||||
@@ -15,14 +15,15 @@ type Payment struct {
|
|||||||
|
|
||||||
type PaymentReceiverVw struct {
|
type PaymentReceiverVw struct {
|
||||||
PaymentID sql.NullString
|
PaymentID sql.NullString
|
||||||
Descriptor sql.NullString
|
Pubkey sql.NullString
|
||||||
Amount sql.NullInt64
|
|
||||||
OnchainAddress sql.NullString
|
OnchainAddress sql.NullString
|
||||||
|
Amount sql.NullInt64
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaymentVtxoVw struct {
|
type PaymentVtxoVw struct {
|
||||||
Txid sql.NullString
|
Txid sql.NullString
|
||||||
Vout sql.NullInt64
|
Vout sql.NullInt64
|
||||||
|
Pubkey sql.NullString
|
||||||
Amount sql.NullInt64
|
Amount sql.NullInt64
|
||||||
PoolTx sql.NullString
|
PoolTx sql.NullString
|
||||||
SpentBy sql.NullString
|
SpentBy sql.NullString
|
||||||
@@ -32,15 +33,14 @@ type PaymentVtxoVw struct {
|
|||||||
ExpireAt sql.NullInt64
|
ExpireAt sql.NullInt64
|
||||||
PaymentID sql.NullString
|
PaymentID sql.NullString
|
||||||
RedeemTx sql.NullString
|
RedeemTx sql.NullString
|
||||||
Descriptor sql.NullString
|
|
||||||
Pending sql.NullBool
|
Pending sql.NullBool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Receiver struct {
|
type Receiver struct {
|
||||||
PaymentID string
|
PaymentID string
|
||||||
Descriptor string
|
Pubkey sql.NullString
|
||||||
|
OnchainAddress sql.NullString
|
||||||
Amount int64
|
Amount int64
|
||||||
OnchainAddress string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Round struct {
|
type Round struct {
|
||||||
@@ -90,6 +90,7 @@ type Tx struct {
|
|||||||
type Vtxo struct {
|
type Vtxo struct {
|
||||||
Txid string
|
Txid string
|
||||||
Vout int64
|
Vout int64
|
||||||
|
Pubkey string
|
||||||
Amount int64
|
Amount int64
|
||||||
PoolTx string
|
PoolTx string
|
||||||
SpentBy string
|
SpentBy string
|
||||||
@@ -99,6 +100,5 @@ type Vtxo struct {
|
|||||||
ExpireAt int64
|
ExpireAt int64
|
||||||
PaymentID sql.NullString
|
PaymentID sql.NullString
|
||||||
RedeemTx sql.NullString
|
RedeemTx sql.NullString
|
||||||
Descriptor sql.NullString
|
|
||||||
Pending bool
|
Pending bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (q *Queries) MarkVtxoAsSwept(ctx context.Context, arg MarkVtxoAsSweptParams
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectNotRedeemedVtxos = `-- name: SelectNotRedeemedVtxos :many
|
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
|
WHERE redeemed = false
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -74,6 +74,7 @@ func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]SelectNotRedeem
|
|||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Vtxo.Txid,
|
&i.Vtxo.Txid,
|
||||||
&i.Vtxo.Vout,
|
&i.Vtxo.Vout,
|
||||||
|
&i.Vtxo.Pubkey,
|
||||||
&i.Vtxo.Amount,
|
&i.Vtxo.Amount,
|
||||||
&i.Vtxo.PoolTx,
|
&i.Vtxo.PoolTx,
|
||||||
&i.Vtxo.SpentBy,
|
&i.Vtxo.SpentBy,
|
||||||
@@ -83,7 +84,6 @@ func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]SelectNotRedeem
|
|||||||
&i.Vtxo.ExpireAt,
|
&i.Vtxo.ExpireAt,
|
||||||
&i.Vtxo.PaymentID,
|
&i.Vtxo.PaymentID,
|
||||||
&i.Vtxo.RedeemTx,
|
&i.Vtxo.RedeemTx,
|
||||||
&i.Vtxo.Descriptor,
|
|
||||||
&i.Vtxo.Pending,
|
&i.Vtxo.Pending,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -100,16 +100,16 @@ func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]SelectNotRedeem
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectNotRedeemedVtxosWithPubkey = `-- name: SelectNotRedeemedVtxosWithPubkey :many
|
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
|
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 INSTR(descriptor, ?) > 0
|
WHERE redeemed = false AND pubkey = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
type SelectNotRedeemedVtxosWithPubkeyRow struct {
|
type SelectNotRedeemedVtxosWithPubkeyRow struct {
|
||||||
Vtxo Vtxo
|
Vtxo Vtxo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, instr string) ([]SelectNotRedeemedVtxosWithPubkeyRow, error) {
|
func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, pubkey string) ([]SelectNotRedeemedVtxosWithPubkeyRow, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, selectNotRedeemedVtxosWithPubkey, instr)
|
rows, err := q.db.QueryContext(ctx, selectNotRedeemedVtxosWithPubkey, pubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -120,6 +120,7 @@ func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, instr st
|
|||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Vtxo.Txid,
|
&i.Vtxo.Txid,
|
||||||
&i.Vtxo.Vout,
|
&i.Vtxo.Vout,
|
||||||
|
&i.Vtxo.Pubkey,
|
||||||
&i.Vtxo.Amount,
|
&i.Vtxo.Amount,
|
||||||
&i.Vtxo.PoolTx,
|
&i.Vtxo.PoolTx,
|
||||||
&i.Vtxo.SpentBy,
|
&i.Vtxo.SpentBy,
|
||||||
@@ -129,7 +130,6 @@ func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, instr st
|
|||||||
&i.Vtxo.ExpireAt,
|
&i.Vtxo.ExpireAt,
|
||||||
&i.Vtxo.PaymentID,
|
&i.Vtxo.PaymentID,
|
||||||
&i.Vtxo.RedeemTx,
|
&i.Vtxo.RedeemTx,
|
||||||
&i.Vtxo.Descriptor,
|
|
||||||
&i.Vtxo.Pending,
|
&i.Vtxo.Pending,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
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,
|
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_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,
|
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_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.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_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
|
FROM round
|
||||||
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
|
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
|
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.ParentTxid,
|
||||||
&i.RoundTxVw.IsLeaf,
|
&i.RoundTxVw.IsLeaf,
|
||||||
&i.PaymentReceiverVw.PaymentID,
|
&i.PaymentReceiverVw.PaymentID,
|
||||||
&i.PaymentReceiverVw.Descriptor,
|
&i.PaymentReceiverVw.Pubkey,
|
||||||
&i.PaymentReceiverVw.Amount,
|
|
||||||
&i.PaymentReceiverVw.OnchainAddress,
|
&i.PaymentReceiverVw.OnchainAddress,
|
||||||
|
&i.PaymentReceiverVw.Amount,
|
||||||
&i.PaymentVtxoVw.Txid,
|
&i.PaymentVtxoVw.Txid,
|
||||||
&i.PaymentVtxoVw.Vout,
|
&i.PaymentVtxoVw.Vout,
|
||||||
|
&i.PaymentVtxoVw.Pubkey,
|
||||||
&i.PaymentVtxoVw.Amount,
|
&i.PaymentVtxoVw.Amount,
|
||||||
&i.PaymentVtxoVw.PoolTx,
|
&i.PaymentVtxoVw.PoolTx,
|
||||||
&i.PaymentVtxoVw.SpentBy,
|
&i.PaymentVtxoVw.SpentBy,
|
||||||
@@ -274,7 +275,6 @@ func (q *Queries) SelectRoundWithRoundId(ctx context.Context, id string) ([]Sele
|
|||||||
&i.PaymentVtxoVw.ExpireAt,
|
&i.PaymentVtxoVw.ExpireAt,
|
||||||
&i.PaymentVtxoVw.PaymentID,
|
&i.PaymentVtxoVw.PaymentID,
|
||||||
&i.PaymentVtxoVw.RedeemTx,
|
&i.PaymentVtxoVw.RedeemTx,
|
||||||
&i.PaymentVtxoVw.Descriptor,
|
|
||||||
&i.PaymentVtxoVw.Pending,
|
&i.PaymentVtxoVw.Pending,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
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,
|
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_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,
|
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_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.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_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
|
FROM round
|
||||||
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
|
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
|
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.ParentTxid,
|
||||||
&i.RoundTxVw.IsLeaf,
|
&i.RoundTxVw.IsLeaf,
|
||||||
&i.PaymentReceiverVw.PaymentID,
|
&i.PaymentReceiverVw.PaymentID,
|
||||||
&i.PaymentReceiverVw.Descriptor,
|
&i.PaymentReceiverVw.Pubkey,
|
||||||
&i.PaymentReceiverVw.Amount,
|
|
||||||
&i.PaymentReceiverVw.OnchainAddress,
|
&i.PaymentReceiverVw.OnchainAddress,
|
||||||
|
&i.PaymentReceiverVw.Amount,
|
||||||
&i.PaymentVtxoVw.Txid,
|
&i.PaymentVtxoVw.Txid,
|
||||||
&i.PaymentVtxoVw.Vout,
|
&i.PaymentVtxoVw.Vout,
|
||||||
|
&i.PaymentVtxoVw.Pubkey,
|
||||||
&i.PaymentVtxoVw.Amount,
|
&i.PaymentVtxoVw.Amount,
|
||||||
&i.PaymentVtxoVw.PoolTx,
|
&i.PaymentVtxoVw.PoolTx,
|
||||||
&i.PaymentVtxoVw.SpentBy,
|
&i.PaymentVtxoVw.SpentBy,
|
||||||
@@ -360,7 +361,6 @@ func (q *Queries) SelectRoundWithRoundTxId(ctx context.Context, txid string) ([]
|
|||||||
&i.PaymentVtxoVw.ExpireAt,
|
&i.PaymentVtxoVw.ExpireAt,
|
||||||
&i.PaymentVtxoVw.PaymentID,
|
&i.PaymentVtxoVw.PaymentID,
|
||||||
&i.PaymentVtxoVw.RedeemTx,
|
&i.PaymentVtxoVw.RedeemTx,
|
||||||
&i.PaymentVtxoVw.Descriptor,
|
|
||||||
&i.PaymentVtxoVw.Pending,
|
&i.PaymentVtxoVw.Pending,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
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,
|
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_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,
|
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_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.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_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
|
FROM round
|
||||||
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
|
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
|
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.ParentTxid,
|
||||||
&i.RoundTxVw.IsLeaf,
|
&i.RoundTxVw.IsLeaf,
|
||||||
&i.PaymentReceiverVw.PaymentID,
|
&i.PaymentReceiverVw.PaymentID,
|
||||||
&i.PaymentReceiverVw.Descriptor,
|
&i.PaymentReceiverVw.Pubkey,
|
||||||
&i.PaymentReceiverVw.Amount,
|
|
||||||
&i.PaymentReceiverVw.OnchainAddress,
|
&i.PaymentReceiverVw.OnchainAddress,
|
||||||
|
&i.PaymentReceiverVw.Amount,
|
||||||
&i.PaymentVtxoVw.Txid,
|
&i.PaymentVtxoVw.Txid,
|
||||||
&i.PaymentVtxoVw.Vout,
|
&i.PaymentVtxoVw.Vout,
|
||||||
|
&i.PaymentVtxoVw.Pubkey,
|
||||||
&i.PaymentVtxoVw.Amount,
|
&i.PaymentVtxoVw.Amount,
|
||||||
&i.PaymentVtxoVw.PoolTx,
|
&i.PaymentVtxoVw.PoolTx,
|
||||||
&i.PaymentVtxoVw.SpentBy,
|
&i.PaymentVtxoVw.SpentBy,
|
||||||
@@ -446,7 +447,6 @@ func (q *Queries) SelectSweepableRounds(ctx context.Context) ([]SelectSweepableR
|
|||||||
&i.PaymentVtxoVw.ExpireAt,
|
&i.PaymentVtxoVw.ExpireAt,
|
||||||
&i.PaymentVtxoVw.PaymentID,
|
&i.PaymentVtxoVw.PaymentID,
|
||||||
&i.PaymentVtxoVw.RedeemTx,
|
&i.PaymentVtxoVw.RedeemTx,
|
||||||
&i.PaymentVtxoVw.Descriptor,
|
|
||||||
&i.PaymentVtxoVw.Pending,
|
&i.PaymentVtxoVw.Pending,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -463,7 +463,7 @@ func (q *Queries) SelectSweepableRounds(ctx context.Context) ([]SelectSweepableR
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectSweepableVtxos = `-- name: SelectSweepableVtxos :many
|
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
|
WHERE redeemed = false AND swept = false
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -483,6 +483,7 @@ func (q *Queries) SelectSweepableVtxos(ctx context.Context) ([]SelectSweepableVt
|
|||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Vtxo.Txid,
|
&i.Vtxo.Txid,
|
||||||
&i.Vtxo.Vout,
|
&i.Vtxo.Vout,
|
||||||
|
&i.Vtxo.Pubkey,
|
||||||
&i.Vtxo.Amount,
|
&i.Vtxo.Amount,
|
||||||
&i.Vtxo.PoolTx,
|
&i.Vtxo.PoolTx,
|
||||||
&i.Vtxo.SpentBy,
|
&i.Vtxo.SpentBy,
|
||||||
@@ -492,7 +493,6 @@ func (q *Queries) SelectSweepableVtxos(ctx context.Context) ([]SelectSweepableVt
|
|||||||
&i.Vtxo.ExpireAt,
|
&i.Vtxo.ExpireAt,
|
||||||
&i.Vtxo.PaymentID,
|
&i.Vtxo.PaymentID,
|
||||||
&i.Vtxo.RedeemTx,
|
&i.Vtxo.RedeemTx,
|
||||||
&i.Vtxo.Descriptor,
|
|
||||||
&i.Vtxo.Pending,
|
&i.Vtxo.Pending,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
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,
|
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_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,
|
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_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.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_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
|
FROM round
|
||||||
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
|
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
|
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.ParentTxid,
|
||||||
&i.RoundTxVw.IsLeaf,
|
&i.RoundTxVw.IsLeaf,
|
||||||
&i.PaymentReceiverVw.PaymentID,
|
&i.PaymentReceiverVw.PaymentID,
|
||||||
&i.PaymentReceiverVw.Descriptor,
|
&i.PaymentReceiverVw.Pubkey,
|
||||||
&i.PaymentReceiverVw.Amount,
|
|
||||||
&i.PaymentReceiverVw.OnchainAddress,
|
&i.PaymentReceiverVw.OnchainAddress,
|
||||||
|
&i.PaymentReceiverVw.Amount,
|
||||||
&i.PaymentVtxoVw.Txid,
|
&i.PaymentVtxoVw.Txid,
|
||||||
&i.PaymentVtxoVw.Vout,
|
&i.PaymentVtxoVw.Vout,
|
||||||
|
&i.PaymentVtxoVw.Pubkey,
|
||||||
&i.PaymentVtxoVw.Amount,
|
&i.PaymentVtxoVw.Amount,
|
||||||
&i.PaymentVtxoVw.PoolTx,
|
&i.PaymentVtxoVw.PoolTx,
|
||||||
&i.PaymentVtxoVw.SpentBy,
|
&i.PaymentVtxoVw.SpentBy,
|
||||||
@@ -578,7 +579,6 @@ func (q *Queries) SelectSweptRounds(ctx context.Context) ([]SelectSweptRoundsRow
|
|||||||
&i.PaymentVtxoVw.ExpireAt,
|
&i.PaymentVtxoVw.ExpireAt,
|
||||||
&i.PaymentVtxoVw.PaymentID,
|
&i.PaymentVtxoVw.PaymentID,
|
||||||
&i.PaymentVtxoVw.RedeemTx,
|
&i.PaymentVtxoVw.RedeemTx,
|
||||||
&i.PaymentVtxoVw.Descriptor,
|
|
||||||
&i.PaymentVtxoVw.Pending,
|
&i.PaymentVtxoVw.Pending,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -595,7 +595,7 @@ func (q *Queries) SelectSweptRounds(ctx context.Context) ([]SelectSweptRoundsRow
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectVtxoByOutpoint = `-- name: SelectVtxoByOutpoint :one
|
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 = ?
|
WHERE txid = ? AND vout = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -614,6 +614,7 @@ func (q *Queries) SelectVtxoByOutpoint(ctx context.Context, arg SelectVtxoByOutp
|
|||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.Vtxo.Txid,
|
&i.Vtxo.Txid,
|
||||||
&i.Vtxo.Vout,
|
&i.Vtxo.Vout,
|
||||||
|
&i.Vtxo.Pubkey,
|
||||||
&i.Vtxo.Amount,
|
&i.Vtxo.Amount,
|
||||||
&i.Vtxo.PoolTx,
|
&i.Vtxo.PoolTx,
|
||||||
&i.Vtxo.SpentBy,
|
&i.Vtxo.SpentBy,
|
||||||
@@ -623,14 +624,13 @@ func (q *Queries) SelectVtxoByOutpoint(ctx context.Context, arg SelectVtxoByOutp
|
|||||||
&i.Vtxo.ExpireAt,
|
&i.Vtxo.ExpireAt,
|
||||||
&i.Vtxo.PaymentID,
|
&i.Vtxo.PaymentID,
|
||||||
&i.Vtxo.RedeemTx,
|
&i.Vtxo.RedeemTx,
|
||||||
&i.Vtxo.Descriptor,
|
|
||||||
&i.Vtxo.Pending,
|
&i.Vtxo.Pending,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectVtxosByPoolTxid = `-- name: SelectVtxosByPoolTxid :many
|
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 = ?
|
WHERE pool_tx = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -650,6 +650,7 @@ func (q *Queries) SelectVtxosByPoolTxid(ctx context.Context, poolTx string) ([]S
|
|||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Vtxo.Txid,
|
&i.Vtxo.Txid,
|
||||||
&i.Vtxo.Vout,
|
&i.Vtxo.Vout,
|
||||||
|
&i.Vtxo.Pubkey,
|
||||||
&i.Vtxo.Amount,
|
&i.Vtxo.Amount,
|
||||||
&i.Vtxo.PoolTx,
|
&i.Vtxo.PoolTx,
|
||||||
&i.Vtxo.SpentBy,
|
&i.Vtxo.SpentBy,
|
||||||
@@ -659,7 +660,6 @@ func (q *Queries) SelectVtxosByPoolTxid(ctx context.Context, poolTx string) ([]S
|
|||||||
&i.Vtxo.ExpireAt,
|
&i.Vtxo.ExpireAt,
|
||||||
&i.Vtxo.PaymentID,
|
&i.Vtxo.PaymentID,
|
||||||
&i.Vtxo.RedeemTx,
|
&i.Vtxo.RedeemTx,
|
||||||
&i.Vtxo.Descriptor,
|
|
||||||
&i.Vtxo.Pending,
|
&i.Vtxo.Pending,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -721,26 +721,26 @@ func (q *Queries) UpsertPayment(ctx context.Context, arg UpsertPaymentParams) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
const upsertReceiver = `-- name: UpsertReceiver :exec
|
const upsertReceiver = `-- name: UpsertReceiver :exec
|
||||||
INSERT INTO receiver (payment_id, descriptor, amount, onchain_address) VALUES (?, ?, ?, ?)
|
INSERT INTO receiver (payment_id, pubkey, onchain_address, amount) VALUES (?, ?, ?, ?)
|
||||||
ON CONFLICT(payment_id, descriptor) DO UPDATE SET
|
ON CONFLICT(payment_id, pubkey, onchain_address) DO UPDATE SET
|
||||||
amount = EXCLUDED.amount,
|
amount = EXCLUDED.amount,
|
||||||
onchain_address = EXCLUDED.onchain_address,
|
pubkey = EXCLUDED.pubkey,
|
||||||
descriptor = EXCLUDED.descriptor
|
onchain_address = EXCLUDED.onchain_address
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpsertReceiverParams struct {
|
type UpsertReceiverParams struct {
|
||||||
PaymentID string
|
PaymentID string
|
||||||
Descriptor string
|
Pubkey sql.NullString
|
||||||
|
OnchainAddress sql.NullString
|
||||||
Amount int64
|
Amount int64
|
||||||
OnchainAddress string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpsertReceiver(ctx context.Context, arg UpsertReceiverParams) error {
|
func (q *Queries) UpsertReceiver(ctx context.Context, arg UpsertReceiverParams) error {
|
||||||
_, err := q.db.ExecContext(ctx, upsertReceiver,
|
_, err := q.db.ExecContext(ctx, upsertReceiver,
|
||||||
arg.PaymentID,
|
arg.PaymentID,
|
||||||
arg.Descriptor,
|
arg.Pubkey,
|
||||||
arg.Amount,
|
|
||||||
arg.OnchainAddress,
|
arg.OnchainAddress,
|
||||||
|
arg.Amount,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -847,9 +847,9 @@ func (q *Queries) UpsertTransaction(ctx context.Context, arg UpsertTransactionPa
|
|||||||
}
|
}
|
||||||
|
|
||||||
const upsertVtxo = `-- name: UpsertVtxo :exec
|
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
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET
|
||||||
descriptor = EXCLUDED.descriptor,
|
pubkey = EXCLUDED.pubkey,
|
||||||
amount = EXCLUDED.amount,
|
amount = EXCLUDED.amount,
|
||||||
pool_tx = EXCLUDED.pool_tx,
|
pool_tx = EXCLUDED.pool_tx,
|
||||||
spent_by = EXCLUDED.spent_by,
|
spent_by = EXCLUDED.spent_by,
|
||||||
@@ -864,7 +864,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SE
|
|||||||
type UpsertVtxoParams struct {
|
type UpsertVtxoParams struct {
|
||||||
Txid string
|
Txid string
|
||||||
Vout int64
|
Vout int64
|
||||||
Descriptor sql.NullString
|
Pubkey string
|
||||||
Amount int64
|
Amount int64
|
||||||
PoolTx string
|
PoolTx string
|
||||||
SpentBy string
|
SpentBy string
|
||||||
@@ -880,7 +880,7 @@ func (q *Queries) UpsertVtxo(ctx context.Context, arg UpsertVtxoParams) error {
|
|||||||
_, err := q.db.ExecContext(ctx, upsertVtxo,
|
_, err := q.db.ExecContext(ctx, upsertVtxo,
|
||||||
arg.Txid,
|
arg.Txid,
|
||||||
arg.Vout,
|
arg.Vout,
|
||||||
arg.Descriptor,
|
arg.Pubkey,
|
||||||
arg.Amount,
|
arg.Amount,
|
||||||
arg.PoolTx,
|
arg.PoolTx,
|
||||||
arg.SpentBy,
|
arg.SpentBy,
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ INSERT INTO payment (id, round_id) VALUES (?, ?)
|
|||||||
ON CONFLICT(id) DO UPDATE SET round_id = EXCLUDED.round_id;
|
ON CONFLICT(id) DO UPDATE SET round_id = EXCLUDED.round_id;
|
||||||
|
|
||||||
-- name: UpsertReceiver :exec
|
-- name: UpsertReceiver :exec
|
||||||
INSERT INTO receiver (payment_id, descriptor, amount, onchain_address) VALUES (?, ?, ?, ?)
|
INSERT INTO receiver (payment_id, pubkey, onchain_address, amount) VALUES (?, ?, ?, ?)
|
||||||
ON CONFLICT(payment_id, descriptor) DO UPDATE SET
|
ON CONFLICT(payment_id, pubkey, onchain_address) DO UPDATE SET
|
||||||
amount = EXCLUDED.amount,
|
amount = EXCLUDED.amount,
|
||||||
onchain_address = EXCLUDED.onchain_address,
|
pubkey = EXCLUDED.pubkey,
|
||||||
descriptor = EXCLUDED.descriptor;
|
onchain_address = EXCLUDED.onchain_address;
|
||||||
|
|
||||||
-- name: UpdateVtxoPaymentId :exec
|
-- name: UpdateVtxoPaymentId :exec
|
||||||
UPDATE vtxo SET payment_id = ? WHERE txid = ? AND vout = ?;
|
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;
|
SELECT id FROM round;
|
||||||
|
|
||||||
-- name: UpsertVtxo :exec
|
-- 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
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET
|
||||||
descriptor = EXCLUDED.descriptor,
|
pubkey = EXCLUDED.pubkey,
|
||||||
amount = EXCLUDED.amount,
|
amount = EXCLUDED.amount,
|
||||||
pool_tx = EXCLUDED.pool_tx,
|
pool_tx = EXCLUDED.pool_tx,
|
||||||
spent_by = EXCLUDED.spent_by,
|
spent_by = EXCLUDED.spent_by,
|
||||||
@@ -135,7 +135,7 @@ WHERE redeemed = false;
|
|||||||
|
|
||||||
-- name: SelectNotRedeemedVtxosWithPubkey :many
|
-- name: SelectNotRedeemedVtxosWithPubkey :many
|
||||||
SELECT sqlc.embed(vtxo) FROM vtxo
|
SELECT sqlc.embed(vtxo) FROM vtxo
|
||||||
WHERE redeemed = false AND INSTR(descriptor, ?) > 0;
|
WHERE redeemed = false AND pubkey = ?;
|
||||||
|
|
||||||
-- name: SelectVtxoByOutpoint :one
|
-- name: SelectVtxoByOutpoint :one
|
||||||
SELECT sqlc.embed(vtxo) FROM vtxo
|
SELECT sqlc.embed(vtxo) FROM vtxo
|
||||||
|
|||||||
@@ -37,11 +37,12 @@ func (v *vxtoRepository) AddVtxos(ctx context.Context, vtxos []domain.Vtxo) erro
|
|||||||
txBody := func(querierWithTx *queries.Queries) error {
|
txBody := func(querierWithTx *queries.Queries) error {
|
||||||
for i := range vtxos {
|
for i := range vtxos {
|
||||||
vtxo := vtxos[i]
|
vtxo := vtxos[i]
|
||||||
|
|
||||||
if err := querierWithTx.UpsertVtxo(
|
if err := querierWithTx.UpsertVtxo(
|
||||||
ctx, queries.UpsertVtxoParams{
|
ctx, queries.UpsertVtxoParams{
|
||||||
Txid: vtxo.Txid,
|
Txid: vtxo.Txid,
|
||||||
Vout: int64(vtxo.VOut),
|
Vout: int64(vtxo.VOut),
|
||||||
Descriptor: sql.NullString{String: vtxo.Descriptor, Valid: true},
|
Pubkey: vtxo.Pubkey,
|
||||||
Amount: int64(vtxo.Amount),
|
Amount: int64(vtxo.Amount),
|
||||||
PoolTx: vtxo.RoundTxid,
|
PoolTx: vtxo.RoundTxid,
|
||||||
SpentBy: vtxo.SpentBy,
|
SpentBy: vtxo.SpentBy,
|
||||||
@@ -81,10 +82,6 @@ func (v *vxtoRepository) GetAllVtxos(ctx context.Context, pubkey string) ([]doma
|
|||||||
|
|
||||||
var rows []queries.Vtxo
|
var rows []queries.Vtxo
|
||||||
if withPubkey {
|
if withPubkey {
|
||||||
if len(pubkey) == 66 {
|
|
||||||
pubkey = pubkey[2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := v.querier.SelectNotRedeemedVtxosWithPubkey(ctx, pubkey)
|
res, err := v.querier.SelectNotRedeemedVtxosWithPubkey(ctx, pubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -253,10 +250,8 @@ func rowToVtxo(row queries.Vtxo) domain.Vtxo {
|
|||||||
Txid: row.Txid,
|
Txid: row.Txid,
|
||||||
VOut: uint32(row.Vout),
|
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,
|
RoundTxid: row.PoolTx,
|
||||||
SpentBy: row.SpentBy,
|
SpentBy: row.SpentBy,
|
||||||
Spent: row.Spent,
|
Spent: row.Spent,
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx strin
|
|||||||
func (b *txBuilder) BuildForfeitTxs(
|
func (b *txBuilder) BuildForfeitTxs(
|
||||||
poolTx string,
|
poolTx string,
|
||||||
payments []domain.Payment,
|
payments []domain.Payment,
|
||||||
|
descriptors map[domain.VtxoKey]string,
|
||||||
minRelayFeeRate chainfee.SatPerKVByte,
|
minRelayFeeRate chainfee.SatPerKVByte,
|
||||||
) (connectors []string, forfeitTxs []string, err error) {
|
) (connectors []string, forfeitTxs []string, err error) {
|
||||||
connectorAddress, err := b.getConnectorAddress(poolTx)
|
connectorAddress, err := b.getConnectorAddress(poolTx)
|
||||||
@@ -106,7 +107,7 @@ func (b *txBuilder) BuildForfeitTxs(
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitTxs, err = b.createForfeitTxs(payments, connectorTxs, connectorAmount, minRelayFeeRate)
|
forfeitTxs, err = b.createForfeitTxs(payments, descriptors, connectorTxs, connectorAmount, minRelayFeeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -147,13 +148,13 @@ func (b *txBuilder) BuildRoundTx(
|
|||||||
return "", nil, "", err
|
return "", nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
receivers, err := getOffchainReceivers(payments)
|
vtxosLeaves, err := getOutputVtxosLeaves(payments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, "", err
|
return "", nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err = tree.CraftCongestionTree(
|
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 {
|
if err != nil {
|
||||||
return "", nil, "", err
|
return "", nil, "", err
|
||||||
@@ -362,7 +363,10 @@ func (b *txBuilder) FindLeaves(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) BuildAsyncPaymentTransactions(
|
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) {
|
) (string, error) {
|
||||||
return "", fmt.Errorf("not implemented")
|
return "", fmt.Errorf("not implemented")
|
||||||
}
|
}
|
||||||
@@ -396,7 +400,6 @@ func (b *txBuilder) createPoolTx(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
receivers := getOnchainReceivers(payments)
|
|
||||||
nbOfInputs := countSpentVtxos(payments)
|
nbOfInputs := countSpentVtxos(payments)
|
||||||
connectorsAmount := (dustAmount + connectorMinRelayFee) * nbOfInputs
|
connectorsAmount := (dustAmount + connectorMinRelayFee) * nbOfInputs
|
||||||
if nbOfInputs > 1 {
|
if nbOfInputs > 1 {
|
||||||
@@ -424,21 +427,17 @@ func (b *txBuilder) createPoolTx(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, receiver := range receivers {
|
onchainOutputs, err := getOnchainOutputs(payments, b.onchainNetwork())
|
||||||
targetAmount += receiver.Amount
|
|
||||||
|
|
||||||
receiverScript, err := address.ToOutputScript(receiver.OnchainAddress)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
outputs = append(outputs, psetv2.OutputArgs{
|
for _, out := range onchainOutputs {
|
||||||
Asset: b.onchainNetwork().AssetID,
|
targetAmount += out.Amount
|
||||||
Amount: receiver.Amount,
|
|
||||||
Script: receiverScript,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outputs = append(outputs, onchainOutputs...)
|
||||||
|
|
||||||
for _, in := range boardingInputs {
|
for _, in := range boardingInputs {
|
||||||
targetAmount -= in.Amount
|
targetAmount -= in.Amount
|
||||||
}
|
}
|
||||||
@@ -786,6 +785,7 @@ func (b *txBuilder) createConnectors(
|
|||||||
|
|
||||||
func (b *txBuilder) createForfeitTxs(
|
func (b *txBuilder) createForfeitTxs(
|
||||||
payments []domain.Payment,
|
payments []domain.Payment,
|
||||||
|
descriptors map[domain.VtxoKey]string,
|
||||||
connectors []*psetv2.Pset,
|
connectors []*psetv2.Pset,
|
||||||
connectorAmount uint64,
|
connectorAmount uint64,
|
||||||
minRelayFeeRate chainfee.SatPerKVByte,
|
minRelayFeeRate chainfee.SatPerKVByte,
|
||||||
@@ -803,7 +803,12 @@ func (b *txBuilder) createForfeitTxs(
|
|||||||
forfeitTxs := make([]string, 0)
|
forfeitTxs := make([]string, 0)
|
||||||
for _, payment := range payments {
|
for _, payment := range payments {
|
||||||
for _, vtxo := range payment.Inputs {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ func TestBuildForfeitTxs(t *testing.T) {
|
|||||||
t.Run("valid", func(t *testing.T) {
|
t.Run("valid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Valid {
|
for _, f := range fixtures.Valid {
|
||||||
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
|
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
|
||||||
f.PoolTx, f.Payments, minRelayFeeRate,
|
f.PoolTx, f.Payments, f.Descriptors, minRelayFeeRate,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, connectors, f.ExpectedNumOfConnectors)
|
require.Len(t, connectors, f.ExpectedNumOfConnectors)
|
||||||
@@ -151,7 +151,7 @@ func TestBuildForfeitTxs(t *testing.T) {
|
|||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Invalid {
|
for _, f := range fixtures.Invalid {
|
||||||
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
|
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
|
||||||
f.PoolTx, f.Payments, minRelayFeeRate,
|
f.PoolTx, f.Payments, f.Descriptors, minRelayFeeRate,
|
||||||
)
|
)
|
||||||
require.EqualError(t, err, f.ExpectedErr)
|
require.EqualError(t, err, f.ExpectedErr)
|
||||||
require.Empty(t, connectors)
|
require.Empty(t, connectors)
|
||||||
@@ -215,6 +215,7 @@ func parsePoolTxFixtures() (*poolTxFixtures, error) {
|
|||||||
type forfeitTxsFixtures struct {
|
type forfeitTxsFixtures struct {
|
||||||
Valid []struct {
|
Valid []struct {
|
||||||
Payments []domain.Payment
|
Payments []domain.Payment
|
||||||
|
Descriptors map[domain.VtxoKey]string
|
||||||
ExpectedNumOfConnectors int
|
ExpectedNumOfConnectors int
|
||||||
ExpectedNumOfForfeitTxs int
|
ExpectedNumOfForfeitTxs int
|
||||||
PoolTx string
|
PoolTx string
|
||||||
@@ -222,6 +223,7 @@ type forfeitTxsFixtures struct {
|
|||||||
}
|
}
|
||||||
Invalid []struct {
|
Invalid []struct {
|
||||||
Payments []domain.Payment
|
Payments []domain.Payment
|
||||||
|
Descriptors map[domain.VtxoKey]string
|
||||||
ExpectedErr string
|
ExpectedErr string
|
||||||
PoolTx string
|
PoolTx string
|
||||||
}
|
}
|
||||||
@@ -244,6 +246,42 @@ func parseForfeitTxsFixtures() (*forfeitTxsFixtures, error) {
|
|||||||
return nil, err
|
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
|
return &fixtures, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,14 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -33,18 +33,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -61,18 +61,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -83,18 +83,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -105,18 +105,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -133,58 +133,58 @@
|
|||||||
{
|
{
|
||||||
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357",
|
"txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909",
|
"txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909",
|
||||||
"vout": 1,
|
"vout": 1,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 500
|
"amount": 500
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey":"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
||||||
"vout": 1,
|
"vout": 1,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -206,25 +206,25 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 1,
|
"vout": 1,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -45,34 +45,38 @@ func getPsetId(pset *psetv2.Pset) (string, error) {
|
|||||||
return utx.TxHash().String(), nil
|
return utx.TxHash().String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOnchainReceivers(
|
func getOnchainOutputs(
|
||||||
payments []domain.Payment,
|
payments []domain.Payment, net *network.Network,
|
||||||
) []domain.Receiver {
|
) ([]psetv2.OutputArgs, error) {
|
||||||
receivers := make([]domain.Receiver, 0)
|
outputs := make([]psetv2.OutputArgs, 0)
|
||||||
for _, payment := range payments {
|
for _, payment := range payments {
|
||||||
for _, receiver := range payment.Receivers {
|
for _, receiver := range payment.Receivers {
|
||||||
if receiver.IsOnchain() {
|
if receiver.IsOnchain() {
|
||||||
receivers = append(receivers, receiver)
|
receiverScript, err := address.ToOutputScript(receiver.OnchainAddress)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
receivers = append(receivers, tree.Receiver{
|
outputs = append(outputs, psetv2.OutputArgs{
|
||||||
Script: vtxoScript,
|
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,
|
Amount: receiver.Amount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,7 +228,10 @@ func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) BuildForfeitTxs(
|
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) {
|
) (connectors []string, forfeitTxs []string, err error) {
|
||||||
connectorPkScript, err := b.getConnectorPkScript(poolTx)
|
connectorPkScript, err := b.getConnectorPkScript(poolTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -245,7 +248,7 @@ func (b *txBuilder) BuildForfeitTxs(
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitTxs, err = b.createForfeitTxs(payments, connectorTxs, minRelayFeeRate)
|
forfeitTxs, err = b.createForfeitTxs(payments, descriptors, connectorTxs, minRelayFeeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -271,7 +274,7 @@ func (b *txBuilder) BuildRoundTx(
|
|||||||
return "", nil, "", fmt.Errorf("missing cosigners")
|
return "", nil, "", fmt.Errorf("missing cosigners")
|
||||||
}
|
}
|
||||||
|
|
||||||
receivers, err := getOffchainReceivers(payments)
|
receivers, err := getOutputVtxosLeaves(payments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, "", err
|
return "", nil, "", err
|
||||||
}
|
}
|
||||||
@@ -399,7 +402,10 @@ func (b *txBuilder) FindLeaves(congestionTree tree.CongestionTree, fromtxid stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) BuildAsyncPaymentTransactions(
|
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) {
|
) (string, error) {
|
||||||
if len(vtxos) <= 0 {
|
if len(vtxos) <= 0 {
|
||||||
return "", fmt.Errorf("missing vtxos")
|
return "", fmt.Errorf("missing vtxos")
|
||||||
@@ -412,6 +418,16 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
|
|
||||||
redeemTxWeightEstimator := &input.TxWeightEstimator{}
|
redeemTxWeightEstimator := &input.TxWeightEstimator{}
|
||||||
for index, vtxo := range vtxos {
|
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 {
|
if vtxo.Spent || vtxo.Redeemed || vtxo.Swept {
|
||||||
return "", fmt.Errorf("all vtxos must be unspent")
|
return "", fmt.Errorf("all vtxos must be unspent")
|
||||||
}
|
}
|
||||||
@@ -426,7 +442,7 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
Index: vtxo.VOut,
|
Index: vtxo.VOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Descriptor)
|
vtxoScript, err := bitcointree.ParseVtxoScript(desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -446,18 +462,7 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
PkScript: vtxoOutputScript,
|
PkScript: vtxoOutputScript,
|
||||||
}
|
}
|
||||||
|
|
||||||
if defaultVtxoScript, ok := vtxoScript.(*bitcointree.DefaultVtxoScript); ok {
|
leafProof, err := vtxoTree.GetTaprootMerkleProof(forfeitLeafHash)
|
||||||
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -473,13 +478,10 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
redeemTxWeightEstimator.AddTapscriptInput(64*2, &waddrmgr.Tapscript{
|
redeemTxWeightEstimator.AddTapscriptInput(64*2+40, &waddrmgr.Tapscript{
|
||||||
RevealedScript: leafProof.Script,
|
RevealedScript: leafProof.Script,
|
||||||
ControlBlock: ctrlBlock,
|
ControlBlock: ctrlBlock,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
return "", fmt.Errorf("vtxo %s:%d script is not default script, can't be async spent", vtxo.Txid, vtxo.VOut)
|
|
||||||
}
|
|
||||||
|
|
||||||
ins = append(ins, vtxoOutpoint)
|
ins = append(ins, vtxoOutpoint)
|
||||||
}
|
}
|
||||||
@@ -498,17 +500,21 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, receiver := range receivers {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
receiverVtxoTaprootKey, _, err := offchainScript.TapTree()
|
pubkey, err := schnorr.ParsePubKey(pubkeyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
newVtxoScript, err := common.P2TRScript(receiverVtxoTaprootKey)
|
newVtxoScript, err := common.P2TRScript(pubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -588,7 +594,6 @@ func (b *txBuilder) createRoundTx(
|
|||||||
|
|
||||||
connectorAmount := dustLimit
|
connectorAmount := dustLimit
|
||||||
|
|
||||||
receivers := getOnchainReceivers(payments)
|
|
||||||
nbOfInputs := countSpentVtxos(payments)
|
nbOfInputs := countSpentVtxos(payments)
|
||||||
connectorsAmount := (connectorAmount + connectorMinRelayFee) * nbOfInputs
|
connectorsAmount := (connectorAmount + connectorMinRelayFee) * nbOfInputs
|
||||||
if nbOfInputs > 1 {
|
if nbOfInputs > 1 {
|
||||||
@@ -614,24 +619,16 @@ func (b *txBuilder) createRoundTx(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, receiver := range receivers {
|
onchainOutputs, err := getOnchainOutputs(payments, b.onchainNetwork())
|
||||||
targetAmount += receiver.Amount
|
|
||||||
|
|
||||||
receiverAddr, err := btcutil.DecodeAddress(receiver.OnchainAddress, b.onchainNetwork())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
receiverScript, err := txscript.PayToAddrScript(receiverAddr)
|
for _, output := range onchainOutputs {
|
||||||
if err != nil {
|
targetAmount += uint64(output.Value)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
outputs = append(outputs, &wire.TxOut{
|
outputs = append(outputs, onchainOutputs...)
|
||||||
Value: int64(receiver.Amount),
|
|
||||||
PkScript: receiverScript,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, input := range boardingInputs {
|
for _, input := range boardingInputs {
|
||||||
targetAmount -= input.Amount
|
targetAmount -= input.Amount
|
||||||
@@ -1016,6 +1013,7 @@ func (b *txBuilder) minRelayFeeTreeTx() (uint64, error) {
|
|||||||
|
|
||||||
func (b *txBuilder) createForfeitTxs(
|
func (b *txBuilder) createForfeitTxs(
|
||||||
payments []domain.Payment,
|
payments []domain.Payment,
|
||||||
|
descriptors map[domain.VtxoKey]string,
|
||||||
connectors []*psbt.Packet,
|
connectors []*psbt.Packet,
|
||||||
minRelayFeeRate chainfee.SatPerKVByte,
|
minRelayFeeRate chainfee.SatPerKVByte,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
@@ -1042,7 +1040,12 @@ func (b *txBuilder) createForfeitTxs(
|
|||||||
forfeitTxs := make([]string, 0)
|
forfeitTxs := make([]string, 0)
|
||||||
for _, payment := range payments {
|
for _, payment := range payments {
|
||||||
for _, vtxo := range payment.Inputs {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ func TestBuildForfeitTxs(t *testing.T) {
|
|||||||
t.Run("valid", func(t *testing.T) {
|
t.Run("valid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Valid {
|
for _, f := range fixtures.Valid {
|
||||||
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
|
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
|
||||||
f.PoolTx, f.Payments, minRelayFeeRate,
|
f.PoolTx, f.Payments, f.Descriptors, minRelayFeeRate,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, connectors, f.ExpectedNumOfConnectors)
|
require.Len(t, connectors, f.ExpectedNumOfConnectors)
|
||||||
@@ -161,7 +161,7 @@ func TestBuildForfeitTxs(t *testing.T) {
|
|||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Invalid {
|
for _, f := range fixtures.Invalid {
|
||||||
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
|
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
|
||||||
f.PoolTx, f.Payments, minRelayFeeRate,
|
f.PoolTx, f.Payments, f.Descriptors, minRelayFeeRate,
|
||||||
)
|
)
|
||||||
require.EqualError(t, err, f.ExpectedErr)
|
require.EqualError(t, err, f.ExpectedErr)
|
||||||
require.Empty(t, connectors)
|
require.Empty(t, connectors)
|
||||||
@@ -225,6 +225,7 @@ func parsePoolTxFixtures() (*poolTxFixtures, error) {
|
|||||||
type forfeitTxsFixtures struct {
|
type forfeitTxsFixtures struct {
|
||||||
Valid []struct {
|
Valid []struct {
|
||||||
Payments []domain.Payment
|
Payments []domain.Payment
|
||||||
|
Descriptors map[domain.VtxoKey]string
|
||||||
ExpectedNumOfConnectors int
|
ExpectedNumOfConnectors int
|
||||||
ExpectedNumOfForfeitTxs int
|
ExpectedNumOfForfeitTxs int
|
||||||
PoolTx string
|
PoolTx string
|
||||||
@@ -232,6 +233,7 @@ type forfeitTxsFixtures struct {
|
|||||||
}
|
}
|
||||||
Invalid []struct {
|
Invalid []struct {
|
||||||
Payments []domain.Payment
|
Payments []domain.Payment
|
||||||
|
Descriptors map[domain.VtxoKey]string
|
||||||
ExpectedErr string
|
ExpectedErr string
|
||||||
PoolTx string
|
PoolTx string
|
||||||
}
|
}
|
||||||
@@ -254,5 +256,41 @@ func parseForfeitTxsFixtures() (*forfeitTxsFixtures, error) {
|
|||||||
return nil, err
|
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
|
return &fixtures, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -32,17 +33,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -59,17 +61,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -80,17 +83,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -101,17 +105,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -128,54 +133,58 @@
|
|||||||
{
|
{
|
||||||
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357",
|
"txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909",
|
"txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909",
|
||||||
"vout": 1,
|
"vout": 1,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
||||||
"vout": 1,
|
"vout": 1,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -197,53 +206,58 @@
|
|||||||
{
|
{
|
||||||
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357",
|
"txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909",
|
"txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909",
|
||||||
"vout": 1,
|
"vout": 1,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
|
||||||
"vout": 1,
|
"vout": 1,
|
||||||
|
"pubkey": "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
"pubkey": "25a43cecfa0e1b1a4f72d64ad15f4cfa7a84d0723e8511c969aa543638ea9967",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,47 +1,58 @@
|
|||||||
package txbuilder
|
package txbuilder
|
||||||
|
|
||||||
import (
|
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/ark-network/ark/server/internal/core/domain"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"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/txscript"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getOnchainReceivers(
|
func getOnchainOutputs(
|
||||||
payments []domain.Payment,
|
payments []domain.Payment, network *chaincfg.Params,
|
||||||
) []domain.Receiver {
|
) ([]*wire.TxOut, error) {
|
||||||
receivers := make([]domain.Receiver, 0)
|
outputs := make([]*wire.TxOut, 0)
|
||||||
for _, payment := range payments {
|
for _, payment := range payments {
|
||||||
for _, receiver := range payment.Receivers {
|
for _, receiver := range payment.Receivers {
|
||||||
if receiver.IsOnchain() {
|
if receiver.IsOnchain() {
|
||||||
receivers = append(receivers, receiver)
|
receiverAddr, err := btcutil.DecodeAddress(receiver.OnchainAddress, network)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
receivers = append(receivers, bitcointree.Receiver{
|
receiverScript, err := txscript.PayToAddrScript(receiverAddr)
|
||||||
Script: vtxoScript,
|
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,
|
Amount: receiver.Amount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return receivers, nil
|
return leaves, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func countSpentVtxos(payments []domain.Payment) uint64 {
|
func countSpentVtxos(payments []domain.Payment) uint64 {
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ func (h *handler) Ping(
|
|||||||
func (h *handler) CreatePayment(
|
func (h *handler) CreatePayment(
|
||||||
ctx context.Context, req *arkv1.CreatePaymentRequest,
|
ctx context.Context, req *arkv1.CreatePaymentRequest,
|
||||||
) (*arkv1.CreatePaymentResponse, error) {
|
) (*arkv1.CreatePaymentResponse, error) {
|
||||||
inputs, err := parseInputs(req.GetInputs())
|
inputs, err := parseAsyncPaymentInputs(req.GetInputs())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
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")
|
return nil, status.Error(codes.InvalidArgument, "output amount must be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(receiver.OnchainAddress) > 0 {
|
if len(receiver.OnchainAddress) <= 0 && len(receiver.Pubkey) <= 0 {
|
||||||
return nil, status.Error(codes.InvalidArgument, "onchain address is not supported as async payment destination")
|
return nil, status.Error(codes.InvalidArgument, "missing address")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(receiver.Descriptor) <= 0 {
|
if receiver.IsOnchain() {
|
||||||
return nil, status.Error(codes.InvalidArgument, "missing output descriptor")
|
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(
|
func (h *handler) ListVtxos(
|
||||||
ctx context.Context, req *arkv1.ListVtxosRequest,
|
ctx context.Context, req *arkv1.ListVtxosRequest,
|
||||||
) (*arkv1.ListVtxosResponse, error) {
|
) (*arkv1.ListVtxosResponse, error) {
|
||||||
_, userPubkey, _, err := parseAddress(req.GetAddress())
|
_, err := parseAddress(req.GetAddress())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,55 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
"github.com/ark-network/ark/common/tree"
|
"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/domain"
|
||||||
"github.com/ark-network/ark/server/internal/core/ports"
|
"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
|
// 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 {
|
if len(addr) <= 0 {
|
||||||
return "", nil, nil, fmt.Errorf("missing address")
|
return nil, fmt.Errorf("missing address")
|
||||||
}
|
}
|
||||||
return common.DecodeAddress(addr)
|
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) {
|
func parseInputs(ins []*arkv1.Input) ([]ports.Input, error) {
|
||||||
if len(ins) <= 0 {
|
if len(ins) <= 0 {
|
||||||
return nil, fmt.Errorf("missing inputs")
|
return nil, fmt.Errorf("missing inputs")
|
||||||
@@ -39,21 +69,38 @@ func parseInputs(ins []*arkv1.Input) ([]ports.Input, error) {
|
|||||||
return inputs, nil
|
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) {
|
func parseReceivers(outs []*arkv1.Output) ([]domain.Receiver, error) {
|
||||||
receivers := make([]domain.Receiver, 0, len(outs))
|
receivers := make([]domain.Receiver, 0, len(outs))
|
||||||
for _, out := range outs {
|
for _, out := range outs {
|
||||||
if out.GetAmount() == 0 {
|
if out.GetAmount() == 0 {
|
||||||
return nil, fmt.Errorf("missing output amount")
|
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")
|
return nil, fmt.Errorf("missing output destination")
|
||||||
}
|
}
|
||||||
|
|
||||||
receivers = append(receivers, domain.Receiver{
|
rcv, err := parseReceiver(out)
|
||||||
Descriptor: out.GetDescriptor_(),
|
if err != nil {
|
||||||
Amount: out.GetAmount(),
|
return nil, err
|
||||||
OnchainAddress: out.GetAddress(),
|
}
|
||||||
})
|
|
||||||
|
receivers = append(receivers, rcv)
|
||||||
}
|
}
|
||||||
return receivers, nil
|
return receivers, nil
|
||||||
}
|
}
|
||||||
@@ -70,7 +117,6 @@ func (v vtxoList) toProto() []*arkv1.Vtxo {
|
|||||||
Txid: vv.Txid,
|
Txid: vv.Txid,
|
||||||
Vout: vv.VOut,
|
Vout: vv.VOut,
|
||||||
},
|
},
|
||||||
Descriptor_: vv.Descriptor,
|
|
||||||
Amount: vv.Amount,
|
Amount: vv.Amount,
|
||||||
RoundTxid: vv.RoundTxid,
|
RoundTxid: vv.RoundTxid,
|
||||||
Spent: vv.Spent,
|
Spent: vv.Spent,
|
||||||
@@ -79,6 +125,7 @@ func (v vtxoList) toProto() []*arkv1.Vtxo {
|
|||||||
Swept: vv.Swept,
|
Swept: vv.Swept,
|
||||||
RedeemTx: vv.RedeemTx,
|
RedeemTx: vv.RedeemTx,
|
||||||
Pending: vv.Pending,
|
Pending: vv.Pending,
|
||||||
|
Pubkey: vv.Pubkey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user