@ar.io/sdk 4.0.0-solana.39 → 4.0.0-solana.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1212,6 +1212,36 @@ export class SolanaARIOReadable {
|
|
|
1212
1212
|
}
|
|
1213
1213
|
return { failureSummaries, reports };
|
|
1214
1214
|
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Observer pubkeys that have an OPEN Observation PDA for `epochIndex`. Lean —
|
|
1217
|
+
* reads only the Observation accounts (no gateway-registry decode like
|
|
1218
|
+
* {@link getObservations}). Used by the crank to close observations before
|
|
1219
|
+
* `close_epoch`, whose `observations_closed == observations_submitted`
|
|
1220
|
+
* precondition would otherwise wedge epoch progression.
|
|
1221
|
+
*/
|
|
1222
|
+
async getEpochObservers(epochIndex) {
|
|
1223
|
+
const epochIndexBuf = Buffer.alloc(8);
|
|
1224
|
+
epochIndexBuf.writeBigUInt64LE(BigInt(epochIndex));
|
|
1225
|
+
const accounts = await this.getAccountsByDiscriminator(this.garProgram, OBSERVATION_DISCRIMINATOR, [
|
|
1226
|
+
{
|
|
1227
|
+
memcmp: {
|
|
1228
|
+
offset: 8n,
|
|
1229
|
+
bytes: bs58.encode(epochIndexBuf),
|
|
1230
|
+
encoding: 'base58',
|
|
1231
|
+
},
|
|
1232
|
+
},
|
|
1233
|
+
]);
|
|
1234
|
+
const observers = [];
|
|
1235
|
+
for (const { data } of accounts) {
|
|
1236
|
+
try {
|
|
1237
|
+
observers.push(address(deserializeObservation(data).observer));
|
|
1238
|
+
}
|
|
1239
|
+
catch {
|
|
1240
|
+
// skip malformed
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return observers;
|
|
1244
|
+
}
|
|
1215
1245
|
async getDistributions(epoch) {
|
|
1216
1246
|
const epochIndex = await this.resolveEpochIndex(epoch);
|
|
1217
1247
|
const epochData = await this.fetchEpoch(epochIndex);
|
|
@@ -233,6 +233,9 @@ export function encodeReportTxId(reportTxId) {
|
|
|
233
233
|
* the per-tx account/1232-byte budget even when none share a gateway.
|
|
234
234
|
*/
|
|
235
235
|
const MAX_COMPOUND_BATCH = 12;
|
|
236
|
+
/** Observation PDAs closed per tx before close_epoch (each ix carries Epoch +
|
|
237
|
+
* Observation + payer + system accounts — keep well under the tx account cap). */
|
|
238
|
+
const MAX_CLOSE_OBSERVATION_BATCH = 8;
|
|
236
239
|
/** Demand-factor period length (seconds) — mirrors `PERIOD_LENGTH_SECONDS` in ario-arns. */
|
|
237
240
|
const DEMAND_FACTOR_PERIOD_SECONDS = 86_400;
|
|
238
241
|
/**
|
|
@@ -2758,12 +2761,47 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
2758
2761
|
};
|
|
2759
2762
|
}
|
|
2760
2763
|
// Close a fully-distributed epoch past retention (GAR-006).
|
|
2764
|
+
//
|
|
2765
|
+
// CRITICAL: this is cleanup of OLD epochs and must NEVER block creation of
|
|
2766
|
+
// NEW ones. `close_epoch` reverts with EpochObservationsNotClosed until the
|
|
2767
|
+
// epoch's Observation PDAs are closed (observations_closed ==
|
|
2768
|
+
// observations_submitted), so we close those FIRST. The whole branch is
|
|
2769
|
+
// wrapped so any failure falls through to create-next instead of throwing
|
|
2770
|
+
// out of crankEpochStep — otherwise a single un-closeable epoch wedges epoch
|
|
2771
|
+
// progression network-wide (every operator's crank hits the same wall).
|
|
2761
2772
|
if (enableClose && targetEpochIndex >= retention) {
|
|
2762
2773
|
const closeTarget = targetEpochIndex - retention;
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2774
|
+
try {
|
|
2775
|
+
const old = await this.getEpochRaw(closeTarget);
|
|
2776
|
+
if (old && old.rewardsDistributed === 1) {
|
|
2777
|
+
if (old.observationsSubmitted > old.observationsClosed) {
|
|
2778
|
+
// Open observations remain — close a batch before close_epoch.
|
|
2779
|
+
const observers = await this.getEpochObservers(closeTarget);
|
|
2780
|
+
if (observers.length > 0) {
|
|
2781
|
+
const batch = observers.slice(0, MAX_CLOSE_OBSERVATION_BATCH);
|
|
2782
|
+
const { id } = await this.closeObservations({
|
|
2783
|
+
epochIndex: closeTarget,
|
|
2784
|
+
observers: batch,
|
|
2785
|
+
});
|
|
2786
|
+
return {
|
|
2787
|
+
action: 'close_observation',
|
|
2788
|
+
epochIndex: closeTarget,
|
|
2789
|
+
txId: id,
|
|
2790
|
+
progress: { index: batch.length, total: observers.length },
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
// Counter says open but no Observation PDA exists (orphaned counter):
|
|
2794
|
+
// can't close it, so don't attempt close_epoch (it would revert) and
|
|
2795
|
+
// don't wedge — fall through to create-next.
|
|
2796
|
+
}
|
|
2797
|
+
else {
|
|
2798
|
+
const { id } = await this.closeEpoch({ epochIndex: closeTarget });
|
|
2799
|
+
return { action: 'close', epochIndex: closeTarget, txId: id };
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
catch {
|
|
2804
|
+
// Best-effort cleanup — never block epoch creation. Retried next tick.
|
|
2767
2805
|
}
|
|
2768
2806
|
}
|
|
2769
2807
|
// Lazy-state maintenance — lower urgency than the lifecycle, reached only
|
|
@@ -2908,10 +2946,13 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
2908
2946
|
* [8 per_obs][8 reward_rate][8 weight_lo][8 weight_hi][32 hashchain]
|
|
2909
2947
|
* [4 active_gw_count][4 dist_idx][4 tally_idx]
|
|
2910
2948
|
* [1 observer_count][1 name_count][1 obs_submitted][1 rewards_dist]
|
|
2911
|
-
* [1 weights_tallied][1 prescriptions_done][1 bump][1
|
|
2949
|
+
* [1 weights_tallied][1 prescriptions_done][1 bump][1 obs_closed]
|
|
2912
2950
|
* [6000 failure_counts][1600 prescribed_observers]
|
|
2913
2951
|
* [1600 prescribed_observer_gateways][64 prescribed_names]
|
|
2914
2952
|
* [7 has_observed][5 _pad2]
|
|
2953
|
+
*
|
|
2954
|
+
* NOTE: byte +123 is `observations_closed` (NOT padding) — `close_epoch`
|
|
2955
|
+
* reverts with EpochObservationsNotClosed until it equals obs_submitted.
|
|
2915
2956
|
*/
|
|
2916
2957
|
fetchEpochRawFields(data) {
|
|
2917
2958
|
const base = 8;
|
|
@@ -2919,15 +2960,19 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
2919
2960
|
const activeGatewayCount = data.readUInt32LE(base + 104);
|
|
2920
2961
|
const distributionIndex = data.readUInt32LE(base + 108);
|
|
2921
2962
|
const tallyIndex = data.readUInt32LE(base + 112);
|
|
2963
|
+
const observationsSubmitted = data.readUInt8(base + 118);
|
|
2922
2964
|
const rewardsDistributed = data.readUInt8(base + 119);
|
|
2923
2965
|
const weightsTallied = data.readUInt8(base + 120);
|
|
2924
2966
|
const prescriptionsDone = data.readUInt8(base + 121);
|
|
2967
|
+
const observationsClosed = data.readUInt8(base + 123);
|
|
2925
2968
|
return {
|
|
2926
2969
|
tallyIndex,
|
|
2927
2970
|
distributionIndex,
|
|
2928
2971
|
weightsTallied,
|
|
2929
2972
|
prescriptionsDone,
|
|
2930
2973
|
rewardsDistributed,
|
|
2974
|
+
observationsSubmitted,
|
|
2975
|
+
observationsClosed,
|
|
2931
2976
|
activeGatewayCount,
|
|
2932
2977
|
endTimestamp,
|
|
2933
2978
|
};
|
|
@@ -3091,6 +3136,29 @@ export class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
3091
3136
|
const sig = await this.sendTransaction([ix]);
|
|
3092
3137
|
return { id: sig };
|
|
3093
3138
|
}
|
|
3139
|
+
/**
|
|
3140
|
+
* Close multiple Observation PDAs for one epoch in a single tx (each
|
|
3141
|
+
* `close_observation` increments the parent Epoch's `observations_closed`).
|
|
3142
|
+
* Permissionless; rent is refunded to the payer. Used by the crank to satisfy
|
|
3143
|
+
* `close_epoch`'s `observations_closed == observations_submitted` precondition
|
|
3144
|
+
* before closing a retention-aged epoch. Keep the batch small — each ix carries
|
|
3145
|
+
* the Epoch + Observation + payer + system accounts.
|
|
3146
|
+
*/
|
|
3147
|
+
async closeObservations(params, _options) {
|
|
3148
|
+
if (params.observers.length === 0) {
|
|
3149
|
+
throw new Error('closeObservations: observers must be non-empty');
|
|
3150
|
+
}
|
|
3151
|
+
const ixs = await Promise.all(params.observers.map(async (obs) => {
|
|
3152
|
+
const [observationPda] = await getObservationPDA(params.epochIndex, address(obs), this.garProgram);
|
|
3153
|
+
return getCloseObservationInstructionAsync({
|
|
3154
|
+
observation: observationPda,
|
|
3155
|
+
payer: this.signer,
|
|
3156
|
+
epochIndex: BigInt(params.epochIndex),
|
|
3157
|
+
}, { programAddress: this.garProgram });
|
|
3158
|
+
}));
|
|
3159
|
+
const sig = await this.sendTransaction(ixs);
|
|
3160
|
+
return { id: sig };
|
|
3161
|
+
}
|
|
3094
3162
|
/**
|
|
3095
3163
|
* Close an empty Delegation PDA (`amount == 0`) and refund rent to the
|
|
3096
3164
|
* original delegator (NOT the caller — see GAR-016, prevents griefing).
|
package/lib/esm/version.js
CHANGED
|
@@ -253,6 +253,14 @@ export declare class SolanaARIOReadable {
|
|
|
253
253
|
getPrescribedObservers(epoch?: EpochInput): Promise<WeightedObserver[]>;
|
|
254
254
|
getPrescribedNames(epoch?: EpochInput): Promise<string[]>;
|
|
255
255
|
getObservations(epoch?: EpochInput): Promise<EpochObservationData>;
|
|
256
|
+
/**
|
|
257
|
+
* Observer pubkeys that have an OPEN Observation PDA for `epochIndex`. Lean —
|
|
258
|
+
* reads only the Observation accounts (no gateway-registry decode like
|
|
259
|
+
* {@link getObservations}). Used by the crank to close observations before
|
|
260
|
+
* `close_epoch`, whose `observations_closed == observations_submitted`
|
|
261
|
+
* precondition would otherwise wedge epoch progression.
|
|
262
|
+
*/
|
|
263
|
+
getEpochObservers(epochIndex: number): Promise<Address[]>;
|
|
256
264
|
getDistributions(epoch?: EpochInput): Promise<EpochDistributionData>;
|
|
257
265
|
getEligibleEpochRewards(epoch?: EpochInput, params?: PaginationParams<EligibleDistribution>): Promise<PaginationResult<EligibleDistribution>>;
|
|
258
266
|
/**
|
|
@@ -122,7 +122,7 @@ export declare function buildObservationBitmap(registryAddresses: string[], fail
|
|
|
122
122
|
*/
|
|
123
123
|
export declare function encodeReportTxId(reportTxId: string | undefined): Buffer;
|
|
124
124
|
/** The single on-chain action a {@link SolanaARIOWriteable.crankEpochStep} call performed. */
|
|
125
|
-
export type CrankAction = 'create' | 'tally' | 'prescribe' | 'distribute' | 'compound' | 'update_demand_factor' | 'prune_returned_names' | 'close' | 'idle';
|
|
125
|
+
export type CrankAction = 'create' | 'tally' | 'prescribe' | 'distribute' | 'compound' | 'update_demand_factor' | 'prune_returned_names' | 'close_observation' | 'close' | 'idle';
|
|
126
126
|
/** Options for {@link SolanaARIOWriteable.crankEpochStep}. */
|
|
127
127
|
export interface CrankEpochStepOptions {
|
|
128
128
|
/** Gateways per tally/distribute batch. Default 30. */
|
|
@@ -776,6 +776,8 @@ export declare class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
776
776
|
weightsTallied: number;
|
|
777
777
|
prescriptionsDone: number;
|
|
778
778
|
rewardsDistributed: number;
|
|
779
|
+
observationsSubmitted: number;
|
|
780
|
+
observationsClosed: number;
|
|
779
781
|
activeGatewayCount: number;
|
|
780
782
|
endTimestamp: number;
|
|
781
783
|
} | null>;
|
|
@@ -788,10 +790,13 @@ export declare class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
788
790
|
* [8 per_obs][8 reward_rate][8 weight_lo][8 weight_hi][32 hashchain]
|
|
789
791
|
* [4 active_gw_count][4 dist_idx][4 tally_idx]
|
|
790
792
|
* [1 observer_count][1 name_count][1 obs_submitted][1 rewards_dist]
|
|
791
|
-
* [1 weights_tallied][1 prescriptions_done][1 bump][1
|
|
793
|
+
* [1 weights_tallied][1 prescriptions_done][1 bump][1 obs_closed]
|
|
792
794
|
* [6000 failure_counts][1600 prescribed_observers]
|
|
793
795
|
* [1600 prescribed_observer_gateways][64 prescribed_names]
|
|
794
796
|
* [7 has_observed][5 _pad2]
|
|
797
|
+
*
|
|
798
|
+
* NOTE: byte +123 is `observations_closed` (NOT padding) — `close_epoch`
|
|
799
|
+
* reverts with EpochObservationsNotClosed until it equals obs_submitted.
|
|
795
800
|
*/
|
|
796
801
|
private fetchEpochRawFields;
|
|
797
802
|
/**
|
|
@@ -856,6 +861,18 @@ export declare class SolanaARIOWriteable extends SolanaARIOReadable {
|
|
|
856
861
|
epochIndex: number;
|
|
857
862
|
observer: string;
|
|
858
863
|
}, _options?: WriteOptions): Promise<MessageResult>;
|
|
864
|
+
/**
|
|
865
|
+
* Close multiple Observation PDAs for one epoch in a single tx (each
|
|
866
|
+
* `close_observation` increments the parent Epoch's `observations_closed`).
|
|
867
|
+
* Permissionless; rent is refunded to the payer. Used by the crank to satisfy
|
|
868
|
+
* `close_epoch`'s `observations_closed == observations_submitted` precondition
|
|
869
|
+
* before closing a retention-aged epoch. Keep the batch small — each ix carries
|
|
870
|
+
* the Epoch + Observation + payer + system accounts.
|
|
871
|
+
*/
|
|
872
|
+
closeObservations(params: {
|
|
873
|
+
epochIndex: number;
|
|
874
|
+
observers: string[];
|
|
875
|
+
}, _options?: WriteOptions): Promise<MessageResult>;
|
|
859
876
|
/**
|
|
860
877
|
* Close an empty Delegation PDA (`amount == 0`) and refund rent to the
|
|
861
878
|
* original delegator (NOT the caller — see GAR-016, prevents griefing).
|
package/lib/types/version.d.ts
CHANGED