From 6807ebade77835b6005bc9014fee1d3368f8d72a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20A=2EP?=
<53834183+Jossec101@users.noreply.github.com>
Date: Wed, 11 Jun 2025 16:34:03 +0200
Subject: [PATCH 01/16] Add migration for legacy withdrawal data and update
model snapshot
- Implemented migration to transfer existing data from WalletWithdrawalRequests to WalletWithdrawalRequestDestinations.
- Added verification step to ensure data integrity during migration.
---
...43_MigrateLegacyWithdrawalData.Designer.cs | 1342 +++++++++++++++++
...50611142943_MigrateLegacyWithdrawalData.cs | 84 ++
.../ApplicationDbContextModelSnapshot.cs | 36 +-
3 files changed, 1444 insertions(+), 18 deletions(-)
create mode 100644 src/Migrations/20250611142943_MigrateLegacyWithdrawalData.Designer.cs
create mode 100644 src/Migrations/20250611142943_MigrateLegacyWithdrawalData.cs
diff --git a/src/Migrations/20250611142943_MigrateLegacyWithdrawalData.Designer.cs b/src/Migrations/20250611142943_MigrateLegacyWithdrawalData.Designer.cs
new file mode 100644
index 00000000..6851f9f3
--- /dev/null
+++ b/src/Migrations/20250611142943_MigrateLegacyWithdrawalData.Designer.cs
@@ -0,0 +1,1342 @@
+//
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NodeGuard.Data;
+using NodeGuard.Helpers;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace NodeGuard.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20250611142943_MigrateLegacyWithdrawalData")]
+ partial class MigrateLegacyWithdrawalData
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.6")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("ApplicationUserNode", b =>
+ {
+ b.Property("NodesId")
+ .HasColumnType("integer");
+
+ b.Property("UsersId")
+ .HasColumnType("text");
+
+ b.HasKey("NodesId", "UsersId");
+
+ b.HasIndex("UsersId");
+
+ b.ToTable("ApplicationUserNode");
+ });
+
+ modelBuilder.Entity("ChannelOperationRequestFMUTXO", b =>
+ {
+ b.Property("ChannelOperationRequestsId")
+ .HasColumnType("integer");
+
+ b.Property("UtxosId")
+ .HasColumnType("integer");
+
+ b.HasKey("ChannelOperationRequestsId", "UtxosId");
+
+ b.HasIndex("UtxosId");
+
+ b.ToTable("ChannelOperationRequestFMUTXO");
+ });
+
+ modelBuilder.Entity("FMUTXOWalletWithdrawalRequest", b =>
+ {
+ b.Property("UTXOsId")
+ .HasColumnType("integer");
+
+ b.Property("WalletWithdrawalRequestsId")
+ .HasColumnType("integer");
+
+ b.HasKey("UTXOsId", "WalletWithdrawalRequestsId");
+
+ b.HasIndex("WalletWithdrawalRequestsId");
+
+ b.ToTable("FMUTXOWalletWithdrawalRequest");
+ });
+
+ modelBuilder.Entity("KeyWallet", b =>
+ {
+ b.Property("KeysId")
+ .HasColumnType("integer");
+
+ b.Property("WalletsId")
+ .HasColumnType("integer");
+
+ b.HasKey("KeysId", "WalletsId");
+
+ b.HasIndex("WalletsId");
+
+ b.ToTable("KeyWallet");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("text");
+
+ b.Property("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("integer");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property("Discriminator")
+ .IsRequired()
+ .HasMaxLength(21)
+ .HasColumnType("character varying(21)");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("text");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("text");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("text");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+
+ b.HasDiscriminator("Discriminator").HasValue("IdentityUser");
+
+ b.UseTphMappingStrategy();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("text");
+
+ b.Property("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("ProviderKey")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("RoleId")
+ .HasColumnType("text");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("LoginProvider")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Name")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.APIToken", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatorId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ExpirationDate")
+ .HasColumnType("timestamp without time zone");
+
+ b.Property("IsBlocked")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TokenHash")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatorId");
+
+ b.ToTable("ApiTokens");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Channel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("BtcCloseAddress")
+ .HasColumnType("text");
+
+ b.Property("ChanId")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("CreatedByNodeGuard")
+ .HasColumnType("boolean");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DestinationNodeId")
+ .HasColumnType("integer");
+
+ b.Property("FundingTx")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("FundingTxOutputIndex")
+ .HasColumnType("bigint");
+
+ b.Property("IsAutomatedLiquidityEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("IsPrivate")
+ .HasColumnType("boolean");
+
+ b.Property("SatsAmount")
+ .HasColumnType("bigint");
+
+ b.Property("SourceNodeId")
+ .HasColumnType("integer");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DestinationNodeId");
+
+ b.HasIndex("SourceNodeId");
+
+ b.ToTable("Channels");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.ChannelOperationRequest", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AmountCryptoUnit")
+ .HasColumnType("integer");
+
+ b.Property("Changeless")
+ .HasColumnType("boolean");
+
+ b.Property("ChannelId")
+ .HasColumnType("integer");
+
+ b.Property("ClosingReason")
+ .HasColumnType("text");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("DestNodeId")
+ .HasColumnType("integer");
+
+ b.Property("FeeRate")
+ .HasColumnType("numeric");
+
+ b.Property("IsChannelPrivate")
+ .HasColumnType("boolean");
+
+ b.Property("MempoolRecommendedFeesType")
+ .HasColumnType("integer");
+
+ b.Property("RequestType")
+ .HasColumnType("integer");
+
+ b.Property("SatsAmount")
+ .HasColumnType("bigint");
+
+ b.Property("SourceNodeId")
+ .HasColumnType("integer");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.Property>("StatusLogs")
+ .HasColumnType("jsonb");
+
+ b.Property("TxId")
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("WalletId")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChannelId");
+
+ b.HasIndex("DestNodeId");
+
+ b.HasIndex("SourceNodeId");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("WalletId");
+
+ b.ToTable("ChannelOperationRequests");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.ChannelOperationRequestPSBT", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ChannelOperationRequestId")
+ .HasColumnType("integer");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsFinalisedPSBT")
+ .HasColumnType("boolean");
+
+ b.Property("IsInternalWalletPSBT")
+ .HasColumnType("boolean");
+
+ b.Property("IsTemplatePSBT")
+ .HasColumnType("boolean");
+
+ b.Property("PSBT")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserSignerId")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChannelOperationRequestId");
+
+ b.HasIndex("UserSignerId");
+
+ b.ToTable("ChannelOperationRequestPSBTs");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.FMUTXO", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("OutputIndex")
+ .HasColumnType("bigint");
+
+ b.Property("SatsAmount")
+ .HasColumnType("bigint");
+
+ b.Property("TxId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.ToTable("FMUTXOs");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.InternalWallet", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DerivationPath")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("MasterFingerprint")
+ .HasColumnType("text");
+
+ b.Property("MnemonicString")
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("XPUB")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("InternalWallets");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Key", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("InternalWalletId")
+ .HasColumnType("integer");
+
+ b.Property("IsArchived")
+ .HasColumnType("boolean");
+
+ b.Property("IsBIP39ImportedKey")
+ .HasColumnType("boolean");
+
+ b.Property("IsCompromised")
+ .HasColumnType("boolean");
+
+ b.Property("MasterFingerprint")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Path")
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("XPUB")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InternalWalletId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Keys");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.LiquidityRule", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ChannelId")
+ .HasColumnType("integer");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsReverseSwapWalletRule")
+ .HasColumnType("boolean");
+
+ b.Property("MinimumLocalBalance")
+ .HasColumnType("numeric");
+
+ b.Property("MinimumRemoteBalance")
+ .HasColumnType("numeric");
+
+ b.Property("NodeId")
+ .HasColumnType("integer");
+
+ b.Property("RebalanceTarget")
+ .HasColumnType("numeric");
+
+ b.Property("ReverseSwapAddress")
+ .HasColumnType("text");
+
+ b.Property("ReverseSwapWalletId")
+ .HasColumnType("integer");
+
+ b.Property("SwapWalletId")
+ .HasColumnType("integer");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChannelId")
+ .IsUnique();
+
+ b.HasIndex("NodeId");
+
+ b.HasIndex("ReverseSwapWalletId");
+
+ b.HasIndex("SwapWalletId");
+
+ b.ToTable("LiquidityRules");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Node", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AutosweepEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("ChannelAdminMacaroon")
+ .HasColumnType("text");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Endpoint")
+ .HasColumnType("text");
+
+ b.Property("IsNodeDisabled")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("PubKey")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ReturningFundsWalletId")
+ .HasColumnType("integer");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PubKey")
+ .IsUnique();
+
+ b.HasIndex("ReturningFundsWalletId");
+
+ b.ToTable("Nodes");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.UTXOTag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Outpoint")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Key", "Outpoint")
+ .IsUnique();
+
+ b.ToTable("UTXOTags");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Wallet", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("BIP39Seedphrase")
+ .HasColumnType("text");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("ImportedOutputDescriptor")
+ .HasColumnType("text");
+
+ b.Property("InternalWalletId")
+ .HasColumnType("integer");
+
+ b.Property("InternalWalletMasterFingerprint")
+ .HasColumnType("text");
+
+ b.Property("InternalWalletSubDerivationPath")
+ .HasColumnType("text");
+
+ b.Property("IsArchived")
+ .HasColumnType("boolean");
+
+ b.Property("IsBIP39Imported")
+ .HasColumnType("boolean");
+
+ b.Property("IsCompromised")
+ .HasColumnType("boolean");
+
+ b.Property("IsFinalised")
+ .HasColumnType("boolean");
+
+ b.Property("IsHotWallet")
+ .HasColumnType("boolean");
+
+ b.Property("IsUnSortedMultiSig")
+ .HasColumnType("boolean");
+
+ b.Property("MofN")
+ .HasColumnType("integer");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ReferenceId")
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("WalletAddressType")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InternalWalletId");
+
+ b.HasIndex("InternalWalletSubDerivationPath", "InternalWalletMasterFingerprint")
+ .IsUnique();
+
+ b.ToTable("Wallets");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.WalletWithdrawalRequest", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Amount")
+ .HasColumnType("numeric");
+
+ b.Property("Changeless")
+ .HasColumnType("boolean");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CustomFeeRate")
+ .HasColumnType("numeric");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("DestinationAddress")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("MempoolRecommendedFeesType")
+ .HasColumnType("integer");
+
+ b.Property("ReferenceId")
+ .HasColumnType("text");
+
+ b.Property("RejectCancelDescription")
+ .HasColumnType("text");
+
+ b.Property("RequestMetadata")
+ .HasColumnType("text");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.Property("TxId")
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserRequestorId")
+ .HasColumnType("text");
+
+ b.Property("WalletId")
+ .HasColumnType("integer");
+
+ b.Property("WithdrawAllFunds")
+ .HasColumnType("boolean");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserRequestorId");
+
+ b.HasIndex("WalletId");
+
+ b.ToTable("WalletWithdrawalRequests");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.WalletWithdrawalRequestDestination", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Address")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Amount")
+ .HasColumnType("numeric");
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("WalletWithdrawalRequestId")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("WalletWithdrawalRequestId");
+
+ b.ToTable("WalletWithdrawalRequestDestinations");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.WalletWithdrawalRequestPSBT", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreationDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsFinalisedPSBT")
+ .HasColumnType("boolean");
+
+ b.Property("IsInternalWalletPSBT")
+ .HasColumnType("boolean");
+
+ b.Property("IsTemplatePSBT")
+ .HasColumnType("boolean");
+
+ b.Property("PSBT")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("SignerId")
+ .HasColumnType("text");
+
+ b.Property("UpdateDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("WalletWithdrawalRequestId")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SignerId");
+
+ b.HasIndex("WalletWithdrawalRequestId");
+
+ b.ToTable("WalletWithdrawalRequestPSBTs");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.ApplicationUser", b =>
+ {
+ b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser");
+
+ b.HasDiscriminator().HasValue("ApplicationUser");
+ });
+
+ modelBuilder.Entity("ApplicationUserNode", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.Node", null)
+ .WithMany()
+ .HasForeignKey("NodesId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("NodeGuard.Data.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UsersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("ChannelOperationRequestFMUTXO", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.ChannelOperationRequest", null)
+ .WithMany()
+ .HasForeignKey("ChannelOperationRequestsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("NodeGuard.Data.Models.FMUTXO", null)
+ .WithMany()
+ .HasForeignKey("UtxosId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("FMUTXOWalletWithdrawalRequest", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.FMUTXO", null)
+ .WithMany()
+ .HasForeignKey("UTXOsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("NodeGuard.Data.Models.WalletWithdrawalRequest", null)
+ .WithMany()
+ .HasForeignKey("WalletWithdrawalRequestsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("KeyWallet", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.Key", null)
+ .WithMany()
+ .HasForeignKey("KeysId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("NodeGuard.Data.Models.Wallet", null)
+ .WithMany()
+ .HasForeignKey("WalletsId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.APIToken", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.ApplicationUser", "Creator")
+ .WithMany()
+ .HasForeignKey("CreatorId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Creator");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Channel", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.Node", "DestinationNode")
+ .WithMany()
+ .HasForeignKey("DestinationNodeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("NodeGuard.Data.Models.Node", "SourceNode")
+ .WithMany()
+ .HasForeignKey("SourceNodeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("DestinationNode");
+
+ b.Navigation("SourceNode");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.ChannelOperationRequest", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.Channel", "Channel")
+ .WithMany("ChannelOperationRequests")
+ .HasForeignKey("ChannelId");
+
+ b.HasOne("NodeGuard.Data.Models.Node", "DestNode")
+ .WithMany("ChannelOperationRequestsAsDestination")
+ .HasForeignKey("DestNodeId");
+
+ b.HasOne("NodeGuard.Data.Models.Node", "SourceNode")
+ .WithMany("ChannelOperationRequestsAsSource")
+ .HasForeignKey("SourceNodeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("NodeGuard.Data.Models.ApplicationUser", "User")
+ .WithMany("ChannelOperationRequests")
+ .HasForeignKey("UserId");
+
+ b.HasOne("NodeGuard.Data.Models.Wallet", "Wallet")
+ .WithMany("ChannelOperationRequestsAsSource")
+ .HasForeignKey("WalletId");
+
+ b.Navigation("Channel");
+
+ b.Navigation("DestNode");
+
+ b.Navigation("SourceNode");
+
+ b.Navigation("User");
+
+ b.Navigation("Wallet");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.ChannelOperationRequestPSBT", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.ChannelOperationRequest", "ChannelOperationRequest")
+ .WithMany("ChannelOperationRequestPsbts")
+ .HasForeignKey("ChannelOperationRequestId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("NodeGuard.Data.Models.ApplicationUser", "UserSigner")
+ .WithMany()
+ .HasForeignKey("UserSignerId");
+
+ b.Navigation("ChannelOperationRequest");
+
+ b.Navigation("UserSigner");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Key", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.InternalWallet", "InternalWallet")
+ .WithMany()
+ .HasForeignKey("InternalWalletId");
+
+ b.HasOne("NodeGuard.Data.Models.ApplicationUser", "User")
+ .WithMany("Keys")
+ .HasForeignKey("UserId");
+
+ b.Navigation("InternalWallet");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.LiquidityRule", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.Channel", "Channel")
+ .WithMany("LiquidityRules")
+ .HasForeignKey("ChannelId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("NodeGuard.Data.Models.Node", "Node")
+ .WithMany()
+ .HasForeignKey("NodeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("NodeGuard.Data.Models.Wallet", "ReverseSwapWallet")
+ .WithMany("LiquidityRulesAsReverseSwapWallet")
+ .HasForeignKey("ReverseSwapWalletId");
+
+ b.HasOne("NodeGuard.Data.Models.Wallet", "SwapWallet")
+ .WithMany("LiquidityRulesAsSwapWallet")
+ .HasForeignKey("SwapWalletId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Channel");
+
+ b.Navigation("Node");
+
+ b.Navigation("ReverseSwapWallet");
+
+ b.Navigation("SwapWallet");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Node", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.Wallet", "ReturningFundsWallet")
+ .WithMany()
+ .HasForeignKey("ReturningFundsWalletId");
+
+ b.Navigation("ReturningFundsWallet");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Wallet", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.InternalWallet", "InternalWallet")
+ .WithMany()
+ .HasForeignKey("InternalWalletId");
+
+ b.Navigation("InternalWallet");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.WalletWithdrawalRequest", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.ApplicationUser", "UserRequestor")
+ .WithMany("WalletWithdrawalRequests")
+ .HasForeignKey("UserRequestorId");
+
+ b.HasOne("NodeGuard.Data.Models.Wallet", "Wallet")
+ .WithMany()
+ .HasForeignKey("WalletId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("UserRequestor");
+
+ b.Navigation("Wallet");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.WalletWithdrawalRequestDestination", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.WalletWithdrawalRequest", "WalletWithdrawalRequest")
+ .WithMany("WalletWithdrawalRequestDestinations")
+ .HasForeignKey("WalletWithdrawalRequestId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("WalletWithdrawalRequest");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.WalletWithdrawalRequestPSBT", b =>
+ {
+ b.HasOne("NodeGuard.Data.Models.ApplicationUser", "Signer")
+ .WithMany()
+ .HasForeignKey("SignerId");
+
+ b.HasOne("NodeGuard.Data.Models.WalletWithdrawalRequest", "WalletWithdrawalRequest")
+ .WithMany("WalletWithdrawalRequestPSBTs")
+ .HasForeignKey("WalletWithdrawalRequestId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Signer");
+
+ b.Navigation("WalletWithdrawalRequest");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Channel", b =>
+ {
+ b.Navigation("ChannelOperationRequests");
+
+ b.Navigation("LiquidityRules");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.ChannelOperationRequest", b =>
+ {
+ b.Navigation("ChannelOperationRequestPsbts");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Node", b =>
+ {
+ b.Navigation("ChannelOperationRequestsAsDestination");
+
+ b.Navigation("ChannelOperationRequestsAsSource");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.Wallet", b =>
+ {
+ b.Navigation("ChannelOperationRequestsAsSource");
+
+ b.Navigation("LiquidityRulesAsReverseSwapWallet");
+
+ b.Navigation("LiquidityRulesAsSwapWallet");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.WalletWithdrawalRequest", b =>
+ {
+ b.Navigation("WalletWithdrawalRequestDestinations");
+
+ b.Navigation("WalletWithdrawalRequestPSBTs");
+ });
+
+ modelBuilder.Entity("NodeGuard.Data.Models.ApplicationUser", b =>
+ {
+ b.Navigation("ChannelOperationRequests");
+
+ b.Navigation("Keys");
+
+ b.Navigation("WalletWithdrawalRequests");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Migrations/20250611142943_MigrateLegacyWithdrawalData.cs b/src/Migrations/20250611142943_MigrateLegacyWithdrawalData.cs
new file mode 100644
index 00000000..b31497f9
--- /dev/null
+++ b/src/Migrations/20250611142943_MigrateLegacyWithdrawalData.cs
@@ -0,0 +1,84 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace NodeGuard.Migrations
+{
+ ///
+ public partial class MigrateLegacyWithdrawalData : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ // Execute data migration in an explicit transaction
+ migrationBuilder.Sql(@"
+ BEGIN;
+
+ -- Step 1: Migrate existing data from Amount and DestinationAddress to WalletWithdrawalRequestDestinations
+ INSERT INTO ""WalletWithdrawalRequestDestinations"" (""Amount"", ""Address"", ""WalletWithdrawalRequestId"", ""CreationDatetime"", ""UpdateDatetime"")
+ SELECT
+ ""Amount"",
+ ""DestinationAddress"",
+ ""Id"",
+ ""CreationDatetime"",
+ ""UpdateDatetime""
+ FROM ""WalletWithdrawalRequests""
+ WHERE ""Amount"" > 0 AND ""DestinationAddress"" IS NOT NULL AND ""DestinationAddress"" != '';
+
+ -- Step 2: Verify migration was successful - if any records failed to migrate, rollback
+ DO $$
+ DECLARE
+ original_count INTEGER;
+ migrated_count INTEGER;
+ BEGIN
+ -- Count original records that should have been migrated
+ SELECT COUNT(*) INTO original_count
+ FROM ""WalletWithdrawalRequests""
+ WHERE ""Amount"" > 0 AND ""DestinationAddress"" IS NOT NULL AND ""DestinationAddress"" != '';
+
+ -- Count migrated records that were just inserted
+ SELECT COUNT(*) INTO migrated_count
+ FROM ""WalletWithdrawalRequestDestinations"" dest
+ INNER JOIN ""WalletWithdrawalRequests"" req ON dest.""WalletWithdrawalRequestId"" = req.""Id""
+ WHERE req.""Amount"" > 0 AND req.""DestinationAddress"" IS NOT NULL AND req.""DestinationAddress"" != '';
+
+ -- If counts don't match, raise an exception to rollback the transaction
+ IF original_count != migrated_count THEN
+ RAISE EXCEPTION 'Data migration failed: Expected % records, but migrated % records. Transaction will be rolled back.', original_count, migrated_count;
+ END IF;
+
+ -- Log success
+ RAISE NOTICE 'Successfully migrated % legacy withdrawal records to destinations table', original_count;
+ END $$;
+
+ COMMIT;
+ ");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ // Rollback: Delete the migrated destinations in an explicit transaction
+ migrationBuilder.Sql(@"
+ BEGIN;
+
+ -- Delete only the migrated destinations (those that match legacy withdrawal requests)
+ DELETE FROM ""WalletWithdrawalRequestDestinations""
+ WHERE ""WalletWithdrawalRequestId"" IN (
+ SELECT ""Id"" FROM ""WalletWithdrawalRequests""
+ WHERE ""Amount"" > 0 AND ""DestinationAddress"" IS NOT NULL AND ""DestinationAddress"" != ''
+ )
+ AND ""Address"" IN (
+ SELECT ""DestinationAddress"" FROM ""WalletWithdrawalRequests""
+ WHERE ""Amount"" > 0 AND ""DestinationAddress"" IS NOT NULL AND ""DestinationAddress"" != ''
+ )
+ AND ""Amount"" IN (
+ SELECT ""Amount"" FROM ""WalletWithdrawalRequests""
+ WHERE ""Amount"" > 0 AND ""DestinationAddress"" IS NOT NULL AND ""DestinationAddress"" != ''
+ );
+
+ COMMIT;
+ ");
+ }
+ }
+}
diff --git a/src/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Migrations/ApplicationDbContextModelSnapshot.cs
index 6c50fee8..c2628e18 100644
--- a/src/Migrations/ApplicationDbContextModelSnapshot.cs
+++ b/src/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -36,7 +36,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("UsersId");
- b.ToTable("ApplicationUserNode");
+ b.ToTable("ApplicationUserNode", (string)null);
});
modelBuilder.Entity("ChannelOperationRequestFMUTXO", b =>
@@ -51,7 +51,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("UtxosId");
- b.ToTable("ChannelOperationRequestFMUTXO");
+ b.ToTable("ChannelOperationRequestFMUTXO", (string)null);
});
modelBuilder.Entity("FMUTXOWalletWithdrawalRequest", b =>
@@ -66,7 +66,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("WalletWithdrawalRequestsId");
- b.ToTable("FMUTXOWalletWithdrawalRequest");
+ b.ToTable("FMUTXOWalletWithdrawalRequest", (string)null);
});
modelBuilder.Entity("KeyWallet", b =>
@@ -81,7 +81,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("WalletsId");
- b.ToTable("KeyWallet");
+ b.ToTable("KeyWallet", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
@@ -329,7 +329,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("CreatorId");
- b.ToTable("ApiTokens");
+ b.ToTable("ApiTokens", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.Channel", b =>
@@ -386,7 +386,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("SourceNodeId");
- b.ToTable("Channels");
+ b.ToTable("Channels", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.ChannelOperationRequest", b =>
@@ -466,7 +466,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("WalletId");
- b.ToTable("ChannelOperationRequests");
+ b.ToTable("ChannelOperationRequests", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.ChannelOperationRequestPSBT", b =>
@@ -508,7 +508,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("UserSignerId");
- b.ToTable("ChannelOperationRequestPSBTs");
+ b.ToTable("ChannelOperationRequestPSBTs", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.FMUTXO", b =>
@@ -537,7 +537,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
- b.ToTable("FMUTXOs");
+ b.ToTable("FMUTXOs", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.InternalWallet", b =>
@@ -569,7 +569,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
- b.ToTable("InternalWallets");
+ b.ToTable("InternalWallets", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.Key", b =>
@@ -624,7 +624,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("UserId");
- b.ToTable("Keys");
+ b.ToTable("Keys", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.LiquidityRule", b =>
@@ -679,7 +679,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("SwapWalletId");
- b.ToTable("LiquidityRules");
+ b.ToTable("LiquidityRules", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.Node", b =>
@@ -729,7 +729,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("ReturningFundsWalletId");
- b.ToTable("Nodes");
+ b.ToTable("Nodes", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.UTXOTag", b =>
@@ -763,7 +763,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("Key", "Outpoint")
.IsUnique();
- b.ToTable("UTXOTags");
+ b.ToTable("UTXOTags", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.Wallet", b =>
@@ -836,7 +836,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("InternalWalletSubDerivationPath", "InternalWalletMasterFingerprint")
.IsUnique();
- b.ToTable("Wallets");
+ b.ToTable("Wallets", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.WalletWithdrawalRequest", b =>
@@ -903,7 +903,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("WalletId");
- b.ToTable("WalletWithdrawalRequests");
+ b.ToTable("WalletWithdrawalRequests", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.WalletWithdrawalRequestDestination", b =>
@@ -934,7 +934,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("WalletWithdrawalRequestId");
- b.ToTable("WalletWithdrawalRequestDestinations");
+ b.ToTable("WalletWithdrawalRequestDestinations", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.WalletWithdrawalRequestPSBT", b =>
@@ -976,7 +976,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("WalletWithdrawalRequestId");
- b.ToTable("WalletWithdrawalRequestPSBTs");
+ b.ToTable("WalletWithdrawalRequestPSBTs", (string)null);
});
modelBuilder.Entity("NodeGuard.Data.Models.ApplicationUser", b =>
From 105a44f10a37a4390128c909973cc7648aba2938 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20A=2EP?=
<53834183+Jossec101@users.noreply.github.com>
Date: Wed, 11 Jun 2025 16:45:36 +0200
Subject: [PATCH 02/16] Error handling in database migration and clean up
whitespace in DbInitializer
---
src/Data/DbInitializer.cs | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/src/Data/DbInitializer.cs b/src/Data/DbInitializer.cs
index f12816d5..1255e595 100644
--- a/src/Data/DbInitializer.cs
+++ b/src/Data/DbInitializer.cs
@@ -74,12 +74,13 @@ public static void Initialize(IServiceProvider serviceProvider)
{
applicationDbContext.Database.Migrate();
dataprotectionKeysContext.Database.Migrate();
-
+
isConnected = true;
}
catch (Exception e)
{
logger.LogError(e, "Error while migrating");
+ throw new Exception("Error while migrating the database", e);
}
Thread.Sleep(1_000);
@@ -327,7 +328,7 @@ public static void Initialize(IServiceProvider serviceProvider)
var lastEventId = evts.LastEventId;
logger?.LogInformation(lastEventId.ToString());
-
+
minerRPC.SendToAddress(legacyMultisigAddress, legacyMultisigFundCoins);
minerRPC.SendToAddress(multisigAddress, multisigFundCoins);
minerRPC.SendToAddress(singlesigAddress, singlesigFundCoins);
@@ -335,7 +336,7 @@ public static void Initialize(IServiceProvider serviceProvider)
//6 blocks to confirm
minerRPC.Generate(6);
-
+
var notification1 = WaitNbxplorerNotification(evts, legacyMultisigDerivationStrategy, lastEventId);
var notification2 = WaitNbxplorerNotification(evts, multisigDerivationStrategy, lastEventId);
var notification3 = WaitNbxplorerNotification(evts, singlesigDerivationStrategy, lastEventId);
@@ -345,7 +346,7 @@ public static void Initialize(IServiceProvider serviceProvider)
{
throw new Exception("Wallets are not initialized");
}
-
+
var legacyMultisigBalance = nbxplorerClient.GetBalance(legacyMultisigDerivationStrategy);
var multisigBalance = nbxplorerClient.GetBalance(multisigDerivationStrategy);
var singleSigbalance = nbxplorerClient.GetBalance(singlesigDerivationStrategy);
@@ -376,7 +377,7 @@ public static void Initialize(IServiceProvider serviceProvider)
applicationDbContext.Add(testingSinglesigWallet);
applicationDbContext.Add(testingSingleSigBIP39Wallet);
}
-
+
// API Tokens generation for services
var authenticatedServices = new Dictionary
{
@@ -385,10 +386,10 @@ public static void Initialize(IServiceProvider serviceProvider)
{ "Liquidator", "8rvSsUGeyXXdDQrHctcTey/xtHdZQEn945KHwccKp9Q=" },
{ "ElenPay", "2otDr3IrdARnarQZU8RO0ImQko6CDICyLmGUflKUQWA=" }
};
-
+
var existingTokens = applicationDbContext.ApiTokens.Where(token => authenticatedServices.Keys.Contains(token.Name)).ToList();
-
+
if (existingTokens.Count != authenticatedServices.Count && adminUser != null)
{
foreach (var service in authenticatedServices)
@@ -462,7 +463,7 @@ private static APIToken CreateApiToken(string name, string token, string userId)
IsBlocked = false,
CreatorId = userId
};
-
+
apiToken.SetCreationDatetime();
apiToken.SetUpdateDatetime();
From 6dd9ccdb1ac632b1e4dedf426e0bb6d1be8cdc0f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20A=2EP?=
<53834183+Jossec101@users.noreply.github.com>
Date: Wed, 11 Jun 2025 17:49:27 +0200
Subject: [PATCH 03/16] Remove obsolete properties from WalletWithdrawalRequest
model and update migration files
---
NodeGuard.code-workspace | 11 +
src/Data/Models/WalletWithdrawalRequest.cs | 28 +-
...veObsoleteWithdrawalProperties.Designer.cs | 1335 +++++++++++++++++
...4856_RemoveObsoleteWithdrawalProperties.cs | 40 +
4 files changed, 1391 insertions(+), 23 deletions(-)
create mode 100644 NodeGuard.code-workspace
create mode 100644 src/Migrations/20250611154856_RemoveObsoleteWithdrawalProperties.Designer.cs
create mode 100644 src/Migrations/20250611154856_RemoveObsoleteWithdrawalProperties.cs
diff --git a/NodeGuard.code-workspace b/NodeGuard.code-workspace
new file mode 100644
index 00000000..30d086ea
--- /dev/null
+++ b/NodeGuard.code-workspace
@@ -0,0 +1,11 @@
+{
+ "folders": [
+ {
+ "path": "."
+ },
+ {
+ "path": "src"
+ }
+ ],
+ "settings": {}
+}
\ No newline at end of file
diff --git a/src/Data/Models/WalletWithdrawalRequest.cs b/src/Data/Models/WalletWithdrawalRequest.cs
index 0a3cdd76..c56b124a 100644
--- a/src/Data/Models/WalletWithdrawalRequest.cs
+++ b/src/Data/Models/WalletWithdrawalRequest.cs
@@ -73,12 +73,6 @@ public class WalletWithdrawalRequest : Entity, IEquatable
- /// base58 address of the output of the request, its mutually exclusive with the WalletWithdrawalRequestDestinations
- ///
- public string DestinationAddress { get; set; }
-
///
/// Description by the requestor
///
@@ -91,28 +85,12 @@ public class WalletWithdrawalRequest : Entity, IEquatable
- /// TX amount in BTC, its mutually exclusive with the WalletWithdrawalRequestDestinations
- ///
- ///
- public decimal Amount { get; set; }
-
///
/// Checks if all the threshold signatures are collected, including the internal wallet key (even if not signed yet)
///
[NotMapped]
public bool AreAllRequiredHumanSignaturesCollected => CheckSignatures();
- [NotMapped]
- ///
- /// This indicates if the request is a legacy request, meaning that it has no destinations and only a single address and amount
- ///
-#pragma warning disable CS0618 // Type or member is obsolete
- public bool IsLegacy => Amount > 0 && string.IsNullOrEmpty(DestinationAddress) &&
- (WalletWithdrawalRequestDestinations == null || WalletWithdrawalRequestDestinations?.Count == 0);
-#pragma warning restore CS0618 // Type or member is obsolete
-
[NotMapped]
public int NumberOfSignaturesCollected =>
WalletWithdrawalRequestPSBTs == null
@@ -193,7 +171,11 @@ public bool Equals(WalletWithdrawalRequest? other)
}
[NotMapped]
- public long SatsAmount => new Money(Amount, MoneyUnit.BTC).Satoshi;
+ public decimal Amount =>
+ WalletWithdrawalRequestDestinations?.Sum(x => x.Amount) ?? 0m;
+
+ [NotMapped]
+ public long SatsAmount => new Money(WalletWithdrawalRequestDestinations?.Sum(x => x.Amount) ?? 0m, MoneyUnit.BTC).Satoshi;
#region Relationships
diff --git a/src/Migrations/20250611154856_RemoveObsoleteWithdrawalProperties.Designer.cs b/src/Migrations/20250611154856_RemoveObsoleteWithdrawalProperties.Designer.cs
new file mode 100644
index 00000000..ddd734a2
--- /dev/null
+++ b/src/Migrations/20250611154856_RemoveObsoleteWithdrawalProperties.Designer.cs
@@ -0,0 +1,1335 @@
+//
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NodeGuard.Data;
+using NodeGuard.Helpers;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace NodeGuard.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20250611154856_RemoveObsoleteWithdrawalProperties")]
+ partial class RemoveObsoleteWithdrawalProperties
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.6")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("ApplicationUserNode", b =>
+ {
+ b.Property("NodesId")
+ .HasColumnType("integer");
+
+ b.Property("UsersId")
+ .HasColumnType("text");
+
+ b.HasKey("NodesId", "UsersId");
+
+ b.HasIndex("UsersId");
+
+ b.ToTable("ApplicationUserNode");
+ });
+
+ modelBuilder.Entity("ChannelOperationRequestFMUTXO", b =>
+ {
+ b.Property("ChannelOperationRequestsId")
+ .HasColumnType("integer");
+
+ b.Property("UtxosId")
+ .HasColumnType("integer");
+
+ b.HasKey("ChannelOperationRequestsId", "UtxosId");
+
+ b.HasIndex("UtxosId");
+
+ b.ToTable("ChannelOperationRequestFMUTXO");
+ });
+
+ modelBuilder.Entity("FMUTXOWalletWithdrawalRequest", b =>
+ {
+ b.Property("UTXOsId")
+ .HasColumnType("integer");
+
+ b.Property("WalletWithdrawalRequestsId")
+ .HasColumnType("integer");
+
+ b.HasKey("UTXOsId", "WalletWithdrawalRequestsId");
+
+ b.HasIndex("WalletWithdrawalRequestsId");
+
+ b.ToTable("FMUTXOWalletWithdrawalRequest");
+ });
+
+ modelBuilder.Entity("KeyWallet", b =>
+ {
+ b.Property("KeysId")
+ .HasColumnType("integer");
+
+ b.Property("WalletsId")
+ .HasColumnType("integer");
+
+ b.HasKey("KeysId", "WalletsId");
+
+ b.HasIndex("WalletsId");
+
+ b.ToTable("KeyWallet");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("text");
+
+ b.Property("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("integer");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property