Skip to content

Commit b5596dc

Browse files
committed
introduce viem client proxys to use RPC from BlockService RPC selection
1 parent f00456c commit b5596dc

File tree

19 files changed

+270
-149
lines changed

19 files changed

+270
-149
lines changed

apps/submitter/lib/handlers/createAccount/computeHappyAccountAddress.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from "viem"
1010
import { encodePacked, parseAbi } from "viem/utils"
1111
import { abis, deployment } from "#lib/env"
12-
import { publicClient } from "#lib/utils/clients"
12+
import { publicClient } from "#lib/services"
1313
import { logger } from "#lib/utils/logger"
1414

1515
const initializeAbi = parseAbi(["function initialize(address owner)"])

apps/submitter/lib/handlers/createAccount/createAccount.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
import { type Address, type Optional, assertDef } from "@happy.tech/common"
2-
import { createWalletClient } from "viem"
32
import { abis, deployment, env } from "#lib/env"
43
import { outputForGenericError } from "#lib/handlers/errors"
5-
import { evmNonceManager, evmReceiptService, replaceTransaction } from "#lib/services"
4+
import { evmNonceManager, evmReceiptService, publicClient, replaceTransaction, walletClient } from "#lib/services"
65
import { accountDeployer } from "#lib/services/evmAccounts"
76
import { traceFunction } from "#lib/telemetry/traces"
87
import { type EvmTxInfo, SubmitterError } from "#lib/types"
9-
import { config, isNonceTooLowError, publicClient } from "#lib/utils/clients"
108
import { getFees } from "#lib/utils/gas"
119
import { logger } from "#lib/utils/logger"
1210
import { decodeEvent } from "#lib/utils/parsing"
11+
import { isNonceTooLowError } from "#lib/utils/viem"
1312
import { computeHappyAccountAddress } from "./computeHappyAccountAddress"
1413
import { CreateAccount, type CreateAccountInput, type CreateAccountOutput } from "./types"
1514

16-
const walletClient = createWalletClient({ ...config, account: accountDeployer })
1715
const WaitForReceiptError = Symbol("WaitForReceiptError")
1816

