import mongoose from "mongoose" ; import { AccountingSchema , CandidateSchema , DelayedTxSchema , EraSchema , NominatorSchema , NominationSchema , ChainMetadataSchema , BotClaimEventSchema , EraPointsSchema , TotalEraPointsSchema , EraStatsSchema , LocationStatsSchema , ValidatorScoreSchema , ValidatorScoreMetadataSchema , ReleaseSchema , ElectionStatsSchema , CouncillorSchema , EraPaidEventSchema , EraRewardSchema , ReferendumSchema , ReferendumVoteSchema , } from "./models" ; import logger from "../logger" ; import { formatAddress } from "../util" ; import { Keyring } from "@polkadot/keyring" ; import { Referendum , ReferendumVote } from "../types" ; export type NodeDetails = [ string , string , string , string , string ] ; export default class Db { private accountingModel ; private candidateModel ; private delayedTxModel ; private eraModel ; private eraPointsModel ; private totalEraPointsModel ; private nominatorModel ; private nominationModel ; private chainMetadataModel ; private botClaimEventModel ; private eraStatsModel ; private locationStatsModel ; private validatorScoreModel ; private validatorScoreMetadataModel ; private releaseModel ; private electionStatsModel ; private councillorModel ; private eraPaidEventModel ; private eraRewardModel ; private referendumModel ; private referendumVoteModel ; constructor ( ) { this . accountingModel = mongoose . model ( "Accounting" , AccountingSchema ) ; this . candidateModel = mongoose . model ( "Candidate" , CandidateSchema ) ; this . delayedTxModel = mongoose . model ( "DelayedTx" , DelayedTxSchema ) ; this . eraModel = mongoose . model ( "Era" , EraSchema ) ; this . eraPointsModel = mongoose . model ( "EraPoints" , EraPointsSchema ) ; this . totalEraPointsModel = mongoose . model ( "TotalEraPoints" , TotalEraPointsSchema ) ; this . nominatorModel = mongoose . model ( "Nominator" , NominatorSchema ) ; this . nominationModel = mongoose . model ( "Nomination" , NominationSchema ) ; this . chainMetadataModel = mongoose . model ( "ChainMetadata" , ChainMetadataSchema ) ; this . botClaimEventModel = mongoose . model ( "BotClaimEvent" , BotClaimEventSchema ) ; this . eraStatsModel = mongoose . model ( "EraStatsModel" , EraStatsSchema ) ; this . locationStatsModel = mongoose . model ( "LocationStatsModel" , LocationStatsSchema ) ; this . validatorScoreModel = mongoose . model ( "ValidatorScore" , ValidatorScoreSchema ) ; this . validatorScoreMetadataModel = mongoose . model ( "ValidatorScoreMetadata" , ValidatorScoreMetadataSchema ) ; this . releaseModel = mongoose . model ( "Release" , ReleaseSchema ) ; this . electionStatsModel = mongoose . model ( "ElectionStats" , ElectionStatsSchema ) ; this . councillorModel = mongoose . model ( "Councillor" , CouncillorSchema ) ; this . eraPaidEventModel = mongoose . model ( "EraPaid" , EraPaidEventSchema ) ; this . eraRewardModel = mongoose . model ( "EraReward" , EraRewardSchema ) ; this . referendumModel = mongoose . model ( "Referendum" , ReferendumSchema ) ; this . referendumVoteModel = mongoose . model ( "ReferendumVote" , ReferendumVoteSchema ) ; } static async create ( uri = "mongodb://localhost:27017/otv" ) { mongoose . connect ( uri , { } ) ; return new Promise ( ( resolve , reject ) => { mongoose . connection . once ( "open" , async ( ) => { logger . info ( `Established a connection to MongoDB.` ) ; const db = new Db ( ) ; if ( ! ( await db . getLastNominatedEraIndex ( ) ) ) { await db . setLastNominatedEraIndex ( 0 ) ; } resolve ( db ) ; } ) ; mongoose . connection . on ( "error" , ( err ) => { logger . error ( `MongoDB connection issue: ${ err }` ) ; reject ( err ) ; } ) ; } ) ; } async addDelayedTx ( number , controller , targets , callHash ) { const delayedTx = new this . delayedTxModel ( { number , controller , targets , callHash , } ) ; return delayedTx . save ( ) ; } async getAllDelayedTxs ( ) { return this . delayedTxModel . find ( { controller : /.*/ } ) . exec ( ) ; } async deleteDelayedTx ( number , controller ) { return this . delayedTxModel . deleteOne ( { number , controller } ) . exec ( ) ; } async addCandidate ( name , stash , kusamaStash , skipSelfStake , bio , bot ? ) { const network = ( await this . getChainMetadata ( ) ) . name ; const keyring = new Keyring ( ) ; const ss58Prefix = network == "Kusama" ? 2 : 0 ; stash = keyring . encodeAddress ( stash , ss58Prefix ) ; logger . info ( `(Db::addCandidate) name: ${ name } stash: ${ stash }` ) ; const data = await this . candidateModel . findOne ( { name } ) ; if ( ! data ) { logger . info ( `(Db::addCandidate) Did not find candidate data for ${ name } - inserting new document.` ) ; const candidate = new this . candidateModel ( { name , stash , kusamaStash , skipSelfStake , bio , } ) ; if ( ! ! bot ) { await bot . sendMessage ( `Adding new candidate: ${ name } (${ stash })` ) ; } return candidate . save ( ) ; } return this . candidateModel . findOneAndUpdate ( { name , } , { stash , kusamaStash , skipSelfStake , bio , } ) ; } async deleteOldCandidateFields ( ) { await this . candidateModel . collection . update ( { } , { $unset : { sentryId : 1 , sentryOnlineSince : 1 , sentryOfflineSince : 1 , telemetryId : 1 , valid : 1 , invalidityReasons : 1 , } , } , { multi : true , safe : true } ) ; return true ; } async deleteOldFieldFrom ( name ) { await this . candidateModel . findOneAndUpdate ( { name } , { $unset : { sentryId : 1 , sentryOnlineSince : 1 , sentryOfflineSince : 1 , telemetryId : 1 , } , } ) . exec ( ) ; return true ; } async clearNodeRefsFrom ( name ) { await this . candidateModel . findOneAndUpdate ( { name } , { nodeRefs : 0 } ) . exec ( ) ; return true ; } param controllers async removeStaleNominators ( controllers ) { const nominators = await this . allNominators ( ) ; const addresses = nominators . map ( ( n ) => n . address ) ; for ( const address of addresses ) { if ( controllers . indexOf ( address ) === - 1 ) { await this . nominatorModel . deleteOne ( { address } ) . exec ( ) ; } } return true ; } async setInvalidityReason ( stash , reason ) { await this . candidateModel . findOneAndUpdate ( { stash } , { invalidityReasons : reason , } ) . exec ( ) ; return true ; } async setLastValid ( stash ) { await this . candidateModel . findOneAndUpdate ( { stash } , { lastValid : Date . now ( ) } ) . exec ( ) ; return true ; } async setCommission ( stash , commission ) { await this . candidateModel . findOneAndUpdate ( { stash } , { commission : commission } ) . exec ( ) ; return true ; } async setController ( stash , controller ) { await this . candidateModel . findOneAndUpdate ( { stash } , { controller : controller } ) . exec ( ) ; return true ; } async setIdentity ( stash , identity ) { await this . candidateModel . findOneAndUpdate ( { stash } , { identity : identity } ) . exec ( ) ; return true ; } async setLocation ( telemetryId , location ) { const data = await this . candidateModel . findOne ( { telemetryId } ) ; if ( ! data || ! location ) return false ; await this . candidateModel . findOneAndUpdate ( telemetryId , { location : location , } ) . exec ( ) ; logger . info ( `Succesfully set location: ${ location } for id: ${ telemetryId }` ) ; return true ; } async reportBestBlock ( telemetryId , details , now ) { const block = details [ 0 ] ; const data = await this . candidateModel . findOne ( { telemetryId } ) ; if ( ! data ) return false ; if ( data . offlineSince && data . offlineSince !== 0 ) { const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "ONLINE" ; } ) ; const timeOffline = now - data . offlineSince ; const accumulated = ( data . offlineAccumulated || 0 ) + timeOffline ; await this . candidateModel . findOneAndUpdate ( telemetryId , { offlineSince : 0 , onlineSince : now , offlineAccumulated : accumulated , invalidity : [ ... invalidityReasons , { valid : true , type : "ONLINE" , updated : Date . now ( ) , details : `` , } , ] , } ) . exec ( ) ; } return true ; } async reportOnline ( telemetryId , details , now , location ) { const [ name , nodeImplementation , version , address , networkId ] = details ; const data = await this . candidateModel . findOne ( { name } ) ; if ( ! data ) { const candidate = new this . candidateModel ( { telemetryId , location , networkId : null , nodeRefs : 1 , name , version , discoveredAt : now , onlineSince : now , offlineSince : 0 , } ) ; return candidate . save ( ) ; } const candidateLocation = location != "No Location" ? location : data . location ; const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "ONLINE" ; } ) ; if ( ! data . discoveredAt ) { await this . candidateModel . findOneAndUpdate ( { name } , { telemetryId , location : candidateLocation , discoveredAt : now , onlineSince : now , offlineSince : 0 , invalidity : [ ... invalidityReasons , { valid : true , type : "ONLINE" , updated : Date . now ( ) , details : `` , } , ] , } ) . exec ( ) ; } await this . candidateModel . findOneAndUpdate ( { name } , { telemetryId , location : candidateLocation , onlineSince : now , version , invalidity : [ ... invalidityReasons , { valid : true , type : "ONLINE" , updated : Date . now ( ) , details : `` , } , ] , $inc : { nodeRefs : 1 } , } ) . exec ( ) ; if ( data . offlineSince && data . offlineSince !== 0 ) { logger . info ( `Online node ${ data . name } was offline since: ${ data . offlineSince }` ) ; const timeOffline = now - data . offlineSince ; const accumulated = ( data . offlineAccumulated || 0 ) + timeOffline ; return this . candidateModel . findOneAndUpdate ( { name , } , { offlineSince : 0 , offlineAccumulated : accumulated , } ) . exec ( ) ; } } param telemetryId param name param now async reportOffline ( telemetryId , name , now ) { logger . info ( `(Db::reportOffline) Reporting ${ name } with telemetry id ${ telemetryId } offline at ${ now }.` ) ; const data = await this . candidateModel . findOne ( { telemetryId } ) ; if ( ! data ) { logger . info ( `(Db::reportOffline) No data for node named ${ name }.` ) ; return false ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "ONLINE" ; } ) ; if ( data . nodeRefs > 1 ) { return this . candidateModel . findOneAndUpdate ( { telemetryId } , { $inc : { nodeRefs : - 1 , } , invalidity : [ ... invalidityReasons , { valid : false , type : "ONLINE" , updated : Date . now ( ) , details : `${ data . name } offline. Offline since ${ data . offlineSince }.` , } , ] , } ) ; } return this . candidateModel . findOneAndUpdate ( { telemetryId , } , { offlineSince : now , onlineSince : 0 , $inc : { nodeRefs : - 1 , } , invalidity : [ ... invalidityReasons , { valid : false , type : "ONLINE" , updated : Date . now ( ) , details : `${ data . name } offline. Offline since ${ data . offlineSince }.` , } , ] , } ) ; } async reportUpdated ( name ) { await this . candidateModel . findOneAndUpdate ( { name , } , { updated : true , } ) . exec ( ) ; return true ; } async reportNotUpdated ( name ) { await this . candidateModel . findOneAndUpdate ( { name , } , { updated : false , } ) . exec ( ) ; return true ; } /** Nominator accessor functions */ async addNominator ( address , stash , proxy , bonded , now , proxyDelay , rewardDestination , avgStake , nominateAmount , newBondedAmount ) { logger . info ( `(Db::addNominator) Adding ${ address } at ${ now }.` ) ; const data = await this . nominatorModel . findOne ( { address } ) ; if ( ! data ) { const nominator = new this . nominatorModel ( { address , stash , proxy , bonded , proxyDelay , rewardDestination , avgStake , nominateAmount , newBondedAmount , current : [ ] , lastNomination : 0 , createdAt : now , } ) ; return nominator . save ( ) ; } return this . nominatorModel . findOneAndUpdate ( { address , } , { createdAt : now , stash , proxy , bonded , proxyDelay , rewardDestination , avgStake , nominateAmount , newBondedAmount , } ) ; } async setNominatorAvgStake ( address , avgStake ) { const data = await this . nominatorModel . findOne ( { address } ) ; if ( ! data ) return ; return this . nominatorModel . findOneAndUpdate ( { address , } , { avgStake , } ) ; } async pushRankEvent ( stash , startEra , activeEra ) { const record = await this . candidateModel . findOne ( { stash } ) ; if ( ! record ) { return false ; } let alreadyRank = false ; for ( const rank of record . rankEvents ) { if ( rank . startEra == startEra ) { alreadyRank = true ; } } if ( alreadyRank ) return false ; await this . candidateModel . findOneAndUpdate ( { stash , } , { $push : { rankEvents : { when : Date . now ( ) , startEra , activeEra , } , } , } ) ; return true ; } async pushFaultEvent ( stash , reason ) { logger . info ( `(Db::pushFault) Adding new fault for ${ stash } for reason ${ reason }` ) ; const record = await this . candidateModel . findOne ( { stash } ) ; if ( ! record ) { return false ; } let alreadyFault = false ; for ( const fault of record . faultEvents ) { if ( fault . reason == reason ) { alreadyFault = true ; } } if ( alreadyFault ) return true ; await this . candidateModel . findOneAndUpdate ( { stash , } , { $push : { faultEvents : { when : Date . now ( ) , reason : reason , prevRank : record . rank , } , } , } ) ; return false ; } param stash param controller async newAccountingRecord ( stash , controller ) { logger . info ( `(Db::newAccountingRecord) Adding stash ${ stash } and controller ${ controller }` ) ; const record = await this . accountingModel . findOne ( { stash , controller } ) ; if ( ! record ) { const accounting = new this . accountingModel ( { stash , controller , total : "0" , records : [ ] , } ) ; return accounting . save ( ) ; } return true ; } async updateAccountingRecord ( controller , stash , era , reward ) { logger . info ( `(Db::updateAccountingRecord) Adding era ${ era } and reward ${ reward }` ) ; const record = await this . accountingModel . findOne ( { stash , controller } ) ; if ( ! record ) { return false ; } await this . accountingModel . findOneAndUpdate ( { stash , controller , } , { $push : { records : { era , reward } } , } ) . exec ( ) ; return true ; } async getAccounting ( controllerOrStash ) { const stashResult = await this . accountingModel . findOne ( { stash : controllerOrStash , } ) . exec ( ) ; if ( stashResult ) { return stashResult ; } const controllerResult = await this . accountingModel . findOne ( { controller : controllerOrStash , } ) . exec ( ) ; if ( controllerResult ) { return controllerResult ; } return null ; } async setTarget ( address , target , now ) { logger . info ( `(Db::setTarget) Setting ${ address } with new target ${ target }.` ) ; await this . candidateModel . findOneAndUpdate ( { stash : target , } , { nominatedAt : now , } ) . exec ( ) ; const candidate = await this . getCandidate ( target ) ; const currentCandidate = { name : candidate . name , stash : candidate . stash , identity : candidate . identity , } ; await this . nominatorModel . findOneAndUpdate ( { address , } , { $push : { current : currentCandidate } , } ) . exec ( ) ; return true ; } async clearCurrent ( address ) { logger . info ( `(Db::clearCurrent) Clearing current for ${ address }.` ) ; await this . nominatorModel . findOneAndUpdate ( { address , } , { current : [ ] , } ) . exec ( ) ; return true ; } async setClaimDelta ( stash , blockDelta , timestampDelta ) { logger . info ( `(Db::setClaimDelta) Setting claim delta timestamp: ${ timestampDelta }, blocks: ${ blockDelta }` ) ; await this . candidateModel . findOneAndUpdate ( { stash , } , { avgClaimTimestampDelta : timestampDelta , avgClaimBlockDelta : blockDelta , } ) . exec ( ) ; return true ; } async setTotalRewards ( stash , totalRewards ) { logger . info ( `(Db::setTotalRewards) Setting ${ totalRewards }` ) ; await this . candidateModel . findOneAndUpdate ( { stash , } , { totalRewards : totalRewards , } ) . exec ( ) ; return true ; } async setUnclaimedEras ( stash , unclaimedEras ) { logger . info ( `(Db::setUnclaimedEras) Setting unclaimed eras for ${ stash } to the following validators: ${ unclaimedEras }` ) ; await this . candidateModel . findOneAndUpdate ( { stash , } , { unclaimedEras : unclaimedEras , } ) . exec ( ) ; return true ; } async setNomination ( address , era , targets , bonded , blockHash ) { logger . info ( `(Db::setNomination) Setting nomination for ${ address } bonded with ${ bonded } for era ${ era } to the following validators: ${ targets }` ) ; const data = await this . nominationModel . findOne ( { address : address , era : era , } ) ; if ( ! ! data && data . blockHash ) return ; if ( ! data ) { const nomination = new this . nominationModel ( { address : address , era : era , validators : targets , timestamp : Date . now ( ) , bonded : bonded , blockHash : blockHash , } ) ; return nomination . save ( ) ; } this . nominationModel . findOneAndUpdate ( { address : address , era : era , validators : targets , timestamp : Date . now ( ) , bonded : bonded , blockHash : blockHash , } ) . exec ( ) ; } async getNomination ( address , era ) { const data = await this . nominationModel . findOne ( { address : address , era : era , } ) ; return data ; } async getLastNominations ( address , eras ) { const data = await this . nominationModel . find ( { address } ) . sort ( "-era" ) . limit ( Number ( eras ) ) ; return data ; } async setBotClaimEvent ( address , era , blockHash ) { logger . info ( `(Db::setBotClaimEvent) Setting bot claim event for ${ address } for era ${ era } with blockhash: ${ blockHash }.` ) ; const data = await this . botClaimEventModel . findOne ( { address : address , era : era , } ) ; if ( ! ! data && data . blockHash ) return ; if ( ! data ) { const botClaimEvent = new this . botClaimEventModel ( { address : address , era : era , timestamp : Date . now ( ) , blockHash : blockHash , } ) ; return botClaimEvent . save ( ) ; } } async setLastNomination ( address , now ) { logger . info ( `(Db::setLastNomination) Setting ${ address } last nomination to ${ now }.` ) ; return this . nominatorModel . findOneAndUpdate ( { address , } , { $set : { lastNomination : now } , } ) . exec ( ) ; } async setLastNominatedEraIndex ( index ) { const data = await this . eraModel . findOne ( { lastNominatedEraIndex : /.*/ } ) ; if ( ! data ) { const eraIndex = new this . eraModel ( { lastNominatedEraIndex : index . toString ( ) , when : Date . now ( ) , } ) ; return eraIndex . save ( ) ; } return this . eraModel . findOneAndUpdate ( { lastNominatedEraIndex : /.*/ } , { $set : { lastNominatedEraIndex : index . toString ( ) , when : Date . now ( ) , nextNomination : Date . now ( ) + 86400000 , } , } ) . exec ( ) ; } async getCurrentTargets ( address ) { return ( await this . nominatorModel . findOne ( { address } ) ) . current ; } /** Adding and removing points */ async addPoint ( stash ) { logger . info ( `Adding a point to ${ stash }.` ) ; const data = await this . candidateModel . findOne ( { stash } ) ; await this . candidateModel . findOneAndUpdate ( { stash , } , { rank : data . rank + 1 , } ) . exec ( ) ; return true ; } async dockPoints ( stash ) { logger . info ( `Docking points for ${ stash }.` ) ; const data = await this . candidateModel . findOne ( { stash } ) ; await this . candidateModel . findOneAndUpdate ( { stash , } , { rank : data . rank - Math . floor ( data . rank / 6 ) , faults : data . faults + 1 , } ) . exec ( ) ; return true ; } async dockPointsUnclaimedReward ( stash ) { logger . info ( `Docking points for ${ stash }.` ) ; const data = await this . candidateModel . findOne ( { stash } ) ; await this . candidateModel . findOneAndUpdate ( { stash , } , { rank : data . rank - 3 , } ) . exec ( ) ; return true ; } async forgiveDockedPoints ( stash ) { logger . info ( `Forgiving docked points for ${ stash }` ) ; const data = await this . candidateModel . findOne ( { stash } ) ; await this . candidateModel . findOneAndUpdate ( { stash , } , { rank : data . rank * 2 + 1 , faults : data . faults - 1 , } ) . exec ( ) ; return true ; } /** Storage GETTERS and SETTERS */ async clearAccumulated ( ) { logger . info ( `(Db::clearAccumulated) Clearing offline accumulated time.` ) ; const candidates = await this . allCandidates ( ) ; if ( ! candidates . length ) { return true ; } for ( const candidate of candidates ) { const { name , offlineAccumulated } = candidate ; if ( offlineAccumulated > 0 ) { await this . candidateModel . findOneAndUpdate ( { name , } , { offlineAccumulated : 0 , } ) ; } } return true ; } async clearCandidates ( ) { logger . info ( `(Db::clearCandidates) Clearing stale candidate data.` ) ; const candidates = await this . allCandidates ( ) ; if ( ! candidates . length ) { return true ; } for ( const candidate of candidates ) { const { name } = candidate ; await this . candidateModel . findOneAndUpdate ( { name , } , { stash : null , offlineSince : 0 , offlineAccumulated : 0 , } ) . exec ( ) ; } return true ; } async allCandidates ( ) { return this . candidateModel . find ( { stash : /.*/ } ) . exec ( ) ; } async allNodes ( ) { return this . candidateModel . find ( { name : /.*/ } ) . exec ( ) ; } async allNominators ( ) { return this . nominatorModel . find ( { address : /.*/ } ) . exec ( ) ; } async allNominations ( ) { return this . nominationModel . find ( { address : /.*/ } ) . exec ( ) ; } param stashOrName async getCandidate ( stashOrName ) { let data = await this . candidateModel . findOne ( { stash : stashOrName } ) . exec ( ) ; if ( ! data ) { data = await this . candidateModel . findOne ( { name : stashOrName } ) . exec ( ) ; } return data ; } async getNodeByName ( name ) { return this . candidateModel . findOne ( { name } ) . exec ( ) ; } async getNominator ( stash ) { return this . nominatorModel . findOne ( { stash : stash } ) . exec ( ) ; } async getLastNominatedEraIndex ( ) { return this . eraModel . findOne ( { lastNominatedEraIndex : /[0-9]+/ } ) . exec ( ) ; } async setChainMetadata ( networkPrefix , handler ) { const networkName = networkPrefix == 2 ? "Kusama" : networkPrefix == 0 ? "Polkadot" : "Local Testnet" ; const decimals = networkPrefix == 2 ? 12 : networkPrefix == 0 ? 10 : 12 ; logger . info ( `(Db::setChainMetadata) Setting chain metadata: ${ networkName } with ${ decimals } decimals` ) ; const data = await this . chainMetadataModel . findOne ( { name : /.*/ } ) ; if ( ! data ) { const chainMetadata = new this . chainMetadataModel ( { name : networkName , decimals : decimals , } ) ; return chainMetadata . save ( ) ; } this . chainMetadataModel . findOneAndUpdate ( { name : networkName , decimals : decimals , } ) ; } async getChainMetadata ( ) { return this . chainMetadataModel . findOne ( { name : /.*/ } ) . exec ( ) ; } async getBotClaimEvents ( ) { return this . botClaimEventModel . find ( { address : /.*/ } ) . exec ( ) ; } async setEraPoints ( era , points , address ) { const data = await this . eraPointsModel . findOne ( { address : address , era : era , } ) ; if ( ! ! data && data . eraPoints == points ) return ; if ( ! data ) { const eraPoints = new this . eraPointsModel ( { address : address , era : era , eraPoints : points , } ) ; return eraPoints . save ( ) ; } this . eraPointsModel . findOneAndUpdate ( { address : address , era : era , } , { eraPoints : points , } ) . exec ( ) ; } async getEraPoints ( era , address ) { return await this . eraPointsModel . findOne ( { address : address , era : era , } ) ; } async setTotalEraPoints ( era , total , validators ) { for ( const validator of validators ) { await this . setEraPoints ( era , validator . eraPoints , validator . address ) ; } const data = await this . totalEraPointsModel . findOne ( { era : era , } ) ; if ( ! ! data && data . total == total && data . median ) return ; const points = [ ] ; for ( const v of validators ) { points . push ( v . eraPoints ) ; } const getAverage = ( list ) => list . reduce ( ( prev , curr ) => prev + curr ) / list . length ; const getMedian = ( array ) => { if ( array . length >= 1 ) { array = array . sort ( ( a , b ) => { return a - b ; } ) ; if ( array . length % 2 === 0 ) { return ( array [ array . length / 2 - 1 ] + array [ array . length / 2 ] ) / 2 ; } else { return array [ ( array . length - 1 ) / 2 ] ; } } else { console . error ( "Error: Empty Array (calculateMedian)" ) ; } } ; const max = Math . max ( ... points ) ; const min = Math . min ( ... points ) ; const avg = getAverage ( points ) ; const median = getMedian ( points ) ; if ( ! data ) { const totalEraPoints = new this . totalEraPointsModel ( { era : era , totalEraPoints : total , validatorsEraPoints : validators , median : median , average : avg , max : max , min : min , } ) ; return totalEraPoints . save ( ) ; } this . totalEraPointsModel . findOneAndUpdate ( { era : era , } , { totalEraPoints : total , validatorsEraPoints : validators , median : median , average : avg , max : max , min : min , } ) . exec ( ) ; } async getTotalEraPoints ( era ) { return await this . totalEraPointsModel . findOne ( { era : era , } ) ; } async getLastTotalEraPoints ( ) { return await this . totalEraPointsModel . find ( { } ) . sort ( "-era" ) . limit ( 1 ) ; } async getSpanEraPoints ( address , currentEra ) { return await this . eraPointsModel . find ( { address : address , era : { $gte : currentEra - 27 } } ) . exec ( ) ; } async getHistoryDepthEraPoints ( address , currentEra ) { return await this . eraPointsModel . find ( { address : address , era : { $gte : currentEra - 83 } } ) . exec ( ) ; } async getHistoryDepthTotalEraPoints ( currentEra ) { return await this . totalEraPointsModel . find ( { era : { $gte : currentEra - 83 } } ) . exec ( ) ; } async setInclusion ( address , inclusion ) { logger . debug ( `(Db::setInclusion) Setting ${ address } inclusion to ${ inclusion }.` ) ; return this . candidateModel . findOneAndUpdate ( { stash : address , } , { $set : { inclusion : inclusion } , } ) . exec ( ) ; } async setSpanInclusion ( address , spanInclusion ) { logger . debug ( `(Db::setInclusion) Setting ${ address } span inclusion to ${ spanInclusion }.` ) ; return this . candidateModel . findOneAndUpdate ( { stash : address , } , { $set : { spanInclusion : spanInclusion } , } ) . exec ( ) ; } async setBonded ( address , bonded ) { logger . debug ( `(Db::setBonded) Setting ${ address } bonded to ${ bonded }.` ) ; return this . candidateModel . findOneAndUpdate ( { stash : address , } , { $set : { bonded : bonded } , } ) . exec ( ) ; } async setRewardDestination ( address , rewardDestination ) { logger . debug ( `(Db::setRewardDestination) Setting ${ address } reward destination to ${ rewardDestination }.` ) ; return this . candidateModel . findOneAndUpdate ( { stash : address , } , { $set : { rewardDestination : rewardDestination } , } ) . exec ( ) ; } async setQueuedKeys ( address , queuedKeys ) { logger . debug ( `(Db::setQueuedKeys) Setting ${ address } queued keys to ${ queuedKeys }.` ) ; return this . candidateModel . findOneAndUpdate ( { stash : address , } , { $set : { queuedKeys : queuedKeys } , } ) . exec ( ) ; } async setNextKeys ( address , nextKeys ) { logger . debug ( `(Db::setNextKeys) Setting ${ address } next keys to ${ nextKeys }.` ) ; return this . candidateModel . findOneAndUpdate ( { stash : address , } , { $set : { nextKeys : nextKeys } , } ) . exec ( ) ; } async setActive ( address , active ) { return this . candidateModel . findOneAndUpdate ( { stash : address , } , { $set : { active : active } , } ) . exec ( ) ; } async setCouncilBacking ( address , councilStake , councilVotes ) { return this . candidateModel . findOneAndUpdate ( { stash : address , } , { $set : { councilStake : councilStake , councilVotes : councilVotes } , } ) . exec ( ) ; } async setEraStats ( era , totalNodes , valid , active ) { const data = await this . eraStatsModel . findOne ( { era : era , } ) ; if ( ! ! data && data . totalNodes == totalNodes && data . valid == valid && data . active == active ) return ; if ( ! data ) { const eraStats = new this . eraStatsModel ( { era : era , when : Date . now ( ) , totalNodes : totalNodes , valid : valid , active : active , } ) ; return eraStats . save ( ) ; } this . eraStatsModel . findOneAndUpdate ( { era : era , } , { when : Date . now ( ) , totalNodes : totalNodes , valid : valid , active : active , } ) . exec ( ) ; } async getLatestEraStats ( ) { return await this . eraStatsModel . find ( { } ) . sort ( "-era" ) . limit ( 1 ) ; } async setValidatorScore ( address , updated , total , aggregate , inclusion , spanInclusion , discovered , nominated , rank , unclaimed , bonded , faults , offline , location , councilStake , democracy , randomness ) { const data = await this . validatorScoreModel . findOne ( { address : address , } ) ; if ( ! data ) { const score = new this . validatorScoreModel ( { address , updated , total , aggregate , inclusion , spanInclusion , discovered , nominated , rank , unclaimed , bonded , faults , offline , location , councilStake , democracy , randomness , } ) ; return score . save ( ) ; } this . validatorScoreModel . findOneAndUpdate ( { address : address , } , { updated , total , aggregate , inclusion , spanInclusion , discovered , nominated , rank , unclaimed , bonded , faults , offline , location , councilStake , democracy , randomness , } ) . exec ( ) ; } async getValidatorScore ( address ) { return await this . validatorScoreModel . findOne ( { address : address , } ) ; } async setValidatorScoreMetadata ( bondedStats , bondedWeight , faultsStats , faultWeight , inclusionStats , inclusionWeight , spanInclusionStats , spanInclusionWeight , discoveredAtStats , discoveredAtWeight , nominatedAtStats , nominatedAtWeight , offlineStats , offlineWeight , rankStats , rankWeight , unclaimedStats , unclaimedWeight , locationStats , locationWeight , councilStakeStats , councilStakeWeight , democracyStats , democracyWeight , updated ) { logger . info ( `(Db::SetScoreMetadata) Setting validator score metadata` ) ; const data = await this . validatorScoreMetadataModel . findOne ( { updated : { $gte : 0 } , } ) . exec ( ) ; if ( ! data ) { logger . info ( `score metadata doesn't exist... creating...` ) ; const validatorScoreMetadata = new this . validatorScoreMetadataModel ( { bondedStats , bondedWeight , faultsStats , faultWeight , inclusionStats , inclusionWeight , spanInclusionStats , spanInclusionWeight , discoveredAtStats , discoveredAtWeight , nominatedAtStats , nominatedAtWeight , offlineStats , offlineWeight , rankStats , rankWeight , unclaimedStats , unclaimedWeight , locationStats , locationWeight , councilStakeWeight , democracyStats , democracyWeight , updated , } ) ; return validatorScoreMetadata . save ( ) ; } this . validatorScoreMetadataModel . findOneAndUpdate ( { updated : { $gte : 0 } } , { bondedStats , bondedWeight , faultsStats , faultWeight , inclusionStats , inclusionWeight , spanInclusionStats , spanInclusionWeight , discoveredAtStats , discoveredAtWeight , nominatedAtStats , nominatedAtWeight , offlineStats , offlineWeight , rankStats , rankWeight , unclaimedStats , unclaimedWeight , locationStats , locationWeight , councilStakeWeight , democracyStats , democracyWeight , updated , } ) . exec ( ) ; } async getValidatorScoreMetadata ( ) { return await this . validatorScoreMetadataModel . findOne ( { updated : { $gte : 0 } , } ) . exec ( ) ; } async setRelease ( name , publishedAt ) { logger . debug ( `{DB::Release} setting release for ${ name }` ) ; let data = await this . releaseModel . findOne ( { name : name } ) . exec ( ) ; if ( ! data ) { data = new this . releaseModel ( { name : name , publishedAt : publishedAt } ) ; return data . save ( ) ; } return data ; } async getLatestRelease ( ) { return ( await this . releaseModel . find ( { } ) . sort ( "-publishedAt" ) . limit ( 1 ) ) [ 0 ] ; } async setOnlineValidity ( address , validity ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Validate Intention} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "ONLINE" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "ONLINE" , updated : Date . now ( ) , details : validity ? "" : `${ data . name } offline. Offline since ${ data . offlineSince }.` , } , ] , } ) . exec ( ) ; } async setValidateIntentionValidity ( address , validity ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Validate Intention} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "VALIDATE_INTENTION" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "VALIDATE_INTENTION" , updated : Date . now ( ) , details : validity ? "" : `${ data . name } does not have a validate intention.` , } , ] , } ) . exec ( ) ; } async setLatestClientReleaseValidity ( address , validity ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Latest Client} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "CLIENT_UPGRADE" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "CLIENT_UPGRADE" , updated : Date . now ( ) , details : validity ? "" : `${ data . name } is not on the latest client version` , } , ] , } ) . exec ( ) ; } async setConnectionTimeInvalidity ( address , validity ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Connection Time} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "CONNECTION_TIME" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "CONNECTION_TIME" , updated : Date . now ( ) , details : validity ? "" : `${ data . name } has not been connected for minimum length` , } , ] , } ) . exec ( ) ; } async setIdentityInvalidity ( address , validity , details ? ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Identity} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "IDENTITY" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "IDENTITY" , updated : Date . now ( ) , details : validity ? "" : details ? details : `${ data . name } has not properly set their identity` , } , ] , } ) . exec ( ) ; } async setOfflineAccumulatedInvalidity ( address , validity ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Offline Accumulated} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "ACCUMULATED_OFFLINE_TIME" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "ACCUMULATED_OFFLINE_TIME" , updated : Date . now ( ) , details : validity ? "" : `${ data . name } has been offline ${ data . offlineAccumulated / 1000 / 60 } minutes this week.` , } , ] , } ) . exec ( ) ; } async setRewardDestinationInvalidity ( address , validity ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Reward Destination} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "REWARD_DESTINATION" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "REWARD_DESTINATION" , updated : Date . now ( ) , details : validity ? "" : `${ data . name } does not have reward destination as Staked` , } , ] , } ) . exec ( ) ; } async setCommissionInvalidity ( address , validity , details ? ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Commission} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "COMMISION" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "COMMISION" , updated : Date . now ( ) , details : validity ? "" : details ? details : `${ data . name } has not properly set their commission` , } , ] , } ) . exec ( ) ; } async setSelfStakeInvalidity ( address , validity , details ? ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Self Stake} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "SELF_STAKE" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "SELF_STAKE" , updated : Date . now ( ) , details : validity ? "" : details ? details : `${ data . name } has not properly bonded enough self stake` , } , ] , } ) . exec ( ) ; } async setUnclaimedInvalidity ( address , validity , details ? ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Self Stake} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "UNCLAIMED_REWARDS" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "UNCLAIMED_REWARDS" , updated : Date . now ( ) , details : validity ? "" : details ? details : `${ data . name } has not properly claimed era rewards` , } , ] , } ) . exec ( ) ; } async setBlockedInvalidity ( address , validity , details ? ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Self Stake} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "BLOCKED" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "BLOCKED" , updated : Date . now ( ) , details : validity ? "" : details ? details : `${ data . name } blocks external nominations` , } , ] , } ) . exec ( ) ; } async setKusamaRankInvalidity ( address , validity , details ? ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Self Stake} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } const invalidityReasons = data . invalidity . filter ( ( invalidityReason ) => { return invalidityReason . type !== "KUSAMA_RANK" ; } ) ; this . candidateModel . findOneAndUpdate ( { stash : address , } , { invalidity : [ ... invalidityReasons , { valid : validity , type : "KUSAMA_RANK" , updated : Date . now ( ) , details : validity ? "" : details ? details : `${ data . name } has not properly claimed era rewards` , } , ] , } ) . exec ( ) ; } async setValid ( address , validity ) { const data = await this . candidateModel . findOne ( { stash : address , } ) ; if ( ! data ) { console . log ( `{Valid} NO CANDIDATE DATA FOUND FOR ${ address }` ) ; return ; } this . candidateModel . findOneAndUpdate ( { stash : address , } , { valid : validity , } ) . exec ( ) ; } async setLocationStats ( session , locations ) { const data = await this . locationStatsModel . findOne ( { session , } ) ; if ( ! ! data && data . locations == locations ) return ; if ( ! data ) { const locationStats = new this . locationStatsModel ( { session , locations , updated : Date . now ( ) , } ) ; return locationStats . save ( ) ; } this . locationStatsModel . findOneAndUpdate ( { session , } , { updated : Date . now ( ) , locations , } ) . exec ( ) ; } async getSessionLocationStats ( session ) { const data = await this . locationStatsModel . findOne ( { session , } ) ; return data ; } async getLatestLocationStats ( ) { return ( await this . locationStatsModel . find ( { } ) . sort ( "-updated" ) . limit ( 1 ) ) [ 0 ] ; } async setElectionStats ( termDuration , candidacyBond , totalMembers , totalRunnersUp , totalCandidates , totalVoters , totalBonded , session ) { const data = await this . electionStatsModel . findOne ( { session , } ) ; if ( ! data ) { const electionStats = new this . electionStatsModel ( { termDuration , candidacyBond , totalMembers , totalRunnersUp , totalCandidates , totalVoters , totalBonded , session , updated : Date . now ( ) , } ) ; return electionStats . save ( ) ; } this . electionStatsModel . findOneAndUpdate ( { session , } , { termDuration , candidacyBond , totalMembers , totalRunnersUp , totalCandidates , totalVoters , totalBonded , updated : Date . now ( ) , } ) . exec ( ) ; } async getLatestElectionStats ( ) { return ( await this . electionStatsModel . find ( { } ) . sort ( "-updated" ) . limit ( 1 ) ) [ 0 ] ; } async setCouncillor ( address , membershipStatus , backing ) { const data = await this . councillorModel . findOne ( { address , } ) ; if ( ! ! data && data . backing == backing && data . status == membershipStatus ) return ; if ( ! data ) { const councillor = new this . councillorModel ( { address , status : membershipStatus , backing , updated : Date . now ( ) , } ) ; return councillor . save ( ) ; } this . councillorModel . findOneAndUpdate ( { address , } , { status : membershipStatus , backing , updated : Date . now ( ) , } ) ; } async getCouncillor ( address ) { const data = await this . councillorModel . findOne ( { address , } ) ; return data ; } async getAllCouncillors ( ) { return await this . councillorModel . find ( { } ) ; } async setEraPaidEvent ( era , blockNumber , blockTimestamp , eventIndex , moduleId , eventId , totalValidatorReward , totalRemainderReward ) { const data = await this . eraPaidEventModel . findOne ( { era , } ) ; if ( ! data ) { const eraPaidEvent = new this . eraPaidEventModel ( { era , blockNumber , blockTimestamp , eventIndex , moduleId , eventId , totalValidatorReward , totalRemainderReward , updated : Date . now ( ) , } ) ; return eraPaidEvent . save ( ) ; } this . eraPaidEventModel . findOneAndUpdate ( { era , } , { blockNumber , blockTimestamp , eventIndex , moduleId , eventId , totalValidatorReward , totalRemainderReward , updated : Date . now ( ) , } ) . exec ( ) ; } async getEraPaidEvent ( era ) { const data = await this . eraPaidEventModel . findOne ( { era , } ) ; return data ; } async getLatestEraPaidEvent ( ) { return ( await this . eraPaidEventModel . find ( { } ) . sort ( "-era" ) . limit ( 1 ) ) [ 0 ] ; } async setEraReward ( era , stash , rewardDestination , validatorStash , amount , blockTimestamp , blockNumber , slashKTon , claimTimestampDelta , claimBlockDelta ) { const data = await this . eraRewardModel . findOne ( { era : era , stash : stash , } ) ; if ( ! ! data && data . blockTimesamp == blockTimestamp ) return ; if ( ! data ) { const eraReward = new this . eraRewardModel ( { era , stash , rewardDestination , validatorStash , amount , blockTimestamp , blockNumber , slashKTon , claimTimestampDelta , claimBlockDelta , updated : Date . now ( ) , } ) ; return eraReward . save ( ) ; } this . eraRewardModel . findOneAndUpdate ( { stash : stash , era : era , } , { rewardDestination , validatorStash , amount , blockTimestamp , blockNumber , slashKTon , claimTimestampDelta , claimBlockDelta , updated : Date . now ( ) , } ) . exec ( ) ; } async getLastEraRewards ( stash , limit ) { return await this . eraRewardModel . find ( { stash : stash } ) . sort ( "-era" ) . limit ( limit ) ; } async getEraReward ( stash , era ) { const data = await this . eraRewardModel . findOne ( { stash : stash , era : era , } ) ; return data ; } async setReferendum ( referendum , updatedBlockNumber , updatedBlockHash ) { const data = await this . referendumModel . findOne ( { referendumIndex : referendum . referendumIndex , } ) ; if ( ! data ) { const referendumData = new this . referendumModel ( { referendumIndex : referendum . referendumIndex , proposedAt : referendum . proposedAt , proposalEnd : referendum . proposalEnd , proposalDelay : referendum . proposalDelay , threshold : referendum . threshold , deposit : referendum . deposit , proposer : referendum . proposer , imageHash : referendum . imageHash , voteCount : referendum . voteCount , voteCountAye : referendum . voteCountAye , voteCountNay : referendum . voteCountNay , voteAyeAmount : referendum . voteAyeAmount , voteNayAmount : referendum . voteNayAmount , voteTotalAmount : referendum . voteTotalAmount , isPassing : referendum . isPassing , updatedBlockNumber : updatedBlockNumber , updatedBlockHash : updatedBlockHash , updatedTimestamp : Date . now ( ) , } ) ; return referendumData . save ( ) ; } this . referendumModel . findOneAndUpdate ( { referendumIndex : referendum . referendumIndex , } , { proposedAt : referendum . proposedAt , proposalEnd : referendum . proposalEnd , proposalDelay : referendum . proposalDelay , threshold : referendum . threshold , deposit : referendum . deposit , proposer : referendum . proposer , imageHash : referendum . imageHash , voteCount : referendum . voteCount , voteCountAye : referendum . voteCountAye , voteCountNay : referendum . voteCountNay , voteAyeAmount : referendum . voteAyeAmount , voteNayAmount : referendum . voteNayAmount , voteTotalAmount : referendum . voteTotalAmount , isPassing : referendum . isPassing , updatedBlockNumber : updatedBlockNumber , updatedBlockHash : updatedBlockHash , updatedTimestamp : Date . now ( ) , } ) . exec ( ) ; } async getReferendum ( index ) { const data = await this . referendumModel . findOne ( { referendumIndex : index , } ) ; return data ; } async getAllReferenda ( ) { return this . referendumModel . find ( { } ) . exec ( ) ; } async getLastReferenda ( ) { return await this . referendumModel . find ( { } ) . sort ( "-referendumIndex" ) . exec ( ) ; } async setReferendumVote ( referendumVote , updatedBlockNumber , updatedBlockHash ) { const data = await this . referendumVoteModel . findOne ( { referendumIndex : referendumVote . referendumIndex , accountId : referendumVote . accountId , } ) ; if ( ! data ) { const referendumVoteData = new this . referendumVoteModel ( { referendumIndex : referendumVote . referendumIndex , accountId : referendumVote . accountId , isDelegating : referendumVote . isDelegating , updatedBlockNumber : updatedBlockNumber , updatedBlockHash : updatedBlockHash , updatedTimestamp : Date . now ( ) , } ) ; referendumVoteData . save ( ) ; const candidate = await this . candidateModel . findOne ( { stash : referendumVote . accountId , } ) ; if ( candidate && ! candidate . democracyVotes ?. includes ( referendumVote . referendumIndex ) ) { await this . candidateModel . findOneAndUpdate ( { stash : referendumVote . accountId , } , { $push : { democracyVotes : referendumVote . referendumIndex , } , $inc : { democracyVoteCount : 1 } , } ) ; } } this . referendumVoteModel . findOneAndUpdate ( { referendumIndex : referendumVote . referendumIndex , accountId : referendumVote . accountId , } , { isDelegating : referendumVote . isDelegating , updatedBlockNumber : updatedBlockNumber , updatedBlockHash : updatedBlockHash , updatedTimestamp : Date . now ( ) , } ) . exec ( ) ; } async getVoteReferendumIndex ( index ) { return this . referendumVoteModel . find ( { referendumIndex : index } ) . exec ( ) ; } async getAccountVoteReferendum ( accountId ) { return this . referendumVoteModel . find ( { accountId : accountId } ) . exec ( ) ; } }