Skip to content

Commit 55b840a

Browse files
markettesAnon2148Rubenro01scrpti
authored
[GEN-1658] RBF for withdrawals in NG (#418)
* fix: connection problems, healthcheck postgres * feat: added ui and minimal functionality to display bumpfee pop up (not working though) * feat: added actions column in withdrawal requests datagrid and functionality to display bumpfee button (not sure if it works) * fix: changed bumpefee modal rendering condition, although not tested * wip * feat: added fee rate check, user cannot bumpfee with a lower new fee rate than the current one * wip: submit confirmation modal fails when trying to bumpfee * feat: enhance fee rate display in withdrawal requests datagrid to clarify custom fee and value usage * wip: bumpfee works partially, although the previous transaction is still active, had to be updated to a new state such as bumped or similar * feat: add Parent Tx column and metadata handling in withdrawal requests * fix: update Docker Compose to use host.docker.internal for BTC RPC URL and endpoint * wip: adding utxos to bumped transaction, not working yet, problem with the database * feat: add handling for changeless transactions in bumpfee process to prevent fee increases * wip: modified how withdrawals register bumped withdrawals, already not working, I am trying to get the Utxos from the database but i cannot update them correctly * wip: keep working on updating the UTXOs, the request could be updated but the UTXOs does not update with the new WithrawalRequest because an exception of pk_keywallet unique constraint arises * feat: added bumping object to WalletWithdrawalRequest and added utxos functionality, also set optInRBF in tx builder when generating template PSBT * feat: add display of last fee used and its type in bumpfee modal * fix: improve error message for insufficient fee in bumpfee modal * feat: enhance bump fee functionality with error handling for changeless transactions and improved fee rate selection * feat: added bumped state and modified logic when bumping transaction to update state correctly * refactor: specify function for wds while also including the wallet wd req in the utxo * remove unused variable for changeless amount in BitcoinService * feat: changeless transactions bump fee method is available * chore: add mempool space to docker compose * fix: bad handling of the vars * fix: fixing migrations * fix: when a bumping tx fails update the other one to onchainconfpending again and dont allow bump fee if fee+amount is more than utxo * fix: remove unused imports * fix: if formats and remove todo * feat: dont return dust * fix: return after error * fix: exception * fix: nit * fix: remove dbtrie * fix: remove dupl * test: set rbf flag * fix: error on failed broadcasting * feat: add mempool profile to docker * fix: old migration * refactor: change from sats vb to sat vb * fix: casing * fix: naming * fix: remove async * feat: make constant Money * feat: add help line to indicate % of fee and amount of fees * fix: refactor variable name * fix: spacing * feat: add max of 200 * fix: bug on multisig --------- Co-authored-by: Anon2148 <[email protected]> Co-authored-by: Rubenro01 <[email protected]> Co-authored-by: scrpti <[email protected]>
1 parent 8280765 commit 55b840a

21 files changed

+2079
-72
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
},
2222
"env": {
2323
"ASPNETCORE_ENVIRONMENT": "Development",
24-
"POSTGRES_CONNECTIONSTRING": "Host=127.0.0.1;Port=5432;Database=nodeguard;Username=rw_dev;Password=rw_dev",
24+
"POSTGRES_CONNECTIONSTRING": "Host=127.0.0.1;Port=25432;Database=nodeguard;Username=rw_dev;Password=rw_dev",
2525
"BITCOIN_NETWORK": "REGTEST",
2626
"MAXIMUM_WITHDRAWAL_BTC_AMOUNT": "21000000",
2727
"NBXPLORER_ENABLE_CUSTOM_BACKEND": "true",

docker/docker-compose.yml

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: '3.4'
1+
version: "3.4"
22

33
name: nodeguard
44
services:
@@ -22,7 +22,9 @@ services:
2222
platform: linux/amd64
2323
hostname: nbxplorer
2424
ports:
25-
- "32838:32838"
25+
- "32838:32838"
26+
depends_on:
27+
- nbxplorer_postgres
2628
environment:
2729
NBXPLORER_NETWORK: regtest
2830
NBXPLORER_BIND: 0.0.0.0:32838
@@ -36,7 +38,7 @@ services:
3638
NBXPLORER_BTCNODEENDPOINT: host.docker.internal:19444
3739
command: ["--noauth"]
3840
volumes:
39-
- "bitcoin_datadir:/root/.bitcoin"
41+
- "bitcoin_datadir:/root/.bitcoin"
4042

4143
nbxplorer_postgres:
4244
container_name: nbxplorer_postgres
@@ -51,12 +53,99 @@ services:
5153
- nbxplorer_postgres_data:/var/lib/postgresql/data
5254
ports:
5355
- 35432:5432
56+
healthcheck:
57+
test: ["CMD", "pg_isready", "-U", "rw_dev"]
58+
interval: 10s
59+
timeout: 5s
60+
retries: 5
5461

62+
mempool-frontend-btc:
63+
profiles: ["mempool"]
64+
environment:
65+
FRONTEND_HTTP_PORT: "8080"
66+
BACKEND_MAINNET_HTTP_HOST: "mempool-backend-btc"
67+
LIQUID_ENABLED: false
68+
LIQUID_TESTNET_ENABLED: false
69+
image: mempool/frontend:latest
70+
container_name: 40swap_mempool_frontend_btc
71+
user: "1000:1000"
72+
restart: always
73+
command: "./wait-for mempool-db-btc:3306 --timeout=720 -- nginx -g 'daemon off;'"
74+
ports:
75+
- 7084:8080
76+
77+
mempool-backend-btc:
78+
profiles: ["mempool"]
79+
environment:
80+
MEMPOOL_BACKEND: "electrum"
81+
CORE_RPC_HOST: "host.docker.internal"
82+
CORE_RPC_PORT: "18443"
83+
CORE_RPC_USERNAME: "polaruser"
84+
CORE_RPC_PASSWORD: "polarpass"
85+
DATABASE_ENABLED: "true"
86+
DATABASE_HOST: "mempool-db-btc"
87+
DATABASE_DATABASE: "mempool_btc"
88+
DATABASE_USERNAME: "mempool"
89+
DATABASE_PASSWORD: "mempool"
90+
STATISTICS_ENABLED: "true"
91+
ELECTRUM_HOST: "electrumx"
92+
ELECTRUM_PORT: "50001"
93+
ELECTRUM_TLS_ENABLED: "false"
94+
image: mempool/backend:latest
95+
container_name: 40swap_mempool_backend_btc
96+
user: "1000:1000"
97+
restart: always
98+
command: "./wait-for-it.sh mempool-db-btc:3306 --timeout=720 --strict -- ./start.sh"
99+
depends_on:
100+
- mempool-db-btc
101+
volumes:
102+
- mempool-backend-btc-data:/backend/cache
55103

104+
mempool-db-btc:
105+
profiles: ["mempool"]
106+
environment:
107+
MYSQL_DATABASE: "mempool_btc"
108+
MYSQL_USER: "mempool"
109+
MYSQL_PASSWORD: "mempool"
110+
MYSQL_ROOT_PASSWORD: "admin"
111+
image: mariadb:10.5.8
112+
container_name: 40swap_mempool_db_btc
113+
restart: always
114+
volumes:
115+
- mempool-db-btc-data:/var/lib/mysql
116+
117+
electrumx:
118+
profiles: ["mempool"]
119+
image: andgohq/electrumx:1.8.7
120+
container_name: 40swap_electrumx
121+
command: ["wait-for-it.sh", "host.docker.internal:18443", "--", "init"]
122+
ports:
123+
- "51002:50002"
124+
- "51001:50001"
125+
expose:
126+
- "50001"
127+
- "50002"
128+
volumes:
129+
- electrumx-data:/data
130+
environment:
131+
# bitcoind is valid
132+
- DAEMON_URL=http://polaruser:[email protected]:18443
133+
- COIN=BitcoinSegwit
134+
- NET=regtest
135+
# 127.0.0.1 or electrumx is valid for RPC_HOST
136+
- RPC_HOST=electrumx
137+
- RPC_PORT=18443
138+
- HOST=electrumx
139+
- TCP_PORT=50001
140+
- SSL_PORT=50002
141+
restart: always
56142

57143
volumes:
58-
nodeguard_postgres_data:
59-
bitcoin_datadir:
60-
nbxplorer_datadir:
61-
nbxplorer_postgres_data:
62-
nodeguard_data_keys_dir:
144+
nodeguard_postgres_data:
145+
bitcoin_datadir:
146+
nbxplorer_datadir:
147+
nbxplorer_postgres_data:
148+
nodeguard_data_keys_dir:
149+
mempool-backend-btc-data:
150+
mempool-db-btc-data:
151+
electrumx-data:

src/Data/Models/WalletWithdrawalRequest.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ public enum WalletWithdrawalRequestStatus
6363
/// <summary>
6464
/// The PSBT is being signed by NodeGuard after all human required signatures have been collected
6565
/// </summary>
66-
FinalizingPSBT = 7
66+
FinalizingPSBT = 7,
67+
68+
/// <summary>
69+
/// The tx was bumped by Replace by Fee (RBF) method
70+
/// </summary>
71+
Bumped = 8
6772
}
6873

