@lodestar/validator 1.35.0-dev.e9dd48f165 → 1.35.0-dev.fcf8d024ea

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 (173) hide show
  1. package/lib/buckets.d.ts.map +1 -0
  2. package/lib/defaults.d.ts.map +1 -0
  3. package/lib/genesis.d.ts.map +1 -0
  4. package/lib/index.d.ts +7 -7
  5. package/lib/index.d.ts.map +1 -0
  6. package/lib/index.js +5 -5
  7. package/lib/index.js.map +1 -1
  8. package/lib/metrics.d.ts.map +1 -0
  9. package/lib/metrics.js +14 -14
  10. package/lib/metrics.js.map +1 -1
  11. package/lib/repositories/index.d.ts.map +1 -0
  12. package/lib/repositories/metaDataRepository.d.ts.map +1 -0
  13. package/lib/repositories/metaDataRepository.js +4 -3
  14. package/lib/repositories/metaDataRepository.js.map +1 -1
  15. package/lib/services/attestation.d.ts.map +1 -0
  16. package/lib/services/attestation.js +77 -60
  17. package/lib/services/attestation.js.map +1 -1
  18. package/lib/services/attestationDuties.d.ts.map +1 -0
  19. package/lib/services/attestationDuties.js +105 -98
  20. package/lib/services/attestationDuties.js.map +1 -1
  21. package/lib/services/block.d.ts.map +1 -0
  22. package/lib/services/block.js +64 -56
  23. package/lib/services/block.js.map +1 -1
  24. package/lib/services/blockDuties.d.ts +2 -2
  25. package/lib/services/blockDuties.d.ts.map +1 -0
  26. package/lib/services/blockDuties.js +35 -26
  27. package/lib/services/blockDuties.js.map +1 -1
  28. package/lib/services/chainHeaderTracker.d.ts.map +1 -0
  29. package/lib/services/chainHeaderTracker.js +30 -27
  30. package/lib/services/chainHeaderTracker.js.map +1 -1
  31. package/lib/services/doppelgangerService.d.ts.map +1 -0
  32. package/lib/services/doppelgangerService.js +52 -45
  33. package/lib/services/doppelgangerService.js.map +1 -1
  34. package/lib/services/emitter.d.ts +1 -1
  35. package/lib/services/emitter.d.ts.map +1 -0
  36. package/lib/services/externalSignerSync.d.ts.map +1 -0
  37. package/lib/services/externalSignerSync.js.map +1 -1
  38. package/lib/services/indices.d.ts.map +1 -0
  39. package/lib/services/indices.js +8 -5
  40. package/lib/services/indices.js.map +1 -1
  41. package/lib/services/prepareBeaconProposer.d.ts.map +1 -0
  42. package/lib/services/prepareBeaconProposer.js.map +1 -1
  43. package/lib/services/syncCommittee.d.ts.map +1 -0
  44. package/lib/services/syncCommittee.js +80 -61
  45. package/lib/services/syncCommittee.js.map +1 -1
  46. package/lib/services/syncCommitteeDuties.d.ts.map +1 -0
  47. package/lib/services/syncCommitteeDuties.js +28 -23
  48. package/lib/services/syncCommitteeDuties.js.map +1 -1
  49. package/lib/services/syncingStatusTracker.d.ts.map +1 -0
  50. package/lib/services/syncingStatusTracker.js +32 -27
  51. package/lib/services/syncingStatusTracker.js.map +1 -1
  52. package/lib/services/utils.d.ts.map +1 -0
  53. package/lib/services/validatorStore.d.ts.map +1 -0
  54. package/lib/services/validatorStore.js +9 -3
  55. package/lib/services/validatorStore.js.map +1 -1
  56. package/lib/slashingProtection/attestation/attestationByTargetRepository.d.ts.map +1 -0
  57. package/lib/slashingProtection/attestation/attestationByTargetRepository.js +7 -3
  58. package/lib/slashingProtection/attestation/attestationByTargetRepository.js.map +1 -1
  59. package/lib/slashingProtection/attestation/attestationLowerBoundRepository.d.ts.map +1 -0
  60. package/lib/slashingProtection/attestation/attestationLowerBoundRepository.js +5 -3
  61. package/lib/slashingProtection/attestation/attestationLowerBoundRepository.js.map +1 -1
  62. package/lib/slashingProtection/attestation/errors.d.ts.map +1 -0
  63. package/lib/slashingProtection/attestation/index.d.ts.map +1 -0
  64. package/lib/slashingProtection/attestation/index.js +3 -0
  65. package/lib/slashingProtection/attestation/index.js.map +1 -1
  66. package/lib/slashingProtection/block/blockBySlotRepository.d.ts.map +1 -0
  67. package/lib/slashingProtection/block/blockBySlotRepository.js +7 -3
  68. package/lib/slashingProtection/block/blockBySlotRepository.js.map +1 -1
  69. package/lib/slashingProtection/block/errors.d.ts.map +1 -0
  70. package/lib/slashingProtection/block/index.d.ts.map +1 -0
  71. package/lib/slashingProtection/block/index.js +1 -0
  72. package/lib/slashingProtection/block/index.js.map +1 -1
  73. package/lib/slashingProtection/index.d.ts +1 -1
  74. package/lib/slashingProtection/index.d.ts.map +1 -0
  75. package/lib/slashingProtection/index.js +3 -0
  76. package/lib/slashingProtection/index.js.map +1 -1
  77. package/lib/slashingProtection/interchange/errors.d.ts.map +1 -0
  78. package/lib/slashingProtection/interchange/formats/completeV4.d.ts.map +1 -0
  79. package/lib/slashingProtection/interchange/formats/index.d.ts.map +1 -0
  80. package/lib/slashingProtection/interchange/formats/v5.d.ts.map +1 -0
  81. package/lib/slashingProtection/interchange/index.d.ts.map +1 -0
  82. package/lib/slashingProtection/interchange/parseInterchange.d.ts.map +1 -0
  83. package/lib/slashingProtection/interchange/serializeInterchange.d.ts.map +1 -0
  84. package/lib/slashingProtection/interchange/types.d.ts.map +1 -0
  85. package/lib/slashingProtection/interface.d.ts.map +1 -0
  86. package/lib/slashingProtection/minMaxSurround/distanceStoreRepository.d.ts.map +1 -0
  87. package/lib/slashingProtection/minMaxSurround/distanceStoreRepository.js +8 -0
  88. package/lib/slashingProtection/minMaxSurround/distanceStoreRepository.js.map +1 -1
  89. package/lib/slashingProtection/minMaxSurround/errors.d.ts.map +1 -0
  90. package/lib/slashingProtection/minMaxSurround/index.d.ts.map +1 -0
  91. package/lib/slashingProtection/minMaxSurround/interface.d.ts.map +1 -0
  92. package/lib/slashingProtection/minMaxSurround/minMaxSurround.d.ts.map +1 -0
  93. package/lib/slashingProtection/minMaxSurround/minMaxSurround.js +2 -0
  94. package/lib/slashingProtection/minMaxSurround/minMaxSurround.js.map +1 -1
  95. package/lib/slashingProtection/types.d.ts.map +1 -0
  96. package/lib/slashingProtection/utils.d.ts +1 -1
  97. package/lib/slashingProtection/utils.d.ts.map +1 -0
  98. package/lib/types.d.ts.map +1 -0
  99. package/lib/util/batch.d.ts.map +1 -0
  100. package/lib/util/clock.d.ts +3 -0
  101. package/lib/util/clock.d.ts.map +1 -0
  102. package/lib/util/clock.js +9 -1
  103. package/lib/util/clock.js.map +1 -1
  104. package/lib/util/difference.d.ts.map +1 -0
  105. package/lib/util/externalSignerClient.d.ts.map +1 -0
  106. package/lib/util/format.d.ts.map +1 -0
  107. package/lib/util/index.d.ts.map +1 -0
  108. package/lib/util/logger.d.ts.map +1 -0
  109. package/lib/util/params.d.ts.map +1 -0
  110. package/lib/util/params.js +18 -2
  111. package/lib/util/params.js.map +1 -1
  112. package/lib/util/url.d.ts.map +1 -0
  113. package/lib/validator.d.ts.map +1 -0
  114. package/lib/validator.js +15 -0
  115. package/lib/validator.js.map +1 -1
  116. package/package.json +19 -16
  117. package/src/buckets.ts +30 -0
  118. package/src/defaults.ts +8 -0
  119. package/src/genesis.ts +19 -0
  120. package/src/index.ts +22 -0
  121. package/src/metrics.ts +417 -0
  122. package/src/repositories/index.ts +1 -0
  123. package/src/repositories/metaDataRepository.ts +42 -0
  124. package/src/services/attestation.ts +362 -0
  125. package/src/services/attestationDuties.ts +406 -0
  126. package/src/services/block.ts +261 -0
  127. package/src/services/blockDuties.ts +217 -0
  128. package/src/services/chainHeaderTracker.ts +89 -0
  129. package/src/services/doppelgangerService.ts +286 -0
  130. package/src/services/emitter.ts +43 -0
  131. package/src/services/externalSignerSync.ts +81 -0
  132. package/src/services/indices.ts +165 -0
  133. package/src/services/prepareBeaconProposer.ts +119 -0
  134. package/src/services/syncCommittee.ts +338 -0
  135. package/src/services/syncCommitteeDuties.ts +337 -0
  136. package/src/services/syncingStatusTracker.ts +74 -0
  137. package/src/services/utils.ts +58 -0
  138. package/src/services/validatorStore.ts +830 -0
  139. package/src/slashingProtection/attestation/attestationByTargetRepository.ts +77 -0
  140. package/src/slashingProtection/attestation/attestationLowerBoundRepository.ts +44 -0
  141. package/src/slashingProtection/attestation/errors.ts +66 -0
  142. package/src/slashingProtection/attestation/index.ts +171 -0
  143. package/src/slashingProtection/block/blockBySlotRepository.ts +78 -0
  144. package/src/slashingProtection/block/errors.ts +28 -0
  145. package/src/slashingProtection/block/index.ts +94 -0
  146. package/src/slashingProtection/index.ts +95 -0
  147. package/src/slashingProtection/interchange/errors.ts +15 -0
  148. package/src/slashingProtection/interchange/formats/completeV4.ts +125 -0
  149. package/src/slashingProtection/interchange/formats/index.ts +7 -0
  150. package/src/slashingProtection/interchange/formats/v5.ts +120 -0
  151. package/src/slashingProtection/interchange/index.ts +5 -0
  152. package/src/slashingProtection/interchange/parseInterchange.ts +55 -0
  153. package/src/slashingProtection/interchange/serializeInterchange.ts +35 -0
  154. package/src/slashingProtection/interchange/types.ts +18 -0
  155. package/src/slashingProtection/interface.ts +28 -0
  156. package/src/slashingProtection/minMaxSurround/distanceStoreRepository.ts +57 -0
  157. package/src/slashingProtection/minMaxSurround/errors.ts +27 -0
  158. package/src/slashingProtection/minMaxSurround/index.ts +4 -0
  159. package/src/slashingProtection/minMaxSurround/interface.ts +23 -0
  160. package/src/slashingProtection/minMaxSurround/minMaxSurround.ts +104 -0
  161. package/src/slashingProtection/types.ts +12 -0
  162. package/src/slashingProtection/utils.ts +42 -0
  163. package/src/types.ts +31 -0
  164. package/src/util/batch.ts +15 -0
  165. package/src/util/clock.ts +170 -0
  166. package/src/util/difference.ts +10 -0
  167. package/src/util/externalSignerClient.ts +277 -0
  168. package/src/util/format.ts +3 -0
  169. package/src/util/index.ts +6 -0
  170. package/src/util/logger.ts +51 -0
  171. package/src/util/params.ts +320 -0
  172. package/src/util/url.ts +16 -0
  173. package/src/validator.ts +418 -0
