Skip to content

Commit 6a8fe46

Browse files
authored
fix(utils): make settlement script work properly (#320)
Primary change: batch into 6 day settlement periods so we don't break gas limit WIP to make settlement more efficient so we can do larger periods. Ref: FilOzone/filecoin-services#252
1 parent 42389da commit 6a8fe46

1 file changed

Lines changed: 149 additions & 58 deletions

File tree

utils/settle-dataset-rails.js

Lines changed: 149 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,71 @@
1111

1212
import { ethers } from 'ethers'
1313
import { SETTLEMENT_FEE, Synapse } from '../packages/synapse-sdk/src/index.ts'
14+
import { getCurrentEpoch } from '../packages/synapse-sdk/src/utils/index.ts'
1415
import { WarmStorageService } from '../packages/synapse-sdk/src/warm-storage/index.ts'
1516

17+
// ANSI color codes
18+
const RESET = '\x1b[0m'
19+
const BOLD = '\x1b[1m'
20+
const DIM = '\x1b[2m'
21+
const GREEN = '\x1b[32m'
22+
const YELLOW = '\x1b[33m'
23+
const CYAN = '\x1b[36m'
24+
const RED = '\x1b[31m'
25+
26+
// Configuration for batch settlement
27+
// Maximum tested batch size to avoid gas limit in validator's epoch loop
28+
const DAYS = 6n // Number of days per batch (7 days currently exceeds block gas limit)
29+
const EPOCHS_PER_DAY = 2880n
30+
const BATCH_SIZE = EPOCHS_PER_DAY * DAYS
31+
32+
/**
33+
* Execute a settlement for a rail, optionally to a specific epoch
34+
* @returns {Object|null} Settlement result with preview, tx, receipt, or null if nothing to settle
35+
*/
36+
async function executeSettlement(synapse, rail, targetEpoch = null, batchNumber = null) {
37+
const indent = batchNumber ? ' ' : ' '
38+
const batchLabel = batchNumber ? ` (Batch ${batchNumber})` : ''
39+
40+
// Get settlement preview
41+
const preview = targetEpoch
42+
? await synapse.payments.getSettlementAmounts(rail.id, targetEpoch)
43+
: await synapse.payments.getSettlementAmounts(rail.id)
44+
45+
// Log settlement amounts
46+
if (batchNumber) {
47+
console.log(`${indent}Amount: ${ethers.formatUnits(preview.totalSettledAmount, 18)} USDFC`)
48+
} else {
49+
console.log(`${indent}Total to settle: ${ethers.formatUnits(preview.totalSettledAmount, 18)} USDFC`)
50+
console.log(`${indent}Payee receives: ${ethers.formatUnits(preview.totalNetPayeeAmount, 18)} USDFC`)
51+
console.log(`${indent}Commission: ${ethers.formatUnits(preview.totalOperatorCommission, 18)} USDFC`)
52+
}
53+
54+
// Nothing to settle
55+
if (preview.totalSettledAmount === 0n) {
56+
console.log(`${indent}${DIM}Nothing to settle${RESET}`)
57+
console.log('')
58+
return null
59+
}
60+
61+
// Execute settlement
62+
console.log(`${indent}Settling...`)
63+
const tx = targetEpoch ? await synapse.payments.settle(rail.id, targetEpoch) : await synapse.payments.settle(rail.id)
64+
console.log(`${indent}Tx: ${tx.hash}`)
65+
66+
// Wait for confirmation
67+
const receipt = await tx.wait()
68+
console.log(`${indent}${GREEN}Confirmed in block ${receipt.blockNumber}${RESET}`)
69+
console.log('')
70+
71+
return {
72+
preview,
73+
tx,
74+
receipt,
75+
type: `${rail.type}${batchLabel}`,
76+
}
77+
}
78+
1679
async function main() {
1780
// Parse arguments
1881
const dataSetId = process.argv[2]
@@ -30,8 +93,8 @@ async function main() {
3093
process.exit(1)
3194
}
3295

33-
console.log('🔗 Connecting to:', rpcUrl)
34-
console.log('📊 Data Set ID:', dataSetId)
96+
console.log(`${CYAN}Connecting to:${RESET} ${rpcUrl}`)
97+
console.log(`${CYAN}Data Set ID:${RESET} ${dataSetId}`)
3598
console.log('')
3699

37100
// Initialize SDK at the top level so we can access it later
@@ -48,21 +111,21 @@ async function main() {
48111
const warmStorageAddress = await synapse.getWarmStorageAddress()
49112
const warmStorage = await WarmStorageService.create(synapse.getProvider(), warmStorageAddress)
50113

51-
console.log('📋 Fetching data set information...')
114+
console.log('Fetching data set information...')
52115

53116
// Get data set info to find rail IDs
54117
const dataSet = await warmStorage.getDataSet(Number(dataSetId))
55118

56119
if (!dataSet) {
57-
console.error(` Data set ${dataSetId} not found`)
120+
console.error(`${RED}Error: Data set ${dataSetId} not found${RESET}`)
58121
process.exit(1)
59122
}
60-
console.log('✅ Data set found:')
61-
console.log(` Client: ${dataSet.payer}`)
62-
console.log(` Provider: ${dataSet.serviceProvider}`)
63-
console.log(` PDP Rail ID: ${dataSet.pdpRailId}`)
64-
console.log(` CDN Rail ID: ${dataSet.cdnRailId}`)
65-
console.log(` Cache Miss Rail ID: ${dataSet.cacheMissRailId}`)
123+
console.log(`${GREEN}Data set found${RESET}`)
124+
console.log(` Client: ${dataSet.payer}`)
125+
console.log(` Provider: ${dataSet.serviceProvider}`)
126+
console.log(` PDP Rail ID: ${dataSet.pdpRailId}`)
127+
console.log(` CDN Rail ID: ${dataSet.cdnRailId}`)
128+
console.log(` Cache Miss Rail: ${dataSet.cacheMissRailId}`)
66129
console.log('')
67130

68131
// Collect all rail IDs to settle
@@ -79,94 +142,122 @@ async function main() {
79142
}
80143

81144
if (railsToSettle.length === 0) {
82-
console.log('⚠️ No rails found for this data set')
145+
console.log(`${YELLOW}No rails found for this data set${RESET}`)
83146
process.exit(0)
84147
}
85148

86-
console.log(`💰 Checking settlement amounts for ${railsToSettle.length} rail(s)...`)
87-
88-
// Display settlement fee
89-
console.log(`📍 Settlement fee per settlement: ${ethers.formatEther(SETTLEMENT_FEE)} FIL`)
149+
console.log(`Checking settlement amounts for ${railsToSettle.length} rail(s)...`)
150+
console.log(`${DIM}Settlement fee: ${ethers.formatEther(SETTLEMENT_FEE)} FIL per transaction${RESET}`)
90151
console.log('')
91152

92153
let totalSettled = 0n
93154
let totalPayeeAmount = 0n
94155
let totalCommission = 0n
95156
const transactions = []
96157

158+
// Get current epoch
159+
const currentEpoch = await getCurrentEpoch(synapse.getProvider())
160+
console.log(`Current epoch: ${currentEpoch}`)
161+
console.log('')
162+
97163
// Process each rail
98164
for (const rail of railsToSettle) {
99-
console.log(`📊 ${rail.type} Rail (ID: ${rail.id}):`)
165+
console.log(`${BOLD}${rail.type} Rail (ID: ${rail.id})${RESET}`)
100166

101167
try {
102-
// Preview settlement amounts
103-
const preview = await synapse.payments.getSettlementAmounts(rail.id)
104-
105-
console.log(` Total to settle: ${ethers.formatUnits(preview.totalSettledAmount, 18)} USDFC`)
106-
console.log(` Payee receives: ${ethers.formatUnits(preview.totalNetPayeeAmount, 18)} USDFC`)
107-
console.log(` Commission: ${ethers.formatUnits(preview.totalOperatorCommission, 18)} USDFC`)
168+
// Get rail info to check settled epoch
169+
const railInfo = await synapse.payments.getRail(rail.id)
170+
const settledUpTo = railInfo.settledUpTo
171+
const epochGap = currentEpoch - settledUpTo
108172

109-
if (preview.totalSettledAmount === 0n) {
110-
console.log(` ⏭️ Nothing to settle, skipping...`)
111-
console.log('')
112-
continue
113-
}
173+
console.log(` Settled up to: ${settledUpTo}`)
174+
console.log(` Epoch gap: ${epochGap} epochs`)
114175

115-
// Settle the rail
116-
console.log(` 🔄 Settling rail...`)
117-
const tx = await synapse.payments.settle(rail.id)
118-
console.log(` 📝 Transaction: ${tx.hash}`)
176+
// Determine if we need to batch
177+
const needsBatching = epochGap > BATCH_SIZE
119178

120-
// Wait for confirmation
121-
console.log(` ⏳ Waiting for confirmation...`)
122-
const receipt = await tx.wait()
123-
console.log(` ✅ Confirmed in block ${receipt.blockNumber}`)
124-
console.log('')
179+
if (needsBatching) {
180+
console.log(` ${YELLOW}Large gap - settling in batches of ${BATCH_SIZE} epochs (${DAYS} days)${RESET}`)
181+
console.log('')
125182

126-
// Track totals
127-
totalSettled += preview.totalSettledAmount
128-
totalPayeeAmount += preview.totalNetPayeeAmount
129-
totalCommission += preview.totalOperatorCommission
130-
transactions.push({
131-
type: rail.type,
132-
railId: rail.id,
133-
txHash: tx.hash,
134-
amount: preview.totalSettledAmount,
135-
})
183+
// Settle in batches
184+
let batchStart = settledUpTo
185+
let batchNumber = 1
186+
187+
while (batchStart < currentEpoch) {
188+
const batchEnd = batchStart + BATCH_SIZE
189+
const targetEpoch = batchEnd > currentEpoch ? currentEpoch : batchEnd
190+
191+
console.log(` ${CYAN}Batch ${batchNumber}:${RESET} Epochs ${batchStart}${targetEpoch}`)
192+
193+
const result = await executeSettlement(synapse, rail, targetEpoch, batchNumber)
194+
195+
if (result) {
196+
// Track totals
197+
totalSettled += result.preview.totalSettledAmount
198+
totalPayeeAmount += result.preview.totalNetPayeeAmount
199+
totalCommission += result.preview.totalOperatorCommission
200+
transactions.push({
201+
type: result.type,
202+
railId: rail.id,
203+
txHash: result.tx.hash,
204+
amount: result.preview.totalSettledAmount,
205+
})
206+
}
207+
208+
batchStart = targetEpoch
209+
batchNumber++
210+
}
211+
} else {
212+
// Normal single settlement
213+
const result = await executeSettlement(synapse, rail)
214+
215+
if (result) {
216+
// Track totals
217+
totalSettled += result.preview.totalSettledAmount
218+
totalPayeeAmount += result.preview.totalNetPayeeAmount
219+
totalCommission += result.preview.totalOperatorCommission
220+
transactions.push({
221+
type: result.type,
222+
railId: rail.id,
223+
txHash: result.tx.hash,
224+
amount: result.preview.totalSettledAmount,
225+
})
226+
}
227+
}
136228
} catch (error) {
137-
console.error(` Error settling ${rail.type} rail:`, error.message)
229+
console.error(` ${RED}Error settling ${rail.type} rail: ${error.message}${RESET}`)
138230

139231
// Check if it's the InsufficientNativeTokenForBurn error
140232
if (error.message.includes('InsufficientNativeTokenForBurn')) {
141-
console.log(` ℹ️ This error means your wallet doesn't have enough FIL for the network fee`)
142-
console.log(` ℹ️ Settlement requires ${ethers.formatEther(networkFee)} FIL as a network fee`)
143-
console.log(` ℹ️ Please ensure your wallet has sufficient FIL balance`)
233+
console.log(` ${YELLOW}Insufficient FIL for network fee${RESET}`)
234+
console.log(` Required: ${ethers.formatEther(SETTLEMENT_FEE)} FIL`)
144235
}
145236

146237
console.log('')
147238
}
148239
}
149240

150241
// Summary
151-
console.log('═══════════════════════════════════════════')
152-
console.log('📊 SETTLEMENT SUMMARY')
153-
console.log('═══════════════════════════════════════════')
242+
console.log('')
243+
console.log(`${BOLD}Settlement Summary${RESET}`)
244+
console.log('─'.repeat(60))
154245
console.log(`Total Settled: ${ethers.formatUnits(totalSettled, 18)} USDFC`)
155246
console.log(`Payee Received: ${ethers.formatUnits(totalPayeeAmount, 18)} USDFC`)
156247
console.log(`Total Commission: ${ethers.formatUnits(totalCommission, 18)} USDFC`)
157248
console.log('')
158249

159250
if (transactions.length > 0) {
160-
console.log('📝 Transactions:')
251+
console.log('Transactions:')
161252
for (const tx of transactions) {
162-
console.log(` ${tx.type}: ${tx.txHash}`)
253+
console.log(` ${tx.type}: ${tx.txHash}`)
163254
}
164255
}
165256

166257
console.log('')
167-
console.log('✅ Settlement complete!')
258+
console.log(`${GREEN}Settlement complete${RESET}`)
168259
} catch (error) {
169-
console.error('❌ Error:', error.message)
260+
console.error(`${RED}Error: ${error.message}${RESET}`)
170261
hasError = true
171262
} finally {
172263
// Always close the WebSocket connection if it exists
@@ -184,6 +275,6 @@ async function main() {
184275

185276
// Run the script
186277
main().catch((error) => {
187-
console.error('❌ Fatal error:', error.message)
278+
console.error(`${RED}Fatal error: ${error.message}${RESET}`)
188279
process.exit(1)
189280
})

0 commit comments

Comments
 (0)