6974
/// <summary>
@@ -189,6 +194,10 @@ public bool Equals(WalletWithdrawalRequest? other)
189194

190195
public Wallet Wallet { get; set; }
191196

197+
public int? BumpingWalletWithdrawalRequestId { get; set; }
198+
199+
public WalletWithdrawalRequest? BumpingWalletWithdrawalRequest { get; set; }
200+
192201
public List<WalletWithdrawalRequestPSBT> WalletWithdrawalRequestPSBTs { get; set; }
193202

194203
public List<FMUTXO> UTXOs { get; set; }

src/Data/Repositories/FUTXORepository.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,21 @@ public async Task<List<FMUTXO>> GetAll()
9191
return _repository.Update(type, applicationDbContext);
9292
}
9393

94+
public async Task<List<FMUTXO>> GetLockedUTXOsByWithdrawalId(int walletWithdrawalRequestId)
95+
{
96+
await using var applicationDbContext = await _dbContextFactory.CreateDbContextAsync();
97+
98+
var result = new List<FMUTXO>();
99+
result = await applicationDbContext.WalletWithdrawalRequests
100+
.Include(x => x.UTXOs)
101+
.Where(x => x.Id == walletWithdrawalRequestId)
102+
.SelectMany(x => x.UTXOs)
103+
.Include(x => x.WalletWithdrawalRequests)
104+
.ToListAsync();
105+
106+
return result;
107+
}
108+
94109
public async Task<List<FMUTXO>> GetLockedUTXOs(int? ignoredWalletWithdrawalRequestId = null, int? ignoredChannelOperationRequestId = null)
95110
{
96111
await using var applicationDbContext = await _dbContextFactory.CreateDbContextAsync();

src/Data/Repositories/Interfaces/IFMUTXORepository.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@ public interface IFMUTXORepository
4343
/// <returns></returns>
4444
Task<List<FMUTXO>> GetLockedUTXOs(int? ignoredWalletWithdrawalRequestId = null, int? ignoredChannelOperationRequestId = null);
4545
Task<List<FMUTXO>> GetLockedUTXOsByWalletId(int walletId);
46+
/// <summary>
47+
/// Gets the current list of UTXOs locked on WalletWithdrawalRequest by passing its id
48+
/// </summary>
49+
/// <returns></returns>
50+
Task<List<FMUTXO>> GetLockedUTXOsByWithdrawalId(int walletWithdrawalRequestId);
4651
}

