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
7ee8d8d3
Unverified
Commit
7ee8d8d3
authored
Jan 19, 2022
by
kf
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: implement getMessageStatus
parent
f8531d88
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
510 additions
and
41 deletions
+510
-41
cross-chain-provider.ts
packages/sdk/src/cross-chain-provider.ts
+209
-1
cross-chain-provider.ts
packages/sdk/src/interfaces/cross-chain-provider.ts
+61
-1
types.ts
packages/sdk/src/interfaces/types.ts
+11
-1
MockSCC.sol
packages/sdk/test/contracts/MockSCC.sol
+48
-0
cross-chain-provider.spec.ts
packages/sdk/test/cross-chain-provider.spec.ts
+173
-38
constants.ts
packages/sdk/test/helpers/constants.ts
+7
-0
index.ts
packages/sdk/test/helpers/index.ts
+1
-0
No files found.
packages/sdk/src/cross-chain-provider.ts
View file @
7ee8d8d3
...
...
@@ -23,6 +23,8 @@ import {
MessageReceiptStatus
,
CustomBridges
,
CustomBridgesLike
,
StateRoot
,
StateRootBatch
,
}
from
'
./interfaces
'
import
{
toProvider
,
...
...
@@ -38,6 +40,7 @@ export class CrossChainProvider implements ICrossChainProvider {
public
l1Provider
:
Provider
public
l2Provider
:
Provider
public
l1ChainId
:
number
public
l1BlockTime
:
number
public
contracts
:
OEContracts
public
bridges
:
CustomBridges
...
...
@@ -48,18 +51,24 @@ export class CrossChainProvider implements ICrossChainProvider {
* @param opts.l1Provider Provider for the L1 chain, or a JSON-RPC url.
* @param opts.l2Provider Provider for the L2 chain, or a JSON-RPC url.
* @param opts.l1ChainId Chain ID for the L1 chain.
* @param opts.l1BlockTime Optional L1 block time in seconds. Defaults to 15 seconds.
* @param opts.contracts Optional contract address overrides.
* @param opts.bridges Optional bridge address list.
*/
constructor
(
opts
:
{
l1Provider
:
ProviderLike
l2Provider
:
ProviderLike
l1ChainId
:
NumberLike
l1BlockTime
?:
NumberLike
contracts
?:
DeepPartial
<
OEContractsLike
>
bridges
?:
Partial
<
CustomBridgesLike
>
})
{
this
.
l1Provider
=
toProvider
(
opts
.
l1Provider
)
this
.
l2Provider
=
toProvider
(
opts
.
l2Provider
)
this
.
l1ChainId
=
toBigNumber
(
opts
.
l1ChainId
).
toNumber
()
this
.
l1BlockTime
=
opts
.
l1BlockTime
?
toBigNumber
(
opts
.
l1ChainId
).
toNumber
()
:
15
this
.
contracts
=
getAllOEContracts
(
this
.
l1ChainId
,
{
l1SignerOrProvider
:
this
.
l1Provider
,
l2SignerOrProvider
:
this
.
l2Provider
,
...
...
@@ -335,7 +344,41 @@ export class CrossChainProvider implements ICrossChainProvider {
}
public
async
getMessageStatus
(
message
:
MessageLike
):
Promise
<
MessageStatus
>
{
throw
new
Error
(
'
Not implemented
'
)
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
const
receipt
=
await
this
.
getMessageReceipt
(
resolved
)
if
(
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
if
(
receipt
===
null
)
{
return
MessageStatus
.
UNCONFIRMED_L1_TO_L2_MESSAGE
}
else
{
if
(
receipt
.
receiptStatus
===
MessageReceiptStatus
.
RELAYED_SUCCEEDED
)
{
return
MessageStatus
.
RELAYED
}
else
{
return
MessageStatus
.
FAILED_L1_TO_L2_MESSAGE
}
}
}
else
{
if
(
receipt
===
null
)
{
const
stateRoot
=
await
this
.
getMessageStateRoot
(
resolved
)
if
(
stateRoot
===
null
)
{
return
MessageStatus
.
STATE_ROOT_NOT_PUBLISHED
}
else
{
const
challengePeriod
=
await
this
.
getChallengePeriodBlocks
()
const
latestBlock
=
await
this
.
l1Provider
.
getBlockNumber
()
if
(
stateRoot
.
blockNumber
+
challengePeriod
>
latestBlock
)
{
return
MessageStatus
.
IN_CHALLENGE_PERIOD
}
else
{
return
MessageStatus
.
READY_FOR_RELAY
}
}
}
else
{
if
(
receipt
.
receiptStatus
===
MessageReceiptStatus
.
RELAYED_SUCCEEDED
)
{
return
MessageStatus
.
RELAYED
}
else
{
return
MessageStatus
.
READY_FOR_RELAY
}
}
}
}
public
async
getMessageReceipt
(
...
...
@@ -436,4 +479,169 @@ export class CrossChainProvider implements ICrossChainProvider {
):
Promise
<
number
>
{
throw
new
Error
(
'
Not implemented
'
)
}
public
async
getChallengePeriodSeconds
():
Promise
<
number
>
{
const
challengePeriod
=
await
this
.
contracts
.
l1
.
StateCommitmentChain
.
FRAUD_PROOF_WINDOW
()
return
challengePeriod
.
toNumber
()
}
public
async
getChallengePeriodBlocks
():
Promise
<
number
>
{
return
Math
.
ceil
(
(
await
this
.
getChallengePeriodSeconds
())
/
this
.
l1BlockTime
)
}
public
async
getMessageStateRoot
(
message
:
MessageLike
):
Promise
<
StateRoot
|
null
>
{
const
resolved
=
await
this
.
toCrossChainMessage
(
message
)
// State roots are only a thing for L2 to L1 messages.
if
(
resolved
.
direction
===
MessageDirection
.
L1_TO_L2
)
{
throw
new
Error
(
`cannot get a state root for an L1 to L2 message`
)
}
// We need the block number of the transaction that triggered the message so we can look up the
// state root batch that corresponds to that block number.
const
messageTxReceipt
=
await
this
.
l2Provider
.
getTransactionReceipt
(
resolved
.
transactionHash
)
// Every block has exactly one transaction in it. Since there's a genesis block, the
// transaction index will always be one less than the block number.
const
messageTxIndex
=
messageTxReceipt
.
blockNumber
-
1
// Pull down the state root batch, we'll try to pick out the specific state root that
// corresponds to our message.
const
stateRootBatch
=
await
this
.
getStateRootBatchByTransactionIndex
(
messageTxIndex
)
// No state root batch, no state root.
if
(
stateRootBatch
===
null
)
{
return
null
}
// We have a state root batch, now we need to find the specific state root for our transaction.
// First we need to figure out the index of the state root within the batch we found. This is
// going to be the original transaction index offset by the total number of previous state
// roots.
const
indexInBatch
=
messageTxIndex
-
stateRootBatch
.
header
.
prevTotalElements
.
toNumber
()
// Just a sanity check.
if
(
stateRootBatch
.
stateRoots
.
length
<=
indexInBatch
)
{
// Should never happen!
throw
new
Error
(
`state root does not exist in batch`
)
}
return
{
blockNumber
:
stateRootBatch
.
blockNumber
,
header
:
stateRootBatch
.
header
,
stateRoot
:
stateRootBatch
.
stateRoots
[
indexInBatch
],
}
}
public
async
getStateBatchAppendedEventByBatchIndex
(
batchIndex
:
number
):
Promise
<
ethers
.
Event
|
null
>
{
const
events
=
await
this
.
contracts
.
l1
.
StateCommitmentChain
.
queryFilter
(
this
.
contracts
.
l1
.
StateCommitmentChain
.
filters
.
StateBatchAppended
(
batchIndex
)
)
if
(
events
.
length
===
0
)
{
return
null
}
else
if
(
events
.
length
>
1
)
{
// Should never happen!
throw
new
Error
(
`found more than one StateBatchAppended event`
)
}
else
{
return
events
[
0
]
}
}
public
async
getStateBatchAppendedEventByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
ethers
.
Event
|
null
>
{
const
isEventHi
=
(
event
:
ethers
.
Event
,
index
:
number
)
=>
{
const
prevTotalElements
=
event
.
args
.
_prevTotalElements
.
toNumber
()
return
index
<
prevTotalElements
}
const
isEventLo
=
(
event
:
ethers
.
Event
,
index
:
number
)
=>
{
const
prevTotalElements
=
event
.
args
.
_prevTotalElements
.
toNumber
()
const
batchSize
=
event
.
args
.
_batchSize
.
toNumber
()
return
index
>=
prevTotalElements
+
batchSize
}
const
totalBatches
:
ethers
.
BigNumber
=
await
this
.
contracts
.
l1
.
StateCommitmentChain
.
getTotalBatches
()
if
(
totalBatches
.
eq
(
0
))
{
return
null
}
let
lowerBound
=
0
let
upperBound
=
totalBatches
.
toNumber
()
-
1
let
batchEvent
:
ethers
.
Event
|
null
=
await
this
.
getStateBatchAppendedEventByBatchIndex
(
upperBound
)
if
(
isEventLo
(
batchEvent
,
transactionIndex
))
{
// Upper bound is too low, means this transaction doesn't have a corresponding state batch yet.
return
null
}
else
if
(
!
isEventHi
(
batchEvent
,
transactionIndex
))
{
// Upper bound is not too low and also not too high. This means the upper bound event is the
// one we're looking for! Return it.
return
batchEvent
}
// Binary search to find the right event. The above checks will guarantee that the event does
// exist and that we'll find it during this search.
while
(
lowerBound
<
upperBound
)
{
const
middleOfBounds
=
Math
.
floor
((
lowerBound
+
upperBound
)
/
2
)
batchEvent
=
await
this
.
getStateBatchAppendedEventByBatchIndex
(
middleOfBounds
)
if
(
isEventHi
(
batchEvent
,
transactionIndex
))
{
upperBound
=
middleOfBounds
}
else
if
(
isEventLo
(
batchEvent
,
transactionIndex
))
{
lowerBound
=
middleOfBounds
}
else
{
break
}
}
return
batchEvent
}
public
async
getStateRootBatchByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
StateRootBatch
|
null
>
{
const
stateBatchAppendedEvent
=
await
this
.
getStateBatchAppendedEventByTransactionIndex
(
transactionIndex
)
if
(
stateBatchAppendedEvent
===
null
)
{
return
null
}
const
stateBatchTransaction
=
await
stateBatchAppendedEvent
.
getTransaction
()
const
[
stateRoots
]
=
this
.
contracts
.
l1
.
StateCommitmentChain
.
interface
.
decodeFunctionData
(
'
appendStateBatch
'
,
stateBatchTransaction
.
data
)
return
{
blockNumber
:
stateBatchAppendedEvent
.
blockNumber
,
stateRoots
,
header
:
{
batchIndex
:
stateBatchAppendedEvent
.
args
.
_batchIndex
,
batchRoot
:
stateBatchAppendedEvent
.
args
.
_batchRoot
,
batchSize
:
stateBatchAppendedEvent
.
args
.
_batchSize
,
prevTotalElements
:
stateBatchAppendedEvent
.
args
.
_prevTotalElements
,
extraData
:
stateBatchAppendedEvent
.
args
.
_extraData
,
},
}
}
}
packages/sdk/src/interfaces/cross-chain-provider.ts
View file @
7ee8d8d3
import
{
BigNumber
}
from
'
ethers
'
import
{
Event
,
BigNumber
}
from
'
ethers
'
import
{
Provider
,
BlockTag
}
from
'
@ethersproject/abstract-provider
'
import
{
MessageLike
,
...
...
@@ -12,6 +12,8 @@ import {
OEContracts
,
MessageReceipt
,
CustomBridges
,
StateRoot
,
StateRootBatch
,
}
from
'
./types
'
/**
...
...
@@ -225,4 +227,62 @@ export interface ICrossChainProvider {
* @returns Estimated amount of time remaining (in blocks) before the message can be executed.
*/
estimateMessageWaitTimeBlocks
(
message
:
MessageLike
):
Promise
<
number
>
/**
* Queries the current challenge period in seconds from the StateCommitmentChain.
*
* @returns Current challenge period in seconds.
*/
getChallengePeriodSeconds
():
Promise
<
number
>
/**
* Queries the current challenge period in blocks from the StateCommitmentChain. Estimation is
* based on the challenge period in seconds divided by the L1 block time.
*
* @returns Current challenge period in blocks.
*/
getChallengePeriodBlocks
():
Promise
<
number
>
/**
* Returns the state root that corresponds to a given message. This is the state root for the
* block in which the transaction was included, as published to the StateCommitmentChain. If the
* state root for the given message has not been published yet, this function returns null.
*
* @param message Message to find a state root for.
* @returns State root for the block in which the message was created.
*/
getMessageStateRoot
(
message
:
MessageLike
):
Promise
<
StateRoot
|
null
>
/**
* Returns the StateBatchAppended event that was emitted when the batch with a given index was
* created. Returns null if no such event exists (the batch has not been submitted).
*
* @param batchIndex Index of the batch to find an event for.
* @returns StateBatchAppended event for the batch, or null if no such batch exists.
*/
getStateBatchAppendedEventByBatchIndex
(
batchIndex
:
number
):
Promise
<
Event
|
null
>
/**
* Returns the StateBatchAppended event for the batch that includes the transaction with the
* given index. Returns null if no such event exists.
*
* @param transactionIndex Index of the L2 transaction to find an event for.
* @returns StateBatchAppended event for the batch that includes the given transaction by index.
*/
getStateBatchAppendedEventByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
Event
|
null
>
/**
* Returns information about the state root batch that included the state root for the given
* transaction by index. Returns null if no such state root has been published yet.
*
* @param transactionIndex Index of the L2 transaction to find a state root batch for.
* @returns State root batch for the given transaction index, or null if none exists yet.
*/
getStateRootBatchByTransactionIndex
(
transactionIndex
:
number
):
Promise
<
StateRootBatch
|
null
>
}
packages/sdk/src/interfaces/types.ts
View file @
7ee8d8d3
...
...
@@ -212,9 +212,19 @@ export interface StateRootBatchHeader {
}
/**
* State root batch, including header and actual state roots.
* Information about a state root, including header, block number, and root iself.
*/
export
interface
StateRoot
{
blockNumber
:
number
header
:
StateRootBatchHeader
stateRoot
:
string
}
/**
* Information about a batch of state roots.
*/
export
interface
StateRootBatch
{
blockNumber
:
number
header
:
StateRootBatchHeader
stateRoots
:
string
[]
}
...
...
packages/sdk/test/contracts/MockSCC.sol
0 → 100644
View file @
7ee8d8d3
pragma solidity ^0.8.9;
contract MockSCC {
event StateBatchAppended(
uint256 indexed _batchIndex,
bytes32 _batchRoot,
uint256 _batchSize,
uint256 _prevTotalElements,
bytes _extraData
);
struct StateBatchAppendedArgs {
uint256 batchIndex;
bytes32 batchRoot;
uint256 batchSize;
uint256 prevTotalElements;
bytes extraData;
}
// Window in seconds, will resolve to 100 blocks.
uint256 public FRAUD_PROOF_WINDOW = 1500;
uint256 public batches = 0;
StateBatchAppendedArgs public sbaParams;
function getTotalBatches() public view returns (uint256) {
return batches;
}
function setSBAParams(
StateBatchAppendedArgs memory _args
) public {
sbaParams = _args;
}
function appendStateBatch(
bytes32[] memory _roots,
uint256 _shouldStartAtIndex
) public {
batches++;
emit StateBatchAppended(
sbaParams.batchIndex,
sbaParams.batchRoot,
sbaParams.batchSize,
sbaParams.prevTotalElements,
sbaParams.extraData
);
}
}
packages/sdk/test/cross-chain-provider.spec.ts
View file @
7ee8d8d3
This diff is collapsed.
Click to expand it.
packages/sdk/test/helpers/constants.ts
0 → 100644
View file @
7ee8d8d3
export
const
DUMMY_MESSAGE
=
{
target
:
'
0x
'
+
'
11
'
.
repeat
(
20
),
sender
:
'
0x
'
+
'
22
'
.
repeat
(
20
),
message
:
'
0x
'
+
'
33
'
.
repeat
(
64
),
messageNonce
:
1234
,
gasLimit
:
100000
,
}
packages/sdk/test/helpers/index.ts
0 → 100644
View file @
7ee8d8d3
export
*
from
'
./constants
'
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