Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
cb81c4cb
Commit
cb81c4cb
authored
Feb 03, 2022
by
Kelvin Fichter
Committed by
smartcontracts
Feb 03, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(sdk): implement message wait time estimation
parent
4ddf58da
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
257 additions
and
29 deletions
+257
-29
cross-chain-messenger.ts
packages/sdk/src/cross-chain-messenger.ts
+76
-3
cross-chain-messenger.ts
packages/sdk/src/interfaces/cross-chain-messenger.ts
+10
-0
chain-constants.ts
packages/sdk/src/utils/chain-constants.ts
+22
-0
coercion.ts
packages/sdk/src/utils/coercion.ts
+10
-0
index.ts
packages/sdk/src/utils/index.ts
+1
-0
cross-chain-messenger.spec.ts
packages/sdk/test/cross-chain-messenger.spec.ts
+138
-26
No files found.
packages/sdk/src/cross-chain-messenger.ts
View file @
cb81c4cb
...
...
@@ -37,7 +37,7 @@ import {
}
from
'
./interfaces
'
import
{
toSignerOrProvider
,
to
Big
Number
,
toNumber
,
toTransactionHash
,
DeepPartial
,
getAllOEContracts
,
...
...
@@ -46,6 +46,8 @@ import {
makeMerkleTreeProof
,
makeStateTrieProof
,
encodeCrossChainMessage
,
DEPOSIT_CONFIRMATION_BLOCKS
,
CHAIN_BLOCK_TIMES
,
}
from
'
./utils
'
export
class
CrossChainMessenger
implements
ICrossChainMessenger
{
...
...
@@ -54,6 +56,8 @@ export class CrossChainMessenger implements ICrossChainMessenger {
public
l1ChainId
:
number
public
contracts
:
OEContracts
public
bridges
:
BridgeAdapters
public
depositConfirmationBlocks
:
number
public
l1BlockTimeSeconds
:
number
/**
* Creates a new CrossChainProvider instance.
...
...
@@ -62,6 +66,8 @@ export class CrossChainMessenger implements ICrossChainMessenger {
* @param opts.l1SignerOrProvider Signer or Provider for the L1 chain, or a JSON-RPC url.
* @param opts.l1SignerOrProvider Signer or Provider for the L2 chain, or a JSON-RPC url.
* @param opts.l1ChainId Chain ID for the L1 chain.
* @param opts.depositConfirmationBlocks Optional number of blocks before a deposit is confirmed.
* @param opts.l1BlockTimeSeconds Optional estimated block time in seconds for the L1 chain.
* @param opts.contracts Optional contract address overrides.
* @param opts.bridges Optional bridge address list.
*/
...
...
@@ -69,17 +75,31 @@ export class CrossChainMessenger implements ICrossChainMessenger {
l1SignerOrProvider
:
SignerOrProviderLike
l2SignerOrProvider
:
SignerOrProviderLike
l1ChainId
:
NumberLike
depositConfirmationBlocks
?:
NumberLike
l1BlockTimeSeconds
?:
NumberLike
contracts
?:
DeepPartial
<
OEContractsLike
>
bridges
?:
BridgeAdapterData
})
{
this
.
l1SignerOrProvider
=
toSignerOrProvider
(
opts
.
l1SignerOrProvider
)
this
.
l2SignerOrProvider
=
toSignerOrProvider
(
opts
.
l2SignerOrProvider
)
this
.
l1ChainId
=
toBigNumber
(
opts
.
l1ChainId
).
toNumber
()
this
.
l1ChainId
=
toNumber
(
opts
.
l1ChainId
)
this
.
depositConfirmationBlocks
=
opts
?.
depositConfirmationBlocks
!==
undefined
?
toNumber
(
opts
.
depositConfirmationBlocks
)
:
DEPOSIT_CONFIRMATION_BLOCKS
[
this
.
l1ChainId
]
||
0
this
.
l1BlockTimeSeconds
=
opts
?.
l1BlockTimeSeconds
!==
undefined
?
toNumber
(
opts
.
l1BlockTimeSeconds
)
:
CHAIN_BLOCK_TIMES
[
this
.
l1ChainId
]
||
1
this
.
contracts
=
getAllOEContracts
(
this
.
l1ChainId
,
{
l1SignerOrProvider
:
this
.
l1SignerOrProvider
,
l2SignerOrProvider
:
this
.
l2SignerOrProvider
,
overrides
:
opts
.
contracts
,
})
this
.
bridges
=
getBridgeAdapters
(
this
.
l1ChainId
,
this
,
{
overrides
:
opts
.
bridges
,
})
...
...
@@ -478,7 +498,60 @@ export class CrossChainMessenger implements ICrossChainMessenger {
public
async
estimateMessageWaitTimeSeconds
(
message
:
MessageLike
):
Promise
<
number
>
{
throw
new
Error
(
'
Not implemented
'
)
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
const
status
=
await
this
.
getMessageStatus
(
resolved
)
if
(
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
if
(
status
===
MessageStatus
.
RELAYED
||
status
===
MessageStatus
.
FAILED_L1_TO_L2_MESSAGE
)
{
// Transactions that are relayed or failed are considered completed, so the wait time is 0.
return
0
}
else
{
// Otherwise we need to estimate the number of blocks left until the transaction will be
// considered confirmed by the Layer 2 system. Then we multiply this by the estimated
// average L1 block time.
const
receipt
=
await
this
.
l1Provider
.
getTransactionReceipt
(
resolved
.
transactionHash
)
const
blocksLeft
=
Math
.
max
(
this
.
depositConfirmationBlocks
-
receipt
.
confirmations
,
0
)
return
blocksLeft
*
this
.
l1BlockTimeSeconds
}
}
else
{
if
(
status
===
MessageStatus
.
RELAYED
||
status
===
MessageStatus
.
READY_FOR_RELAY
)
{
// Transactions that are relayed or ready for relay are considered complete.
return
0
}
else
if
(
status
===
MessageStatus
.
STATE_ROOT_NOT_PUBLISHED
)
{
// If the state root hasn't been published yet, just assume it'll be published relatively
// quickly and return the challenge period for now. In the future we could use more
// advanced techniques to figure out average time between transaction execution and
// state root publication.
return
this
.
getChallengePeriodSeconds
()
}
else
if
(
status
===
MessageStatus
.
IN_CHALLENGE_PERIOD
)
{
// If the message is still within the challenge period, then we need to estimate exactly
// the amount of time left until the challenge period expires. The challenge period starts
// when the state root is published.
const
stateRoot
=
await
this
.
getMessageStateRoot
(
resolved
)
const
challengePeriod
=
await
this
.
getChallengePeriodSeconds
()
const
targetBlock
=
await
this
.
l1Provider
.
getBlock
(
stateRoot
.
batch
.
blockNumber
)
const
latestBlock
=
await
this
.
l1Provider
.
getBlock
(
'
latest
'
)
return
Math
.
max
(
challengePeriod
-
(
latestBlock
.
timestamp
-
targetBlock
.
timestamp
),
0
)
}
else
{
// Should not happen
throw
new
Error
(
`unexpected message status`
)
}
}
}
public
async
getChallengePeriodSeconds
():
Promise
<
number
>
{
...
...
packages/sdk/src/interfaces/cross-chain-messenger.ts
View file @
cb81c4cb
...
...
@@ -76,6 +76,16 @@ export interface ICrossChainMessenger {
*/
l2Signer
:
Signer
/**
* Number of blocks before a deposit is considered confirmed.
*/
depositConfirmationBlocks
:
number
/**
* Estimated average L1 block time in seconds.
*/
l1BlockTimeSeconds
:
number
/**
* Retrieves all cross chain messages sent within a given transaction.
*
...
...
packages/sdk/src/utils/chain-constants.ts
0 → 100644
View file @
cb81c4cb
export
const
DEPOSIT_CONFIRMATION_BLOCKS
=
{
// Mainnet
1
:
50
,
// Goerli
5
:
12
,
// Kovan
42
:
12
,
// Hardhat Local
// 2 just for testing purposes
31337
:
2
,
}
export
const
CHAIN_BLOCK_TIMES
=
{
// Mainnet
1
:
13
,
// Goerli
5
:
15
,
// Kovan
42
:
4
,
// Hardhat Local
31337
:
1
,
}
packages/sdk/src/utils/coercion.ts
View file @
cb81c4cb
...
...
@@ -69,6 +69,16 @@ export const toBigNumber = (num: NumberLike): BigNumber => {
return
ethers
.
BigNumber
.
from
(
num
)
}
/**
* Converts a number-like into a number.
*
* @param num Number-like to convert into a number.
* @returns Number-like as a number.
*/
export
const
toNumber
=
(
num
:
NumberLike
):
number
=>
{
return
toBigNumber
(
num
).
toNumber
()
}
/**
* Converts an address-like into a 0x-prefixed address string.
*
...
...
packages/sdk/src/utils/index.ts
View file @
cb81c4cb
...
...
@@ -4,3 +4,4 @@ export * from './message-encoding'
export
*
from
'
./type-utils
'
export
*
from
'
./misc-utils
'
export
*
from
'
./merkle-utils
'
export
*
from
'
./chain-constants
'
packages/sdk/test/cross-chain-messenger.spec.ts
View file @
cb81c4cb
...
...
@@ -1281,48 +1281,160 @@ describe('CrossChainMessenger', () => {
})
})
describe
(
'
estimateMessageWaitTimeBlocks
'
,
()
=>
{
describe
(
'
when the message exists
'
,
()
=>
{
describe
(
'
when the message is an L1 => L2 message
'
,
()
=>
{
describe
(
'
when the message has not been executed on L2 yet
'
,
()
=>
{
it
(
'
should return the estimated blocks until the message will be confirmed on L2
'
describe
(
'
estimateMessageWaitTimeSeconds
'
,
()
=>
{
let
scc
:
Contract
let
l1Messenger
:
Contract
let
l2Messenger
:
Contract
let
messenger
:
CrossChainMessenger
beforeEach
(
async
()
=>
{
// TODO: Get rid of the nested awaits here. Could be a good first issue for someone.
scc
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockSCC
'
)).
deploy
())
as
any
l1Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
l2Messenger
=
(
await
(
await
ethers
.
getContractFactory
(
'
MockMessenger
'
)
).
deploy
())
as
any
messenger
=
new
CrossChainMessenger
({
l1SignerOrProvider
:
ethers
.
provider
,
l2SignerOrProvider
:
ethers
.
provider
,
l1ChainId
:
31337
,
contracts
:
{
l1
:
{
L1CrossDomainMessenger
:
l1Messenger
.
address
,
StateCommitmentChain
:
scc
.
address
,
},
l2
:
{
L2CrossDomainMessenger
:
l2Messenger
.
address
,
},
},
})
})
const
sendAndGetDummyMessage
=
async
(
direction
:
MessageDirection
)
=>
{
const
mockMessenger
=
direction
===
MessageDirection
.
L1_TO_L2
?
l1Messenger
:
l2Messenger
const
tx
=
await
mockMessenger
.
triggerSentMessageEvents
([
DUMMY_MESSAGE
])
return
(
await
messenger
.
getMessagesByTransaction
(
tx
,
{
direction
,
})
)[
0
]
}
const
submitStateRootBatchForMessage
=
async
(
message
:
CrossChainMessage
)
=>
{
await
scc
.
setSBAParams
({
batchIndex
:
0
,
batchRoot
:
ethers
.
constants
.
HashZero
,
batchSize
:
1
,
prevTotalElements
:
message
.
blockNumber
,
extraData
:
'
0x
'
,
})
await
scc
.
appendStateBatch
([
ethers
.
constants
.
HashZero
],
0
)
}
describe
(
'
when the message is an L1 => L2 message
'
,
()
=>
{
describe
(
'
when the message has not been executed on L2 yet
'
,
()
=>
{
it
(
'
should return the estimated seconds until the message will be confirmed on L2
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L1_TO_L2
)
await
l1Messenger
.
triggerSentMessageEvents
([
message
])
expect
(
await
messenger
.
estimateMessageWaitTimeSeconds
(
message
)
).
to
.
equal
(
1
)
})
})
describe
(
'
when the message has been executed on L2
'
,
()
=>
{
it
(
'
should return 0
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L1_TO_L2
)
await
l1Messenger
.
triggerSentMessageEvents
([
message
])
await
l2Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
describe
(
'
when the message has been executed on L2
'
,
()
=>
{
it
(
'
should return 0
'
)
expect
(
await
messenger
.
estimateMessageWaitTimeSeconds
(
message
)
).
to
.
equal
(
0
)
})
})
})
describe
(
'
when the message is an L2 => L1 message
'
,
()
=>
{
describe
(
'
when the state root has not been published
'
,
()
=>
{
it
(
'
should return the estimated blocks until the state root will be published and pass the challenge period
'
describe
(
'
when the message is an L2 => L1 message
'
,
()
=>
{
describe
(
'
when the state root has not been published
'
,
()
=>
{
it
(
'
should return the estimated seconds until the state root will be published and pass the challenge period
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
expect
(
await
messenger
.
estimateMessageWaitTimeSeconds
(
message
)
).
to
.
equal
(
await
messenger
.
getChallengePeriodSeconds
())
})
})
describe
(
'
when the state root is within the challenge period
'
,
()
=>
{
it
(
'
should return the estimated blocks until the state root passes the challenge period
'
describe
(
'
when the state root is within the challenge period
'
,
()
=>
{
it
(
'
should return the estimated seconds until the state root passes the challenge period
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
await
submitStateRootBatchForMessage
(
message
)
const
challengePeriod
=
await
messenger
.
getChallengePeriodSeconds
()
ethers
.
provider
.
send
(
'
evm_increaseTime
'
,
[
challengePeriod
/
2
])
ethers
.
provider
.
send
(
'
evm_mine
'
,
[])
expect
(
await
messenger
.
estimateMessageWaitTimeSeconds
(
message
)
).
to
.
equal
(
challengePeriod
/
2
)
})
})
describe
(
'
when the state root passes the challenge period
'
,
()
=>
{
it
(
'
should return 0
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
await
submitStateRootBatchForMessage
(
message
)
const
challengePeriod
=
await
messenger
.
getChallengePeriodSeconds
()
ethers
.
provider
.
send
(
'
evm_increaseTime
'
,
[
challengePeriod
+
1
])
ethers
.
provider
.
send
(
'
evm_mine
'
,
[])
describe
(
'
when the state root passes the challenge period
'
,
()
=>
{
it
(
'
should return 0
'
)
expect
(
await
messenger
.
estimateMessageWaitTimeSeconds
(
message
)
).
to
.
equal
(
0
)
})
})
})
describe
(
'
when the message does not exist
'
,
()
=>
{
it
(
'
should throw an error
'
)
})
})
describe
(
'
when the message has been executed
'
,
()
=>
{
it
(
'
should return 0
'
,
async
()
=>
{
const
message
=
await
sendAndGetDummyMessage
(
MessageDirection
.
L2_TO_L1
)
describe
(
'
estimateMessageWaitTimeSeconds
'
,
()
=>
{
it
(
'
should be the result of estimateMessageWaitTimeBlocks multiplied by the L1 block time
'
)
await
l2Messenger
.
triggerSentMessageEvents
([
message
])
await
l1Messenger
.
triggerRelayedMessageEvents
([
hashCrossChainMessage
(
message
),
])
expect
(
await
messenger
.
estimateMessageWaitTimeSeconds
(
message
)
).
to
.
equal
(
0
)
})
})
})
})
describe
(
'
sendMessage
'
,
()
=>
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment