feat: add support for slot update pubsub subscriptions (#16990)
This commit is contained in:
parent
8e561354d5
commit
da81ad0c41
|
@ -1056,6 +1056,112 @@ const SlotNotificationResult = pick({
|
||||||
result: SlotInfoResult,
|
result: SlotInfoResult,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slot updates which can be used for tracking the live progress of a cluster.
|
||||||
|
* - `"firstShredReceived"`: connected node received the first shred of a block.
|
||||||
|
* Indicates that a new block that is being produced.
|
||||||
|
* - `"completed"`: connected node has received all shreds of a block. Indicates
|
||||||
|
* a block was recently produced.
|
||||||
|
* - `"optimisticConfirmation"`: block was optimistically confirmed by the
|
||||||
|
* cluster. It is not guaranteed that an optimistic confirmation notification
|
||||||
|
* will be sent for every finalized blocks.
|
||||||
|
* - `"root"`: the connected node rooted this block.
|
||||||
|
* - `"createdBank"`: the connected node has started validating this block.
|
||||||
|
* - `"frozen"`: the connected node has validated this block.
|
||||||
|
* - `"dead"`: the connected node failed to validate this block.
|
||||||
|
*/
|
||||||
|
export type SlotUpdate =
|
||||||
|
| {
|
||||||
|
type: 'firstShredReceived';
|
||||||
|
slot: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'completed';
|
||||||
|
slot: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'createdBank';
|
||||||
|
slot: number;
|
||||||
|
timestamp: number;
|
||||||
|
parent: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'frozen';
|
||||||
|
slot: number;
|
||||||
|
timestamp: number;
|
||||||
|
stats: {
|
||||||
|
numTransactionEntries: number;
|
||||||
|
numSuccessfulTransactions: number;
|
||||||
|
numFailedTransactions: number;
|
||||||
|
maxTransactionsPerEntry: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'dead';
|
||||||
|
slot: number;
|
||||||
|
timestamp: number;
|
||||||
|
err: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'optimisticConfirmation';
|
||||||
|
slot: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'root';
|
||||||
|
slot: number;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
const SlotUpdateResult = union([
|
||||||
|
pick({
|
||||||
|
type: union([
|
||||||
|
literal('firstShredReceived'),
|
||||||
|
literal('completed'),
|
||||||
|
literal('optimisticConfirmation'),
|
||||||
|
literal('root'),
|
||||||
|
]),
|
||||||
|
slot: number(),
|
||||||
|
timestamp: number(),
|
||||||
|
}),
|
||||||
|
pick({
|
||||||
|
type: literal('createdBank'),
|
||||||
|
parent: number(),
|
||||||
|
slot: number(),
|
||||||
|
timestamp: number(),
|
||||||
|
}),
|
||||||
|
pick({
|
||||||
|
type: literal('frozen'),
|
||||||
|
slot: number(),
|
||||||
|
timestamp: number(),
|
||||||
|
stats: pick({
|
||||||
|
numTransactionEntries: number(),
|
||||||
|
numSuccessfulTransactions: number(),
|
||||||
|
numFailedTransactions: number(),
|
||||||
|
maxTransactionsPerEntry: number(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
pick({
|
||||||
|
type: literal('dead'),
|
||||||
|
slot: number(),
|
||||||
|
timestamp: number(),
|
||||||
|
err: string(),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expected JSON RPC response for the "slotsUpdatesNotification" message
|
||||||
|
*/
|
||||||
|
const SlotUpdateNotificationResult = pick({
|
||||||
|
subscription: number(),
|
||||||
|
result: SlotUpdateResult,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expected JSON RPC response for the "signatureNotification" message
|
* Expected JSON RPC response for the "signatureNotification" message
|
||||||
*/
|
*/
|
||||||
|
@ -1569,6 +1675,19 @@ type SlotSubscriptionInfo = {
|
||||||
subscriptionId: SubscriptionId | null; // null when there's no current server subscription id
|
subscriptionId: SubscriptionId | null; // null when there's no current server subscription id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function for slot update notifications
|
||||||
|
*/
|
||||||
|
export type SlotUpdateCallback = (slotUpdate: SlotUpdate) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
type SlotUpdateSubscriptionInfo = {
|
||||||
|
callback: SlotUpdateCallback;
|
||||||
|
subscriptionId: SubscriptionId | null; // null when there's no current server subscription id
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback function for signature status notifications
|
* Callback function for signature status notifications
|
||||||
*/
|
*/
|
||||||
|
@ -1815,6 +1934,11 @@ export class Connection {
|
||||||
[id: number]: LogsSubscriptionInfo;
|
[id: number]: LogsSubscriptionInfo;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
/** @internal */ _slotUpdateSubscriptionCounter: number = 0;
|
||||||
|
/** @internal */ _slotUpdateSubscriptions: {
|
||||||
|
[id: number]: SlotUpdateSubscriptionInfo;
|
||||||
|
} = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establish a JSON RPC connection
|
* Establish a JSON RPC connection
|
||||||
*
|
*
|
||||||
|
@ -1887,6 +2011,10 @@ export class Connection {
|
||||||
'slotNotification',
|
'slotNotification',
|
||||||
this._wsOnSlotNotification.bind(this),
|
this._wsOnSlotNotification.bind(this),
|
||||||
);
|
);
|
||||||
|
this._rpcWebSocket.on(
|
||||||
|
'slotsUpdatesNotification',
|
||||||
|
this._wsOnSlotUpdatesNotification.bind(this),
|
||||||
|
);
|
||||||
this._rpcWebSocket.on(
|
this._rpcWebSocket.on(
|
||||||
'signatureNotification',
|
'signatureNotification',
|
||||||
this._wsOnSignatureNotification.bind(this),
|
this._wsOnSignatureNotification.bind(this),
|
||||||
|
@ -3323,13 +3451,16 @@ export class Connection {
|
||||||
Object.values(this._programAccountChangeSubscriptions).forEach(
|
Object.values(this._programAccountChangeSubscriptions).forEach(
|
||||||
s => (s.subscriptionId = null),
|
s => (s.subscriptionId = null),
|
||||||
);
|
);
|
||||||
|
Object.values(this._rootSubscriptions).forEach(
|
||||||
|
s => (s.subscriptionId = null),
|
||||||
|
);
|
||||||
Object.values(this._signatureSubscriptions).forEach(
|
Object.values(this._signatureSubscriptions).forEach(
|
||||||
s => (s.subscriptionId = null),
|
s => (s.subscriptionId = null),
|
||||||
);
|
);
|
||||||
Object.values(this._slotSubscriptions).forEach(
|
Object.values(this._slotSubscriptions).forEach(
|
||||||
s => (s.subscriptionId = null),
|
s => (s.subscriptionId = null),
|
||||||
);
|
);
|
||||||
Object.values(this._rootSubscriptions).forEach(
|
Object.values(this._slotUpdateSubscriptions).forEach(
|
||||||
s => (s.subscriptionId = null),
|
s => (s.subscriptionId = null),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3345,6 +3476,9 @@ export class Connection {
|
||||||
this._programAccountChangeSubscriptions,
|
this._programAccountChangeSubscriptions,
|
||||||
).map(Number);
|
).map(Number);
|
||||||
const slotKeys = Object.keys(this._slotSubscriptions).map(Number);
|
const slotKeys = Object.keys(this._slotSubscriptions).map(Number);
|
||||||
|
const slotUpdateKeys = Object.keys(this._slotUpdateSubscriptions).map(
|
||||||
|
Number,
|
||||||
|
);
|
||||||
const signatureKeys = Object.keys(this._signatureSubscriptions).map(Number);
|
const signatureKeys = Object.keys(this._signatureSubscriptions).map(Number);
|
||||||
const rootKeys = Object.keys(this._rootSubscriptions).map(Number);
|
const rootKeys = Object.keys(this._rootSubscriptions).map(Number);
|
||||||
const logsKeys = Object.keys(this._logsSubscriptions).map(Number);
|
const logsKeys = Object.keys(this._logsSubscriptions).map(Number);
|
||||||
|
@ -3352,6 +3486,7 @@ export class Connection {
|
||||||
accountKeys.length === 0 &&
|
accountKeys.length === 0 &&
|
||||||
programKeys.length === 0 &&
|
programKeys.length === 0 &&
|
||||||
slotKeys.length === 0 &&
|
slotKeys.length === 0 &&
|
||||||
|
slotUpdateKeys.length === 0 &&
|
||||||
signatureKeys.length === 0 &&
|
signatureKeys.length === 0 &&
|
||||||
rootKeys.length === 0 &&
|
rootKeys.length === 0 &&
|
||||||
logsKeys.length === 0
|
logsKeys.length === 0
|
||||||
|
@ -3400,6 +3535,11 @@ export class Connection {
|
||||||
this._subscribe(sub, 'slotSubscribe', []);
|
this._subscribe(sub, 'slotSubscribe', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let id of slotUpdateKeys) {
|
||||||
|
const sub = this._slotUpdateSubscriptions[id];
|
||||||
|
this._subscribe(sub, 'slotsUpdatesSubscribe', []);
|
||||||
|
}
|
||||||
|
|
||||||
for (let id of signatureKeys) {
|
for (let id of signatureKeys) {
|
||||||
const sub = this._signatureSubscriptions[id];
|
const sub = this._signatureSubscriptions[id];
|
||||||
const args: any[] = [sub.signature];
|
const args: any[] = [sub.signature];
|
||||||
|
@ -3639,6 +3779,49 @@ export class Connection {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
_wsOnSlotUpdatesNotification(notification: Object) {
|
||||||
|
const res = create(notification, SlotUpdateNotificationResult);
|
||||||
|
for (const sub of Object.values(this._slotUpdateSubscriptions)) {
|
||||||
|
if (sub.subscriptionId === res.subscription) {
|
||||||
|
sub.callback(res.result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a callback to be invoked upon slot updates. {@link SlotUpdate}'s
|
||||||
|
* may be useful to track live progress of a cluster.
|
||||||
|
*
|
||||||
|
* @param callback Function to invoke whenever the slot updates
|
||||||
|
* @return subscription id
|
||||||
|
*/
|
||||||
|
onSlotUpdate(callback: SlotUpdateCallback): number {
|
||||||
|
const id = ++this._slotUpdateSubscriptionCounter;
|
||||||
|
this._slotUpdateSubscriptions[id] = {
|
||||||
|
callback,
|
||||||
|
subscriptionId: null,
|
||||||
|
};
|
||||||
|
this._updateSubscriptions();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deregister a slot update notification callback
|
||||||
|
*
|
||||||
|
* @param id subscription id to deregister
|
||||||
|
*/
|
||||||
|
async removeSlotUpdateListener(id: number): Promise<void> {
|
||||||
|
if (this._slotUpdateSubscriptions[id]) {
|
||||||
|
const subInfo = this._slotUpdateSubscriptions[id];
|
||||||
|
delete this._slotUpdateSubscriptions[id];
|
||||||
|
await this._unsubscribe(subInfo, 'slotsUpdatesUnsubscribe');
|
||||||
|
this._updateSubscriptions();
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown slot update id: ${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_buildArgs(
|
_buildArgs(
|
||||||
args: Array<any>,
|
args: Array<any>,
|
||||||
override?: Commitment,
|
override?: Commitment,
|
||||||
|
|
Loading…
Reference in New Issue