Add fuzzing for amounts
* +- 0 to 1%, or +- 1 sat for tiny amounts * Ensure balance cannot go negative
This commit is contained in:
parent
874c984c6d
commit
c16db7bbb2
@ -17,7 +17,7 @@ for (const key of ["DB_USER", "DB_PASSWORD", "DB_NAME", "ANONYMIZATION_KEY"]) {
|
|||||||
// Adjust based on database capacity
|
// Adjust based on database capacity
|
||||||
const BATCH_SIZE = parseInt(env.BATCH_SIZE) || 10;
|
const BATCH_SIZE = parseInt(env.BATCH_SIZE) || 10;
|
||||||
// Maximum number of invoices to process per user
|
// Maximum number of invoices to process per user
|
||||||
// Skip user if exceeded (likely the case for streaming payments e.g.)
|
// Skip user if exceeded (likely the case for streaming payments)
|
||||||
const MAX_INVOICES = parseInt(env.MAX_INVOICES) || 21000;
|
const MAX_INVOICES = parseInt(env.MAX_INVOICES) || 21000;
|
||||||
|
|
||||||
// Database connection configuration from .env or environment variables
|
// Database connection configuration from .env or environment variables
|
||||||
@ -72,7 +72,7 @@ const csvHeaders = [
|
|||||||
// Write headers to CSV (create file or overwrite if it exists)
|
// Write headers to CSV (create file or overwrite if it exists)
|
||||||
await Deno.writeTextFile(csvFilePath, csvHeaders.join(",") + "\n");
|
await Deno.writeTextFile(csvFilePath, csvHeaders.join(",") + "\n");
|
||||||
|
|
||||||
// Function to compute HMAC-SHA256 hash of user_id, truncated to 48 bits (12 hex chars)
|
// Compute HMAC-SHA256 hash of user_id, truncated to 48 bits (12 hex chars)
|
||||||
async function anonymizeUserId(userId) {
|
async function anonymizeUserId(userId) {
|
||||||
const keyData = new TextEncoder().encode(env.ANONYMIZATION_KEY);
|
const keyData = new TextEncoder().encode(env.ANONYMIZATION_KEY);
|
||||||
const data = new TextEncoder().encode(userId.toString());
|
const data = new TextEncoder().encode(userId.toString());
|
||||||
@ -88,7 +88,19 @@ async function anonymizeUserId(userId) {
|
|||||||
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to process a single user and return CSV rows
|
// Generate a random fuzzing factor
|
||||||
|
function getFuzzFactor(amount) {
|
||||||
|
const bytes = new Uint8Array(4);
|
||||||
|
crypto.getRandomValues(bytes);
|
||||||
|
const randomValue = new Uint32Array(bytes.buffer)[0];
|
||||||
|
const normalized = randomValue / 0xFFFFFFFF;
|
||||||
|
if (amount < 100) {
|
||||||
|
return normalized < 0.5 ? -1 : 1; // ±1 satoshi for small amounts
|
||||||
|
}
|
||||||
|
return normalized * 0.02 - 0.01; // -1% to +1% for larger amounts
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process a single user and return CSV rows
|
||||||
async function processUser(userId, client) {
|
async function processUser(userId, client) {
|
||||||
// Check the number of settled invoices for the user
|
// Check the number of settled invoices for the user
|
||||||
const countResult = await client.queryObject(
|
const countResult = await client.queryObject(
|
||||||
@ -123,16 +135,34 @@ async function processUser(userId, client) {
|
|||||||
|
|
||||||
for (const invoice of invoices) {
|
for (const invoice of invoices) {
|
||||||
const day = invoice.settled_at.toISOString().split("T")[0]; // YYYY-MM-DD
|
const day = invoice.settled_at.toISOString().split("T")[0]; // YYYY-MM-DD
|
||||||
// Convert amounts to BigInt, handling NULL fees
|
|
||||||
const amount = BigInt(invoice.amount);
|
|
||||||
const serviceFee = invoice.service_fee ? BigInt(invoice.service_fee) : BigInt(0);
|
|
||||||
const routingFee = invoice.routing_fee ? BigInt(invoice.routing_fee) : BigInt(0);
|
|
||||||
|
|
||||||
// Calculate effective amount: include fees for outgoing, not for incoming
|
// Get fuzzing factor for this invoice
|
||||||
const effectiveAmount = invoice.type === "incoming"
|
const fuzzFactor = getFuzzFactor(Number(invoice.amount));
|
||||||
? amount
|
|
||||||
: amount + serviceFee + routingFee; // Add fees for outgoing
|
// Convert amounts to BigInt
|
||||||
const signedAmount = invoice.type === "incoming" ? effectiveAmount : -effectiveAmount;
|
const amount = BigInt(invoice.amount);
|
||||||
|
const serviceFee = BigInt(invoice.service_fee);
|
||||||
|
const routingFee = BigInt(invoice.routing_fee);
|
||||||
|
|
||||||
|
// Apply fuzzing: value * (1 + fuzzFactor) or ±1 sat, rounded to nearest integer
|
||||||
|
let fuzzedAmount = amount < 100
|
||||||
|
? amount + BigInt(fuzzFactor)
|
||||||
|
: BigInt(Math.round(Number(amount) * (1 + fuzzFactor)));
|
||||||
|
|
||||||
|
// Calculate effective amount: include fees for outgoing
|
||||||
|
let effectiveAmount = invoice.type === "incoming"
|
||||||
|
? fuzzedAmount
|
||||||
|
: fuzzedAmount + serviceFee + routingFee; // Add fees for outgoing
|
||||||
|
let signedAmount = invoice.type === "incoming" ? effectiveAmount : -effectiveAmount;
|
||||||
|
|
||||||
|
// Prevent negative running balance for outgoing invoices
|
||||||
|
if (invoice.type === "outgoing" && runningBalance + signedAmount < 0) {
|
||||||
|
// Adjust fuzzed amount to spend only up to available balance
|
||||||
|
const maxSpend = runningBalance;
|
||||||
|
fuzzedAmount = maxSpend - serviceFee - routingFee;
|
||||||
|
effectiveAmount = fuzzedAmount + serviceFee + routingFee;
|
||||||
|
signedAmount = -effectiveAmount;
|
||||||
|
}
|
||||||
|
|
||||||
if (!dailyData[day]) {
|
if (!dailyData[day]) {
|
||||||
dailyData[day] = {
|
dailyData[day] = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user