diff --git a/api-spec/openapi/swagger/ark/v1/service.swagger.json b/api-spec/openapi/swagger/ark/v1/service.swagger.json index b12e7c2..5f3e701 100644 --- a/api-spec/openapi/swagger/ark/v1/service.swagger.json +++ b/api-spec/openapi/swagger/ark/v1/service.swagger.json @@ -475,21 +475,6 @@ } } }, - "v1BoardingInput": { - "type": "object", - "properties": { - "txid": { - "type": "string" - }, - "vout": { - "type": "integer", - "format": "int64" - }, - "descriptor": { - "type": "string" - } - } - }, "v1ClaimPaymentRequest": { "type": "object", "properties": { @@ -668,11 +653,11 @@ "v1Input": { "type": "object", "properties": { - "vtxoInput": { - "$ref": "#/definitions/v1VtxoInput" + "outpoint": { + "$ref": "#/definitions/v1Outpoint" }, - "boardingInput": { - "$ref": "#/definitions/v1BoardingInput" + "descriptor": { + "type": "string" } } }, @@ -709,12 +694,28 @@ } } }, + "v1Outpoint": { + "type": "object", + "properties": { + "txid": { + "type": "string" + }, + "vout": { + "type": "integer", + "format": "int64" + } + } + }, "v1Output": { "type": "object", "properties": { "address": { "type": "string", - "description": "Either the offchain or onchain address." + "title": "onchain" + }, + "descriptor": { + "type": "string", + "title": "offchain" }, "amount": { "type": "string", @@ -838,12 +839,6 @@ "poolTx": { "type": "string" }, - "forfeitTxs": { - "type": "array", - "items": { - "type": "string" - } - }, "congestionTree": { "$ref": "#/definitions/v1Tree" }, @@ -852,6 +847,10 @@ "items": { "type": "string" } + }, + "minRelayFeeRate": { + "type": "string", + "format": "int64" } } }, @@ -970,10 +969,10 @@ "type": "object", "properties": { "outpoint": { - "$ref": "#/definitions/v1Input" + "$ref": "#/definitions/v1Outpoint" }, - "receiver": { - "$ref": "#/definitions/v1Output" + "descriptor": { + "type": "string" }, "spent": { "type": "boolean" @@ -996,18 +995,10 @@ }, "pendingData": { "$ref": "#/definitions/v1PendingPayment" - } - } - }, - "v1VtxoInput": { - "type": "object", - "properties": { - "txid": { - "type": "string" }, - "vout": { - "type": "integer", - "format": "int64" + "amount": { + "type": "string", + "format": "uint64" } } } diff --git a/api-spec/protobuf/ark/v1/service.proto b/api-spec/protobuf/ark/v1/service.proto index 94d17a1..6c522e4 100755 --- a/api-spec/protobuf/ark/v1/service.proto +++ b/api-spec/protobuf/ark/v1/service.proto @@ -196,9 +196,9 @@ message GetInfoResponse { message RoundFinalizationEvent { string id = 1; string pool_tx = 2; - repeated string forfeit_txs = 3; - Tree congestion_tree = 4; - repeated string connectors = 5; + Tree congestion_tree = 3; + repeated string connectors = 4; + int64 min_relay_fee_rate = 5; } message RoundFinalizedEvent { @@ -244,29 +244,23 @@ message Round { RoundStage stage = 8; } -message VtxoInput { +message Outpoint { string txid = 1; uint32 vout = 2; } -message BoardingInput { - string txid = 1; - uint32 vout = 2; - string descriptor = 3; -} - message Input { - oneof input { - VtxoInput vtxo_input = 1; - BoardingInput boarding_input = 2; - } + Outpoint outpoint = 1; + string descriptor = 2; } message Output { - // Either the offchain or onchain address. - string address = 1; + oneof destination { + string address = 1; // onchain + string descriptor = 2; // offchain + } // Amount to send in satoshis. - uint64 amount = 2; + uint64 amount = 3; } message Tree { @@ -284,8 +278,8 @@ message Node { } message Vtxo { - Input outpoint = 1; - Output receiver = 2; + Outpoint outpoint = 1; + string descriptor = 2; bool spent = 3; string pool_txid = 4; string spent_by = 5; @@ -293,6 +287,7 @@ message Vtxo { bool swept = 7; bool pending = 8; PendingPayment pending_data = 9; + uint64 amount = 10; } message PendingPayment { diff --git a/api-spec/protobuf/gen/ark/v1/admin.pb.gw.go b/api-spec/protobuf/gen/ark/v1/admin.pb.gw.go index 6647354..521a687 100644 --- a/api-spec/protobuf/gen/ark/v1/admin.pb.gw.go +++ b/api-spec/protobuf/gen/ark/v1/admin.pb.gw.go @@ -131,6 +131,7 @@ func local_request_AdminService_GetRounds_0(ctx context.Context, marshaler runti // UnaryRPC :call AdminServiceServer directly. // 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. +// 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 { 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". // 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 -// "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 { mux.Handle("GET", pattern_AdminService_GetScheduledSweep_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/api-spec/protobuf/gen/ark/v1/service.pb.go b/api-spec/protobuf/gen/ark/v1/service.pb.go index 7a30815..af88d78 100644 --- a/api-spec/protobuf/gen/ark/v1/service.pb.go +++ b/api-spec/protobuf/gen/ark/v1/service.pb.go @@ -1433,11 +1433,11 @@ type RoundFinalizationEvent struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - PoolTx string `protobuf:"bytes,2,opt,name=pool_tx,json=poolTx,proto3" json:"pool_tx,omitempty"` - ForfeitTxs []string `protobuf:"bytes,3,rep,name=forfeit_txs,json=forfeitTxs,proto3" json:"forfeit_txs,omitempty"` - CongestionTree *Tree `protobuf:"bytes,4,opt,name=congestion_tree,json=congestionTree,proto3" json:"congestion_tree,omitempty"` - Connectors []string `protobuf:"bytes,5,rep,name=connectors,proto3" json:"connectors,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + PoolTx string `protobuf:"bytes,2,opt,name=pool_tx,json=poolTx,proto3" json:"pool_tx,omitempty"` + CongestionTree *Tree `protobuf:"bytes,3,opt,name=congestion_tree,json=congestionTree,proto3" json:"congestion_tree,omitempty"` + Connectors []string `protobuf:"bytes,4,rep,name=connectors,proto3" json:"connectors,omitempty"` + MinRelayFeeRate int64 `protobuf:"varint,5,opt,name=min_relay_fee_rate,json=minRelayFeeRate,proto3" json:"min_relay_fee_rate,omitempty"` } func (x *RoundFinalizationEvent) Reset() { @@ -1486,13 +1486,6 @@ func (x *RoundFinalizationEvent) GetPoolTx() string { return "" } -func (x *RoundFinalizationEvent) GetForfeitTxs() []string { - if x != nil { - return x.ForfeitTxs - } - return nil -} - func (x *RoundFinalizationEvent) GetCongestionTree() *Tree { if x != nil { return x.CongestionTree @@ -1507,6 +1500,13 @@ func (x *RoundFinalizationEvent) GetConnectors() []string { return nil } +func (x *RoundFinalizationEvent) GetMinRelayFeeRate() int64 { + if x != nil { + return x.MinRelayFeeRate + } + return 0 +} + type RoundFinalizedEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1846,7 +1846,7 @@ func (x *Round) GetStage() RoundStage { return RoundStage_ROUND_STAGE_UNSPECIFIED } -type VtxoInput struct { +type Outpoint struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -1855,8 +1855,8 @@ type VtxoInput struct { Vout uint32 `protobuf:"varint,2,opt,name=vout,proto3" json:"vout,omitempty"` } -func (x *VtxoInput) Reset() { - *x = VtxoInput{} +func (x *Outpoint) Reset() { + *x = Outpoint{} if protoimpl.UnsafeEnabled { mi := &file_ark_v1_service_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1864,13 +1864,13 @@ func (x *VtxoInput) Reset() { } } -func (x *VtxoInput) String() string { +func (x *Outpoint) String() string { return protoimpl.X.MessageStringOf(x) } -func (*VtxoInput) ProtoMessage() {} +func (*Outpoint) ProtoMessage() {} -func (x *VtxoInput) ProtoReflect() protoreflect.Message { +func (x *Outpoint) ProtoReflect() protoreflect.Message { mi := &file_ark_v1_service_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1882,104 +1882,38 @@ func (x *VtxoInput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use VtxoInput.ProtoReflect.Descriptor instead. -func (*VtxoInput) Descriptor() ([]byte, []int) { +// Deprecated: Use Outpoint.ProtoReflect.Descriptor instead. +func (*Outpoint) Descriptor() ([]byte, []int) { return file_ark_v1_service_proto_rawDescGZIP(), []int{30} } -func (x *VtxoInput) GetTxid() string { +func (x *Outpoint) GetTxid() string { if x != nil { return x.Txid } return "" } -func (x *VtxoInput) GetVout() uint32 { +func (x *Outpoint) GetVout() uint32 { if x != nil { return x.Vout } return 0 } -type BoardingInput struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` - Vout uint32 `protobuf:"varint,2,opt,name=vout,proto3" json:"vout,omitempty"` - Descriptor_ string `protobuf:"bytes,3,opt,name=descriptor,proto3" json:"descriptor,omitempty"` -} - -func (x *BoardingInput) Reset() { - *x = BoardingInput{} - if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *BoardingInput) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*BoardingInput) ProtoMessage() {} - -func (x *BoardingInput) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[31] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use BoardingInput.ProtoReflect.Descriptor instead. -func (*BoardingInput) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{31} -} - -func (x *BoardingInput) GetTxid() string { - if x != nil { - return x.Txid - } - return "" -} - -func (x *BoardingInput) GetVout() uint32 { - if x != nil { - return x.Vout - } - return 0 -} - -func (x *BoardingInput) GetDescriptor_() string { - if x != nil { - return x.Descriptor_ - } - return "" -} - type Input struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Types that are assignable to Input: - // - // *Input_VtxoInput - // *Input_BoardingInput - Input isInput_Input `protobuf_oneof:"input"` + Outpoint *Outpoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` + Descriptor_ string `protobuf:"bytes,2,opt,name=descriptor,proto3" json:"descriptor,omitempty"` } func (x *Input) Reset() { *x = Input{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[32] + mi := &file_ark_v1_service_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1992,7 +1926,7 @@ func (x *Input) String() string { func (*Input) ProtoMessage() {} func (x *Input) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[32] + mi := &file_ark_v1_service_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2005,61 +1939,41 @@ func (x *Input) ProtoReflect() protoreflect.Message { // Deprecated: Use Input.ProtoReflect.Descriptor instead. func (*Input) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{32} + return file_ark_v1_service_proto_rawDescGZIP(), []int{31} } -func (m *Input) GetInput() isInput_Input { - if m != nil { - return m.Input +func (x *Input) GetOutpoint() *Outpoint { + if x != nil { + return x.Outpoint } return nil } -func (x *Input) GetVtxoInput() *VtxoInput { - if x, ok := x.GetInput().(*Input_VtxoInput); ok { - return x.VtxoInput +func (x *Input) GetDescriptor_() string { + if x != nil { + return x.Descriptor_ } - return nil + return "" } -func (x *Input) GetBoardingInput() *BoardingInput { - if x, ok := x.GetInput().(*Input_BoardingInput); ok { - return x.BoardingInput - } - return nil -} - -type isInput_Input interface { - isInput_Input() -} - -type Input_VtxoInput struct { - VtxoInput *VtxoInput `protobuf:"bytes,1,opt,name=vtxo_input,json=vtxoInput,proto3,oneof"` -} - -type Input_BoardingInput struct { - BoardingInput *BoardingInput `protobuf:"bytes,2,opt,name=boarding_input,json=boardingInput,proto3,oneof"` -} - -func (*Input_VtxoInput) isInput_Input() {} - -func (*Input_BoardingInput) isInput_Input() {} - type Output struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Either the offchain or onchain address. - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // Types that are assignable to Destination: + // + // *Output_Address + // *Output_Descriptor_ + Destination isOutput_Destination `protobuf_oneof:"destination"` // Amount to send in satoshis. - Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` + Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` } func (x *Output) Reset() { *x = Output{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[33] + mi := &file_ark_v1_service_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2072,7 +1986,7 @@ func (x *Output) String() string { func (*Output) ProtoMessage() {} func (x *Output) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[33] + mi := &file_ark_v1_service_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2085,16 +1999,30 @@ func (x *Output) ProtoReflect() protoreflect.Message { // Deprecated: Use Output.ProtoReflect.Descriptor instead. func (*Output) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{33} + return file_ark_v1_service_proto_rawDescGZIP(), []int{32} +} + +func (m *Output) GetDestination() isOutput_Destination { + if m != nil { + return m.Destination + } + return nil } func (x *Output) GetAddress() string { - if x != nil { + if x, ok := x.GetDestination().(*Output_Address); ok { return x.Address } return "" } +func (x *Output) GetDescriptor_() string { + if x, ok := x.GetDestination().(*Output_Descriptor_); ok { + return x.Descriptor_ + } + return "" +} + func (x *Output) GetAmount() uint64 { if x != nil { return x.Amount @@ -2102,6 +2030,22 @@ func (x *Output) GetAmount() uint64 { return 0 } +type isOutput_Destination interface { + isOutput_Destination() +} + +type Output_Address struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3,oneof"` // onchain +} + +type Output_Descriptor_ struct { + Descriptor_ string `protobuf:"bytes,2,opt,name=descriptor,proto3,oneof"` // offchain +} + +func (*Output_Address) isOutput_Destination() {} + +func (*Output_Descriptor_) isOutput_Destination() {} + type Tree struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2113,7 +2057,7 @@ type Tree struct { func (x *Tree) Reset() { *x = Tree{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[34] + mi := &file_ark_v1_service_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2126,7 +2070,7 @@ func (x *Tree) String() string { func (*Tree) ProtoMessage() {} func (x *Tree) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[34] + mi := &file_ark_v1_service_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2139,7 +2083,7 @@ func (x *Tree) ProtoReflect() protoreflect.Message { // Deprecated: Use Tree.ProtoReflect.Descriptor instead. func (*Tree) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{34} + return file_ark_v1_service_proto_rawDescGZIP(), []int{33} } func (x *Tree) GetLevels() []*TreeLevel { @@ -2160,7 +2104,7 @@ type TreeLevel struct { func (x *TreeLevel) Reset() { *x = TreeLevel{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[35] + mi := &file_ark_v1_service_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2173,7 +2117,7 @@ func (x *TreeLevel) String() string { func (*TreeLevel) ProtoMessage() {} func (x *TreeLevel) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[35] + mi := &file_ark_v1_service_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2186,7 +2130,7 @@ func (x *TreeLevel) ProtoReflect() protoreflect.Message { // Deprecated: Use TreeLevel.ProtoReflect.Descriptor instead. func (*TreeLevel) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{35} + return file_ark_v1_service_proto_rawDescGZIP(), []int{34} } func (x *TreeLevel) GetNodes() []*Node { @@ -2209,7 +2153,7 @@ type Node struct { func (x *Node) Reset() { *x = Node{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[36] + mi := &file_ark_v1_service_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2222,7 +2166,7 @@ func (x *Node) String() string { func (*Node) ProtoMessage() {} func (x *Node) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[36] + mi := &file_ark_v1_service_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2235,7 +2179,7 @@ func (x *Node) ProtoReflect() protoreflect.Message { // Deprecated: Use Node.ProtoReflect.Descriptor instead. func (*Node) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{36} + return file_ark_v1_service_proto_rawDescGZIP(), []int{35} } func (x *Node) GetTxid() string { @@ -2264,8 +2208,8 @@ type Vtxo struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Outpoint *Input `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` - Receiver *Output `protobuf:"bytes,2,opt,name=receiver,proto3" json:"receiver,omitempty"` + Outpoint *Outpoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` + Descriptor_ string `protobuf:"bytes,2,opt,name=descriptor,proto3" json:"descriptor,omitempty"` Spent bool `protobuf:"varint,3,opt,name=spent,proto3" json:"spent,omitempty"` PoolTxid string `protobuf:"bytes,4,opt,name=pool_txid,json=poolTxid,proto3" json:"pool_txid,omitempty"` SpentBy string `protobuf:"bytes,5,opt,name=spent_by,json=spentBy,proto3" json:"spent_by,omitempty"` @@ -2273,12 +2217,13 @@ type Vtxo struct { Swept bool `protobuf:"varint,7,opt,name=swept,proto3" json:"swept,omitempty"` Pending bool `protobuf:"varint,8,opt,name=pending,proto3" json:"pending,omitempty"` PendingData *PendingPayment `protobuf:"bytes,9,opt,name=pending_data,json=pendingData,proto3" json:"pending_data,omitempty"` + Amount uint64 `protobuf:"varint,10,opt,name=amount,proto3" json:"amount,omitempty"` } func (x *Vtxo) Reset() { *x = Vtxo{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[37] + mi := &file_ark_v1_service_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2291,7 +2236,7 @@ func (x *Vtxo) String() string { func (*Vtxo) ProtoMessage() {} func (x *Vtxo) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[37] + mi := &file_ark_v1_service_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2304,21 +2249,21 @@ func (x *Vtxo) ProtoReflect() protoreflect.Message { // Deprecated: Use Vtxo.ProtoReflect.Descriptor instead. func (*Vtxo) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{37} + return file_ark_v1_service_proto_rawDescGZIP(), []int{36} } -func (x *Vtxo) GetOutpoint() *Input { +func (x *Vtxo) GetOutpoint() *Outpoint { if x != nil { return x.Outpoint } return nil } -func (x *Vtxo) GetReceiver() *Output { +func (x *Vtxo) GetDescriptor_() string { if x != nil { - return x.Receiver + return x.Descriptor_ } - return nil + return "" } func (x *Vtxo) GetSpent() bool { @@ -2370,6 +2315,13 @@ func (x *Vtxo) GetPendingData() *PendingPayment { return nil } +func (x *Vtxo) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + type PendingPayment struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2382,7 +2334,7 @@ type PendingPayment struct { func (x *PendingPayment) Reset() { *x = PendingPayment{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[38] + mi := &file_ark_v1_service_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2395,7 +2347,7 @@ func (x *PendingPayment) String() string { func (*PendingPayment) ProtoMessage() {} func (x *PendingPayment) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[38] + mi := &file_ark_v1_service_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2408,7 +2360,7 @@ func (x *PendingPayment) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingPayment.ProtoReflect.Descriptor instead. func (*PendingPayment) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{38} + return file_ark_v1_service_proto_rawDescGZIP(), []int{37} } func (x *PendingPayment) GetRedeemTx() string { @@ -2438,7 +2390,7 @@ type SendTreeNoncesRequest struct { func (x *SendTreeNoncesRequest) Reset() { *x = SendTreeNoncesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[39] + mi := &file_ark_v1_service_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2451,7 +2403,7 @@ func (x *SendTreeNoncesRequest) String() string { func (*SendTreeNoncesRequest) ProtoMessage() {} func (x *SendTreeNoncesRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[39] + mi := &file_ark_v1_service_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2464,7 +2416,7 @@ func (x *SendTreeNoncesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendTreeNoncesRequest.ProtoReflect.Descriptor instead. func (*SendTreeNoncesRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{39} + return file_ark_v1_service_proto_rawDescGZIP(), []int{38} } func (x *SendTreeNoncesRequest) GetRoundId() string { @@ -2497,7 +2449,7 @@ type SendTreeNoncesResponse struct { func (x *SendTreeNoncesResponse) Reset() { *x = SendTreeNoncesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[40] + mi := &file_ark_v1_service_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2510,7 +2462,7 @@ func (x *SendTreeNoncesResponse) String() string { func (*SendTreeNoncesResponse) ProtoMessage() {} func (x *SendTreeNoncesResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[40] + mi := &file_ark_v1_service_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2523,7 +2475,7 @@ func (x *SendTreeNoncesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendTreeNoncesResponse.ProtoReflect.Descriptor instead. func (*SendTreeNoncesResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{40} + return file_ark_v1_service_proto_rawDescGZIP(), []int{39} } type SendTreeSignaturesRequest struct { @@ -2539,7 +2491,7 @@ type SendTreeSignaturesRequest struct { func (x *SendTreeSignaturesRequest) Reset() { *x = SendTreeSignaturesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[41] + mi := &file_ark_v1_service_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2552,7 +2504,7 @@ func (x *SendTreeSignaturesRequest) String() string { func (*SendTreeSignaturesRequest) ProtoMessage() {} func (x *SendTreeSignaturesRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[41] + mi := &file_ark_v1_service_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2565,7 +2517,7 @@ func (x *SendTreeSignaturesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendTreeSignaturesRequest.ProtoReflect.Descriptor instead. func (*SendTreeSignaturesRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{41} + return file_ark_v1_service_proto_rawDescGZIP(), []int{40} } func (x *SendTreeSignaturesRequest) GetRoundId() string { @@ -2598,7 +2550,7 @@ type SendTreeSignaturesResponse struct { func (x *SendTreeSignaturesResponse) Reset() { *x = SendTreeSignaturesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[42] + mi := &file_ark_v1_service_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2611,7 +2563,7 @@ func (x *SendTreeSignaturesResponse) String() string { func (*SendTreeSignaturesResponse) ProtoMessage() {} func (x *SendTreeSignaturesResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[42] + mi := &file_ark_v1_service_proto_msgTypes[41] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2624,7 +2576,7 @@ func (x *SendTreeSignaturesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendTreeSignaturesResponse.ProtoReflect.Descriptor instead. func (*SendTreeSignaturesResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{42} + return file_ark_v1_service_proto_rawDescGZIP(), []int{41} } var File_ark_v1_service_proto protoreflect.FileDescriptor @@ -2795,248 +2747,245 @@ var file_ark_v1_service_proto_rawDesc = []byte{ 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, - 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0xb9, 0x01, 0x0a, 0x16, 0x52, 0x6f, 0x75, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0xc5, 0x01, 0x0a, 0x16, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x12, 0x1f, 0x0a, 0x0b, - 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x35, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x12, 0x35, 0x0a, 0x0f, + 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x72, 0x65, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x72, 0x65, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, + 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, + 0x22, 0x42, 0x0a, 0x13, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, + 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6f, 0x6c, + 0x54, 0x78, 0x69, 0x64, 0x22, 0x35, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x11, + 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x73, 0x5f, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x73, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x31, + 0x0a, 0x0d, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x72, 0x65, 0x65, 0x52, 0x0c, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x65, + 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x6e, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x22, 0x53, 0x0a, + 0x20, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, + 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, + 0x65, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x03, 0x65, 0x6e, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x74, 0x78, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x12, 0x35, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x65, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, - 0x54, 0x72, 0x65, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x73, 0x22, 0x42, 0x0a, 0x13, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, - 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, - 0x6f, 0x6f, 0x6c, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x69, 0x64, 0x22, 0x35, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x6e, - 0x64, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, - 0xaf, 0x01, 0x0a, 0x11, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x72, 0x73, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x10, 0x63, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x73, 0x50, 0x75, 0x62, 0x6b, 0x65, - 0x79, 0x73, 0x12, 0x31, 0x0a, 0x0d, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x74, - 0x72, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, 0x0c, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x54, 0x72, 0x65, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0f, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x54, - 0x78, 0x22, 0x53, 0x0a, 0x20, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, - 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6e, 0x6f, - 0x6e, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x65, 0x65, - 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x6e, 0x64, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, - 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x54, - 0x78, 0x12, 0x35, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x74, 0x72, 0x65, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, - 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x65, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6f, 0x72, 0x66, - 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x66, - 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x73, 0x74, 0x61, - 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x67, 0x65, 0x22, 0x33, 0x0a, 0x09, 0x56, 0x74, 0x78, 0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, - 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x74, 0x78, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x22, 0x57, 0x0a, 0x0d, 0x42, 0x6f, 0x61, 0x72, - 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x12, 0x0a, - 0x04, 0x76, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x76, 0x6f, 0x75, + 0x54, 0x72, 0x65, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, + 0x74, 0x78, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x66, 0x65, + 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, + 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x22, + 0x32, 0x0a, 0x08, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, + 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x76, + 0x6f, 0x75, 0x74, 0x22, 0x55, 0x0a, 0x05, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x2c, 0x0a, 0x08, + 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x22, 0x6d, 0x0a, 0x06, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x12, 0x20, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x0d, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x31, 0x0a, 0x04, 0x54, 0x72, 0x65, + 0x65, 0x12, 0x29, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x22, 0x2f, 0x0a, 0x09, + 0x54, 0x72, 0x65, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x22, 0x0a, 0x05, 0x6e, 0x6f, 0x64, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x4b, 0x0a, + 0x04, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x78, 0x69, 0x64, 0x22, 0xc2, 0x02, 0x0a, 0x04, 0x56, + 0x74, 0x78, 0x6f, 0x12, 0x2c, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, + 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, - 0x72, 0x22, 0x84, 0x01, 0x0a, 0x05, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x76, - 0x74, 0x78, 0x6f, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x74, 0x78, 0x6f, 0x49, 0x6e, 0x70, - 0x75, 0x74, 0x48, 0x00, 0x52, 0x09, 0x76, 0x74, 0x78, 0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, - 0x3e, 0x0a, 0x0e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x70, 0x75, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, - 0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x48, 0x00, - 0x52, 0x0d, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x42, - 0x07, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x22, 0x3a, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x31, 0x0a, 0x04, 0x54, 0x72, 0x65, 0x65, 0x12, 0x29, 0x0a, 0x06, - 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, - 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x22, 0x2f, 0x0a, 0x09, 0x54, 0x72, 0x65, 0x65, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x22, 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x52, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x74, 0x78, 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x74, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, - 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, - 0x74, 0x54, 0x78, 0x69, 0x64, 0x22, 0xb3, 0x02, 0x0a, 0x04, 0x56, 0x74, 0x78, 0x6f, 0x12, 0x29, - 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, - 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x08, 0x72, 0x65, 0x63, - 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x08, 0x72, 0x65, 0x63, - 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, - 0x6f, 0x6f, 0x6c, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x65, 0x6e, - 0x74, 0x5f, 0x62, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x65, 0x6e, - 0x74, 0x42, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x61, 0x74, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, - 0x12, 0x14, 0x0a, 0x05, 0x73, 0x77, 0x65, 0x70, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x05, 0x73, 0x77, 0x65, 0x70, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x12, 0x39, 0x0a, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0b, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x22, 0x69, 0x0a, 0x0e, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, - 0x09, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x78, 0x12, 0x3a, 0x0a, 0x19, 0x75, 0x6e, - 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x6f, 0x72, 0x66, - 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x75, - 0x6e, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x46, 0x6f, 0x72, 0x66, - 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x22, 0x72, 0x0a, 0x15, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, - 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, - 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x65, - 0x65, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x74, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x53, 0x65, - 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7e, 0x0a, 0x19, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, - 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x74, - 0x72, 0x65, 0x65, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, - 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2a, 0x98, 0x01, 0x0a, 0x0a, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x67, - 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, - 0x0a, 0x18, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x52, 0x45, - 0x47, 0x49, 0x53, 0x54, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, - 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x49, 0x4e, 0x41, - 0x4c, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x4f, - 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, - 0x5a, 0x45, 0x44, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, - 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x32, 0xd0, 0x0b, - 0x0a, 0x0a, 0x41, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0f, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, - 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x12, 0x67, 0x0a, 0x0c, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x12, 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, - 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x73, 0x0a, 0x0e, 0x53, 0x65, - 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, - 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1c, 0x3a, 0x01, 0x2a, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, - 0x83, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x21, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x73, 0x0a, 0x0f, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, - 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x2f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x57, 0x0a, 0x08, 0x47, 0x65, - 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x7b, 0x74, 0x78, - 0x69, 0x64, 0x7d, 0x12, 0x64, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, - 0x79, 0x49, 0x64, 0x12, 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, - 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, - 0x64, 0x2f, 0x69, 0x64, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x65, 0x0a, 0x0e, 0x47, 0x65, 0x74, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1d, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x30, 0x01, - 0x12, 0x50, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x13, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, - 0x2f, 0x70, 0x69, 0x6e, 0x67, 0x2f, 0x7b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, - 0x64, 0x7d, 0x12, 0x5d, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, - 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, - 0x6f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, - 0x31, 0x2f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x2f, 0x7b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x7d, 0x12, 0x4c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x12, - 0x74, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6f, 0x61, - 0x72, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x64, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b, - 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x73, 0x0a, 0x0f, 0x43, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, - 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, - 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, - 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x42, 0x92, 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x42, - 0x0c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, - 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x6b, 0x2d, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x61, 0x72, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2d, - 0x73, 0x70, 0x65, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67, 0x65, - 0x6e, 0x2f, 0x61, 0x72, 0x6b, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x72, 0x6b, 0x76, 0x31, 0xa2, 0x02, - 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, - 0x41, 0x72, 0x6b, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x12, 0x41, 0x72, 0x6b, 0x5c, 0x56, 0x31, 0x5c, - 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x41, 0x72, - 0x6b, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, + 0x74, 0x78, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6f, 0x6c, + 0x54, 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x79, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x12, + 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x73, 0x77, 0x65, 0x70, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x77, 0x65, + 0x70, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x39, 0x0a, 0x0c, + 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, + 0x69, 0x0a, 0x0e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x5f, 0x74, 0x78, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x78, 0x12, 0x3a, + 0x0a, 0x19, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, + 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x17, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, + 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x22, 0x72, 0x0a, 0x15, 0x53, 0x65, + 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, + 0x0b, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x18, + 0x0a, 0x16, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7e, 0x0a, 0x19, 0x53, 0x65, 0x6e, 0x64, + 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, + 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x65, 0x65, 0x53, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x65, 0x6e, 0x64, + 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x98, 0x01, 0x0a, 0x0a, 0x52, 0x6f, 0x75, 0x6e, 0x64, + 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, + 0x54, 0x41, 0x47, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, + 0x45, 0x5f, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, + 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, + 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x19, + 0x0a, 0x15, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x49, + 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x4f, 0x55, + 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, + 0x04, 0x32, 0xd0, 0x0b, 0x0a, 0x0a, 0x41, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x73, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, + 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x67, 0x0a, 0x0c, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, + 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, + 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x73, + 0x0a, 0x0e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, + 0x12, 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, + 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, + 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x01, 0x2a, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x6e, 0x6f, 0x6e, + 0x63, 0x65, 0x73, 0x12, 0x83, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x21, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, + 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x76, + 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x73, 0x0a, 0x0f, 0x46, 0x69, 0x6e, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x57, + 0x0a, 0x08, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x2f, 0x7b, 0x74, 0x78, 0x69, 0x64, 0x7d, 0x12, 0x64, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x6f, + 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x69, 0x64, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x65, 0x0a, + 0x0e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, + 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x13, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, + 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x69, 0x6e, 0x67, 0x2f, 0x7b, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x5d, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, + 0x78, 0x6f, 0x73, 0x12, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, + 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, + 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x2f, 0x7b, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x7d, 0x12, 0x4c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x69, + 0x6e, 0x66, 0x6f, 0x12, 0x74, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, + 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x2e, 0x61, 0x72, 0x6b, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x76, 0x31, + 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x64, 0x0a, 0x0d, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x3a, + 0x01, 0x2a, 0x22, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x73, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, + 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x42, 0x92, 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x61, 0x72, 0x6b, 0x2d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x61, 0x72, 0x6b, 0x2f, + 0x61, 0x70, 0x69, 0x2d, 0x73, 0x70, 0x65, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x72, 0x6b, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x72, 0x6b, + 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x2e, 0x56, + 0x31, 0xca, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x12, 0x41, 0x72, 0x6b, + 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, + 0x02, 0x07, 0x41, 0x72, 0x6b, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -3052,7 +3001,7 @@ func file_ark_v1_service_proto_rawDescGZIP() []byte { } var file_ark_v1_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_ark_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 43) +var file_ark_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 42) var file_ark_v1_service_proto_goTypes = []interface{}{ (RoundStage)(0), // 0: ark.v1.RoundStage (*CreatePaymentRequest)(nil), // 1: ark.v1.CreatePaymentRequest @@ -3085,25 +3034,24 @@ var file_ark_v1_service_proto_goTypes = []interface{}{ (*RoundSigningEvent)(nil), // 28: ark.v1.RoundSigningEvent (*RoundSigningNoncesGeneratedEvent)(nil), // 29: ark.v1.RoundSigningNoncesGeneratedEvent (*Round)(nil), // 30: ark.v1.Round - (*VtxoInput)(nil), // 31: ark.v1.VtxoInput - (*BoardingInput)(nil), // 32: ark.v1.BoardingInput - (*Input)(nil), // 33: ark.v1.Input - (*Output)(nil), // 34: ark.v1.Output - (*Tree)(nil), // 35: ark.v1.Tree - (*TreeLevel)(nil), // 36: ark.v1.TreeLevel - (*Node)(nil), // 37: ark.v1.Node - (*Vtxo)(nil), // 38: ark.v1.Vtxo - (*PendingPayment)(nil), // 39: ark.v1.PendingPayment - (*SendTreeNoncesRequest)(nil), // 40: ark.v1.SendTreeNoncesRequest - (*SendTreeNoncesResponse)(nil), // 41: ark.v1.SendTreeNoncesResponse - (*SendTreeSignaturesRequest)(nil), // 42: ark.v1.SendTreeSignaturesRequest - (*SendTreeSignaturesResponse)(nil), // 43: ark.v1.SendTreeSignaturesResponse + (*Outpoint)(nil), // 31: ark.v1.Outpoint + (*Input)(nil), // 32: ark.v1.Input + (*Output)(nil), // 33: ark.v1.Output + (*Tree)(nil), // 34: ark.v1.Tree + (*TreeLevel)(nil), // 35: ark.v1.TreeLevel + (*Node)(nil), // 36: ark.v1.Node + (*Vtxo)(nil), // 37: ark.v1.Vtxo + (*PendingPayment)(nil), // 38: ark.v1.PendingPayment + (*SendTreeNoncesRequest)(nil), // 39: ark.v1.SendTreeNoncesRequest + (*SendTreeNoncesResponse)(nil), // 40: ark.v1.SendTreeNoncesResponse + (*SendTreeSignaturesRequest)(nil), // 41: ark.v1.SendTreeSignaturesRequest + (*SendTreeSignaturesResponse)(nil), // 42: ark.v1.SendTreeSignaturesResponse } var file_ark_v1_service_proto_depIdxs = []int32{ - 33, // 0: ark.v1.CreatePaymentRequest.inputs:type_name -> ark.v1.Input - 34, // 1: ark.v1.CreatePaymentRequest.outputs:type_name -> ark.v1.Output - 33, // 2: ark.v1.RegisterPaymentRequest.inputs:type_name -> ark.v1.Input - 34, // 3: ark.v1.ClaimPaymentRequest.outputs:type_name -> ark.v1.Output + 32, // 0: ark.v1.CreatePaymentRequest.inputs:type_name -> ark.v1.Input + 33, // 1: ark.v1.CreatePaymentRequest.outputs:type_name -> ark.v1.Output + 32, // 2: ark.v1.RegisterPaymentRequest.inputs:type_name -> ark.v1.Input + 33, // 3: ark.v1.ClaimPaymentRequest.outputs:type_name -> ark.v1.Output 30, // 4: ark.v1.GetRoundResponse.round:type_name -> ark.v1.Round 30, // 5: ark.v1.GetRoundByIdResponse.round:type_name -> ark.v1.Round 25, // 6: ark.v1.GetEventStreamResponse.round_finalization:type_name -> ark.v1.RoundFinalizationEvent @@ -3116,52 +3064,50 @@ var file_ark_v1_service_proto_depIdxs = []int32{ 27, // 13: ark.v1.PingResponse.round_failed:type_name -> ark.v1.RoundFailed 28, // 14: ark.v1.PingResponse.round_signing:type_name -> ark.v1.RoundSigningEvent 29, // 15: ark.v1.PingResponse.round_signing_nonces_generated:type_name -> ark.v1.RoundSigningNoncesGeneratedEvent - 38, // 16: ark.v1.ListVtxosResponse.spendable_vtxos:type_name -> ark.v1.Vtxo - 38, // 17: ark.v1.ListVtxosResponse.spent_vtxos:type_name -> ark.v1.Vtxo - 35, // 18: ark.v1.RoundFinalizationEvent.congestion_tree:type_name -> ark.v1.Tree - 35, // 19: ark.v1.RoundSigningEvent.unsigned_tree:type_name -> ark.v1.Tree - 35, // 20: ark.v1.Round.congestion_tree:type_name -> ark.v1.Tree + 37, // 16: ark.v1.ListVtxosResponse.spendable_vtxos:type_name -> ark.v1.Vtxo + 37, // 17: ark.v1.ListVtxosResponse.spent_vtxos:type_name -> ark.v1.Vtxo + 34, // 18: ark.v1.RoundFinalizationEvent.congestion_tree:type_name -> ark.v1.Tree + 34, // 19: ark.v1.RoundSigningEvent.unsigned_tree:type_name -> ark.v1.Tree + 34, // 20: ark.v1.Round.congestion_tree:type_name -> ark.v1.Tree 0, // 21: ark.v1.Round.stage:type_name -> ark.v1.RoundStage - 31, // 22: ark.v1.Input.vtxo_input:type_name -> ark.v1.VtxoInput - 32, // 23: ark.v1.Input.boarding_input:type_name -> ark.v1.BoardingInput - 36, // 24: ark.v1.Tree.levels:type_name -> ark.v1.TreeLevel - 37, // 25: ark.v1.TreeLevel.nodes:type_name -> ark.v1.Node - 33, // 26: ark.v1.Vtxo.outpoint:type_name -> ark.v1.Input - 34, // 27: ark.v1.Vtxo.receiver:type_name -> ark.v1.Output - 39, // 28: ark.v1.Vtxo.pending_data:type_name -> ark.v1.PendingPayment - 7, // 29: ark.v1.ArkService.RegisterPayment:input_type -> ark.v1.RegisterPaymentRequest - 9, // 30: ark.v1.ArkService.ClaimPayment:input_type -> ark.v1.ClaimPaymentRequest - 40, // 31: ark.v1.ArkService.SendTreeNonces:input_type -> ark.v1.SendTreeNoncesRequest - 42, // 32: ark.v1.ArkService.SendTreeSignatures:input_type -> ark.v1.SendTreeSignaturesRequest - 11, // 33: ark.v1.ArkService.FinalizePayment:input_type -> ark.v1.FinalizePaymentRequest - 13, // 34: ark.v1.ArkService.GetRound:input_type -> ark.v1.GetRoundRequest - 15, // 35: ark.v1.ArkService.GetRoundById:input_type -> ark.v1.GetRoundByIdRequest - 17, // 36: ark.v1.ArkService.GetEventStream:input_type -> ark.v1.GetEventStreamRequest - 19, // 37: ark.v1.ArkService.Ping:input_type -> ark.v1.PingRequest - 21, // 38: ark.v1.ArkService.ListVtxos:input_type -> ark.v1.ListVtxosRequest - 23, // 39: ark.v1.ArkService.GetInfo:input_type -> ark.v1.GetInfoRequest - 5, // 40: ark.v1.ArkService.GetBoardingAddress:input_type -> ark.v1.GetBoardingAddressRequest - 1, // 41: ark.v1.ArkService.CreatePayment:input_type -> ark.v1.CreatePaymentRequest - 3, // 42: ark.v1.ArkService.CompletePayment:input_type -> ark.v1.CompletePaymentRequest - 8, // 43: ark.v1.ArkService.RegisterPayment:output_type -> ark.v1.RegisterPaymentResponse - 10, // 44: ark.v1.ArkService.ClaimPayment:output_type -> ark.v1.ClaimPaymentResponse - 41, // 45: ark.v1.ArkService.SendTreeNonces:output_type -> ark.v1.SendTreeNoncesResponse - 43, // 46: ark.v1.ArkService.SendTreeSignatures:output_type -> ark.v1.SendTreeSignaturesResponse - 12, // 47: ark.v1.ArkService.FinalizePayment:output_type -> ark.v1.FinalizePaymentResponse - 14, // 48: ark.v1.ArkService.GetRound:output_type -> ark.v1.GetRoundResponse - 16, // 49: ark.v1.ArkService.GetRoundById:output_type -> ark.v1.GetRoundByIdResponse - 18, // 50: ark.v1.ArkService.GetEventStream:output_type -> ark.v1.GetEventStreamResponse - 20, // 51: ark.v1.ArkService.Ping:output_type -> ark.v1.PingResponse - 22, // 52: ark.v1.ArkService.ListVtxos:output_type -> ark.v1.ListVtxosResponse - 24, // 53: ark.v1.ArkService.GetInfo:output_type -> ark.v1.GetInfoResponse - 6, // 54: ark.v1.ArkService.GetBoardingAddress:output_type -> ark.v1.GetBoardingAddressResponse - 2, // 55: ark.v1.ArkService.CreatePayment:output_type -> ark.v1.CreatePaymentResponse - 4, // 56: ark.v1.ArkService.CompletePayment:output_type -> ark.v1.CompletePaymentResponse - 43, // [43:57] is the sub-list for method output_type - 29, // [29:43] is the sub-list for method input_type - 29, // [29:29] is the sub-list for extension type_name - 29, // [29:29] is the sub-list for extension extendee - 0, // [0:29] is the sub-list for field type_name + 31, // 22: ark.v1.Input.outpoint:type_name -> ark.v1.Outpoint + 35, // 23: ark.v1.Tree.levels:type_name -> ark.v1.TreeLevel + 36, // 24: ark.v1.TreeLevel.nodes:type_name -> ark.v1.Node + 31, // 25: ark.v1.Vtxo.outpoint:type_name -> ark.v1.Outpoint + 38, // 26: ark.v1.Vtxo.pending_data:type_name -> ark.v1.PendingPayment + 7, // 27: ark.v1.ArkService.RegisterPayment:input_type -> ark.v1.RegisterPaymentRequest + 9, // 28: ark.v1.ArkService.ClaimPayment:input_type -> ark.v1.ClaimPaymentRequest + 39, // 29: ark.v1.ArkService.SendTreeNonces:input_type -> ark.v1.SendTreeNoncesRequest + 41, // 30: ark.v1.ArkService.SendTreeSignatures:input_type -> ark.v1.SendTreeSignaturesRequest + 11, // 31: ark.v1.ArkService.FinalizePayment:input_type -> ark.v1.FinalizePaymentRequest + 13, // 32: ark.v1.ArkService.GetRound:input_type -> ark.v1.GetRoundRequest + 15, // 33: ark.v1.ArkService.GetRoundById:input_type -> ark.v1.GetRoundByIdRequest + 17, // 34: ark.v1.ArkService.GetEventStream:input_type -> ark.v1.GetEventStreamRequest + 19, // 35: ark.v1.ArkService.Ping:input_type -> ark.v1.PingRequest + 21, // 36: ark.v1.ArkService.ListVtxos:input_type -> ark.v1.ListVtxosRequest + 23, // 37: ark.v1.ArkService.GetInfo:input_type -> ark.v1.GetInfoRequest + 5, // 38: ark.v1.ArkService.GetBoardingAddress:input_type -> ark.v1.GetBoardingAddressRequest + 1, // 39: ark.v1.ArkService.CreatePayment:input_type -> ark.v1.CreatePaymentRequest + 3, // 40: ark.v1.ArkService.CompletePayment:input_type -> ark.v1.CompletePaymentRequest + 8, // 41: ark.v1.ArkService.RegisterPayment:output_type -> ark.v1.RegisterPaymentResponse + 10, // 42: ark.v1.ArkService.ClaimPayment:output_type -> ark.v1.ClaimPaymentResponse + 40, // 43: ark.v1.ArkService.SendTreeNonces:output_type -> ark.v1.SendTreeNoncesResponse + 42, // 44: ark.v1.ArkService.SendTreeSignatures:output_type -> ark.v1.SendTreeSignaturesResponse + 12, // 45: ark.v1.ArkService.FinalizePayment:output_type -> ark.v1.FinalizePaymentResponse + 14, // 46: ark.v1.ArkService.GetRound:output_type -> ark.v1.GetRoundResponse + 16, // 47: ark.v1.ArkService.GetRoundById:output_type -> ark.v1.GetRoundByIdResponse + 18, // 48: ark.v1.ArkService.GetEventStream:output_type -> ark.v1.GetEventStreamResponse + 20, // 49: ark.v1.ArkService.Ping:output_type -> ark.v1.PingResponse + 22, // 50: ark.v1.ArkService.ListVtxos:output_type -> ark.v1.ListVtxosResponse + 24, // 51: ark.v1.ArkService.GetInfo:output_type -> ark.v1.GetInfoResponse + 6, // 52: ark.v1.ArkService.GetBoardingAddress:output_type -> ark.v1.GetBoardingAddressResponse + 2, // 53: ark.v1.ArkService.CreatePayment:output_type -> ark.v1.CreatePaymentResponse + 4, // 54: ark.v1.ArkService.CompletePayment:output_type -> ark.v1.CompletePaymentResponse + 41, // [41:55] is the sub-list for method output_type + 27, // [27:41] is the sub-list for method input_type + 27, // [27:27] is the sub-list for extension type_name + 27, // [27:27] is the sub-list for extension extendee + 0, // [0:27] is the sub-list for field type_name } func init() { file_ark_v1_service_proto_init() } @@ -3531,7 +3477,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VtxoInput); i { + switch v := v.(*Outpoint); i { case 0: return &v.state case 1: @@ -3543,18 +3489,6 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BoardingInput); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_ark_v1_service_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Input); i { case 0: return &v.state @@ -3566,7 +3500,7 @@ func file_ark_v1_service_proto_init() { return nil } } - file_ark_v1_service_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + file_ark_v1_service_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Output); i { case 0: return &v.state @@ -3578,7 +3512,7 @@ func file_ark_v1_service_proto_init() { return nil } } - file_ark_v1_service_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + file_ark_v1_service_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Tree); i { case 0: return &v.state @@ -3590,7 +3524,7 @@ func file_ark_v1_service_proto_init() { return nil } } - file_ark_v1_service_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + file_ark_v1_service_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TreeLevel); i { case 0: return &v.state @@ -3602,7 +3536,7 @@ func file_ark_v1_service_proto_init() { return nil } } - file_ark_v1_service_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { + file_ark_v1_service_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Node); i { case 0: return &v.state @@ -3614,7 +3548,7 @@ func file_ark_v1_service_proto_init() { return nil } } - file_ark_v1_service_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { + file_ark_v1_service_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Vtxo); i { case 0: return &v.state @@ -3626,7 +3560,7 @@ func file_ark_v1_service_proto_init() { return nil } } - file_ark_v1_service_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { + file_ark_v1_service_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingPayment); i { case 0: return &v.state @@ -3638,7 +3572,7 @@ func file_ark_v1_service_proto_init() { return nil } } - file_ark_v1_service_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { + file_ark_v1_service_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SendTreeNoncesRequest); i { case 0: return &v.state @@ -3650,7 +3584,7 @@ func file_ark_v1_service_proto_init() { return nil } } - file_ark_v1_service_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { + file_ark_v1_service_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SendTreeNoncesResponse); i { case 0: return &v.state @@ -3662,7 +3596,7 @@ func file_ark_v1_service_proto_init() { return nil } } - file_ark_v1_service_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { + file_ark_v1_service_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SendTreeSignaturesRequest); i { case 0: return &v.state @@ -3674,7 +3608,7 @@ func file_ark_v1_service_proto_init() { return nil } } - file_ark_v1_service_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { + file_ark_v1_service_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SendTreeSignaturesResponse); i { case 0: return &v.state @@ -3704,8 +3638,8 @@ func file_ark_v1_service_proto_init() { (*PingResponse_RoundSigningNoncesGenerated)(nil), } file_ark_v1_service_proto_msgTypes[32].OneofWrappers = []interface{}{ - (*Input_VtxoInput)(nil), - (*Input_BoardingInput)(nil), + (*Output_Address)(nil), + (*Output_Descriptor_)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -3713,7 +3647,7 @@ func file_ark_v1_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_ark_v1_service_proto_rawDesc, NumEnums: 1, - NumMessages: 43, + NumMessages: 42, NumExtensions: 0, NumServices: 1, }, diff --git a/api-spec/protobuf/gen/ark/v1/service.pb.gw.go b/api-spec/protobuf/gen/ark/v1/service.pb.gw.go index ff89525..3d47396 100644 --- a/api-spec/protobuf/gen/ark/v1/service.pb.gw.go +++ b/api-spec/protobuf/gen/ark/v1/service.pb.gw.go @@ -486,6 +486,7 @@ func local_request_ArkService_CompletePayment_0(ctx context.Context, marshaler r // UnaryRPC :call ArkServiceServer directly. // 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. +// 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 { 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". // 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 -// "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 { mux.Handle("POST", pattern_ArkService_RegisterPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/api-spec/protobuf/gen/ark/v1/wallet.pb.gw.go b/api-spec/protobuf/gen/ark/v1/wallet.pb.gw.go index 56236c1..2329357 100644 --- a/api-spec/protobuf/gen/ark/v1/wallet.pb.gw.go +++ b/api-spec/protobuf/gen/ark/v1/wallet.pb.gw.go @@ -211,6 +211,7 @@ func local_request_WalletService_GetBalance_0(ctx context.Context, marshaler run // UnaryRPC :call WalletInitializerServiceServer directly. // 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. +// 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 { 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. // 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. +// 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 { 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". // 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 -// "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 { 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". // 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 -// "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 { mux.Handle("POST", pattern_WalletService_Lock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { diff --git a/common/bitcointree/builder.go b/common/bitcointree/builder.go index bad85fd..44327c2 100644 --- a/common/bitcointree/builder.go +++ b/common/bitcointree/builder.go @@ -1,7 +1,6 @@ package bitcointree import ( - "encoding/hex" "fmt" "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 func CraftSharedOutput( cosigners []*secp256k1.PublicKey, aspPubkey *secp256k1.PublicKey, receivers []Receiver, - feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64, + feeSatsPerNode uint64, roundLifetime int64, ) ([]byte, int64, error) { aggregatedKey, _, err := createAggregatedKeyWithSweep( cosigners, aspPubkey, roundLifetime, @@ -26,7 +25,7 @@ func CraftSharedOutput( return nil, 0, err } - root, err := createRootNode(aggregatedKey, cosigners, aspPubkey, receivers, feeSatsPerNode, unilateralExitDelay) + root, err := createRootNode(aggregatedKey, cosigners, receivers, feeSatsPerNode) if err != nil { return nil, 0, err } @@ -44,7 +43,7 @@ func CraftSharedOutput( // CraftCongestionTree creates all the tree's transactions func CraftCongestionTree( initialInput *wire.OutPoint, cosigners []*secp256k1.PublicKey, aspPubkey *secp256k1.PublicKey, receivers []Receiver, - feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64, + feeSatsPerNode uint64, roundLifetime int64, ) (tree.CongestionTree, error) { aggregatedKey, sweepTapLeaf, err := createAggregatedKeyWithSweep( cosigners, aspPubkey, roundLifetime, @@ -53,7 +52,7 @@ func CraftCongestionTree( return nil, err } - root, err := createRootNode(aggregatedKey, cosigners, aspPubkey, receivers, feeSatsPerNode, unilateralExitDelay) + root, err := createRootNode(aggregatedKey, cosigners, receivers, feeSatsPerNode) if err != nil { return nil, err } @@ -109,10 +108,8 @@ type node interface { } type leaf struct { - aspKey *secp256k1.PublicKey - vtxoKey *secp256k1.PublicKey - exitDelay int64 - amount int64 + vtxoScript VtxoScript + amount int64 } type branch struct { @@ -145,36 +142,11 @@ func (l *leaf) getAmount() int64 { } func (l *leaf) getOutputs() ([]*wire.TxOut, error) { - redeemClosure := &CSVSigClosure{ - Pubkey: l.vtxoKey, - Seconds: uint(l.exitDelay), - } - - redeemLeaf, err := redeemClosure.Leaf() + taprootKey, _, err := l.vtxoScript.TapTree() if err != nil { 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) if err != nil { return nil, err @@ -272,9 +244,10 @@ func getTx( } func createRootNode( - aggregatedKey *musig2.AggregateKey, cosigners []*secp256k1.PublicKey, - aspPubkey *secp256k1.PublicKey, receivers []Receiver, - feeSatsPerNode uint64, unilateralExitDelay int64, + aggregatedKey *musig2.AggregateKey, + cosigners []*secp256k1.PublicKey, + receivers []Receiver, + feeSatsPerNode uint64, ) (root node, err error) { if len(receivers) == 0 { return nil, fmt.Errorf("no receivers provided") @@ -282,21 +255,9 @@ func createRootNode( nodes := make([]node, 0, len(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{ - aspKey: aspPubkey, - vtxoKey: receiverKey, - exitDelay: unilateralExitDelay, - amount: int64(r.Amount), + vtxoScript: r.Script, + amount: int64(r.Amount), } nodes = append(nodes, leafNode) } diff --git a/common/bitcointree/forfeit.go b/common/bitcointree/forfeit.go new file mode 100644 index 0000000..2e74cb8 --- /dev/null +++ b/common/bitcointree/forfeit.go @@ -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 +} diff --git a/common/bitcointree/musig2_test.go b/common/bitcointree/musig2_test.go index 1bb5d06..2ba1859 100644 --- a/common/bitcointree/musig2_test.go +++ b/common/bitcointree/musig2_test.go @@ -2,6 +2,7 @@ package bitcointree_test import ( "bytes" + "encoding/hex" "encoding/json" "fmt" "os" @@ -43,10 +44,9 @@ func TestRoundTripSignTree(t *testing.T) { _, sharedOutputAmount, err := bitcointree.CraftSharedOutput( cosigners, asp.PubKey(), - f.Receivers, + castReceivers(f.Receivers, asp.PubKey()), minRelayFee, lifetime, - exitDelay, ) require.NoError(t, err) @@ -58,10 +58,9 @@ func TestRoundTripSignTree(t *testing.T) { }, cosigners, asp.PubKey(), - f.Receivers, + castReceivers(f.Receivers, asp.PubKey()), minRelayFee, lifetime, - exitDelay, ) 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 { Valid []struct { - Receivers []bitcointree.Receiver `json:"receivers"` + Receivers []receiverFixture `json:"receivers"` } `json:"valid"` } diff --git a/common/bitcointree/script.go b/common/bitcointree/script.go index ab56303..1944f43 100644 --- a/common/bitcointree/script.go +++ b/common/bitcointree/script.go @@ -141,44 +141,6 @@ func (d *CSVSigClosure) Decode(script []byte) (bool, error) { 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) { data32Index := bytes.Index(script, []byte{txscript.OP_DATA_32}) if data32Index == -1 { diff --git a/common/bitcointree/type.go b/common/bitcointree/type.go index afe7f9d..f023daa 100644 --- a/common/bitcointree/type.go +++ b/common/bitcointree/type.go @@ -1,6 +1,6 @@ package bitcointree type Receiver struct { - Pubkey string + Script VtxoScript Amount uint64 } diff --git a/common/bitcointree/vtxo.go b/common/bitcointree/vtxo.go new file mode 100644 index 0000000..2f8c123 --- /dev/null +++ b/common/bitcointree/vtxo.go @@ -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 +} diff --git a/common/bitcointree/vtxo_test.go b/common/bitcointree/vtxo_test.go new file mode 100644 index 0000000..927824e --- /dev/null +++ b/common/bitcointree/vtxo_test.go @@ -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))) +} diff --git a/common/descriptor/ark.go b/common/descriptor/ark.go index ab93963..5b21be7 100644 --- a/common/descriptor/ark.go +++ b/common/descriptor/ark.go @@ -8,37 +8,149 @@ import ( "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, -) (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 { if andLeaf, ok := leaf.(*And); ok { - if first, ok := andLeaf.First.(*Older); ok { - timeout = first.Timeout + 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, 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) + if err != nil { + 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 second, ok := andLeaf.Second.(*PK); ok { - keyBytes, err := hex.DecodeString(second.Key.Hex) - if err != nil { - return 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 + } - user, err = schnorr.ParsePubKey(keyBytes) - if err != nil { - return nil, 0, err + sender, err = schnorr.ParsePubKey(keyBytes) + if err != nil { + return nil, nil, nil, 0, err + } } } } } 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 { - 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 diff --git a/common/descriptor/expression.go b/common/descriptor/expression.go index 667c481..2af20a1 100644 --- a/common/descriptor/expression.go +++ b/common/descriptor/expression.go @@ -204,6 +204,9 @@ func (e *And) Script(verify bool) (string, error) { func parseExpression(policy string) (Expression, error) { policy = strings.TrimSpace(policy) + if policy[0] == '{' { + policy = policy[1:] + } expressions := make([]Expression, 0) expressions = append(expressions, &PK{}) expressions = append(expressions, &Older{}) diff --git a/common/descriptor/parser.go b/common/descriptor/parser.go index 3bd9caf..e473a4d 100644 --- a/common/descriptor/parser.go +++ b/common/descriptor/parser.go @@ -41,6 +41,10 @@ func ParseTaprootDescriptor(desc string) (*TaprootDescriptor, error) { return nil, err } for _, scriptStr := range scriptParts { + if scriptStr == "}" { + continue + } + leaf, err := parseExpression(scriptStr) if err != nil { return nil, err diff --git a/common/descriptor/parser_test.go b/common/descriptor/parser_test.go index 4684dcd..a6e515b 100644 --- a/common/descriptor/parser_test.go +++ b/common/descriptor/parser_test.go @@ -62,7 +62,7 @@ func TestParseTaprootDescriptor(t *testing.T) { }, { 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{ InternalKey: descriptor.Key{Hex: "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"}, ScriptTree: []descriptor.Expression{ @@ -70,7 +70,7 @@ func TestParseTaprootDescriptor(t *testing.T) { First: &descriptor.PK{ Key: descriptor.XOnlyKey{ descriptor.Key{ - Hex: "873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465", + Hex: "973079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465", }, }, }, @@ -125,16 +125,72 @@ func TestParseTaprootDescriptor(t *testing.T) { }, 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 { t.Run(tt.name, func(t *testing.T) { got, err := descriptor.ParseTaprootDescriptor(tt.desc) - if (err != nil) != tt.wantErr { - require.Equal(t, tt.wantErr, err != nil, err) + if tt.wantErr { + require.Error(t, err) return } - require.Equal(t, tt.expected, got) + require.NoError(t, err) + require.NotNil(t, got) + require.Equal(t, tt.expected, *got) }) } } diff --git a/common/fees.go b/common/fees.go index e8f10f4..735a702 100644 --- a/common/fees.go +++ b/common/fees.go @@ -1,8 +1,12 @@ package common import ( + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) var TreeTxSize = (&input.TxWeightEstimator{}). @@ -19,3 +23,29 @@ var ConnectorTxSize = (&input.TxWeightEstimator{}). AddP2WKHOutput(). AddP2WKHOutput(). 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 +} diff --git a/common/go.mod b/common/go.mod index dfaac86..308f4d8 100644 --- a/common/go.mod +++ b/common/go.mod @@ -8,6 +8,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.5 github.com/btcsuite/btcd/btcutil/psbt v1.1.9 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/lightningnetwork/lnd v0.18.2-beta github.com/stretchr/testify v1.9.0 @@ -17,7 +18,6 @@ require ( dario.cat/mergo 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/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd // 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/txsizes v1.2.4 // indirect @@ -25,16 +25,19 @@ require ( github.com/btcsuite/btcwallet/wtxmgr v1.5.3 // 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/winsvc v1.0.0 // indirect github.com/coreos/go-semver v0.3.1 // 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/lru v1.1.3 // indirect github.com/docker/cli v27.1.1+incompatible // 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-viper/mapstructure/v2 v2.1.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/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/gorilla/websocket v1.5.3 // 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/jessevdk/go-flags v1.6.1 // 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/klauspost/cpuid/v2 v2.2.8 // 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/ltcsuite/ltcd v0.23.5 // 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/prometheus/client_golang v1.19.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/rogpeppe/go-internal v1.12.0 // 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/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 // indirect github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 // indirect diff --git a/common/go.sum b/common/go.sum index 4f4a924..39fd8a8 100644 --- a/common/go.sum +++ b/common/go.sum @@ -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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 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-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 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/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 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/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 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= 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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= diff --git a/common/tree/builder.go b/common/tree/builder.go index 03a5f5a..cdeeaba 100644 --- a/common/tree/builder.go +++ b/common/tree/builder.go @@ -1,9 +1,9 @@ package tree import ( - "encoding/hex" "fmt" + "github.com/ark-network/ark/common" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" @@ -14,13 +14,13 @@ import ( func CraftCongestionTree( asset string, aspPubkey *secp256k1.PublicKey, receivers []Receiver, - feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64, + feeSatsPerNode uint64, roundLifetime int64, ) ( buildCongestionTree TreeFactory, sharedOutputScript []byte, sharedOutputAmount uint64, err error, ) { root, err := createPartialCongestionTree( - asset, aspPubkey, receivers, feeSatsPerNode, roundLifetime, unilateralExitDelay, + asset, aspPubkey, receivers, feeSatsPerNode, roundLifetime, ) if err != nil { return @@ -42,14 +42,13 @@ func CraftCongestionTree( } type node struct { - sweepKey *secp256k1.PublicKey - receivers []Receiver - left *node - right *node - asset string - feeSats uint64 - roundLifetime int64 - unilateralExitDelay int64 + sweepKey *secp256k1.PublicKey + receivers []Receiver + left *node + right *node + asset string + feeSats uint64 + roundLifetime int64 _inputTaprootKey *secp256k1.PublicKey _inputTaprootTree *taproot.IndexedElementsTapScriptTree @@ -242,53 +241,15 @@ func (n *node) getWitnessData() ( } func (n *node) getVtxoWitnessData() ( - *secp256k1.PublicKey, *taproot.IndexedElementsTapScriptTree, error, + *secp256k1.PublicKey, common.TaprootTree, error, ) { if !n.isLeaf() { return nil, nil, fmt.Errorf("cannot call vtxoWitness on a non-leaf node") } - key, err := hex.DecodeString(n.receivers[0].Pubkey) - if err != nil { - return nil, nil, err - } + receiver := n.receivers[0] - pubkey, err := secp256k1.ParsePubKey(key) - 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 + return receiver.Script.TapTree() } func (n *node) getTreeNode( @@ -413,7 +374,7 @@ func (n *node) createFinalCongestionTree() TreeFactory { func createPartialCongestionTree( asset string, aspPubkey *secp256k1.PublicKey, receivers []Receiver, - feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64, + feeSatsPerNode uint64, roundLifetime int64, ) (root *node, err error) { if len(receivers) == 0 { return nil, fmt.Errorf("no receivers provided") @@ -422,12 +383,11 @@ func createPartialCongestionTree( nodes := make([]*node, 0, len(receivers)) for _, r := range receivers { leafNode := &node{ - sweepKey: aspPubkey, - receivers: []Receiver{r}, - asset: asset, - feeSats: feeSatsPerNode, - roundLifetime: roundLifetime, - unilateralExitDelay: unilateralExitDelay, + sweepKey: aspPubkey, + receivers: []Receiver{r}, + asset: asset, + feeSats: feeSatsPerNode, + roundLifetime: roundLifetime, } nodes = append(nodes, leafNode) } diff --git a/server/internal/infrastructure/tx-builder/covenant/forfeit.go b/common/tree/forfeit.go similarity index 50% rename from server/internal/infrastructure/tx-builder/covenant/forfeit.go rename to common/tree/forfeit.go index ca9fc56..82ad55a 100644 --- a/server/internal/infrastructure/tx-builder/covenant/forfeit.go +++ b/common/tree/forfeit.go @@ -1,31 +1,26 @@ -package txbuilder +package tree import ( - "context" - - "github.com/ark-network/ark/common/tree" - "github.com/ark-network/ark/server/internal/core/domain" + "github.com/ark-network/ark/common" "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcwallet/waddrmgr" - "github.com/lightningnetwork/lnd/input" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/vulpemventures/go-elements/elementsutil" "github.com/vulpemventures/go-elements/psetv2" - "github.com/vulpemventures/go-elements/taproot" "github.com/vulpemventures/go-elements/transaction" ) -func (b *txBuilder) craftForfeitTxs( +func BuildForfeitTxs( connectorTx *psetv2.Pset, - connectorAmount uint64, - vtxo domain.Vtxo, - vtxoForfeitTapleaf taproot.TapscriptElementsProof, - vtxoScript, aspScript []byte, -) (forfeitTxs []string, err error) { + vtxoInput psetv2.InputArgs, + vtxoAmount, + connectorAmount, + feeAmount uint64, + vtxoScript []byte, + aspPubKey *secp256k1.PublicKey, +) (forfeitTxs []*psetv2.Pset, err error) { connectors, prevouts := getConnectorInputs(connectorTx, connectorAmount) for i, connectorInput := range connectors { - weightEstimator := &input.TxWeightEstimator{} - connectorPrevout := prevouts[i] asset := elementsutil.AssetHashFromBytes(connectorPrevout.Asset) @@ -39,13 +34,6 @@ func (b *txBuilder) craftForfeitTxs( 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 { return nil, err } @@ -58,9 +46,12 @@ func (b *txBuilder) craftForfeitTxs( 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 { return nil, err @@ -70,27 +61,7 @@ func (b *txBuilder) craftForfeitTxs( return nil, err } - unspendableKey := tree.UnspendableKey() - - 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())) + aspScript, err := common.P2TRScript(aspPubKey) if err != nil { return nil, err } @@ -98,7 +69,7 @@ func (b *txBuilder) craftForfeitTxs( err = updater.AddOutputs([]psetv2.OutputArgs{ { Asset: asset, - Amount: vtxo.Amount + connectorAmount - feeAmount, + Amount: vtxoAmount + connectorAmount - feeAmount, Script: aspScript, }, { @@ -110,12 +81,28 @@ func (b *txBuilder) craftForfeitTxs( return nil, err } - tx, err := pset.ToBase64() - if err != nil { - return nil, err - } - - forfeitTxs = append(forfeitTxs, tx) + forfeitTxs = append(forfeitTxs, pset) } 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 +} diff --git a/common/tree/script.go b/common/tree/script.go index b426b94..8a0a7e1 100644 --- a/common/tree/script.go +++ b/common/tree/script.go @@ -10,9 +10,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/txscript" "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" ) @@ -40,7 +37,7 @@ type CSVSigClosure struct { Seconds uint } -type ForfeitClosure struct { +type MultisigClosure struct { Pubkey *secp256k1.PublicKey AspPubkey *secp256k1.PublicKey } @@ -58,7 +55,7 @@ func DecodeClosure(script []byte) (Closure, error) { return closure, nil } - closure = &ForfeitClosure{} + closure = &MultisigClosure{} if valid, err := closure.Decode(script); err == nil && valid { return closure, nil } @@ -66,7 +63,7 @@ func DecodeClosure(script []byte) (Closure, error) { 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) userKeyBytes := schnorr.SerializePubKey(f.Pubkey) @@ -81,7 +78,7 @@ func (f *ForfeitClosure) Leaf() (*taproot.TapElementsLeaf, error) { return &tapLeaf, nil } -func (f *ForfeitClosure) Decode(script []byte) (bool, error) { +func (f *MultisigClosure) Decode(script []byte) (bool, error) { valid, aspPubKey, err := decodeChecksigScript(script) if err != nil { return false, err @@ -284,59 +281,6 @@ func (c *UnrollClosure) Decode(script []byte) (valid bool, err error) { 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( script []byte, expectedIndex byte, isVerify bool, ) (bool, *secp256k1.PublicKey, uint64, error) { diff --git a/common/tree/type.go b/common/tree/type.go index 239b2fa..502bd50 100644 --- a/common/tree/type.go +++ b/common/tree/type.go @@ -1,10 +1,12 @@ 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 Receiver struct { - Pubkey string + Script VtxoScript Amount uint64 } diff --git a/common/tree/vtxo.go b/common/tree/vtxo.go new file mode 100644 index 0000000..cc44bd0 --- /dev/null +++ b/common/tree/vtxo.go @@ -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 +} diff --git a/common/utils.go b/common/utils.go new file mode 100644 index 0000000..93b2aac --- /dev/null +++ b/common/utils.go @@ -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() +} diff --git a/common/vtxo.go b/common/vtxo.go new file mode 100644 index 0000000..aa41420 --- /dev/null +++ b/common/vtxo.go @@ -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 +} diff --git a/go.work.sum b/go.work.sum index 0e5446b..7fe52e3 100644 --- a/go.work.sum +++ b/go.work.sum @@ -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-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-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/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= @@ -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-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-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/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= 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-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-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-20210616094352-59db8d763f22/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-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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= diff --git a/pkg/client-sdk/client/client.go b/pkg/client-sdk/client/client.go index d24b1fa..9ac1d3b 100644 --- a/pkg/client-sdk/client/client.go +++ b/pkg/client-sdk/client/client.go @@ -7,6 +7,7 @@ import ( "github.com/ark-network/ark/common/bitcointree" "github.com/ark-network/ark/common/tree" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) const ( @@ -37,7 +38,7 @@ type ASPClient interface { ctx context.Context, signedForfeitTxs []string, signedRoundTx string, ) error CreatePayment( - ctx context.Context, inputs []VtxoKey, outputs []Output, + ctx context.Context, inputs []Input, outputs []Output, ) (string, []string, error) CompletePayment( ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string, @@ -67,40 +68,19 @@ type RoundEventChannel struct { Err error } -type Input interface { - GetTxID() string - GetVOut() uint32 - GetDescriptor() string -} - -type VtxoKey struct { +type Outpoint struct { Txid string VOut uint32 } -func (k VtxoKey) GetTxID() string { - return k.Txid -} - -func (k VtxoKey) GetVOut() uint32 { - return k.VOut -} - -func (k VtxoKey) GetDescriptor() string { - return "" -} - -type BoardingInput struct { - VtxoKey +type Input struct { + Outpoint Descriptor string } -func (k BoardingInput) GetDescriptor() string { - return k.Descriptor -} - type Vtxo struct { - VtxoKey + Outpoint + Descriptor string Amount uint64 RoundTxid string ExpiresAt *time.Time @@ -111,8 +91,9 @@ type Vtxo struct { } type Output struct { - Address string - Amount uint64 + Address string // onchain output address + Descriptor string // offchain vtxo descriptor + Amount uint64 } type RoundStage int @@ -152,11 +133,11 @@ type Round struct { } type RoundFinalizationEvent struct { - ID string - Tx string - ForfeitTxs []string - Tree tree.CongestionTree - Connectors []string + ID string + Tx string + Tree tree.CongestionTree + Connectors []string + MinRelayFeeRate chainfee.SatPerKVByte } func (e RoundFinalizationEvent) isRoundEvent() {} diff --git a/pkg/client-sdk/client/grpc/client.go b/pkg/client-sdk/client/grpc/client.go index 5d283e5..845d592 100644 --- a/pkg/client-sdk/client/grpc/client.go +++ b/pkg/client-sdk/client/grpc/client.go @@ -14,6 +14,7 @@ import ( "github.com/ark-network/ark/pkg/client-sdk/client" "github.com/ark-network/ark/pkg/client-sdk/internal/utils" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" @@ -202,15 +203,10 @@ func (a *grpcClient) FinalizePayment( } 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) { - insCast := make([]client.Input, 0, len(inputs)) - for _, in := range inputs { - insCast = append(insCast, in) - } - req := &arkv1.CreatePaymentRequest{ - Inputs: ins(insCast).toProto(), + Inputs: ins(inputs).toProto(), Outputs: outs(outputs).toProto(), } resp, err := a.svc.CreatePayment(ctx, req) @@ -323,9 +319,20 @@ func (a *grpcClient) SendTreeSignatures( type out client.Output func (o out) toProto() *arkv1.Output { + if len(o.Address) > 0 { + return &arkv1.Output{ + Destination: &arkv1.Output_Address{ + Address: o.Address, + }, + Amount: o.Amount, + } + } + return &arkv1.Output{ - Address: o.Address, - Amount: o.Amount, + Destination: &arkv1.Output_Descriptor_{ + Descriptor_: o.Descriptor, + }, + Amount: o.Amount, } } @@ -362,11 +369,11 @@ func (e event) toRoundEvent() (client.RoundEvent, error) { if ee := e.GetRoundFinalization(); ee != nil { tree := treeFromProto{ee.GetCongestionTree()}.parse() return client.RoundFinalizationEvent{ - ID: ee.GetId(), - Tx: ee.GetPoolTx(), - ForfeitTxs: ee.GetForfeitTxs(), - Tree: tree, - Connectors: ee.GetConnectors(), + ID: ee.GetId(), + Tx: ee.GetPoolTx(), + Tree: tree, + Connectors: ee.GetConnectors(), + MinRelayFeeRate: chainfee.SatPerKVByte(ee.MinRelayFeeRate), }, nil } @@ -430,17 +437,18 @@ func (v vtxo) toVtxo() client.Vtxo { uncondForfeitTxs = v.GetPendingData().GetUnconditionalForfeitTxs() } return client.Vtxo{ - VtxoKey: client.VtxoKey{ - Txid: v.GetOutpoint().GetVtxoInput().GetTxid(), - VOut: v.GetOutpoint().GetVtxoInput().GetVout(), + Outpoint: client.Outpoint{ + Txid: v.GetOutpoint().GetTxid(), + VOut: v.GetOutpoint().GetVout(), }, - Amount: v.GetReceiver().GetAmount(), + Amount: v.GetAmount(), RoundTxid: v.GetPoolTxid(), ExpiresAt: expiresAt, Pending: v.GetPending(), RedeemTx: redeemTx, UnconditionalForfeitTxs: uncondForfeitTxs, SpentBy: v.GetSpentBy(), + Descriptor: v.GetDescriptor_(), } } @@ -455,25 +463,12 @@ func (v vtxos) toVtxos() []client.Vtxo { } func toProtoInput(i client.Input) *arkv1.Input { - if len(i.GetDescriptor()) > 0 { - return &arkv1.Input{ - Input: &arkv1.Input_BoardingInput{ - BoardingInput: &arkv1.BoardingInput{ - Txid: i.GetTxID(), - Vout: i.GetVOut(), - Descriptor_: i.GetDescriptor(), - }, - }, - } - } - return &arkv1.Input{ - Input: &arkv1.Input_VtxoInput{ - VtxoInput: &arkv1.VtxoInput{ - Txid: i.GetTxID(), - Vout: i.GetVOut(), - }, + Outpoint: &arkv1.Outpoint{ + Txid: i.Txid, + Vout: i.VOut, }, + Descriptor_: i.Descriptor, } } diff --git a/pkg/client-sdk/client/rest/client.go b/pkg/client-sdk/client/rest/client.go index 67d1bbf..4c0cb37 100644 --- a/pkg/client-sdk/client/rest/client.go +++ b/pkg/client-sdk/client/rest/client.go @@ -21,6 +21,7 @@ import ( "github.com/decred/dcrd/dcrec/secp256k1/v4" httptransport "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) type restClient struct { @@ -146,7 +147,7 @@ func (a *restClient) ListVtxos( expiresAt = &t } - amount, err := strconv.Atoi(v.Receiver.Amount) + amount, err := strconv.Atoi(v.Amount) if err != nil { return nil, nil, err } @@ -159,9 +160,9 @@ func (a *restClient) ListVtxos( } spendableVtxos = append(spendableVtxos, client.Vtxo{ - VtxoKey: client.VtxoKey{ - Txid: v.Outpoint.VtxoInput.Txid, - VOut: uint32(v.Outpoint.VtxoInput.Vout), + Outpoint: client.Outpoint{ + Txid: v.Outpoint.Txid, + VOut: uint32(v.Outpoint.Vout), }, Amount: uint64(amount), RoundTxid: v.PoolTxid, @@ -170,6 +171,7 @@ func (a *restClient) ListVtxos( RedeemTx: redeemTx, UnconditionalForfeitTxs: uncondForfeitTxs, SpentBy: v.SpentBy, + Descriptor: v.Descriptor, }) } @@ -185,20 +187,21 @@ func (a *restClient) ListVtxos( expiresAt = &t } - amount, err := strconv.Atoi(v.Receiver.Amount) + amount, err := strconv.Atoi(v.Amount) if err != nil { return nil, nil, err } spentVtxos = append(spentVtxos, client.Vtxo{ - VtxoKey: client.VtxoKey{ - Txid: v.Outpoint.VtxoInput.Txid, - VOut: uint32(v.Outpoint.VtxoInput.Vout), + Outpoint: client.Outpoint{ + Txid: v.Outpoint.Txid, + VOut: uint32(v.Outpoint.Vout), }, - Amount: uint64(amount), - RoundTxid: v.PoolTxid, - ExpiresAt: expiresAt, - SpentBy: v.SpentBy, + Amount: uint64(amount), + RoundTxid: v.PoolTxid, + ExpiresAt: expiresAt, + SpentBy: v.SpentBy, + Descriptor: v.Descriptor, }) } @@ -249,26 +252,13 @@ func (a *restClient) RegisterPayment( ) (string, error) { ins := make([]*models.V1Input, 0, len(inputs)) for _, i := range inputs { - var input *models.V1Input - - if len(i.GetDescriptor()) > 0 { - input = &models.V1Input{ - BoardingInput: &models.V1BoardingInput{ - Txid: i.GetTxID(), - Vout: int64(i.GetVOut()), - Descriptor: i.GetDescriptor(), - }, - } - } else { - input = &models.V1Input{ - VtxoInput: &models.V1VtxoInput{ - Txid: i.GetTxID(), - Vout: int64(i.GetVOut()), - }, - } - } - - ins = append(ins, input) + ins = append(ins, &models.V1Input{ + Outpoint: &models.V1Outpoint{ + Txid: i.Txid, + Vout: int64(i.VOut), + }, + Descriptor: i.Descriptor, + }) } body := &models.V1RegisterPaymentRequest{ Inputs: ins, @@ -328,12 +318,18 @@ func (a *restClient) Ping( } if e := payload.RoundFinalization; e != nil { tree := treeFromProto{e.CongestionTree}.parse() + + minRelayFeeRate, err := strconv.Atoi(e.MinRelayFeeRate) + if err != nil { + return nil, err + } + return client.RoundFinalizationEvent{ - ID: e.ID, - Tx: e.PoolTx, - ForfeitTxs: e.ForfeitTxs, - Tree: tree, - Connectors: e.Connectors, + ID: e.ID, + Tx: e.PoolTx, + Tree: tree, + Connectors: e.Connectors, + MinRelayFeeRate: chainfee.SatPerKVByte(minRelayFeeRate), }, nil } @@ -394,26 +390,24 @@ func (a *restClient) FinalizePayment( } 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) { ins := make([]*models.V1Input, 0, len(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{ - VtxoInput: &models.V1VtxoInput{ + Outpoint: &models.V1Outpoint{ Txid: i.Txid, Vout: int64(i.VOut), }, + Descriptor: i.Descriptor, }) } outs := make([]*models.V1Output, 0, len(outputs)) for _, o := range outputs { outs = append(outs, &models.V1Output{ - Address: o.Address, - Amount: strconv.Itoa(int(o.Amount)), + Address: o.Address, + Amount: strconv.Itoa(int(o.Amount)), + Descriptor: o.Descriptor, }) } body := models.V1CreatePaymentRequest{ diff --git a/pkg/client-sdk/client/rest/service/models/v1_input.go b/pkg/client-sdk/client/rest/service/models/v1_input.go index 887451b..d2e9647 100644 --- a/pkg/client-sdk/client/rest/service/models/v1_input.go +++ b/pkg/client-sdk/client/rest/service/models/v1_input.go @@ -18,22 +18,18 @@ import ( // swagger:model v1Input type V1Input struct { - // boarding input - BoardingInput *V1BoardingInput `json:"boardingInput,omitempty"` + // descriptor + Descriptor string `json:"descriptor,omitempty"` - // vtxo input - VtxoInput *V1VtxoInput `json:"vtxoInput,omitempty"` + // outpoint + Outpoint *V1Outpoint `json:"outpoint,omitempty"` } // Validate validates this v1 input func (m *V1Input) Validate(formats strfmt.Registry) error { var res []error - if err := m.validateBoardingInput(formats); err != nil { - res = append(res, err) - } - - if err := m.validateVtxoInput(formats); err != nil { + if err := m.validateOutpoint(formats); err != nil { res = append(res, err) } @@ -43,36 +39,17 @@ func (m *V1Input) Validate(formats strfmt.Registry) error { return nil } -func (m *V1Input) validateBoardingInput(formats strfmt.Registry) error { - if swag.IsZero(m.BoardingInput) { // not required +func (m *V1Input) validateOutpoint(formats strfmt.Registry) error { + if swag.IsZero(m.Outpoint) { // not required return nil } - if m.BoardingInput != nil { - if err := m.BoardingInput.Validate(formats); err != nil { + if m.Outpoint != nil { + if err := m.Outpoint.Validate(formats); err != nil { if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("boardingInput") + return ve.ValidateName("outpoint") } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("boardingInput") - } - 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 ce.ValidateName("outpoint") } 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 { var res []error - if err := m.contextValidateBoardingInput(ctx, formats); err != nil { - res = append(res, err) - } - - if err := m.contextValidateVtxoInput(ctx, formats); err != nil { + if err := m.contextValidateOutpoint(ctx, formats); err != nil { res = append(res, err) } @@ -99,40 +72,19 @@ func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry) 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 } - 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 { - return ve.ValidateName("boardingInput") + return ve.ValidateName("outpoint") } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("boardingInput") - } - 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 ce.ValidateName("outpoint") } return err } diff --git a/pkg/client-sdk/client/rest/service/models/v1_outpoint.go b/pkg/client-sdk/client/rest/service/models/v1_outpoint.go new file mode 100644 index 0000000..9cf1b75 --- /dev/null +++ b/pkg/client-sdk/client/rest/service/models/v1_outpoint.go @@ -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 +} diff --git a/pkg/client-sdk/client/rest/service/models/v1_output.go b/pkg/client-sdk/client/rest/service/models/v1_output.go index 0882b64..63f7d35 100644 --- a/pkg/client-sdk/client/rest/service/models/v1_output.go +++ b/pkg/client-sdk/client/rest/service/models/v1_output.go @@ -17,11 +17,14 @@ import ( // swagger:model v1Output type V1Output struct { - // Either the offchain or onchain address. + // onchain Address string `json:"address,omitempty"` // Amount to send in satoshis. Amount string `json:"amount,omitempty"` + + // offchain + Descriptor string `json:"descriptor,omitempty"` } // Validate validates this v1 output diff --git a/pkg/client-sdk/client/rest/service/models/v1_round_finalization_event.go b/pkg/client-sdk/client/rest/service/models/v1_round_finalization_event.go index d8f52c3..88d3e3e 100644 --- a/pkg/client-sdk/client/rest/service/models/v1_round_finalization_event.go +++ b/pkg/client-sdk/client/rest/service/models/v1_round_finalization_event.go @@ -24,12 +24,12 @@ type V1RoundFinalizationEvent struct { // connectors Connectors []string `json:"connectors"` - // forfeit txs - ForfeitTxs []string `json:"forfeitTxs"` - // id ID string `json:"id,omitempty"` + // min relay fee rate + MinRelayFeeRate string `json:"minRelayFeeRate,omitempty"` + // pool tx PoolTx string `json:"poolTx,omitempty"` } diff --git a/pkg/client-sdk/client/rest/service/models/v1_vtxo.go b/pkg/client-sdk/client/rest/service/models/v1_vtxo.go index ff9ef86..408cbe5 100644 --- a/pkg/client-sdk/client/rest/service/models/v1_vtxo.go +++ b/pkg/client-sdk/client/rest/service/models/v1_vtxo.go @@ -18,11 +18,17 @@ import ( // swagger:model v1Vtxo type V1Vtxo struct { + // amount + Amount string `json:"amount,omitempty"` + + // descriptor + Descriptor string `json:"descriptor,omitempty"` + // expire at ExpireAt string `json:"expireAt,omitempty"` // outpoint - Outpoint *V1Input `json:"outpoint,omitempty"` + Outpoint *V1Outpoint `json:"outpoint,omitempty"` // pending Pending bool `json:"pending,omitempty"` @@ -33,9 +39,6 @@ type V1Vtxo struct { // pool txid PoolTxid string `json:"poolTxid,omitempty"` - // receiver - Receiver *V1Output `json:"receiver,omitempty"` - // spent Spent bool `json:"spent,omitempty"` @@ -58,10 +61,6 @@ func (m *V1Vtxo) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := m.validateReceiver(formats); err != nil { - res = append(res, err) - } - if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -106,25 +105,6 @@ func (m *V1Vtxo) validatePendingData(formats strfmt.Registry) error { 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 func (m *V1Vtxo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -137,10 +117,6 @@ func (m *V1Vtxo) ContextValidate(ctx context.Context, formats strfmt.Registry) e res = append(res, err) } - if err := m.contextValidateReceiver(ctx, formats); err != nil { - res = append(res, err) - } - if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -189,27 +165,6 @@ func (m *V1Vtxo) contextValidatePendingData(ctx context.Context, formats strfmt. 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 func (m *V1Vtxo) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/pkg/client-sdk/client_test.go b/pkg/client-sdk/client_test.go index bf6158a..69cd92e 100644 --- a/pkg/client-sdk/client_test.go +++ b/pkg/client-sdk/client_test.go @@ -226,7 +226,7 @@ func loadFixtures(jsonStr string) (vtxos, []Transaction, error) { return vtxos{}, nil, err } spendable[i] = client.Vtxo{ - VtxoKey: client.VtxoKey{ + Outpoint: client.Outpoint{ Txid: vtxo.Outpoint.Txid, VOut: vtxo.Outpoint.Vout, }, @@ -251,7 +251,7 @@ func loadFixtures(jsonStr string) (vtxos, []Transaction, error) { return vtxos{}, nil, err } spent[i] = client.Vtxo{ - VtxoKey: client.VtxoKey{ + Outpoint: client.Outpoint{ Txid: vtxo.Outpoint.Txid, VOut: vtxo.Outpoint.Vout, }, diff --git a/pkg/client-sdk/covenant_client.go b/pkg/client-sdk/covenant_client.go index f792a13..284e0f2 100644 --- a/pkg/client-sdk/covenant_client.go +++ b/pkg/client-sdk/covenant_client.go @@ -12,7 +12,6 @@ import ( "time" "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/pkg/client-sdk/client" "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/chaincfg/chainhash" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" log "github.com/sirupsen/logrus" "github.com/vulpemventures/go-elements/address" "github.com/vulpemventures/go-elements/psetv2" + "github.com/vulpemventures/go-elements/taproot" ) type liquidReceiver struct { @@ -435,18 +436,27 @@ func (a *covenantArkClient) CollaborativeRedeem( if err != nil { return "", err } + + desc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddr) + if err != nil { + return "", err + } + receivers = append(receivers, client.Output{ - Address: offchainAddr, - Amount: changeAmount, + Descriptor: desc, + Amount: changeAmount, }) } inputs := make([]client.Input, 0, len(selectedCoins)) for _, coin := range selectedCoins { - inputs = append(inputs, client.VtxoKey{ - Txid: coin.Txid, - VOut: coin.VOut, + inputs = append(inputs, client.Input{ + Outpoint: client.Outpoint{ + Txid: coin.Txid, + VOut: coin.VOut, + }, + Descriptor: coin.Descriptor, }) } @@ -460,7 +470,7 @@ func (a *covenantArkClient) CollaborativeRedeem( } poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, false, receivers, + ctx, paymentID, selectedCoins, nil, "", receivers, ) if err != nil { return "", err @@ -500,14 +510,22 @@ func (a *covenantArkClient) Claim(ctx context.Context) (string, error) { return "", fmt.Errorf("no funds to claim") } - receiver := client.Output{ - Address: myselfOffchain, - Amount: pendingBalance, + desc, err := a.offchainAddressToDefaultVtxoDescriptor(myselfOffchain) + if err != nil { + return "", err } - desc := strings.ReplaceAll(a.BoardingDescriptorTemplate, "USER", hex.EncodeToString(schnorr.SerializePubKey(mypubkey))) + receiver := client.Output{ + Descriptor: desc, + Amount: pendingBalance, + } - return a.selfTransferAllPendingPayments(ctx, boardingUtxos, receiver, desc) + return a.selfTransferAllPendingPayments( + ctx, + boardingUtxos, + receiver, + hex.EncodeToString(mypubkey.SerializeCompressed()), + ) } func (a *covenantArkClient) GetTransactionHistory(ctx context.Context) ([]Transaction, error) { @@ -577,14 +595,18 @@ func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]ex descriptorStr := strings.ReplaceAll( a.BoardingDescriptorTemplate, "USER", myPubkeyStr, ) - desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + + boardingScript, err := tree.ParseVtxoScript(descriptorStr) if err != nil { return nil, err } - _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) - if err != nil { - return nil, err + var boardingTimeout uint + + if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok { + boardingTimeout = defaultVtxo.ExitDelay + } else { + return nil, fmt.Errorf("unsupported boarding descriptor: %s", descriptorStr) } for _, addr := range boardingAddrs { @@ -800,9 +822,14 @@ func (a *covenantArkClient) sendOffchain( 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{ - Address: receiver.To(), - Amount: receiver.Amount(), + Descriptor: desc, + Amount: receiver.Amount(), }) sumOfReceivers += receiver.Amount() } @@ -828,18 +855,27 @@ func (a *covenantArkClient) sendOffchain( if err != nil { return "", err } + + desc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddr) + if err != nil { + return "", err + } + changeReceiver := client.Output{ - Address: offchainAddr, - Amount: changeAmount, + Descriptor: desc, + Amount: changeAmount, } receiversOutput = append(receiversOutput, changeReceiver) } inputs := make([]client.Input, 0, len(selectedCoins)) for _, coin := range selectedCoins { - inputs = append(inputs, client.VtxoKey{ - Txid: coin.Txid, - VOut: coin.VOut, + inputs = append(inputs, client.Input{ + Outpoint: client.Outpoint{ + Txid: coin.Txid, + VOut: coin.VOut, + }, + Descriptor: coin.Descriptor, }) } @@ -859,7 +895,7 @@ func (a *covenantArkClient) sendOffchain( log.Infof("payment registered with id: %s", paymentID) poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, false, receiversOutput, + ctx, paymentID, selectedCoins, nil, "", receiversOutput, ) if err != nil { return "", err @@ -900,16 +936,46 @@ func (a *covenantArkClient) addInputs( return err } - _, leafProof, _, _, err := tree.ComputeVtxoTaprootScript( - userPubkey, aspPubkey, utxo.Delay, utils.ToElementsNetwork(a.Network), - ) + vtxoScript := &tree.DefaultVtxoScript{ + 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 { return err } 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 } } @@ -921,7 +987,8 @@ func (a *covenantArkClient) handleRoundStream( ctx context.Context, paymentID string, vtxosToSign []client.Vtxo, - mustSignRoundTx bool, + boardingUtxos []explorer.Utxo, + boardingDescriptor string, receivers []client.Output, ) (string, error) { eventsCh, err := a.client.GetEventStream(ctx, paymentID) @@ -955,7 +1022,7 @@ func (a *covenantArkClient) handleRoundStream( log.Info("a round finalization started") signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization( - ctx, event.(client.RoundFinalizationEvent), vtxosToSign, mustSignRoundTx, receivers, + ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, boardingDescriptor, receivers, ) if err != nil { return "", err @@ -979,30 +1046,104 @@ func (a *covenantArkClient) handleRoundStream( } func (a *covenantArkClient) handleRoundFinalization( - ctx context.Context, event client.RoundFinalizationEvent, - vtxos []client.Vtxo, mustSignRoundTx bool, receivers []client.Output, + ctx context.Context, + event client.RoundFinalizationEvent, + vtxos []client.Vtxo, + boardingUtxos []explorer.Utxo, + boardingDescriptor string, + receivers []client.Output, ) (signedForfeits []string, signedRoundTx string, err error) { if err = a.validateCongestionTree(event, receivers); err != nil { 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 { - signedForfeits, err = a.loopAndSign( - ctx, event.ForfeitTxs, vtxos, event.Connectors, - ) + signedForfeits, err = a.createAndSignForfeits(ctx, vtxos, event.Connectors, event.MinRelayFeeRate, myPubkey) if err != nil { return } } - if mustSignRoundTx { - signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, event.Tx) + if len(boardingUtxos) > 0 { + boardingVtxoScript, err := tree.ParseVtxoScript(boardingDescriptor) 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 + } + } + } + + 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 + return signedForfeits, signedRoundTx, nil } func (a *covenantArkClient) validateCongestionTree( @@ -1016,7 +1157,7 @@ func (a *covenantArkClient) validateCongestionTree( connectors := event.Connectors - if !utils.IsLiquidOnchainOnly(receivers) { + if !utils.IsOnchainOnly(receivers) { if err := tree.ValidateCongestionTree( event.Tree, poolTx, a.StoreData.AspPubkey, a.RoundLifetime, ); err != nil { @@ -1029,7 +1170,7 @@ func (a *covenantArkClient) validateCongestionTree( } if err := a.validateReceivers( - ptx, receivers, event.Tree, a.StoreData.AspPubkey, + ptx, receivers, event.Tree, ); err != nil { return err } @@ -1043,14 +1184,13 @@ func (a *covenantArkClient) validateReceivers( ptx *psetv2.Pset, receivers []client.Output, congestionTree tree.CongestionTree, - aspPubkey *secp256k1.PublicKey, ) error { for _, receiver := range receivers { - isOnChain, onchainScript, userPubkey, err := utils.ParseLiquidAddress( + isOnChain, onchainScript, err := utils.ParseLiquidAddress( receiver.Address, ) if err != nil { - return err + return fmt.Errorf("invalid receiver address: %s err = %s", receiver.Address, err) } if isOnChain { @@ -1059,7 +1199,7 @@ func (a *covenantArkClient) validateReceivers( } } else { if err := a.validateOffChainReceiver( - congestionTree, receiver, userPubkey, aspPubkey, + congestionTree, receiver, ); err != nil { return err } @@ -1095,13 +1235,15 @@ func (a *covenantArkClient) validateOnChainReceiver( func (a *covenantArkClient) validateOffChainReceiver( congestionTree tree.CongestionTree, receiver client.Output, - userPubkey, aspPubkey *secp256k1.PublicKey, ) error { found := false - net := utils.ToElementsNetwork(a.Network) - outputTapKey, _, _, _, err := tree.ComputeVtxoTaprootScript( - userPubkey, aspPubkey, uint(a.UnilateralExitDelay), net, - ) + + receiverVtxoScript, err := tree.ParseVtxoScript(receiver.Descriptor) + if err != nil { + return err + } + + outputTapKey, _, err := receiverVtxoScript.TapTree() if err != nil { return err } @@ -1136,36 +1278,105 @@ func (a *covenantArkClient) validateOffChainReceiver( return nil } -func (a *covenantArkClient) loopAndSign( +func (a *covenantArkClient) createAndSignForfeits( ctx context.Context, - forfeitTxs []string, vtxosToSign []client.Vtxo, connectors []string, + vtxosToSign []client.Vtxo, + connectors []string, + feeRate chainfee.SatPerKVByte, + myPubKey *secp256k1.PublicKey, ) ([]string, error) { signedForfeits := make([]string, 0) + connectorsPsets := make([]*psetv2.Pset, 0, len(connectors)) - connectorsTxids := make([]string, 0, len(connectors)) for _, connector := range connectors { - p, _ := psetv2.NewPsetFromBase64(connector) - utx, _ := p.UnsignedTx() - txid := utx.TxHash().String() - connectorsTxids = append(connectorsTxids, txid) - } - - for _, forfeitTx := range forfeitTxs { - pset, err := psetv2.NewPsetFromBase64(forfeitTx) + p, err := psetv2.NewPsetFromBase64(connector) if err != nil { return nil, err } - for _, input := range pset.Inputs { - inputTxid := chainhash.Hash(input.PreviousTxid).String() - for _, coin := range vtxosToSign { - if inputTxid == coin.Txid { - signedPset, err := a.signForfeitTx(ctx, forfeitTx, pset, connectorsTxids) - if err != nil { - return nil, err - } - signedForfeits = append(signedForfeits, signedPset) + connectorsPsets = append(connectorsPsets, p) + } + + for _, vtxo := range vtxosToSign { + vtxoScript, err := tree.ParseVtxoScript(vtxo.Descriptor) + if err != nil { + return nil, err + } + + 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 } -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( ctx context.Context, targetAmount uint64, exclude []explorer.Utxo, ) ([]explorer.Utxo, uint64, error) { @@ -1208,14 +1401,17 @@ func (a *covenantArkClient) coinSelectOnchain( descriptorStr := strings.ReplaceAll( a.BoardingDescriptorTemplate, "USER", myPubkeyStr, ) - desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + boardingScript, err := tree.ParseVtxoScript(descriptorStr) if err != nil { return nil, 0, err } - _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) - if err != nil { - return nil, 0, err + var boardingTimeout uint + + if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok { + boardingTimeout = defaultVtxo.ExitDelay + } else { + return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", descriptorStr) } now := time.Now() @@ -1387,13 +1583,17 @@ func (a *covenantArkClient) getVtxos( } 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) { - inputs := make([]client.Input, 0, len(boardingUtxo)) + inputs := make([]client.Input, 0, len(boardingUtxos)) - for _, utxo := range boardingUtxo { - inputs = append(inputs, client.BoardingInput{ - VtxoKey: client.VtxoKey{ + boardingDescriptor := strings.ReplaceAll( + a.BoardingDescriptorTemplate, "USER", mypubkey[2:], + ) + + for _, utxo := range boardingUtxos { + inputs = append(inputs, client.Input{ + Outpoint: client.Outpoint{ Txid: utxo.Txid, VOut: utxo.Vout, }, @@ -1413,7 +1613,7 @@ func (a *covenantArkClient) selfTransferAllPendingPayments( } 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 { return "", err @@ -1422,6 +1622,21 @@ func (a *covenantArkClient) selfTransferAllPendingPayments( 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) { utxos, err := a.getClaimableBoardingUtxos(ctx) if err != nil { diff --git a/pkg/client-sdk/covenantless_client.go b/pkg/client-sdk/covenantless_client.go index f9faa6a..6af1899 100644 --- a/pkg/client-sdk/covenantless_client.go +++ b/pkg/client-sdk/covenantless_client.go @@ -13,7 +13,6 @@ import ( "github.com/ark-network/ark/common" "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/pkg/client-sdk/client" "github.com/ark-network/ark/pkg/client-sdk/explorer" @@ -28,6 +27,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" log "github.com/sirupsen/logrus" ) @@ -425,18 +425,27 @@ func (a *covenantlessArkClient) CollaborativeRedeem( if err != nil { return "", err } + + desc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddr) + if err != nil { + return "", err + } + receivers = append(receivers, client.Output{ - Address: offchainAddr, - Amount: changeAmount, + Descriptor: desc, + Amount: changeAmount, }) } inputs := make([]client.Input, 0, len(selectedCoins)) for _, coin := range selectedCoins { - inputs = append(inputs, client.VtxoKey{ - Txid: coin.Txid, - VOut: coin.VOut, + inputs = append(inputs, client.Input{ + Outpoint: client.Outpoint{ + Txid: coin.Txid, + VOut: coin.VOut, + }, + Descriptor: coin.Descriptor, }) } @@ -459,7 +468,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem( } poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, false, receivers, roundEphemeralKey, + ctx, paymentID, selectedCoins, nil, "", receivers, roundEphemeralKey, ) if err != nil { return "", err @@ -478,7 +487,7 @@ func (a *covenantlessArkClient) SendAsync( netParams := utils.ToBitcoinNetwork(a.Network) for _, receiver := range receivers { - isOnchain, _, _, err := utils.ParseBitcoinAddress(receiver.To(), netParams) + isOnchain, _, err := utils.ParseBitcoinAddress(receiver.To(), netParams) if err != nil { return "", err } @@ -516,9 +525,27 @@ func (a *covenantlessArkClient) SendAsync( 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{ - Address: receiver.To(), - Amount: receiver.Amount(), + Descriptor: desc, + Amount: receiver.Amount(), }) sumOfReceivers += receiver.Amount() } @@ -535,17 +562,28 @@ func (a *covenantlessArkClient) SendAsync( } if changeAmount > 0 { + changeDesc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddrs[0]) + if err != nil { + return "", err + } + changeReceiver := client.Output{ - Address: offchainAddrs[0], - Amount: changeAmount, + Descriptor: changeDesc, + Amount: changeAmount, } receiversOutput = append(receiversOutput, changeReceiver) } - inputs := make([]client.VtxoKey, 0, len(selectedCoins)) + inputs := make([]client.Input, 0, len(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( @@ -556,23 +594,13 @@ func (a *covenantlessArkClient) SendAsync( // 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) if err != nil { return "", err } if err = a.client.CompletePayment( - ctx, signedRedeemTx, signedUnconditionalForfeitTxs, + ctx, signedRedeemTx, unconditionalForfeitTxs, ); err != nil { return "", err } @@ -612,13 +640,23 @@ func (a *covenantlessArkClient) Claim(ctx context.Context) (string, error) { return "", nil } - receiver := client.Output{ - Address: myselfOffchain, - Amount: pendingBalance, + desc, err := a.offchainAddressToDefaultVtxoDescriptor(myselfOffchain) + if err != nil { + return "", err } - desc := strings.ReplaceAll(a.BoardingDescriptorTemplate, "USER", hex.EncodeToString(schnorr.SerializePubKey(mypubkey))) - return a.selfTransferAllPendingPayments(ctx, pendingVtxos, boardingUtxos, receiver, desc) + receiver := client.Output{ + Descriptor: desc, + Amount: pendingBalance, + } + + return a.selfTransferAllPendingPayments( + ctx, + pendingVtxos, + boardingUtxos, + receiver, + hex.EncodeToString(mypubkey.SerializeCompressed()), + ) } func (a *covenantlessArkClient) GetTransactionHistory(ctx context.Context) ([]Transaction, error) { @@ -818,9 +856,14 @@ func (a *covenantlessArkClient) sendOffchain( 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{ - Address: receiver.To(), - Amount: receiver.Amount(), + Descriptor: desc, + Amount: receiver.Amount(), }) sumOfReceivers += receiver.Amount() } @@ -846,18 +889,27 @@ func (a *covenantlessArkClient) sendOffchain( if err != nil { return "", err } + + desc, err := a.offchainAddressToDefaultVtxoDescriptor(offchainAddr) + if err != nil { + return "", err + } + changeReceiver := client.Output{ - Address: offchainAddr, - Amount: changeAmount, + Descriptor: desc, + Amount: changeAmount, } receiversOutput = append(receiversOutput, changeReceiver) } inputs := make([]client.Input, 0, len(selectedCoins)) for _, coin := range selectedCoins { - inputs = append(inputs, client.VtxoKey{ - Txid: coin.Txid, - VOut: coin.VOut, + inputs = append(inputs, client.Input{ + Outpoint: client.Outpoint{ + Txid: coin.Txid, + VOut: coin.VOut, + }, + Descriptor: coin.Descriptor, }) } @@ -882,7 +934,7 @@ func (a *covenantlessArkClient) sendOffchain( log.Infof("payment registered with id: %s", paymentID) poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, false, receiversOutput, roundEphemeralKey, + ctx, paymentID, selectedCoins, nil, "", receiversOutput, roundEphemeralKey, ) if err != nil { return "", err @@ -926,25 +978,38 @@ func (a *covenantlessArkClient) addInputs( Sequence: sequence, }) - _, leafProof, err := bitcointree.ComputeVtxoTaprootScript( - userPubkey, aspPubkey, utxo.Delay, - ) + vtxoScript := &bitcointree.DefaultVtxoScript{ + Owner: userPubkey, + Asp: aspPubkey, + ExitDelay: utxo.Delay, + } + + exitClosure := &bitcointree.CSVSigClosure{ + Pubkey: userPubkey, + Seconds: uint(utxo.Delay), + } + + exitLeaf, err := exitClosure.Leaf() if err != nil { return err } - controlBlock := leafProof.ToControlBlock(bitcointree.UnspendableKey()) - controlBlockBytes, err := controlBlock.ToBytes() + _, taprootTree, err := vtxoScript.TapTree() if err != nil { 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{ TaprootLeafScript: []*psbt.TaprootTapLeafScript{ { - ControlBlock: controlBlockBytes, + ControlBlock: leafProof.ControlBlock, Script: leafProof.Script, - LeafVersion: leafProof.LeafVersion, + LeafVersion: txscript.BaseLeafVersion, }, }, }) @@ -957,7 +1022,8 @@ func (a *covenantlessArkClient) handleRoundStream( ctx context.Context, paymentID string, vtxosToSign []client.Vtxo, - mustSignRoundTx bool, + boardingUtxos []explorer.Utxo, + boardingDescriptor string, receivers []client.Output, roundEphemeralKey *secp256k1.PrivateKey, ) (string, error) { @@ -1033,7 +1099,7 @@ func (a *covenantlessArkClient) handleRoundStream( log.Info("a round finalization started") signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization( - ctx, event.(client.RoundFinalizationEvent), vtxosToSign, mustSignRoundTx, receivers, + ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, boardingDescriptor, receivers, ) if err != nil { return "", err @@ -1134,30 +1200,103 @@ func (a *covenantlessArkClient) handleRoundSigningNoncesGenerated( } func (a *covenantlessArkClient) handleRoundFinalization( - ctx context.Context, event client.RoundFinalizationEvent, - vtxos []client.Vtxo, mustSignRoundTx bool, receivers []client.Output, -) (signedForfeits []string, signedRoundTx string, err error) { + ctx context.Context, + event client.RoundFinalizationEvent, + vtxos []client.Vtxo, + boardingUtxos []explorer.Utxo, + boardingDescriptor string, + receivers []client.Output, +) ([]string, string, error) { if err := a.validateCongestionTree(event, receivers); err != nil { 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 { - signedForfeits, err = a.loopAndSign( - ctx, event.ForfeitTxs, vtxos, event.Connectors, + signedForfeits, err := a.createAndSignForfeits( + ctx, vtxos, event.Connectors, event.MinRelayFeeRate, myPubkey, ) if err != nil { - return + return nil, "", err } + + forfeits = signedForfeits } - if mustSignRoundTx { - signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, event.Tx) + if len(boardingUtxos) > 0 { + boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingDescriptor) 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 + } + } + } + + 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 + return forfeits, "", nil } func (a *covenantlessArkClient) validateCongestionTree( @@ -1169,8 +1308,7 @@ func (a *covenantlessArkClient) validateCongestionTree( return err } - netParams := utils.ToBitcoinNetwork(a.Network) - if !utils.IsBitcoinOnchainOnly(receivers, netParams) { + if !utils.IsOnchainOnly(receivers) { if err := bitcointree.ValidateCongestionTree( event.Tree, poolTx, a.StoreData.AspPubkey, a.RoundLifetime, ); err != nil { @@ -1183,7 +1321,7 @@ func (a *covenantlessArkClient) validateCongestionTree( // } if err := a.validateReceivers( - ptx, receivers, event.Tree, a.StoreData.AspPubkey, + ptx, receivers, event.Tree, ); err != nil { return err } @@ -1197,15 +1335,14 @@ func (a *covenantlessArkClient) validateReceivers( ptx *psbt.Packet, receivers []client.Output, congestionTree tree.CongestionTree, - aspPubkey *secp256k1.PublicKey, ) error { netParams := utils.ToBitcoinNetwork(a.Network) for _, receiver := range receivers { - isOnChain, onchainScript, userPubkey, err := utils.ParseBitcoinAddress( + isOnChain, onchainScript, err := utils.ParseBitcoinAddress( receiver.Address, netParams, ) if err != nil { - return err + return fmt.Errorf("invalid receiver address: %s err = %s", receiver.Address, err) } if isOnChain { @@ -1214,7 +1351,7 @@ func (a *covenantlessArkClient) validateReceivers( } } else { if err := a.validateOffChainReceiver( - congestionTree, receiver, userPubkey, aspPubkey, + congestionTree, receiver, ); err != nil { return err } @@ -1250,12 +1387,15 @@ func (a *covenantlessArkClient) validateOnChainReceiver( func (a *covenantlessArkClient) validateOffChainReceiver( congestionTree tree.CongestionTree, receiver client.Output, - userPubkey, aspPubkey *secp256k1.PublicKey, ) error { 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 { return err } @@ -1298,57 +1438,107 @@ func (a *covenantlessArkClient) validateOffChainReceiver( return nil } -func (a *covenantlessArkClient) loopAndSign( +func (a *covenantlessArkClient) createAndSignForfeits( ctx context.Context, - forfeitTxs []string, vtxosToSign []client.Vtxo, connectors []string, + vtxosToSign []client.Vtxo, + connectors []string, + feeRate chainfee.SatPerKVByte, + myPubkey *secp256k1.PublicKey, ) ([]string, error) { - signedForfeits := make([]string, 0) - connectorsTxids := make([]string, 0, len(connectors)) + signedForfeits := make([]string, 0) + connectorsPsets := make([]*psbt.Packet, 0, len(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) + + connectorsPsets = append(connectorsPsets, p) } - for _, forfeitTx := range forfeitTxs { - ptx, err := psbt.NewFromRawBytes(strings.NewReader(forfeitTx), true) + for _, vtxo := range vtxosToSign { + vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Descriptor) if err != nil { return nil, err } - for _, input := range ptx.UnsignedTx.TxIn { - inputTxid := input.PreviousOutPoint.Hash.String() + vtxoTapKey, vtxoTapTree, err := vtxoScript.TapTree() + if err != nil { + return nil, err + } - 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 - } - } + feeAmount, err := common.ComputeForfeitMinRelayFee(feeRate, vtxoTapTree) + if err != nil { + return nil, err + } - if !connectorFound { - return nil, fmt.Errorf("connector txid %s not found in the connectors list", connectorTxid) - } + vtxoOutputScript, err := common.P2TRScript(vtxoTapKey) + if err != nil { + return nil, err + } - signedForfeitTx, err := a.wallet.SignTransaction(ctx, a.explorer, forfeitTx) - if err != nil { - return nil, err - } + vtxoTxHash, err := chainhash.NewHashFromStr(vtxo.Txid) + if err != nil { + return nil, err + } - signedForfeits = append(signedForfeits, signedForfeitTx) + 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 @@ -1371,14 +1561,18 @@ func (a *covenantlessArkClient) coinSelectOnchain( descriptorStr := strings.ReplaceAll( a.BoardingDescriptorTemplate, "USER", myPubkeyStr, ) - desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + + boardingScript, err := bitcointree.ParseVtxoScript(descriptorStr) if err != nil { return nil, 0, err } - _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) - if err != nil { - return nil, 0, err + var boardingTimeout uint + + if defaultVtxo, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok { + boardingTimeout = defaultVtxo.ExitDelay + } else { + return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", descriptorStr) } now := time.Now() @@ -1567,14 +1761,18 @@ func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context) ( descriptorStr := strings.ReplaceAll( a.BoardingDescriptorTemplate, "USER", myPubkeyStr, ) - desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + + boardingScript, err := bitcointree.ParseVtxoScript(descriptorStr) if err != nil { return nil, err } - _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) - if err != nil { - return nil, err + var boardingTimeout uint + + 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) @@ -1644,17 +1842,27 @@ func (a *covenantlessArkClient) getVtxos( } 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) { - 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 { - 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 { - inputs = append(inputs, client.BoardingInput{ - VtxoKey: client.VtxoKey{ + for _, utxo := range boardingUtxos { + inputs = append(inputs, client.Input{ + Outpoint: client.Outpoint{ Txid: utxo.Txid, VOut: utxo.Vout, }, @@ -1682,7 +1890,7 @@ func (a *covenantlessArkClient) selfTransferAllPendingPayments( } roundTxid, err := a.handleRoundStream( - ctx, paymentID, pendingVtxos, len(boardingUtxo) > 0, outputs, roundEphemeralKey, + ctx, paymentID, pendingVtxos, boardingUtxos, boardingDescriptor, outputs, roundEphemeralKey, ) if err != nil { return "", err @@ -1691,6 +1899,42 @@ func (a *covenantlessArkClient) selfTransferAllPendingPayments( 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) { utxos, err := a.getClaimableBoardingUtxos(ctx) if err != nil { diff --git a/pkg/client-sdk/go.mod b/pkg/client-sdk/go.mod index 5779d1f..2e4a056 100644 --- a/pkg/client-sdk/go.mod +++ b/pkg/client-sdk/go.mod @@ -18,6 +18,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 github.com/go-openapi/swag v0.23.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/stretchr/testify v1.9.0 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/go-socks v0.0.0-20170105172521-4720035b7bfd // 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/containerd/continuity v0.4.3 // 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/docker/docker v27.1.1+incompatible // 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/stdr v1.2.2 // 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/loads v0.22.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/grpc-ecosystem/grpc-gateway/v2 v2.21.0 // 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/jrick/logrotate v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kkdai/bstream v1.0.0 // 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/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/fn v1.2.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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // 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/runc v1.1.13 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect go.etcd.io/etcd/client/v2 v2.305.15 // indirect diff --git a/pkg/client-sdk/go.sum b/pkg/client-sdk/go.sum index e158eda..b6e59a9 100644 --- a/pkg/client-sdk/go.sum +++ b/pkg/client-sdk/go.sum @@ -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/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.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 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-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/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= 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/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 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.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.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= 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.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.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= 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/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/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/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/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/client-sdk/internal/utils/utils.go b/pkg/client-sdk/internal/utils/utils.go index 7b6c6ae..d8504cc 100644 --- a/pkg/client-sdk/internal/utils/utils.go +++ b/pkg/client-sdk/internal/utils/utils.go @@ -67,60 +67,34 @@ func CoinSelect( } func ParseLiquidAddress(addr string) ( - bool, []byte, *secp256k1.PublicKey, error, + bool, []byte, error, ) { outputScript, err := address.ToOutputScript(addr) if err != nil { - _, userPubkey, _, err := common.DecodeAddress(addr) - if err != nil { - return false, nil, nil, err - } - return false, nil, userPubkey, nil + return false, nil, nil } - return true, outputScript, nil, nil + return true, outputScript, nil } func ParseBitcoinAddress(addr string, net chaincfg.Params) ( - bool, []byte, *secp256k1.PublicKey, error, + bool, []byte, error, ) { btcAddr, err := btcutil.DecodeAddress(addr, &net) if err != nil { - _, userPubkey, _, err := common.DecodeAddress(addr) - if err != nil { - return false, nil, nil, err - } - return false, nil, userPubkey, nil + return false, nil, nil } onchainScript, err := txscript.PayToAddrScript(btcAddr) 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 { - isOnChain, _, _, err := ParseBitcoinAddress(receiver.Address, net) - 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 - } + isOnChain := len(receiver.Address) > 0 if !isOnChain { return false diff --git a/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go b/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go index a79a99e..825e4dc 100644 --- a/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go +++ b/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go @@ -9,7 +9,6 @@ import ( "github.com/ark-network/ark/common" "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/internal/utils" "github.com/ark-network/ark/pkg/client-sdk/store" @@ -219,9 +218,13 @@ func (w *bitcoinWallet) getAddress( netParams := utils.ToBitcoinNetwork(data.Network) - vtxoTapKey, _, err := bitcointree.ComputeVtxoTaprootScript( - w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay), - ) + defaultVtxoScript := &bitcointree.DefaultVtxoScript{ + Asp: data.AspPubkey, + Owner: w.walletData.Pubkey, + ExitDelay: uint(data.UnilateralExitDelay), + } + + vtxoTapKey, _, err := defaultVtxoScript.TapTree() if err != nil { return "", "", "", err } @@ -239,19 +242,12 @@ func (w *bitcoinWallet) getAddress( data.BoardingDescriptorTemplate, "USER", myPubkeyStr, ) - desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + boardingVtxoScript, err := bitcointree.ParseVtxoScript(descriptorStr) if err != nil { return "", "", "", err } - _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) - if err != nil { - return "", "", "", err - } - - boardingTapKey, _, err := bitcointree.ComputeVtxoTaprootScript( - w.walletData.Pubkey, data.AspPubkey, boardingTimeout, - ) + boardingTapKey, _, err := boardingVtxoScript.TapTree() if err != nil { return "", "", "", err } diff --git a/pkg/client-sdk/wallet/singlekey/liquid_wallet.go b/pkg/client-sdk/wallet/singlekey/liquid_wallet.go index 425f4f5..4153e49 100644 --- a/pkg/client-sdk/wallet/singlekey/liquid_wallet.go +++ b/pkg/client-sdk/wallet/singlekey/liquid_wallet.go @@ -8,7 +8,6 @@ import ( "strings" "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/pkg/client-sdk/explorer" "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/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" + "github.com/vulpemventures/go-elements/payment" "github.com/vulpemventures/go-elements/psetv2" "github.com/vulpemventures/go-elements/transaction" ) @@ -165,7 +165,7 @@ func (s *liquidWallet) SignTransaction( switch c := closure.(type) { case *tree.CSVSigClosure: sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], serializedPubKey[1:]) - case *tree.ForfeitClosure: + case *tree.MultisigClosure: sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], serializedPubKey[1:]) } @@ -242,9 +242,23 @@ func (w *liquidWallet) getAddress( liquidNet := utils.ToElementsNetwork(data.Network) - _, _, _, redemptionAddr, err := tree.ComputeVtxoTaprootScript( - w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay), liquidNet, - ) + vtxoScript := &tree.DefaultVtxoScript{ + 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 { return "", "", "", err } @@ -254,19 +268,22 @@ func (w *liquidWallet) getAddress( data.BoardingDescriptorTemplate, "USER", myPubkeyStr, ) - desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + onboardingScript, err := tree.ParseVtxoScript(descriptorStr) if err != nil { return "", "", "", err } - _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) + tapKey, _, err := onboardingScript.TapTree() if err != nil { return "", "", "", err } - _, _, _, boardingAddr, err := tree.ComputeVtxoTaprootScript( - w.walletData.Pubkey, data.AspPubkey, boardingTimeout, liquidNet, - ) + p2tr, err := payment.FromTweakedKey(tapKey, &liquidNet, nil) + if err != nil { + return "", "", "", err + } + + boardingAddr, err := p2tr.TaprootAddress() if err != nil { return "", "", "", err } diff --git a/server/internal/app-config/config.go b/server/internal/app-config/config.go index d536a19..863418e 100644 --- a/server/internal/app-config/config.go +++ b/server/internal/app-config/config.go @@ -277,11 +277,11 @@ func (c *Config) txBuilderService() error { switch c.TxBuilderType { case "covenant": svc = txbuilder.NewTxBuilder( - c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay, + c.wallet, c.Network, c.RoundLifetime, c.BoardingExitDelay, ) case "covenantless": svc = cltxbuilder.NewTxBuilder( - c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay, + c.wallet, c.Network, c.RoundLifetime, c.BoardingExitDelay, ) default: err = fmt.Errorf("unknown tx builder type") diff --git a/server/internal/core/application/covenant.go b/server/internal/core/application/covenant.go index 924972a..3ee7f75 100644 --- a/server/internal/core/application/covenant.go +++ b/server/internal/core/application/covenant.go @@ -18,6 +18,8 @@ import ( "github.com/decred/dcrd/dcrec/secp256k1/v4" log "github.com/sirupsen/logrus" "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/transaction" ) @@ -118,159 +120,158 @@ func (s *covenantService) Stop() { close(s.eventsCh) } -func (s *covenantService) GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, error) { - addr, _, err := s.builder.GetBoardingScript(userPubkey, s.pubkey) - if err != nil { - return "", err +func (s *covenantService) GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, string, error) { + vtxoScript := &tree.DefaultVtxoScript{ + Asp: s.pubkey, + 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) { - vtxosInputs := make([]domain.VtxoKey, 0) - boardingInputs := make([]Input, 0) +func (s *covenantService) SpendVtxos(ctx context.Context, inputs []ports.Input) (string, error) { + vtxosInputs := make([]domain.Vtxo, 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() - for _, in := range boardingInputs { - if _, ok := boardingTxs[in.Txid]; !ok { - txhex, err := s.wallet.GetTransaction(ctx, in.Txid) + boardingTxs := make(map[string]*transaction.Transaction, 0) // txid -> txhex + + 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 { + return "", fmt.Errorf("failed to get tx %s: %s", input.Txid, err) + } + + tx, err := transaction.NewTxFromHex(txhex) + if err != nil { + 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 { + 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 { + return "", fmt.Errorf("tx %s expired", input.Txid) + } + + boardingTxs[input.Txid] = tx + } + + tx := boardingTxs[input.Txid] + boardingInput, err := s.newBoardingInput(tx, input) if err != nil { - return "", fmt.Errorf("failed to get tx %s: %s", in.Txid, err) + return "", err } - confirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, in.Txid) - if err != nil { - return "", fmt.Errorf("failed to check tx %s: %s", in.Txid, err) - } - - if !confirmed { - return "", fmt.Errorf("tx %s not confirmed", in.Txid) - } - - if blocktime+int64(s.boardingExitDelay) < now { - return "", fmt.Errorf("tx %s expired", in.Txid) - } - - boardingTxs[in.Txid] = txhex + 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) } - utxos := make([]ports.BoardingInput, 0, len(boardingInputs)) - - 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) + payment, err := domain.NewPayment(vtxosInputs) if err != nil { return "", err } - if err := s.paymentRequests.push(*payment, utxos); err != nil { + if err := s.paymentRequests.push(*payment, boardingInputs); err != nil { return "", err } return payment.Id, nil } -func (s *covenantService) newBoardingInput( - txhex string, vout uint32, desc descriptor.TaprootDescriptor, -) (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) { +func (s *covenantService) newBoardingInput(tx *transaction.Transaction, input ports.Input) (*ports.BoardingInput, error) { + if len(tx.Outputs) <= int(input.VtxoKey.VOut) { return nil, fmt.Errorf("output not found") } - out := tx.Outputs[vout] - script := out.Script + output := tx.Outputs[input.VtxoKey.VOut] - 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") } - scriptFromDescriptor, err := tree.ComputeOutputScript(desc) - 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) + amount, err := elementsutil.ValueFromBytes(output.Value) if err != nil { return nil, fmt.Errorf("failed to parse value: %s", err) } - return &boardingInput{ - txId: tx.TxHash(), - vout: vout, - boardingPubKey: pubkey, - amount: value, + boardingScript, err := tree.ParseVtxoScript(input.Descriptor) + if err != nil { + return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err) + } + + 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 } @@ -315,7 +316,7 @@ func (s *covenantService) CompleteAsyncPayment(ctx context.Context, redeemTx str 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") } @@ -373,10 +374,10 @@ func (s *covenantService) GetInfo(ctx context.Context) (*ServiceInfo, error) { Network: s.network.Name, Dust: dust, BoardingDescriptorTemplate: fmt.Sprintf( - descriptor.BoardingDescriptorTemplate, + descriptor.DefaultVtxoDescriptorTemplate, hex.EncodeToString(tree.UnspendableKey().SerializeCompressed()), - hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)), "USER", + hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)), s.boardingExitDelay, "USER", ), @@ -496,8 +497,10 @@ func (s *covenantService) startFinalization() { var forfeitTxs, connectors []string + minRelayFeeRate := s.wallet.MinRelayFeeRate(ctx) + 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 { round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err)) 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) + + 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( @@ -515,8 +524,6 @@ func (s *covenantService) startFinalization() { return } - s.forfeitTxs.push(forfeitTxs) - 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] switch e := lastEvent.(type) { case domain.RoundFinalizationStarted: - forfeitTxs := s.forfeitTxs.view() ev := domain.RoundFinalizationStarted{ - Id: e.Id, - CongestionTree: e.CongestionTree, - Connectors: e.Connectors, - PoolTx: e.PoolTx, - UnsignedForfeitTxs: forfeitTxs, + Id: e.Id, + CongestionTree: e.CongestionTree, + Connectors: e.Connectors, + PoolTx: e.PoolTx, + MinRelayFeeRate: int64(s.wallet.MinRelayFeeRate(context.Background())), } s.lastEvent = ev s.eventsCh <- ev @@ -888,31 +894,56 @@ func (s *covenantService) getNewVtxos(round *domain.Round) []domain.Vtxo { for _, node := range leaves { tx, _ := psetv2.NewPsetFromBase64(node.Tx) for i, out := range tx.Outputs { + if len(out.Script) <= 0 { + continue // skip fee outputs + } + + desc := "" + found := false + for _, p := range round.Payments { - var pubkey string - found := false + if found { + break + } + for _, r := range p.Receivers { if r.IsOnchain() { continue } - buf, _ := hex.DecodeString(r.Pubkey) - pk, _ := secp256k1.ParsePubKey(buf) - script, _ := s.builder.GetVtxoScript(pk, s.pubkey) + vtxoScript, err := tree.ParseVtxoScript(r.Descriptor) + if err != nil { + log.WithError(err).Warn("failed to parse vtxo descriptor") + continue + } + + tapKey, _, err := vtxoScript.TapTree() + if err != nil { + log.WithError(err).Warn("failed to compute vtxo tap key") + continue + } + + script, err := common.P2TRScript(tapKey) + if err != nil { + log.WithError(err).Warn("failed to create vtxo scriptpubkey") + continue + } + if bytes.Equal(script, out.Script) { found = true - pubkey = r.Pubkey + desc = r.Descriptor break } } - if found { - vtxos = append(vtxos, domain.Vtxo{ - VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)}, - Receiver: domain.Receiver{Pubkey: pubkey, Amount: out.Value}, - PoolTx: round.Txid, - }) - break - } + } + + if found { + vtxos = append(vtxos, domain.Vtxo{ + VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)}, + Receiver: domain.Receiver{Descriptor: desc, Amount: uint64(out.Value)}, + PoolTx: round.Txid, + }) + break } } } @@ -984,15 +1015,17 @@ func (s *covenantService) restoreWatchingVtxos() error { func (s *covenantService) extractVtxosScripts(vtxos []domain.Vtxo) ([]string, error) { indexedScripts := make(map[string]struct{}) for _, vtxo := range vtxos { - buf, err := hex.DecodeString(vtxo.Pubkey) + vtxoScript, err := tree.ParseVtxoScript(vtxo.Receiver.Descriptor) if err != nil { return nil, err } - userPubkey, err := secp256k1.ParsePubKey(buf) + + tapKey, _, err := vtxoScript.TapTree() if err != nil { return nil, err } - script, err := s.builder.GetVtxoScript(userPubkey, s.pubkey) + + script, err := common.P2TRScript(tapKey) if err != nil { return nil, err } @@ -1019,6 +1052,19 @@ func (s *covenantService) saveEvents( 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( forfeits []string, connectorTxid string, connectorVout uint32, vtxoTxid string, ) (string, error) { diff --git a/server/internal/core/application/covenantless.go b/server/internal/core/application/covenantless.go index a23fb95..01f0294 100644 --- a/server/internal/core/application/covenantless.go +++ b/server/internal/core/application/covenantless.go @@ -16,7 +16,9 @@ import ( "github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/ports" "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/decred/dcrd/dcrec/secp256k1/v4" @@ -153,10 +155,9 @@ func (s *covenantlessService) CompleteAsyncPayment( return fmt.Errorf("async payment not found") } - txs := append([]string{redeemTx}, unconditionalForfeitTxs...) vtxoRepo := s.repoManager.Vtxos() - for _, tx := range txs { + for _, tx := range []string{redeemTx} { ptx, err := psbt.NewFromRawBytes(strings.NewReader(tx), true) if err != nil { return fmt.Errorf("failed to parse tx: %s", err) @@ -200,39 +201,40 @@ func (s *covenantlessService) CompleteAsyncPayment( return fmt.Errorf("vtxo already swept") } - // verify that the user signs the tx using the right public key - - vtxoPublicKey, err := hex.DecodeString(vtxo[0].Pubkey) + vtxoScript, err := bitcointree.ParseVtxoScript(vtxo[0].Descriptor) 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 parse pubkey: %s", err) + return fmt.Errorf("failed to get taproot key: %s", err) } - xonlyPubkey := schnorr.SerializePubKey(pubkey) + // verify that the user signs a forfeit closure + var userPubKey *secp256k1.PublicKey - // find signature belonging to the pubkey - found := false + aspXOnlyPubKey := schnorr.SerializePubKey(s.pubkey) for _, sig := range input.TaprootScriptSpendSig { - if bytes.Equal(sig.XOnlyPubKey, xonlyPubkey) { - found = true + if !bytes.Equal(sig.XOnlyPubKey, aspXOnlyPubKey) { + parsed, err := schnorr.ParsePubKey(sig.XOnlyPubKey) + if err != nil { + return fmt.Errorf("failed to parse pubkey: %s", err) + } + userPubKey = parsed break } } - if !found { - return fmt.Errorf("signature not found for pubkey") + if userPubKey == nil { + return fmt.Errorf("redeem transaction is not signed") } // verify witness utxo - - pkscript, err := s.builder.GetVtxoScript(pubkey, s.pubkey) + pkscript, err := common.P2TRScript(vtxoTapKey) 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) { @@ -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 { spentVtxos = append(spentVtxos, domain.VtxoKey{ Txid: in.PreviousOutPoint.Hash.String(), @@ -267,8 +269,8 @@ func (s *covenantlessService) CompleteAsyncPayment( VOut: uint32(outIndex), }, Receiver: domain.Receiver{ - Pubkey: asyncPayData.receivers[outIndex].Pubkey, - Amount: uint64(out.Value), + Descriptor: asyncPayData.receivers[outIndex].Descriptor, + Amount: uint64(out.Value), }, ExpireAt: asyncPayData.expireAt, AsyncPayment: &domain.AsyncPaymentTxs{ @@ -300,9 +302,14 @@ func (s *covenantlessService) CompleteAsyncPayment( } 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) { - 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 { return "", nil, err } @@ -310,6 +317,8 @@ func (s *covenantlessService) CreateAsyncPayment( return "", nil, fmt.Errorf("vtxos not found") } + vtxosInputs := make([]domain.Vtxo, 0, len(inputs)) + expiration := vtxos[0].ExpireAt for _, vtxo := range vtxos { if vtxo.Spent { @@ -327,10 +336,12 @@ func (s *covenantlessService) CreateAsyncPayment( if vtxo.ExpireAt < expiration { expiration = vtxo.ExpireAt } + + vtxosInputs = append(vtxosInputs, vtxo) } res, err := s.builder.BuildAsyncPaymentTransactions( - vtxos, s.pubkey, receivers, + vtxosInputs, s.pubkey, receivers, ) if err != nil { 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 } -func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []Input) (string, error) { - vtxosInputs := make([]domain.VtxoKey, 0) - boardingInputs := make([]Input, 0) +func (s *covenantlessService) GetBoardingAddress( + ctx context.Context, userPubkey *secp256k1.PublicKey, +) (address string, descriptor string, err error) { + vtxoScript := &bitcointree.DefaultVtxoScript{ + Asp: s.pubkey, + Owner: userPubkey, + ExitDelay: uint(s.boardingExitDelay), + } + + tapKey, _, err := vtxoScript.TapTree() + if err != nil { + return "", "", fmt.Errorf("failed to get taproot key: %s", err) + } + + addr, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(tapKey), s.chainParams(), + ) + if err != nil { + return "", "", fmt.Errorf("failed to get address: %s", err) + } + + return addr.EncodeAddress(), vtxoScript.ToDescriptor(), nil +} + +func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Input) (string, error) { + vtxosInputs := make([]domain.Vtxo, 0) + boardingInputs := make([]ports.BoardingInput, 0) + + now := time.Now().Unix() + + boardingTxs := make(map[string]wire.MsgTx, 0) // txid -> txhex for _, input := range inputs { - if input.IsVtxo() { - vtxosInputs = append(vtxosInputs, input.VtxoKey()) + 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 { + return "", fmt.Errorf("failed to get tx %s: %s", input.Txid, err) + } + + 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 { + return "", fmt.Errorf("failed to check tx %s: %s", input.Txid, err) + } + + if !confirmed { + 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 { + return "", fmt.Errorf("tx %s expired", input.Txid) + } + + boardingTxs[input.Txid] = tx + } + + tx := boardingTxs[input.Txid] + boardingInput, err := s.newBoardingInput(tx, input) + if err != nil { + return "", err + } + + boardingInputs = append(boardingInputs, *boardingInput) continue } - boardingInputs = append(boardingInputs, input) + 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) } - 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() - - for _, in := range boardingInputs { - if _, ok := boardingTxs[in.Txid]; !ok { - // check if the tx exists and is confirmed - txhex, err := s.wallet.GetTransaction(ctx, in.Txid) - if err != nil { - return "", fmt.Errorf("failed to get tx %s: %s", in.Txid, err) - } - - confirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, in.Txid) - if err != nil { - return "", fmt.Errorf("failed to check tx %s: %s", in.Txid, err) - } - - if !confirmed { - return "", fmt.Errorf("tx %s not confirmed", in.Txid) - } - - // if the exit path is available, forbid registering the boarding utxo - if blocktime+int64(s.boardingExitDelay) < now { - return "", fmt.Errorf("tx %s expired", in.Txid) - } - - boardingTxs[in.Txid] = txhex - } - } - - utxos := make([]ports.BoardingInput, 0, len(boardingInputs)) - - 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) + payment, err := domain.NewPayment(vtxosInputs) if err != nil { return "", err } - if err := s.paymentRequests.push(*payment, utxos); err != nil { + if err := s.paymentRequests.push(*payment, boardingInputs); err != nil { return "", err } return payment.Id, nil } -func (s *covenantlessService) newBoardingInput(txhex string, vout uint32, desc descriptor.TaprootDescriptor) (ports.BoardingInput, error) { - var tx wire.MsgTx - - 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) { +func (s *covenantlessService) newBoardingInput(tx wire.MsgTx, input ports.Input) (*ports.BoardingInput, error) { + if len(tx.TxOut) <= int(input.VtxoKey.VOut) { return nil, fmt.Errorf("output not found") } - out := tx.TxOut[vout] - script := out.PkScript + output := tx.TxOut[input.VtxoKey.VOut] - scriptFromDescriptor, err := bitcointree.ComputeOutputScript(desc) - 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) + boardingScript, err := bitcointree.ParseVtxoScript(input.Descriptor) 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) + tapKey, _, err := boardingScript.TapTree() if err != nil { - return nil, fmt.Errorf("failed to get boarding script: %s", err) + return nil, fmt.Errorf("failed to get taproot key: %s", err) } - if !bytes.Equal(script, expectedScript) { - return nil, fmt.Errorf("invalid boarding input output script") + expectedScriptPubKey, err := common.P2TRScript(tapKey) + if err != nil { + return nil, fmt.Errorf("failed to get script pubkey: %s", err) } - return &boardingInput{ - txId: tx.TxHash(), - vout: vout, - boardingPubKey: pubkey, - amount: uint64(out.Value), + 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") + } + } else { + return nil, fmt.Errorf("only default vtxo script is supported for boarding") + } + + return &ports.BoardingInput{ + Amount: uint64(output.Value), + Input: input, }, nil } @@ -584,27 +599,16 @@ func (s *covenantlessService) GetInfo(ctx context.Context) (*ServiceInfo, error) Network: s.network.Name, Dust: dust, BoardingDescriptorTemplate: fmt.Sprintf( - descriptor.BoardingDescriptorTemplate, + descriptor.DefaultVtxoDescriptorTemplate, hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()), - hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)), "USER", + hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)), s.boardingExitDelay, "USER", ), }, 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 { pubkeyBytes, err := hex.DecodeString(pubkey) if err != nil { @@ -944,14 +948,22 @@ func (s *covenantlessService) startFinalization() { var forfeitTxs, connectors []string + minRelayFeeRate := s.wallet.MinRelayFeeRate(ctx) + 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 { round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err)) log.WithError(err).Warn("failed to create connectors and forfeit txs") return } 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( @@ -962,8 +974,6 @@ func (s *covenantlessService) startFinalization() { return } - s.forfeitTxs.push(forfeitTxs) - 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] switch e := lastEvent.(type) { case domain.RoundFinalizationStarted: - forfeitTxs := s.forfeitTxs.view() ev := domain.RoundFinalizationStarted{ - Id: e.Id, - CongestionTree: e.CongestionTree, - Connectors: e.Connectors, - PoolTx: e.PoolTx, - UnsignedForfeitTxs: forfeitTxs, + Id: e.Id, + CongestionTree: e.CongestionTree, + Connectors: e.Connectors, + PoolTx: e.PoolTx, + MinRelayFeeRate: int64(s.wallet.MinRelayFeeRate(context.Background())), } s.lastEvent = ev s.eventsCh <- ev @@ -1291,36 +1300,52 @@ func (s *covenantlessService) getNewVtxos(round *domain.Round) []domain.Vtxo { continue } for i, out := range tx.UnsignedTx.TxOut { + desc := "" + found := false + for _, p := range round.Payments { - var pubkey string - found := false + if found { + break + } + for _, r := range p.Receivers { if r.IsOnchain() { continue } - buf, _ := hex.DecodeString(r.Pubkey) - pk, _ := secp256k1.ParsePubKey(buf) - script, err := s.builder.GetVtxoScript(pk, s.pubkey) + vtxoScript, err := bitcointree.ParseVtxoScript(r.Descriptor) 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 } if bytes.Equal(script, out.PkScript) { found = true - pubkey = r.Pubkey + desc = r.Descriptor break } } - if found { - vtxos = append(vtxos, domain.Vtxo{ - VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)}, - Receiver: domain.Receiver{Pubkey: pubkey, Amount: uint64(out.Value)}, - PoolTx: round.Txid, - }) - break - } + } + + if found { + vtxos = append(vtxos, domain.Vtxo{ + VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)}, + Receiver: domain.Receiver{Descriptor: desc, Amount: uint64(out.Value)}, + PoolTx: round.Txid, + }) + break } } } @@ -1391,16 +1416,19 @@ func (s *covenantlessService) restoreWatchingVtxos() error { func (s *covenantlessService) extractVtxosScripts(vtxos []domain.Vtxo) ([]string, error) { indexedScripts := make(map[string]struct{}) + for _, vtxo := range vtxos { - buf, err := hex.DecodeString(vtxo.Pubkey) + vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Receiver.Descriptor) if err != nil { return nil, err } - userPubkey, err := secp256k1.ParsePubKey(buf) + + tapKey, _, err := vtxoScript.TapTree() if err != nil { return nil, err } - script, err := s.builder.GetVtxoScript(userPubkey, s.pubkey) + + script, err := common.P2TRScript(tapKey) if err != nil { return nil, err } @@ -1427,6 +1455,19 @@ func (s *covenantlessService) saveEvents( 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 { mutx.Lock() defer mutx.Unlock() diff --git a/server/internal/core/application/types.go b/server/internal/core/application/types.go index c7fbcfe..6443327 100644 --- a/server/internal/core/application/types.go +++ b/server/internal/core/application/types.go @@ -2,10 +2,9 @@ package application import ( "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/ports" "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -16,7 +15,7 @@ var ( type Service interface { Start() error 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 SignVtxos(ctx context.Context, forfeitTxs []string) error SignRoundTx(ctx context.Context, roundTx string) error @@ -33,12 +32,14 @@ type Service interface { GetInfo(ctx context.Context) (*ServiceInfo, error) // Async payments CreateAsyncPayment( - ctx context.Context, inputs []domain.VtxoKey, receivers []domain.Receiver, + ctx context.Context, inputs []ports.Input, receivers []domain.Receiver, ) (string, []string, error) CompleteAsyncPayment( ctx context.Context, redeemTx string, unconditionalForfeitTxs []string, ) 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 RegisterCosignerPubkey(ctx context.Context, paymentId string, ephemeralPublicKey string) error RegisterCosignerNonces( @@ -67,30 +68,6 @@ type WalletStatus struct { 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 { txid string vout uint32 diff --git a/server/internal/core/application/utils.go b/server/internal/core/application/utils.go index 097c85d..d9a0059 100644 --- a/server/internal/core/application/utils.go +++ b/server/internal/core/application/utils.go @@ -10,7 +10,6 @@ import ( "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/ports" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/sirupsen/logrus" ) @@ -180,14 +179,19 @@ func newForfeitTxsMap(txBuilder ports.TxBuilder) *forfeitTxsMap { 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() defer m.lock.Unlock() for _, tx := range txs { - signed, txid, _ := m.builder.VerifyTapscriptPartialSigs(tx) - m.forfeitTxs[txid] = &signedTx{tx, signed} + txid, err := m.builder.GetTxID(tx) + if err != nil { + return err + } + m.forfeitTxs[txid] = &signedTx{tx, false} } + + return nil } func (m *forfeitTxsMap) sign(txs []string) error { @@ -229,17 +233,6 @@ func (m *forfeitTxsMap) pop() (signed, unsigned []string) { 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 // returns the sweepable outputs as ports.SweepInput mapped by their expiration time func findSweepableOutputs( @@ -313,26 +306,3 @@ func getSpentVtxos(payments map[string]domain.Payment) []domain.VtxoKey { } 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 -} diff --git a/server/internal/core/domain/events.go b/server/internal/core/domain/events.go index 9a2b14a..6b12677 100644 --- a/server/internal/core/domain/events.go +++ b/server/internal/core/domain/events.go @@ -18,12 +18,12 @@ type RoundStarted struct { } type RoundFinalizationStarted struct { - Id string - CongestionTree tree.CongestionTree // BTC: signed - Connectors []string - ConnectorAddress string - UnsignedForfeitTxs []string - PoolTx string + Id string + CongestionTree tree.CongestionTree // BTC: signed + Connectors []string + ConnectorAddress string + PoolTx string + MinRelayFeeRate int64 } type RoundFinalized struct { diff --git a/server/internal/core/domain/payment.go b/server/internal/core/domain/payment.go index 997a17e..885a454 100644 --- a/server/internal/core/domain/payment.go +++ b/server/internal/core/domain/payment.go @@ -68,7 +68,7 @@ func (p Payment) validate(ignoreOuts bool) error { return fmt.Errorf("missing outputs") } 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") } } @@ -96,7 +96,7 @@ func (k VtxoKey) Hash() string { } type Receiver struct { - Pubkey string + Descriptor string Amount uint64 OnchainAddress string } diff --git a/server/internal/core/domain/payment_test.go b/server/internal/core/domain/payment_test.go index 8c19e15..a2a07c8 100644 --- a/server/internal/core/domain/payment_test.go +++ b/server/internal/core/domain/payment_test.go @@ -1,12 +1,23 @@ package domain_test import ( + "fmt" "testing" + "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/server/internal/core/domain" "github.com/stretchr/testify/require" ) +var desc = fmt.Sprintf( + descriptor.DefaultVtxoDescriptorTemplate, + "030000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000001", + 512, + "0000000000000000000000000000000000000000000000000000000000000001", +) + var inputs = []domain.Vtxo{ { VtxoKey: domain.VtxoKey{ @@ -14,8 +25,8 @@ var inputs = []domain.Vtxo{ VOut: 0, }, Receiver: domain.Receiver{ - Pubkey: "030000000000000000000000000000000000000000000000000000000000000001", - Amount: 1000, + Descriptor: desc, + Amount: 1000, }, }, } @@ -40,12 +51,12 @@ func TestPayment(t *testing.T) { err = payment.AddReceivers([]domain.Receiver{ { - Pubkey: "030000000000000000000000000000000000000000000000000000000000000001", - Amount: 450, + Descriptor: desc, + Amount: 450, }, { - Pubkey: "020000000000000000000000000000000000000000000000000000000000000002", - Amount: 550, + Descriptor: desc, + Amount: 550, }, }) require.NoError(t, err) diff --git a/server/internal/core/domain/round_test.go b/server/internal/core/domain/round_test.go index 6108730..4d3255a 100644 --- a/server/internal/core/domain/round_test.go +++ b/server/internal/core/domain/round_test.go @@ -14,28 +14,30 @@ var ( payments = []domain.Payment{ { Id: "0", - Inputs: []domain.Vtxo{{ - VtxoKey: domain.VtxoKey{ - Txid: txid, - VOut: 0, + Inputs: []domain.Vtxo{ + { + VtxoKey: domain.VtxoKey{ + Txid: txid, + VOut: 0, + }, + Receiver: domain.Receiver{ + Descriptor: desc, + Amount: 2000, + }, }, - Receiver: domain.Receiver{ - Pubkey: pubkey, - Amount: 2000, - }, - }}, + }, Receivers: []domain.Receiver{ { - Pubkey: pubkey, - Amount: 700, + Descriptor: desc, + Amount: 700, }, { - Pubkey: pubkey, - Amount: 700, + Descriptor: desc, + Amount: 700, }, { - Pubkey: pubkey, - Amount: 600, + Descriptor: desc, + Amount: 600, }, }, }, @@ -48,8 +50,8 @@ var ( VOut: 0, }, Receiver: domain.Receiver{ - Pubkey: pubkey, - Amount: 1000, + Descriptor: desc, + Amount: 1000, }, }, { @@ -58,21 +60,20 @@ var ( VOut: 0, }, Receiver: domain.Receiver{ - Pubkey: pubkey, - Amount: 1000, + Descriptor: desc, + Amount: 1000, }, }, }, Receivers: []domain.Receiver{{ - Pubkey: pubkey, - Amount: 2000, + Descriptor: desc, + Amount: 2000, }}, }, } emptyPtx = "cHNldP8BAgQCAAAAAQQBAAEFAQABBgEDAfsEAgAAAAA=" emptyTx = "0200000000000000000000" txid = "0000000000000000000000000000000000000000000000000000000000000000" - pubkey = "030000000000000000000000000000000000000000000000000000000000000001" congestionTree = tree.CongestionTree{ { { diff --git a/server/internal/core/ports/tx_builder.go b/server/internal/core/ports/tx_builder.go index 7c0953f..486cd1d 100644 --- a/server/internal/core/ports/tx_builder.go +++ b/server/internal/core/ports/tx_builder.go @@ -5,6 +5,7 @@ import ( "github.com/ark-network/ark/server/internal/core/domain" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) type SweepInput interface { @@ -16,11 +17,14 @@ type SweepInput interface { GetInternalKey() *secp256k1.PublicKey } -type BoardingInput interface { - GetAmount() uint64 - GetIndex() uint32 - GetHash() chainhash.Hash - GetBoardingPubkey() *secp256k1.PublicKey +type Input struct { + domain.VtxoKey + Descriptor string +} + +type BoardingInput struct { + Input + Amount uint64 } type TxBuilder interface { @@ -28,9 +32,8 @@ type TxBuilder interface { aspPubkey *secp256k1.PublicKey, payments []domain.Payment, boardingInputs []BoardingInput, sweptRounds []domain.Round, cosigners ...*secp256k1.PublicKey, ) (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) - GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error) GetSweepInput(parentblocktime int64, node tree.Node) (expirationtime int64, sweepInput SweepInput, err error) FinalizeAndExtract(tx string) (txhex string, err error) VerifyTapscriptPartialSigs(tx string) (valid bool, txid string, err error) @@ -40,6 +43,6 @@ type TxBuilder interface { vtxosToSpend []domain.Vtxo, aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver, ) (*domain.AsyncPaymentTxs, error) - GetBoardingScript(userPubkey, aspPubkey *secp256k1.PublicKey) (addr string, script []byte, err error) VerifyAndCombinePartialTx(dest string, src string) (string, error) + GetTxID(tx string) (string, error) } diff --git a/server/internal/core/ports/wallet.go b/server/internal/core/ports/wallet.go index 02d49f4..3dc9807 100644 --- a/server/internal/core/ports/wallet.go +++ b/server/internal/core/ports/wallet.go @@ -5,6 +5,7 @@ import ( "errors" "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. @@ -30,6 +31,7 @@ type WalletService interface { WaitForSync(ctx context.Context, txid string) error EstimateFees(ctx context.Context, psbt string) (uint64, error) MinRelayFee(ctx context.Context, vbytes uint64) (uint64, error) + MinRelayFeeRate(ctx context.Context) chainfee.SatPerKVByte ListConnectorUtxos(ctx context.Context, connectorAddress string) ([]TxInput, error) MainAccountBalance(ctx context.Context) (uint64, uint64, error) ConnectorsAccountBalance(ctx context.Context) (uint64, uint64, error) diff --git a/server/internal/infrastructure/db/badger/vtxo_repo.go b/server/internal/infrastructure/db/badger/vtxo_repo.go index dea17c5..1608925 100644 --- a/server/internal/infrastructure/db/badger/vtxo_repo.go +++ b/server/internal/infrastructure/db/badger/vtxo_repo.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "path/filepath" + "regexp" "strings" "github.com/ark-network/ark/server/internal/core/domain" @@ -100,7 +101,13 @@ func (r *vtxoRepository) GetAllVtxos( ) ([]domain.Vtxo, []domain.Vtxo, error) { query := badgerhold.Where("Redeemed").Eq(false) 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) if err != nil { diff --git a/server/internal/infrastructure/db/service_test.go b/server/internal/infrastructure/db/service_test.go index f820fbf..67ff5da 100644 --- a/server/internal/infrastructure/db/service_test.go +++ b/server/internal/infrastructure/db/service_test.go @@ -4,12 +4,14 @@ import ( "context" "crypto/rand" "encoding/hex" + "fmt" "os" "reflect" "sort" "testing" "time" + "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/ports" @@ -22,8 +24,26 @@ import ( const ( emptyPtx = "cHNldP8BAgQCAAAAAQQBAAEFAQABBgEDAfsEAgAAAAA=" emptyTx = "0200000000000000000000" - pubkey1 = "0300000000000000000000000000000000000000000000000000000000000000001" - pubkey2 = "0200000000000000000000000000000000000000000000000000000000000000002" + pubkey1 = "00000000000000000000000000000000000000000000000000000000000000001" + pubkey2 = "00000000000000000000000000000000000000000000000000000000000000002" +) + +var desc1 = fmt.Sprintf( + descriptor.DefaultVtxoDescriptorTemplate, + randomString(66), + pubkey1, + pubkey1, + 512, + pubkey1, +) + +var desc2 = fmt.Sprintf( + descriptor.DefaultVtxoDescriptorTemplate, + randomString(66), + pubkey2, + pubkey2, + 512, + pubkey2, ) var congestionTree = [][]tree.Node{ @@ -251,19 +271,20 @@ func testRoundRepository(t *testing.T, svc ports.RepoManager) { PoolTx: randomString(32), ExpireAt: 7980322, Receiver: domain.Receiver{ - Pubkey: randomString(36), - Amount: 300, + Descriptor: randomString(120), + Amount: 300, }, }, }, Receivers: []domain.Receiver{{ - Pubkey: randomString(36), - Amount: 300, + Descriptor: randomString(120), + Amount: 300, }}, }, { Id: uuid.New().String(), Inputs: []domain.Vtxo{ + { VtxoKey: domain.VtxoKey{ Txid: randomString(32), @@ -272,19 +293,19 @@ func testRoundRepository(t *testing.T, svc ports.RepoManager) { PoolTx: randomString(32), ExpireAt: 7980322, Receiver: domain.Receiver{ - Pubkey: randomString(36), - Amount: 600, + Descriptor: randomString(120), + Amount: 600, }, }, }, Receivers: []domain.Receiver{ { - Pubkey: randomString(36), - Amount: 400, + Descriptor: randomString(120), + Amount: 400, }, { - Pubkey: randomString(34), - Amount: 200, + Descriptor: randomString(120), + Amount: 200, }, }, }, @@ -350,8 +371,8 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) { VOut: 0, }, Receiver: domain.Receiver{ - Pubkey: pubkey1, - Amount: 1000, + Descriptor: desc1, + Amount: 1000, }, }, { @@ -360,8 +381,8 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) { VOut: 1, }, Receiver: domain.Receiver{ - Pubkey: pubkey1, - Amount: 2000, + Descriptor: desc1, + Amount: 2000, }, }, } @@ -371,8 +392,8 @@ func testVtxoRepository(t *testing.T, svc ports.RepoManager) { VOut: 1, }, Receiver: domain.Receiver{ - Pubkey: pubkey2, - Amount: 2000, + Descriptor: desc2, + Amount: 2000, }, }) @@ -531,7 +552,7 @@ type sortReceivers []domain.Receiver 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) 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 diff --git a/server/internal/infrastructure/db/sqlite/migration/20240913142235_add-receiver-descriptor.down.sql b/server/internal/infrastructure/db/sqlite/migration/20240913142235_add-receiver-descriptor.down.sql new file mode 100644 index 0000000..b4d7d7f --- /dev/null +++ b/server/internal/infrastructure/db/sqlite/migration/20240913142235_add-receiver-descriptor.down.sql @@ -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; diff --git a/server/internal/infrastructure/db/sqlite/migration/20240913142235_add-receiver-descriptor.up.sql b/server/internal/infrastructure/db/sqlite/migration/20240913142235_add-receiver-descriptor.up.sql new file mode 100644 index 0000000..8e4dcc3 --- /dev/null +++ b/server/internal/infrastructure/db/sqlite/migration/20240913142235_add-receiver-descriptor.up.sql @@ -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; \ No newline at end of file diff --git a/server/internal/infrastructure/db/sqlite/round_repo.go b/server/internal/infrastructure/db/sqlite/round_repo.go index ff880a7..010c1c1 100644 --- a/server/internal/infrastructure/db/sqlite/round_repo.go +++ b/server/internal/infrastructure/db/sqlite/round_repo.go @@ -165,7 +165,7 @@ func (r *roundRepository) AddOrUpdateRound(ctx context.Context, round domain.Rou ctx, queries.UpsertReceiverParams{ PaymentID: payment.Id, - Pubkey: receiver.Pubkey, + Descriptor: receiver.Descriptor, Amount: int64(receiver.Amount), OnchainAddress: receiver.OnchainAddress, }, @@ -320,7 +320,7 @@ func (r *roundRepository) GetSweptRounds(ctx context.Context) ([]domain.Round, e func rowToReceiver(row queries.PaymentReceiverVw) domain.Receiver { return domain.Receiver{ - Pubkey: row.Pubkey.String, + Descriptor: row.Descriptor.String, Amount: uint64(row.Amount.Int64), OnchainAddress: row.OnchainAddress.String, } @@ -413,8 +413,8 @@ func readRoundRows(rows []roundPaymentTxReceiverVtxoRow) ([]*domain.Round, error found := false for _, rcv := range payment.Receivers { - if v.receiver.Pubkey.Valid && v.receiver.Amount.Valid { - if rcv.Pubkey == v.receiver.Pubkey.String && int64(rcv.Amount) == v.receiver.Amount.Int64 { + if v.receiver.Descriptor.Valid && v.receiver.Amount.Valid { + if rcv.Descriptor == v.receiver.Descriptor.String && int64(rcv.Amount) == v.receiver.Amount.Int64 { found = true break } @@ -470,8 +470,8 @@ func rowToPaymentVtxoVw(row queries.PaymentVtxoVw) domain.Vtxo { VOut: uint32(row.Vout.Int64), }, Receiver: domain.Receiver{ - Pubkey: row.Pubkey.String, - Amount: uint64(row.Amount.Int64), + Descriptor: row.Descriptor.String, + Amount: uint64(row.Amount.Int64), }, PoolTx: row.PoolTx.String, SpentBy: row.SpentBy.String, diff --git a/server/internal/infrastructure/db/sqlite/sqlc/queries/models.go b/server/internal/infrastructure/db/sqlite/sqlc/queries/models.go index f81a5af..08547ee 100644 --- a/server/internal/infrastructure/db/sqlite/sqlc/queries/models.go +++ b/server/internal/infrastructure/db/sqlite/sqlc/queries/models.go @@ -15,29 +15,29 @@ type Payment struct { type PaymentReceiverVw struct { PaymentID sql.NullString - Pubkey sql.NullString + Descriptor sql.NullString Amount sql.NullInt64 OnchainAddress sql.NullString } type PaymentVtxoVw struct { - Txid sql.NullString - Vout sql.NullInt64 - Pubkey sql.NullString - Amount sql.NullInt64 - PoolTx sql.NullString - SpentBy sql.NullString - Spent sql.NullBool - Redeemed sql.NullBool - Swept sql.NullBool - ExpireAt sql.NullInt64 - PaymentID sql.NullString - RedeemTx sql.NullString + Txid sql.NullString + Vout sql.NullInt64 + Amount sql.NullInt64 + PoolTx sql.NullString + SpentBy sql.NullString + Spent sql.NullBool + Redeemed sql.NullBool + Swept sql.NullBool + ExpireAt sql.NullInt64 + PaymentID sql.NullString + RedeemTx sql.NullString + Descriptor sql.NullString } type Receiver struct { PaymentID string - Pubkey string + Descriptor string Amount int64 OnchainAddress string } @@ -103,16 +103,16 @@ type UncondForfeitTxVw struct { } type Vtxo struct { - Txid string - Vout int64 - Pubkey string - Amount int64 - PoolTx string - SpentBy string - Spent bool - Redeemed bool - Swept bool - ExpireAt int64 - PaymentID sql.NullString - RedeemTx sql.NullString + Txid string + Vout int64 + Amount int64 + PoolTx string + SpentBy string + Spent bool + Redeemed bool + Swept bool + ExpireAt int64 + PaymentID sql.NullString + RedeemTx sql.NullString + Descriptor sql.NullString } diff --git a/server/internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go b/server/internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go index bf7d53e..80a6f02 100644 --- a/server/internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go +++ b/server/internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go @@ -54,7 +54,7 @@ func (q *Queries) MarkVtxoAsSwept(ctx context.Context, arg MarkVtxoAsSweptParams } 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 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 @@ -78,7 +78,6 @@ func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]SelectNotRedeem if err := rows.Scan( &i.Vtxo.Txid, &i.Vtxo.Vout, - &i.Vtxo.Pubkey, &i.Vtxo.Amount, &i.Vtxo.PoolTx, &i.Vtxo.SpentBy, @@ -88,6 +87,7 @@ func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]SelectNotRedeem &i.Vtxo.ExpireAt, &i.Vtxo.PaymentID, &i.Vtxo.RedeemTx, + &i.Vtxo.Descriptor, &i.UncondForfeitTxVw.ID, &i.UncondForfeitTxVw.Tx, &i.UncondForfeitTxVw.VtxoTxid, @@ -108,11 +108,11 @@ func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]SelectNotRedeem } 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 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 -WHERE redeemed = false AND pubkey = ? +WHERE redeemed = false AND INSTR(descriptor, ?) > 0 ` type SelectNotRedeemedVtxosWithPubkeyRow struct { @@ -120,8 +120,8 @@ type SelectNotRedeemedVtxosWithPubkeyRow struct { UncondForfeitTxVw UncondForfeitTxVw } -func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, pubkey string) ([]SelectNotRedeemedVtxosWithPubkeyRow, error) { - rows, err := q.db.QueryContext(ctx, selectNotRedeemedVtxosWithPubkey, pubkey) +func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, instr string) ([]SelectNotRedeemedVtxosWithPubkeyRow, error) { + rows, err := q.db.QueryContext(ctx, selectNotRedeemedVtxosWithPubkey, instr) if err != nil { return nil, err } @@ -132,7 +132,6 @@ func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, pubkey s if err := rows.Scan( &i.Vtxo.Txid, &i.Vtxo.Vout, - &i.Vtxo.Pubkey, &i.Vtxo.Amount, &i.Vtxo.PoolTx, &i.Vtxo.SpentBy, @@ -142,6 +141,7 @@ func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, pubkey s &i.Vtxo.ExpireAt, &i.Vtxo.PaymentID, &i.Vtxo.RedeemTx, + &i.Vtxo.Descriptor, &i.UncondForfeitTxVw.ID, &i.UncondForfeitTxVw.Tx, &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, round_payment_vw.id, round_payment_vw.round_id, round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf, - payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, 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_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address, + payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.descriptor FROM round LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id @@ -276,12 +276,11 @@ func (q *Queries) SelectRoundWithRoundId(ctx context.Context, id string) ([]Sele &i.RoundTxVw.ParentTxid, &i.RoundTxVw.IsLeaf, &i.PaymentReceiverVw.PaymentID, - &i.PaymentReceiverVw.Pubkey, + &i.PaymentReceiverVw.Descriptor, &i.PaymentReceiverVw.Amount, &i.PaymentReceiverVw.OnchainAddress, &i.PaymentVtxoVw.Txid, &i.PaymentVtxoVw.Vout, - &i.PaymentVtxoVw.Pubkey, &i.PaymentVtxoVw.Amount, &i.PaymentVtxoVw.PoolTx, &i.PaymentVtxoVw.SpentBy, @@ -291,6 +290,7 @@ func (q *Queries) SelectRoundWithRoundId(ctx context.Context, id string) ([]Sele &i.PaymentVtxoVw.ExpireAt, &i.PaymentVtxoVw.PaymentID, &i.PaymentVtxoVw.RedeemTx, + &i.PaymentVtxoVw.Descriptor, ); err != nil { 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, round_payment_vw.id, round_payment_vw.round_id, round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf, - payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, 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_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address, + payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.descriptor FROM round LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id @@ -361,12 +361,11 @@ func (q *Queries) SelectRoundWithRoundTxId(ctx context.Context, txid string) ([] &i.RoundTxVw.ParentTxid, &i.RoundTxVw.IsLeaf, &i.PaymentReceiverVw.PaymentID, - &i.PaymentReceiverVw.Pubkey, + &i.PaymentReceiverVw.Descriptor, &i.PaymentReceiverVw.Amount, &i.PaymentReceiverVw.OnchainAddress, &i.PaymentVtxoVw.Txid, &i.PaymentVtxoVw.Vout, - &i.PaymentVtxoVw.Pubkey, &i.PaymentVtxoVw.Amount, &i.PaymentVtxoVw.PoolTx, &i.PaymentVtxoVw.SpentBy, @@ -376,6 +375,7 @@ func (q *Queries) SelectRoundWithRoundTxId(ctx context.Context, txid string) ([] &i.PaymentVtxoVw.ExpireAt, &i.PaymentVtxoVw.PaymentID, &i.PaymentVtxoVw.RedeemTx, + &i.PaymentVtxoVw.Descriptor, ); err != nil { 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, round_payment_vw.id, round_payment_vw.round_id, round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf, - payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, 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_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address, + payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.descriptor FROM round LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id @@ -446,12 +446,11 @@ func (q *Queries) SelectSweepableRounds(ctx context.Context) ([]SelectSweepableR &i.RoundTxVw.ParentTxid, &i.RoundTxVw.IsLeaf, &i.PaymentReceiverVw.PaymentID, - &i.PaymentReceiverVw.Pubkey, + &i.PaymentReceiverVw.Descriptor, &i.PaymentReceiverVw.Amount, &i.PaymentReceiverVw.OnchainAddress, &i.PaymentVtxoVw.Txid, &i.PaymentVtxoVw.Vout, - &i.PaymentVtxoVw.Pubkey, &i.PaymentVtxoVw.Amount, &i.PaymentVtxoVw.PoolTx, &i.PaymentVtxoVw.SpentBy, @@ -461,6 +460,7 @@ func (q *Queries) SelectSweepableRounds(ctx context.Context) ([]SelectSweepableR &i.PaymentVtxoVw.ExpireAt, &i.PaymentVtxoVw.PaymentID, &i.PaymentVtxoVw.RedeemTx, + &i.PaymentVtxoVw.Descriptor, ); err != nil { return nil, err } @@ -476,7 +476,7 @@ func (q *Queries) SelectSweepableRounds(ctx context.Context) ([]SelectSweepableR } 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 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 @@ -500,7 +500,6 @@ func (q *Queries) SelectSweepableVtxos(ctx context.Context) ([]SelectSweepableVt if err := rows.Scan( &i.Vtxo.Txid, &i.Vtxo.Vout, - &i.Vtxo.Pubkey, &i.Vtxo.Amount, &i.Vtxo.PoolTx, &i.Vtxo.SpentBy, @@ -510,6 +509,7 @@ func (q *Queries) SelectSweepableVtxos(ctx context.Context) ([]SelectSweepableVt &i.Vtxo.ExpireAt, &i.Vtxo.PaymentID, &i.Vtxo.RedeemTx, + &i.Vtxo.Descriptor, &i.UncondForfeitTxVw.ID, &i.UncondForfeitTxVw.Tx, &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, round_payment_vw.id, round_payment_vw.round_id, round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf, - payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, 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_receiver_vw.payment_id, payment_receiver_vw.descriptor, payment_receiver_vw.amount, payment_receiver_vw.onchain_address, + payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx, payment_vtxo_vw.descriptor FROM round LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id @@ -585,12 +585,11 @@ func (q *Queries) SelectSweptRounds(ctx context.Context) ([]SelectSweptRoundsRow &i.RoundTxVw.ParentTxid, &i.RoundTxVw.IsLeaf, &i.PaymentReceiverVw.PaymentID, - &i.PaymentReceiverVw.Pubkey, + &i.PaymentReceiverVw.Descriptor, &i.PaymentReceiverVw.Amount, &i.PaymentReceiverVw.OnchainAddress, &i.PaymentVtxoVw.Txid, &i.PaymentVtxoVw.Vout, - &i.PaymentVtxoVw.Pubkey, &i.PaymentVtxoVw.Amount, &i.PaymentVtxoVw.PoolTx, &i.PaymentVtxoVw.SpentBy, @@ -600,6 +599,7 @@ func (q *Queries) SelectSweptRounds(ctx context.Context) ([]SelectSweptRoundsRow &i.PaymentVtxoVw.ExpireAt, &i.PaymentVtxoVw.PaymentID, &i.PaymentVtxoVw.RedeemTx, + &i.PaymentVtxoVw.Descriptor, ); err != nil { return nil, err } @@ -615,7 +615,7 @@ func (q *Queries) SelectSweptRounds(ctx context.Context) ([]SelectSweptRoundsRow } 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 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 @@ -638,7 +638,6 @@ func (q *Queries) SelectVtxoByOutpoint(ctx context.Context, arg SelectVtxoByOutp err := row.Scan( &i.Vtxo.Txid, &i.Vtxo.Vout, - &i.Vtxo.Pubkey, &i.Vtxo.Amount, &i.Vtxo.PoolTx, &i.Vtxo.SpentBy, @@ -648,6 +647,7 @@ func (q *Queries) SelectVtxoByOutpoint(ctx context.Context, arg SelectVtxoByOutp &i.Vtxo.ExpireAt, &i.Vtxo.PaymentID, &i.Vtxo.RedeemTx, + &i.Vtxo.Descriptor, &i.UncondForfeitTxVw.ID, &i.UncondForfeitTxVw.Tx, &i.UncondForfeitTxVw.VtxoTxid, @@ -658,7 +658,7 @@ func (q *Queries) SelectVtxoByOutpoint(ctx context.Context, arg SelectVtxoByOutp } 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 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 @@ -682,7 +682,6 @@ func (q *Queries) SelectVtxosByPoolTxid(ctx context.Context, poolTx string) ([]S if err := rows.Scan( &i.Vtxo.Txid, &i.Vtxo.Vout, - &i.Vtxo.Pubkey, &i.Vtxo.Amount, &i.Vtxo.PoolTx, &i.Vtxo.SpentBy, @@ -692,6 +691,7 @@ func (q *Queries) SelectVtxosByPoolTxid(ctx context.Context, poolTx string) ([]S &i.Vtxo.ExpireAt, &i.Vtxo.PaymentID, &i.Vtxo.RedeemTx, + &i.Vtxo.Descriptor, &i.UncondForfeitTxVw.ID, &i.UncondForfeitTxVw.Tx, &i.UncondForfeitTxVw.VtxoTxid, @@ -757,16 +757,16 @@ func (q *Queries) UpsertPayment(ctx context.Context, arg UpsertPaymentParams) er } const upsertReceiver = `-- name: UpsertReceiver :exec -INSERT INTO receiver (payment_id, pubkey, amount, onchain_address) VALUES (?, ?, ?, ?) -ON CONFLICT(payment_id, pubkey) DO UPDATE SET +INSERT INTO receiver (payment_id, descriptor, amount, onchain_address) VALUES (?, ?, ?, ?) +ON CONFLICT(payment_id, descriptor) DO UPDATE SET amount = EXCLUDED.amount, onchain_address = EXCLUDED.onchain_address, - pubkey = EXCLUDED.pubkey + descriptor = EXCLUDED.descriptor ` type UpsertReceiverParams struct { PaymentID string - Pubkey string + Descriptor string Amount int64 OnchainAddress string } @@ -774,7 +774,7 @@ type UpsertReceiverParams struct { func (q *Queries) UpsertReceiver(ctx context.Context, arg UpsertReceiverParams) error { _, err := q.db.ExecContext(ctx, upsertReceiver, arg.PaymentID, - arg.Pubkey, + arg.Descriptor, arg.Amount, arg.OnchainAddress, ) @@ -909,9 +909,9 @@ func (q *Queries) UpsertUnconditionalForfeitTx(ctx context.Context, arg UpsertUn } 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 - pubkey = EXCLUDED.pubkey, + descriptor = EXCLUDED.descriptor, amount = EXCLUDED.amount, pool_tx = EXCLUDED.pool_tx, spent_by = EXCLUDED.spent_by, @@ -923,24 +923,24 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET ` type UpsertVtxoParams struct { - Txid string - Vout int64 - Pubkey string - Amount int64 - PoolTx string - SpentBy string - Spent bool - Redeemed bool - Swept bool - ExpireAt int64 - RedeemTx sql.NullString + Txid string + Vout int64 + Descriptor sql.NullString + Amount int64 + PoolTx string + SpentBy string + Spent bool + Redeemed bool + Swept bool + ExpireAt int64 + RedeemTx sql.NullString } func (q *Queries) UpsertVtxo(ctx context.Context, arg UpsertVtxoParams) error { _, err := q.db.ExecContext(ctx, upsertVtxo, arg.Txid, arg.Vout, - arg.Pubkey, + arg.Descriptor, arg.Amount, arg.PoolTx, arg.SpentBy, diff --git a/server/internal/infrastructure/db/sqlite/sqlc/query.sql b/server/internal/infrastructure/db/sqlite/sqlc/query.sql index 620c1a7..df9d24a 100644 --- a/server/internal/infrastructure/db/sqlite/sqlc/query.sql +++ b/server/internal/infrastructure/db/sqlite/sqlc/query.sql @@ -44,11 +44,11 @@ INSERT INTO payment (id, round_id) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET round_id = EXCLUDED.round_id; -- name: UpsertReceiver :exec -INSERT INTO receiver (payment_id, pubkey, amount, onchain_address) VALUES (?, ?, ?, ?) -ON CONFLICT(payment_id, pubkey) DO UPDATE SET +INSERT INTO receiver (payment_id, descriptor, amount, onchain_address) VALUES (?, ?, ?, ?) +ON CONFLICT(payment_id, descriptor) DO UPDATE SET amount = EXCLUDED.amount, onchain_address = EXCLUDED.onchain_address, - pubkey = EXCLUDED.pubkey; + descriptor = EXCLUDED.descriptor; -- name: UpdateVtxoPaymentId :exec UPDATE vtxo SET payment_id = ? WHERE txid = ? AND vout = ?; @@ -120,9 +120,9 @@ VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET position = EXCLUDED.position; -- 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 - pubkey = EXCLUDED.pubkey, + descriptor = EXCLUDED.descriptor, amount = EXCLUDED.amount, pool_tx = EXCLUDED.pool_tx, spent_by = EXCLUDED.spent_by, @@ -151,7 +151,7 @@ SELECT sqlc.embed(vtxo), sqlc.embed(uncond_forfeit_tx_vw) 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 -WHERE redeemed = false AND pubkey = ?; +WHERE redeemed = false AND INSTR(descriptor, ?) > 0; -- name: SelectVtxoByOutpoint :one SELECT sqlc.embed(vtxo), diff --git a/server/internal/infrastructure/db/sqlite/vtxo_repo.go b/server/internal/infrastructure/db/sqlite/vtxo_repo.go index 1932b72..0e678b0 100644 --- a/server/internal/infrastructure/db/sqlite/vtxo_repo.go +++ b/server/internal/infrastructure/db/sqlite/vtxo_repo.go @@ -43,17 +43,17 @@ func (v *vxtoRepository) AddVtxos(ctx context.Context, vtxos []domain.Vtxo) erro } if err := querierWithTx.UpsertVtxo( ctx, queries.UpsertVtxoParams{ - Txid: vtxo.Txid, - Vout: int64(vtxo.VOut), - Pubkey: vtxo.Pubkey, - Amount: int64(vtxo.Amount), - PoolTx: vtxo.PoolTx, - SpentBy: vtxo.SpentBy, - Spent: vtxo.Spent, - Redeemed: vtxo.Redeemed, - Swept: vtxo.Swept, - ExpireAt: vtxo.ExpireAt, - RedeemTx: sql.NullString{String: redeemTx, Valid: true}, + Txid: vtxo.Txid, + Vout: int64(vtxo.VOut), + Descriptor: sql.NullString{String: vtxo.Descriptor, Valid: true}, + Amount: int64(vtxo.Amount), + PoolTx: vtxo.PoolTx, + SpentBy: vtxo.SpentBy, + Spent: vtxo.Spent, + Redeemed: vtxo.Redeemed, + Swept: vtxo.Swept, + ExpireAt: vtxo.ExpireAt, + RedeemTx: sql.NullString{String: redeemTx, Valid: true}, }, ); err != nil { return err @@ -100,6 +100,10 @@ func (v *vxtoRepository) GetAllVtxos(ctx context.Context, pubkey string) ([]doma var rows []vtxoWithUnconditionalForfeitTxs if withPubkey { + if len(pubkey) == 66 { + pubkey = pubkey[2:] + } + res, err := v.querier.SelectNotRedeemedVtxosWithPubkey(ctx, pubkey) if err != nil { return nil, nil, err @@ -294,8 +298,8 @@ func rowToVtxo(row queries.Vtxo, uncondForfeitTxs []queries.UncondForfeitTxVw) d VOut: uint32(row.Vout), }, Receiver: domain.Receiver{ - Pubkey: row.Pubkey, - Amount: uint64(row.Amount), + Descriptor: row.Descriptor.String, + Amount: uint64(row.Amount), }, PoolTx: row.PoolTx, SpentBy: row.SpentBy, diff --git a/server/internal/infrastructure/tx-builder/covenant/builder.go b/server/internal/infrastructure/tx-builder/covenant/builder.go index 80d14d7..d243193 100644 --- a/server/internal/infrastructure/tx-builder/covenant/builder.go +++ b/server/internal/infrastructure/tx-builder/covenant/builder.go @@ -3,7 +3,6 @@ package txbuilder import ( "bytes" "context" - "encoding/hex" "fmt" "github.com/ark-network/ark/common" @@ -14,6 +13,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/vulpemventures/go-elements/address" "github.com/vulpemventures/go-elements/elementsutil" "github.com/vulpemventures/go-elements/network" @@ -27,7 +27,6 @@ type txBuilder struct { wallet ports.WalletService net common.Network roundLifetime int64 // in seconds - exitDelay int64 // in seconds boardingExitDelay int64 // in seconds } @@ -35,27 +34,13 @@ func NewTxBuilder( wallet ports.WalletService, net common.Network, roundLifetime int64, - exitDelay int64, boardingExitDelay int64, ) 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) { - addr, script, _, err := b.getBoardingTaproot(owner, asp) - 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) GetTxID(tx string) (string, error) { + return getTxid(tx) } 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( - 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) if err != nil { return nil, nil, err @@ -118,7 +107,7 @@ func (b *txBuilder) BuildForfeitTxs( return nil, nil, err } - forfeitTxs, err = b.createForfeitTxs(aspPubkey, payments, connectorTxs, connectorAmount) + forfeitTxs, err = b.createForfeitTxs(aspPubkey, payments, connectorTxs, connectorAmount, minRelayFeeRate) if err != nil { return nil, nil, err } @@ -159,8 +148,13 @@ func (b *txBuilder) BuildPoolTx( return "", nil, "", err } + receivers, err := getOffchainReceivers(payments) + if err != nil { + return "", nil, "", err + } + 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 { return "", nil, "", err @@ -270,7 +264,7 @@ func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, string, error) rootHash := tapLeaf.ControlBlock.RootHash(tapLeaf.Script) tapKeyFromControlBlock := taproot.ComputeTaprootOutputKey(tree.UnspendableKey(), rootHash[:]) - pkscript, err := p2trScript(tapKeyFromControlBlock) + pkscript, err := common.P2TRScript(tapKeyFromControlBlock) if err != nil { return false, txid, err } @@ -376,51 +370,13 @@ func (b *txBuilder) BuildAsyncPaymentTransactions( 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( sharedOutputAmount uint64, sharedOutputScript []byte, payments []domain.Payment, boardingInputs []ports.BoardingInput, - aspPubKey *secp256k1.PublicKey, connectorAddress string, + aspPubKey *secp256k1.PublicKey, + connectorAddress string, sweptRounds []domain.Round, ) (*psetv2.Pset, error) { aspScript, err := p2wpkhScript(aspPubKey, b.onchainNetwork()) @@ -487,7 +443,7 @@ func (b *txBuilder) createPoolTx( } for _, in := range boardingInputs { - targetAmount -= in.GetAmount() + targetAmount -= in.Amount } ctx := context.Background() @@ -529,8 +485,8 @@ func (b *txBuilder) createPoolTx( if err := updater.AddInputs( []psetv2.InputArgs{ { - Txid: in.GetHash().String(), - TxIndex: in.GetIndex(), + Txid: in.Txid, + TxIndex: in.VtxoKey.VOut, }, }, ); err != nil { @@ -544,21 +500,27 @@ func (b *txBuilder) createPoolTx( 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 { 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 { 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 } - 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 } @@ -740,10 +702,13 @@ func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string, 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 } + if err := roundSigner.SignTaprootInputTapscriptSig(i, partialSig); err != nil { + return "", err + } } return roundSigner.Pset.ToBase64() @@ -823,56 +788,59 @@ func (b *txBuilder) createConnectors( } 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) { - aspScript, err := p2wpkhScript(aspPubkey, b.onchainNetwork()) - if err != nil { - return nil, err - } - forfeitTxs := make([]string, 0) for _, payment := range payments { for _, vtxo := range payment.Inputs { - pubkeyBytes, err := hex.DecodeString(vtxo.Pubkey) - if err != nil { - return nil, fmt.Errorf("failed to decode pubkey: %s", err) - } - - vtxoPubkey, err := secp256k1.ParsePubKey(pubkeyBytes) + offchainScript, err := tree.ParseVtxoScript(vtxo.Descriptor) if err != nil { return nil, err } - vtxoScript, vtxoTaprootTree, err := b.getLeafScriptAndTree(vtxoPubkey, aspPubkey) + vtxoTapKey, vtxoTree, err := offchainScript.TapTree() if err != nil { return nil, err } - var forfeitProof *taproot.TapscriptElementsProof - - for _, proof := range vtxoTaprootTree.LeafMerkleProofs { - isForfeit, err := (&tree.ForfeitClosure{}).Decode(proof.Script) - if !isForfeit || err != nil { - continue - } - - forfeitProof = &proof - break + vtxoScript, err := common.P2TRScript(vtxoTapKey) + if err != nil { + return nil, err } - if forfeitProof == nil { - return nil, fmt.Errorf("forfeit proof not found") + feeAmount, err := common.ComputeForfeitMinRelayFee(minRelayFeeRate, vtxoTree) + if err != nil { + return nil, err } for _, connector := range connectors { - txs, err := b.craftForfeitTxs( - connector, connectorAmount, vtxo, *forfeitProof, vtxoScript, aspScript, + txs, err := tree.BuildForfeitTxs( + connector, + psetv2.InputArgs{ + Txid: vtxo.Txid, + TxIndex: vtxo.VOut, + }, + vtxo.Amount, + connectorAmount, + feeAmount, + vtxoScript, + aspPubkey, ) if err != nil { 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) { for _, leaf := range input.TapLeafScript { closure := &tree.CSVSigClosure{} diff --git a/server/internal/infrastructure/tx-builder/covenant/builder_test.go b/server/internal/infrastructure/tx-builder/covenant/builder_test.go index a15b39c..c2d4c41 100644 --- a/server/internal/infrastructure/tx-builder/covenant/builder_test.go +++ b/server/internal/infrastructure/tx-builder/covenant/builder_test.go @@ -20,12 +20,12 @@ import ( ) const ( - testingKey = "0218d5ca8b58797b7dbd65c075dd7ba7784b3f38ab71b1a5a8e3f94ba0257654a6" - connectorAddress = "tex1qekd5u0qj8jl07vy60830xy7n9qtmcx9u3s0cqc" - minRelayFee = uint64(30) - roundLifetime = int64(1209344) - unilateralExitDelay = int64(512) - boardingExitDelay = int64(512) + testingKey = "020000000000000000000000000000000000000000000000000000000000000001" + connectorAddress = "tex1qekd5u0qj8jl07vy60830xy7n9qtmcx9u3s0cqc" + minRelayFee = uint64(30) + roundLifetime = int64(1209344) + boardingExitDelay = int64(512) + minRelayFeeRate = 3 ) var ( @@ -54,7 +54,7 @@ func TestMain(m *testing.M) { func TestBuildPoolTx(t *testing.T) { builder := txbuilder.NewTxBuilder( - wallet, common.Liquid, roundLifetime, unilateralExitDelay, boardingExitDelay, + wallet, common.Liquid, roundLifetime, boardingExitDelay, ) fixtures, err := parsePoolTxFixtures() @@ -99,7 +99,7 @@ func TestBuildPoolTx(t *testing.T) { func TestBuildForfeitTxs(t *testing.T) { builder := txbuilder.NewTxBuilder( - wallet, common.Liquid, 1209344, unilateralExitDelay, boardingExitDelay, + wallet, common.Liquid, 1209344, boardingExitDelay, ) fixtures, err := parseForfeitTxsFixtures() @@ -110,7 +110,7 @@ func TestBuildForfeitTxs(t *testing.T) { t.Run("valid", func(t *testing.T) { for _, f := range fixtures.Valid { connectors, forfeitTxs, err := builder.BuildForfeitTxs( - pubkey, f.PoolTx, f.Payments, + pubkey, f.PoolTx, f.Payments, minRelayFeeRate, ) require.NoError(t, err) require.Len(t, connectors, f.ExpectedNumOfConnectors) @@ -148,7 +148,7 @@ func TestBuildForfeitTxs(t *testing.T) { t.Run("invalid", func(t *testing.T) { for _, f := range fixtures.Invalid { connectors, forfeitTxs, err := builder.BuildForfeitTxs( - pubkey, f.PoolTx, f.Payments, + pubkey, f.PoolTx, f.Payments, minRelayFeeRate, ) require.EqualError(t, err, f.ExpectedErr) require.Empty(t, connectors) diff --git a/server/internal/infrastructure/tx-builder/covenant/connectors.go b/server/internal/infrastructure/tx-builder/covenant/connectors.go index 78cb299..6eb9c21 100644 --- a/server/internal/infrastructure/tx-builder/covenant/connectors.go +++ b/server/internal/infrastructure/tx-builder/covenant/connectors.go @@ -51,24 +51,3 @@ func craftConnectorTx( 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 -} diff --git a/server/internal/infrastructure/tx-builder/covenant/mocks_test.go b/server/internal/infrastructure/tx-builder/covenant/mocks_test.go index a4f8b2e..3d409f4 100644 --- a/server/internal/infrastructure/tx-builder/covenant/mocks_test.go +++ b/server/internal/infrastructure/tx-builder/covenant/mocks_test.go @@ -5,6 +5,7 @@ import ( "github.com/ark-network/ark/server/internal/core/ports" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/stretchr/testify/mock" ) @@ -259,6 +260,16 @@ func (m *mockedWallet) GetTransaction(ctx context.Context, txid string) (string, 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 { mock.Mock } diff --git a/server/internal/infrastructure/tx-builder/covenant/sweep.go b/server/internal/infrastructure/tx-builder/covenant/sweep.go index 9cc6775..f00cabe 100644 --- a/server/internal/infrastructure/tx-builder/covenant/sweep.go +++ b/server/internal/infrastructure/tx-builder/covenant/sweep.go @@ -79,7 +79,7 @@ func sweepTransaction( root := leaf.ControlBlock.RootHash(leaf.Script) taprootKey := taproot.ComputeTaprootOutputKey(leaf.ControlBlock.InternalKey, root) - script, err := p2trScript(taprootKey) + script, err := common.P2TRScript(taprootKey) if err != nil { return nil, err } diff --git a/server/internal/infrastructure/tx-builder/covenant/testdata/fixtures.json b/server/internal/infrastructure/tx-builder/covenant/testdata/fixtures.json index 4dbdef0..5221591 100644 --- a/server/internal/infrastructure/tx-builder/covenant/testdata/fixtures.json +++ b/server/internal/infrastructure/tx-builder/covenant/testdata/fixtures.json @@ -9,13 +9,14 @@ { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 0, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 1100 } ], "receivers": [ { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1100 } ] @@ -32,17 +33,18 @@ { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 0, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 1100 } ], "receivers": [ { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 600 }, { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] @@ -59,17 +61,18 @@ { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 0, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 1100 } ], "receivers": [ { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 600 }, { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] @@ -80,17 +83,18 @@ { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 0, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 1100 } ], "receivers": [ { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 600 }, { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] @@ -101,17 +105,18 @@ { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 0, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 1100 } ], "receivers": [ { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 600 }, { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] @@ -128,53 +133,58 @@ { "txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41", "vout": 0, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 1000 }, { "txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357", "vout": 0, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 1000 }, { "txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909", "vout": 1, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 500 }, { "txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60", "vout": 0, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 1000 }, { "txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60", "vout": 1, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 1000 } ], "receivers": [ { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] @@ -196,23 +206,25 @@ { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 0, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 600 }, { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 1, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", + "signerPubkey": "020000000000000000000000000000000000000000000000000000000000000001", "amount": 500 } ], "receivers": [ { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 600 }, { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] diff --git a/server/internal/infrastructure/tx-builder/covenant/utils.go b/server/internal/infrastructure/tx-builder/covenant/utils.go index d2981fb..990fd47 100644 --- a/server/internal/infrastructure/tx-builder/covenant/utils.go +++ b/server/internal/infrastructure/tx-builder/covenant/utils.go @@ -7,7 +7,6 @@ import ( "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/ports" - "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/txscript" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/vulpemventures/go-elements/address" @@ -62,19 +61,24 @@ func getOnchainReceivers( func getOffchainReceivers( payments []domain.Payment, -) []tree.Receiver { +) ([]tree.Receiver, error) { receivers := make([]tree.Receiver, 0) for _, payment := range payments { for _, receiver := range payment.Receivers { if !receiver.IsOnchain() { + vtxoScript, err := tree.ParseVtxoScript(receiver.Descriptor) + if err != nil { + return nil, err + } + receivers = append(receivers, tree.Receiver{ - Pubkey: receiver.Pubkey, + Script: vtxoScript, Amount: receiver.Amount, }) } } } - return receivers + return receivers, nil } func toWitnessUtxo(in ports.TxInput) (*transaction.TxOutput, error) { @@ -136,10 +140,6 @@ func addInputs( 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 { for _, p := range payments { for _, r := range p.Receivers { diff --git a/server/internal/infrastructure/tx-builder/covenantless/builder.go b/server/internal/infrastructure/tx-builder/covenantless/builder.go index 1e7dd12..9767b07 100644 --- a/server/internal/infrastructure/tx-builder/covenantless/builder.go +++ b/server/internal/infrastructure/tx-builder/covenantless/builder.go @@ -22,20 +22,29 @@ import ( "github.com/btcsuite/btcwallet/waddrmgr" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) type txBuilder struct { wallet ports.WalletService net common.Network roundLifetime int64 // in seconds - exitDelay int64 // in seconds boardingExitDelay int64 // in seconds } func NewTxBuilder( - wallet ports.WalletService, net common.Network, roundLifetime, exitDelay, boardingExitDelay int64, + wallet ports.WalletService, net common.Network, roundLifetime, boardingExitDelay int64, ) 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) { @@ -64,7 +73,7 @@ func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, string, error) rootHash := controlBlock.RootHash(tapLeaf.Script) tapKeyFromControlBlock := txscript.ComputeTaprootOutputKey(bitcointree.UnspendableKey(), rootHash[:]) - pkscript, err := p2trScript(tapKeyFromControlBlock) + pkscript, err := common.P2TRScript(tapKeyFromControlBlock) if err != nil { return false, txid, err } @@ -173,14 +182,6 @@ func (b *txBuilder) FinalizeAndExtract(tx string) (string, error) { 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) { sweepPsbt, err := sweepTransaction( b.wallet, @@ -227,7 +228,7 @@ func (b *txBuilder) BuildSweepTx(inputs []ports.SweepInput) (signedSweepTx strin } 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) { connectorPkScript, err := b.getConnectorPkScript(poolTx) if err != nil { @@ -244,12 +245,7 @@ func (b *txBuilder) BuildForfeitTxs( return nil, nil, err } - minRelayFeeForfeitTx, err := b.minRelayFeeForfeitTx() - if err != nil { - return nil, nil, err - } - - forfeitTxs, err = b.createForfeitTxs(aspPubkey, payments, connectorTxs, minRelayFeeForfeitTx) + forfeitTxs, err = b.createForfeitTxs(aspPubkey, payments, connectorTxs, minRelayFeeRate) if err != nil { return nil, nil, err } @@ -275,7 +271,10 @@ func (b *txBuilder) BuildPoolTx( return "", nil, "", fmt.Errorf("missing cosigners") } - receivers := getOffchainReceivers(payments) + receivers, err := getOffchainReceivers(payments) + if err != nil { + return "", nil, "", err + } feeAmount, err := b.minRelayFeeTreeTx() if err != nil { @@ -284,7 +283,7 @@ func (b *txBuilder) BuildPoolTx( if !isOnchainOnly(payments) { sharedOutputScript, sharedOutputAmount, err = bitcointree.CraftSharedOutput( - cosigners, aspPubkey, receivers, feeAmount, b.roundLifetime, b.exitDelay, + cosigners, aspPubkey, receivers, feeAmount, b.roundLifetime, ) if err != nil { return @@ -297,7 +296,7 @@ func (b *txBuilder) BuildPoolTx( } ptx, err := b.createPoolTx( - aspPubkey, sharedOutputAmount, sharedOutputScript, payments, boardingInputs, connectorAddress, sweptRounds, + sharedOutputAmount, sharedOutputScript, payments, boardingInputs, connectorAddress, sweptRounds, ) if err != nil { return @@ -315,7 +314,7 @@ func (b *txBuilder) BuildPoolTx( } congestionTree, err = bitcointree.CraftCongestionTree( - initialOutpoint, cosigners, aspPubkey, receivers, feeAmount, b.roundLifetime, b.exitDelay, + initialOutpoint, cosigners, aspPubkey, receivers, feeAmount, b.roundLifetime, ) if err != nil { return @@ -401,7 +400,6 @@ func (b *txBuilder) FindLeaves(congestionTree tree.CongestionTree, fromtxid stri return foundLeaves, nil } -// TODO add locktimes to txs func (b *txBuilder) BuildAsyncPaymentTransactions( vtxos []domain.Vtxo, aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver, ) (*domain.AsyncPaymentTxs, error) { @@ -410,6 +408,7 @@ func (b *txBuilder) BuildAsyncPaymentTransactions( } for _, vtxo := range vtxos { + // TODO allow to chain async payment ? if vtxo.AsyncPayment != nil { 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)) redeemTxWeightEstimator := &input.TxWeightEstimator{} for _, vtxo := range vtxos { - if vtxo.Spent { + if vtxo.Spent || vtxo.Redeemed || vtxo.Swept { return nil, fmt.Errorf("all vtxos must be unspent") } - senderBytes, err := hex.DecodeString(vtxo.Pubkey) - if err != nil { - return nil, err - } - - sender, err := secp256k1.ParsePubKey(senderBytes) - if err != nil { - return nil, err - } - - aspScript, err := p2trScript(aspPubKey) + aspScript, err := common.P2TRScript(aspPubKey) if err != nil { return nil, err } @@ -449,35 +438,54 @@ func (b *txBuilder) BuildAsyncPaymentTransactions( Index: vtxo.VOut, } - vtxoScript, vtxoTree, err := b.getLeafScriptAndTree(sender, aspPubKey) + vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Descriptor) if err != nil { return nil, err } - forfeitClosure := &bitcointree.MultisigClosure{ - Pubkey: sender, - AspPubkey: aspPubKey, - } - - forfeitLeaf, err := forfeitClosure.Leaf() + vtxoTapKey, vtxoTree, err := vtxoScript.TapTree() if err != nil { return nil, err } - leafProof := vtxoTree.LeafMerkleProofs[vtxoTree.LeafProofIndex[forfeitLeaf.TapHash()]] - ctrlBlock := leafProof.ToControlBlock(bitcointree.UnspendableKey()) - ctrlBlockBytes, err := ctrlBlock.ToBytes() + vtxoOutputScript, err := common.P2TRScript(vtxoTapKey) if err != nil { return nil, err } + var tapscript *waddrmgr.Tapscript forfeitTxWeightEstimator := &input.TxWeightEstimator{} - tapscript := &waddrmgr.Tapscript{ - RevealedScript: leafProof.Script, - ControlBlock: &ctrlBlock, + + if defaultVtxoScript, ok := vtxoScript.(*bitcointree.DefaultVtxoScript); ok { + forfeitClosure := &bitcointree.MultisigClosure{ + Pubkey: defaultVtxoScript.Owner, + AspPubkey: defaultVtxoScript.Asp, + } + + forfeitLeaf, err := forfeitClosure.Leaf() + if err != nil { + return nil, err + } + + forfeitProof, err := vtxoTree.GetTaprootMerkleProof(forfeitLeaf.TapHash()) + if err != nil { + return nil, err + } + + ctrlBlock, err := txscript.ParseControlBlock(forfeitProof.ControlBlock) + if err != nil { + return nil, err + } + + tapscript = &waddrmgr.Tapscript{ + RevealedScript: forfeitProof.Script, + ControlBlock: ctrlBlock, + } + forfeitTxWeightEstimator.AddTapscriptInput(64*2, tapscript) + forfeitTxWeightEstimator.AddP2TROutput() // ASP output + } else { + return nil, fmt.Errorf("vtxo script is not a default vtxo script, cannot be async spent") } - forfeitTxWeightEstimator.AddTapscriptInput(64*2, tapscript) - forfeitTxWeightEstimator.AddP2TROutput() // ASP output forfeitTxFee, err := b.wallet.MinRelayFee(context.Background(), uint64(forfeitTxWeightEstimator.VSize())) if err != nil { @@ -506,14 +514,18 @@ func (b *txBuilder) BuildAsyncPaymentTransactions( unconditionnalForfeitPtx.Inputs[0].WitnessUtxo = &wire.TxOut{ 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{ { - ControlBlock: ctrlBlockBytes, - Script: forfeitLeaf.Script, + Script: tapscript.RevealedScript, + ControlBlock: ctrlBlock, LeafVersion: txscript.BaseLeafVersion, }, } @@ -542,16 +554,17 @@ func (b *txBuilder) BuildAsyncPaymentTransactions( } for i, receiver := range receivers { - // TODO (@louisinger): Add revert policy (sender+ASP) - buf, err := hex.DecodeString(receiver.Pubkey) + offchainScript, err := bitcointree.ParseVtxoScript(receiver.Descriptor) if err != nil { return nil, err } - receiverPk, err := secp256k1.ParsePubKey(buf) + + receiverVtxoTaprootKey, _, err := offchainScript.TapTree() if err != nil { return nil, err } - newVtxoScript, _, err := b.getLeafScriptAndTree(receiverPk, aspPubKey) + + newVtxoScript, err := common.P2TRScript(receiverVtxoTaprootKey) if err != nil { return nil, err } @@ -607,58 +620,12 @@ func (b *txBuilder) BuildAsyncPaymentTransactions( }, 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( - aspPubKey *secp256k1.PublicKey, - sharedOutputAmount int64, sharedOutputScript []byte, - payments []domain.Payment, boardingInputs []ports.BoardingInput, connectorAddress string, + sharedOutputAmount int64, + sharedOutputScript []byte, + payments []domain.Payment, + boardingInputs []ports.BoardingInput, + connectorAddress string, sweptRounds []domain.Round, ) (*psbt.Packet, error) { connectorAddr, err := btcutil.DecodeAddress(connectorAddress, b.onchainNetwork()) @@ -729,7 +696,7 @@ func (b *txBuilder) createPoolTx( } for _, input := range boardingInputs { - targetAmount -= input.GetAmount() + targetAmount -= input.Amount } ctx := context.Background() @@ -769,7 +736,7 @@ func (b *txBuilder) createPoolTx( ins := make([]*wire.OutPoint, 0) nSequences := make([]uint32, 0) witnessUtxos := make(map[int]*wire.TxOut) - boardingTapLeaves := make(map[int]*psbt.TaprootTapLeafScript) + tapLeaves := make(map[int]*psbt.TaprootTapLeafScript) nextIndex := 0 for _, utxo := range utxos { @@ -796,23 +763,48 @@ func (b *txBuilder) createPoolTx( nextIndex++ } - for _, input := range boardingInputs { - ins = append(ins, &wire.OutPoint{ - Hash: input.GetHash(), - Index: input.GetIndex(), - }) - nSequences = append(nSequences, wire.MaxTxInSequenceNum) - - _, script, tapLeaf, err := b.craftBoardingTaproot(input.GetBoardingPubkey(), aspPubKey) + for _, boardingInput := range boardingInputs { + txHash, err := chainhash.NewHashFromStr(boardingInput.Txid) if err != nil { return nil, err } - boardingTapLeaves[nextIndex] = tapLeaf - witnessUtxos[nextIndex] = &wire.TxOut{ - Value: int64(input.GetAmount()), - PkScript: script, + ins = append(ins, &wire.OutPoint{ + Hash: *txHash, + Index: boardingInput.VtxoKey.VOut, + }) + 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++ } @@ -832,11 +824,8 @@ func (b *txBuilder) createPoolTx( } } - unspendableInternalKey := schnorr.SerializePubKey(bitcointree.UnspendableKey()) - - for inIndex, tapLeaf := range boardingTapLeaves { + for inIndex, tapLeaf := range tapLeaves { updater.Upsbt.Inputs[inIndex].TaprootLeafScript = []*psbt.TaprootTapLeafScript{tapLeaf} - updater.Upsbt.Inputs[inIndex].TaprootInternalKey = unspendableInternalKey } 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 } @@ -1048,6 +1043,7 @@ func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string, } 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)) } -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( - 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) { - aspScript, err := p2trScript(aspPubkey) - if err != nil { - return nil, err - } - forfeitTxs := make([]string, 0) for _, payment := range payments { for _, vtxo := range payment.Inputs { - pubkeyBytes, err := hex.DecodeString(vtxo.Pubkey) - if err != nil { - return nil, fmt.Errorf("failed to decode pubkey: %s", err) - } - - vtxoPubkey, err := secp256k1.ParsePubKey(pubkeyBytes) + offchainscript, err := bitcointree.ParseVtxoScript(vtxo.Descriptor) if err != nil { return nil, err } - vtxoScript, vtxoTaprootTree, err := b.getLeafScriptAndTree(vtxoPubkey, aspPubkey) - 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() + vtxoTaprootKey, tapTree, err := offchainscript.TapTree() if err != nil { return nil, err } @@ -1216,21 +1142,46 @@ func (b *txBuilder) createForfeitTxs( 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 { - txs, err := craftForfeitTxs( - connector, vtxo, - &psbt.TaprootTapLeafScript{ - ControlBlock: ctrlBlockBytes, - Script: forfeitProof.Script, - LeafVersion: forfeitProof.LeafVersion, + txs, err := bitcointree.BuildForfeitTxs( + connector, + &wire.OutPoint{ + Hash: *vtxoTxHash, + Index: vtxo.VOut, }, - vtxoScript, aspScript, feeAmount, int64(connectorAmount), + vtxo.Amount, + connectorAmount, + feeAmount, + vtxoScript, + aspPubkey, ) if err != nil { 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 { outpoints := make([]ports.TxOutpoint, 0, len(inputs)) for _, input := range inputs { diff --git a/server/internal/infrastructure/tx-builder/covenantless/builder_test.go b/server/internal/infrastructure/tx-builder/covenantless/builder_test.go index 6bf31c0..cf64301 100644 --- a/server/internal/infrastructure/tx-builder/covenantless/builder_test.go +++ b/server/internal/infrastructure/tx-builder/covenantless/builder_test.go @@ -20,11 +20,11 @@ import ( ) const ( - testingKey = "0218d5ca8b58797b7dbd65c075dd7ba7784b3f38ab71b1a5a8e3f94ba0257654a6" - connectorAddress = "bc1py00yhcjpcj0k0sqra0etq0u3yy0purmspppsw0shyzyfe8c83tmq5h6kc2" - roundLifetime = int64(1209344) - unilateralExitDelay = int64(512) - boardingExitDelay = int64(512) + testingKey = "020000000000000000000000000000000000000000000000000000000000000001" + connectorAddress = "bc1py00yhcjpcj0k0sqra0etq0u3yy0purmspppsw0shyzyfe8c83tmq5h6kc2" + roundLifetime = int64(1209344) + boardingExitDelay = int64(512) + minRelayFeeRate = 3 ) var ( @@ -53,7 +53,7 @@ func TestMain(m *testing.M) { func TestBuildPoolTx(t *testing.T) { builder := txbuilder.NewTxBuilder( - wallet, common.Bitcoin, roundLifetime, unilateralExitDelay, boardingExitDelay, + wallet, common.Bitcoin, roundLifetime, boardingExitDelay, ) fixtures, err := parsePoolTxFixtures() @@ -64,15 +64,11 @@ func TestBuildPoolTx(t *testing.T) { t.Run("valid", func(t *testing.T) { for _, f := range fixtures.Valid { cosigners := make([]*secp256k1.PublicKey, 0) - for _, payment := range f.Payments { - for _, input := range payment.Inputs { - pubkeyBytes, err := hex.DecodeString(input.Pubkey) - require.NoError(t, err) - pubkey, err := secp256k1.ParsePubKey(pubkeyBytes) - require.NoError(t, err) + for range f.Payments { + randKey, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) - cosigners = append(cosigners, pubkey) - } + cosigners = append(cosigners, randKey.PubKey()) } poolTx, congestionTree, connAddr, err := builder.BuildPoolTx( @@ -110,7 +106,7 @@ func TestBuildPoolTx(t *testing.T) { func TestBuildForfeitTxs(t *testing.T) { builder := txbuilder.NewTxBuilder( - wallet, common.Bitcoin, 1209344, unilateralExitDelay, boardingExitDelay, + wallet, common.Bitcoin, 1209344, boardingExitDelay, ) fixtures, err := parseForfeitTxsFixtures() @@ -121,7 +117,7 @@ func TestBuildForfeitTxs(t *testing.T) { t.Run("valid", func(t *testing.T) { for _, f := range fixtures.Valid { connectors, forfeitTxs, err := builder.BuildForfeitTxs( - pubkey, f.PoolTx, f.Payments, + pubkey, f.PoolTx, f.Payments, minRelayFeeRate, ) require.NoError(t, err) require.Len(t, connectors, f.ExpectedNumOfConnectors) @@ -159,7 +155,7 @@ func TestBuildForfeitTxs(t *testing.T) { t.Run("invalid", func(t *testing.T) { for _, f := range fixtures.Invalid { connectors, forfeitTxs, err := builder.BuildForfeitTxs( - pubkey, f.PoolTx, f.Payments, + pubkey, f.PoolTx, f.Payments, minRelayFeeRate, ) require.EqualError(t, err, f.ExpectedErr) require.Empty(t, connectors) diff --git a/server/internal/infrastructure/tx-builder/covenantless/connectors.go b/server/internal/infrastructure/tx-builder/covenantless/connectors.go index 420a187..4075691 100644 --- a/server/internal/infrastructure/tx-builder/covenantless/connectors.go +++ b/server/internal/infrastructure/tx-builder/covenantless/connectors.go @@ -38,20 +38,3 @@ func craftConnectorTx( 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 -} diff --git a/server/internal/infrastructure/tx-builder/covenantless/forfeit.go b/server/internal/infrastructure/tx-builder/covenantless/forfeit.go deleted file mode 100644 index 7b54c83..0000000 --- a/server/internal/infrastructure/tx-builder/covenantless/forfeit.go +++ /dev/null @@ -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 -} diff --git a/server/internal/infrastructure/tx-builder/covenantless/mocks_test.go b/server/internal/infrastructure/tx-builder/covenantless/mocks_test.go index 69b0bcf..c46d2c6 100644 --- a/server/internal/infrastructure/tx-builder/covenantless/mocks_test.go +++ b/server/internal/infrastructure/tx-builder/covenantless/mocks_test.go @@ -5,6 +5,7 @@ import ( "github.com/ark-network/ark/server/internal/core/ports" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/stretchr/testify/mock" ) @@ -140,6 +141,17 @@ func (m *mockedWallet) MinRelayFee(ctx context.Context, vbytes uint64) (uint64, 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) { args := m.Called(ctx) diff --git a/server/internal/infrastructure/tx-builder/covenantless/testdata/fixtures.json b/server/internal/infrastructure/tx-builder/covenantless/testdata/fixtures.json index 3b7d830..e3e5f00 100644 --- a/server/internal/infrastructure/tx-builder/covenantless/testdata/fixtures.json +++ b/server/internal/infrastructure/tx-builder/covenantless/testdata/fixtures.json @@ -9,13 +9,13 @@ { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 0, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1100 } ], "receivers": [ { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1100 } ] @@ -32,17 +32,17 @@ { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 0, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1100 } ], "receivers": [ { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 600 }, { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] @@ -59,17 +59,17 @@ { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 0, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1100 } ], "receivers": [ { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 600 }, { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] @@ -80,17 +80,17 @@ { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 0, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1100 } ], "receivers": [ { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 600 }, { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] @@ -101,17 +101,17 @@ { "txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6", "vout": 0, - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1100 } ], "receivers": [ { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 600 }, { - "pubkey": "020000000000000000000000000000000000000000000000000000000000000002", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] @@ -128,53 +128,54 @@ { "txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41", "vout": 0, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { "txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357", "vout": 0, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { "txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909", "vout": 1, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 }, { "txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60", "vout": 0, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { "txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60", "vout": 1, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 } ], "receivers": [ { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] @@ -196,53 +197,53 @@ { "txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41", "vout": 0, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { "txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357", "vout": 0, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { "txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909", "vout": 1, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 }, { "txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60", "vout": 0, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { "txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60", "vout": 1, - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 } ], "receivers": [ { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 1000 }, { - "pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5", + "descriptor": "tr(020000000000000000000000000000000000000000000000000000000000000001,{ and(pk(0000000000000000000000000000000000000000000000000000000000000001), pk(0000000000000000000000000000000000000000000000000000000000000001)), and(older(512), pk(0000000000000000000000000000000000000000000000000000000000000001)) })", "amount": 500 } ] diff --git a/server/internal/infrastructure/tx-builder/covenantless/utils.go b/server/internal/infrastructure/tx-builder/covenantless/utils.go index 5e42810..aac6c1b 100644 --- a/server/internal/infrastructure/tx-builder/covenantless/utils.go +++ b/server/internal/infrastructure/tx-builder/covenantless/utils.go @@ -8,10 +8,6 @@ import ( "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( payments []domain.Payment, ) []domain.Receiver { @@ -28,19 +24,24 @@ func getOnchainReceivers( func getOffchainReceivers( payments []domain.Payment, -) []bitcointree.Receiver { +) ([]bitcointree.Receiver, error) { receivers := make([]bitcointree.Receiver, 0) for _, payment := range payments { for _, receiver := range payment.Receivers { if !receiver.IsOnchain() { + vtxoScript, err := bitcointree.ParseVtxoScript(receiver.Descriptor) + if err != nil { + return nil, err + } + receivers = append(receivers, bitcointree.Receiver{ - Pubkey: receiver.Pubkey, + Script: vtxoScript, Amount: receiver.Amount, }) } } } - return receivers + return receivers, nil } func countSpentVtxos(payments []domain.Payment) uint64 { diff --git a/server/internal/infrastructure/wallet/btc-embedded/wallet.go b/server/internal/infrastructure/wallet/btc-embedded/wallet.go index 846b270..0403122 100644 --- a/server/internal/infrastructure/wallet/btc-embedded/wallet.go +++ b/server/internal/infrastructure/wallet/btc-embedded/wallet.go @@ -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) { fee := s.feeEstimator.RelayFeePerKW().FeeForVByte(lntypes.VByte(vbytes)) return uint64(fee.ToUnit(btcutil.AmountSatoshi)), nil diff --git a/server/internal/infrastructure/wallet/liquid-standalone/transaction.go b/server/internal/infrastructure/wallet/liquid-standalone/transaction.go index 187968f..99a03b4 100644 --- a/server/internal/infrastructure/wallet/liquid-standalone/transaction.go +++ b/server/internal/infrastructure/wallet/liquid-standalone/transaction.go @@ -16,6 +16,7 @@ import ( "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/vulpemventures/go-elements/elementsutil" "github.com/vulpemventures/go-elements/psetv2" ) @@ -58,7 +59,7 @@ func (s *service) SignTransaction( } switch c := closure.(type) { - case *tree.ForfeitClosure: + case *tree.MultisigClosure: asp := schnorr.SerializePubKey(c.AspPubkey) owner := schnorr.SerializePubKey(c.Pubkey) @@ -274,6 +275,12 @@ func (s *service) LockConnectorUtxos(ctx context.Context, utxos []ports.TxOutpoi 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) { feeRate := 0.2 fee := uint64(float64(vbytes) * feeRate) diff --git a/server/internal/interface/grpc/handlers/arkservice.go b/server/internal/interface/grpc/handlers/arkservice.go index 20e77d9..59ead44 100644 --- a/server/internal/interface/grpc/handlers/arkservice.go +++ b/server/internal/interface/grpc/handlers/arkservice.go @@ -6,7 +6,6 @@ import ( "sync" arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" - "github.com/ark-network/ark/common" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/application" "github.com/ark-network/ark/server/internal/core/domain" @@ -65,22 +64,27 @@ func (h *handler) CreatePayment(ctx context.Context, req *arkv1.CreatePaymentReq 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()) if err != nil { 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( - ctx, vtxosKeys, receivers, + ctx, inputs, receivers, ) if err != nil { return nil, err @@ -109,11 +113,11 @@ func (h *handler) Ping(ctx context.Context, req *arkv1.PingRequest) (*arkv1.Ping resp = &arkv1.PingResponse{ Event: &arkv1.PingResponse_RoundFinalization{ RoundFinalization: &arkv1.RoundFinalizationEvent{ - Id: e.Id, - PoolTx: e.PoolTx, - CongestionTree: castCongestionTree(e.CongestionTree), - ForfeitTxs: e.UnsignedForfeitTxs, - Connectors: e.Connectors, + Id: e.Id, + PoolTx: e.PoolTx, + CongestionTree: castCongestionTree(e.CongestionTree), + 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) { - hrp, userPubkey, aspPubkey, err := parseAddress(req.GetAddress()) + _, userPubkey, _, err := parseAddress(req.GetAddress()) if err != nil { 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{ - SpendableVtxos: vtxoList(spendableVtxos).toProto(hrp, aspPubkey), - SpentVtxos: vtxoList(spentVtxos).toProto(hrp, aspPubkey), + SpendableVtxos: vtxoList(spendableVtxos).toProto(), + SpentVtxos: vtxoList(spentVtxos).toProto(), }, 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)") } - addr, err := h.svc.GetBoardingAddress(ctx, userPubkey) + addr, descriptor, err := h.svc.GetBoardingAddress(ctx, userPubkey) if err != nil { return nil, err } return &arkv1.GetBoardingAddressResponse{ - Address: addr, + Address: addr, + Descriptor_: descriptor, }, nil } @@ -478,11 +483,11 @@ func (h *handler) listenToEvents() { ev = &arkv1.GetEventStreamResponse{ Event: &arkv1.GetEventStreamResponse_RoundFinalization{ RoundFinalization: &arkv1.RoundFinalizationEvent{ - Id: e.Id, - PoolTx: e.PoolTx, - CongestionTree: castCongestionTree(e.CongestionTree), - ForfeitTxs: e.UnsignedForfeitTxs, - Connectors: e.Connectors, + Id: e.Id, + PoolTx: e.PoolTx, + CongestionTree: castCongestionTree(e.CongestionTree), + Connectors: e.Connectors, + MinRelayFeeRate: e.MinRelayFeeRate, }, }, } @@ -547,15 +552,9 @@ func (h *handler) listenToEvents() { 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)) 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 if vv.AsyncPayment != nil { pendingData = &arkv1.PendingPayment{ @@ -564,18 +563,12 @@ func (v vtxoList) toProto(hrp string, aspKey *secp256k1.PublicKey) []*arkv1.Vtxo } } list = append(list, &arkv1.Vtxo{ - Outpoint: &arkv1.Input{ - Input: &arkv1.Input_VtxoInput{ - VtxoInput: &arkv1.VtxoInput{ - Txid: vv.Txid, - Vout: vv.VOut, - }, - }, - }, - Receiver: &arkv1.Output{ - Address: addr, - Amount: vv.Amount, + Outpoint: &arkv1.Outpoint{ + Txid: vv.Txid, + Vout: vv.VOut, }, + Descriptor_: vv.Descriptor, + Amount: vv.Amount, PoolTxid: vv.PoolTx, Spent: vv.Spent, ExpireAt: vv.ExpireAt, diff --git a/server/internal/interface/grpc/handlers/utils.go b/server/internal/interface/grpc/handlers/utils.go index 2a2c7a4..71b1c96 100644 --- a/server/internal/interface/grpc/handlers/utils.go +++ b/server/internal/interface/grpc/handlers/utils.go @@ -1,13 +1,12 @@ package handlers import ( - "encoding/hex" "fmt" arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" "github.com/ark-network/ark/common" - "github.com/ark-network/ark/server/internal/core/application" "github.com/ark-network/ark/server/internal/core/domain" + "github.com/ark-network/ark/server/internal/core/ports" "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -18,27 +17,19 @@ func parseAddress(addr string) (string, *secp256k1.PublicKey, *secp256k1.PublicK return common.DecodeAddress(addr) } -func parseInputs(ins []*arkv1.Input) ([]application.Input, error) { +func parseInputs(ins []*arkv1.Input) ([]ports.Input, error) { if len(ins) <= 0 { return nil, fmt.Errorf("missing inputs") } - inputs := make([]application.Input, 0, len(ins)) + inputs := make([]ports.Input, 0, len(ins)) for _, input := range ins { - if input.GetBoardingInput() != nil { - desc := input.GetBoardingInput().GetDescriptor_() - inputs = append(inputs, application.Input{ - Txid: input.GetBoardingInput().GetTxid(), - Index: input.GetBoardingInput().GetVout(), - Descriptor: desc, - }) - - continue - } - - inputs = append(inputs, application.Input{ - Txid: input.GetVtxoInput().GetTxid(), - Index: input.GetVtxoInput().GetVout(), + inputs = append(inputs, ports.Input{ + VtxoKey: domain.VtxoKey{ + Txid: input.GetOutpoint().GetTxid(), + VOut: input.GetOutpoint().GetVout(), + }, + Descriptor: input.GetDescriptor_(), }) } @@ -51,21 +42,14 @@ func parseReceivers(outs []*arkv1.Output) ([]domain.Receiver, error) { if out.GetAmount() == 0 { return nil, fmt.Errorf("missing output amount") } - if len(out.GetAddress()) <= 0 { - return nil, fmt.Errorf("missing output address") - } - var pubkey, addr string - _, pk, _, err := common.DecodeAddress(out.GetAddress()) - if err != nil { - addr = out.GetAddress() - } - if pk != nil { - pubkey = hex.EncodeToString(pk.SerializeCompressed()) + if len(out.GetAddress()) <= 0 && len(out.GetDescriptor_()) <= 0 { + return nil, fmt.Errorf("missing output destination") } + receivers = append(receivers, domain.Receiver{ - Pubkey: pubkey, + Descriptor: out.GetDescriptor_(), Amount: out.GetAmount(), - OnchainAddress: addr, + OnchainAddress: out.GetAddress(), }) } return receivers, nil