Automatically detect network by searching the transaction in both chains (#377)

This commit is contained in:
Gerardo Nardelli 2020-06-29 09:39:31 -03:00 committed by GitHub
parent 4a727dc159
commit 691e4294ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 216 additions and 68 deletions

@ -1,10 +1,9 @@
import React, { useState, FormEvent, useEffect } from 'react'
import React, { useState, FormEvent } from 'react'
import styled from 'styled-components'
import { FormSubmitParams } from './MainPage'
import { useStateProvider } from '../state/StateProvider'
import { useParams } from 'react-router-dom'
import { Button } from './commons/Button'
import { RadioButtonLabel, RadioButtonContainer } from './commons/RadioButton'
import { TransactionSelector } from './TransactionSelector'
import { TransactionReceipt } from 'web3-eth'
const LabelText = styled.label`
line-height: 36px;
@ -23,33 +22,21 @@ const Input = styled.input`
}
`
export const Form = ({
onSubmit,
lastUsedChain
}: {
onSubmit: ({ chainId, txHash }: FormSubmitParams) => void
lastUsedChain: number
}) => {
const { home, foreign, loading } = useStateProvider()
const { chainId: paramChainId, txHash: paramTxHash } = useParams()
const [chainId, setChainId] = useState(lastUsedChain)
export const Form = ({ onSubmit }: { onSubmit: ({ chainId, txHash, receipt }: FormSubmitParams) => void }) => {
const [txHash, setTxHash] = useState('')
useEffect(
() => {
if (!paramChainId) {
setChainId(lastUsedChain > 0 ? lastUsedChain : foreign.chainId)
} else {
setChainId(parseInt(paramChainId))
setTxHash(paramTxHash)
}
},
[foreign.chainId, paramChainId, paramTxHash, lastUsedChain]
)
const [searchTx, setSearchTx] = useState(false)
const formSubmit = (e: FormEvent) => {
e.preventDefault()
onSubmit({ chainId, txHash })
setSearchTx(true)
}
const onSelected = (chainId: number, receipt: TransactionReceipt) => {
onSubmit({ chainId, txHash, receipt })
}
if (searchTx) {
return <TransactionSelector txHash={txHash} onSelected={onSelected} />
}
return (
@ -72,32 +59,6 @@ export const Form = ({
</Button>
</div>
</div>
{!loading && (
<div className="row is-center">
<RadioButtonContainer className="is-vertical-align" onClick={() => setChainId(foreign.chainId)}>
<input
className="is-marginless"
type="radio"
name="network"
value={foreign.name}
checked={chainId === foreign.chainId}
onChange={() => setChainId(foreign.chainId)}
/>
<RadioButtonLabel htmlFor={foreign.name}>{foreign.name}</RadioButtonLabel>
</RadioButtonContainer>
<RadioButtonContainer className="is-vertical-align" onClick={() => setChainId(home.chainId)}>
<input
className="is-marginless"
type="radio"
name="network"
value={home.name}
checked={chainId === home.chainId}
onChange={() => setChainId(home.chainId)}
/>
<RadioButtonLabel htmlFor={home.name}>{home.name}</RadioButtonLabel>
</RadioButtonContainer>
</div>
)}
</form>
)
}

@ -4,6 +4,7 @@ import { Route, useHistory } from 'react-router-dom'
import { Form } from './Form'
import { StatusContainer } from './StatusContainer'
import { useStateProvider } from '../state/StateProvider'
import { TransactionReceipt } from 'web3-eth'
const StyledMainPage = styled.div`
text-align: center;
@ -34,24 +35,24 @@ const HeaderContainer = styled.header`
export interface FormSubmitParams {
chainId: number
txHash: string
receipt: TransactionReceipt
}
export const MainPage = () => {
const history = useHistory()
const { home, foreign } = useStateProvider()
const [selectedChainId, setSelectedChainId] = useState(0)
const [networkName, setNetworkName] = useState('')
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
const setNetworkData = (chainId: number) => {
const network = chainId === home.chainId ? home.name : foreign.name
setNetworkName(network)
setSelectedChainId(chainId)
}
const onFormSubmit = ({ chainId, txHash }: FormSubmitParams) => {
const onFormSubmit = ({ chainId, txHash, receipt }: FormSubmitParams) => {
setNetworkData(chainId)
setReceipt(receipt)
history.push(`/${chainId}/${txHash}`)
}
@ -72,10 +73,16 @@ export const MainPage = () => {
</HeaderContainer>
</Header>
<div className="container">
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} lastUsedChain={selectedChainId} />} />
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} />
<Route
path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']}
children={<StatusContainer onBackToMain={resetNetworkHeader} setNetworkFromParams={setNetworkFromParams} />}
children={
<StatusContainer
onBackToMain={resetNetworkHeader}
setNetworkFromParams={setNetworkFromParams}
receiptParam={receipt}
/>
}
/>
</div>
</StyledMainPage>

@ -0,0 +1,47 @@
import React, { useState } from 'react'
import { Button } from './commons/Button'
import { RadioButtonLabel, RadioButtonContainer } from './commons/RadioButton'
import { useStateProvider } from '../state/StateProvider'
export const NetworkTransactionSelector = ({ onNetworkSelected }: { onNetworkSelected: (chainId: number) => void }) => {
const { home, foreign } = useStateProvider()
const [chainId, setChainId] = useState(home.chainId)
const networks = [home, foreign]
const onSelect = () => {
onNetworkSelected(chainId)
}
return (
<div>
<p>The transaction was found in both networks, please select one:</p>
<div className="row is-center">
<div className="col-3-lg col-12 is-marginless">
{networks.map((network, i) => (
<RadioButtonContainer
className="row is-center is-vertical-align"
key={i}
onClick={() => setChainId(network.chainId)}
>
<input
className="is-marginless"
type="radio"
name="message"
value={network.chainId}
checked={network.chainId === chainId}
onChange={() => setChainId(network.chainId)}
/>
<RadioButtonLabel htmlFor={i.toString()}>{network.name}</RadioButtonLabel>
</RadioButtonContainer>
))}
</div>
<div className="col-3-lg col-12 is-marginless">
<Button className="button outline" onClick={onSelect}>
Select
</Button>
</div>
</div>
</div>
)
}

@ -10,6 +10,7 @@ import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { ConfirmationsContainer } from './ConfirmationsContainer'
import { LeftArrow } from './commons/LeftArrow'
import styled from 'styled-components'
import { TransactionReceipt } from 'web3-eth'
const BackButton = styled.button`
color: var(--button-color);
@ -28,9 +29,10 @@ const BackLabel = styled.label`
export interface StatusContainerParam {
onBackToMain: () => void
setNetworkFromParams: (chainId: number) => void
receiptParam: Maybe<TransactionReceipt>
}
export const StatusContainer = ({ onBackToMain, setNetworkFromParams }: StatusContainerParam) => {
export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptParam }: StatusContainerParam) => {
const { home, foreign } = useStateProvider()
const history = useHistory()
const { chainId, txHash, messageIdParam } = useParams()
@ -39,7 +41,8 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams }: StatusCo
const { messages, receipt, status, description, timestamp, loading } = useTransactionStatus({
txHash: validParameters ? txHash : '',
chainId: validParameters ? parseInt(chainId) : 0
chainId: validParameters ? parseInt(chainId) : 0,
receiptParam
})
const selectedMessageId = messageIdParam === undefined || messages[messageIdParam] === undefined ? -1 : messageIdParam

@ -0,0 +1,47 @@
import React, { useEffect } from 'react'
import { useTransactionFinder } from '../hooks/useTransactionFinder'
import { useStateProvider } from '../state/StateProvider'
import { TRANSACTION_STATUS } from '../config/constants'
import { TransactionReceipt } from 'web3-eth'
import { Loading } from './commons/Loading'
import { NetworkTransactionSelector } from './NetworkTransactionSelector'
export const TransactionSelector = ({
txHash,
onSelected
}: {
txHash: string
onSelected: (chainId: number, receipt: TransactionReceipt) => void
}) => {
const { home, foreign } = useStateProvider()
const { receipt: homeReceipt, status: homeStatus } = useTransactionFinder({ txHash, web3: home.web3 })
const { receipt: foreignReceipt, status: foreignStatus } = useTransactionFinder({ txHash, web3: foreign.web3 })
useEffect(
() => {
if (!home.chainId || !foreign.chainId) return
if (homeStatus === TRANSACTION_STATUS.FOUND && foreignStatus === TRANSACTION_STATUS.NOT_FOUND) {
if (!homeReceipt) return
onSelected(home.chainId, homeReceipt)
} else if (foreignStatus === TRANSACTION_STATUS.FOUND && homeStatus === TRANSACTION_STATUS.NOT_FOUND) {
if (!foreignReceipt) return
onSelected(foreign.chainId, foreignReceipt)
}
},
[homeReceipt, homeStatus, foreignReceipt, foreignStatus, home.chainId, foreign.chainId, onSelected]
)
const onSelectedNetwork = (chainId: number) => {
const chain = chainId === home.chainId ? home.chainId : foreign.chainId
const receipt = chainId === home.chainId ? homeReceipt : foreignReceipt
if (!receipt) return
onSelected(chain, receipt)
}
if (foreignStatus === TRANSACTION_STATUS.FOUND && homeStatus === TRANSACTION_STATUS.FOUND) {
return <NetworkTransactionSelector onNetworkSelected={onSelectedNetwork} />
}
return <Loading />
}

@ -32,7 +32,9 @@ export const TRANSACTION_STATUS = {
SUCCESS_ONE_MESSAGE: 'SUCCESS_ONE_MESSAGE',
SUCCESS_NO_MESSAGES: 'SUCCESS_NO_MESSAGES',
FAILED: 'FAILED',
NOT_FOUND: 'NOT_FOUND'
FOUND: 'FOUND',
NOT_FOUND: 'NOT_FOUND',
UNDEFINED: 'UNDEFINED'
}
export const CONFIRMATIONS_STATUS = {

@ -0,0 +1,56 @@
import { useEffect, useState } from 'react'
import { TransactionReceipt } from 'web3-eth'
import { HOME_RPC_POLLING_INTERVAL, TRANSACTION_STATUS } from '../config/constants'
import Web3 from 'web3'
export const useTransactionFinder = ({ txHash, web3 }: { txHash: string; web3: Maybe<Web3> }) => {
const [status, setStatus] = useState(TRANSACTION_STATUS.UNDEFINED)
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
useEffect(
() => {
if (!txHash || !web3) return
const subscriptions: number[] = []
const unsubscribe = () => {
subscriptions.forEach(s => {
clearTimeout(s)
})
}
const getReceipt = async (
web3: Web3,
txHash: string,
setReceipt: Function,
setStatus: Function,
subscriptions: number[]
) => {
const txReceipt = await web3.eth.getTransactionReceipt(txHash)
setReceipt(txReceipt)
if (!txReceipt) {
setStatus(TRANSACTION_STATUS.NOT_FOUND)
const timeoutId = setTimeout(
() => getReceipt(web3, txHash, setReceipt, setStatus, subscriptions),
HOME_RPC_POLLING_INTERVAL
)
subscriptions.push(timeoutId)
} else {
setStatus(TRANSACTION_STATUS.FOUND)
}
}
getReceipt(web3, txHash, setReceipt, setStatus, subscriptions)
return () => {
unsubscribe()
}
},
[txHash, web3]
)
return {
status,
receipt
}
}

@ -6,7 +6,15 @@ import { useStateProvider } from '../state/StateProvider'
import { getHomeMessagesFromReceipt, getForeignMessagesFromReceipt, MessageObject, getBlock } from '../utils/web3'
import useInterval from '@use-it/interval'
export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chainId: number }) => {
export const useTransactionStatus = ({
txHash,
chainId,
receiptParam
}: {
txHash: string
chainId: number
receiptParam: Maybe<TransactionReceipt>
}) => {
const { home, foreign } = useStateProvider()
const [messages, setMessages] = useState<Array<MessageObject>>([])
const [status, setStatus] = useState('')
@ -37,7 +45,14 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai
const isHome = chainId === home.chainId
const web3 = isHome ? home.web3 : foreign.web3
const txReceipt = await web3.eth.getTransactionReceipt(txHash)
let txReceipt
if (receiptParam) {
txReceipt = receiptParam
} else {
txReceipt = await web3.eth.getTransactionReceipt(txHash)
}
setReceipt(txReceipt)
if (!txReceipt) {
@ -92,7 +107,17 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai
unsubscribe()
}
},
[txHash, chainId, home.chainId, foreign.chainId, home.web3, foreign.web3, home.bridgeAddress, foreign.bridgeAddress]
[
txHash,
chainId,
home.chainId,
foreign.chainId,
home.web3,
foreign.web3,
home.bridgeAddress,
foreign.bridgeAddress,
receiptParam
]
)
return {

@ -53,7 +53,7 @@ export const getValidatorSuccessTransaction = (
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
const { validator } = validatorData
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}`
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}`
const fromCache = validatorsCache.getData(validatorCacheKey)
if (fromCache && fromCache.txHash) {
@ -100,7 +100,7 @@ export const getValidatorFailedTransaction = (
timestamp: number,
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}`
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}`
const failedFromCache = validatorsCache.getData(validatorCacheKey)
if (failedFromCache && failedFromCache.txHash) {

@ -80,7 +80,7 @@ export const getFinalizationEvent = async (
})
setPendingExecution(true)
} else {
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}`
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}-${message.id}`
const failedFromCache = validatorsCache.get(validatorExecutionCacheKey)
if (!failedFromCache) {