2024-08-28 11:52:08 +02:00
|
|
|
using System;
|
2019-05-12 07:51:24 +02:00
|
|
|
using System.Collections.Generic;
|
2020-06-28 10:55:27 +02:00
|
|
|
using System.Linq;
|
2022-02-17 09:58:56 +01:00
|
|
|
using System.Threading;
|
2019-05-12 07:51:24 +02:00
|
|
|
using System.Threading.Tasks;
|
|
|
|
using BTCPayServer.Controllers;
|
2020-06-28 10:55:27 +02:00
|
|
|
using BTCPayServer.Models;
|
2019-05-12 07:51:24 +02:00
|
|
|
using BTCPayServer.Models.WalletViewModels;
|
|
|
|
using BTCPayServer.Tests.Logging;
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
using NBitcoin;
|
|
|
|
using NBitpayClient;
|
2022-02-17 09:58:56 +01:00
|
|
|
using OpenQA.Selenium;
|
|
|
|
using OpenQA.Selenium.Support.Extensions;
|
2019-05-12 07:51:24 +02:00
|
|
|
using Xunit;
|
|
|
|
using Xunit.Abstractions;
|
|
|
|
|
|
|
|
namespace BTCPayServer.Tests
|
|
|
|
{
|
2021-11-22 09:16:08 +01:00
|
|
|
public class PSBTTests : UnitTestBase
|
2019-05-12 07:51:24 +02:00
|
|
|
{
|
2021-11-22 09:16:08 +01:00
|
|
|
public PSBTTests(ITestOutputHelper helper) : base(helper)
|
2019-05-12 07:51:24 +02:00
|
|
|
{
|
|
|
|
}
|
2023-01-06 14:18:07 +01:00
|
|
|
|
2019-05-12 07:51:24 +02:00
|
|
|
[Fact]
|
2022-02-18 02:55:54 +01:00
|
|
|
[Trait("Selenium", "Selenium")]
|
2019-05-12 07:51:24 +02:00
|
|
|
public async Task CanPlayWithPSBT()
|
|
|
|
{
|
2022-02-17 09:58:56 +01:00
|
|
|
using var s = CreateSeleniumTester(newDb: true);
|
|
|
|
await s.StartAsync();
|
2019-05-12 07:51:24 +02:00
|
|
|
|
2024-01-18 01:47:39 +01:00
|
|
|
s.RegisterNewUser(true);
|
2022-02-17 09:58:56 +01:00
|
|
|
var hot = s.CreateNewStore();
|
|
|
|
var seed = s.GenerateWallet(isHotWallet: true);
|
|
|
|
var cold = s.CreateNewStore();
|
|
|
|
s.GenerateWallet(isHotWallet: false, seed: seed.ToString());
|
2019-05-12 07:51:24 +02:00
|
|
|
|
2022-02-17 09:58:56 +01:00
|
|
|
// Scenario 1: one user has two stores sharing same seed
|
|
|
|
// one store is hot wallet, the other not.
|
2019-05-12 07:51:24 +02:00
|
|
|
|
2022-02-17 09:58:56 +01:00
|
|
|
// Here, the cold wallet create a PSBT, then we switch to hot wallet to sign
|
|
|
|
// the PSBT and broadcast
|
|
|
|
s.GoToStore(cold.storeId);
|
|
|
|
var address = await s.FundStoreWallet();
|
|
|
|
Thread.Sleep(1000);
|
|
|
|
s.GoToWallet(navPages: Views.Wallets.WalletsNavPages.Send);
|
|
|
|
SendAllTo(s, address);
|
|
|
|
s.Driver.FindElement(By.Id("SignWithPSBT")).Click();
|
2019-05-12 07:51:24 +02:00
|
|
|
|
2022-02-17 09:58:56 +01:00
|
|
|
var psbt = ExtractPSBT(s);
|
2019-05-12 07:51:24 +02:00
|
|
|
|
2022-02-17 09:58:56 +01:00
|
|
|
s.GoToStore(hot.storeId);
|
|
|
|
s.GoToWallet(navPages: Views.Wallets.WalletsNavPages.PSBT);
|
|
|
|
s.Driver.FindElement(By.Name("PSBT")).SendKeys(psbt);
|
|
|
|
s.Driver.FindElement(By.Id("Decode")).Click();
|
|
|
|
s.Driver.FindElement(By.Id("SignTransaction")).Click();
|
|
|
|
s.Driver.FindElement(By.Id("BroadcastTransaction")).Click();
|
|
|
|
s.FindAlertMessage();
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2022-02-17 09:58:56 +01:00
|
|
|
// Scenario 2: Same as scenario 1, except we create a PSBT from hot wallet, then sign by manually
|
|
|
|
// entering the seed on the cold wallet.
|
|
|
|
s.GoToWallet(navPages: Views.Wallets.WalletsNavPages.Send);
|
|
|
|
SendAllTo(s, address);
|
|
|
|
psbt = ExtractPSBT(s);
|
2019-05-12 07:51:24 +02:00
|
|
|
|
2022-02-17 09:58:56 +01:00
|
|
|
// Let's check it has been signed, then remove the signature.
|
|
|
|
// Also remove the hdkeys so we can test the update later
|
|
|
|
var psbtParsed = PSBT.Parse(psbt, s.Server.NetworkProvider.BTC.NBitcoinNetwork);
|
|
|
|
var signedPSBT = psbtParsed.Clone();
|
|
|
|
Assert.True(psbtParsed.Clone().TryFinalize(out _));
|
|
|
|
Assert.Single(psbtParsed.Inputs[0].PartialSigs);
|
|
|
|
psbtParsed.Inputs[0].PartialSigs.Clear();
|
|
|
|
Assert.Single(psbtParsed.Inputs[0].HDKeyPaths);
|
|
|
|
psbtParsed.Inputs[0].HDKeyPaths.Clear();
|
|
|
|
var skeletonPSBT = psbtParsed;
|
2019-05-12 07:51:24 +02:00
|
|
|
|
2022-02-17 09:58:56 +01:00
|
|
|
s.GoToStore(cold.storeId);
|
|
|
|
s.GoToWallet(navPages: Views.Wallets.WalletsNavPages.PSBT);
|
|
|
|
s.Driver.FindElement(By.Name("PSBT")).SendKeys(skeletonPSBT.ToBase64());
|
|
|
|
s.Driver.FindElement(By.Id("Decode")).Click();
|
|
|
|
s.Driver.FindElement(By.Id("SignTransaction")).Click();
|
|
|
|
s.Driver.FindElement(By.Id("SignWithSeed")).Click();
|
|
|
|
s.Driver.FindElement(By.Name("SeedOrKey")).SendKeys(seed.ToString());
|
|
|
|
s.Driver.FindElement(By.Id("Submit")).Click();
|
|
|
|
s.Driver.FindElement(By.Id("BroadcastTransaction")).Click();
|
|
|
|
s.FindAlertMessage();
|
2019-05-12 07:51:24 +02:00
|
|
|
|
2022-02-17 09:58:56 +01:00
|
|
|
// Let's check if the update feature works
|
|
|
|
s.GoToWallet(navPages: Views.Wallets.WalletsNavPages.PSBT);
|
|
|
|
s.Driver.FindElement(By.Name("PSBT")).SendKeys(skeletonPSBT.ToBase64());
|
|
|
|
s.Driver.FindElement(By.Id("Decode")).Click();
|
|
|
|
s.Driver.FindElement(By.Id("PSBTOptionsAdvancedHeader")).Click();
|
|
|
|
s.Driver.WaitForElement(By.Id("update-psbt")).Click();
|
2020-07-14 09:52:17 +02:00
|
|
|
|
2022-02-17 09:58:56 +01:00
|
|
|
psbt = ExtractPSBT(s);
|
|
|
|
psbtParsed = PSBT.Parse(psbt, s.Server.NetworkProvider.BTC.NBitcoinNetwork);
|
|
|
|
Assert.Single(psbtParsed.Inputs[0].HDKeyPaths);
|
|
|
|
Assert.Empty(psbtParsed.Inputs[0].PartialSigs);
|
|
|
|
|
|
|
|
// Let's if we can combine the updated psbt (which has hdkeys, but no sig)
|
|
|
|
// with the signed psbt (which has sig, but no hdkeys)
|
|
|
|
s.GoToWallet(navPages: Views.Wallets.WalletsNavPages.PSBT);
|
|
|
|
s.Driver.FindElement(By.Name("PSBT")).SendKeys(psbtParsed.ToBase64());
|
|
|
|
s.Driver.FindElement(By.Id("Decode")).Click();
|
|
|
|
s.Driver.FindElement(By.Id("PSBTOptionsAdvancedHeader")).Click();
|
|
|
|
s.Driver.WaitForElement(By.Id("combine-psbt")).Click();
|
|
|
|
signedPSBT.Inputs[0].HDKeyPaths.Clear();
|
|
|
|
s.Driver.FindElement(By.Name("PSBT")).SendKeys(signedPSBT.ToBase64());
|
|
|
|
s.Driver.WaitForElement(By.Id("Submit")).Click();
|
|
|
|
|
|
|
|
psbt = ExtractPSBT(s);
|
|
|
|
psbtParsed = PSBT.Parse(psbt, s.Server.NetworkProvider.BTC.NBitcoinNetwork);
|
|
|
|
Assert.Single(psbtParsed.Inputs[0].HDKeyPaths);
|
|
|
|
Assert.Single(psbtParsed.Inputs[0].PartialSigs);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void SendAllTo(SeleniumTester s, string address)
|
|
|
|
{
|
|
|
|
s.Driver.FindElement(By.Name("Outputs[0].DestinationAddress")).SendKeys(address);
|
|
|
|
s.Driver.FindElement(By.ClassName("crypto-balance-link")).Click();
|
|
|
|
s.Driver.FindElement(By.Id("SignTransaction")).Click();
|
2019-05-12 07:51:24 +02:00
|
|
|
}
|
2019-11-08 13:42:34 +01:00
|
|
|
|
2024-08-28 11:52:08 +02:00
|
|
|
private string ExtractPSBT(SeleniumTester s) => s.Driver.FindElement(By.Id("psbt-base64")).GetAttribute("innerText");
|
2019-05-12 07:51:24 +02:00
|
|
|
}
|
|
|
|
}
|