Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2316,17 +2316,7 @@ workflows:
not:
equal: [scheduled_pipeline, << pipeline.trigger_source >>]
jobs:
# Wait for approval on the release
- hold:
type: approval
filters:
tags:
only: /^(da-server|cannon|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)\/v.*/
branches:
ignore: /.*/
- initialize:
requires:
- hold
context:
- circleci-repo-readonly-authenticated-github-token
filters:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package sync_tester_elsync

import (
"testing"

"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/dsl"
"github.com/ethereum-optimism/optimism/op-devstack/presets"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)

func TestSyncTesterELSync(gt *testing.T) {
t := devtest.SerialT(gt)
sys := presets.NewSimpleWithSyncTester(t)
require := t.Require()
logger := t.Logger()
ctx := t.Ctx()

target := uint64(5)
dsl.CheckAll(t,
sys.L2CL.AdvancedFn(types.LocalUnsafe, target, 30),
sys.L2CL2.AdvancedFn(types.LocalUnsafe, target, 30),
)

// Stop L2CL2 attached to Sync Tester EL Endpoint
sys.L2CL2.Stop()

// Reset Sync Tester EL
sessionIDs := sys.SyncTester.ListSessions()
require.GreaterOrEqual(len(sessionIDs), 1, "at least one session")
sessionID := sessionIDs[0]
logger.Info("SyncTester EL", "sessionID", sessionID)
syncTesterClient := sys.SyncTester.Escape().APIWithSession(sessionID)
require.NoError(syncTesterClient.ResetSession(ctx))

// Wait for L2CL to advance more unsafe blocks
sys.L2CL.Advanced(types.LocalUnsafe, target+5, 30)

// EL Sync not done yet
session, err := syncTesterClient.GetSession(ctx)
require.NoError(err)
require.True(session.ELSyncActive)

// Restarting will trigger EL sync since unsafe head payload will arrive to L2CL2 via P2P
sys.L2CL2.Start()

// Wait until P2P is connected
sys.L2CL2.IsP2PConnected(sys.L2CL)

// Reaches EL Sync Target and advances
target = uint64(40)
sys.L2CL2.Reached(types.LocalUnsafe, target, 30)

session, err = syncTesterClient.GetSession(ctx)
require.NoError(err)
require.False(session.ELSyncActive)

// Check CL2 view is consistent with read only EL
unsafeHead := sys.L2CL2.SyncStatus().UnsafeL2
require.GreaterOrEqual(unsafeHead.Number, target)
require.Equal(sys.L2EL.BlockRefByNumber(unsafeHead.Number).Hash, unsafeHead.Hash)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package sync_tester_elsync

import (
"testing"

"github.com/ethereum-optimism/optimism/op-devstack/compat"
"github.com/ethereum-optimism/optimism/op-devstack/presets"
)

func TestMain(m *testing.M) {
presets.DoMain(m,
presets.WithExecutionLayerSyncOnVerifiers(),
presets.WithSimpleWithSyncTester(),
presets.WithELSyncTarget(35),
presets.WithCompatibleTypes(compat.SysGo),
)
}
2 changes: 1 addition & 1 deletion op-deployer/pkg/deployer/forge/binary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestStandardBinary_ForgeBins(t *testing.T) {
)
require.NoError(t, err)

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
require.NoError(t, bin.Ensure(ctx))
})
Expand Down
13 changes: 9 additions & 4 deletions op-deployer/pkg/deployer/forge/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func NewClient(binary Binary) *Client {
func (c *Client) Version(ctx context.Context) (VersionInfo, error) {
buf := new(bytes.Buffer)
if err := c.execCmd(ctx, buf, io.Discard, "--version"); err != nil {
return VersionInfo{}, fmt.Errorf("failed to execute command: %w", err)
return VersionInfo{}, fmt.Errorf("failed to run forge version command: %w", err)
}
outputStr := buf.String()
matches := versionRegexp.FindAllStringSubmatch(outputStr, -1)
Expand All @@ -67,7 +67,7 @@ func (c *Client) RunScript(ctx context.Context, script string, sig string, args
cliOpts = append(cliOpts, opts...)
cliOpts = append(cliOpts, "--sig", sig, script, "0x"+hex.EncodeToString(args))
if err := c.execCmd(ctx, buf, io.Discard, cliOpts...); err != nil {
return "", fmt.Errorf("failed to execute command: %w", err)
return "", fmt.Errorf("failed to execute forge script: %w", err)
}
return buf.String(), nil
}
Expand All @@ -93,7 +93,7 @@ func (c *Client) execCmd(ctx context.Context, stdout io.Writer, stderr io.Writer
cmd.Stderr = mwStderr
cmd.Dir = c.Wd
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to execute forge: %w", err)
return fmt.Errorf("failed to run forge command: %w", err)
}
return nil
}
Expand All @@ -106,6 +106,11 @@ type ScriptCallDecoder[O any] interface {
Decode(raw []byte) (O, error)
}

// ScriptCaller is a function that calls a forge script
// Ouputs:
// - Return value of the script (decoded into go type)
// - Bool indicating if the script was recompiled (mostly used for testing)
// - Error if the script fails to run
type ScriptCaller[I any, O any] func(ctx context.Context, input I, opts ...string) (O, bool, error)

func NewScriptCaller[I any, O any](client *Client, script string, sig string, encoder ScriptCallEncoder[I], decoder ScriptCallDecoder[O]) ScriptCaller[I, O] {
Expand All @@ -117,7 +122,7 @@ func NewScriptCaller[I any, O any](client *Client, script string, sig string, en
}
rawOut, err := client.RunScript(ctx, script, sig, encArgs, opts...)
if err != nil {
return out, false, fmt.Errorf("failed to execute forge: %w", err)
return out, false, fmt.Errorf("failed to run forge script: %w", err)
}
sigilMatches := sigilRegexp.FindAllStringSubmatch(rawOut, -1)
if len(sigilMatches) != 1 || len(sigilMatches[0]) != 2 {
Expand Down
96 changes: 39 additions & 57 deletions op-deployer/pkg/deployer/forge/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,24 @@ package forge
import (
"bytes"
"context"
"fmt"
"io"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strings"
"testing"
"time"

"github.com/ethereum/go-ethereum/accounts/abi"

"github.com/stretchr/testify/require"
)

type ioStruct struct {
ID uint8
Data []byte
ID uint8
Data []byte
Slice []uint32
Array [3]uint64
}

func TestMinimalSources(t *testing.T) {
Expand All @@ -44,19 +42,28 @@ func TestMinimalSources(t *testing.T) {

// Then see if we can successfully run a script
cl.Wd = tmpDir
encDec := new(testEncoderDecoder)
caller := NewScriptCaller[ioStruct, ioStruct](cl, "script/Test.s.sol:TestScript", "run(bytes)", encDec, encDec)
caller := NewScriptCaller(
cl,
"script/Test.s.sol:TestScript",
"runEncoded(bytes)",
&BytesScriptEncoder[ioStruct]{TypeName: "ioStruct"},
&BytesScriptDecoder[ioStruct]{TypeName: "ioStruct"},
)
// It should not recompile since we included the cache.
in := ioStruct{
ID: 1,
Data: []byte{0x01, 0x02, 0x03, 0x04},
ID: 1,
Data: []byte{0x01, 0x02, 0x03, 0x04},
Slice: []uint32{0x01, 0x02, 0x03, 0x04},
Array: [3]uint64{0x01, 0x02, 0x03},
}
out, changed, err := caller(ctx, in)
require.NoError(t, err)
require.False(t, changed)
require.EqualValues(t, ioStruct{
ID: 2,
Data: in.Data,
ID: 2,
Data: in.Data,
Slice: in.Slice,
Array: in.Array,
}, out)
}

Expand Down Expand Up @@ -90,65 +97,40 @@ func TestScriptCaller(t *testing.T) {
cl.Wd = projDir(t)

require.NoError(t, cl.Clean(ctx))
encDec := new(testEncoderDecoder)
caller := NewScriptCaller[ioStruct, ioStruct](cl, "script/Test.s.sol:TestScript", "run(bytes)", encDec, encDec)
caller := NewScriptCaller(
cl,
"script/Test.s.sol:TestScript",
"runEncoded(bytes)",
&BytesScriptEncoder[ioStruct]{TypeName: "ioStruct"},
&BytesScriptDecoder[ioStruct]{TypeName: "ioStruct"},
)

in := ioStruct{
ID: 1,
Data: []byte{0x01, 0x02},
ID: 1,
Data: []byte{0x01, 0x02},
Slice: []uint32{0x01, 0x02, 0x03, 0x04},
Array: [3]uint64{0x01, 0x02, 0x03},
}
out, recompiled, err := caller(context.Background(), in)
require.NoError(t, err)
require.True(t, recompiled)
require.EqualValues(t, ioStruct{
ID: 2,
Data: []byte{0x01, 0x02},
ID: 2,
Data: in.Data,
Slice: in.Slice,
Array: in.Array,
}, out)
out, recompiled, err = caller(context.Background(), in)
require.NoError(t, err)
require.False(t, recompiled)
require.EqualValues(t, ioStruct{
ID: 2,
Data: []byte{0x01, 0x02},
ID: 2,
Data: in.Data,
Slice: in.Slice,
Array: in.Array,
}, out)
}

var (
runArgs = abi.Arguments{{Type: mustTuple()}}
)

func mustTuple() abi.Type {
t, err := abi.NewType("tuple", "", []abi.ArgumentMarshaling{
{Name: "ID", Type: "uint8"},
{Name: "Data", Type: "bytes"},
})
if err != nil {
panic(err)
}
return t
}

type testEncoderDecoder struct {
}

func (t *testEncoderDecoder) Encode(in ioStruct) ([]byte, error) {
return runArgs.Pack(in)
}

func (t *testEncoderDecoder) Decode(v []byte) (ioStruct, error) {
var out ioStruct
decoded, err := runArgs.Unpack(v)
if err != nil {
return out, fmt.Errorf("error unpacking args: %w", err)
}
// Geth's ABI decoding library returns an anonymous strut
// which requires reflection to parse.
anonStruct := decoded[0]
val := reflect.ValueOf(anonStruct)
out.ID = uint8(val.FieldByName("ID").Uint())
out.Data = val.FieldByName("Data").Bytes()
return out, nil
}

func copyDir(src, dst string) error {
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
Expand Down
Loading