Skip to content

Commit 81937ff

Browse files
feat(EthereumRegistryWriter): onCidAdded + transaction receipts (#953)
1 parent 50028ec commit 81937ff

File tree

11 files changed

+557
-256
lines changed

11 files changed

+557
-256
lines changed

package-lock.json

Lines changed: 258 additions & 222 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,20 +60,22 @@
6060
"bitcoin-core": "2.2.0",
6161
"bs58": "4.0.1",
6262
"form-data": "2.3.3",
63+
"fp-ts": "2.2.0",
6364
"ipfs-http-client": "28.1.0",
6465
"joi": "14.3.0",
6566
"koa": "2.6.2",
6667
"koa-body": "4.0.4",
6768
"koa-cors": "0.0.16",
6869
"koa-helmet": "4.0.0",
6970
"koa-router": "7.4.0",
71+
"luxon": "1.21.2",
7072
"mongodb": "3.1.10",
7173
"node-fetch": "1.7.3",
7274
"pino": "4.17.6",
7375
"protobufjs": "6.8.8",
7476
"ramda": "0.26.1",
7577
"string-to-stream": "1.1.1",
76-
"web3": "1.2.2"
78+
"web3": "1.2.4"
7779
},
7880
"devDependencies": {
7981
"@po.et/tslint-rules": "2.2.0",
@@ -84,6 +86,7 @@
8486
"@types/koa": "2.0.47",
8587
"@types/koa-helmet": "3.1.2",
8688
"@types/koa-router": "7.0.35",
89+
"@types/luxon": "1.21.0",
8790
"@types/mongodb": "3.3.8",
8891
"@types/node-fetch": "1.6.9",
8992
"@types/pino": "4.16.1",

src/Configuration.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ export interface Configuration extends LoggingConfiguration, BitcoinRPCConfigura
5050
readonly ethereumRegistryPrivateKey: string
5151
readonly ethereumRegistryUploadAnchorReceiptIntervalInSeconds: number
5252
readonly ethereumRegistryRegisterNextDirectoryIntervalInSeconds: number
53+
readonly ethereumGasPrice: number
54+
readonly ethereumMaximumUnconfirmedTransactionAgeInSeconds: number
5355
}
5456