@@ -0,0 +1,830 @@
1
+ import {SecretKey} from "@chainsafe/blst";
2
+ import {BitArray} from "@chainsafe/ssz";
3
+ import {routes} from "@lodestar/api";
4
+ import {BeaconConfig} from "@lodestar/config";
5
+ import {
6
+ DOMAIN_AGGREGATE_AND_PROOF,
7
+ DOMAIN_APPLICATION_BUILDER,
8
+ DOMAIN_BEACON_ATTESTER,
9
+ DOMAIN_BEACON_PROPOSER,
10
+ DOMAIN_CONTRIBUTION_AND_PROOF,
11
+ DOMAIN_RANDAO,
12
+ DOMAIN_SELECTION_PROOF,
13
+ DOMAIN_SYNC_COMMITTEE,
14
+ DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF,
15
+ ForkSeq,
16
+ } from "@lodestar/params";
17
+ import {
18
+ ZERO_HASH,
19
+ blindedOrFullBlockHashTreeRoot,
20
+ computeDomain,
21
+ computeEpochAtSlot,
22
+ computeSigningRoot,
23
+ computeStartSlotAtEpoch,
24
+ } from "@lodestar/state-transition";
25
+ import {
26
+ AggregateAndProof,
27
+ Attestation,
28
+ BLSPubkey,
29
+ BLSSignature,
30
+ BeaconBlock,
31
+ BlindedBeaconBlock,
32
+ Epoch,
33
+ Root,
34
+ SignedAggregateAndProof,
35
+ SignedBeaconBlock,
36
+ SignedBlindedBeaconBlock,
37
+ SingleAttestation,
38
+ Slot,
39
+ ValidatorIndex,
40
+ altair,
41
+ bellatrix,
42
+ phase0,
43
+ ssz,
44
+ } from "@lodestar/types";
45
+ import {fromHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
46
+ import {Metrics} from "../metrics.js";
47
+ import {ISlashingProtection} from "../slashingProtection/index.js";
48
+ import {PubkeyHex} from "../types.js";
49
+ import {SignableMessage, SignableMessageType, externalSignerPostSignature} from "../util/externalSignerClient.js";
50
+ import {isValidatePubkeyHex} from "../util/format.js";
51
+ import {LoggerVc} from "../util/logger.js";
52
+ import {DoppelgangerService} from "./doppelgangerService.js";
53
+ import {IndicesService} from "./indices.js";
54
+
55
+ type BLSPubkeyMaybeHex = BLSPubkey | PubkeyHex;
56
+ type Eth1Address = string;
57
+
58
+ export enum SignerType {
59
+ Local,
60
+ Remote,
61
+ }
62
+
63
+ export type SignerLocal = {
64
+ type: SignerType.Local;
65
+ secretKey: SecretKey;
66
+ };
67
+
68
+ export type SignerRemote = {
69
+ type: SignerType.Remote;
70
+ url: string;
71
+ pubkey: PubkeyHex;
72
+ };
73
+
74
+ type DefaultProposerConfig = {
75
+ graffiti?: string;
76
+ strictFeeRecipientCheck: boolean;
77
+ feeRecipient: Eth1Address;
78
+ builder: {
79
+ gasLimit: number;
80
+ selection: routes.validator.BuilderSelection;
81
+ boostFactor: bigint;
82
+ };
83
+ };
84
+
85
+ export type ProposerConfig = {
86
+ graffiti?: string;
87
+ strictFeeRecipientCheck?: boolean;
88
+ feeRecipient?: Eth1Address;
89
+ builder?: {
90
+ gasLimit?: number;
91
+ selection?: routes.validator.BuilderSelection;
92
+ boostFactor?: bigint;
93
+ };
94
+ };
95
+
96
+ export type ValidatorProposerConfig = {
97
+ proposerConfig: {[index: PubkeyHex]: ProposerConfig};
98
+ defaultConfig: ProposerConfig;
99
+ };
100
+
101
+ export type ValidatorStoreModules = {
102
+ config: BeaconConfig;
103
+ slashingProtection: ISlashingProtection;
104
+ indicesService: IndicesService;
105
+ doppelgangerService: DoppelgangerService | null;
106
+ metrics: Metrics | null;
107
+ };
108
+
109
+ /**
110
+ * This cache stores SignedValidatorRegistrationV1 data for a validator so that
111
+ * we do not create and send new registration objects to avoid DOSing the builder
112
+ *
113
+ * See: https://github.com/ChainSafe/lodestar/issues/4208
114
+ */
115
+ type BuilderData = {
116
+ validatorRegistration: bellatrix.SignedValidatorRegistrationV1;
117
+ regFullKey: string;
118
+ };
119
+
120
+ /**
121
+ * Validator entity capable of producing signatures. Either:
122
+ * - local: With BLS secret key
123
+ * - remote: With data to contact a remote signer
124
+ */
125
+ export type Signer = SignerLocal | SignerRemote;
126
+
127
+ type ValidatorData = ProposerConfig & {
128
+ signer: Signer;
129
+ builderData?: BuilderData;
130
+ };
131
+
132
+ export const defaultOptions = {
133
+ suggestedFeeRecipient: "0x0000000000000000000000000000000000000000",
134
+ defaultGasLimit: 45_000_000,
135
+ builderSelection: routes.validator.BuilderSelection.ExecutionOnly,
136
+ builderAliasSelection: routes.validator.BuilderSelection.Default,
137
+ builderBoostFactor: BigInt(100),
138
+ // spec asks for gossip validation by default
139
+ broadcastValidation: routes.beacon.BroadcastValidation.gossip,
140
+ // should request fetching the locally produced block in blinded format
141
+ blindedLocal: false,
142
+ };
143
+
144
+ export const MAX_BUILDER_BOOST_FACTOR = 2n ** 64n - 1n;
145
+
146
+ /**
147
+ * Service that sets up and handles validator attester duties.
148
+ */
149
+ export class ValidatorStore {
150
+ private readonly config: BeaconConfig;
151
+ private readonly slashingProtection: ISlashingProtection;
152
+ private readonly indicesService: IndicesService;
153
+ private readonly doppelgangerService: DoppelgangerService | null;
154
+ private readonly metrics: Metrics | null;
155
+
156
+ private readonly validators = new Map<PubkeyHex, ValidatorData>();
157
+ /** Initially true because there are no validators */
158
+ private pubkeysToDiscover: PubkeyHex[] = [];
159
+ private readonly defaultProposerConfig: DefaultProposerConfig;
160
+
161
+ constructor(modules: ValidatorStoreModules, valProposerConfig: ValidatorProposerConfig) {
162
+ const {config, slashingProtection, indicesService, doppelgangerService, metrics} = modules;
163
+ this.config = config;
164
+ this.slashingProtection = slashingProtection;
165
+ this.indicesService = indicesService;
166
+ this.doppelgangerService = doppelgangerService;
167
+ this.metrics = metrics;
168
+
169
+ const defaultConfig = valProposerConfig.defaultConfig;
170
+ const builderBoostFactor = defaultConfig.builder?.boostFactor ?? defaultOptions.builderBoostFactor;
171
+ if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
172
+ throw Error(`Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR for defaultConfig`);
173
+ }
174
+
175
+ this.defaultProposerConfig = {
176
+ graffiti: defaultConfig.graffiti,
177
+ strictFeeRecipientCheck: defaultConfig.strictFeeRecipientCheck ?? false,
178
+ feeRecipient: defaultConfig.feeRecipient ?? defaultOptions.suggestedFeeRecipient,
179
+ builder: {
180
+ gasLimit: defaultConfig.builder?.gasLimit ?? defaultOptions.defaultGasLimit,
181
+ selection: defaultConfig.builder?.selection ?? defaultOptions.builderSelection,
182
+ boostFactor: builderBoostFactor,
183
+ },
184
+ };
185
+
186
+ if (metrics) {
187
+ metrics.signers.addCollect(() => metrics.signers.set(this.validators.size));
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Create a validator store with initial signers
193
+ */
194
+ static async init(
195
+ modules: ValidatorStoreModules,
196
+ initialSigners: Signer[],
197
+ valProposerConfig: ValidatorProposerConfig = {defaultConfig: {}, proposerConfig: {}}
198
+ ): Promise<ValidatorStore> {
199
+ const validatorStore = new ValidatorStore(modules, valProposerConfig);
200
+
201
+ await Promise.all(initialSigners.map((signer) => validatorStore.addSigner(signer, valProposerConfig)));
202
+
203
+ return validatorStore;
204
+ }
205
+
206
+ /** Return all known indices from the validatorStore pubkeys */
207
+ getAllLocalIndices(): ValidatorIndex[] {
208
+ return this.indicesService.getAllLocalIndices();
209
+ }
210
+
211
+ getPubkeyOfIndex(index: ValidatorIndex): PubkeyHex | undefined {
212
+ return this.indicesService.index2pubkey.get(index);
213
+ }
214
+
215
+ pollValidatorIndices(): Promise<ValidatorIndex[]> {
216
+ // Consumers will call this function every epoch forever. If everyone has been discovered, skip
217
+ return this.indicesService.indexCount >= this.validators.size
218
+ ? Promise.resolve([])
219
+ : this.indicesService.pollValidatorIndices(Array.from(this.validators.keys()));
220
+ }
221
+
222
+ getFeeRecipient(pubkeyHex: PubkeyHex): Eth1Address {
223
+ const validatorData = this.validators.get(pubkeyHex);
224
+ if (validatorData === undefined) {
225
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
226
+ }
227
+ return validatorData.feeRecipient ?? this.defaultProposerConfig.feeRecipient;
228
+ }
229
+
230
+ getFeeRecipientByIndex(index: ValidatorIndex): Eth1Address {
231
+ const pubkey = this.indicesService.index2pubkey.get(index);
232
+ return pubkey ? this.getFeeRecipient(pubkey) : this.defaultProposerConfig.feeRecipient;
233
+ }
234
+
235
+ setFeeRecipient(pubkeyHex: PubkeyHex, feeRecipient: Eth1Address): void {
236
+ const validatorData = this.validators.get(pubkeyHex);
237
+ if (validatorData === undefined) {
238
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
239
+ }
240
+ // This should directly modify data in the map
241
+ validatorData.feeRecipient = feeRecipient;
242
+ }
243
+
244
+ deleteFeeRecipient(pubkeyHex: PubkeyHex): void {
245
+ const validatorData = this.validators.get(pubkeyHex);
246
+ if (validatorData === undefined) {
247
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
248
+ }
249
+ // This should directly modify data in the map
250
+ delete validatorData.feeRecipient;
251
+ }
252
+
253
+ getGraffiti(pubkeyHex: PubkeyHex): string | undefined {
254
+ const validatorData = this.validators.get(pubkeyHex);
255
+ if (validatorData === undefined) {
256
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
257
+ }
258
+ return validatorData.graffiti ?? this.defaultProposerConfig.graffiti;
259
+ }
260
+
261
+ setGraffiti(pubkeyHex: PubkeyHex, graffiti: string): void {
262
+ const validatorData = this.validators.get(pubkeyHex);
263
+ if (validatorData === undefined) {
264
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
265
+ }
266
+ validatorData.graffiti = graffiti;
267
+ }
268
+
269
+ deleteGraffiti(pubkeyHex: PubkeyHex): void {
270
+ const validatorData = this.validators.get(pubkeyHex);
271
+ if (validatorData === undefined) {
272
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
273
+ }
274
+ delete validatorData.graffiti;
275
+ }
276
+
277
+ getBuilderSelectionParams(pubkeyHex: PubkeyHex): {selection: routes.validator.BuilderSelection; boostFactor: bigint} {
278
+ const selection =
279
+ this.validators.get(pubkeyHex)?.builder?.selection ?? this.defaultProposerConfig.builder.selection;
280
+
281
+ let boostFactor: bigint;
282
+ switch (selection) {
283
+ case routes.validator.BuilderSelection.Default:
284
+ // Default value slightly favors local block to improve censorship resistance of Ethereum
285
+ // The people have spoken and so it shall be https://x.com/lodestar_eth/status/1772679499928191044
286
+ boostFactor = BigInt(90);
287
+ break;
288
+
289
+ case routes.validator.BuilderSelection.MaxProfit:
290
+ boostFactor =
291
+ this.validators.get(pubkeyHex)?.builder?.boostFactor ?? this.defaultProposerConfig.builder.boostFactor;
292
+ break;
293
+
294
+ case routes.validator.BuilderSelection.BuilderAlways:
295
+ case routes.validator.BuilderSelection.BuilderOnly:
296
+ boostFactor = MAX_BUILDER_BOOST_FACTOR;
297
+ break;
298
+
299
+ case routes.validator.BuilderSelection.ExecutionAlways:
300
+ case routes.validator.BuilderSelection.ExecutionOnly:
301
+ boostFactor = BigInt(0);
302
+ }
303
+
304
+ return {selection, boostFactor};
305
+ }
306
+
307
+ strictFeeRecipientCheck(pubkeyHex: PubkeyHex): boolean {
308
+ return (
309
+ this.validators.get(pubkeyHex)?.strictFeeRecipientCheck ?? this.defaultProposerConfig?.strictFeeRecipientCheck
310
+ );
311
+ }
312
+
313
+ getGasLimit(pubkeyHex: PubkeyHex): number {
314
+ const validatorData = this.validators.get(pubkeyHex);
315
+ if (validatorData === undefined) {
316
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
317
+ }
318
+ return validatorData?.builder?.gasLimit ?? this.defaultProposerConfig.builder.gasLimit;
319
+ }
320
+
321
+ setGasLimit(pubkeyHex: PubkeyHex, gasLimit: number): void {
322
+ const validatorData = this.validators.get(pubkeyHex);
323
+ if (validatorData === undefined) {
324
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
325
+ }
326
+ validatorData.builder = {...validatorData.builder, gasLimit};
327
+ }
328
+
329
+ deleteGasLimit(pubkeyHex: PubkeyHex): void {
330
+ const validatorData = this.validators.get(pubkeyHex);
331
+ if (validatorData === undefined) {
332
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
333
+ }
334
+ delete validatorData.builder?.gasLimit;
335
+ }
336
+
337
+ getBuilderBoostFactor(pubkeyHex: PubkeyHex): bigint {
338
+ const validatorData = this.validators.get(pubkeyHex);
339
+ if (validatorData === undefined) {
340
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
341
+ }
342
+ return validatorData?.builder?.boostFactor ?? this.defaultProposerConfig.builder.boostFactor;
343
+ }
344
+
345
+ setBuilderBoostFactor(pubkeyHex: PubkeyHex, boostFactor: bigint): void {
346
+ if (boostFactor > MAX_BUILDER_BOOST_FACTOR) {
347
+ throw Error(`Invalid builderBoostFactor=${boostFactor} > MAX_BUILDER_BOOST_FACTOR`);
348
+ }
349
+
350
+ const validatorData = this.validators.get(pubkeyHex);
351
+ if (validatorData === undefined) {
352
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
353
+ }
354
+ validatorData.builder = {...validatorData.builder, boostFactor};
355
+ }
356
+
357
+ deleteBuilderBoostFactor(pubkeyHex: PubkeyHex): void {
358
+ const validatorData = this.validators.get(pubkeyHex);
359
+ if (validatorData === undefined) {
360
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
361
+ }
362
+ delete validatorData.builder?.boostFactor;
363
+ }
364
+
365
+ /** Return true if `index` is active part of this validator client */
366
+ hasValidatorIndex(index: ValidatorIndex): boolean {
367
+ return this.indicesService.index2pubkey.has(index);
368
+ }
369
+
370
+ getProposerConfig(pubkeyHex: PubkeyHex): ProposerConfig | null {
371
+ let proposerConfig: ProposerConfig | null = null;
372
+ const validatorData = this.validators.get(pubkeyHex);
373
+ if (validatorData === undefined) {
374
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
375
+ }
376
+
377
+ const {graffiti, strictFeeRecipientCheck, feeRecipient, builder} = validatorData;
378
+
379
+ // if anything is set , i.e not default then return
380
+ if (
381
+ graffiti !== undefined ||
382
+ strictFeeRecipientCheck !== undefined ||
383
+ feeRecipient !== undefined ||
384
+ builder?.gasLimit !== undefined ||
385
+ builder?.selection !== undefined ||
386
+ builder?.boostFactor !== undefined
387
+ ) {
388
+ proposerConfig = {graffiti, strictFeeRecipientCheck, feeRecipient, builder};
389
+ }
390
+ return proposerConfig;
391
+ }
392
+
393
+ async addSigner(signer: Signer, valProposerConfig?: ValidatorProposerConfig): Promise<void> {
394
+ const pubkey = getSignerPubkeyHex(signer);
395
+ const proposerConfig = valProposerConfig?.proposerConfig?.[pubkey];
396
+ const builderBoostFactor = proposerConfig?.builder?.boostFactor;
397
+ if (builderBoostFactor !== undefined && builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
398
+ throw Error(`Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR for pubkey=${pubkey}`);
399
+ }
400
+
401
+ if (!this.validators.has(pubkey)) {
402
+ // Doppelganger registration must be done before adding validator to signers
403
+ await this.doppelgangerService?.registerValidator(pubkey);
404
+
405
+ this.pubkeysToDiscover.push(pubkey);
406
+ this.validators.set(pubkey, {
407
+ signer,
408
+ ...proposerConfig,
409
+ });
410
+ }
411
+ }
412
+
413
+ getSigner(pubkeyHex: PubkeyHex): Signer | undefined {
414
+ return this.validators.get(pubkeyHex)?.signer;
415
+ }
416
+
417
+ removeSigner(pubkeyHex: PubkeyHex): boolean {
418
+ this.doppelgangerService?.unregisterValidator(pubkeyHex);
419
+
420
+ return this.indicesService.removeForKey(pubkeyHex) || this.validators.delete(pubkeyHex);
421
+ }
422
+
423
+ /** Return true if there is at least 1 pubkey registered */
424
+ hasSomeValidators(): boolean {
425
+ return this.validators.size > 0;
426
+ }
427
+
428
+ votingPubkeys(): PubkeyHex[] {
429
+ return Array.from(this.validators.keys());
430
+ }
431
+
432
+ hasVotingPubkey(pubkeyHex: PubkeyHex): boolean {
433
+ return this.validators.has(pubkeyHex);
434
+ }
435
+
436
+ getRemoteSignerPubkeys(signerUrl: string): PubkeyHex[] {
437
+ const pubkeysHex = [];
438
+ for (const {signer} of this.validators.values()) {
439
+ if (signer.type === SignerType.Remote && signer.url === signerUrl) {
440
+ pubkeysHex.push(signer.pubkey);
441
+ }
442
+ }
443
+ return pubkeysHex;
444
+ }
445
+
446
+ async signBlock(
447
+ pubkey: BLSPubkey,
448
+ blindedOrFull: BeaconBlock | BlindedBeaconBlock,
449
+ currentSlot: Slot,
450
+ logger?: LoggerVc
451
+ ): Promise<SignedBeaconBlock | SignedBlindedBeaconBlock> {
452
+ // Make sure the block slot is not higher than the current slot to avoid potential attacks.
453
+ if (blindedOrFull.slot > currentSlot) {
454
+ throw Error(`Not signing block with slot ${blindedOrFull.slot} greater than current slot ${currentSlot}`);
455
+ }
456
+
457
+ // Duties are filtered before-hard by doppelganger-safe, this assert should never throw
458
+ this.assertDoppelgangerSafe(pubkey);
459
+
460
+ const signingSlot = blindedOrFull.slot;
461
+ const domain = this.config.getDomain(signingSlot, DOMAIN_BEACON_PROPOSER);
462
+ const blockRoot = blindedOrFullBlockHashTreeRoot(this.config, blindedOrFull);
463
+ // Don't use `computeSigningRoot()` here to compute the objectRoot in typesafe function blindedOrFullBlockHashTreeRoot()
464
+ const signingRoot = ssz.phase0.SigningData.hashTreeRoot({objectRoot: blockRoot, domain});
465
+
466
+ logger?.debug("Signing the block proposal", {
467
+ slot: signingSlot,
468
+ blockRoot: toRootHex(blockRoot),
469
+ signingRoot: toRootHex(signingRoot),
470
+ });
471
+
472
+ try {
473
+ await this.slashingProtection.checkAndInsertBlockProposal(pubkey, {slot: signingSlot, signingRoot});
474
+ } catch (e) {
475
+ this.metrics?.slashingProtectionBlockError.inc();
476
+ throw e;
477
+ }
478
+
479
+ const signableMessage: SignableMessage = {
480
+ type: SignableMessageType.BLOCK_V2,
481
+ data: blindedOrFull,
482
+ };
483
+
484
+ return {
485
+ message: blindedOrFull,
486
+ signature: await this.getSignature(pubkey, signingRoot, signingSlot, signableMessage),
487
+ } as SignedBeaconBlock | SignedBlindedBeaconBlock;
488
+ }
489
+
490
+ async signRandao(pubkey: BLSPubkey, slot: Slot): Promise<BLSSignature> {
491
+ const signingSlot = slot;
492
+ const domain = this.config.getDomain(slot, DOMAIN_RANDAO);
493
+ const epoch = computeEpochAtSlot(slot);
494
+ const signingRoot = computeSigningRoot(ssz.Epoch, epoch, domain);
495
+
496
+ const signableMessage: SignableMessage = {
497
+ type: SignableMessageType.RANDAO_REVEAL,
498
+ data: {epoch},
499
+ };
500
+
501
+ return this.getSignature(pubkey, signingRoot, signingSlot, signableMessage);
502
+ }
503
+
504
+ async signAttestation(
505
+ duty: routes.validator.AttesterDuty,
506
+ attestationData: phase0.AttestationData,
507
+ currentEpoch: Epoch
508
+ ): Promise<SingleAttestation> {
509
+ // Make sure the target epoch is not higher than the current epoch to avoid potential attacks.
510
+ if (attestationData.target.epoch > currentEpoch) {
511
+ throw Error(
512
+ `Not signing attestation with target epoch ${attestationData.target.epoch} greater than current epoch ${currentEpoch}`
513
+ );
514
+ }
515
+
516
+ // Duties are filtered before-hard by doppelganger-safe, this assert should never throw
517
+ this.assertDoppelgangerSafe(duty.pubkey);
518
+
519
+ this.validateAttestationDuty(duty, attestationData);
520
+ const signingSlot = computeStartSlotAtEpoch(attestationData.target.epoch);
521
+ const domain = this.config.getDomain(signingSlot, DOMAIN_BEACON_ATTESTER);
522
+ const signingRoot = computeSigningRoot(ssz.phase0.AttestationData, attestationData, domain);
523
+
524
+ try {
525
+ await this.slashingProtection.checkAndInsertAttestation(duty.pubkey, {
526
+ sourceEpoch: attestationData.source.epoch,
527
+ targetEpoch: attestationData.target.epoch,
528
+ signingRoot,
529
+ });
530
+ } catch (e) {
531
+ this.metrics?.slashingProtectionAttestationError.inc();
532
+ throw e;
533
+ }
534
+
535
+ const signableMessage: SignableMessage = {
536
+ type: SignableMessageType.ATTESTATION,
537
+ data: attestationData,
538
+ };
539
+
540
+ if (this.config.getForkSeq(signingSlot) >= ForkSeq.electra) {
541
+ return {
542
+ committeeIndex: duty.committeeIndex,
543
+ attesterIndex: duty.validatorIndex,
544
+ data: attestationData,
545
+ signature: await this.getSignature(duty.pubkey, signingRoot, signingSlot, signableMessage),
546
+ };
547
+ }
548
+
549
+ return {
550
+ aggregationBits: BitArray.fromSingleBit(duty.committeeLength, duty.validatorCommitteeIndex),
551
+ data: attestationData,
552
+ signature: await this.getSignature(duty.pubkey, signingRoot, signingSlot, signableMessage),
553
+ } as phase0.Attestation;
554
+ }
555
+
556
+ async signAggregateAndProof(
557
+ duty: routes.validator.AttesterDuty,
558
+ selectionProof: BLSSignature,
559
+ aggregate: Attestation
560
+ ): Promise<SignedAggregateAndProof> {
561
+ this.validateAttestationDuty(duty, aggregate.data);
562
+
563
+ const aggregateAndProof: AggregateAndProof = {
564
+ aggregate,
565
+ aggregatorIndex: duty.validatorIndex,
566
+ selectionProof,
567
+ };
568
+
569
+ const signingSlot = aggregate.data.slot;
570
+ const domain = this.config.getDomain(signingSlot, DOMAIN_AGGREGATE_AND_PROOF);
571
+ const isPostElectra = this.config.getForkSeq(signingSlot) >= ForkSeq.electra;
572
+ const signingRoot = isPostElectra
573
+ ? computeSigningRoot(ssz.electra.AggregateAndProof, aggregateAndProof, domain)
574
+ : computeSigningRoot(ssz.phase0.AggregateAndProof, aggregateAndProof, domain);
575
+
576
+ const signableMessage: SignableMessage = {
577
+ type: isPostElectra ? SignableMessageType.AGGREGATE_AND_PROOF_V2 : SignableMessageType.AGGREGATE_AND_PROOF,
578
+ data: aggregateAndProof,
579
+ };
580
+
581
+ return {
582
+ message: aggregateAndProof,
583
+ signature: await this.getSignature(duty.pubkey, signingRoot, signingSlot, signableMessage),
584
+ };
585
+ }
586
+
587
+ async signSyncCommitteeSignature(
588
+ pubkey: BLSPubkeyMaybeHex,
589
+ validatorIndex: ValidatorIndex,
590
+ slot: Slot,
591
+ beaconBlockRoot: Root
592
+ ): Promise<altair.SyncCommitteeMessage> {
593
+ const signingSlot = slot;
594
+ const domain = this.config.getDomain(slot, DOMAIN_SYNC_COMMITTEE);
595
+ const signingRoot = computeSigningRoot(ssz.Root, beaconBlockRoot, domain);
596
+ const signableMessage: SignableMessage = {
597
+ type: SignableMessageType.SYNC_COMMITTEE_MESSAGE,
598
+ data: {beaconBlockRoot, slot},
599
+ };
600
+
601
+ return {
602
+ slot,
603
+ validatorIndex,
604
+ beaconBlockRoot,
605
+ signature: await this.getSignature(pubkey, signingRoot, signingSlot, signableMessage),
606
+ };
607
+ }
608
+
609
+ async signContributionAndProof(
610
+ duty: {pubkey: BLSPubkeyMaybeHex; validatorIndex: number},
611
+ selectionProof: BLSSignature,
612
+ contribution: altair.SyncCommitteeContribution
613
+ ): Promise<altair.SignedContributionAndProof> {
614
+ const contributionAndProof: altair.ContributionAndProof = {
615
+ contribution,
616
+ aggregatorIndex: duty.validatorIndex,
617
+ selectionProof,
618
+ };
619
+
620
+ const signingSlot = contribution.slot;
621
+ const domain = this.config.getDomain(signingSlot, DOMAIN_CONTRIBUTION_AND_PROOF);
622
+ const signingRoot = computeSigningRoot(ssz.altair.ContributionAndProof, contributionAndProof, domain);
623
+
624
+ const signableMessage: SignableMessage = {
625
+ type: SignableMessageType.SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF,
626
+ data: contributionAndProof,
627
+ };
628
+
629
+ return {
630
+ message: contributionAndProof,
631
+ signature: await this.getSignature(duty.pubkey, signingRoot, signingSlot, signableMessage),
632
+ };
633
+ }
634
+
635
+ async signAttestationSelectionProof(pubkey: BLSPubkeyMaybeHex, slot: Slot): Promise<BLSSignature> {
636
+ const signingSlot = slot;
637
+ const domain = this.config.getDomain(slot, DOMAIN_SELECTION_PROOF);
638
+ const signingRoot = computeSigningRoot(ssz.Slot, slot, domain);
639
+
640
+ const signableMessage: SignableMessage = {
641
+ type: SignableMessageType.AGGREGATION_SLOT,
642
+ data: {slot},
643
+ };
644
+
645
+ return this.getSignature(pubkey, signingRoot, signingSlot, signableMessage);
646
+ }
647
+
648
+ async signSyncCommitteeSelectionProof(
649
+ pubkey: BLSPubkeyMaybeHex,
650
+ slot: Slot,
651
+ subcommitteeIndex: number
652
+ ): Promise<BLSSignature> {
653
+ const signingSlot = slot;
654
+ const domain = this.config.getDomain(signingSlot, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF);
655
+ const signingData: altair.SyncAggregatorSelectionData = {
656
+ slot,
657
+ subcommitteeIndex,
658
+ };
659
+
660
+ const signingRoot = computeSigningRoot(ssz.altair.SyncAggregatorSelectionData, signingData, domain);
661
+
662
+ const signableMessage: SignableMessage = {
663
+ type: SignableMessageType.SYNC_COMMITTEE_SELECTION_PROOF,
664
+ data: {slot, subcommitteeIndex},
665
+ };
666
+
667
+ return this.getSignature(pubkey, signingRoot, signingSlot, signableMessage);
668
+ }
669
+
670
+ async signVoluntaryExit(
671
+ pubkey: BLSPubkeyMaybeHex,
672
+ validatorIndex: number,
673
+ exitEpoch: Epoch
674
+ ): Promise<phase0.SignedVoluntaryExit> {
675
+ const signingSlot = computeStartSlotAtEpoch(exitEpoch);
676
+ const domain = this.config.getDomainForVoluntaryExit(signingSlot);
677
+
678
+ const voluntaryExit: phase0.VoluntaryExit = {epoch: exitEpoch, validatorIndex};
679
+ const signingRoot = computeSigningRoot(ssz.phase0.VoluntaryExit, voluntaryExit, domain);
680
+
681
+ const signableMessage: SignableMessage = {
682
+ type: SignableMessageType.VOLUNTARY_EXIT,
683
+ data: voluntaryExit,
684
+ };
685
+
686
+ return {
687
+ message: voluntaryExit,
688
+ signature: await this.getSignature(pubkey, signingRoot, signingSlot, signableMessage),
689
+ };
690
+ }
691
+
692
+ isDoppelgangerSafe(pubkeyHex: PubkeyHex): boolean {
693
+ // If doppelganger is not enabled we assumed all keys to be safe for use
694
+ return !this.doppelgangerService || this.doppelgangerService.isDoppelgangerSafe(pubkeyHex);
695
+ }
696
+
697
+ async signValidatorRegistration(
698
+ pubkeyMaybeHex: BLSPubkeyMaybeHex,
699
+ regAttributes: {feeRecipient: Eth1Address; gasLimit: number},
700
+ _slot: Slot
701
+ ): Promise<bellatrix.SignedValidatorRegistrationV1> {
702
+ const pubkey = typeof pubkeyMaybeHex === "string" ? fromHex(pubkeyMaybeHex) : pubkeyMaybeHex;
703
+ const feeRecipient = fromHex(regAttributes.feeRecipient);
704
+ const {gasLimit} = regAttributes;
705
+
706
+ const validatorRegistration: bellatrix.ValidatorRegistrationV1 = {
707
+ feeRecipient,
708
+ gasLimit,
709
+ timestamp: Math.floor(Date.now() / 1000),
710
+ pubkey,
711
+ };
712
+
713
+ const signingSlot = 0;
714
+ const domain = computeDomain(DOMAIN_APPLICATION_BUILDER, this.config.GENESIS_FORK_VERSION, ZERO_HASH);
715
+ const signingRoot = computeSigningRoot(ssz.bellatrix.ValidatorRegistrationV1, validatorRegistration, domain);
716
+
717
+ const signableMessage: SignableMessage = {
718
+ type: SignableMessageType.VALIDATOR_REGISTRATION,
719
+ data: validatorRegistration,
720
+ };
721
+
722
+ return {
723
+ message: validatorRegistration,
724
+ signature: await this.getSignature(pubkeyMaybeHex, signingRoot, signingSlot, signableMessage),
725
+ };
726
+ }
727
+
728
+ async getValidatorRegistration(
729
+ pubkeyMaybeHex: BLSPubkeyMaybeHex,
730
+ regAttributes: {feeRecipient: Eth1Address; gasLimit: number},
731
+ slot: Slot
732
+ ): Promise<bellatrix.SignedValidatorRegistrationV1> {
733
+ const pubkeyHex = typeof pubkeyMaybeHex === "string" ? pubkeyMaybeHex : toPubkeyHex(pubkeyMaybeHex);
734
+ const {feeRecipient, gasLimit} = regAttributes;
735
+ const regFullKey = `${feeRecipient}-${gasLimit}`;
736
+ const validatorData = this.validators.get(pubkeyHex);
737
+ const builderData = validatorData?.builderData;
738
+ if (builderData?.regFullKey === regFullKey) {
739
+ return builderData.validatorRegistration;
740
+ }
741
+ const validatorRegistration = await this.signValidatorRegistration(pubkeyMaybeHex, regAttributes, slot);
742
+ // If pubkeyHex was actually registered, then update the regData
743
+ if (validatorData !== undefined) {
744
+ validatorData.builderData = {validatorRegistration, regFullKey};
745
+ this.validators.set(pubkeyHex, validatorData);
746
+ }
747
+ return validatorRegistration;
748
+ }
749
+
750
+ private async getSignature(
751
+ pubkey: BLSPubkeyMaybeHex,
752
+ signingRoot: Uint8Array,
753
+ signingSlot: Slot,
754
+ signableMessage: SignableMessage
755
+ ): Promise<BLSSignature> {
756
+ // TODO: Refactor indexing to not have to run toHex() on the pubkey every time
757
+ const pubkeyHex = typeof pubkey === "string" ? pubkey : toPubkeyHex(pubkey);
758
+
759
+ const signer = this.validators.get(pubkeyHex)?.signer;
760
+ if (!signer) {
761
+ throw Error(`Validator pubkey ${pubkeyHex} not known`);
762
+ }
763
+
764
+ switch (signer.type) {
765
+ case SignerType.Local: {
766
+ const timer = this.metrics?.localSignTime.startTimer();
767
+ const signature = signer.secretKey.sign(signingRoot).toBytes();
768
+ timer?.();
769
+ return signature;
770
+ }
771
+
772
+ case SignerType.Remote: {
773
+ const timer = this.metrics?.remoteSignTime.startTimer();
774
+ try {
775
+ const signatureHex = await externalSignerPostSignature(
776
+ this.config,
777
+ signer.url,
778
+ pubkeyHex,
779
+ signingRoot,
780
+ signingSlot,
781
+ signableMessage
782
+ );
783
+ return fromHex(signatureHex);
784
+ } catch (e) {
785
+ this.metrics?.remoteSignErrors.inc();
786
+ throw e;
787
+ } finally {
788
+ timer?.();
789
+ }
790
+ }
791
+ }
792
+ }
793
+
794
+ /** Prevent signing bad data sent by the Beacon node */
795
+ private validateAttestationDuty(duty: routes.validator.AttesterDuty, data: phase0.AttestationData): void {
796
+ if (duty.slot !== data.slot) {
797
+ throw Error(`Inconsistent duties during signing: duty.slot ${duty.slot} != att.slot ${data.slot}`);
798
+ }
799
+
800
+ const isPostElectra = this.config.getForkSeq(data.slot) >= ForkSeq.electra;
801
+ if (!isPostElectra && duty.committeeIndex !== data.index) {
802
+ throw Error(
803
+ `Inconsistent duties during signing: duty.committeeIndex ${duty.committeeIndex} != att.committeeIndex ${data.index}`
804
+ );
805
+ }
806
+ if (isPostElectra && data.index !== 0) {
807
+ throw Error(`Non-zero committee index post-electra during signing: att.committeeIndex ${data.index}`);
808
+ }
809
+ }
810
+
811
+ private assertDoppelgangerSafe(pubKey: PubkeyHex | BLSPubkey): void {
812
+ const pubkeyHex = typeof pubKey === "string" ? pubKey : toPubkeyHex(pubKey);
813
+ if (!this.isDoppelgangerSafe(pubkeyHex)) {
814
+ throw new Error(`Doppelganger state for key ${pubkeyHex} is not safe`);
815
+ }
816
+ }
817
+ }
818
+
819
+ function getSignerPubkeyHex(signer: Signer): PubkeyHex {
820
+ switch (signer.type) {
821
+ case SignerType.Local:
822
+ return toPubkeyHex(signer.secretKey.toPublicKey().toBytes());
823
+
824
+ case SignerType.Remote:
825
+ if (!isValidatePubkeyHex(signer.pubkey)) {
826
+ throw Error(`Bad format in RemoteSigner.pubkey ${signer.pubkey}`);
827
+ }
828
+ return signer.pubkey;
829
+ }
830
+ }