btcpayserver/BTCPayServer.Tests/PSBTTests.cs
d11n d5d0be5824
Code formatting updates (#4502)
* Editorconfig: Add space_before_self_closing setting

This was a difference between the way dotnet-format and Rider format code. See https://www.jetbrains.com/help/rider/EditorConfig_Index.html

* Editorconfig: Keep 4 spaces indentation for Swagger JSON files

They are all formatted that way, let's keep it like that.

* Apply dotnet-format, mostly white-space related changes
2023-01-06 22:18:07 +09:00

134 lines
5.8 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Models;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitpayClient;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.Extensions;
using Xunit;
using Xunit.Abstractions;
namespace BTCPayServer.Tests
{
public class PSBTTests : UnitTestBase
{
public PSBTTests(ITestOutputHelper helper) : base(helper)
{
}
[Fact]
[Trait("Selenium", "Selenium")]
public async Task CanPlayWithPSBT()
{
using var s = CreateSeleniumTester(newDb: true);
await s.StartAsync();
var u1 = s.RegisterNewUser(true);
var hot = s.CreateNewStore();
var seed = s.GenerateWallet(isHotWallet: true);
var cold = s.CreateNewStore();
s.GenerateWallet(isHotWallet: false, seed: seed.ToString());
// Scenario 1: one user has two stores sharing same seed
// one store is hot wallet, the other not.
// 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();
var psbt = ExtractPSBT(s);
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();
// 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);
// 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;
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();
// 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();
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();
}
private static string ExtractPSBT(SeleniumTester s)
{
var pageSource = s.Driver.PageSource;
var start = pageSource.IndexOf("id=\"psbt-base64\">");
start += "id=\"psbt-base64\">".Length;
var end = pageSource.IndexOf("<", start);
return pageSource[start..end];
}
}
}