From 2523b430c0311d7ff2ddafc6c06eb6f2770c70d3 Mon Sep 17 00:00:00 2001 From: avalonche Date: Wed, 4 Sep 2024 14:52:16 +1000 Subject: [PATCH] Add builder and sequencer auth for payloads --- go.mod | 7 +- go.sum | 30 --- op-e2e/actions/l2_verifier.go | 5 +- op-node/flags/flags.go | 8 + op-node/node/builder.go | 347 +++++++++++++++++--------------- op-node/node/config.go | 7 +- op-node/node/node.go | 19 +- op-node/node/types/payload.go | 254 +++++++++++++++++++++++ op-node/node/types/version.go | 58 ++++++ op-node/service.go | 7 +- op-service/testutils/metrics.go | 5 +- ops-bedrock/docker-compose.yml | 7 +- pbs/README.md | 34 ++-- 13 files changed, 558 insertions(+), 230 deletions(-) create mode 100644 op-node/node/types/payload.go create mode 100644 op-node/node/types/version.go diff --git a/go.mod b/go.mod index 1e49d0ec5f6d..94cef9de6036 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/DataDog/zstd v1.5.2 github.com/andybalholm/brotli v1.1.0 - github.com/attestantio/go-builder-client v0.4.6 github.com/btcsuite/btcd v0.24.0 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/cockroachdb/pebble v0.0.0-20231018212520-f6cde3fc2fa4 @@ -23,7 +22,6 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.1 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/raft v1.6.1 @@ -64,7 +62,6 @@ require ( github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/allegro/bigcache v1.2.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect - github.com/attestantio/go-eth2-client v0.21.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect @@ -107,7 +104,6 @@ require ( github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/goccy/go-yaml v1.9.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -115,6 +111,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect + github.com/gorilla/websocket v1.5.1 // indirect github.com/graph-gophers/graphql-go v1.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect @@ -204,7 +201,6 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/quic-go v0.44.0 // indirect github.com/quic-go/webtransport-go v0.8.0 // indirect @@ -236,7 +232,6 @@ require ( golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/tools v0.21.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index ad9a61c1c7de..74983de5a171 100644 --- a/go.sum +++ b/go.sum @@ -37,10 +37,6 @@ github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQh github.com/armon/go-metrics v0.3.8/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= -github.com/attestantio/go-builder-client v0.4.6 h1:l8t6hpo6zlw+07IvPFWl/QemAqKbdFjIlvZdZSktpAI= -github.com/attestantio/go-builder-client v0.4.6/go.mod h1:ZMmatuguvfy/JRHF8eFUy9RQgkHzdslCCVMofiywRkE= -github.com/attestantio/go-eth2-client v0.21.1 h1:yvsMd/azPUbxiJzWZhgqfOJJRNF1zLvAJpcBXTHzyh8= -github.com/attestantio/go-eth2-client v0.21.1/go.mod h1:Tb412NpzhsC0sbtpXS4D51y5se6nDkWAi6amsJrqX9c= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= @@ -185,7 +181,6 @@ github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-2024060308503 github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240603085035-9c8f6081266e/go.mod h1:7xh2awFQqsiZxFrHKTgEd+InVfDRrkKVUIuK8SAFHp0= github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= @@ -238,16 +233,6 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -256,8 +241,6 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-yaml v1.9.2 h1:2Njwzw+0+pjU2gb805ZC1B/uBuAs2VcZ3K+ZgHwDs7w= -github.com/goccy/go-yaml v1.9.2/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -371,10 +354,6 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/go-clone v1.6.0 h1:HMo5uvg4wgfiy5FoGOqlFLQED/VGRm2D9Pi8g1FXPGc= -github.com/huandu/go-clone v1.6.0/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE= -github.com/huandu/go-clone/generic v1.6.0 h1:Wgmt/fUZ28r16F2Y3APotFD59sHk1p78K0XLdbUYN5U= -github.com/huandu/go-clone/generic v1.6.0/go.mod h1:xgd9ZebcMsBWWcBx5mVMCoqMX24gLWr5lQicr+nVXNs= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -510,9 +489,6 @@ github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4F github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -758,8 +734,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4tdWQK9ZpYygoV7+vS6QkDvQVySboMVEIxBJmXw= -github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0= @@ -1142,8 +1116,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= @@ -1204,8 +1176,6 @@ gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM= gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/op-e2e/actions/l2_verifier.go b/op-e2e/actions/l2_verifier.go index 3ad098f9599c..5e77f53d7d64 100644 --- a/op-e2e/actions/l2_verifier.go +++ b/op-e2e/actions/l2_verifier.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/node" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/attributes" + "github.com/ethereum-optimism/optimism/op-node/rollup/builder" "github.com/ethereum-optimism/optimism/op-node/rollup/clsync" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/driver" @@ -71,9 +72,9 @@ type safeDB interface { func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher, blobsSrc derive.L1BlobsFetcher, plasmaSrc driver.PlasmaIface, eng L2API, cfg *rollup.Config, syncCfg *sync.Config, safeHeadListener safeDB) *L2Verifier { metrics := &testutils.TestDerivationMetrics{} - engine := derive.NewEngineController(eng, log, metrics, cfg, syncCfg.SyncMode) + engine := derive.NewEngineController(eng, log, metrics, cfg, syncCfg.SyncMode, &builder.NoOpBuilder{}) - clSync := clsync.NewCLSync(log, cfg, metrics, engine) + clSync := clsync.NewCLSync(log, cfg, metrics, engine, nil, nil, nil, false) var finalizer driver.Finalizer if cfg.PlasmaEnabled() { diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index 12dfc7619dd1..af75ce619ac9 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -401,6 +401,13 @@ var ( Value: time.Millisecond * 500, Category: BuilderCategory, } + BuilderRequestSignerFlag = &cli.StringFlag{ + Name: "l2.builder.request-signer", + Usage: "Private key from proposer in hex format to sign get block payload requests to the builder.", + Required: false, + EnvVars: prefixEnvVars("L2_BUILDER_SIGNER"), + Category: BuilderCategory, + } ) var requiredFlags = []cli.Flag{ @@ -413,6 +420,7 @@ var optionalFlags = []cli.Flag{ BuilderEnabledFlag, BuilderEndpointFlag, BuilderRequestTimeoutFlag, + BuilderRequestSignerFlag, BeaconAddr, BeaconHeader, BeaconFallbackAddrs, diff --git a/op-node/node/builder.go b/op-node/node/builder.go index 76efd5314460..0cbde35af896 100644 --- a/op-node/node/builder.go +++ b/op-node/node/builder.go @@ -1,60 +1,86 @@ package node import ( + "bytes" "context" + "crypto/ecdsa" "encoding/json" "fmt" "io" "net/http" + "net/url" "time" - builderSpec "github.com/attestantio/go-builder-client/spec" - "github.com/attestantio/go-eth2-client/spec" - "github.com/attestantio/go-eth2-client/spec/bellatrix" - "github.com/attestantio/go-eth2-client/spec/capella" - "github.com/attestantio/go-eth2-client/spec/deneb" + builderTypes "github.com/ethereum-optimism/optimism/op-node/node/types" + "github.com/ethereum-optimism/optimism/op-node/p2p" + opservice "github.com/ethereum-optimism/optimism/op-service" "github.com/holiman/uint256" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/builder" - "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" ) const PathGetPayload = "/eth/v1/builder/payload" type BuilderAPIConfig struct { Timeout time.Duration - Endpoint string + Endpoint *url.URL + Address common.Address } type BuilderAPIClient struct { - log log.Logger - config *BuilderAPIConfig - rollupCfg *rollup.Config - httpClient *client.BasicHTTPClient + log log.Logger + config *BuilderAPIConfig + rollupCfg *rollup.Config + requestSigner *ecdsa.PrivateKey } type BuilderMetrics interface { RecordBuilderPayloadBytes(num int) } -func NewBuilderClient(log log.Logger, rollupCfg *rollup.Config, endpoint string, timeout time.Duration) *BuilderAPIClient { - httpClient := client.NewBasicHTTPClient(endpoint, log) +func BuilderSigningHash(cfg *rollup.Config, payloadBytes []byte) (common.Hash, error) { + return p2p.SigningHash(builderTypes.SigningDomainBuilderV1, cfg.L2ChainID, payloadBytes) +} + +func NewBuilderClient(log log.Logger, rollupCfg *rollup.Config, endpoint string, timeout time.Duration, signer *ecdsa.PrivateKey) (*BuilderAPIClient, error) { + endpointURL, err := url.ParseRequestURI(endpoint) + if err != nil { + return nil, fmt.Errorf("builder endpoint is invalid url: %w", err) + } + + builderAddress := common.Address{} + if endpointURL.User.Username() == "" { + log.Warn("builder endpoint is missing builder address") + } else { + builderAddress, err = opservice.ParseAddress(endpointURL.User.Username()) + if err != nil { + log.Warn("builder endpoint is invalid address", "error", err) + } + } + if builderAddress == (common.Address{}) { + log.Warn("no builder address found, builder payloads will not be verified against known builders") + } + config := &BuilderAPIConfig{ Timeout: timeout, - Endpoint: endpoint, + Endpoint: endpointURL, + Address: builderAddress, } return &BuilderAPIClient{ - httpClient: httpClient, - config: config, - rollupCfg: rollupCfg, - log: log, - } + config: config, + rollupCfg: rollupCfg, + log: log, + requestSigner: signer, + }, nil } func (s *BuilderAPIClient) Enabled() bool { @@ -73,196 +99,187 @@ type httpErrorResp struct { } func (s *BuilderAPIClient) GetPayload(ctx context.Context, ref eth.L2BlockRef, log log.Logger, metrics builder.BuilderMetrics) (*eth.ExecutionPayloadEnvelope, error) { - submitBlockRequest := new(builderSpec.VersionedSubmitBlockRequest) + blockResponse := new(builderTypes.VersionedBuilderPayloadResponse) slot := ref.Number + 1 parentHash := ref.Hash - url := fmt.Sprintf("%s/%d/%s", PathGetPayload, slot, parentHash.String()) - header := http.Header{"Accept": {"application/json"}} - resp, err := s.httpClient.Get(ctx, url, nil, header) + + msg := builderTypes.PayloadRequestV1{ + Slot: slot, + ParentHash: parentHash, + } + + signature, err := s.signBuilderRequest(&msg) if err != nil { return nil, err } - defer resp.Body.Close() - - bodyBytes, err := io.ReadAll(resp.Body) + request := &builderTypes.BuilderPayloadRequest{ + Message: msg, + Signature: signature, + } + err = s.requestBuilder(ctx, request, blockResponse, metrics) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to request builder: %w", err) } - if resp.StatusCode != http.StatusOK { - metrics.RecordBuilderPayloadBytes(len(bodyBytes)) - var errResp httpErrorResp - if err := json.Unmarshal(bodyBytes, &errResp); err != nil { - log.Warn("failed to unmarshal error response", "error", err, "response", string(bodyBytes)) - return nil, fmt.Errorf("HTTP error response: %v", resp.Status) - } - return nil, fmt.Errorf("HTTP error response: %v", errResp.Message) + err = s.validateBlockResponse(blockResponse, ref) + if err != nil { + return nil, fmt.Errorf("failed to validate builder response: %w", err) } - if err := json.Unmarshal(bodyBytes, submitBlockRequest); err != nil { - return nil, err + envelope, err := getExecutionPayloadEnvelope(blockResponse) + if err != nil { + return nil, fmt.Errorf("failed to get execution payload envelope: %w", err) } + return envelope, nil +} - // selects expected data version from the optimism version. - // Bedrock - Bellatrix - // Canyon - Capella - // Ecotone - Deneb - var expectedVersion spec.DataVersion - if s.rollupCfg.IsEcotone(ref.Time) { - expectedVersion = spec.DataVersionDeneb - } else if s.rollupCfg.IsCanyon(ref.Time) { - expectedVersion = spec.DataVersionCapella - } else { - expectedVersion = spec.DataVersionBellatrix +func (s *BuilderAPIClient) signBuilderRequest(request *builderTypes.PayloadRequestV1) ([]byte, error) { + if s.requestSigner == nil { + signature := make([]byte, 65) + return signature, nil } - if expectedVersion != submitBlockRequest.Version { - return nil, fmt.Errorf("expected version %s, got %s", expectedVersion, submitBlockRequest.Version) + requestBytes, err := rlp.EncodeToBytes(request) + if err != nil { + return nil, err } - envelope, err := getExecutionPayloadEnvelope(submitBlockRequest, ref) + hash, err := BuilderSigningHash(s.rollupCfg, requestBytes) if err != nil { return nil, err } - return envelope, nil + + return crypto.Sign(hash[:], s.requestSigner) } -func getExecutionPayloadEnvelope(blockRequest *builderSpec.VersionedSubmitBlockRequest, ref eth.L2BlockRef) (*eth.ExecutionPayloadEnvelope, error) { - switch blockRequest.Version { - case spec.DataVersionBellatrix: - return bellatrixExecutionPayloadToExecutionPayload(blockRequest.Bellatrix.ExecutionPayload), nil - case spec.DataVersionCapella: - return capellaExecutionPayloadToExecutionPayload(blockRequest.Capella.ExecutionPayload), nil - case spec.DataVersionDeneb: - return denebExecutionPayloadToExecutionPayload(blockRequest.Deneb.ExecutionPayload), nil - default: - return nil, fmt.Errorf("unsupported version: %s", blockRequest.Version) +func (s *BuilderAPIClient) requestBuilder(ctx context.Context, request *builderTypes.BuilderPayloadRequest, blockResponse *builderTypes.VersionedBuilderPayloadResponse, metrics builder.BuilderMetrics) error { + body, err := json.Marshal(request) + if err != nil { + return fmt.Errorf("failed to marshal request: %w", err) } -} + path := s.config.Endpoint.String() + PathGetPayload + req, err := http.NewRequestWithContext(ctx, http.MethodPost, path, bytes.NewReader(body)) + if err != nil { + return fmt.Errorf("failed to create HTTP request: %w", err) + } + req.Header.Set("Content-Type", "application/octet-stream") -func reverse(src []byte) []byte { - dst := make([]byte, len(src)) - copy(dst, src) - for i := len(dst)/2 - 1; i >= 0; i-- { - opp := len(dst) - 1 - i - dst[i], dst[opp] = dst[opp], dst[i] + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err } - return dst -} + defer resp.Body.Close() -func bellatrixExecutionPayloadToExecutionPayload(payload *bellatrix.ExecutionPayload) *eth.ExecutionPayloadEnvelope { - txs := make([]eth.Data, len(payload.Transactions)) - for i, tx := range payload.Transactions { - txs[i] = eth.Data(tx) + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return err } - envelope := ð.ExecutionPayloadEnvelope{ - ExecutionPayload: ð.ExecutionPayload{ - ParentHash: common.Hash(payload.ParentHash), - FeeRecipient: common.Address(payload.FeeRecipient), - StateRoot: eth.Bytes32(payload.StateRoot), - ReceiptsRoot: eth.Bytes32(payload.ReceiptsRoot), - LogsBloom: eth.Bytes256(payload.LogsBloom), - PrevRandao: eth.Bytes32(payload.PrevRandao), - BlockNumber: eth.Uint64Quantity(payload.BlockNumber), - GasLimit: eth.Uint64Quantity(payload.GasLimit), - GasUsed: eth.Uint64Quantity(payload.GasUsed), - Timestamp: eth.Uint64Quantity(payload.Timestamp), - ExtraData: eth.BytesMax32(payload.ExtraData), - BaseFeePerGas: eth.Uint256Quantity(*uint256.NewInt(0).SetBytes(reverse(payload.BaseFeePerGas[:]))), - BlockHash: common.BytesToHash(payload.BlockHash[:]), - Transactions: txs, - Withdrawals: nil, - BlobGasUsed: nil, - ExcessBlobGas: nil, - }, - ParentBeaconBlockRoot: nil, + if resp.StatusCode != http.StatusOK { + metrics.RecordBuilderPayloadBytes(len(bodyBytes)) + var errResp httpErrorResp + if err := json.Unmarshal(bodyBytes, &errResp); err != nil { + log.Warn("failed to unmarshal error response", "error", err, "response", string(bodyBytes)) + return fmt.Errorf("HTTP error response: %v", resp.Status) + } + return fmt.Errorf("HTTP error response: %v", errResp.Message) } - return envelope -} -func capellaExecutionPayloadToExecutionPayload(payload *capella.ExecutionPayload) *eth.ExecutionPayloadEnvelope { - txs := make([]eth.Data, len(payload.Transactions)) - for i, tx := range payload.Transactions { - txs[i] = eth.Data(tx) + if err := json.Unmarshal(bodyBytes, blockResponse); err != nil { + return err } - withdrawals := make([]*types.Withdrawal, len(payload.Withdrawals)) - for i, withdrawal := range payload.Withdrawals { - withdrawals[i] = &types.Withdrawal{ - Index: uint64(withdrawal.Index), - Validator: uint64(withdrawal.ValidatorIndex), - Address: common.BytesToAddress(withdrawal.Address[:]), - Amount: uint64(withdrawal.Amount), - } + return nil +} + +func getExecutionPayloadEnvelope(blockResponse *builderTypes.VersionedBuilderPayloadResponse) (*eth.ExecutionPayloadEnvelope, error) { + withdrawals := make(types.Withdrawals, 0) + for _, withdrawal := range blockResponse.ExecutionPayload.Withdrawals { + withdrawals = append(withdrawals, withdrawal) + } + transactions := make([]hexutil.Bytes, 0) + for _, tx := range blockResponse.ExecutionPayload.Transactions { + transactions = append(transactions, hexutil.Bytes(tx)) + } + baseFee, overflow := uint256.FromBig(blockResponse.ExecutionPayload.BaseFeePerGas) + if overflow { + return nil, fmt.Errorf("base fee overflow") + } + blockValue, overflow := uint256.FromBig(blockResponse.Message.Value) + if overflow { + return nil, fmt.Errorf("block value overflow") } - ws := types.Withdrawals(withdrawals) - envelope := ð.ExecutionPayloadEnvelope{ + return ð.ExecutionPayloadEnvelope{ ExecutionPayload: ð.ExecutionPayload{ - ParentHash: common.Hash(payload.ParentHash), - FeeRecipient: common.Address(payload.FeeRecipient), - StateRoot: eth.Bytes32(payload.StateRoot), - ReceiptsRoot: eth.Bytes32(payload.ReceiptsRoot), - LogsBloom: eth.Bytes256(payload.LogsBloom), - PrevRandao: eth.Bytes32(payload.PrevRandao), - BlockNumber: eth.Uint64Quantity(payload.BlockNumber), - GasLimit: eth.Uint64Quantity(payload.GasLimit), - GasUsed: eth.Uint64Quantity(payload.GasUsed), - Timestamp: eth.Uint64Quantity(payload.Timestamp), - ExtraData: eth.BytesMax32(payload.ExtraData), - BaseFeePerGas: eth.Uint256Quantity(*uint256.NewInt(0).SetBytes(reverse(payload.BaseFeePerGas[:]))), - BlockHash: common.BytesToHash(payload.BlockHash[:]), - Transactions: txs, - Withdrawals: &ws, - BlobGasUsed: nil, - ExcessBlobGas: nil, + ParentHash: blockResponse.ExecutionPayload.ParentHash, + FeeRecipient: blockResponse.ExecutionPayload.FeeRecipient, + StateRoot: eth.Bytes32(blockResponse.ExecutionPayload.StateRoot), + ReceiptsRoot: eth.Bytes32(blockResponse.ExecutionPayload.ReceiptsRoot), + LogsBloom: eth.Bytes256(blockResponse.ExecutionPayload.LogsBloom), + PrevRandao: eth.Bytes32(blockResponse.ExecutionPayload.Random), + BlockNumber: eth.Uint64Quantity(blockResponse.ExecutionPayload.Number), + GasLimit: eth.Uint64Quantity(blockResponse.ExecutionPayload.GasLimit), + GasUsed: eth.Uint64Quantity(blockResponse.ExecutionPayload.GasUsed), + Timestamp: (hexutil.Uint64)(blockResponse.ExecutionPayload.Timestamp), + ExtraData: blockResponse.ExecutionPayload.ExtraData, + BaseFeePerGas: eth.Uint256Quantity(*baseFee), + BlockHash: blockResponse.ExecutionPayload.BlockHash, + Transactions: transactions, + Withdrawals: &withdrawals, + BlobGasUsed: (*hexutil.Uint64)(blockResponse.ExecutionPayload.BlobGasUsed), + ExcessBlobGas: (*hexutil.Uint64)(blockResponse.ExecutionPayload.ExcessBlobGas), }, ParentBeaconBlockRoot: nil, + BlockValue: (*hexutil.U256)(blockValue), + }, nil +} + +func (s *BuilderAPIClient) validateBlockResponse(blockResponse *builderTypes.VersionedBuilderPayloadResponse, ref eth.L2BlockRef) error { + // selects expected data version from the optimism version. + var expectedVersion builderTypes.SpecVersion + if s.rollupCfg.IsEcotone(ref.Time) { + expectedVersion = builderTypes.SpecVersionBedrock + } else if s.rollupCfg.IsCanyon(ref.Time) { + expectedVersion = builderTypes.SpecVersionCanyon + } else { + expectedVersion = builderTypes.SpecVersionEcotone + } + if expectedVersion != blockResponse.Version { + return fmt.Errorf("expected version %s, got %s", expectedVersion, blockResponse.Version) + } + + err := s.verifyBuilderSignature(blockResponse.Message, blockResponse.Signature) + if err != nil { + return fmt.Errorf("failed to verify builder signature: %w", err) } - return envelope + return nil } -func denebExecutionPayloadToExecutionPayload(payload *deneb.ExecutionPayload) *eth.ExecutionPayloadEnvelope { - txs := make([]eth.Data, len(payload.Transactions)) - for i, tx := range payload.Transactions { - txs[i] = eth.Data(tx) +func (s *BuilderAPIClient) verifyBuilderSignature(bidTrace *builderTypes.BidTrace, signature []byte) error { + if s.config.Address != (common.Address{}) && bidTrace.BuilderAddress != s.config.Address { + return fmt.Errorf("block builder address does not match known builder address") } - withdrawals := make([]*types.Withdrawal, len(payload.Withdrawals)) - for i, withdrawal := range payload.Withdrawals { - withdrawals[i] = &types.Withdrawal{ - Index: uint64(withdrawal.Index), - Validator: uint64(withdrawal.ValidatorIndex), - Address: common.BytesToAddress(withdrawal.Address[:]), - Amount: uint64(withdrawal.Amount), - } + payloadBytes, err := rlp.EncodeToBytes(bidTrace) + if err != nil { + return fmt.Errorf("could not encode block bid message: %w", err) + } + signingHash, err := BuilderSigningHash(s.rollupCfg, payloadBytes) + if err != nil { + return fmt.Errorf("failed to compute block signing hash: %w", err) + } + pub, err := crypto.SigToPub(signingHash[:], signature) + if err != nil { + return fmt.Errorf("invalid builder signature: %w", err) } - ws := types.Withdrawals(withdrawals) - envelope := ð.ExecutionPayloadEnvelope{ - ExecutionPayload: ð.ExecutionPayload{ - ParentHash: common.Hash(payload.ParentHash), - FeeRecipient: common.Address(payload.FeeRecipient), - StateRoot: eth.Bytes32(payload.StateRoot), - ReceiptsRoot: eth.Bytes32(payload.ReceiptsRoot), - LogsBloom: eth.Bytes256(payload.LogsBloom), - PrevRandao: eth.Bytes32(payload.PrevRandao), - BlockNumber: eth.Uint64Quantity(payload.BlockNumber), - GasLimit: eth.Uint64Quantity(payload.GasLimit), - GasUsed: eth.Uint64Quantity(payload.GasUsed), - Timestamp: eth.Uint64Quantity(payload.Timestamp), - ExtraData: eth.BytesMax32(payload.ExtraData), - BaseFeePerGas: eth.Uint256Quantity(*payload.BaseFeePerGas), - BlockHash: common.BytesToHash(payload.BlockHash[:]), - Transactions: txs, - Withdrawals: &ws, - BlobGasUsed: (*eth.Uint64Quantity)(&payload.BlobGasUsed), - ExcessBlobGas: (*eth.Uint64Quantity)(&payload.ExcessBlobGas), - }, - ParentBeaconBlockRoot: nil, + addr := crypto.PubkeyToAddress(*pub) + if addr != bidTrace.BuilderAddress { + return fmt.Errorf("block bid signature address does not match builder address: %w", err) } - return envelope + + return nil } diff --git a/op-node/node/config.go b/op-node/node/config.go index 29250d3b5452..95f90fd90040 100644 --- a/op-node/node/config.go +++ b/op-node/node/config.go @@ -77,9 +77,10 @@ type Config struct { ConductorRpcTimeout time.Duration // Builder is used to get payloads from external block builder. - BuilderEnabled bool - BuilderEndpoint string - BuilderTimeout time.Duration + BuilderEnabled bool + BuilderEndpoint string + BuilderTimeout time.Duration + BuilderRequestSigner string // Plasma DA config Plasma plasma.CLIConfig diff --git a/op-node/node/node.go b/op-node/node/node.go index 1424c1ed838f..f1b0136c83f8 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -2,6 +2,7 @@ package node import ( "context" + "crypto/ecdsa" "encoding/json" "errors" "fmt" @@ -9,6 +10,7 @@ import ( "net" "net/http" "strconv" + "strings" "sync/atomic" "time" @@ -23,6 +25,7 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -401,7 +404,19 @@ func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger var payloadBuilder builder.PayloadBuilder = &builder.NoOpBuilder{} if cfg.BuilderEnabled { - payloadBuilder = NewBuilderClient(n.log, &cfg.Rollup, cfg.BuilderEndpoint, cfg.BuilderTimeout) + var signer *ecdsa.PrivateKey + if cfg.BuilderRequestSigner != "" { + signer, err = crypto.HexToECDSA(strings.TrimPrefix(cfg.BuilderRequestSigner, "0x")) + if err != nil { + return fmt.Errorf("invalid proposer signer: %w", err) + } + } else { + log.Warn("Proposer signer not provided, reuests to the builder will be unauthenticated") + } + payloadBuilder, err = NewBuilderClient(n.log, &cfg.Rollup, cfg.BuilderEndpoint, cfg.BuilderTimeout, signer) + if err != nil { + return fmt.Errorf("failed to create builder client: %w", err) + } } // if plasma is not explicitly activated in the node CLI, the config + any error will be ignored. @@ -630,7 +645,7 @@ func (n *OpNode) PublishL2Attributes(ctx context.Context, attrs *derive.Attribut n.log.Warn("failed to marshal payload attributes", "err", err) return err } - n.log.Info("Publishing execution payload attributes on event stream", "attrs", builderAttrs) + n.log.Info("Publishing execution payload attributes on event stream", "slot", builderAttrs.Slot) n.httpEventStream.Publish("payload_attributes", &sse.Event{Data: jsonBytes}) return nil } diff --git a/op-node/node/types/payload.go b/op-node/node/types/payload.go new file mode 100644 index 000000000000..e637a63dac73 --- /dev/null +++ b/op-node/node/types/payload.go @@ -0,0 +1,254 @@ +package types + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" +) + +var SigningDomainBuilderV1 = [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + +type PayloadRequestV1 struct { + Slot uint64 `json:"slot"` + ParentHash common.Hash `json:"parentHash"` +} + +// MarshalJSON marshals as JSON. +func (p *PayloadRequestV1) MarshalJSON() ([]byte, error) { + type PayloadRequestV1 struct { + Slot hexutil.Uint64 `json:"slot"` + ParentHash common.Hash `json:"parentHash"` + } + + var enc PayloadRequestV1 + enc.Slot = hexutil.Uint64(p.Slot) + enc.ParentHash = p.ParentHash + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (p *PayloadRequestV1) UnmarshalJSON(input []byte) error { + type PayloadRequestV1 struct { + Slot *hexutil.Uint64 `json:"slot"` + ParentHash *common.Hash `json:"parentHash"` + } + + var dec PayloadRequestV1 + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + + if dec.Slot == nil { + return errors.New("missing required field 'slot' for PayloadRequestV1") + } + p.Slot = uint64(*dec.Slot) + if dec.ParentHash == nil { + return errors.New("missing required field 'parentHash' for PayloadRequestV1") + } + p.ParentHash = *dec.ParentHash + return nil +} + +type BuilderPayloadRequest struct { + Message PayloadRequestV1 `json:"message"` + Signature []byte `json:"signature"` +} + +// MarshalJSON marshals as JSON. +func (p *BuilderPayloadRequest) MarshalJSON() ([]byte, error) { + type BuilderPayloadRequest struct { + Message PayloadRequestV1 `json:"message"` + Signature hexutil.Bytes `json:"signature"` + } + + var enc BuilderPayloadRequest + enc.Message = p.Message + enc.Signature = p.Signature + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (p *BuilderPayloadRequest) UnmarshalJSON(input []byte) error { + type BuilderPayloadRequest struct { + Message *PayloadRequestV1 `json:"message"` + Signature *hexutil.Bytes `json:"signature"` + } + + var dec BuilderPayloadRequest + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + + if dec.Message == nil { + return errors.New("missing required field 'message' for PayloadRequestV1") + } + p.Message = *dec.Message + if dec.Signature == nil { + return errors.New("missing required field 'signature' for PayloadRequestV1") + } + p.Signature = *dec.Signature + return nil +} + +// BidTrace represents a bid trace. +type BidTrace struct { + Slot uint64 `json:"slot"` + ParentHash common.Hash `json:"parentHash"` + BlockHash common.Hash `json:"blockHash"` + BuilderAddress common.Address `json:"builderAddress"` + ProposerAddress common.Address `json:"proposerAddress"` + ProposerFeeRecipient common.Address `json:"proposerFeeRecipient"` + GasLimit uint64 `json:"gasLimit"` + GasUsed uint64 `json:"gasUsed"` + Value *big.Int `json:"value"` +} + +// MarshalJSON marshals as JSON. +func (b *BidTrace) MarshalJSON() ([]byte, error) { + type BidTrace struct { + Slot hexutil.Uint64 `json:"slot"` + ParentHash common.Hash `json:"parentHash"` + BlockHash common.Hash `json:"blockHash"` + BuilderAddress common.Address `json:"builderAddress"` + ProposerAddress common.Address `json:"proposerAddress"` + ProposerFeeRecipient common.Address `json:"proposerFeeRecipient"` + GasLimit hexutil.Uint64 `json:"gasLimit"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + Value *hexutil.Big `json:"value"` + } + + var enc BidTrace + enc.Slot = hexutil.Uint64(b.Slot) + enc.ParentHash = b.ParentHash + enc.BlockHash = b.BlockHash + enc.BuilderAddress = b.BuilderAddress + enc.ProposerAddress = b.ProposerAddress + enc.ProposerFeeRecipient = b.ProposerFeeRecipient + enc.GasLimit = hexutil.Uint64(b.GasLimit) + enc.GasUsed = hexutil.Uint64(b.GasUsed) + enc.Value = (*hexutil.Big)(b.Value) + return json.Marshal(enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (b *BidTrace) UnmarshalJSON(input []byte) error { + type BidTrace struct { + Slot *hexutil.Uint64 `json:"slot"` + ParentHash *common.Hash `json:"parentHash"` + BlockHash *common.Hash `json:"blockHash"` + BuilderAddress *common.Address `json:"builderAddress"` + ProposerAddress *common.Address `json:"proposerAddress"` + ProposerFeeRecipient *common.Address `json:"proposerFeeRecipient"` + GasLimit *hexutil.Uint64 `json:"gasLimit"` + GasUsed *hexutil.Uint64 `json:"gasUsed"` + Value *hexutil.Big `json:"value"` + } + + var dec BidTrace + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + + if dec.Slot == nil { + return errors.New("missing required field 'slot' for BidTrace") + } + b.Slot = uint64(*dec.Slot) + if dec.ParentHash == nil { + return errors.New("missing required field 'parentHash' for BidTrace") + } + b.ParentHash = *dec.ParentHash + if dec.BlockHash == nil { + return errors.New("missing required field 'blockHash' for BidTrace") + } + b.BlockHash = *dec.BlockHash + if dec.BuilderAddress == nil { + return errors.New("missing required field 'builderAddress' for BidTrace") + } + b.BuilderAddress = *dec.BuilderAddress + if dec.ProposerAddress == nil { + return errors.New("missing required field 'proposerAddress' for BidTrace") + } + b.ProposerAddress = *dec.ProposerAddress + if dec.ProposerFeeRecipient == nil { + return errors.New("missing required field 'proposerFeeRecipient' for BidTrace") + } + b.ProposerFeeRecipient = *dec.ProposerFeeRecipient + if dec.GasLimit == nil { + return errors.New("missing required field 'gasLimit' for BidTrace") + } + b.GasLimit = uint64(*dec.GasLimit) + if dec.GasUsed == nil { + return errors.New("missing required field 'gasUsed' for BidTrace") + } + b.GasUsed = uint64(*dec.GasUsed) + if dec.Value == nil { + return errors.New("missing required field 'value' for BidTrace") + } + b.Value = (*big.Int)(dec.Value) + if b.Value == nil { + return errors.New("missing required field 'value' for BidTrace") + } + return nil +} + +// VersionedBuilderPayloadResponse contains a versioned signed builder payload. +type VersionedBuilderPayloadResponse struct { + Version SpecVersion `json:"version"` + Message *BidTrace `json:"message"` + ExecutionPayload *engine.ExecutableData `json:"executionPayload"` + Signature []byte `json:"signature"` +} + +// MarshalJSON marshals as JSON. +func (p *VersionedBuilderPayloadResponse) MarshalJSON() ([]byte, error) { + type VersionedBuilderPayloadResponse struct { + Version *SpecVersion `json:"version"` + Message *BidTrace `json:"message"` + ExecutionPayload *engine.ExecutableData `json:"executionPayload"` + Signature hexutil.Bytes `json:"signature"` + } + + var enc VersionedBuilderPayloadResponse + enc.Version = &p.Version + enc.Message = p.Message + enc.ExecutionPayload = p.ExecutionPayload + enc.Signature = p.Signature + return json.Marshal(enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (p *VersionedBuilderPayloadResponse) UnmarshalJSON(input []byte) error { + type VersionedBuilderPayloadResponse struct { + Version *SpecVersion `json:"version"` + Message *BidTrace `json:"message"` + ExecutionPayload *engine.ExecutableData `json:"executionPayload"` + Signature hexutil.Bytes `json:"signature"` + } + + var dec VersionedBuilderPayloadResponse + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + + if dec.Version == nil { + return errors.New("missing required field 'version' for VersionedBuilderPayloadResponse") + } + p.Version = *dec.Version + if dec.Message == nil { + return errors.New("missing required field 'message' for VersionedBuilderPayloadResponse") + } + p.Message = dec.Message + if dec.ExecutionPayload == nil { + return errors.New("missing required field 'executionPayload' for VersionedBuilderPayloadResponse") + } + p.ExecutionPayload = dec.ExecutionPayload + if dec.Signature == nil { + return errors.New("missing required field 'signature' for VersionedBuilderPayloadResponse") + } + p.Signature = dec.Signature + return nil +} diff --git a/op-node/node/types/version.go b/op-node/node/types/version.go new file mode 100644 index 000000000000..025b60606c96 --- /dev/null +++ b/op-node/node/types/version.go @@ -0,0 +1,58 @@ +package types + +import ( + "fmt" + "strings" +) + +// SpecVersion defines the version of the spec in the payload. +type SpecVersion uint64 + +const ( + // Unknown is an unknown spec version. + Unknown SpecVersion = iota + // SpecVersionBedrock is the spec version equivalent to the bellatrix release of the l1 beacon chain. + SpecVersionBedrock + // SpecVersionCanyon is the spec version equivalent to the capella release of the l1 beacon chain. + SpecVersionCanyon + // SpecVersionEcotone is the spec version equivalent to the deneb release of the l1 beacon chain. + SpecVersionEcotone +) + +var specVersionStrings = [...]string{ + "unknown", + "bedrock", + "canyon", + "ecotone", +} + +// MarshalJSON implements json.Marshaler. +func (d *SpecVersion) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("%q", specVersionStrings[*d])), nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (d *SpecVersion) UnmarshalJSON(input []byte) error { + var err error + switch strings.ToLower(string(input)) { + case `"bedrock"`: + *d = SpecVersionBedrock + case `"canyon"`: + *d = SpecVersionCanyon + case `"ecotone"`: + *d = SpecVersionEcotone + default: + err = fmt.Errorf("unrecognised spec version %s", string(input)) + } + + return err +} + +// String returns a string representation of the struct. +func (d SpecVersion) String() string { + if int(d) >= len(specVersionStrings) { + return "unknown" + } + + return specVersionStrings[d] +} diff --git a/op-node/service.go b/op-node/service.go index 07faa83a14ca..e673bef38ab1 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -114,9 +114,10 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { ConductorRpc: ctx.String(flags.ConductorRpcFlag.Name), ConductorRpcTimeout: ctx.Duration(flags.ConductorRpcTimeoutFlag.Name), - BuilderEnabled: ctx.Bool(flags.BuilderEnabledFlag.Name), - BuilderEndpoint: ctx.String(flags.BuilderEndpointFlag.Name), - BuilderTimeout: ctx.Duration(flags.BuilderRequestTimeoutFlag.Name), + BuilderEnabled: ctx.Bool(flags.BuilderEnabledFlag.Name), + BuilderEndpoint: ctx.String(flags.BuilderEndpointFlag.Name), + BuilderTimeout: ctx.Duration(flags.BuilderRequestTimeoutFlag.Name), + BuilderRequestSigner: ctx.String(flags.BuilderRequestSignerFlag.Name), Plasma: plasma.ReadCLIConfig(ctx), diff --git a/op-service/testutils/metrics.go b/op-service/testutils/metrics.go index 3783e710376d..9dbe0e718e39 100644 --- a/op-service/testutils/metrics.go +++ b/op-service/testutils/metrics.go @@ -71,7 +71,7 @@ func (n *TestDerivationMetrics) RecordBuilderRequestFail() { func (n *TestDerivationMetrics) RecordBuilderRequestTimeout() { } -func (n *TestDerivationMetrics) RecordSequencerProfit(profit float64, source string) { +func (n *TestDerivationMetrics) RecordSequencerProfit(profit float64, source metrics.PayloadSource) { } func (n *TestDerivationMetrics) RecordSequencerPayloadInserted(source metrics.PayloadSource) { @@ -80,6 +80,9 @@ func (n *TestDerivationMetrics) RecordSequencerPayloadInserted(source metrics.Pa func (n *TestDerivationMetrics) RecordPayloadGas(gas float64, source string) { } +func (n *TestDerivationMetrics) RecordBuilderPayloadBytes(bytes int) { +} + type TestRPCMetrics struct{} func (n *TestRPCMetrics) RecordRPCServerRequest(method string) func() { diff --git a/ops-bedrock/docker-compose.yml b/ops-bedrock/docker-compose.yml index 9c8b1600d822..19240509f96f 100644 --- a/ops-bedrock/docker-compose.yml +++ b/ops-bedrock/docker-compose.yml @@ -102,7 +102,8 @@ services: --plasma.da-service=${PLASMA_DA_SERVICE} --plasma.da-server=http://da-server:3100 --l2.builder.enabled=true - --l2.builder.endpoint=http://builder-op-geth:28545 + --l2.builder.endpoint=http://0xc64498FA4661Ac5Ecd8c9915E232f57eb020cA00@builder-op-geth:28545 + --l2.builder.request-signer=8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba ports: - "7545:8545" - "9003:9003" @@ -317,6 +318,7 @@ services: ports: - "5545:8545" - "6063:6060" + - "28545:28545" volumes: - "builder_data:/db" - "${PWD}/../.devnet/genesis-l2.json:/genesis.json" @@ -336,10 +338,11 @@ services: - "--builder.beacon_endpoints=http://builder-op-node:9546" - "--builder.block_retry_interval=100ms" - "--builder.block_time=2s" + - "--builder.proposer_signing_address=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" + - "--builder.signing_key=2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11" - "--metrics" environment: GETH_MINER_RECOMMIT: 100ms - BUILDER_TX_SIGNING_KEY: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" networks: custom_net: ipv4_address: "10.5.0.201" diff --git a/pbs/README.md b/pbs/README.md index 531c99000dba..33644920c31d 100644 --- a/pbs/README.md +++ b/pbs/README.md @@ -40,29 +40,31 @@ These are the configuration options to enable PBS for the devnet. There are three flags that configure the sequencer to request payloads from the builder API endpoint: -| Flag | Description | Default Value | -|---------------------------|---------------------------------------------------------------------|---------------| -| `l2.builder.enabled` | Enable the builder API request to get payloads built from the builder. | `false` | -| `l2.builder.endpoint` | The URL of the builder API endpoint. | `""` | -| `l2.builder.timeout` | The timeout for the builder API request. | `500ms` | +| Flag | Description | Default Value | +|----------------------------|----------------------------------------------------------------------------|---------------| +| `l2.builder.enabled` | Enable the builder API request to get payloads built from the builder. | `false` | +| `l2.builder.endpoint` | The URL of the builder API endpoint. | `""` | +| `l2.builder.timeout` | The timeout for the builder API request. | `500ms` | +| `l2.builder.request-signer`| The key of the proposer signing the builder requests for authentication. | `""` | ### builder-op-node The op-geth builder requires the op-node to publish the latest attributes as server-sent events in order to start building the payloads. -| Flag | Description | Default Value | -|-----------------------------|---------------------------------------------------------------------------------|---------------| -| `sequencer.publish-attributes` | Set to true to enable the sequencer to publish attributes to the event stream. | `false` | -| `eventstream.addr` | The address of the eventstream server. | `127.0.0.1` | -| `eventstream.port` | The port of the eventstream server. | `9546` | +| Flag | Description | Default Value | +|----------------------------------|---------------------------------------------------------------------------------|---------------| +| `sequencer.publish-attributes` | Set to true to enable the sequencer to publish attributes to the event stream | `false` | +| `eventstream.addr` | The address of the eventstream server | `127.0.0.1` | +| `eventstream.port` | The port of the eventstream server | `9546` | ### builder-op-geth These are the builder flags to enable the builder service in op-geth: -| Flag | Description | Default Value | -|----------------------------------|----------------------------------------------------------------------------------------------|---------------| -| `builder` | Enable the builder service. | `false` | -| `builder.beacon_endpoints` | The op-node address to get the payload attributes from. Should be set to `builder-op-node`. | `""` | -| `builder.block_retry_interval` | The interval to retry building the payload. | `500ms` | -| `builder.block_time` | Block time of the network. | `2s` | +| Flag | Description | Default Value | +|-----------------------------------|----------------------------------------------------------------------------------------------|---------------| +| `builder` | Enable the builder service. | `false` | +| `builder.beacon_endpoints` | The op-node address to get the payload attributes from. Should be set to `builder-op-node`. | `""` | +| `builder.block_retry_interval` | The interval to retry building the payload. | `500ms` | +| `builder.block_time` | Block time of the network. | `2s` | +| `builder.proposer_signing_address`| The address of the proposer signing the builder requests. | `""` |