Commit b6a28df8 authored by inphi's avatar inphi

op-program: Bootstrap program via local oracle

parent 13d2bccf
...@@ -3,70 +3,65 @@ package client ...@@ -3,70 +3,65 @@ package client
import ( import (
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"fmt"
"io"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-program/preimage"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
const (
L1HeadLocalIndex preimage.LocalIndexKey = iota + 1
L2HeadLocalIndex
L2ClaimLocalIndex
L2ClaimBlockNumberLocalIndex
L2ChainConfigLocalIndex
RollupConfigLocalIndex
)
type BootInfo struct { type BootInfo struct {
// TODO(CLI-XXX): The rollup config will be hardcoded. It's configurable for testing purposes. L1Head common.Hash
Rollup *rollup.Config `json:"rollup"` L2Head common.Hash
L2ChainConfig *params.ChainConfig `json:"l2_chain_config"` L2Claim common.Hash
L1Head common.Hash `json:"l1_head"` L2ClaimBlockNumber uint64
L2Head common.Hash `json:"l2_head"` L2ChainConfig *params.ChainConfig
L2Claim common.Hash `json:"l2_claim"` RollupConfig *rollup.Config
L2ClaimBlockNumber uint64 `json:"l2_claim_block_number"`
} }
type BootstrapOracleWriter struct { type oracleClient interface {
w io.Writer Get(key preimage.Key) []byte
} }
func NewBootstrapOracleWriter(w io.Writer) *BootstrapOracleWriter { type BootstrapClient struct {
return &BootstrapOracleWriter{w: w} r oracleClient
} }
func (bw *BootstrapOracleWriter) WriteBootInfo(info *BootInfo) error { func NewBootstrapClient(r oracleClient) *BootstrapClient {
// TODO(CLI-3751): Bootstrap from local oracle return &BootstrapClient{r: r}
payload, err := json.Marshal(info)
if err != nil {
return err
}
var b []byte
b = binary.BigEndian.AppendUint32(b, uint32(len(payload)))
b = append(b, payload...)
_, err = bw.w.Write(b)
return err
} }
type BootstrapOracleReader struct { func (br *BootstrapClient) BootInfo() *BootInfo {
r io.Reader l1Head := common.BytesToHash(br.r.Get(L1HeadLocalIndex))
} l2Head := common.BytesToHash(br.r.Get(L2HeadLocalIndex))
l2Claim := common.BytesToHash(br.r.Get(L2ClaimLocalIndex))
func NewBootstrapOracleReader(r io.Reader) *BootstrapOracleReader { l2ClaimBlockNumber := binary.BigEndian.Uint64(br.r.Get(L2ClaimBlockNumberLocalIndex))
return &BootstrapOracleReader{r: r} l2ChainConfig := new(params.ChainConfig)
} err := json.Unmarshal(br.r.Get(L2ChainConfigLocalIndex), &l2ChainConfig)
if err != nil {
func (br *BootstrapOracleReader) BootInfo() (*BootInfo, error) { panic("failed to bootstrap l2ChainConfig")
var length uint32
if err := binary.Read(br.r, binary.BigEndian, &length); err != nil {
if err == io.EOF {
return nil, io.EOF
}
return nil, fmt.Errorf("failed to read bootinfo length prefix: %w", err)
}
payload := make([]byte, length)
if length > 0 {
if _, err := io.ReadFull(br.r, payload); err != nil {
return nil, fmt.Errorf("failed to read bootinfo data (length %d): %w", length, err)
} }
rollupConfig := new(rollup.Config)
err = json.Unmarshal(br.r.Get(RollupConfigLocalIndex), rollupConfig)
if err != nil {
panic("failed to bootstrap rollup config")
} }
var bootInfo BootInfo
if err := json.Unmarshal(payload, &bootInfo); err != nil { return &BootInfo{
return nil, err L1Head: l1Head,
L2Head: l2Head,
L2Claim: l2Claim,
L2ClaimBlockNumber: l2ClaimBlockNumber,
L2ChainConfig: l2ChainConfig,
RollupConfig: rollupConfig,
} }
return &bootInfo, nil
} }
package client package client
import ( import (
"io" "encoding/binary"
"encoding/json"
"testing" "testing"
"time"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-program/preimage"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestBootstrapOracle(t *testing.T) { func TestBootstrapClient(t *testing.T) {
r, w := io.Pipe() bootInfo := &BootInfo{
br := NewBootstrapOracleReader(r) L1Head: common.HexToHash("0x1111"),
bw := NewBootstrapOracleWriter(w) L2Head: common.HexToHash("0x2222"),
L2Claim: common.HexToHash("0x3333"),
bootInfo := BootInfo{
Rollup: new(rollup.Config),
L2ChainConfig: new(params.ChainConfig),
L1Head: common.HexToHash("0xffffa"),
L2Head: common.HexToHash("0xffffb"),
L2Claim: common.HexToHash("0xffffc"),
L2ClaimBlockNumber: 1, L2ClaimBlockNumber: 1,
L2ChainConfig: params.GoerliChainConfig,
RollupConfig: &chaincfg.Goerli,
} }
mockOracle := &mockBoostrapOracle{bootInfo}
readBootInfo := NewBootstrapClient(mockOracle).BootInfo()
require.EqualValues(t, bootInfo, readBootInfo)
}
go func() { type mockBoostrapOracle struct {
err := bw.WriteBootInfo(&bootInfo) b *BootInfo
require.NoError(t, err) }
}()
type result struct {
bootInnfo *BootInfo
err error
}
read := make(chan result)
go func() {
readBootInfo, err := br.BootInfo()
read <- result{readBootInfo, err}
close(read)
}()
select { func (o *mockBoostrapOracle) Get(key preimage.Key) []byte {
case <-time.After(time.Second * 30): switch key.PreimageKey() {
t.Error("timeout waiting for bootstrap oracle") case L1HeadLocalIndex.PreimageKey():
case r := <-read: return o.b.L1Head[:]
require.NoError(t, r.err) case L2HeadLocalIndex.PreimageKey():
require.Equal(t, bootInfo, *r.bootInnfo) return o.b.L2Head[:]
case L2ClaimLocalIndex.PreimageKey():
return o.b.L2Claim[:]
case L2ClaimBlockNumberLocalIndex.PreimageKey():
return binary.BigEndian.AppendUint64(nil, o.b.L2ClaimBlockNumber)
case L2ChainConfigLocalIndex.PreimageKey():
b, _ := json.Marshal(o.b.L2ChainConfig)
return b
case RollupConfigLocalIndex.PreimageKey():
b, _ := json.Marshal(o.b.RollupConfig)
return b
default:
panic("unknown key")
} }
} }
...@@ -6,6 +6,5 @@ const ( ...@@ -6,6 +6,5 @@ const (
HClientWFd HClientWFd
PClientRFd PClientRFd
PClientWFd PClientWFd
BootRFd // TODO(CLI-3751): remove
MaxFd MaxFd
) )
...@@ -26,8 +26,7 @@ func Main(logger log.Logger) { ...@@ -26,8 +26,7 @@ func Main(logger log.Logger) {
log.Info("Starting fault proof program client") log.Info("Starting fault proof program client")
preimageOracle := CreatePreimageChannel() preimageOracle := CreatePreimageChannel()
preimageHinter := CreateHinterChannel() preimageHinter := CreateHinterChannel()
bootOracle := os.NewFile(BootRFd, "bootR") err := RunProgram(logger, preimageOracle, preimageHinter)
err := RunProgram(logger, bootOracle, preimageOracle, preimageHinter)
if err != nil { if err != nil {
log.Error("Program failed", "err", err) log.Error("Program failed", "err", err)
os.Exit(1) os.Exit(1)
...@@ -37,21 +36,18 @@ func Main(logger log.Logger) { ...@@ -37,21 +36,18 @@ func Main(logger log.Logger) {
} }
// RunProgram executes the Program, while attached to an IO based pre-image oracle, to be served by a host. // RunProgram executes the Program, while attached to an IO based pre-image oracle, to be served by a host.
func RunProgram(logger log.Logger, bootOracle io.Reader, preimageOracle io.ReadWriter, preimageHinter io.ReadWriter) error { func RunProgram(logger log.Logger, preimageOracle io.ReadWriter, preimageHinter io.ReadWriter) error {
bootReader := NewBootstrapOracleReader(bootOracle)
bootInfo, err := bootReader.BootInfo()
if err != nil {
return fmt.Errorf("failed to read boot info: %w", err)
}
logger.Debug("Loaded boot info", "bootInfo", bootInfo)
pClient := preimage.NewOracleClient(preimageOracle) pClient := preimage.NewOracleClient(preimageOracle)
hClient := preimage.NewHintWriter(preimageHinter) hClient := preimage.NewHintWriter(preimageHinter)
l1PreimageOracle := l1.NewPreimageOracle(pClient, hClient) l1PreimageOracle := l1.NewPreimageOracle(pClient, hClient)
l2PreimageOracle := l2.NewPreimageOracle(pClient, hClient) l2PreimageOracle := l2.NewPreimageOracle(pClient, hClient)
bootInfo := NewBootstrapClient(pClient).BootInfo()
logger.Info("Program Bootstrapped", "bootInfo", bootInfo)
return runDerivation( return runDerivation(
logger, logger,
bootInfo.Rollup, bootInfo.RollupConfig,
bootInfo.L2ChainConfig, bootInfo.L2ChainConfig,
bootInfo.L1Head, bootInfo.L1Head,
bootInfo.L2Head, bootInfo.L2Head,
......
...@@ -79,7 +79,6 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error { ...@@ -79,7 +79,6 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error {
} }
} }
// TODO(CLI-3751: Load local preimages
localPreimageSource := kvstore.NewLocalPreimageSource(cfg) localPreimageSource := kvstore.NewLocalPreimageSource(cfg)
splitter := kvstore.NewPreimageSourceSplitter(localPreimageSource.Get, getPreimage) splitter := kvstore.NewPreimageSourceSplitter(localPreimageSource.Get, getPreimage)
...@@ -101,20 +100,14 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error { ...@@ -101,20 +100,14 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error {
hHost := preimage.NewHintReader(hHostRW) hHost := preimage.NewHintReader(hHostRW)
routeHints(logger, hHost, hinter) routeHints(logger, hHost, hinter)
bootClientR, bootHostW, err := os.Pipe()
if err != nil {
return fmt.Errorf("failed to create boot info pipe: %w", err)
}
var cmd *exec.Cmd var cmd *exec.Cmd
if cfg.Detached { if cfg.Detached {
cmd = exec.Command(os.Args[0], os.Args[1:]...) cmd = exec.CommandContext(ctx, os.Args[0], os.Args[1:]...)
cmd.ExtraFiles = make([]*os.File, cl.MaxFd-3) // not including stdin, stdout and stderr cmd.ExtraFiles = make([]*os.File, cl.MaxFd-3) // not including stdin, stdout and stderr
cmd.ExtraFiles[cl.HClientRFd-3] = hClientRW.Reader() cmd.ExtraFiles[cl.HClientRFd-3] = hClientRW.Reader()
cmd.ExtraFiles[cl.HClientWFd-3] = hClientRW.Writer() cmd.ExtraFiles[cl.HClientWFd-3] = hClientRW.Writer()
cmd.ExtraFiles[cl.PClientRFd-3] = pClientRW.Reader() cmd.ExtraFiles[cl.PClientRFd-3] = pClientRW.Reader()
cmd.ExtraFiles[cl.PClientWFd-3] = pClientRW.Writer() cmd.ExtraFiles[cl.PClientWFd-3] = pClientRW.Writer()
cmd.ExtraFiles[cl.BootRFd-3] = bootClientR
cmd.Stdout = os.Stdout // for debugging cmd.Stdout = os.Stdout // for debugging
cmd.Stderr = os.Stderr // for debugging cmd.Stderr = os.Stderr // for debugging
cmd.Env = append(os.Environ(), fmt.Sprintf("%s=true", opProgramChildEnvName)) cmd.Env = append(os.Environ(), fmt.Sprintf("%s=true", opProgramChildEnvName))
...@@ -123,33 +116,13 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error { ...@@ -123,33 +116,13 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error {
if err != nil { if err != nil {
return fmt.Errorf("program cmd failed to start: %w", err) return fmt.Errorf("program cmd failed to start: %w", err)
} }
} if err := cmd.Wait(); err != nil {
bootInfo := cl.BootInfo{
Rollup: cfg.Rollup,
L2ChainConfig: cfg.L2ChainConfig,
L1Head: cfg.L1Head,
L2Head: cfg.L2Head,
L2Claim: cfg.L2Claim,
L2ClaimBlockNumber: cfg.L2ClaimBlockNumber,
}
// Spawn a goroutine to write the boot info to avoid blocking this host's goroutine
// if we're running in detached mode
bootInitErrorCh := initializeBootInfoAsync(&bootInfo, bootHostW)
if !cfg.Detached {
return cl.RunProgram(logger, bootClientR, pClientRW, hClientRW)
}
if err := <-bootInitErrorCh; err != nil {
// return early as a detached client is blocked waiting for the boot info
return fmt.Errorf("failed to write boot info: %w", err)
}
if cfg.Detached {
err := cmd.Wait()
if err != nil {
return fmt.Errorf("failed to wait for child program: %w", err) return fmt.Errorf("failed to wait for child program: %w", err)
} }
}
return nil return nil
} else {
return cl.RunProgram(logger, pClientRW, hClientRW)
}
} }
func makePrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV, cfg *config.Config) (*prefetcher.Prefetcher, error) { func makePrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV, cfg *config.Config) (*prefetcher.Prefetcher, error) {
...@@ -179,16 +152,6 @@ func makePrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV, cfg * ...@@ -179,16 +152,6 @@ func makePrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV, cfg *
return prefetcher.NewPrefetcher(logger, l1Cl, l2DebugCl, kv), nil return prefetcher.NewPrefetcher(logger, l1Cl, l2DebugCl, kv), nil
} }
func initializeBootInfoAsync(bootInfo *cl.BootInfo, bootOracle *os.File) <-chan error {
bootWriteErr := make(chan error, 1)
go func() {
bootOracleWriter := cl.NewBootstrapOracleWriter(bootOracle)
bootWriteErr <- bootOracleWriter.WriteBootInfo(bootInfo)
close(bootWriteErr)
}()
return bootWriteErr
}
func routeHints(logger log.Logger, hintReader *preimage.HintReader, hinter func(hint string) error) { func routeHints(logger log.Logger, hintReader *preimage.HintReader, hinter func(hint string) error) {
go func() { go func() {
for { for {
......
...@@ -4,8 +4,8 @@ import ( ...@@ -4,8 +4,8 @@ import (
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"github.com/ethereum-optimism/optimism/op-program/client"
"github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum-optimism/optimism/op-program/host/config"
"github.com/ethereum-optimism/optimism/op-program/preimage"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
...@@ -17,32 +17,28 @@ func NewLocalPreimageSource(config *config.Config) *LocalPreimageSource { ...@@ -17,32 +17,28 @@ func NewLocalPreimageSource(config *config.Config) *LocalPreimageSource {
return &LocalPreimageSource{config} return &LocalPreimageSource{config}
} }
func localKey(num int64) common.Hash {
return preimage.LocalIndexKey(num).PreimageKey()
}
var ( var (
L1HeadKey = localKey(1) l1HeadKey = client.L1HeadLocalIndex.PreimageKey()
L2HeadKey = localKey(2) l2HeadKey = client.L2HeadLocalIndex.PreimageKey()
L2ClaimKey = localKey(3) l2ClaimKey = client.L2ClaimLocalIndex.PreimageKey()
L2ClaimBlockNumberKey = localKey(4) l2ClaimBlockNumberKey = client.L2ClaimBlockNumberLocalIndex.PreimageKey()
L2ChainConfigKey = localKey(5) l2ChainConfigKey = client.L2ChainConfigLocalIndex.PreimageKey()
RollupKey = localKey(6) rollupKey = client.RollupConfigLocalIndex.PreimageKey()
) )
func (s *LocalPreimageSource) Get(key common.Hash) ([]byte, error) { func (s *LocalPreimageSource) Get(key common.Hash) ([]byte, error) {
switch key { switch key {
case L1HeadKey: case l1HeadKey:
return s.config.L1Head.Bytes(), nil return s.config.L1Head.Bytes(), nil
case L2HeadKey: case l2HeadKey:
return s.config.L2Head.Bytes(), nil return s.config.L2Head.Bytes(), nil
case L2ClaimKey: case l2ClaimKey:
return s.config.L2Claim.Bytes(), nil return s.config.L2Claim.Bytes(), nil
case L2ClaimBlockNumberKey: case l2ClaimBlockNumberKey:
return binary.BigEndian.AppendUint64(nil, s.config.L2ClaimBlockNumber), nil return binary.BigEndian.AppendUint64(nil, s.config.L2ClaimBlockNumber), nil
case L2ChainConfigKey: case l2ChainConfigKey:
return json.Marshal(s.config.L2ChainConfig) return json.Marshal(s.config.L2ChainConfig)
case RollupKey: case rollupKey:
return json.Marshal(s.config.Rollup) return json.Marshal(s.config.Rollup)
default: default:
return nil, ErrNotFound return nil, ErrNotFound
......
...@@ -28,12 +28,12 @@ func TestLocalPreimageSource(t *testing.T) { ...@@ -28,12 +28,12 @@ func TestLocalPreimageSource(t *testing.T) {
key common.Hash key common.Hash
expected []byte expected []byte
}{ }{
{"L1Head", L1HeadKey, cfg.L1Head.Bytes()}, {"L1Head", l1HeadKey, cfg.L1Head.Bytes()},
{"L2Head", L2HeadKey, cfg.L2Head.Bytes()}, {"L2Head", l2HeadKey, cfg.L2Head.Bytes()},
{"L2Claim", L2ClaimKey, cfg.L2Claim.Bytes()}, {"L2Claim", l2ClaimKey, cfg.L2Claim.Bytes()},
{"L2ClaimBlockNumber", L2ClaimBlockNumberKey, binary.BigEndian.AppendUint64(nil, cfg.L2ClaimBlockNumber)}, {"L2ClaimBlockNumber", l2ClaimBlockNumberKey, binary.BigEndian.AppendUint64(nil, cfg.L2ClaimBlockNumber)},
{"Rollup", RollupKey, asJson(t, cfg.Rollup)}, {"Rollup", rollupKey, asJson(t, cfg.Rollup)},
{"ChainConfig", L2ChainConfigKey, asJson(t, cfg.L2ChainConfig)}, {"ChainConfig", l2ChainConfigKey, asJson(t, cfg.L2ChainConfig)},
{"Unknown", preimage.LocalIndexKey(1000).PreimageKey(), nil}, {"Unknown", preimage.LocalIndexKey(1000).PreimageKey(), nil},
} }
for _, test := range tests { for _, test := range tests {
......
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