mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-18 12:44:19 +01:00
Ark Notes (#379)
* ark credits * rename "ecash" --> "ark credit" * rework note_test.go * NewFromString * create several notes * note repo: rename "push" to "add" * RegisterInputsForNextRoundRequest: move "notes" to field #3 * use uint64 as note ID * rename to voucher * add nostr notification * nostr notification test and fixes * bump badger to 4.3 * allow npub to be registered * rename poolTxID * add default relays * Update server/internal/config/config.go Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com> * fix RedeemVouchers test * notification = voucher * WASM wrappers * fix arkd voucher cmd * test_utils.go ignore gosec rule G101 * fix permissions * rename ALL to notes * add URI prefix * note.go : fix signature encoding * fix decode note.Data * Update server/internal/infrastructure/notifier/nostr/nostr.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> * Update pkg/client-sdk/wasm/browser/wrappers.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> * Update server/internal/infrastructure/notifier/nostr/nostr.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> * rework note and entity db + sqlite implementations * NOTIFICATION_PREFIX -> NOTE_URI_PREFIX * validate NOTE_URI_PREFIX * Update defaults to convenant-less mainnet (#2) * config: defaults to convenant-less tx builder * Drop env var for blockchain scanner --------- Co-authored-by: altafan <18440657+altafan@users.noreply.github.com> * add // before URI prefix * add URI prefix in admin CreateNote * Fixes * rework nonces encoding (#4) * rework nonces encoding * add a check in Musig2Nonce decode function * musig2_test: increase number of signers to 20 * musig2.json: add a test case with a 35 leaves tree * GetEventStream REST rework * fix round phases time intervals * [SDK] Use server-side streams in rest client * Fix history * make the URI optional * Updates * Fix settled txs in history * fix e2e test * go work sync in sdk unit test * fix signMessage in btc and liquid sdk wallets --------- Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com> Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com>
This commit is contained in:
@@ -12,6 +12,7 @@ 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/note"
|
||||
"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"
|
||||
@@ -34,6 +35,8 @@ type covenantlessService struct {
|
||||
unilateralExitDelay int64
|
||||
boardingExitDelay int64
|
||||
|
||||
nostrDefaultRelays []string
|
||||
|
||||
wallet ports.WalletService
|
||||
repoManager ports.RepoManager
|
||||
builder ports.TxBuilder
|
||||
@@ -57,9 +60,11 @@ type covenantlessService struct {
|
||||
func NewCovenantlessService(
|
||||
network common.Network,
|
||||
roundInterval, roundLifetime, unilateralExitDelay, boardingExitDelay int64,
|
||||
defaultNostrRelays []string,
|
||||
walletSvc ports.WalletService, repoManager ports.RepoManager,
|
||||
builder ports.TxBuilder, scanner ports.BlockchainScanner,
|
||||
scheduler ports.SchedulerService,
|
||||
notificationPrefix string,
|
||||
) (Service, error) {
|
||||
pubkey, err := walletSvc.GetPubkey(context.Background())
|
||||
if err != nil {
|
||||
@@ -76,7 +81,7 @@ func NewCovenantlessService(
|
||||
repoManager: repoManager,
|
||||
builder: builder,
|
||||
scanner: scanner,
|
||||
sweeper: newSweeper(walletSvc, repoManager, builder, scheduler),
|
||||
sweeper: newSweeper(walletSvc, repoManager, builder, scheduler, notificationPrefix),
|
||||
paymentRequests: newPaymentsMap(),
|
||||
forfeitTxs: newForfeitTxsMap(builder),
|
||||
eventsCh: make(chan domain.RoundEvent),
|
||||
@@ -85,6 +90,7 @@ func NewCovenantlessService(
|
||||
asyncPaymentsCache: make(map[string]asyncPaymentData),
|
||||
treeSigningSessions: make(map[string]*musigSigningSession),
|
||||
boardingExitDelay: boardingExitDelay,
|
||||
nostrDefaultRelays: defaultNostrRelays,
|
||||
}
|
||||
|
||||
repoManager.RegisterEventsHandler(
|
||||
@@ -396,6 +402,45 @@ func (s *covenantlessService) GetBoardingAddress(
|
||||
return addr.EncodeAddress(), vtxoScript.ToDescriptor(), nil
|
||||
}
|
||||
|
||||
func (s *covenantlessService) SpendNotes(ctx context.Context, notes []note.Note) (string, error) {
|
||||
notesRepo := s.repoManager.Notes()
|
||||
|
||||
for _, note := range notes {
|
||||
// verify the note signature
|
||||
hash := note.Hash()
|
||||
|
||||
valid, err := s.wallet.VerifyMessageSignature(ctx, hash, note.Signature)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to verify note signature: %s", err)
|
||||
}
|
||||
|
||||
if !valid {
|
||||
return "", fmt.Errorf("invalid note signature %s", note)
|
||||
}
|
||||
|
||||
// verify that the note is spendable
|
||||
spent, err := notesRepo.Contains(ctx, note.ID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to check if note is spent: %s", err)
|
||||
}
|
||||
|
||||
if spent {
|
||||
return "", fmt.Errorf("note already spent: %s", note)
|
||||
}
|
||||
}
|
||||
|
||||
payment, err := domain.NewPayment(make([]domain.Vtxo, 0))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create payment: %s", err)
|
||||
}
|
||||
|
||||
if err := s.paymentRequests.pushWithNotes(*payment, notes); err != nil {
|
||||
return "", fmt.Errorf("failed to push payment: %s", err)
|
||||
}
|
||||
|
||||
return payment.Id, nil
|
||||
}
|
||||
|
||||
func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Input) (string, error) {
|
||||
vtxosInputs := make([]domain.Vtxo, 0)
|
||||
boardingInputs := make([]ports.BoardingInput, 0)
|
||||
@@ -701,6 +746,43 @@ func (s *covenantlessService) RegisterCosignerSignatures(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *covenantlessService) SetNostrRecipient(ctx context.Context, nostrRecipient string, signedVtxoOutpoints []SignedVtxoOutpoint) error {
|
||||
nprofileRecipient, err := nip19toNostrProfile(nostrRecipient, s.nostrDefaultRelays)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert nostr recipient: %s", err)
|
||||
}
|
||||
|
||||
if err := validateProofs(ctx, s.repoManager.Vtxos(), signedVtxoOutpoints); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vtxoKeys := make([]domain.VtxoKey, 0, len(signedVtxoOutpoints))
|
||||
for _, signedVtxo := range signedVtxoOutpoints {
|
||||
vtxoKeys = append(vtxoKeys, signedVtxo.Outpoint)
|
||||
}
|
||||
|
||||
return s.repoManager.Entities().Add(
|
||||
ctx,
|
||||
domain.Entity{
|
||||
NostrRecipient: nprofileRecipient,
|
||||
},
|
||||
vtxoKeys,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *covenantlessService) DeleteNostrRecipient(ctx context.Context, signedVtxoOutpoints []SignedVtxoOutpoint) error {
|
||||
if err := validateProofs(ctx, s.repoManager.Vtxos(), signedVtxoOutpoints); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vtxoKeys := make([]domain.VtxoKey, 0, len(signedVtxoOutpoints))
|
||||
for _, signedVtxo := range signedVtxoOutpoints {
|
||||
vtxoKeys = append(vtxoKeys, signedVtxo.Outpoint)
|
||||
}
|
||||
|
||||
return s.repoManager.Entities().Delete(ctx, vtxoKeys)
|
||||
}
|
||||
|
||||
func (s *covenantlessService) start() {
|
||||
s.startRound()
|
||||
}
|
||||
@@ -732,6 +814,7 @@ func (s *covenantlessService) startFinalization() {
|
||||
roundRemainingDuration := time.Duration((s.roundInterval/3)*2-1) * time.Second
|
||||
thirdOfRemainingDuration := time.Duration(roundRemainingDuration / 3)
|
||||
|
||||
var notes []note.Note
|
||||
var roundAborted bool
|
||||
defer func() {
|
||||
delete(s.treeSigningSessions, round.Id)
|
||||
@@ -749,7 +832,7 @@ func (s *covenantlessService) startFinalization() {
|
||||
return
|
||||
}
|
||||
time.Sleep(thirdOfRemainingDuration)
|
||||
s.finalizeRound()
|
||||
s.finalizeRound(notes)
|
||||
}()
|
||||
|
||||
if round.IsFailed() {
|
||||
@@ -768,7 +851,7 @@ func (s *covenantlessService) startFinalization() {
|
||||
if num > paymentsThreshold {
|
||||
num = paymentsThreshold
|
||||
}
|
||||
payments, boardingInputs, descriptors, cosigners := s.paymentRequests.pop(num)
|
||||
payments, boardingInputs, descriptors, cosigners, paymentsNotes := 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))
|
||||
@@ -776,6 +859,8 @@ func (s *covenantlessService) startFinalization() {
|
||||
return
|
||||
}
|
||||
|
||||
notes = paymentsNotes
|
||||
|
||||
if _, err := round.RegisterPayments(payments); err != nil {
|
||||
round.Fail(fmt.Errorf("failed to register payments: %s", err))
|
||||
log.WithError(err).Warn("failed to register payments")
|
||||
@@ -1020,7 +1105,7 @@ func (s *covenantlessService) propagateRoundSigningNoncesGeneratedEvent(combined
|
||||
s.eventsCh <- ev
|
||||
}
|
||||
|
||||
func (s *covenantlessService) finalizeRound() {
|
||||
func (s *covenantlessService) finalizeRound(notes []note.Note) {
|
||||
defer s.startRound()
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -1104,6 +1189,13 @@ func (s *covenantlessService) finalizeRound() {
|
||||
return
|
||||
}
|
||||
|
||||
// mark the notes as spent
|
||||
for _, note := range notes {
|
||||
if err := s.repoManager.Notes().Add(ctx, note.ID); err != nil {
|
||||
log.WithError(err).Warn("failed to mark note as spent")
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
s.transactionEventsCh <- RoundTransactionEvent{
|
||||
RoundTxID: round.Txid,
|
||||
|
||||
Reference in New Issue
Block a user