@xyo-network/xl1-protocol-sdk 1.15.2 → 1.15.4
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.
- package/README.md +1507 -388
- package/dist/neutral/block/hydrate/hydrateBlock.d.ts +5 -2
- package/dist/neutral/block/hydrate/hydrateBlock.d.ts.map +1 -1
- package/dist/neutral/block/primitives/balances/balancesStepSummaryFromRange.d.ts.map +1 -1
- package/dist/neutral/block/primitives/balances/balancesSummary.d.ts.map +1 -1
- package/dist/neutral/block/primitives/blockFromBlockNumber.d.ts +2 -2
- package/dist/neutral/block/primitives/blockFromBlockNumber.d.ts.map +1 -1
- package/dist/neutral/block/primitives/hashFromBlockNumber.d.ts +2 -2
- package/dist/neutral/block/primitives/hashFromBlockNumber.d.ts.map +1 -1
- package/dist/neutral/block/primitives/model.d.ts +10 -8
- package/dist/neutral/block/primitives/model.d.ts.map +1 -1
- package/dist/neutral/block/primitives/payloads/StepSummary.d.ts.map +1 -1
- package/dist/neutral/block/primitives/payloads/TransfersSummary.d.ts +1 -2
- package/dist/neutral/block/primitives/payloads/TransfersSummary.d.ts.map +1 -1
- package/dist/neutral/block/primitives/transfers/transfersStepSummaryFromRange.d.ts.map +1 -1
- package/dist/neutral/block/primitives/transfers/transfersSummary.d.ts +1 -1
- package/dist/neutral/block/primitives/transfers/transfersSummary.d.ts.map +1 -1
- package/dist/neutral/config/Bridge.d.ts +8 -0
- package/dist/neutral/config/Bridge.d.ts.map +1 -0
- package/dist/neutral/config/Config.d.ts +5 -0
- package/dist/neutral/config/Config.d.ts.map +1 -1
- package/dist/neutral/index.d.ts +2 -0
- package/dist/neutral/index.d.ts.map +1 -1
- package/dist/neutral/index.mjs +335 -327
- package/dist/neutral/index.mjs.map +1 -1
- package/dist/neutral/instances/ShiftedBigIntConfig.d.ts +1 -1
- package/dist/neutral/instances/ShiftedBigIntConfig.d.ts.map +1 -1
- package/dist/neutral/instances/XL1Amount.d.ts +1 -1
- package/dist/neutral/instances/XL1Amount.d.ts.map +1 -1
- package/dist/neutral/map/AsynchronousMap.d.ts +14 -0
- package/dist/neutral/map/AsynchronousMap.d.ts.map +1 -0
- package/dist/neutral/map/MapType.d.ts +6 -0
- package/dist/neutral/map/MapType.d.ts.map +1 -0
- package/dist/neutral/map/SynchronousMap.d.ts +13 -0
- package/dist/neutral/map/SynchronousMap.d.ts.map +1 -0
- package/dist/neutral/map/index.d.ts +4 -0
- package/dist/neutral/map/index.d.ts.map +1 -0
- package/dist/neutral/model/ChainContext/ChainContext.d.ts +15 -0
- package/dist/neutral/model/ChainContext/ChainContext.d.ts.map +1 -0
- package/dist/neutral/model/ChainContext/ChainStakeContext.d.ts +10 -0
- package/dist/neutral/model/ChainContext/ChainStakeContext.d.ts.map +1 -0
- package/dist/neutral/model/ChainContext/ChainStateContext.d.ts +9 -0
- package/dist/neutral/model/ChainContext/ChainStateContext.d.ts.map +1 -0
- package/dist/neutral/model/ChainContext/ChainStoreContext.d.ts +10 -0
- package/dist/neutral/model/ChainContext/ChainStoreContext.d.ts.map +1 -0
- package/dist/neutral/model/ChainContext/index.d.ts +5 -0
- package/dist/neutral/model/ChainContext/index.d.ts.map +1 -0
- package/dist/neutral/model/ChainFork/ChainForkStatic.d.ts +8 -0
- package/dist/neutral/model/ChainFork/ChainForkStatic.d.ts.map +1 -0
- package/dist/neutral/model/ChainFork/index.d.ts +2 -0
- package/dist/neutral/model/ChainFork/index.d.ts.map +1 -0
- package/dist/neutral/model/ChainIdentity.d.ts +5 -0
- package/dist/neutral/model/ChainIdentity.d.ts.map +1 -0
- package/dist/neutral/model/ChainStake/ChainStakeRead.d.ts +17 -0
- package/dist/neutral/model/ChainStake/ChainStakeRead.d.ts.map +1 -0
- package/dist/neutral/model/ChainStake/ChainStakeStatic.d.ts +7 -0
- package/dist/neutral/model/ChainStake/ChainStakeStatic.d.ts.map +1 -0
- package/dist/neutral/model/ChainStake/ChainStakeWrite.d.ts +6 -0
- package/dist/neutral/model/ChainStake/ChainStakeWrite.d.ts.map +1 -0
- package/dist/neutral/model/ChainStake/index.d.ts +4 -0
- package/dist/neutral/model/ChainStake/index.d.ts.map +1 -0
- package/dist/neutral/model/ChainStore.d.ts +10 -0
- package/dist/neutral/model/ChainStore.d.ts.map +1 -0
- package/dist/neutral/model/PayloadMap.d.ts +7 -0
- package/dist/neutral/model/PayloadMap.d.ts.map +1 -0
- package/dist/neutral/model/StakeEvents.d.ts +31 -0
- package/dist/neutral/model/StakeEvents.d.ts.map +1 -0
- package/dist/neutral/model/index.d.ts +8 -0
- package/dist/neutral/model/index.d.ts.map +1 -0
- package/dist/neutral/payload/netTransfersForPayloads.d.ts +1 -1
- package/dist/neutral/payload/netTransfersForPayloads.d.ts.map +1 -1
- package/dist/neutral/steps/primitives/index.d.ts +0 -10
- package/dist/neutral/steps/primitives/index.d.ts.map +1 -1
- package/dist/neutral/time/primitives/xl1BlockNumberToEthBlockNumber.d.ts +2 -2
- package/dist/neutral/time/primitives/xl1BlockNumberToEthBlockNumber.d.ts.map +1 -1
- package/dist/neutral/transaction/buildTransaction.d.ts +2 -2
- package/dist/neutral/transaction/buildTransaction.d.ts.map +1 -1
- package/dist/neutral/transaction/buildUnsignedTransaction.d.ts +2 -2
- package/dist/neutral/transaction/buildUnsignedTransaction.d.ts.map +1 -1
- package/dist/neutral/transaction/hydrateTransaction.d.ts +5 -5
- package/dist/neutral/transaction/hydrateTransaction.d.ts.map +1 -1
- package/package.json +30 -29
- package/src/block/hydrate/hydrateBlock.ts +32 -10
- package/src/block/primitives/balances/balancesStepSummaryFromRange.ts +30 -23
- package/src/block/primitives/balances/balancesSummary.ts +3 -2
- package/src/block/primitives/blockFromBlockNumber.ts +8 -6
- package/src/block/primitives/hashFromBlockNumber.ts +2 -2
- package/src/block/primitives/model.ts +15 -11
- package/src/block/primitives/payloads/StepSummary.ts +1 -1
- package/src/block/primitives/payloads/TransfersSummary.ts +1 -2
- package/src/block/primitives/transfers/transfersStepSummaryFromRange.ts +43 -28
- package/src/block/primitives/transfers/transfersSummary.ts +12 -7
- package/src/config/Bridge.ts +26 -0
- package/src/config/Config.ts +2 -0
- package/src/index.ts +2 -0
- package/src/instances/ShiftedBigIntConfig.ts +1 -1
- package/src/instances/XL1Amount.ts +1 -1
- package/src/map/AsynchronousMap.ts +15 -0
- package/src/map/MapType.ts +8 -0
- package/src/map/SynchronousMap.ts +13 -0
- package/src/map/index.ts +3 -0
- package/src/model/ChainContext/ChainContext.ts +20 -0
- package/src/model/ChainContext/ChainStakeContext.ts +12 -0
- package/src/model/ChainContext/ChainStateContext.ts +10 -0
- package/src/model/ChainContext/ChainStoreContext.ts +12 -0
- package/src/model/ChainContext/index.ts +4 -0
- package/src/model/ChainFork/ChainForkStatic.ts +8 -0
- package/src/model/ChainFork/index.ts +1 -0
- package/src/model/ChainIdentity.ts +5 -0
- package/src/model/ChainStake/ChainStakeRead.ts +17 -0
- package/src/model/ChainStake/ChainStakeStatic.ts +7 -0
- package/src/model/ChainStake/ChainStakeWrite.ts +5 -0
- package/src/model/ChainStake/index.ts +3 -0
- package/src/model/ChainStore.ts +13 -0
- package/src/model/PayloadMap.ts +10 -0
- package/src/model/StakeEvents.ts +38 -0
- package/src/model/index.ts +7 -0
- package/src/payload/netTransfersForPayloads.ts +7 -12
- package/src/steps/primitives/index.ts +0 -10
- package/src/time/primitives/xl1BlockNumberToEthBlockNumber.ts +4 -5
- package/src/transaction/buildTransaction.ts +2 -2
- package/src/transaction/buildUnsignedTransaction.ts +2 -2
- package/src/transaction/hydrateTransaction.ts +24 -9
- package/dist/neutral/steps/primitives/networkStakeStepAddressReward.d.ts +0 -4
- package/dist/neutral/steps/primitives/networkStakeStepAddressReward.d.ts.map +0 -1
- package/dist/neutral/steps/primitives/networkStakeStepPoolRewardShares.d.ts +0 -4
- package/dist/neutral/steps/primitives/networkStakeStepPoolRewardShares.d.ts.map +0 -1
- package/dist/neutral/steps/primitives/networkStakeStepPoolRewards.d.ts +0 -4
- package/dist/neutral/steps/primitives/networkStakeStepPoolRewards.d.ts.map +0 -1
- package/dist/neutral/steps/primitives/networkStakeStepRewardAddressHistory.d.ts +0 -4
- package/dist/neutral/steps/primitives/networkStakeStepRewardAddressHistory.d.ts.map +0 -1
- package/dist/neutral/steps/primitives/networkStakeStepRewardAddressShare.d.ts +0 -4
- package/dist/neutral/steps/primitives/networkStakeStepRewardAddressShare.d.ts.map +0 -1
- package/dist/neutral/steps/primitives/networkStakeStepRewardForStep.d.ts +0 -3
- package/dist/neutral/steps/primitives/networkStakeStepRewardForStep.d.ts.map +0 -1
- package/dist/neutral/steps/primitives/networkStakeStepRewardPositionWeight.d.ts +0 -3
- package/dist/neutral/steps/primitives/networkStakeStepRewardPositionWeight.d.ts.map +0 -1
- package/dist/neutral/steps/primitives/networkStakeStepRewardPotentialPositionLoss.d.ts +0 -3
- package/dist/neutral/steps/primitives/networkStakeStepRewardPotentialPositionLoss.d.ts.map +0 -1
- package/dist/neutral/steps/primitives/networkStakeStepRewardRandomizer.d.ts +0 -3
- package/dist/neutral/steps/primitives/networkStakeStepRewardRandomizer.d.ts.map +0 -1
- package/dist/neutral/steps/primitives/networkStakeStepRewardWeightForAddress.d.ts +0 -4
- package/dist/neutral/steps/primitives/networkStakeStepRewardWeightForAddress.d.ts.map +0 -1
- package/src/steps/primitives/networkStakeStepAddressReward.ts +0 -6
- package/src/steps/primitives/networkStakeStepPoolRewardShares.ts +0 -6
- package/src/steps/primitives/networkStakeStepPoolRewards.ts +0 -6
- package/src/steps/primitives/networkStakeStepRewardAddressHistory.ts +0 -6
- package/src/steps/primitives/networkStakeStepRewardAddressShare.ts +0 -6
- package/src/steps/primitives/networkStakeStepRewardForStep.ts +0 -5
- package/src/steps/primitives/networkStakeStepRewardPositionWeight.ts +0 -5
- package/src/steps/primitives/networkStakeStepRewardPotentialPositionLoss.ts +0 -5
- package/src/steps/primitives/networkStakeStepRewardRandomizer.ts +0 -5
- package/src/steps/primitives/networkStakeStepRewardWeightForAddress.ts +0 -6
|
@@ -1,13 +1,34 @@
|
|
|
1
1
|
import { assertEx } from '@xylabs/assert'
|
|
2
2
|
import type { Hash } from '@xylabs/hex'
|
|
3
|
-
import
|
|
3
|
+
import { isDefined } from '@xylabs/typeof'
|
|
4
|
+
import { type ReadArchivist } from '@xyo-network/archivist-model'
|
|
5
|
+
import type { Payload } from '@xyo-network/payload-model'
|
|
4
6
|
import type { HydratedBlock } from '@xyo-network/xl1-protocol'
|
|
5
|
-
import {
|
|
7
|
+
import { asBlockBoundWitnessWithStorageMeta, isTransactionBoundWitnessWithStorageMeta } from '@xyo-network/xl1-protocol'
|
|
6
8
|
|
|
9
|
+
import type { ChainStoreRead, PayloadMapRead } from '../../model/index.ts'
|
|
10
|
+
import { isReadArchivist } from '../primitives/index.ts'
|
|
7
11
|
import { allHashesPresent } from './allHashesPresent.ts'
|
|
8
12
|
|
|
13
|
+
export function readPayloadMapFromStore<T extends Payload>(store: ReadArchivist<T> | PayloadMapRead<T>): PayloadMapRead<T> {
|
|
14
|
+
if (isReadArchivist(store)) {
|
|
15
|
+
return {
|
|
16
|
+
get: async (hash: Hash): Promise<T | undefined> => {
|
|
17
|
+
return (await store.get([hash]))[0]
|
|
18
|
+
},
|
|
19
|
+
getMany: async (hashes: Hash[]): Promise<T[]> => {
|
|
20
|
+
return (await store.get(hashes))
|
|
21
|
+
},
|
|
22
|
+
has: async (hash: Hash): Promise<boolean> => {
|
|
23
|
+
return isDefined((await store.get([hash]))[0])
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return store
|
|
28
|
+
}
|
|
29
|
+
|
|
9
30
|
export const hydrateBlock = async (
|
|
10
|
-
|
|
31
|
+
{ chainMap }: ChainStoreRead,
|
|
11
32
|
hash: Hash,
|
|
12
33
|
maxDepth: number = 1,
|
|
13
34
|
minDepth = maxDepth,
|
|
@@ -15,20 +36,21 @@ export const hydrateBlock = async (
|
|
|
15
36
|
assertEx(maxDepth >= 0, () => 'maxDepth must be greater than or equal to 0')
|
|
16
37
|
assertEx(minDepth >= 0, () => 'minDepth must be greater than or equal to 0')
|
|
17
38
|
assertEx(maxDepth >= minDepth, () => 'maxDepth must be greater than or equal to minDepth')
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
() => `block ${hash} not found
|
|
21
|
-
)
|
|
39
|
+
|
|
40
|
+
const bw = assertEx(asBlockBoundWitnessWithStorageMeta(
|
|
41
|
+
assertEx(await chainMap.get(hash), () => `block ${hash} not found`),
|
|
42
|
+
), () => `hash ${hash} is not a BlockBoundWitness`)
|
|
43
|
+
|
|
22
44
|
if (maxDepth === 0) return [bw, []]
|
|
23
|
-
const blkPayloads = await
|
|
45
|
+
const blkPayloads = await chainMap.getMany(bw.payload_hashes)
|
|
24
46
|
if (minDepth === 1) assertEx(allHashesPresent(bw.payload_hashes, blkPayloads), () => `Unable to find all payloads for block ${hash}`)
|
|
25
47
|
if (maxDepth === 1) return [bw, blkPayloads]
|
|
26
48
|
const transactions = blkPayloads.filter(isTransactionBoundWitnessWithStorageMeta)
|
|
27
49
|
const transactionsPayloadHashes = transactions.flatMap(tx => tx.payload_hashes)
|
|
28
|
-
const transactionsPayloads = await
|
|
50
|
+
const transactionsPayloads = await chainMap.getMany(transactionsPayloadHashes)
|
|
29
51
|
assertEx(allHashesPresent(transactionsPayloadHashes, transactionsPayloads), () => `Unable to find all payloads for transactions in block ${hash}`)
|
|
30
52
|
const allPayloadsHashes = new Set([...blkPayloads, ...transactionsPayloads].flatMap(p => p._hash))
|
|
31
|
-
const allPayloads = await
|
|
53
|
+
const allPayloads = await chainMap.getMany([...allPayloadsHashes])
|
|
32
54
|
const allPayloadsFiltered = allPayloads.filter(p => allPayloadsHashes.has(p._hash))
|
|
33
55
|
if (maxDepth === 2) assertEx(allHashesPresent(
|
|
34
56
|
[...allPayloadsHashes],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
1
2
|
import { assertEx } from '@xylabs/assert'
|
|
2
3
|
import { type Address } from '@xylabs/hex'
|
|
3
4
|
import { PayloadBuilder } from '@xyo-network/payload-builder'
|
|
@@ -23,54 +24,60 @@ export async function balancesStepSummaryFromRange(
|
|
|
23
24
|
// console.log(`balanceStepSummaryFromRange: head=${head}, range=${range[0]}-${range[1]}`)
|
|
24
25
|
const frameHeadHash = await hashFromBlockNumber(context, range[1])
|
|
25
26
|
const frameSize = range[1] - range[0] + 1
|
|
27
|
+
const head = await context.head()
|
|
26
28
|
|
|
27
29
|
let result: BalancesStepSummary | undefined = undefined
|
|
28
30
|
|
|
29
31
|
if (frameSize === 1) {
|
|
30
32
|
const hash = await hashFromBlockNumber(context, range[0])
|
|
31
|
-
const [, payloads] = await hydrateBlock(context.
|
|
33
|
+
const [, payloads] = await hydrateBlock(context.store, hash)
|
|
32
34
|
const balances: Record<Address, SignedBigInt> = {}
|
|
33
35
|
for (const [address, balance] of Object.entries(netBalancesForPayloads(payloads))) {
|
|
34
36
|
balances[address as Address] = toSignedBigInt(balance)
|
|
35
37
|
}
|
|
36
38
|
result = {
|
|
37
|
-
schema: BalancesStepSummarySchema, hash:
|
|
39
|
+
schema: BalancesStepSummarySchema, hash: head, stepSize: -1, balances,
|
|
38
40
|
}
|
|
39
41
|
} else {
|
|
40
42
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
43
|
const step = (StepSizes as any).indexOf(frameSize)
|
|
42
44
|
assertEx(step !== -1, () => `Invalid step size: ${frameSize}. Must be one of ${StepSizes.join(', ')}`)
|
|
43
45
|
|
|
44
|
-
const
|
|
46
|
+
const summaryResult = await context.summaryMap.get(`${frameHeadHash}|${frameSize}`)
|
|
45
47
|
if (isAnyPayload(summaryResult)) {
|
|
46
48
|
result = summaryResult as WithStorageMeta<BalancesStepSummary>
|
|
47
49
|
} else {
|
|
48
50
|
// We do not have it, so lets build it
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
subRange
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
await context.stepSemaphores[step].acquire()
|
|
52
|
+
try {
|
|
53
|
+
const subRanges = deepCalculateFramesFromRange(range, step - 1)
|
|
54
|
+
const promises = subRanges.map(subRange => balancesStepSummaryFromRange(
|
|
55
|
+
context,
|
|
56
|
+
subRange,
|
|
57
|
+
))
|
|
58
|
+
const subResults = await Promise.all(promises)
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
// add them all up
|
|
61
|
+
const bigIntBalances: Record<Address, bigint> = {}
|
|
62
|
+
for (const subResult of subResults) {
|
|
63
|
+
for (const [address, balance] of Object.entries(subResult.balances)) {
|
|
64
|
+
bigIntBalances[address as Address] = (bigIntBalances[address as Address] ?? 0n) + parseSignedBigInt(balance)
|
|
65
|
+
}
|
|
61
66
|
}
|
|
62
|
-
}
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
const balances: Record<Address, SignedBigInt> = {}
|
|
69
|
+
for (const [address, balance] of Object.entries(bigIntBalances)) {
|
|
70
|
+
balances[address as Address] = toSignedBigInt(balance)
|
|
71
|
+
}
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
result = {
|
|
74
|
+
schema: BalancesStepSummarySchema, hash: frameHeadHash, stepSize: frameSize, balances,
|
|
75
|
+
}
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
await context.summaryMap.set(`${frameHeadHash}|${frameSize}`, result)
|
|
78
|
+
} finally {
|
|
79
|
+
context.stepSemaphores[step].release()
|
|
80
|
+
}
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
83
|
// console.log(`balanceStepSummaryFromRange-result: head=${head}, range=${range[0]}-${range[1]}: ${toSafeJsonString(result, 10)}`)
|
|
@@ -13,8 +13,9 @@ export async function balancesSummary(
|
|
|
13
13
|
context: BalanceStepSummaryContext,
|
|
14
14
|
): Promise<Partial<Record<Address, bigint>>> {
|
|
15
15
|
return await spanRootAsync('balanceSummary', async () => {
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
const head = await context.head()
|
|
17
|
+
const headResult = await context.store.chainMap.get(head)
|
|
18
|
+
const headBoundWitness = asBlockBoundWitnessWithStorageMeta(headResult, () => `Head block not found for hash: ${context.head}`, { required: true })
|
|
18
19
|
const rangeStart = isDefined(context.windowSize) ? Math.max(headBoundWitness.block - context.windowSize + 1, 0) : 0
|
|
19
20
|
const ranges = deepCalculateFramesFromRange([rangeStart, headBoundWitness.block])
|
|
20
21
|
const summaries = await Promise.all(ranges.map(range => balancesStepSummaryFromRange(context, range)))
|
|
@@ -4,11 +4,12 @@ import type { WithStorageMeta } from '@xyo-network/payload-model'
|
|
|
4
4
|
import type { BlockBoundWitness } from '@xyo-network/xl1-protocol'
|
|
5
5
|
import { asBlockBoundWitnessWithStorageMeta, StepSizes } from '@xyo-network/xl1-protocol'
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type { ChainContextRead } from '../../model/index.ts'
|
|
8
8
|
|
|
9
|
-
export async function blockFromBlockNumber(context:
|
|
10
|
-
const
|
|
11
|
-
|
|
9
|
+
export async function blockFromBlockNumber(context: ChainContextRead, blockNumber: number): Promise<WithStorageMeta<BlockBoundWitness>> {
|
|
10
|
+
const head = await context.head()
|
|
11
|
+
const result = await context.store.chainMap.get(head)
|
|
12
|
+
let currentBlock = asBlockBoundWitnessWithStorageMeta(result, () => `Head block not found for hash: ${head}`, { required: true })
|
|
12
13
|
while (currentBlock.block > blockNumber) {
|
|
13
14
|
let jumpHash: Hash | null = currentBlock.previous
|
|
14
15
|
let jumpBlockNumber = currentBlock.block - 1
|
|
@@ -19,8 +20,9 @@ export async function blockFromBlockNumber(context: ChainContext, blockNumber: n
|
|
|
19
20
|
jumpHash = asHash(currentBlock.step_hashes.at(step), () => `Step hash not found for step ${step} in block ${currentBlock.block}`)
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
|
-
const
|
|
23
|
-
asHash(jumpHash, () => `Jump hash not found for block number [${blockNumber}]: ${jumpBlockNumber} ${toSafeJsonString(currentBlock, 10)}`)
|
|
23
|
+
const newBlock = await context.store.chainMap.get(
|
|
24
|
+
asHash(jumpHash, () => `Jump hash not found for block number [${blockNumber}]: ${jumpBlockNumber} ${toSafeJsonString(currentBlock, 10)}`),
|
|
25
|
+
)
|
|
24
26
|
currentBlock = asBlockBoundWitnessWithStorageMeta(newBlock, () => `Block not found for hash: ${jumpHash}`, { required: true })
|
|
25
27
|
if (currentBlock.block === blockNumber) {
|
|
26
28
|
break
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type Hash } from '@xylabs/hex'
|
|
2
2
|
|
|
3
|
+
import type { ChainContextRead } from '../../model/index.ts'
|
|
3
4
|
import { blockFromBlockNumber } from './blockFromBlockNumber.ts'
|
|
4
|
-
import type { ChainContext } from './model.ts'
|
|
5
5
|
|
|
6
|
-
export async function hashFromBlockNumber(context:
|
|
6
|
+
export async function hashFromBlockNumber(context: ChainContextRead, blockNumber: number): Promise<Hash> {
|
|
7
7
|
return (await blockFromBlockNumber(context, blockNumber))._hash
|
|
8
8
|
}
|
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import type { Address, Hash } from '@xylabs/hex'
|
|
2
1
|
import type { ReadArchivist } from '@xyo-network/archivist-model'
|
|
3
2
|
import type { Payload } from '@xyo-network/payload-model'
|
|
4
|
-
import type {
|
|
3
|
+
import type { Semaphore } from 'async-mutex'
|
|
5
4
|
|
|
5
|
+
import type { MapTypeRead, MapTypeWrite } from '../../map/index.ts'
|
|
6
|
+
import type { ChainContextRead } from '../../model/index.ts'
|
|
6
7
|
import type { BalancesStepSummary, TransfersStepSummary } from './payloads/index.ts'
|
|
7
8
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
export function isReadArchivist(obj: unknown): obj is ReadArchivist {
|
|
10
|
+
return (obj as ReadArchivist).get !== undefined && (obj as ReadArchivist).next !== undefined
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ChainSummaryContextRead<T extends Payload> extends ChainContextRead {
|
|
14
|
+
stepSemaphores: Semaphore[]
|
|
15
|
+
summaryMap: MapTypeRead<string, T>
|
|
11
16
|
windowSize?: number
|
|
12
17
|
}
|
|
13
18
|
|
|
14
|
-
export interface
|
|
15
|
-
|
|
19
|
+
export interface ChainSummaryContextWrite<T extends Payload> extends ChainContextRead {
|
|
20
|
+
summaryMap: MapTypeWrite<string, T>
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
export type ChainSummaryContext<T extends Payload> = ChainSummaryContextRead<T> & ChainSummaryContextWrite<T>
|
|
24
|
+
|
|
18
25
|
export interface BalanceStepSummaryContext extends ChainSummaryContext<BalancesStepSummary> {}
|
|
19
26
|
|
|
20
|
-
export interface TransfersStepSummaryContext extends ChainSummaryContext<TransfersStepSummary> {
|
|
21
|
-
/* The address that the transfers to/from are being summarized */
|
|
22
|
-
account: Address
|
|
23
|
-
}
|
|
27
|
+
export interface TransfersStepSummaryContext extends ChainSummaryContext<TransfersStepSummary> {}
|
|
@@ -12,4 +12,4 @@ export interface StepSummaryFields {
|
|
|
12
12
|
|
|
13
13
|
export type StepSummary<TAdditionalFields extends EmptyObject | void = void,
|
|
14
14
|
TSchema extends Schema | void = void> = Payload<TAdditionalFields extends void ? StepSummaryFields : TAdditionalFields & StepSummaryFields,
|
|
15
|
-
|
|
15
|
+
TSchema extends void ? StepSummarySchema : TSchema>
|
|
@@ -10,8 +10,7 @@ export const TransfersStepSummarySchema: Schema = 'network.xyo.step.summary.tran
|
|
|
10
10
|
export type TransfersStepSummarySchema = typeof TransfersStepSummarySchema
|
|
11
11
|
|
|
12
12
|
export type TransfersStepSummary = StepSummary<{
|
|
13
|
-
|
|
14
|
-
transfers: Record<Address, SignedBigInt>
|
|
13
|
+
transfers: Record<Address, Record<Address, SignedBigInt>>
|
|
15
14
|
}, TransfersStepSummarySchema>
|
|
16
15
|
|
|
17
16
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
1
2
|
import { assertEx } from '@xylabs/assert'
|
|
2
3
|
import { type Address } from '@xylabs/hex'
|
|
3
4
|
import { PayloadBuilder } from '@xyo-network/payload-builder'
|
|
@@ -22,55 +23,69 @@ export async function transfersStepSummaryFromRange(
|
|
|
22
23
|
// console.log(`transfersStepSummaryFromRange: head=${head}, range=${range[0]}-${range[1]}`)
|
|
23
24
|
const frameHeadHash = await hashFromBlockNumber(context, range[1])
|
|
24
25
|
const frameSize = range[1] - range[0] + 1
|
|
25
|
-
const
|
|
26
|
+
const head = await context.head()
|
|
26
27
|
|
|
27
28
|
let result: TransfersStepSummary | undefined = undefined
|
|
28
29
|
|
|
29
30
|
if (frameSize === 1) {
|
|
30
31
|
const hash = await hashFromBlockNumber(context, range[0])
|
|
31
|
-
const [, payloads] = await hydrateBlock(context.
|
|
32
|
-
const transfers: Record<Address, SignedBigInt
|
|
33
|
-
for (const [
|
|
34
|
-
transfers[
|
|
32
|
+
const [, payloads] = await hydrateBlock(context.store, hash)
|
|
33
|
+
const transfers: Record<Address, Record<Address, SignedBigInt>> = {}
|
|
34
|
+
for (const [from, toMap] of Object.entries(netTransfersForPayloads(payloads))) {
|
|
35
|
+
transfers[from as Address] = transfers[from as Address] ?? {}
|
|
36
|
+
for (const [to, amount] of Object.entries(toMap)) {
|
|
37
|
+
transfers[from as Address][to as Address] = toSignedBigInt(amount)
|
|
38
|
+
}
|
|
35
39
|
}
|
|
36
40
|
result = {
|
|
37
|
-
schema: TransfersStepSummarySchema, hash:
|
|
41
|
+
schema: TransfersStepSummarySchema, hash: head, stepSize: -1, transfers,
|
|
38
42
|
}
|
|
39
43
|
} else {
|
|
40
44
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
45
|
const step = (StepSizes as any).indexOf(frameSize)
|
|
42
46
|
assertEx(step !== -1, () => `Invalid step size: ${frameSize}. Must be one of ${StepSizes.join(', ')}`)
|
|
43
47
|
|
|
44
|
-
const
|
|
48
|
+
const summaryResult = await context.summaryMap.get(`${frameHeadHash}|${frameSize}`)
|
|
45
49
|
if (isAnyPayload(summaryResult)) {
|
|
46
50
|
result = summaryResult as WithStorageMeta<TransfersStepSummary>
|
|
47
51
|
} else {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
subRange
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
await context.stepSemaphores[step].acquire()
|
|
53
|
+
// We do not have it, so lets build it
|
|
54
|
+
try {
|
|
55
|
+
const subRanges = deepCalculateFramesFromRange(range, step - 1)
|
|
56
|
+
const promises = subRanges.map(subRange => transfersStepSummaryFromRange(
|
|
57
|
+
context,
|
|
58
|
+
subRange,
|
|
59
|
+
))
|
|
60
|
+
const subResults = await Promise.all(promises)
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
// add them all up
|
|
63
|
+
const bigIntBalances: Record<Address, Record<Address, bigint>> = {}
|
|
64
|
+
for (const subResult of subResults) {
|
|
65
|
+
for (const [from, toMap] of Object.entries(subResult.transfers)) {
|
|
66
|
+
bigIntBalances[from as Address] = bigIntBalances[from as Address] ?? {}
|
|
67
|
+
for (const [to, transfer] of Object.entries(toMap)) {
|
|
68
|
+
bigIntBalances[from as Address][to as Address] = (bigIntBalances[from as Address][to as Address] ?? 0n) + parseSignedBigInt(transfer)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
61
71
|
}
|
|
62
|
-
}
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
const transfers: Record<Address, Record<Address, SignedBigInt>> = {}
|
|
74
|
+
for (const [from, toMap] of Object.entries(bigIntBalances)) {
|
|
75
|
+
transfers[from as Address] = transfers[from as Address] ?? {}
|
|
76
|
+
for (const [to, transfer] of Object.entries(toMap)) {
|
|
77
|
+
transfers[from as Address][to as Address] = toSignedBigInt(transfer)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
68
80
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
result = {
|
|
82
|
+
schema: TransfersStepSummarySchema, hash: frameHeadHash, stepSize: frameSize, transfers,
|
|
83
|
+
}
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
await context.summaryMap.set(`${frameHeadHash}|${frameSize}`, result)
|
|
86
|
+
} finally {
|
|
87
|
+
context.stepSemaphores[step].release()
|
|
88
|
+
}
|
|
74
89
|
}
|
|
75
90
|
}
|
|
76
91
|
// console.log(`transfersStepSummaryFromRange-result: head=${head}, range=${range[0]}-${range[1]}: ${toSafeJsonString(result, 10)}`)
|
|
@@ -12,18 +12,23 @@ import { transfersStepSummaryFromRange } from './transfersStepSummaryFromRange.t
|
|
|
12
12
|
// the summary of amount of rewards claimed from the step reward pool by addresses
|
|
13
13
|
export async function transfersSummary(
|
|
14
14
|
context: TransfersStepSummaryContext,
|
|
15
|
-
): Promise<Partial<Record<Address, bigint
|
|
15
|
+
): Promise<Partial<Record<Address, Partial<Record<Address, bigint>>>>> {
|
|
16
16
|
return await spanRootAsync('transferSummary', async () => {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const head = await context.head()
|
|
18
|
+
const headResult = await context.store.chainMap.get(head)
|
|
19
|
+
const headBoundWitness = asBlockBoundWitnessWithStorageMeta(headResult, () => `Head block not found for hash: ${context.head}`, { required: true })
|
|
19
20
|
const rangeStart = isDefined(context.windowSize) ? Math.max(headBoundWitness.block - context.windowSize + 1, 0) : 0
|
|
20
21
|
const ranges = deepCalculateFramesFromRange([rangeStart, headBoundWitness.block])
|
|
21
22
|
const summaries = await Promise.all(ranges.map(range => transfersStepSummaryFromRange(context, range)))
|
|
22
|
-
const transfers: Partial<Record<Address, bigint
|
|
23
|
+
const transfers: Partial<Record<Address, Partial<Record<Address, bigint>>>> = {}
|
|
23
24
|
for (let summary of summaries) {
|
|
24
|
-
for (const [
|
|
25
|
-
const
|
|
26
|
-
transfers[
|
|
25
|
+
for (const [from, toMap] of Object.entries(summary.transfers)) {
|
|
26
|
+
const validFrom = asAddress(from, () => `Invalid address: ${from}`)
|
|
27
|
+
transfers[validFrom] = transfers[validFrom] ?? {}
|
|
28
|
+
for (const [to, transfer] of Object.entries(toMap)) {
|
|
29
|
+
const validTo = asAddress(to, () => `Invalid address: ${to}`)
|
|
30
|
+
transfers[validFrom][validTo] = (transfers[validFrom][validTo] ?? 0n) + parseSignedBigInt(transfer)
|
|
31
|
+
}
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
34
|
return transfers
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { globalRegistry } from 'zod'
|
|
2
|
+
import * as z from 'zod'
|
|
3
|
+
|
|
4
|
+
import { MnemonicStringZod } from '../validation/index.ts'
|
|
5
|
+
|
|
6
|
+
export const BridgeConfigZod = z.object({
|
|
7
|
+
host: z.string().default('localhost').register(globalRegistry, {
|
|
8
|
+
default: 'localhost',
|
|
9
|
+
description: 'Host for the Bridge',
|
|
10
|
+
title: 'api.host',
|
|
11
|
+
type: 'string',
|
|
12
|
+
}),
|
|
13
|
+
mnemonic: MnemonicStringZod.optional().register(globalRegistry, {
|
|
14
|
+
description: 'Mnemonic for the Bridge wallet',
|
|
15
|
+
title: 'api.mnemonic',
|
|
16
|
+
type: 'string',
|
|
17
|
+
}),
|
|
18
|
+
port: z.coerce.number().default(8081).register(globalRegistry, {
|
|
19
|
+
default: 8081,
|
|
20
|
+
description: 'Port for the Bridge',
|
|
21
|
+
title: 'api.port',
|
|
22
|
+
type: 'number',
|
|
23
|
+
}),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export type BridgeConfig = z.infer<typeof BridgeConfigZod>
|
package/src/config/Config.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as z from 'zod'
|
|
|
2
2
|
|
|
3
3
|
import { ApiConfigZod } from './Api.ts'
|
|
4
4
|
import { AppConfigZod } from './App.ts'
|
|
5
|
+
import { BridgeConfigZod } from './Bridge.ts'
|
|
5
6
|
import { ChainConfigZod } from './Chain.ts'
|
|
6
7
|
import { EvmConfigZod } from './Evm.ts'
|
|
7
8
|
import { LogConfigZod } from './Log.ts'
|
|
@@ -15,6 +16,7 @@ export const ConfigZod = z.object({
|
|
|
15
16
|
...Xl1CommonConfigSchema.shape,
|
|
16
17
|
api: ApiConfigZod.default(ApiConfigZod.parse({})).describe('Configuration for the API node'),
|
|
17
18
|
app: AppConfigZod.default(AppConfigZod.parse({})).describe('Configuration for the application'),
|
|
19
|
+
bridge: BridgeConfigZod.default(BridgeConfigZod.parse({})).describe('Configuration for the Bridge node'),
|
|
18
20
|
chain: ChainConfigZod.default(ChainConfigZod.parse({})).describe('Configuration for the chain'),
|
|
19
21
|
evm: EvmConfigZod.default(EvmConfigZod.parse({})).describe('Configuration for EVM-backed services'),
|
|
20
22
|
producer: ProducerConfigZod.default(ProducerConfigZod.parse({})).describe('Configuration for the producer'),
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ export * from './ChainServiceCollection.ts'
|
|
|
3
3
|
export * from './ChainServiceCollectionV2.ts'
|
|
4
4
|
export * from './config/index.ts'
|
|
5
5
|
export * from './instances/index.ts'
|
|
6
|
+
export * from './map/index.ts'
|
|
7
|
+
export * from './model/index.ts'
|
|
6
8
|
export * from './payload/index.ts'
|
|
7
9
|
export * from './SignedBigInt.ts'
|
|
8
10
|
export * from './steps/index.ts'
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
|
|
8
8
|
import type { ShiftedBigIntConfig } from './ShiftedBigIntConfig.ts'
|
|
9
9
|
|
|
10
|
-
/** @deprecated use from
|
|
10
|
+
/** @deprecated use from \@xyo-network/xl1-protocol instead */
|
|
11
11
|
export interface XL1AmountInstance {
|
|
12
12
|
value: AttoXL1
|
|
13
13
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Promisable } from '@xylabs/promise'
|
|
2
|
+
|
|
3
|
+
export interface AsynchronousMapRead<K, V> {
|
|
4
|
+
get(id: K): Promisable<V | undefined>
|
|
5
|
+
getMany(id: K[]): Promisable<V[]>
|
|
6
|
+
has(id: K): Promisable<boolean>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AsynchronousMapWrite<K, V> {
|
|
10
|
+
clear(): Promisable<void>
|
|
11
|
+
delete(id: K): Promisable<boolean>
|
|
12
|
+
set(id: K, data: V): Promisable<void>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AsynchronousMap<K, V> extends AsynchronousMapRead<K, V>, AsynchronousMapWrite<K, V> {}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AsynchronousMapRead, AsynchronousMapWrite } from './AsynchronousMap.ts'
|
|
2
|
+
import type { SynchronousMapRead, SynchronousMapWrite } from './SynchronousMap.ts'
|
|
3
|
+
|
|
4
|
+
export type MapTypeWrite<TId, TData> = SynchronousMapWrite<TId, TData> | AsynchronousMapWrite<TId, TData>
|
|
5
|
+
|
|
6
|
+
export type MapTypeRead<TId, TData> = SynchronousMapRead<TId, TData> | AsynchronousMapRead<TId, TData>
|
|
7
|
+
|
|
8
|
+
export type MapType<TId, TData> = MapTypeRead<TId, TData> & MapTypeWrite<TId, TData>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface SynchronousMapRead<K, V> {
|
|
2
|
+
get(id: K): V | undefined
|
|
3
|
+
getMany(id: K[]): V[]
|
|
4
|
+
has(id: K): boolean
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface SynchronousMapWrite<K, V> {
|
|
8
|
+
clear(): void
|
|
9
|
+
delete(id: K): boolean
|
|
10
|
+
set(id: K, data: V): void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SynchronousMap<K, V> extends SynchronousMapRead<K, V>, SynchronousMapWrite<K, V> {}
|
package/src/map/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ChainIdentity } from '../ChainIdentity.ts'
|
|
2
|
+
import type {
|
|
3
|
+
ChainStakeContext, ChainStakeContextRead, ChainStakeContextWrite,
|
|
4
|
+
} from './ChainStakeContext.ts'
|
|
5
|
+
import type { ChainStateContextRead, ChainStateContextWrite } from './ChainStateContext.ts'
|
|
6
|
+
import type {
|
|
7
|
+
ChainStoreContext, ChainStoreContextRead, ChainStoreContextWrite,
|
|
8
|
+
} from './ChainStoreContext.ts'
|
|
9
|
+
|
|
10
|
+
export interface ChainContextWrite extends ChainIdentity, ChainStateContextWrite, ChainStoreContextWrite {}
|
|
11
|
+
|
|
12
|
+
export interface StakedChainContextWrite extends ChainContextWrite, ChainStakeContextWrite {}
|
|
13
|
+
|
|
14
|
+
export interface ChainContextRead extends ChainIdentity, ChainStateContextRead, ChainStoreContextRead {}
|
|
15
|
+
|
|
16
|
+
export interface StakedChainContextRead extends ChainContextRead, ChainStakeContextRead {}
|
|
17
|
+
|
|
18
|
+
export type ChainContext = ChainIdentity & ChainContextRead & ChainContextWrite & ChainStoreContext
|
|
19
|
+
|
|
20
|
+
export type StakedChainContext = ChainContext & ChainStakeContext & ChainStakeContextRead & ChainStakeContextWrite
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChainIdentity } from '../ChainIdentity.ts'
|
|
2
|
+
import type { ChainStakeRead, ChainStakeWrite } from '../ChainStake/index.ts'
|
|
3
|
+
|
|
4
|
+
export interface ChainStakeContextWrite extends ChainIdentity {
|
|
5
|
+
stake: ChainStakeWrite
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ChainStakeContextRead extends ChainIdentity {
|
|
9
|
+
stake: ChainStakeRead
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ChainStakeContext = ChainStakeContextRead & ChainStakeContextWrite
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Hash } from '@xylabs/hex'
|
|
2
|
+
import type { Promisable } from '@xylabs/promise'
|
|
3
|
+
|
|
4
|
+
export interface ChainStateContextRead {
|
|
5
|
+
head(): Promisable<Hash>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ChainStateContextWrite {}
|
|
9
|
+
|
|
10
|
+
export type ChainStateContext = ChainStateContextRead & ChainStateContextWrite
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChainIdentity } from '../ChainIdentity.ts'
|
|
2
|
+
import type { ChainStoreRead, ChainStoreWrite } from '../ChainStore.ts'
|
|
3
|
+
|
|
4
|
+
export interface ChainStoreContextWrite extends ChainIdentity {
|
|
5
|
+
store: ChainStoreWrite
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ChainStoreContextRead extends ChainIdentity {
|
|
9
|
+
store: ChainStoreRead
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ChainStoreContext = ChainStoreContextRead & ChainStoreContextWrite
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ChainForkStatic.ts'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Address } from '@xylabs/hex'
|
|
2
|
+
import type { Promisable } from '@xylabs/promise'
|
|
3
|
+
import type { Stake } from '@xyo-network/xl1-protocol'
|
|
4
|
+
|
|
5
|
+
export interface ChainStakeRead {
|
|
6
|
+
active(): Promisable<bigint>
|
|
7
|
+
activeByAddressStaked(address: string): Promisable<bigint>
|
|
8
|
+
activeByStaker(address: string): Promisable<bigint>
|
|
9
|
+
pending(): Promisable<bigint>
|
|
10
|
+
pendingByStaker(staker: string): Promisable<bigint>
|
|
11
|
+
stakeById(id: number): Promisable<Stake>
|
|
12
|
+
stakeByStaker(staker: Address, slot: number): Promisable<Stake>
|
|
13
|
+
stakesByStaked(staked: Address, range?: [number, number | 'latest']): Promisable<Stake[]>
|
|
14
|
+
stakesByStaker(staker: Address, range?: [number, number | 'latest']): Promisable<Stake[]>
|
|
15
|
+
withdrawn(): Promisable<bigint>
|
|
16
|
+
withdrawnByStaker(staker: string): Promisable<bigint>
|
|
17
|
+
}
|