5557
export interface LoggingConfiguration {
@@ -141,6 +143,8 @@ export const DefaultConfiguration: Configuration = {
141143
ethereumRegistryPrivateKey: '',
142144
ethereumRegistryUploadAnchorReceiptIntervalInSeconds: 60,
143145
ethereumRegistryRegisterNextDirectoryIntervalInSeconds: 60,
146+
ethereumGasPrice: 1e9,
147+
ethereumMaximumUnconfirmedTransactionAgeInSeconds: 5 * 60 * 1000,
144148

145149
exchangeAnchorNextHashRequest: 'ANCHOR_NEXT_HASH_REQUEST',
146150
exchangeBatchReaderReadNextDirectoryRequest: 'BATCH_READER::READ_NEXT_DIRECTORY_REQUEST',

src/EthereumRegistryWriter/Business.ts

Lines changed: 158 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { PoetBlockAnchor } from '@po.et/poet-js'
2+
import { DateTime } from 'luxon'
23
import { Collection, Db } from 'mongodb'
34
import * as Pino from 'pino'
4-
import { identity } from 'ramda'
5+
import { identity, map, filter, tap, complement } from 'ramda'
6+
import { TransactionReceipt } from 'web3-core'
57

68
import { EthereumRegistryContract } from 'Helpers/EthereumRegistryContract'
79
import { IPFS } from 'Helpers/IPFS'
810
import { childWithFileName } from 'Helpers/Logging'
11+
import { asyncPipe } from 'Helpers/asyncPipe'
912
import { ClaimIPFSHashPair } from 'Interfaces'
1013

1114
export interface Dependencies {
@@ -15,8 +18,13 @@ export interface Dependencies {
1518
readonly ethereumRegistryContract: EthereumRegistryContract
1619
}
1720

21+
export interface Configuration {
22+
readonly maximumUnconfirmedTransactionAgeInSeconds: number
23+
}
24+
1825
export interface Arguments {
1926
readonly dependencies: Dependencies
27+
readonly configuration: Configuration
2028
}
2129

2230
export interface Business {
@@ -28,7 +36,15 @@ export interface Business {
2836
readonly confirmClaimFiles: (claimIPFSHashPairs: ReadonlyArray<ClaimIPFSHashPair>) => Promise<void>
2937
readonly uploadNextAnchorReceipt: () => Promise<void>
3038
readonly uploadNextClaimFileAnchorReceiptPair: () => Promise<void>
31-
readonly registerNextDirectory: () => Promise<void>
39+
readonly writeNextDirectoryToEthereum: () => Promise<void>
40+
readonly setRegistryIndex: (
41+
confirmClaimAndAnchorReceiptDirectory: string,
42+
registryIndex: number,
43+
transactionHash: string,
44+
blockHash: string,
45+
blockNumber: number,
46+
) => Promise<void>
47+
readonly getEthereumTransactionReceipts: () => Promise<void>
3248
}
3349

3450
interface DbEntry {
@@ -40,6 +56,17 @@ interface DbEntry {
4056
readonly batchDirectoryConfirmed?: boolean
4157
readonly claimAndAnchorReceiptDirectory?: string
4258
readonly registryIndex?: number
59+
readonly registryAdditionTransactionReceipt?: {
60+
readonly transactionHash?: string
61+
readonly transactionCreationDate?: Date
62+
readonly blockHash?: string
63+
readonly blockNumber?: number, // comma due to some tslint randomness, will move to eslint in the future
64+
}
65+
readonly onCidAdded?: {
66+
readonly transactionHash?: string
67+
readonly blockHash?: string
68+
readonly blockNumber?: number, // comma due to some tslint randomness, will move to eslint in the future
69+
}
4370
}
4471

4572
export const Business = ({
@@ -49,6 +76,9 @@ export const Business = ({
4976
ipfs,
5077
ethereumRegistryContract,
5178
},
79+
configuration: {
80+
maximumUnconfirmedTransactionAgeInSeconds,
81+
},
5282
}: Arguments): Business => {
5383
const businessLogger: Pino.Logger = childWithFileName(logger, __filename)
5484
const claimAnchorReceiptsCollection: Collection<DbEntry> = db.collection('claimAnchorReceipts')
@@ -187,26 +217,135 @@ export const Business = ({
187217
await claimAnchorReceiptsCollection.updateOne({ claimId }, { $set: { claimAndAnchorReceiptDirectory } })
188218
}
189219

190-
const registerNextDirectory = async () => {
220+
const writeNextDirectoryToEthereum = async () => {
191221
const logger = businessLogger.child({ method: 'registerNextDirectory' })
192-
const entry = await claimAnchorReceiptsCollection.findOne({
193-
claimAndAnchorReceiptDirectory: { $ne: null },
194-
registryIndex: null,
195-
})
196-
if (!entry)
222+
223+
const entry = await claimAnchorReceiptsCollection.findOneAndUpdate(
224+
{
225+
claimAndAnchorReceiptDirectory: { $ne: null },
226+
$or: [
227+
{ 'registryAdditionTransactionReceipt.transactionCreationDate': null },
228+
{ $and: [
229+
{ 'registryAdditionTransactionReceipt.transactionCreationDate': {
230+
$lt: DateTime.utc().minus({ seconds: maximumUnconfirmedTransactionAgeInSeconds }).toJSDate(),
231+
} },
232+
{ 'registryAdditionTransactionReceipt.blockHash': null },
233+
]},
234+
],
235+
},
236+
{
237+
$set: { 'registryAdditionTransactionReceipt.transactionCreationDate': new Date() },
238+
},
239+
)
240+
if (!entry.value)
197241
return
198-
const { claimId, claimAndAnchorReceiptDirectory } = entry
199-
const cidCount = await ethereumRegistryContract.getCidCount()
242+
const { claimId, claimAndAnchorReceiptDirectory } = entry.value
200243
logger.debug(
201-
{ claimId, claimAndAnchorReceiptDirectory, cidCount },
202-
'Registering next (claim + anchor receipt) directory to Ethereum',
244+
{ claimId, claimAndAnchorReceiptDirectory },
245+
'Adding (claim + anchor receipt) directory to Ethereum',
203246
)
204-
await ethereumRegistryContract.addCid(claimAndAnchorReceiptDirectory)
247+
const transactionHash = await ethereumRegistryContract.addCid(claimAndAnchorReceiptDirectory)
248+
205249
logger.info(
206-
{ claimId, claimAndAnchorReceiptDirectory, cidCount },
207-
'(claim + anchor receipt) directory added to Ethereum',
250+
{ claimId, claimAndAnchorReceiptDirectory, transactionHash },
251+
'(claim + anchor receipt) transaction sent',
208252
)
209-
await claimAnchorReceiptsCollection.updateOne({ claimId }, { $set: { registryIndex: cidCount } })
253+
254+
await claimAnchorReceiptsCollection.updateOne(
255+
{ claimAndAnchorReceiptDirectory },
256+
{ $set: { 'registryAdditionTransactionReceipt.transactionHash': transactionHash } },
257+
)
258+
259+
}
260+
261+
const setRegistryIndex = async (
262+
claimAndAnchorReceiptDirectory: string,
263+
registryIndex: number,
264+
transactionHash: string,
265+
blockHash: string,
266+
blockNumber: number,
267+
) => {
268+
const logger = businessLogger.child({ method: 'setRegistryIndex' })
269+
await claimAnchorReceiptsCollection.updateOne(
270+
{ claimAndAnchorReceiptDirectory },
271+
{ $set: { registryIndex, onCidAdded: { transactionHash, blockHash, blockNumber } } },
272+
)
273+
logger.info(
274+
{ claimAndAnchorReceiptDirectory, registryIndex, blockNumber, transactionHash },
275+
'Registry index for (claim + anchor receipt) directory set',
276+
)
277+
}
278+
279+
const getEthereumTransactionReceipts = async () => {
280+
const logger = businessLogger.child({ method: 'getEthereumTransactionReceipts' })
281+
282+
logger.trace('Looking for transactions without confirmation')
283+
284+
const entries = await claimAnchorReceiptsCollection.find(
285+
{
286+
'registryAdditionTransactionReceipt.transactionHash': { $ne: null },
287+
'registryAdditionTransactionReceipt.blockHash': null,
288+
},
289+
{ projection: { 'registryAdditionTransactionReceipt.transactionHash': 1 } },
290+
).toArray()
291+
292+
if (!entries.length) {
293+
logger.trace('No transactions without confirmation found')
294+
return
295+
}
296+
297+
const transactionHashes = entries.map(_ => _.registryAdditionTransactionReceipt.transactionHash)
298+
299+
logger.debug({ transactionHashes }, 'These transactions have no known confirmations yet')
300+
301+
const receiptToSimplified = ({ transactionHash, blockHash, blockNumber }: TransactionReceipt) => ({
302+
transactionHash,
303+
blockHash,
304+
blockNumber,
305+
})
306+
307+
const logErrors = (transactionReceipt: TransactionReceipt) => {
308+
logger.error({ transactionReceipt }, 'Error in transaction receipt')
309+
}
310+
311+
const transactionReceiptIsOk = (transactionReceipt: TransactionReceipt) => transactionReceipt.status
312+
313+
const filterAndLogErrors = asyncPipe(
314+
tap(asyncPipe(
315+
filter(complement(transactionReceiptIsOk)),
316+
map(logErrors),
317+
)),
318+
filter(transactionReceiptIsOk),
319+
)
320+
321+
const getReceipts = asyncPipe(
322+
map(ethereumRegistryContract.getTransactionReceipt),
323+
Promise.all.bind(Promise),
324+
filter(identity),
325+
filterAndLogErrors,
326+
map(receiptToSimplified),
327+
) as (transactionHashes: ReadonlyArray<string>) => Promise<ReadonlyArray<Partial<TransactionReceipt>>>
328+
329+
const receipts = await getReceipts(transactionHashes)
330+
331+
if (!receipts.length) {
332+
logger.trace({ transactionHashes }, 'No transactions receipts available yet for these transactions')
333+
return
334+
}
335+
336+
logger.debug({ receipts }, 'Got these transaction receipts')
337+
338+
await Promise.all(receipts.map(({ transactionHash, blockHash, blockNumber }) =>
339+
claimAnchorReceiptsCollection.updateOne(
340+
{ 'registryAdditionTransactionReceipt.transactionHash': transactionHash },
341+
{ $set: {
342+
'registryAdditionTransactionReceipt.blockHash': blockHash,
343+
'registryAdditionTransactionReceipt.blockNumber': blockNumber,
344+
} },
345+
),
346+
))
347+
348+
logger.info({ receipts }, 'Transaction receipts set')
210349
}
211350

212351
return {
@@ -218,6 +357,8 @@ export const Business = ({
218357
confirmClaimFiles,
219358
uploadNextAnchorReceipt,
220359
uploadNextClaimFileAnchorReceiptPair,
221-
registerNextDirectory,
360+
writeNextDirectoryToEthereum,
361+
setRegistryIndex,
362+
getEthereumTransactionReceipts,
222363
}
223364
}

src/EthereumRegistryWriter/EthereumRegistryWriter.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export interface EthereumRegistryWriterConfiguration extends LoggingConfiguratio
2424
readonly privateKey: string
2525
readonly uploadAnchorReceiptIntervalInSeconds: number
2626
readonly registerNextDirectoryIntervalInSeconds: number
27+
readonly gasPrice?: number
28+
readonly maximumUnconfirmedTransactionAgeInSeconds: number
2729
}
2830

2931
type stop = () => Promise<void>
@@ -47,15 +49,21 @@ export const EthereumRegistryWriter = async (configuration: EthereumRegistryWrit
4749
chainId: configuration.chainId,
4850
contractAddress: configuration.contractAddress,
4951
privateKey: configuration.privateKey,
52+
gasPrice: configuration.gasPrice,
5053
})
5154

55+
logger.info({ ethereumAccountAddress: ethereumRegistryContract.accountAddress })
56+
5257
const business = Business({
5358
dependencies: {
5459
logger,
5560
db,
5661
ipfs,
5762
ethereumRegistryContract,
5863
},
64+
configuration: {
65+
maximumUnconfirmedTransactionAgeInSeconds: configuration.maximumUnconfirmedTransactionAgeInSeconds,
66+
},
5967
})
6068
await business.createDbIndices()
6169

@@ -64,6 +72,7 @@ export const EthereumRegistryWriter = async (configuration: EthereumRegistryWrit
6472
logger,
6573
messaging,
6674
business,
75+
ethereumRegistryContract,
6776
},
6877
exchange: configuration.exchanges,
6978
})
@@ -92,6 +101,9 @@ export const EthereumRegistryWriter = async (configuration: EthereumRegistryWrit
92101
logger.debug('Closing database connection...')
93102
await mongoClient.close()
94103
logger.info('Database connection closed')
104+
logger.debug('Closing WS connection to geth...')
105+
ethereumRegistryContract.close()
106+
logger.info('WS connection to geth closed')
95107
logger.info('Stopped EthereumRegistryWriter...')
96108
}
97109

0 commit comments

Comments
 (0)