1917
async function createAccount({ salt, owner }: CreateAccountInput): Promise<CreateAccountOutput> {

apps/submitter/lib/handlers/execute/execute.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import { env } from "#lib/env"
1010
import type { ExecuteError, ExecuteSuccess } from "#lib/handlers/execute"
1111
import type { SimulateError } from "#lib/handlers/simulate"
1212
import { blockService, boopReceiptService } from "#lib/services"
13+
import { chain } from "#lib/services/clients"
1314
import { type Boop, Onchain, SubmitterError } from "#lib/types"
1415
import { computeBoopHash } from "#lib/utils/boop"
15-
import { config } from "#lib/utils/clients"
1616
import {
1717
apiClient,
1818
assertMintLog,
@@ -23,10 +23,11 @@ import {
2323
getNonce,
2424
mockDeployments,
2525
signBoop,
26+
transport,
2627
withInterval,
2728
} from "#lib/utils/test"
2829

29-
const testClient = createTestClient({ ...config, mode: "anvil" })
30+
const testClient = createTestClient({ chain, transport, mode: "anvil" })
3031
const testAccount = privateKeyToAccount(generatePrivateKey())
3132
const sign = async (tx: Boop) => await signBoop(testAccount, tx)
3233

apps/submitter/lib/handlers/simulate/simulate.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ import { abis, deployment, env } from "#lib/env"
55
import { outputForExecuteError, outputForGenericError, outputForRevertError } from "#lib/handlers/errors"
66
import { notePossibleMisbehaviour } from "#lib/policies/misbehaviour"
77
import { getSubmitterFee, validateSubmitterFee } from "#lib/policies/submitterFee"
8-
import { computeHash, simulationCache } from "#lib/services"
8+
import { computeHash, publicClient, simulationCache } from "#lib/services"
99
import { traceFunction } from "#lib/telemetry/traces"
1010
import { type Boop, CallStatus, Onchain, type OnchainStatus, SubmitterError } from "#lib/types"
1111
import { encodeBoop } from "#lib/utils/boop"
12-
import { publicClient } from "#lib/utils/clients"
1312
import { getFees } from "#lib/utils/gas"
1413
import { logger } from "#lib/utils/logger"
1514
import { getRevertError } from "#lib/utils/parsing"

apps/submitter/lib/handlers/submit/submit.spec.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,15 @@ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"
88
import { env } from "#lib/env"
99
import type { SubmitError, SubmitSuccess } from "#lib/handlers/submit"
1010
import { type Boop, type BoopReceipt, Onchain, SubmitterError } from "#lib/types"
11-
import { publicClient } from "#lib/utils/clients"
12-
import { apiClient, assertMintLog, createMintBoop, createSmartAccount, getNonce, signBoop } from "#lib/utils/test"
11+
import {
12+
apiClient,
13+
assertMintLog,
14+
createMintBoop,
15+
createSmartAccount,
16+
getNonce,
17+
publicClient,
18+
signBoop,
19+
} from "#lib/utils/test"
1320

1421
const testAccount = privateKeyToAccount(generatePrivateKey())
1522
const sign = (tx: Boop) => signBoop(testAccount, tx)

apps/submitter/lib/handlers/submit/submit.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ import {
1212
computeHash,
1313
evmNonceManager,
1414
findExecutionAccount,
15+
walletClient,
1516
} from "#lib/services"
1617
import { accountDeployer } from "#lib/services/evmAccounts"
1718
import { traceFunction } from "#lib/telemetry/traces"
1819
import { type Boop, type EvmTxInfo, Onchain, SubmitterError } from "#lib/types"
1920
import { encodeBoop, updateBoopFromSimulation } from "#lib/utils/boop"
20-
import { isNonceTooLowError, walletClient } from "#lib/utils/clients"
2121
import { getFees } from "#lib/utils/gas"
2222
import { logger } from "#lib/utils/logger"
23+
import { isNonceTooLowError } from "#lib/utils/viem"
2324
import type { SubmitError, SubmitInput, SubmitOutput, SubmitSuccess } from "./types"
2425

2526
async function submit(input: SubmitInput): Promise<SubmitOutput> {

apps/submitter/lib/server/accountRoute.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import { describe, expect, it } from "bun:test"
55
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"
66
import { abis, deployment } from "#lib/env"
77
import { computeHappyAccountAddress } from "#lib/handlers/createAccount/computeHappyAccountAddress"
8-
import { publicClient } from "#lib/utils/clients"
9-
import { createSmartAccount } from "#lib/utils/test"
8+
import { createSmartAccount, publicClient } from "#lib/utils/test"
109

1110
const testAccount = privateKeyToAccount(generatePrivateKey())
1211
describe("routes: api/accounts", () => {

apps/submitter/lib/services/BlockService.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ import { AlertType } from "#lib/policies/alerting"
1818
import { currentBlockGauge } from "#lib/telemetry/metrics"
1919
import { LruCache } from "#lib/utils/LruCache"
2020
import { recoverAlert, sendAlert } from "#lib/utils/alert"
21-
import { chain, publicClient, rpcUrls, stringify } from "#lib/utils/clients"
2221
import { blockLogger } from "#lib/utils/logger"
2322
import { Bytes } from "#lib/utils/validation/ark"
23+
import { stringify } from "#lib/utils/viem"
24+
import { chain, publicClient, rpcUrls } from "./clients"
2425

2526
/**
2627
* Type of block we get from Viem's `getBlock` — made extra permissive for safety,
@@ -65,8 +66,6 @@ const TIMEOUT_MSG = "Timed out while waiting for block"
6566
export class BlockService {
6667
#current?: Block
6768
#previous?: Block
68-
#client!: PublicClient
69-
#backfillMutex = new Mutex()
7069
#callbacks: Set<(block: Block) => void | Promise<void>> = new Set()
7170

7271
/** Zero-index attempt number for the current client. */
@@ -75,6 +74,9 @@ export class BlockService {
7574
/** Current RPC URL (a value from {@link rpcUrls}) */
7675
#rpcUrl = ""
7776

77+
/** RPC URls besides {@link #rpcUrl} that are live and keeping up with blocks, as of the latest RPC selection. */
78+
#otherLiveRpcUrls: string[] = rpcUrls
79+
7880
/** Set of RPCs that failed in the last minute, we will prioritize selecting a RPC not in this set if possible. */
7981
#recentlyFailedRpcs = new Set<string>()
8082

@@ -117,6 +119,14 @@ export class BlockService {
117119
}
118120
}
119121

122+
getRpcUrl(): string {
123+
return this.#rpcUrl
124+
}
125+
126+
getOtherLiveRpcUrls(): string[] {
127+
return this.#otherLiveRpcUrls
128+
}
129+
120130
getCurrentBlock(): Block {
121131
if (!this.#current) throw Error("BlockService not initialized!")
122132
return this.#current
@@ -137,7 +147,7 @@ export class BlockService {
137147
// RPC SELECTION
138148

139149
/**
140-
* Select a new RPC service and sets {@link this.#client} to a client for it.
150+
* Select a new RPC service and sets {@link this.#rpcUrl}.
141151
*
142152
* Sketch of the process:
143153
* - Ping all RPCs for latest block to determine who is alive.
@@ -254,7 +264,9 @@ export class BlockService {
254264
}
255265

256266
this.#rpcUrl = rpcUrls[index]
257-
this.#client = createClient(this.#rpcUrl)
267+
this.#otherLiveRpcUrls = rpcResults
268+
.map((it, i) => (isProgress(it) && rpcUrls[i] !== this.#rpcUrl ? rpcUrls[i] : null))
269+
.filter((i) => i !== null)
258270
this.#attempt = 0
259271

260272
// We got a new block in the whole affair, handle it.
@@ -293,21 +305,21 @@ export class BlockService {
293305
while (true) {
294306
// 1. Initialize next client if needed, or wait until next attempt.
295307
init: try {
296-
if (!this.#client /* very first init */ || skipToNextClient) {
308+
if (!this.#rpcUrl /* very first init */ || skipToNextClient) {
297309
await this.#nextRPC()
298310
break init
299311
}
300312

301313
if (this.#attempt >= maxAttempts) {
302-
blockLogger.warn(`Max retries (${maxAttempts}) reached for ${this.#client.name}.`)
314+
blockLogger.warn(`Max retries (${maxAttempts}) reached for ${this.#rpcUrl}.`)
303315
await this.#nextRPC()
304316
break init
305317
}
306318

307319
if (this.#attempt > 1) {
308320
// We want first retry (attempt = 1) to be instant.
309321
const delay = Math.min(baseDelay * 2 ** (this.#attempt - 2), maxDelay)
310-
blockLogger.info(`Waiting ${delay / 1000} seconds to retry with ${this.#client.name}`)
322+
blockLogger.info(`Waiting ${delay / 1000} seconds to retry with ${this.#rpcUrl}`)
311323
await sleep(delay)
312324
}
313325
} catch (e) {
@@ -316,8 +328,8 @@ export class BlockService {
316328
continue
317329
}
318330

319-
const client = this.#client.name
320-
blockLogger.info(`Starting block watcher with ${client} (Attempt ${this.#attempt + 1}/${maxAttempts}).`)
331+
const attemptString = `Attempt ${this.#attempt + 1}/${maxAttempts}`
332+
blockLogger.info(`Starting block watcher with ${this.#rpcUrl} (${attemptString})`)
321333

322334
// 2. Setup subscription
323335
const { promise, reject } = promiseWithResolvers<void>()
@@ -331,9 +343,9 @@ export class BlockService {
331343

332344
this.#startBlockTimeout()
333345

334-
if (this.#client.transport.type === "webSocket") {
346+
if (publicClient.transport.type === "webSocket") {
335347
try {
336-
;({ unsubscribe } = await this.#client.transport.subscribe({
348+
;({ unsubscribe } = await publicClient.transport.subscribe({
337349
params: ["newHeads"],
338350
// Type is unchecked, we're being conservative with what we receive.
339351
onData: (data?: { result?: Partial<RpcBlock> }) => {
@@ -358,7 +370,7 @@ export class BlockService {
358370
} else {
359371
pollingTimer = setInterval(async () => {
360372
// biome-ignore format: terse
361-
try { void this.#handleNewBlock(await this.#client.getBlock({ includeTransactions: false })) }
373+
try { void this.#handleNewBlock(await publicClient.getBlock({ includeTransactions: false })) }
362374
catch (e) { reject(e) }
363375
}, pollingInterval)
364376
}
@@ -367,7 +379,7 @@ export class BlockService {
367379
// it because we need to control the retry logic ourselves to implement RPC fallback
368380
// for subscriptions, as well as resubscriptions — two things Viem doesn't handle.
369381

370-
// unwatch = this.#client.watchBlocks({
382+
// unwatch = publicClient.watchBlocks({
371383
// pollingInterval,
372384
// includeTransactions: false,
373385
// emitOnBegin: false,
@@ -384,8 +396,8 @@ export class BlockService {
384396
clearTimeout(this.blockTimeout)
385397
// This happens more than the rest, and if the timeout persist, there will be plenty of other logs
386398
// for us to notice as the RPC will rotate.
387-
if (e === TIMEOUT_MSG) blockLogger.info(TIMEOUT_MSG, client)
388-
else blockLogger.error("Block watcher error", client, stringify(e))
399+
if (e === TIMEOUT_MSG) blockLogger.info(TIMEOUT_MSG, this.#rpcUrl)
400+
else blockLogger.error("Block watcher error", this.#rpcUrl, stringify(e))
389401
++this.#attempt
390402
}
391403
}
@@ -436,7 +448,7 @@ export class BlockService {
436448
for (let i = 1; i <= 3; ++i) {
437449
try {
438450
// `includeTransactions: false` still gives us a list of transaction hashes
439-
block = await this.#client.getBlock({ blockNumber, includeTransactions: false })
451+
block = await publicClient.getBlock({ blockNumber, includeTransactions: false })
440452
break
441453
} catch {
442454
await sleep(env.LINEAR_RETRY_DELAY * i)
@@ -462,7 +474,7 @@ export class BlockService {
462474
// Check for duplicates
463475
if (block.hash === this.#current?.hash) {
464476
// Don't warn when polling, since this is expected to happen all the time.
465-
if (this.#client.transport.type === "webSocket")
477+
if (publicClient.transport.type === "webSocket")
466478
blockLogger.warn(`Received duplicate block ${block.number}, skipping.`)
467479
return false
468480
}
@@ -507,7 +519,7 @@ export class BlockService {
507519
}
508520

509521
if (this.#attempt > 0) {
510-
blockLogger.info(`Retrieved block ${block.number} with ${this.#client.name}. Resetting attempt count.`)
522+
blockLogger.info(`Retrieved block ${block.number} with ${this.#rpcUrl}. Resetting attempt count.`)
511523
this.#attempt = 0
512524
}
513525

@@ -532,6 +544,8 @@ export class BlockService {
532544
return true
533545
}
534546

547+
#backfillMutex = new Mutex()
548+
535549
/**
536550
* Backfills blocks with numbers in [from, to] (inclusive).
537551
* Returns true iff the backfill is successful for the entire range.
@@ -551,7 +565,7 @@ export class BlockService {
551565

552566
const promises = []
553567
for (let blockNumber = from; blockNumber <= to; blockNumber++) {
554-
promises.push(this.#client.getBlock({ blockNumber, includeTransactions: false }))
568+
promises.push(publicClient.getBlock({ blockNumber, includeTransactions: false }))
555569
}
556570

557571
for (let blockNumber = from; blockNumber <= to; blockNumber++) {

apps/submitter/lib/services/BoopNonceManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { abis, env } from "#lib/env"
44
import type { SubmitError } from "#lib/handlers/submit"
55
import { TraceMethod } from "#lib/telemetry/traces"
66
import { type Boop, SubmitterError, type SubmitterErrorStatus } from "#lib/types"
7-
import { publicClient } from "#lib/utils/clients"
7+
import { computeHash } from "#lib/utils/boop/computeHash"
88
import { logger } from "#lib/utils/logger"
9-
import { computeHash } from "../utils/boop/computeHash"
9+
import { publicClient } from "./clients"
1010

1111
type NonceTrack = bigint
1212
type NonceValue = bigint

apps/submitter/lib/services/EvmNonceManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type Address, HappyMap, Mutex, tryCatchAsync } from "@happy.tech/common"
22
import type { GetTransactionCountErrorType } from "viem"
3-
import { publicClient } from "#lib/utils/clients"
43
import { logger } from "#lib/utils/logger"
4+
import { publicClient } from "./clients"
55

66
type _Import = GetTransactionCountErrorType
77

0 commit comments

Comments
 (0)