@effectstream/sync 0.100.0
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/package.json +29 -0
- package/src/mod.ts +3 -0
- package/src/sync-protocols/avail/AvailClient.ts +120 -0
- package/src/sync-protocols/avail/fetcher.ts +174 -0
- package/src/sync-protocols/avail/state.ts +134 -0
- package/src/sync-protocols/avail/types.ts +92 -0
- package/src/sync-protocols/base/fetcher.ts +43 -0
- package/src/sync-protocols/base/page.ts +89 -0
- package/src/sync-protocols/base/primitive.ts +24 -0
- package/src/sync-protocols/base/state.ts +143 -0
- package/src/sync-protocols/bitcoin/fetcher.ts +549 -0
- package/src/sync-protocols/bitcoin/state.ts +144 -0
- package/src/sync-protocols/bitcoin/types.ts +92 -0
- package/src/sync-protocols/celestia/CelestiaClient.ts +139 -0
- package/src/sync-protocols/celestia/fetcher.ts +165 -0
- package/src/sync-protocols/celestia/state.ts +131 -0
- package/src/sync-protocols/celestia/types.ts +46 -0
- package/src/sync-protocols/common/page-helpers.ts +65 -0
- package/src/sync-protocols/common/root.ts +35 -0
- package/src/sync-protocols/common/utils.ts +29 -0
- package/src/sync-protocols/evm/fetcher.ts +290 -0
- package/src/sync-protocols/evm/state.ts +122 -0
- package/src/sync-protocols/evm/types.ts +53 -0
- package/src/sync-protocols/midnight/MidnightClient.ts +210 -0
- package/src/sync-protocols/midnight/fetcher.ts +252 -0
- package/src/sync-protocols/midnight/state.ts +147 -0
- package/src/sync-protocols/midnight/types.ts +128 -0
- package/src/sync-protocols/midnight/zswap-decoder.ts +123 -0
- package/src/sync-protocols/mod.ts +29 -0
- package/src/sync-protocols/near/NearClient.ts +178 -0
- package/src/sync-protocols/near/fetcher.ts +274 -0
- package/src/sync-protocols/near/state.ts +130 -0
- package/src/sync-protocols/near/types.ts +46 -0
- package/src/sync-protocols/ntp/fetcher.ts +199 -0
- package/src/sync-protocols/ntp/state.ts +118 -0
- package/src/sync-protocols/ntp/types.ts +40 -0
- package/src/sync-protocols/orchestration/merge.ts +215 -0
- package/src/sync-protocols/orchestration/sync.ts +74 -0
- package/src/sync-protocols/types.ts +32 -0
- package/src/sync-protocols/utxorpc/BufferedRpc.ts +169 -0
- package/src/sync-protocols/utxorpc/fetcher.ts +116 -0
- package/src/sync-protocols/utxorpc/state.ts +186 -0
- package/src/sync-protocols/utxorpc/types.ts +53 -0
- package/src/sync-protocols/utxorpc/utils.ts +80 -0
- package/src/syncProtocolFactory.ts +149 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { BaseDataFetcher, type DataFetched } from "../base/fetcher.ts";
|
|
2
|
+
import type { RootOutput, RootPage } from "../types.ts";
|
|
3
|
+
import type { Operation } from "effection";
|
|
4
|
+
import { bound, uint8ArrayToHexString } from "@effectstream/utils";
|
|
5
|
+
import type { ChainPoint, ConfigType, Input, Output, Page, PrimitiveType } from "./types.ts";
|
|
6
|
+
import type { OutputAndCleanup, RootConversion } from "../base/state.ts";
|
|
7
|
+
import type { BufferedRpc } from "./BufferedRpc.ts";
|
|
8
|
+
import { Buffer } from "node:buffer";
|
|
9
|
+
import type { cardano } from "@utxorpc/spec";
|
|
10
|
+
import { hashEqual, matchesPredicate } from "./utils.ts";
|
|
11
|
+
|
|
12
|
+
export class UtxoRpcFetcher
|
|
13
|
+
extends BaseDataFetcher<Input, Output, RootOutput, Page, RootPage> // PrimitiveFetcher<Input, Page, cardano.Block, PrimitiveType>,
|
|
14
|
+
{
|
|
15
|
+
constructor(
|
|
16
|
+
readonly config: ConfigType,
|
|
17
|
+
readonly client: BufferedRpc,
|
|
18
|
+
) {
|
|
19
|
+
super(config.syncProtocol.name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@bound
|
|
23
|
+
async startAsync(point?: ChainPoint) {
|
|
24
|
+
await this.client.start(point);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@bound
|
|
28
|
+
async resolveOrigin(): Promise<ChainPoint> {
|
|
29
|
+
return this.client.resolveOrigin();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@bound
|
|
33
|
+
override *readData(
|
|
34
|
+
data: Input,
|
|
35
|
+
rootConversion: RootConversion<Output, RootOutput, RootPage>,
|
|
36
|
+
): Operation<DataFetched<Output, Page, RootPage>> {
|
|
37
|
+
console.log(
|
|
38
|
+
`[UTXORPC] Fetching blocks from ${data.from} to ${data.to}. ${
|
|
39
|
+
data.isPresync ? "[presync]" : ""
|
|
40
|
+
}`,
|
|
41
|
+
);
|
|
42
|
+
const outputs: OutputAndCleanup<Output>[] = [];
|
|
43
|
+
const blocks = this.client.fetchBlocks(data.from, data.to);
|
|
44
|
+
for (const block of blocks) {
|
|
45
|
+
outputs.push({
|
|
46
|
+
output: {
|
|
47
|
+
// TODO: What is the correct block hash?
|
|
48
|
+
blockHashes: [String(block.output.header?.hash)],
|
|
49
|
+
raw: block.output,
|
|
50
|
+
primitives: this.findPrimitives(block.output),
|
|
51
|
+
},
|
|
52
|
+
cleanup: block.cleanup,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
output: outputs,
|
|
57
|
+
lastPage: {
|
|
58
|
+
ownBlockNumber: Number(data.to),
|
|
59
|
+
own: {
|
|
60
|
+
slot: Number(
|
|
61
|
+
outputs[outputs.length - 1].output.raw.header!.slot,
|
|
62
|
+
),
|
|
63
|
+
height: Number(
|
|
64
|
+
outputs[outputs.length - 1].output.raw.header!.height,
|
|
65
|
+
),
|
|
66
|
+
hash: Buffer.from(
|
|
67
|
+
outputs[outputs.length - 1].output.raw.header!.hash,
|
|
68
|
+
)
|
|
69
|
+
.toString("hex"),
|
|
70
|
+
},
|
|
71
|
+
root: rootConversion.toRootPage({
|
|
72
|
+
blockHashes: [],
|
|
73
|
+
primitives: [], // TODO: I think this can be left empty here
|
|
74
|
+
raw: outputs[outputs.length - 1].output.raw,
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@bound
|
|
81
|
+
findPrimitives(block: cardano.Block): PrimitiveType[] {
|
|
82
|
+
const primitiveResponses: PrimitiveType[] = [];
|
|
83
|
+
for (const tx of block.body?.tx ?? []) {
|
|
84
|
+
let logIndex = 0;
|
|
85
|
+
for (const primitveEntry of this.config.primitives) {
|
|
86
|
+
if (!matchesPredicate(tx, primitveEntry.primitive.predicate)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const transactionIndex = block.body?.tx.findIndex(t => hashEqual(t.hash, tx.hash));
|
|
90
|
+
primitiveResponses.push({
|
|
91
|
+
syncProtocol: {
|
|
92
|
+
name: primitveEntry.syncProtocol,
|
|
93
|
+
blockNumber: Number(block.header!.height),
|
|
94
|
+
transactionHash: uint8ArrayToHexString(tx.hash),
|
|
95
|
+
transactionIndex,
|
|
96
|
+
contractAddress: '',
|
|
97
|
+
logIndex,
|
|
98
|
+
},
|
|
99
|
+
primitive: primitveEntry.primitive.name,
|
|
100
|
+
output: {
|
|
101
|
+
payloadType: 'utxorpc-response',
|
|
102
|
+
payload: { tx }
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
++logIndex;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return primitiveResponses;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
lastHeight(): bigint | undefined {
|
|
112
|
+
// Calling this.client.syncClient.getTip();
|
|
113
|
+
// returns the hash and slot, but not the block height
|
|
114
|
+
return this.client.lastHeight();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { applyDelay } from "../common/utils.ts";
|
|
2
|
+
import type { Operation } from "effection";
|
|
3
|
+
import { call } from "effection";
|
|
4
|
+
import { getPage } from "@effectstream/db";
|
|
5
|
+
import type { PoolClient } from "pg";
|
|
6
|
+
import { bound, type TimestampMs } from "@effectstream/utils";
|
|
7
|
+
import { type LastPage, SyncState } from "../base/state.ts";
|
|
8
|
+
import type { RootOutput, RootPage } from "../types.ts";
|
|
9
|
+
import type { UtxoRpcFetcher } from "./fetcher.ts";
|
|
10
|
+
import type { ConfigType, Input, Output, Page } from "./types.ts";
|
|
11
|
+
import { chainPointRelation } from "./types.ts";
|
|
12
|
+
import { Buffer } from "node:buffer";
|
|
13
|
+
|
|
14
|
+
export class UtxoRpcSyncState extends SyncState<
|
|
15
|
+
Input,
|
|
16
|
+
Output,
|
|
17
|
+
Page,
|
|
18
|
+
RootOutput,
|
|
19
|
+
RootPage,
|
|
20
|
+
UtxoRpcFetcher
|
|
21
|
+
> {
|
|
22
|
+
constructor(
|
|
23
|
+
lastPage: undefined | LastPage<Page, RootPage>,
|
|
24
|
+
readonly config: ConfigType,
|
|
25
|
+
fetcher: UtxoRpcFetcher,
|
|
26
|
+
dbConn: PoolClient,
|
|
27
|
+
) {
|
|
28
|
+
super(
|
|
29
|
+
config.syncProtocol.name,
|
|
30
|
+
lastPage,
|
|
31
|
+
fetcher,
|
|
32
|
+
chainPointRelation,
|
|
33
|
+
dbConn,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@bound
|
|
38
|
+
override *startAsync(): Operation<void> {
|
|
39
|
+
if (this.lastPage == null) {
|
|
40
|
+
const cp = this.config.syncProtocol.startChainPoint;
|
|
41
|
+
if (cp === "origin") {
|
|
42
|
+
const origin = yield* call(() => this.fetcher.resolveOrigin());
|
|
43
|
+
yield* call(() => this.fetcher.startAsync(origin));
|
|
44
|
+
} else if (cp === "tip") {
|
|
45
|
+
yield* call(() => this.fetcher.startAsync(undefined));
|
|
46
|
+
} else {
|
|
47
|
+
yield* call(() => this.fetcher.startAsync({ slot: cp.slot, hash: cp.hash }));
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
yield* call(() =>
|
|
52
|
+
this.fetcher.startAsync({
|
|
53
|
+
slot: this.lastPage!.own.slot,
|
|
54
|
+
hash: this.lastPage!.own.hash,
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@bound
|
|
60
|
+
override toRootOutput(data: Output): RootOutput {
|
|
61
|
+
throw new Error("Only main chains create root outputs");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@bound
|
|
65
|
+
override toRootPage(data: Output): RootPage {
|
|
66
|
+
return applyDelay(
|
|
67
|
+
Number(data.raw.timestamp * 1000n),
|
|
68
|
+
this.config.syncProtocol.delayMs,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@bound
|
|
73
|
+
override *stateToInput(): Operation<Input | undefined> {
|
|
74
|
+
// Get the value of the latest block height.
|
|
75
|
+
const tipHeight = yield* call(() => this.fetcher.lastHeight());
|
|
76
|
+
if (!tipHeight) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Using 0 as the startHeight for both "origin" and a specified ChainPoint is safe because:
|
|
81
|
+
// the FollowTip stream only buffers blocks starting from the intersection,
|
|
82
|
+
// so fetchBlocks(from=1) will match the first block, regardless of it's height.
|
|
83
|
+
// After that first block, lastPage.own.height will give a real height value.
|
|
84
|
+
const cp = this.config.syncProtocol.startChainPoint;
|
|
85
|
+
const startHeight = this.lastPage?.own.height ??
|
|
86
|
+
(cp === "tip"
|
|
87
|
+
? Number(tipHeight) - 1
|
|
88
|
+
: 0);
|
|
89
|
+
|
|
90
|
+
if (BigInt(startHeight) >= tipHeight) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// TODO We need to check how to handle the presync process.
|
|
95
|
+
// const storedBlocks = yield* this.fetcher.client.storedBlocks(
|
|
96
|
+
// this.lastPage?.own.height,
|
|
97
|
+
// );
|
|
98
|
+
// if (storedBlocks.from.slot < this.config.syncProtocol.startSlot) {
|
|
99
|
+
// return {
|
|
100
|
+
// from: storedBlocks.from.height,
|
|
101
|
+
// to: Math.max(
|
|
102
|
+
// storedBlocks.to.height,
|
|
103
|
+
// // TODO: this should be a height and not a slot number
|
|
104
|
+
// // either we need to resolve the actual block at that slot
|
|
105
|
+
// // (blocked by https://github.com/utxorpc/spec/issues/148)
|
|
106
|
+
// // or we need to guess and refine if wrong
|
|
107
|
+
// this.config.syncProtocol.startSlot - 1,
|
|
108
|
+
// ),
|
|
109
|
+
// isPresync: true,
|
|
110
|
+
// };
|
|
111
|
+
// }
|
|
112
|
+
|
|
113
|
+
// TODO Is this correct?
|
|
114
|
+
// EVM Handles this with `genInputRange`
|
|
115
|
+
const from = startHeight + 1;
|
|
116
|
+
const to = tipHeight ? Math.min(
|
|
117
|
+
from + 1,
|
|
118
|
+
Number(tipHeight),
|
|
119
|
+
) : from + 1;
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
from,
|
|
123
|
+
to,
|
|
124
|
+
isPresync: false, // TODO: handle presync
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@bound
|
|
129
|
+
override toPage(
|
|
130
|
+
input: Input,
|
|
131
|
+
data: Output[],
|
|
132
|
+
): Page {
|
|
133
|
+
return {
|
|
134
|
+
slot: Number(data[data.length - 1].raw.header!.slot),
|
|
135
|
+
height: Number(data[data.length - 1].raw.header!.height),
|
|
136
|
+
hash: Buffer.from(data[data.length - 1].raw.header!.hash).toString(
|
|
137
|
+
"hex",
|
|
138
|
+
),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@bound
|
|
143
|
+
override mergeDatum(
|
|
144
|
+
ourOutput: Output,
|
|
145
|
+
rootOutput: RootOutput,
|
|
146
|
+
): void {
|
|
147
|
+
const primitives = ourOutput.primitives.map((p) => ({
|
|
148
|
+
...p,
|
|
149
|
+
source: this.config.syncProtocol.name,
|
|
150
|
+
}));
|
|
151
|
+
const blockInfo = ourOutput.blockHashes.map((h) => ({
|
|
152
|
+
protocol_name: this.config.syncProtocol.name,
|
|
153
|
+
block_number: Number(ourOutput.raw.header!.height),
|
|
154
|
+
blockHash: h,
|
|
155
|
+
}));
|
|
156
|
+
rootOutput.primitives.push(...primitives);
|
|
157
|
+
rootOutput.blockInfo.push(...blockInfo);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@bound
|
|
161
|
+
override getNamespace(): string[] {
|
|
162
|
+
return [this.config.network.name, this.config.syncProtocol.name];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static *restoreState(
|
|
166
|
+
dbConn: PoolClient,
|
|
167
|
+
config: ConfigType,
|
|
168
|
+
fetcher: UtxoRpcFetcher,
|
|
169
|
+
): Operation<UtxoRpcSyncState> {
|
|
170
|
+
// TODO: move this DB query into page-helpers?
|
|
171
|
+
const [result] = yield* call(async () =>
|
|
172
|
+
await getPage.run({
|
|
173
|
+
protocol_name: config.syncProtocol.name,
|
|
174
|
+
}, dbConn)
|
|
175
|
+
);
|
|
176
|
+
const page = result
|
|
177
|
+
? result.page as unknown as LastPage<Page, RootPage>
|
|
178
|
+
: undefined;
|
|
179
|
+
return new UtxoRpcSyncState(
|
|
180
|
+
page,
|
|
181
|
+
config,
|
|
182
|
+
fetcher,
|
|
183
|
+
dbConn,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AbsoluteSlotNumber,
|
|
3
|
+
BlockNumber,
|
|
4
|
+
CardanoBlockHash,
|
|
5
|
+
CardanoBlockNumber,
|
|
6
|
+
TimestampMs,
|
|
7
|
+
} from "@effectstream/utils";
|
|
8
|
+
import type { PageRelation } from "../base/page.ts";
|
|
9
|
+
import type { cardano } from "@utxorpc/spec";
|
|
10
|
+
import type { PageSyncRange } from "../common/page-helpers.ts";
|
|
11
|
+
import type {
|
|
12
|
+
ConfigSyncProtocolType,
|
|
13
|
+
FlattenSyncProtocolIOFor,
|
|
14
|
+
PrimitiveEntry,
|
|
15
|
+
SyncProtocolWithNetwork,
|
|
16
|
+
} from "@effectstream/config";
|
|
17
|
+
|
|
18
|
+
import type { CardanoChainPoint } from "@utxorpc/sdk";
|
|
19
|
+
export type ChainPoint = CardanoChainPoint;
|
|
20
|
+
|
|
21
|
+
export type Page = {
|
|
22
|
+
slot: AbsoluteSlotNumber;
|
|
23
|
+
height: CardanoBlockNumber;
|
|
24
|
+
hash: CardanoBlockHash;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type PrimitiveEntryType = Extract<
|
|
28
|
+
PrimitiveEntry,
|
|
29
|
+
{ syncProtocol: ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL }
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
// TODO: blocked on https://github.com/utxorpc/spec/issues/135
|
|
33
|
+
export type PrimitiveType = FlattenSyncProtocolIOFor<
|
|
34
|
+
ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL
|
|
35
|
+
>;
|
|
36
|
+
export type Input = PageSyncRange<BlockNumber>;
|
|
37
|
+
export type Output = {
|
|
38
|
+
raw: cardano.Block;
|
|
39
|
+
primitives: PrimitiveType[];
|
|
40
|
+
blockHashes: CardanoBlockHash[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const chainPointRelation: PageRelation<Page> = {
|
|
44
|
+
compare: (p1, p2) => p1.slot - p2.slot,
|
|
45
|
+
equals: (p1, p2) => p1.slot === p2.slot,
|
|
46
|
+
min: (p1, p2) => (p1.slot < p2.slot ? p1 : p2),
|
|
47
|
+
max: (p1, p2) => (p1.slot > p2.slot ? p1 : p2),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type ConfigType = Extract<
|
|
51
|
+
SyncProtocolWithNetwork,
|
|
52
|
+
{ syncProtocolType: ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL }
|
|
53
|
+
>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { cardano } from '@utxorpc/spec';
|
|
2
|
+
import type { UtxorpcAddressPattern, UtxorpcAnyChainTxPattern, UtxorpcAssetPattern, UtxorpcTxOutputPattern, UtxorpcTxPredicate } from "@effectstream/config";
|
|
3
|
+
import { hexStringToUint8Array } from "@effectstream/utils";
|
|
4
|
+
import { Cardano } from '@cardano-sdk/core';
|
|
5
|
+
import { HexBlob } from '@cardano-sdk/util';
|
|
6
|
+
|
|
7
|
+
export function hashEqual(lhs: Uint8Array, rhs: Uint8Array): boolean {
|
|
8
|
+
if (lhs.length !== rhs.length) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
for (let i = 0; i < lhs.length; ++i) {
|
|
12
|
+
if (lhs[i] !== rhs[i]) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function matchesPredicate(tx: cardano.Tx, predicate: UtxorpcTxPredicate): boolean {
|
|
20
|
+
if (predicate.match && !matchesPattern(tx, predicate.match)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (predicate.not && predicate.not.some(p => matchesPredicate(tx, p))) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (predicate.all_of && !predicate.all_of.every(p => matchesPredicate(tx, p))) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (predicate.any_of && !predicate.any_of.some(p => matchesPredicate(tx, p))) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function matchesPattern(tx: cardano.Tx, pattern: UtxorpcAnyChainTxPattern): boolean {
|
|
36
|
+
const cardanoPattern = pattern.cardano;
|
|
37
|
+
if (!cardanoPattern) return false;
|
|
38
|
+
const outputs = tx.outputs;
|
|
39
|
+
const inputs = tx.inputs.map(input => input.asOutput!).filter(x => x);
|
|
40
|
+
const matchHasAddress = !cardanoPattern.has_address || matchesAddress(outputs, cardanoPattern.has_address) || matchesAddress(inputs, cardanoPattern.has_address);
|
|
41
|
+
const matchMovesAsset = !cardanoPattern.moves_asset || matchesAsset(inputs, cardanoPattern.moves_asset);
|
|
42
|
+
const matchMintsAsset = !cardanoPattern.mints_asset || matchesAsset(outputs, cardanoPattern.mints_asset);
|
|
43
|
+
return matchHasAddress && matchMovesAsset && matchMintsAsset;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function matchesOutputPattern(outputs: cardano.TxOutput[], pattern: UtxorpcTxOutputPattern): boolean {
|
|
47
|
+
const matchAddress = !pattern.address || matchesAddress(outputs, pattern.address);
|
|
48
|
+
const matchAsset = !pattern.asset || matchesAsset(outputs, pattern.asset);
|
|
49
|
+
return matchAddress && matchAsset;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function matchesAddress(outputs: cardano.TxOutput[], pattern: UtxorpcAddressPattern): boolean {
|
|
53
|
+
if (pattern.exact_address) {
|
|
54
|
+
const address = Uint8Array.from(atob(pattern.exact_address), c => c.charCodeAt(0));
|
|
55
|
+
if (!outputs.some(o => hashEqual(o.address, address))) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (pattern.payment_part || pattern.delegation_part) {
|
|
60
|
+
const addresses = outputs.map(o => Cardano.Address.fromBytes(HexBlob.fromBytes(o.address)));
|
|
61
|
+
if (pattern.payment_part && !addresses.some(a => a.getProps()?.paymentPart?.hash === pattern.payment_part)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (pattern.delegation_part && !addresses.some(a => a.getProps()?.delegationPart?.hash === pattern.delegation_part)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function matchesAsset(outputs: cardano.TxOutput[], pattern: UtxorpcAssetPattern): boolean {
|
|
72
|
+
const policyId = pattern.policy_id ? hexStringToUint8Array(pattern.policy_id) : null;
|
|
73
|
+
const assetName = pattern.asset_name ? hexStringToUint8Array(pattern.asset_name) : null;
|
|
74
|
+
return outputs.some(o => o.assets.some(ma => {
|
|
75
|
+
if (policyId && !hashEqual(policyId, ma.policyId)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return ma.assets.some(a => !assetName || hashEqual(a.name, assetName));
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { EvmSyncState } from "./sync-protocols/evm/state.ts";
|
|
2
|
+
import { EvmFetcher } from "./sync-protocols/evm/fetcher.ts";
|
|
3
|
+
import type { Operation } from "effection";
|
|
4
|
+
import type { PoolClient } from "pg";
|
|
5
|
+
import type { AllSyncProtocols } from "./sync-protocols/types.ts";
|
|
6
|
+
import { createViemPublicClient } from "@effectstream/utils";
|
|
7
|
+
import type { SyncProtocolWithNetwork } from "@effectstream/config";
|
|
8
|
+
import {
|
|
9
|
+
ConfigNetworkType,
|
|
10
|
+
ConfigSyncProtocolType,
|
|
11
|
+
getViemNetwork,
|
|
12
|
+
} from "@effectstream/config";
|
|
13
|
+
import { CardanoSyncClient } from "@utxorpc/sdk";
|
|
14
|
+
import { BufferedRpc } from "./sync-protocols/utxorpc/BufferedRpc.ts";
|
|
15
|
+
import { UtxoRpcFetcher } from "./sync-protocols/utxorpc/fetcher.ts";
|
|
16
|
+
import { UtxoRpcSyncState } from "./sync-protocols/utxorpc/state.ts";
|
|
17
|
+
import { MidnightFetcher, MidnightSyncState } from "@effectstream/sync";
|
|
18
|
+
import { AvailFetcher } from "./sync-protocols/avail/fetcher.ts";
|
|
19
|
+
import { AvailSyncState } from "./sync-protocols/avail/state.ts";
|
|
20
|
+
import { NtpFetcher } from "./sync-protocols/ntp/fetcher.ts";
|
|
21
|
+
import { NtpSyncState } from "./sync-protocols/ntp/state.ts";
|
|
22
|
+
import {
|
|
23
|
+
BitcoinFetcher,
|
|
24
|
+
BitcoinRpcClient,
|
|
25
|
+
} from "./sync-protocols/bitcoin/fetcher.ts";
|
|
26
|
+
import { BitcoinSyncState } from "./sync-protocols/bitcoin/state.ts";
|
|
27
|
+
import { CelestiaFetcher } from "./sync-protocols/celestia/fetcher.ts";
|
|
28
|
+
import { CelestiaSyncState } from "./sync-protocols/celestia/state.ts";
|
|
29
|
+
import { NearFetcher } from "./sync-protocols/near/fetcher.ts";
|
|
30
|
+
import { NearSyncState } from "./sync-protocols/near/state.ts";
|
|
31
|
+
|
|
32
|
+
export function* genSyncProtocols(
|
|
33
|
+
dbConn: PoolClient,
|
|
34
|
+
syncInfo: SyncProtocolWithNetwork[],
|
|
35
|
+
): Operation<AllSyncProtocols[]> {
|
|
36
|
+
const result: AllSyncProtocols[] = [];
|
|
37
|
+
for (const entry of syncInfo) {
|
|
38
|
+
if (entry.networkType === ConfigNetworkType.NTP) {
|
|
39
|
+
const fetcher = new NtpFetcher(entry);
|
|
40
|
+
const state = yield* NtpSyncState.restoreState(
|
|
41
|
+
dbConn,
|
|
42
|
+
entry,
|
|
43
|
+
fetcher,
|
|
44
|
+
);
|
|
45
|
+
result.push(state);
|
|
46
|
+
} else if (
|
|
47
|
+
entry.networkType === ConfigNetworkType.EVM
|
|
48
|
+
) {
|
|
49
|
+
const viemNetwork = yield* getViemNetwork(entry.network);
|
|
50
|
+
const fetcher = new EvmFetcher(
|
|
51
|
+
entry,
|
|
52
|
+
createViemPublicClient(viemNetwork, {
|
|
53
|
+
cacheTime: entry.syncProtocol.pollingInterval,
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
const state = yield* EvmSyncState.restoreState(
|
|
57
|
+
dbConn,
|
|
58
|
+
entry,
|
|
59
|
+
fetcher,
|
|
60
|
+
);
|
|
61
|
+
result.push(state);
|
|
62
|
+
} else if (
|
|
63
|
+
entry.networkType === ConfigNetworkType.CARDANO
|
|
64
|
+
) {
|
|
65
|
+
if (
|
|
66
|
+
entry.syncProtocolType === ConfigSyncProtocolType.CARDANO_CARP_PARALLEL
|
|
67
|
+
) {
|
|
68
|
+
throw new Error("CARP not supported yet");
|
|
69
|
+
}
|
|
70
|
+
const syncClient = new CardanoSyncClient({
|
|
71
|
+
uri: entry.syncProtocol.rpcUrl,
|
|
72
|
+
headers: entry.syncProtocol.headers,
|
|
73
|
+
});
|
|
74
|
+
const bufferedRpc = new BufferedRpc(
|
|
75
|
+
syncClient,
|
|
76
|
+
entry.syncProtocol.confirmationDepth,
|
|
77
|
+
);
|
|
78
|
+
const fetcher = new UtxoRpcFetcher(
|
|
79
|
+
entry,
|
|
80
|
+
bufferedRpc,
|
|
81
|
+
);
|
|
82
|
+
const state = yield* UtxoRpcSyncState.restoreState(
|
|
83
|
+
dbConn,
|
|
84
|
+
entry,
|
|
85
|
+
fetcher,
|
|
86
|
+
);
|
|
87
|
+
result.push(state);
|
|
88
|
+
} else if (
|
|
89
|
+
entry.networkType === ConfigNetworkType.MIDNIGHT
|
|
90
|
+
) {
|
|
91
|
+
const fetcher = new MidnightFetcher(entry);
|
|
92
|
+
const state = yield* MidnightSyncState.restoreState(
|
|
93
|
+
dbConn,
|
|
94
|
+
entry,
|
|
95
|
+
fetcher,
|
|
96
|
+
);
|
|
97
|
+
result.push(state);
|
|
98
|
+
} else if (
|
|
99
|
+
entry.networkType === ConfigNetworkType.AVAIL
|
|
100
|
+
) {
|
|
101
|
+
const fetcher = new AvailFetcher(entry);
|
|
102
|
+
const state = yield* AvailSyncState.restoreState(
|
|
103
|
+
dbConn,
|
|
104
|
+
entry,
|
|
105
|
+
fetcher,
|
|
106
|
+
);
|
|
107
|
+
result.push(state);
|
|
108
|
+
} else if (
|
|
109
|
+
entry.networkType === ConfigNetworkType.BITCOIN
|
|
110
|
+
) {
|
|
111
|
+
const rpcClient = new BitcoinRpcClient({
|
|
112
|
+
url: entry.network.rpcUrl,
|
|
113
|
+
username: entry.network.rpcAuth?.username ?? null,
|
|
114
|
+
password: entry.network.rpcAuth?.password ?? null,
|
|
115
|
+
});
|
|
116
|
+
const fetcher = new BitcoinFetcher(entry, rpcClient);
|
|
117
|
+
const state = yield* BitcoinSyncState.restoreState(
|
|
118
|
+
dbConn,
|
|
119
|
+
entry,
|
|
120
|
+
fetcher,
|
|
121
|
+
);
|
|
122
|
+
result.push(state);
|
|
123
|
+
} else if (
|
|
124
|
+
entry.networkType === ConfigNetworkType.CELESTIA
|
|
125
|
+
) {
|
|
126
|
+
const fetcher = new CelestiaFetcher(entry);
|
|
127
|
+
const state = yield* CelestiaSyncState.restoreState(
|
|
128
|
+
dbConn,
|
|
129
|
+
entry,
|
|
130
|
+
fetcher,
|
|
131
|
+
);
|
|
132
|
+
result.push(state);
|
|
133
|
+
} else if (
|
|
134
|
+
entry.networkType === ConfigNetworkType.NEAR
|
|
135
|
+
) {
|
|
136
|
+
const fetcher = new NearFetcher(entry);
|
|
137
|
+
const state = yield* NearSyncState.restoreState(
|
|
138
|
+
dbConn,
|
|
139
|
+
entry,
|
|
140
|
+
fetcher,
|
|
141
|
+
);
|
|
142
|
+
result.push(state);
|
|
143
|
+
} else {
|
|
144
|
+
throw new Error(`Unsupported network type: ${entry.network.type}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result;
|
|
149
|
+
}
|