@aztec/slasher 0.0.1-commit.c2eed6949 → 0.0.1-commit.c52d6e7

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.
Files changed (99) hide show
  1. package/README.md +83 -79
  2. package/dest/config.d.ts +1 -1
  3. package/dest/config.d.ts.map +1 -1
  4. package/dest/config.js +28 -28
  5. package/dest/factory/create_facade.d.ts +6 -3
  6. package/dest/factory/create_facade.d.ts.map +1 -1
  7. package/dest/factory/create_facade.js +5 -2
  8. package/dest/factory/create_implementation.d.ts +5 -6
  9. package/dest/factory/create_implementation.d.ts.map +1 -1
  10. package/dest/factory/create_implementation.js +7 -59
  11. package/dest/factory/get_settings.d.ts +4 -4
  12. package/dest/factory/get_settings.d.ts.map +1 -1
  13. package/dest/factory/get_settings.js +3 -3
  14. package/dest/factory/index.d.ts +2 -2
  15. package/dest/factory/index.d.ts.map +1 -1
  16. package/dest/factory/index.js +1 -1
  17. package/dest/generated/slasher-defaults.d.ts +4 -4
  18. package/dest/generated/slasher-defaults.js +4 -4
  19. package/dest/index.d.ts +6 -4
  20. package/dest/index.d.ts.map +1 -1
  21. package/dest/index.js +5 -3
  22. package/dest/metrics.d.ts +23 -0
  23. package/dest/metrics.d.ts.map +1 -0
  24. package/dest/metrics.js +38 -0
  25. package/dest/null_slasher_client.d.ts +3 -4
  26. package/dest/null_slasher_client.d.ts.map +1 -1
  27. package/dest/null_slasher_client.js +1 -4
  28. package/dest/own_validator_slash_monitor.d.ts +66 -0
  29. package/dest/own_validator_slash_monitor.d.ts.map +1 -0
  30. package/dest/own_validator_slash_monitor.js +163 -0
  31. package/dest/slash_offenses_collector.d.ts +6 -8
  32. package/dest/slash_offenses_collector.d.ts.map +1 -1
  33. package/dest/slash_offenses_collector.js +48 -28
  34. package/dest/slasher_client.d.ts +118 -0
  35. package/dest/slasher_client.d.ts.map +1 -0
  36. package/dest/{tally_slasher_client.js → slasher_client.js} +59 -40
  37. package/dest/slasher_client_facade.d.ts +6 -7
  38. package/dest/slasher_client_facade.d.ts.map +1 -1
  39. package/dest/slasher_client_facade.js +6 -9
  40. package/dest/slasher_client_interface.d.ts +7 -21
  41. package/dest/slasher_client_interface.d.ts.map +1 -1
  42. package/dest/slasher_client_interface.js +1 -4
  43. package/dest/stores/offenses_store.d.ts +12 -12
  44. package/dest/stores/offenses_store.d.ts.map +1 -1
  45. package/dest/stores/offenses_store.js +61 -38
  46. package/dest/watcher.d.ts +8 -1
  47. package/dest/watcher.d.ts.map +1 -1
  48. package/dest/watcher.js +1 -0
  49. package/dest/watchers/attestations_block_watcher.d.ts +26 -13
  50. package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
  51. package/dest/watchers/attestations_block_watcher.js +76 -61
  52. package/dest/watchers/attested_invalid_proposal_watcher.d.ts +42 -0
  53. package/dest/watchers/attested_invalid_proposal_watcher.d.ts.map +1 -0
  54. package/dest/watchers/attested_invalid_proposal_watcher.js +117 -0
  55. package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.d.ts +38 -0
  56. package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.d.ts.map +1 -0
  57. package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.js +138 -0
  58. package/dest/watchers/checkpoint_equivocation_watcher.d.ts +30 -0
  59. package/dest/watchers/checkpoint_equivocation_watcher.d.ts.map +1 -0
  60. package/dest/watchers/checkpoint_equivocation_watcher.js +69 -0
  61. package/dest/watchers/data_withholding_watcher.d.ts +63 -0
  62. package/dest/watchers/data_withholding_watcher.d.ts.map +1 -0
  63. package/dest/watchers/data_withholding_watcher.js +193 -0
  64. package/package.json +10 -10
  65. package/src/config.ts +33 -28
  66. package/src/factory/create_facade.ts +6 -3
  67. package/src/factory/create_implementation.ts +16 -117
  68. package/src/factory/get_settings.ts +8 -8
  69. package/src/factory/index.ts +1 -1
  70. package/src/generated/slasher-defaults.ts +4 -4
  71. package/src/index.ts +5 -3
  72. package/src/metrics.ts +59 -0
  73. package/src/null_slasher_client.ts +2 -6
  74. package/src/own_validator_slash_monitor.ts +184 -0
  75. package/src/slash_offenses_collector.ts +62 -29
  76. package/src/{tally_slasher_client.ts → slasher_client.ts} +78 -48
  77. package/src/slasher_client_facade.ts +6 -10
  78. package/src/slasher_client_interface.ts +6 -21
  79. package/src/stores/offenses_store.ts +73 -47
  80. package/src/watcher.ts +8 -0
  81. package/src/watchers/attestations_block_watcher.ts +88 -82
  82. package/src/watchers/attested_invalid_proposal_watcher.ts +168 -0
  83. package/src/watchers/broadcasted_invalid_checkpoint_proposal_watcher.ts +192 -0
  84. package/src/watchers/checkpoint_equivocation_watcher.ts +96 -0
  85. package/src/watchers/data_withholding_watcher.ts +225 -0
  86. package/dest/empire_slasher_client.d.ts +0 -190
  87. package/dest/empire_slasher_client.d.ts.map +0 -1
  88. package/dest/empire_slasher_client.js +0 -564
  89. package/dest/stores/payloads_store.d.ts +0 -29
  90. package/dest/stores/payloads_store.d.ts.map +0 -1
  91. package/dest/stores/payloads_store.js +0 -128
  92. package/dest/tally_slasher_client.d.ts +0 -125
  93. package/dest/tally_slasher_client.d.ts.map +0 -1
  94. package/dest/watchers/epoch_prune_watcher.d.ts +0 -39
  95. package/dest/watchers/epoch_prune_watcher.d.ts.map +0 -1
  96. package/dest/watchers/epoch_prune_watcher.js +0 -179
  97. package/src/empire_slasher_client.ts +0 -649
  98. package/src/stores/payloads_store.ts +0 -149
  99. package/src/watchers/epoch_prune_watcher.ts +0 -256
@@ -1,3 +1,3 @@
1
1
  export { createSlasherFacade as createSlasher } from './create_facade.js';
