From 1f081b59363aeb7deea0bdd63676d74b83b9b64f Mon Sep 17 00:00:00 2001 From: elnosh Date: Thu, 1 Aug 2024 15:37:04 -0500 Subject: [PATCH] refactor mint config --- cmd/mint/mint.go | 136 +++++++++++++++++++++++++++++- mint/config.go | 152 +++------------------------------- mint/lightning/lightning.go | 27 ------ mint/lightning/lnd.go | 54 +++--------- mint/mint.go | 151 ++++++++++++++++++++++++++------- mint/mint_integration_test.go | 29 ++----- mint/server.go | 13 ++- testutils/utils.go | 59 +++++++++---- 8 files changed, 333 insertions(+), 288 deletions(-) diff --git a/cmd/mint/mint.go b/cmd/mint/mint.go index 468b5df..d1ff443 100644 --- a/cmd/mint/mint.go +++ b/cmd/mint/mint.go @@ -1,20 +1,152 @@ package main import ( + "encoding/json" + "errors" + "fmt" "log" + "os" + "strconv" + "github.com/elnosh/gonuts/cashu/nuts/nut06" "github.com/elnosh/gonuts/mint" + "github.com/elnosh/gonuts/mint/lightning" "github.com/joho/godotenv" + "github.com/lightningnetwork/lnd/macaroons" + "google.golang.org/grpc/credentials" + "gopkg.in/macaroon.v2" ) +func configFromEnv() (*mint.Config, error) { + var inputFeePpk uint = 0 + if inputFeeEnv, ok := os.LookupEnv("INPUT_FEE_PPK"); ok { + fee, err := strconv.ParseUint(inputFeeEnv, 10, 16) + if err != nil { + return nil, fmt.Errorf("invalid INPUT_FEE_PPK: %v", err) + } + inputFeePpk = uint(fee) + } + + derivationPathIdx, err := strconv.ParseUint(os.Getenv("DERIVATION_PATH_IDX"), 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid DERIVATION_PATH_IDX: %v", err) + } + + mintLimits := mint.MintLimits{} + if maxBalanceEnv, ok := os.LookupEnv("MAX_BALANCE"); ok { + maxBalance, err := strconv.ParseUint(maxBalanceEnv, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid MAX_BALANCE: %v", err) + } + mintLimits.MaxBalance = maxBalance + } + + if maxMintEnv, ok := os.LookupEnv("MINTING_MAX_AMOUNT"); ok { + maxMint, err := strconv.ParseUint(maxMintEnv, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid MINTING_MAX_AMOUNT: %v", err) + } + mintLimits.MintingSettings = mint.MintMethodSettings{MaxAmount: maxMint} + } + + if maxMeltEnv, ok := os.LookupEnv("MELTING_MAX_AMOUNT"); ok { + maxMelt, err := strconv.ParseUint(maxMeltEnv, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid MELTING_MAX_AMOUNT: %v", err) + } + mintLimits.MeltingSettings = mint.MeltMethodSettings{MaxAmount: maxMelt} + } + + mintInfo := mint.MintInfo{ + Name: os.Getenv("MINT_NAME"), + Description: os.Getenv("MINT_DESCRIPTION"), + } + + mintInfo.LongDescription = os.Getenv("MINT_DESCRIPTION_LONG") + mintInfo.Motd = os.Getenv("MINT_MOTD") + + contact := os.Getenv("MINT_CONTACT_INFO") + var mintContactInfo []nut06.ContactInfo + if len(contact) > 0 { + var infoArr [][]string + err := json.Unmarshal([]byte(contact), &infoArr) + if err != nil { + return nil, fmt.Errorf("error parsing contact info: %v", err) + } + + for _, info := range infoArr { + contactInfo := nut06.ContactInfo{Method: info[0], Info: info[1]} + mintContactInfo = append(mintContactInfo, contactInfo) + } + } + mintInfo.Contact = mintContactInfo + + // read values for setting up LND + host := os.Getenv("LND_GRPC_HOST") + if host == "" { + return nil, errors.New("LND_GRPC_HOST cannot be empty") + } + certPath := os.Getenv("LND_CERT_PATH") + if certPath == "" { + return nil, errors.New("LND_CERT_PATH cannot be empty") + } + macaroonPath := os.Getenv("LND_MACAROON_PATH") + if macaroonPath == "" { + return nil, errors.New("LND_MACAROON_PATH cannot be empty") + } + + creds, err := credentials.NewClientTLSFromFile(certPath, "") + if err != nil { + return nil, err + } + + macaroonBytes, err := os.ReadFile(macaroonPath) + if err != nil { + return nil, fmt.Errorf("error reading macaroon: os.ReadFile %v", err) + } + + macaroon := &macaroon.Macaroon{} + if err = macaroon.UnmarshalBinary(macaroonBytes); err != nil { + return nil, fmt.Errorf("unable to decode macaroon: %v", err) + } + macarooncreds, err := macaroons.NewMacaroonCredential(macaroon) + if err != nil { + return nil, fmt.Errorf("error setting macaroon creds: %v", err) + } + lndConfig := lightning.LndConfig{ + GRPCHost: host, + Cert: creds, + Macaroon: macarooncreds, + } + + lndClient, err := lightning.SetupLndClient(lndConfig) + if err != nil { + return nil, fmt.Errorf("error setting LND client: %v", err) + } + + return &mint.Config{ + DerivationPathIdx: uint32(derivationPathIdx), + Port: os.Getenv("MINT_PORT"), + MintPath: os.Getenv("MINT_DB_PATH"), + DBMigrationPath: "../../mint/storage/sqlite/migrations", + InputFeePpk: inputFeePpk, + MintInfo: mintInfo, + Limits: mintLimits, + LightningClient: lndClient, + }, nil +} + func main() { err := godotenv.Load() if err != nil { log.Fatal("error loading .env file") } - mintConfig := mint.GetConfig() + mintConfig, err := configFromEnv() + if err != nil { + log.Fatalf("error reading config: %v", err) + } - mintServer, err := mint.SetupMintServer(mintConfig) + mintServer, err := mint.SetupMintServer(*mintConfig) if err != nil { log.Fatalf("error starting mint server: %v", err) } diff --git a/mint/config.go b/mint/config.go index c8bab11..d550d6f 100644 --- a/mint/config.go +++ b/mint/config.go @@ -1,25 +1,27 @@ package mint import ( - "encoding/hex" - "encoding/json" - "fmt" - "log" - "os" - "strconv" - - "github.com/btcsuite/btcd/btcutil/hdkeychain" - "github.com/btcsuite/btcd/chaincfg" "github.com/elnosh/gonuts/cashu/nuts/nut06" + "github.com/elnosh/gonuts/mint/lightning" ) type Config struct { DerivationPathIdx uint32 Port string - DBPath string + MintPath string DBMigrationPath string InputFeePpk uint + MintInfo MintInfo Limits MintLimits + LightningClient lightning.Client +} + +type MintInfo struct { + Name string + Description string + LongDescription string + Contact []nut06.ContactInfo + Motd string } type MintMethodSettings struct { @@ -37,133 +39,3 @@ type MintLimits struct { MintingSettings MintMethodSettings MeltingSettings MeltMethodSettings } - -func GetConfig() Config { - var inputFeePpk uint = 0 - if inputFeeEnv, ok := os.LookupEnv("INPUT_FEE_PPK"); ok { - fee, err := strconv.ParseUint(inputFeeEnv, 10, 16) - if err != nil { - log.Fatalf("invalid INPUT_FEE_PPK: %v", err) - } - inputFeePpk = uint(fee) - } - - derivationPathIdx, err := strconv.ParseUint(os.Getenv("DERIVATION_PATH_IDX"), 10, 32) - if err != nil { - log.Fatalf("invalid DERIVATION_PATH_IDX: %v", err) - } - - mintLimits := MintLimits{} - if maxBalanceEnv, ok := os.LookupEnv("MAX_BALANCE"); ok { - maxBalance, err := strconv.ParseUint(maxBalanceEnv, 10, 64) - if err != nil { - log.Fatalf("invalid MAX_BALANCE: %v", err) - } - mintLimits.MaxBalance = maxBalance - } - - if maxMintEnv, ok := os.LookupEnv("MINTING_MAX_AMOUNT"); ok { - maxMint, err := strconv.ParseUint(maxMintEnv, 10, 64) - if err != nil { - log.Fatalf("invalid MINTING_MAX_AMOUNT: %v", err) - } - mintLimits.MintingSettings = MintMethodSettings{MaxAmount: maxMint} - } - - if maxMeltEnv, ok := os.LookupEnv("MELTING_MAX_AMOUNT"); ok { - maxMelt, err := strconv.ParseUint(maxMeltEnv, 10, 64) - if err != nil { - log.Fatalf("invalid MELTING_MAX_AMOUNT: %v", err) - } - mintLimits.MeltingSettings = MeltMethodSettings{MaxAmount: maxMelt} - } - - return Config{ - DerivationPathIdx: uint32(derivationPathIdx), - Port: os.Getenv("MINT_PORT"), - DBPath: os.Getenv("MINT_DB_PATH"), - DBMigrationPath: "../../mint/storage/sqlite/migrations", - InputFeePpk: inputFeePpk, - Limits: mintLimits, - } -} - -// getMintInfo returns information about the mint as -// defined in NUT-06: https://github.com/cashubtc/nuts/blob/main/06.md -func (m *Mint) getMintInfo() (*nut06.MintInfo, error) { - mintInfo := nut06.MintInfo{ - Name: os.Getenv("MINT_NAME"), - Version: "gonuts/0.0.1", - Description: os.Getenv("MINT_DESCRIPTION"), - } - - mintInfo.LongDescription = os.Getenv("MINT_DESCRIPTION_LONG") - mintInfo.Motd = os.Getenv("MINT_MOTD") - - seed, err := m.db.GetSeed() - if err != nil { - return nil, err - } - - master, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) - if err != nil { - return nil, err - } - - publicKey, err := master.ECPubKey() - if err != nil { - return nil, err - } - - mintInfo.Pubkey = hex.EncodeToString(publicKey.SerializeCompressed()) - - contact := os.Getenv("MINT_CONTACT_INFO") - var mintContactInfo []nut06.ContactInfo - if len(contact) > 0 { - var infoArr [][]string - err := json.Unmarshal([]byte(contact), &infoArr) - if err != nil { - return nil, fmt.Errorf("error parsing contact info: %v", err) - } - - for _, info := range infoArr { - contactInfo := nut06.ContactInfo{Method: info[0], Info: info[1]} - mintContactInfo = append(mintContactInfo, contactInfo) - } - } - mintInfo.Contact = mintContactInfo - - nuts := nut06.NutsMap{ - 4: nut06.NutSetting{ - Methods: []nut06.MethodSetting{ - { - Method: "bolt11", - Unit: "sat", - MinAmount: m.Limits.MintingSettings.MinAmount, - MaxAmount: m.Limits.MintingSettings.MaxAmount, - }, - }, - Disabled: false, - }, - 5: nut06.NutSetting{ - Methods: []nut06.MethodSetting{ - { - Method: "bolt11", - Unit: "sat", - MinAmount: m.Limits.MeltingSettings.MinAmount, - MaxAmount: m.Limits.MeltingSettings.MaxAmount, - }, - }, - Disabled: false, - }, - 7: map[string]bool{"supported": false}, - 8: map[string]bool{"supported": false}, - 9: map[string]bool{"supported": false}, - 10: map[string]bool{"supported": false}, - 11: map[string]bool{"supported": false}, - 12: map[string]bool{"supported": false}, - } - - mintInfo.Nuts = nuts - return &mintInfo, nil -} diff --git a/mint/lightning/lightning.go b/mint/lightning/lightning.go index 97ba33c..392ad11 100644 --- a/mint/lightning/lightning.go +++ b/mint/lightning/lightning.go @@ -1,14 +1,5 @@ package lightning -import ( - "log" - "os" -) - -const ( - LND = "Lnd" -) - // Client interface to interact with a Lightning backend type Client interface { CreateInvoice(amount uint64) (Invoice, error) @@ -17,24 +8,6 @@ type Client interface { SendPayment(request string, amount uint64) (string, error) } -func NewLightningClient() Client { - backend := os.Getenv("LIGHTNING_BACKEND") - - switch backend { - case LND: - lndClient, err := CreateLndClient() - if err != nil { - log.Fatalf("error setting up lightning backend: %v", err) - } - return lndClient - - default: - log.Fatal("please specify a lightning backend") - } - - return nil -} - type Invoice struct { PaymentRequest string PaymentHash string diff --git a/mint/lightning/lnd.go b/mint/lightning/lnd.go index 5a827ea..a3d5627 100644 --- a/mint/lightning/lnd.go +++ b/mint/lightning/lnd.go @@ -6,20 +6,12 @@ import ( "errors" "fmt" "math" - "os" "time" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" "google.golang.org/grpc/credentials" - "gopkg.in/macaroon.v2" -) - -const ( - LND_GRPC_HOST = "LND_GRPC_HOST" - LND_CERT_PATH = "LND_CERT_PATH" - LND_MACAROON_PATH = "LND_MACAROON_PATH" ) const ( @@ -27,49 +19,23 @@ const ( FeePercent float64 = 0.01 ) +type LndConfig struct { + GRPCHost string + Cert credentials.TransportCredentials + Macaroon macaroons.MacaroonCredential +} + type LndClient struct { grpcClient lnrpc.LightningClient } -func CreateLndClient() (*LndClient, error) { - host := os.Getenv(LND_GRPC_HOST) - if host == "" { - return nil, errors.New(LND_GRPC_HOST + " cannot be empty") - } - certPath := os.Getenv(LND_CERT_PATH) - if certPath == "" { - return nil, errors.New(LND_CERT_PATH + " cannot be empty") - } - macaroonPath := os.Getenv(LND_MACAROON_PATH) - if macaroonPath == "" { - return nil, errors.New(LND_MACAROON_PATH + " cannot be empty") - } - - creds, err := credentials.NewClientTLSFromFile(certPath, "") - if err != nil { - return nil, err - } - - macaroonBytes, err := os.ReadFile(macaroonPath) - if err != nil { - return nil, fmt.Errorf("error reading macaroon: os.ReadFile %v", err) - } - - macaroon := &macaroon.Macaroon{} - if err = macaroon.UnmarshalBinary(macaroonBytes); err != nil { - return nil, fmt.Errorf("unable to decode macaroon: %v", err) - } - macarooncreds, err := macaroons.NewMacaroonCredential(macaroon) - if err != nil { - return nil, fmt.Errorf("error setting macaroon creds: %v", err) - } - +func SetupLndClient(config LndConfig) (*LndClient, error) { opts := []grpc.DialOption{ - grpc.WithTransportCredentials(creds), - grpc.WithPerRPCCredentials(macarooncreds), + grpc.WithTransportCredentials(config.Cert), + grpc.WithPerRPCCredentials(config.Macaroon), } - conn, err := grpc.NewClient(host, opts...) + conn, err := grpc.NewClient(config.GRPCHost, opts...) if err != nil { return nil, fmt.Errorf("error setting up grpc client: %v", err) } diff --git a/mint/mint.go b/mint/mint.go index 65e653b..9fdb788 100644 --- a/mint/mint.go +++ b/mint/mint.go @@ -35,18 +35,18 @@ type Mint struct { db storage.MintDB // active keysets - ActiveKeysets map[string]crypto.MintKeyset + activeKeysets map[string]crypto.MintKeyset // map of all keysets (both active and inactive) - Keysets map[string]crypto.MintKeyset + keysets map[string]crypto.MintKeyset - LightningClient lightning.Client - MintInfo *nut06.MintInfo - Limits MintLimits + lightningClient lightning.Client + mintInfo nut06.MintInfo + limits MintLimits } func LoadMint(config Config) (*Mint, error) { - path := config.DBPath + path := config.MintPath if len(path) == 0 { path = mintPath() } @@ -87,8 +87,8 @@ func LoadMint(config Config) (*Mint, error) { mint := &Mint{ db: db, - ActiveKeysets: map[string]crypto.MintKeyset{activeKeyset.Id: *activeKeyset}, - Limits: config.Limits, + activeKeysets: map[string]crypto.MintKeyset{activeKeyset.Id: *activeKeyset}, + limits: config.Limits, } dbKeysets, err := mint.db.GetKeysets() @@ -135,20 +135,23 @@ func LoadMint(config Config) (*Mint, error) { return nil, fmt.Errorf("error saving new active keyset: %v", err) } } + mint.keysets = mintKeysets + mint.keysets[activeKeyset.Id] = *activeKeyset + if config.LightningClient == nil { + return nil, errors.New("invalid lightning client") + } + mint.lightningClient = config.LightningClient - mint.Keysets = mintKeysets - mint.Keysets[activeKeyset.Id] = *activeKeyset - mint.LightningClient = lightning.NewLightningClient() - mint.MintInfo, err = mint.getMintInfo() + err = mint.SetMintInfo(config.MintInfo) if err != nil { - return nil, err + return nil, fmt.Errorf("error setting mint info: %v", err) } - for _, keyset := range mint.Keysets { + for _, keyset := range mint.keysets { if keyset.Id != activeKeyset.Id && keyset.Active { keyset.Active = false mint.db.UpdateKeysetActive(keyset.Id, false) - mint.Keysets[keyset.Id] = keyset + mint.keysets[keyset.Id] = keyset } } @@ -186,17 +189,17 @@ func (m *Mint) RequestMintQuote(method string, amount uint64, unit string) (stor } // check limits - if m.Limits.MintingSettings.MaxAmount > 0 { - if amount > m.Limits.MintingSettings.MaxAmount { + if m.limits.MintingSettings.MaxAmount > 0 { + if amount > m.limits.MintingSettings.MaxAmount { return storage.MintQuote{}, cashu.MintAmountExceededErr } } - if m.Limits.MaxBalance > 0 { + if m.limits.MaxBalance > 0 { balance, err := m.db.GetBalance() if err != nil { return storage.MintQuote{}, err } - if balance+amount > m.Limits.MaxBalance { + if balance+amount > m.limits.MaxBalance { return storage.MintQuote{}, cashu.MintingDisabled } } @@ -243,7 +246,7 @@ func (m *Mint) GetMintQuoteState(method, quoteId string) (storage.MintQuote, err } // check if the invoice has been paid - status, err := m.LightningClient.InvoiceStatus(mintQuote.PaymentHash) + status, err := m.lightningClient.InvoiceStatus(mintQuote.PaymentHash) if err != nil { msg := fmt.Sprintf("error getting status of payment request: %v", err) return storage.MintQuote{}, cashu.BuildCashuError(msg, cashu.LightningBackendErrCode) @@ -274,7 +277,7 @@ func (m *Mint) MintTokens(method, id string, blindedMessages cashu.BlindedMessag } var blindedSignatures cashu.BlindedSignatures - status, err := m.LightningClient.InvoiceStatus(mintQuote.PaymentHash) + status, err := m.lightningClient.InvoiceStatus(mintQuote.PaymentHash) if err != nil { msg := fmt.Sprintf("error getting status of payment request: %v", err) return nil, cashu.BuildCashuError(msg, cashu.LightningBackendErrCode) @@ -395,8 +398,8 @@ func (m *Mint) RequestMeltQuote(method, request, unit string) (storage.MeltQuote satAmount := uint64(bolt11.MSatoshi) / 1000 // check melt limit - if m.Limits.MeltingSettings.MaxAmount > 0 { - if satAmount > m.Limits.MeltingSettings.MaxAmount { + if m.limits.MeltingSettings.MaxAmount > 0 { + if satAmount > m.limits.MeltingSettings.MaxAmount { return storage.MeltQuote{}, cashu.MeltAmountExceededErr } } @@ -407,7 +410,7 @@ func (m *Mint) RequestMeltQuote(method, request, unit string) (storage.MeltQuote } // Fee reserve that is required by the mint - fee := m.LightningClient.FeeReserve(satAmount) + fee := m.lightningClient.FeeReserve(satAmount) expiry := uint64(time.Now().Add(time.Minute * QuoteExpiryMins).Unix()) meltQuote := storage.MeltQuote{ @@ -483,7 +486,7 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (storage. // if proofs are valid, ask the lightning backend // to make the payment - preimage, err := m.LightningClient.SendPayment(meltQuote.InvoiceRequest, meltQuote.Amount) + preimage, err := m.lightningClient.SendPayment(meltQuote.InvoiceRequest, meltQuote.Amount) if err != nil { return storage.MeltQuote{}, cashu.BuildCashuError(err.Error(), cashu.LightningBackendErrCode) } @@ -533,7 +536,7 @@ func (m *Mint) verifyProofs(proofs cashu.Proofs, Ys []string) error { // check that id in the proof matches id of any // of the mint's keyset var k *secp256k1.PrivateKey - if keyset, ok := m.Keysets[proof.Id]; !ok { + if keyset, ok := m.keysets[proof.Id]; !ok { return cashu.UnknownKeysetErr } else { if key, ok := keyset.Keys[proof.Amount]; ok { @@ -566,11 +569,11 @@ func (m *Mint) signBlindedMessages(blindedMessages cashu.BlindedMessages) (cashu blindedSignatures := make(cashu.BlindedSignatures, len(blindedMessages)) for i, msg := range blindedMessages { - if _, ok := m.Keysets[msg.Id]; !ok { + if _, ok := m.keysets[msg.Id]; !ok { return nil, cashu.UnknownKeysetErr } var k *secp256k1.PrivateKey - keyset, ok := m.ActiveKeysets[msg.Id] + keyset, ok := m.activeKeysets[msg.Id] if !ok { return nil, cashu.InactiveKeysetSignatureRequest } else { @@ -605,7 +608,7 @@ func (m *Mint) signBlindedMessages(blindedMessages cashu.BlindedMessages) (cashu // requestInvoices requests an invoice from the Lightning backend // for the given amount func (m *Mint) requestInvoice(amount uint64) (*lightning.Invoice, error) { - invoice, err := m.LightningClient.CreateInvoice(amount) + invoice, err := m.lightningClient.CreateInvoice(amount) if err != nil { return nil, err } @@ -617,7 +620,95 @@ func (m *Mint) TransactionFees(inputs cashu.Proofs) uint { for _, proof := range inputs { // note: not checking that proof id is from valid keyset // because already doing that in call to verifyProofs - fees += m.Keysets[proof.Id].InputFeePpk + fees += m.keysets[proof.Id].InputFeePpk } return (fees + 999) / 1000 } + +func (m *Mint) GetActiveKeyset() crypto.MintKeyset { + var keyset crypto.MintKeyset + for _, k := range m.activeKeysets { + keyset = k + break + } + return keyset +} + +func (m *Mint) SetMintInfo(mintInfo MintInfo) error { + nuts := nut06.NutsMap{ + 4: nut06.NutSetting{ + Methods: []nut06.MethodSetting{ + { + Method: BOLT11_METHOD, + Unit: SAT_UNIT, + MinAmount: m.limits.MintingSettings.MinAmount, + MaxAmount: m.limits.MintingSettings.MaxAmount, + }, + }, + Disabled: false, + }, + 5: nut06.NutSetting{ + Methods: []nut06.MethodSetting{ + { + Method: BOLT11_METHOD, + Unit: SAT_UNIT, + MinAmount: m.limits.MeltingSettings.MinAmount, + MaxAmount: m.limits.MeltingSettings.MaxAmount, + }, + }, + Disabled: false, + }, + 7: map[string]bool{"supported": false}, + 8: map[string]bool{"supported": false}, + 9: map[string]bool{"supported": false}, + 10: map[string]bool{"supported": false}, + 11: map[string]bool{"supported": false}, + 12: map[string]bool{"supported": false}, + } + + info := nut06.MintInfo{ + Name: mintInfo.Name, + Version: "gonuts/0.0.1", + Description: mintInfo.Description, + LongDescription: mintInfo.LongDescription, + Contact: mintInfo.Contact, + Motd: mintInfo.Motd, + Nuts: nuts, + } + m.mintInfo = info + return nil +} + +func (m *Mint) RetrieveMintInfo() (nut06.MintInfo, error) { + seed, err := m.db.GetSeed() + if err != nil { + return nut06.MintInfo{}, err + } + master, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) + if err != nil { + return nut06.MintInfo{}, err + } + publicKey, err := master.ECPubKey() + if err != nil { + return nut06.MintInfo{}, err + } + + mintingDisabled := false + mintBalance, err := m.db.GetBalance() + if err != nil { + msg := fmt.Sprintf("error getting mint balance: %v", err) + return nut06.MintInfo{}, cashu.BuildCashuError(msg, cashu.DBErrCode) + } + + if m.limits.MaxBalance > 0 { + if mintBalance >= m.limits.MaxBalance { + mintingDisabled = true + } + } + nut04 := m.mintInfo.Nuts[4].(nut06.NutSetting) + nut04.Disabled = mintingDisabled + m.mintInfo.Nuts[4] = nut04 + m.mintInfo.Pubkey = hex.EncodeToString(publicKey.SerializeCompressed()) + + return m.mintInfo, nil +} diff --git a/mint/mint_integration_test.go b/mint/mint_integration_test.go index e50c0ad..18b93b7 100644 --- a/mint/mint_integration_test.go +++ b/mint/mint_integration_test.go @@ -121,11 +121,7 @@ func TestMintQuoteState(t *testing.T) { t.Fatalf("error requesting mint quote: %v", err) } - var keyset crypto.MintKeyset - for _, k := range testMint.ActiveKeysets { - keyset = k - break - } + keyset := testMint.GetActiveKeyset() // test invalid method _, err = testMint.GetMintQuoteState("strike", mintQuoteResponse.Id) @@ -192,11 +188,7 @@ func TestMintTokens(t *testing.T) { t.Fatalf("error requesting mint quote: %v", err) } - var keyset crypto.MintKeyset - for _, k := range testMint.ActiveKeysets { - keyset = k - break - } + keyset := testMint.GetActiveKeyset() blindedMessages, _, _, err := testutils.CreateBlindedMessages(mintAmount, keyset) @@ -255,11 +247,7 @@ func TestSwap(t *testing.T) { t.Fatalf("error generating valid proofs: %v", err) } - var keyset crypto.MintKeyset - for _, k := range testMint.ActiveKeysets { - keyset = k - break - } + keyset := testMint.GetActiveKeyset() newBlindedMessages, _, _, err := testutils.CreateBlindedMessages(amount, keyset) overBlindedMessages, _, _, err := testutils.CreateBlindedMessages(amount+200, keyset) @@ -308,10 +296,7 @@ func TestSwap(t *testing.T) { t.Fatalf("error generating valid proofs: %v", err) } - for _, k := range mintFees.ActiveKeysets { - keyset = k - break - } + keyset = mintFees.GetActiveKeyset() fees := mintFees.TransactionFees(proofs) invalidAmtblindedMessages, _, _, err := testutils.CreateBlindedMessages(amount, keyset) @@ -573,11 +558,7 @@ func TestMintLimits(t *testing.T) { os.RemoveAll(limitsMintPath) }() - var keyset crypto.MintKeyset - for _, k := range limitsMint.ActiveKeysets { - keyset = k - break - } + keyset := limitsMint.GetActiveKeyset() // test above mint max amount var mintAmount uint64 = 20000 diff --git a/mint/server.go b/mint/server.go index ac6ba6f..892e5a9 100644 --- a/mint/server.go +++ b/mint/server.go @@ -154,7 +154,7 @@ func (ms *MintServer) writeErr(rw http.ResponseWriter, req *http.Request, errRes } func (ms *MintServer) getActiveKeysets(rw http.ResponseWriter, req *http.Request) { - getKeysResponse := buildKeysResponse(ms.mint.ActiveKeysets) + getKeysResponse := buildKeysResponse(ms.mint.activeKeysets) jsonRes, err := json.Marshal(getKeysResponse) if err != nil { ms.writeErr(rw, req, cashu.StandardErr) @@ -179,7 +179,7 @@ func (ms *MintServer) getKeysetById(rw http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) id := vars["id"] - ks, ok := ms.mint.Keysets[id] + ks, ok := ms.mint.keysets[id] if !ok { ms.writeErr(rw, req, cashu.UnknownKeysetErr) return @@ -469,7 +469,12 @@ func (ms *MintServer) meltTokens(rw http.ResponseWriter, req *http.Request) { } func (ms *MintServer) mintInfo(rw http.ResponseWriter, req *http.Request) { - jsonRes, err := json.Marshal(ms.mint.MintInfo) + mintInfo, err := ms.mint.RetrieveMintInfo() + if err != nil { + ms.writeErr(rw, req, cashu.StandardErr) + return + } + jsonRes, err := json.Marshal(mintInfo) if err != nil { ms.writeErr(rw, req, cashu.StandardErr) return @@ -492,7 +497,7 @@ func buildKeysResponse(keysets map[string]crypto.MintKeyset) nut01.GetKeysRespon func (ms *MintServer) buildAllKeysetsResponse() nut02.GetKeysetsResponse { keysetsResponse := nut02.GetKeysetsResponse{} - for _, keyset := range ms.mint.Keysets { + for _, keyset := range ms.mint.keysets { keysetRes := nut02.Keyset{ Id: keyset.Id, Unit: keyset.Unit, diff --git a/testutils/utils.go b/testutils/utils.go index 4056ec5..5a6b931 100644 --- a/testutils/utils.go +++ b/testutils/utils.go @@ -17,10 +17,14 @@ import ( "github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/crypto" "github.com/elnosh/gonuts/mint" + "github.com/elnosh/gonuts/mint/lightning" "github.com/elnosh/gonuts/wallet" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/macaroons" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" + "google.golang.org/grpc/credentials" + "gopkg.in/macaroon.v2" ) const ( @@ -187,14 +191,6 @@ func mintConfig( if err := os.MkdirAll(dbpath, 0750); err != nil { return nil, err } - mintConfig := &mint.Config{ - DerivationPathIdx: 0, - Port: port, - DBPath: dbpath, - DBMigrationPath: dbMigrationPath, - InputFeePpk: inputFeePpk, - Limits: limits, - } nodeDir := lnd.LndDir macaroonPath := filepath.Join(dbpath, "/admin.macaroon") @@ -208,10 +204,43 @@ func mintConfig( return nil, fmt.Errorf("error writing to macaroon file: %v", err) } - os.Setenv("LIGHTNING_BACKEND", "Lnd") - os.Setenv("LND_GRPC_HOST", lnd.Host+":"+lnd.GrpcPort) - os.Setenv("LND_CERT_PATH", filepath.Join(nodeDir, "/tls.cert")) - os.Setenv("LND_MACAROON_PATH", macaroonPath) + creds, err := credentials.NewClientTLSFromFile(filepath.Join(nodeDir, "/tls.cert"), "") + if err != nil { + return nil, err + } + + macaroonBytes, err := os.ReadFile(macaroonPath) + if err != nil { + return nil, fmt.Errorf("error reading macaroon: os.ReadFile %v", err) + } + + macaroon := &macaroon.Macaroon{} + if err = macaroon.UnmarshalBinary(macaroonBytes); err != nil { + return nil, fmt.Errorf("unable to decode macaroon: %v", err) + } + macarooncreds, err := macaroons.NewMacaroonCredential(macaroon) + if err != nil { + return nil, fmt.Errorf("error setting macaroon creds: %v", err) + } + lndConfig := lightning.LndConfig{ + GRPCHost: lnd.Host + ":" + lnd.GrpcPort, + Cert: creds, + Macaroon: macarooncreds, + } + lndClient, err := lightning.SetupLndClient(lndConfig) + if err != nil { + return nil, fmt.Errorf("error setting LND client: %v", err) + } + + mintConfig := &mint.Config{ + DerivationPathIdx: 0, + Port: port, + MintPath: dbpath, + DBMigrationPath: dbMigrationPath, + InputFeePpk: inputFeePpk, + Limits: limits, + LightningClient: lndClient, + } return mintConfig, nil } @@ -341,11 +370,7 @@ func GetValidProofsForAmount(amount uint64, mint *mint.Mint, payer *btcdocker.Ln return nil, fmt.Errorf("error requesting mint quote: %v", err) } - var keyset crypto.MintKeyset - for _, k := range mint.ActiveKeysets { - keyset = k - break - } + keyset := mint.GetActiveKeyset() blindedMessages, secrets, rs, err := CreateBlindedMessages(amount, keyset) if err != nil {