Skip to content

Commit

Permalink
Fast Proposal Viewing (#10652)
Browse files Browse the repository at this point in the history
* speed up viewing of governance proposals by concurrent fetching and some avoided fetches

* save chain id to the connection,
add contracts from sourcify to the contract mapping

* remove console.time

* lint

* add cs

* Bump zod from 3.22.2 to 3.22.4

* Avoid refetching the correct transaction.
  • Loading branch information
aaronmgdr authored Oct 17, 2023
1 parent 53bbd49 commit d48c68a
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 117 deletions.
5 changes: 5 additions & 0 deletions .changeset/bitter-cacti-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/contractkit': minor
---

Add MultiSig.getTransaction() now optionally takes a second boolean param to avoid fetching confirmations information
5 changes: 5 additions & 0 deletions .changeset/chilled-carpets-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/celocli': patch
---

Speeds up governance:show command by parallelize async calls, and reducing data fetched
5 changes: 5 additions & 0 deletions .changeset/clean-suns-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/connect': patch
---

Add memoization to Connection.chainId() funciton. this is reset when setProvider is called.
5 changes: 5 additions & 0 deletions .changeset/green-rocks-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/contractkit': minor
---

Add method getConfirmations() to Multisig Wrapper
5 changes: 5 additions & 0 deletions .changeset/sour-trees-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/contractkit': patch
---

parallelize async calls in Governance Wrapper
5 changes: 5 additions & 0 deletions .changeset/thin-cycles-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/explorer': patch
---

Calls to getContractMappingFromSourcify() are now memoized in the same structure (this.addressMapping) as getContractMappingFromCore, getContractMappingWithSelector now runs in parallel
5 changes: 5 additions & 0 deletions .changeset/thirty-trees-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/governance': patch
---

Parallelize Fetching Proposal Info
2 changes: 2 additions & 0 deletions packages/cli/src/utils/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,8 @@ class CheckBuilder {

if (!allPassed) {
return this.cmd.error("Some checks didn't pass!")
} else {
console.log(`All checks passed`)
}
}

Expand Down
10 changes: 9 additions & 1 deletion packages/sdk/connect/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface ConnectionOptions {
*/
export class Connection {
private config: ConnectionOptions
private _chainID: number | undefined
readonly paramsPopulator: TxParamsNormalizer
rpcCaller!: RpcCaller

Expand All @@ -76,6 +77,7 @@ export class Connection {
if (!provider) {
throw new Error('Must have a valid Provider')
}
this._chainID = undefined
try {
if (!(provider instanceof CeloProvider)) {
this.rpcCaller = new HttpRpcCaller(provider)
Expand Down Expand Up @@ -395,10 +397,16 @@ export class Connection {
}
}

// An instance of Connection will only change chain id if provider is changed.
chainId = async (): Promise<number> => {
if (this._chainID) {
return this._chainID
}
// Reference: https://eth.wiki/json-rpc/API#net_version
const response = await this.rpcCaller.call('net_version', [])
return parseInt(response.result.toString(), 10)
const chainID = parseInt(response.result.toString(), 10)
this._chainID = chainID
return chainID
}

getTransactionCount = async (address: Address): Promise<number> => {
Expand Down
47 changes: 31 additions & 16 deletions packages/sdk/contractkit/src/wrappers/Governance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,13 @@ export class GovernanceWrapper extends BaseWrapperForGoverning<Governance> {

// simulates proposal.getSupportWithQuorumPadding
async getSupport(proposalID: BigNumber.Value) {
const participation = await this.getParticipationParameters()
const [participation, votes, lockedGold] = await Promise.all([
this.getParticipationParameters(),
this.getVotes(proposalID),
this.contracts.getLockedGold(),
])
const quorum = participation.baseline.times(participation.baselineQuorumFactor)
const votes = await this.getVotes(proposalID)
const total = votes.Yes.plus(votes.No).plus(votes.Abstain)
const lockedGold = await this.contracts.getLockedGold()
// NOTE: this networkWeight is not as governance calculates it,
// but we don't have access to proposal.networkWeight
const networkWeight = await lockedGold.getTotalLockedGold()
Expand Down Expand Up @@ -455,11 +457,18 @@ export class GovernanceWrapper extends BaseWrapperForGoverning<Governance> {
}

async getApprovalStatus(proposalID: BigNumber.Value): Promise<ApprovalStatus> {
const multisig = await this.getApproverMultisig()
const approveTx = await this.approve(proposalID)
const multisigTxs = await multisig.getTransactionDataByContent(this.address, approveTx.txo)
const [multisig, approveTx] = await Promise.all([
this.getApproverMultisig(),
this.approve(proposalID),
])

const [multisigTxs, approvers] = await Promise.all([
multisig.getTransactionDataByContent(this.address, approveTx.txo),
multisig.getOwners() as Promise<Address[]>,
])

const confirmations = multisigTxs ? multisigTxs.confirmations : []
const approvers = await multisig.getOwners()

return {
completion: `${confirmations.length} / ${approvers.length}`,
confirmations,
Expand All @@ -472,9 +481,11 @@ export class GovernanceWrapper extends BaseWrapperForGoverning<Governance> {
* @param proposalID Governance proposal UUID
*/
async getProposalRecord(proposalID: BigNumber.Value): Promise<ProposalRecord> {
const metadata = await this.getProposalMetadata(proposalID)
const proposal = await this.getProposal(proposalID)
const stage = await this.getProposalStage(proposalID)
const [proposal, metadata, stage] = await Promise.all([
this.getProposal(proposalID),
this.getProposalMetadata(proposalID),
this.getProposalStage(proposalID),
])

const record: ProposalRecord = {
proposal,
Expand All @@ -487,13 +498,17 @@ export class GovernanceWrapper extends BaseWrapperForGoverning<Governance> {
if (stage === ProposalStage.Queued) {
record.upvotes = await this.getUpvotes(proposalID)
} else if (stage === ProposalStage.Referendum || stage === ProposalStage.Execution) {
record.approved = true
record.passed = await this.isProposalPassing(proposalID)
record.votes = await this.getVotes(proposalID)
record.approved = await this.isApproved(proposalID)
record.approvals = await this.getApprovalStatus(proposalID)
const [passed, votes, approved, approvals] = await Promise.all([
this.isProposalPassing(proposalID) as Promise<boolean>,
this.getVotes(proposalID),
this.isApproved(proposalID),
this.getApprovalStatus(proposalID),
])
record.passed = passed as boolean
record.votes = votes
record.approved = approved
record.approvals = approvals
}

return record
}

Expand Down
80 changes: 61 additions & 19 deletions packages/sdk/contractkit/src/wrappers/MultiSig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ export interface TransactionData {
executed: boolean
confirmations: string[]
}
export interface TransactionDataWithOutConfirmations {
destination: string
value: BigNumber
data: string
executed: boolean
}

/**
* Contract for handling multisig actions
Expand All @@ -30,11 +36,7 @@ export class MultiSigWrapper extends BaseWrapper<MultiSig> {
* Otherwise, submits the `txObject` to the multisig and add confirmation.
* @param index The index of the pending withdrawal to withdraw.
*/
async submitOrConfirmTransaction(
destination: string,
txObject: CeloTxObject<any>,
value: string = '0'
) {
async submitOrConfirmTransaction(destination: string, txObject: CeloTxObject<any>, value = '0') {
const data = stringToSolidityBytes(txObject.encodeABI())
const transactionCount = await this.contract.methods.getTransactionCount(true, true).call()
let transactionId
Expand Down Expand Up @@ -81,35 +83,75 @@ export class MultiSigWrapper extends BaseWrapper<MultiSig> {
) {
const data = stringToSolidityBytes(txo.encodeABI())
const transactionCount = await this.getTransactionCount(true, true)
// reverse order for recency
for (let transactionId = transactionCount - 1; transactionId >= 0; transactionId--) {
const tx = await this.getTransaction(transactionId)
if (tx.data === data && tx.destination === destination && tx.value.isEqualTo(value)) {
return tx
}
const transactionsOrEmpties = await Promise.all(
Array(transactionCount)
.fill(0)
.map(async (_, index) => {
const tx = await this.getTransaction(index, false)
if (tx.data === data && tx.destination === destination && tx.value.isEqualTo(value)) {
return { index, ...tx }
}
return null
})
)
const wantedTransaction = transactionsOrEmpties.find((tx) => tx !== null)
if (!wantedTransaction) {
return
}
const confirmations = await this.getConfirmations(wantedTransaction.index)
return {
...wantedTransaction,
confirmations,
}
return undefined
}

async getTransaction(i: number): Promise<TransactionData> {
async getTransaction(i: number): Promise<TransactionData>
async getTransaction(
i: number,
includeConfirmations: false
): Promise<TransactionDataWithOutConfirmations>
async getTransaction(i: number, includeConfirmations = true) {
const { destination, value, data, executed } = await this.contract.methods
.transactions(i)
.call()
const confirmations = []
for (const e of await this.getOwners()) {
if (await this.contract.methods.confirmations(i, e).call()) {
confirmations.push(e)
if (!includeConfirmations) {
return {
destination,
data,
executed,
value: new BigNumber(value),
}
}

const confirmations = await this.getConfirmations(i)
return {
confirmations,
destination,
data,
executed,
confirmations,
value: new BigNumber(value),
}
}

/*
* Returns array of signer addresses which have confirmed a transaction
* when given the index of that transaction.
*/
async getConfirmations(txId: number) {
const owners = await this.getOwners()
const confirmationsOrEmpties = await Promise.all(
owners.map(async (owner) => {
const confirmation = await this.contract.methods.confirmations(txId, owner).call()
if (confirmation) {
return owner
} else {
return null
}
})
)
const confirmations = confirmationsOrEmpties.filter((c) => c !== null) as string[]
return confirmations
}

async getTransactions(): Promise<TransactionData[]> {
const txcount = await this.totalTransactionCount()
const res: TransactionData[] = []
Expand Down
25 changes: 18 additions & 7 deletions packages/sdk/explorer/src/block-explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,16 @@ export class BlockExplorer {
getContractMappingFromSourcify = async (
address: string
): Promise<ContractMapping | undefined> => {
const cached = this.addressMapping.get(address)
if (cached) {
return cached
}
const metadata = await fetchMetadata(this.kit.connection, address)
return metadata?.toContractMapping()
const mapping = metadata?.toContractMapping()
if (mapping) {
this.addressMapping.set(address, mapping)
}
return mapping
}

/**
Expand Down Expand Up @@ -369,11 +377,14 @@ export class BlockExplorer {
this.getContractMappingFromSourcifyAsProxy,
]
): Promise<ContractMapping | undefined> {
for (const strategy of strategies) {
const contractMapping = await strategy(address)
if (contractMapping && contractMapping.fnMapping.get(selector)) {
return contractMapping
}
}
const mappings = await Promise.all(
strategies.map(async (strategy) => {
const contractMapping = await strategy(address)
if (contractMapping && contractMapping.fnMapping.get(selector)) {
return contractMapping
}
})
)
return mappings.find((mapping) => mapping !== undefined)
}
}
Loading

0 comments on commit d48c68a

Please sign in to comment.