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:
Louis Singer
2024-11-15 19:07:33 +01:00
committed by GitHub
parent 963f5d89e6
commit ff96524f22
94 changed files with 6377 additions and 1230 deletions

View File

@@ -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,