Skip to content

Commit

Permalink
Added fee rate checks (#190)
Browse files Browse the repository at this point in the history
feat(Constants.cs): add MIN_SAT_PER_VB_RATIO, MAX_SAT_PER_VB_RATIO, and MAX_TX_FEE_RATIO for fee rate validation
feat(LightningService.cs): implement fee rate validation for finalized PSBT using new constants to ensure fees are within acceptable range
  • Loading branch information
Jossec101 committed Jun 5, 2023
1 parent affed30 commit 8cb34de
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 4 deletions.
16 changes: 16 additions & 0 deletions src/Helpers/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ public class Constants
public static readonly long ANCHOR_CLOSINGS_MINIMUM_SATS;
public static readonly string DEFAULT_DERIVATION_PATH = "48'/1'";
public static readonly int SESSION_TIMEOUT_MILLISECONDS = 3_600_000;

//Sat/vb ratio
public static decimal MIN_SAT_PER_VB_RATIO = 0.9m;
public static decimal MAX_SAT_PER_VB_RATIO = 2.0m;
/// <summary>
/// Max ratio of the tx total input sum that could be used as fee
/// </summary>
public static decimal MAX_TX_FEE_RATIO =0.5m;

private static string? GetEnvironmentalVariableOrThrowIfNotTesting(string envVariableName, string? errorMessage = null)
{
Expand Down Expand Up @@ -190,6 +198,14 @@ static Constants()

var timeout = Environment.GetEnvironmentVariable("SESSION_TIMEOUT_MILLISECONDS");
if (timeout != null) SESSION_TIMEOUT_MILLISECONDS = int.Parse(timeout);

//Sat/vb ratio
var minSatPerVbRatioEnv = Environment.GetEnvironmentVariable("MIN_SAT_PER_VB_RATIO");
MIN_SAT_PER_VB_RATIO = minSatPerVbRatioEnv!= null ? decimal.Parse(minSatPerVbRatioEnv) : MIN_SAT_PER_VB_RATIO;

var maxSatPerVbRatioEnv = Environment.GetEnvironmentVariable("MAX_SAT_PER_VB_RATIO");
MAX_SAT_PER_VB_RATIO = maxSatPerVbRatioEnv!= null ? decimal.Parse(maxSatPerVbRatioEnv) : MAX_SAT_PER_VB_RATIO;

}
}

Expand Down
39 changes: 35 additions & 4 deletions src/Services/LightningService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,9 @@ public async Task OpenChannel(ChannelOperationRequest channelOperationRequest)
{
// 8 value + 1 script pub key size + 34 script pub key hash (Segwit output 2-0f-2 multisig)
var outputVirtualSize = combinedPSBT.GetGlobalTransaction().GetVirtualSize() + 43;
var feeRateResult = await LightningHelper.GetFeeRateResult(network, _nbXplorerService);
var initialFeeRate = await LightningHelper.GetFeeRateResult(network, _nbXplorerService);

var totalFees = new Money(outputVirtualSize * feeRateResult.FeeRate.SatoshiPerByte, MoneyUnit.Satoshi);
var totalFees = new Money(outputVirtualSize * initialFeeRate.FeeRate.SatoshiPerByte, MoneyUnit.Satoshi);

long fundingAmount = channelOperationRequest.Changeless ? channelOperationRequest.SatsAmount - totalFees : channelOperationRequest.SatsAmount;
//We prepare the request (shim) with the base PSBT we had presigned with the UTXOs to fund the channel
Expand Down Expand Up @@ -404,7 +404,7 @@ await foreach (var response in openStatusUpdateStream.ResponseStream.ReadAllAsyn
{
var totalIn = fundedPSBT.Inputs.Sum(i => i.GetTxOut()?.Value);
//We manually fix the change (it was wrong from the Base template due to nbitcoin requiring a change on a PSBT)
var totalChangefulFees = new Money(vsize * feeRateResult.FeeRate.SatoshiPerByte, MoneyUnit.Satoshi);
var totalChangefulFees = new Money(vsize * initialFeeRate.FeeRate.SatoshiPerByte, MoneyUnit.Satoshi);
var changeOutput = channelfundingTx.Outputs.SingleOrDefault(o => o.Value != channelOperationRequest.SatsAmount) ?? channelfundingTx.Outputs.First();
changeOutput.Value = totalIn - totalOut - totalChangefulFees;

Expand Down Expand Up @@ -473,7 +473,38 @@ await foreach (var response in openStatusUpdateStream.ResponseStream.ReadAllAsyn
finalizedPSBT.AssertSanity();

channelfundingTx = finalizedPSBT.ExtractTransaction();


//We check the feerate of the finalized PSBT by checking a minimum and maximum allowed and also a fee-level max check in ratio
var feerate = new FeeRate(finalizedPSBT.GetFee(), channelfundingTx.GetVirtualSize());

var minFeeRate = Constants.MIN_SAT_PER_VB_RATIO * initialFeeRate.FeeRate.SatoshiPerByte;

var maxFeeRate = Constants.MAX_SAT_PER_VB_RATIO * initialFeeRate.FeeRate.SatoshiPerByte;

if (feerate.SatoshiPerByte < minFeeRate)
{
_logger.LogError("Channel operation request id: {RequestId} finalized PSBT sat/vb: {SatPerVb} is lower than the minimum allowed: {MinSatPerVb}", channelOperationRequest.Id, feerate.SatoshiPerByte, minFeeRate);
throw new Exception("The finalized PSBT sat/vb is lower than the minimum allowed");
}

if (feerate.SatoshiPerByte > maxFeeRate)
{
_logger.LogError("Channel operation request id: {RequestId} finalized PSBT sat/vb: {SatPerVb} is higher than the maximum allowed: {MaxSatPerVb}", channelOperationRequest.Id, feerate.SatoshiPerByte, maxFeeRate);
throw new Exception("The finalized PSBT sat/vb is higher than the maximum allowed");
}

//if the fee is too high, we throw an exception
var finalizedTotalIn = finalizedPSBT.Inputs.Sum(x => (long) x.GetCoin()?.Amount);
if (finalizedPSBT.GetFee().Satoshi >=
finalizedTotalIn * Constants.MAX_TX_FEE_RATIO)
{
_logger.LogError("Channel operation request id: {RequestId} finalized PSBT fee: {Fee} is higher than the maximum allowed: {MaxFee} sats", channelOperationRequest.Id, finalizedPSBT.GetFee().Satoshi, finalizedTotalIn * Constants.MAX_TX_FEE_RATIO);
throw new Exception("The finalized PSBT fee is higher than the maximum allowed");
}


_logger.LogInformation("Channel operation request id: {RequestId} finalized PSBT sat/vb: {SatPerVb}", channelOperationRequest.Id, feerate.SatoshiPerByte);

//Just a check of the tx based on the finalizedPSBT
var checkTx = channelfundingTx.Check();

Expand Down

0 comments on commit 8cb34de

Please sign in to comment.