mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 12:14:21 +01:00
Add reversible policy to pending vtxos (#311)
* [server] descriptor-based vtxo script * [server] fix unit tests * [sdk] descriptor based vtxo * empty config check & version flag support * fix: empty config check & version flag support (#309) * fix * [sdk] several fixes * [sdk][server] several fixes * [common][sdk] add reversible VtxoScript type, use it in async payment * [common] improve parser * [common] fix reversible vtxo parser * [sdk] remove logs * fix forfeit map * remove debug log * [sdk] do not allow reversible vtxo script in case of self-transfer * remove signing pubkey * remove signer public key, craft forfeit txs client side * go work sync * fix linter errors * rename MakeForfeitTxs to BuildForfeitTxs * fix conflicts * fix tests * comment VtxoScript type * revert ROUND_INTERVAL value --------- Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> Co-authored-by: sekulicd <sekula87@gmail.com>
This commit is contained in:
@@ -475,21 +475,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"v1BoardingInput": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"txid": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"vout": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int64"
|
|
||||||
},
|
|
||||||
"descriptor": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"v1ClaimPaymentRequest": {
|
"v1ClaimPaymentRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -668,11 +653,11 @@
|
|||||||
"v1Input": {
|
"v1Input": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"vtxoInput": {
|
"outpoint": {
|
||||||
"$ref": "#/definitions/v1VtxoInput"
|
"$ref": "#/definitions/v1Outpoint"
|
||||||
},
|
},
|
||||||
"boardingInput": {
|
"descriptor": {
|
||||||
"$ref": "#/definitions/v1BoardingInput"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -709,12 +694,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"v1Outpoint": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"txid": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"vout": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"v1Output": {
|
"v1Output": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"address": {
|
"address": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Either the offchain or onchain address."
|
"title": "onchain"
|
||||||
|
},
|
||||||
|
"descriptor": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "offchain"
|
||||||
},
|
},
|
||||||
"amount": {
|
"amount": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -838,12 +839,6 @@
|
|||||||
"poolTx": {
|
"poolTx": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"forfeitTxs": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"congestionTree": {
|
"congestionTree": {
|
||||||
"$ref": "#/definitions/v1Tree"
|
"$ref": "#/definitions/v1Tree"
|
||||||
},
|
},
|
||||||
@@ -852,6 +847,10 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"minRelayFeeRate": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "int64"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -970,10 +969,10 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"outpoint": {
|
"outpoint": {
|
||||||
"$ref": "#/definitions/v1Input"
|
"$ref": "#/definitions/v1Outpoint"
|
||||||
},
|
},
|
||||||
"receiver": {
|
"descriptor": {
|
||||||
"$ref": "#/definitions/v1Output"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"spent": {
|
"spent": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@@ -996,18 +995,10 @@
|
|||||||
},
|
},
|
||||||
"pendingData": {
|
"pendingData": {
|
||||||
"$ref": "#/definitions/v1PendingPayment"
|
"$ref": "#/definitions/v1PendingPayment"
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"v1VtxoInput": {
|
"amount": {
|
||||||
"type": "object",
|
"type": "string",
|
||||||
"properties": {
|
"format": "uint64"
|
||||||
"txid": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"vout": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int64"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,9 +196,9 @@ message GetInfoResponse {
|
|||||||
message RoundFinalizationEvent {
|
message RoundFinalizationEvent {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string pool_tx = 2;
|
string pool_tx = 2;
|
||||||
repeated string forfeit_txs = 3;
|
Tree congestion_tree = 3;
|
||||||
Tree congestion_tree = 4;
|
repeated string connectors = 4;
|
||||||
repeated string connectors = 5;
|
int64 min_relay_fee_rate = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RoundFinalizedEvent {
|
message RoundFinalizedEvent {
|
||||||
@@ -244,29 +244,23 @@ message Round {
|
|||||||
RoundStage stage = 8;
|
RoundStage stage = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message VtxoInput {
|
message Outpoint {
|
||||||
string txid = 1;
|
string txid = 1;
|
||||||
uint32 vout = 2;
|
uint32 vout = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BoardingInput {
|
|
||||||
string txid = 1;
|
|
||||||
uint32 vout = 2;
|
|
||||||
string descriptor = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Input {
|
message Input {
|
||||||
oneof input {
|
Outpoint outpoint = 1;
|
||||||
VtxoInput vtxo_input = 1;
|
string descriptor = 2;
|
||||||
BoardingInput boarding_input = 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Output {
|
message Output {
|
||||||
// Either the offchain or onchain address.
|
oneof destination {
|
||||||
string address = 1;
|
string address = 1; // onchain
|
||||||
|
string descriptor = 2; // offchain
|
||||||
|
}
|
||||||
// Amount to send in satoshis.
|
// Amount to send in satoshis.
|
||||||
uint64 amount = 2;
|
uint64 amount = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Tree {
|
message Tree {
|
||||||
@@ -284,8 +278,8 @@ message Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message Vtxo {
|
message Vtxo {
|
||||||
Input outpoint = 1;
|
Outpoint outpoint = 1;
|
||||||
Output receiver = 2;
|
string descriptor = 2;
|
||||||
bool spent = 3;
|
bool spent = 3;
|
||||||
string pool_txid = 4;
|
string pool_txid = 4;
|
||||||
string spent_by = 5;
|
string spent_by = 5;
|
||||||
@@ -293,6 +287,7 @@ message Vtxo {
|
|||||||
bool swept = 7;
|
bool swept = 7;
|
||||||
bool pending = 8;
|
bool pending = 8;
|
||||||
PendingPayment pending_data = 9;
|
PendingPayment pending_data = 9;
|
||||||
|
uint64 amount = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PendingPayment {
|
message PendingPayment {
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ func local_request_AdminService_GetRounds_0(ctx context.Context, marshaler runti
|
|||||||
// UnaryRPC :call AdminServiceServer directly.
|
// UnaryRPC :call AdminServiceServer directly.
|
||||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||||
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAdminServiceHandlerFromEndpoint instead.
|
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAdminServiceHandlerFromEndpoint instead.
|
||||||
|
// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call.
|
||||||
func RegisterAdminServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AdminServiceServer) error {
|
func RegisterAdminServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AdminServiceServer) error {
|
||||||
|
|
||||||
mux.Handle("GET", pattern_AdminService_GetScheduledSweep_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("GET", pattern_AdminService_GetScheduledSweep_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
@@ -246,7 +247,7 @@ func RegisterAdminServiceHandler(ctx context.Context, mux *runtime.ServeMux, con
|
|||||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "AdminServiceClient".
|
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "AdminServiceClient".
|
||||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "AdminServiceClient"
|
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "AdminServiceClient"
|
||||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||||
// "AdminServiceClient" to call the correct interceptors.
|
// "AdminServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares.
|
||||||
func RegisterAdminServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AdminServiceClient) error {
|
func RegisterAdminServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AdminServiceClient) error {
|
||||||
|
|
||||||
mux.Handle("GET", pattern_AdminService_GetScheduledSweep_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("GET", pattern_AdminService_GetScheduledSweep_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -486,6 +486,7 @@ func local_request_ArkService_CompletePayment_0(ctx context.Context, marshaler r
|
|||||||
// UnaryRPC :call ArkServiceServer directly.
|
// UnaryRPC :call ArkServiceServer directly.
|
||||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||||
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterArkServiceHandlerFromEndpoint instead.
|
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterArkServiceHandlerFromEndpoint instead.
|
||||||
|
// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call.
|
||||||
func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ArkServiceServer) error {
|
func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ArkServiceServer) error {
|
||||||
|
|
||||||
mux.Handle("POST", pattern_ArkService_RegisterPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("POST", pattern_ArkService_RegisterPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
@@ -858,7 +859,7 @@ func RegisterArkServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn
|
|||||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "ArkServiceClient".
|
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "ArkServiceClient".
|
||||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "ArkServiceClient"
|
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "ArkServiceClient"
|
||||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||||
// "ArkServiceClient" to call the correct interceptors.
|
// "ArkServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares.
|
||||||
func RegisterArkServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ArkServiceClient) error {
|
func RegisterArkServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ArkServiceClient) error {
|
||||||
|
|
||||||
mux.Handle("POST", pattern_ArkService_RegisterPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("POST", pattern_ArkService_RegisterPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ func local_request_WalletService_GetBalance_0(ctx context.Context, marshaler run
|
|||||||
// UnaryRPC :call WalletInitializerServiceServer directly.
|
// UnaryRPC :call WalletInitializerServiceServer directly.
|
||||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||||
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterWalletInitializerServiceHandlerFromEndpoint instead.
|
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterWalletInitializerServiceHandlerFromEndpoint instead.
|
||||||
|
// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call.
|
||||||
func RegisterWalletInitializerServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server WalletInitializerServiceServer) error {
|
func RegisterWalletInitializerServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server WalletInitializerServiceServer) error {
|
||||||
|
|
||||||
mux.Handle("GET", pattern_WalletInitializerService_GenSeed_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("GET", pattern_WalletInitializerService_GenSeed_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
@@ -345,6 +346,7 @@ func RegisterWalletInitializerServiceHandlerServer(ctx context.Context, mux *run
|
|||||||
// UnaryRPC :call WalletServiceServer directly.
|
// UnaryRPC :call WalletServiceServer directly.
|
||||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||||
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterWalletServiceHandlerFromEndpoint instead.
|
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterWalletServiceHandlerFromEndpoint instead.
|
||||||
|
// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call.
|
||||||
func RegisterWalletServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server WalletServiceServer) error {
|
func RegisterWalletServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server WalletServiceServer) error {
|
||||||
|
|
||||||
mux.Handle("POST", pattern_WalletService_Lock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("POST", pattern_WalletService_Lock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
@@ -460,7 +462,7 @@ func RegisterWalletInitializerServiceHandler(ctx context.Context, mux *runtime.S
|
|||||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "WalletInitializerServiceClient".
|
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "WalletInitializerServiceClient".
|
||||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "WalletInitializerServiceClient"
|
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "WalletInitializerServiceClient"
|
||||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||||
// "WalletInitializerServiceClient" to call the correct interceptors.
|
// "WalletInitializerServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares.
|
||||||
func RegisterWalletInitializerServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client WalletInitializerServiceClient) error {
|
func RegisterWalletInitializerServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client WalletInitializerServiceClient) error {
|
||||||
|
|
||||||
mux.Handle("GET", pattern_WalletInitializerService_GenSeed_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("GET", pattern_WalletInitializerService_GenSeed_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
@@ -635,7 +637,7 @@ func RegisterWalletServiceHandler(ctx context.Context, mux *runtime.ServeMux, co
|
|||||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "WalletServiceClient".
|
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "WalletServiceClient".
|
||||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "WalletServiceClient"
|
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "WalletServiceClient"
|
||||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||||
// "WalletServiceClient" to call the correct interceptors.
|
// "WalletServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares.
|
||||||
func RegisterWalletServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client WalletServiceClient) error {
|
func RegisterWalletServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client WalletServiceClient) error {
|
||||||
|
|
||||||
mux.Handle("POST", pattern_WalletService_Lock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("POST", pattern_WalletService_Lock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package bitcointree
|
package bitcointree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
@@ -17,7 +16,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 []Receiver,
|
||||||
feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64,
|
feeSatsPerNode uint64, roundLifetime int64,
|
||||||
) ([]byte, int64, error) {
|
) ([]byte, int64, error) {
|
||||||
aggregatedKey, _, err := createAggregatedKeyWithSweep(
|
aggregatedKey, _, err := createAggregatedKeyWithSweep(
|
||||||
cosigners, aspPubkey, roundLifetime,
|
cosigners, aspPubkey, roundLifetime,
|
||||||
@@ -26,7 +25,7 @@ func CraftSharedOutput(
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
root, err := createRootNode(aggregatedKey, cosigners, aspPubkey, receivers, feeSatsPerNode, unilateralExitDelay)
|
root, err := createRootNode(aggregatedKey, cosigners, receivers, feeSatsPerNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -44,7 +43,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 []Receiver,
|
||||||
feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64,
|
feeSatsPerNode uint64, roundLifetime int64,
|
||||||
) (tree.CongestionTree, error) {
|
) (tree.CongestionTree, error) {
|
||||||
aggregatedKey, sweepTapLeaf, err := createAggregatedKeyWithSweep(
|
aggregatedKey, sweepTapLeaf, err := createAggregatedKeyWithSweep(
|
||||||
cosigners, aspPubkey, roundLifetime,
|
cosigners, aspPubkey, roundLifetime,
|
||||||
@@ -53,7 +52,7 @@ func CraftCongestionTree(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
root, err := createRootNode(aggregatedKey, cosigners, aspPubkey, receivers, feeSatsPerNode, unilateralExitDelay)
|
root, err := createRootNode(aggregatedKey, cosigners, receivers, feeSatsPerNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -109,9 +108,7 @@ type node interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type leaf struct {
|
type leaf struct {
|
||||||
aspKey *secp256k1.PublicKey
|
vtxoScript VtxoScript
|
||||||
vtxoKey *secp256k1.PublicKey
|
|
||||||
exitDelay int64
|
|
||||||
amount int64
|
amount int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,36 +142,11 @@ func (l *leaf) getAmount() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *leaf) getOutputs() ([]*wire.TxOut, error) {
|
func (l *leaf) getOutputs() ([]*wire.TxOut, error) {
|
||||||
redeemClosure := &CSVSigClosure{
|
taprootKey, _, err := l.vtxoScript.TapTree()
|
||||||
Pubkey: l.vtxoKey,
|
|
||||||
Seconds: uint(l.exitDelay),
|
|
||||||
}
|
|
||||||
|
|
||||||
redeemLeaf, err := redeemClosure.Leaf()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitClosure := &MultisigClosure{
|
|
||||||
Pubkey: l.vtxoKey,
|
|
||||||
AspPubkey: l.aspKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitLeaf, err := forfeitClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
leafTaprootTree := txscript.AssembleTaprootScriptTree(
|
|
||||||
*redeemLeaf, *forfeitLeaf,
|
|
||||||
)
|
|
||||||
root := leafTaprootTree.RootNode.TapHash()
|
|
||||||
|
|
||||||
taprootKey := txscript.ComputeTaprootOutputKey(
|
|
||||||
UnspendableKey(),
|
|
||||||
root[:],
|
|
||||||
)
|
|
||||||
|
|
||||||
script, err := taprootOutputScript(taprootKey)
|
script, err := taprootOutputScript(taprootKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -272,9 +244,10 @@ func getTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createRootNode(
|
func createRootNode(
|
||||||
aggregatedKey *musig2.AggregateKey, cosigners []*secp256k1.PublicKey,
|
aggregatedKey *musig2.AggregateKey,
|
||||||
aspPubkey *secp256k1.PublicKey, receivers []Receiver,
|
cosigners []*secp256k1.PublicKey,
|
||||||
feeSatsPerNode uint64, unilateralExitDelay int64,
|
receivers []Receiver,
|
||||||
|
feeSatsPerNode uint64,
|
||||||
) (root node, err error) {
|
) (root node, err error) {
|
||||||
if len(receivers) == 0 {
|
if len(receivers) == 0 {
|
||||||
return nil, fmt.Errorf("no receivers provided")
|
return nil, fmt.Errorf("no receivers provided")
|
||||||
@@ -282,20 +255,8 @@ 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
|
|
||||||
}
|
|
||||||
|
|
||||||
receiverKey, err := secp256k1.ParsePubKey(pubkeyBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
leafNode := &leaf{
|
leafNode := &leaf{
|
||||||
aspKey: aspPubkey,
|
vtxoScript: r.Script,
|
||||||
vtxoKey: receiverKey,
|
|
||||||
exitDelay: unilateralExitDelay,
|
|
||||||
amount: int64(r.Amount),
|
amount: int64(r.Amount),
|
||||||
}
|
}
|
||||||
nodes = append(nodes, leafNode)
|
nodes = append(nodes, leafNode)
|
||||||
|
|||||||
85
common/bitcointree/forfeit.go
Normal file
85
common/bitcointree/forfeit.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package bitcointree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ark-network/ark/common"
|
||||||
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildForfeitTxs(
|
||||||
|
connectorTx *psbt.Packet,
|
||||||
|
vtxoInput *wire.OutPoint,
|
||||||
|
vtxoAmount,
|
||||||
|
connectorAmount,
|
||||||
|
feeAmount uint64,
|
||||||
|
vtxoScript []byte,
|
||||||
|
aspPubKey *secp256k1.PublicKey,
|
||||||
|
) (forfeitTxs []*psbt.Packet, err error) {
|
||||||
|
aspScript, err := common.P2TRScript(aspPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
connectors, prevouts := getConnectorInputs(connectorTx, int64(connectorAmount))
|
||||||
|
|
||||||
|
for i, connectorInput := range connectors {
|
||||||
|
connectorPrevout := prevouts[i]
|
||||||
|
|
||||||
|
partialTx, err := psbt.New(
|
||||||
|
[]*wire.OutPoint{connectorInput, vtxoInput},
|
||||||
|
[]*wire.TxOut{{
|
||||||
|
Value: int64(vtxoAmount) + int64(connectorAmount) - int64(feeAmount),
|
||||||
|
PkScript: aspScript,
|
||||||
|
}},
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
[]uint32{wire.MaxTxInSequenceNum, wire.MaxTxInSequenceNum},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updater, err := psbt.NewUpdater(partialTx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updater.AddInWitnessUtxo(connectorPrevout, 0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updater.AddInWitnessUtxo(&wire.TxOut{
|
||||||
|
Value: int64(vtxoAmount),
|
||||||
|
PkScript: vtxoScript,
|
||||||
|
}, 1); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updater.AddInSighashType(txscript.SigHashDefault, 1); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitTxs = append(forfeitTxs, partialTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return forfeitTxs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConnectorInputs(partialTx *psbt.Packet, connectorAmount int64) ([]*wire.OutPoint, []*wire.TxOut) {
|
||||||
|
inputs := make([]*wire.OutPoint, 0)
|
||||||
|
witnessUtxos := make([]*wire.TxOut, 0)
|
||||||
|
|
||||||
|
for i, output := range partialTx.UnsignedTx.TxOut {
|
||||||
|
if output.Value == connectorAmount {
|
||||||
|
inputs = append(inputs, &wire.OutPoint{
|
||||||
|
Hash: partialTx.UnsignedTx.TxHash(),
|
||||||
|
Index: uint32(i),
|
||||||
|
})
|
||||||
|
witnessUtxos = append(witnessUtxos, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputs, witnessUtxos
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package bitcointree_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -43,10 +44,9 @@ func TestRoundTripSignTree(t *testing.T) {
|
|||||||
_, sharedOutputAmount, err := bitcointree.CraftSharedOutput(
|
_, sharedOutputAmount, err := bitcointree.CraftSharedOutput(
|
||||||
cosigners,
|
cosigners,
|
||||||
asp.PubKey(),
|
asp.PubKey(),
|
||||||
f.Receivers,
|
castReceivers(f.Receivers, asp.PubKey()),
|
||||||
minRelayFee,
|
minRelayFee,
|
||||||
lifetime,
|
lifetime,
|
||||||
exitDelay,
|
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -58,10 +58,9 @@ func TestRoundTripSignTree(t *testing.T) {
|
|||||||
},
|
},
|
||||||
cosigners,
|
cosigners,
|
||||||
asp.PubKey(),
|
asp.PubKey(),
|
||||||
f.Receivers,
|
castReceivers(f.Receivers, asp.PubKey()),
|
||||||
minRelayFee,
|
minRelayFee,
|
||||||
lifetime,
|
lifetime,
|
||||||
exitDelay,
|
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -218,9 +217,43 @@ func TestRoundTripSignTree(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type receiverFixture struct {
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Pubkey string `json:"pubkey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r receiverFixture) toVtxoScript(asp *secp256k1.PublicKey) bitcointree.VtxoScript {
|
||||||
|
bytesKey, err := hex.DecodeString(r.Pubkey)
|
||||||
|
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 {
|
||||||
|
receiversOut = append(receiversOut, bitcointree.Receiver{
|
||||||
|
Script: r.toVtxoScript(asp),
|
||||||
|
Amount: uint64(r.Amount),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return receiversOut
|
||||||
|
}
|
||||||
|
|
||||||
type fixture struct {
|
type fixture struct {
|
||||||
Valid []struct {
|
Valid []struct {
|
||||||
Receivers []bitcointree.Receiver `json:"receivers"`
|
Receivers []receiverFixture `json:"receivers"`
|
||||||
} `json:"valid"`
|
} `json:"valid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,44 +141,6 @@ func (d *CSVSigClosure) Decode(script []byte) (bool, error) {
|
|||||||
return valid, nil
|
return valid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ComputeVtxoTaprootScript(
|
|
||||||
userPubkey, aspPubkey *secp256k1.PublicKey, exitDelay uint,
|
|
||||||
) (*secp256k1.PublicKey, *txscript.TapscriptProof, error) {
|
|
||||||
redeemClosure := &CSVSigClosure{
|
|
||||||
Pubkey: userPubkey,
|
|
||||||
Seconds: exitDelay,
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitClosure := &MultisigClosure{
|
|
||||||
Pubkey: userPubkey,
|
|
||||||
AspPubkey: aspPubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
redeemLeaf, err := redeemClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitLeaf, err := forfeitClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoTaprootTree := txscript.AssembleTaprootScriptTree(
|
|
||||||
*redeemLeaf, *forfeitLeaf,
|
|
||||||
)
|
|
||||||
root := vtxoTaprootTree.RootNode.TapHash()
|
|
||||||
|
|
||||||
unspendableKey := UnspendableKey()
|
|
||||||
vtxoTaprootKey := txscript.ComputeTaprootOutputKey(unspendableKey, root[:])
|
|
||||||
|
|
||||||
redeemLeafHash := redeemLeaf.TapHash()
|
|
||||||
proofIndex := vtxoTaprootTree.LeafProofIndex[redeemLeafHash]
|
|
||||||
proof := vtxoTaprootTree.LeafMerkleProofs[proofIndex]
|
|
||||||
|
|
||||||
return vtxoTaprootKey, &proof, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeChecksigScript(script []byte) (bool, *secp256k1.PublicKey, error) {
|
func decodeChecksigScript(script []byte) (bool, *secp256k1.PublicKey, error) {
|
||||||
data32Index := bytes.Index(script, []byte{txscript.OP_DATA_32})
|
data32Index := bytes.Index(script, []byte{txscript.OP_DATA_32})
|
||||||
if data32Index == -1 {
|
if data32Index == -1 {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package bitcointree
|
package bitcointree
|
||||||
|
|
||||||
type Receiver struct {
|
type Receiver struct {
|
||||||
Pubkey string
|
Script VtxoScript
|
||||||
Amount uint64
|
Amount uint64
|
||||||
}
|
}
|
||||||
|
|||||||
236
common/bitcointree/vtxo.go
Normal file
236
common/bitcointree/vtxo.go
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
package bitcointree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VtxoScript common.VtxoScript[bitcoinTapTree]
|
||||||
|
|
||||||
|
func ParseVtxoScript(desc string) (VtxoScript, error) {
|
||||||
|
v := &DefaultVtxoScript{}
|
||||||
|
// TODO add other type
|
||||||
|
err := v.FromDescriptor(desc)
|
||||||
|
if err != nil {
|
||||||
|
v := &ReversibleVtxoScript{}
|
||||||
|
err = v.FromDescriptor(desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid vtxo descriptor: %s", desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DefaultVtxoScript is the default implementation of VTXO with 2 closures
|
||||||
|
* - Owner and ASP (forfeit)
|
||||||
|
* - Owner after t (unilateral exit)
|
||||||
|
*/
|
||||||
|
type DefaultVtxoScript struct {
|
||||||
|
Owner *secp256k1.PublicKey
|
||||||
|
Asp *secp256k1.PublicKey
|
||||||
|
ExitDelay uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *DefaultVtxoScript) ToDescriptor() string {
|
||||||
|
owner := hex.EncodeToString(schnorr.SerializePubKey(v.Owner))
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
descriptor.DefaultVtxoDescriptorTemplate,
|
||||||
|
hex.EncodeToString(UnspendableKey().SerializeCompressed()),
|
||||||
|
owner,
|
||||||
|
hex.EncodeToString(schnorr.SerializePubKey(v.Asp)),
|
||||||
|
v.ExitDelay,
|
||||||
|
owner,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *DefaultVtxoScript) FromDescriptor(desc string) error {
|
||||||
|
taprootDesc, err := descriptor.ParseTaprootDescriptor(desc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, asp, exitDelay, err := descriptor.ParseDefaultVtxoDescriptor(*taprootDesc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Owner = owner
|
||||||
|
v.Asp = asp
|
||||||
|
v.ExitDelay = exitDelay
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *DefaultVtxoScript) TapTree() (*secp256k1.PublicKey, bitcoinTapTree, error) {
|
||||||
|
redeemClosure := &CSVSigClosure{
|
||||||
|
Pubkey: v.Owner,
|
||||||
|
Seconds: v.ExitDelay,
|
||||||
|
}
|
||||||
|
|
||||||
|
redeemLeaf, err := redeemClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, bitcoinTapTree{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitClosure := &MultisigClosure{
|
||||||
|
Pubkey: v.Owner,
|
||||||
|
AspPubkey: v.Asp,
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, bitcoinTapTree{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tapTree := txscript.AssembleTaprootScriptTree(
|
||||||
|
*redeemLeaf, *forfeitLeaf,
|
||||||
|
)
|
||||||
|
|
||||||
|
root := tapTree.RootNode.TapHash()
|
||||||
|
taprootKey := txscript.ComputeTaprootOutputKey(
|
||||||
|
UnspendableKey(),
|
||||||
|
root[:],
|
||||||
|
)
|
||||||
|
|
||||||
|
return taprootKey, bitcoinTapTree{tapTree}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ReversibleVtxoScript allows sender of the VTXO to revert the transaction
|
||||||
|
* unilateral exit is in favor of the sender
|
||||||
|
* - Owner and ASP (forfeit owner)
|
||||||
|
* - Sender and ASP (forfeit sender)
|
||||||
|
* - Sender after t (unilateral exit)
|
||||||
|
*/
|
||||||
|
type ReversibleVtxoScript struct {
|
||||||
|
Asp *secp256k1.PublicKey
|
||||||
|
Sender *secp256k1.PublicKey
|
||||||
|
Owner *secp256k1.PublicKey
|
||||||
|
ExitDelay uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ReversibleVtxoScript) ToDescriptor() string {
|
||||||
|
owner := hex.EncodeToString(schnorr.SerializePubKey(v.Owner))
|
||||||
|
sender := hex.EncodeToString(schnorr.SerializePubKey(v.Sender))
|
||||||
|
asp := hex.EncodeToString(schnorr.SerializePubKey(v.Asp))
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
descriptor.ReversibleVtxoScriptTemplate,
|
||||||
|
hex.EncodeToString(UnspendableKey().SerializeCompressed()),
|
||||||
|
sender,
|
||||||
|
asp,
|
||||||
|
v.ExitDelay,
|
||||||
|
sender,
|
||||||
|
owner,
|
||||||
|
asp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ReversibleVtxoScript) FromDescriptor(desc string) error {
|
||||||
|
taprootDesc, err := descriptor.ParseTaprootDescriptor(desc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, sender, asp, exitDelay, err := descriptor.ParseReversibleVtxoDescriptor(*taprootDesc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Owner = owner
|
||||||
|
v.Sender = sender
|
||||||
|
v.Asp = asp
|
||||||
|
v.ExitDelay = exitDelay
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ReversibleVtxoScript) TapTree() (*secp256k1.PublicKey, bitcoinTapTree, error) {
|
||||||
|
redeemClosure := &CSVSigClosure{
|
||||||
|
Pubkey: v.Sender,
|
||||||
|
Seconds: v.ExitDelay,
|
||||||
|
}
|
||||||
|
|
||||||
|
redeemLeaf, err := redeemClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, bitcoinTapTree{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitClosure := &MultisigClosure{
|
||||||
|
Pubkey: v.Owner,
|
||||||
|
AspPubkey: v.Asp,
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, bitcoinTapTree{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseForfeitClosure := &MultisigClosure{
|
||||||
|
Pubkey: v.Sender,
|
||||||
|
AspPubkey: v.Asp,
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseForfeitLeaf, err := reverseForfeitClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, bitcoinTapTree{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tapTree := txscript.AssembleTaprootScriptTree(
|
||||||
|
*redeemLeaf, *forfeitLeaf, *reverseForfeitLeaf,
|
||||||
|
)
|
||||||
|
|
||||||
|
root := tapTree.RootNode.TapHash()
|
||||||
|
taprootKey := txscript.ComputeTaprootOutputKey(
|
||||||
|
UnspendableKey(),
|
||||||
|
root[:],
|
||||||
|
)
|
||||||
|
|
||||||
|
return taprootKey, bitcoinTapTree{tapTree}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitcoinTapTree is a wrapper around txscript.IndexedTapScriptTree to implement the common.TaprootTree interface
|
||||||
|
type bitcoinTapTree struct {
|
||||||
|
*txscript.IndexedTapScriptTree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bitcoinTapTree) GetRoot() chainhash.Hash {
|
||||||
|
return b.RootNode.TapHash()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bitcoinTapTree) GetTaprootMerkleProof(leafhash chainhash.Hash) (*common.TaprootMerkleProof, error) {
|
||||||
|
index, ok := b.LeafProofIndex[leafhash]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("leaf %s not found in tree", leafhash.String())
|
||||||
|
}
|
||||||
|
proof := b.LeafMerkleProofs[index]
|
||||||
|
|
||||||
|
controlBlock := proof.ToControlBlock(UnspendableKey())
|
||||||
|
controlBlockBytes, err := controlBlock.ToBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &common.TaprootMerkleProof{
|
||||||
|
ControlBlock: controlBlockBytes,
|
||||||
|
Script: proof.Script,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bitcoinTapTree) GetLeaves() []chainhash.Hash {
|
||||||
|
leafHashes := make([]chainhash.Hash, 0)
|
||||||
|
for hash := range b.LeafProofIndex {
|
||||||
|
leafHashes = append(leafHashes, hash)
|
||||||
|
}
|
||||||
|
return leafHashes
|
||||||
|
}
|
||||||
67
common/bitcointree/vtxo_test.go
Normal file
67
common/bitcointree/vtxo_test.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package bitcointree_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common/bitcointree"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseDescriptor(t *testing.T) {
|
||||||
|
aspKey, err := secp256k1.GeneratePrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
aliceKey, err := secp256k1.GeneratePrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
bobKey, err := secp256k1.GeneratePrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
aspPubKey := hex.EncodeToString(schnorr.SerializePubKey(aspKey.PubKey()))
|
||||||
|
alicePubKey := hex.EncodeToString(schnorr.SerializePubKey(aliceKey.PubKey()))
|
||||||
|
bobPubKey := hex.EncodeToString(schnorr.SerializePubKey(bobKey.PubKey()))
|
||||||
|
|
||||||
|
unspendableKey := hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed())
|
||||||
|
|
||||||
|
defaultScriptDescriptor := fmt.Sprintf(
|
||||||
|
descriptor.DefaultVtxoDescriptorTemplate,
|
||||||
|
unspendableKey,
|
||||||
|
alicePubKey,
|
||||||
|
aspPubKey,
|
||||||
|
512,
|
||||||
|
alicePubKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
vtxo, err := bitcointree.ParseVtxoScript(defaultScriptDescriptor)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.IsType(t, &bitcointree.DefaultVtxoScript{}, vtxo)
|
||||||
|
require.Equal(t, defaultScriptDescriptor, vtxo.ToDescriptor())
|
||||||
|
require.Equal(t, alicePubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.DefaultVtxoScript).Owner)))
|
||||||
|
require.Equal(t, aspPubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.DefaultVtxoScript).Asp)))
|
||||||
|
|
||||||
|
reversibleScriptDescriptor := fmt.Sprintf(
|
||||||
|
descriptor.ReversibleVtxoScriptTemplate,
|
||||||
|
unspendableKey,
|
||||||
|
alicePubKey,
|
||||||
|
aspPubKey,
|
||||||
|
512,
|
||||||
|
alicePubKey,
|
||||||
|
bobPubKey,
|
||||||
|
aspPubKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
vtxo, err = bitcointree.ParseVtxoScript(reversibleScriptDescriptor)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.IsType(t, &bitcointree.ReversibleVtxoScript{}, vtxo)
|
||||||
|
require.Equal(t, reversibleScriptDescriptor, vtxo.ToDescriptor())
|
||||||
|
require.Equal(t, alicePubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.ReversibleVtxoScript).Sender)))
|
||||||
|
require.Equal(t, bobPubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.ReversibleVtxoScript).Owner)))
|
||||||
|
require.Equal(t, aspPubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.ReversibleVtxoScript).Asp)))
|
||||||
|
}
|
||||||
@@ -8,37 +8,149 @@ import (
|
|||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
const BoardingDescriptorTemplate = "tr(%s,{ and(pk(%s), pk(%s)), and(older(%d), pk(%s)) })"
|
// tr(unspendable, { and(pk(user), pk(asp)), and(older(timeout), pk(user)) })
|
||||||
|
const DefaultVtxoDescriptorTemplate = "tr(%s,{ and(pk(%s), pk(%s)), and(older(%d), pk(%s)) })"
|
||||||
|
|
||||||
func ParseBoardingDescriptor(
|
// tr(unspendable, { { and(pk(sender), pk(asp)), and(older(timeout), pk(sender)) }, and(pk(receiver), pk(asp)) })
|
||||||
|
const ReversibleVtxoScriptTemplate = "tr(%s,{ { and(pk(%s), pk(%s)), and(older(%d), pk(%s)) }, and(pk(%s), pk(%s)) })"
|
||||||
|
|
||||||
|
func ParseReversibleVtxoDescriptor(
|
||||||
desc TaprootDescriptor,
|
desc TaprootDescriptor,
|
||||||
) (user *secp256k1.PublicKey, timeout uint, err error) {
|
) (user, sender, asp *secp256k1.PublicKey, timeout uint, err error) {
|
||||||
|
if len(desc.ScriptTree) != 3 {
|
||||||
|
return nil, nil, nil, 0, errors.New("not a reversible vtxo script descriptor")
|
||||||
|
}
|
||||||
|
|
||||||
for _, leaf := range desc.ScriptTree {
|
for _, leaf := range desc.ScriptTree {
|
||||||
if andLeaf, ok := leaf.(*And); ok {
|
if andLeaf, ok := leaf.(*And); ok {
|
||||||
if first, ok := andLeaf.First.(*Older); ok {
|
if first, ok := andLeaf.First.(*PK); ok {
|
||||||
timeout = first.Timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
if second, ok := andLeaf.Second.(*PK); ok {
|
if second, ok := andLeaf.Second.(*PK); ok {
|
||||||
keyBytes, err := hex.DecodeString(second.Key.Hex)
|
keyBytes, err := hex.DecodeString(first.Key.Hex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
if sender == nil {
|
||||||
|
sender, err = schnorr.ParsePubKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, 0, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
user, err = schnorr.ParsePubKey(keyBytes)
|
user, err = schnorr.ParsePubKey(keyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, nil, nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if asp == nil {
|
||||||
|
keyBytes, err = hex.DecodeString(second.Key.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
asp, err = schnorr.ParsePubKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if first, ok := andLeaf.First.(*Older); ok {
|
||||||
|
if second, ok := andLeaf.Second.(*PK); ok {
|
||||||
|
timeout = first.Timeout
|
||||||
|
keyBytes, err := hex.DecodeString(second.Key.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sender, err = schnorr.ParsePubKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, 0, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return nil, 0, errors.New("boarding descriptor is invalid")
|
return nil, nil, nil, 0, errors.New("descriptor is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if asp == nil {
|
||||||
|
return nil, nil, nil, 0, errors.New("descriptor is invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
return nil, 0, errors.New("boarding descriptor is invalid")
|
return nil, nil, nil, 0, errors.New("descriptor is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sender == nil {
|
||||||
|
return nil, nil, nil, 0, errors.New("descriptor is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseDefaultVtxoDescriptor(
|
||||||
|
desc TaprootDescriptor,
|
||||||
|
) (user, asp *secp256k1.PublicKey, timeout uint, err error) {
|
||||||
|
if len(desc.ScriptTree) != 2 {
|
||||||
|
return nil, nil, 0, errors.New("not a default vtxo script descriptor")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, leaf := range desc.ScriptTree {
|
||||||
|
if andLeaf, ok := leaf.(*And); ok {
|
||||||
|
if first, ok := andLeaf.First.(*PK); ok {
|
||||||
|
if second, ok := andLeaf.Second.(*PK); ok {
|
||||||
|
keyBytes, err := hex.DecodeString(first.Key.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err = schnorr.ParsePubKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyBytes, err = hex.DecodeString(second.Key.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
asp, err = schnorr.ParsePubKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if first, ok := andLeaf.First.(*Older); ok {
|
||||||
|
if second, ok := andLeaf.Second.(*PK); ok {
|
||||||
|
timeout = first.Timeout
|
||||||
|
keyBytes, err := hex.DecodeString(second.Key.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err = schnorr.ParsePubKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
return nil, nil, 0, errors.New("boarding descriptor is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if asp == nil {
|
||||||
|
return nil, nil, 0, errors.New("boarding descriptor is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeout == 0 {
|
||||||
|
return nil, nil, 0, errors.New("boarding descriptor is invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -204,6 +204,9 @@ func (e *And) Script(verify bool) (string, error) {
|
|||||||
|
|
||||||
func parseExpression(policy string) (Expression, error) {
|
func parseExpression(policy string) (Expression, error) {
|
||||||
policy = strings.TrimSpace(policy)
|
policy = strings.TrimSpace(policy)
|
||||||
|
if policy[0] == '{' {
|
||||||
|
policy = policy[1:]
|
||||||
|
}
|
||||||
expressions := make([]Expression, 0)
|
expressions := make([]Expression, 0)
|
||||||
expressions = append(expressions, &PK{})
|
expressions = append(expressions, &PK{})
|
||||||
expressions = append(expressions, &Older{})
|
expressions = append(expressions, &Older{})
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ func ParseTaprootDescriptor(desc string) (*TaprootDescriptor, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, scriptStr := range scriptParts {
|
for _, scriptStr := range scriptParts {
|
||||||
|
if scriptStr == "}" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
leaf, err := parseExpression(scriptStr)
|
leaf, err := parseExpression(scriptStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func TestParseTaprootDescriptor(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Boarding",
|
name: "Boarding",
|
||||||
desc: "tr(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,{ and(pk(873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)), and(older(604672), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)) })",
|
desc: "tr(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,{ and(pk(973079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)), and(older(604672), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)) })",
|
||||||
expected: descriptor.TaprootDescriptor{
|
expected: descriptor.TaprootDescriptor{
|
||||||
InternalKey: descriptor.Key{Hex: "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"},
|
InternalKey: descriptor.Key{Hex: "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"},
|
||||||
ScriptTree: []descriptor.Expression{
|
ScriptTree: []descriptor.Expression{
|
||||||
@@ -70,7 +70,7 @@ func TestParseTaprootDescriptor(t *testing.T) {
|
|||||||
First: &descriptor.PK{
|
First: &descriptor.PK{
|
||||||
Key: descriptor.XOnlyKey{
|
Key: descriptor.XOnlyKey{
|
||||||
descriptor.Key{
|
descriptor.Key{
|
||||||
Hex: "873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465",
|
Hex: "973079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -125,16 +125,72 @@ func TestParseTaprootDescriptor(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Reversible VTXO",
|
||||||
|
desc: "tr(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,{ { and(pk(873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)), and(older(604672), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)) }, {and(pk(873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798))}})",
|
||||||
|
expected: descriptor.TaprootDescriptor{
|
||||||
|
InternalKey: descriptor.Key{Hex: "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"},
|
||||||
|
ScriptTree: []descriptor.Expression{
|
||||||
|
&descriptor.And{
|
||||||
|
First: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Second: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&descriptor.And{
|
||||||
|
Second: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
First: &descriptor.Older{
|
||||||
|
Timeout: 604672,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&descriptor.And{
|
||||||
|
First: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Second: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := descriptor.ParseTaprootDescriptor(tt.desc)
|
got, err := descriptor.ParseTaprootDescriptor(tt.desc)
|
||||||
if (err != nil) != tt.wantErr {
|
if tt.wantErr {
|
||||||
require.Equal(t, tt.wantErr, err != nil, err)
|
require.Error(t, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
require.Equal(t, tt.expected, got)
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, got)
|
||||||
|
require.Equal(t, tt.expected, *got)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
)
|
)
|
||||||
|
|
||||||
var TreeTxSize = (&input.TxWeightEstimator{}).
|
var TreeTxSize = (&input.TxWeightEstimator{}).
|
||||||
@@ -19,3 +23,29 @@ var ConnectorTxSize = (&input.TxWeightEstimator{}).
|
|||||||
AddP2WKHOutput().
|
AddP2WKHOutput().
|
||||||
AddP2WKHOutput().
|
AddP2WKHOutput().
|
||||||
VSize()
|
VSize()
|
||||||
|
|
||||||
|
func ComputeForfeitMinRelayFee(feeRate chainfee.SatPerKVByte, vtxoScriptTapTree TaprootTree) (uint64, error) {
|
||||||
|
txWeightEstimator := &input.TxWeightEstimator{}
|
||||||
|
|
||||||
|
biggestVtxoLeafProof, err := BiggestLeafMerkleProof(vtxoScriptTapTree)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrlBlock, err := txscript.ParseControlBlock(biggestVtxoLeafProof.ControlBlock)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
txWeightEstimator.AddP2PKHInput() // connector input
|
||||||
|
txWeightEstimator.AddTapscriptInput(
|
||||||
|
64*2, // forfeit witness = 2 signatures
|
||||||
|
&waddrmgr.Tapscript{
|
||||||
|
RevealedScript: biggestVtxoLeafProof.Script,
|
||||||
|
ControlBlock: ctrlBlock,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
txWeightEstimator.AddP2TROutput() // asp output
|
||||||
|
|
||||||
|
return uint64(feeRate.FeeForVSize(lntypes.VByte(txWeightEstimator.VSize())).ToUnit(btcutil.AmountSatoshi)), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ require (
|
|||||||
github.com/btcsuite/btcd/btcutil v1.1.5
|
github.com/btcsuite/btcd/btcutil v1.1.5
|
||||||
github.com/btcsuite/btcd/btcutil/psbt v1.1.9
|
github.com/btcsuite/btcd/btcutil/psbt v1.1.9
|
||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
|
||||||
|
github.com/btcsuite/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
|
||||||
github.com/lightningnetwork/lnd v0.18.2-beta
|
github.com/lightningnetwork/lnd v0.18.2-beta
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
@@ -17,7 +18,6 @@ require (
|
|||||||
dario.cat/mergo v1.0.1 // indirect
|
dario.cat/mergo v1.0.1 // indirect
|
||||||
github.com/aead/siphash v1.0.1 // indirect
|
github.com/aead/siphash v1.0.1 // indirect
|
||||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
||||||
github.com/btcsuite/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd // indirect
|
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4 // indirect
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4 // indirect
|
||||||
github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 // indirect
|
github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 // indirect
|
||||||
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.4 // indirect
|
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.4 // indirect
|
||||||
@@ -25,16 +25,19 @@ require (
|
|||||||
github.com/btcsuite/btcwallet/wtxmgr v1.5.3 // indirect
|
github.com/btcsuite/btcwallet/wtxmgr v1.5.3 // indirect
|
||||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
||||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
|
||||||
|
github.com/btcsuite/winsvc v1.0.0 // indirect
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||||
github.com/decred/dcrd/lru v1.1.3 // indirect
|
github.com/decred/dcrd/lru v1.1.3 // indirect
|
||||||
github.com/docker/cli v27.1.1+incompatible // indirect
|
github.com/docker/cli v27.1.1+incompatible // indirect
|
||||||
github.com/fergusstrange/embedded-postgres v1.28.0 // indirect
|
github.com/fergusstrange/embedded-postgres v1.28.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/go-errors/errors v1.5.1 // indirect
|
github.com/go-errors/errors v1.5.1 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
github.com/golang-migrate/migrate/v4 v4.17.1 // indirect
|
github.com/golang-migrate/migrate/v4 v4.17.1 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||||
@@ -46,6 +49,7 @@ require (
|
|||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jessevdk/go-flags v1.6.1 // indirect
|
github.com/jessevdk/go-flags v1.6.1 // indirect
|
||||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||||
|
github.com/jrick/logrotate v1.0.0 // indirect
|
||||||
github.com/kkdai/bstream v1.0.0 // indirect
|
github.com/kkdai/bstream v1.0.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
@@ -64,6 +68,7 @@ require (
|
|||||||
github.com/lightningnetwork/lnd/tor v1.1.3 // indirect
|
github.com/lightningnetwork/lnd/tor v1.1.3 // indirect
|
||||||
github.com/ltcsuite/ltcd v0.23.5 // indirect
|
github.com/ltcsuite/ltcd v0.23.5 // indirect
|
||||||
github.com/miekg/dns v1.1.61 // indirect
|
github.com/miekg/dns v1.1.61 // indirect
|
||||||
|
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||||
github.com/ory/dockertest/v3 v3.11.0 // indirect
|
github.com/ory/dockertest/v3 v3.11.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
@@ -71,6 +76,7 @@ require (
|
|||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect
|
||||||
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 // indirect
|
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 // indirect
|
||||||
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect
|
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
|
|||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/fergusstrange/embedded-postgres v1.28.0 h1:Atixd24HCuBHBavnG4eiZAjRizOViwUahKGSjJdz1SU=
|
github.com/fergusstrange/embedded-postgres v1.28.0 h1:Atixd24HCuBHBavnG4eiZAjRizOViwUahKGSjJdz1SU=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
@@ -45,6 +46,7 @@ github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMn
|
|||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
@@ -101,6 +103,9 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
|
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||||
|
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
github.com/opencontainers/runc v1.1.13 h1:98S2srgG9vw0zWcDpFMn5TRrh8kLxa/5OFUstuUhmRs=
|
github.com/opencontainers/runc v1.1.13 h1:98S2srgG9vw0zWcDpFMn5TRrh8kLxa/5OFUstuUhmRs=
|
||||||
@@ -164,6 +169,7 @@ google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
|||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -14,13 +14,13 @@ import (
|
|||||||
|
|
||||||
func CraftCongestionTree(
|
func CraftCongestionTree(
|
||||||
asset string, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
|
asset string, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
|
||||||
feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64,
|
feeSatsPerNode uint64, roundLifetime int64,
|
||||||
) (
|
) (
|
||||||
buildCongestionTree TreeFactory,
|
buildCongestionTree TreeFactory,
|
||||||
sharedOutputScript []byte, sharedOutputAmount uint64, err error,
|
sharedOutputScript []byte, sharedOutputAmount uint64, err error,
|
||||||
) {
|
) {
|
||||||
root, err := createPartialCongestionTree(
|
root, err := createPartialCongestionTree(
|
||||||
asset, aspPubkey, receivers, feeSatsPerNode, roundLifetime, unilateralExitDelay,
|
asset, aspPubkey, receivers, feeSatsPerNode, roundLifetime,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -49,7 +49,6 @@ type node struct {
|
|||||||
asset string
|
asset string
|
||||||
feeSats uint64
|
feeSats uint64
|
||||||
roundLifetime int64
|
roundLifetime int64
|
||||||
unilateralExitDelay int64
|
|
||||||
|
|
||||||
_inputTaprootKey *secp256k1.PublicKey
|
_inputTaprootKey *secp256k1.PublicKey
|
||||||
_inputTaprootTree *taproot.IndexedElementsTapScriptTree
|
_inputTaprootTree *taproot.IndexedElementsTapScriptTree
|
||||||
@@ -242,53 +241,15 @@ func (n *node) getWitnessData() (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) getVtxoWitnessData() (
|
func (n *node) getVtxoWitnessData() (
|
||||||
*secp256k1.PublicKey, *taproot.IndexedElementsTapScriptTree, error,
|
*secp256k1.PublicKey, common.TaprootTree, error,
|
||||||
) {
|
) {
|
||||||
if !n.isLeaf() {
|
if !n.isLeaf() {
|
||||||
return nil, nil, fmt.Errorf("cannot call vtxoWitness on a non-leaf node")
|
return nil, nil, fmt.Errorf("cannot call vtxoWitness on a non-leaf node")
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := hex.DecodeString(n.receivers[0].Pubkey)
|
receiver := n.receivers[0]
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pubkey, err := secp256k1.ParsePubKey(key)
|
return receiver.Script.TapTree()
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
redeemClosure := &CSVSigClosure{
|
|
||||||
Pubkey: pubkey,
|
|
||||||
Seconds: uint(n.unilateralExitDelay),
|
|
||||||
}
|
|
||||||
|
|
||||||
redeemLeaf, err := redeemClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitClosure := &ForfeitClosure{
|
|
||||||
Pubkey: pubkey,
|
|
||||||
AspPubkey: n.sweepKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitLeaf, err := forfeitClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
leafTaprootTree := taproot.AssembleTaprootScriptTree(
|
|
||||||
*redeemLeaf, *forfeitLeaf,
|
|
||||||
)
|
|
||||||
root := leafTaprootTree.RootNode.TapHash()
|
|
||||||
|
|
||||||
taprootKey := taproot.ComputeTaprootOutputKey(
|
|
||||||
UnspendableKey(),
|
|
||||||
root[:],
|
|
||||||
)
|
|
||||||
|
|
||||||
return taprootKey, leafTaprootTree, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) getTreeNode(
|
func (n *node) getTreeNode(
|
||||||
@@ -413,7 +374,7 @@ func (n *node) createFinalCongestionTree() TreeFactory {
|
|||||||
|
|
||||||
func createPartialCongestionTree(
|
func createPartialCongestionTree(
|
||||||
asset string, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
|
asset string, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
|
||||||
feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64,
|
feeSatsPerNode uint64, roundLifetime int64,
|
||||||
) (root *node, err error) {
|
) (root *node, err error) {
|
||||||
if len(receivers) == 0 {
|
if len(receivers) == 0 {
|
||||||
return nil, fmt.Errorf("no receivers provided")
|
return nil, fmt.Errorf("no receivers provided")
|
||||||
@@ -427,7 +388,6 @@ func createPartialCongestionTree(
|
|||||||
asset: asset,
|
asset: asset,
|
||||||
feeSats: feeSatsPerNode,
|
feeSats: feeSatsPerNode,
|
||||||
roundLifetime: roundLifetime,
|
roundLifetime: roundLifetime,
|
||||||
unilateralExitDelay: unilateralExitDelay,
|
|
||||||
}
|
}
|
||||||
nodes = append(nodes, leafNode)
|
nodes = append(nodes, leafNode)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,26 @@
|
|||||||
package txbuilder
|
package tree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/ark-network/ark/common"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common/tree"
|
|
||||||
"github.com/ark-network/ark/server/internal/core/domain"
|
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
|
||||||
"github.com/vulpemventures/go-elements/elementsutil"
|
"github.com/vulpemventures/go-elements/elementsutil"
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
"github.com/vulpemventures/go-elements/taproot"
|
|
||||||
"github.com/vulpemventures/go-elements/transaction"
|
"github.com/vulpemventures/go-elements/transaction"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *txBuilder) craftForfeitTxs(
|
func BuildForfeitTxs(
|
||||||
connectorTx *psetv2.Pset,
|
connectorTx *psetv2.Pset,
|
||||||
connectorAmount uint64,
|
vtxoInput psetv2.InputArgs,
|
||||||
vtxo domain.Vtxo,
|
vtxoAmount,
|
||||||
vtxoForfeitTapleaf taproot.TapscriptElementsProof,
|
connectorAmount,
|
||||||
vtxoScript, aspScript []byte,
|
feeAmount uint64,
|
||||||
) (forfeitTxs []string, err error) {
|
vtxoScript []byte,
|
||||||
|
aspPubKey *secp256k1.PublicKey,
|
||||||
|
) (forfeitTxs []*psetv2.Pset, err error) {
|
||||||
connectors, prevouts := getConnectorInputs(connectorTx, connectorAmount)
|
connectors, prevouts := getConnectorInputs(connectorTx, connectorAmount)
|
||||||
|
|
||||||
for i, connectorInput := range connectors {
|
for i, connectorInput := range connectors {
|
||||||
weightEstimator := &input.TxWeightEstimator{}
|
|
||||||
|
|
||||||
connectorPrevout := prevouts[i]
|
connectorPrevout := prevouts[i]
|
||||||
asset := elementsutil.AssetHashFromBytes(connectorPrevout.Asset)
|
asset := elementsutil.AssetHashFromBytes(connectorPrevout.Asset)
|
||||||
|
|
||||||
@@ -39,13 +34,6 @@ func (b *txBuilder) craftForfeitTxs(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxoInput := psetv2.InputArgs{
|
|
||||||
Txid: vtxo.Txid,
|
|
||||||
TxIndex: vtxo.VOut,
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoAmount, _ := elementsutil.ValueToBytes(vtxo.Amount)
|
|
||||||
|
|
||||||
if err := updater.AddInputs([]psetv2.InputArgs{connectorInput, vtxoInput}); err != nil {
|
if err := updater.AddInputs([]psetv2.InputArgs{connectorInput, vtxoInput}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -58,9 +46,12 @@ func (b *txBuilder) craftForfeitTxs(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
weightEstimator.AddP2WKHInput()
|
amountBytes, err := elementsutil.ValueToBytes(vtxoAmount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
vtxoPrevout := transaction.NewTxOutput(connectorPrevout.Asset, vtxoAmount, vtxoScript)
|
vtxoPrevout := transaction.NewTxOutput(connectorPrevout.Asset, amountBytes, vtxoScript)
|
||||||
|
|
||||||
if err = updater.AddInWitnessUtxo(1, vtxoPrevout); err != nil {
|
if err = updater.AddInWitnessUtxo(1, vtxoPrevout); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -70,27 +61,7 @@ func (b *txBuilder) craftForfeitTxs(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unspendableKey := tree.UnspendableKey()
|
aspScript, err := common.P2TRScript(aspPubKey)
|
||||||
|
|
||||||
tapScript := psetv2.NewTapLeafScript(vtxoForfeitTapleaf, unspendableKey)
|
|
||||||
if err := updater.AddInTapLeafScript(1, tapScript); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
weightEstimator.AddTapscriptInput(64*2, &waddrmgr.Tapscript{
|
|
||||||
ControlBlock: &tapScript.ControlBlock.ControlBlock,
|
|
||||||
RevealedScript: tapScript.TapLeaf.Script,
|
|
||||||
})
|
|
||||||
|
|
||||||
connectorAmount, err := elementsutil.ValueFromBytes(connectorPrevout.Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
weightEstimator.AddP2WKHOutput()
|
|
||||||
weightEstimator.AddP2WKHOutput()
|
|
||||||
|
|
||||||
feeAmount, err := b.wallet.MinRelayFee(context.Background(), uint64(weightEstimator.VSize()))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -98,7 +69,7 @@ func (b *txBuilder) craftForfeitTxs(
|
|||||||
err = updater.AddOutputs([]psetv2.OutputArgs{
|
err = updater.AddOutputs([]psetv2.OutputArgs{
|
||||||
{
|
{
|
||||||
Asset: asset,
|
Asset: asset,
|
||||||
Amount: vtxo.Amount + connectorAmount - feeAmount,
|
Amount: vtxoAmount + connectorAmount - feeAmount,
|
||||||
Script: aspScript,
|
Script: aspScript,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -110,12 +81,28 @@ func (b *txBuilder) craftForfeitTxs(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := pset.ToBase64()
|
forfeitTxs = append(forfeitTxs, pset)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitTxs = append(forfeitTxs, tx)
|
|
||||||
}
|
}
|
||||||
return forfeitTxs, nil
|
return forfeitTxs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getConnectorInputs(pset *psetv2.Pset, connectorAmount uint64) ([]psetv2.InputArgs, []*transaction.TxOutput) {
|
||||||
|
txID, _ := getPsetId(pset)
|
||||||
|
|
||||||
|
inputs := make([]psetv2.InputArgs, 0, len(pset.Outputs))
|
||||||
|
witnessUtxos := make([]*transaction.TxOutput, 0, len(pset.Outputs))
|
||||||
|
|
||||||
|
for i, output := range pset.Outputs {
|
||||||
|
utx, _ := pset.UnsignedTx()
|
||||||
|
|
||||||
|
if output.Value == connectorAmount && len(output.Script) > 0 {
|
||||||
|
inputs = append(inputs, psetv2.InputArgs{
|
||||||
|
Txid: txID,
|
||||||
|
TxIndex: uint32(i),
|
||||||
|
})
|
||||||
|
witnessUtxos = append(witnessUtxos, utx.Outputs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputs, witnessUtxos
|
||||||
|
}
|
||||||
@@ -10,9 +10,6 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/vulpemventures/go-elements/address"
|
|
||||||
"github.com/vulpemventures/go-elements/network"
|
|
||||||
"github.com/vulpemventures/go-elements/payment"
|
|
||||||
"github.com/vulpemventures/go-elements/taproot"
|
"github.com/vulpemventures/go-elements/taproot"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,7 +37,7 @@ type CSVSigClosure struct {
|
|||||||
Seconds uint
|
Seconds uint
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForfeitClosure struct {
|
type MultisigClosure struct {
|
||||||
Pubkey *secp256k1.PublicKey
|
Pubkey *secp256k1.PublicKey
|
||||||
AspPubkey *secp256k1.PublicKey
|
AspPubkey *secp256k1.PublicKey
|
||||||
}
|
}
|
||||||
@@ -58,7 +55,7 @@ func DecodeClosure(script []byte) (Closure, error) {
|
|||||||
return closure, nil
|
return closure, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
closure = &ForfeitClosure{}
|
closure = &MultisigClosure{}
|
||||||
if valid, err := closure.Decode(script); err == nil && valid {
|
if valid, err := closure.Decode(script); err == nil && valid {
|
||||||
return closure, nil
|
return closure, nil
|
||||||
}
|
}
|
||||||
@@ -66,7 +63,7 @@ func DecodeClosure(script []byte) (Closure, error) {
|
|||||||
return nil, fmt.Errorf("invalid closure script %s", hex.EncodeToString(script))
|
return nil, fmt.Errorf("invalid closure script %s", hex.EncodeToString(script))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *ForfeitClosure) Leaf() (*taproot.TapElementsLeaf, error) {
|
func (f *MultisigClosure) Leaf() (*taproot.TapElementsLeaf, error) {
|
||||||
aspKeyBytes := schnorr.SerializePubKey(f.AspPubkey)
|
aspKeyBytes := schnorr.SerializePubKey(f.AspPubkey)
|
||||||
userKeyBytes := schnorr.SerializePubKey(f.Pubkey)
|
userKeyBytes := schnorr.SerializePubKey(f.Pubkey)
|
||||||
|
|
||||||
@@ -81,7 +78,7 @@ func (f *ForfeitClosure) Leaf() (*taproot.TapElementsLeaf, error) {
|
|||||||
return &tapLeaf, nil
|
return &tapLeaf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *ForfeitClosure) Decode(script []byte) (bool, error) {
|
func (f *MultisigClosure) Decode(script []byte) (bool, error) {
|
||||||
valid, aspPubKey, err := decodeChecksigScript(script)
|
valid, aspPubKey, err := decodeChecksigScript(script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -284,59 +281,6 @@ func (c *UnrollClosure) Decode(script []byte) (valid bool, err error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ComputeVtxoTaprootScript(
|
|
||||||
userPubkey, aspPubkey *secp256k1.PublicKey, exitDelay uint, net network.Network,
|
|
||||||
) (*secp256k1.PublicKey, *taproot.TapscriptElementsProof, []byte, string, error) {
|
|
||||||
redeemClosure := &CSVSigClosure{
|
|
||||||
Pubkey: userPubkey,
|
|
||||||
Seconds: exitDelay,
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitClosure := &ForfeitClosure{
|
|
||||||
Pubkey: userPubkey,
|
|
||||||
AspPubkey: aspPubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
redeemLeaf, err := redeemClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitLeaf, err := forfeitClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoTaprootTree := taproot.AssembleTaprootScriptTree(
|
|
||||||
*redeemLeaf, *forfeitLeaf,
|
|
||||||
)
|
|
||||||
root := vtxoTaprootTree.RootNode.TapHash()
|
|
||||||
|
|
||||||
unspendableKey := UnspendableKey()
|
|
||||||
vtxoTaprootKey := taproot.ComputeTaprootOutputKey(unspendableKey, root[:])
|
|
||||||
|
|
||||||
redeemLeafHash := redeemLeaf.TapHash()
|
|
||||||
proofIndex := vtxoTaprootTree.LeafProofIndex[redeemLeafHash]
|
|
||||||
proof := vtxoTaprootTree.LeafMerkleProofs[proofIndex]
|
|
||||||
|
|
||||||
pay, err := payment.FromTweakedKey(vtxoTaprootKey, &net, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr, err := pay.TaprootAddress()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
script, err := address.ToOutputScript(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return vtxoTaprootKey, &proof, script, addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeIntrospectionScript(
|
func decodeIntrospectionScript(
|
||||||
script []byte, expectedIndex byte, isVerify bool,
|
script []byte, expectedIndex byte, isVerify bool,
|
||||||
) (bool, *secp256k1.PublicKey, uint64, error) {
|
) (bool, *secp256k1.PublicKey, uint64, error) {
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package tree
|
package tree
|
||||||
|
|
||||||
import "github.com/vulpemventures/go-elements/psetv2"
|
import (
|
||||||
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
|
)
|
||||||
|
|
||||||
type TreeFactory func(outpoint psetv2.InputArgs) (CongestionTree, error)
|
type TreeFactory func(outpoint psetv2.InputArgs) (CongestionTree, error)
|
||||||
|
|
||||||
type Receiver struct {
|
type Receiver struct {
|
||||||
Pubkey string
|
Script VtxoScript
|
||||||
Amount uint64
|
Amount uint64
|
||||||
}
|
}
|
||||||
|
|||||||
131
common/tree/vtxo.go
Normal file
131
common/tree/vtxo.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/vulpemventures/go-elements/taproot"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VtxoScript common.VtxoScript[elementsTapTree]
|
||||||
|
|
||||||
|
func ParseVtxoScript(desc string) (VtxoScript, error) {
|
||||||
|
v := &DefaultVtxoScript{}
|
||||||
|
// TODO add other type
|
||||||
|
err := v.FromDescriptor(desc)
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DefaultVtxoScript is the default implementation of VTXO with 2 closures
|
||||||
|
* - Owner and ASP (forfeit)
|
||||||
|
* - Owner after t (unilateral exit)
|
||||||
|
*/
|
||||||
|
type DefaultVtxoScript struct {
|
||||||
|
Owner *secp256k1.PublicKey
|
||||||
|
Asp *secp256k1.PublicKey
|
||||||
|
ExitDelay uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *DefaultVtxoScript) ToDescriptor() string {
|
||||||
|
owner := hex.EncodeToString(schnorr.SerializePubKey(v.Owner))
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
descriptor.DefaultVtxoDescriptorTemplate,
|
||||||
|
hex.EncodeToString(UnspendableKey().SerializeCompressed()),
|
||||||
|
owner,
|
||||||
|
hex.EncodeToString(schnorr.SerializePubKey(v.Asp)),
|
||||||
|
v.ExitDelay,
|
||||||
|
owner,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *DefaultVtxoScript) FromDescriptor(desc string) error {
|
||||||
|
taprootDesc, err := descriptor.ParseTaprootDescriptor(desc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, asp, exitDelay, err := descriptor.ParseDefaultVtxoDescriptor(*taprootDesc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Owner = owner
|
||||||
|
v.Asp = asp
|
||||||
|
v.ExitDelay = exitDelay
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *DefaultVtxoScript) TapTree() (*secp256k1.PublicKey, elementsTapTree, error) {
|
||||||
|
redeemClosure := &CSVSigClosure{
|
||||||
|
Pubkey: v.Owner,
|
||||||
|
Seconds: v.ExitDelay,
|
||||||
|
}
|
||||||
|
|
||||||
|
redeemLeaf, err := redeemClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, elementsTapTree{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitClosure := &MultisigClosure{
|
||||||
|
Pubkey: v.Owner,
|
||||||
|
AspPubkey: v.Asp,
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, elementsTapTree{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tapTree := taproot.AssembleTaprootScriptTree(
|
||||||
|
*redeemLeaf, *forfeitLeaf,
|
||||||
|
)
|
||||||
|
|
||||||
|
root := tapTree.RootNode.TapHash()
|
||||||
|
|
||||||
|
taprootKey := taproot.ComputeTaprootOutputKey(UnspendableKey(), root[:])
|
||||||
|
|
||||||
|
return taprootKey, elementsTapTree{tapTree}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// elementsTapTree wraps the IndexedElementsTapScriptTree to implement the common.TaprootTree interface
|
||||||
|
type elementsTapTree struct {
|
||||||
|
*taproot.IndexedElementsTapScriptTree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b elementsTapTree) GetRoot() chainhash.Hash {
|
||||||
|
return b.RootNode.TapHash()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b elementsTapTree) GetTaprootMerkleProof(leafhash chainhash.Hash) (*common.TaprootMerkleProof, error) {
|
||||||
|
index, ok := b.LeafProofIndex[leafhash]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("leaf %s not found in taproot tree", leafhash.String())
|
||||||
|
}
|
||||||
|
proof := b.LeafMerkleProofs[index]
|
||||||
|
|
||||||
|
controlBlock := proof.ToControlBlock(UnspendableKey())
|
||||||
|
controlBlockBytes, err := controlBlock.ToBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &common.TaprootMerkleProof{
|
||||||
|
ControlBlock: controlBlockBytes,
|
||||||
|
Script: proof.Script,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b elementsTapTree) GetLeaves() []chainhash.Hash {
|
||||||
|
hashes := make([]chainhash.Hash, 0)
|
||||||
|
for h := range b.LeafProofIndex {
|
||||||
|
hashes = append(hashes, h)
|
||||||
|
}
|
||||||
|
return hashes
|
||||||
|
}
|
||||||
11
common/utils.go
Normal file
11
common/utils.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func P2TRScript(taprootKey *secp256k1.PublicKey) ([]byte, error) {
|
||||||
|
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(schnorr.SerializePubKey(taprootKey)).Script()
|
||||||
|
}
|
||||||
62
common/vtxo.go
Normal file
62
common/vtxo.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrWrongDescriptor = errors.New("wrong descriptor, cannot parse vtxo script")
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaprootMerkleProof struct {
|
||||||
|
ControlBlock []byte
|
||||||
|
Script []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaprootTree is an interface wrapping the methods needed to spend a vtxo taproot contract
|
||||||
|
// the implementation depends on the chain (liquid or bitcoin)
|
||||||
|
type TaprootTree interface {
|
||||||
|
GetLeaves() []chainhash.Hash
|
||||||
|
GetTaprootMerkleProof(leafhash chainhash.Hash) (*TaprootMerkleProof, error)
|
||||||
|
GetRoot() chainhash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
A vtxo script is defined as a taproot contract with at least 1 forfeit closure (User && ASP) and 1 exit closure (A after t).
|
||||||
|
It may also contain others closures implementing specific use cases.
|
||||||
|
|
||||||
|
VtxoScript abstracts the taproot complexity behind vtxo contracts.
|
||||||
|
it is compiled, transferred and parsed using descriptor string.
|
||||||
|
|
||||||
|
default vtxo script = tr(_,{ and(pk(USER), pk(ASP)), and(older(T), pk(USER)) })
|
||||||
|
reversible vtxo script = tr(_,{ { and(pk(SENDER), pk(ASP)), and(older(T), pk(SENDER)) }, { and(pk(RECEIVER), pk(ASP) } })
|
||||||
|
*/
|
||||||
|
type VtxoScript[T TaprootTree] interface {
|
||||||
|
TapTree() (taprootKey *secp256k1.PublicKey, taprootScriptTree T, err error)
|
||||||
|
ToDescriptor() string
|
||||||
|
FromDescriptor(descriptor string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BiggestLeafMerkleProof returns the leaf with the biggest witness size (for fee estimation)
|
||||||
|
// we need this to estimate the fee without knowning the exact leaf that will be spent
|
||||||
|
func BiggestLeafMerkleProof(t TaprootTree) (*TaprootMerkleProof, error) {
|
||||||
|
var biggest *TaprootMerkleProof
|
||||||
|
var biggestSize int
|
||||||
|
|
||||||
|
for _, leaf := range t.GetLeaves() {
|
||||||
|
proof, err := t.GetTaprootMerkleProof(leaf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(proof.ControlBlock)+len(proof.Script) > biggestSize {
|
||||||
|
biggest = proof
|
||||||
|
biggestSize = len(proof.ControlBlock) + len(proof.Script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return biggest, nil
|
||||||
|
}
|
||||||
@@ -418,6 +418,7 @@ github.com/ark-network/ark/common v0.0.0-20240812222508-b097e943fb45/go.mod h1:8
|
|||||||
github.com/ark-network/ark/common v0.0.0-20240812233307-18e343b31899/go.mod h1:8DYeb06Dl8onmrV09xfsdDMGv5HoVtWoKhLBLXOYHew=
|
github.com/ark-network/ark/common v0.0.0-20240812233307-18e343b31899/go.mod h1:8DYeb06Dl8onmrV09xfsdDMGv5HoVtWoKhLBLXOYHew=
|
||||||
github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87/go.mod h1:aYAGDfoeBLofnZt9n85wusFyCkrS7hvwdo5TynBlkuY=
|
github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87/go.mod h1:aYAGDfoeBLofnZt9n85wusFyCkrS7hvwdo5TynBlkuY=
|
||||||
github.com/ark-network/ark/pkg/client-sdk v0.0.0-20240812230256-910716f72d1a/go.mod h1:avKeK73ezowttW3PaycYB4mChaqigAxr4q8pFwIuHww=
|
github.com/ark-network/ark/pkg/client-sdk v0.0.0-20240812230256-910716f72d1a/go.mod h1:avKeK73ezowttW3PaycYB4mChaqigAxr4q8pFwIuHww=
|
||||||
|
github.com/ark-network/ark/pkg/client-sdk v0.0.0-20240913171921-2174e4b04d86/go.mod h1:CRN5aL3u3Q/3tCQLp/ND7NT34/GRsG1ccLk5aX2r7mQ=
|
||||||
github.com/ark-network/ark/server/pkg/kvdb v0.0.0-20240812230256-910716f72d1a/go.mod h1:ivr4Qm16kbJMTovsdscYiV1s1vPOYmEBtp9EgrHFGi4=
|
github.com/ark-network/ark/server/pkg/kvdb v0.0.0-20240812230256-910716f72d1a/go.mod h1:ivr4Qm16kbJMTovsdscYiV1s1vPOYmEBtp9EgrHFGi4=
|
||||||
github.com/ark-network/ark/server/pkg/macaroons v0.0.0-20240812230256-910716f72d1a/go.mod h1:OtZoQaSumPsVKWq/OkduHZdpAutQYaB2yVf1rlm6vI4=
|
github.com/ark-network/ark/server/pkg/macaroons v0.0.0-20240812230256-910716f72d1a/go.mod h1:OtZoQaSumPsVKWq/OkduHZdpAutQYaB2yVf1rlm6vI4=
|
||||||
github.com/ark-network/ark/server/pkg/macaroons v0.0.0-20240812233307-18e343b31899/go.mod h1:OtZoQaSumPsVKWq/OkduHZdpAutQYaB2yVf1rlm6vI4=
|
github.com/ark-network/ark/server/pkg/macaroons v0.0.0-20240812233307-18e343b31899/go.mod h1:OtZoQaSumPsVKWq/OkduHZdpAutQYaB2yVf1rlm6vI4=
|
||||||
@@ -562,6 +563,7 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE
|
|||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
|
github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
|
||||||
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
|
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
|
||||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||||
@@ -1040,6 +1042,7 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -1083,6 +1086,7 @@ golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"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/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -37,7 +38,7 @@ type ASPClient interface {
|
|||||||
ctx context.Context, signedForfeitTxs []string, signedRoundTx string,
|
ctx context.Context, signedForfeitTxs []string, signedRoundTx string,
|
||||||
) error
|
) error
|
||||||
CreatePayment(
|
CreatePayment(
|
||||||
ctx context.Context, inputs []VtxoKey, outputs []Output,
|
ctx context.Context, inputs []Input, outputs []Output,
|
||||||
) (string, []string, error)
|
) (string, []string, error)
|
||||||
CompletePayment(
|
CompletePayment(
|
||||||
ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string,
|
ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string,
|
||||||
@@ -67,40 +68,19 @@ type RoundEventChannel struct {
|
|||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Input interface {
|
type Outpoint struct {
|
||||||
GetTxID() string
|
|
||||||
GetVOut() uint32
|
|
||||||
GetDescriptor() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type VtxoKey struct {
|
|
||||||
Txid string
|
Txid string
|
||||||
VOut uint32
|
VOut uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k VtxoKey) GetTxID() string {
|
type Input struct {
|
||||||
return k.Txid
|
Outpoint
|
||||||
}
|
|
||||||
|
|
||||||
func (k VtxoKey) GetVOut() uint32 {
|
|
||||||
return k.VOut
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k VtxoKey) GetDescriptor() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type BoardingInput struct {
|
|
||||||
VtxoKey
|
|
||||||
Descriptor string
|
Descriptor string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k BoardingInput) GetDescriptor() string {
|
|
||||||
return k.Descriptor
|
|
||||||
}
|
|
||||||
|
|
||||||
type Vtxo struct {
|
type Vtxo struct {
|
||||||
VtxoKey
|
Outpoint
|
||||||
|
Descriptor string
|
||||||
Amount uint64
|
Amount uint64
|
||||||
RoundTxid string
|
RoundTxid string
|
||||||
ExpiresAt *time.Time
|
ExpiresAt *time.Time
|
||||||
@@ -111,7 +91,8 @@ type Vtxo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Output struct {
|
type Output struct {
|
||||||
Address string
|
Address string // onchain output address
|
||||||
|
Descriptor string // offchain vtxo descriptor
|
||||||
Amount uint64
|
Amount uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,9 +135,9 @@ type Round struct {
|
|||||||
type RoundFinalizationEvent struct {
|
type RoundFinalizationEvent struct {
|
||||||
ID string
|
ID string
|
||||||
Tx string
|
Tx string
|
||||||
ForfeitTxs []string
|
|
||||||
Tree tree.CongestionTree
|
Tree tree.CongestionTree
|
||||||
Connectors []string
|
Connectors []string
|
||||||
|
MinRelayFeeRate chainfee.SatPerKVByte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e RoundFinalizationEvent) isRoundEvent() {}
|
func (e RoundFinalizationEvent) isRoundEvent() {}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/ark-network/ark/pkg/client-sdk/client"
|
"github.com/ark-network/ark/pkg/client-sdk/client"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
@@ -202,15 +203,10 @@ func (a *grpcClient) FinalizePayment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *grpcClient) CreatePayment(
|
func (a *grpcClient) CreatePayment(
|
||||||
ctx context.Context, inputs []client.VtxoKey, outputs []client.Output,
|
ctx context.Context, inputs []client.Input, outputs []client.Output,
|
||||||
) (string, []string, error) {
|
) (string, []string, error) {
|
||||||
insCast := make([]client.Input, 0, len(inputs))
|
|
||||||
for _, in := range inputs {
|
|
||||||
insCast = append(insCast, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &arkv1.CreatePaymentRequest{
|
req := &arkv1.CreatePaymentRequest{
|
||||||
Inputs: ins(insCast).toProto(),
|
Inputs: ins(inputs).toProto(),
|
||||||
Outputs: outs(outputs).toProto(),
|
Outputs: outs(outputs).toProto(),
|
||||||
}
|
}
|
||||||
resp, err := a.svc.CreatePayment(ctx, req)
|
resp, err := a.svc.CreatePayment(ctx, req)
|
||||||
@@ -323,8 +319,19 @@ func (a *grpcClient) SendTreeSignatures(
|
|||||||
type out client.Output
|
type out client.Output
|
||||||
|
|
||||||
func (o out) toProto() *arkv1.Output {
|
func (o out) toProto() *arkv1.Output {
|
||||||
|
if len(o.Address) > 0 {
|
||||||
return &arkv1.Output{
|
return &arkv1.Output{
|
||||||
|
Destination: &arkv1.Output_Address{
|
||||||
Address: o.Address,
|
Address: o.Address,
|
||||||
|
},
|
||||||
|
Amount: o.Amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &arkv1.Output{
|
||||||
|
Destination: &arkv1.Output_Descriptor_{
|
||||||
|
Descriptor_: o.Descriptor,
|
||||||
|
},
|
||||||
Amount: o.Amount,
|
Amount: o.Amount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,9 +371,9 @@ func (e event) toRoundEvent() (client.RoundEvent, error) {
|
|||||||
return client.RoundFinalizationEvent{
|
return client.RoundFinalizationEvent{
|
||||||
ID: ee.GetId(),
|
ID: ee.GetId(),
|
||||||
Tx: ee.GetPoolTx(),
|
Tx: ee.GetPoolTx(),
|
||||||
ForfeitTxs: ee.GetForfeitTxs(),
|
|
||||||
Tree: tree,
|
Tree: tree,
|
||||||
Connectors: ee.GetConnectors(),
|
Connectors: ee.GetConnectors(),
|
||||||
|
MinRelayFeeRate: chainfee.SatPerKVByte(ee.MinRelayFeeRate),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,17 +437,18 @@ func (v vtxo) toVtxo() client.Vtxo {
|
|||||||
uncondForfeitTxs = v.GetPendingData().GetUnconditionalForfeitTxs()
|
uncondForfeitTxs = v.GetPendingData().GetUnconditionalForfeitTxs()
|
||||||
}
|
}
|
||||||
return client.Vtxo{
|
return client.Vtxo{
|
||||||
VtxoKey: client.VtxoKey{
|
Outpoint: client.Outpoint{
|
||||||
Txid: v.GetOutpoint().GetVtxoInput().GetTxid(),
|
Txid: v.GetOutpoint().GetTxid(),
|
||||||
VOut: v.GetOutpoint().GetVtxoInput().GetVout(),
|
VOut: v.GetOutpoint().GetVout(),
|
||||||
},
|
},
|
||||||
Amount: v.GetReceiver().GetAmount(),
|
Amount: v.GetAmount(),
|
||||||
RoundTxid: v.GetPoolTxid(),
|
RoundTxid: v.GetPoolTxid(),
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
Pending: v.GetPending(),
|
Pending: v.GetPending(),
|
||||||
RedeemTx: redeemTx,
|
RedeemTx: redeemTx,
|
||||||
UnconditionalForfeitTxs: uncondForfeitTxs,
|
UnconditionalForfeitTxs: uncondForfeitTxs,
|
||||||
SpentBy: v.GetSpentBy(),
|
SpentBy: v.GetSpentBy(),
|
||||||
|
Descriptor: v.GetDescriptor_(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,25 +463,12 @@ func (v vtxos) toVtxos() []client.Vtxo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func toProtoInput(i client.Input) *arkv1.Input {
|
func toProtoInput(i client.Input) *arkv1.Input {
|
||||||
if len(i.GetDescriptor()) > 0 {
|
|
||||||
return &arkv1.Input{
|
return &arkv1.Input{
|
||||||
Input: &arkv1.Input_BoardingInput{
|
Outpoint: &arkv1.Outpoint{
|
||||||
BoardingInput: &arkv1.BoardingInput{
|
Txid: i.Txid,
|
||||||
Txid: i.GetTxID(),
|
Vout: i.VOut,
|
||||||
Vout: i.GetVOut(),
|
|
||||||
Descriptor_: i.GetDescriptor(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &arkv1.Input{
|
|
||||||
Input: &arkv1.Input_VtxoInput{
|
|
||||||
VtxoInput: &arkv1.VtxoInput{
|
|
||||||
Txid: i.GetTxID(),
|
|
||||||
Vout: i.GetVOut(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
Descriptor_: i.Descriptor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
httptransport "github.com/go-openapi/runtime/client"
|
httptransport "github.com/go-openapi/runtime/client"
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
)
|
)
|
||||||
|
|
||||||
type restClient struct {
|
type restClient struct {
|
||||||
@@ -146,7 +147,7 @@ func (a *restClient) ListVtxos(
|
|||||||
expiresAt = &t
|
expiresAt = &t
|
||||||
}
|
}
|
||||||
|
|
||||||
amount, err := strconv.Atoi(v.Receiver.Amount)
|
amount, err := strconv.Atoi(v.Amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -159,9 +160,9 @@ func (a *restClient) ListVtxos(
|
|||||||
}
|
}
|
||||||
|
|
||||||
spendableVtxos = append(spendableVtxos, client.Vtxo{
|
spendableVtxos = append(spendableVtxos, client.Vtxo{
|
||||||
VtxoKey: client.VtxoKey{
|
Outpoint: client.Outpoint{
|
||||||
Txid: v.Outpoint.VtxoInput.Txid,
|
Txid: v.Outpoint.Txid,
|
||||||
VOut: uint32(v.Outpoint.VtxoInput.Vout),
|
VOut: uint32(v.Outpoint.Vout),
|
||||||
},
|
},
|
||||||
Amount: uint64(amount),
|
Amount: uint64(amount),
|
||||||
RoundTxid: v.PoolTxid,
|
RoundTxid: v.PoolTxid,
|
||||||
@@ -170,6 +171,7 @@ func (a *restClient) ListVtxos(
|
|||||||
RedeemTx: redeemTx,
|
RedeemTx: redeemTx,
|
||||||
UnconditionalForfeitTxs: uncondForfeitTxs,
|
UnconditionalForfeitTxs: uncondForfeitTxs,
|
||||||
SpentBy: v.SpentBy,
|
SpentBy: v.SpentBy,
|
||||||
|
Descriptor: v.Descriptor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,20 +187,21 @@ func (a *restClient) ListVtxos(
|
|||||||
expiresAt = &t
|
expiresAt = &t
|
||||||
}
|
}
|
||||||
|
|
||||||
amount, err := strconv.Atoi(v.Receiver.Amount)
|
amount, err := strconv.Atoi(v.Amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
spentVtxos = append(spentVtxos, client.Vtxo{
|
spentVtxos = append(spentVtxos, client.Vtxo{
|
||||||
VtxoKey: client.VtxoKey{
|
Outpoint: client.Outpoint{
|
||||||
Txid: v.Outpoint.VtxoInput.Txid,
|
Txid: v.Outpoint.Txid,
|
||||||
VOut: uint32(v.Outpoint.VtxoInput.Vout),
|
VOut: uint32(v.Outpoint.Vout),
|
||||||
},
|
},
|
||||||
Amount: uint64(amount),
|
Amount: uint64(amount),
|
||||||
RoundTxid: v.PoolTxid,
|
RoundTxid: v.PoolTxid,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
SpentBy: v.SpentBy,
|
SpentBy: v.SpentBy,
|
||||||
|
Descriptor: v.Descriptor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,26 +252,13 @@ func (a *restClient) RegisterPayment(
|
|||||||
) (string, error) {
|
) (string, error) {
|
||||||
ins := make([]*models.V1Input, 0, len(inputs))
|
ins := make([]*models.V1Input, 0, len(inputs))
|
||||||
for _, i := range inputs {
|
for _, i := range inputs {
|
||||||
var input *models.V1Input
|
ins = append(ins, &models.V1Input{
|
||||||
|
Outpoint: &models.V1Outpoint{
|
||||||
if len(i.GetDescriptor()) > 0 {
|
Txid: i.Txid,
|
||||||
input = &models.V1Input{
|
Vout: int64(i.VOut),
|
||||||
BoardingInput: &models.V1BoardingInput{
|
|
||||||
Txid: i.GetTxID(),
|
|
||||||
Vout: int64(i.GetVOut()),
|
|
||||||
Descriptor: i.GetDescriptor(),
|
|
||||||
},
|
},
|
||||||
}
|
Descriptor: i.Descriptor,
|
||||||
} else {
|
})
|
||||||
input = &models.V1Input{
|
|
||||||
VtxoInput: &models.V1VtxoInput{
|
|
||||||
Txid: i.GetTxID(),
|
|
||||||
Vout: int64(i.GetVOut()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ins = append(ins, input)
|
|
||||||
}
|
}
|
||||||
body := &models.V1RegisterPaymentRequest{
|
body := &models.V1RegisterPaymentRequest{
|
||||||
Inputs: ins,
|
Inputs: ins,
|
||||||
@@ -328,12 +318,18 @@ func (a *restClient) Ping(
|
|||||||
}
|
}
|
||||||
if e := payload.RoundFinalization; e != nil {
|
if e := payload.RoundFinalization; e != nil {
|
||||||
tree := treeFromProto{e.CongestionTree}.parse()
|
tree := treeFromProto{e.CongestionTree}.parse()
|
||||||
|
|
||||||
|
minRelayFeeRate, err := strconv.Atoi(e.MinRelayFeeRate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return client.RoundFinalizationEvent{
|
return client.RoundFinalizationEvent{
|
||||||
ID: e.ID,
|
ID: e.ID,
|
||||||
Tx: e.PoolTx,
|
Tx: e.PoolTx,
|
||||||
ForfeitTxs: e.ForfeitTxs,
|
|
||||||
Tree: tree,
|
Tree: tree,
|
||||||
Connectors: e.Connectors,
|
Connectors: e.Connectors,
|
||||||
|
MinRelayFeeRate: chainfee.SatPerKVByte(minRelayFeeRate),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,19 +390,16 @@ func (a *restClient) FinalizePayment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *restClient) CreatePayment(
|
func (a *restClient) CreatePayment(
|
||||||
ctx context.Context, inputs []client.VtxoKey, outputs []client.Output,
|
ctx context.Context, inputs []client.Input, outputs []client.Output,
|
||||||
) (string, []string, error) {
|
) (string, []string, error) {
|
||||||
ins := make([]*models.V1Input, 0, len(inputs))
|
ins := make([]*models.V1Input, 0, len(inputs))
|
||||||
for _, i := range inputs {
|
for _, i := range inputs {
|
||||||
if len(i.GetDescriptor()) > 0 {
|
|
||||||
return "", nil, fmt.Errorf("boarding inputs are not allowed in create payment")
|
|
||||||
}
|
|
||||||
|
|
||||||
ins = append(ins, &models.V1Input{
|
ins = append(ins, &models.V1Input{
|
||||||
VtxoInput: &models.V1VtxoInput{
|
Outpoint: &models.V1Outpoint{
|
||||||
Txid: i.Txid,
|
Txid: i.Txid,
|
||||||
Vout: int64(i.VOut),
|
Vout: int64(i.VOut),
|
||||||
},
|
},
|
||||||
|
Descriptor: i.Descriptor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
outs := make([]*models.V1Output, 0, len(outputs))
|
outs := make([]*models.V1Output, 0, len(outputs))
|
||||||
@@ -414,6 +407,7 @@ 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{
|
||||||
|
|||||||
@@ -18,22 +18,18 @@ import (
|
|||||||
// swagger:model v1Input
|
// swagger:model v1Input
|
||||||
type V1Input struct {
|
type V1Input struct {
|
||||||
|
|
||||||
// boarding input
|
// descriptor
|
||||||
BoardingInput *V1BoardingInput `json:"boardingInput,omitempty"`
|
Descriptor string `json:"descriptor,omitempty"`
|
||||||
|
|
||||||
// vtxo input
|
// outpoint
|
||||||
VtxoInput *V1VtxoInput `json:"vtxoInput,omitempty"`
|
Outpoint *V1Outpoint `json:"outpoint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates this v1 input
|
// Validate validates this v1 input
|
||||||
func (m *V1Input) Validate(formats strfmt.Registry) error {
|
func (m *V1Input) Validate(formats strfmt.Registry) error {
|
||||||
var res []error
|
var res []error
|
||||||
|
|
||||||
if err := m.validateBoardingInput(formats); err != nil {
|
if err := m.validateOutpoint(formats); err != nil {
|
||||||
res = append(res, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.validateVtxoInput(formats); err != nil {
|
|
||||||
res = append(res, err)
|
res = append(res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,36 +39,17 @@ func (m *V1Input) Validate(formats strfmt.Registry) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *V1Input) validateBoardingInput(formats strfmt.Registry) error {
|
func (m *V1Input) validateOutpoint(formats strfmt.Registry) error {
|
||||||
if swag.IsZero(m.BoardingInput) { // not required
|
if swag.IsZero(m.Outpoint) { // not required
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.BoardingInput != nil {
|
if m.Outpoint != nil {
|
||||||
if err := m.BoardingInput.Validate(formats); err != nil {
|
if err := m.Outpoint.Validate(formats); err != nil {
|
||||||
if ve, ok := err.(*errors.Validation); ok {
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
return ve.ValidateName("boardingInput")
|
return ve.ValidateName("outpoint")
|
||||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
return ce.ValidateName("boardingInput")
|
return ce.ValidateName("outpoint")
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *V1Input) validateVtxoInput(formats strfmt.Registry) error {
|
|
||||||
if swag.IsZero(m.VtxoInput) { // not required
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.VtxoInput != nil {
|
|
||||||
if err := m.VtxoInput.Validate(formats); err != nil {
|
|
||||||
if ve, ok := err.(*errors.Validation); ok {
|
|
||||||
return ve.ValidateName("vtxoInput")
|
|
||||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
|
||||||
return ce.ValidateName("vtxoInput")
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -85,11 +62,7 @@ func (m *V1Input) validateVtxoInput(formats strfmt.Registry) error {
|
|||||||
func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
var res []error
|
var res []error
|
||||||
|
|
||||||
if err := m.contextValidateBoardingInput(ctx, formats); err != nil {
|
if err := m.contextValidateOutpoint(ctx, formats); err != nil {
|
||||||
res = append(res, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.contextValidateVtxoInput(ctx, formats); err != nil {
|
|
||||||
res = append(res, err)
|
res = append(res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,40 +72,19 @@ func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *V1Input) contextValidateBoardingInput(ctx context.Context, formats strfmt.Registry) error {
|
func (m *V1Input) contextValidateOutpoint(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
|
||||||
if m.BoardingInput != nil {
|
if m.Outpoint != nil {
|
||||||
|
|
||||||
if swag.IsZero(m.BoardingInput) { // not required
|
if swag.IsZero(m.Outpoint) { // not required
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.BoardingInput.ContextValidate(ctx, formats); err != nil {
|
if err := m.Outpoint.ContextValidate(ctx, formats); err != nil {
|
||||||
if ve, ok := err.(*errors.Validation); ok {
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
return ve.ValidateName("boardingInput")
|
return ve.ValidateName("outpoint")
|
||||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
return ce.ValidateName("boardingInput")
|
return ce.ValidateName("outpoint")
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *V1Input) contextValidateVtxoInput(ctx context.Context, formats strfmt.Registry) error {
|
|
||||||
|
|
||||||
if m.VtxoInput != nil {
|
|
||||||
|
|
||||||
if swag.IsZero(m.VtxoInput) { // not required
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.VtxoInput.ContextValidate(ctx, formats); err != nil {
|
|
||||||
if ve, ok := err.(*errors.Validation); ok {
|
|
||||||
return ve.ValidateName("vtxoInput")
|
|
||||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
|
||||||
return ce.ValidateName("vtxoInput")
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
53
pkg/client-sdk/client/rest/service/models/v1_outpoint.go
Normal file
53
pkg/client-sdk/client/rest/service/models/v1_outpoint.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// 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/strfmt"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// V1Outpoint v1 outpoint
|
||||||
|
//
|
||||||
|
// swagger:model v1Outpoint
|
||||||
|
type V1Outpoint struct {
|
||||||
|
|
||||||
|
// txid
|
||||||
|
Txid string `json:"txid,omitempty"`
|
||||||
|
|
||||||
|
// vout
|
||||||
|
Vout int64 `json:"vout,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates this v1 outpoint
|
||||||
|
func (m *V1Outpoint) Validate(formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validates this v1 outpoint based on context it is used
|
||||||
|
func (m *V1Outpoint) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary interface implementation
|
||||||
|
func (m *V1Outpoint) MarshalBinary() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return swag.WriteJSON(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary interface implementation
|
||||||
|
func (m *V1Outpoint) UnmarshalBinary(b []byte) error {
|
||||||
|
var res V1Outpoint
|
||||||
|
if err := swag.ReadJSON(b, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = res
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -17,11 +17,14 @@ import (
|
|||||||
// swagger:model v1Output
|
// swagger:model v1Output
|
||||||
type V1Output struct {
|
type V1Output struct {
|
||||||
|
|
||||||
// Either the offchain or onchain address.
|
// onchain
|
||||||
Address string `json:"address,omitempty"`
|
Address string `json:"address,omitempty"`
|
||||||
|
|
||||||
// Amount to send in satoshis.
|
// Amount to send in satoshis.
|
||||||
Amount string `json:"amount,omitempty"`
|
Amount string `json:"amount,omitempty"`
|
||||||
|
|
||||||
|
// offchain
|
||||||
|
Descriptor string `json:"descriptor,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates this v1 output
|
// Validate validates this v1 output
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ type V1RoundFinalizationEvent struct {
|
|||||||
// connectors
|
// connectors
|
||||||
Connectors []string `json:"connectors"`
|
Connectors []string `json:"connectors"`
|
||||||
|
|
||||||
// forfeit txs
|
|
||||||
ForfeitTxs []string `json:"forfeitTxs"`
|
|
||||||
|
|
||||||
// id
|
// id
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
|
|
||||||
|
// min relay fee rate
|
||||||
|
MinRelayFeeRate string `json:"minRelayFeeRate,omitempty"`
|
||||||
|
|
||||||
// pool tx
|
// pool tx
|
||||||
PoolTx string `json:"poolTx,omitempty"`
|
PoolTx string `json:"poolTx,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,17 @@ import (
|
|||||||
// swagger:model v1Vtxo
|
// swagger:model v1Vtxo
|
||||||
type V1Vtxo struct {
|
type V1Vtxo struct {
|
||||||
|
|
||||||
|
// amount
|
||||||
|
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"`
|
||||||
|
|
||||||
// outpoint
|
// outpoint
|
||||||
Outpoint *V1Input `json:"outpoint,omitempty"`
|
Outpoint *V1Outpoint `json:"outpoint,omitempty"`
|
||||||
|
|
||||||
// pending
|
// pending
|
||||||
Pending bool `json:"pending,omitempty"`
|
Pending bool `json:"pending,omitempty"`
|
||||||
@@ -33,9 +39,6 @@ type V1Vtxo struct {
|
|||||||
// pool txid
|
// pool txid
|
||||||
PoolTxid string `json:"poolTxid,omitempty"`
|
PoolTxid string `json:"poolTxid,omitempty"`
|
||||||
|
|
||||||
// receiver
|
|
||||||
Receiver *V1Output `json:"receiver,omitempty"`
|
|
||||||
|
|
||||||
// spent
|
// spent
|
||||||
Spent bool `json:"spent,omitempty"`
|
Spent bool `json:"spent,omitempty"`
|
||||||
|
|
||||||
@@ -58,10 +61,6 @@ func (m *V1Vtxo) Validate(formats strfmt.Registry) error {
|
|||||||
res = append(res, err)
|
res = append(res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.validateReceiver(formats); err != nil {
|
|
||||||
res = append(res, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res) > 0 {
|
if len(res) > 0 {
|
||||||
return errors.CompositeValidationError(res...)
|
return errors.CompositeValidationError(res...)
|
||||||
}
|
}
|
||||||
@@ -106,25 +105,6 @@ func (m *V1Vtxo) validatePendingData(formats strfmt.Registry) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *V1Vtxo) validateReceiver(formats strfmt.Registry) error {
|
|
||||||
if swag.IsZero(m.Receiver) { // not required
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Receiver != nil {
|
|
||||||
if err := m.Receiver.Validate(formats); err != nil {
|
|
||||||
if ve, ok := err.(*errors.Validation); ok {
|
|
||||||
return ve.ValidateName("receiver")
|
|
||||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
|
||||||
return ce.ValidateName("receiver")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContextValidate validate this v1 vtxo based on the context it is used
|
// ContextValidate validate this v1 vtxo based on the context it is used
|
||||||
func (m *V1Vtxo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
func (m *V1Vtxo) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
var res []error
|
var res []error
|
||||||
@@ -137,10 +117,6 @@ func (m *V1Vtxo) ContextValidate(ctx context.Context, formats strfmt.Registry) e
|
|||||||
res = append(res, err)
|
res = append(res, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.contextValidateReceiver(ctx, formats); err != nil {
|
|
||||||
res = append(res, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res) > 0 {
|
if len(res) > 0 {
|
||||||
return errors.CompositeValidationError(res...)
|
return errors.CompositeValidationError(res...)
|
||||||
}
|
}
|
||||||
@@ -189,27 +165,6 @@ func (m *V1Vtxo) contextValidatePendingData(ctx context.Context, formats strfmt.
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *V1Vtxo) contextValidateReceiver(ctx context.Context, formats strfmt.Registry) error {
|
|
||||||
|
|
||||||
if m.Receiver != nil {
|
|
||||||
|
|
||||||
if swag.IsZero(m.Receiver) { // not required
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.Receiver.ContextValidate(ctx, formats); err != nil {
|
|
||||||
if ve, ok := err.(*errors.Validation); ok {
|
|
||||||
return ve.ValidateName("receiver")
|
|
||||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
|
||||||
return ce.ValidateName("receiver")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary interface implementation
|
// MarshalBinary interface implementation
|
||||||
func (m *V1Vtxo) MarshalBinary() ([]byte, error) {
|
func (m *V1Vtxo) MarshalBinary() ([]byte, error) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ func loadFixtures(jsonStr string) (vtxos, []Transaction, error) {
|
|||||||
return vtxos{}, nil, err
|
return vtxos{}, nil, err
|
||||||
}
|
}
|
||||||
spendable[i] = client.Vtxo{
|
spendable[i] = client.Vtxo{
|
||||||
VtxoKey: client.VtxoKey{
|
Outpoint: client.Outpoint{
|
||||||
Txid: vtxo.Outpoint.Txid,
|
Txid: vtxo.Outpoint.Txid,
|
||||||
VOut: vtxo.Outpoint.Vout,
|
VOut: vtxo.Outpoint.Vout,
|
||||||
},
|
},
|
||||||
@@ -251,7 +251,7 @@ func loadFixtures(jsonStr string) (vtxos, []Transaction, error) {
|
|||||||
return vtxos{}, nil, err
|
return vtxos{}, nil, err
|
||||||
}
|
}
|
||||||
spent[i] = client.Vtxo{
|
spent[i] = client.Vtxo{
|
||||||
VtxoKey: client.VtxoKey{
|
Outpoint: client.Outpoint{
|
||||||
Txid: vtxo.Outpoint.Txid,
|
Txid: vtxo.Outpoint.Txid,
|
||||||
VOut: vtxo.Outpoint.Vout,
|
VOut: vtxo.Outpoint.Vout,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
"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/pkg/client-sdk/client"
|
"github.com/ark-network/ark/pkg/client-sdk/client"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
||||||
@@ -23,9 +22,11 @@ import (
|
|||||||
"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/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/vulpemventures/go-elements/address"
|
"github.com/vulpemventures/go-elements/address"
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
|
"github.com/vulpemventures/go-elements/taproot"
|
||||||
)
|
)
|
||||||
|
|
||||||
type liquidReceiver struct {
|
type liquidReceiver struct {
|
||||||
@@ -435,8 +436,14 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
desc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
receivers = append(receivers, client.Output{
|
receivers = append(receivers, client.Output{
|
||||||
Address: offchainAddr,
|
Descriptor: desc,
|
||||||
Amount: changeAmount,
|
Amount: changeAmount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -444,9 +451,12 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
|||||||
inputs := make([]client.Input, 0, len(selectedCoins))
|
inputs := make([]client.Input, 0, len(selectedCoins))
|
||||||
|
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, client.VtxoKey{
|
inputs = append(inputs, client.Input{
|
||||||
|
Outpoint: client.Outpoint{
|
||||||
Txid: coin.Txid,
|
Txid: coin.Txid,
|
||||||
VOut: coin.VOut,
|
VOut: coin.VOut,
|
||||||
|
},
|
||||||
|
Descriptor: coin.Descriptor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,7 +470,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
poolTxID, err := a.handleRoundStream(
|
poolTxID, err := a.handleRoundStream(
|
||||||
ctx, paymentID, selectedCoins, false, receivers,
|
ctx, paymentID, selectedCoins, nil, "", receivers,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -500,14 +510,22 @@ 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{
|
||||||
Address: myselfOffchain,
|
Descriptor: desc,
|
||||||
Amount: pendingBalance,
|
Amount: pendingBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
desc := strings.ReplaceAll(a.BoardingDescriptorTemplate, "USER", hex.EncodeToString(schnorr.SerializePubKey(mypubkey)))
|
return a.selfTransferAllPendingPayments(
|
||||||
|
ctx,
|
||||||
return a.selfTransferAllPendingPayments(ctx, boardingUtxos, receiver, desc)
|
boardingUtxos,
|
||||||
|
receiver,
|
||||||
|
hex.EncodeToString(mypubkey.SerializeCompressed()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) GetTransactionHistory(ctx context.Context) ([]Transaction, error) {
|
func (a *covenantArkClient) GetTransactionHistory(ctx context.Context) ([]Transaction, error) {
|
||||||
@@ -577,14 +595,18 @@ func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]ex
|
|||||||
descriptorStr := strings.ReplaceAll(
|
descriptorStr := strings.ReplaceAll(
|
||||||
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
)
|
)
|
||||||
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
|
||||||
|
boardingScript, err := tree.ParseVtxoScript(descriptorStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
var boardingTimeout uint
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok {
|
||||||
|
boardingTimeout = defaultVtxo.ExitDelay
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("unsupported boarding descriptor: %s", descriptorStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, addr := range boardingAddrs {
|
for _, addr := range boardingAddrs {
|
||||||
@@ -800,8 +822,13 @@ func (a *covenantArkClient) sendOffchain(
|
|||||||
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{
|
||||||
Address: receiver.To(),
|
Descriptor: desc,
|
||||||
Amount: receiver.Amount(),
|
Amount: receiver.Amount(),
|
||||||
})
|
})
|
||||||
sumOfReceivers += receiver.Amount()
|
sumOfReceivers += receiver.Amount()
|
||||||
@@ -828,8 +855,14 @@ func (a *covenantArkClient) 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{
|
||||||
Address: offchainAddr,
|
Descriptor: desc,
|
||||||
Amount: changeAmount,
|
Amount: changeAmount,
|
||||||
}
|
}
|
||||||
receiversOutput = append(receiversOutput, changeReceiver)
|
receiversOutput = append(receiversOutput, changeReceiver)
|
||||||
@@ -837,9 +870,12 @@ func (a *covenantArkClient) sendOffchain(
|
|||||||
|
|
||||||
inputs := make([]client.Input, 0, len(selectedCoins))
|
inputs := make([]client.Input, 0, len(selectedCoins))
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, client.VtxoKey{
|
inputs = append(inputs, client.Input{
|
||||||
|
Outpoint: client.Outpoint{
|
||||||
Txid: coin.Txid,
|
Txid: coin.Txid,
|
||||||
VOut: coin.VOut,
|
VOut: coin.VOut,
|
||||||
|
},
|
||||||
|
Descriptor: coin.Descriptor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -859,7 +895,7 @@ func (a *covenantArkClient) sendOffchain(
|
|||||||
log.Infof("payment registered with id: %s", paymentID)
|
log.Infof("payment registered with id: %s", paymentID)
|
||||||
|
|
||||||
poolTxID, err := a.handleRoundStream(
|
poolTxID, err := a.handleRoundStream(
|
||||||
ctx, paymentID, selectedCoins, false, receiversOutput,
|
ctx, paymentID, selectedCoins, nil, "", receiversOutput,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -900,16 +936,46 @@ func (a *covenantArkClient) addInputs(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, leafProof, _, _, err := tree.ComputeVtxoTaprootScript(
|
vtxoScript := &tree.DefaultVtxoScript{
|
||||||
userPubkey, aspPubkey, utxo.Delay, utils.ToElementsNetwork(a.Network),
|
Owner: userPubkey,
|
||||||
)
|
Asp: aspPubkey,
|
||||||
|
ExitDelay: utxo.Delay,
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitClosure := &tree.MultisigClosure{
|
||||||
|
Pubkey: userPubkey,
|
||||||
|
AspPubkey: aspPubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, taprootTree, err := vtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
leafProof, err := taprootTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
controlBlock, err := taproot.ParseControlBlock(leafProof.Script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
inputIndex := len(updater.Pset.Inputs) - 1
|
inputIndex := len(updater.Pset.Inputs) - 1
|
||||||
|
|
||||||
if err := updater.AddInTapLeafScript(inputIndex, psetv2.NewTapLeafScript(*leafProof, tree.UnspendableKey())); err != nil {
|
if err := updater.AddInTapLeafScript(
|
||||||
|
inputIndex,
|
||||||
|
psetv2.TapLeafScript{
|
||||||
|
TapElementsLeaf: taproot.NewBaseTapElementsLeaf(leafProof.Script),
|
||||||
|
ControlBlock: *controlBlock,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -921,7 +987,8 @@ func (a *covenantArkClient) handleRoundStream(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
paymentID string,
|
paymentID string,
|
||||||
vtxosToSign []client.Vtxo,
|
vtxosToSign []client.Vtxo,
|
||||||
mustSignRoundTx bool,
|
boardingUtxos []explorer.Utxo,
|
||||||
|
boardingDescriptor string,
|
||||||
receivers []client.Output,
|
receivers []client.Output,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
eventsCh, err := a.client.GetEventStream(ctx, paymentID)
|
eventsCh, err := a.client.GetEventStream(ctx, paymentID)
|
||||||
@@ -955,7 +1022,7 @@ func (a *covenantArkClient) handleRoundStream(
|
|||||||
log.Info("a round finalization started")
|
log.Info("a round finalization started")
|
||||||
|
|
||||||
signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization(
|
signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization(
|
||||||
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, mustSignRoundTx, receivers,
|
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, boardingDescriptor, receivers,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -979,30 +1046,104 @@ func (a *covenantArkClient) handleRoundStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) handleRoundFinalization(
|
func (a *covenantArkClient) handleRoundFinalization(
|
||||||
ctx context.Context, event client.RoundFinalizationEvent,
|
ctx context.Context,
|
||||||
vtxos []client.Vtxo, mustSignRoundTx bool, receivers []client.Output,
|
event client.RoundFinalizationEvent,
|
||||||
|
vtxos []client.Vtxo,
|
||||||
|
boardingUtxos []explorer.Utxo,
|
||||||
|
boardingDescriptor string,
|
||||||
|
receivers []client.Output,
|
||||||
) (signedForfeits []string, signedRoundTx string, err error) {
|
) (signedForfeits []string, signedRoundTx string, err error) {
|
||||||
if err = a.validateCongestionTree(event, receivers); err != nil {
|
if err = a.validateCongestionTree(event, receivers); err != nil {
|
||||||
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.loopAndSign(
|
signedForfeits, err = a.createAndSignForfeits(ctx, vtxos, event.Connectors, event.MinRelayFeeRate, myPubkey)
|
||||||
ctx, event.ForfeitTxs, vtxos, event.Connectors,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mustSignRoundTx {
|
if len(boardingUtxos) > 0 {
|
||||||
signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, event.Tx)
|
boardingVtxoScript, err := tree.ParseVtxoScript(boardingDescriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
roundPtx, err := psetv2.NewPsetFromBase64(event.Tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add tapscript leaf
|
||||||
|
forfeitClosure := &tree.MultisigClosure{
|
||||||
|
Pubkey: myPubkey,
|
||||||
|
AspPubkey: a.AspPubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, taprootTree, err := boardingVtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitProof, err := taprootTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrlBlock, err := taproot.ParseControlBlock(forfeitProof.ControlBlock)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tapscript := psetv2.TapLeafScript{
|
||||||
|
TapElementsLeaf: taproot.NewBaseTapElementsLeaf(forfeitProof.Script),
|
||||||
|
ControlBlock: *ctrlBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
updater, err := psetv2.NewUpdater(roundPtx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, input := range updater.Pset.Inputs {
|
||||||
|
for _, boardingUtxo := range boardingUtxos {
|
||||||
|
if chainhash.Hash(input.PreviousTxid).String() == boardingUtxo.Txid && boardingUtxo.Vout == input.PreviousTxIndex {
|
||||||
|
if err := updater.AddInTapLeafScript(i, tapscript); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
b64, err := updater.Pset.ToBase64()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, b64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedForfeits, signedRoundTx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) validateCongestionTree(
|
func (a *covenantArkClient) validateCongestionTree(
|
||||||
@@ -1016,7 +1157,7 @@ func (a *covenantArkClient) validateCongestionTree(
|
|||||||
|
|
||||||
connectors := event.Connectors
|
connectors := event.Connectors
|
||||||
|
|
||||||
if !utils.IsLiquidOnchainOnly(receivers) {
|
if !utils.IsOnchainOnly(receivers) {
|
||||||
if err := tree.ValidateCongestionTree(
|
if err := tree.ValidateCongestionTree(
|
||||||
event.Tree, poolTx, a.StoreData.AspPubkey, a.RoundLifetime,
|
event.Tree, poolTx, a.StoreData.AspPubkey, a.RoundLifetime,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -1029,7 +1170,7 @@ func (a *covenantArkClient) validateCongestionTree(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := a.validateReceivers(
|
if err := a.validateReceivers(
|
||||||
ptx, receivers, event.Tree, a.StoreData.AspPubkey,
|
ptx, receivers, event.Tree,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1043,14 +1184,13 @@ func (a *covenantArkClient) validateReceivers(
|
|||||||
ptx *psetv2.Pset,
|
ptx *psetv2.Pset,
|
||||||
receivers []client.Output,
|
receivers []client.Output,
|
||||||
congestionTree tree.CongestionTree,
|
congestionTree tree.CongestionTree,
|
||||||
aspPubkey *secp256k1.PublicKey,
|
|
||||||
) error {
|
) error {
|
||||||
for _, receiver := range receivers {
|
for _, receiver := range receivers {
|
||||||
isOnChain, onchainScript, userPubkey, err := utils.ParseLiquidAddress(
|
isOnChain, onchainScript, err := utils.ParseLiquidAddress(
|
||||||
receiver.Address,
|
receiver.Address,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("invalid receiver address: %s err = %s", receiver.Address, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isOnChain {
|
if isOnChain {
|
||||||
@@ -1059,7 +1199,7 @@ func (a *covenantArkClient) validateReceivers(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := a.validateOffChainReceiver(
|
if err := a.validateOffChainReceiver(
|
||||||
congestionTree, receiver, userPubkey, aspPubkey,
|
congestionTree, receiver,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1095,13 +1235,15 @@ func (a *covenantArkClient) validateOnChainReceiver(
|
|||||||
func (a *covenantArkClient) validateOffChainReceiver(
|
func (a *covenantArkClient) validateOffChainReceiver(
|
||||||
congestionTree tree.CongestionTree,
|
congestionTree tree.CongestionTree,
|
||||||
receiver client.Output,
|
receiver client.Output,
|
||||||
userPubkey, aspPubkey *secp256k1.PublicKey,
|
|
||||||
) error {
|
) error {
|
||||||
found := false
|
found := false
|
||||||
net := utils.ToElementsNetwork(a.Network)
|
|
||||||
outputTapKey, _, _, _, err := tree.ComputeVtxoTaprootScript(
|
receiverVtxoScript, err := tree.ParseVtxoScript(receiver.Descriptor)
|
||||||
userPubkey, aspPubkey, uint(a.UnilateralExitDelay), net,
|
if err != nil {
|
||||||
)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
outputTapKey, _, err := receiverVtxoScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1136,36 +1278,105 @@ func (a *covenantArkClient) validateOffChainReceiver(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) loopAndSign(
|
func (a *covenantArkClient) createAndSignForfeits(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
forfeitTxs []string, vtxosToSign []client.Vtxo, connectors []string,
|
vtxosToSign []client.Vtxo,
|
||||||
|
connectors []string,
|
||||||
|
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))
|
||||||
|
|
||||||
connectorsTxids := make([]string, 0, len(connectors))
|
|
||||||
for _, connector := range connectors {
|
for _, connector := range connectors {
|
||||||
p, _ := psetv2.NewPsetFromBase64(connector)
|
p, err := psetv2.NewPsetFromBase64(connector)
|
||||||
utx, _ := p.UnsignedTx()
|
|
||||||
txid := utx.TxHash().String()
|
|
||||||
connectorsTxids = append(connectorsTxids, txid)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, forfeitTx := range forfeitTxs {
|
|
||||||
pset, err := psetv2.NewPsetFromBase64(forfeitTx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, input := range pset.Inputs {
|
connectorsPsets = append(connectorsPsets, p)
|
||||||
inputTxid := chainhash.Hash(input.PreviousTxid).String()
|
}
|
||||||
for _, coin := range vtxosToSign {
|
|
||||||
if inputTxid == coin.Txid {
|
for _, vtxo := range vtxosToSign {
|
||||||
signedPset, err := a.signForfeitTx(ctx, forfeitTx, pset, connectorsTxids)
|
vtxoScript, err := tree.ParseVtxoScript(vtxo.Descriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
signedForfeits = append(signedForfeits, signedPset)
|
|
||||||
|
vtxoTapKey, vtxoTapTree, err := vtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
feeAmount, err := common.ComputeForfeitMinRelayFee(feeRate, vtxoTapTree)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxoOutputScript, err := common.P2TRScript(vtxoTapKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxoInput := psetv2.InputArgs{
|
||||||
|
Txid: vtxo.Txid,
|
||||||
|
TxIndex: vtxo.VOut,
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitClosure := &tree.MultisigClosure{
|
||||||
|
Pubkey: myPubKey,
|
||||||
|
AspPubkey: a.AspPubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
leafProof, err := vtxoTapTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrlBlock, err := taproot.ParseControlBlock(leafProof.ControlBlock)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tapscript := psetv2.TapLeafScript{
|
||||||
|
TapElementsLeaf: taproot.NewBaseTapElementsLeaf(leafProof.Script),
|
||||||
|
ControlBlock: *ctrlBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, connectorPset := range connectorsPsets {
|
||||||
|
forfeits, err := tree.BuildForfeitTxs(
|
||||||
|
connectorPset, vtxoInput, vtxo.Amount, a.Dust, feeAmount, vtxoOutputScript, a.AspPubkey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, forfeit := range forfeits {
|
||||||
|
updater, err := psetv2.NewUpdater(forfeit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updater.AddInTapLeafScript(1, tapscript); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b64, err := updater.Pset.ToBase64()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedForfeit, err := a.wallet.SignTransaction(ctx, a.explorer, b64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedForfeits = append(signedForfeits, signedForfeit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1173,24 +1384,6 @@ func (a *covenantArkClient) loopAndSign(
|
|||||||
return signedForfeits, nil
|
return signedForfeits, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) signForfeitTx(
|
|
||||||
ctx context.Context, txStr string, tx *psetv2.Pset, connectorsTxids []string,
|
|
||||||
) (string, error) {
|
|
||||||
connectorTxid := chainhash.Hash(tx.Inputs[0].PreviousTxid).String()
|
|
||||||
connectorFound := false
|
|
||||||
for _, id := range connectorsTxids {
|
|
||||||
if id == connectorTxid {
|
|
||||||
connectorFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !connectorFound {
|
|
||||||
return "", fmt.Errorf("connector txid %s not found in the connectors list", connectorTxid)
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.wallet.SignTransaction(ctx, a.explorer, txStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
||||||
@@ -1208,14 +1401,17 @@ func (a *covenantArkClient) coinSelectOnchain(
|
|||||||
descriptorStr := strings.ReplaceAll(
|
descriptorStr := strings.ReplaceAll(
|
||||||
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
)
|
)
|
||||||
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
boardingScript, err := tree.ParseVtxoScript(descriptorStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
var boardingTimeout uint
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok {
|
||||||
|
boardingTimeout = defaultVtxo.ExitDelay
|
||||||
|
} else {
|
||||||
|
return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", descriptorStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@@ -1387,13 +1583,17 @@ func (a *covenantArkClient) getVtxos(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) selfTransferAllPendingPayments(
|
func (a *covenantArkClient) selfTransferAllPendingPayments(
|
||||||
ctx context.Context, boardingUtxo []explorer.Utxo, myself client.Output, boardingDescriptor string,
|
ctx context.Context, boardingUtxos []explorer.Utxo, myself client.Output, mypubkey string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
inputs := make([]client.Input, 0, len(boardingUtxo))
|
inputs := make([]client.Input, 0, len(boardingUtxos))
|
||||||
|
|
||||||
for _, utxo := range boardingUtxo {
|
boardingDescriptor := strings.ReplaceAll(
|
||||||
inputs = append(inputs, client.BoardingInput{
|
a.BoardingDescriptorTemplate, "USER", mypubkey[2:],
|
||||||
VtxoKey: client.VtxoKey{
|
)
|
||||||
|
|
||||||
|
for _, utxo := range boardingUtxos {
|
||||||
|
inputs = append(inputs, client.Input{
|
||||||
|
Outpoint: client.Outpoint{
|
||||||
Txid: utxo.Txid,
|
Txid: utxo.Txid,
|
||||||
VOut: utxo.Vout,
|
VOut: utxo.Vout,
|
||||||
},
|
},
|
||||||
@@ -1413,7 +1613,7 @@ func (a *covenantArkClient) selfTransferAllPendingPayments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
roundTxid, err := a.handleRoundStream(
|
roundTxid, err := a.handleRoundStream(
|
||||||
ctx, paymentID, make([]client.Vtxo, 0), len(boardingUtxo) > 0, outputs,
|
ctx, paymentID, make([]client.Vtxo, 0), boardingUtxos, boardingDescriptor, outputs,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -1422,6 +1622,21 @@ func (a *covenantArkClient) selfTransferAllPendingPayments(
|
|||||||
return roundTxid, nil
|
return roundTxid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *covenantArkClient) offchainAddressToDefaultVtxoDescriptor(addr string) (string, error) {
|
||||||
|
_, 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 []Transaction) {
|
func (a *covenantArkClient) getBoardingTxs(ctx context.Context) (transactions []Transaction) {
|
||||||
utxos, err := a.getClaimableBoardingUtxos(ctx)
|
utxos, err := a.getClaimableBoardingUtxos(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"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/descriptor"
|
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/client"
|
"github.com/ark-network/ark/pkg/client-sdk/client"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
||||||
@@ -28,6 +27,7 @@ import (
|
|||||||
"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"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -425,8 +425,14 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
desc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
receivers = append(receivers, client.Output{
|
receivers = append(receivers, client.Output{
|
||||||
Address: offchainAddr,
|
Descriptor: desc,
|
||||||
Amount: changeAmount,
|
Amount: changeAmount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -434,9 +440,12 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
|
|||||||
inputs := make([]client.Input, 0, len(selectedCoins))
|
inputs := make([]client.Input, 0, len(selectedCoins))
|
||||||
|
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, client.VtxoKey{
|
inputs = append(inputs, client.Input{
|
||||||
|
Outpoint: client.Outpoint{
|
||||||
Txid: coin.Txid,
|
Txid: coin.Txid,
|
||||||
VOut: coin.VOut,
|
VOut: coin.VOut,
|
||||||
|
},
|
||||||
|
Descriptor: coin.Descriptor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +468,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
poolTxID, err := a.handleRoundStream(
|
poolTxID, err := a.handleRoundStream(
|
||||||
ctx, paymentID, selectedCoins, false, receivers, roundEphemeralKey,
|
ctx, paymentID, selectedCoins, nil, "", receivers, roundEphemeralKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -478,7 +487,7 @@ func (a *covenantlessArkClient) SendAsync(
|
|||||||
|
|
||||||
netParams := utils.ToBitcoinNetwork(a.Network)
|
netParams := utils.ToBitcoinNetwork(a.Network)
|
||||||
for _, receiver := range receivers {
|
for _, receiver := range receivers {
|
||||||
isOnchain, _, _, err := utils.ParseBitcoinAddress(receiver.To(), netParams)
|
isOnchain, _, err := utils.ParseBitcoinAddress(receiver.To(), netParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -516,8 +525,26 @@ func (a *covenantlessArkClient) SendAsync(
|
|||||||
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{
|
||||||
Address: receiver.To(),
|
Descriptor: desc,
|
||||||
Amount: receiver.Amount(),
|
Amount: receiver.Amount(),
|
||||||
})
|
})
|
||||||
sumOfReceivers += receiver.Amount()
|
sumOfReceivers += receiver.Amount()
|
||||||
@@ -535,17 +562,28 @@ 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{
|
||||||
Address: offchainAddrs[0],
|
Descriptor: changeDesc,
|
||||||
Amount: changeAmount,
|
Amount: changeAmount,
|
||||||
}
|
}
|
||||||
receiversOutput = append(receiversOutput, changeReceiver)
|
receiversOutput = append(receiversOutput, changeReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs := make([]client.VtxoKey, 0, len(selectedCoins))
|
inputs := make([]client.Input, 0, len(selectedCoins))
|
||||||
|
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, coin.VtxoKey)
|
inputs = append(inputs, client.Input{
|
||||||
|
Outpoint: client.Outpoint{
|
||||||
|
Txid: coin.Txid,
|
||||||
|
VOut: coin.VOut,
|
||||||
|
},
|
||||||
|
Descriptor: coin.Descriptor,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
redeemTx, unconditionalForfeitTxs, err := a.client.CreatePayment(
|
redeemTx, unconditionalForfeitTxs, err := a.client.CreatePayment(
|
||||||
@@ -556,23 +594,13 @@ func (a *covenantlessArkClient) SendAsync(
|
|||||||
|
|
||||||
// TODO verify the redeem tx signature
|
// TODO verify the redeem tx signature
|
||||||
|
|
||||||
signedUnconditionalForfeitTxs := make([]string, 0, len(unconditionalForfeitTxs))
|
|
||||||
for _, tx := range unconditionalForfeitTxs {
|
|
||||||
signedForfeitTx, err := a.wallet.SignTransaction(ctx, a.explorer, tx)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
signedUnconditionalForfeitTxs = append(signedUnconditionalForfeitTxs, signedForfeitTx)
|
|
||||||
}
|
|
||||||
|
|
||||||
signedRedeemTx, err := a.wallet.SignTransaction(ctx, a.explorer, redeemTx)
|
signedRedeemTx, err := a.wallet.SignTransaction(ctx, a.explorer, redeemTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = a.client.CompletePayment(
|
if err = a.client.CompletePayment(
|
||||||
ctx, signedRedeemTx, signedUnconditionalForfeitTxs,
|
ctx, signedRedeemTx, unconditionalForfeitTxs,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -612,13 +640,23 @@ 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{
|
||||||
Address: myselfOffchain,
|
Descriptor: desc,
|
||||||
Amount: pendingBalance,
|
Amount: pendingBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
desc := strings.ReplaceAll(a.BoardingDescriptorTemplate, "USER", hex.EncodeToString(schnorr.SerializePubKey(mypubkey)))
|
return a.selfTransferAllPendingPayments(
|
||||||
return a.selfTransferAllPendingPayments(ctx, pendingVtxos, boardingUtxos, receiver, desc)
|
ctx,
|
||||||
|
pendingVtxos,
|
||||||
|
boardingUtxos,
|
||||||
|
receiver,
|
||||||
|
hex.EncodeToString(mypubkey.SerializeCompressed()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) GetTransactionHistory(ctx context.Context) ([]Transaction, error) {
|
func (a *covenantlessArkClient) GetTransactionHistory(ctx context.Context) ([]Transaction, error) {
|
||||||
@@ -818,8 +856,13 @@ func (a *covenantlessArkClient) sendOffchain(
|
|||||||
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{
|
||||||
Address: receiver.To(),
|
Descriptor: desc,
|
||||||
Amount: receiver.Amount(),
|
Amount: receiver.Amount(),
|
||||||
})
|
})
|
||||||
sumOfReceivers += receiver.Amount()
|
sumOfReceivers += receiver.Amount()
|
||||||
@@ -846,8 +889,14 @@ 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{
|
||||||
Address: offchainAddr,
|
Descriptor: desc,
|
||||||
Amount: changeAmount,
|
Amount: changeAmount,
|
||||||
}
|
}
|
||||||
receiversOutput = append(receiversOutput, changeReceiver)
|
receiversOutput = append(receiversOutput, changeReceiver)
|
||||||
@@ -855,9 +904,12 @@ func (a *covenantlessArkClient) sendOffchain(
|
|||||||
|
|
||||||
inputs := make([]client.Input, 0, len(selectedCoins))
|
inputs := make([]client.Input, 0, len(selectedCoins))
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, client.VtxoKey{
|
inputs = append(inputs, client.Input{
|
||||||
|
Outpoint: client.Outpoint{
|
||||||
Txid: coin.Txid,
|
Txid: coin.Txid,
|
||||||
VOut: coin.VOut,
|
VOut: coin.VOut,
|
||||||
|
},
|
||||||
|
Descriptor: coin.Descriptor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -882,7 +934,7 @@ func (a *covenantlessArkClient) sendOffchain(
|
|||||||
log.Infof("payment registered with id: %s", paymentID)
|
log.Infof("payment registered with id: %s", paymentID)
|
||||||
|
|
||||||
poolTxID, err := a.handleRoundStream(
|
poolTxID, err := a.handleRoundStream(
|
||||||
ctx, paymentID, selectedCoins, false, receiversOutput, roundEphemeralKey,
|
ctx, paymentID, selectedCoins, nil, "", receiversOutput, roundEphemeralKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -926,25 +978,38 @@ func (a *covenantlessArkClient) addInputs(
|
|||||||
Sequence: sequence,
|
Sequence: sequence,
|
||||||
})
|
})
|
||||||
|
|
||||||
_, leafProof, err := bitcointree.ComputeVtxoTaprootScript(
|
vtxoScript := &bitcointree.DefaultVtxoScript{
|
||||||
userPubkey, aspPubkey, utxo.Delay,
|
Owner: userPubkey,
|
||||||
)
|
Asp: aspPubkey,
|
||||||
|
ExitDelay: utxo.Delay,
|
||||||
|
}
|
||||||
|
|
||||||
|
exitClosure := &bitcointree.CSVSigClosure{
|
||||||
|
Pubkey: userPubkey,
|
||||||
|
Seconds: uint(utxo.Delay),
|
||||||
|
}
|
||||||
|
|
||||||
|
exitLeaf, err := exitClosure.Leaf()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
controlBlock := leafProof.ToControlBlock(bitcointree.UnspendableKey())
|
_, taprootTree, err := vtxoScript.TapTree()
|
||||||
controlBlockBytes, err := controlBlock.ToBytes()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
leafProof, err := taprootTree.GetTaprootMerkleProof(exitLeaf.TapHash())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get taproot merkle proof: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{
|
updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{
|
||||||
TaprootLeafScript: []*psbt.TaprootTapLeafScript{
|
TaprootLeafScript: []*psbt.TaprootTapLeafScript{
|
||||||
{
|
{
|
||||||
ControlBlock: controlBlockBytes,
|
ControlBlock: leafProof.ControlBlock,
|
||||||
Script: leafProof.Script,
|
Script: leafProof.Script,
|
||||||
LeafVersion: leafProof.LeafVersion,
|
LeafVersion: txscript.BaseLeafVersion,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -957,7 +1022,8 @@ func (a *covenantlessArkClient) handleRoundStream(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
paymentID string,
|
paymentID string,
|
||||||
vtxosToSign []client.Vtxo,
|
vtxosToSign []client.Vtxo,
|
||||||
mustSignRoundTx bool,
|
boardingUtxos []explorer.Utxo,
|
||||||
|
boardingDescriptor string,
|
||||||
receivers []client.Output,
|
receivers []client.Output,
|
||||||
roundEphemeralKey *secp256k1.PrivateKey,
|
roundEphemeralKey *secp256k1.PrivateKey,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
@@ -1033,7 +1099,7 @@ func (a *covenantlessArkClient) handleRoundStream(
|
|||||||
log.Info("a round finalization started")
|
log.Info("a round finalization started")
|
||||||
|
|
||||||
signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization(
|
signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization(
|
||||||
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, mustSignRoundTx, receivers,
|
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, boardingDescriptor, receivers,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -1134,30 +1200,103 @@ func (a *covenantlessArkClient) handleRoundSigningNoncesGenerated(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) handleRoundFinalization(
|
func (a *covenantlessArkClient) handleRoundFinalization(
|
||||||
ctx context.Context, event client.RoundFinalizationEvent,
|
ctx context.Context,
|
||||||
vtxos []client.Vtxo, mustSignRoundTx bool, receivers []client.Output,
|
event client.RoundFinalizationEvent,
|
||||||
) (signedForfeits []string, signedRoundTx string, err error) {
|
vtxos []client.Vtxo,
|
||||||
|
boardingUtxos []explorer.Utxo,
|
||||||
|
boardingDescriptor string,
|
||||||
|
receivers []client.Output,
|
||||||
|
) ([]string, string, error) {
|
||||||
if err := a.validateCongestionTree(event, receivers); err != nil {
|
if err := a.validateCongestionTree(event, receivers); err != nil {
|
||||||
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
|
||||||
|
|
||||||
if len(vtxos) > 0 {
|
if len(vtxos) > 0 {
|
||||||
signedForfeits, err = a.loopAndSign(
|
signedForfeits, err := a.createAndSignForfeits(
|
||||||
ctx, event.ForfeitTxs, vtxos, event.Connectors,
|
ctx, vtxos, event.Connectors, event.MinRelayFeeRate, myPubkey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, "", err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if mustSignRoundTx {
|
forfeits = signedForfeits
|
||||||
signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, event.Tx)
|
}
|
||||||
|
|
||||||
|
if len(boardingUtxos) > 0 {
|
||||||
|
boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingDescriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
roundPtx, err := psbt.NewFromRawBytes(strings.NewReader(event.Tx), true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add tapscript leaf
|
||||||
|
forfeitClosure := &bitcointree.MultisigClosure{
|
||||||
|
Pubkey: myPubkey,
|
||||||
|
AspPubkey: a.AspPubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, taprootTree, err := boardingVtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitProof, err := taprootTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to get taproot merkle proof for boarding utxo: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tapscript := &psbt.TaprootTapLeafScript{
|
||||||
|
ControlBlock: forfeitProof.ControlBlock,
|
||||||
|
Script: forfeitProof.Script,
|
||||||
|
LeafVersion: txscript.BaseLeafVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range roundPtx.Inputs {
|
||||||
|
previousOutpoint := roundPtx.UnsignedTx.TxIn[i].PreviousOutPoint
|
||||||
|
|
||||||
|
for _, boardingUtxo := range boardingUtxos {
|
||||||
|
if boardingUtxo.Txid == previousOutpoint.Hash.String() && boardingUtxo.Vout == previousOutpoint.Index {
|
||||||
|
roundPtx.Inputs[i].TaprootLeafScript = []*psbt.TaprootTapLeafScript{tapscript}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
b64, err := roundPtx.B64Encode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedRoundTx, err := a.wallet.SignTransaction(ctx, a.explorer, b64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return forfeits, signedRoundTx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return forfeits, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) validateCongestionTree(
|
func (a *covenantlessArkClient) validateCongestionTree(
|
||||||
@@ -1169,8 +1308,7 @@ func (a *covenantlessArkClient) validateCongestionTree(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
netParams := utils.ToBitcoinNetwork(a.Network)
|
if !utils.IsOnchainOnly(receivers) {
|
||||||
if !utils.IsBitcoinOnchainOnly(receivers, netParams) {
|
|
||||||
if err := bitcointree.ValidateCongestionTree(
|
if err := bitcointree.ValidateCongestionTree(
|
||||||
event.Tree, poolTx, a.StoreData.AspPubkey, a.RoundLifetime,
|
event.Tree, poolTx, a.StoreData.AspPubkey, a.RoundLifetime,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -1183,7 +1321,7 @@ func (a *covenantlessArkClient) validateCongestionTree(
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
if err := a.validateReceivers(
|
if err := a.validateReceivers(
|
||||||
ptx, receivers, event.Tree, a.StoreData.AspPubkey,
|
ptx, receivers, event.Tree,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1197,15 +1335,14 @@ func (a *covenantlessArkClient) validateReceivers(
|
|||||||
ptx *psbt.Packet,
|
ptx *psbt.Packet,
|
||||||
receivers []client.Output,
|
receivers []client.Output,
|
||||||
congestionTree tree.CongestionTree,
|
congestionTree tree.CongestionTree,
|
||||||
aspPubkey *secp256k1.PublicKey,
|
|
||||||
) error {
|
) error {
|
||||||
netParams := utils.ToBitcoinNetwork(a.Network)
|
netParams := utils.ToBitcoinNetwork(a.Network)
|
||||||
for _, receiver := range receivers {
|
for _, receiver := range receivers {
|
||||||
isOnChain, onchainScript, userPubkey, err := utils.ParseBitcoinAddress(
|
isOnChain, onchainScript, err := utils.ParseBitcoinAddress(
|
||||||
receiver.Address, netParams,
|
receiver.Address, netParams,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("invalid receiver address: %s err = %s", receiver.Address, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isOnChain {
|
if isOnChain {
|
||||||
@@ -1214,7 +1351,7 @@ func (a *covenantlessArkClient) validateReceivers(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := a.validateOffChainReceiver(
|
if err := a.validateOffChainReceiver(
|
||||||
congestionTree, receiver, userPubkey, aspPubkey,
|
congestionTree, receiver,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1250,12 +1387,15 @@ func (a *covenantlessArkClient) validateOnChainReceiver(
|
|||||||
func (a *covenantlessArkClient) validateOffChainReceiver(
|
func (a *covenantlessArkClient) validateOffChainReceiver(
|
||||||
congestionTree tree.CongestionTree,
|
congestionTree tree.CongestionTree,
|
||||||
receiver client.Output,
|
receiver client.Output,
|
||||||
userPubkey, aspPubkey *secp256k1.PublicKey,
|
|
||||||
) error {
|
) error {
|
||||||
found := false
|
found := false
|
||||||
outputTapKey, _, err := bitcointree.ComputeVtxoTaprootScript(
|
|
||||||
userPubkey, aspPubkey, uint(a.UnilateralExitDelay),
|
receiverVtxoScript, err := bitcointree.ParseVtxoScript(receiver.Descriptor)
|
||||||
)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
outputTapKey, _, err := receiverVtxoScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1298,57 +1438,107 @@ func (a *covenantlessArkClient) validateOffChainReceiver(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) loopAndSign(
|
func (a *covenantlessArkClient) createAndSignForfeits(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
forfeitTxs []string, vtxosToSign []client.Vtxo, connectors []string,
|
vtxosToSign []client.Vtxo,
|
||||||
|
connectors []string,
|
||||||
|
feeRate chainfee.SatPerKVByte,
|
||||||
|
myPubkey *secp256k1.PublicKey,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
|
|
||||||
signedForfeits := make([]string, 0)
|
signedForfeits := make([]string, 0)
|
||||||
|
connectorsPsets := make([]*psbt.Packet, 0, len(connectors))
|
||||||
|
|
||||||
connectorsTxids := make([]string, 0, len(connectors))
|
|
||||||
for _, connector := range connectors {
|
for _, connector := range connectors {
|
||||||
ptx, err := psbt.NewFromRawBytes(strings.NewReader(connector), true)
|
p, err := psbt.NewFromRawBytes(strings.NewReader(connector), true)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
txid := ptx.UnsignedTx.TxHash().String()
|
|
||||||
connectorsTxids = append(connectorsTxids, txid)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, forfeitTx := range forfeitTxs {
|
|
||||||
ptx, err := psbt.NewFromRawBytes(strings.NewReader(forfeitTx), true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, input := range ptx.UnsignedTx.TxIn {
|
connectorsPsets = append(connectorsPsets, p)
|
||||||
inputTxid := input.PreviousOutPoint.Hash.String()
|
|
||||||
|
|
||||||
for _, coin := range vtxosToSign {
|
|
||||||
// check if it contains one of the input to sign
|
|
||||||
if inputTxid == coin.Txid {
|
|
||||||
// verify that the connector is in the connectors list
|
|
||||||
connectorTxid := ptx.UnsignedTx.TxIn[0].PreviousOutPoint.Hash.String()
|
|
||||||
connectorFound := false
|
|
||||||
for _, txid := range connectorsTxids {
|
|
||||||
if txid == connectorTxid {
|
|
||||||
connectorFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !connectorFound {
|
for _, vtxo := range vtxosToSign {
|
||||||
return nil, fmt.Errorf("connector txid %s not found in the connectors list", connectorTxid)
|
vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Descriptor)
|
||||||
}
|
|
||||||
|
|
||||||
signedForfeitTx, err := a.wallet.SignTransaction(ctx, a.explorer, forfeitTx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
signedForfeits = append(signedForfeits, signedForfeitTx)
|
vtxoTapKey, vtxoTapTree, err := vtxoScript.TapTree()
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
feeAmount, err := common.ComputeForfeitMinRelayFee(feeRate, vtxoTapTree)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxoOutputScript, err := common.P2TRScript(vtxoTapKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxoTxHash, err := chainhash.NewHashFromStr(vtxo.Txid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxoInput := &wire.OutPoint{
|
||||||
|
Hash: *vtxoTxHash,
|
||||||
|
Index: vtxo.VOut,
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitClosure := &bitcointree.MultisigClosure{
|
||||||
|
Pubkey: myPubkey,
|
||||||
|
AspPubkey: a.AspPubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
leafProof, err := vtxoTapTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tapscript := psbt.TaprootTapLeafScript{
|
||||||
|
ControlBlock: leafProof.ControlBlock,
|
||||||
|
Script: leafProof.Script,
|
||||||
|
LeafVersion: txscript.BaseLeafVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, connectorPset := range connectorsPsets {
|
||||||
|
forfeits, err := bitcointree.BuildForfeitTxs(
|
||||||
|
connectorPset, vtxoInput, vtxo.Amount, a.Dust, feeAmount, vtxoOutputScript, a.AspPubkey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(forfeits) <= 0 {
|
||||||
|
return nil, fmt.Errorf("no forfeit txs created dust = %d", a.Dust)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, forfeit := range forfeits {
|
||||||
|
forfeit.Inputs[1].TaprootLeafScript = []*psbt.TaprootTapLeafScript{&tapscript}
|
||||||
|
|
||||||
|
b64, err := forfeit.B64Encode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedForfeit, err := a.wallet.SignTransaction(ctx, a.explorer, b64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedForfeits = append(signedForfeits, signedForfeit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return signedForfeits, nil
|
return signedForfeits, nil
|
||||||
@@ -1371,14 +1561,18 @@ func (a *covenantlessArkClient) coinSelectOnchain(
|
|||||||
descriptorStr := strings.ReplaceAll(
|
descriptorStr := strings.ReplaceAll(
|
||||||
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
)
|
)
|
||||||
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
|
||||||
|
boardingScript, err := bitcointree.ParseVtxoScript(descriptorStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
var boardingTimeout uint
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
if defaultVtxo, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok {
|
||||||
|
boardingTimeout = defaultVtxo.ExitDelay
|
||||||
|
} else {
|
||||||
|
return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", descriptorStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@@ -1567,14 +1761,18 @@ func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context) (
|
|||||||
descriptorStr := strings.ReplaceAll(
|
descriptorStr := strings.ReplaceAll(
|
||||||
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
)
|
)
|
||||||
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
|
||||||
|
boardingScript, err := bitcointree.ParseVtxoScript(descriptorStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
var boardingTimeout uint
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
if defaultVtxo, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok {
|
||||||
|
boardingTimeout = defaultVtxo.ExitDelay
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("unsupported boarding descriptor: %s", descriptorStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
claimable := make([]explorer.Utxo, 0)
|
claimable := make([]explorer.Utxo, 0)
|
||||||
@@ -1644,17 +1842,27 @@ func (a *covenantlessArkClient) getVtxos(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) selfTransferAllPendingPayments(
|
func (a *covenantlessArkClient) selfTransferAllPendingPayments(
|
||||||
ctx context.Context, pendingVtxos []client.Vtxo, boardingUtxo []explorer.Utxo, myself client.Output, boardingDescriptor string,
|
ctx context.Context, pendingVtxos []client.Vtxo, boardingUtxos []explorer.Utxo, myself client.Output, mypubkey string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
inputs := make([]client.Input, 0, len(pendingVtxos)+len(boardingUtxo))
|
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, coin.VtxoKey)
|
inputs = append(inputs, client.Input{
|
||||||
|
Outpoint: client.Outpoint{
|
||||||
|
Txid: coin.Txid,
|
||||||
|
VOut: coin.VOut,
|
||||||
|
},
|
||||||
|
Descriptor: coin.Descriptor,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, utxo := range boardingUtxo {
|
for _, utxo := range boardingUtxos {
|
||||||
inputs = append(inputs, client.BoardingInput{
|
inputs = append(inputs, client.Input{
|
||||||
VtxoKey: client.VtxoKey{
|
Outpoint: client.Outpoint{
|
||||||
Txid: utxo.Txid,
|
Txid: utxo.Txid,
|
||||||
VOut: utxo.Vout,
|
VOut: utxo.Vout,
|
||||||
},
|
},
|
||||||
@@ -1682,7 +1890,7 @@ func (a *covenantlessArkClient) selfTransferAllPendingPayments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
roundTxid, err := a.handleRoundStream(
|
roundTxid, err := a.handleRoundStream(
|
||||||
ctx, paymentID, pendingVtxos, len(boardingUtxo) > 0, outputs, roundEphemeralKey,
|
ctx, paymentID, pendingVtxos, boardingUtxos, boardingDescriptor, outputs, roundEphemeralKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -1691,6 +1899,42 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) getBoardingTxs(ctx context.Context) (transactions []Transaction) {
|
func (a *covenantlessArkClient) getBoardingTxs(ctx context.Context) (transactions []Transaction) {
|
||||||
utxos, err := a.getClaimableBoardingUtxos(ctx)
|
utxos, err := a.getClaimableBoardingUtxos(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ require (
|
|||||||
github.com/go-openapi/strfmt v0.23.0
|
github.com/go-openapi/strfmt v0.23.0
|
||||||
github.com/go-openapi/swag v0.23.0
|
github.com/go-openapi/swag v0.23.0
|
||||||
github.com/go-openapi/validate v0.24.0
|
github.com/go-openapi/validate v0.24.0
|
||||||
|
github.com/lightningnetwork/lnd v0.18.2-beta
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/vulpemventures/go-elements v0.5.4
|
github.com/vulpemventures/go-elements v0.5.4
|
||||||
@@ -38,6 +39,7 @@ require (
|
|||||||
github.com/btcsuite/btcwallet/wtxmgr v1.5.3 // indirect
|
github.com/btcsuite/btcwallet/wtxmgr v1.5.3 // indirect
|
||||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
||||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
|
||||||
|
github.com/btcsuite/winsvc v1.0.0 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/containerd/continuity v0.4.3 // indirect
|
github.com/containerd/continuity v0.4.3 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
@@ -45,6 +47,7 @@ require (
|
|||||||
github.com/decred/dcrd/lru v1.1.3 // indirect
|
github.com/decred/dcrd/lru v1.1.3 // indirect
|
||||||
github.com/docker/docker v27.1.1+incompatible // indirect
|
github.com/docker/docker v27.1.1+incompatible // indirect
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||||
@@ -52,16 +55,18 @@ require (
|
|||||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||||
github.com/go-openapi/loads v0.22.0 // indirect
|
github.com/go-openapi/loads v0.22.0 // indirect
|
||||||
github.com/go-openapi/spec v0.21.0 // indirect
|
github.com/go-openapi/spec v0.21.0 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/jessevdk/go-flags v1.6.1 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/jrick/logrotate v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kkdai/bstream v1.0.0 // indirect
|
github.com/kkdai/bstream v1.0.0 // indirect
|
||||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
|
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
|
||||||
github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd // indirect
|
github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd // indirect
|
||||||
github.com/lightninglabs/neutrino/cache v1.1.2 // indirect
|
github.com/lightninglabs/neutrino/cache v1.1.2 // indirect
|
||||||
github.com/lightningnetwork/lnd v0.18.2-beta // indirect
|
|
||||||
github.com/lightningnetwork/lnd/clock v1.1.1 // indirect
|
github.com/lightningnetwork/lnd/clock v1.1.1 // indirect
|
||||||
github.com/lightningnetwork/lnd/fn v1.2.1 // indirect
|
github.com/lightningnetwork/lnd/fn v1.2.1 // indirect
|
||||||
github.com/lightningnetwork/lnd/queue v1.1.1 // indirect
|
github.com/lightningnetwork/lnd/queue v1.1.1 // indirect
|
||||||
@@ -74,11 +79,13 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/oklog/ulid v1.3.1 // indirect
|
github.com/oklog/ulid v1.3.1 // indirect
|
||||||
|
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||||
github.com/opencontainers/runc v1.1.13 // indirect
|
github.com/opencontainers/runc v1.1.13 // indirect
|
||||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||||
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 // indirect
|
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
go.etcd.io/etcd/client/v2 v2.305.15 // indirect
|
go.etcd.io/etcd/client/v2 v2.305.15 // indirect
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ github.com/fergusstrange/embedded-postgres v1.28.0 h1:Atixd24HCuBHBavnG4eiZAjRiz
|
|||||||
github.com/fergusstrange/embedded-postgres v1.28.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw=
|
github.com/fergusstrange/embedded-postgres v1.28.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
@@ -263,16 +264,19 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
|
|||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||||
|
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
@@ -432,6 +436,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
|
|||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|||||||
@@ -67,60 +67,34 @@ func CoinSelect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ParseLiquidAddress(addr string) (
|
func ParseLiquidAddress(addr string) (
|
||||||
bool, []byte, *secp256k1.PublicKey, error,
|
bool, []byte, error,
|
||||||
) {
|
) {
|
||||||
outputScript, err := address.ToOutputScript(addr)
|
outputScript, err := address.ToOutputScript(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, userPubkey, _, err := common.DecodeAddress(addr)
|
return false, nil, nil
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
return false, nil, userPubkey, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, outputScript, nil, nil
|
return true, outputScript, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseBitcoinAddress(addr string, net chaincfg.Params) (
|
func ParseBitcoinAddress(addr string, net chaincfg.Params) (
|
||||||
bool, []byte, *secp256k1.PublicKey, error,
|
bool, []byte, error,
|
||||||
) {
|
) {
|
||||||
btcAddr, err := btcutil.DecodeAddress(addr, &net)
|
btcAddr, err := btcutil.DecodeAddress(addr, &net)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, userPubkey, _, err := common.DecodeAddress(addr)
|
return false, nil, nil
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
return false, nil, userPubkey, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onchainScript, err := txscript.PayToAddrScript(btcAddr)
|
onchainScript, err := txscript.PayToAddrScript(btcAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, err
|
return false, nil, err
|
||||||
}
|
}
|
||||||
return true, onchainScript, nil, nil
|
return true, onchainScript, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsBitcoinOnchainOnly(receivers []client.Output, net chaincfg.Params) bool {
|
func IsOnchainOnly(receivers []client.Output) bool {
|
||||||
for _, receiver := range receivers {
|
for _, receiver := range receivers {
|
||||||
isOnChain, _, _, err := ParseBitcoinAddress(receiver.Address, net)
|
isOnChain := len(receiver.Address) > 0
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isOnChain {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsLiquidOnchainOnly(receivers []client.Output) bool {
|
|
||||||
for _, receiver := range receivers {
|
|
||||||
isOnChain, _, _, err := ParseLiquidAddress(receiver.Address)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isOnChain {
|
if !isOnChain {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"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/descriptor"
|
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/store"
|
"github.com/ark-network/ark/pkg/client-sdk/store"
|
||||||
@@ -219,9 +218,13 @@ func (w *bitcoinWallet) getAddress(
|
|||||||
|
|
||||||
netParams := utils.ToBitcoinNetwork(data.Network)
|
netParams := utils.ToBitcoinNetwork(data.Network)
|
||||||
|
|
||||||
vtxoTapKey, _, err := bitcointree.ComputeVtxoTaprootScript(
|
defaultVtxoScript := &bitcointree.DefaultVtxoScript{
|
||||||
w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay),
|
Asp: data.AspPubkey,
|
||||||
)
|
Owner: w.walletData.Pubkey,
|
||||||
|
ExitDelay: uint(data.UnilateralExitDelay),
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxoTapKey, _, err := defaultVtxoScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
@@ -239,19 +242,12 @@ func (w *bitcoinWallet) getAddress(
|
|||||||
data.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
data.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
)
|
)
|
||||||
|
|
||||||
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
boardingVtxoScript, err := bitcointree.ParseVtxoScript(descriptorStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
boardingTapKey, _, err := boardingVtxoScript.TapTree()
|
||||||
if err != nil {
|
|
||||||
return "", "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
boardingTapKey, _, err := bitcointree.ComputeVtxoTaprootScript(
|
|
||||||
w.walletData.Pubkey, data.AspPubkey, boardingTimeout,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
"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/pkg/client-sdk/explorer"
|
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
||||||
@@ -18,6 +17,7 @@ import (
|
|||||||
"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"
|
||||||
|
"github.com/vulpemventures/go-elements/payment"
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
"github.com/vulpemventures/go-elements/transaction"
|
"github.com/vulpemventures/go-elements/transaction"
|
||||||
)
|
)
|
||||||
@@ -165,7 +165,7 @@ func (s *liquidWallet) SignTransaction(
|
|||||||
switch c := closure.(type) {
|
switch c := closure.(type) {
|
||||||
case *tree.CSVSigClosure:
|
case *tree.CSVSigClosure:
|
||||||
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], serializedPubKey[1:])
|
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], serializedPubKey[1:])
|
||||||
case *tree.ForfeitClosure:
|
case *tree.MultisigClosure:
|
||||||
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], serializedPubKey[1:])
|
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], serializedPubKey[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,9 +242,23 @@ func (w *liquidWallet) getAddress(
|
|||||||
|
|
||||||
liquidNet := utils.ToElementsNetwork(data.Network)
|
liquidNet := utils.ToElementsNetwork(data.Network)
|
||||||
|
|
||||||
_, _, _, redemptionAddr, err := tree.ComputeVtxoTaprootScript(
|
vtxoScript := &tree.DefaultVtxoScript{
|
||||||
w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay), liquidNet,
|
Owner: w.walletData.Pubkey,
|
||||||
)
|
Asp: data.AspPubkey,
|
||||||
|
ExitDelay: uint(data.UnilateralExitDelay),
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxoTapKey, _, err := vtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxoP2tr, err := payment.FromTweakedKey(vtxoTapKey, &liquidNet, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
redemptionAddr, err := vtxoP2tr.TaprootAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
@@ -254,19 +268,22 @@ func (w *liquidWallet) getAddress(
|
|||||||
data.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
data.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
)
|
)
|
||||||
|
|
||||||
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
onboardingScript, err := tree.ParseVtxoScript(descriptorStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
tapKey, _, err := onboardingScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, boardingAddr, err := tree.ComputeVtxoTaprootScript(
|
p2tr, err := payment.FromTweakedKey(tapKey, &liquidNet, nil)
|
||||||
w.walletData.Pubkey, data.AspPubkey, boardingTimeout, liquidNet,
|
if err != nil {
|
||||||
)
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingAddr, err := p2tr.TaprootAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,11 +277,11 @@ func (c *Config) txBuilderService() error {
|
|||||||
switch c.TxBuilderType {
|
switch c.TxBuilderType {
|
||||||
case "covenant":
|
case "covenant":
|
||||||
svc = txbuilder.NewTxBuilder(
|
svc = txbuilder.NewTxBuilder(
|
||||||
c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay,
|
c.wallet, c.Network, c.RoundLifetime, c.BoardingExitDelay,
|
||||||
)
|
)
|
||||||
case "covenantless":
|
case "covenantless":
|
||||||
svc = cltxbuilder.NewTxBuilder(
|
svc = cltxbuilder.NewTxBuilder(
|
||||||
c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay,
|
c.wallet, c.Network, c.RoundLifetime, c.BoardingExitDelay,
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknown tx builder type")
|
err = fmt.Errorf("unknown tx builder type")
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import (
|
|||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/vulpemventures/go-elements/elementsutil"
|
"github.com/vulpemventures/go-elements/elementsutil"
|
||||||
|
"github.com/vulpemventures/go-elements/network"
|
||||||
|
"github.com/vulpemventures/go-elements/payment"
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
"github.com/vulpemventures/go-elements/transaction"
|
"github.com/vulpemventures/go-elements/transaction"
|
||||||
)
|
)
|
||||||
@@ -118,159 +120,158 @@ func (s *covenantService) Stop() {
|
|||||||
close(s.eventsCh)
|
close(s.eventsCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantService) GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, error) {
|
func (s *covenantService) GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, string, error) {
|
||||||
addr, _, err := s.builder.GetBoardingScript(userPubkey, s.pubkey)
|
vtxoScript := &tree.DefaultVtxoScript{
|
||||||
if err != nil {
|
Asp: s.pubkey,
|
||||||
return "", err
|
Owner: userPubkey,
|
||||||
|
ExitDelay: uint(s.boardingExitDelay),
|
||||||
}
|
}
|
||||||
return addr, nil
|
|
||||||
|
tapKey, _, err := vtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("failed to get taproot key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p2tr, err := payment.FromTweakedKey(tapKey, s.onchainNetwork(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := p2tr.TaprootAddress()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, vtxoScript.ToDescriptor(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantService) SpendVtxos(ctx context.Context, inputs []Input) (string, error) {
|
func (s *covenantService) SpendVtxos(ctx context.Context, inputs []ports.Input) (string, error) {
|
||||||
vtxosInputs := make([]domain.VtxoKey, 0)
|
vtxosInputs := make([]domain.Vtxo, 0)
|
||||||
boardingInputs := make([]Input, 0)
|
boardingInputs := make([]ports.BoardingInput, 0)
|
||||||
|
|
||||||
for _, in := range inputs {
|
|
||||||
if in.IsVtxo() {
|
|
||||||
vtxosInputs = append(vtxosInputs, in.VtxoKey())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
boardingInputs = append(boardingInputs, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxos := make([]domain.Vtxo, 0)
|
|
||||||
|
|
||||||
if len(vtxosInputs) > 0 {
|
|
||||||
var err error
|
|
||||||
vtxos, err = s.repoManager.Vtxos().GetVtxos(ctx, vtxosInputs)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
for _, v := range vtxos {
|
|
||||||
if v.Spent {
|
|
||||||
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Redeemed {
|
|
||||||
return "", fmt.Errorf("input %s:%d already redeemed", v.Txid, v.VOut)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Spent {
|
|
||||||
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boardingTxs := make(map[string]string, 0) // txid -> txhex
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
|
|
||||||
for _, in := range boardingInputs {
|
boardingTxs := make(map[string]*transaction.Transaction, 0) // txid -> txhex
|
||||||
if _, ok := boardingTxs[in.Txid]; !ok {
|
|
||||||
txhex, err := s.wallet.GetTransaction(ctx, in.Txid)
|
for _, input := range inputs {
|
||||||
|
vtxosResult, err := s.repoManager.Vtxos().GetVtxos(ctx, []domain.VtxoKey{input.VtxoKey})
|
||||||
|
if err != nil || len(vtxosResult) == 0 {
|
||||||
|
// vtxo not found in db, check if it exists on-chain
|
||||||
|
if _, ok := boardingTxs[input.Txid]; !ok {
|
||||||
|
// check if the tx exists and is confirmed
|
||||||
|
txhex, err := s.wallet.GetTransaction(ctx, input.Txid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get tx %s: %s", in.Txid, err)
|
return "", fmt.Errorf("failed to get tx %s: %s", input.Txid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, in.Txid)
|
tx, err := transaction.NewTxFromHex(txhex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to check tx %s: %s", in.Txid, err)
|
return "", fmt.Errorf("failed to parse tx %s: %s", input.Txid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, input.Txid)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to check tx %s: %s", input.Txid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !confirmed {
|
if !confirmed {
|
||||||
return "", fmt.Errorf("tx %s not confirmed", in.Txid)
|
return "", fmt.Errorf("tx %s not confirmed", input.Txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the exit path is available, forbid registering the boarding utxo
|
||||||
if blocktime+int64(s.boardingExitDelay) < now {
|
if blocktime+int64(s.boardingExitDelay) < now {
|
||||||
return "", fmt.Errorf("tx %s expired", in.Txid)
|
return "", fmt.Errorf("tx %s expired", input.Txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
boardingTxs[in.Txid] = txhex
|
boardingTxs[input.Txid] = tx
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos := make([]ports.BoardingInput, 0, len(boardingInputs))
|
tx := boardingTxs[input.Txid]
|
||||||
|
boardingInput, err := s.newBoardingInput(tx, input)
|
||||||
for _, in := range boardingInputs {
|
|
||||||
desc, err := in.GetDescriptor()
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Debugf("failed to parse boarding input descriptor")
|
|
||||||
return "", fmt.Errorf("failed to parse descriptor %s for input %s:%d", in.Descriptor, in.Txid, in.Index)
|
|
||||||
}
|
|
||||||
input, err := s.newBoardingInput(boardingTxs[in.Txid], in.Index, *desc)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Debugf("failed to create boarding input")
|
|
||||||
return "", fmt.Errorf("input %s:%d is not a valid boarding input", in.Txid, in.Index)
|
|
||||||
}
|
|
||||||
|
|
||||||
utxos = append(utxos, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
payment, err := domain.NewPayment(vtxos)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := s.paymentRequests.push(*payment, utxos); err != nil {
|
|
||||||
|
boardingInputs = append(boardingInputs, *boardingInput)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxo := vtxosResult[0]
|
||||||
|
if vtxo.Spent {
|
||||||
|
return "", fmt.Errorf("input %s:%d already spent", vtxo.Txid, vtxo.VOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vtxo.Redeemed {
|
||||||
|
return "", fmt.Errorf("input %s:%d already redeemed", vtxo.Txid, vtxo.VOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vtxo.Swept {
|
||||||
|
return "", fmt.Errorf("input %s:%d already swept", vtxo.Txid, vtxo.VOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxosInputs = append(vtxosInputs, vtxo)
|
||||||
|
}
|
||||||
|
|
||||||
|
payment, err := domain.NewPayment(vtxosInputs)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := s.paymentRequests.push(*payment, boardingInputs); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return payment.Id, nil
|
return payment.Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantService) newBoardingInput(
|
func (s *covenantService) newBoardingInput(tx *transaction.Transaction, input ports.Input) (*ports.BoardingInput, error) {
|
||||||
txhex string, vout uint32, desc descriptor.TaprootDescriptor,
|
if len(tx.Outputs) <= int(input.VtxoKey.VOut) {
|
||||||
) (ports.BoardingInput, error) {
|
|
||||||
tx, err := transaction.NewTxFromHex(txhex)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse tx: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tx.Outputs) <= int(vout) {
|
|
||||||
return nil, fmt.Errorf("output not found")
|
return nil, fmt.Errorf("output not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
out := tx.Outputs[vout]
|
output := tx.Outputs[input.VtxoKey.VOut]
|
||||||
script := out.Script
|
|
||||||
|
|
||||||
if len(out.RangeProof) > 0 || len(out.SurjectionProof) > 0 {
|
if len(output.RangeProof) > 0 || len(output.SurjectionProof) > 0 {
|
||||||
return nil, fmt.Errorf("output is confidential")
|
return nil, fmt.Errorf("output is confidential")
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptFromDescriptor, err := tree.ComputeOutputScript(desc)
|
amount, err := elementsutil.ValueFromBytes(output.Value)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to compute output script: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(script, scriptFromDescriptor) {
|
|
||||||
return nil, fmt.Errorf("descriptor does not match script in transaction output")
|
|
||||||
}
|
|
||||||
|
|
||||||
pubkey, timeout, err := descriptor.ParseBoardingDescriptor(desc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if timeout != uint(s.boardingExitDelay) {
|
|
||||||
return nil, fmt.Errorf("invalid boarding descriptor, timeout mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, expectedScript, err := s.builder.GetBoardingScript(pubkey, s.pubkey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to compute boarding script: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(script, expectedScript) {
|
|
||||||
return nil, fmt.Errorf("output script mismatch expected script")
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := elementsutil.ValueFromBytes(out.Value)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse value: %s", err)
|
return nil, fmt.Errorf("failed to parse value: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &boardingInput{
|
boardingScript, err := tree.ParseVtxoScript(input.Descriptor)
|
||||||
txId: tx.TxHash(),
|
if err != nil {
|
||||||
vout: vout,
|
return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err)
|
||||||
boardingPubKey: pubkey,
|
}
|
||||||
amount: value,
|
|
||||||
|
tapKey, _, err := boardingScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get taproot key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedScriptPubKey, err := common.P2TRScript(tapKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get script pubkey: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(output.Script, expectedScriptPubKey) {
|
||||||
|
return nil, fmt.Errorf("descriptor does not match script in transaction output")
|
||||||
|
}
|
||||||
|
|
||||||
|
if defaultVtxoScript, ok := boardingScript.(*tree.DefaultVtxoScript); ok {
|
||||||
|
if !bytes.Equal(schnorr.SerializePubKey(defaultVtxoScript.Asp), schnorr.SerializePubKey(s.pubkey)) {
|
||||||
|
return nil, fmt.Errorf("invalid boarding descriptor, ASP mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
if defaultVtxoScript.ExitDelay != uint(s.boardingExitDelay) {
|
||||||
|
return nil, fmt.Errorf("invalid boarding descriptor, timeout mismatch")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("only default vtxo script is supported for boarding")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ports.BoardingInput{
|
||||||
|
Amount: amount,
|
||||||
|
Input: input,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +316,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 []domain.VtxoKey, receivers []domain.Receiver) (string, []string, error) {
|
func (s *covenantService) CreateAsyncPayment(ctx context.Context, inputs []ports.Input, receivers []domain.Receiver) (string, []string, error) {
|
||||||
return "", nil, fmt.Errorf("unimplemented")
|
return "", nil, fmt.Errorf("unimplemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,10 +374,10 @@ func (s *covenantService) GetInfo(ctx context.Context) (*ServiceInfo, error) {
|
|||||||
Network: s.network.Name,
|
Network: s.network.Name,
|
||||||
Dust: dust,
|
Dust: dust,
|
||||||
BoardingDescriptorTemplate: fmt.Sprintf(
|
BoardingDescriptorTemplate: fmt.Sprintf(
|
||||||
descriptor.BoardingDescriptorTemplate,
|
descriptor.DefaultVtxoDescriptorTemplate,
|
||||||
hex.EncodeToString(tree.UnspendableKey().SerializeCompressed()),
|
hex.EncodeToString(tree.UnspendableKey().SerializeCompressed()),
|
||||||
hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)),
|
|
||||||
"USER",
|
"USER",
|
||||||
|
hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)),
|
||||||
s.boardingExitDelay,
|
s.boardingExitDelay,
|
||||||
"USER",
|
"USER",
|
||||||
),
|
),
|
||||||
@@ -496,8 +497,10 @@ func (s *covenantService) startFinalization() {
|
|||||||
|
|
||||||
var forfeitTxs, connectors []string
|
var forfeitTxs, connectors []string
|
||||||
|
|
||||||
|
minRelayFeeRate := s.wallet.MinRelayFeeRate(ctx)
|
||||||
|
|
||||||
if needForfeits {
|
if needForfeits {
|
||||||
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments)
|
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments, 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")
|
||||||
@@ -505,6 +508,12 @@ func (s *covenantService) startFinalization() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("forfeit transactions created for round %s", round.Id)
|
log.Debugf("forfeit transactions created for round %s", round.Id)
|
||||||
|
|
||||||
|
if err := s.forfeitTxs.push(forfeitTxs); err != nil {
|
||||||
|
round.Fail(fmt.Errorf("failed to cache forfeit txs: %s", err))
|
||||||
|
log.WithError(err).Warn("failed to cache forfeit txs")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := round.StartFinalization(
|
if _, err := round.StartFinalization(
|
||||||
@@ -515,8 +524,6 @@ func (s *covenantService) startFinalization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.forfeitTxs.push(forfeitTxs)
|
|
||||||
|
|
||||||
log.Debugf("started finalization stage for round: %s", round.Id)
|
log.Debugf("started finalization stage for round: %s", round.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -845,13 +852,12 @@ func (s *covenantService) propagateEvents(round *domain.Round) {
|
|||||||
lastEvent := round.Events()[len(round.Events())-1]
|
lastEvent := round.Events()[len(round.Events())-1]
|
||||||
switch e := lastEvent.(type) {
|
switch e := lastEvent.(type) {
|
||||||
case domain.RoundFinalizationStarted:
|
case domain.RoundFinalizationStarted:
|
||||||
forfeitTxs := s.forfeitTxs.view()
|
|
||||||
ev := domain.RoundFinalizationStarted{
|
ev := domain.RoundFinalizationStarted{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
CongestionTree: e.CongestionTree,
|
CongestionTree: e.CongestionTree,
|
||||||
Connectors: e.Connectors,
|
Connectors: e.Connectors,
|
||||||
PoolTx: e.PoolTx,
|
PoolTx: e.PoolTx,
|
||||||
UnsignedForfeitTxs: forfeitTxs,
|
MinRelayFeeRate: int64(s.wallet.MinRelayFeeRate(context.Background())),
|
||||||
}
|
}
|
||||||
s.lastEvent = ev
|
s.lastEvent = ev
|
||||||
s.eventsCh <- ev
|
s.eventsCh <- ev
|
||||||
@@ -888,34 +894,59 @@ func (s *covenantService) getNewVtxos(round *domain.Round) []domain.Vtxo {
|
|||||||
for _, node := range leaves {
|
for _, node := range leaves {
|
||||||
tx, _ := psetv2.NewPsetFromBase64(node.Tx)
|
tx, _ := psetv2.NewPsetFromBase64(node.Tx)
|
||||||
for i, out := range tx.Outputs {
|
for i, out := range tx.Outputs {
|
||||||
for _, p := range round.Payments {
|
if len(out.Script) <= 0 {
|
||||||
var pubkey string
|
continue // skip fee outputs
|
||||||
|
}
|
||||||
|
|
||||||
|
desc := ""
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
|
for _, p := range round.Payments {
|
||||||
|
if found {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
for _, r := range p.Receivers {
|
for _, r := range p.Receivers {
|
||||||
if r.IsOnchain() {
|
if r.IsOnchain() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, _ := hex.DecodeString(r.Pubkey)
|
vtxoScript, err := tree.ParseVtxoScript(r.Descriptor)
|
||||||
pk, _ := secp256k1.ParsePubKey(buf)
|
if err != nil {
|
||||||
script, _ := s.builder.GetVtxoScript(pk, s.pubkey)
|
log.WithError(err).Warn("failed to parse vtxo descriptor")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tapKey, _, err := vtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Warn("failed to compute vtxo tap key")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := common.P2TRScript(tapKey)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Warn("failed to create vtxo scriptpubkey")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if bytes.Equal(script, out.Script) {
|
if bytes.Equal(script, out.Script) {
|
||||||
found = true
|
found = true
|
||||||
pubkey = r.Pubkey
|
desc = r.Descriptor
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if found {
|
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{Pubkey: pubkey, Amount: out.Value},
|
Receiver: domain.Receiver{Descriptor: desc, Amount: uint64(out.Value)},
|
||||||
PoolTx: round.Txid,
|
PoolTx: round.Txid,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return vtxos
|
return vtxos
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -984,15 +1015,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 {
|
||||||
buf, err := hex.DecodeString(vtxo.Pubkey)
|
vtxoScript, err := tree.ParseVtxoScript(vtxo.Receiver.Descriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
userPubkey, err := secp256k1.ParsePubKey(buf)
|
|
||||||
|
tapKey, _, err := vtxoScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
script, err := s.builder.GetVtxoScript(userPubkey, s.pubkey)
|
|
||||||
|
script, err := common.P2TRScript(tapKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1019,6 +1052,19 @@ func (s *covenantService) saveEvents(
|
|||||||
return s.repoManager.Rounds().AddOrUpdateRound(ctx, *round)
|
return s.repoManager.Rounds().AddOrUpdateRound(ctx, *round)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *covenantService) onchainNetwork() *network.Network {
|
||||||
|
switch s.network {
|
||||||
|
case common.Liquid:
|
||||||
|
return &network.Liquid
|
||||||
|
case common.LiquidTestNet:
|
||||||
|
return &network.Testnet
|
||||||
|
case common.LiquidRegTest:
|
||||||
|
return &network.Regtest
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func findForfeitTxLiquid(
|
func findForfeitTxLiquid(
|
||||||
forfeits []string, connectorTxid string, connectorVout uint32, vtxoTxid string,
|
forfeits []string, connectorTxid string, connectorVout uint32, vtxoTxid string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ 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/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
|
"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/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"
|
||||||
@@ -153,10 +155,9 @@ func (s *covenantlessService) CompleteAsyncPayment(
|
|||||||
return fmt.Errorf("async payment not found")
|
return fmt.Errorf("async payment not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
txs := append([]string{redeemTx}, unconditionalForfeitTxs...)
|
|
||||||
vtxoRepo := s.repoManager.Vtxos()
|
vtxoRepo := s.repoManager.Vtxos()
|
||||||
|
|
||||||
for _, tx := range txs {
|
for _, tx := range []string{redeemTx} {
|
||||||
ptx, err := psbt.NewFromRawBytes(strings.NewReader(tx), true)
|
ptx, err := psbt.NewFromRawBytes(strings.NewReader(tx), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse tx: %s", err)
|
return fmt.Errorf("failed to parse tx: %s", err)
|
||||||
@@ -200,39 +201,40 @@ func (s *covenantlessService) CompleteAsyncPayment(
|
|||||||
return fmt.Errorf("vtxo already swept")
|
return fmt.Errorf("vtxo already swept")
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify that the user signs the tx using the right public key
|
vtxoScript, err := bitcointree.ParseVtxoScript(vtxo[0].Descriptor)
|
||||||
|
|
||||||
vtxoPublicKey, err := hex.DecodeString(vtxo[0].Pubkey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to decode pubkey: %s", err)
|
return fmt.Errorf("failed to parse vtxo script: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pubkey, err := secp256k1.ParsePubKey(vtxoPublicKey)
|
vtxoTapKey, _, err := vtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get taproot key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that the user signs a forfeit closure
|
||||||
|
var userPubKey *secp256k1.PublicKey
|
||||||
|
|
||||||
|
aspXOnlyPubKey := schnorr.SerializePubKey(s.pubkey)
|
||||||
|
|
||||||
|
for _, sig := range input.TaprootScriptSpendSig {
|
||||||
|
if !bytes.Equal(sig.XOnlyPubKey, aspXOnlyPubKey) {
|
||||||
|
parsed, err := schnorr.ParsePubKey(sig.XOnlyPubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse pubkey: %s", err)
|
return fmt.Errorf("failed to parse pubkey: %s", err)
|
||||||
}
|
}
|
||||||
|
userPubKey = parsed
|
||||||
xonlyPubkey := schnorr.SerializePubKey(pubkey)
|
|
||||||
|
|
||||||
// find signature belonging to the pubkey
|
|
||||||
found := false
|
|
||||||
|
|
||||||
for _, sig := range input.TaprootScriptSpendSig {
|
|
||||||
if bytes.Equal(sig.XOnlyPubKey, xonlyPubkey) {
|
|
||||||
found = true
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if userPubKey == nil {
|
||||||
return fmt.Errorf("signature not found for pubkey")
|
return fmt.Errorf("redeem transaction is not signed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify witness utxo
|
// verify witness utxo
|
||||||
|
pkscript, err := common.P2TRScript(vtxoTapKey)
|
||||||
pkscript, err := s.builder.GetVtxoScript(pubkey, s.pubkey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get vtxo script: %s", err)
|
return fmt.Errorf("failed to get pkscript: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(input.WitnessUtxo.PkScript, pkscript) {
|
if !bytes.Equal(input.WitnessUtxo.PkScript, pkscript) {
|
||||||
@@ -250,7 +252,7 @@ func (s *covenantlessService) CompleteAsyncPayment(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spentVtxos := make([]domain.VtxoKey, 0, len(unconditionalForfeitTxs))
|
spentVtxos := make([]domain.VtxoKey, 0)
|
||||||
for _, in := range redeemPtx.UnsignedTx.TxIn {
|
for _, in := range redeemPtx.UnsignedTx.TxIn {
|
||||||
spentVtxos = append(spentVtxos, domain.VtxoKey{
|
spentVtxos = append(spentVtxos, domain.VtxoKey{
|
||||||
Txid: in.PreviousOutPoint.Hash.String(),
|
Txid: in.PreviousOutPoint.Hash.String(),
|
||||||
@@ -267,7 +269,7 @@ func (s *covenantlessService) CompleteAsyncPayment(
|
|||||||
VOut: uint32(outIndex),
|
VOut: uint32(outIndex),
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: asyncPayData.receivers[outIndex].Pubkey,
|
Descriptor: asyncPayData.receivers[outIndex].Descriptor,
|
||||||
Amount: uint64(out.Value),
|
Amount: uint64(out.Value),
|
||||||
},
|
},
|
||||||
ExpireAt: asyncPayData.expireAt,
|
ExpireAt: asyncPayData.expireAt,
|
||||||
@@ -300,9 +302,14 @@ func (s *covenantlessService) CompleteAsyncPayment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) CreateAsyncPayment(
|
func (s *covenantlessService) CreateAsyncPayment(
|
||||||
ctx context.Context, inputs []domain.VtxoKey, receivers []domain.Receiver,
|
ctx context.Context, inputs []ports.Input, receivers []domain.Receiver,
|
||||||
) (string, []string, error) {
|
) (string, []string, error) {
|
||||||
vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, inputs)
|
vtxosKeys := make([]domain.VtxoKey, 0, len(inputs))
|
||||||
|
for _, in := range inputs {
|
||||||
|
vtxosKeys = append(vtxosKeys, in.VtxoKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, vtxosKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -310,6 +317,8 @@ func (s *covenantlessService) CreateAsyncPayment(
|
|||||||
return "", nil, fmt.Errorf("vtxos not found")
|
return "", nil, fmt.Errorf("vtxos not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vtxosInputs := make([]domain.Vtxo, 0, len(inputs))
|
||||||
|
|
||||||
expiration := vtxos[0].ExpireAt
|
expiration := vtxos[0].ExpireAt
|
||||||
for _, vtxo := range vtxos {
|
for _, vtxo := range vtxos {
|
||||||
if vtxo.Spent {
|
if vtxo.Spent {
|
||||||
@@ -327,10 +336,12 @@ func (s *covenantlessService) CreateAsyncPayment(
|
|||||||
if vtxo.ExpireAt < expiration {
|
if vtxo.ExpireAt < expiration {
|
||||||
expiration = vtxo.ExpireAt
|
expiration = vtxo.ExpireAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vtxosInputs = append(vtxosInputs, vtxo)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := s.builder.BuildAsyncPaymentTransactions(
|
res, err := s.builder.BuildAsyncPaymentTransactions(
|
||||||
vtxos, s.pubkey, receivers,
|
vtxosInputs, s.pubkey, receivers,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, fmt.Errorf("failed to build async payment txs: %s", err)
|
return "", nil, fmt.Errorf("failed to build async payment txs: %s", err)
|
||||||
@@ -352,144 +363,148 @@ func (s *covenantlessService) CreateAsyncPayment(
|
|||||||
return res.RedeemTx, res.UnconditionalForfeitTxs, nil
|
return res.RedeemTx, res.UnconditionalForfeitTxs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []Input) (string, error) {
|
func (s *covenantlessService) GetBoardingAddress(
|
||||||
vtxosInputs := make([]domain.VtxoKey, 0)
|
ctx context.Context, userPubkey *secp256k1.PublicKey,
|
||||||
boardingInputs := make([]Input, 0)
|
) (address string, descriptor string, err error) {
|
||||||
|
vtxoScript := &bitcointree.DefaultVtxoScript{
|
||||||
for _, input := range inputs {
|
Asp: s.pubkey,
|
||||||
if input.IsVtxo() {
|
Owner: userPubkey,
|
||||||
vtxosInputs = append(vtxosInputs, input.VtxoKey())
|
ExitDelay: uint(s.boardingExitDelay),
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boardingInputs = append(boardingInputs, input)
|
tapKey, _, err := vtxoScript.TapTree()
|
||||||
}
|
|
||||||
|
|
||||||
vtxos := make([]domain.Vtxo, 0)
|
|
||||||
if len(vtxosInputs) > 0 {
|
|
||||||
var err error
|
|
||||||
vtxos, err = s.repoManager.Vtxos().GetVtxos(ctx, vtxosInputs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", "", fmt.Errorf("failed to get taproot key: %s", err)
|
||||||
}
|
|
||||||
for _, v := range vtxos {
|
|
||||||
if v.Spent {
|
|
||||||
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Redeemed {
|
addr, err := btcutil.NewAddressTaproot(
|
||||||
return "", fmt.Errorf("input %s:%d already redeemed", v.Txid, v.VOut)
|
schnorr.SerializePubKey(tapKey), s.chainParams(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("failed to get address: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Spent {
|
return addr.EncodeAddress(), vtxoScript.ToDescriptor(), nil
|
||||||
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
|
}
|
||||||
}
|
|
||||||
}
|
func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Input) (string, error) {
|
||||||
}
|
vtxosInputs := make([]domain.Vtxo, 0)
|
||||||
|
boardingInputs := make([]ports.BoardingInput, 0)
|
||||||
|
|
||||||
boardingTxs := make(map[string]string, 0) // txid -> txhex
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
|
|
||||||
for _, in := range boardingInputs {
|
boardingTxs := make(map[string]wire.MsgTx, 0) // txid -> txhex
|
||||||
if _, ok := boardingTxs[in.Txid]; !ok {
|
|
||||||
|
for _, input := range inputs {
|
||||||
|
vtxosResult, err := s.repoManager.Vtxos().GetVtxos(ctx, []domain.VtxoKey{input.VtxoKey})
|
||||||
|
if err != nil || len(vtxosResult) == 0 {
|
||||||
|
// vtxo not found in db, check if it exists on-chain
|
||||||
|
if _, ok := boardingTxs[input.Txid]; !ok {
|
||||||
// check if the tx exists and is confirmed
|
// check if the tx exists and is confirmed
|
||||||
txhex, err := s.wallet.GetTransaction(ctx, in.Txid)
|
txhex, err := s.wallet.GetTransaction(ctx, input.Txid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get tx %s: %s", in.Txid, err)
|
return "", fmt.Errorf("failed to get tx %s: %s", input.Txid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, in.Txid)
|
var tx wire.MsgTx
|
||||||
|
if err := tx.Deserialize(hex.NewDecoder(strings.NewReader(txhex))); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to deserialize tx %s: %s", input.Txid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, input.Txid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to check tx %s: %s", in.Txid, err)
|
return "", fmt.Errorf("failed to check tx %s: %s", input.Txid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !confirmed {
|
if !confirmed {
|
||||||
return "", fmt.Errorf("tx %s not confirmed", in.Txid)
|
return "", fmt.Errorf("tx %s not confirmed", input.Txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the exit path is available, forbid registering the boarding utxo
|
// if the exit path is available, forbid registering the boarding utxo
|
||||||
if blocktime+int64(s.boardingExitDelay) < now {
|
if blocktime+int64(s.boardingExitDelay) < now {
|
||||||
return "", fmt.Errorf("tx %s expired", in.Txid)
|
return "", fmt.Errorf("tx %s expired", input.Txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
boardingTxs[in.Txid] = txhex
|
boardingTxs[input.Txid] = tx
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos := make([]ports.BoardingInput, 0, len(boardingInputs))
|
tx := boardingTxs[input.Txid]
|
||||||
|
boardingInput, err := s.newBoardingInput(tx, input)
|
||||||
for _, in := range boardingInputs {
|
|
||||||
desc, err := in.GetDescriptor()
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Debugf("failed to parse boarding input descriptor")
|
|
||||||
return "", fmt.Errorf("failed to parse descriptor %s for input %s:%d", in.Descriptor, in.Txid, in.Index)
|
|
||||||
}
|
|
||||||
|
|
||||||
input, err := s.newBoardingInput(boardingTxs[in.Txid], in.Index, *desc)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Debugf("failed to create boarding input")
|
|
||||||
return "", fmt.Errorf("input %s:%d is not a valid boarding input", in.Txid, in.Index)
|
|
||||||
}
|
|
||||||
|
|
||||||
utxos = append(utxos, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
payment, err := domain.NewPayment(vtxos)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := s.paymentRequests.push(*payment, utxos); err != nil {
|
|
||||||
|
boardingInputs = append(boardingInputs, *boardingInput)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxo := vtxosResult[0]
|
||||||
|
if vtxo.Spent {
|
||||||
|
return "", fmt.Errorf("input %s:%d already spent", vtxo.Txid, vtxo.VOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vtxo.Redeemed {
|
||||||
|
return "", fmt.Errorf("input %s:%d already redeemed", vtxo.Txid, vtxo.VOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vtxo.Swept {
|
||||||
|
return "", fmt.Errorf("input %s:%d already swept", vtxo.Txid, vtxo.VOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxosInputs = append(vtxosInputs, vtxo)
|
||||||
|
}
|
||||||
|
|
||||||
|
payment, err := domain.NewPayment(vtxosInputs)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := s.paymentRequests.push(*payment, boardingInputs); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return payment.Id, nil
|
return payment.Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) newBoardingInput(txhex string, vout uint32, desc descriptor.TaprootDescriptor) (ports.BoardingInput, error) {
|
func (s *covenantlessService) newBoardingInput(tx wire.MsgTx, input ports.Input) (*ports.BoardingInput, error) {
|
||||||
var tx wire.MsgTx
|
if len(tx.TxOut) <= int(input.VtxoKey.VOut) {
|
||||||
|
|
||||||
if err := tx.Deserialize(hex.NewDecoder(strings.NewReader(txhex))); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to deserialize tx: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tx.TxOut) <= int(vout) {
|
|
||||||
return nil, fmt.Errorf("output not found")
|
return nil, fmt.Errorf("output not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
out := tx.TxOut[vout]
|
output := tx.TxOut[input.VtxoKey.VOut]
|
||||||
script := out.PkScript
|
|
||||||
|
|
||||||
scriptFromDescriptor, err := bitcointree.ComputeOutputScript(desc)
|
boardingScript, err := bitcointree.ParseVtxoScript(input.Descriptor)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to compute output script: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(script, scriptFromDescriptor) {
|
|
||||||
return nil, fmt.Errorf("descriptor does not match script in transaction output")
|
|
||||||
}
|
|
||||||
|
|
||||||
pubkey, timeout, err := descriptor.ParseBoardingDescriptor(desc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err)
|
return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if timeout != uint(s.boardingExitDelay) {
|
tapKey, _, err := boardingScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get taproot key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedScriptPubKey, err := common.P2TRScript(tapKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get script pubkey: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(output.PkScript, expectedScriptPubKey) {
|
||||||
|
return nil, fmt.Errorf("descriptor does not match script in transaction output")
|
||||||
|
}
|
||||||
|
|
||||||
|
if defaultVtxoScript, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok {
|
||||||
|
if !bytes.Equal(schnorr.SerializePubKey(defaultVtxoScript.Asp), schnorr.SerializePubKey(s.pubkey)) {
|
||||||
|
return nil, fmt.Errorf("invalid boarding descriptor, ASP mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
if defaultVtxoScript.ExitDelay != uint(s.boardingExitDelay) {
|
||||||
return nil, fmt.Errorf("invalid boarding descriptor, timeout mismatch")
|
return nil, fmt.Errorf("invalid boarding descriptor, timeout mismatch")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
_, expectedScript, err := s.builder.GetBoardingScript(pubkey, s.pubkey)
|
return nil, fmt.Errorf("only default vtxo script is supported for boarding")
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get boarding script: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(script, expectedScript) {
|
return &ports.BoardingInput{
|
||||||
return nil, fmt.Errorf("invalid boarding input output script")
|
Amount: uint64(output.Value),
|
||||||
}
|
Input: input,
|
||||||
|
|
||||||
return &boardingInput{
|
|
||||||
txId: tx.TxHash(),
|
|
||||||
vout: vout,
|
|
||||||
boardingPubKey: pubkey,
|
|
||||||
amount: uint64(out.Value),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,27 +599,16 @@ func (s *covenantlessService) GetInfo(ctx context.Context) (*ServiceInfo, error)
|
|||||||
Network: s.network.Name,
|
Network: s.network.Name,
|
||||||
Dust: dust,
|
Dust: dust,
|
||||||
BoardingDescriptorTemplate: fmt.Sprintf(
|
BoardingDescriptorTemplate: fmt.Sprintf(
|
||||||
descriptor.BoardingDescriptorTemplate,
|
descriptor.DefaultVtxoDescriptorTemplate,
|
||||||
hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()),
|
hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()),
|
||||||
hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)),
|
|
||||||
"USER",
|
"USER",
|
||||||
|
hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)),
|
||||||
s.boardingExitDelay,
|
s.boardingExitDelay,
|
||||||
"USER",
|
"USER",
|
||||||
),
|
),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) GetBoardingAddress(
|
|
||||||
ctx context.Context, userPubkey *secp256k1.PublicKey,
|
|
||||||
) (string, error) {
|
|
||||||
addr, _, err := s.builder.GetBoardingScript(userPubkey, s.pubkey)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to compute boarding script: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *covenantlessService) RegisterCosignerPubkey(ctx context.Context, paymentId string, pubkey string) error {
|
func (s *covenantlessService) RegisterCosignerPubkey(ctx context.Context, paymentId string, pubkey string) error {
|
||||||
pubkeyBytes, err := hex.DecodeString(pubkey)
|
pubkeyBytes, err := hex.DecodeString(pubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -944,14 +948,22 @@ func (s *covenantlessService) startFinalization() {
|
|||||||
|
|
||||||
var forfeitTxs, connectors []string
|
var forfeitTxs, connectors []string
|
||||||
|
|
||||||
|
minRelayFeeRate := s.wallet.MinRelayFeeRate(ctx)
|
||||||
|
|
||||||
if needForfeits {
|
if needForfeits {
|
||||||
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(s.pubkey, unsignedRoundTx, payments)
|
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(s.pubkey, unsignedRoundTx, payments, 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")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Debugf("forfeit transactions created for round %s", round.Id)
|
log.Debugf("forfeit transactions created for round %s", round.Id)
|
||||||
|
|
||||||
|
if err := s.forfeitTxs.push(forfeitTxs); err != nil {
|
||||||
|
round.Fail(fmt.Errorf("failed to store forfeit txs: %s", err))
|
||||||
|
log.WithError(err).Warn("failed to store forfeit txs")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := round.StartFinalization(
|
if _, err := round.StartFinalization(
|
||||||
@@ -962,8 +974,6 @@ func (s *covenantlessService) startFinalization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.forfeitTxs.push(forfeitTxs)
|
|
||||||
|
|
||||||
log.Debugf("started finalization stage for round: %s", round.Id)
|
log.Debugf("started finalization stage for round: %s", round.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1244,13 +1254,12 @@ func (s *covenantlessService) propagateEvents(round *domain.Round) {
|
|||||||
lastEvent := round.Events()[len(round.Events())-1]
|
lastEvent := round.Events()[len(round.Events())-1]
|
||||||
switch e := lastEvent.(type) {
|
switch e := lastEvent.(type) {
|
||||||
case domain.RoundFinalizationStarted:
|
case domain.RoundFinalizationStarted:
|
||||||
forfeitTxs := s.forfeitTxs.view()
|
|
||||||
ev := domain.RoundFinalizationStarted{
|
ev := domain.RoundFinalizationStarted{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
CongestionTree: e.CongestionTree,
|
CongestionTree: e.CongestionTree,
|
||||||
Connectors: e.Connectors,
|
Connectors: e.Connectors,
|
||||||
PoolTx: e.PoolTx,
|
PoolTx: e.PoolTx,
|
||||||
UnsignedForfeitTxs: forfeitTxs,
|
MinRelayFeeRate: int64(s.wallet.MinRelayFeeRate(context.Background())),
|
||||||
}
|
}
|
||||||
s.lastEvent = ev
|
s.lastEvent = ev
|
||||||
s.eventsCh <- ev
|
s.eventsCh <- ev
|
||||||
@@ -1291,39 +1300,55 @@ 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 {
|
||||||
for _, p := range round.Payments {
|
desc := ""
|
||||||
var pubkey string
|
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
|
for _, p := range round.Payments {
|
||||||
|
if found {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
for _, r := range p.Receivers {
|
for _, r := range p.Receivers {
|
||||||
if r.IsOnchain() {
|
if r.IsOnchain() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, _ := hex.DecodeString(r.Pubkey)
|
vtxoScript, err := bitcointree.ParseVtxoScript(r.Descriptor)
|
||||||
pk, _ := secp256k1.ParsePubKey(buf)
|
|
||||||
script, err := s.builder.GetVtxoScript(pk, s.pubkey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Warn("failed to get vtxo script")
|
log.WithError(err).Warn("failed to parse vtxo descriptor")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tapKey, _, err := vtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Warn("failed to compute vtxo tap key")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := common.P2TRScript(tapKey)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Warn("failed to create vtxo scriptpubkey")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Equal(script, out.PkScript) {
|
if bytes.Equal(script, out.PkScript) {
|
||||||
found = true
|
found = true
|
||||||
pubkey = r.Pubkey
|
desc = r.Descriptor
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if found {
|
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{Pubkey: pubkey, Amount: uint64(out.Value)},
|
Receiver: domain.Receiver{Descriptor: desc, Amount: uint64(out.Value)},
|
||||||
PoolTx: round.Txid,
|
PoolTx: round.Txid,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return vtxos
|
return vtxos
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1391,16 +1416,19 @@ func (s *covenantlessService) restoreWatchingVtxos() error {
|
|||||||
|
|
||||||
func (s *covenantlessService) extractVtxosScripts(vtxos []domain.Vtxo) ([]string, error) {
|
func (s *covenantlessService) extractVtxosScripts(vtxos []domain.Vtxo) ([]string, error) {
|
||||||
indexedScripts := make(map[string]struct{})
|
indexedScripts := make(map[string]struct{})
|
||||||
|
|
||||||
for _, vtxo := range vtxos {
|
for _, vtxo := range vtxos {
|
||||||
buf, err := hex.DecodeString(vtxo.Pubkey)
|
vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Receiver.Descriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
userPubkey, err := secp256k1.ParsePubKey(buf)
|
|
||||||
|
tapKey, _, err := vtxoScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
script, err := s.builder.GetVtxoScript(userPubkey, s.pubkey)
|
|
||||||
|
script, err := common.P2TRScript(tapKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1427,6 +1455,19 @@ func (s *covenantlessService) saveEvents(
|
|||||||
return s.repoManager.Rounds().AddOrUpdateRound(ctx, *round)
|
return s.repoManager.Rounds().AddOrUpdateRound(ctx, *round)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *covenantlessService) chainParams() *chaincfg.Params {
|
||||||
|
switch s.network.Name {
|
||||||
|
case common.Bitcoin.Name:
|
||||||
|
return &chaincfg.MainNetParams
|
||||||
|
case common.BitcoinTestNet.Name:
|
||||||
|
return &chaincfg.TestNet3Params
|
||||||
|
case common.BitcoinRegTest.Name:
|
||||||
|
return &chaincfg.RegressionNetParams
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) reactToFraud(ctx context.Context, vtxo domain.Vtxo, mutx *sync.Mutex) error {
|
func (s *covenantlessService) reactToFraud(ctx context.Context, vtxo domain.Vtxo, mutx *sync.Mutex) error {
|
||||||
mutx.Lock()
|
mutx.Lock()
|
||||||
defer mutx.Unlock()
|
defer mutx.Unlock()
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ package application
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"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/ark-network/ark/server/internal/core/ports"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ var (
|
|||||||
type Service interface {
|
type Service interface {
|
||||||
Start() error
|
Start() error
|
||||||
Stop()
|
Stop()
|
||||||
SpendVtxos(ctx context.Context, inputs []Input) (string, error)
|
SpendVtxos(ctx context.Context, inputs []ports.Input) (string, error)
|
||||||
ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error
|
ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error
|
||||||
SignVtxos(ctx context.Context, forfeitTxs []string) error
|
SignVtxos(ctx context.Context, forfeitTxs []string) error
|
||||||
SignRoundTx(ctx context.Context, roundTx string) error
|
SignRoundTx(ctx context.Context, roundTx string) error
|
||||||
@@ -33,12 +32,14 @@ type Service interface {
|
|||||||
GetInfo(ctx context.Context) (*ServiceInfo, error)
|
GetInfo(ctx context.Context) (*ServiceInfo, error)
|
||||||
// Async payments
|
// Async payments
|
||||||
CreateAsyncPayment(
|
CreateAsyncPayment(
|
||||||
ctx context.Context, inputs []domain.VtxoKey, receivers []domain.Receiver,
|
ctx context.Context, inputs []ports.Input, receivers []domain.Receiver,
|
||||||
) (string, []string, error)
|
) (string, []string, error)
|
||||||
CompleteAsyncPayment(
|
CompleteAsyncPayment(
|
||||||
ctx context.Context, redeemTx string, unconditionalForfeitTxs []string,
|
ctx context.Context, redeemTx string, unconditionalForfeitTxs []string,
|
||||||
) error
|
) error
|
||||||
GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, error)
|
GetBoardingAddress(
|
||||||
|
ctx context.Context, userPubkey *secp256k1.PublicKey,
|
||||||
|
) (address string, descriptor string, err error)
|
||||||
// Tree signing methods
|
// Tree signing methods
|
||||||
RegisterCosignerPubkey(ctx context.Context, paymentId string, ephemeralPublicKey string) error
|
RegisterCosignerPubkey(ctx context.Context, paymentId string, ephemeralPublicKey string) error
|
||||||
RegisterCosignerNonces(
|
RegisterCosignerNonces(
|
||||||
@@ -67,30 +68,6 @@ type WalletStatus struct {
|
|||||||
IsSynced bool
|
IsSynced bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Input struct {
|
|
||||||
Txid string
|
|
||||||
Index uint32
|
|
||||||
Descriptor string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i Input) IsVtxo() bool {
|
|
||||||
return len(i.Descriptor) <= 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i Input) VtxoKey() domain.VtxoKey {
|
|
||||||
return domain.VtxoKey{
|
|
||||||
Txid: i.Txid,
|
|
||||||
VOut: i.Index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i Input) GetDescriptor() (*descriptor.TaprootDescriptor, error) {
|
|
||||||
if i.IsVtxo() {
|
|
||||||
return nil, fmt.Errorf("input is not a boarding input")
|
|
||||||
}
|
|
||||||
return descriptor.ParseTaprootDescriptor(i.Descriptor)
|
|
||||||
}
|
|
||||||
|
|
||||||
type txOutpoint struct {
|
type txOutpoint struct {
|
||||||
txid string
|
txid string
|
||||||
vout uint32
|
vout uint32
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"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"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -180,14 +179,19 @@ func newForfeitTxsMap(txBuilder ports.TxBuilder) *forfeitTxsMap {
|
|||||||
return &forfeitTxsMap{&sync.RWMutex{}, make(map[string]*signedTx), txBuilder}
|
return &forfeitTxsMap{&sync.RWMutex{}, make(map[string]*signedTx), txBuilder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *forfeitTxsMap) push(txs []string) {
|
func (m *forfeitTxsMap) push(txs []string) error {
|
||||||
m.lock.Lock()
|
m.lock.Lock()
|
||||||
defer m.lock.Unlock()
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
signed, txid, _ := m.builder.VerifyTapscriptPartialSigs(tx)
|
txid, err := m.builder.GetTxID(tx)
|
||||||
m.forfeitTxs[txid] = &signedTx{tx, signed}
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
m.forfeitTxs[txid] = &signedTx{tx, false}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *forfeitTxsMap) sign(txs []string) error {
|
func (m *forfeitTxsMap) sign(txs []string) error {
|
||||||
@@ -229,17 +233,6 @@ func (m *forfeitTxsMap) pop() (signed, unsigned []string) {
|
|||||||
return signed, unsigned
|
return signed, unsigned
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *forfeitTxsMap) view() []string {
|
|
||||||
m.lock.RLock()
|
|
||||||
defer m.lock.RUnlock()
|
|
||||||
|
|
||||||
txs := make([]string, 0, len(m.forfeitTxs))
|
|
||||||
for _, tx := range m.forfeitTxs {
|
|
||||||
txs = append(txs, tx.tx)
|
|
||||||
}
|
|
||||||
return txs
|
|
||||||
}
|
|
||||||
|
|
||||||
// onchainOutputs iterates over all the nodes' outputs in the congestion tree and checks their onchain state
|
// onchainOutputs iterates over all the nodes' outputs in the congestion tree and checks their onchain state
|
||||||
// returns the sweepable outputs as ports.SweepInput mapped by their expiration time
|
// returns the sweepable outputs as ports.SweepInput mapped by their expiration time
|
||||||
func findSweepableOutputs(
|
func findSweepableOutputs(
|
||||||
@@ -313,26 +306,3 @@ func getSpentVtxos(payments map[string]domain.Payment) []domain.VtxoKey {
|
|||||||
}
|
}
|
||||||
return vtxos
|
return vtxos
|
||||||
}
|
}
|
||||||
|
|
||||||
type boardingInput struct {
|
|
||||||
txId chainhash.Hash
|
|
||||||
vout uint32
|
|
||||||
boardingPubKey *secp256k1.PublicKey
|
|
||||||
amount uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b boardingInput) GetHash() chainhash.Hash {
|
|
||||||
return b.txId
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b boardingInput) GetIndex() uint32 {
|
|
||||||
return b.vout
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b boardingInput) GetAmount() uint64 {
|
|
||||||
return b.amount
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b boardingInput) GetBoardingPubkey() *secp256k1.PublicKey {
|
|
||||||
return b.boardingPubKey
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ type RoundFinalizationStarted struct {
|
|||||||
CongestionTree tree.CongestionTree // BTC: signed
|
CongestionTree tree.CongestionTree // BTC: signed
|
||||||
Connectors []string
|
Connectors []string
|
||||||
ConnectorAddress string
|
ConnectorAddress string
|
||||||
UnsignedForfeitTxs []string
|
|
||||||
PoolTx string
|
PoolTx string
|
||||||
|
MinRelayFeeRate int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoundFinalized struct {
|
type RoundFinalized struct {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ 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.Pubkey) <= 0 {
|
if len(r.OnchainAddress) <= 0 && len(r.Descriptor) <= 0 {
|
||||||
return fmt.Errorf("missing receiver destination")
|
return fmt.Errorf("missing receiver destination")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ func (k VtxoKey) Hash() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Receiver struct {
|
type Receiver struct {
|
||||||
Pubkey string
|
Descriptor string
|
||||||
Amount uint64
|
Amount uint64
|
||||||
OnchainAddress string
|
OnchainAddress string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
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(
|
||||||
|
descriptor.DefaultVtxoDescriptorTemplate,
|
||||||
|
"030000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
512,
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
)
|
||||||
|
|
||||||
var inputs = []domain.Vtxo{
|
var inputs = []domain.Vtxo{
|
||||||
{
|
{
|
||||||
VtxoKey: domain.VtxoKey{
|
VtxoKey: domain.VtxoKey{
|
||||||
@@ -14,7 +25,7 @@ var inputs = []domain.Vtxo{
|
|||||||
VOut: 0,
|
VOut: 0,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: "030000000000000000000000000000000000000000000000000000000000000001",
|
Descriptor: desc,
|
||||||
Amount: 1000,
|
Amount: 1000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -40,11 +51,11 @@ func TestPayment(t *testing.T) {
|
|||||||
|
|
||||||
err = payment.AddReceivers([]domain.Receiver{
|
err = payment.AddReceivers([]domain.Receiver{
|
||||||
{
|
{
|
||||||
Pubkey: "030000000000000000000000000000000000000000000000000000000000000001",
|
Descriptor: desc,
|
||||||
Amount: 450,
|
Amount: 450,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
Descriptor: desc,
|
||||||
Amount: 550,
|
Amount: 550,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,27 +14,29 @@ var (
|
|||||||
payments = []domain.Payment{
|
payments = []domain.Payment{
|
||||||
{
|
{
|
||||||
Id: "0",
|
Id: "0",
|
||||||
Inputs: []domain.Vtxo{{
|
Inputs: []domain.Vtxo{
|
||||||
|
{
|
||||||
VtxoKey: domain.VtxoKey{
|
VtxoKey: domain.VtxoKey{
|
||||||
Txid: txid,
|
Txid: txid,
|
||||||
VOut: 0,
|
VOut: 0,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: pubkey,
|
Descriptor: desc,
|
||||||
Amount: 2000,
|
Amount: 2000,
|
||||||
},
|
},
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
Receivers: []domain.Receiver{
|
Receivers: []domain.Receiver{
|
||||||
{
|
{
|
||||||
Pubkey: pubkey,
|
Descriptor: desc,
|
||||||
Amount: 700,
|
Amount: 700,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Pubkey: pubkey,
|
Descriptor: desc,
|
||||||
Amount: 700,
|
Amount: 700,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Pubkey: pubkey,
|
Descriptor: desc,
|
||||||
Amount: 600,
|
Amount: 600,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -48,7 +50,7 @@ var (
|
|||||||
VOut: 0,
|
VOut: 0,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: pubkey,
|
Descriptor: desc,
|
||||||
Amount: 1000,
|
Amount: 1000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -58,13 +60,13 @@ var (
|
|||||||
VOut: 0,
|
VOut: 0,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: pubkey,
|
Descriptor: desc,
|
||||||
Amount: 1000,
|
Amount: 1000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Receivers: []domain.Receiver{{
|
Receivers: []domain.Receiver{{
|
||||||
Pubkey: pubkey,
|
Descriptor: desc,
|
||||||
Amount: 2000,
|
Amount: 2000,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
@@ -72,7 +74,6 @@ var (
|
|||||||
emptyPtx = "cHNldP8BAgQCAAAAAQQBAAEFAQABBgEDAfsEAgAAAAA="
|
emptyPtx = "cHNldP8BAgQCAAAAAQQBAAEFAQABBgEDAfsEAgAAAAA="
|
||||||
emptyTx = "0200000000000000000000"
|
emptyTx = "0200000000000000000000"
|
||||||
txid = "0000000000000000000000000000000000000000000000000000000000000000"
|
txid = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
pubkey = "030000000000000000000000000000000000000000000000000000000000000001"
|
|
||||||
congestionTree = tree.CongestionTree{
|
congestionTree = tree.CongestionTree{
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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/btcsuite/btcd/chaincfg/chainhash"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SweepInput interface {
|
type SweepInput interface {
|
||||||
@@ -16,11 +17,14 @@ type SweepInput interface {
|
|||||||
GetInternalKey() *secp256k1.PublicKey
|
GetInternalKey() *secp256k1.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
type BoardingInput interface {
|
type Input struct {
|
||||||
GetAmount() uint64
|
domain.VtxoKey
|
||||||
GetIndex() uint32
|
Descriptor string
|
||||||
GetHash() chainhash.Hash
|
}
|
||||||
GetBoardingPubkey() *secp256k1.PublicKey
|
|
||||||
|
type BoardingInput struct {
|
||||||
|
Input
|
||||||
|
Amount uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type TxBuilder interface {
|
type TxBuilder interface {
|
||||||
@@ -28,9 +32,8 @@ 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,
|
||||||
) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error)
|
) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error)
|
||||||
BuildForfeitTxs(aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment) (connectors []string, forfeitTxs []string, err error)
|
BuildForfeitTxs(aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment, minRelayFeeRate chainfee.SatPerKVByte) (connectors []string, forfeitTxs []string, err error)
|
||||||
BuildSweepTx(inputs []SweepInput) (signedSweepTx string, err error)
|
BuildSweepTx(inputs []SweepInput) (signedSweepTx string, err error)
|
||||||
GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error)
|
|
||||||
GetSweepInput(parentblocktime int64, node tree.Node) (expirationtime int64, sweepInput SweepInput, err error)
|
GetSweepInput(parentblocktime int64, node tree.Node) (expirationtime int64, sweepInput SweepInput, err error)
|
||||||
FinalizeAndExtract(tx string) (txhex string, err error)
|
FinalizeAndExtract(tx string) (txhex string, err error)
|
||||||
VerifyTapscriptPartialSigs(tx string) (valid bool, txid string, err error)
|
VerifyTapscriptPartialSigs(tx string) (valid bool, txid string, err error)
|
||||||
@@ -40,6 +43,6 @@ type TxBuilder interface {
|
|||||||
vtxosToSpend []domain.Vtxo,
|
vtxosToSpend []domain.Vtxo,
|
||||||
aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver,
|
aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver,
|
||||||
) (*domain.AsyncPaymentTxs, error)
|
) (*domain.AsyncPaymentTxs, error)
|
||||||
GetBoardingScript(userPubkey, aspPubkey *secp256k1.PublicKey) (addr string, script []byte, err error)
|
|
||||||
VerifyAndCombinePartialTx(dest string, src string) (string, error)
|
VerifyAndCombinePartialTx(dest string, src string) (string, error)
|
||||||
|
GetTxID(tx string) (string, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNonFinalBIP68 is returned when a transaction spending a CSV-locked output is not final.
|
// ErrNonFinalBIP68 is returned when a transaction spending a CSV-locked output is not final.
|
||||||
@@ -30,6 +31,7 @@ type WalletService interface {
|
|||||||
WaitForSync(ctx context.Context, txid string) error
|
WaitForSync(ctx context.Context, txid string) error
|
||||||
EstimateFees(ctx context.Context, psbt string) (uint64, error)
|
EstimateFees(ctx context.Context, psbt string) (uint64, error)
|
||||||
MinRelayFee(ctx context.Context, vbytes uint64) (uint64, error)
|
MinRelayFee(ctx context.Context, vbytes uint64) (uint64, error)
|
||||||
|
MinRelayFeeRate(ctx context.Context) chainfee.SatPerKVByte
|
||||||
ListConnectorUtxos(ctx context.Context, connectorAddress string) ([]TxInput, error)
|
ListConnectorUtxos(ctx context.Context, connectorAddress string) ([]TxInput, error)
|
||||||
MainAccountBalance(ctx context.Context) (uint64, uint64, error)
|
MainAccountBalance(ctx context.Context) (uint64, uint64, error)
|
||||||
ConnectorsAccountBalance(ctx context.Context) (uint64, uint64, error)
|
ConnectorsAccountBalance(ctx context.Context) (uint64, uint64, error)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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"
|
||||||
@@ -100,7 +101,13 @@ 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 {
|
||||||
query = query.And("Pubkey").Eq(pubkey)
|
if len(pubkey) == 66 {
|
||||||
|
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,12 +4,14 @@ 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"
|
||||||
@@ -22,8 +24,26 @@ import (
|
|||||||
const (
|
const (
|
||||||
emptyPtx = "cHNldP8BAgQCAAAAAQQBAAEFAQABBgEDAfsEAgAAAAA="
|
emptyPtx = "cHNldP8BAgQCAAAAAQQBAAEFAQABBgEDAfsEAgAAAAA="
|
||||||
emptyTx = "0200000000000000000000"
|
emptyTx = "0200000000000000000000"
|
||||||
pubkey1 = "0300000000000000000000000000000000000000000000000000000000000000001"
|
pubkey1 = "00000000000000000000000000000000000000000000000000000000000000001"
|
||||||
pubkey2 = "0200000000000000000000000000000000000000000000000000000000000000002"
|
pubkey2 = "00000000000000000000000000000000000000000000000000000000000000002"
|
||||||
|
)
|
||||||
|
|
||||||
|
var desc1 = fmt.Sprintf(
|
||||||
|
descriptor.DefaultVtxoDescriptorTemplate,
|
||||||
|
randomString(66),
|
||||||
|
pubkey1,
|
||||||
|
pubkey1,
|
||||||
|
512,
|
||||||
|
pubkey1,
|
||||||
|
)
|
||||||
|
|
||||||
|
var desc2 = fmt.Sprintf(
|
||||||
|
descriptor.DefaultVtxoDescriptorTemplate,
|
||||||
|
randomString(66),
|
||||||
|
pubkey2,
|
||||||
|
pubkey2,
|
||||||
|
512,
|
||||||
|
pubkey2,
|
||||||
)
|
)
|
||||||
|
|
||||||
var congestionTree = [][]tree.Node{
|
var congestionTree = [][]tree.Node{
|
||||||
@@ -251,19 +271,20 @@ func testRoundRepository(t *testing.T, svc ports.RepoManager) {
|
|||||||
PoolTx: randomString(32),
|
PoolTx: randomString(32),
|
||||||
ExpireAt: 7980322,
|
ExpireAt: 7980322,
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: randomString(36),
|
Descriptor: randomString(120),
|
||||||
Amount: 300,
|
Amount: 300,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Receivers: []domain.Receiver{{
|
Receivers: []domain.Receiver{{
|
||||||
Pubkey: randomString(36),
|
Descriptor: randomString(120),
|
||||||
Amount: 300,
|
Amount: 300,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Id: uuid.New().String(),
|
Id: uuid.New().String(),
|
||||||
Inputs: []domain.Vtxo{
|
Inputs: []domain.Vtxo{
|
||||||
|
|
||||||
{
|
{
|
||||||
VtxoKey: domain.VtxoKey{
|
VtxoKey: domain.VtxoKey{
|
||||||
Txid: randomString(32),
|
Txid: randomString(32),
|
||||||
@@ -272,18 +293,18 @@ func testRoundRepository(t *testing.T, svc ports.RepoManager) {
|
|||||||
PoolTx: randomString(32),
|
PoolTx: randomString(32),
|
||||||
ExpireAt: 7980322,
|
ExpireAt: 7980322,
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: randomString(36),
|
Descriptor: randomString(120),
|
||||||
Amount: 600,
|
Amount: 600,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Receivers: []domain.Receiver{
|
Receivers: []domain.Receiver{
|
||||||
{
|
{
|
||||||
Pubkey: randomString(36),
|
Descriptor: randomString(120),
|
||||||
Amount: 400,
|
Amount: 400,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Pubkey: randomString(34),
|
Descriptor: randomString(120),
|
||||||
Amount: 200,
|
Amount: 200,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -350,7 +371,7 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
|
|||||||
VOut: 0,
|
VOut: 0,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: pubkey1,
|
Descriptor: desc1,
|
||||||
Amount: 1000,
|
Amount: 1000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -360,7 +381,7 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
|
|||||||
VOut: 1,
|
VOut: 1,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: pubkey1,
|
Descriptor: desc1,
|
||||||
Amount: 2000,
|
Amount: 2000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -371,7 +392,7 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
|
|||||||
VOut: 1,
|
VOut: 1,
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: pubkey2,
|
Descriptor: desc2,
|
||||||
Amount: 2000,
|
Amount: 2000,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -531,7 +552,7 @@ type sortReceivers []domain.Receiver
|
|||||||
|
|
||||||
func (a sortReceivers) Len() int { return len(a) }
|
func (a sortReceivers) Len() int { return len(a) }
|
||||||
func (a sortReceivers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a sortReceivers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
func (a sortReceivers) Less(i, j int) bool { return a[i].Pubkey < a[j].Pubkey }
|
func (a sortReceivers) Less(i, j int) bool { return a[i].Amount < a[j].Amount }
|
||||||
|
|
||||||
type sortStrings []string
|
type sortStrings []string
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
@@ -165,7 +165,7 @@ func (r *roundRepository) AddOrUpdateRound(ctx context.Context, round domain.Rou
|
|||||||
ctx,
|
ctx,
|
||||||
queries.UpsertReceiverParams{
|
queries.UpsertReceiverParams{
|
||||||
PaymentID: payment.Id,
|
PaymentID: payment.Id,
|
||||||
Pubkey: receiver.Pubkey,
|
Descriptor: receiver.Descriptor,
|
||||||
Amount: int64(receiver.Amount),
|
Amount: int64(receiver.Amount),
|
||||||
OnchainAddress: receiver.OnchainAddress,
|
OnchainAddress: receiver.OnchainAddress,
|
||||||
},
|
},
|
||||||
@@ -320,7 +320,7 @@ 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{
|
||||||
Pubkey: row.Pubkey.String,
|
Descriptor: row.Descriptor.String,
|
||||||
Amount: uint64(row.Amount.Int64),
|
Amount: uint64(row.Amount.Int64),
|
||||||
OnchainAddress: row.OnchainAddress.String,
|
OnchainAddress: row.OnchainAddress.String,
|
||||||
}
|
}
|
||||||
@@ -413,8 +413,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.Pubkey.Valid && v.receiver.Amount.Valid {
|
if v.receiver.Descriptor.Valid && v.receiver.Amount.Valid {
|
||||||
if rcv.Pubkey == v.receiver.Pubkey.String && int64(rcv.Amount) == v.receiver.Amount.Int64 {
|
if rcv.Descriptor == v.receiver.Descriptor.String && int64(rcv.Amount) == v.receiver.Amount.Int64 {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -470,7 +470,7 @@ func rowToPaymentVtxoVw(row queries.PaymentVtxoVw) domain.Vtxo {
|
|||||||
VOut: uint32(row.Vout.Int64),
|
VOut: uint32(row.Vout.Int64),
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: row.Pubkey.String,
|
Descriptor: row.Descriptor.String,
|
||||||
Amount: uint64(row.Amount.Int64),
|
Amount: uint64(row.Amount.Int64),
|
||||||
},
|
},
|
||||||
PoolTx: row.PoolTx.String,
|
PoolTx: row.PoolTx.String,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type Payment struct {
|
|||||||
|
|
||||||
type PaymentReceiverVw struct {
|
type PaymentReceiverVw struct {
|
||||||
PaymentID sql.NullString
|
PaymentID sql.NullString
|
||||||
Pubkey sql.NullString
|
Descriptor sql.NullString
|
||||||
Amount sql.NullInt64
|
Amount sql.NullInt64
|
||||||
OnchainAddress sql.NullString
|
OnchainAddress sql.NullString
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,6 @@ type PaymentReceiverVw struct {
|
|||||||
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
|
||||||
@@ -33,11 +32,12 @@ type PaymentVtxoVw struct {
|
|||||||
ExpireAt sql.NullInt64
|
ExpireAt sql.NullInt64
|
||||||
PaymentID sql.NullString
|
PaymentID sql.NullString
|
||||||
RedeemTx sql.NullString
|
RedeemTx sql.NullString
|
||||||
|
Descriptor sql.NullString
|
||||||
}
|
}
|
||||||
|
|
||||||
type Receiver struct {
|
type Receiver struct {
|
||||||
PaymentID string
|
PaymentID string
|
||||||
Pubkey string
|
Descriptor string
|
||||||
Amount int64
|
Amount int64
|
||||||
OnchainAddress string
|
OnchainAddress string
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,6 @@ type UncondForfeitTxVw 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
|
||||||
@@ -115,4 +114,5 @@ type Vtxo struct {
|
|||||||
ExpireAt int64
|
ExpireAt int64
|
||||||
PaymentID sql.NullString
|
PaymentID sql.NullString
|
||||||
RedeemTx sql.NullString
|
RedeemTx sql.NullString
|
||||||
|
Descriptor sql.NullString
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx,
|
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,
|
||||||
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
|
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
|
||||||
FROM vtxo
|
FROM vtxo
|
||||||
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
||||||
@@ -78,7 +78,6 @@ 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,
|
||||||
@@ -88,6 +87,7 @@ 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.UncondForfeitTxVw.ID,
|
&i.UncondForfeitTxVw.ID,
|
||||||
&i.UncondForfeitTxVw.Tx,
|
&i.UncondForfeitTxVw.Tx,
|
||||||
&i.UncondForfeitTxVw.VtxoTxid,
|
&i.UncondForfeitTxVw.VtxoTxid,
|
||||||
@@ -108,11 +108,11 @@ func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]SelectNotRedeem
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectNotRedeemedVtxosWithPubkey = `-- name: SelectNotRedeemedVtxosWithPubkey :many
|
const selectNotRedeemedVtxosWithPubkey = `-- name: SelectNotRedeemedVtxosWithPubkey :many
|
||||||
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,
|
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,
|
||||||
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
|
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
|
||||||
FROM vtxo
|
FROM vtxo
|
||||||
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
||||||
WHERE redeemed = false AND pubkey = ?
|
WHERE redeemed = false AND INSTR(descriptor, ?) > 0
|
||||||
`
|
`
|
||||||
|
|
||||||
type SelectNotRedeemedVtxosWithPubkeyRow struct {
|
type SelectNotRedeemedVtxosWithPubkeyRow struct {
|
||||||
@@ -120,8 +120,8 @@ type SelectNotRedeemedVtxosWithPubkeyRow struct {
|
|||||||
UncondForfeitTxVw UncondForfeitTxVw
|
UncondForfeitTxVw UncondForfeitTxVw
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, pubkey string) ([]SelectNotRedeemedVtxosWithPubkeyRow, error) {
|
func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, instr string) ([]SelectNotRedeemedVtxosWithPubkeyRow, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, selectNotRedeemedVtxosWithPubkey, pubkey)
|
rows, err := q.db.QueryContext(ctx, selectNotRedeemedVtxosWithPubkey, instr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -132,7 +132,6 @@ func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, pubkey 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,
|
||||||
@@ -142,6 +141,7 @@ func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, pubkey s
|
|||||||
&i.Vtxo.ExpireAt,
|
&i.Vtxo.ExpireAt,
|
||||||
&i.Vtxo.PaymentID,
|
&i.Vtxo.PaymentID,
|
||||||
&i.Vtxo.RedeemTx,
|
&i.Vtxo.RedeemTx,
|
||||||
|
&i.Vtxo.Descriptor,
|
||||||
&i.UncondForfeitTxVw.ID,
|
&i.UncondForfeitTxVw.ID,
|
||||||
&i.UncondForfeitTxVw.Tx,
|
&i.UncondForfeitTxVw.Tx,
|
||||||
&i.UncondForfeitTxVw.VtxoTxid,
|
&i.UncondForfeitTxVw.VtxoTxid,
|
||||||
@@ -224,8 +224,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.pubkey, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
|
payment_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
|
||||||
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.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.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
|
||||||
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
|
||||||
@@ -276,12 +276,11 @@ 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.Pubkey,
|
&i.PaymentReceiverVw.Descriptor,
|
||||||
&i.PaymentReceiverVw.Amount,
|
&i.PaymentReceiverVw.Amount,
|
||||||
&i.PaymentReceiverVw.OnchainAddress,
|
&i.PaymentReceiverVw.OnchainAddress,
|
||||||
&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,
|
||||||
@@ -291,6 +290,7 @@ 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,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -309,8 +309,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.pubkey, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
|
payment_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
|
||||||
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.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.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
|
||||||
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
|
||||||
@@ -361,12 +361,11 @@ 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.Pubkey,
|
&i.PaymentReceiverVw.Descriptor,
|
||||||
&i.PaymentReceiverVw.Amount,
|
&i.PaymentReceiverVw.Amount,
|
||||||
&i.PaymentReceiverVw.OnchainAddress,
|
&i.PaymentReceiverVw.OnchainAddress,
|
||||||
&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,
|
||||||
@@ -376,6 +375,7 @@ 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,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -394,8 +394,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.pubkey, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
|
payment_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
|
||||||
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.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.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
|
||||||
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
|
||||||
@@ -446,12 +446,11 @@ 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.Pubkey,
|
&i.PaymentReceiverVw.Descriptor,
|
||||||
&i.PaymentReceiverVw.Amount,
|
&i.PaymentReceiverVw.Amount,
|
||||||
&i.PaymentReceiverVw.OnchainAddress,
|
&i.PaymentReceiverVw.OnchainAddress,
|
||||||
&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,
|
||||||
@@ -461,6 +460,7 @@ 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,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -476,7 +476,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.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx,
|
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,
|
||||||
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
|
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
|
||||||
FROM vtxo
|
FROM vtxo
|
||||||
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
||||||
@@ -500,7 +500,6 @@ 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,
|
||||||
@@ -510,6 +509,7 @@ 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.UncondForfeitTxVw.ID,
|
&i.UncondForfeitTxVw.ID,
|
||||||
&i.UncondForfeitTxVw.Tx,
|
&i.UncondForfeitTxVw.Tx,
|
||||||
&i.UncondForfeitTxVw.VtxoTxid,
|
&i.UncondForfeitTxVw.VtxoTxid,
|
||||||
@@ -533,8 +533,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.pubkey, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
|
payment_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
|
||||||
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.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.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
|
||||||
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
|
||||||
@@ -585,12 +585,11 @@ 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.Pubkey,
|
&i.PaymentReceiverVw.Descriptor,
|
||||||
&i.PaymentReceiverVw.Amount,
|
&i.PaymentReceiverVw.Amount,
|
||||||
&i.PaymentReceiverVw.OnchainAddress,
|
&i.PaymentReceiverVw.OnchainAddress,
|
||||||
&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,
|
||||||
@@ -600,6 +599,7 @@ 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,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -615,7 +615,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.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx,
|
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,
|
||||||
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
|
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
|
||||||
FROM vtxo
|
FROM vtxo
|
||||||
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
||||||
@@ -638,7 +638,6 @@ 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,
|
||||||
@@ -648,6 +647,7 @@ 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.UncondForfeitTxVw.ID,
|
&i.UncondForfeitTxVw.ID,
|
||||||
&i.UncondForfeitTxVw.Tx,
|
&i.UncondForfeitTxVw.Tx,
|
||||||
&i.UncondForfeitTxVw.VtxoTxid,
|
&i.UncondForfeitTxVw.VtxoTxid,
|
||||||
@@ -658,7 +658,7 @@ func (q *Queries) SelectVtxoByOutpoint(ctx context.Context, arg SelectVtxoByOutp
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectVtxosByPoolTxid = `-- name: SelectVtxosByPoolTxid :many
|
const selectVtxosByPoolTxid = `-- name: SelectVtxosByPoolTxid :many
|
||||||
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,
|
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,
|
||||||
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
|
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
|
||||||
FROM vtxo
|
FROM vtxo
|
||||||
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
||||||
@@ -682,7 +682,6 @@ 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,
|
||||||
@@ -692,6 +691,7 @@ 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.UncondForfeitTxVw.ID,
|
&i.UncondForfeitTxVw.ID,
|
||||||
&i.UncondForfeitTxVw.Tx,
|
&i.UncondForfeitTxVw.Tx,
|
||||||
&i.UncondForfeitTxVw.VtxoTxid,
|
&i.UncondForfeitTxVw.VtxoTxid,
|
||||||
@@ -757,16 +757,16 @@ 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, pubkey, amount, onchain_address) VALUES (?, ?, ?, ?)
|
INSERT INTO receiver (payment_id, descriptor, amount, onchain_address) VALUES (?, ?, ?, ?)
|
||||||
ON CONFLICT(payment_id, pubkey) DO UPDATE SET
|
ON CONFLICT(payment_id, descriptor) DO UPDATE SET
|
||||||
amount = EXCLUDED.amount,
|
amount = EXCLUDED.amount,
|
||||||
onchain_address = EXCLUDED.onchain_address,
|
onchain_address = EXCLUDED.onchain_address,
|
||||||
pubkey = EXCLUDED.pubkey
|
descriptor = EXCLUDED.descriptor
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpsertReceiverParams struct {
|
type UpsertReceiverParams struct {
|
||||||
PaymentID string
|
PaymentID string
|
||||||
Pubkey string
|
Descriptor string
|
||||||
Amount int64
|
Amount int64
|
||||||
OnchainAddress string
|
OnchainAddress string
|
||||||
}
|
}
|
||||||
@@ -774,7 +774,7 @@ type UpsertReceiverParams struct {
|
|||||||
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.Pubkey,
|
arg.Descriptor,
|
||||||
arg.Amount,
|
arg.Amount,
|
||||||
arg.OnchainAddress,
|
arg.OnchainAddress,
|
||||||
)
|
)
|
||||||
@@ -909,9 +909,9 @@ func (q *Queries) UpsertUnconditionalForfeitTx(ctx context.Context, arg UpsertUn
|
|||||||
}
|
}
|
||||||
|
|
||||||
const upsertVtxo = `-- name: UpsertVtxo :exec
|
const upsertVtxo = `-- name: UpsertVtxo :exec
|
||||||
INSERT INTO vtxo (txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, redeem_tx)
|
INSERT INTO vtxo (txid, vout, descriptor, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, redeem_tx)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET
|
||||||
pubkey = EXCLUDED.pubkey,
|
descriptor = EXCLUDED.descriptor,
|
||||||
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,
|
||||||
@@ -925,7 +925,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET
|
|||||||
type UpsertVtxoParams struct {
|
type UpsertVtxoParams struct {
|
||||||
Txid string
|
Txid string
|
||||||
Vout int64
|
Vout int64
|
||||||
Pubkey string
|
Descriptor sql.NullString
|
||||||
Amount int64
|
Amount int64
|
||||||
PoolTx string
|
PoolTx string
|
||||||
SpentBy string
|
SpentBy string
|
||||||
@@ -940,7 +940,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.Pubkey,
|
arg.Descriptor,
|
||||||
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, pubkey, amount, onchain_address) VALUES (?, ?, ?, ?)
|
INSERT INTO receiver (payment_id, descriptor, amount, onchain_address) VALUES (?, ?, ?, ?)
|
||||||
ON CONFLICT(payment_id, pubkey) DO UPDATE SET
|
ON CONFLICT(payment_id, descriptor) DO UPDATE SET
|
||||||
amount = EXCLUDED.amount,
|
amount = EXCLUDED.amount,
|
||||||
onchain_address = EXCLUDED.onchain_address,
|
onchain_address = EXCLUDED.onchain_address,
|
||||||
pubkey = EXCLUDED.pubkey;
|
descriptor = EXCLUDED.descriptor;
|
||||||
|
|
||||||
-- name: UpdateVtxoPaymentId :exec
|
-- name: UpdateVtxoPaymentId :exec
|
||||||
UPDATE vtxo SET payment_id = ? WHERE txid = ? AND vout = ?;
|
UPDATE vtxo SET payment_id = ? WHERE txid = ? AND vout = ?;
|
||||||
@@ -120,9 +120,9 @@ VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET
|
|||||||
position = EXCLUDED.position;
|
position = EXCLUDED.position;
|
||||||
|
|
||||||
-- name: UpsertVtxo :exec
|
-- name: UpsertVtxo :exec
|
||||||
INSERT INTO vtxo (txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, redeem_tx)
|
INSERT INTO vtxo (txid, vout, descriptor, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, redeem_tx)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET
|
||||||
pubkey = EXCLUDED.pubkey,
|
descriptor = EXCLUDED.descriptor,
|
||||||
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,
|
||||||
@@ -151,7 +151,7 @@ SELECT sqlc.embed(vtxo),
|
|||||||
sqlc.embed(uncond_forfeit_tx_vw)
|
sqlc.embed(uncond_forfeit_tx_vw)
|
||||||
FROM vtxo
|
FROM vtxo
|
||||||
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
|
||||||
WHERE redeemed = false AND pubkey = ?;
|
WHERE redeemed = false AND INSTR(descriptor, ?) > 0;
|
||||||
|
|
||||||
-- name: SelectVtxoByOutpoint :one
|
-- name: SelectVtxoByOutpoint :one
|
||||||
SELECT sqlc.embed(vtxo),
|
SELECT sqlc.embed(vtxo),
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func (v *vxtoRepository) AddVtxos(ctx context.Context, vtxos []domain.Vtxo) erro
|
|||||||
ctx, queries.UpsertVtxoParams{
|
ctx, queries.UpsertVtxoParams{
|
||||||
Txid: vtxo.Txid,
|
Txid: vtxo.Txid,
|
||||||
Vout: int64(vtxo.VOut),
|
Vout: int64(vtxo.VOut),
|
||||||
Pubkey: vtxo.Pubkey,
|
Descriptor: sql.NullString{String: vtxo.Descriptor, Valid: true},
|
||||||
Amount: int64(vtxo.Amount),
|
Amount: int64(vtxo.Amount),
|
||||||
PoolTx: vtxo.PoolTx,
|
PoolTx: vtxo.PoolTx,
|
||||||
SpentBy: vtxo.SpentBy,
|
SpentBy: vtxo.SpentBy,
|
||||||
@@ -100,6 +100,10 @@ func (v *vxtoRepository) GetAllVtxos(ctx context.Context, pubkey string) ([]doma
|
|||||||
|
|
||||||
var rows []vtxoWithUnconditionalForfeitTxs
|
var rows []vtxoWithUnconditionalForfeitTxs
|
||||||
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
|
||||||
@@ -294,7 +298,7 @@ func rowToVtxo(row queries.Vtxo, uncondForfeitTxs []queries.UncondForfeitTxVw) d
|
|||||||
VOut: uint32(row.Vout),
|
VOut: uint32(row.Vout),
|
||||||
},
|
},
|
||||||
Receiver: domain.Receiver{
|
Receiver: domain.Receiver{
|
||||||
Pubkey: row.Pubkey,
|
Descriptor: row.Descriptor.String,
|
||||||
Amount: uint64(row.Amount),
|
Amount: uint64(row.Amount),
|
||||||
},
|
},
|
||||||
PoolTx: row.PoolTx,
|
PoolTx: row.PoolTx,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package txbuilder
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
@@ -14,6 +13,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
"github.com/vulpemventures/go-elements/address"
|
"github.com/vulpemventures/go-elements/address"
|
||||||
"github.com/vulpemventures/go-elements/elementsutil"
|
"github.com/vulpemventures/go-elements/elementsutil"
|
||||||
"github.com/vulpemventures/go-elements/network"
|
"github.com/vulpemventures/go-elements/network"
|
||||||
@@ -27,7 +27,6 @@ type txBuilder struct {
|
|||||||
wallet ports.WalletService
|
wallet ports.WalletService
|
||||||
net common.Network
|
net common.Network
|
||||||
roundLifetime int64 // in seconds
|
roundLifetime int64 // in seconds
|
||||||
exitDelay int64 // in seconds
|
|
||||||
boardingExitDelay int64 // in seconds
|
boardingExitDelay int64 // in seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,27 +34,13 @@ func NewTxBuilder(
|
|||||||
wallet ports.WalletService,
|
wallet ports.WalletService,
|
||||||
net common.Network,
|
net common.Network,
|
||||||
roundLifetime int64,
|
roundLifetime int64,
|
||||||
exitDelay int64,
|
|
||||||
boardingExitDelay int64,
|
boardingExitDelay int64,
|
||||||
) ports.TxBuilder {
|
) ports.TxBuilder {
|
||||||
return &txBuilder{wallet, net, roundLifetime, exitDelay, boardingExitDelay}
|
return &txBuilder{wallet, net, roundLifetime, boardingExitDelay}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) GetBoardingScript(owner, asp *secp256k1.PublicKey) (string, []byte, error) {
|
func (b *txBuilder) GetTxID(tx string) (string, error) {
|
||||||
addr, script, _, err := b.getBoardingTaproot(owner, asp)
|
return getTxid(tx)
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr, script, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *txBuilder) GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error) {
|
|
||||||
outputScript, _, err := b.getLeafScriptAndTree(userPubkey, aspPubkey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return outputScript, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx string, err error) {
|
func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx string, err error) {
|
||||||
@@ -97,7 +82,11 @@ func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) BuildForfeitTxs(
|
func (b *txBuilder) BuildForfeitTxs(
|
||||||
aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment) (connectors []string, forfeitTxs []string, err error) {
|
aspPubkey *secp256k1.PublicKey,
|
||||||
|
poolTx string,
|
||||||
|
payments []domain.Payment,
|
||||||
|
minRelayFeeRate chainfee.SatPerKVByte,
|
||||||
|
) (connectors []string, forfeitTxs []string, err error) {
|
||||||
connectorAddress, err := b.getConnectorAddress(poolTx)
|
connectorAddress, err := b.getConnectorAddress(poolTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -118,7 +107,7 @@ func (b *txBuilder) BuildForfeitTxs(
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitTxs, err = b.createForfeitTxs(aspPubkey, payments, connectorTxs, connectorAmount)
|
forfeitTxs, err = b.createForfeitTxs(aspPubkey, payments, connectorTxs, connectorAmount, minRelayFeeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -159,8 +148,13 @@ func (b *txBuilder) BuildPoolTx(
|
|||||||
return "", nil, "", err
|
return "", nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
receivers, err := getOffchainReceivers(payments)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err = tree.CraftCongestionTree(
|
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err = tree.CraftCongestionTree(
|
||||||
b.onchainNetwork().AssetID, aspPubkey, getOffchainReceivers(payments), feeSatsPerNode, b.roundLifetime, b.exitDelay,
|
b.onchainNetwork().AssetID, aspPubkey, receivers, feeSatsPerNode, b.roundLifetime,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, "", err
|
return "", nil, "", err
|
||||||
@@ -270,7 +264,7 @@ func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, string, error)
|
|||||||
rootHash := tapLeaf.ControlBlock.RootHash(tapLeaf.Script)
|
rootHash := tapLeaf.ControlBlock.RootHash(tapLeaf.Script)
|
||||||
tapKeyFromControlBlock := taproot.ComputeTaprootOutputKey(tree.UnspendableKey(), rootHash[:])
|
tapKeyFromControlBlock := taproot.ComputeTaprootOutputKey(tree.UnspendableKey(), rootHash[:])
|
||||||
|
|
||||||
pkscript, err := p2trScript(tapKeyFromControlBlock)
|
pkscript, err := common.P2TRScript(tapKeyFromControlBlock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, txid, err
|
return false, txid, err
|
||||||
}
|
}
|
||||||
@@ -376,51 +370,13 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
return nil, fmt.Errorf("not implemented")
|
return nil, fmt.Errorf("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) getLeafScriptAndTree(
|
|
||||||
userPubkey, aspPubkey *secp256k1.PublicKey,
|
|
||||||
) ([]byte, *taproot.IndexedElementsTapScriptTree, error) {
|
|
||||||
redeemClosure := &tree.CSVSigClosure{
|
|
||||||
Pubkey: userPubkey,
|
|
||||||
Seconds: uint(b.exitDelay),
|
|
||||||
}
|
|
||||||
|
|
||||||
redeemLeaf, err := redeemClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitClosure := &tree.ForfeitClosure{
|
|
||||||
Pubkey: userPubkey,
|
|
||||||
AspPubkey: aspPubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitLeaf, err := forfeitClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
taprootTree := taproot.AssembleTaprootScriptTree(
|
|
||||||
*redeemLeaf, *forfeitLeaf,
|
|
||||||
)
|
|
||||||
|
|
||||||
root := taprootTree.RootNode.TapHash()
|
|
||||||
unspendableKey := tree.UnspendableKey()
|
|
||||||
taprootKey := taproot.ComputeTaprootOutputKey(unspendableKey, root[:])
|
|
||||||
|
|
||||||
outputScript, err := p2trScript(taprootKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputScript, taprootTree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *txBuilder) createPoolTx(
|
func (b *txBuilder) createPoolTx(
|
||||||
sharedOutputAmount uint64,
|
sharedOutputAmount uint64,
|
||||||
sharedOutputScript []byte,
|
sharedOutputScript []byte,
|
||||||
payments []domain.Payment,
|
payments []domain.Payment,
|
||||||
boardingInputs []ports.BoardingInput,
|
boardingInputs []ports.BoardingInput,
|
||||||
aspPubKey *secp256k1.PublicKey, connectorAddress string,
|
aspPubKey *secp256k1.PublicKey,
|
||||||
|
connectorAddress string,
|
||||||
sweptRounds []domain.Round,
|
sweptRounds []domain.Round,
|
||||||
) (*psetv2.Pset, error) {
|
) (*psetv2.Pset, error) {
|
||||||
aspScript, err := p2wpkhScript(aspPubKey, b.onchainNetwork())
|
aspScript, err := p2wpkhScript(aspPubKey, b.onchainNetwork())
|
||||||
@@ -487,7 +443,7 @@ func (b *txBuilder) createPoolTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, in := range boardingInputs {
|
for _, in := range boardingInputs {
|
||||||
targetAmount -= in.GetAmount()
|
targetAmount -= in.Amount
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
@@ -529,8 +485,8 @@ func (b *txBuilder) createPoolTx(
|
|||||||
if err := updater.AddInputs(
|
if err := updater.AddInputs(
|
||||||
[]psetv2.InputArgs{
|
[]psetv2.InputArgs{
|
||||||
{
|
{
|
||||||
Txid: in.GetHash().String(),
|
Txid: in.Txid,
|
||||||
TxIndex: in.GetIndex(),
|
TxIndex: in.VtxoKey.VOut,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -544,21 +500,27 @@ func (b *txBuilder) createPoolTx(
|
|||||||
return nil, fmt.Errorf("failed to convert asset to bytes: %s", err)
|
return nil, fmt.Errorf("failed to convert asset to bytes: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
valueBytes, err := elementsutil.ValueToBytes(in.GetAmount())
|
valueBytes, err := elementsutil.ValueToBytes(in.Amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to convert value to bytes: %s", err)
|
return nil, fmt.Errorf("failed to convert value to bytes: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, script, tapLeafProof, err := b.getBoardingTaproot(in.GetBoardingPubkey(), aspPubKey)
|
boardingVtxoScript, err := tree.ParseVtxoScript(in.Descriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updater.AddInWitnessUtxo(index, transaction.NewTxOutput(assetBytes, valueBytes, script)); err != nil {
|
boardingTapKey, _, err := boardingVtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updater.AddInTapLeafScript(index, psetv2.NewTapLeafScript(*tapLeafProof, tree.UnspendableKey())); err != nil {
|
boardingOutputScript, err := common.P2TRScript(boardingTapKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updater.AddInWitnessUtxo(index, transaction.NewTxOutput(assetBytes, valueBytes, boardingOutputScript)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,10 +702,13 @@ func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string,
|
|||||||
return "", fmt.Errorf("invalid signature")
|
return "", fmt.Errorf("invalid signature")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := roundSigner.SignTaprootInputTapscriptSig(i, partialSig); err != nil {
|
if err := roundSigner.AddInTapLeafScript(i, input.TapLeafScript[0]); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := roundSigner.SignTaprootInputTapscriptSig(i, partialSig); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return roundSigner.Pset.ToBase64()
|
return roundSigner.Pset.ToBase64()
|
||||||
@@ -823,56 +788,59 @@ func (b *txBuilder) createConnectors(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) createForfeitTxs(
|
func (b *txBuilder) createForfeitTxs(
|
||||||
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, connectors []*psetv2.Pset, connectorAmount uint64,
|
aspPubkey *secp256k1.PublicKey,
|
||||||
|
payments []domain.Payment,
|
||||||
|
connectors []*psetv2.Pset,
|
||||||
|
connectorAmount uint64,
|
||||||
|
minRelayFeeRate chainfee.SatPerKVByte,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
aspScript, err := p2wpkhScript(aspPubkey, b.onchainNetwork())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
pubkeyBytes, err := hex.DecodeString(vtxo.Pubkey)
|
offchainScript, err := tree.ParseVtxoScript(vtxo.Descriptor)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode pubkey: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoPubkey, err := secp256k1.ParsePubKey(pubkeyBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxoScript, vtxoTaprootTree, err := b.getLeafScriptAndTree(vtxoPubkey, aspPubkey)
|
vtxoTapKey, vtxoTree, err := offchainScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var forfeitProof *taproot.TapscriptElementsProof
|
vtxoScript, err := common.P2TRScript(vtxoTapKey)
|
||||||
|
if err != nil {
|
||||||
for _, proof := range vtxoTaprootTree.LeafMerkleProofs {
|
return nil, err
|
||||||
isForfeit, err := (&tree.ForfeitClosure{}).Decode(proof.Script)
|
|
||||||
if !isForfeit || err != nil {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitProof = &proof
|
feeAmount, err := common.ComputeForfeitMinRelayFee(minRelayFeeRate, vtxoTree)
|
||||||
break
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
|
||||||
if forfeitProof == nil {
|
|
||||||
return nil, fmt.Errorf("forfeit proof not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, connector := range connectors {
|
for _, connector := range connectors {
|
||||||
txs, err := b.craftForfeitTxs(
|
txs, err := tree.BuildForfeitTxs(
|
||||||
connector, connectorAmount, vtxo, *forfeitProof, vtxoScript, aspScript,
|
connector,
|
||||||
|
psetv2.InputArgs{
|
||||||
|
Txid: vtxo.Txid,
|
||||||
|
TxIndex: vtxo.VOut,
|
||||||
|
},
|
||||||
|
vtxo.Amount,
|
||||||
|
connectorAmount,
|
||||||
|
feeAmount,
|
||||||
|
vtxoScript,
|
||||||
|
aspPubkey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitTxs = append(forfeitTxs, txs...)
|
for _, tx := range txs {
|
||||||
|
b64, err := tx.ToBase64()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
forfeitTxs = append(forfeitTxs, b64)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -946,52 +914,6 @@ func (b *txBuilder) onchainNetwork() *network.Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) getBoardingTaproot(owner, asp *secp256k1.PublicKey) (string, []byte, *taproot.TapscriptElementsProof, error) {
|
|
||||||
multisigClosure := tree.ForfeitClosure{
|
|
||||||
Pubkey: owner,
|
|
||||||
AspPubkey: asp,
|
|
||||||
}
|
|
||||||
|
|
||||||
csvClosure := tree.CSVSigClosure{
|
|
||||||
Pubkey: owner,
|
|
||||||
Seconds: uint(b.boardingExitDelay),
|
|
||||||
}
|
|
||||||
|
|
||||||
multisigLeaf, err := multisigClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
csvLeaf, err := csvClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tapTree := taproot.AssembleTaprootScriptTree(*multisigLeaf, *csvLeaf)
|
|
||||||
root := tapTree.RootNode.TapHash()
|
|
||||||
tapKey := taproot.ComputeTaprootOutputKey(tree.UnspendableKey(), root[:])
|
|
||||||
|
|
||||||
p2tr, err := payment.FromTweakedKey(tapKey, b.onchainNetwork(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr, err := p2tr.TaprootAddress()
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tapLeaf, err := multisigClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
leafProofIndex := tapTree.LeafProofIndex[tapLeaf.TapHash()]
|
|
||||||
leafProof := tapTree.LeafMerkleProofs[leafProofIndex]
|
|
||||||
|
|
||||||
return addr, p2tr.Script, &leafProof, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractSweepLeaf(input psetv2.Input) (sweepLeaf *psetv2.TapLeafScript, lifetime int64, err error) {
|
func extractSweepLeaf(input psetv2.Input) (sweepLeaf *psetv2.TapLeafScript, lifetime int64, err error) {
|
||||||
for _, leaf := range input.TapLeafScript {
|
for _, leaf := range input.TapLeafScript {
|
||||||
closure := &tree.CSVSigClosure{}
|
closure := &tree.CSVSigClosure{}
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testingKey = "0218d5ca8b58797b7dbd65c075dd7ba7784b3f38ab71b1a5a8e3f94ba0257654a6"
|
testingKey = "020000000000000000000000000000000000000000000000000000000000000001"
|
||||||
connectorAddress = "tex1qekd5u0qj8jl07vy60830xy7n9qtmcx9u3s0cqc"
|
connectorAddress = "tex1qekd5u0qj8jl07vy60830xy7n9qtmcx9u3s0cqc"
|
||||||
minRelayFee = uint64(30)
|
minRelayFee = uint64(30)
|
||||||
roundLifetime = int64(1209344)
|
roundLifetime = int64(1209344)
|
||||||
unilateralExitDelay = int64(512)
|
|
||||||
boardingExitDelay = int64(512)
|
boardingExitDelay = int64(512)
|
||||||
|
minRelayFeeRate = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -54,7 +54,7 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
func TestBuildPoolTx(t *testing.T) {
|
func TestBuildPoolTx(t *testing.T) {
|
||||||
builder := txbuilder.NewTxBuilder(
|
builder := txbuilder.NewTxBuilder(
|
||||||
wallet, common.Liquid, roundLifetime, unilateralExitDelay, boardingExitDelay,
|
wallet, common.Liquid, roundLifetime, boardingExitDelay,
|
||||||
)
|
)
|
||||||
|
|
||||||
fixtures, err := parsePoolTxFixtures()
|
fixtures, err := parsePoolTxFixtures()
|
||||||
@@ -99,7 +99,7 @@ func TestBuildPoolTx(t *testing.T) {
|
|||||||
|
|
||||||
func TestBuildForfeitTxs(t *testing.T) {
|
func TestBuildForfeitTxs(t *testing.T) {
|
||||||
builder := txbuilder.NewTxBuilder(
|
builder := txbuilder.NewTxBuilder(
|
||||||
wallet, common.Liquid, 1209344, unilateralExitDelay, boardingExitDelay,
|
wallet, common.Liquid, 1209344, boardingExitDelay,
|
||||||
)
|
)
|
||||||
|
|
||||||
fixtures, err := parseForfeitTxsFixtures()
|
fixtures, err := parseForfeitTxsFixtures()
|
||||||
@@ -110,7 +110,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(
|
||||||
pubkey, f.PoolTx, f.Payments,
|
pubkey, f.PoolTx, f.Payments, minRelayFeeRate,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, connectors, f.ExpectedNumOfConnectors)
|
require.Len(t, connectors, f.ExpectedNumOfConnectors)
|
||||||
@@ -148,7 +148,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(
|
||||||
pubkey, f.PoolTx, f.Payments,
|
pubkey, f.PoolTx, f.Payments, minRelayFeeRate,
|
||||||
)
|
)
|
||||||
require.EqualError(t, err, f.ExpectedErr)
|
require.EqualError(t, err, f.ExpectedErr)
|
||||||
require.Empty(t, connectors)
|
require.Empty(t, connectors)
|
||||||
|
|||||||
@@ -51,24 +51,3 @@ func craftConnectorTx(
|
|||||||
|
|
||||||
return ptx, nil
|
return ptx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConnectorInputs(pset *psetv2.Pset, connectorAmount uint64) ([]psetv2.InputArgs, []*transaction.TxOutput) {
|
|
||||||
txID, _ := getPsetId(pset)
|
|
||||||
|
|
||||||
inputs := make([]psetv2.InputArgs, 0, len(pset.Outputs))
|
|
||||||
witnessUtxos := make([]*transaction.TxOutput, 0, len(pset.Outputs))
|
|
||||||
|
|
||||||
for i, output := range pset.Outputs {
|
|
||||||
utx, _ := pset.UnsignedTx()
|
|
||||||
|
|
||||||
if output.Value == connectorAmount && len(output.Script) > 0 {
|
|
||||||
inputs = append(inputs, psetv2.InputArgs{
|
|
||||||
Txid: txID,
|
|
||||||
TxIndex: uint32(i),
|
|
||||||
})
|
|
||||||
witnessUtxos = append(witnessUtxos, utx.Outputs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return inputs, witnessUtxos
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"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/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -259,6 +260,16 @@ func (m *mockedWallet) GetTransaction(ctx context.Context, txid string) (string,
|
|||||||
return res, args.Error(1)
|
return res, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockedWallet) MinRelayFeeRate(ctx context.Context) chainfee.SatPerKVByte {
|
||||||
|
args := m.Called(ctx)
|
||||||
|
|
||||||
|
var res chainfee.SatPerKVByte
|
||||||
|
if a := args.Get(0); a != nil {
|
||||||
|
res = a.(chainfee.SatPerKVByte)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
type mockedInput struct {
|
type mockedInput struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func sweepTransaction(
|
|||||||
|
|
||||||
root := leaf.ControlBlock.RootHash(leaf.Script)
|
root := leaf.ControlBlock.RootHash(leaf.Script)
|
||||||
taprootKey := taproot.ComputeTaprootOutputKey(leaf.ControlBlock.InternalKey, root)
|
taprootKey := taproot.ComputeTaprootOutputKey(leaf.ControlBlock.InternalKey, root)
|
||||||
script, err := p2trScript(taprootKey)
|
script, err := common.P2TRScript(taprootKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
|
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -32,17 +33,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
|
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -59,17 +61,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
|
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -80,17 +83,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
|
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -101,17 +105,18 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
|
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -128,53 +133,58 @@
|
|||||||
{
|
{
|
||||||
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
|
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -196,23 +206,25 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"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": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
|
"signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"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"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/vulpemventures/go-elements/address"
|
"github.com/vulpemventures/go-elements/address"
|
||||||
@@ -62,19 +61,24 @@ func getOnchainReceivers(
|
|||||||
|
|
||||||
func getOffchainReceivers(
|
func getOffchainReceivers(
|
||||||
payments []domain.Payment,
|
payments []domain.Payment,
|
||||||
) []tree.Receiver {
|
) ([]tree.Receiver, error) {
|
||||||
receivers := make([]tree.Receiver, 0)
|
receivers := make([]tree.Receiver, 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() {
|
||||||
|
vtxoScript, err := tree.ParseVtxoScript(receiver.Descriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
receivers = append(receivers, tree.Receiver{
|
receivers = append(receivers, tree.Receiver{
|
||||||
Pubkey: receiver.Pubkey,
|
Script: vtxoScript,
|
||||||
Amount: receiver.Amount,
|
Amount: receiver.Amount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return receivers
|
return receivers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toWitnessUtxo(in ports.TxInput) (*transaction.TxOutput, error) {
|
func toWitnessUtxo(in ports.TxInput) (*transaction.TxOutput, error) {
|
||||||
@@ -136,10 +140,6 @@ func addInputs(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func p2trScript(taprootKey *secp256k1.PublicKey) ([]byte, error) {
|
|
||||||
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(schnorr.SerializePubKey(taprootKey)).Script()
|
|
||||||
}
|
|
||||||
|
|
||||||
func isOnchainOnly(payments []domain.Payment) bool {
|
func isOnchainOnly(payments []domain.Payment) bool {
|
||||||
for _, p := range payments {
|
for _, p := range payments {
|
||||||
for _, r := range p.Receivers {
|
for _, r := range p.Receivers {
|
||||||
|
|||||||
@@ -22,20 +22,29 @@ import (
|
|||||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
)
|
)
|
||||||
|
|
||||||
type txBuilder struct {
|
type txBuilder struct {
|
||||||
wallet ports.WalletService
|
wallet ports.WalletService
|
||||||
net common.Network
|
net common.Network
|
||||||
roundLifetime int64 // in seconds
|
roundLifetime int64 // in seconds
|
||||||
exitDelay int64 // in seconds
|
|
||||||
boardingExitDelay int64 // in seconds
|
boardingExitDelay int64 // in seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTxBuilder(
|
func NewTxBuilder(
|
||||||
wallet ports.WalletService, net common.Network, roundLifetime, exitDelay, boardingExitDelay int64,
|
wallet ports.WalletService, net common.Network, roundLifetime, boardingExitDelay int64,
|
||||||
) ports.TxBuilder {
|
) ports.TxBuilder {
|
||||||
return &txBuilder{wallet, net, roundLifetime, exitDelay, boardingExitDelay}
|
return &txBuilder{wallet, net, roundLifetime, boardingExitDelay}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *txBuilder) GetTxID(tx string) (string, error) {
|
||||||
|
ptx, err := psbt.NewFromRawBytes(strings.NewReader(tx), true)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptx.UnsignedTx.TxHash().String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, string, error) {
|
func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, string, error) {
|
||||||
@@ -64,7 +73,7 @@ func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, string, error)
|
|||||||
|
|
||||||
rootHash := controlBlock.RootHash(tapLeaf.Script)
|
rootHash := controlBlock.RootHash(tapLeaf.Script)
|
||||||
tapKeyFromControlBlock := txscript.ComputeTaprootOutputKey(bitcointree.UnspendableKey(), rootHash[:])
|
tapKeyFromControlBlock := txscript.ComputeTaprootOutputKey(bitcointree.UnspendableKey(), rootHash[:])
|
||||||
pkscript, err := p2trScript(tapKeyFromControlBlock)
|
pkscript, err := common.P2TRScript(tapKeyFromControlBlock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, txid, err
|
return false, txid, err
|
||||||
}
|
}
|
||||||
@@ -173,14 +182,6 @@ func (b *txBuilder) FinalizeAndExtract(tx string) (string, error) {
|
|||||||
return hex.EncodeToString(serialized.Bytes()), nil
|
return hex.EncodeToString(serialized.Bytes()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error) {
|
|
||||||
outputScript, _, err := b.getLeafScriptAndTree(userPubkey, aspPubkey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return outputScript, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx string, err error) {
|
func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx string, err error) {
|
||||||
sweepPsbt, err := sweepTransaction(
|
sweepPsbt, err := sweepTransaction(
|
||||||
b.wallet,
|
b.wallet,
|
||||||
@@ -227,7 +228,7 @@ func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) BuildForfeitTxs(
|
func (b *txBuilder) BuildForfeitTxs(
|
||||||
aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment,
|
aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment, 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 {
|
||||||
@@ -244,12 +245,7 @@ func (b *txBuilder) BuildForfeitTxs(
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
minRelayFeeForfeitTx, err := b.minRelayFeeForfeitTx()
|
forfeitTxs, err = b.createForfeitTxs(aspPubkey, payments, connectorTxs, minRelayFeeRate)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitTxs, err = b.createForfeitTxs(aspPubkey, payments, connectorTxs, minRelayFeeForfeitTx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -275,7 +271,10 @@ func (b *txBuilder) BuildPoolTx(
|
|||||||
return "", nil, "", fmt.Errorf("missing cosigners")
|
return "", nil, "", fmt.Errorf("missing cosigners")
|
||||||
}
|
}
|
||||||
|
|
||||||
receivers := getOffchainReceivers(payments)
|
receivers, err := getOffchainReceivers(payments)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
feeAmount, err := b.minRelayFeeTreeTx()
|
feeAmount, err := b.minRelayFeeTreeTx()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -284,7 +283,7 @@ func (b *txBuilder) BuildPoolTx(
|
|||||||
|
|
||||||
if !isOnchainOnly(payments) {
|
if !isOnchainOnly(payments) {
|
||||||
sharedOutputScript, sharedOutputAmount, err = bitcointree.CraftSharedOutput(
|
sharedOutputScript, sharedOutputAmount, err = bitcointree.CraftSharedOutput(
|
||||||
cosigners, aspPubkey, receivers, feeAmount, b.roundLifetime, b.exitDelay,
|
cosigners, aspPubkey, receivers, feeAmount, b.roundLifetime,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -297,7 +296,7 @@ func (b *txBuilder) BuildPoolTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ptx, err := b.createPoolTx(
|
ptx, err := b.createPoolTx(
|
||||||
aspPubkey, sharedOutputAmount, sharedOutputScript, payments, boardingInputs, connectorAddress, sweptRounds,
|
sharedOutputAmount, sharedOutputScript, payments, boardingInputs, connectorAddress, sweptRounds,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -315,7 +314,7 @@ func (b *txBuilder) BuildPoolTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
congestionTree, err = bitcointree.CraftCongestionTree(
|
congestionTree, err = bitcointree.CraftCongestionTree(
|
||||||
initialOutpoint, cosigners, aspPubkey, receivers, feeAmount, b.roundLifetime, b.exitDelay,
|
initialOutpoint, cosigners, aspPubkey, receivers, feeAmount, b.roundLifetime,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -401,7 +400,6 @@ func (b *txBuilder) FindLeaves(congestionTree tree.CongestionTree, fromtxid stri
|
|||||||
return foundLeaves, nil
|
return foundLeaves, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add locktimes to txs
|
|
||||||
func (b *txBuilder) BuildAsyncPaymentTransactions(
|
func (b *txBuilder) BuildAsyncPaymentTransactions(
|
||||||
vtxos []domain.Vtxo, aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver,
|
vtxos []domain.Vtxo, aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver,
|
||||||
) (*domain.AsyncPaymentTxs, error) {
|
) (*domain.AsyncPaymentTxs, error) {
|
||||||
@@ -410,6 +408,7 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, vtxo := range vtxos {
|
for _, vtxo := range vtxos {
|
||||||
|
// TODO allow to chain async payment ?
|
||||||
if vtxo.AsyncPayment != nil {
|
if vtxo.AsyncPayment != nil {
|
||||||
return nil, fmt.Errorf("vtxo %s is an async payment", vtxo.Txid)
|
return nil, fmt.Errorf("vtxo %s is an async payment", vtxo.Txid)
|
||||||
}
|
}
|
||||||
@@ -420,21 +419,11 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
unconditionalForfeitTxs := make([]string, 0, len(vtxos))
|
unconditionalForfeitTxs := make([]string, 0, len(vtxos))
|
||||||
redeemTxWeightEstimator := &input.TxWeightEstimator{}
|
redeemTxWeightEstimator := &input.TxWeightEstimator{}
|
||||||
for _, vtxo := range vtxos {
|
for _, vtxo := range vtxos {
|
||||||
if vtxo.Spent {
|
if vtxo.Spent || vtxo.Redeemed || vtxo.Swept {
|
||||||
return nil, fmt.Errorf("all vtxos must be unspent")
|
return nil, fmt.Errorf("all vtxos must be unspent")
|
||||||
}
|
}
|
||||||
|
|
||||||
senderBytes, err := hex.DecodeString(vtxo.Pubkey)
|
aspScript, err := common.P2TRScript(aspPubKey)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sender, err := secp256k1.ParsePubKey(senderBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
aspScript, err := p2trScript(aspPubKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -449,14 +438,28 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
Index: vtxo.VOut,
|
Index: vtxo.VOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxoScript, vtxoTree, err := b.getLeafScriptAndTree(sender, aspPubKey)
|
vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Descriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vtxoTapKey, vtxoTree, err := vtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxoOutputScript, err := common.P2TRScript(vtxoTapKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var tapscript *waddrmgr.Tapscript
|
||||||
|
forfeitTxWeightEstimator := &input.TxWeightEstimator{}
|
||||||
|
|
||||||
|
if defaultVtxoScript, ok := vtxoScript.(*bitcointree.DefaultVtxoScript); ok {
|
||||||
forfeitClosure := &bitcointree.MultisigClosure{
|
forfeitClosure := &bitcointree.MultisigClosure{
|
||||||
Pubkey: sender,
|
Pubkey: defaultVtxoScript.Owner,
|
||||||
AspPubkey: aspPubKey,
|
AspPubkey: defaultVtxoScript.Asp,
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitLeaf, err := forfeitClosure.Leaf()
|
forfeitLeaf, err := forfeitClosure.Leaf()
|
||||||
@@ -464,20 +467,25 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
leafProof := vtxoTree.LeafMerkleProofs[vtxoTree.LeafProofIndex[forfeitLeaf.TapHash()]]
|
forfeitProof, err := vtxoTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
|
||||||
ctrlBlock := leafProof.ToControlBlock(bitcointree.UnspendableKey())
|
|
||||||
ctrlBlockBytes, err := ctrlBlock.ToBytes()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitTxWeightEstimator := &input.TxWeightEstimator{}
|
ctrlBlock, err := txscript.ParseControlBlock(forfeitProof.ControlBlock)
|
||||||
tapscript := &waddrmgr.Tapscript{
|
if err != nil {
|
||||||
RevealedScript: leafProof.Script,
|
return nil, err
|
||||||
ControlBlock: &ctrlBlock,
|
}
|
||||||
|
|
||||||
|
tapscript = &waddrmgr.Tapscript{
|
||||||
|
RevealedScript: forfeitProof.Script,
|
||||||
|
ControlBlock: ctrlBlock,
|
||||||
}
|
}
|
||||||
forfeitTxWeightEstimator.AddTapscriptInput(64*2, tapscript)
|
forfeitTxWeightEstimator.AddTapscriptInput(64*2, tapscript)
|
||||||
forfeitTxWeightEstimator.AddP2TROutput() // ASP output
|
forfeitTxWeightEstimator.AddP2TROutput() // ASP output
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("vtxo script is not a default vtxo script, cannot be async spent")
|
||||||
|
}
|
||||||
|
|
||||||
forfeitTxFee, err := b.wallet.MinRelayFee(context.Background(), uint64(forfeitTxWeightEstimator.VSize()))
|
forfeitTxFee, err := b.wallet.MinRelayFee(context.Background(), uint64(forfeitTxWeightEstimator.VSize()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -506,14 +514,18 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
|
|
||||||
unconditionnalForfeitPtx.Inputs[0].WitnessUtxo = &wire.TxOut{
|
unconditionnalForfeitPtx.Inputs[0].WitnessUtxo = &wire.TxOut{
|
||||||
Value: int64(vtxo.Amount),
|
Value: int64(vtxo.Amount),
|
||||||
PkScript: vtxoScript,
|
PkScript: vtxoOutputScript,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrlBlock, err := tapscript.ControlBlock.ToBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unconditionnalForfeitPtx.Inputs[0].TaprootInternalKey = schnorr.SerializePubKey(bitcointree.UnspendableKey())
|
|
||||||
unconditionnalForfeitPtx.Inputs[0].TaprootLeafScript = []*psbt.TaprootTapLeafScript{
|
unconditionnalForfeitPtx.Inputs[0].TaprootLeafScript = []*psbt.TaprootTapLeafScript{
|
||||||
{
|
{
|
||||||
ControlBlock: ctrlBlockBytes,
|
Script: tapscript.RevealedScript,
|
||||||
Script: forfeitLeaf.Script,
|
ControlBlock: ctrlBlock,
|
||||||
LeafVersion: txscript.BaseLeafVersion,
|
LeafVersion: txscript.BaseLeafVersion,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -542,16 +554,17 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, receiver := range receivers {
|
for i, receiver := range receivers {
|
||||||
// TODO (@louisinger): Add revert policy (sender+ASP)
|
offchainScript, err := bitcointree.ParseVtxoScript(receiver.Descriptor)
|
||||||
buf, err := hex.DecodeString(receiver.Pubkey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
receiverPk, err := secp256k1.ParsePubKey(buf)
|
|
||||||
|
receiverVtxoTaprootKey, _, err := offchainScript.TapTree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
newVtxoScript, _, err := b.getLeafScriptAndTree(receiverPk, aspPubKey)
|
|
||||||
|
newVtxoScript, err := common.P2TRScript(receiverVtxoTaprootKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -607,58 +620,12 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) GetBoardingScript(userPubkey, aspPubkey *secp256k1.PublicKey) (string, []byte, error) {
|
|
||||||
addr, script, _, err := b.craftBoardingTaproot(userPubkey, aspPubkey)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr, script, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *txBuilder) getLeafScriptAndTree(
|
|
||||||
userPubkey, aspPubkey *secp256k1.PublicKey,
|
|
||||||
) ([]byte, *txscript.IndexedTapScriptTree, error) {
|
|
||||||
redeemClosure := &bitcointree.CSVSigClosure{
|
|
||||||
Pubkey: userPubkey,
|
|
||||||
Seconds: uint(b.exitDelay),
|
|
||||||
}
|
|
||||||
|
|
||||||
redeemLeaf, err := redeemClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitClosure := &bitcointree.MultisigClosure{
|
|
||||||
Pubkey: userPubkey,
|
|
||||||
AspPubkey: aspPubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitLeaf, err := forfeitClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
taprootTree := txscript.AssembleTaprootScriptTree(
|
|
||||||
*redeemLeaf, *forfeitLeaf,
|
|
||||||
)
|
|
||||||
|
|
||||||
root := taprootTree.RootNode.TapHash()
|
|
||||||
unspendableKey := bitcointree.UnspendableKey()
|
|
||||||
taprootKey := txscript.ComputeTaprootOutputKey(unspendableKey, root[:])
|
|
||||||
|
|
||||||
outputScript, err := taprootOutputScript(taprootKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputScript, taprootTree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *txBuilder) createPoolTx(
|
func (b *txBuilder) createPoolTx(
|
||||||
aspPubKey *secp256k1.PublicKey,
|
sharedOutputAmount int64,
|
||||||
sharedOutputAmount int64, sharedOutputScript []byte,
|
sharedOutputScript []byte,
|
||||||
payments []domain.Payment, boardingInputs []ports.BoardingInput, connectorAddress string,
|
payments []domain.Payment,
|
||||||
|
boardingInputs []ports.BoardingInput,
|
||||||
|
connectorAddress string,
|
||||||
sweptRounds []domain.Round,
|
sweptRounds []domain.Round,
|
||||||
) (*psbt.Packet, error) {
|
) (*psbt.Packet, error) {
|
||||||
connectorAddr, err := btcutil.DecodeAddress(connectorAddress, b.onchainNetwork())
|
connectorAddr, err := btcutil.DecodeAddress(connectorAddress, b.onchainNetwork())
|
||||||
@@ -729,7 +696,7 @@ func (b *txBuilder) createPoolTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, input := range boardingInputs {
|
for _, input := range boardingInputs {
|
||||||
targetAmount -= input.GetAmount()
|
targetAmount -= input.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -769,7 +736,7 @@ func (b *txBuilder) createPoolTx(
|
|||||||
ins := make([]*wire.OutPoint, 0)
|
ins := make([]*wire.OutPoint, 0)
|
||||||
nSequences := make([]uint32, 0)
|
nSequences := make([]uint32, 0)
|
||||||
witnessUtxos := make(map[int]*wire.TxOut)
|
witnessUtxos := make(map[int]*wire.TxOut)
|
||||||
boardingTapLeaves := make(map[int]*psbt.TaprootTapLeafScript)
|
tapLeaves := make(map[int]*psbt.TaprootTapLeafScript)
|
||||||
nextIndex := 0
|
nextIndex := 0
|
||||||
|
|
||||||
for _, utxo := range utxos {
|
for _, utxo := range utxos {
|
||||||
@@ -796,23 +763,48 @@ func (b *txBuilder) createPoolTx(
|
|||||||
nextIndex++
|
nextIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, input := range boardingInputs {
|
for _, boardingInput := range boardingInputs {
|
||||||
ins = append(ins, &wire.OutPoint{
|
txHash, err := chainhash.NewHashFromStr(boardingInput.Txid)
|
||||||
Hash: input.GetHash(),
|
|
||||||
Index: input.GetIndex(),
|
|
||||||
})
|
|
||||||
nSequences = append(nSequences, wire.MaxTxInSequenceNum)
|
|
||||||
|
|
||||||
_, script, tapLeaf, err := b.craftBoardingTaproot(input.GetBoardingPubkey(), aspPubKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
boardingTapLeaves[nextIndex] = tapLeaf
|
ins = append(ins, &wire.OutPoint{
|
||||||
witnessUtxos[nextIndex] = &wire.TxOut{
|
Hash: *txHash,
|
||||||
Value: int64(input.GetAmount()),
|
Index: boardingInput.VtxoKey.VOut,
|
||||||
PkScript: script,
|
})
|
||||||
|
nSequences = append(nSequences, wire.MaxTxInSequenceNum)
|
||||||
|
|
||||||
|
boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingInput.Descriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boardingTapKey, boardingTapTree, err := boardingVtxoScript.TapTree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingOutputScript, err := common.P2TRScript(boardingTapKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
witnessUtxos[nextIndex] = &wire.TxOut{
|
||||||
|
Value: int64(boardingInput.Amount),
|
||||||
|
PkScript: boardingOutputScript,
|
||||||
|
}
|
||||||
|
|
||||||
|
biggestProof, err := common.BiggestLeafMerkleProof(boardingTapTree)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tapLeaves[nextIndex] = &psbt.TaprootTapLeafScript{
|
||||||
|
Script: biggestProof.Script,
|
||||||
|
ControlBlock: biggestProof.ControlBlock,
|
||||||
|
}
|
||||||
|
|
||||||
nextIndex++
|
nextIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,11 +824,8 @@ func (b *txBuilder) createPoolTx(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unspendableInternalKey := schnorr.SerializePubKey(bitcointree.UnspendableKey())
|
for inIndex, tapLeaf := range tapLeaves {
|
||||||
|
|
||||||
for inIndex, tapLeaf := range boardingTapLeaves {
|
|
||||||
updater.Upsbt.Inputs[inIndex].TaprootLeafScript = []*psbt.TaprootTapLeafScript{tapLeaf}
|
updater.Upsbt.Inputs[inIndex].TaprootLeafScript = []*psbt.TaprootTapLeafScript{tapLeaf}
|
||||||
updater.Upsbt.Inputs[inIndex].TaprootInternalKey = unspendableInternalKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b64, err := ptx.B64Encode()
|
b64, err := ptx.B64Encode()
|
||||||
@@ -991,6 +980,12 @@ func (b *txBuilder) createPoolTx(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove input taproot leaf script
|
||||||
|
// used only to compute an accurate fee estimation
|
||||||
|
for i := range ptx.Inputs {
|
||||||
|
ptx.Inputs[i].TaprootLeafScript = nil
|
||||||
|
}
|
||||||
|
|
||||||
return ptx, nil
|
return ptx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1048,6 +1043,7 @@ func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
roundTx.Inputs[i].TaprootScriptSpendSig = sourceInput.TaprootScriptSpendSig
|
roundTx.Inputs[i].TaprootScriptSpendSig = sourceInput.TaprootScriptSpendSig
|
||||||
|
roundTx.Inputs[i].TaprootLeafScript = sourceInput.TaprootLeafScript
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1125,88 +1121,18 @@ func (b *txBuilder) minRelayFeeTreeTx() (uint64, error) {
|
|||||||
return b.wallet.MinRelayFee(context.Background(), uint64(common.TreeTxSize))
|
return b.wallet.MinRelayFee(context.Background(), uint64(common.TreeTxSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) minRelayFeeForfeitTx() (uint64, error) {
|
|
||||||
// rebuild the forfeit leaf in order to estimate the input witness size
|
|
||||||
randomKey, err := secp256k1.GeneratePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
pubkey := randomKey.PubKey()
|
|
||||||
|
|
||||||
forfeitClosure := &bitcointree.MultisigClosure{
|
|
||||||
Pubkey: pubkey,
|
|
||||||
AspPubkey: pubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
leaf, err := forfeitClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, vtxoTaprootTree, err := b.getLeafScriptAndTree(pubkey, pubkey)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
merkleProofIndex := vtxoTaprootTree.LeafProofIndex[leaf.TapHash()]
|
|
||||||
merkleProof := vtxoTaprootTree.LeafMerkleProofs[merkleProofIndex]
|
|
||||||
controlBlock := merkleProof.ToControlBlock(bitcointree.UnspendableKey())
|
|
||||||
|
|
||||||
weightEstimator := &input.TxWeightEstimator{}
|
|
||||||
weightEstimator.AddP2WKHInput() // connector input
|
|
||||||
weightEstimator.AddTapscriptInput(64*2, &waddrmgr.Tapscript{
|
|
||||||
RevealedScript: merkleProof.Script,
|
|
||||||
ControlBlock: &controlBlock,
|
|
||||||
}) // forfeit input
|
|
||||||
weightEstimator.AddP2TROutput() // the asp output
|
|
||||||
|
|
||||||
return b.wallet.MinRelayFee(context.Background(), uint64(weightEstimator.VSize()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *txBuilder) createForfeitTxs(
|
func (b *txBuilder) createForfeitTxs(
|
||||||
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, connectors []*psbt.Packet, feeAmount uint64,
|
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, connectors []*psbt.Packet, minRelayFeeRate chainfee.SatPerKVByte,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
aspScript, err := p2trScript(aspPubkey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
pubkeyBytes, err := hex.DecodeString(vtxo.Pubkey)
|
offchainscript, err := bitcointree.ParseVtxoScript(vtxo.Descriptor)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode pubkey: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoPubkey, err := secp256k1.ParsePubKey(pubkeyBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxoScript, vtxoTaprootTree, err := b.getLeafScriptAndTree(vtxoPubkey, aspPubkey)
|
vtxoTaprootKey, tapTree, err := offchainscript.TapTree()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var forfeitProof *txscript.TapscriptProof
|
|
||||||
|
|
||||||
for _, proof := range vtxoTaprootTree.LeafMerkleProofs {
|
|
||||||
isForfeit, err := (&bitcointree.MultisigClosure{}).Decode(proof.Script)
|
|
||||||
if !isForfeit || err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitProof = &proof
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if forfeitProof == nil {
|
|
||||||
return nil, fmt.Errorf("forfeit proof not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
controlBlock := forfeitProof.ToControlBlock(bitcointree.UnspendableKey())
|
|
||||||
ctrlBlockBytes, err := controlBlock.ToBytes()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1216,21 +1142,46 @@ func (b *txBuilder) createForfeitTxs(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vtxoScript, err := common.P2TRScript(vtxoTaprootKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
feeAmount, err := common.ComputeForfeitMinRelayFee(minRelayFeeRate, tapTree)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxoTxHash, err := chainhash.NewHashFromStr(vtxo.Txid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for _, connector := range connectors {
|
for _, connector := range connectors {
|
||||||
txs, err := craftForfeitTxs(
|
txs, err := bitcointree.BuildForfeitTxs(
|
||||||
connector, vtxo,
|
connector,
|
||||||
&psbt.TaprootTapLeafScript{
|
&wire.OutPoint{
|
||||||
ControlBlock: ctrlBlockBytes,
|
Hash: *vtxoTxHash,
|
||||||
Script: forfeitProof.Script,
|
Index: vtxo.VOut,
|
||||||
LeafVersion: forfeitProof.LeafVersion,
|
|
||||||
},
|
},
|
||||||
vtxoScript, aspScript, feeAmount, int64(connectorAmount),
|
vtxo.Amount,
|
||||||
|
connectorAmount,
|
||||||
|
feeAmount,
|
||||||
|
vtxoScript,
|
||||||
|
aspPubkey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitTxs = append(forfeitTxs, txs...)
|
for _, tx := range txs {
|
||||||
|
b64, err := tx.B64Encode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
forfeitTxs = append(forfeitTxs, b64)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1337,61 +1288,6 @@ func (b *txBuilder) onchainNetwork() *chaincfg.Params {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// craftBoardingTaproot returns the addr, script and the leaf belonging to the ASP
|
|
||||||
func (b *txBuilder) craftBoardingTaproot(userPubkey, aspPubkey *secp256k1.PublicKey) (string, []byte, *psbt.TaprootTapLeafScript, error) {
|
|
||||||
multisigClosure := bitcointree.MultisigClosure{
|
|
||||||
Pubkey: userPubkey,
|
|
||||||
AspPubkey: aspPubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
csvClosure := bitcointree.CSVSigClosure{
|
|
||||||
Pubkey: userPubkey,
|
|
||||||
Seconds: uint(b.boardingExitDelay),
|
|
||||||
}
|
|
||||||
|
|
||||||
multisigLeaf, err := multisigClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
csvLeaf, err := csvClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tree := txscript.AssembleTaprootScriptTree(*multisigLeaf, *csvLeaf)
|
|
||||||
|
|
||||||
root := tree.RootNode.TapHash()
|
|
||||||
|
|
||||||
taprootKey := txscript.ComputeTaprootOutputKey(bitcointree.UnspendableKey(), root[:])
|
|
||||||
script, err := txscript.PayToTaprootScript(taprootKey)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr, err := btcutil.NewAddressTaproot(schnorr.SerializePubKey(taprootKey), b.onchainNetwork())
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
proofIndex := tree.LeafProofIndex[multisigLeaf.TapHash()]
|
|
||||||
proof := tree.LeafMerkleProofs[proofIndex]
|
|
||||||
|
|
||||||
ctrlBlock := proof.ToControlBlock(bitcointree.UnspendableKey())
|
|
||||||
ctrlBlockBytes, err := ctrlBlock.ToBytes()
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tapLeaf := &psbt.TaprootTapLeafScript{
|
|
||||||
ControlBlock: ctrlBlockBytes,
|
|
||||||
Script: multisigLeaf.Script,
|
|
||||||
LeafVersion: txscript.BaseLeafVersion,
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr.String(), script, tapLeaf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func castToOutpoints(inputs []ports.TxInput) []ports.TxOutpoint {
|
func castToOutpoints(inputs []ports.TxInput) []ports.TxOutpoint {
|
||||||
outpoints := make([]ports.TxOutpoint, 0, len(inputs))
|
outpoints := make([]ports.TxOutpoint, 0, len(inputs))
|
||||||
for _, input := range inputs {
|
for _, input := range inputs {
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testingKey = "0218d5ca8b58797b7dbd65c075dd7ba7784b3f38ab71b1a5a8e3f94ba0257654a6"
|
testingKey = "020000000000000000000000000000000000000000000000000000000000000001"
|
||||||
connectorAddress = "bc1py00yhcjpcj0k0sqra0etq0u3yy0purmspppsw0shyzyfe8c83tmq5h6kc2"
|
connectorAddress = "bc1py00yhcjpcj0k0sqra0etq0u3yy0purmspppsw0shyzyfe8c83tmq5h6kc2"
|
||||||
roundLifetime = int64(1209344)
|
roundLifetime = int64(1209344)
|
||||||
unilateralExitDelay = int64(512)
|
|
||||||
boardingExitDelay = int64(512)
|
boardingExitDelay = int64(512)
|
||||||
|
minRelayFeeRate = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -53,7 +53,7 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
func TestBuildPoolTx(t *testing.T) {
|
func TestBuildPoolTx(t *testing.T) {
|
||||||
builder := txbuilder.NewTxBuilder(
|
builder := txbuilder.NewTxBuilder(
|
||||||
wallet, common.Bitcoin, roundLifetime, unilateralExitDelay, boardingExitDelay,
|
wallet, common.Bitcoin, roundLifetime, boardingExitDelay,
|
||||||
)
|
)
|
||||||
|
|
||||||
fixtures, err := parsePoolTxFixtures()
|
fixtures, err := parsePoolTxFixtures()
|
||||||
@@ -64,15 +64,11 @@ func TestBuildPoolTx(t *testing.T) {
|
|||||||
t.Run("valid", func(t *testing.T) {
|
t.Run("valid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Valid {
|
for _, f := range fixtures.Valid {
|
||||||
cosigners := make([]*secp256k1.PublicKey, 0)
|
cosigners := make([]*secp256k1.PublicKey, 0)
|
||||||
for _, payment := range f.Payments {
|
for range f.Payments {
|
||||||
for _, input := range payment.Inputs {
|
randKey, err := secp256k1.GeneratePrivateKey()
|
||||||
pubkeyBytes, err := hex.DecodeString(input.Pubkey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
pubkey, err := secp256k1.ParsePubKey(pubkeyBytes)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
cosigners = append(cosigners, pubkey)
|
cosigners = append(cosigners, randKey.PubKey())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
poolTx, congestionTree, connAddr, err := builder.BuildPoolTx(
|
poolTx, congestionTree, connAddr, err := builder.BuildPoolTx(
|
||||||
@@ -110,7 +106,7 @@ func TestBuildPoolTx(t *testing.T) {
|
|||||||
|
|
||||||
func TestBuildForfeitTxs(t *testing.T) {
|
func TestBuildForfeitTxs(t *testing.T) {
|
||||||
builder := txbuilder.NewTxBuilder(
|
builder := txbuilder.NewTxBuilder(
|
||||||
wallet, common.Bitcoin, 1209344, unilateralExitDelay, boardingExitDelay,
|
wallet, common.Bitcoin, 1209344, boardingExitDelay,
|
||||||
)
|
)
|
||||||
|
|
||||||
fixtures, err := parseForfeitTxsFixtures()
|
fixtures, err := parseForfeitTxsFixtures()
|
||||||
@@ -121,7 +117,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(
|
||||||
pubkey, f.PoolTx, f.Payments,
|
pubkey, f.PoolTx, f.Payments, minRelayFeeRate,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, connectors, f.ExpectedNumOfConnectors)
|
require.Len(t, connectors, f.ExpectedNumOfConnectors)
|
||||||
@@ -159,7 +155,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(
|
||||||
pubkey, f.PoolTx, f.Payments,
|
pubkey, f.PoolTx, f.Payments, minRelayFeeRate,
|
||||||
)
|
)
|
||||||
require.EqualError(t, err, f.ExpectedErr)
|
require.EqualError(t, err, f.ExpectedErr)
|
||||||
require.Empty(t, connectors)
|
require.Empty(t, connectors)
|
||||||
|
|||||||
@@ -38,20 +38,3 @@ func craftConnectorTx(
|
|||||||
|
|
||||||
return ptx, nil
|
return ptx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConnectorInputs(partialTx *psbt.Packet, connectorAmount int64) ([]*wire.OutPoint, []*wire.TxOut) {
|
|
||||||
inputs := make([]*wire.OutPoint, 0)
|
|
||||||
witnessUtxos := make([]*wire.TxOut, 0)
|
|
||||||
|
|
||||||
for i, output := range partialTx.UnsignedTx.TxOut {
|
|
||||||
if output.Value == connectorAmount {
|
|
||||||
inputs = append(inputs, &wire.OutPoint{
|
|
||||||
Hash: partialTx.UnsignedTx.TxHash(),
|
|
||||||
Index: uint32(i),
|
|
||||||
})
|
|
||||||
witnessUtxos = append(witnessUtxos, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return inputs, witnessUtxos
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
package txbuilder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ark-network/ark/server/internal/core/domain"
|
|
||||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/btcsuite/btcd/txscript"
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
|
||||||
)
|
|
||||||
|
|
||||||
func craftForfeitTxs(
|
|
||||||
connectorTx *psbt.Packet,
|
|
||||||
vtxo domain.Vtxo,
|
|
||||||
vtxoForfeitTapLeaf *psbt.TaprootTapLeafScript,
|
|
||||||
vtxoScript, aspScript []byte,
|
|
||||||
minRelayFee uint64,
|
|
||||||
connectorAmount int64,
|
|
||||||
) (forfeitTxs []string, err error) {
|
|
||||||
connectors, prevouts := getConnectorInputs(connectorTx, connectorAmount)
|
|
||||||
|
|
||||||
for i, connectorInput := range connectors {
|
|
||||||
connectorPrevout := prevouts[i]
|
|
||||||
|
|
||||||
vtxoHash, err := chainhash.NewHashFromStr(vtxo.Txid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoInput := &wire.OutPoint{
|
|
||||||
Hash: *vtxoHash,
|
|
||||||
Index: vtxo.VOut,
|
|
||||||
}
|
|
||||||
|
|
||||||
partialTx, err := psbt.New(
|
|
||||||
[]*wire.OutPoint{connectorInput, vtxoInput},
|
|
||||||
[]*wire.TxOut{{
|
|
||||||
Value: int64(vtxo.Amount) + int64(connectorAmount) - int64(minRelayFee),
|
|
||||||
PkScript: aspScript,
|
|
||||||
}},
|
|
||||||
2,
|
|
||||||
0,
|
|
||||||
[]uint32{wire.MaxTxInSequenceNum, wire.MaxTxInSequenceNum},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
updater, err := psbt.NewUpdater(partialTx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInWitnessUtxo(connectorPrevout, 0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInWitnessUtxo(&wire.TxOut{
|
|
||||||
Value: int64(vtxo.Amount),
|
|
||||||
PkScript: vtxoScript,
|
|
||||||
}, 1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInSighashType(txscript.SigHashDefault, 1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
updater.Upsbt.Inputs[1].TaprootLeafScript = []*psbt.TaprootTapLeafScript{vtxoForfeitTapLeaf}
|
|
||||||
|
|
||||||
tx, err := partialTx.B64Encode()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitTxs = append(forfeitTxs, tx)
|
|
||||||
}
|
|
||||||
return forfeitTxs, nil
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"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/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -140,6 +141,17 @@ func (m *mockedWallet) MinRelayFee(ctx context.Context, vbytes uint64) (uint64,
|
|||||||
return res, args.Error(1)
|
return res, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockedWallet) MinRelayFeeRate(ctx context.Context) chainfee.SatPerKVByte {
|
||||||
|
args := m.Called(ctx)
|
||||||
|
|
||||||
|
var res chainfee.SatPerKVByte
|
||||||
|
if a := args.Get(0); a != nil {
|
||||||
|
res = a.(chainfee.SatPerKVByte)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockedWallet) GetDustAmount(ctx context.Context) (uint64, error) {
|
func (m *mockedWallet) GetDustAmount(ctx context.Context) (uint64, error) {
|
||||||
args := m.Called(ctx)
|
args := m.Called(ctx)
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -32,17 +32,17 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -59,17 +59,17 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -80,17 +80,17 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -101,17 +101,17 @@
|
|||||||
{
|
{
|
||||||
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1100
|
"amount": 1100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 600
|
"amount": 600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -128,53 +128,54 @@
|
|||||||
{
|
{
|
||||||
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
||||||
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -196,53 +197,53 @@
|
|||||||
{
|
{
|
||||||
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
|
||||||
"vout": 0,
|
"vout": 0,
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"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": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 1000
|
"amount": 1000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
|
"descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })",
|
||||||
"amount": 500
|
"amount": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ import (
|
|||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func p2trScript(taprootKey *secp256k1.PublicKey) ([]byte, error) {
|
|
||||||
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(schnorr.SerializePubKey(taprootKey)).Script()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOnchainReceivers(
|
func getOnchainReceivers(
|
||||||
payments []domain.Payment,
|
payments []domain.Payment,
|
||||||
) []domain.Receiver {
|
) []domain.Receiver {
|
||||||
@@ -28,19 +24,24 @@ func getOnchainReceivers(
|
|||||||
|
|
||||||
func getOffchainReceivers(
|
func getOffchainReceivers(
|
||||||
payments []domain.Payment,
|
payments []domain.Payment,
|
||||||
) []bitcointree.Receiver {
|
) ([]bitcointree.Receiver, error) {
|
||||||
receivers := make([]bitcointree.Receiver, 0)
|
receivers := make([]bitcointree.Receiver, 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() {
|
||||||
|
vtxoScript, err := bitcointree.ParseVtxoScript(receiver.Descriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
receivers = append(receivers, bitcointree.Receiver{
|
receivers = append(receivers, bitcointree.Receiver{
|
||||||
Pubkey: receiver.Pubkey,
|
Script: vtxoScript,
|
||||||
Amount: receiver.Amount,
|
Amount: receiver.Amount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return receivers
|
return receivers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func countSpentVtxos(payments []domain.Payment) uint64 {
|
func countSpentVtxos(payments []domain.Payment) uint64 {
|
||||||
|
|||||||
@@ -727,6 +727,10 @@ func (s *service) WaitForSync(ctx context.Context, txid string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *service) MinRelayFeeRate(ctx context.Context) chainfee.SatPerKVByte {
|
||||||
|
return s.feeEstimator.RelayFeePerKW().FeePerKVByte()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *service) MinRelayFee(ctx context.Context, vbytes uint64) (uint64, error) {
|
func (s *service) MinRelayFee(ctx context.Context, vbytes uint64) (uint64, error) {
|
||||||
fee := s.feeEstimator.RelayFeePerKW().FeeForVByte(lntypes.VByte(vbytes))
|
fee := s.feeEstimator.RelayFeePerKW().FeeForVByte(lntypes.VByte(vbytes))
|
||||||
return uint64(fee.ToUnit(btcutil.AmountSatoshi)), nil
|
return uint64(fee.ToUnit(btcutil.AmountSatoshi)), nil
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
"github.com/vulpemventures/go-elements/elementsutil"
|
"github.com/vulpemventures/go-elements/elementsutil"
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
)
|
)
|
||||||
@@ -58,7 +59,7 @@ func (s *service) SignTransaction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch c := closure.(type) {
|
switch c := closure.(type) {
|
||||||
case *tree.ForfeitClosure:
|
case *tree.MultisigClosure:
|
||||||
asp := schnorr.SerializePubKey(c.AspPubkey)
|
asp := schnorr.SerializePubKey(c.AspPubkey)
|
||||||
owner := schnorr.SerializePubKey(c.Pubkey)
|
owner := schnorr.SerializePubKey(c.Pubkey)
|
||||||
|
|
||||||
@@ -274,6 +275,12 @@ func (s *service) LockConnectorUtxos(ctx context.Context, utxos []ports.TxOutpoi
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var minRate = chainfee.SatPerKVByte(0.2 * 1000)
|
||||||
|
|
||||||
|
func (s *service) MinRelayFeeRate(ctx context.Context) chainfee.SatPerKVByte {
|
||||||
|
return minRate
|
||||||
|
}
|
||||||
|
|
||||||
func (s *service) MinRelayFee(ctx context.Context, vbytes uint64) (uint64, error) {
|
func (s *service) MinRelayFee(ctx context.Context, vbytes uint64) (uint64, error) {
|
||||||
feeRate := 0.2
|
feeRate := 0.2
|
||||||
fee := uint64(float64(vbytes) * feeRate)
|
fee := uint64(float64(vbytes) * feeRate)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
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/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/application"
|
||||||
"github.com/ark-network/ark/server/internal/core/domain"
|
"github.com/ark-network/ark/server/internal/core/domain"
|
||||||
@@ -65,22 +64,27 @@ func (h *handler) CreatePayment(ctx context.Context, req *arkv1.CreatePaymentReq
|
|||||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxosKeys := make([]domain.VtxoKey, 0, len(inputs))
|
|
||||||
for _, input := range inputs {
|
|
||||||
if !input.IsVtxo() {
|
|
||||||
return nil, status.Error(codes.InvalidArgument, "only vtxos input allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxosKeys = append(vtxosKeys, input.VtxoKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
receivers, err := parseReceivers(req.GetOutputs())
|
receivers, err := parseReceivers(req.GetOutputs())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, receiver := range receivers {
|
||||||
|
if receiver.Amount <= 0 {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "output amount must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(receiver.OnchainAddress) > 0 {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "onchain address is not supported as async payment destination")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(receiver.Descriptor) <= 0 {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "missing output descriptor")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
redeemTx, unconditionalForfeitTxs, err := h.svc.CreateAsyncPayment(
|
redeemTx, unconditionalForfeitTxs, err := h.svc.CreateAsyncPayment(
|
||||||
ctx, vtxosKeys, receivers,
|
ctx, inputs, receivers,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -112,8 +116,8 @@ func (h *handler) Ping(ctx context.Context, req *arkv1.PingRequest) (*arkv1.Ping
|
|||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
PoolTx: e.PoolTx,
|
PoolTx: e.PoolTx,
|
||||||
CongestionTree: castCongestionTree(e.CongestionTree),
|
CongestionTree: castCongestionTree(e.CongestionTree),
|
||||||
ForfeitTxs: e.UnsignedForfeitTxs,
|
|
||||||
Connectors: e.Connectors,
|
Connectors: e.Connectors,
|
||||||
|
MinRelayFeeRate: e.MinRelayFeeRate,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -321,7 +325,7 @@ func (h *handler) GetEventStream(_ *arkv1.GetEventStreamRequest, stream arkv1.Ar
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) ListVtxos(ctx context.Context, req *arkv1.ListVtxosRequest) (*arkv1.ListVtxosResponse, error) {
|
func (h *handler) ListVtxos(ctx context.Context, req *arkv1.ListVtxosRequest) (*arkv1.ListVtxosResponse, error) {
|
||||||
hrp, userPubkey, aspPubkey, err := parseAddress(req.GetAddress())
|
_, userPubkey, _, 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())
|
||||||
}
|
}
|
||||||
@@ -332,8 +336,8 @@ func (h *handler) ListVtxos(ctx context.Context, req *arkv1.ListVtxosRequest) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &arkv1.ListVtxosResponse{
|
return &arkv1.ListVtxosResponse{
|
||||||
SpendableVtxos: vtxoList(spendableVtxos).toProto(hrp, aspPubkey),
|
SpendableVtxos: vtxoList(spendableVtxos).toProto(),
|
||||||
SpentVtxos: vtxoList(spentVtxos).toProto(hrp, aspPubkey),
|
SpentVtxos: vtxoList(spentVtxos).toProto(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,13 +374,14 @@ func (h *handler) GetBoardingAddress(ctx context.Context, req *arkv1.GetBoarding
|
|||||||
return nil, status.Error(codes.InvalidArgument, "invalid pubkey (parse error)")
|
return nil, status.Error(codes.InvalidArgument, "invalid pubkey (parse error)")
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := h.svc.GetBoardingAddress(ctx, userPubkey)
|
addr, descriptor, err := h.svc.GetBoardingAddress(ctx, userPubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &arkv1.GetBoardingAddressResponse{
|
return &arkv1.GetBoardingAddressResponse{
|
||||||
Address: addr,
|
Address: addr,
|
||||||
|
Descriptor_: descriptor,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,8 +486,8 @@ func (h *handler) listenToEvents() {
|
|||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
PoolTx: e.PoolTx,
|
PoolTx: e.PoolTx,
|
||||||
CongestionTree: castCongestionTree(e.CongestionTree),
|
CongestionTree: castCongestionTree(e.CongestionTree),
|
||||||
ForfeitTxs: e.UnsignedForfeitTxs,
|
|
||||||
Connectors: e.Connectors,
|
Connectors: e.Connectors,
|
||||||
|
MinRelayFeeRate: e.MinRelayFeeRate,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -547,15 +552,9 @@ func (h *handler) listenToEvents() {
|
|||||||
|
|
||||||
type vtxoList []domain.Vtxo
|
type vtxoList []domain.Vtxo
|
||||||
|
|
||||||
func (v vtxoList) toProto(hrp string, aspKey *secp256k1.PublicKey) []*arkv1.Vtxo {
|
func (v vtxoList) toProto() []*arkv1.Vtxo {
|
||||||
list := make([]*arkv1.Vtxo, 0, len(v))
|
list := make([]*arkv1.Vtxo, 0, len(v))
|
||||||
for _, vv := range v {
|
for _, vv := range v {
|
||||||
addr := vv.OnchainAddress
|
|
||||||
if vv.Pubkey != "" {
|
|
||||||
buf, _ := hex.DecodeString(vv.Pubkey)
|
|
||||||
key, _ := secp256k1.ParsePubKey(buf)
|
|
||||||
addr, _ = common.EncodeAddress(hrp, key, aspKey)
|
|
||||||
}
|
|
||||||
var pendingData *arkv1.PendingPayment
|
var pendingData *arkv1.PendingPayment
|
||||||
if vv.AsyncPayment != nil {
|
if vv.AsyncPayment != nil {
|
||||||
pendingData = &arkv1.PendingPayment{
|
pendingData = &arkv1.PendingPayment{
|
||||||
@@ -564,18 +563,12 @@ func (v vtxoList) toProto(hrp string, aspKey *secp256k1.PublicKey) []*arkv1.Vtxo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
list = append(list, &arkv1.Vtxo{
|
list = append(list, &arkv1.Vtxo{
|
||||||
Outpoint: &arkv1.Input{
|
Outpoint: &arkv1.Outpoint{
|
||||||
Input: &arkv1.Input_VtxoInput{
|
|
||||||
VtxoInput: &arkv1.VtxoInput{
|
|
||||||
Txid: vv.Txid,
|
Txid: vv.Txid,
|
||||||
Vout: vv.VOut,
|
Vout: vv.VOut,
|
||||||
},
|
},
|
||||||
},
|
Descriptor_: vv.Descriptor,
|
||||||
},
|
|
||||||
Receiver: &arkv1.Output{
|
|
||||||
Address: addr,
|
|
||||||
Amount: vv.Amount,
|
Amount: vv.Amount,
|
||||||
},
|
|
||||||
PoolTxid: vv.PoolTx,
|
PoolTxid: vv.PoolTx,
|
||||||
Spent: vv.Spent,
|
Spent: vv.Spent,
|
||||||
ExpireAt: vv.ExpireAt,
|
ExpireAt: vv.ExpireAt,
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
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/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/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,27 +17,19 @@ func parseAddress(addr string) (string, *secp256k1.PublicKey, *secp256k1.PublicK
|
|||||||
return common.DecodeAddress(addr)
|
return common.DecodeAddress(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseInputs(ins []*arkv1.Input) ([]application.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")
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs := make([]application.Input, 0, len(ins))
|
inputs := make([]ports.Input, 0, len(ins))
|
||||||
for _, input := range ins {
|
for _, input := range ins {
|
||||||
if input.GetBoardingInput() != nil {
|
inputs = append(inputs, ports.Input{
|
||||||
desc := input.GetBoardingInput().GetDescriptor_()
|
VtxoKey: domain.VtxoKey{
|
||||||
inputs = append(inputs, application.Input{
|
Txid: input.GetOutpoint().GetTxid(),
|
||||||
Txid: input.GetBoardingInput().GetTxid(),
|
VOut: input.GetOutpoint().GetVout(),
|
||||||
Index: input.GetBoardingInput().GetVout(),
|
},
|
||||||
Descriptor: desc,
|
Descriptor: input.GetDescriptor_(),
|
||||||
})
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs = append(inputs, application.Input{
|
|
||||||
Txid: input.GetVtxoInput().GetTxid(),
|
|
||||||
Index: input.GetVtxoInput().GetVout(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,21 +42,14 @@ func parseReceivers(outs []*arkv1.Output) ([]domain.Receiver, error) {
|
|||||||
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 {
|
if len(out.GetAddress()) <= 0 && len(out.GetDescriptor_()) <= 0 {
|
||||||
return nil, fmt.Errorf("missing output address")
|
return nil, fmt.Errorf("missing output destination")
|
||||||
}
|
|
||||||
var pubkey, addr string
|
|
||||||
_, pk, _, err := common.DecodeAddress(out.GetAddress())
|
|
||||||
if err != nil {
|
|
||||||
addr = out.GetAddress()
|
|
||||||
}
|
|
||||||
if pk != nil {
|
|
||||||
pubkey = hex.EncodeToString(pk.SerializeCompressed())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
receivers = append(receivers, domain.Receiver{
|
receivers = append(receivers, domain.Receiver{
|
||||||
Pubkey: pubkey,
|
Descriptor: out.GetDescriptor_(),
|
||||||
Amount: out.GetAmount(),
|
Amount: out.GetAmount(),
|
||||||
OnchainAddress: addr,
|
OnchainAddress: out.GetAddress(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return receivers, nil
|
return receivers, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user