src/Data/Repositories/WalletWithdrawalRequestRepository.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ INBXplorerService nBXplorerService
6565
.Include(x => x.UserRequestor)
6666
.Include(x => x.WalletWithdrawalRequestPSBTs)
6767
.Include(x => x.WalletWithdrawalRequestDestinations)
68+
.Include(x => x.UTXOs)
6869
.SingleOrDefaultAsync(x => x.Id == id);
6970

7071
return request;
@@ -94,6 +95,7 @@ public async Task<List<WalletWithdrawalRequest>> GetAll()
9495
.Include(x => x.UserRequestor)
9596
.Include(x => x.WalletWithdrawalRequestPSBTs)
9697
.Include(x => x.WalletWithdrawalRequestDestinations)
98+
.Include(x => x.UTXOs)
9799
.AsSplitQuery()
98100
.ToListAsync();
99101
}

src/Helpers/Constants.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
using System.Globalization;
2020
using System.Reflection;
21+
using NBitcoin;
2122
using NodeGuard.Helpers;
2223

2324
public class Constants
@@ -75,6 +76,7 @@ public class Constants
7576
public static readonly long MINIMUM_SWEEP_TRANSACTION_AMOUNT_SATS = 25_000_000; //25M sats
7677
public static readonly string DEFAULT_DERIVATION_PATH = "48'/1'";
7778
public static readonly int SESSION_TIMEOUT_MILLISECONDS = 3_600_000;
79+
public static readonly Money BITCOIN_DUST = new Money(0.00000546m, MoneyUnit.BTC); // 546 satoshi in BTC
7880

