Skip to content

Commit 14a6234

Browse files
authored
Merge pull request #29 from osiastedian/ISSUE-001-proposal-timer-refactor
feat: extend proposal hash verification retries
2 parents dc005cb + fbbb84e commit 14a6234

1 file changed

Lines changed: 103 additions & 98 deletions

File tree

controllers/proposal.js

Lines changed: 103 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ const check = async (req, res, next) => {
4545
} = req.body
4646
// eslint-disable-next-line max-len
4747
if (
48-
!type
49-
|| !name
50-
|| !title
51-
|| !nPayment
52-
|| !firstEpoch
53-
|| !startEpoch
54-
|| !endEpoch
55-
|| !paymentAddress
56-
|| !paymentAmount
48+
!type ||
49+
!name ||
50+
!title ||
51+
!nPayment ||
52+
!firstEpoch ||
53+
!startEpoch ||
54+
!endEpoch ||
55+
!paymentAddress ||
56+
!paymentAmount
5757
) {
5858
return res.status(406).json({
5959
ok: false,
@@ -99,8 +99,8 @@ const check = async (req, res, next) => {
9999
}
100100
} catch (err) {
101101
if (
102-
err.message
103-
=== 'Invalid proposal data, error messages:data exceeds 512 characters;JSON parsing error;'
102+
err.message ===
103+
'Invalid proposal data, error messages:data exceeds 512 characters;JSON parsing error;'
104104
) {
105105
return res.status(400).json({
106106
ok: false,
@@ -165,15 +165,15 @@ const prepare = async (req, res, next) => {
165165
} = req.body
166166
// eslint-disable-next-line max-len
167167
if (
168-
!type
169-
|| !name
170-
|| !title
171-
|| !nPayment
172-
|| !firstEpoch
173-
|| !startEpoch
174-
|| !endEpoch
175-
|| !paymentAddress
176-
|| !paymentAmount
168+
!type ||
169+
!name ||
170+
!title ||
171+
!nPayment ||
172+
!firstEpoch ||
173+
!startEpoch ||
174+
!endEpoch ||
175+
!paymentAddress ||
176+
!paymentAmount
177177
) {
178178
return res.status(406).json({
179179
ok: false,
@@ -244,9 +244,7 @@ const prepare = async (req, res, next) => {
244244
dataHex: hexProposal,
245245
}
246246

247-
const {
248-
parentHash, revision, time, dataHex,
249-
} = prepareObjectProposal
247+
const { parentHash, revision, time, dataHex } = prepareObjectProposal
250248
const command = `gobject_prepare ${parentHash} ${revision} ${time} ${dataHex}`
251249
const proposalResp = await admin
252250
.firestore()
@@ -322,9 +320,7 @@ const prepare = async (req, res, next) => {
322320
const submit = async (req, res, next) => {
323321
try {
324322
const { id } = req.params
325-
const {
326-
parentHash, revision, time, dataHex, txId,
327-
} = req.body
323+
const { parentHash, revision, time, dataHex, txId } = req.body
328324
if (!parentHash || !revision || !time || !dataHex || !txId) {
329325
return res.status(406).json({
330326
ok: false,
@@ -355,7 +351,8 @@ const submit = async (req, res, next) => {
355351
})
356352
}
357353

358-
const commandSubmit = `gobject_submit ${parentHash} ${revision} ${time} ${dataHex} ${txId}`.trim()
354+
const commandSubmit =
355+
`gobject_submit ${parentHash} ${revision} ${time} ${dataHex} ${txId}`.trim()
359356

360357
const verifyHex = await clientRPC
361358
.callRpc('gobject_check', [dataHex])
@@ -413,18 +410,17 @@ const submit = async (req, res, next) => {
413410
// eslint-disable-next-line consistent-return
414411
const vote = async (req, res, next) => {
415412
try {
416-
const {
417-
txHash, txIndex, governanceHash, signal, vote, time, signature,
418-
} = req.body
413+
const { txHash, txIndex, governanceHash, signal, vote, time, signature } =
414+
req.body
419415

420416
if (
421-
!txHash
422-
|| !txIndex
423-
|| !governanceHash
424-
|| !signal
425-
|| !vote
426-
|| !time
427-
|| !signature
417+
!txHash ||
418+
!txIndex ||
419+
!governanceHash ||
420+
!signal ||
421+
!vote ||
422+
!time ||
423+
!signature
428424
) {
429425
return res.status(406).json({
430426
ok: false,
@@ -463,7 +459,7 @@ const vote = async (req, res, next) => {
463459
signal,
464460
vote,
465461
time,
466-
signature,
462+
signature
467463
)
468464
.call(true)
469465
.then(({ data }) => {
@@ -475,17 +471,12 @@ const vote = async (req, res, next) => {
475471
})
476472
return res.status(200).json(voteRaw)
477473
} catch (err) {
478-
if (
479-
err.message.trim() === 'Failure to find masternode in list'
480-
) {
474+
if (err.message.trim() === 'Failure to find masternode in list') {
481475
return res
482476
.status(400)
483477
.json({ ok: false, message: 'Failure to find masterNode in list' })
484478
}
485-
if (
486-
err.message.trim()
487-
=== 'GOVERNANCE_EXCEPTION_TEMPORARY_ERROR'
488-
) {
479+
if (err.message.trim() === 'GOVERNANCE_EXCEPTION_TEMPORARY_ERROR') {
489480
return res.status(400).json({
490481
ok: false,
491482
// eslint-disable-next-line max-len
@@ -498,10 +489,7 @@ const vote = async (req, res, next) => {
498489
.status(400)
499490
.json({ ok: false, message: 'Invalid proposal hash. Please check' })
500491
}
501-
if (
502-
err.message.trim()
503-
=== 'mn tx hash must be hexadecimal string'
504-
) {
492+
if (err.message.trim() === 'mn tx hash must be hexadecimal string') {
505493
return res
506494
.status(400)
507495
.json({ ok: false, message: 'Invalid txId. Please check' })
@@ -511,9 +499,7 @@ const vote = async (req, res, next) => {
511499
.status(400)
512500
.json({ ok: false, message: 'The vote cannot be verified' })
513501
}
514-
if (
515-
/mn tx hash must be of length/.test(err.message)
516-
) {
502+
if (/mn tx hash must be of length/.test(err.message)) {
517503
return res
518504
.status(400)
519505
.json({ ok: false, message: 'Invalid txId. Please check' })
@@ -552,34 +538,36 @@ const getProposalsPendingByUser = async (req, res, next) => {
552538
// eslint-disable-next-line no-underscore-dangle,max-len
553539
Promise.all(
554540
user._fieldsProto.proposalList.arrayValue.values.map(
555-
async (proposal) => new Promise((resolve, reject) => {
556-
admin
557-
.firestore()
558-
.collection(process.env.COLLECTION_NAME_PROPOSAL)
559-
.doc(proposal.stringValue)
560-
.get()
561-
.then((resp) => {
562-
resolve(resp)
563-
})
564-
.catch((err) => {
565-
reject(err)
566-
})
567-
}),
568-
),
541+
async (proposal) =>
542+
new Promise((resolve, reject) => {
543+
admin
544+
.firestore()
545+
.collection(process.env.COLLECTION_NAME_PROPOSAL)
546+
.doc(proposal.stringValue)
547+
.get()
548+
.then((resp) => {
549+
resolve(resp)
550+
})
551+
.catch((err) => {
552+
reject(err)
553+
})
554+
})
555+
)
569556
)
570557
.then((elements) => {
571558
// eslint-disable-next-line no-underscore-dangle
572559
const proposalNoComplete = elements.filter(
573-
(elem) => elem._fieldsProto.complete.booleanValue === false,
560+
(elem) => elem._fieldsProto.complete.booleanValue === false
574561
)
575562
// eslint-disable-next-line prefer-spread,no-underscore-dangle
576563
const proposalReciente = Math.max.apply(
577564
Math,
578-
proposalNoComplete.map((o) => o._createTime.nanoseconds),
565+
proposalNoComplete.map((o) => o._createTime.nanoseconds)
579566
)
580567
// eslint-disable-next-line no-underscore-dangle,max-len
581568
const currentNoCompleteProposal = proposalNoComplete.find(
582-
(proposal) => proposal._createTime.nanoseconds === proposalReciente,
569+
(proposal) =>
570+
proposal._createTime.nanoseconds === proposalReciente
583571
)
584572

585573
if (typeof currentNoCompleteProposal === 'undefined') {
@@ -603,18 +591,20 @@ const getProposalsPendingByUser = async (req, res, next) => {
603591
) {
604592
// eslint-disable-next-line no-underscore-dangle
605593
proposalData[key] = Number(
606-
currentNoCompleteProposal._fieldsProto[key].integerValue,
594+
currentNoCompleteProposal._fieldsProto[key].integerValue
607595
)
608596
// eslint-disable-next-line no-underscore-dangle
609597
} else if (
610598
typeof currentNoCompleteProposal._fieldsProto[key]
611599
.booleanValue !== 'undefined'
612600
) {
613601
// eslint-disable-next-line no-underscore-dangle
614-
proposalData[key] = currentNoCompleteProposal._fieldsProto[key].booleanValue
602+
proposalData[key] =
603+
currentNoCompleteProposal._fieldsProto[key].booleanValue
615604
} else {
616605
// eslint-disable-next-line no-underscore-dangle
617-
proposalData[key] = currentNoCompleteProposal._fieldsProto[key].stringValue
606+
proposalData[key] =
607+
currentNoCompleteProposal._fieldsProto[key].stringValue
618608
}
619609
}
620610
}
@@ -680,13 +670,14 @@ const getOneProposal = async (req, res, next) => {
680670
}
681671
// eslint-disable-next-line no-underscore-dangle
682672
if (
683-
user._fieldsProto.proposalList
684-
&& user._fieldsProto.proposalList.arrayValue.values.length > 0
673+
user._fieldsProto.proposalList &&
674+
user._fieldsProto.proposalList.arrayValue.values.length > 0
685675
) {
686676
// eslint-disable-next-line max-len,no-underscore-dangle
687-
existProposalsInUser = user._fieldsProto.proposalList.arrayValue.values.find(
688-
(element) => element.stringValue === id,
689-
)
677+
existProposalsInUser =
678+
user._fieldsProto.proposalList.arrayValue.values.find(
679+
(element) => element.stringValue === id
680+
)
690681
if (typeof existProposalsInUser !== 'undefined') {
691682
const { _fieldsProto } = await admin
692683
.firestore()
@@ -862,7 +853,8 @@ const getAllHiddenProposal = async (req, res, next) => {
862853
const createHiddenProposal = async (req, res, next) => {
863854
try {
864855
const { hash } = req.body
865-
if (!hash) return res.status(406).json({ ok: false, message: 'required fields' })
856+
if (!hash)
857+
return res.status(406).json({ ok: false, message: 'required fields' })
866858
if (hash.length !== 64) {
867859
return res
868860
.status(406)
@@ -892,12 +884,10 @@ const createHiddenProposal = async (req, res, next) => {
892884
const existingHash = Object.keys(gobjectData).find((e) => e === hash)
893885

894886
if (typeof existingHash === 'undefined') {
895-
return res
896-
.status(406)
897-
.json({
898-
ok: false,
899-
message: 'the proposal does not exist in the governance list',
900-
})
887+
return res.status(406).json({
888+
ok: false,
889+
message: 'the proposal does not exist in the governance list',
890+
})
901891
}
902892
await admin
903893
.firestore()
@@ -1011,11 +1001,12 @@ const updateProposal = async (req, res, next) => {
10111001
message: 'you do not have permissions to perform this action',
10121002
})
10131003
}
1014-
if (!data) return res.status(406).json({ ok: false, message: 'Required fields' })
1004+
if (!data)
1005+
return res.status(406).json({ ok: false, message: 'Required fields' })
10151006

10161007
// eslint-disable-next-line prefer-const,max-len,no-underscore-dangle
10171008
existProposalInUser = user._fieldsProto.proposalList.arrayValue.values.find(
1018-
(element) => element.stringValue === id,
1009+
(element) => element.stringValue === id
10191010
)
10201011

10211012
if (typeof existProposalInUser === 'undefined') {
@@ -1033,10 +1024,14 @@ const updateProposal = async (req, res, next) => {
10331024
// If hash is present and complete is true, call gobject_get with retry logic
10341025
if (data.hash && data.complete === true) {
10351026
const { hash } = data
1036-
const maxRetryCount = typeof data.maxRetryCount === 'number' ? data.maxRetryCount : 10
1027+
const maxRetryCount =
1028+
typeof data.maxRetryCount === 'number' ? data.maxRetryCount : 30
10371029
let attempt = 0
10381030
let rpcSuccess = false
10391031
let rpcResult
1032+
const retryDelayMs = 10_000
1033+
const maxDurationMs = 10 * 60 * 1000
1034+
const startTime = Date.now()
10401035
while (attempt < maxRetryCount && !rpcSuccess) {
10411036
try {
10421037
// eslint-disable-next-line no-await-in-loop
@@ -1045,6 +1040,13 @@ const updateProposal = async (req, res, next) => {
10451040
rpcSuccess = true
10461041
} catch (rpcErr) {
10471042
attempt += 1
1043+
const elapsed = Date.now() - startTime
1044+
if (elapsed >= maxDurationMs) {
1045+
return res.status(408).json({
1046+
ok: false,
1047+
message: 'Proposal submission failed. Please try again in 5mins.',
1048+
})
1049+
}
10481050
if (attempt >= maxRetryCount) {
10491051
return res.status(500).json({
10501052
ok: false,
@@ -1053,7 +1055,7 @@ const updateProposal = async (req, res, next) => {
10531055
}
10541056
// Wait 10 seconds before retrying
10551057
// eslint-disable-next-line no-await-in-loop
1056-
await new Promise((resolve) => setTimeout(resolve, 10000))
1058+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs))
10571059
}
10581060
}
10591061
// Optionally, you can do something with rpcResult if needed
@@ -1102,14 +1104,16 @@ const updateProposal = async (req, res, next) => {
11021104
].valueType === 'integerValue'
11031105
) {
11041106
// eslint-disable-next-line no-underscore-dangle,max-len
1105-
prepareObjectProposal[prepareKey] = proposal._fieldsProto.prepareObjectProposal.mapValue.fields[
1106-
prepareKey
1107-
].integerValue
1107+
prepareObjectProposal[prepareKey] =
1108+
proposal._fieldsProto.prepareObjectProposal.mapValue.fields[
1109+
prepareKey
1110+
].integerValue
11081111
} else {
11091112
// eslint-disable-next-line no-underscore-dangle,max-len
1110-
prepareObjectProposal[prepareKey] = proposal._fieldsProto.prepareObjectProposal.mapValue.fields[
1111-
prepareKey
1112-
].stringValue
1113+
prepareObjectProposal[prepareKey] =
1114+
proposal._fieldsProto.prepareObjectProposal.mapValue.fields[
1115+
prepareKey
1116+
].stringValue
11131117
}
11141118
}
11151119
}
@@ -1162,16 +1166,17 @@ const deleteProposal = async (req, res, next) => {
11621166
// eslint-disable-next-line no-underscore-dangle
11631167
if (user._fieldsProto.proposalList.arrayValue.values.length > 0) {
11641168
// eslint-disable-next-line no-underscore-dangle,max-len
1165-
existProposalInUser = user._fieldsProto.proposalList.arrayValue.values.find(
1166-
(element) => element.stringValue === id,
1167-
)
1169+
existProposalInUser =
1170+
user._fieldsProto.proposalList.arrayValue.values.find(
1171+
(element) => element.stringValue === id
1172+
)
11681173
if (typeof existProposalInUser !== 'undefined') {
11691174
// eslint-disable-next-line no-underscore-dangle,array-callback-return
11701175
user._fieldsProto.proposalList.arrayValue.values.map((proposal) => {
11711176
proposals.push(proposal.stringValue)
11721177
})
11731178
const index = proposals.findIndex(
1174-
(element) => element === existProposalInUser.stringValue,
1179+
(element) => element === existProposalInUser.stringValue
11751180
)
11761181
proposals.splice(index, 1)
11771182
await admin

0 commit comments

Comments
 (0)