@xyo-network/chain-producer 1.19.0 → 1.19.2

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.
@@ -6,6 +6,17 @@ export type ProducerActorParams = ActorParams<{
6
6
  rewardAddress: Address;
7
7
  }>;
8
8
  export declare class ProducerActor extends Actor<ProducerActorParams> {
9
+ /**
10
+ * The interval time (in MS) between block production attempts.
11
+ */
12
+ static readonly BlockSubmissionCheckInterval = 1500;
13
+ /**
14
+ * The threshold time (in MS) for resubmitting the same block number if the head has not changed.
15
+ */
16
+ static readonly HeadResubmissionThreshold: number;
17
+ /**
18
+ * The window (in blocks) before expiration to attempt redeclaration of producer intent.
19
+ */
9
20
  static readonly RedeclarationWindow = 10000;
10
21
  protected _lastProducedBlock?: HydratedBlockWithHashMeta;
11
22
  protected _lastRedeclarationIntent?: ChainStakeIntent;
@@ -18,6 +29,8 @@ export declare class ProducerActor extends Actor<ProducerActorParams> {
18
29
  private _blockRunner?;
19
30
  private _blockViewer?;
20
31
  private _chainId?;
32
+ private _lastHeadChangeTime?;
33
+ private _lastHeadHash?;
21
34
  private _mempoolRunner?;
22
35
  private _mempoolViewer?;
23
36
  private _produceBlockMutex;
@@ -1 +1 @@
1
- {"version":3,"file":"ProducerActor.d.ts","sourceRoot":"","sources":["../../src/ProducerActor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAQ7C,OAAO,KAAK,EACD,gBAAgB,EAAE,yBAAyB,EAAE,cAAc,EACrE,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,oBAAoB,EAA+B,KAAK,EAAE,WAAW,EAAoB,WAAW,EAAsB,WAAW,EACjB,aAAa,EACjI,aAAa,EAA4F,iBAAiB,EAE3H,MAAM,sBAAsB,CAAA;AAG7B,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC;IAC5C,aAAa,EAAE,OAAO,CAAA;CACvB,CAAC,CAAA;AAKF,qBACa,aAAc,SAAQ,KAAK,CAAC,mBAAmB,CAAC;IAC3D,MAAM,CAAC,QAAQ,CAAC,mBAAmB,SAAS;IAE5C,SAAS,CAAC,kBAAkB,CAAC,EAAE,yBAAyB,CAAA;IACxD,SAAS,CAAC,wBAAwB,CAAC,EAAE,gBAAgB,CAAA;IACrD,SAAS,CAAC,iBAAiB,CAAC,EAAE,UAAU,CAAA;IACxC,SAAS,CAAC,qCAAqC,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAA;IACrE,SAAS,CAAC,mCAAmC,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAA;IACnE,SAAS,CAAC,4BAA4B,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAA;IAC5D,SAAS,CAAC,6BAA6B,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAA;IAE7D,OAAO,CAAC,qBAAqB,CAAC,CAAsB;IACpD,OAAO,CAAC,YAAY,CAAC,CAAa;IAClC,OAAO,CAAC,YAAY,CAAC,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAC,CAAe;IACtC,OAAO,CAAC,cAAc,CAAC,CAAe;IAEtC,OAAO,CAAC,kBAAkB,CAAc;IAExC,OAAO,CAAC,kBAAkB,CAAC,CAAmB;IAE9C,SAAS,KAAK,oBAAoB,yBAEjC;IAED,SAAS,KAAK,WAAW,gBAExB;IAED,SAAS,KAAK,WAAW,+BAExB;IAED,SAAS,KAAK,OAAO,wCAEpB;IAED,SAAS,KAAK,aAAa,kBAE1B;IAED,SAAS,KAAK,aAAa,kBAE1B;IAED,SAAS,KAAK,aAAa;;;;MAE1B;IAED,SAAS,KAAK,iBAAiB,sBAE9B;WAEqB,aAAa,CAAC,CAAC,SAAS,aAAa,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;qBAwB1D,WAAW,CACzC,MAAM,EAAE,mBAAmB;IA0Bd,aAAa;IAgDb,YAAY;IAe3B,SAAS,CAAC,iDAAiD,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;cAIzE,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAwC7B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;cAiEhC,yBAAyB,CAAC,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;cAkB7G,sBAAsB,IAAI,OAAO,CAAC,OAAO,CAAC;cAe1C,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;CAWzD"}
1
+ {"version":3,"file":"ProducerActor.d.ts","sourceRoot":"","sources":["../../src/ProducerActor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAS7C,OAAO,KAAK,EAC8B,gBAAgB,EAAE,yBAAyB,EAAE,cAAc,EACpG,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,oBAAoB,EAA+B,KAAK,EAAE,WAAW,EAAoB,WAAW,EAAsB,WAAW,EACjB,aAAa,EACjI,aAAa,EAA4F,iBAAiB,EAE3H,MAAM,sBAAsB,CAAA;AAG7B,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC;IAC5C,aAAa,EAAE,OAAO,CAAA;CACvB,CAAC,CAAA;AAcF,qBACa,aAAc,SAAQ,KAAK,CAAC,mBAAmB,CAAC;IAC3D;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,4BAA4B,QAAO;IAEnD;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,yBAAyB,SAAkD;IAE3F;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,mBAAmB,SAAS;IAE5C,SAAS,CAAC,kBAAkB,CAAC,EAAE,yBAAyB,CAAA;IACxD,SAAS,CAAC,wBAAwB,CAAC,EAAE,gBAAgB,CAAA;IACrD,SAAS,CAAC,iBAAiB,CAAC,EAAE,UAAU,CAAA;IACxC,SAAS,CAAC,qCAAqC,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAA;IACrE,SAAS,CAAC,mCAAmC,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAA;IACnE,SAAS,CAAC,4BAA4B,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAA;IAC5D,SAAS,CAAC,6BAA6B,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAA;IAE7D,OAAO,CAAC,qBAAqB,CAAC,CAAsB;IACpD,OAAO,CAAC,YAAY,CAAC,CAAa;IAClC,OAAO,CAAC,YAAY,CAAC,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,mBAAmB,CAAC,CAAQ;IACpC,OAAO,CAAC,aAAa,CAAC,CAAQ;IAC9B,OAAO,CAAC,cAAc,CAAC,CAAe;IACtC,OAAO,CAAC,cAAc,CAAC,CAAe;IACtC,OAAO,CAAC,kBAAkB,CAAc;IACxC,OAAO,CAAC,kBAAkB,CAAC,CAAmB;IAE9C,SAAS,KAAK,oBAAoB,yBAEjC;IAED,SAAS,KAAK,WAAW,gBAExB;IAED,SAAS,KAAK,WAAW,+BAExB;IAED,SAAS,KAAK,OAAO,wCAEpB;IAED,SAAS,KAAK,aAAa,kBAE1B;IAED,SAAS,KAAK,aAAa,kBAE1B;IAED,SAAS,KAAK,aAAa;;;;MAE1B;IAED,SAAS,KAAK,iBAAiB,sBAE9B;WAEqB,aAAa,CAAC,CAAC,SAAS,aAAa,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;qBAwB1D,WAAW,CACzC,MAAM,EAAE,mBAAmB;IA0Bd,aAAa;IAgDb,YAAY;IAe3B,SAAS,CAAC,iDAAiD,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;cAIzE,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAgF7B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;cAiEhC,yBAAyB,CAAC,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;cAkB7G,sBAAsB,IAAI,OAAO,CAAC,OAAO,CAAC;cAe1C,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;CAWzD"}
@@ -18,10 +18,26 @@ function _ts_decorate(decorators, target, key, desc) {
18
18
  __name(_ts_decorate, "_ts_decorate");
19
19
  var SHOULD_REGISTER_REDECLARATION_INTENT_TIMER = true;
20
20
  var TEN_MINUTES = 10 * 60 * 1e3;
21
+ var toFormattedBlockReference = /* @__PURE__ */ __name((blockBoundWitness) => {
22
+ return `${blockBoundWitness.block} [${toHex(blockBoundWitness._hash, {
23
+ prefix: true
24
+ })}]`;
25
+ }, "toFormattedBlockReference");
21
26
  var ProducerActor = class _ProducerActor extends Actor {
22
27
  static {
23
28
  __name(this, "ProducerActor");
24
29
  }
30
+ /**
31
+ * The interval time (in MS) between block production attempts.
32
+ */
33
+ static BlockSubmissionCheckInterval = 1500;
34
+ /**
35
+ * The threshold time (in MS) for resubmitting the same block number if the head has not changed.
36
+ */
37
+ static HeadResubmissionThreshold = _ProducerActor.BlockSubmissionCheckInterval * 30;
38
+ /**
39
+ * The window (in blocks) before expiration to attempt redeclaration of producer intent.
40
+ */
25
41
  static RedeclarationWindow = 1e4;
26
42
  _lastProducedBlock;
27
43
  _lastRedeclarationIntent;
@@ -34,6 +50,8 @@ var ProducerActor = class _ProducerActor extends Actor {
34
50
  _blockRunner;
35
51
  _blockViewer;
36
52
  _chainId;
53
+ _lastHeadChangeTime;
54
+ _lastHeadHash;
37
55
  _mempoolRunner;
38
56
  _mempoolViewer;
39
57
  _produceBlockMutex = new Mutex();
@@ -144,15 +162,9 @@ var ProducerActor = class _ProducerActor extends Actor {
144
162
  }
145
163
  async startHandler() {
146
164
  await super.startHandler();
147
- this.registerTimer(
148
- "BlockProductionTimer",
149
- async () => {
150
- await this.produceBlock();
151
- },
152
- 2e3,
153
- 1500
154
- /* 500 */
155
- );
165
+ this.registerTimer("BlockProductionTimer", async () => {
166
+ await this.produceBlock();
167
+ }, 2e3, _ProducerActor.BlockSubmissionCheckInterval);
156
168
  if (SHOULD_REGISTER_REDECLARATION_INTENT_TIMER) {
157
169
  this.registerTimer("ProducerRedeclarationTimer", async () => {
158
170
  await this.redeclareIntent();
@@ -172,24 +184,44 @@ var ProducerActor = class _ProducerActor extends Actor {
172
184
  await this._produceBlockMutex.runExclusive(async () => {
173
185
  const head = (await this.blockViewer.currentBlock())[0];
174
186
  const headHash = head._hash;
175
- if (this._lastProducedBlock && this._lastProducedBlock[0].previous === headHash) {
176
- this.logger?.log("Block already produced:", `0x${toHex(this._lastProducedBlock[0].block)}`, this._lastProducedBlock[0].block);
177
- } else {
187
+ const currentTime = Date.now();
188
+ if (this._lastHeadHash !== headHash) {
189
+ const lastHeadHashHex = isDefined(this._lastHeadHash) ? `0x${this._lastHeadHash}` : "undefined";
190
+ const currentHeadHashHex = `0x${headHash}`;
191
+ this.logger?.log(`Found updated head ${lastHeadHashHex} -> ${currentHeadHashHex}`);
192
+ this._lastHeadHash = headHash;
193
+ this._lastHeadChangeTime = currentTime;
194
+ }
195
+ const timeSinceHeadChange = isDefined(this._lastHeadChangeTime) ? currentTime - this._lastHeadChangeTime : 0;
196
+ const shouldSubmit = !this._lastProducedBlock || this._lastProducedBlock[0].previous !== headHash;
197
+ const shouldResubmit = timeSinceHeadChange > _ProducerActor.HeadResubmissionThreshold;
198
+ const shouldSubmitBlock = shouldSubmit || shouldResubmit;
199
+ if (shouldSubmitBlock) {
200
+ if (shouldResubmit) {
201
+ this.logger?.log(`Resubmitting block due to stale head. Head ${toFormattedBlockReference(head)} unchanged for ${timeSinceHeadChange}ms`);
202
+ this._lastHeadChangeTime = currentTime;
203
+ }
178
204
  this._producerActorBlockProductionAttempts?.add(1, this._metricAttributes);
179
205
  const nextBlock = await this.blockRunner.next(head);
180
206
  if (nextBlock) {
181
- const displayBlockNumber = `0x${toHex(nextBlock[0].block)}`;
207
+ const displayBlockNumber = toFormattedBlockReference(nextBlock[0]);
182
208
  this.logger?.log("Produced block:", displayBlockNumber);
183
209
  this._producerActorBlocksProduced?.add(1, this._metricAttributes);
184
210
  await this.mempoolRunner.submitBlocks([
185
211
  nextBlock
186
212
  ]);
187
- this.logger?.log("Published block:", displayBlockNumber, nextBlock[0].block);
213
+ this.logger?.log("Published block:", displayBlockNumber);
188
214
  this._producerActorBlocksPublished?.add(1, this._metricAttributes);
189
215
  this._lastProducedBlock = nextBlock;
190
216
  } else {
191
- this.logger?.log("No block produced at this time.");
217
+ this.logger?.log("No block produced for submission.");
218
+ }
219
+ } else {
220
+ let nonSubmissionMessage = "No block submission required at this time";
221
+ if (isDefined(this._lastProducedBlock)) {
222
+ nonSubmissionMessage = nonSubmissionMessage + `, already produced: ${toFormattedBlockReference(this._lastProducedBlock[0])}`;
192
223
  }
224
+ this.logger?.log(`${nonSubmissionMessage}.`);
193
225
  }
194
226
  });
195
227
  }, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ProducerActor.ts","../../src/runProducer.ts"],"sourcesContent":["import type { Attributes, Counter } from '@opentelemetry/api'\nimport type { Address } from '@xylabs/sdk-js'\nimport {\n asAddress, assertEx, creatable, isDefined, isUndefined, toHex,\n} from '@xylabs/sdk-js'\nimport { EvmChainContractViewer, EvmStakeTotalsViewer } from '@xyo-network/chain-ethereum'\nimport { initEvmProvider, initProducerAccount } from '@xyo-network/chain-orchestration'\nimport { SimpleBlockRunner } from '@xyo-network/chain-services'\nimport { validateHydratedBlock, validateHydratedBlockState } from '@xyo-network/chain-validation'\nimport type {\n ChainId, ChainStakeIntent, HydratedBlockWithHashMeta, XL1BlockNumber,\n} from '@xyo-network/xl1-sdk'\nimport {\n AccountBalanceViewer, AccountBalanceViewerMoniker, Actor, ActorParams, asXL1BlockNumber, BlockRunner, BlockRunnerMoniker, BlockViewer, BlockViewerMoniker,\n buildJsonRpcProviderLocator, buildTransaction, Config, createDeclarationIntent, getDefaultConfig, HttpRpcTransport, MempoolRunner, MempoolRunnerMoniker,\n MempoolViewer, MempoolViewerMoniker, RpcSchemaMap, SimpleBlockRewardViewer, SimpleBlockValidationViewer, StakeTotalsViewer, StakeTotalsViewerMoniker,\n TransportFactory,\n} from '@xyo-network/xl1-sdk'\nimport { Mutex } from 'async-mutex'\n\nexport type ProducerActorParams = ActorParams<{\n rewardAddress: Address\n}>\n\nconst SHOULD_REGISTER_REDECLARATION_INTENT_TIMER = true\nconst TEN_MINUTES = 10 * 60 * 1000 // 10 minutes in milliseconds\n\n@creatable()\nexport class ProducerActor extends Actor<ProducerActorParams> {\n static readonly RedeclarationWindow = 10_000\n\n protected _lastProducedBlock?: HydratedBlockWithHashMeta\n protected _lastRedeclarationIntent?: ChainStakeIntent\n protected _metricAttributes?: Attributes\n protected _producerActorBlockProductionAttempts?: Counter<Attributes>\n protected _producerActorBlockProductionChecks?: Counter<Attributes>\n protected _producerActorBlocksProduced?: Counter<Attributes>\n protected _producerActorBlocksPublished?: Counter<Attributes>\n\n private _accountBalanceViewer?: AccountBalanceViewer\n private _blockRunner?: BlockRunner\n private _blockViewer?: BlockViewer\n private _chainId?: ChainId\n private _mempoolRunner?: MempoolRunner\n private _mempoolViewer?: MempoolViewer\n\n private _produceBlockMutex = new Mutex()\n\n private _stakeTotalsViewer?: StakeTotalsViewer\n\n protected get accountBalanceViewer() {\n return this._accountBalanceViewer!\n }\n\n protected get blockRunner() {\n return this._blockRunner!\n }\n\n protected get blockViewer() {\n return this._blockViewer!\n }\n\n protected get chainId() {\n return this._chainId!\n }\n\n protected get mempoolRunner() {\n return this._mempoolRunner!\n }\n\n protected get mempoolViewer() {\n return this._mempoolViewer!\n }\n\n protected get rewardAddress() {\n return this.params.rewardAddress!\n }\n\n protected get stakeTotalsViewer() {\n return this._stakeTotalsViewer!\n }\n\n static override async paramsHandler<T extends ProducerActor>(params?: Partial<T['params']>) {\n const logger = params?.context?.logger\n const config = params?.context?.config ?? getDefaultConfig()\n const displayName = params?.displayName ?? 'ProducerActor'\n const account = params?.account ?? await initProducerAccount({ config })\n const id = account?.address.toString() ?? 'unknown'\n logger?.info(`Running producer for account ${account.address}`)\n const rewardAddress = params?.rewardAddress ?? account.address\n logger?.info(`Using reward address ${rewardAddress}`)\n if (rewardAddress === account.address) {\n logger?.warn(`Using producer account address as reward address: ${account.address}`)\n }\n\n return {\n ...await super.paramsHandler({\n ...params,\n account,\n displayName,\n id,\n }),\n rewardAddress,\n }\n }\n\n protected static override async initContext(\n params: ProducerActorParams,\n ) {\n const config: Config = params?.context?.config ?? getDefaultConfig()\n const endpoint = assertEx(config.services.apiEndpoint, () => 'API endpoint is required in config.services.apiEndpoint')\n\n const context = await super.initContext(params)\n\n const chainId = assertEx(config.chain.id, () => 'Chain ID is required in config.chain.id')\n\n const transportFactory: TransportFactory = (schemas: RpcSchemaMap) => new HttpRpcTransport(endpoint, schemas)\n const locator = await buildJsonRpcProviderLocator({ context, transportFactory })\n const runner = await initEvmProvider({ config })\n locator.register(EvmChainContractViewer.factory<EvmChainContractViewer>(EvmChainContractViewer.dependencies, { address: asAddress(chainId), runner }))\n locator.register(EvmStakeTotalsViewer.factory<EvmStakeTotalsViewer>(EvmStakeTotalsViewer.dependencies, { address: asAddress(chainId), runner }))\n locator.register(SimpleBlockRewardViewer.factory<SimpleBlockRewardViewer>(SimpleBlockRewardViewer.dependencies))\n locator.register(SimpleBlockValidationViewer.factory<SimpleBlockValidationViewer>(\n SimpleBlockValidationViewer.dependencies,\n { state: validateHydratedBlockState, protocol: validateHydratedBlock },\n ))\n locator.register(SimpleBlockRunner.factory<SimpleBlockRunner>(\n SimpleBlockRunner.dependencies,\n { account: params.account, rewardAddress: params.rewardAddress },\n ))\n return locator.context\n }\n\n override async createHandler() {\n await super.createHandler()\n // Create the consistent meter attributes that will\n // be included with all metrics from this actor\n this._metricAttributes = { address: this.account.address.toString() }\n // Create the metrics\n this._producerActorBlockProductionChecks = this.meter?.createCounter(\n 'producer_actor_block_production_checks',\n { description: 'Number of block production checks' },\n )\n this._producerActorBlockProductionAttempts = this.meter?.createCounter(\n 'producer_actor_block_production_attempts',\n { description: 'Number of block production attempts' },\n )\n this._producerActorBlocksProduced = this.meter?.createCounter(\n 'producer_actor_blocks_produced',\n { description: 'Number of blocks produced' },\n )\n this._producerActorBlocksPublished = this.meter?.createCounter(\n 'producer_actor_blocks_published',\n { description: 'Number of blocks published' },\n )\n this._accountBalanceViewer = assertEx(\n await this.locator?.getInstance<AccountBalanceViewer>(AccountBalanceViewerMoniker),\n () => 'Unable to locate AccountBalanceViewer',\n )\n this._blockRunner = assertEx(\n await this.locator?.getInstance<BlockRunner>(BlockRunnerMoniker),\n () => 'Unable to locate BlockRunner',\n )\n this._blockViewer = assertEx(\n await this.locator?.getInstance<BlockViewer>(BlockViewerMoniker),\n () => 'Unable to locate BlockViewer',\n )\n this._mempoolRunner = assertEx(\n await this.locator?.getInstance<MempoolRunner>(MempoolRunnerMoniker),\n () => 'Unable to locate MempoolRunner',\n )\n this._mempoolViewer = assertEx(\n await this.locator?.getInstance<MempoolViewer>(MempoolViewerMoniker),\n () => 'Unable to locate MempoolViewer',\n )\n this._stakeTotalsViewer = assertEx(\n await this.locator?.getInstance<StakeTotalsViewer>(StakeTotalsViewerMoniker),\n () => 'Unable to locate StakeTotalsViewer',\n )\n }\n\n override async startHandler() {\n await super.startHandler()\n // Register a timer to check if we should produce a block\n this.registerTimer('BlockProductionTimer', async () => {\n await this.produceBlock()\n }, 2000, 1500/* 500 */)\n\n if (SHOULD_REGISTER_REDECLARATION_INTENT_TIMER) {\n // Register a timer to check if we should redeclare the producer\n this.registerTimer('ProducerRedeclarationTimer', async () => {\n await this.redeclareIntent()\n }, TEN_MINUTES, TEN_MINUTES)\n }\n }\n\n protected calculateBlocksUntilProducerDeclarationExpiration(currentBlock: number): number {\n return (this._lastRedeclarationIntent?.exp ?? currentBlock) - currentBlock\n }\n\n protected async produceBlock(): Promise<void> {\n this._producerActorBlockProductionChecks?.add(1, this._metricAttributes)\n await this.spanAsync('produceBlock', async () => {\n if (this._produceBlockMutex.isLocked()) {\n this.logger?.log('Skipping block production, previous production still in progress')\n return\n }\n\n await this._produceBlockMutex.runExclusive(async () => {\n // Get the updated head\n const head = (await this.blockViewer.currentBlock())[0]\n // Check if we've already produced the next block for this head\n const headHash = head._hash\n // If our last produced block was the next block for the current head, we do not\n // need to produce another. This prevents duplicate blocks from being produced\n if (this._lastProducedBlock && this._lastProducedBlock[0].previous === headHash) {\n this.logger?.log('Block already produced:', `0x${toHex(this._lastProducedBlock[0].block)}`, this._lastProducedBlock[0].block)\n } else {\n this._producerActorBlockProductionAttempts?.add(1, this._metricAttributes)\n // Produce the next block\n const nextBlock = await this.blockRunner.next(head)\n // If it was produced\n if (nextBlock) {\n const displayBlockNumber = `0x${toHex(nextBlock[0].block)}`\n this.logger?.log('Produced block:', displayBlockNumber)\n this._producerActorBlocksProduced?.add(1, this._metricAttributes)\n // Insert the block into the chain\n await this.mempoolRunner.submitBlocks([nextBlock])\n this.logger?.log('Published block:', displayBlockNumber, nextBlock[0].block)\n this._producerActorBlocksPublished?.add(1, this._metricAttributes)\n // Record that we have produced a block so we do not produce it again\n this._lastProducedBlock = nextBlock\n } else {\n this.logger?.log('No block produced at this time.')\n }\n }\n })\n }, { ...this.context, timeBudgetLimit: 1000 })\n }\n\n protected async redeclareIntent(): Promise<void> {\n await this.spanAsync('redeclareIntent', async () => {\n // Decide if we should redeclare intent\n if (this.config.producer.disableIntentRedeclaration) return\n\n // Get the current block\n const head = (await this.blockViewer.currentBlock())[0]\n if (isUndefined(head)) return\n const currentBlock = head.block\n\n // // Calculate the time until the producer's declaration expires\n const blocksUntilExpiration = this.calculateBlocksUntilProducerDeclarationExpiration(currentBlock)\n\n // Allow the producer time to redeclare itself via block production\n // (for free) before submitting a redeclaration intent transaction.\n if (blocksUntilExpiration > ProducerActor.RedeclarationWindow * 0.1) {\n // Clear any previous redeclaration intent\n this._lastRedeclarationIntent = undefined\n // No need to redeclare yet\n return\n }\n\n // If we already have a valid redeclaration intent, do not create another\n // unless it has expired.\n if (this._lastRedeclarationIntent) {\n // Check if the last redeclaration intent is still valid\n if (this._lastRedeclarationIntent.exp > currentBlock) return\n // If it has expired, clear the last redeclaration intent\n this._lastRedeclarationIntent = undefined\n }\n\n // Check if we have a valid balance before declaring intent\n if (!await this.validateCurrentBalance()) {\n this.logger?.error(\n `Add balance to address ${this.account.address} for the producer to declare it's intent.`,\n )\n return\n }\n\n // Check if we have a valid stake before declaring intent\n if (!(await this.validateCurrentStake())) {\n this.logger?.error(\n `Add stake to contract address ${this.config.chain.id}`\n + ' for the producer to declare it\\'s intent.',\n )\n return\n }\n\n // Create a redeclaration intent\n this.logger?.log('Creating redeclaration intent for producer:', this.account.address)\n const redeclarationIntent = createDeclarationIntent(\n this.account.address,\n 'producer',\n currentBlock,\n currentBlock + SimpleBlockRunner.RedeclarationDuration,\n )\n\n // Submit the redeclaration intent\n await this.submitRedeclarationIntent(currentBlock, redeclarationIntent)\n\n // On successful submission, save the redeclaration intent\n this._lastRedeclarationIntent = redeclarationIntent\n }, { ...this.context, timeBudgetLimit: 1000 })\n }\n\n protected async submitRedeclarationIntent(currentBlock: XL1BlockNumber, redeclarationIntent: ChainStakeIntent): Promise<void> {\n this.logger?.log('Submitting redeclaration intent for producer:', this.account.address)\n // Create a transaction to submit the redeclaration intent\n const tx = await buildTransaction(\n this.chainId,\n [redeclarationIntent],\n [],\n this.account,\n currentBlock,\n asXL1BlockNumber(currentBlock + 1000, true),\n )\n\n // Submit the redeclaration intent\n await this.mempoolRunner.submitTransactions([tx])\n\n this.logger?.log('Submitted redeclaration intent for producer:', this.account.address)\n }\n\n protected async validateCurrentBalance(): Promise<boolean> {\n // Check if we have a valid balance before declaring intent\n const head = (await this.blockViewer.currentBlock())?.[0]?._hash\n if (isDefined(head)) {\n const balances = await this.accountBalanceViewer.accountBalances([this.account.address], { head })\n const currentBalance = balances[this.account.address] ?? 0n\n if (currentBalance <= 0n) {\n this.logger?.error(`Producer ${this.account.address} has no balance.`)\n return false\n }\n return true\n }\n return true\n }\n\n protected async validateCurrentStake(): Promise<boolean> {\n // Use StakeIntentService to get the required minimum stake\n const requiredMinimumStake = 1n // this.stakeIntentService.getRequiredMinimumStakeForIntent('producer')\n // Check if we have a valid stake before declaring intent\n const currentStake = await this.stakeTotalsViewer.activeByStaked(this.account.address)\n if (currentStake < requiredMinimumStake) {\n this.logger?.error(`Producer ${this.account.address} has insufficient stake.`)\n return false\n }\n return true\n }\n}\n","import {\n asAddress, exists, IdLogger,\n} from '@xylabs/sdk-js'\nimport { initProducerAccount, type OrchestratorInstance } from '@xyo-network/chain-orchestration'\nimport type {\n BaseContext, Config, ProviderFactoryLocatorInstance,\n} from '@xyo-network/xl1-sdk'\n\nimport { ProducerActor } from './ProducerActor.ts'\n\nexport interface RunProducerContext extends BaseContext {\n config: Config\n locator?: ProviderFactoryLocatorInstance\n orchestrator: OrchestratorInstance\n}\n\nexport const runProducer = async ({\n config, logger, orchestrator, locator,\n}: RunProducerContext) => {\n const localLogger = logger ?? new IdLogger(logger ?? console, () => 'ResolveHelper [runProducer]')\n const account = await initProducerAccount({ config })\n localLogger.info(`Running producer for account ${account.address}`)\n const rewardAddress = asAddress(config.producer.rewardAddress, () => 'rewardAddress is required in config.producer')\n localLogger.info(`Using reward address ${rewardAddress}`)\n\n // Create actors\n const producer = await ProducerActor.create({\n logger,\n context: {\n config, locator, singletons: {}, caches: {},\n },\n })\n const actors = [producer].filter(exists)\n\n for (const actor of actors) {\n // Register the actor with the orchestrator\n await orchestrator.registerActor(actor)\n }\n // Start the orchestrator => automatically activates the actor\n await orchestrator.start()\n}\n"],"mappings":";;;;AAEA,SACEA,WAAWC,UAAUC,WAAWC,WAAWC,aAAaC,aACnD;AACP,SAASC,wBAAwBC,4BAA4B;AAC7D,SAASC,iBAAiBC,2BAA2B;AACrD,SAASC,yBAAyB;AAClC,SAASC,uBAAuBC,kCAAkC;AAIlE,SACwBC,6BAA6BC,OAAoBC,kBAA+BC,oBAAiCC,oBACvIC,6BAA6BC,kBAA0BC,yBAAyBC,kBAAkBC,kBAAiCC,sBACpHC,sBAAoCC,yBAAyBC,6BAAgDC,gCAEvH;AACP,SAASC,aAAa;;;;;;;;AAMtB,IAAMC,6CAA6C;AACnD,IAAMC,cAAc,KAAK,KAAK;AAGvB,IAAMC,gBAAN,MAAMA,uBAAsBC,MAAAA;SAAAA;;;EACjC,OAAgBC,sBAAsB;EAE5BC;EACAC;EACAC;EACAC;EACAC;EACAC;EACAC;EAEFC;EACAC;EACAC;EACAC;EACAC;EACAC;EAEAC,qBAAqB,IAAIC,MAAAA;EAEzBC;EAER,IAAcC,uBAAuB;AACnC,WAAO,KAAKT;EACd;EAEA,IAAcU,cAAc;AAC1B,WAAO,KAAKT;EACd;EAEA,IAAcU,cAAc;AAC1B,WAAO,KAAKT;EACd;EAEA,IAAcU,UAAU;AACtB,WAAO,KAAKT;EACd;EAEA,IAAcU,gBAAgB;AAC5B,WAAO,KAAKT;EACd;EAEA,IAAcU,gBAAgB;AAC5B,WAAO,KAAKT;EACd;EAEA,IAAcU,gBAAgB;AAC5B,WAAO,KAAKC,OAAOD;EACrB;EAEA,IAAcE,oBAAoB;AAChC,WAAO,KAAKT;EACd;EAEA,aAAsBU,cAAuCF,QAA+B;AAC1F,UAAMG,SAASH,QAAQI,SAASD;AAChC,UAAME,SAASL,QAAQI,SAASC,UAAUC,iBAAAA;AAC1C,UAAMC,cAAcP,QAAQO,eAAe;AAC3C,UAAMC,UAAUR,QAAQQ,WAAW,MAAMC,oBAAoB;MAAEJ;IAAO,CAAA;AACtE,UAAMK,KAAKF,SAASG,QAAQC,SAAAA,KAAc;AAC1CT,YAAQU,KAAK,gCAAgCL,QAAQG,OAAO,EAAE;AAC9D,UAAMZ,gBAAgBC,QAAQD,iBAAiBS,QAAQG;AACvDR,YAAQU,KAAK,wBAAwBd,aAAAA,EAAe;AACpD,QAAIA,kBAAkBS,QAAQG,SAAS;AACrCR,cAAQW,KAAK,qDAAqDN,QAAQG,OAAO,EAAE;IACrF;AAEA,WAAO;MACL,GAAG,MAAM,MAAMT,cAAc;QAC3B,GAAGF;QACHQ;QACAD;QACAG;MACF,CAAA;MACAX;IACF;EACF;EAEA,aAAgCgB,YAC9Bf,QACA;AACA,UAAMK,SAAiBL,QAAQI,SAASC,UAAUC,iBAAAA;AAClD,UAAMU,WAAWC,SAASZ,OAAOa,SAASC,aAAa,MAAM,yDAAA;AAE7D,UAAMf,UAAU,MAAM,MAAMW,YAAYf,MAAAA;AAExC,UAAMJ,UAAUqB,SAASZ,OAAOe,MAAMV,IAAI,MAAM,yCAAA;AAEhD,UAAMW,mBAAqC,wBAACC,YAA0B,IAAIC,iBAAiBP,UAAUM,OAAAA,GAA1D;AAC3C,UAAME,UAAU,MAAMC,4BAA4B;MAAErB;MAASiB;IAAiB,CAAA;AAC9E,UAAMK,SAAS,MAAMC,gBAAgB;MAAEtB;IAAO,CAAA;AAC9CmB,YAAQI,SAASC,uBAAuBC,QAAgCD,uBAAuBE,cAAc;MAAEpB,SAASqB,UAAUpC,OAAAA;MAAU8B;IAAO,CAAA,CAAA;AACnJF,YAAQI,SAASK,qBAAqBH,QAA8BG,qBAAqBF,cAAc;MAAEpB,SAASqB,UAAUpC,OAAAA;MAAU8B;IAAO,CAAA,CAAA;AAC7IF,YAAQI,SAASM,wBAAwBJ,QAAiCI,wBAAwBH,YAAY,CAAA;AAC9GP,YAAQI,SAASO,4BAA4BL,QAC3CK,4BAA4BJ,cAC5B;MAAEK,OAAOC;MAA4BC,UAAUC;IAAsB,CAAA,CAAA;AAEvEf,YAAQI,SAASY,kBAAkBV,QACjCU,kBAAkBT,cAClB;MAAEvB,SAASR,OAAOQ;MAAST,eAAeC,OAAOD;IAAc,CAAA,CAAA;AAEjE,WAAOyB,QAAQpB;EACjB;EAEA,MAAeqC,gBAAgB;AAC7B,UAAM,MAAMA,cAAAA;AAGZ,SAAK9D,oBAAoB;MAAEgC,SAAS,KAAKH,QAAQG,QAAQC,SAAQ;IAAG;AAEpE,SAAK/B,sCAAsC,KAAK6D,OAAOC,cACrD,0CACA;MAAEC,aAAa;IAAoC,CAAA;AAErD,SAAKhE,wCAAwC,KAAK8D,OAAOC,cACvD,4CACA;MAAEC,aAAa;IAAsC,CAAA;AAEvD,SAAK9D,+BAA+B,KAAK4D,OAAOC,cAC9C,kCACA;MAAEC,aAAa;IAA4B,CAAA;AAE7C,SAAK7D,gCAAgC,KAAK2D,OAAOC,cAC/C,mCACA;MAAEC,aAAa;IAA6B,CAAA;AAE9C,SAAK5D,wBAAwBiC,SAC3B,MAAM,KAAKO,SAASqB,YAAkCC,2BAAAA,GACtD,MAAM,uCAAA;AAER,SAAK7D,eAAegC,SAClB,MAAM,KAAKO,SAASqB,YAAyBE,kBAAAA,GAC7C,MAAM,8BAAA;AAER,SAAK7D,eAAe+B,SAClB,MAAM,KAAKO,SAASqB,YAAyBG,kBAAAA,GAC7C,MAAM,8BAAA;AAER,SAAK5D,iBAAiB6B,SACpB,MAAM,KAAKO,SAASqB,YAA2BI,oBAAAA,GAC/C,MAAM,gCAAA;AAER,SAAK5D,iBAAiB4B,SACpB,MAAM,KAAKO,SAASqB,YAA2BK,oBAAAA,GAC/C,MAAM,gCAAA;AAER,SAAK1D,qBAAqByB,SACxB,MAAM,KAAKO,SAASqB,YAA+BM,wBAAAA,GACnD,MAAM,oCAAA;EAEV;EAEA,MAAeC,eAAe;AAC5B,UAAM,MAAMA,aAAAA;AAEZ,SAAKC;MAAc;MAAwB,YAAA;AACzC,cAAM,KAAKC,aAAY;MACzB;MAAG;MAAM;;IAAW;AAEpB,QAAIlF,4CAA4C;AAE9C,WAAKiF,cAAc,8BAA8B,YAAA;AAC/C,cAAM,KAAKE,gBAAe;MAC5B,GAAGlF,aAAaA,WAAAA;IAClB;EACF;EAEUmF,kDAAkDC,cAA8B;AACxF,YAAQ,KAAK/E,0BAA0BgF,OAAOD,gBAAgBA;EAChE;EAEA,MAAgBH,eAA8B;AAC5C,SAAKzE,qCAAqC8E,IAAI,GAAG,KAAKhF,iBAAiB;AACvE,UAAM,KAAKiF,UAAU,gBAAgB,YAAA;AACnC,UAAI,KAAKtE,mBAAmBuE,SAAQ,GAAI;AACtC,aAAK1D,QAAQ2D,IAAI,kEAAA;AACjB;MACF;AAEA,YAAM,KAAKxE,mBAAmByE,aAAa,YAAA;AAEzC,cAAMC,QAAQ,MAAM,KAAKrE,YAAY8D,aAAY,GAAI,CAAA;AAErD,cAAMQ,WAAWD,KAAKE;AAGtB,YAAI,KAAKzF,sBAAsB,KAAKA,mBAAmB,CAAA,EAAG0F,aAAaF,UAAU;AAC/E,eAAK9D,QAAQ2D,IAAI,2BAA2B,KAAKM,MAAM,KAAK3F,mBAAmB,CAAA,EAAG4F,KAAK,CAAA,IAAK,KAAK5F,mBAAmB,CAAA,EAAG4F,KAAK;QAC9H,OAAO;AACL,eAAKzF,uCAAuC+E,IAAI,GAAG,KAAKhF,iBAAiB;AAEzE,gBAAM2F,YAAY,MAAM,KAAK5E,YAAY6E,KAAKP,IAAAA;AAE9C,cAAIM,WAAW;AACb,kBAAME,qBAAqB,KAAKJ,MAAME,UAAU,CAAA,EAAGD,KAAK,CAAA;AACxD,iBAAKlE,QAAQ2D,IAAI,mBAAmBU,kBAAAA;AACpC,iBAAK1F,8BAA8B6E,IAAI,GAAG,KAAKhF,iBAAiB;AAEhE,kBAAM,KAAKkB,cAAc4E,aAAa;cAACH;aAAU;AACjD,iBAAKnE,QAAQ2D,IAAI,oBAAoBU,oBAAoBF,UAAU,CAAA,EAAGD,KAAK;AAC3E,iBAAKtF,+BAA+B4E,IAAI,GAAG,KAAKhF,iBAAiB;AAEjE,iBAAKF,qBAAqB6F;UAC5B,OAAO;AACL,iBAAKnE,QAAQ2D,IAAI,iCAAA;UACnB;QACF;MACF,CAAA;IACF,GAAG;MAAE,GAAG,KAAK1D;MAASsE,iBAAiB;IAAK,CAAA;EAC9C;EAEA,MAAgBnB,kBAAiC;AAC/C,UAAM,KAAKK,UAAU,mBAAmB,YAAA;AAEtC,UAAI,KAAKvD,OAAOsE,SAASC,2BAA4B;AAGrD,YAAMZ,QAAQ,MAAM,KAAKrE,YAAY8D,aAAY,GAAI,CAAA;AACrD,UAAIoB,YAAYb,IAAAA,EAAO;AACvB,YAAMP,eAAeO,KAAKK;AAG1B,YAAMS,wBAAwB,KAAKtB,kDAAkDC,YAAAA;AAIrF,UAAIqB,wBAAwBxG,eAAcE,sBAAsB,KAAK;AAEnE,aAAKE,2BAA2BqG;AAEhC;MACF;AAIA,UAAI,KAAKrG,0BAA0B;AAEjC,YAAI,KAAKA,yBAAyBgF,MAAMD,aAAc;AAEtD,aAAK/E,2BAA2BqG;MAClC;AAGA,UAAI,CAAC,MAAM,KAAKC,uBAAsB,GAAI;AACxC,aAAK7E,QAAQ8E,MACX,0BAA0B,KAAKzE,QAAQG,OAAO,2CAA2C;AAE3F;MACF;AAGA,UAAI,CAAE,MAAM,KAAKuE,qBAAoB,GAAK;AACxC,aAAK/E,QAAQ8E,MACX,iCAAiC,KAAK5E,OAAOe,MAAMV,EAAE,2CACnD;AAEJ;MACF;AAGA,WAAKP,QAAQ2D,IAAI,+CAA+C,KAAKtD,QAAQG,OAAO;AACpF,YAAMwE,sBAAsBC,wBAC1B,KAAK5E,QAAQG,SACb,YACA8C,cACAA,eAAejB,kBAAkB6C,qBAAqB;AAIxD,YAAM,KAAKC,0BAA0B7B,cAAc0B,mBAAAA;AAGnD,WAAKzG,2BAA2ByG;IAClC,GAAG;MAAE,GAAG,KAAK/E;MAASsE,iBAAiB;IAAK,CAAA;EAC9C;EAEA,MAAgBY,0BAA0B7B,cAA8B0B,qBAAsD;AAC5H,SAAKhF,QAAQ2D,IAAI,iDAAiD,KAAKtD,QAAQG,OAAO;AAEtF,UAAM4E,KAAK,MAAMC,iBACf,KAAK5F,SACL;MAACuF;OACD,CAAA,GACA,KAAK3E,SACLiD,cACAgC,iBAAiBhC,eAAe,KAAM,IAAA,CAAA;AAIxC,UAAM,KAAK5D,cAAc6F,mBAAmB;MAACH;KAAG;AAEhD,SAAKpF,QAAQ2D,IAAI,gDAAgD,KAAKtD,QAAQG,OAAO;EACvF;EAEA,MAAgBqE,yBAA2C;AAEzD,UAAMhB,QAAQ,MAAM,KAAKrE,YAAY8D,aAAY,KAAM,CAAA,GAAIS;AAC3D,QAAIyB,UAAU3B,IAAAA,GAAO;AACnB,YAAM4B,WAAW,MAAM,KAAKnG,qBAAqBoG,gBAAgB;QAAC,KAAKrF,QAAQG;SAAU;QAAEqD;MAAK,CAAA;AAChG,YAAM8B,iBAAiBF,SAAS,KAAKpF,QAAQG,OAAO,KAAK;AACzD,UAAImF,kBAAkB,IAAI;AACxB,aAAK3F,QAAQ8E,MAAM,YAAY,KAAKzE,QAAQG,OAAO,kBAAkB;AACrE,eAAO;MACT;AACA,aAAO;IACT;AACA,WAAO;EACT;EAEA,MAAgBuE,uBAAyC;AAEvD,UAAMa,uBAAuB;AAE7B,UAAMC,eAAe,MAAM,KAAK/F,kBAAkBgG,eAAe,KAAKzF,QAAQG,OAAO;AACrF,QAAIqF,eAAeD,sBAAsB;AACvC,WAAK5F,QAAQ8E,MAAM,YAAY,KAAKzE,QAAQG,OAAO,0BAA0B;AAC7E,aAAO;IACT;AACA,WAAO;EACT;AACF;;;;;;AC7VA,SACEuF,aAAAA,YAAWC,QAAQC,gBACd;AACP,SAASC,uBAAAA,4BAAsD;AAaxD,IAAMC,cAAc,8BAAO,EAChCC,QAAQC,QAAQC,cAAcC,QAAO,MAClB;AACnB,QAAMC,cAAcH,UAAU,IAAII,SAASJ,UAAUK,SAAS,MAAM,6BAAA;AACpE,QAAMC,UAAU,MAAMC,qBAAoB;IAAER;EAAO,CAAA;AACnDI,cAAYK,KAAK,gCAAgCF,QAAQG,OAAO,EAAE;AAClE,QAAMC,gBAAgBC,WAAUZ,OAAOa,SAASF,eAAe,MAAM,8CAAA;AACrEP,cAAYK,KAAK,wBAAwBE,aAAAA,EAAe;AAGxD,QAAME,WAAW,MAAMC,cAAcC,OAAO;IAC1Cd;IACAe,SAAS;MACPhB;MAAQG;MAASc,YAAY,CAAC;MAAGC,QAAQ,CAAC;IAC5C;EACF,CAAA;AACA,QAAMC,SAAS;IAACN;IAAUO,OAAOC,MAAAA;AAEjC,aAAWC,SAASH,QAAQ;AAE1B,UAAMjB,aAAaqB,cAAcD,KAAAA;EACnC;AAEA,QAAMpB,aAAasB,MAAK;AAC1B,GAxB2B;","names":["asAddress","assertEx","creatable","isDefined","isUndefined","toHex","EvmChainContractViewer","EvmStakeTotalsViewer","initEvmProvider","initProducerAccount","SimpleBlockRunner","validateHydratedBlock","validateHydratedBlockState","AccountBalanceViewerMoniker","Actor","asXL1BlockNumber","BlockRunnerMoniker","BlockViewerMoniker","buildJsonRpcProviderLocator","buildTransaction","createDeclarationIntent","getDefaultConfig","HttpRpcTransport","MempoolRunnerMoniker","MempoolViewerMoniker","SimpleBlockRewardViewer","SimpleBlockValidationViewer","StakeTotalsViewerMoniker","Mutex","SHOULD_REGISTER_REDECLARATION_INTENT_TIMER","TEN_MINUTES","ProducerActor","Actor","RedeclarationWindow","_lastProducedBlock","_lastRedeclarationIntent","_metricAttributes","_producerActorBlockProductionAttempts","_producerActorBlockProductionChecks","_producerActorBlocksProduced","_producerActorBlocksPublished","_accountBalanceViewer","_blockRunner","_blockViewer","_chainId","_mempoolRunner","_mempoolViewer","_produceBlockMutex","Mutex","_stakeTotalsViewer","accountBalanceViewer","blockRunner","blockViewer","chainId","mempoolRunner","mempoolViewer","rewardAddress","params","stakeTotalsViewer","paramsHandler","logger","context","config","getDefaultConfig","displayName","account","initProducerAccount","id","address","toString","info","warn","initContext","endpoint","assertEx","services","apiEndpoint","chain","transportFactory","schemas","HttpRpcTransport","locator","buildJsonRpcProviderLocator","runner","initEvmProvider","register","EvmChainContractViewer","factory","dependencies","asAddress","EvmStakeTotalsViewer","SimpleBlockRewardViewer","SimpleBlockValidationViewer","state","validateHydratedBlockState","protocol","validateHydratedBlock","SimpleBlockRunner","createHandler","meter","createCounter","description","getInstance","AccountBalanceViewerMoniker","BlockRunnerMoniker","BlockViewerMoniker","MempoolRunnerMoniker","MempoolViewerMoniker","StakeTotalsViewerMoniker","startHandler","registerTimer","produceBlock","redeclareIntent","calculateBlocksUntilProducerDeclarationExpiration","currentBlock","exp","add","spanAsync","isLocked","log","runExclusive","head","headHash","_hash","previous","toHex","block","nextBlock","next","displayBlockNumber","submitBlocks","timeBudgetLimit","producer","disableIntentRedeclaration","isUndefined","blocksUntilExpiration","undefined","validateCurrentBalance","error","validateCurrentStake","redeclarationIntent","createDeclarationIntent","RedeclarationDuration","submitRedeclarationIntent","tx","buildTransaction","asXL1BlockNumber","submitTransactions","isDefined","balances","accountBalances","currentBalance","requiredMinimumStake","currentStake","activeByStaked","asAddress","exists","IdLogger","initProducerAccount","runProducer","config","logger","orchestrator","locator","localLogger","IdLogger","console","account","initProducerAccount","info","address","rewardAddress","asAddress","producer","ProducerActor","create","context","singletons","caches","actors","filter","exists","actor","registerActor","start"]}
1
+ {"version":3,"sources":["../../src/ProducerActor.ts","../../src/runProducer.ts"],"sourcesContent":["import type { Attributes, Counter } from '@opentelemetry/api'\nimport type { Address } from '@xylabs/sdk-js'\nimport {\n asAddress, assertEx, creatable, isDefined, isUndefined,\n toHex,\n} from '@xylabs/sdk-js'\nimport { EvmChainContractViewer, EvmStakeTotalsViewer } from '@xyo-network/chain-ethereum'\nimport { initEvmProvider, initProducerAccount } from '@xyo-network/chain-orchestration'\nimport { SimpleBlockRunner } from '@xyo-network/chain-services'\nimport { validateHydratedBlock, validateHydratedBlockState } from '@xyo-network/chain-validation'\nimport type {\n BlockBoundWitnessWithHashMeta, ChainId, ChainStakeIntent, HydratedBlockWithHashMeta, XL1BlockNumber,\n} from '@xyo-network/xl1-sdk'\nimport {\n AccountBalanceViewer, AccountBalanceViewerMoniker, Actor, ActorParams, asXL1BlockNumber, BlockRunner, BlockRunnerMoniker, BlockViewer, BlockViewerMoniker,\n buildJsonRpcProviderLocator, buildTransaction, Config, createDeclarationIntent, getDefaultConfig, HttpRpcTransport, MempoolRunner, MempoolRunnerMoniker,\n MempoolViewer, MempoolViewerMoniker, RpcSchemaMap, SimpleBlockRewardViewer, SimpleBlockValidationViewer, StakeTotalsViewer, StakeTotalsViewerMoniker,\n TransportFactory,\n} from '@xyo-network/xl1-sdk'\nimport { Mutex } from 'async-mutex'\n\nexport type ProducerActorParams = ActorParams<{\n rewardAddress: Address\n}>\n\nconst SHOULD_REGISTER_REDECLARATION_INTENT_TIMER = true\nconst TEN_MINUTES = 10 * 60 * 1000 // 10 minutes in milliseconds\n\n/**\n * Formats a hydrated block with hash meta into a string representation of its hash and block number.\n * @param blockBoundWitness The hydrated block with hash meta to format\n * @returns The formatted block reference string\n */\nconst toFormattedBlockReference = (blockBoundWitness: BlockBoundWitnessWithHashMeta): string => {\n return `${blockBoundWitness.block} [${toHex(blockBoundWitness._hash, { prefix: true })}]`\n}\n\n@creatable()\nexport class ProducerActor extends Actor<ProducerActorParams> {\n /**\n * The interval time (in MS) between block production attempts.\n */\n static readonly BlockSubmissionCheckInterval = 1500 // 1.5 seconds\n\n /**\n * The threshold time (in MS) for resubmitting the same block number if the head has not changed.\n */\n static readonly HeadResubmissionThreshold = ProducerActor.BlockSubmissionCheckInterval * 30\n\n /**\n * The window (in blocks) before expiration to attempt redeclaration of producer intent.\n */\n static readonly RedeclarationWindow = 10_000\n\n protected _lastProducedBlock?: HydratedBlockWithHashMeta\n protected _lastRedeclarationIntent?: ChainStakeIntent\n protected _metricAttributes?: Attributes\n protected _producerActorBlockProductionAttempts?: Counter<Attributes>\n protected _producerActorBlockProductionChecks?: Counter<Attributes>\n protected _producerActorBlocksProduced?: Counter<Attributes>\n protected _producerActorBlocksPublished?: Counter<Attributes>\n\n private _accountBalanceViewer?: AccountBalanceViewer\n private _blockRunner?: BlockRunner\n private _blockViewer?: BlockViewer\n private _chainId?: ChainId\n private _lastHeadChangeTime?: number\n private _lastHeadHash?: string\n private _mempoolRunner?: MempoolRunner\n private _mempoolViewer?: MempoolViewer\n private _produceBlockMutex = new Mutex()\n private _stakeTotalsViewer?: StakeTotalsViewer\n\n protected get accountBalanceViewer() {\n return this._accountBalanceViewer!\n }\n\n protected get blockRunner() {\n return this._blockRunner!\n }\n\n protected get blockViewer() {\n return this._blockViewer!\n }\n\n protected get chainId() {\n return this._chainId!\n }\n\n protected get mempoolRunner() {\n return this._mempoolRunner!\n }\n\n protected get mempoolViewer() {\n return this._mempoolViewer!\n }\n\n protected get rewardAddress() {\n return this.params.rewardAddress!\n }\n\n protected get stakeTotalsViewer() {\n return this._stakeTotalsViewer!\n }\n\n static override async paramsHandler<T extends ProducerActor>(params?: Partial<T['params']>) {\n const logger = params?.context?.logger\n const config = params?.context?.config ?? getDefaultConfig()\n const displayName = params?.displayName ?? 'ProducerActor'\n const account = params?.account ?? await initProducerAccount({ config })\n const id = account?.address.toString() ?? 'unknown'\n logger?.info(`Running producer for account ${account.address}`)\n const rewardAddress = params?.rewardAddress ?? account.address\n logger?.info(`Using reward address ${rewardAddress}`)\n if (rewardAddress === account.address) {\n logger?.warn(`Using producer account address as reward address: ${account.address}`)\n }\n\n return {\n ...await super.paramsHandler({\n ...params,\n account,\n displayName,\n id,\n }),\n rewardAddress,\n }\n }\n\n protected static override async initContext(\n params: ProducerActorParams,\n ) {\n const config: Config = params?.context?.config ?? getDefaultConfig()\n const endpoint = assertEx(config.services.apiEndpoint, () => 'API endpoint is required in config.services.apiEndpoint')\n\n const context = await super.initContext(params)\n\n const chainId = assertEx(config.chain.id, () => 'Chain ID is required in config.chain.id')\n\n const transportFactory: TransportFactory = (schemas: RpcSchemaMap) => new HttpRpcTransport(endpoint, schemas)\n const locator = await buildJsonRpcProviderLocator({ context, transportFactory })\n const runner = await initEvmProvider({ config })\n locator.register(EvmChainContractViewer.factory<EvmChainContractViewer>(EvmChainContractViewer.dependencies, { address: asAddress(chainId), runner }))\n locator.register(EvmStakeTotalsViewer.factory<EvmStakeTotalsViewer>(EvmStakeTotalsViewer.dependencies, { address: asAddress(chainId), runner }))\n locator.register(SimpleBlockRewardViewer.factory<SimpleBlockRewardViewer>(SimpleBlockRewardViewer.dependencies))\n locator.register(SimpleBlockValidationViewer.factory<SimpleBlockValidationViewer>(\n SimpleBlockValidationViewer.dependencies,\n { state: validateHydratedBlockState, protocol: validateHydratedBlock },\n ))\n locator.register(SimpleBlockRunner.factory<SimpleBlockRunner>(\n SimpleBlockRunner.dependencies,\n { account: params.account, rewardAddress: params.rewardAddress },\n ))\n return locator.context\n }\n\n override async createHandler() {\n await super.createHandler()\n // Create the consistent meter attributes that will\n // be included with all metrics from this actor\n this._metricAttributes = { address: this.account.address.toString() }\n // Create the metrics\n this._producerActorBlockProductionChecks = this.meter?.createCounter(\n 'producer_actor_block_production_checks',\n { description: 'Number of block production checks' },\n )\n this._producerActorBlockProductionAttempts = this.meter?.createCounter(\n 'producer_actor_block_production_attempts',\n { description: 'Number of block production attempts' },\n )\n this._producerActorBlocksProduced = this.meter?.createCounter(\n 'producer_actor_blocks_produced',\n { description: 'Number of blocks produced' },\n )\n this._producerActorBlocksPublished = this.meter?.createCounter(\n 'producer_actor_blocks_published',\n { description: 'Number of blocks published' },\n )\n this._accountBalanceViewer = assertEx(\n await this.locator?.getInstance<AccountBalanceViewer>(AccountBalanceViewerMoniker),\n () => 'Unable to locate AccountBalanceViewer',\n )\n this._blockRunner = assertEx(\n await this.locator?.getInstance<BlockRunner>(BlockRunnerMoniker),\n () => 'Unable to locate BlockRunner',\n )\n this._blockViewer = assertEx(\n await this.locator?.getInstance<BlockViewer>(BlockViewerMoniker),\n () => 'Unable to locate BlockViewer',\n )\n this._mempoolRunner = assertEx(\n await this.locator?.getInstance<MempoolRunner>(MempoolRunnerMoniker),\n () => 'Unable to locate MempoolRunner',\n )\n this._mempoolViewer = assertEx(\n await this.locator?.getInstance<MempoolViewer>(MempoolViewerMoniker),\n () => 'Unable to locate MempoolViewer',\n )\n this._stakeTotalsViewer = assertEx(\n await this.locator?.getInstance<StakeTotalsViewer>(StakeTotalsViewerMoniker),\n () => 'Unable to locate StakeTotalsViewer',\n )\n }\n\n override async startHandler() {\n await super.startHandler()\n // Register a timer to check if we should produce a block\n this.registerTimer('BlockProductionTimer', async () => {\n await this.produceBlock()\n }, 2000, ProducerActor.BlockSubmissionCheckInterval)\n\n if (SHOULD_REGISTER_REDECLARATION_INTENT_TIMER) {\n // Register a timer to check if we should redeclare the producer\n this.registerTimer('ProducerRedeclarationTimer', async () => {\n await this.redeclareIntent()\n }, TEN_MINUTES, TEN_MINUTES)\n }\n }\n\n protected calculateBlocksUntilProducerDeclarationExpiration(currentBlock: number): number {\n return (this._lastRedeclarationIntent?.exp ?? currentBlock) - currentBlock\n }\n\n protected async produceBlock(): Promise<void> {\n this._producerActorBlockProductionChecks?.add(1, this._metricAttributes)\n await this.spanAsync('produceBlock', async () => {\n if (this._produceBlockMutex.isLocked()) {\n this.logger?.log('Skipping block production, previous production still in progress')\n return\n }\n\n // eslint-disable-next-line complexity\n await this._produceBlockMutex.runExclusive(async () => {\n // Get the updated head\n const head = (await this.blockViewer.currentBlock())[0]\n // Check if we've already produced the next block for this head\n const headHash = head._hash\n\n // Track head changes\n const currentTime = Date.now()\n if (this._lastHeadHash !== headHash) {\n // Log the head change\n const lastHeadHashHex = isDefined(this._lastHeadHash) ? `0x${this._lastHeadHash}` : 'undefined'\n const currentHeadHashHex = `0x${headHash}`\n this.logger?.log(`Found updated head ${lastHeadHashHex} -> ${currentHeadHashHex}`)\n\n // Update the head change info\n this._lastHeadHash = headHash\n this._lastHeadChangeTime = currentTime\n }\n\n // Check if we should resubmit due to stale head\n const timeSinceHeadChange = isDefined(this._lastHeadChangeTime) ? currentTime - this._lastHeadChangeTime : 0\n\n // If we have never produced a block or the last produced block was not built on the current head we\n // need to attempt to produce a new block\n const shouldSubmit = !this._lastProducedBlock || this._lastProducedBlock[0].previous !== headHash\n\n // If the head has not changed determine if we should resubmit again based on head staleness\n const shouldResubmit = timeSinceHeadChange > ProducerActor.HeadResubmissionThreshold\n\n // Determine if we should submit or resubmit\n const shouldSubmitBlock = shouldSubmit || shouldResubmit\n\n if (shouldSubmitBlock) {\n // If we are resubmitting due to stale head\n if (shouldResubmit) {\n // Log that we are resubmitting due to stale head\n this.logger?.log(`Resubmitting block due to stale head. Head ${toFormattedBlockReference(head)} unchanged for ${timeSinceHeadChange}ms`)\n // Reset timer since we're resubmitting\n this._lastHeadChangeTime = currentTime\n }\n this._producerActorBlockProductionAttempts?.add(1, this._metricAttributes)\n // Produce the next block\n const nextBlock = await this.blockRunner.next(head)\n // If it was produced\n if (nextBlock) {\n const displayBlockNumber = toFormattedBlockReference(nextBlock[0])\n this.logger?.log('Produced block:', displayBlockNumber)\n this._producerActorBlocksProduced?.add(1, this._metricAttributes)\n // Insert the block into the chain\n await this.mempoolRunner.submitBlocks([nextBlock])\n this.logger?.log('Published block:', displayBlockNumber)\n this._producerActorBlocksPublished?.add(1, this._metricAttributes)\n // Record that we have produced a block so we do not produce it again\n this._lastProducedBlock = nextBlock\n } else {\n this.logger?.log('No block produced for submission.')\n }\n } else {\n // Log that we are not submitting a block at this time\n let nonSubmissionMessage = 'No block submission required at this time'\n // If we have already produced a block\n if (isDefined(this._lastProducedBlock)) {\n // include that info in the log\n nonSubmissionMessage = nonSubmissionMessage + `, already produced: ${toFormattedBlockReference(this._lastProducedBlock[0])}`\n }\n this.logger?.log(`${nonSubmissionMessage}.`)\n }\n })\n }, { ...this.context, timeBudgetLimit: 1000 })\n }\n\n protected async redeclareIntent(): Promise<void> {\n await this.spanAsync('redeclareIntent', async () => {\n // Decide if we should redeclare intent\n if (this.config.producer.disableIntentRedeclaration) return\n\n // Get the current block\n const head = (await this.blockViewer.currentBlock())[0]\n if (isUndefined(head)) return\n const currentBlock = head.block\n\n // // Calculate the time until the producer's declaration expires\n const blocksUntilExpiration = this.calculateBlocksUntilProducerDeclarationExpiration(currentBlock)\n\n // Allow the producer time to redeclare itself via block production\n // (for free) before submitting a redeclaration intent transaction.\n if (blocksUntilExpiration > ProducerActor.RedeclarationWindow * 0.1) {\n // Clear any previous redeclaration intent\n this._lastRedeclarationIntent = undefined\n // No need to redeclare yet\n return\n }\n\n // If we already have a valid redeclaration intent, do not create another\n // unless it has expired.\n if (this._lastRedeclarationIntent) {\n // Check if the last redeclaration intent is still valid\n if (this._lastRedeclarationIntent.exp > currentBlock) return\n // If it has expired, clear the last redeclaration intent\n this._lastRedeclarationIntent = undefined\n }\n\n // Check if we have a valid balance before declaring intent\n if (!await this.validateCurrentBalance()) {\n this.logger?.error(\n `Add balance to address ${this.account.address} for the producer to declare it's intent.`,\n )\n return\n }\n\n // Check if we have a valid stake before declaring intent\n if (!(await this.validateCurrentStake())) {\n this.logger?.error(\n `Add stake to contract address ${this.config.chain.id}`\n + ' for the producer to declare it\\'s intent.',\n )\n return\n }\n\n // Create a redeclaration intent\n this.logger?.log('Creating redeclaration intent for producer:', this.account.address)\n const redeclarationIntent = createDeclarationIntent(\n this.account.address,\n 'producer',\n currentBlock,\n currentBlock + SimpleBlockRunner.RedeclarationDuration,\n )\n\n // Submit the redeclaration intent\n await this.submitRedeclarationIntent(currentBlock, redeclarationIntent)\n\n // On successful submission, save the redeclaration intent\n this._lastRedeclarationIntent = redeclarationIntent\n }, { ...this.context, timeBudgetLimit: 1000 })\n }\n\n protected async submitRedeclarationIntent(currentBlock: XL1BlockNumber, redeclarationIntent: ChainStakeIntent): Promise<void> {\n this.logger?.log('Submitting redeclaration intent for producer:', this.account.address)\n // Create a transaction to submit the redeclaration intent\n const tx = await buildTransaction(\n this.chainId,\n [redeclarationIntent],\n [],\n this.account,\n currentBlock,\n asXL1BlockNumber(currentBlock + 1000, true),\n )\n\n // Submit the redeclaration intent\n await this.mempoolRunner.submitTransactions([tx])\n\n this.logger?.log('Submitted redeclaration intent for producer:', this.account.address)\n }\n\n protected async validateCurrentBalance(): Promise<boolean> {\n // Check if we have a valid balance before declaring intent\n const head = (await this.blockViewer.currentBlock())?.[0]?._hash\n if (isDefined(head)) {\n const balances = await this.accountBalanceViewer.accountBalances([this.account.address], { head })\n const currentBalance = balances[this.account.address] ?? 0n\n if (currentBalance <= 0n) {\n this.logger?.error(`Producer ${this.account.address} has no balance.`)\n return false\n }\n return true\n }\n return true\n }\n\n protected async validateCurrentStake(): Promise<boolean> {\n // Use StakeIntentService to get the required minimum stake\n const requiredMinimumStake = 1n // this.stakeIntentService.getRequiredMinimumStakeForIntent('producer')\n // Check if we have a valid stake before declaring intent\n const currentStake = await this.stakeTotalsViewer.activeByStaked(this.account.address)\n if (currentStake < requiredMinimumStake) {\n this.logger?.error(`Producer ${this.account.address} has insufficient stake.`)\n return false\n }\n return true\n }\n}\n","import {\n asAddress, exists, IdLogger,\n} from '@xylabs/sdk-js'\nimport { initProducerAccount, type OrchestratorInstance } from '@xyo-network/chain-orchestration'\nimport type {\n BaseContext, Config, ProviderFactoryLocatorInstance,\n} from '@xyo-network/xl1-sdk'\n\nimport { ProducerActor } from './ProducerActor.ts'\n\nexport interface RunProducerContext extends BaseContext {\n config: Config\n locator?: ProviderFactoryLocatorInstance\n orchestrator: OrchestratorInstance\n}\n\nexport const runProducer = async ({\n config, logger, orchestrator, locator,\n}: RunProducerContext) => {\n const localLogger = logger ?? new IdLogger(logger ?? console, () => 'ResolveHelper [runProducer]')\n const account = await initProducerAccount({ config })\n localLogger.info(`Running producer for account ${account.address}`)\n const rewardAddress = asAddress(config.producer.rewardAddress, () => 'rewardAddress is required in config.producer')\n localLogger.info(`Using reward address ${rewardAddress}`)\n\n // Create actors\n const producer = await ProducerActor.create({\n logger,\n context: {\n config, locator, singletons: {}, caches: {},\n },\n })\n const actors = [producer].filter(exists)\n\n for (const actor of actors) {\n // Register the actor with the orchestrator\n await orchestrator.registerActor(actor)\n }\n // Start the orchestrator => automatically activates the actor\n await orchestrator.start()\n}\n"],"mappings":"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","names":["asAddress","assertEx","creatable","isDefined","isUndefined","toHex","EvmChainContractViewer","EvmStakeTotalsViewer","initEvmProvider","initProducerAccount","SimpleBlockRunner","validateHydratedBlock","validateHydratedBlockState","AccountBalanceViewerMoniker","Actor","asXL1BlockNumber","BlockRunnerMoniker","BlockViewerMoniker","buildJsonRpcProviderLocator","buildTransaction","createDeclarationIntent","getDefaultConfig","HttpRpcTransport","MempoolRunnerMoniker","MempoolViewerMoniker","SimpleBlockRewardViewer","SimpleBlockValidationViewer","StakeTotalsViewerMoniker","Mutex","SHOULD_REGISTER_REDECLARATION_INTENT_TIMER","TEN_MINUTES","toFormattedBlockReference","blockBoundWitness","block","toHex","_hash","prefix","ProducerActor","Actor","BlockSubmissionCheckInterval","HeadResubmissionThreshold","RedeclarationWindow","_lastProducedBlock","_lastRedeclarationIntent","_metricAttributes","_producerActorBlockProductionAttempts","_producerActorBlockProductionChecks","_producerActorBlocksProduced","_producerActorBlocksPublished","_accountBalanceViewer","_blockRunner","_blockViewer","_chainId","_lastHeadChangeTime","_lastHeadHash","_mempoolRunner","_mempoolViewer","_produceBlockMutex","Mutex","_stakeTotalsViewer","accountBalanceViewer","blockRunner","blockViewer","chainId","mempoolRunner","mempoolViewer","rewardAddress","params","stakeTotalsViewer","paramsHandler","logger","context","config","getDefaultConfig","displayName","account","initProducerAccount","id","address","toString","info","warn","initContext","endpoint","assertEx","services","apiEndpoint","chain","transportFactory","schemas","HttpRpcTransport","locator","buildJsonRpcProviderLocator","runner","initEvmProvider","register","EvmChainContractViewer","factory","dependencies","asAddress","EvmStakeTotalsViewer","SimpleBlockRewardViewer","SimpleBlockValidationViewer","state","validateHydratedBlockState","protocol","validateHydratedBlock","SimpleBlockRunner","createHandler","meter","createCounter","description","getInstance","AccountBalanceViewerMoniker","BlockRunnerMoniker","BlockViewerMoniker","MempoolRunnerMoniker","MempoolViewerMoniker","StakeTotalsViewerMoniker","startHandler","registerTimer","produceBlock","redeclareIntent","calculateBlocksUntilProducerDeclarationExpiration","currentBlock","exp","add","spanAsync","isLocked","log","runExclusive","head","headHash","currentTime","Date","now","lastHeadHashHex","isDefined","currentHeadHashHex","timeSinceHeadChange","shouldSubmit","previous","shouldResubmit","shouldSubmitBlock","nextBlock","next","displayBlockNumber","submitBlocks","nonSubmissionMessage","timeBudgetLimit","producer","disableIntentRedeclaration","isUndefined","blocksUntilExpiration","undefined","validateCurrentBalance","error","validateCurrentStake","redeclarationIntent","createDeclarationIntent","RedeclarationDuration","submitRedeclarationIntent","tx","buildTransaction","asXL1BlockNumber","submitTransactions","balances","accountBalances","currentBalance","requiredMinimumStake","currentStake","activeByStaked","asAddress","exists","IdLogger","initProducerAccount","runProducer","config","logger","orchestrator","locator","localLogger","IdLogger","console","account","initProducerAccount","info","address","rewardAddress","asAddress","producer","ProducerActor","create","context","singletons","caches","actors","filter","exists","actor","registerActor","start"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xyo-network/chain-producer",
3
- "version": "1.19.0",
3
+ "version": "1.19.2",
4
4
  "description": "XYO Layer One Producer",
5
5
  "homepage": "https://xylabs.com",
6
6
  "bugs": {
@@ -50,11 +50,11 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@xylabs/sdk-js": "~5.0.64",
53
- "@xyo-network/chain-ethereum": "~1.19.0",
54
- "@xyo-network/chain-orchestration": "~1.19.0",
55
- "@xyo-network/chain-services": "~1.19.0",
56
- "@xyo-network/chain-validation": "~1.19.0",
57
- "@xyo-network/xl1-sdk": "~1.20.2",
53
+ "@xyo-network/chain-ethereum": "~1.19.2",
54
+ "@xyo-network/chain-orchestration": "~1.19.2",
55
+ "@xyo-network/chain-services": "~1.19.2",
56
+ "@xyo-network/chain-validation": "~1.19.2",
57
+ "@xyo-network/xl1-sdk": "~1.20.9",
58
58
  "async-mutex": "~0.5.0"
59
59
  },
60
60
  "devDependencies": {
@@ -72,12 +72,12 @@
72
72
  "@xyo-network/archivist-abstract": "~5.3.2",
73
73
  "@xyo-network/bios-model": "~7.2.1",
74
74
  "@xyo-network/boundwitness-builder": "~5.3.2",
75
- "@xyo-network/chain-services": "~1.19.0",
76
- "@xyo-network/chain-validation": "~1.19.0",
75
+ "@xyo-network/chain-services": "~1.19.2",
76
+ "@xyo-network/chain-validation": "~1.19.2",
77
77
  "@xyo-network/module-abstract-mongodb": "~5.3.2",
78
78
  "@xyo-network/module-model-mongodb": "~5.3.2",
79
79
  "@xyo-network/node-memory": "~5.3.2",
80
- "@xyo-network/xl1-sdk": "~1.20.2",
80
+ "@xyo-network/xl1-sdk": "~1.20.9",
81
81
  "dotenv": "~17.2.3",
82
82
  "eslint": "^9.39.2",
83
83
  "nodemon": "~3.1.11",
@@ -1,14 +1,15 @@
1
1
  import type { Attributes, Counter } from '@opentelemetry/api'
2
2
  import type { Address } from '@xylabs/sdk-js'
3
3
  import {
4
- asAddress, assertEx, creatable, isDefined, isUndefined, toHex,
4
+ asAddress, assertEx, creatable, isDefined, isUndefined,
5
+ toHex,
5
6
  } from '@xylabs/sdk-js'
6
7
  import { EvmChainContractViewer, EvmStakeTotalsViewer } from '@xyo-network/chain-ethereum'
7
8
  import { initEvmProvider, initProducerAccount } from '@xyo-network/chain-orchestration'
8
9
  import { SimpleBlockRunner } from '@xyo-network/chain-services'
9
10
  import { validateHydratedBlock, validateHydratedBlockState } from '@xyo-network/chain-validation'
10
11
  import type {
11
- ChainId, ChainStakeIntent, HydratedBlockWithHashMeta, XL1BlockNumber,
12
+ BlockBoundWitnessWithHashMeta, ChainId, ChainStakeIntent, HydratedBlockWithHashMeta, XL1BlockNumber,
12
13
  } from '@xyo-network/xl1-sdk'
13
14
  import {
14
15
  AccountBalanceViewer, AccountBalanceViewerMoniker, Actor, ActorParams, asXL1BlockNumber, BlockRunner, BlockRunnerMoniker, BlockViewer, BlockViewerMoniker,
@@ -25,8 +26,30 @@ export type ProducerActorParams = ActorParams<{
25
26
  const SHOULD_REGISTER_REDECLARATION_INTENT_TIMER = true
26
27
  const TEN_MINUTES = 10 * 60 * 1000 // 10 minutes in milliseconds
27
28
 
29
+ /**
30
+ * Formats a hydrated block with hash meta into a string representation of its hash and block number.
31
+ * @param blockBoundWitness The hydrated block with hash meta to format
32
+ * @returns The formatted block reference string
33
+ */
34
+ const toFormattedBlockReference = (blockBoundWitness: BlockBoundWitnessWithHashMeta): string => {
35
+ return `${blockBoundWitness.block} [${toHex(blockBoundWitness._hash, { prefix: true })}]`
36
+ }
37
+
28
38
  @creatable()
29
39
  export class ProducerActor extends Actor<ProducerActorParams> {
40
+ /**
41
+ * The interval time (in MS) between block production attempts.
42
+ */
43
+ static readonly BlockSubmissionCheckInterval = 1500 // 1.5 seconds
44
+
45
+ /**
46
+ * The threshold time (in MS) for resubmitting the same block number if the head has not changed.
47
+ */
48
+ static readonly HeadResubmissionThreshold = ProducerActor.BlockSubmissionCheckInterval * 30
49
+
50
+ /**
51
+ * The window (in blocks) before expiration to attempt redeclaration of producer intent.
52
+ */
30
53
  static readonly RedeclarationWindow = 10_000
31
54
 
32
55
  protected _lastProducedBlock?: HydratedBlockWithHashMeta
@@ -41,11 +64,11 @@ export class ProducerActor extends Actor<ProducerActorParams> {
41
64
  private _blockRunner?: BlockRunner
42
65
  private _blockViewer?: BlockViewer
43
66
  private _chainId?: ChainId
67
+ private _lastHeadChangeTime?: number
68
+ private _lastHeadHash?: string
44
69
  private _mempoolRunner?: MempoolRunner
45
70
  private _mempoolViewer?: MempoolViewer
46
-
47
71
  private _produceBlockMutex = new Mutex()
48
-
49
72
  private _stakeTotalsViewer?: StakeTotalsViewer
50
73
 
51
74
  protected get accountBalanceViewer() {
@@ -184,7 +207,7 @@ export class ProducerActor extends Actor<ProducerActorParams> {
184
207
  // Register a timer to check if we should produce a block
185
208
  this.registerTimer('BlockProductionTimer', async () => {
186
209
  await this.produceBlock()
187
- }, 2000, 1500/* 500 */)
210
+ }, 2000, ProducerActor.BlockSubmissionCheckInterval)
188
211
 
189
212
  if (SHOULD_REGISTER_REDECLARATION_INTENT_TIMER) {
190
213
  // Register a timer to check if we should redeclare the producer
@@ -206,33 +229,73 @@ export class ProducerActor extends Actor<ProducerActorParams> {
206
229
  return
207
230
  }
208
231
 
232
+ // eslint-disable-next-line complexity
209
233
  await this._produceBlockMutex.runExclusive(async () => {
210
234
  // Get the updated head
211
235
  const head = (await this.blockViewer.currentBlock())[0]
212
236
  // Check if we've already produced the next block for this head
213
237
  const headHash = head._hash
214
- // If our last produced block was the next block for the current head, we do not
215
- // need to produce another. This prevents duplicate blocks from being produced
216
- if (this._lastProducedBlock && this._lastProducedBlock[0].previous === headHash) {
217
- this.logger?.log('Block already produced:', `0x${toHex(this._lastProducedBlock[0].block)}`, this._lastProducedBlock[0].block)
218
- } else {
238
+
239
+ // Track head changes
240
+ const currentTime = Date.now()
241
+ if (this._lastHeadHash !== headHash) {
242
+ // Log the head change
243
+ const lastHeadHashHex = isDefined(this._lastHeadHash) ? `0x${this._lastHeadHash}` : 'undefined'
244
+ const currentHeadHashHex = `0x${headHash}`
245
+ this.logger?.log(`Found updated head ${lastHeadHashHex} -> ${currentHeadHashHex}`)
246
+
247
+ // Update the head change info
248
+ this._lastHeadHash = headHash
249
+ this._lastHeadChangeTime = currentTime
250
+ }
251
+
252
+ // Check if we should resubmit due to stale head
253
+ const timeSinceHeadChange = isDefined(this._lastHeadChangeTime) ? currentTime - this._lastHeadChangeTime : 0
254
+
255
+ // If we have never produced a block or the last produced block was not built on the current head we
256
+ // need to attempt to produce a new block
257
+ const shouldSubmit = !this._lastProducedBlock || this._lastProducedBlock[0].previous !== headHash
258
+
259
+ // If the head has not changed determine if we should resubmit again based on head staleness
260
+ const shouldResubmit = timeSinceHeadChange > ProducerActor.HeadResubmissionThreshold
261
+
262
+ // Determine if we should submit or resubmit
263
+ const shouldSubmitBlock = shouldSubmit || shouldResubmit
264
+
265
+ if (shouldSubmitBlock) {
266
+ // If we are resubmitting due to stale head
267
+ if (shouldResubmit) {
268
+ // Log that we are resubmitting due to stale head
269
+ this.logger?.log(`Resubmitting block due to stale head. Head ${toFormattedBlockReference(head)} unchanged for ${timeSinceHeadChange}ms`)
270
+ // Reset timer since we're resubmitting
271
+ this._lastHeadChangeTime = currentTime
272
+ }
219
273
  this._producerActorBlockProductionAttempts?.add(1, this._metricAttributes)
220
274
  // Produce the next block
221
275
  const nextBlock = await this.blockRunner.next(head)
222
276
  // If it was produced
223
277
  if (nextBlock) {
224
- const displayBlockNumber = `0x${toHex(nextBlock[0].block)}`
278
+ const displayBlockNumber = toFormattedBlockReference(nextBlock[0])
225
279
  this.logger?.log('Produced block:', displayBlockNumber)
226
280
  this._producerActorBlocksProduced?.add(1, this._metricAttributes)
227
281
  // Insert the block into the chain
228
282
  await this.mempoolRunner.submitBlocks([nextBlock])
229
- this.logger?.log('Published block:', displayBlockNumber, nextBlock[0].block)
283
+ this.logger?.log('Published block:', displayBlockNumber)
230
284
  this._producerActorBlocksPublished?.add(1, this._metricAttributes)
231
285
  // Record that we have produced a block so we do not produce it again
232
286
  this._lastProducedBlock = nextBlock
233
287
  } else {
234
- this.logger?.log('No block produced at this time.')
288
+ this.logger?.log('No block produced for submission.')
289
+ }
290
+ } else {
291
+ // Log that we are not submitting a block at this time
292
+ let nonSubmissionMessage = 'No block submission required at this time'
293
+ // If we have already produced a block
294
+ if (isDefined(this._lastProducedBlock)) {
295
+ // include that info in the log
296
+ nonSubmissionMessage = nonSubmissionMessage + `, already produced: ${toFormattedBlockReference(this._lastProducedBlock[0])}`
235
297
  }
298
+ this.logger?.log(`${nonSubmissionMessage}.`)
236
299
  }
237
300
  })
238
301
  }, { ...this.context, timeBudgetLimit: 1000 })