mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-18 04:34:19 +01:00
New boarding protocol (#279)
* [domain] add reverse boarding inputs in Payment struct * [tx-builder] support reverse boarding script * [wallet] add GetTransaction * [api-spec][application] add reverse boarding support in covenantless * [config] add reverse boarding config * [api-spec] add ReverseBoardingAddress RPC * [domain][application] support empty forfeits txs in EndFinalization events * [tx-builder] optional connector output in round tx * [btc-embedded] fix getTx and taproot finalizer * whitelist ReverseBoardingAddress RPC * [test] add reverse boarding integration test * [client] support reverse boarding * [sdk] support reverse boarding * [e2e] add sleep time after faucet * [test] run using bitcoin-core RPC * [tx-builder] fix GetSweepInput * [application][tx-builder] support reverse onboarding in covenant * [cli] support reverse onboarding in covenant CLI * [test] rework integration tests * [sdk] remove onchain wallet, replace by onboarding address * remove old onboarding protocols * [sdk] Fix RegisterPayment * [e2e] add more funds to covenant ASP * [e2e] add sleeping time * several fixes * descriptor boarding * remove boarding delay from info * [sdk] implement descriptor boarding * go mod tidy * fixes and revert error msgs * move descriptor pkg to common * add replace in go.mod * [sdk] fix unit tests * rename DescriptorInput --> BoardingInput * genrest in SDK * remove boarding input from domain * remove all "reverse boarding" * rename "onboarding" ==> "boarding" * remove outdate payment unit test * use tmpfs docker volument for compose testing files * several fixes
This commit is contained in:
@@ -11,12 +11,15 @@ 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/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/psbt"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -27,6 +30,7 @@ type covenantlessService struct {
|
||||
roundLifetime int64
|
||||
roundInterval int64
|
||||
unilateralExitDelay int64
|
||||
boardingExitDelay int64
|
||||
minRelayFee uint64
|
||||
|
||||
wallet ports.WalletService
|
||||
@@ -38,11 +42,11 @@ type covenantlessService struct {
|
||||
paymentRequests *paymentsMap
|
||||
forfeitTxs *forfeitTxsMap
|
||||
|
||||
eventsCh chan domain.RoundEvent
|
||||
onboardingCh chan onboarding
|
||||
eventsCh chan domain.RoundEvent
|
||||
|
||||
// cached data for the current round
|
||||
lastEvent domain.RoundEvent
|
||||
currentRoundLock sync.Mutex
|
||||
currentRound *domain.Round
|
||||
treeSigningSessions map[string]*musigSigningSession
|
||||
asyncPaymentsCache map[domain.VtxoKey]struct {
|
||||
@@ -53,14 +57,13 @@ type covenantlessService struct {
|
||||
|
||||
func NewCovenantlessService(
|
||||
network common.Network,
|
||||
roundInterval, roundLifetime, unilateralExitDelay int64, minRelayFee uint64,
|
||||
roundInterval, roundLifetime, unilateralExitDelay, boardingExitDelay int64, minRelayFee uint64,
|
||||
walletSvc ports.WalletService, repoManager ports.RepoManager,
|
||||
builder ports.TxBuilder, scanner ports.BlockchainScanner,
|
||||
scheduler ports.SchedulerService,
|
||||
) (Service, error) {
|
||||
eventsCh := make(chan domain.RoundEvent)
|
||||
onboardingCh := make(chan onboarding)
|
||||
paymentRequests := newPaymentsMap(nil)
|
||||
paymentRequests := newPaymentsMap()
|
||||
|
||||
forfeitTxs := newForfeitTxsMap(builder)
|
||||
pubkey, err := walletSvc.GetPubkey(context.Background())
|
||||
@@ -89,9 +92,10 @@ func NewCovenantlessService(
|
||||
paymentRequests: paymentRequests,
|
||||
forfeitTxs: forfeitTxs,
|
||||
eventsCh: eventsCh,
|
||||
onboardingCh: onboardingCh,
|
||||
currentRoundLock: sync.Mutex{},
|
||||
asyncPaymentsCache: asyncPaymentsCache,
|
||||
treeSigningSessions: make(map[string]*musigSigningSession),
|
||||
boardingExitDelay: boardingExitDelay,
|
||||
}
|
||||
|
||||
repoManager.RegisterEventsHandler(
|
||||
@@ -109,7 +113,6 @@ func NewCovenantlessService(
|
||||
return nil, fmt.Errorf("failed to restore watching vtxos: %s", err)
|
||||
}
|
||||
go svc.listenToScannerNotifications()
|
||||
go svc.listenToOnboarding()
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
@@ -137,7 +140,6 @@ func (s *covenantlessService) Stop() {
|
||||
s.repoManager.Close()
|
||||
log.Debug("closed connection to db")
|
||||
close(s.eventsCh)
|
||||
close(s.onboardingCh)
|
||||
}
|
||||
|
||||
func (s *covenantlessService) CompleteAsyncPayment(
|
||||
@@ -242,27 +244,147 @@ func (s *covenantlessService) CreateAsyncPayment(
|
||||
return res.RedeemTx, res.UnconditionalForfeitTxs, nil
|
||||
}
|
||||
|
||||
func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error) {
|
||||
vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, inputs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, v := range vtxos {
|
||||
if v.Spent {
|
||||
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
|
||||
func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []Input) (string, error) {
|
||||
vtxosInputs := make([]domain.VtxoKey, 0)
|
||||
boardingInputs := make([]Input, 0)
|
||||
|
||||
for _, input := range inputs {
|
||||
if input.IsVtxo() {
|
||||
vtxosInputs = append(vtxosInputs, input.VtxoKey())
|
||||
continue
|
||||
}
|
||||
|
||||
boardingInputs = append(boardingInputs, input)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := s.paymentRequests.push(*payment); err != nil {
|
||||
if err := s.paymentRequests.push(*payment, utxos); 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) {
|
||||
return nil, fmt.Errorf("output not found")
|
||||
}
|
||||
|
||||
out := tx.TxOut[vout]
|
||||
script := out.PkScript
|
||||
|
||||
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)
|
||||
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 get boarding script: %s", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(script, expectedScript) {
|
||||
return nil, fmt.Errorf("invalid boarding input output script")
|
||||
}
|
||||
|
||||
return &boardingInput{
|
||||
txId: tx.TxHash(),
|
||||
vout: vout,
|
||||
boardingPubKey: pubkey,
|
||||
amount: uint64(out.Value),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *covenantlessService) ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error {
|
||||
// Check credentials
|
||||
payment, ok := s.paymentRequests.view(creds)
|
||||
@@ -293,6 +415,19 @@ func (s *covenantlessService) SignVtxos(ctx context.Context, forfeitTxs []string
|
||||
return s.forfeitTxs.sign(forfeitTxs)
|
||||
}
|
||||
|
||||
func (s *covenantlessService) SignRoundTx(ctx context.Context, signedRoundTx string) error {
|
||||
s.currentRoundLock.Lock()
|
||||
defer s.currentRoundLock.Unlock()
|
||||
|
||||
combined, err := s.builder.VerifyAndCombinePartialTx(s.currentRound.UnsignedTx, signedRoundTx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to verify and combine partial tx: %s", err)
|
||||
}
|
||||
|
||||
s.currentRound.UnsignedTx = combined
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *covenantlessService) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, []domain.Vtxo, error) {
|
||||
pk := hex.EncodeToString(pubkey.SerializeCompressed())
|
||||
return s.repoManager.Vtxos().GetAllVtxos(ctx, pk)
|
||||
@@ -324,50 +459,26 @@ func (s *covenantlessService) GetInfo(ctx context.Context) (*ServiceInfo, error)
|
||||
RoundInterval: s.roundInterval,
|
||||
Network: s.network.Name,
|
||||
MinRelayFee: int64(s.minRelayFee),
|
||||
BoardingDescriptorTemplate: fmt.Sprintf(
|
||||
descriptor.BoardingDescriptorTemplate,
|
||||
hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()),
|
||||
hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)),
|
||||
"USER",
|
||||
s.boardingExitDelay,
|
||||
"USER",
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO clArk changes the onboard flow (2 rounds ?)
|
||||
func (s *covenantlessService) Onboard(
|
||||
ctx context.Context, boardingTx string,
|
||||
congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey,
|
||||
) error {
|
||||
ptx, err := psbt.NewFromRawBytes(strings.NewReader(boardingTx), true)
|
||||
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 parse boarding tx: %s", err)
|
||||
return "", fmt.Errorf("failed to compute boarding script: %s", err)
|
||||
}
|
||||
|
||||
if err := bitcointree.ValidateCongestionTree(
|
||||
congestionTree, boardingTx, s.pubkey, s.roundLifetime, int64(s.minRelayFee),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
extracted, err := psbt.Extract(ptx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract boarding tx: %s", err)
|
||||
}
|
||||
|
||||
var serialized bytes.Buffer
|
||||
|
||||
if err := extracted.Serialize(&serialized); err != nil {
|
||||
return fmt.Errorf("failed to serialize boarding tx: %s", err)
|
||||
}
|
||||
|
||||
txid, err := s.wallet.BroadcastTransaction(ctx, hex.EncodeToString(serialized.Bytes()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to broadcast boarding tx: %s", err)
|
||||
}
|
||||
|
||||
log.Debugf("broadcasted boarding tx %s", txid)
|
||||
|
||||
s.onboardingCh <- onboarding{
|
||||
tx: boardingTx,
|
||||
congestionTree: congestionTree,
|
||||
userPubkey: userPubkey,
|
||||
}
|
||||
|
||||
return nil
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
func (s *covenantlessService) RegisterCosignerPubkey(ctx context.Context, paymentId string, pubkey string) error {
|
||||
@@ -504,7 +615,7 @@ func (s *covenantlessService) startFinalization() {
|
||||
if num > paymentsThreshold {
|
||||
num = paymentsThreshold
|
||||
}
|
||||
payments, cosigners := s.paymentRequests.pop(num)
|
||||
payments, boardingInputs, cosigners := s.paymentRequests.pop(num)
|
||||
if len(payments) > len(cosigners) {
|
||||
err := fmt.Errorf("missing ephemeral key for payments")
|
||||
round.Fail(fmt.Errorf("round aborted: %s", err))
|
||||
@@ -534,7 +645,7 @@ func (s *covenantlessService) startFinalization() {
|
||||
|
||||
cosigners = append(cosigners, ephemeralKey.PubKey())
|
||||
|
||||
unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, s.minRelayFee, sweptRounds, cosigners...)
|
||||
unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, boardingInputs, s.minRelayFee, sweptRounds, cosigners...)
|
||||
if err != nil {
|
||||
round.Fail(fmt.Errorf("failed to create pool tx: %s", err))
|
||||
log.WithError(err).Warn("failed to create pool tx")
|
||||
@@ -683,14 +794,25 @@ func (s *covenantlessService) startFinalization() {
|
||||
tree = signedTree
|
||||
}
|
||||
|
||||
connectors, forfeitTxs, err := s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments, s.minRelayFee)
|
||||
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
|
||||
needForfeits := false
|
||||
for _, pay := range payments {
|
||||
if len(pay.Inputs) > 0 {
|
||||
needForfeits = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("forfeit transactions created for round %s", round.Id)
|
||||
var forfeitTxs, connectors []string
|
||||
|
||||
if needForfeits {
|
||||
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments, s.minRelayFee)
|
||||
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 := round.StartFinalization(
|
||||
connectorAddress, connectors, tree, unsignedPoolTx,
|
||||
@@ -763,73 +885,61 @@ func (s *covenantlessService) finalizeRound() {
|
||||
}
|
||||
|
||||
log.Debugf("signing round transaction %s\n", round.Id)
|
||||
signedPoolTx, err := s.wallet.SignTransaction(ctx, round.UnsignedTx, true)
|
||||
|
||||
boardingInputs := make([]int, 0)
|
||||
roundTx, err := psbt.NewFromRawBytes(strings.NewReader(round.UnsignedTx), true)
|
||||
if err != nil {
|
||||
changes = round.Fail(fmt.Errorf("failed to parse round tx: %s", err))
|
||||
log.WithError(err).Warn("failed to parse round tx")
|
||||
return
|
||||
}
|
||||
|
||||
for i, in := range roundTx.Inputs {
|
||||
if len(in.TaprootLeafScript) > 0 {
|
||||
if len(in.TaprootScriptSpendSig) == 0 {
|
||||
err = fmt.Errorf("missing tapscript spend sig for input %d", i)
|
||||
changes = round.Fail(err)
|
||||
log.WithError(err).Warn("missing boarding sig")
|
||||
return
|
||||
}
|
||||
|
||||
boardingInputs = append(boardingInputs, i)
|
||||
}
|
||||
}
|
||||
|
||||
signedRoundTx := round.UnsignedTx
|
||||
|
||||
if len(boardingInputs) > 0 {
|
||||
signedRoundTx, err = s.wallet.SignTransactionTapscript(ctx, signedRoundTx, boardingInputs)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
signedRoundTx, err = s.wallet.SignTransaction(ctx, signedRoundTx, 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)
|
||||
txid, err := s.wallet.BroadcastTransaction(ctx, signedRoundTx)
|
||||
if err != nil {
|
||||
changes = round.Fail(fmt.Errorf("failed to broadcast pool tx: %s", err))
|
||||
log.WithError(err).Warn("failed to broadcast pool tx")
|
||||
return
|
||||
}
|
||||
|
||||
changes, _ = round.EndFinalization(forfeitTxs, txid)
|
||||
|
||||
log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid)
|
||||
}
|
||||
|
||||
func (s *covenantlessService) listenToOnboarding() {
|
||||
for onboarding := range s.onboardingCh {
|
||||
go s.handleOnboarding(onboarding)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *covenantlessService) handleOnboarding(onboarding onboarding) {
|
||||
ctx := context.Background()
|
||||
|
||||
ptx, _ := psbt.NewFromRawBytes(strings.NewReader(onboarding.tx), true)
|
||||
txid := ptx.UnsignedTx.TxHash().String()
|
||||
|
||||
// wait for the tx to be confirmed with a timeout
|
||||
timeout := time.NewTimer(15 * time.Minute)
|
||||
defer timeout.Stop()
|
||||
|
||||
isConfirmed := false
|
||||
|
||||
for !isConfirmed {
|
||||
select {
|
||||
case <-timeout.C:
|
||||
log.WithError(fmt.Errorf("operation timed out")).Warnf("failed to get confirmation for boarding tx %s", txid)
|
||||
return
|
||||
default:
|
||||
var err error
|
||||
isConfirmed, _, err = s.wallet.IsTransactionConfirmed(ctx, txid)
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("failed to check tx confirmation")
|
||||
}
|
||||
|
||||
if err != nil || !isConfirmed {
|
||||
log.Debugf("waiting for boarding tx %s to be confirmed", txid)
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("boarding tx %s confirmed", txid)
|
||||
|
||||
pubkey := hex.EncodeToString(onboarding.userPubkey.SerializeCompressed())
|
||||
payments := getPaymentsFromOnboardingBitcoin(onboarding.congestionTree, pubkey)
|
||||
round := domain.NewFinalizedRound(
|
||||
dustAmount, pubkey, txid, onboarding.tx, onboarding.congestionTree, payments,
|
||||
)
|
||||
if err := s.saveEvents(ctx, round.Id, round.Events()); err != nil {
|
||||
log.WithError(err).Warn("failed to store new round events")
|
||||
changes, err = round.EndFinalization(forfeitTxs, txid)
|
||||
if err != nil {
|
||||
changes = round.Fail(fmt.Errorf("failed to finalize round: %s", err))
|
||||
log.WithError(err).Warn("failed to finalize round")
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid)
|
||||
}
|
||||
|
||||
func (s *covenantlessService) listenToScannerNotifications() {
|
||||
@@ -1232,24 +1342,6 @@ func (s *covenantlessService) saveEvents(
|
||||
return s.repoManager.Rounds().AddOrUpdateRound(ctx, *round)
|
||||
}
|
||||
|
||||
func getPaymentsFromOnboardingBitcoin(
|
||||
congestionTree tree.CongestionTree, userKey string,
|
||||
) []domain.Payment {
|
||||
leaves := congestionTree.Leaves()
|
||||
receivers := make([]domain.Receiver, 0, len(leaves))
|
||||
for _, node := range leaves {
|
||||
ptx, _ := psbt.NewFromRawBytes(strings.NewReader(node.Tx), true)
|
||||
|
||||
receiver := domain.Receiver{
|
||||
Pubkey: userKey,
|
||||
Amount: uint64(ptx.UnsignedTx.TxOut[0].Value),
|
||||
}
|
||||
receivers = append(receivers, receiver)
|
||||
}
|
||||
payment := domain.NewPaymentUnsafe(nil, receivers)
|
||||
return []domain.Payment{*payment}
|
||||
}
|
||||
|
||||
func findForfeitTxBitcoin(
|
||||
forfeits []string, connectorTxid string, connectorVout uint32, vtxoTxid string,
|
||||
) (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user