Skip to content

Commit

Permalink
Merge pull request #380 from 0xsequence/v2-faster-local-2
Browse files Browse the repository at this point in the history
Sequence v2: Faster local tracker and cache
  • Loading branch information
Agusx1211 committed Jun 28, 2023
2 parents 8c11fd8 + 074d752 commit 78e71ef
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 51 deletions.
76 changes: 37 additions & 39 deletions packages/sessions/src/trackers/cached.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,51 @@ export class CachedTracker implements migrator.PresignedMigrationTracker, Config
) {}

async loadPresignedConfiguration(args: { wallet: string; fromImageHash: string; longestPath?: boolean | undefined }): Promise<PresignedConfigLink[]> {
const configs = new Map<string, Promise<commons.config.Config | undefined>>()
const configOf = (imageHash: string): Promise<commons.config.Config | undefined> => {
if (!configs.has(imageHash)) {
configs.set(imageHash, this.configOfImageHash({ imageHash }))
}
return configs.get(imageHash)!
}

// We need to check both, and return the one with the highest checkpoint
// eventually we could try to combine them, but for now we'll just return
// the one with the highest checkpoint
const results = [this.tracker.loadPresignedConfiguration(args), this.cache.loadPresignedConfiguration(args)]
const checkpoints = await Promise.all(results.map(async result => {
const r = await result
const last = r[r.length - 1]
if (!last) return undefined

// TODO: This will fire a lot of requests, optimize it
const config = await configOf(last.nextImageHash)
if (!config) return undefined

return { checkpoint: universal.genericCoderFor(config.version).config.checkpointOf(config), result: r }
}))

const best = checkpoints.reduce((acc, val) => {
if (!val) return acc
if (!acc) return val
if (val.checkpoint.gt(acc.checkpoint)) return val
return acc
})
let best: PresignedConfigLink[]

// If both results end with the same image hash, we can just return the longest/shortest one
const [result1, result2] = await Promise.all(results)
if (
result1.length > 0 &&
result2.length > 0 &&
result1[result1.length - 1].nextImageHash === result2[result2.length - 1].nextImageHash
) {
best = (
args.longestPath === true ?
result1.length > result2.length ? result1 : result2 :
result1.length < result2.length ? result1 : result2
)
} else {
// Otherwise we need to check the checkpoints
// this requires us to fetch the config for each image hash
const checkpoints = await Promise.all(results.map(async result => {
const r = await result
const last = r[r.length - 1]
if (!last) return undefined

// TODO: This will fire a lot of requests, optimize it
const config = await this.configOfImageHash({ imageHash: last.nextImageHash })
if (!config) return undefined

return { checkpoint: universal.genericCoderFor(config.version).config.checkpointOf(config), result: r }
}))

best = checkpoints.reduce((acc, val) => {
if (!val) return acc
if (!acc) return val
if (val.checkpoint.gt(acc.checkpoint)) return val
return acc
})?.result ?? []
}

if (!best) return []

;(async () => {
for (const result of best.result) {
const nextConfig = await configOf(result.nextImageHash)
if (nextConfig) {
this.cache.savePresignedConfiguration({
wallet: args.wallet,
nextConfig,
signature: result.signature
})
}
}
})()

return best.result
return best
}

async savePresignedConfiguration(args: PresignedConfig): Promise<void> {
Expand Down
72 changes: 60 additions & 12 deletions packages/sessions/src/trackers/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,25 +128,36 @@ export class LocalConfigTracker implements ConfigTracker, migrator.PresignedMigr
return
}

private configOfImageHashCache = {} as { [key: string]: commons.config.Config }

