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
92e070e3
Commit
92e070e3
authored
Jun 23, 2023
by
Kevin Chen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Call getL2Output to get output by index instead of using event logs.
parent
5ce5bbdf
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
43 additions
and
173 deletions
+43
-173
helpers.ts
packages/fault-detector/src/helpers.ts
+20
-143
service.ts
packages/fault-detector/src/service.ts
+11
-17
helpers.spec.ts
packages/fault-detector/test/helpers.spec.ts
+12
-13
No files found.
packages/fault-detector/src/helpers.ts
View file @
92e070e3
import
{
Contract
}
from
'
ethers
'
import
{
Logger
}
from
'
@eth-optimism/common-ts
'
import
{
BedrockOutputData
}
from
'
@eth-optimism/core-utils
'
/**
* Partial event interface, meant to reduce the size of the event cache to avoid
* running out of memory.
*/
export
interface
PartialEvent
{
blockNumber
:
number
transactionHash
:
string
args
:
any
}
// Event caching is necessary for the fault detector to work properly with Geth.
const
caches
:
{
[
contractAddress
:
string
]:
{
highestBlock
:
number
eventCache
:
Map
<
string
,
PartialEvent
>
}
}
=
{}
/**
* Retrieves the cache for a given address.
*
* @param address Address to get cache for.
* @returns Address cache.
*/
const
getCache
=
(
address
:
string
):
{
highestBlock
:
number
eventCache
:
Map
<
string
,
PartialEvent
>
}
=>
{
if
(
!
caches
[
address
])
{
caches
[
address
]
=
{
highestBlock
:
-
1
,
eventCache
:
new
Map
(),
}
}
return
caches
[
address
]
}
/**
* Updates the event cache for a contract and event.
*
* @param contract Contract to update cache for.
* @param filter Event filter to use.
*/
export
const
updateOracleCache
=
async
(
oracle
:
Contract
,
logger
?:
Logger
):
Promise
<
void
>
=>
{
const
cache
=
getCache
(
oracle
.
address
)
const
endBlock
=
await
oracle
.
provider
.
getBlockNumber
()
logger
?.
info
(
'
visiting uncached oracle events for range
'
,
{
node
:
'
l1
'
,
cachedUntilBlock
:
cache
.
highestBlock
,
latestBlock
:
endBlock
,
})
let
failures
=
[]
let
currentBlock
=
cache
.
highestBlock
+
1
let
step
=
endBlock
-
currentBlock
while
(
currentBlock
<
endBlock
)
{
try
{
logger
?.
info
(
'
polling events for range
'
,
{
node
:
'
l1
'
,
startBlock
:
currentBlock
,
blockRangeSize
:
step
,
})
const
events
=
await
oracle
.
queryFilter
(
oracle
.
filters
.
OutputProposed
(),
currentBlock
,
currentBlock
+
step
)
// Throw the events into the cache.
for
(
const
event
of
events
)
{
cache
.
eventCache
[
event
.
args
.
l2OutputIndex
.
toNumber
()]
=
{
blockNumber
:
event
.
blockNumber
,
transactionHash
:
event
.
transactionHash
,
args
:
event
.
args
,
}
}
// Update the current block and increase the step size for the next iteration.
currentBlock
+=
step
step
=
Math
.
ceil
(
step
*
2
)
}
catch
(
err
)
{
logger
?.
warn
(
'
error fetching events
'
,
{
err
,
node
:
'
l1
'
,
section
:
'
getLogs
'
,
})
// Might happen if we're querying too large an event range.
step
=
Math
.
floor
(
step
/
2
)
// When the step gets down to zero, we're pretty much guaranteed that range size isn't the
// problem. If we get three failures like this in a row then we should just give up.
if
(
step
===
0
)
{
failures
.
push
(
err
)
}
else
{
failures
=
[]
}
// We've failed 5 times in a row, we're probably stuck.
if
(
failures
.
length
>=
5
)
{
logger
?.
fatal
(
'
unable to fetch oracle events
'
,
{
errors
:
failures
})
throw
new
Error
(
'
failed to update event cache
'
)
}
}
}
// Update the highest block.
cache
.
highestBlock
=
endBlock
logger
?.
info
(
'
done caching oracle events
'
)
}
/**
* Finds the Event that corresponds to a given state batch by index.
* Finds the BedrockOutputData that corresponds to a given output index.
*
* @param oracle Output oracle contract
* @param index
State batch
index to search for.
* @returns
Event corresponding to the batch
.
* @param index
Output
index to search for.
* @returns
BedrockOutputData corresponding to the output index
.
*/
export
const
find
EventForStateBatch
=
async
(
export
const
find
OutputForIndex
=
async
(
oracle
:
Contract
,
index
:
number
,
logger
?:
Logger
):
Promise
<
PartialEvent
>
=>
{
const
cache
=
getCache
(
oracle
.
address
)
// Try to find the event in cache first.
if
(
cache
.
eventCache
[
index
])
{
return
cache
.
eventCache
[
index
]
}
// Update the event cache if we don't have the event.
logger
?.
info
(
'
event not cached for index. warming cache...
'
,
{
index
})
await
updateOracleCache
(
oracle
,
logger
)
// Event better be in cache now!
if
(
cache
.
eventCache
[
index
]
===
undefined
)
{
logger
?.
fatal
(
'
expected event for index!
'
,
{
index
})
throw
new
Error
(
`unable to find event for batch
${
index
}
`
)
):
Promise
<
BedrockOutputData
>
=>
{
try
{
const
proposal
=
await
oracle
.
getL2Output
(
index
)
return
{
outputRoot
:
proposal
.
outputRoot
,
l1Timestamp
:
proposal
.
timestamp
.
toNumber
(),
l2BlockNumber
:
proposal
.
l2BlockNumber
.
toNumber
(),
l2OutputIndex
:
index
,
}
}
catch
(
err
)
{
logger
?.
fatal
(
'
error when calling L2OuputOracle.getL2Output
'
,
{
errors
:
err
})
throw
new
Error
(
`unable to find output for index
${
index
}
`
)
}
return
cache
.
eventCache
[
index
]
}
/**
...
...
@@ -170,10 +47,10 @@ export const findFirstUnfinalizedStateBatchIndex = async (
let
hi
=
totalBatches
while
(
lo
!==
hi
)
{
const
mid
=
Math
.
floor
((
lo
+
hi
)
/
2
)
const
event
=
await
findEventForStateBatch
(
oracle
,
mid
,
logger
)
const
block
=
await
oracle
.
provider
.
getBlock
(
event
.
blockNumber
)
const
outputData
=
await
findOutputForIndex
(
oracle
,
mid
,
logger
)
const
eventTimestamp
=
outputData
.
l1Timestamp
if
(
block
.
t
imestamp
+
fpw
<
latestBlock
.
timestamp
)
{
if
(
eventT
imestamp
+
fpw
<
latestBlock
.
timestamp
)
{
lo
=
mid
+
1
}
else
{
hi
=
mid
...
...
packages/fault-detector/src/service.ts
View file @
92e070e3
...
...
@@ -6,7 +6,7 @@ import {
validators
,
waitForProvider
,
}
from
'
@eth-optimism/common-ts
'
import
{
getChainId
,
sleep
,
toRpcHexString
}
from
'
@eth-optimism/core-utils
'
import
{
BedrockOutputData
,
getChainId
,
sleep
,
toRpcHexString
}
from
'
@eth-optimism/core-utils
'
import
{
config
}
from
'
dotenv
'
import
{
CONTRACT_ADDRESSES
,
...
...
@@ -22,9 +22,7 @@ import dateformat from 'dateformat'
import
{
version
}
from
'
../package.json
'
import
{
findFirstUnfinalizedStateBatchIndex
,
findEventForStateBatch
,
PartialEvent
,
updateOracleCache
,
findOutputForIndex
,
}
from
'
./helpers
'
type
Options
=
{
...
...
@@ -196,10 +194,6 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
this
.
state
.
outputOracle
=
this
.
state
.
messenger
.
contracts
.
l1
.
L2OutputOracle
// Populate the event cache.
this
.
logger
.
info
(
'
warming event cache, this might take a while...
'
)
await
updateOracleCache
(
this
.
state
.
outputOracle
,
this
.
logger
)
// Figure out where to start syncing from.
if
(
this
.
options
.
startBatchIndex
===
-
1
)
{
this
.
logger
.
info
(
'
finding appropriate starting unfinalized batch
'
)
...
...
@@ -275,23 +269,23 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
latestBatchIndex
,
})
let
event
:
PartialEvent
let
outputData
:
BedrockOutputData
try
{
event
=
await
findEventForStateBatch
(
outputData
=
await
findOutputForIndex
(
this
.
state
.
outputOracle
,
this
.
state
.
currentBatchIndex
,
this
.
logger
)
}
catch
(
err
)
{
this
.
logger
.
error
(
'
failed to fetch
even
t associated with batch
'
,
{
this
.
logger
.
error
(
'
failed to fetch
outpu
t associated with batch
'
,
{
error
:
err
,
node
:
'
l1
'
,
section
:
'
find
EventForStateBatch
'
,
section
:
'
find
OutputForIndex
'
,
batchIndex
:
this
.
state
.
currentBatchIndex
,
})
this
.
metrics
.
nodeConnectionFailures
.
inc
({
layer
:
'
l1
'
,
section
:
'
find
EventForStateBatch
'
,
section
:
'
find
OutputForIndex
'
,
})
await
sleep
(
15000
)
return
...
...
@@ -314,7 +308,7 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
return
}
const
outputBlockNumber
=
event
.
args
.
l2BlockNumber
.
toNumber
()
const
outputBlockNumber
=
outputData
.
l2BlockNumber
if
(
latestBlock
<
outputBlockNumber
)
{
this
.
logger
.
info
(
'
L2 node is behind, waiting for sync...
'
,
{
l2BlockHeight
:
latestBlock
,
...
...
@@ -377,18 +371,18 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
]
)
if
(
outputRoot
!==
event
.
args
.
outputRoot
)
{
if
(
outputRoot
!==
outputData
.
outputRoot
)
{
this
.
state
.
diverged
=
true
this
.
metrics
.
isCurrentlyMismatched
.
set
(
1
)
this
.
logger
.
error
(
'
state root mismatch
'
,
{
blockNumber
:
outputBlock
.
number
,
expectedStateRoot
:
event
.
args
.
outputRoot
,
expectedStateRoot
:
outputData
.
outputRoot
,
actualStateRoot
:
outputRoot
,
finalizationTime
:
dateformat
(
new
Date
(
(
ethers
.
BigNumber
.
from
(
outputBlock
.
timestamp
).
toNumber
()
+
this
.
state
.
faultProofWindow
)
*
1000
1000
),
'
mmmm dS, yyyy, h:MM:ss TT
'
),
...
...
packages/fault-detector/test/helpers.spec.ts
View file @
92e070e3
...
...
@@ -6,7 +6,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import
{
expect
}
from
'
./setup
'
import
{
find
EventForStateBatch
,
find
OutputForIndex
,
findFirstUnfinalizedStateBatchIndex
,
}
from
'
../src
'
...
...
@@ -40,7 +40,7 @@ describe('helpers', () => {
)
})
describe
(
'
find
EventForStateBatch
'
,
()
=>
{
describe
(
'
find
OutputForIndex
'
,
()
=>
{
describe
(
'
when the event exists once
'
,
()
=>
{
beforeEach
(
async
()
=>
{
const
latestBlock
=
await
hre
.
ethers
.
provider
.
getBlock
(
'
latest
'
)
...
...
@@ -61,17 +61,17 @@ describe('helpers', () => {
})
it
(
'
should return the event
'
,
async
()
=>
{
const
event
=
await
find
EventForStateBatch
(
L2OutputOracle
,
0
)
const
event
=
await
find
OutputForIndex
(
L2OutputOracle
,
0
)
expect
(
event
.
args
.
l2OutputIndex
).
to
.
equal
(
0
)
expect
(
event
.
l2OutputIndex
).
to
.
equal
(
0
)
})
})
describe
(
'
when the event does not exist
'
,
()
=>
{
it
(
'
should throw an error
'
,
async
()
=>
{
await
expect
(
find
EventForStateBatch
(
L2OutputOracle
,
0
)
).
to
.
eventually
.
be
.
rejectedWith
(
'
unable to find
event for batch
'
)
find
OutputForIndex
(
L2OutputOracle
,
0
)
).
to
.
eventually
.
be
.
rejectedWith
(
'
unable to find
output for index
'
)
})
})
})
...
...
@@ -81,7 +81,6 @@ describe('helpers', () => {
beforeEach
(
async
()
=>
{
const
latestBlock
=
await
hre
.
ethers
.
provider
.
getBlock
(
'
latest
'
)
const
params
=
{
_outputRoot
:
utils
.
formatBytes32String
(
'
testhash
'
),
_l2BlockNumber
:
deployConfig
.
l2OutputOracleStartingBlockNumber
+
deployConfig
.
l2OutputOracleSubmissionInterval
,
...
...
@@ -89,7 +88,7 @@ describe('helpers', () => {
_l1BlockNumber
:
latestBlock
.
number
,
}
await
L2OutputOracle
.
proposeL2Output
(
params
.
_outputRoot
,
utils
.
formatBytes32String
(
'
outputRoot1
'
)
,
params
.
_l2BlockNumber
,
params
.
_l1BlockHash
,
params
.
_l1BlockNumber
...
...
@@ -101,15 +100,15 @@ describe('helpers', () => {
])
await
L2OutputOracle
.
proposeL2Output
(
params
.
_outputRoot
,
utils
.
formatBytes32String
(
'
outputRoot2
'
)
,
params
.
_l2BlockNumber
+
deployConfig
.
l2OutputOracleSubmissionInterval
,
params
.
_l1BlockHash
,
params
.
_l1BlockNumber
)
await
L2OutputOracle
.
proposeL2Output
(
params
.
_outputRoot
,
utils
.
formatBytes32String
(
'
outputRoot3
'
)
,
params
.
_l2BlockNumber
+
deployConfig
.
l2OutputOracleSubmissionInterval
*
2
,
deployConfig
.
l2OutputOracleSubmissionInterval
*
2
,
params
.
_l1BlockHash
,
params
.
_l1BlockNumber
)
...
...
@@ -151,7 +150,7 @@ describe('helpers', () => {
await
L2OutputOracle
.
proposeL2Output
(
params
.
_outputRoot
,
params
.
_l2BlockNumber
+
deployConfig
.
l2OutputOracleSubmissionInterval
*
2
,
deployConfig
.
l2OutputOracleSubmissionInterval
*
2
,
params
.
_l1BlockHash
,
params
.
_l1BlockNumber
)
...
...
@@ -193,7 +192,7 @@ describe('helpers', () => {
await
L2OutputOracle
.
proposeL2Output
(
params
.
_outputRoot
,
params
.
_l2BlockNumber
+
deployConfig
.
l2OutputOracleSubmissionInterval
*
2
,
deployConfig
.
l2OutputOracleSubmissionInterval
*
2
,
params
.
_l1BlockHash
,
params
.
_l1BlockNumber
)
...
...
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