7981
//Sat/vb ratio
8082
public static decimal MIN_SAT_PER_VB_RATIO = 0.9m;
@@ -100,7 +102,7 @@ public class Constants
100102
public static readonly string ALICE_PUBKEY = "02dc2ae598a02fc1e9709a23b68cd51d7fa14b1132295a4d75aa4f5acd23ee9527";
101103
public static readonly string ALICE_HOST = "host.docker.internal:10001";
102104
public static readonly string ALICE_MACAROON = "0201036c6e6402f801030a108cdfeb2614b8335c11aebb358f888d6d1201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e657261746512047265616400000620c999e1a30842cbae3f79bd633b19d5ec0d2b6ebdc4880f6f5d5c230ce38f26ab";
103-
public static readonly string BOB_PUBKEY = "038644c6b13cdfc59bc97c2cc2b1418ced78f6d01da94f3bfd5fdf8b197335ea84";
105+
public static readonly string BOB_PUBKEY = "038644c6b13cdfc59bc97c2cc2b1418ced78f6d01da94f3bfd5fdf8b197335ea84";
104106
public static readonly string BOB_HOST = "host.docker.internal:10002";
105107
public static readonly string BOB_MACAROON = "0201036c6e6402f801030a10e0e89a68f9e2398228a995890637d2531201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e657261746512047265616400000620b85ae6b693338987cd65eda60a24573e962301b2a91d8f7c5625650d6368751f";
106108
public static readonly string CAROL_PUBKEY = "03650f49929d84d9a6d9b5a66235c603a1a0597dd609f7cd3b15052382cf9bb1b4";

src/Jobs/PerformWithdrawalJob.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,29 @@ public async Task Execute(IJobExecutionContext context)
5858
catch (Exception e)
5959
{
6060
var request = await _walletWithdrawalRequestRepository.GetById(withdrawalRequestId);
61+
62+
// If an error occurs and the wd was bumping another one, we need to update the status of the withdrawal request to previous status
63+
if (request != null && request!.BumpingWalletWithdrawalRequestId.HasValue)
64+
{
65+
var bumped = await _walletWithdrawalRequestRepository.GetById(request.BumpingWalletWithdrawalRequestId.Value);
66+
if (bumped == null)
67+
{
68+
_logger.LogError("Failed to find withdrawal request with ID {WithdrawalRequestId}", withdrawalRequestId);
69+
return;
70+
}
71+
72+
bumped.Status = WalletWithdrawalRequestStatus.OnChainConfirmationPending;
73+
var (ok, _) = _walletWithdrawalRequestRepository.Update(bumped);
74+
if (!ok)
75+
{
76+
_logger.LogError("Failed to update withdrawal request status to OnChainConfirmationPending for ID {WithdrawalRequestId}", withdrawalRequestId);
77+
}
78+
else
79+
{
80+
_logger.LogInformation("Updated withdrawal request status to OnChainConfirmationPending for ID {WithdrawalRequestId}", withdrawalRequestId);
81+
}
82+
}
83+
6184
request!.Status = WalletWithdrawalRequestStatus.Failed;
6285
var (updated, _) = _walletWithdrawalRequestRepository.Update(request);
6386
if (!updated)

src/Migrations/20230309121807_UpdateSourceDestChannelNodeIdFromRequests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ protected override void Down(MigrationBuilder migrationBuilder)
3636
UPDATE public.""Channels"" SET ""SourceNodeId"" = 1, ""DestinationNodeId"" = 1;
3737
COMMIT;";
3838
migrationBuilder.Sql(query);
39-
4039
}
4140
}
4241
}

src/Migrations/20250611142943_MigrateLegacyWithdrawalData.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ public partial class MigrateLegacyWithdrawalData : Migration
1010
/// <inheritdoc />
1111
protected override void Up(MigrationBuilder migrationBuilder)
1212
{
13-
// Execute data migration in an explicit transaction
13+
// Execute data migration
1414
migrationBuilder.Sql(@"
15-
BEGIN;
16-
1715
-- Step 1: Migrate existing data from Amount and DestinationAddress to WalletWithdrawalRequestDestinations
1816
INSERT INTO ""WalletWithdrawalRequestDestinations"" (""Amount"", ""Address"", ""WalletWithdrawalRequestId"", ""CreationDatetime"", ""UpdateDatetime"")
1917
SELECT
@@ -24,7 +22,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
2422
""UpdateDatetime""
2523
FROM ""WalletWithdrawalRequests""
2624
WHERE ""Amount"" > 0 AND ""DestinationAddress"" IS NOT NULL AND ""DestinationAddress"" != '';
27-
25+
2826
-- Step 2: Verify migration was successful - if any records failed to migrate, rollback
2927
DO $$
3028
DECLARE
@@ -50,8 +48,6 @@ SELECT COUNT(*) INTO migrated_count
5048
-- Log success
5149
RAISE NOTICE 'Successfully migrated % legacy withdrawal records to destinations table', original_count;
5250
END $$;
53-
54-
COMMIT;
5551
");
5652
}
5753

0 commit comments

Comments
 (0)