configOfImageHash = async (args: {
imageHash: string
}): Promise<commons.config.Config | undefined> => {
const { imageHash } = args

if (this.configOfImageHashCache[args.imageHash]) {
return this.configOfImageHashCache[args.imageHash]
}

const config = await this.store.loadConfig(imageHash)
if (!config) return undefined
if (!config) {
return undefined
}

if (config.version === 1 || (config.version === 2 && !isPlainV2Config(config))) {
this.configOfImageHashCache[args.imageHash] = config
return config
}

if (isPlainV2Config(config)) {
return {
const fullConfig = {
version: 2,
threshold: ethers.BigNumber.from(config.threshold),
checkpoint: ethers.BigNumber.from(config.checkpoint),
tree: await this.loadTopology(config.tree)
} as v2.config.WalletConfig
this.configOfImageHashCache[args.imageHash] = fullConfig
return fullConfig
}

throw new Error(`Unknown config type: ${config}`)
Expand Down Expand Up @@ -193,11 +204,23 @@ export class LocalConfigTracker implements ConfigTracker, migrator.PresignedMigr
await this.store.savePayloadOfSubdigest(subdigest, payload)
}

private payloadOfSubdigestCache = {} as { [key: string]: commons.signature.SignedPayload }

payloadOfSubdigest = async (args: {
subdigest: string
}): Promise<commons.signature.SignedPayload | undefined> => {
if (this.payloadOfSubdigestCache[args.subdigest]) {
return this.payloadOfSubdigestCache[args.subdigest]
}

const { subdigest } = args
return this.store.loadPayloadOfSubdigest(subdigest)
const res = await this.store.loadPayloadOfSubdigest(subdigest)

if (res) {
this.payloadOfSubdigestCache[subdigest] = res
}

return res
}

savePresignedConfiguration = async (args: PresignedConfig): Promise<void> => {
Expand Down Expand Up @@ -264,14 +287,34 @@ export class LocalConfigTracker implements ConfigTracker, migrator.PresignedMigr
signature: string,
} | undefined

for (const { payload, nextImageHash } of nextImageHashes) {
// Get config of next imageHash
const nextConfigsAndCheckpoints = await Promise.all(nextImageHashes.map(async ({ nextImageHash, payload }) => {
const nextConfig = await this.configOfImageHash({ imageHash: nextImageHash })
if (!nextConfig || !v2.config.isWalletConfig(nextConfig)) continue
if (!nextConfig || !v2.config.isWalletConfig(nextConfig)) return undefined
const nextCheckpoint = ethers.BigNumber.from(nextConfig.checkpoint)

// Only consider candidates later than the starting checkpoint
if (nextCheckpoint.lte(fromConfig.checkpoint)) continue
return { nextConfig, nextCheckpoint, nextImageHash, payload }
}))

const sortedNextConfigsAndCheckpoints = nextConfigsAndCheckpoints
.filter((c) => c !== undefined)
.filter((c) => c!.nextCheckpoint.gt(fromConfig.checkpoint))
.sort((a, b) => (
// If we are looking for the longest path, sort by ascending checkpoint
// because we want to find the smalles jump, and we should start with the
// closest one. If we are not looking for the longest path, sort by
// descending checkpoint, because we want to find the largest jump.
//
// We don't have a guarantee that all "next configs" will be valid
// so worst case scenario we will need to try all of them.
// But we can try to optimize for the most common case.
a!.nextCheckpoint.gt(b!.nextCheckpoint) ? (
longestPath ? 1 : -1
) : (
longestPath ? -1 : 1
)
))

for (const entry of sortedNextConfigsAndCheckpoints) {
const { nextConfig, nextCheckpoint, nextImageHash, payload } = entry!

if (bestCandidate) {
const bestCheckpoint = bestCandidate.checkpoint
Expand Down Expand Up @@ -306,7 +349,10 @@ export class LocalConfigTracker implements ConfigTracker, migrator.PresignedMigr
).filter((signature): signature is [string, commons.signature.SignaturePart] => Boolean(signature[1]))
)

// Encode the full signature
// Skip if we don't have ANY signatures (it can never reach the threshold)
if (signatures.size === 0) continue

// Encode the full signature (to see if it has enough weight)
const encoded = v2.signature.SignatureCoder.encodeSigners(fromConfig, signatures, [], 0)
if (encoded.weight.lt(fromConfig.threshold)) continue

Expand All @@ -317,8 +363,10 @@ export class LocalConfigTracker implements ConfigTracker, migrator.PresignedMigr
signature: encoded.encoded
}
}

if (!bestCandidate) return []

if (!bestCandidate) {
return []
}

// Get the next step
const nextStep = await this.loadPresignedConfiguration({
Expand Down

0 comments on commit 78e71ef

Please sign in to comment.