2
- export { getTallySlasherSettings } from './get_settings.js';
3
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9mYWN0b3J5L2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxtQkFBbUIsSUFBSSxhQUFhLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUMxRSxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQyJ9
2
+ export { getSlasherSettings } from './get_settings.js';
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9mYWN0b3J5L2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxtQkFBbUIsSUFBSSxhQUFhLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUMxRSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQyJ9
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/factory/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/factory/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -1,2 +1,2 @@
1
1
  export { createSlasherFacade as createSlasher } from './create_facade.js';
2
- export { getTallySlasherSettings } from './get_settings.js';
2
+ export { getSlasherSettings } from './get_settings.js';
@@ -1,21 +1,21 @@
1
1
  /** Default slasher configuration values from network-defaults.yml */
2
2
  export declare const slasherDefaultEnv: {
3
- readonly SLASH_MIN_PENALTY_PERCENTAGE: 0.5;
4
- readonly SLASH_MAX_PENALTY_PERCENTAGE: 2;
5
3
  readonly SLASH_OFFENSE_EXPIRATION_ROUNDS: 4;
6
4
  readonly SLASH_MAX_PAYLOAD_SIZE: 80;
7
5
  readonly SLASH_EXECUTE_ROUNDS_LOOK_BACK: 4;
8
- readonly SLASH_PRUNE_PENALTY: 10000000000000000000;
9
6
  readonly SLASH_DATA_WITHHOLDING_PENALTY: 10000000000000000000;
7
+ readonly SLASH_DATA_WITHHOLDING_TOLERANCE_SLOTS: 3;
10
8
  readonly SLASH_INACTIVITY_TARGET_PERCENTAGE: 0.9;
11
9
  readonly SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD: 1;
12
10
  readonly SLASH_INACTIVITY_PENALTY: 10000000000000000000;
13
11
  readonly SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY: 10000000000000000000;
14
- readonly SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY: 10000000000000000000;
12
+ readonly SLASH_PROPOSE_DESCENDANT_OF_CHECKPOINT_WITH_INVALID_ATTESTATIONS_PENALTY: 10000000000000000000;
13
+ readonly SLASH_ATTEST_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 10000000000000000000;
15
14
  readonly SLASH_DUPLICATE_PROPOSAL_PENALTY: 0;
16
15
  readonly SLASH_DUPLICATE_ATTESTATION_PENALTY: 0;
17
16
  readonly SLASH_UNKNOWN_PENALTY: 10000000000000000000;
18
17
  readonly SLASH_INVALID_BLOCK_PENALTY: 10000000000000000000;
18
+ readonly SLASH_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 0;
19
19
  readonly SLASH_GRACE_PERIOD_L2_SLOTS: 0;
20
20
  };
21
21
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hlci1kZWZhdWx0cy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2dlbmVyYXRlZC9zbGFzaGVyLWRlZmF1bHRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBLHFFQUFxRTtBQUNyRSxlQUFPLE1BQU0saUJBQWlCOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Q0FrQnBCLENBQUMifQ==
@@ -1,21 +1,21 @@
1
1
  // Auto-generated from spartan/environments/network-defaults.yml
2
2
  // Do not edit manually - run yarn generate to regenerate
3
3
  /** Default slasher configuration values from network-defaults.yml */ export const slasherDefaultEnv = {
4
- SLASH_MIN_PENALTY_PERCENTAGE: 0.5,
5
- SLASH_MAX_PENALTY_PERCENTAGE: 2,
6
4
  SLASH_OFFENSE_EXPIRATION_ROUNDS: 4,
7
5
  SLASH_MAX_PAYLOAD_SIZE: 80,
8
6
  SLASH_EXECUTE_ROUNDS_LOOK_BACK: 4,
9
- SLASH_PRUNE_PENALTY: 10000000000000000000,
10
7
  SLASH_DATA_WITHHOLDING_PENALTY: 10000000000000000000,
8
+ SLASH_DATA_WITHHOLDING_TOLERANCE_SLOTS: 3,
11
9
  SLASH_INACTIVITY_TARGET_PERCENTAGE: 0.9,
12
10
  SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD: 1,
13
11
  SLASH_INACTIVITY_PENALTY: 10000000000000000000,
14
12
  SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY: 10000000000000000000,
15
- SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY: 10000000000000000000,
13
+ SLASH_PROPOSE_DESCENDANT_OF_CHECKPOINT_WITH_INVALID_ATTESTATIONS_PENALTY: 10000000000000000000,
14
+ SLASH_ATTEST_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 10000000000000000000,
16
15
  SLASH_DUPLICATE_PROPOSAL_PENALTY: 0,
17
16
  SLASH_DUPLICATE_ATTESTATION_PENALTY: 0,
18
17
  SLASH_UNKNOWN_PENALTY: 10000000000000000000,
19
18
  SLASH_INVALID_BLOCK_PENALTY: 10000000000000000000,
19
+ SLASH_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 0,
20
20
  SLASH_GRACE_PERIOD_L2_SLOTS: 0
21
21
  };
package/dest/index.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  export * from './config.js';
2
- export * from './watchers/epoch_prune_watcher.js';
2
+ export * from './watchers/data_withholding_watcher.js';
3
3
  export * from './watchers/attestations_block_watcher.js';
4
- export * from './empire_slasher_client.js';
5
- export * from './tally_slasher_client.js';
4
+ export * from './watchers/attested_invalid_proposal_watcher.js';
5
+ export * from './watchers/broadcasted_invalid_checkpoint_proposal_watcher.js';
6
+ export * from './watchers/checkpoint_equivocation_watcher.js';
7
+ export * from './slasher_client.js';
6
8
  export * from './slash_offenses_collector.js';
7
9
  export * from './slasher_client_interface.js';
8
10
  export * from './factory/index.js';
9
11
  export * from './watcher.js';
10
12
  export * from '@aztec/stdlib/slashing';
11
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLG1DQUFtQyxDQUFDO0FBQ2xELGNBQWMsMENBQTBDLENBQUM7QUFDekQsY0FBYyw0QkFBNEIsQ0FBQztBQUMzQyxjQUFjLDJCQUEyQixDQUFDO0FBQzFDLGNBQWMsK0JBQStCLENBQUM7QUFDOUMsY0FBYywrQkFBK0IsQ0FBQztBQUM5QyxjQUFjLG9CQUFvQixDQUFDO0FBQ25DLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsd0JBQXdCLENBQUMifQ==
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLHdDQUF3QyxDQUFDO0FBQ3ZELGNBQWMsMENBQTBDLENBQUM7QUFDekQsY0FBYyxpREFBaUQsQ0FBQztBQUNoRSxjQUFjLCtEQUErRCxDQUFDO0FBQzlFLGNBQWMsK0NBQStDLENBQUM7QUFDOUQsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLCtCQUErQixDQUFDO0FBQzlDLGNBQWMsK0JBQStCLENBQUM7QUFDOUMsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLHdCQUF3QixDQUFDIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,mCAAmC,CAAC;AAClD,cAAc,0CAA0C,CAAC;AACzD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,wCAAwC,CAAC;AACvD,cAAc,0CAA0C,CAAC;AACzD,cAAc,iDAAiD,CAAC;AAChE,cAAc,+DAA+D,CAAC;AAC9E,cAAc,+CAA+C,CAAC;AAC9D,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC"}
package/dest/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  export * from './config.js';
2
- export * from './watchers/epoch_prune_watcher.js';
2
+ export * from './watchers/data_withholding_watcher.js';
3
3
  export * from './watchers/attestations_block_watcher.js';
