From edb5b03d55038a836ffe4cd72ae538ce87b63ba6 Mon Sep 17 00:00:00 2001 From: elnosh Date: Fri, 21 Jun 2024 12:53:54 -0500 Subject: [PATCH] test wallet restore --- cmd/nutw/nutw.go | 13 +++--- testutils/utils.go | 52 +++++++++++++++++++++ wallet/wallet.go | 16 +++---- wallet/wallet_integration_test.go | 75 +++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 14 deletions(-) diff --git a/cmd/nutw/nutw.go b/cmd/nutw/nutw.go index 6325b34..7899e2d 100644 --- a/cmd/nutw/nutw.go +++ b/cmd/nutw/nutw.go @@ -334,12 +334,15 @@ var restoreCmd = &cli.Command{ } func restore(ctx *cli.Context) error { - args := ctx.Args() - if args.Len() < 1 { - printErr(errors.New("specify mnemonic")) - } - mnemonic := args.First() config := walletConfig() + fmt.Printf("enter mnemonic: ") + + reader := bufio.NewReader(os.Stdin) + mnemonic, err := reader.ReadString('\n') + if err != nil { + log.Fatal("error reading input, please try again") + } + mnemonic = mnemonic[:len(mnemonic)-1] proofs, err := wallet.Restore(config.WalletPath, mnemonic, []string{config.CurrentMintURL}) if err != nil { diff --git a/testutils/utils.go b/testutils/utils.go index eb3a55d..7b7f41b 100644 --- a/testutils/utils.go +++ b/testutils/utils.go @@ -19,6 +19,8 @@ import ( "github.com/elnosh/gonuts/mint" "github.com/elnosh/gonuts/wallet" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" ) const ( @@ -361,3 +363,53 @@ func GetValidProofsForAmount(amount uint64, mint *mint.Mint, payer *btcdocker.Ln return proofs, nil } + +type NutshellMintContainer struct { + testcontainers.Container + Host string +} + +func CreateNutshellMintContainer(ctx context.Context) (*NutshellMintContainer, error) { + req := testcontainers.ContainerRequest{ + Image: "cashubtc/nutshell:0.15.3", + ExposedPorts: []string{"3338"}, + Cmd: []string{ + "poetry", + "run", + "mint", + }, + Env: map[string]string{ + "MINT_LISTEN_HOST": "0.0.0.0", + "MINT_LISTEN_PORT": "3338", + "MINT_BACKEND_BOLT11_SAT": "FakeWallet", + "MINT_PRIVATE_KEY": "secretkey", + }, + WaitingFor: wait.ForListeningPort("3338"), + } + + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + if err != nil { + return nil, err + } + + ip, err := container.Host(ctx) + if err != nil { + return nil, err + } + + mappedPort, err := container.MappedPort(ctx, "3338") + if err != nil { + return nil, err + } + + nutshellHost := "http://" + ip + ":" + mappedPort.Port() + nutshellContainer := &NutshellMintContainer{ + Container: container, + Host: nutshellHost, + } + + return nutshellContainer, nil +} diff --git a/wallet/wallet.go b/wallet/wallet.go index 17390e7..f5023a1 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -955,17 +955,17 @@ func Restore(walletPath, mnemonic string, mintsToRestore []string) (cashu.Proofs return nil, errors.New("wallet already exists") } + // check mnemonic is valid + if !bip39.IsMnemonicValid(mnemonic) { + return nil, errors.New("invalid mnemonic") + } + // create wallet db db, err := InitStorage(walletPath) if err != nil { return nil, fmt.Errorf("error restoring wallet: %v", err) } - // check mnemonic is valid - if !bip39.IsMnemonicValid(mnemonic) { - return nil, errors.New("invalid mnemonic") - } - seed := bip39.NewSeed(mnemonic, "") // get master key from seed masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) @@ -1035,12 +1035,11 @@ func Restore(walletPath, mnemonic string, mintsToRestore []string) (cashu.Proofs // stop when it reaches 3 consecutive empty batches emptyBatches := 0 for emptyBatches < 3 { - blindedMessages := make(cashu.BlindedMessages, 100) rs := make([]*secp256k1.PrivateKey, 100) secrets := make([]string, 100) - // loop and create batch of 100 blinded messages here + // create batch of 100 blinded messages for i := 0; i < 100; i++ { B_, secret, r, err := blindMessage(keysetDerivationPath, counter) if err != nil { @@ -1054,7 +1053,7 @@ func Restore(walletPath, mnemonic string, mintsToRestore []string) (cashu.Proofs counter++ } - // call signature restore endpoint. if response has signatures, unblind them and check proof states + // if response has signatures, unblind them and check proof states restoreRequest := nut09.PostRestoreRequest{Outputs: blindedMessages} restoreResponse, err := PostRestore(mint, restoreRequest) if err != nil { @@ -1123,7 +1122,6 @@ func Restore(walletPath, mnemonic string, mintsToRestore []string) (cashu.Proofs } emptyBatches = 0 } - } } diff --git a/wallet/wallet_integration_test.go b/wallet/wallet_integration_test.go index 44f0533..2614ee3 100644 --- a/wallet/wallet_integration_test.go +++ b/wallet/wallet_integration_test.go @@ -376,3 +376,78 @@ func TestWalletBalance(t *testing.T) { t.Fatalf("expected balance of '%v' but got '%v' instead", balanceBeforeMelt, balanceTestWallet.GetBalance()) } } + +func TestWalletRestore(t *testing.T) { + nutshellMint, err := testutils.CreateNutshellMintContainer(ctx) + if err != nil { + t.Fatalf("error starting nutshell mint: %v", err) + } + defer nutshellMint.Terminate(ctx) + + mintURL := nutshellMint.Host + + testWalletPath := filepath.Join(".", "/testrestorewallet") + testWallet, err := testutils.CreateTestWallet(testWalletPath, mintURL) + if err != nil { + t.Fatal(err) + } + + testWalletPath2 := filepath.Join(".", "/testrestorewallet2") + testWallet2, err := testutils.CreateTestWallet(testWalletPath2, mintURL) + if err != nil { + t.Fatal(err) + } + defer func() { + os.RemoveAll(testWalletPath2) + }() + + var mintAmount uint64 = 20000 + mintRequest, err := testWallet.RequestMint(mintAmount) + if err != nil { + t.Fatalf("unexpected error in mint request: %v", err) + } + _, err = testWallet.MintTokens(mintRequest.Quote) + if err != nil { + t.Fatalf("unexpected error in mint tokens: %v", err) + } + + var sendAmount1 uint64 = 5000 + token, err := testWallet.Send(sendAmount1, mintURL) + if err != nil { + t.Fatalf("unexpected error in send: %v", err) + } + + _, err = testWallet2.Receive(*token, false) + if err != nil { + t.Fatalf("got unexpected error in receive: %v", err) + } + + var sendAmount2 uint64 = 1000 + token, err = testWallet.Send(sendAmount2, mintURL) + if err != nil { + t.Fatalf("unexpected error in send: %v", err) + } + + _, err = testWallet2.Receive(*token, false) + if err != nil { + t.Fatalf("got unexpected error in receive: %v", err) + } + + mnemonic := testWallet.Mnemonic() + + // delete wallet db to restore + os.RemoveAll(filepath.Join(testWalletPath, "wallet.db")) + + proofs, err := wallet.Restore(testWalletPath, mnemonic, []string{mintURL}) + if err != nil { + t.Fatalf("error restoring wallet: %v\n", err) + } + + expectedAmount := mintAmount - sendAmount1 - sendAmount2 + if proofs.Amount() != expectedAmount { + t.Fatalf("restored proofs amount '%v' does not match to expected amount '%v'", proofs.Amount(), expectedAmount) + } + defer func() { + os.RemoveAll(testWalletPath) + }() +}