Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send unsigned pool transactions to clients #85

Merged
merged 8 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions asp/internal/core/application/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ func (s *service) startFinalization() {
return
}

signedPoolTx, tree, err := s.builder.BuildPoolTx(s.pubkey, s.wallet, payments, s.minRelayFee)
unsignedPoolTx, tree, err := s.builder.BuildPoolTx(s.pubkey, s.wallet, payments, s.minRelayFee)
if err != nil {
changes = round.Fail(fmt.Errorf("failed to create pool tx: %s", err))
log.WithError(err).Warn("failed to create pool tx")
Expand All @@ -275,7 +275,7 @@ func (s *service) startFinalization() {

log.Debugf("pool tx created for round %s", round.Id)

connectors, forfeitTxs, err := s.builder.BuildForfeitTxs(s.pubkey, signedPoolTx, payments)
connectors, forfeitTxs, err := s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments)
if err != nil {
changes = round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
log.WithError(err).Warn("failed to create connectors and forfeit txs")
Expand All @@ -284,7 +284,7 @@ func (s *service) startFinalization() {

log.Debugf("forfeit transactions created for round %s", round.Id)

events, err := round.StartFinalization(connectors, tree, signedPoolTx)
events, err := round.StartFinalization(connectors, tree, unsignedPoolTx)
if err != nil {
changes = round.Fail(fmt.Errorf("failed to start finalization: %s", err))
log.WithError(err).Warn("failed to start finalization")
Expand Down Expand Up @@ -326,7 +326,15 @@ func (s *service) finalizeRound() {
return
}

txid, err := s.wallet.BroadcastTransaction(ctx, round.TxHex)
log.Debugf("signing round transaction %s\n", round.Id)
signedPoolTx, err := s.wallet.SignPset(ctx, round.UnsignedTx, true)
if err != nil {
changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err))
log.WithError(err).Warn("failed to sign round tx")
return
}

txid, err := s.wallet.BroadcastTransaction(ctx, signedPoolTx)
if err != nil {
changes = round.Fail(fmt.Errorf("failed to broadcast pool tx: %s", err))
log.WithError(err).Warn("failed to broadcast pool tx")
Expand Down
4 changes: 2 additions & 2 deletions asp/internal/core/domain/round.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type Round struct {
Stage Stage
Payments map[string]Payment
Txid string
TxHex string
UnsignedTx string
ForfeitTxs []string
CongestionTree tree.CongestionTree
Connectors []string
Expand Down Expand Up @@ -84,7 +84,7 @@ func (r *Round) On(event RoundEvent, replayed bool) {
r.Stage.Code = FinalizationStage
r.CongestionTree = e.CongestionTree
r.Connectors = append([]string{}, e.Connectors...)
r.TxHex = e.PoolTx
r.UnsignedTx = e.PoolTx
case RoundFinalized:
r.Stage.Ended = true
r.Txid = e.Txid
Expand Down
11 changes: 3 additions & 8 deletions asp/internal/core/ports/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ type WalletService interface {
SignPset(
ctx context.Context, pset string, extractRawTx bool,
) (string, error)
Transfer(ctx context.Context, outs []TxOutput) (string, error)
SelectUtxos(ctx context.Context, asset string, amount uint64) ([]TxInput, uint64, error)
BroadcastTransaction(ctx context.Context, txHex string) (string, error)
EstimateFees(ctx context.Context, pset string) (uint64, error)
Close()
}

Expand All @@ -28,12 +29,6 @@ type TxInput interface {
GetTxid() string
GetIndex() uint32
GetScript() string
GetScriptSigSize() int
GetWitnessSize() int
}

type TxOutput interface {
GetAmount() uint64
GetAsset() string
GetScript() string
GetValue() uint64
}
2 changes: 1 addition & 1 deletion asp/internal/infrastructure/db/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ func roundsMatch(expected, got domain.Round) assert.Comparison {
if expected.Txid != got.Txid {
return false
}
if expected.TxHex != got.TxHex {
if expected.UnsignedTx != got.UnsignedTx {
return false
}
if !reflect.DeepEqual(expected.ForfeitTxs, got.ForfeitTxs) {
Expand Down
106 changes: 85 additions & 21 deletions asp/internal/infrastructure/ocean-wallet/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ package oceanwallet

import (
"context"
"encoding/hex"
"fmt"

pb "github.com/ark-network/ark/api-spec/protobuf/gen/ocean/v1"
"github.com/ark-network/ark/internal/core/ports"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/vulpemventures/go-elements/elementsutil"
"github.com/vulpemventures/go-elements/psetv2"
)

const msatsPerByte = 110
const (
zero32 = "0000000000000000000000000000000000000000000000000000000000000000"
)

func (s *service) SignPset(
ctx context.Context, pset string, extractRawTx bool,
Expand All @@ -21,29 +26,54 @@ func (s *service) SignPset(
return "", err
}
signedPset := res.GetPset()

if !extractRawTx {
return signedPset, nil
}

ptx, _ := psetv2.NewPsetFromBase64(signedPset)
ptx, err := psetv2.NewPsetFromBase64(signedPset)
if err != nil {
return "", err
}

if err := psetv2.MaybeFinalizeAll(ptx); err != nil {
return "", fmt.Errorf("failed to finalize signed pset: %s", err)
}
return ptx.ToBase64()

extractedTx, err := psetv2.Extract(ptx)
if err != nil {
return "", fmt.Errorf("failed to extract signed pset: %s", err)
}

txHex, err := extractedTx.ToHex()
if err != nil {
return "", fmt.Errorf("failed to convert extracted tx to hex: %s", err)
}

return txHex, nil
}

func (s *service) Transfer(
ctx context.Context, outs []ports.TxOutput,
) (string, error) {
res, err := s.txClient.Transfer(ctx, &pb.TransferRequest{
AccountName: accountLabel,
Receivers: outputList(outs).toProto(),
MillisatsPerByte: msatsPerByte,
func (s *service) SelectUtxos(ctx context.Context, asset string, amount uint64) ([]ports.TxInput, uint64, error) {
res, err := s.txClient.SelectUtxos(ctx, &pb.SelectUtxosRequest{
AccountName: accountLabel,
TargetAsset: asset,
TargetAmount: amount,
})
if err != nil {
return "", err
return nil, 0, err
}

inputs := make([]ports.TxInput, 0, len(res.GetUtxos()))
for _, utxo := range res.GetUtxos() {
// check that the utxos are not confidential
if utxo.GetAssetBlinder() != zero32 || utxo.GetValueBlinder() != zero32 {
return nil, 0, fmt.Errorf("utxo is confidential")
}

inputs = append(inputs, utxo)
}
return res.GetTxHex(), nil

return inputs, res.GetChange(), nil
}

func (s *service) BroadcastTransaction(
Expand All @@ -60,16 +90,50 @@ func (s *service) BroadcastTransaction(
return res.GetTxid(), nil
}

type outputList []ports.TxOutput
func (s *service) EstimateFees(
ctx context.Context, pset string,
) (uint64, error) {
tx, err := psetv2.NewPsetFromBase64(pset)
if err != nil {
return 0, err
}

inputs := make([]*pb.Input, 0, len(tx.Inputs))
outputs := make([]*pb.Output, 0, len(tx.Outputs))

for _, in := range tx.Inputs {
if in.WitnessUtxo == nil {
return 0, fmt.Errorf("missing witness utxo, cannot estimate fees")
}

inputs = append(inputs, &pb.Input{
Txid: chainhash.Hash(in.PreviousTxid).String(),
Index: in.PreviousTxIndex,
Script: hex.EncodeToString(in.WitnessUtxo.Script),
})
}

func (l outputList) toProto() []*pb.Output {
list := make([]*pb.Output, 0, len(l))
for _, out := range l {
list = append(list, &pb.Output{
Amount: out.GetAmount(),
Script: out.GetScript(),
Asset: out.GetAsset(),
for _, out := range tx.Outputs {
outputs = append(outputs, &pb.Output{
Asset: elementsutil.AssetHashFromBytes(
append([]byte{0x01}, out.Asset...),
),
Amount: out.Value,
Script: hex.EncodeToString(out.Script),
})
}
return list

fee, err := s.txClient.EstimateFees(
ctx,
&pb.EstimateFeesRequest{
Inputs: inputs,
Outputs: outputs,
},
)
if err != nil {
return 0, fmt.Errorf("failed to estimate fees: %s", err)
}

// we add 5 sats in order to avoid min-relay-fee not met errors
return fee.GetFeeAmount() + 5, nil
}
Loading
Loading