4
- export * from './empire_slasher_client.js';
5
- export * from './tally_slasher_client.js';
4
+ export * from './watchers/attested_invalid_proposal_watcher.js';
5
+ export * from './watchers/broadcasted_invalid_checkpoint_proposal_watcher.js';
6
+ export * from './watchers/checkpoint_equivocation_watcher.js';
7
+ export * from './slasher_client.js';
6
8
  export * from './slash_offenses_collector.js';
7
9
  export * from './slasher_client_interface.js';
8
10
  export * from './factory/index.js';
@@ -0,0 +1,23 @@
1
+ import { type TelemetryClient } from '@aztec/telemetry-client';
2
+ export declare class SlasherMetrics {
3
+ private readonly roundExecuted;
4
+ private readonly ownValidatorTargeted;
5
+ private readonly ownValidatorSlashedCount;
6
+ private readonly ownValidatorSlashedAmount;
7
+ private readonly ownValidatorCurrentRoundVotesMax;
8
+ private readonly quorumSize;
9
+ constructor(client: TelemetryClient, name?: string);
10
+ recordRoundExecuted(): void;
11
+ /** Records the quorum a validator must reach in a round to be slashed, so dashboards can plot the threshold. */
12
+ recordQuorumSize(quorum: number): void;
13
+ /** Records that an onchain slashing vote named one of the node's own validators as a target. */
14
+ recordOwnValidatorTargeted(): void;
15
+ /**
16
+ * Records how close the most-voted committee position held by the node's own validators is to quorum this round.
17
+ * Recorded as an absolute value rather than a delta so a vote seen across a round rollover cannot make it drift.
18
+ */
19
+ recordCurrentRoundVotesMax(votes: number): void;
20
+ /** Records an executed slash against one of the node's own validators. */
21
+ recordOwnValidatorSlashed(amount: bigint): void;
22
+ }
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL21ldHJpY3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUdMLEtBQUssZUFBZSxFQUdyQixNQUFNLHlCQUF5QixDQUFDO0FBSWpDLHFCQUFhLGNBQWM7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxhQUFhLENBQWdCO0lBQzlDLE9BQU8sQ0FBQyxRQUFRLENBQUMsb0JBQW9CLENBQWdCO0lBQ3JELE9BQU8sQ0FBQyxRQUFRLENBQUMsd0JBQXdCLENBQWdCO0lBQ3pELE9BQU8sQ0FBQyxRQUFRLENBQUMseUJBQXlCLENBQWdCO0lBQzFELE9BQU8sQ0FBQyxRQUFRLENBQUMsZ0NBQWdDLENBQVE7SUFDekQsT0FBTyxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQVE7SUFFbkMsWUFBWSxNQUFNLEVBQUUsZUFBZSxFQUFFLElBQUksU0FBWSxFQVdwRDtJQUVNLG1CQUFtQixJQUFJLElBQUksQ0FFakM7SUFFRCxnSEFBZ0g7SUFDekcsZ0JBQWdCLENBQUMsTUFBTSxFQUFFLE1BQU0sR0FBRyxJQUFJLENBRTVDO0lBRUQsZ0dBQWdHO0lBQ3pGLDBCQUEwQixJQUFJLElBQUksQ0FFeEM7SUFFRDs7O09BR0c7SUFDSSwwQkFBMEIsQ0FBQyxLQUFLLEVBQUUsTUFBTSxHQUFHLElBQUksQ0FFckQ7SUFFRCwwRUFBMEU7SUFDbkUseUJBQXlCLENBQUMsTUFBTSxFQUFFLE1BQU0sR0FBRyxJQUFJLENBR3JEO0NBQ0YifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EAGrB,MAAM,yBAAyB,CAAC;AAIjC,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAgB;IACrD,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAgB;IACzD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAgB;IAC1D,OAAO,CAAC,QAAQ,CAAC,gCAAgC,CAAQ;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IAEnC,YAAY,MAAM,EAAE,eAAe,EAAE,IAAI,SAAY,EAWpD;IAEM,mBAAmB,IAAI,IAAI,CAEjC;IAED,gHAAgH;IACzG,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE5C;IAED,gGAAgG;IACzF,0BAA0B,IAAI,IAAI,CAExC;IAED;;;OAGG;IACI,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAErD;IAED,0EAA0E;IACnE,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAGrD;CACF"}
@@ -0,0 +1,38 @@
1
+ import { Metrics, createUpDownCounterWithDefault } from '@aztec/telemetry-client';
2
+ import { formatEther } from 'viem/utils';
3
+ export class SlasherMetrics {
4
+ roundExecuted;
5
+ ownValidatorTargeted;
6
+ ownValidatorSlashedCount;
7
+ ownValidatorSlashedAmount;
8
+ ownValidatorCurrentRoundVotesMax;
9
+ quorumSize;
10
+ constructor(client, name = 'Slasher'){
11
+ const meter = client.getMeter(name);
12
+ this.roundExecuted = createUpDownCounterWithDefault(meter, Metrics.SLASHER_ROUND_EXECUTED_COUNT);
13
+ this.ownValidatorTargeted = createUpDownCounterWithDefault(meter, Metrics.SLASHER_OWN_VALIDATOR_TARGETED_COUNT);
14
+ this.ownValidatorSlashedCount = createUpDownCounterWithDefault(meter, Metrics.SLASHER_OWN_VALIDATOR_SLASHED_COUNT);
15
+ this.ownValidatorSlashedAmount = createUpDownCounterWithDefault(meter, Metrics.SLASHER_OWN_VALIDATOR_SLASHED_AMOUNT);
16
+ this.ownValidatorCurrentRoundVotesMax = meter.createGauge(Metrics.SLASHER_OWN_VALIDATOR_CURRENT_ROUND_VOTES_MAX);
17
+ this.quorumSize = meter.createGauge(Metrics.SLASHER_QUORUM_SIZE);
18
+ }
19
+ recordRoundExecuted() {
20
+ this.roundExecuted.add(1);
21
+ }
22
+ /** Records the quorum a validator must reach in a round to be slashed, so dashboards can plot the threshold. */ recordQuorumSize(quorum) {
23
+ this.quorumSize.record(quorum);
24
+ }
25
+ /** Records that an onchain slashing vote named one of the node's own validators as a target. */ recordOwnValidatorTargeted() {
26
+ this.ownValidatorTargeted.add(1);
27
+ }
28
+ /**
29
+ * Records how close the most-voted committee position held by the node's own validators is to quorum this round.
30
+ * Recorded as an absolute value rather than a delta so a vote seen across a round rollover cannot make it drift.
31
+ */ recordCurrentRoundVotesMax(votes) {
32
+ this.ownValidatorCurrentRoundVotesMax.record(votes);
33
+ }
34
+ /** Records an executed slash against one of the node's own validators. */ recordOwnValidatorSlashed(amount) {
35
+ this.ownValidatorSlashedCount.add(1);
36
+ this.ownValidatorSlashedAmount.add(parseFloat(formatEther(amount)));
37
+ }
38
+ }
@@ -1,5 +1,5 @@
1
1
  import type { SlotNumber } from '@aztec/foundation/branded-types';
