mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
chore: update bark bindings to v0.12.1 (#2477)
Moves from bark 0.2.3 to 0.4.0, which changed the FFI surface: - WalletOpen takes the network and a WalletOpenArgs, replacing WalletCreate and the separate RunDaemon call. - Bolt11Invoice takes an optional anti-DoS token, unused here. - LightningReceiveStatus is now LightningReceiveState, reporting progress via State rather than a PreimageRevealed bool. Movements expose PaymentHash and sends expose a typed terminal state, so both are read from those instead of the movement metadata JSON. A send movement that is neither pending nor successful now resolves the SendPaymentSync waiter instead of being ignored.
This commit is contained in:
parent
be17bc4e26
commit
ce46a0f8a0
3 changed files with 78 additions and 64 deletions
2
go.mod
2
go.mod
|
|
@ -19,7 +19,7 @@ require (
|
|||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tyler-smith/go-bip39 v1.1.0
|
||||
github.com/wailsapp/wails/v2 v2.12.0
|
||||
gitlab.com/ark-bitcoin/bark-ffi-bindings/golang v0.8.0
|
||||
gitlab.com/ark-bitcoin/bark-ffi-bindings/golang v0.12.1
|
||||
golang.org/x/crypto v0.52.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
google.golang.org/grpc v1.79.3
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -666,8 +666,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
|||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
gitlab.com/ark-bitcoin/bark-ffi-bindings/golang v0.8.0 h1:YKHSM8iNFMmTQ/QPUxC5U9KVim10F7sIGt3ou2wet+o=
|
||||
gitlab.com/ark-bitcoin/bark-ffi-bindings/golang v0.8.0/go.mod h1:1jAwB/XR4i3D72fz3qWAd41tQLYcOCGfWZHMagn5fNg=
|
||||
gitlab.com/ark-bitcoin/bark-ffi-bindings/golang v0.12.1 h1:jEnl0leC9n7EsT9Rn339nC9e/9GWCMPXzmzowzxmY24=
|
||||
gitlab.com/ark-bitcoin/bark-ffi-bindings/golang v0.12.1/go.mod h1:1jAwB/XR4i3D72fz3qWAd41tQLYcOCGfWZHMagn5fNg=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0=
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@ package bark
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -31,11 +29,14 @@ const (
|
|||
// Subsystem name reported on movements produced for outgoing lightning
|
||||
// payments (see bark's Subsystem::LIGHTNING_SEND).
|
||||
lightningSendSubsystem = "lightning_send"
|
||||
// The status a movement is created with; every other status is terminal.
|
||||
movementStatusPending = "pending"
|
||||
// Movement status reported once a movement has settled. A movement first
|
||||
// appears as "pending" and is updated to this once complete.
|
||||
movementStatusSuccessful = "successful"
|
||||
// Movement status reported when a send was definitively not paid.
|
||||
movementStatusFailed = "failed"
|
||||
// LightningReceive.State values in which we hold the preimage.
|
||||
receiveStatePreimageRevealed = "preimage-revealed"
|
||||
receiveStateSettled = "settled"
|
||||
// Grace period to allow the notification loop to unwind on shutdown.
|
||||
shutdownGracePeriod = 10 * time.Second
|
||||
)
|
||||
|
|
@ -123,7 +124,6 @@ func NewBarkService(ctx context.Context, eventPublisher events.EventPublisher, w
|
|||
|
||||
cfg := bark.Config{
|
||||
ServerAddress: config.ServerAddress,
|
||||
Network: network,
|
||||
RoundTxRequiredConfirmations: &roundTxRequiredConfirmations,
|
||||
}
|
||||
esploraAddress := config.EsploraAddress
|
||||
|
|
@ -135,23 +135,7 @@ func NewBarkService(ctx context.Context, eventPublisher events.EventPublisher, w
|
|||
cfg.ServerAccessToken = &token
|
||||
}
|
||||
|
||||
_, statErr := os.Stat(workDir)
|
||||
isFirstSetup := statErr != nil && errors.Is(statErr, os.ErrNotExist)
|
||||
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"workDir": workDir,
|
||||
"isFirstSetup": isFirstSetup,
|
||||
}).Info("Opening Bark wallet")
|
||||
|
||||
var wallet *bark.Wallet
|
||||
if isFirstSetup {
|
||||
wallet, err = bark.WalletCreate(mnemonic, cfg, workDir, false)
|
||||
} else {
|
||||
wallet, err = bark.WalletOpen(mnemonic, cfg, workDir)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open bark wallet: %w", err)
|
||||
}
|
||||
logger.Logger.WithField("workDir", workDir).Info("Opening Bark wallet")
|
||||
|
||||
// Bark provides a built-in background daemon that periodically syncs with
|
||||
// the Ark server and blockchain, participates in rounds, and — crucially for
|
||||
|
|
@ -159,8 +143,13 @@ func NewBarkService(ctx context.Context, eventPublisher events.EventPublisher, w
|
|||
// payment notifications and reveals the preimage, crediting the balance). We
|
||||
// don't poll for receives ourselves; instead we observe the resulting wallet
|
||||
// notifications (see runNotificationLoop) to emit payment-received events.
|
||||
if err := wallet.RunDaemon(nil); err != nil {
|
||||
logger.Logger.WithError(err).Warn("Bark daemon failed to start")
|
||||
wallet, err := bark.WalletOpen(network, mnemonic, cfg, bark.WalletOpenArgs{
|
||||
Datadir: workDir,
|
||||
RunDaemon: true,
|
||||
CreateIfNotExists: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open bark wallet: %w", err)
|
||||
}
|
||||
|
||||
loopCtx, cancelFn := context.WithCancel(context.Background())
|
||||
|
|
@ -260,6 +249,8 @@ func (bs *BarkService) handleLightningReceiveMovement(movement bark.Movement) {
|
|||
// A receive is only credited once its movement settles. We always hold the
|
||||
// preimage for our own receives, so PreimageRevealed isn't a useful signal;
|
||||
// the balance is credited when the movement status reaches "successful".
|
||||
// An abandoned receive finishes as "canceled": no funds arrived, so there is
|
||||
// nothing to report.
|
||||
if movement.Status != movementStatusSuccessful {
|
||||
return
|
||||
}
|
||||
|
|
@ -269,13 +260,13 @@ func (bs *BarkService) handleLightningReceiveMovement(movement bark.Movement) {
|
|||
return
|
||||
}
|
||||
|
||||
receive, err := bs.wallet.LightningReceiveStatus(paymentHash)
|
||||
if err != nil || receive == nil {
|
||||
receive, err := bs.wallet.LightningReceiveState(paymentHash)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithField("paymentHash", paymentHash).Warn("Failed to look up claimed Bark receive")
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := bs.lightningReceiveToTransaction(receive)
|
||||
tx, err := bs.lightningReceiveToTransaction(&receive)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithField("paymentHash", receive.PaymentHash).Warn("Failed to convert claimed Bark receive to transaction")
|
||||
return
|
||||
|
|
@ -296,55 +287,60 @@ func (bs *BarkService) handleLightningReceiveMovement(movement bark.Movement) {
|
|||
// goroutine is gone) it falls back to publishing nwc_lnclient_payment_sent /
|
||||
// _failed so the transactions service can recover the db transaction state.
|
||||
func (bs *BarkService) handleLightningSendMovement(movement bark.Movement) {
|
||||
if movement.Status != movementStatusSuccessful && movement.Status != movementStatusFailed {
|
||||
if movement.Status == movementStatusPending {
|
||||
return
|
||||
}
|
||||
|
||||
var meta struct {
|
||||
PaymentHash string `json:"payment_hash"`
|
||||
PaymentPreimage string `json:"payment_preimage"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(movement.MetadataJson), &meta); err != nil || meta.PaymentHash == "" {
|
||||
logger.Logger.WithError(err).WithField("movementId", movement.Id).Debug("Bark lightning send movement missing payment_hash")
|
||||
paymentHash, ok := paymentHashFromMovement(movement)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if movement.Status == movementStatusFailed {
|
||||
bs.deliverSendResult(meta.PaymentHash, sendResult{err: errors.New("bark lightning send failed")}, func() {
|
||||
// The movement can be canceled or failed so we should just check if it
|
||||
// wasn't successful.
|
||||
if movement.Status != movementStatusSuccessful {
|
||||
reason := fmt.Sprintf("bark lightning send %s", movement.Status)
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": paymentHash,
|
||||
"status": movement.Status,
|
||||
"reason": reason,
|
||||
}).Warn("Bark lightning send did not succeed")
|
||||
bs.deliverSendResult(paymentHash, sendResult{err: errors.New(reason)}, func() {
|
||||
bs.eventPublisher.Publish(&events.Event{
|
||||
Event: "nwc_lnclient_payment_failed",
|
||||
Properties: &lnclient.PaymentFailedEventProperties{
|
||||
Transaction: &lnclient.Transaction{
|
||||
Type: constants.TRANSACTION_TYPE_OUTGOING,
|
||||
PaymentHash: meta.PaymentHash,
|
||||
PaymentHash: paymentHash,
|
||||
},
|
||||
Reason: "bark lightning send failed",
|
||||
Reason: reason,
|
||||
},
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if meta.PaymentPreimage == "" {
|
||||
logger.Logger.WithField("paymentHash", meta.PaymentHash).Error("Bark lightning send reported successful but preimage is missing from movement metadata")
|
||||
bs.deliverSendResult(meta.PaymentHash, sendResult{err: errors.New("bark lightning send completed without a preimage")}, nil)
|
||||
preimage, err := bs.getSettledSendPreimage(paymentHash)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithField("paymentHash", paymentHash).Error("Bark lightning send reported successful but no preimage is available")
|
||||
bs.deliverSendResult(paymentHash, sendResult{err: fmt.Errorf("bark lightning send completed without a preimage: %w", err)}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
feeMsat := movement.OffchainFeeSats * 1000
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"paymentHash": meta.PaymentHash,
|
||||
"paymentHash": paymentHash,
|
||||
"feeMsat": feeMsat,
|
||||
}).Info("Bark lightning send completed")
|
||||
|
||||
bs.deliverSendResult(meta.PaymentHash, sendResult{preimage: meta.PaymentPreimage, feeMsat: feeMsat}, func() {
|
||||
bs.deliverSendResult(paymentHash, sendResult{preimage: preimage, feeMsat: feeMsat}, func() {
|
||||
settledAt := time.Now().Unix()
|
||||
bs.eventPublisher.Publish(&events.Event{
|
||||
Event: "nwc_lnclient_payment_sent",
|
||||
Properties: &lnclient.Transaction{
|
||||
Type: constants.TRANSACTION_TYPE_OUTGOING,
|
||||
PaymentHash: meta.PaymentHash,
|
||||
Preimage: meta.PaymentPreimage,
|
||||
PaymentHash: paymentHash,
|
||||
Preimage: preimage,
|
||||
FeesPaidMsat: int64(feeMsat),
|
||||
SettledAt: &settledAt,
|
||||
},
|
||||
|
|
@ -352,6 +348,24 @@ func (bs *BarkService) handleLightningSendMovement(movement bark.Movement) {
|
|||
})
|
||||
}
|
||||
|
||||
// Reads the preimage from the lightning-send's own state. Bark records the paid
|
||||
// invoice before finishing the movement, so it is always persisted by the time
|
||||
// the successful movement is observed.
|
||||
func (bs *BarkService) getSettledSendPreimage(paymentHash string) (string, error) {
|
||||
status, err := bs.wallet.LightningSendState(paymentHash)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to look up bark lightning send state: %w", err)
|
||||
}
|
||||
paid, ok := status.(bark.LightningSendStatusPaid)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("send is in state %T, expected settled", status)
|
||||
}
|
||||
if paid.Preimage == "" {
|
||||
return "", errors.New("settled send has an empty preimage")
|
||||
}
|
||||
return paid.Preimage, nil
|
||||
}
|
||||
|
||||
// deliverSendResult delivers to the SendPaymentSync waiter if present, else
|
||||
// runs fallback (used to publish an event for the hub-restart recovery path).
|
||||
func (bs *BarkService) deliverSendResult(paymentHash string, res sendResult, fallback func()) {
|
||||
|
|
@ -365,17 +379,14 @@ func (bs *BarkService) deliverSendResult(paymentHash string, res sendResult, fal
|
|||
}
|
||||
|
||||
func paymentHashFromMovement(movement bark.Movement) (string, bool) {
|
||||
var meta struct {
|
||||
PaymentHash string `json:"payment_hash"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(movement.MetadataJson), &meta); err != nil || meta.PaymentHash == "" {
|
||||
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
||||
if movement.PaymentHash == nil || *movement.PaymentHash == "" {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"movementId": movement.Id,
|
||||
"subsystemName": movement.SubsystemName,
|
||||
}).Debug("Bark lightning movement missing payment_hash")
|
||||
return "", false
|
||||
}
|
||||
return meta.PaymentHash, true
|
||||
return *movement.PaymentHash, true
|
||||
}
|
||||
|
||||
// notificationLogFields turns a Bark wallet notification into structured log
|
||||
|
|
@ -429,7 +440,8 @@ func (bs *BarkService) MakeInvoice(ctx context.Context, amountMsat int64, descri
|
|||
desc = &description
|
||||
}
|
||||
|
||||
invoice, err := bs.wallet.Bolt11Invoice(uint64(amountMsat/1000), desc)
|
||||
// The nil argument is an optional anti-DoS token, which we don't use.
|
||||
invoice, err := bs.wallet.Bolt11Invoice(uint64(amountMsat/1000), desc, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bark Bolt11Invoice failed: %w", err)
|
||||
}
|
||||
|
|
@ -443,18 +455,17 @@ func (bs *BarkService) MakeInvoice(ctx context.Context, amountMsat int64, descri
|
|||
expiresAtUnix := time.UnixMilli(int64(paymentRequest.CreatedAt) * 1000).Add(time.Duration(paymentRequest.Expiry) * time.Second).Unix()
|
||||
|
||||
// The preimage is generated alongside the invoice but is not returned by
|
||||
// Bolt11Invoice. Fetch it via the receive status so consumers can rely on
|
||||
// Bolt11Invoice. Fetch it via the receive state so consumers can rely on
|
||||
// lookup_invoice exposing the real preimage.
|
||||
var preimage string
|
||||
receive, err := bs.wallet.LightningReceiveStatus(paymentRequest.PaymentHash)
|
||||
receive, err := bs.wallet.LightningReceiveState(paymentRequest.PaymentHash)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithField("paymentHash", paymentRequest.PaymentHash).Error("Failed to fetch bark receive status for preimage")
|
||||
return nil, err
|
||||
logger.Logger.WithError(err).WithField("paymentHash", paymentRequest.PaymentHash).Error("Failed to fetch bark receive state for preimage")
|
||||
return nil, fmt.Errorf("failed to fetch bark receive state for preimage: %w", err)
|
||||
}
|
||||
preimage = receive.PaymentPreimage
|
||||
if preimage == "" {
|
||||
if receive.PaymentPreimage == nil || *receive.PaymentPreimage == "" {
|
||||
return nil, errors.New("no preimage available")
|
||||
}
|
||||
preimage := *receive.PaymentPreimage
|
||||
|
||||
return &lnclient.Transaction{
|
||||
Type: constants.TRANSACTION_TYPE_INCOMING,
|
||||
|
|
@ -551,10 +562,13 @@ func (bs *BarkService) lightningReceiveToTransaction(receive *bark.LightningRece
|
|||
Description: paymentRequest.Description,
|
||||
DescriptionHash: paymentRequest.DescriptionHash,
|
||||
}
|
||||
if receive.PreimageRevealed {
|
||||
// "preimage-revealed" until the claim is recorded, "settled" after.
|
||||
if receive.State == receiveStatePreimageRevealed || receive.State == receiveStateSettled {
|
||||
now := time.Now().Unix()
|
||||
tx.SettledAt = &now
|
||||
tx.Preimage = receive.PaymentPreimage
|
||||
if receive.PaymentPreimage != nil {
|
||||
tx.Preimage = *receive.PaymentPreimage
|
||||
}
|
||||
}
|
||||
return tx, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue