Build the list of validators and required signatures at the moment of the transaction (#367)

This commit is contained in:
Gerardo Nardelli 2020-06-24 14:47:12 -03:00 committed by GitHub
parent 9e9e891db8
commit 0eb7c41278
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 144 additions and 90 deletions

@ -10,6 +10,7 @@ import { ValidatorsConfirmations } from './ValidatorsConfirmations'
import { getConfirmationsStatusDescription } from '../utils/networks' import { getConfirmationsStatusDescription } from '../utils/networks'
import { useStateProvider } from '../state/StateProvider' import { useStateProvider } from '../state/StateProvider'
import { ExecutionConfirmation } from './ExecutionConfirmation' import { ExecutionConfirmation } from './ExecutionConfirmation'
import { useValidatorContract } from '../hooks/useValidatorContract'
const StatusLabel = styled.label` const StatusLabel = styled.label`
font-weight: bold; font-weight: bold;
@ -43,11 +44,14 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
home: { name: homeName }, home: { name: homeName },
foreign: { name: foreignName } foreign: { name: foreignName }
} = useStateProvider() } = useStateProvider()
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
const { confirmations, status, executionData, signatureCollected } = useMessageConfirmations({ const { confirmations, status, executionData, signatureCollected } = useMessageConfirmations({
message, message,
receipt, receipt,
fromHome, fromHome,
timestamp timestamp,
requiredSignatures,
validatorList
}) })
return ( return (
@ -66,7 +70,11 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
: ''} : ''}
</p> </p>
</StatusDescription> </StatusDescription>
<ValidatorsConfirmations confirmations={confirmations} /> <ValidatorsConfirmations
confirmations={confirmations}
requiredSignatures={requiredSignatures}
validatorList={validatorList}
/>
{signatureCollected && <ExecutionConfirmation executionData={executionData} isHome={!fromHome} />} {signatureCollected && <ExecutionConfirmation executionData={executionData} isHome={!fromHome} />}
</StyledConfirmationContainer> </StyledConfirmationContainer>
</div> </div>

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import { formatTxHashExtended } from '../utils/networks' import { formatTxHashExtended } from '../utils/networks'
import { useStateProvider } from '../state/StateProvider'
import { useWindowWidth } from '@react-hook/window-size' import { useWindowWidth } from '@react-hook/window-size'
import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants' import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
import { SimpleLoading } from './commons/Loading' import { SimpleLoading } from './commons/Loading'
@ -18,12 +17,15 @@ const RequiredConfirmations = styled.label`
export interface ValidatorsConfirmationsParams { export interface ValidatorsConfirmationsParams {
confirmations: Array<ConfirmationParam> confirmations: Array<ConfirmationParam>
requiredSignatures: number
validatorList: string[]
} }
export const ValidatorsConfirmations = ({ confirmations }: ValidatorsConfirmationsParams) => { export const ValidatorsConfirmations = ({
const { confirmations,
home: { requiredSignatures, validatorList } requiredSignatures,
} = useStateProvider() validatorList
}: ValidatorsConfirmationsParams) => {
const windowWidth = useWindowWidth() const windowWidth = useWindowWidth()
const getValidatorStatusElement = (validatorStatus = '') => { const getValidatorStatusElement = (validatorStatus = '') => {

@ -1,14 +1,9 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { HOME_AMB_ABI, FOREIGN_AMB_ABI, BRIDGE_VALIDATORS_ABI } from '../../../commons' import { HOME_AMB_ABI, FOREIGN_AMB_ABI } from '../../../commons'
import { FOREIGN_BRIDGE_ADDRESS, HOME_BRIDGE_ADDRESS } from '../config/constants' import { FOREIGN_BRIDGE_ADDRESS, HOME_BRIDGE_ADDRESS } from '../config/constants'
import { Contract } from 'web3-eth-contract' import { Contract } from 'web3-eth-contract'
import Web3 from 'web3' import Web3 from 'web3'
import { import { getRequiredBlockConfirmations } from '../utils/contract'
getRequiredBlockConfirmations,
getRequiredSignatures,
getValidatorAddress,
getValidatorList
} from '../utils/contract'
export interface useBridgeContractsParams { export interface useBridgeContractsParams {
homeWeb3: Web3 homeWeb3: Web3
@ -20,9 +15,6 @@ export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContracts
const [foreignBridge, setForeignBridge] = useState<Maybe<Contract>>(null) const [foreignBridge, setForeignBridge] = useState<Maybe<Contract>>(null)
const [homeBlockConfirmations, setHomeBlockConfirmations] = useState(0) const [homeBlockConfirmations, setHomeBlockConfirmations] = useState(0)
const [foreignBlockConfirmations, setForeignBlockConfirmations] = useState(0) const [foreignBlockConfirmations, setForeignBlockConfirmations] = useState(0)
const [homeValidatorContract, setHomeValidatorContract] = useState<Maybe<Contract>>(null)
const [homeRequiredSignatures, setHomeRequiredSignatures] = useState(0)
const [homeValidatorList, setHomeValidatorList] = useState([])
const callRequireBlockConfirmations = async (contract: Maybe<Contract>, setResult: Function) => { const callRequireBlockConfirmations = async (contract: Maybe<Contract>, setResult: Function) => {
if (!contract) return if (!contract) return
@ -30,31 +22,11 @@ export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContracts
setResult(result) setResult(result)
} }
const callValidatorContract = async (bridgeContract: Maybe<Contract>, web3: Web3, setValidatorContract: Function) => {
if (!web3 || !bridgeContract) return
const address = await getValidatorAddress(bridgeContract)
const contract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, address)
setValidatorContract(contract)
}
const callRequiredSignatures = async (contract: Maybe<Contract>, setResult: Function) => {
if (!contract) return
const result = await getRequiredSignatures(contract)
setResult(result)
}
const callValidatorList = async (contract: Maybe<Contract>, setResult: Function) => {
if (!contract) return
const result = await getValidatorList(contract)
setResult(result)
}
useEffect( useEffect(
() => { () => {
if (!homeWeb3) return if (!homeWeb3) return
const homeContract = new homeWeb3.eth.Contract(HOME_AMB_ABI, HOME_BRIDGE_ADDRESS) const homeContract = new homeWeb3.eth.Contract(HOME_AMB_ABI, HOME_BRIDGE_ADDRESS)
callRequireBlockConfirmations(homeContract, setHomeBlockConfirmations) callRequireBlockConfirmations(homeContract, setHomeBlockConfirmations)
callValidatorContract(homeContract, homeWeb3, setHomeValidatorContract)
setHomeBridge(homeContract) setHomeBridge(homeContract)
}, },
[homeWeb3] [homeWeb3]
@ -70,21 +42,10 @@ export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContracts
[foreignWeb3] [foreignWeb3]
) )
useEffect(
() => {
callRequiredSignatures(homeValidatorContract, setHomeRequiredSignatures)
callValidatorList(homeValidatorContract, setHomeValidatorList)
},
[homeValidatorContract]
)
return { return {
homeBridge, homeBridge,
foreignBridge, foreignBridge,
homeBlockConfirmations, homeBlockConfirmations,
foreignBlockConfirmations, foreignBlockConfirmations
homeValidatorContract,
homeRequiredSignatures,
homeValidatorList
} }
} }

@ -29,6 +29,8 @@ export interface useMessageConfirmationsParams {
receipt: Maybe<TransactionReceipt> receipt: Maybe<TransactionReceipt>
fromHome: boolean fromHome: boolean
timestamp: number timestamp: number
requiredSignatures: number
validatorList: string[]
} }
export interface ConfirmationParam { export interface ConfirmationParam {
@ -44,7 +46,14 @@ export interface ExecutionData {
executionResult: boolean executionResult: boolean
} }
export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp }: useMessageConfirmationsParams) => { export const useMessageConfirmations = ({
message,
receipt,
fromHome,
timestamp,
requiredSignatures,
validatorList
}: useMessageConfirmationsParams) => {
const { home, foreign } = useStateProvider() const { home, foreign } = useStateProvider()
const [confirmations, setConfirmations] = useState<Array<ConfirmationParam>>([]) const [confirmations, setConfirmations] = useState<Array<ConfirmationParam>>([])
const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED) const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED)
@ -91,7 +100,7 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
targetBlock, targetBlock,
setWaitingBlocks, setWaitingBlocks,
setWaitingBlocksResolved, setWaitingBlocksResolved,
home.validatorList, validatorList,
setConfirmations, setConfirmations,
blockProvider, blockProvider,
interval, interval,
@ -103,15 +112,7 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
blockProvider.stop() blockProvider.stop()
} }
}, },
[ [foreign.blockConfirmations, foreign.web3, fromHome, home.blockConfirmations, validatorList, home.web3, receipt]
foreign.blockConfirmations,
foreign.web3,
fromHome,
home.blockConfirmations,
home.validatorList,
home.web3,
receipt
]
) )
// The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if // The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if
@ -208,11 +209,11 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
getConfirmationsForTx( getConfirmationsForTx(
message.data, message.data,
home.web3, home.web3,
home.validatorList, validatorList,
home.bridgeContract, home.bridgeContract,
confirmationContractMethod, confirmationContractMethod,
setConfirmations, setConfirmations,
home.requiredSignatures, requiredSignatures,
setSignatureCollected, setSignatureCollected,
waitingBlocksResolved, waitingBlocksResolved,
subscriptions, subscriptions,
@ -231,9 +232,9 @@ export const useMessageConfirmations = ({ message, receipt, fromHome, timestamp
fromHome, fromHome,
message.data, message.data,
home.web3, home.web3,
home.validatorList, validatorList,
home.bridgeContract, home.bridgeContract,
home.requiredSignatures, requiredSignatures,
waitingBlocksResolved, waitingBlocksResolved,
timestamp timestamp
] ]

@ -0,0 +1,68 @@
import { useEffect, useState } from 'react'
import { Contract } from 'web3-eth-contract'
import Web3 from 'web3'
import { getRequiredSignatures, getValidatorAddress, getValidatorList } from '../utils/contract'
import { BRIDGE_VALIDATORS_ABI } from '../../../commons'
import { useStateProvider } from '../state/StateProvider'
import { TransactionReceipt } from 'web3-eth'
export interface useValidatorContractParams {
fromHome: boolean
receipt: Maybe<TransactionReceipt>
}
export const useValidatorContract = ({ receipt, fromHome }: useValidatorContractParams) => {
const [validatorContract, setValidatorContract] = useState<Maybe<Contract>>(null)
const [requiredSignatures, setRequiredSignatures] = useState(0)
const [validatorList, setValidatorList] = useState([])
const { home, foreign } = useStateProvider()
const callValidatorContract = async (bridgeContract: Maybe<Contract>, web3: Web3, setValidatorContract: Function) => {
if (!web3 || !bridgeContract) return
const address = await getValidatorAddress(bridgeContract)
const contract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, address)
setValidatorContract(contract)
}
const callRequiredSignatures = async (
contract: Maybe<Contract>,
receipt: TransactionReceipt,
setResult: Function
) => {
if (!contract) return
const result = await getRequiredSignatures(contract, receipt.blockNumber)
setResult(result)
}
const callValidatorList = async (contract: Maybe<Contract>, receipt: TransactionReceipt, setResult: Function) => {
if (!contract) return
const result = await getValidatorList(contract, receipt.blockNumber)
setResult(result)
}
useEffect(
() => {
const web3 = fromHome ? home.web3 : foreign.web3
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
if (!web3 || !bridgeContract) return
callValidatorContract(bridgeContract, web3, setValidatorContract)
},
[home.web3, foreign.web3, home.bridgeContract, foreign.bridgeContract, fromHome]
)
useEffect(
() => {
if (!receipt) return
callRequiredSignatures(validatorContract, receipt, setRequiredSignatures)
callValidatorList(validatorContract, receipt, setValidatorList)
},
[validatorContract, receipt]
)
return {
requiredSignatures,
validatorList
}
}

@ -21,14 +21,8 @@ export interface BaseNetworkParams {
blockConfirmations: number blockConfirmations: number
} }
export interface HomeNetworkParams extends BaseNetworkParams {
validatorContract: Maybe<Contract>
requiredSignatures: number
validatorList: Array<string>
}
export interface StateContext { export interface StateContext {
home: HomeNetworkParams home: BaseNetworkParams
foreign: BaseNetworkParams foreign: BaseNetworkParams
loading: boolean loading: boolean
} }
@ -40,10 +34,7 @@ const initialState = {
web3: null, web3: null,
bridgeAddress: HOME_BRIDGE_ADDRESS, bridgeAddress: HOME_BRIDGE_ADDRESS,
bridgeContract: null, bridgeContract: null,
blockConfirmations: 0, blockConfirmations: 0
validatorContract: null,
requiredSignatures: 0,
validatorList: []
}, },
foreign: { foreign: {
chainId: 0, chainId: 0,
@ -61,15 +52,7 @@ const StateContext = createContext<StateContext>(initialState)
export const StateProvider = ({ children }: { children: ReactNode }) => { export const StateProvider = ({ children }: { children: ReactNode }) => {
const homeNetwork = useNetwork(HOME_RPC_URL) const homeNetwork = useNetwork(HOME_RPC_URL)
const foreignNetwork = useNetwork(FOREIGN_RPC_URL) const foreignNetwork = useNetwork(FOREIGN_RPC_URL)
const { const { homeBridge, foreignBridge, homeBlockConfirmations, foreignBlockConfirmations } = useBridgeContracts({
homeBridge,
foreignBridge,
homeBlockConfirmations,
foreignBlockConfirmations,
homeValidatorContract,
homeRequiredSignatures,
homeValidatorList
} = useBridgeContracts({
homeWeb3: homeNetwork.web3, homeWeb3: homeNetwork.web3,
foreignWeb3: foreignNetwork.web3 foreignWeb3: foreignNetwork.web3
}) })
@ -80,9 +63,6 @@ export const StateProvider = ({ children }: { children: ReactNode }) => {
name: HOME_NETWORK_NAME, name: HOME_NETWORK_NAME,
bridgeContract: homeBridge, bridgeContract: homeBridge,
blockConfirmations: homeBlockConfirmations, blockConfirmations: homeBlockConfirmations,
validatorContract: homeValidatorContract,
requiredSignatures: homeRequiredSignatures,
validatorList: homeValidatorList,
...homeNetwork ...homeNetwork
}, },
foreign: { foreign: {

@ -7,12 +7,46 @@ export const getRequiredBlockConfirmations = async (contract: Contract) => {
export const getValidatorAddress = (contract: Contract) => contract.methods.validatorContract().call() export const getValidatorAddress = (contract: Contract) => contract.methods.validatorContract().call()
export const getRequiredSignatures = async (contract: Contract) => { export const getRequiredSignatures = async (contract: Contract, blockNumber: number) => {
const requiredSignatures = await contract.methods.requiredSignatures().call() const events = await contract.getPastEvents('RequiredSignaturesChanged', {
fromBlock: 0,
toBlock: blockNumber
})
// Use the value form last event before the transaction
const event = events[events.length - 1]
const { requiredSignatures } = event.returnValues
return parseInt(requiredSignatures) return parseInt(requiredSignatures)
} }
export const getValidatorList = (contract: Contract) => contract.methods.validatorList().call() export const getValidatorList = async (contract: Contract, blockNumber: number) => {
let currentList: string[] = await contract.methods.validatorList().call()
const [added, removed] = await Promise.all([
contract.getPastEvents('ValidatorAdded', {
fromBlock: blockNumber
}),
contract.getPastEvents('ValidatorRemoved', {
fromBlock: blockNumber
})
])
// Ordered desc
const orderedEvents = [...added, ...removed].sort(({ blockNumber: prev }, { blockNumber: next }) => next - prev)
// Stored as a Set to avoid duplicates
const validatorList = new Set(currentList)
orderedEvents.forEach(e => {
const { validator } = e.returnValues
if (e.event === 'ValidatorRemoved') {
validatorList.add(validator)
} else if (e.event === 'ValidatorAdded') {
validatorList.delete(validator)
}
})
return Array.from(validatorList)
}
export const getMessagesSigned = (contract: Contract, hash: string) => contract.methods.messagesSigned(hash).call() export const getMessagesSigned = (contract: Contract, hash: string) => contract.methods.messagesSigned(hash).call()