2
- import type { Offense, ProposerSlashAction, SlashPayloadRound } from '@aztec/stdlib/slashing';
2
+ import type { Offense, ProposerSlashAction } from '@aztec/stdlib/slashing';
3
3
  import type { SlasherConfig } from './config.js';
4
4
  import type { SlasherClientInterface } from './slasher_client_interface.js';
5
5
  export declare class NullSlasherClient implements SlasherClientInterface {
@@ -7,11 +7,10 @@ export declare class NullSlasherClient implements SlasherClientInterface {
7
7
  constructor(config: SlasherConfig);
8
8
  start(): Promise<void>;
9
9
  stop(): Promise<void>;
10
- getSlashPayloads(): Promise<SlashPayloadRound[]>;
11
10
  gatherOffensesForRound(_round?: bigint): Promise<Offense[]>;
12
- getPendingOffenses(): Promise<Offense[]>;
11
+ getOffenses(): Promise<Offense[]>;
13
12
  updateConfig(config: Partial<SlasherConfig>): void;
14
13
  getProposerActions(_slotNumber: SlotNumber): Promise<ProposerSlashAction[]>;
15
14
  getConfig(): SlasherConfig;
16
15
  }
17
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnVsbF9zbGFzaGVyX2NsaWVudC5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL251bGxfc2xhc2hlcl9jbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDbEUsT0FBTyxLQUFLLEVBQUUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLGlCQUFpQixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFFOUYsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ2pELE9BQU8sS0FBSyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFFNUUscUJBQWEsaUJBQWtCLFlBQVcsc0JBQXNCO0lBQ2xELE9BQU8sQ0FBQyxNQUFNO0lBQTFCLFlBQW9CLE1BQU0sRUFBRSxhQUFhLEVBQUk7SUFFdEMsS0FBSyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FFNUI7SUFFTSxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUUzQjtJQUVNLGdCQUFnQixJQUFJLE9BQU8sQ0FBQyxpQkFBaUIsRUFBRSxDQUFDLENBRXREO0lBRU0sc0JBQXNCLENBQUMsTUFBTSxDQUFDLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUVqRTtJQUVNLGtCQUFrQixJQUFJLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUU5QztJQUVNLFlBQVksQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLGFBQWEsQ0FBQyxHQUFHLElBQUksQ0FFeEQ7SUFFTSxrQkFBa0IsQ0FBQyxXQUFXLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsRUFBRSxDQUFDLENBRWpGO0lBRU0sU0FBUyxJQUFJLGFBQWEsQ0FFaEM7Q0FDRiJ9
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnVsbF9zbGFzaGVyX2NsaWVudC5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL251bGxfc2xhc2hlcl9jbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDbEUsT0FBTyxLQUFLLEVBQUUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFFM0UsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ2pELE9BQU8sS0FBSyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFFNUUscUJBQWEsaUJBQWtCLFlBQVcsc0JBQXNCO0lBQ2xELE9BQU8sQ0FBQyxNQUFNO0lBQTFCLFlBQW9CLE1BQU0sRUFBRSxhQUFhLEVBQUk7SUFFdEMsS0FBSyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FFNUI7SUFFTSxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUUzQjtJQUVNLHNCQUFzQixDQUFDLE1BQU0sQ0FBQyxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FFakU7SUFFTSxXQUFXLElBQUksT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBRXZDO0lBRU0sWUFBWSxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsYUFBYSxDQUFDLEdBQUcsSUFBSSxDQUV4RDtJQUVNLGtCQUFrQixDQUFDLFdBQVcsRUFBRSxVQUFVLEdBQUcsT0FBTyxDQUFDLG1CQUFtQixFQUFFLENBQUMsQ0FFakY7SUFFTSxTQUFTLElBQUksYUFBYSxDQUVoQztDQUNGIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"null_slasher_client.d.ts","sourceRoot":"","sources":["../src/null_slasher_client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE9F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAE5E,qBAAa,iBAAkB,YAAW,sBAAsB;IAClD,OAAO,CAAC,MAAM;IAA1B,YAAoB,MAAM,EAAE,aAAa,EAAI;IAEtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5B;IAEM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAEM,gBAAgB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAEtD;IAEM,sBAAsB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAEjE;IAEM,kBAAkB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAE9C;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAExD;IAEM,kBAAkB,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAEjF;IAEM,SAAS,IAAI,aAAa,CAEhC;CACF"}
1
+ {"version":3,"file":"null_slasher_client.d.ts","sourceRoot":"","sources":["../src/null_slasher_client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAE5E,qBAAa,iBAAkB,YAAW,sBAAsB;IAClD,OAAO,CAAC,MAAM;IAA1B,YAAoB,MAAM,EAAE,aAAa,EAAI;IAEtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5B;IAEM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAEM,sBAAsB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAEjE;IAEM,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAEvC;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAExD;IAEM,kBAAkB,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAEjF;IAEM,SAAS,IAAI,aAAa,CAEhC;CACF"}
@@ -9,13 +9,10 @@ export class NullSlasherClient {
9
9
  stop() {
10
10
  return Promise.resolve();
11
11
  }
12
- getSlashPayloads() {
13
- return Promise.resolve([]);
14
- }
15
12
  gatherOffensesForRound(_round) {
16
13
  return Promise.resolve([]);
17
14
  }
18
- getPendingOffenses() {
15
+ getOffenses() {
19
16
  return Promise.resolve([]);
20
17
  }
21
18
  updateConfig(config) {
@@ -0,0 +1,66 @@
1
+ import type { SlashingProposerContract } from '@aztec/ethereum/contracts';
2
+ import type { EthAddress } from '@aztec/foundation/eth-address';
3
+ import type { Hex } from 'viem';
4
+ import type { SlasherMetrics } from './metrics.js';
5
+ /**
6
+ * Watches slashing activity that targets the node's own validators: warns and counts on every vote naming one of
7
+ * them, tracks how close the current round's tally is to quorum, and reports executed slashes. Does nothing when
8
+ * the node runs no validators. Lifecycle calls must be sequential: start() before events are wired, and stop()
9
+ * awaited before any restart.
10
+ */
11
+ export declare class OwnValidatorSlashMonitor {
12
+ private readonly slashingProposer;
13
+ private readonly settings;
14
+ private readonly ownValidators;
15
+ private readonly metrics;
16
+ private readonly log;
17
+ /**
18
+ * Tally for the single round being tracked (votes are only ever cast for the current round). `nextVoteIndex` is
19
+ * the cursor: the index of the first vote in the round not yet processed, advanced from the indices the VoteCast
20
+ * events carry. It is undefined until the first event of a round that started mid-round, whose own vote is then
21
+ * the first one counted, so votes cast before the node subscribed are never replayed into the cumulative metrics.
22
+ * The tally is per flattened committee position because that is the unit the contract tallies quorum by.
23
+ */
24
+ private state;
25
+ /**
26
+ * All vote processing runs through this queue, one job at a time in arrival order. Serialization is what makes
27
+ * the cursor sound: without it, concurrent event handlers could process the same index twice or emit warnings
28
+ * with out-of-order running tallies. Replaced on every start(), since a queue that has been ended cannot run
29
+ * jobs again.
30
+ */
31
+ private queue;
32
+ private stopped;
33
+ constructor(slashingProposer: SlashingProposerContract, settings: {
34
+ slashingQuorumSize: number;
35
+ }, ownValidators: EthAddress[], metrics: SlasherMetrics, log?: import("@aztec/foundation/log").Logger);
36
+ private get enabled();
37
+ /**
38
+ * Starts tracking at the given round, leaving the cursor unset so the round's votes already cast are skipped:
39
+ * replaying them would warn about votes the operator can no longer react to sooner, and would double-count
40
+ * cumulative counters across restarts.
41
+ */
42
+ start(currentRound: bigint): void;
43
+ /** Stops processing and waits for any in-flight drain, so no warning or metric is emitted after shutdown. */
44
+ stop(): Promise<void>;
45
+ /**
46
+ * Called by the round monitor clock. Strictly greater so the clock catching up to a round an event already rolled
47
+ * to keeps the tally.
48
+ */
49
+ handleNewRound(round: bigint): void;
50
+ /**
51
+ * Called on each VoteCast event with the index the event carries. Reads every vote from the cursor up to that
52
+ * index, so an event delivery that never arrived is healed by the next one.
53
+ */
54
+ handleVoteCast(round: bigint, voteIndex: bigint): Promise<void>;
55
+ /** Reports executed slashes against own validators. Called with the Slashed events already fetched by the client. */
56
+ handleSlashes(round: bigint, slashes: {
57
+ attester: EthAddress;
58
+ amount: bigint;
59
+ }[], l1BlockHash: Hex): void;
60
+ private drainVotesTo;
61
+ private processVote;
62
+ /** Starts a fresh tally, zeroing the gauge so a quiet round does not keep the previous round's value. */
63
+ private rollTo;
64
+ private isOwnValidator;
65
+ }
66
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3duX3ZhbGlkYXRvcl9zbGFzaF9tb25pdG9yLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvb3duX3ZhbGlkYXRvcl9zbGFzaF9tb25pdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFtQix3QkFBd0IsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRTNGLE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBSWhFLE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUVoQyxPQUFPLEtBQUssRUFBRSxjQUFjLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFbkQ7Ozs7O0dBS0c7QUFDSCxxQkFBYSx3QkFBd0I7SUF5QmpDLE9BQU8sQ0FBQyxRQUFRLENBQUMsZ0JBQWdCO0lBQ2pDLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUTtJQUN6QixPQUFPLENBQUMsUUFBUSxDQUFDLGFBQWE7SUFDOUIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPO0lBQ3hCLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRztJQTVCdEI7Ozs7OztPQU1HO0lBQ0gsT0FBTyxDQUFDLEtBQUssQ0FJWDtJQUVGOzs7OztPQUtHO0lBQ0gsT0FBTyxDQUFDLEtBQUssQ0FBcUI7SUFFbEMsT0FBTyxDQUFDLE9BQU8sQ0FBUztJQUV4QixZQUNtQixnQkFBZ0IsRUFBRSx3QkFBd0IsRUFDMUMsUUFBUSxFQUFFO1FBQUUsa0JBQWtCLEVBQUUsTUFBTSxDQUFBO0tBQUUsRUFDeEMsYUFBYSxFQUFFLFVBQVUsRUFBRSxFQUMzQixPQUFPLEVBQUUsY0FBYyxFQUN2QixHQUFHLHlDQUF5QyxFQUc5RDtJQUVELE9BQU8sS0FBSyxPQUFPLEdBRWxCO0lBRUQ7Ozs7T0FJRztJQUNJLEtBQUssQ0FBQyxZQUFZLEVBQUUsTUFBTSxHQUFHLElBQUksQ0FVdkM7SUFFRCw2R0FBNkc7SUFDaEcsSUFBSSxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FLakM7SUFFRDs7O09BR0c7SUFDSSxjQUFjLENBQUMsS0FBSyxFQUFFLE1BQU0sR0FBRyxJQUFJLENBSXpDO0lBRUQ7OztPQUdHO0lBQ0ksY0FBYyxDQUFDLEtBQUssRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBU3JFO0lBRUQscUhBQXFIO0lBQzlHLGFBQWEsQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRTtRQUFFLFFBQVEsRUFBRSxVQUFVLENBQUM7UUFBQyxNQUFNLEVBQUUsTUFBTSxDQUFBO0tBQUUsRUFBRSxFQUFFLFdBQVcsRUFBRSxHQUFHLEdBQUcsSUFBSSxDQWEvRztZQUdhLFlBQVk7SUE0QjFCLE9BQU8sQ0FBQyxXQUFXO0lBd0JuQix5R0FBeUc7SUFDekcsT0FBTyxDQUFDLE1BQU07SUFNZCxPQUFPLENBQUMsY0FBYztDQUd2QiJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"own_validator_slash_monitor.d.ts","sourceRoot":"","sources":["../src/own_validator_slash_monitor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAE3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAIhE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEhC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD;;;;;GAKG;AACH,qBAAa,wBAAwB;IAyBjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG;IA5BtB;;;;;;OAMG;IACH,OAAO,CAAC,KAAK,CAIX;IAEF;;;;;OAKG;IACH,OAAO,CAAC,KAAK,CAAqB;IAElC,OAAO,CAAC,OAAO,CAAS;IAExB,YACmB,gBAAgB,EAAE,wBAAwB,EAC1C,QAAQ,EAAE;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,EACxC,aAAa,EAAE,UAAU,EAAE,EAC3B,OAAO,EAAE,cAAc,EACvB,GAAG,yCAAyC,EAG9D;IAED,OAAO,KAAK,OAAO,GAElB;IAED;;;;OAIG;IACI,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAUvC;IAED,6GAA6G;IAChG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAKjC;IAED;;;OAGG;IACI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAIzC;IAED;;;OAGG;IACI,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASrE;IAED,qHAAqH;IAC9G,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,EAAE,WAAW,EAAE,GAAG,GAAG,IAAI,CAa/G;YAGa,YAAY;IA4B1B,OAAO,CAAC,WAAW;IAwBnB,yGAAyG;IACzG,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,cAAc;CAGvB"}
@@ -0,0 +1,163 @@
1
+ import { uniqueBy } from '@aztec/foundation/collection';
2
+ import { createLogger } from '@aztec/foundation/log';
3
+ import { SerialQueue } from '@aztec/foundation/queue';
4
+ /**
5
+ * Watches slashing activity that targets the node's own validators: warns and counts on every vote naming one of
6
+ * them, tracks how close the current round's tally is to quorum, and reports executed slashes. Does nothing when
7
+ * the node runs no validators. Lifecycle calls must be sequential: start() before events are wired, and stop()
8
+ * awaited before any restart.
9
+ */ export class OwnValidatorSlashMonitor {
10
+ slashingProposer;
11
+ settings;
12
+ ownValidators;
13
+ metrics;
14
+ log;
15
+ /**
16
+ * Tally for the single round being tracked (votes are only ever cast for the current round). `nextVoteIndex` is
17
+ * the cursor: the index of the first vote in the round not yet processed, advanced from the indices the VoteCast
18
+ * events carry. It is undefined until the first event of a round that started mid-round, whose own vote is then
19
+ * the first one counted, so votes cast before the node subscribed are never replayed into the cumulative metrics.
20
+ * The tally is per flattened committee position because that is the unit the contract tallies quorum by.
21
+ */ state;
22
+ /**
23
+ * All vote processing runs through this queue, one job at a time in arrival order. Serialization is what makes
24
+ * the cursor sound: without it, concurrent event handlers could process the same index twice or emit warnings
25
+ * with out-of-order running tallies. Replaced on every start(), since a queue that has been ended cannot run
26
+ * jobs again.
27
+ */ queue;
28
+ stopped;
29
+ constructor(slashingProposer, settings, ownValidators, metrics, log = createLogger('slasher:own-validators')){
30
+ this.slashingProposer = slashingProposer;
31
+ this.settings = settings;
32
+ this.ownValidators = ownValidators;
33
+ this.metrics = metrics;
34
+ this.log = log;
35
+ this.state = {
36
+ round: -1n,
37
+ nextVoteIndex: undefined,
38
+ countByPosition: new Map()
39
+ };
40
+ this.queue = new SerialQueue();
41
+ this.stopped = false;
42
+ this.queue.start();
43
+ }
44
+ get enabled() {
45
+ return this.ownValidators.length > 0;
46
+ }
47
+ /**
48
+ * Starts tracking at the given round, leaving the cursor unset so the round's votes already cast are skipped:
49
+ * replaying them would warn about votes the operator can no longer react to sooner, and would double-count
50
+ * cumulative counters across restarts.
51
+ */ start(currentRound) {
52
+ if (!this.enabled) {
53
+ return;
54
+ }
55
+ this.stopped = false;
56
+ this.queue = new SerialQueue();
57
+ this.queue.start();
58
+ this.state = {
59
+ round: currentRound,
60
+ nextVoteIndex: undefined,
61
+ countByPosition: new Map()
62
+ };
63
+ this.metrics.recordQuorumSize(this.settings.slashingQuorumSize);
64
+ this.metrics.recordCurrentRoundVotesMax(0);
65
+ }
66
+ /** Stops processing and waits for any in-flight drain, so no warning or metric is emitted after shutdown. */ async stop() {
67
+ this.stopped = true;
68
+ // Jobs already queued are no-ops now that the flag is set, so end() runs through them at no cost while still
69
+ // settling the promises their callers hold, where cancel() would discard them and leave those promises pending.
70
+ await this.queue.end();
71
+ }
72
+ /**
73
+ * Called by the round monitor clock. Strictly greater so the clock catching up to a round an event already rolled
74
+ * to keeps the tally.
75
+ */ handleNewRound(round) {
76
+ if (this.enabled && round > this.state.round) {
77
+ this.rollTo(round);
78
+ }
79
+ }
80
+ /**
81
+ * Called on each VoteCast event with the index the event carries. Reads every vote from the cursor up to that
82
+ * index, so an event delivery that never arrived is healed by the next one.
83
+ */ handleVoteCast(round, voteIndex) {
84
+ if (!this.enabled || this.stopped) {
85
+ return Promise.resolve();
86
+ }
87
+ // Errors are logged rather than propagated: callers fire and forget, and a failed read is retried by the drain
88
+ // the next event triggers.
89
+ return this.queue.put(()=>this.drainVotesTo(round, voteIndex)).catch((err)=>this.log.error('Error processing slashing votes', err));
90
+ }
91
+ /** Reports executed slashes against own validators. Called with the Slashed events already fetched by the client. */ handleSlashes(round, slashes, l1BlockHash) {
92
+ if (!this.enabled || this.stopped) {
93
+ return;
94
+ }
95
+ for (const { attester, amount } of slashes.filter((slash)=>this.isOwnValidator(slash.attester))){
96
+ this.log.warn(`Own validator ${attester} was slashed for ${amount}`, {
97
+ round,
98
+ validator: attester.toString(),
99
+ amount,
100
+ l1BlockHash
101
+ });
102
+ this.metrics.recordOwnValidatorSlashed(amount);
103
+ }
104
+ }
105
+ /** Processes votes [cursor, voteIndex] of a round, in order, advancing the cursor after each success. */ async drainVotesTo(round, voteIndex) {
106
+ if (this.stopped) {
107
+ return; // enqueued before stop() but not yet started: emit nothing and read nothing after shutdown
108
+ }
109
+ if (round < this.state.round) {
110
+ return; // a vote for a round that already closed can no longer reach quorum
111
+ }
112
+ if (round > this.state.round) {
113
+ this.rollTo(round); // the event beat the round-monitor clock to the boundary
114
+ }
115
+ // The first event of a round tracked from mid-round counts its own vote only: earlier ones predate the subscription
116
+ const from = this.state.nextVoteIndex ?? voteIndex;
117
+ if (voteIndex < from) {
118
+ return; // a duplicate or out-of-order delivery of a vote already processed
119
+ }
120
+ for(let index = from; index <= voteIndex; index++){
121
+ const vote = await this.slashingProposer.getVoteAt(round, index);
122
+ if (this.stopped || round !== this.state.round) {
123
+ return; // stopped, or rolled mid-drain: either way this vote must no longer be counted
124
+ }
125
+ this.processVote(round, vote);
126
+ // Advanced only after successful processing, so a failed read is retried on the next event
127
+ this.state.nextVoteIndex = index + 1n;
128
+ }
129
+ }
130
+ processVote(round, vote) {
131
+ const own = vote.filter((target)=>this.isOwnValidator(target.validator));
132
+ for (const { position } of own){
133
+ this.state.countByPosition.set(position, (this.state.countByPosition.get(position) ?? 0) + 1);
134
+ }
135
+ // Warn once per (vote, validator) at the validator's highest position tally: the operator cares about the
136
+ // validator, not which of its committee seats is being voted on. No slash amount (the amount voted is not
137
+ // necessarily the amount slashed) and no proposer (a catch-up drain cannot attribute votes to proposers).
138
+ const quorum = this.settings.slashingQuorumSize;
139
+ for (const { validator } of uniqueBy(own, (target)=>target.validator.toString())){
140
+ const votes = Math.max(...own.filter((t)=>t.validator.equals(validator)).map((t)=>this.state.countByPosition.get(t.position)));
141
+ this.metrics.recordOwnValidatorTargeted();
142
+ this.log.warn(`Own validator ${validator} targeted by slashing vote (${votes} of ${quorum} votes needed to slash)`, {
143
+ round,
144
+ validator: validator.toString(),
145
+ votes,
146
+ quorum
147
+ });
148
+ }
149
+ this.metrics.recordCurrentRoundVotesMax(Math.max(0, ...this.state.countByPosition.values()));
150
+ }
151
+ /** Starts a fresh tally, zeroing the gauge so a quiet round does not keep the previous round's value. */ rollTo(round) {
152
+ this.state = {
153
+ round,
154
+ nextVoteIndex: 0n,
155
+ countByPosition: new Map()
156
+ };
157
+ this.metrics.recordCurrentRoundVotesMax(0);
158
+ this.metrics.recordQuorumSize(this.settings.slashingQuorumSize); // keeps the gauge fresh across export cycles
159
+ }
160
+ isOwnValidator(address) {
161
+ return this.ownValidators.some((validator)=>validator.equals(address));
162
+ }
163
+ }
@@ -2,9 +2,8 @@ import type { SlotNumber } from '@aztec/foundation/branded-types';
2
2
  import type { Prettify } from '@aztec/foundation/types';
3
3
  import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
4
4
  import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
5
- import { type OffenseIdentifier } from '@aztec/stdlib/slashing';
6
5
  import type { SlasherOffensesStore } from './stores/offenses_store.js';
7
- import { type WantToSlashArgs, type Watcher } from './watcher.js';
6
+ import { type WantToClearSlashArgs, type WantToSlashArgs, type Watcher } from './watcher.js';
8
7
  export type SlashOffensesCollectorConfig = Prettify<Pick<SlasherConfig, 'slashGracePeriodL2Slots'>>;
9
8
  export type SlashOffensesCollectorSettings = Prettify<Pick<L1RollupConstants, 'epochDuration'> & {
10
9
  slashingAmounts: [bigint, bigint, bigint] | undefined;
@@ -23,6 +22,7 @@ export declare class SlashOffensesCollector {
23
22
  private readonly offensesStore;
24
23
  private readonly log;
25
24
  private readonly unwatchCallbacks;
25
+ private readonly storeMutationQueue;
26
26
  constructor(config: SlashOffensesCollectorConfig, settings: SlashOffensesCollectorSettings, watchers: Watcher[], offensesStore: SlasherOffensesStore, log?: import("@aztec/foundation/log").Logger);
27
27
  start(): Promise<void>;
28
28
  stop(): Promise<void>;
@@ -32,17 +32,15 @@ export declare class SlashOffensesCollector {
32
32
  * @param args - the arguments from the watcher, including the validators, amounts, and offenses
33
33
  */
34
34
  handleWantToSlash(args: WantToSlashArgs[]): Promise<void>;
35
+ handleWantToClearSlash(args: WantToClearSlashArgs[]): Promise<void>;
35
36
  /**
36
37
  * Triggered on a time basis when we enter a new slashing round.
37
38
  * Clears expired offenses from stores.
38
39
  */
39
40
  handleNewRound(round: bigint): Promise<void>;
40
- /**
41
- * Marks offenses as slashed (no longer pending)
42
- * @param offenses - The offenses to mark as slashed
43
- */
44
- markAsSlashed(offenses: OffenseIdentifier[]): Promise<void>;
45
41
  /** Returns whether to skip an offense if it happened during the grace period after the network upgrade */
46
42
  private shouldSkipOffense;
43
+ private getOffenseLogData;
44
+ private enqueueStoreMutation;
47
45
  }
48
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hfb2ZmZW5zZXNfY29sbGVjdG9yLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2xhc2hfb2ZmZW5zZXNfY29sbGVjdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRWxFLE9BQU8sS0FBSyxFQUFFLFFBQVEsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3hELE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDckUsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDckUsT0FBTyxFQUFnQixLQUFLLGlCQUFpQixFQUFxQixNQUFNLHdCQUF3QixDQUFDO0FBRWpHLE9BQU8sS0FBSyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDdkUsT0FBTyxFQUF1QixLQUFLLGVBQWUsRUFBRSxLQUFLLE9BQU8sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUV2RixNQUFNLE1BQU0sNEJBQTRCLEdBQUcsUUFBUSxDQUFDLElBQUksQ0FBQyxhQUFhLEVBQUUseUJBQXlCLENBQUMsQ0FBQyxDQUFDO0FBQ3BHLE1BQU0sTUFBTSw4QkFBOEIsR0FBRyxRQUFRLENBQ25ELElBQUksQ0FBQyxpQkFBaUIsRUFBRSxlQUFlLENBQUMsR0FBRztJQUN6QyxlQUFlLEVBQUUsQ0FBQyxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxHQUFHLFNBQVMsQ0FBQztJQUN0RCxzSEFBc0g7SUFDdEgsd0JBQXdCLEVBQUUsVUFBVSxDQUFDO0NBQ3RDLENBQ0YsQ0FBQztBQUVGOzs7O0dBSUc7QUFDSCxxQkFBYSxzQkFBc0I7SUFJL0IsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNO0lBQ3ZCLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUTtJQUN6QixPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVE7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxhQUFhO0lBQzlCLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRztJQVB0QixPQUFPLENBQUMsUUFBUSxDQUFDLGdCQUFnQixDQUFzQjtJQUV2RCxZQUNtQixNQUFNLEVBQUUsNEJBQTRCLEVBQ3BDLFFBQVEsRUFBRSw4QkFBOEIsRUFDeEMsUUFBUSxFQUFFLE9BQU8sRUFBRSxFQUNuQixhQUFhLEVBQUUsb0JBQW9CLEVBQ25DLEdBQUcseUNBQTZDLEVBQy9EO0lBRUcsS0FBSyxrQkFhWDtJQUVNLElBQUksa0JBU1Y7SUFFRDs7OztPQUlHO0lBQ1UsaUJBQWlCLENBQUMsSUFBSSxFQUFFLGVBQWUsRUFBRSxpQkE2QnJEO0lBRUQ7OztPQUdHO0lBQ1UsY0FBYyxDQUFDLEtBQUssRUFBRSxNQUFNLGlCQUt4QztJQUVEOzs7T0FHRztJQUNJLGFBQWEsQ0FBQyxRQUFRLEVBQUUsaUJBQWlCLEVBQUUsaUJBR2pEO0lBRUQsMEdBQTBHO0lBQzFHLE9BQU8sQ0FBQyxpQkFBaUI7Q0FJMUIifQ==
46
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hfb2ZmZW5zZXNfY29sbGVjdG9yLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2xhc2hfb2ZmZW5zZXNfY29sbGVjdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBR2xFLE9BQU8sS0FBSyxFQUFFLFFBQVEsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3hELE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDckUsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFHckUsT0FBTyxLQUFLLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUN2RSxPQUFPLEVBR0wsS0FBSyxvQkFBb0IsRUFDekIsS0FBSyxlQUFlLEVBQ3BCLEtBQUssT0FBTyxFQUNiLE1BQU0sY0FBYyxDQUFDO0FBRXRCLE1BQU0sTUFBTSw0QkFBNEIsR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLGFBQWEsRUFBRSx5QkFBeUIsQ0FBQyxDQUFDLENBQUM7QUFDcEcsTUFBTSxNQUFNLDhCQUE4QixHQUFHLFFBQVEsQ0FDbkQsSUFBSSxDQUFDLGlCQUFpQixFQUFFLGVBQWUsQ0FBQyxHQUFHO0lBQ3pDLGVBQWUsRUFBRSxDQUFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDLEdBQUcsU0FBUyxDQUFDO0lBQ3RELHNIQUFzSDtJQUN0SCx3QkFBd0IsRUFBRSxVQUFVLENBQUM7Q0FDdEMsQ0FDRixDQUFDO0FBRUY7Ozs7R0FJRztBQUNILHFCQUFhLHNCQUFzQjtJQUsvQixPQUFPLENBQUMsUUFBUSxDQUFDLE1BQU07SUFDdkIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRO0lBQ3pCLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUTtJQUN6QixPQUFPLENBQUMsUUFBUSxDQUFDLGFBQWE7SUFDOUIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHO0lBUnRCLE9BQU8sQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLENBQXNCO0lBQ3ZELE9BQU8sQ0FBQyxRQUFRLENBQUMsa0JBQWtCLENBQXFCO0lBRXhELFlBQ21CLE1BQU0sRUFBRSw0QkFBNEIsRUFDcEMsUUFBUSxFQUFFLDhCQUE4QixFQUN4QyxRQUFRLEVBQUUsT0FBTyxFQUFFLEVBQ25CLGFBQWEsRUFBRSxvQkFBb0IsRUFDbkMsR0FBRyx5Q0FBNkMsRUFDL0Q7SUFFRyxLQUFLLGtCQW1CWDtJQUVZLElBQUksa0JBVWhCO0lBRUQ7Ozs7T0FJRztJQUNVLGlCQUFpQixDQUFDLElBQUksRUFBRSxlQUFlLEVBQUUsaUJBK0JyRDtJQUVZLHNCQUFzQixDQUFDLElBQUksRUFBRSxvQkFBb0IsRUFBRSxpQkFXL0Q7SUFFRDs7O09BR0c7SUFDVSxjQUFjLENBQUMsS0FBSyxFQUFFLE1BQU0saUJBS3hDO0lBRUQsMEdBQTBHO0lBQzFHLE9BQU8sQ0FBQyxpQkFBaUI7SUFLekIsT0FBTyxDQUFDLGlCQUFpQjtJQVF6QixPQUFPLENBQUMsb0JBQW9CO0NBRzdCIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"slash_offenses_collector.d.ts","sourceRoot":"","sources":["../src/slash_offenses_collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAgB,KAAK,iBAAiB,EAAqB,MAAM,wBAAwB,CAAC;AAEjG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAuB,KAAK,eAAe,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvF,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC,CAAC;AACpG,MAAM,MAAM,8BAA8B,GAAG,QAAQ,CACnD,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,GAAG;IACzC,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACtD,sHAAsH;IACtH,wBAAwB,EAAE,UAAU,CAAC;CACtC,CACF,CAAC;AAEF;;;;GAIG;AACH,qBAAa,sBAAsB;IAI/B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPtB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IAEvD,YACmB,MAAM,EAAE,4BAA4B,EACpC,QAAQ,EAAE,8BAA8B,EACxC,QAAQ,EAAE,OAAO,EAAE,EACnB,aAAa,EAAE,oBAAoB,EACnC,GAAG,yCAA6C,EAC/D;IAEG,KAAK,kBAaX;IAEM,IAAI,kBASV;IAED;;;;OAIG;IACU,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,iBA6BrD;IAED;;;OAGG;IACU,cAAc,CAAC,KAAK,EAAE,MAAM,iBAKxC;IAED;;;OAGG;IACI,aAAa,CAAC,QAAQ,EAAE,iBAAiB,EAAE,iBAGjD;IAED,0GAA0G;IAC1G,OAAO,CAAC,iBAAiB;CAI1B"}
1
+ {"version":3,"file":"slash_offenses_collector.d.ts","sourceRoot":"","sources":["../src/slash_offenses_collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAGlE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,OAAO,EACb,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC,CAAC;AACpG,MAAM,MAAM,8BAA8B,GAAG,QAAQ,CACnD,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,GAAG;IACzC,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACtD,sHAAsH;IACtH,wBAAwB,EAAE,UAAU,CAAC;CACtC,CACF,CAAC;AAEF;;;;GAIG;AACH,qBAAa,sBAAsB;IAK/B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG;IARtB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IACvD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IAExD,YACmB,MAAM,EAAE,4BAA4B,EACpC,QAAQ,EAAE,8BAA8B,EACxC,QAAQ,EAAE,OAAO,EAAE,EACnB,aAAa,EAAE,oBAAoB,EACnC,GAAG,yCAA6C,EAC/D;IAEG,KAAK,kBAmBX;IAEY,IAAI,kBAUhB;IAED;;;;OAIG;IACU,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,iBA+BrD;IAEY,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,EAAE,iBAW/D;IAED;;;OAGG;IACU,cAAc,CAAC,KAAK,EAAE,MAAM,iBAKxC;IAED,0GAA0G;IAC1G,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,oBAAoB;CAG7B"}