Commit 14c4c50b authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into aj/kick-bad-peers

parents e4a02287 a48e53c1
---
'@eth-optimism/contracts-bedrock': minor
'@eth-optimism/contracts': minor
'@eth-optimism/sdk': minor
---
Update sdk contract addresses for bedrock
......@@ -293,7 +293,7 @@ jobs:
contracts-bedrock-coverage:
docker:
- image: ethereumoptimism/ci-builder:latest
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
resource_class: large
steps:
- checkout
......@@ -553,7 +553,7 @@ jobs:
sdk-next-tests:
docker:
- image: ethereumoptimism/ci-builder:latest
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
resource_class: large
steps:
- checkout
......@@ -700,7 +700,7 @@ jobs:
atst-tests:
docker:
- image: ethereumoptimism/ci-builder:latest
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
resource_class: large
steps:
- checkout
......
......@@ -5,12 +5,14 @@
"main": "index.js",
"scripts": {
"dev": "vuepress dev src",
"build": "vuepress build src"
"build": "vuepress build src",
"preview": "yarn build && serve -s src/.vuepress/dist -p 8080"
},
"license": "MIT",
"devDependencies": {
"@vuepress/plugin-medium-zoom": "^1.8.2",
"@vuepress/plugin-pwa": "^1.9.7",
"serve": "^14.2.0",
"vuepress": "^1.8.2",
"vuepress-plugin-plausible-analytics": "^0.2.1",
"vuepress-theme-hope": "^1.22.0"
......
......@@ -13,7 +13,12 @@ export default ({ router }) => {
// the refresh button. For more details see:
// https://linear.app/optimism/issue/FE-1003/investigate-archive-issue-on-docs
const registerAutoReload = () => {
event.$on('sw-updated', e => e.skipWaiting().then(() => {
location.reload(true);
}))
event.$on('sw-updated', e => {
e.skipWaiting().then(() =>
{
if (typeof location !== 'undefined')
location.reload(true);
}
)
})
}
This diff is collapsed.
......@@ -51,6 +51,8 @@ func main() {
return err
}
log.Info("Requires an archive node")
log.Info("Connecting to AddressManager", "address", addresses.AddressManager)
addressManager, err := bindings.NewAddressManager(addresses.AddressManager, clients.L1Client)
if err != nil {
......@@ -70,28 +72,42 @@ func main() {
time.Sleep(3 * time.Second)
}
shutoffBlock, err := addressManager.GetAddress(&bind.CallOpts{}, "DTL_SHUTOFF_BLOCK")
if err != nil {
return err
}
shutoffHeight := shutoffBlock.Big()
log.Info("Connecting to CanonicalTransactionChain", "address", addresses.CanonicalTransactionChain)
ctc, err := legacy_bindings.NewCanonicalTransactionChain(addresses.CanonicalTransactionChain, clients.L1Client)
if err != nil {
return err
}
queueLength, err := ctc.GetQueueLength(&bind.CallOpts{})
queueLength, err := ctc.GetQueueLength(&bind.CallOpts{
BlockNumber: shutoffHeight,
})
if err != nil {
return err
}
totalElements, err := ctc.GetTotalElements(&bind.CallOpts{})
totalElements, err := ctc.GetTotalElements(&bind.CallOpts{
BlockNumber: shutoffHeight,
})
if err != nil {
return err
}
totalBatches, err := ctc.GetTotalBatches(&bind.CallOpts{})
totalBatches, err := ctc.GetTotalBatches(&bind.CallOpts{
BlockNumber: shutoffHeight,
})
if err != nil {
return err
}
pending, err := ctc.GetNumPendingQueueElements(&bind.CallOpts{})
pending, err := ctc.GetNumPendingQueueElements(&bind.CallOpts{
BlockNumber: shutoffHeight,
})
if err != nil {
return err
}
......@@ -131,6 +147,7 @@ func main() {
if err != nil {
return err
}
// If the queue origin is l1, then it is a deposit.
if json.QueueOrigin == "l1" {
if json.QueueIndex == nil {
......@@ -138,12 +155,26 @@ func main() {
return fmt.Errorf("queue index is nil for tx %s at height %d", hash.Hex(), blockNumber)
}
queueIndex := uint64(*json.QueueIndex)
if json.L1BlockNumber == nil {
// This should never happen.
return fmt.Errorf("L1 block number is nil for tx %s at height %d", hash.Hex(), blockNumber)
}
l1BlockNumber := json.L1BlockNumber.ToInt()
log.Info("Deposit found", "l2-block", blockNumber, "l1-block", l1BlockNumber, "queue-index", queueIndex)
// This should never happen
if json.L1BlockNumber.ToInt().Uint64() > shutoffHeight.Uint64() {
log.Warn("Lost deposit")
return fmt.Errorf("Lost deposit: %s", hash.Hex())
}
// Check to see if the final deposit was ingested. Subtract 1 here to handle zero
// indexing.
if queueIndex == queueLength.Uint64()-1 {
log.Info("Found final deposit in l2geth", "queue-index", queueIndex)
break
}
// If the queue index is less than the queue length, then not all deposits have
// been ingested by l2geth yet. This means that we need to reset the blocknumber
// to the latest block number to restart walking backwards to find deposits that
......@@ -258,7 +289,7 @@ func waitForTotalElements(wg *sync.WaitGroup, contract RollupContract, client *e
log.Info(
"Waiting for elements to be submitted",
"name", name,
"count", totalElements.Uint64()-bn,
"count", bn-totalElements.Uint64(),
"height", bn,
"total-elements", totalElements.Uint64(),
)
......
......@@ -368,6 +368,7 @@ func (s *SyncClient) onRangeRequest(ctx context.Context, req rangeRequest) {
}
if _, ok := s.inFlight[num]; ok {
log.Debug("request still in-flight, not rescheduling sync request", "num", num)
continue // request still in flight
}
pr := peerRequest{num: num, complete: new(atomic.Bool)}
......
......@@ -3,7 +3,9 @@ package p2p
import (
"context"
"math/big"
"sync"
"testing"
"time"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
......@@ -29,7 +31,42 @@ func (fn mockPayloadFn) PayloadByNumber(_ context.Context, number uint64) (*eth.
var _ L2Chain = mockPayloadFn(nil)
func setupSyncTestData(length uint64) (*rollup.Config, map[uint64]*eth.ExecutionPayload, func(i uint64) eth.L2BlockRef) {
type syncTestData struct {
sync.RWMutex
payloads map[uint64]*eth.ExecutionPayload
}
func (s *syncTestData) getPayload(i uint64) (payload *eth.ExecutionPayload, ok bool) {
s.RLock()
defer s.RUnlock()
payload, ok = s.payloads[i]
return payload, ok
}
func (s *syncTestData) deletePayload(i uint64) {
s.Lock()
defer s.Unlock()
delete(s.payloads, i)
}
func (s *syncTestData) addPayload(payload *eth.ExecutionPayload) {
s.Lock()
defer s.Unlock()
s.payloads[uint64(payload.BlockNumber)] = payload
}
func (s *syncTestData) getBlockRef(i uint64) eth.L2BlockRef {
s.RLock()
defer s.RUnlock()
return eth.L2BlockRef{
Hash: s.payloads[i].BlockHash,
Number: uint64(s.payloads[i].BlockNumber),
ParentHash: s.payloads[i].ParentHash,
Time: uint64(s.payloads[i].Timestamp),
}
}
func setupSyncTestData(length uint64) (*rollup.Config, *syncTestData) {
// minimal rollup config to build mock blocks & verify their time.
cfg := &rollup.Config{
Genesis: rollup.Genesis{
......@@ -57,15 +94,7 @@ func setupSyncTestData(length uint64) (*rollup.Config, map[uint64]*eth.Execution
payloads[i] = payload
}
l2Ref := func(i uint64) eth.L2BlockRef {
return eth.L2BlockRef{
Hash: payloads[i].BlockHash,
Number: uint64(payloads[i].BlockNumber),
ParentHash: payloads[i].ParentHash,
Time: uint64(payloads[i].Timestamp),
}
}
return cfg, payloads, l2Ref
return cfg, &syncTestData{payloads: payloads}
}
func TestSinglePeerSync(t *testing.T) {
......@@ -73,11 +102,11 @@ func TestSinglePeerSync(t *testing.T) {
log := testlog.Logger(t, log.LvlError)
cfg, payloads, l2Ref := setupSyncTestData(25)
cfg, payloads := setupSyncTestData(25)
// Serving payloads: just load them from the map, if they exist
servePayload := mockPayloadFn(func(n uint64) (*eth.ExecutionPayload, error) {
p, ok := payloads[n]
p, ok := payloads.getPayload(n)
if !ok {
return nil, ethereum.NotFound
}
......@@ -116,13 +145,13 @@ func TestSinglePeerSync(t *testing.T) {
defer cl.Close()
// request to start syncing between 10 and 20
require.NoError(t, cl.RequestL2Range(ctx, l2Ref(10), l2Ref(20)))
require.NoError(t, cl.RequestL2Range(ctx, payloads.getBlockRef(10), payloads.getBlockRef(20)))
// and wait for the sync results to come in (in reverse order)
for i := uint64(19); i > 10; i-- {
p := <-received
require.Equal(t, uint64(p.BlockNumber), i, "expecting payloads in order")
exp, ok := payloads[uint64(p.BlockNumber)]
exp, ok := payloads.getPayload(uint64(p.BlockNumber))
require.True(t, ok, "expecting known payload")
require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
}
......@@ -131,14 +160,14 @@ func TestSinglePeerSync(t *testing.T) {
func TestMultiPeerSync(t *testing.T) {
t.Parallel() // Takes a while, but can run in parallel
log := testlog.Logger(t, log.LvlError)
log := testlog.Logger(t, log.LvlDebug)
cfg, payloads, l2Ref := setupSyncTestData(100)
cfg, payloads := setupSyncTestData(100)
setupPeer := func(ctx context.Context, h host.Host) (*SyncClient, chan *eth.ExecutionPayload) {
// Serving payloads: just load them from the map, if they exist
servePayload := mockPayloadFn(func(n uint64) (*eth.ExecutionPayload, error) {
p, ok := payloads[n]
p, ok := payloads.getPayload(n)
if !ok {
return nil, ethereum.NotFound
}
......@@ -190,23 +219,25 @@ func TestMultiPeerSync(t *testing.T) {
clC.Start()
defer clC.Close()
// request to start syncing between 10 and 100
require.NoError(t, clA.RequestL2Range(ctx, l2Ref(10), l2Ref(90)))
// request to start syncing between 10 and 90
require.NoError(t, clA.RequestL2Range(ctx, payloads.getBlockRef(10), payloads.getBlockRef(90)))
// With such large range to request we are going to hit the rate-limits of B and C,
// but that means we'll balance the work between the peers.
p := <-recvA
exp, ok := payloads[uint64(p.BlockNumber)]
require.True(t, ok, "expecting known payload")
require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
for i := uint64(89); i > 10; i-- { // wait for all payloads
p := <-recvA
exp, ok := payloads.getPayload(uint64(p.BlockNumber))
require.True(t, ok, "expecting known payload")
require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
}
// now see if B can sync a range, and fill the gap with a re-request
bl25 := payloads[25] // temporarily remove it from the available payloads. This will create a gap
delete(payloads, uint64(25))
require.NoError(t, clB.RequestL2Range(ctx, l2Ref(20), l2Ref(30)))
bl25, _ := payloads.getPayload(25) // temporarily remove it from the available payloads. This will create a gap
payloads.deletePayload(25)
require.NoError(t, clB.RequestL2Range(ctx, payloads.getBlockRef(20), payloads.getBlockRef(30)))
for i := uint64(29); i > 25; i-- {
p := <-recvB
exp, ok := payloads[uint64(p.BlockNumber)]
exp, ok := payloads.getPayload(uint64(p.BlockNumber))
require.True(t, ok, "expecting known payload")
require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
}
......@@ -215,13 +246,19 @@ func TestMultiPeerSync(t *testing.T) {
// client: WARN failed p2p sync request num=25 err="peer failed to serve request with code 1"
require.Zero(t, len(recvB), "there is a gap, should not see other payloads yet")
// Add back the block
payloads[25] = bl25
payloads.addPayload(bl25)
// race-condition fix: the request for 25 is expected to error, but is marked as complete in the peer-loop.
// But the re-request checks the status in the main loop, and it may thus look like it's still in-flight,
// and thus not run the new request.
// Wait till the failed request is recognized as marked as done, so the re-request actually runs.
for !clB.inFlight[25].Load() {
time.Sleep(time.Second)
}
// And request a range again, 25 is there now, and 21-24 should follow quickly (some may already have been fetched and wait in quarantine)
require.NoError(t, clB.RequestL2Range(ctx, l2Ref(20), l2Ref(26)))
require.NoError(t, clB.RequestL2Range(ctx, payloads.getBlockRef(20), payloads.getBlockRef(26)))
for i := uint64(25); i > 20; i-- {
p := <-recvB
exp, ok := payloads[uint64(p.BlockNumber)]
exp, ok := payloads.getPayload(uint64(p.BlockNumber))
require.True(t, ok, "expecting known payload")
require.Equal(t, exp.BlockHash, p.BlockHash, "expecting the correct payload")
}
......
......@@ -187,7 +187,7 @@ func NewL2OutputSubmitter(cfg Config, l log.Logger, m metrics.Metricer) (*L2Outp
l2ooContract, err := bindings.NewL2OutputOracleCaller(cfg.L2OutputOracleAddr, cfg.L1Client)
if err != nil {
cancel()
return nil, err
return nil, fmt.Errorf("failed to create L2OO at address %s: %w", cfg.L2OutputOracleAddr, err)
}
cCtx, cCancel := context.WithTimeout(ctx, cfg.NetworkTimeout)
......
......@@ -14,6 +14,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
......@@ -379,6 +380,13 @@ func SetBalance(addr common.Address, amount *big.Int) HeadFn {
}
}
func SetCode(addr common.Address, code hexutil.Bytes) HeadFn {
return func(headState *state.StateDB) error {
headState.SetCode(addr, code)
return nil
}
}
func SetNonce(addr common.Address, nonce uint64) HeadFn {
return func(headState *state.StateDB) error {
headState.SetNonce(addr, nonce)
......
......@@ -12,6 +12,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rpc"
......@@ -179,6 +180,10 @@ func addrFlag(name string, usage string) cli.GenericFlag {
return textFlag[*common.Address](name, usage, new(common.Address))
}
func bytesFlag(name string, usage string) cli.GenericFlag {
return textFlag[*hexutil.Bytes](name, usage, new(hexutil.Bytes))
}
func hashFlag(name string, usage string) cli.GenericFlag {
return textFlag[*common.Hash](name, usage, new(common.Hash))
}
......@@ -191,6 +196,10 @@ func addrFlagValue(name string, ctx *cli.Context) common.Address {
return *ctx.Generic(name).(*TextFlag[*common.Address]).Value
}
func bytesFlagValue(name string, ctx *cli.Context) hexutil.Bytes {
return *ctx.Generic(name).(*TextFlag[*hexutil.Bytes]).Value
}
func hashFlagValue(name string, ctx *cli.Context) common.Hash {
return *ctx.Generic(name).(*TextFlag[*common.Hash]).Value
}
......@@ -271,6 +280,17 @@ var (
return ch.RunAndClose(cheat.SetBalance(addrFlagValue("address", ctx), bigFlagValue("balance", ctx)))
}),
}
CheatSetCodeCmd = cli.Command{
Name: "code",
Flags: []cli.Flag{
DataDirFlag,
addrFlag("address", "Address to change code of"),
bytesFlag("code", "New code of the account"),
},
Action: CheatAction(false, func(ctx *cli.Context, ch *cheat.Cheater) error {
return ch.RunAndClose(cheat.SetCode(addrFlagValue("address", ctx), bytesFlagValue("code", ctx)))
}),
}
CheatSetNonceCmd = cli.Command{
Name: "nonce",
Flags: []cli.Flag{
......@@ -440,6 +460,7 @@ var CheatCmd = cli.Command{
Subcommands: []cli.Command{
CheatStorageCmd,
CheatSetBalanceCmd,
CheatSetCodeCmd,
CheatSetNonceCmd,
CheatOvmOwnersCmd,
CheatPrintHeadBlock,
......
......@@ -97,3 +97,11 @@ RUN echo "downloading and verifying Codecov uploader" && \
cp codecov /usr/local/bin/codecov && \
chmod +x /usr/local/bin/codecov && \
rm codecov
RUN echo "downloading mockery tool" && \
mkdir -p mockery-tmp-dir && \
curl -o mockery-tmp-dir/mockery.tar.gz -sL https://github.com/vektra/mockery/releases/download/v2.28.1/mockery_2.28.1_Linux_x86_64.tar.gz && \
tar -xzvf mockery-tmp-dir/mockery.tar.gz -C mockery-tmp-dir && \
cp mockery-tmp-dir/mockery /usr/local/bin/mockery && \
chmod +x /usr/local/bin/mockery && \
rm -rf mockery-tmp-dir
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { StdInvariant } from "forge-std/StdInvariant.sol";
import { Encoding } from "../../libraries/Encoding.sol";
contract Encoding_Converter {
bool public failedRoundtripAToB;
bool public failedRoundtripBToA;
/**
* @notice Takes a pair of integers to be encoded into a versioned nonce with the
* Encoding library and then decoded and updates the test contract's state
* indicating if the round trip encoding failed.
*/
function convertRoundTripAToB(uint240 _nonce, uint16 _version) external {
// Encode the nonce and version
uint256 encodedVersionedNonce = Encoding.encodeVersionedNonce(_nonce, _version);
// Decode the nonce and version
uint240 decodedNonce;
uint16 decodedVersion;
(decodedNonce, decodedVersion) = Encoding.decodeVersionedNonce(encodedVersionedNonce);
// If our round trip encoding did not return the original result, set our state.
if ((decodedNonce != _nonce) || (decodedVersion != _version)) {
failedRoundtripAToB = true;
}
}
/**
* @notice Takes an integer representing a packed version and nonce and attempts
* to decode them using the Encoding library before re-encoding and updates
* the test contract's state indicating if the round trip encoding failed.
*/
function convertRoundTripBToA(uint256 _versionedNonce) external {
// Decode the nonce and version
uint240 decodedNonce;
uint16 decodedVersion;
(decodedNonce, decodedVersion) = Encoding.decodeVersionedNonce(_versionedNonce);
// Encode the nonce and version
uint256 encodedVersionedNonce = Encoding.encodeVersionedNonce(decodedNonce, decodedVersion);
// If our round trip encoding did not return the original result, set our state.
if (encodedVersionedNonce != _versionedNonce) {
failedRoundtripBToA = true;
}
}
}
contract Encoding_Invariant is StdInvariant, Test {
Encoding_Converter internal actor;
function setUp() public {
// Create a converter actor.
actor = new Encoding_Converter();
targetContract(address(actor));
bytes4[] memory selectors = new bytes4[](2);
selectors[0] = actor.convertRoundTripAToB.selector;
selectors[1] = actor.convertRoundTripBToA.selector;
FuzzSelector memory selector = FuzzSelector({ addr: address(actor), selectors: selectors });
targetSelector(selector);
}
/**
* @custom:invariant `convertRoundTripAToB` never fails.
*
* Asserts that a raw versioned nonce can be encoded / decoded to reach the same raw value.
*/
function invariant_round_trip_encoding_AToB() external {
// ASSERTION: The round trip encoding done in testRoundTripAToB(...)
assertEq(actor.failedRoundtripAToB(), false);
}
/**
* @custom:invariant `convertRoundTripBToA` never fails.
*
* Asserts that an encoded versioned nonce can always be decoded / re-encoded to reach
* the same encoded value.
*/
function invariant_round_trip_encoding_BToA() external {
// ASSERTION: The round trip encoding done in testRoundTripBToA should never
// fail.
assertEq(actor.failedRoundtripBToA(), false);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { StdInvariant } from "forge-std/StdInvariant.sol";
import { Encoding } from "../../libraries/Encoding.sol";
import { Hashing } from "../../libraries/Hashing.sol";
contract Hash_CrossDomainHasher {
bool public failedCrossDomainHashHighVersion;
bool public failedCrossDomainHashV0;
bool public failedCrossDomainHashV1;
/**
* @notice Takes the necessary parameters to perform a cross domain hash with a randomly
* generated version. Only schema versions 0 and 1 are supported and all others should revert.
*/
function hashCrossDomainMessageHighVersion(
uint16 _version,
uint240 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
) external {
// generate the versioned nonce
uint256 encodedNonce = Encoding.encodeVersionedNonce(_nonce, _version);
// hash the cross domain message. we don't need to store the result since the function
// validates and should revert if an invalid version (>1) is encoded
Hashing.hashCrossDomainMessage(encodedNonce, _sender, _target, _value, _gasLimit, _data);
// check that execution never makes it this far for an invalid version
if (_version > 1) {
failedCrossDomainHashHighVersion = true;
}
}
/**
* @notice Takes the necessary parameters to perform a cross domain hash using the v0 schema
* and compares the output of a call to the unversioned function to the v0 function directly
*/
function hashCrossDomainMessageV0(
uint240 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
) external {
// generate the versioned nonce with the version set to 0
uint256 encodedNonce = Encoding.encodeVersionedNonce(_nonce, 0);
// hash the cross domain message using the unversioned and versioned functions for
// comparison
bytes32 sampleHash1 = Hashing.hashCrossDomainMessage(
encodedNonce,
_sender,
_target,
_value,
_gasLimit,
_data
);
bytes32 sampleHash2 = Hashing.hashCrossDomainMessageV0(
_target,
_sender,
_data,
encodedNonce
);
// check that the output of both functions matches
if (sampleHash1 != sampleHash2) {
failedCrossDomainHashV0 = true;
}
}
/**
* @notice Takes the necessary parameters to perform a cross domain hash using the v1 schema
* and compares the output of a call to the unversioned function to the v1 function directly
*/
function hashCrossDomainMessageV1(
uint240 _nonce,
address _sender,
address _target,
uint256 _value,
uint256 _gasLimit,
bytes memory _data
) external {
// generate the versioned nonce with the version set to 1
uint256 encodedNonce = Encoding.encodeVersionedNonce(_nonce, 1);
// hash the cross domain message using the unversioned and versioned functions for
// comparison
bytes32 sampleHash1 = Hashing.hashCrossDomainMessage(
encodedNonce,
_sender,
_target,
_value,
_gasLimit,
_data
);
bytes32 sampleHash2 = Hashing.hashCrossDomainMessageV1(
encodedNonce,
_sender,
_target,
_value,
_gasLimit,
_data
);
// check that the output of both functions matches
if (sampleHash1 != sampleHash2) {
failedCrossDomainHashV1 = true;
}
}
}
contract Hashing_Invariant is StdInvariant, Test {
Hash_CrossDomainHasher internal actor;
function setUp() public {
// Create a hasher actor.
actor = new Hash_CrossDomainHasher();
targetContract(address(actor));
bytes4[] memory selectors = new bytes4[](3);
selectors[0] = actor.hashCrossDomainMessageHighVersion.selector;
selectors[1] = actor.hashCrossDomainMessageV0.selector;
selectors[2] = actor.hashCrossDomainMessageV1.selector;
FuzzSelector memory selector = FuzzSelector({ addr: address(actor), selectors: selectors });
targetSelector(selector);
}
/**
* @custom:invariant `hashCrossDomainMessage` reverts if `version` is > `1`.
*
* The `hashCrossDomainMessage` function should always revert if the `version` passed is > `1`.
*/
function invariant_hash_xdomain_msg_high_version() external {
// ASSERTION: The round trip aliasing done in testRoundTrip(...) should never fail.
assertFalse(actor.failedCrossDomainHashHighVersion());
}
/**
* @custom:invariant `version` = `0`: `hashCrossDomainMessage` and `hashCrossDomainMessageV0`
* are equivalent.
*
* If the version passed is 0, `hashCrossDomainMessage` and `hashCrossDomainMessageV0` should be
* equivalent.
*/
function invariant_hash_xdomain_msg_0() external {
// ASSERTION: A call to hashCrossDomainMessage and hashCrossDomainMessageV0
// should always match when the version passed is 0
assertFalse(actor.failedCrossDomainHashV0());
}
/**
* @custom:invariant `version` = `1`: `hashCrossDomainMessage` and `hashCrossDomainMessageV1`
* are equivalent.
*
* If the version passed is 1, `hashCrossDomainMessage` and `hashCrossDomainMessageV1` should be
* equivalent.
*/
function invariant_hash_xdomain_msg_1() external {
// ASSERTION: A call to hashCrossDomainMessage and hashCrossDomainMessageV1
// should always match when the version passed is 1
assertFalse(actor.failedCrossDomainHashV1());
}
}
# `Encoding` Invariants
## `convertRoundTripAToB` never fails.
**Test:** [`Encoding.t.sol#L76`](../contracts/test/invariants/Encoding.t.sol#L76)
Asserts that a raw versioned nonce can be encoded / decoded to reach the same raw value.
## `convertRoundTripBToA` never fails.
**Test:** [`Encoding.t.sol#L87`](../contracts/test/invariants/Encoding.t.sol#L87)
Asserts that an encoded versioned nonce can always be decoded / re-encoded to reach the same encoded value.
## `testRoundTripAToB` never fails.
**Test:** [`FuzzEncoding.sol#L56`](../contracts/echidna/FuzzEncoding.sol#L56)
......
# `Hashing` Invariants
## `hashCrossDomainMessage` reverts if `version` is > `1`.
**Test:** [`Hashing.t.sol#L141`](../contracts/test/invariants/Hashing.t.sol#L141)
The `hashCrossDomainMessage` function should always revert if the `version` passed is > `1`.
## `version` = `0`: `hashCrossDomainMessage` and `hashCrossDomainMessageV0` are equivalent.
**Test:** [`Hashing.t.sol#L153`](../contracts/test/invariants/Hashing.t.sol#L153)
If the version passed is 0, `hashCrossDomainMessage` and `hashCrossDomainMessageV0` should be equivalent.
## `version` = `1`: `hashCrossDomainMessage` and `hashCrossDomainMessageV1` are equivalent.
**Test:** [`Hashing.t.sol#L166`](../contracts/test/invariants/Hashing.t.sol#L166)
If the version passed is 1, `hashCrossDomainMessage` and `hashCrossDomainMessageV1` should be equivalent.
## `hashCrossDomainMessage` reverts if `version` is > `1`.
**Test:** [`FuzzHashing.sol#L120`](../contracts/echidna/FuzzHashing.sol#L120)
......
......@@ -3,6 +3,20 @@ import {
getDeployedContractDefinition,
} from '@eth-optimism/contracts'
import { predeploys as bedrockPredeploys } from '@eth-optimism/contracts-bedrock'
import portalArtifactsMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/OptimismPortalProxy.json'
import portalArtifactsGoerli from '@eth-optimism/contracts-bedrock/deployments/goerli/OptimismPortalProxy.json'
import l2OutputOracleArtifactsMainnet from '@eth-optimism/contracts-bedrock/deployments/mainnet/L2OutputOracleProxy.json'
import l2OutputOracleArtifactsGoerli from '@eth-optimism/contracts-bedrock/deployments/goerli/L2OutputOracleProxy.json'
const portalAddresses = {
mainnet: portalArtifactsMainnet,
goerli: portalArtifactsGoerli,
}
const l2OutputOracleAddresses = {
mainnet: l2OutputOracleArtifactsMainnet,
goerli: l2OutputOracleArtifactsGoerli,
}
import {
L1ChainID,
......@@ -64,6 +78,7 @@ export const DEFAULT_L2_CONTRACT_ADDRESSES: OEL2ContractsLike = {
* @returns The L1 contracts for the given network.
*/
const getL1ContractsByNetworkName = (network: string): OEL1ContractsLike => {
// TODO this doesn't code split and makes the sdk artifacts way too big
const getDeployedAddress = (name: string) => {
return getDeployedContractDefinition(name, network).address
}
......@@ -77,8 +92,8 @@ const getL1ContractsByNetworkName = (network: string): OEL1ContractsLike => {
StateCommitmentChain: getDeployedAddress('StateCommitmentChain'),
CanonicalTransactionChain: getDeployedAddress('CanonicalTransactionChain'),
BondManager: getDeployedAddress('BondManager'),
OptimismPortal: '0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383' as const,
L2OutputOracle: '0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0' as const,
OptimismPortal: portalAddresses[network].address,
L2OutputOracle: l2OutputOracleAddresses[network].address,
}
}
......@@ -109,6 +124,7 @@ export const CONTRACT_ADDRESSES: {
CanonicalTransactionChain:
'0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9' as const,
BondManager: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' as const,
// FIXME
OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
},
......
......@@ -8,6 +8,7 @@ import (
"os"
"path"
"testing"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -630,6 +631,55 @@ func TestConsensus(t *testing.T) {
require.Equal(t, len(nodes["node2"].mockBackend.Requests()), 0, msg)
})
t.Run("load balancing should not hit if node is degraded", func(t *testing.T) {
reset()
useOnlyNode1()
// replace node1 handler with one that adds a 500ms delay
oldHandler := nodes["node1"].mockBackend.handler
defer func() { nodes["node1"].mockBackend.handler = oldHandler }()
nodes["node1"].mockBackend.SetHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(500 * time.Millisecond)
oldHandler.ServeHTTP(w, r)
}))
update()
// send 10 requests to make node1 degraded
numberReqs := 10
for numberReqs > 0 {
_, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false})
require.NoError(t, err)
require.Equal(t, 200, statusCode)
numberReqs--
}
// bring back node2
nodes["node2"].handler.ResetOverrides()
update()
// reset request counts
nodes["node1"].mockBackend.Reset()
nodes["node2"].mockBackend.Reset()
require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests()))
require.Equal(t, 0, len(nodes["node2"].mockBackend.Requests()))
numberReqs = 10
for numberReqs > 0 {
_, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x101", false})
require.NoError(t, err)
require.Equal(t, 200, statusCode)
numberReqs--
}
msg := fmt.Sprintf("n1 %d, n2 %d",
len(nodes["node1"].mockBackend.Requests()), len(nodes["node2"].mockBackend.Requests()))
require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests()), msg)
require.Equal(t, 10, len(nodes["node2"].mockBackend.Requests()), msg)
})
t.Run("rewrite response of eth_blockNumber", func(t *testing.T) {
reset()
update()
......
......@@ -3,6 +3,7 @@ rpc_port = 8545
[backend]
response_timeout_seconds = 1
max_degraded_latency_threshold = "30ms"
[backends]
[backends.node1]
......
......@@ -358,6 +358,14 @@ var (
"backend_name",
})
degradedBackends = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: MetricsNamespace,
Name: "backend_degraded",
Help: "Bool gauge for degraded backends",
}, []string{
"backend_name",
})
networkErrorRateBackend = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: MetricsNamespace,
Name: "backend_error_rate",
......@@ -493,6 +501,7 @@ func RecordConsensusBackendUpdateDelay(b *Backend, lastUpdate time.Time) {
func RecordBackendNetworkLatencyAverageSlidingWindow(b *Backend, avgLatency time.Duration) {
avgLatencyBackend.WithLabelValues(b.Name).Set(float64(avgLatency.Milliseconds()))
degradedBackends.WithLabelValues(b.Name).Set(boolToFloat64(b.IsDegraded()))
}
func RecordBackendNetworkErrorRateSlidingWindow(b *Backend, rate float64) {
......
......@@ -107,7 +107,7 @@ To derive the L2 blocks in an epoch `E`, we need the following inputs:
is the sequencing window size (note that this means that epochs are overlapping). In particular, we need:
- The [batcher transactions][g-batcher-transaction] included in the sequencing window. These allow us to
reconstruct [sequencer batches][g-sequencer-batch] containing the transactions to include in L2 blocks (each batch
maps to a single L2 block).
contains a list of L2 blocks).
- Note that it is impossible to have a batcher transaction containing a batch relative to epoch `E` on L1 block
`E`, as the batch must contain the hash of L1 block `E`.
- The [deposits][g-deposits] made in L1 block `E` (in the form of events emitted by the [deposit
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment