Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
f1cb42df
Unverified
Commit
f1cb42df
authored
Oct 11, 2023
by
Danyal Prout
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Canyon: P2P / Testing
parent
9b59013c
Changes
26
Hide whitespace changes
Inline
Side-by-side
Showing
26 changed files
with
862 additions
and
137 deletions
+862
-137
config.go
op-chain-ops/genesis/config.go
+16
-1
config_test.go
op-chain-ops/genesis/config_test.go
+12
-0
genesis.go
op-chain-ops/genesis/genesis.go
+2
-0
l2_engine_test.go
op-e2e/actions/l2_engine_test.go
+11
-3
setup.go
op-e2e/e2eutils/setup.go
+10
-0
op_geth.go
op-e2e/op_geth.go
+8
-1
op_geth_test.go
op-e2e/op_geth_test.go
+147
-5
setup.go
op-e2e/setup.go
+2
-0
system_test.go
op-e2e/system_test.go
+1
-1
node.go
op-node/node/node.go
+1
-0
gossip.go
op-node/p2p/gossip.go
+116
-35
gossip_test.go
op-node/p2p/gossip_test.go
+5
-0
rpc_server.go
op-node/p2p/rpc_server.go
+13
-11
sync.go
op-node/p2p/sync.go
+11
-5
attributes.go
op-node/rollup/derive/attributes.go
+6
-0
engine_queue.go
op-node/rollup/derive/engine_queue.go
+1
-0
engine.go
op-program/client/l2/engine.go
+8
-4
engine_test.go
op-program/client/l2/engine_test.go
+3
-3
l2_engine_api.go
op-program/client/l2/engineapi/l2_engine_api.go
+21
-1
l2_engine_api_tests.go
op-program/client/l2/engineapi/test/l2_engine_api_tests.go
+42
-18
Makefile
op-service/Makefile
+2
-1
ssz.go
op-service/eth/ssz.go
+165
-19
ssz_test.go
op-service/eth/ssz_test.go
+206
-18
types.go
op-service/eth/types.go
+39
-8
types.go
op-service/sources/types.go
+2
-0
rollup-node-p2p.md
specs/rollup-node-p2p.md
+12
-3
No files found.
op-chain-ops/genesis/config.go
View file @
f1cb42df
...
...
@@ -107,8 +107,11 @@ type DeployConfig struct {
L2GenesisBlockBaseFeePerGas
*
hexutil
.
Big
`json:"l2GenesisBlockBaseFeePerGas"`
// L2GenesisRegolithTimeOffset is the number of seconds after genesis block that Regolith hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable
r
egolith.
// Set it to 0 to activate at genesis. Nil to disable
R
egolith.
L2GenesisRegolithTimeOffset
*
hexutil
.
Uint64
`json:"l2GenesisRegolithTimeOffset,omitempty"`
// L2GenesisCanyonTimeOffset is the number of seconds after genesis block that Canyon hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Canyon.
L2GenesisCanyonTimeOffset
*
hexutil
.
Uint64
`json:"L2GenesisCanyonTimeOffset,omitempty"`
// L2GenesisSpanBatchTimeOffset is the number of seconds after genesis block that Span Batch hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable SpanBatch.
L2GenesisSpanBatchTimeOffset
*
hexutil
.
Uint64
`json:"l2GenesisSpanBatchTimeOffset,omitempty"`
...
...
@@ -444,6 +447,17 @@ func (d *DeployConfig) RegolithTime(genesisTime uint64) *uint64 {
return
&
v
}
func
(
d
*
DeployConfig
)
CanyonTime
(
genesisTime
uint64
)
*
uint64
{
if
d
.
L2GenesisCanyonTimeOffset
==
nil
{
return
nil
}
v
:=
uint64
(
0
)
if
offset
:=
*
d
.
L2GenesisCanyonTimeOffset
;
offset
>
0
{
v
=
genesisTime
+
uint64
(
offset
)
}
return
&
v
}
func
(
d
*
DeployConfig
)
SpanBatchTime
(
genesisTime
uint64
)
*
uint64
{
if
d
.
L2GenesisSpanBatchTimeOffset
==
nil
{
return
nil
...
...
@@ -492,6 +506,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHas
DepositContractAddress
:
d
.
OptimismPortalProxy
,
L1SystemConfigAddress
:
d
.
SystemConfigProxy
,
RegolithTime
:
d
.
RegolithTime
(
l1StartBlock
.
Time
()),
CanyonTime
:
d
.
CanyonTime
(
l1StartBlock
.
Time
()),
SpanBatchTime
:
d
.
SpanBatchTime
(
l1StartBlock
.
Time
()),
},
nil
}
...
...
op-chain-ops/genesis/config_test.go
View file @
f1cb42df
...
...
@@ -49,6 +49,18 @@ func TestRegolithTimeAsOffset(t *testing.T) {
require
.
Equal
(
t
,
uint64
(
1500
+
5000
),
*
config
.
RegolithTime
(
5000
))
}
func
TestCanyonTimeZero
(
t
*
testing
.
T
)
{
canyonOffset
:=
hexutil
.
Uint64
(
0
)
config
:=
&
DeployConfig
{
L2GenesisCanyonTimeOffset
:
&
canyonOffset
}
require
.
Equal
(
t
,
uint64
(
0
),
*
config
.
CanyonTime
(
1234
))
}
func
TestCanyonTimeOffset
(
t
*
testing
.
T
)
{
canyonOffset
:=
hexutil
.
Uint64
(
1500
)
config
:=
&
DeployConfig
{
L2GenesisCanyonTimeOffset
:
&
canyonOffset
}
require
.
Equal
(
t
,
uint64
(
1234
+
1500
),
*
config
.
CanyonTime
(
1234
))
}
// TestCopy will copy a DeployConfig and ensure that the copy is equal to the original.
func
TestCopy
(
t
*
testing
.
T
)
{
b
,
err
:=
os
.
ReadFile
(
"testdata/test-deploy-config-full.json"
)
...
...
op-chain-ops/genesis/genesis.go
View file @
f1cb42df
...
...
@@ -58,6 +58,8 @@ func NewL2Genesis(config *DeployConfig, block *types.Block) (*core.Genesis, erro
TerminalTotalDifficultyPassed
:
true
,
BedrockBlock
:
new
(
big
.
Int
)
.
SetUint64
(
uint64
(
config
.
L2GenesisBlockNumber
)),
RegolithTime
:
config
.
RegolithTime
(
block
.
Time
()),
CanyonTime
:
config
.
CanyonTime
(
block
.
Time
()),
ShanghaiTime
:
config
.
CanyonTime
(
block
.
Time
()),
Optimism
:
&
params
.
OptimismConfig
{
EIP1559Denominator
:
eip1559Denom
,
EIP1559Elasticity
:
eip1559Elasticity
,
...
...
op-e2e/actions/l2_engine_test.go
View file @
f1cb42df
...
...
@@ -46,7 +46,7 @@ func TestL2EngineAPI(gt *testing.T) {
chainA
,
_
:=
core
.
GenerateChain
(
sd
.
L2Cfg
.
Config
,
genesisBlock
,
consensus
,
db
,
1
,
func
(
i
int
,
gen
*
core
.
BlockGen
)
{
gen
.
SetCoinbase
(
common
.
Address
{
'A'
})
})
payloadA
,
err
:=
eth
.
BlockAsPayload
(
chainA
[
0
])
payloadA
,
err
:=
eth
.
BlockAsPayload
(
chainA
[
0
]
,
sd
.
RollupCfg
.
CanyonTime
)
require
.
NoError
(
t
,
err
)
// apply the payload
...
...
@@ -69,7 +69,7 @@ func TestL2EngineAPI(gt *testing.T) {
chainB
,
_
:=
core
.
GenerateChain
(
sd
.
L2Cfg
.
Config
,
genesisBlock
,
consensus
,
db
,
1
,
func
(
i
int
,
gen
*
core
.
BlockGen
)
{
gen
.
SetCoinbase
(
common
.
Address
{
'B'
})
})
payloadB
,
err
:=
eth
.
BlockAsPayload
(
chainB
[
0
])
payloadB
,
err
:=
eth
.
BlockAsPayload
(
chainB
[
0
]
,
sd
.
RollupCfg
.
CanyonTime
)
require
.
NoError
(
t
,
err
)
// apply the payload
...
...
@@ -125,18 +125,26 @@ func TestL2EngineAPIBlockBuilding(gt *testing.T) {
l2Cl
,
err
:=
sources
.
NewEngineClient
(
engine
.
RPCClient
(),
log
,
nil
,
sources
.
EngineClientDefaultConfig
(
sd
.
RollupCfg
))
require
.
NoError
(
t
,
err
)
nextBlockTime
:=
eth
.
Uint64Quantity
(
parent
.
Time
)
+
2
var
w
*
eth
.
Withdrawals
if
sd
.
RollupCfg
.
IsCanyon
(
uint64
(
nextBlockTime
))
{
w
=
&
eth
.
Withdrawals
{}
}
// Now let's ask the engine to build a block
fcRes
,
err
:=
l2Cl
.
ForkchoiceUpdate
(
t
.
Ctx
(),
&
eth
.
ForkchoiceState
{
HeadBlockHash
:
parent
.
Hash
(),
SafeBlockHash
:
genesisBlock
.
Hash
(),
FinalizedBlockHash
:
genesisBlock
.
Hash
(),
},
&
eth
.
PayloadAttributes
{
Timestamp
:
eth
.
Uint64Quantity
(
parent
.
Time
)
+
2
,
Timestamp
:
nextBlockTime
,
PrevRandao
:
eth
.
Bytes32
{},
SuggestedFeeRecipient
:
common
.
Address
{
'C'
},
Transactions
:
nil
,
NoTxPool
:
false
,
GasLimit
:
(
*
eth
.
Uint64Quantity
)(
&
sd
.
RollupCfg
.
Genesis
.
SystemConfig
.
GasLimit
),
Withdrawals
:
w
,
})
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
fcRes
.
PayloadStatus
.
Status
,
eth
.
ExecutionValid
)
...
...
op-e2e/e2eutils/setup.go
View file @
f1cb42df
...
...
@@ -58,6 +58,7 @@ func MakeDeployParams(t require.TestingT, tp *TestParams) *DeployParams {
deployConfig
.
ChannelTimeout
=
tp
.
ChannelTimeout
deployConfig
.
L1BlockTime
=
tp
.
L1BlockTime
deployConfig
.
L2GenesisRegolithTimeOffset
=
nil
deployConfig
.
L2GenesisCanyonTimeOffset
=
CanyonTimeOffset
()
deployConfig
.
L2GenesisSpanBatchTimeOffset
=
SpanBatchTimeOffset
()
require
.
NoError
(
t
,
deployConfig
.
Check
())
...
...
@@ -157,6 +158,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
DepositContractAddress
:
deployConf
.
OptimismPortalProxy
,
L1SystemConfigAddress
:
deployConf
.
SystemConfigProxy
,
RegolithTime
:
deployConf
.
RegolithTime
(
uint64
(
deployConf
.
L1GenesisBlockTimestamp
)),
CanyonTime
:
deployConf
.
CanyonTime
(
uint64
(
deployConf
.
L1GenesisBlockTimestamp
)),
SpanBatchTime
:
deployConf
.
SpanBatchTime
(
uint64
(
deployConf
.
L1GenesisBlockTimestamp
)),
}
...
...
@@ -191,3 +193,11 @@ func SpanBatchTimeOffset() *hexutil.Uint64 {
}
return
nil
}
func
CanyonTimeOffset
()
*
hexutil
.
Uint64
{
if
os
.
Getenv
(
"OP_E2E_USE_CANYON"
)
==
"true"
{
offset
:=
hexutil
.
Uint64
(
0
)
return
&
offset
}
return
nil
}
op-e2e/op_geth.go
View file @
f1cb42df
...
...
@@ -105,7 +105,7 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e
l2Client
,
err
:=
ethclient
.
Dial
(
node
.
HTTPEndpoint
())
require
.
Nil
(
t
,
err
)
genesisPayload
,
err
:=
eth
.
BlockAsPayload
(
l2GenesisBlock
)
genesisPayload
,
err
:=
eth
.
BlockAsPayload
(
l2GenesisBlock
,
cfg
.
DeployConfig
.
CanyonTime
(
l2GenesisBlock
.
Time
())
)
require
.
Nil
(
t
,
err
)
return
&
OpGeth
{
...
...
@@ -209,11 +209,18 @@ func (d *OpGeth) CreatePayloadAttributes(txs ...*types.Transaction) (*eth.Payloa
}
txBytes
=
append
(
txBytes
,
bin
)
}
var
withdrawals
*
eth
.
Withdrawals
if
d
.
L2ChainConfig
.
IsCanyon
(
uint64
(
timestamp
))
{
withdrawals
=
&
eth
.
Withdrawals
{}
}
attrs
:=
eth
.
PayloadAttributes
{
Timestamp
:
timestamp
,
Transactions
:
txBytes
,
NoTxPool
:
true
,
GasLimit
:
(
*
eth
.
Uint64Quantity
)(
&
d
.
SystemConfig
.
GasLimit
),
Withdrawals
:
withdrawals
,
}
return
&
attrs
,
nil
}
op-e2e/op_geth_test.go
View file @
f1cb42df
...
...
@@ -2,12 +2,13 @@ package op_e2e
import
(
"context"
"fmt"
"math/big"
"testing"
"time"
"github.com/
stretchr/testify/requir
e"
"github.com/
ethereum-optimism/optimism/op-node/rollup/deriv
e"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
...
...
@@ -16,9 +17,8 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestMissingGasLimit tests that op-geth cannot build a block without gas limit while optimism is active in the chain config.
...
...
@@ -719,3 +719,145 @@ func TestRegolith(t *testing.T) {
})
}
}
func
TestPreCanyon
(
t
*
testing
.
T
)
{
InitParallel
(
t
)
futureTimestamp
:=
hexutil
.
Uint64
(
4
)
tests
:=
[]
struct
{
name
string
canyonTime
*
hexutil
.
Uint64
}{
{
name
:
"CanyonNotScheduled"
},
{
name
:
"CanyonNotYetActive"
,
canyonTime
:
&
futureTimestamp
},
}
for
_
,
test
:=
range
tests
{
test
:=
test
t
.
Run
(
fmt
.
Sprintf
(
"ReturnsNilWithdrawals_%s"
,
test
.
name
),
func
(
t
*
testing
.
T
)
{
InitParallel
(
t
)
cfg
:=
DefaultSystemConfig
(
t
)
cfg
.
DeployConfig
.
L2GenesisCanyonTimeOffset
=
test
.
canyonTime
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
60
*
time
.
Second
)
defer
cancel
()
opGeth
,
err
:=
NewOpGeth
(
t
,
ctx
,
&
cfg
)
require
.
NoError
(
t
,
err
)
defer
opGeth
.
Close
()
b
,
err
:=
opGeth
.
AddL2Block
(
ctx
)
require
.
NoError
(
t
,
err
)
assert
.
Nil
(
t
,
b
.
Withdrawals
,
"should not have withdrawals"
)
l1Block
,
err
:=
opGeth
.
L2Client
.
BlockByNumber
(
ctx
,
nil
)
require
.
Nil
(
t
,
err
)
assert
.
Equal
(
t
,
types
.
Withdrawals
(
nil
),
l1Block
.
Withdrawals
())
})
t
.
Run
(
fmt
.
Sprintf
(
"RejectPushZeroTx_%s"
,
test
.
name
),
func
(
t
*
testing
.
T
)
{
InitParallel
(
t
)
cfg
:=
DefaultSystemConfig
(
t
)
cfg
.
DeployConfig
.
L2GenesisCanyonTimeOffset
=
test
.
canyonTime
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
60
*
time
.
Second
)
defer
cancel
()
opGeth
,
err
:=
NewOpGeth
(
t
,
ctx
,
&
cfg
)
require
.
NoError
(
t
,
err
)
defer
opGeth
.
Close
()
pushZeroContractCreateTxn
:=
types
.
NewTx
(
&
types
.
DepositTx
{
From
:
cfg
.
Secrets
.
Addresses
()
.
Alice
,
Value
:
big
.
NewInt
(
params
.
Ether
),
Gas
:
1000001
,
Data
:
[]
byte
{
byte
(
vm
.
PUSH0
),
},
IsSystemTransaction
:
false
,
})
_
,
err
=
opGeth
.
AddL2Block
(
ctx
,
pushZeroContractCreateTxn
)
require
.
NoError
(
t
,
err
)
receipt
,
err
:=
opGeth
.
L2Client
.
TransactionReceipt
(
ctx
,
pushZeroContractCreateTxn
.
Hash
())
require
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
types
.
ReceiptStatusFailed
,
receipt
.
Status
)
})
}
}
func
TestCanyon
(
t
*
testing
.
T
)
{
InitParallel
(
t
)
tests
:=
[]
struct
{
name
string
canyonTime
hexutil
.
Uint64
activeCanyon
func
(
ctx
context
.
Context
,
opGeth
*
OpGeth
)
}{
{
name
:
"ActivateAtGenesis"
,
canyonTime
:
0
,
activeCanyon
:
func
(
ctx
context
.
Context
,
opGeth
*
OpGeth
)
{}},
{
name
:
"ActivateAfterGenesis"
,
canyonTime
:
2
,
activeCanyon
:
func
(
ctx
context
.
Context
,
opGeth
*
OpGeth
)
{
// Adding this block advances us to the fork time.
_
,
err
:=
opGeth
.
AddL2Block
(
ctx
)
require
.
NoError
(
t
,
err
)
}},
}
for
_
,
test
:=
range
tests
{
test
:=
test
t
.
Run
(
fmt
.
Sprintf
(
"ReturnsEmptyWithdrawals_%s"
,
test
.
name
),
func
(
t
*
testing
.
T
)
{
InitParallel
(
t
)
cfg
:=
DefaultSystemConfig
(
t
)
s
:=
hexutil
.
Uint64
(
0
)
cfg
.
DeployConfig
.
L2GenesisRegolithTimeOffset
=
&
s
cfg
.
DeployConfig
.
L2GenesisCanyonTimeOffset
=
&
test
.
canyonTime
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
60
*
time
.
Second
)
defer
cancel
()
opGeth
,
err
:=
NewOpGeth
(
t
,
ctx
,
&
cfg
)
require
.
NoError
(
t
,
err
)
defer
opGeth
.
Close
()
test
.
activeCanyon
(
ctx
,
opGeth
)
b
,
err
:=
opGeth
.
AddL2Block
(
ctx
)
require
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
*
b
.
Withdrawals
,
eth
.
Withdrawals
{})
l1Block
,
err
:=
opGeth
.
L2Client
.
BlockByNumber
(
ctx
,
nil
)
require
.
Nil
(
t
,
err
)
assert
.
Equal
(
t
,
l1Block
.
Withdrawals
(),
types
.
Withdrawals
{})
})
t
.
Run
(
fmt
.
Sprintf
(
"AcceptsPushZeroTxn_%s"
,
test
.
name
),
func
(
t
*
testing
.
T
)
{
InitParallel
(
t
)
cfg
:=
DefaultSystemConfig
(
t
)
cfg
.
DeployConfig
.
L2GenesisCanyonTimeOffset
=
&
test
.
canyonTime
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
60
*
time
.
Second
)
defer
cancel
()
opGeth
,
err
:=
NewOpGeth
(
t
,
ctx
,
&
cfg
)
require
.
NoError
(
t
,
err
)
defer
opGeth
.
Close
()
pushZeroContractCreateTxn
:=
types
.
NewTx
(
&
types
.
DepositTx
{
From
:
cfg
.
Secrets
.
Addresses
()
.
Alice
,
Value
:
big
.
NewInt
(
params
.
Ether
),
Gas
:
1000001
,
Data
:
[]
byte
{
byte
(
vm
.
PUSH0
),
},
IsSystemTransaction
:
false
,
})
_
,
err
=
opGeth
.
AddL2Block
(
ctx
,
pushZeroContractCreateTxn
)
require
.
NoError
(
t
,
err
)
receipt
,
err
:=
opGeth
.
L2Client
.
TransactionReceipt
(
ctx
,
pushZeroContractCreateTxn
.
Hash
())
require
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
types
.
ReceiptStatusSuccessful
,
receipt
.
Status
)
})
}
}
op-e2e/setup.go
View file @
f1cb42df
...
...
@@ -86,6 +86,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig {
require
.
NoError
(
t
,
err
)
deployConfig
:=
config
.
DeployConfig
.
Copy
()
deployConfig
.
L1GenesisBlockTimestamp
=
hexutil
.
Uint64
(
time
.
Now
()
.
Unix
())
deployConfig
.
L2GenesisCanyonTimeOffset
=
e2eutils
.
CanyonTimeOffset
()
deployConfig
.
L2GenesisSpanBatchTimeOffset
=
e2eutils
.
SpanBatchTimeOffset
()
require
.
NoError
(
t
,
deployConfig
.
Check
(),
"Deploy config is invalid, do you need to run make devnet-allocs?"
)
l1Deployments
:=
config
.
L1Deployments
.
Copy
()
...
...
@@ -425,6 +426,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
DepositContractAddress
:
cfg
.
DeployConfig
.
OptimismPortalProxy
,
L1SystemConfigAddress
:
cfg
.
DeployConfig
.
SystemConfigProxy
,
RegolithTime
:
cfg
.
DeployConfig
.
RegolithTime
(
uint64
(
cfg
.
DeployConfig
.
L1GenesisBlockTimestamp
)),
CanyonTime
:
cfg
.
DeployConfig
.
CanyonTime
(
uint64
(
cfg
.
DeployConfig
.
L1GenesisBlockTimestamp
)),
SpanBatchTime
:
cfg
.
DeployConfig
.
SpanBatchTime
(
uint64
(
cfg
.
DeployConfig
.
L1GenesisBlockTimestamp
)),
ProtocolVersionsAddress
:
cfg
.
L1Deployments
.
ProtocolVersionsProxy
,
}
...
...
op-e2e/system_test.go
View file @
f1cb42df
...
...
@@ -491,7 +491,7 @@ func TestSystemMockP2P(t *testing.T) {
verifierPeerID
:=
sys
.
RollupNodes
[
"verifier"
]
.
P2P
()
.
Host
()
.
ID
()
check
:=
func
()
bool
{
sequencerBlocksTopicPeers
:=
sys
.
RollupNodes
[
"sequencer"
]
.
P2P
()
.
GossipOut
()
.
BlocksTopic
Peers
()
sequencerBlocksTopicPeers
:=
sys
.
RollupNodes
[
"sequencer"
]
.
P2P
()
.
GossipOut
()
.
AllBlockTopics
Peers
()
return
slices
.
Contains
[[]
peer
.
ID
](
sequencerBlocksTopicPeers
,
verifierPeerID
)
}
...
...
op-node/node/node.go
View file @
f1cb42df
...
...
@@ -510,6 +510,7 @@ func (n *OpNode) OnUnsafeL2Payload(ctx context.Context, from peer.ID, payload *e
// Pass on the event to the L2 Engine
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
time
.
Second
*
30
)
defer
cancel
()
if
err
:=
n
.
l2Driver
.
OnUnsafeL2Payload
(
ctx
,
payload
);
err
!=
nil
{
n
.
log
.
Warn
(
"failed to notify engine driver of new L2 payload"
,
"err"
,
err
,
"id"
,
payload
.
ID
())
}
...
...
op-node/p2p/gossip.go
View file @
f1cb42df
...
...
@@ -70,10 +70,14 @@ func blocksTopicV1(cfg *rollup.Config) string {
return
fmt
.
Sprintf
(
"/optimism/%s/0/blocks"
,
cfg
.
L2ChainID
.
String
())
}
func
blocksTopicV2
(
cfg
*
rollup
.
Config
)
string
{
return
fmt
.
Sprintf
(
"/optimism/%s/1/blocks"
,
cfg
.
L2ChainID
.
String
())
}
// BuildSubscriptionFilter builds a simple subscription filter,
// to help protect against peers spamming useless subscriptions.
func
BuildSubscriptionFilter
(
cfg
*
rollup
.
Config
)
pubsub
.
SubscriptionFilter
{
return
pubsub
.
NewAllowlistSubscriptionFilter
(
blocksTopicV1
(
cfg
))
// add more topics here in the future, if any.
return
pubsub
.
NewAllowlistSubscriptionFilter
(
blocksTopicV1
(
cfg
)
,
blocksTopicV2
(
cfg
)
)
// add more topics here in the future, if any.
}
var
msgBufPool
=
sync
.
Pool
{
New
:
func
()
any
{
...
...
@@ -239,7 +243,7 @@ func (sb *seenBlocks) markSeen(h common.Hash) {
sb
.
blockHashes
=
append
(
sb
.
blockHashes
,
h
)
}
func
BuildBlocksValidator
(
log
log
.
Logger
,
cfg
*
rollup
.
Config
,
runCfg
GossipRuntimeConfig
)
pubsub
.
ValidatorEx
{
func
BuildBlocksValidator
(
log
log
.
Logger
,
cfg
*
rollup
.
Config
,
runCfg
GossipRuntimeConfig
,
blockVersion
eth
.
BlockVersion
)
pubsub
.
ValidatorEx
{
// Seen block hashes per block height
// uint64 -> *seenBlocks
...
...
@@ -284,7 +288,7 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config, runCfg GossipRunti
// [REJECT] if the block encoding is not valid
var
payload
eth
.
ExecutionPayload
if
err
:=
payload
.
UnmarshalSSZ
(
uint32
(
len
(
payloadBytes
)),
bytes
.
NewReader
(
payloadBytes
));
err
!=
nil
{
if
err
:=
payload
.
UnmarshalSSZ
(
blockVersion
,
uint32
(
len
(
payloadBytes
)),
bytes
.
NewReader
(
payloadBytes
));
err
!=
nil
{
log
.
Warn
(
"invalid payload"
,
"err"
,
err
,
"peer"
,
id
)
return
pubsub
.
ValidationReject
}
...
...
@@ -310,6 +314,18 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config, runCfg GossipRunti
return
pubsub
.
ValidationReject
}
// [REJECT] if a V1 Block has withdrawals
if
blockVersion
==
eth
.
BlockV1
&&
payload
.
Withdrawals
!=
nil
{
log
.
Warn
(
"payload is on v1 topic, but has withdrawals"
,
"bad_hash"
,
payload
.
BlockHash
.
String
())
return
pubsub
.
ValidationReject
}
// [REJECT] if a V2 Block does not have withdrawals
if
blockVersion
==
eth
.
BlockV2
&&
payload
.
Withdrawals
==
nil
{
log
.
Warn
(
"payload is on v2 topic, but does not have withdrawals"
,
"bad_hash"
,
payload
.
BlockHash
.
String
())
return
pubsub
.
ValidationReject
}
seen
,
ok
:=
blockHeightLRU
.
Get
(
uint64
(
payload
.
BlockNumber
))
if
!
ok
{
seen
=
new
(
seenBlocks
)
...
...
@@ -370,7 +386,9 @@ type GossipIn interface {
}
type
GossipTopicInfo
interface
{
BlocksTopicPeers
()
[]
peer
.
ID
AllBlockTopicsPeers
()
[]
peer
.
ID
BlocksTopicV1Peers
()
[]
peer
.
ID
BlocksTopicV2Peers
()
[]
peer
.
ID
}
type
GossipOut
interface
{
...
...
@@ -379,6 +397,21 @@ type GossipOut interface {
Close
()
error
}
type
blockTopic
struct
{
// blocks topic, main handle on block gossip
topic
*
pubsub
.
Topic
// block events handler, to be cancelled before closing the blocks topic.
events
*
pubsub
.
TopicEventHandler
// block subscriptions, to be cancelled before closing blocks topic.
sub
*
pubsub
.
Subscription
}
func
(
bt
*
blockTopic
)
Close
()
error
{
bt
.
events
.
Cancel
()
bt
.
sub
.
Cancel
()
return
bt
.
topic
.
Close
()
}
type
publisher
struct
{
log
log
.
Logger
cfg
*
rollup
.
Config
...
...
@@ -388,20 +421,39 @@ type publisher struct {
// thus we have to stop it ourselves this way.
p2pCancel
context
.
CancelFunc
// blocks topic, main handle on block gossip
blocksTopic
*
pubsub
.
Topic
// block events handler, to be cancelled before closing the blocks topic.
blocksEvents
*
pubsub
.
TopicEventHandler
// block subscriptions, to be cancelled before closing blocks topic.
blocksSub
*
pubsub
.
Subscription
blocksV1
*
blockTopic
blocksV2
*
blockTopic
runCfg
GossipRuntimeConfig
}
var
_
GossipOut
=
(
*
publisher
)(
nil
)
func
(
p
*
publisher
)
BlocksTopicPeers
()
[]
peer
.
ID
{
return
p
.
blocksTopic
.
ListPeers
()
func
combinePeers
(
allPeers
...
[]
peer
.
ID
)
[]
peer
.
ID
{
var
seen
=
make
(
map
[
peer
.
ID
]
bool
)
var
res
[]
peer
.
ID
for
_
,
peers
:=
range
allPeers
{
for
_
,
p
:=
range
peers
{
if
_
,
ok
:=
seen
[
p
];
ok
{
continue
}
res
=
append
(
res
,
p
)
seen
[
p
]
=
true
}
}
return
res
}
func
(
p
*
publisher
)
AllBlockTopicsPeers
()
[]
peer
.
ID
{
return
combinePeers
(
p
.
BlocksTopicV1Peers
(),
p
.
BlocksTopicV2Peers
())
}
func
(
p
*
publisher
)
BlocksTopicV1Peers
()
[]
peer
.
ID
{
return
p
.
blocksV1
.
topic
.
ListPeers
()
}
func
(
p
*
publisher
)
BlocksTopicV2Peers
()
[]
peer
.
ID
{
return
p
.
blocksV2
.
topic
.
ListPeers
()
}
func
(
p
*
publisher
)
PublishL2Payload
(
ctx
context
.
Context
,
payload
*
eth
.
ExecutionPayload
,
signer
Signer
)
error
{
...
...
@@ -428,55 +480,84 @@ func (p *publisher) PublishL2Payload(ctx context.Context, payload *eth.Execution
// This also copies the data, freeing up the original buffer to go back into the pool
out
:=
snappy
.
Encode
(
nil
,
data
)
return
p
.
blocksTopic
.
Publish
(
ctx
,
out
)
if
p
.
cfg
.
IsCanyon
(
uint64
(
payload
.
Timestamp
))
{
return
p
.
blocksV2
.
topic
.
Publish
(
ctx
,
out
)
}
else
{
return
p
.
blocksV1
.
topic
.
Publish
(
ctx
,
out
)
}
}
func
(
p
*
publisher
)
Close
()
error
{
p
.
p2pCancel
()
p
.
blocksEvents
.
Cancel
()
p
.
blocksSub
.
Cancel
()
return
p
.
blocksTopic
.
Close
(
)
e1
:=
p
.
blocksV1
.
Close
()
e2
:=
p
.
blocksV2
.
Close
()
return
errors
.
Join
(
e1
,
e2
)
}
func
JoinGossip
(
self
peer
.
ID
,
ps
*
pubsub
.
PubSub
,
log
log
.
Logger
,
cfg
*
rollup
.
Config
,
runCfg
GossipRuntimeConfig
,
gossipIn
GossipIn
)
(
GossipOut
,
error
)
{
val
:=
guardGossipValidator
(
log
,
logValidationResult
(
self
,
"validated block"
,
log
,
BuildBlocksValidator
(
log
,
cfg
,
runCfg
)))
blocksTopicName
:=
blocksTopicV1
(
cfg
)
err
:=
ps
.
RegisterTopicValidator
(
blocksTopicName
,
val
,
p2pCtx
,
p2pCancel
:=
context
.
WithCancel
(
context
.
Background
())
v1Logger
:=
log
.
New
(
"topic"
,
"blocksV1"
)
blocksV1Validator
:=
guardGossipValidator
(
log
,
logValidationResult
(
self
,
"validated blockv1"
,
v1Logger
,
BuildBlocksValidator
(
v1Logger
,
cfg
,
runCfg
,
eth
.
BlockV1
)))
blocksV1
,
err
:=
newBlockTopic
(
p2pCtx
,
blocksTopicV1
(
cfg
),
ps
,
v1Logger
,
gossipIn
,
blocksV1Validator
)
if
err
!=
nil
{
p2pCancel
()
return
nil
,
fmt
.
Errorf
(
"failed to setup blocks v1 p2p: %w"
,
err
)
}
v2Logger
:=
log
.
New
(
"topic"
,
"blocksV2"
)
blocksV2Validator
:=
guardGossipValidator
(
log
,
logValidationResult
(
self
,
"validated blockv2"
,
v2Logger
,
BuildBlocksValidator
(
v2Logger
,
cfg
,
runCfg
,
eth
.
BlockV2
)))
blocksV2
,
err
:=
newBlockTopic
(
p2pCtx
,
blocksTopicV2
(
cfg
),
ps
,
v2Logger
,
gossipIn
,
blocksV2Validator
)
if
err
!=
nil
{
p2pCancel
()
return
nil
,
fmt
.
Errorf
(
"failed to setup blocks v2 p2p: %w"
,
err
)
}
return
&
publisher
{
log
:
log
,
cfg
:
cfg
,
p2pCancel
:
p2pCancel
,
blocksV1
:
blocksV1
,
blocksV2
:
blocksV2
,
runCfg
:
runCfg
,
},
nil
}
func
newBlockTopic
(
ctx
context
.
Context
,
topicId
string
,
ps
*
pubsub
.
PubSub
,
log
log
.
Logger
,
gossipIn
GossipIn
,
validator
pubsub
.
ValidatorEx
)
(
*
blockTopic
,
error
)
{
err
:=
ps
.
RegisterTopicValidator
(
topicId
,
validator
,
pubsub
.
WithValidatorTimeout
(
3
*
time
.
Second
),
pubsub
.
WithValidatorConcurrency
(
4
))
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to register
blocks
gossip topic: %w"
,
err
)
return
nil
,
fmt
.
Errorf
(
"failed to register gossip topic: %w"
,
err
)
}
blocksTopic
,
err
:=
ps
.
Join
(
blocksTopicName
)
blocksTopic
,
err
:=
ps
.
Join
(
topicId
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to join
blocks
gossip topic: %w"
,
err
)
return
nil
,
fmt
.
Errorf
(
"failed to join gossip topic: %w"
,
err
)
}
blocksTopicEvents
,
err
:=
blocksTopic
.
EventHandler
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to create blocks gossip topic handler: %w"
,
err
)
}
p2pCtx
,
p2pCancel
:=
context
.
WithCancel
(
context
.
Background
())
go
LogTopicEvents
(
p2pCtx
,
log
.
New
(
"topic"
,
"blocks"
)
,
blocksTopicEvents
)
go
LogTopicEvents
(
ctx
,
log
,
blocksTopicEvents
)
subscription
,
err
:=
blocksTopic
.
Subscribe
()
if
err
!=
nil
{
p2pCancel
()
err
=
errors
.
Join
(
err
,
blocksTopic
.
Close
())
return
nil
,
fmt
.
Errorf
(
"failed to subscribe to blocks gossip topic: %w"
,
err
)
}
subscriber
:=
MakeSubscriber
(
log
,
BlocksHandler
(
gossipIn
.
OnUnsafeL2Payload
))
go
subscriber
(
p2pC
tx
,
subscription
)
go
subscriber
(
c
tx
,
subscription
)
return
&
publisher
{
log
:
log
,
cfg
:
cfg
,
blocksTopic
:
blocksTopic
,
blocksEvents
:
blocksTopicEvents
,
blocksSub
:
subscription
,
p2pCancel
:
p2pCancel
,
runCfg
:
runCfg
,
return
&
blockTopic
{
topic
:
blocksTopic
,
events
:
blocksTopicEvents
,
sub
:
subscription
,
},
nil
}
...
...
op-node/p2p/gossip_test.go
View file @
f1cb42df
...
...
@@ -40,6 +40,11 @@ func TestGuardGossipValidator(t *testing.T) {
require
.
Equal
(
t
,
pubsub
.
ValidationIgnore
,
val
(
context
.
Background
(),
"bob"
,
nil
))
}
func
TestCombinePeers
(
t
*
testing
.
T
)
{
res
:=
combinePeers
([]
peer
.
ID
{
"foo"
,
"bar"
},
[]
peer
.
ID
{
"bar"
,
"baz"
})
require
.
Equal
(
t
,
[]
peer
.
ID
{
"foo"
,
"bar"
,
"baz"
},
res
)
}
func
TestVerifyBlockSignature
(
t
*
testing
.
T
)
{
logger
:=
testlog
.
Logger
(
t
,
log
.
LvlCrit
)
cfg
:=
&
rollup
.
Config
{
...
...
op-node/p2p/rpc_server.go
View file @
f1cb42df
...
...
@@ -194,7 +194,7 @@ func (s *APIBackend) Peers(ctx context.Context, connected bool) (*PeerDump, erro
dump
.
TotalConnected
+=
1
}
}
for
_
,
id
:=
range
s
.
node
.
GossipOut
()
.
BlocksTopic
Peers
()
{
for
_
,
id
:=
range
s
.
node
.
GossipOut
()
.
AllBlockTopics
Peers
()
{
if
p
,
ok
:=
dump
.
Peers
[
id
.
String
()];
ok
{
p
.
GossipBlocks
=
true
}
...
...
@@ -208,11 +208,12 @@ func (s *APIBackend) Peers(ctx context.Context, connected bool) (*PeerDump, erro
}
type
PeerStats
struct
{
Connected
uint
`json:"connected"`
Table
uint
`json:"table"`
BlocksTopic
uint
`json:"blocksTopic"`
Banned
uint
`json:"banned"`
Known
uint
`json:"known"`
Connected
uint
`json:"connected"`
Table
uint
`json:"table"`
BlocksTopic
uint
`json:"blocksTopic"`
BlocksTopicV2
uint
`json:"blocksTopicV2"`
Banned
uint
`json:"banned"`
Known
uint
`json:"known"`
}
func
(
s
*
APIBackend
)
PeerStats
(
_
context
.
Context
)
(
*
PeerStats
,
error
)
{
...
...
@@ -223,11 +224,12 @@ func (s *APIBackend) PeerStats(_ context.Context) (*PeerStats, error) {
pstore
:=
h
.
Peerstore
()
stats
:=
&
PeerStats
{
Connected
:
uint
(
len
(
nw
.
Peers
())),
Table
:
0
,
BlocksTopic
:
uint
(
len
(
s
.
node
.
GossipOut
()
.
BlocksTopicPeers
())),
Banned
:
0
,
Known
:
uint
(
len
(
pstore
.
Peers
())),
Connected
:
uint
(
len
(
nw
.
Peers
())),
Table
:
0
,
BlocksTopic
:
uint
(
len
(
s
.
node
.
GossipOut
()
.
BlocksTopicV1Peers
())),
BlocksTopicV2
:
uint
(
len
(
s
.
node
.
GossipOut
()
.
BlocksTopicV2Peers
())),
Banned
:
0
,
Known
:
uint
(
len
(
pstore
.
Peers
())),
}
if
gater
:=
s
.
node
.
ConnectionGater
();
gater
!=
nil
{
stats
.
Banned
=
uint
(
len
(
gater
.
ListBlockedPeers
()))
...
...
op-node/p2p/sync.go
View file @
f1cb42df
...
...
@@ -571,7 +571,7 @@ func (r requestResultErr) ResultCode() byte {
return
byte
(
r
)
}
func
(
s
*
SyncClient
)
doRequest
(
ctx
context
.
Context
,
id
peer
.
ID
,
n
uint64
)
error
{
func
(
s
*
SyncClient
)
doRequest
(
ctx
context
.
Context
,
id
peer
.
ID
,
expectedBlockNum
uint64
)
error
{
// open stream to peer
reqCtx
,
reqCancel
:=
context
.
WithTimeout
(
ctx
,
streamTimeout
)
str
,
err
:=
s
.
newStreamFn
(
reqCtx
,
id
,
s
.
payloadByNumber
)
...
...
@@ -582,8 +582,8 @@ func (s *SyncClient) doRequest(ctx context.Context, id peer.ID, n uint64) error
defer
str
.
Close
()
// set write timeout (if available)
_
=
str
.
SetWriteDeadline
(
time
.
Now
()
.
Add
(
clientWriteRequestTimeout
))
if
err
:=
binary
.
Write
(
str
,
binary
.
LittleEndian
,
n
);
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to write request (%d): %w"
,
n
,
err
)
if
err
:=
binary
.
Write
(
str
,
binary
.
LittleEndian
,
expectedBlockNum
);
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to write request (%d): %w"
,
expectedBlockNum
,
err
)
}
if
err
:=
str
.
CloseWrite
();
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to close writer side while making request: %w"
,
err
)
...
...
@@ -620,14 +620,20 @@ func (s *SyncClient) doRequest(ctx context.Context, id peer.ID, n uint64) error
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to read response: %w"
,
err
)
}
blockVersion
:=
eth
.
BlockV1
if
s
.
cfg
.
IsCanyon
(
expectedBlockNum
)
{
blockVersion
=
eth
.
BlockV2
}
var
res
eth
.
ExecutionPayload
if
err
:=
res
.
UnmarshalSSZ
(
uint32
(
len
(
data
)),
bytes
.
NewReader
(
data
));
err
!=
nil
{
if
err
:=
res
.
UnmarshalSSZ
(
blockVersion
,
uint32
(
len
(
data
)),
bytes
.
NewReader
(
data
));
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to decode response: %w"
,
err
)
}
if
err
:=
str
.
CloseRead
();
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to close reading side"
)
}
if
err
:=
verifyBlock
(
&
res
,
n
);
err
!=
nil
{
if
err
:=
verifyBlock
(
&
res
,
expectedBlockNum
);
err
!=
nil
{
return
fmt
.
Errorf
(
"received execution payload is invalid: %w"
,
err
)
}
select
{
...
...
op-node/rollup/derive/attributes.go
View file @
f1cb42df
...
...
@@ -109,6 +109,11 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
txs
=
append
(
txs
,
l1InfoTx
)
txs
=
append
(
txs
,
depositTxs
...
)
var
withdrawals
*
eth
.
Withdrawals
if
ba
.
cfg
.
IsCanyon
(
nextL2Time
)
{
withdrawals
=
&
eth
.
Withdrawals
{}
}
return
&
eth
.
PayloadAttributes
{
Timestamp
:
hexutil
.
Uint64
(
nextL2Time
),
PrevRandao
:
eth
.
Bytes32
(
l1Info
.
MixDigest
()),
...
...
@@ -116,5 +121,6 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
Transactions
:
txs
,
NoTxPool
:
true
,
GasLimit
:
(
*
eth
.
Uint64Quantity
)(
&
sysConfig
.
GasLimit
),
Withdrawals
:
withdrawals
,
},
nil
}
op-node/rollup/derive/engine_queue.go
View file @
f1cb42df
...
...
@@ -182,6 +182,7 @@ func (eq *EngineQueue) AddUnsafePayload(payload *eth.ExecutionPayload) {
eq
.
log
.
Warn
(
"cannot add nil unsafe payload"
)
return
}
if
err
:=
eq
.
unsafePayloads
.
Push
(
payload
);
err
!=
nil
{
eq
.
log
.
Warn
(
"Could not add unsafe payload"
,
"id"
,
payload
.
ID
(),
"timestamp"
,
uint64
(
payload
.
Timestamp
),
"err"
,
err
)
return
...
...
op-program/client/l2/engine.go
View file @
f1cb42df
...
...
@@ -48,15 +48,19 @@ func (o *OracleEngine) L2OutputRoot() (eth.Bytes32, error) {
}
func
(
o
*
OracleEngine
)
GetPayload
(
ctx
context
.
Context
,
payloadId
eth
.
PayloadID
)
(
*
eth
.
ExecutionPayload
,
error
)
{
return
o
.
api
.
GetPayloadV1
(
ctx
,
payloadId
)
res
,
err
:=
o
.
api
.
GetPayloadV2
(
ctx
,
payloadId
)
if
err
!=
nil
{
return
nil
,
err
}
return
res
.
ExecutionPayload
,
nil
}
func
(
o
*
OracleEngine
)
ForkchoiceUpdate
(
ctx
context
.
Context
,
state
*
eth
.
ForkchoiceState
,
attr
*
eth
.
PayloadAttributes
)
(
*
eth
.
ForkchoiceUpdatedResult
,
error
)
{
return
o
.
api
.
ForkchoiceUpdatedV
1
(
ctx
,
state
,
attr
)
return
o
.
api
.
ForkchoiceUpdatedV
2
(
ctx
,
state
,
attr
)
}
func
(
o
*
OracleEngine
)
NewPayload
(
ctx
context
.
Context
,
payload
*
eth
.
ExecutionPayload
)
(
*
eth
.
PayloadStatusV1
,
error
)
{
return
o
.
api
.
NewPayloadV
1
(
ctx
,
payload
)
return
o
.
api
.
NewPayloadV
2
(
ctx
,
payload
)
}
func
(
o
*
OracleEngine
)
PayloadByHash
(
ctx
context
.
Context
,
hash
common
.
Hash
)
(
*
eth
.
ExecutionPayload
,
error
)
{
...
...
@@ -64,7 +68,7 @@ func (o *OracleEngine) PayloadByHash(ctx context.Context, hash common.Hash) (*et
if
block
==
nil
{
return
nil
,
ErrNotFound
}
return
eth
.
BlockAsPayload
(
block
)
return
eth
.
BlockAsPayload
(
block
,
o
.
rollupCfg
.
CanyonTime
)
}
func
(
o
*
OracleEngine
)
PayloadByNumber
(
ctx
context
.
Context
,
n
uint64
)
(
*
eth
.
ExecutionPayload
,
error
)
{
...
...
op-program/client/l2/engine_test.go
View file @
f1cb42df
...
...
@@ -29,7 +29,7 @@ func TestPayloadByHash(t *testing.T) {
block
:=
stub
.
head
payload
,
err
:=
engine
.
PayloadByHash
(
ctx
,
block
.
Hash
())
require
.
NoError
(
t
,
err
)
expected
,
err
:=
eth
.
BlockAsPayload
(
block
)
expected
,
err
:=
eth
.
BlockAsPayload
(
block
,
engine
.
rollupCfg
.
CanyonTime
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
expected
,
payload
)
})
...
...
@@ -51,7 +51,7 @@ func TestPayloadByNumber(t *testing.T) {
block
:=
stub
.
head
payload
,
err
:=
engine
.
PayloadByNumber
(
ctx
,
block
.
NumberU64
())
require
.
NoError
(
t
,
err
)
expected
,
err
:=
eth
.
BlockAsPayload
(
block
)
expected
,
err
:=
eth
.
BlockAsPayload
(
block
,
engine
.
rollupCfg
.
CanyonTime
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
expected
,
payload
)
})
...
...
@@ -124,7 +124,7 @@ func TestSystemConfigByL2Hash(t *testing.T) {
engine
,
stub
:=
createOracleEngine
(
t
)
t
.
Run
(
"KnownBlock"
,
func
(
t
*
testing
.
T
)
{
payload
,
err
:=
eth
.
BlockAsPayload
(
stub
.
safe
)
payload
,
err
:=
eth
.
BlockAsPayload
(
stub
.
safe
,
engine
.
rollupCfg
.
CanyonTime
)
require
.
NoError
(
t
,
err
)
expected
,
err
:=
derive
.
PayloadToSystemConfig
(
payload
,
engine
.
rollupCfg
)
require
.
NoError
(
t
,
err
)
...
...
op-program/client/l2/engineapi/l2_engine_api.go
View file @
f1cb42df
...
...
@@ -264,7 +264,7 @@ func (ea *L2EngineAPI) getPayload(ctx context.Context, payloadId eth.PayloadID)
ea
.
log
.
Error
(
"failed to finish block building"
,
"err"
,
err
)
return
nil
,
engine
.
UnknownPayload
}
return
eth
.
BlockAsPayload
(
bl
)
return
eth
.
BlockAsPayload
(
bl
,
ea
.
config
()
.
CanyonTime
)
}
func
(
ea
*
L2EngineAPI
)
forkchoiceUpdated
(
ctx
context
.
Context
,
state
*
eth
.
ForkchoiceState
,
attr
*
eth
.
PayloadAttributes
)
(
*
eth
.
ForkchoiceUpdatedResult
,
error
)
{
...
...
@@ -350,6 +350,25 @@ func (ea *L2EngineAPI) forkchoiceUpdated(ctx context.Context, state *eth.Forkcho
return
valid
(
nil
),
nil
}
func
toGethWithdrawals
(
payload
*
eth
.
ExecutionPayload
)
[]
*
types
.
Withdrawal
{
if
payload
.
Withdrawals
==
nil
{
return
nil
}
result
:=
[]
*
types
.
Withdrawal
{}
for
_
,
w
:=
range
*
payload
.
Withdrawals
{
result
=
append
(
result
,
&
types
.
Withdrawal
{
Index
:
w
.
Index
,
Validator
:
w
.
Validator
,
Address
:
w
.
Address
,
Amount
:
w
.
Amount
,
})
}
return
result
}
func
(
ea
*
L2EngineAPI
)
newPayload
(
ctx
context
.
Context
,
payload
*
eth
.
ExecutionPayload
)
(
*
eth
.
PayloadStatusV1
,
error
)
{
ea
.
log
.
Trace
(
"L2Engine API request received"
,
"method"
,
"ExecutePayload"
,
"number"
,
payload
.
BlockNumber
,
"hash"
,
payload
.
BlockHash
)
txs
:=
make
([][]
byte
,
len
(
payload
.
Transactions
))
...
...
@@ -371,6 +390,7 @@ func (ea *L2EngineAPI) newPayload(ctx context.Context, payload *eth.ExecutionPay
BaseFeePerGas
:
payload
.
BaseFeePerGas
.
ToBig
(),
BlockHash
:
payload
.
BlockHash
,
Transactions
:
txs
,
Withdrawals
:
toGethWithdrawals
(
payload
),
},
nil
,
nil
)
if
err
!=
nil
{
log
.
Debug
(
"Invalid NewPayload params"
,
"params"
,
payload
,
"error"
,
err
)
...
...
op-program/client/l2/engineapi/test/l2_engine_api_tests.go
View file @
f1cb42df
...
...
@@ -55,17 +55,25 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
txRlp
,
err
:=
tx
.
MarshalBinary
()
api
.
assert
.
NoError
(
err
)
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV1
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
nextBlockTime
:=
eth
.
Uint64Quantity
(
genesis
.
Time
+
1
)
var
w
*
eth
.
Withdrawals
if
api
.
backend
.
Config
()
.
IsCanyon
(
uint64
(
nextBlockTime
))
{
w
=
&
eth
.
Withdrawals
{}
}
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV2
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
HeadBlockHash
:
genesis
.
Hash
(),
SafeBlockHash
:
genesis
.
Hash
(),
FinalizedBlockHash
:
genesis
.
Hash
(),
},
&
eth
.
PayloadAttributes
{
Timestamp
:
eth
.
Uint64Quantity
(
genesis
.
Time
+
1
)
,
Timestamp
:
nextBlockTime
,
PrevRandao
:
eth
.
Bytes32
(
genesis
.
MixDigest
),
SuggestedFeeRecipient
:
feeRecipient
,
Transactions
:
[]
eth
.
Data
{
txRlp
},
NoTxPool
:
true
,
GasLimit
:
&
gasLimit
,
Withdrawals
:
w
,
})
api
.
assert
.
Error
(
err
)
api
.
assert
.
Equal
(
eth
.
ExecutionInvalid
,
result
.
PayloadStatus
.
Status
)
...
...
@@ -103,9 +111,16 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
t
.
Run
(
"RejectInvalidBlockHash"
,
func
(
t
*
testing
.
T
)
{
api
:=
newTestHelper
(
t
,
createBackend
)
var
w
*
eth
.
Withdrawals
if
api
.
backend
.
Config
()
.
IsCanyon
(
uint64
(
0
))
{
w
=
&
eth
.
Withdrawals
{}
}
// Invalid because BlockHash won't be correct (among many other reasons)
block
:=
&
eth
.
ExecutionPayload
{}
r
,
err
:=
api
.
engine
.
NewPayloadV1
(
api
.
ctx
,
block
)
block
:=
&
eth
.
ExecutionPayload
{
Withdrawals
:
w
,
}
r
,
err
:=
api
.
engine
.
NewPayloadV2
(
api
.
ctx
,
block
)
api
.
assert
.
NoError
(
err
)
api
.
assert
.
Equal
(
eth
.
ExecutionInvalidBlockHash
,
r
.
Status
)
})
...
...
@@ -122,7 +137,7 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
newBlock
.
StateRoot
=
eth
.
Bytes32
(
genesis
.
TxHash
)
updateBlockHash
(
newBlock
)
r
,
err
:=
api
.
engine
.
NewPayloadV
1
(
api
.
ctx
,
newBlock
)
r
,
err
:=
api
.
engine
.
NewPayloadV
2
(
api
.
ctx
,
newBlock
)
api
.
assert
.
NoError
(
err
)
api
.
assert
.
Equal
(
eth
.
ExecutionInvalid
,
r
.
Status
)
})
...
...
@@ -139,7 +154,7 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
newBlock
.
Timestamp
=
eth
.
Uint64Quantity
(
genesis
.
Time
)
updateBlockHash
(
newBlock
)
r
,
err
:=
api
.
engine
.
NewPayloadV
1
(
api
.
ctx
,
newBlock
)
r
,
err
:=
api
.
engine
.
NewPayloadV
2
(
api
.
ctx
,
newBlock
)
api
.
assert
.
NoError
(
err
)
api
.
assert
.
Equal
(
eth
.
ExecutionInvalid
,
r
.
Status
)
})
...
...
@@ -156,7 +171,7 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
newBlock
.
Timestamp
=
eth
.
Uint64Quantity
(
genesis
.
Time
-
1
)
updateBlockHash
(
newBlock
)
r
,
err
:=
api
.
engine
.
NewPayloadV
1
(
api
.
ctx
,
newBlock
)
r
,
err
:=
api
.
engine
.
NewPayloadV
2
(
api
.
ctx
,
newBlock
)
api
.
assert
.
NoError
(
err
)
api
.
assert
.
Equal
(
eth
.
ExecutionInvalid
,
r
.
Status
)
})
...
...
@@ -165,7 +180,7 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
api
:=
newTestHelper
(
t
,
createBackend
)
genesis
:=
api
.
backend
.
CurrentHeader
()
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV
1
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV
2
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
HeadBlockHash
:
genesis
.
Hash
(),
SafeBlockHash
:
genesis
.
Hash
(),
FinalizedBlockHash
:
genesis
.
Hash
(),
...
...
@@ -185,7 +200,7 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
api
:=
newTestHelper
(
t
,
createBackend
)
genesis
:=
api
.
backend
.
CurrentHeader
()
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV
1
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV
2
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
HeadBlockHash
:
genesis
.
Hash
(),
SafeBlockHash
:
genesis
.
Hash
(),
FinalizedBlockHash
:
genesis
.
Hash
(),
...
...
@@ -207,7 +222,7 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
gasLimit
:=
eth
.
Uint64Quantity
(
params
.
MaxGasLimit
+
1
)
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV
1
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV
2
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
HeadBlockHash
:
genesis
.
Hash
(),
SafeBlockHash
:
genesis
.
Hash
(),
FinalizedBlockHash
:
genesis
.
Hash
(),
...
...
@@ -246,7 +261,7 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
chainB1
:=
api
.
addBlockWithParent
(
genesis
,
eth
.
Uint64Quantity
(
genesis
.
Time
+
3
))
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV
1
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV
2
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
HeadBlockHash
:
chainA3
.
BlockHash
,
SafeBlockHash
:
chainB1
.
BlockHash
,
FinalizedBlockHash
:
chainA2
.
BlockHash
,
...
...
@@ -266,7 +281,7 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi.
chainB1
:=
api
.
addBlockWithParent
(
genesis
,
eth
.
Uint64Quantity
(
genesis
.
Time
+
3
))
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV
1
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
result
,
err
:=
api
.
engine
.
ForkchoiceUpdatedV
2
(
api
.
ctx
,
&
eth
.
ForkchoiceState
{
HeadBlockHash
:
chainA3
.
BlockHash
,
SafeBlockHash
:
chainA2
.
BlockHash
,
FinalizedBlockHash
:
chainB1
.
BlockHash
,
...
...
@@ -349,7 +364,7 @@ func (h *testHelper) addBlockWithParent(head *types.Header, timestamp eth.Uint64
func
(
h
*
testHelper
)
forkChoiceUpdated
(
head
common
.
Hash
,
safe
common
.
Hash
,
finalized
common
.
Hash
)
{
h
.
Log
(
"forkChoiceUpdated"
,
"head"
,
head
,
"safe"
,
safe
,
"finalized"
,
finalized
)
result
,
err
:=
h
.
engine
.
ForkchoiceUpdatedV
1
(
h
.
ctx
,
&
eth
.
ForkchoiceState
{
result
,
err
:=
h
.
engine
.
ForkchoiceUpdatedV
2
(
h
.
ctx
,
&
eth
.
ForkchoiceState
{
HeadBlockHash
:
head
,
SafeBlockHash
:
safe
,
FinalizedBlockHash
:
finalized
,
...
...
@@ -368,7 +383,14 @@ func (h *testHelper) startBlockBuilding(head *types.Header, newBlockTimestamp et
h
.
assert
.
NoError
(
err
,
"Failed to marshall tx %v"
,
tx
)
txData
=
append
(
txData
,
rlp
)
}
result
,
err
:=
h
.
engine
.
ForkchoiceUpdatedV1
(
h
.
ctx
,
&
eth
.
ForkchoiceState
{
canyonTime
:=
h
.
backend
.
Config
()
.
CanyonTime
var
w
*
eth
.
Withdrawals
if
canyonTime
!=
nil
&&
*
canyonTime
<=
uint64
(
newBlockTimestamp
)
{
w
=
&
eth
.
Withdrawals
{}
}
result
,
err
:=
h
.
engine
.
ForkchoiceUpdatedV2
(
h
.
ctx
,
&
eth
.
ForkchoiceState
{
HeadBlockHash
:
head
.
Hash
(),
SafeBlockHash
:
head
.
Hash
(),
FinalizedBlockHash
:
head
.
Hash
(),
...
...
@@ -379,6 +401,7 @@ func (h *testHelper) startBlockBuilding(head *types.Header, newBlockTimestamp et
Transactions
:
txData
,
NoTxPool
:
true
,
GasLimit
:
&
gasLimit
,
Withdrawals
:
w
,
})
h
.
assert
.
NoError
(
err
)
h
.
assert
.
Equal
(
eth
.
ExecutionValid
,
result
.
PayloadStatus
.
Status
)
...
...
@@ -389,15 +412,16 @@ func (h *testHelper) startBlockBuilding(head *types.Header, newBlockTimestamp et
func
(
h
*
testHelper
)
getPayload
(
id
*
eth
.
PayloadID
)
*
eth
.
ExecutionPayload
{
h
.
Log
(
"getPayload"
,
"id"
,
id
)
block
,
err
:=
h
.
engine
.
GetPayloadV1
(
h
.
ctx
,
*
id
)
envelope
,
err
:=
h
.
engine
.
GetPayloadV2
(
h
.
ctx
,
*
id
)
h
.
assert
.
NoError
(
err
)
h
.
assert
.
NotNil
(
block
)
return
block
h
.
assert
.
NotNil
(
envelope
)
h
.
assert
.
NotNil
(
envelope
.
ExecutionPayload
)
return
envelope
.
ExecutionPayload
}
func
(
h
*
testHelper
)
newPayload
(
block
*
eth
.
ExecutionPayload
)
{
h
.
Log
(
"newPayload"
,
"hash"
,
block
.
BlockHash
)
r
,
err
:=
h
.
engine
.
NewPayloadV
1
(
h
.
ctx
,
block
)
r
,
err
:=
h
.
engine
.
NewPayloadV
2
(
h
.
ctx
,
block
)
h
.
assert
.
NoError
(
err
)
h
.
assert
.
Equal
(
eth
.
ExecutionValid
,
r
.
Status
)
h
.
assert
.
Nil
(
r
.
ValidationError
)
...
...
op-service/Makefile
View file @
f1cb42df
...
...
@@ -15,5 +15,6 @@ generate-mocks:
fuzz
:
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzExecutionPayloadUnmarshal ./eth
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzExecutionPayloadMarshalUnmarshal ./eth
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzExecutionPayloadMarshalUnmarshalV1 ./eth
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzExecutionPayloadMarshalUnmarshalV2 ./eth
go
test
-run
NOTAREALTEST
-v
-fuzztime
10s
-fuzz
FuzzOBP01 ./eth
op-service/eth/ssz.go
View file @
f1cb42df
...
...
@@ -9,16 +9,33 @@ import (
"sync"
)
type
BlockVersion
int
const
(
// iota is reset to 0
BlockV1
BlockVersion
=
iota
BlockV2
=
iota
)
// ExecutionPayload is the only SSZ type we have to marshal/unmarshal,
// so instead of importing a SSZ lib we implement the bare minimum.
// This is more efficient than RLP, and matches the L1 consensus-layer encoding of ExecutionPayload.
// All fields (4s are offsets to dynamic data)
const
executionPayloadFixedPart
=
32
+
20
+
32
+
32
+
256
+
32
+
8
+
8
+
8
+
8
+
4
+
32
+
32
+
4
const
blockV1FixedPart
=
32
+
20
+
32
+
32
+
256
+
32
+
8
+
8
+
8
+
8
+
4
+
32
+
32
+
4
// V1 + Withdrawals offset
const
blockV2FixedPart
=
blockV1FixedPart
+
4
const
withdrawalSize
=
8
+
8
+
32
+
8
// MAX_TRANSACTIONS_PER_PAYLOAD in consensus spec
// https://github.com/ethereum/consensus-specs/blob/ef434e87165e9a4c82a99f54ffd4974ae113f732/specs/bellatrix/beacon-chain.md#execution
const
maxTransactionsPerPayload
=
1
<<
20
// MAX_WITHDRAWALS_PER_PAYLOAD in consensus spec
// https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#execution
const
maxWithdrawalsPerPayload
=
1
<<
4
// ErrExtraDataTooLarge occurs when the ExecutionPayload's ExtraData field
// is too large to be properly represented in SSZ.
var
ErrExtraDataTooLarge
=
errors
.
New
(
"extra data too large"
)
...
...
@@ -31,16 +48,44 @@ var payloadBufPool = sync.Pool{New: func() any {
}}
var
ErrBadTransactionOffset
=
errors
.
New
(
"transactions offset is smaller than extra data offset, aborting"
)
var
ErrBadWithdrawalsOffset
=
errors
.
New
(
"withdrawals offset is smaller than transaction offset, aborting"
)
func
executionPayloadFixedPart
(
version
BlockVersion
)
uint32
{
if
version
==
BlockV2
{
return
blockV2FixedPart
}
else
{
return
blockV1FixedPart
}
}
func
(
payload
*
ExecutionPayload
)
inferVersion
()
BlockVersion
{
if
payload
.
Withdrawals
!=
nil
{
return
BlockV2
}
else
{
return
BlockV1
}
}
func
(
payload
*
ExecutionPayload
)
SizeSSZ
()
(
full
uint32
)
{
full
=
executionPayloadFixedPart
+
uint32
(
len
(
payload
.
ExtraData
))
return
executionPayloadFixedPart
(
payload
.
inferVersion
())
+
uint32
(
len
(
payload
.
ExtraData
))
+
payload
.
transactionSize
()
+
payload
.
withdrawalSize
()
}
func
(
payload
*
ExecutionPayload
)
withdrawalSize
()
uint32
{
if
payload
.
Withdrawals
==
nil
{
return
0
}
return
uint32
(
len
(
*
payload
.
Withdrawals
)
*
withdrawalSize
)
}
func
(
payload
*
ExecutionPayload
)
transactionSize
()
uint32
{
// One offset to each transaction
full
+
=
uint32
(
len
(
payload
.
Transactions
))
*
4
result
:
=
uint32
(
len
(
payload
.
Transactions
))
*
4
// Each transaction
for
_
,
tx
:=
range
payload
.
Transactions
{
full
+=
uint32
(
len
(
tx
))
result
+=
uint32
(
len
(
tx
))
}
return
full
return
result
}
// marshalBytes32LE returns the value of z as a 32-byte little-endian array.
...
...
@@ -62,9 +107,13 @@ func unmarshalBytes32LE(in []byte, z *Uint256Quantity) {
// MarshalSSZ encodes the ExecutionPayload as SSZ type
func
(
payload
*
ExecutionPayload
)
MarshalSSZ
(
w
io
.
Writer
)
(
n
int
,
err
error
)
{
fixedSize
:=
executionPayloadFixedPart
(
payload
.
inferVersion
())
transactionSize
:=
payload
.
transactionSize
()
// Cast to uint32 to enable 32-bit MIPS support where math.MaxUint32-executionPayloadFixedPart is too big for int
// In that case, len(payload.ExtraData) can't be longer than an int so this is always false anyway.
if
uint32
(
len
(
payload
.
ExtraData
))
>
math
.
MaxUint32
-
uint32
(
executionPayloadFixedPart
)
{
extraDataSize
:=
uint32
(
len
(
payload
.
ExtraData
))
if
extraDataSize
>
math
.
MaxUint32
-
fixedSize
{
return
0
,
ErrExtraDataTooLarge
}
...
...
@@ -100,26 +149,58 @@ func (payload *ExecutionPayload) MarshalSSZ(w io.Writer) (n int, err error) {
binary
.
LittleEndian
.
PutUint64
(
buf
[
offset
:
offset
+
8
],
uint64
(
payload
.
Timestamp
))
offset
+=
8
// offset to ExtraData
binary
.
LittleEndian
.
PutUint32
(
buf
[
offset
:
offset
+
4
],
executionPayloadFixedPart
)
binary
.
LittleEndian
.
PutUint32
(
buf
[
offset
:
offset
+
4
],
fixedSize
)
offset
+=
4
marshalBytes32LE
(
buf
[
offset
:
offset
+
32
],
&
payload
.
BaseFeePerGas
)
offset
+=
32
copy
(
buf
[
offset
:
offset
+
32
],
payload
.
BlockHash
[
:
])
offset
+=
32
// offset to Transactions
binary
.
LittleEndian
.
PutUint32
(
buf
[
offset
:
offset
+
4
],
executionPayloadFixedPart
+
uint32
(
len
(
payload
.
ExtraData
))
)
binary
.
LittleEndian
.
PutUint32
(
buf
[
offset
:
offset
+
4
],
fixedSize
+
extraDataSize
)
offset
+=
4
if
offset
!=
executionPayloadFixedPart
{
panic
(
"fixed part size is inconsistent"
)
if
payload
.
Withdrawals
==
nil
&&
offset
!=
fixedSize
{
panic
(
"transactions - fixed part size is inconsistent"
)
}
if
payload
.
Withdrawals
!=
nil
{
binary
.
LittleEndian
.
PutUint32
(
buf
[
offset
:
offset
+
4
],
fixedSize
+
extraDataSize
+
transactionSize
)
offset
+=
4
if
offset
!=
fixedSize
{
panic
(
"withdrawals - fixed part size is inconsistent"
)
}
}
// dynamic value 1: ExtraData
copy
(
buf
[
offset
:
offset
+
uint32
(
len
(
payload
.
ExtraData
))
],
payload
.
ExtraData
[
:
])
offset
+=
uint32
(
len
(
payload
.
ExtraData
))
copy
(
buf
[
offset
:
offset
+
extraDataSize
],
payload
.
ExtraData
[
:
])
offset
+=
extraDataSize
// dynamic value 2: Transactions
marshalTransactions
(
buf
[
offset
:
],
payload
.
Transactions
)
marshalTransactions
(
buf
[
offset
:
offset
+
transactionSize
],
payload
.
Transactions
)
offset
+=
transactionSize
// dyanmic value 3: Withdrawals
if
payload
.
Withdrawals
!=
nil
{
marshalWithdrawals
(
buf
[
offset
:
],
payload
.
Withdrawals
)
}
return
w
.
Write
(
buf
)
}
func
marshalWithdrawals
(
out
[]
byte
,
withdrawals
*
Withdrawals
)
{
offset
:=
uint32
(
0
)
for
_
,
withdrawal
:=
range
*
withdrawals
{
binary
.
LittleEndian
.
PutUint64
(
out
[
offset
:
offset
+
8
],
withdrawal
.
Index
)
offset
+=
8
binary
.
LittleEndian
.
PutUint64
(
out
[
offset
:
offset
+
8
],
withdrawal
.
Validator
)
offset
+=
8
copy
(
out
[
offset
:
offset
+
32
],
withdrawal
.
Address
[
:
])
offset
+=
32
binary
.
LittleEndian
.
PutUint64
(
out
[
offset
:
offset
+
8
],
withdrawal
.
Amount
)
offset
+=
8
}
}
func
marshalTransactions
(
out
[]
byte
,
txs
[]
Data
)
{
offset
:=
uint32
(
0
)
txOffset
:=
uint32
(
len
(
txs
))
*
4
...
...
@@ -133,8 +214,10 @@ func marshalTransactions(out []byte, txs []Data) {
}
// UnmarshalSSZ decodes the ExecutionPayload as SSZ type
func
(
payload
*
ExecutionPayload
)
UnmarshalSSZ
(
scope
uint32
,
r
io
.
Reader
)
error
{
if
scope
<
executionPayloadFixedPart
{
func
(
payload
*
ExecutionPayload
)
UnmarshalSSZ
(
version
BlockVersion
,
scope
uint32
,
r
io
.
Reader
)
error
{
fixedSize
:=
executionPayloadFixedPart
(
version
)
if
scope
<
fixedSize
{
return
fmt
.
Errorf
(
"scope too small to decode execution payload: %d"
,
scope
)
}
...
...
@@ -171,36 +254,99 @@ func (payload *ExecutionPayload) UnmarshalSSZ(scope uint32, r io.Reader) error {
payload
.
Timestamp
=
Uint64Quantity
(
binary
.
LittleEndian
.
Uint64
(
buf
[
offset
:
offset
+
8
]))
offset
+=
8
extraDataOffset
:=
binary
.
LittleEndian
.
Uint32
(
buf
[
offset
:
offset
+
4
])
if
extraDataOffset
!=
executionPayloadFixedPart
{
return
fmt
.
Errorf
(
"unexpected extra data offset: %d <> %d"
,
extraDataOffset
,
executionPayloadFixedPart
)
if
extraDataOffset
!=
fixedSize
{
return
fmt
.
Errorf
(
"unexpected extra data offset: %d <> %d"
,
extraDataOffset
,
fixedSize
)
}
offset
+=
4
unmarshalBytes32LE
(
buf
[
offset
:
offset
+
32
],
&
payload
.
BaseFeePerGas
)
offset
+=
32
copy
(
payload
.
BlockHash
[
:
],
buf
[
offset
:
offset
+
32
])
offset
+=
32
transactionsOffset
:=
binary
.
LittleEndian
.
Uint32
(
buf
[
offset
:
offset
+
4
])
if
transactionsOffset
<
extraDataOffset
{
return
ErrBadTransactionOffset
}
offset
+=
4
if
offset
!=
executionPayloadFixedPart
{
if
version
==
BlockV1
&&
offset
!=
fixedSize
{
panic
(
"fixed part size is inconsistent"
)
}
withdrawalsOffset
:=
scope
if
version
==
BlockV2
{
withdrawalsOffset
=
binary
.
LittleEndian
.
Uint32
(
buf
[
offset
:
offset
+
4
])
// No offset increment, due to this being the last field
if
withdrawalsOffset
<
transactionsOffset
{
return
ErrBadWithdrawalsOffset
}
}
if
transactionsOffset
>
extraDataOffset
+
32
||
transactionsOffset
>
scope
{
return
fmt
.
Errorf
(
"extra-data is too large: %d"
,
transactionsOffset
-
extraDataOffset
)
}
extraDataSize
:=
transactionsOffset
-
extraDataOffset
payload
.
ExtraData
=
make
(
BytesMax32
,
extraDataSize
)
copy
(
payload
.
ExtraData
,
buf
[
extraDataOffset
:
transactionsOffset
])
txs
,
err
:=
unmarshalTransactions
(
buf
[
transactionsOffset
:
])
txs
,
err
:=
unmarshalTransactions
(
buf
[
transactionsOffset
:
withdrawalsOffset
])
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to unmarshal transactions list: %w"
,
err
)
}
payload
.
Transactions
=
txs
if
version
==
BlockV2
{
if
withdrawalsOffset
>
scope
{
return
fmt
.
Errorf
(
"withdrawals offset is too large: %d"
,
withdrawalsOffset
)
}
withdrawals
,
err
:=
unmarshalWithdrawals
(
buf
[
withdrawalsOffset
:
])
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to unmarshal withdrawals list: %w"
,
err
)
}
payload
.
Withdrawals
=
withdrawals
}
return
nil
}
func
unmarshalWithdrawals
(
in
[]
byte
)
(
*
Withdrawals
,
error
)
{
result
:=
&
Withdrawals
{}
if
len
(
in
)
%
withdrawalSize
!=
0
{
return
nil
,
errors
.
New
(
"invalid withdrawals data"
)
}
withdrawalCount
:=
len
(
in
)
/
withdrawalSize
if
withdrawalCount
>
maxWithdrawalsPerPayload
{
return
nil
,
fmt
.
Errorf
(
"too many withdrawals: %d > %d"
,
withdrawalCount
,
maxWithdrawalsPerPayload
)
}
offset
:=
0
for
i
:=
0
;
i
<
withdrawalCount
;
i
++
{
withdrawal
:=
Withdrawal
{}
withdrawal
.
Index
=
binary
.
LittleEndian
.
Uint64
(
in
[
offset
:
offset
+
8
])
offset
+=
8
withdrawal
.
Validator
=
binary
.
LittleEndian
.
Uint64
(
in
[
offset
:
offset
+
8
])
offset
+=
8
copy
(
withdrawal
.
Address
[
:
],
in
[
offset
:
offset
+
32
])
offset
+=
32
withdrawal
.
Amount
=
binary
.
LittleEndian
.
Uint64
(
in
[
offset
:
offset
+
8
])
offset
+=
8
*
result
=
append
(
*
result
,
withdrawal
)
}
return
result
,
nil
}
func
unmarshalTransactions
(
in
[]
byte
)
(
txs
[]
Data
,
err
error
)
{
scope
:=
uint32
(
len
(
in
))
if
scope
==
0
{
// empty txs list
...
...
op-service/eth/ssz_test.go
View file @
f1cb42df
...
...
@@ -3,29 +3,41 @@ package eth
import
(
"bytes"
"encoding/binary"
"fmt"
"math"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/google/go-cmp/cmp"
"github.com/holiman/uint256"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
)
// FuzzExecutionPayloadUnmarshal checks that our SSZ decoding never panics
func
FuzzExecutionPayloadUnmarshal
(
f
*
testing
.
F
)
{
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
data
[]
byte
)
{
var
payload
ExecutionPayload
err
:=
payload
.
UnmarshalSSZ
(
uint32
(
len
(
data
)),
bytes
.
NewReader
(
data
))
if
err
!=
nil
{
// not every input is a valid ExecutionPayload, that's ok. Should just not panic.
return
{
var
payload
ExecutionPayload
err
:=
payload
.
UnmarshalSSZ
(
BlockV1
,
uint32
(
len
(
data
)),
bytes
.
NewReader
(
data
))
if
err
!=
nil
{
// not every input is a valid ExecutionPayload, that's ok. Should just not panic.
return
}
}
{
var
payload
ExecutionPayload
err
:=
payload
.
UnmarshalSSZ
(
BlockV2
,
uint32
(
len
(
data
)),
bytes
.
NewReader
(
data
))
if
err
!=
nil
{
// not every input is a valid ExecutionPayload, that's ok. Should just not panic.
return
}
}
})
}
// FuzzExecutionPayloadMarshalUnmarshal checks that our SSZ encoding>decoding round trips properly
func
FuzzExecutionPayloadMarshalUnmarshal
(
f
*
testing
.
F
)
{
func
FuzzExecutionPayloadMarshalUnmarshal
V1
(
f
*
testing
.
F
)
{
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
data
[]
byte
,
a
,
b
,
c
,
d
uint64
,
extraData
[]
byte
,
txs
uint16
,
txsData
[]
byte
)
{
if
len
(
data
)
<
32
+
20
+
32
+
32
+
256
+
32
+
32
+
32
{
return
...
...
@@ -72,7 +84,77 @@ func FuzzExecutionPayloadMarshalUnmarshal(f *testing.F) {
t
.
Fatalf
(
"failed to marshal ExecutionPayload: %v"
,
err
)
}
var
roundTripped
ExecutionPayload
err
:=
roundTripped
.
UnmarshalSSZ
(
uint32
(
len
(
buf
.
Bytes
())),
bytes
.
NewReader
(
buf
.
Bytes
()))
err
:=
roundTripped
.
UnmarshalSSZ
(
BlockV1
,
uint32
(
len
(
buf
.
Bytes
())),
bytes
.
NewReader
(
buf
.
Bytes
()))
if
err
!=
nil
{
t
.
Fatalf
(
"failed to decode previously marshalled payload: %v"
,
err
)
}
if
diff
:=
cmp
.
Diff
(
payload
,
roundTripped
);
diff
!=
""
{
t
.
Fatalf
(
"The data did not round trip correctly:
\n
%s"
,
diff
)
}
})
}
func
FuzzExecutionPayloadMarshalUnmarshalV2
(
f
*
testing
.
F
)
{
f
.
Fuzz
(
func
(
t
*
testing
.
T
,
data
[]
byte
,
a
,
b
,
c
,
d
uint64
,
extraData
[]
byte
,
txs
uint16
,
txsData
[]
byte
,
wCount
uint16
)
{
if
len
(
data
)
<
32
+
20
+
32
+
32
+
256
+
32
+
32
+
32
{
return
}
var
payload
ExecutionPayload
payload
.
ParentHash
=
*
(
*
common
.
Hash
)(
data
[
:
32
])
data
=
data
[
32
:
]
payload
.
FeeRecipient
=
*
(
*
common
.
Address
)(
data
[
:
20
])
data
=
data
[
20
:
]
payload
.
StateRoot
=
*
(
*
Bytes32
)(
data
[
:
32
])
data
=
data
[
32
:
]
payload
.
ReceiptsRoot
=
*
(
*
Bytes32
)(
data
[
:
32
])
data
=
data
[
32
:
]
payload
.
LogsBloom
=
*
(
*
Bytes256
)(
data
[
:
256
])
data
=
data
[
256
:
]
payload
.
PrevRandao
=
*
(
*
Bytes32
)(
data
[
:
32
])
data
=
data
[
32
:
]
payload
.
BlockNumber
=
Uint64Quantity
(
a
)
payload
.
GasLimit
=
Uint64Quantity
(
a
)
payload
.
GasUsed
=
Uint64Quantity
(
a
)
payload
.
Timestamp
=
Uint64Quantity
(
a
)
if
len
(
extraData
)
>
32
{
extraData
=
extraData
[
:
32
]
}
payload
.
ExtraData
=
extraData
payload
.
BaseFeePerGas
.
SetBytes
(
data
[
:
32
])
payload
.
BlockHash
=
*
(
*
common
.
Hash
)(
data
[
:
32
])
payload
.
Transactions
=
make
([]
Data
,
txs
)
for
i
:=
0
;
i
<
int
(
txs
);
i
++
{
if
len
(
txsData
)
<
2
{
payload
.
Transactions
[
i
]
=
make
(
Data
,
0
)
continue
}
txSize
:=
binary
.
LittleEndian
.
Uint16
(
txsData
[
:
2
])
txsData
=
txsData
[
2
:
]
if
int
(
txSize
)
>
len
(
txsData
)
{
txSize
=
uint16
(
len
(
txsData
))
}
payload
.
Transactions
[
i
]
=
txsData
[
:
txSize
]
txsData
=
txsData
[
txSize
:
]
}
wCount
=
wCount
%
maxWithdrawalsPerPayload
withdrawals
:=
make
(
Withdrawals
,
wCount
)
for
i
:=
0
;
i
<
int
(
wCount
);
i
++
{
withdrawals
[
i
]
=
Withdrawal
{
Index
:
a
,
Validator
:
b
,
Address
:
common
.
BytesToAddress
(
data
[
:
20
]),
Amount
:
c
,
}
}
payload
.
Withdrawals
=
&
withdrawals
var
buf
bytes
.
Buffer
if
_
,
err
:=
payload
.
MarshalSSZ
(
&
buf
);
err
!=
nil
{
t
.
Fatalf
(
"failed to marshal ExecutionPayload: %v"
,
err
)
}
var
roundTripped
ExecutionPayload
err
:=
roundTripped
.
UnmarshalSSZ
(
BlockV2
,
uint32
(
len
(
buf
.
Bytes
())),
bytes
.
NewReader
(
buf
.
Bytes
()))
if
err
!=
nil
{
t
.
Fatalf
(
"failed to decode previously marshalled payload: %v"
,
err
)
}
...
...
@@ -99,7 +181,7 @@ func FuzzOBP01(f *testing.F) {
binary
.
LittleEndian
.
PutUint32
(
clone
[
504
:
508
],
txOffset
)
var
unmarshalled
ExecutionPayload
err
=
unmarshalled
.
UnmarshalSSZ
(
uint32
(
len
(
clone
)),
bytes
.
NewReader
(
clone
))
err
=
unmarshalled
.
UnmarshalSSZ
(
BlockV1
,
uint32
(
len
(
clone
)),
bytes
.
NewReader
(
clone
))
if
err
==
nil
{
t
.
Fatalf
(
"expected a failure, but didn't get one"
)
}
...
...
@@ -122,7 +204,7 @@ func TestOPB01(t *testing.T) {
copy
(
data
[
504
:
508
],
make
([]
byte
,
4
))
var
unmarshalled
ExecutionPayload
err
=
unmarshalled
.
UnmarshalSSZ
(
uint32
(
len
(
data
)),
bytes
.
NewReader
(
data
))
err
=
unmarshalled
.
UnmarshalSSZ
(
BlockV1
,
uint32
(
len
(
data
)),
bytes
.
NewReader
(
data
))
require
.
Equal
(
t
,
ErrBadTransactionOffset
,
err
)
}
...
...
@@ -130,20 +212,126 @@ func TestOPB01(t *testing.T) {
// properly returns an error when the ExtraData field
// cannot be represented in the outputted SSZ.
func
TestOPB04
(
t
*
testing
.
T
)
{
data
:=
make
([]
byte
,
math
.
MaxUint32
)
var
buf
bytes
.
Buffer
// First, test the maximum len - which in this case is the max uint32
// minus the execution payload fixed part.
payload
:=
&
ExecutionPayload
{
ExtraData
:
make
([]
byte
,
math
.
MaxUint32
-
executionPayloadFixedPart
),
ExtraData
:
data
[
:
math
.
MaxUint32
-
executionPayloadFixedPart
(
BlockV1
)],
Withdrawals
:
nil
,
}
var
buf
bytes
.
Buffer
_
,
err
:=
payload
.
MarshalSSZ
(
&
buf
)
require
.
NoError
(
t
,
err
)
buf
.
Reset
()
payload
=
&
ExecutionPayload
{
ExtraData
:
make
([]
byte
,
math
.
MaxUint32
-
executionPayloadFixedPart
+
1
),
tests
:=
[]
struct
{
version
BlockVersion
withdrawals
*
Withdrawals
}{
{
BlockV1
,
nil
},
{
BlockV2
,
&
Withdrawals
{}},
}
for
_
,
test
:=
range
tests
{
payload
:=
&
ExecutionPayload
{
ExtraData
:
data
[
:
math
.
MaxUint32
-
executionPayloadFixedPart
(
test
.
version
)
+
1
],
Withdrawals
:
test
.
withdrawals
,
}
_
,
err
:=
payload
.
MarshalSSZ
(
&
buf
)
require
.
Error
(
t
,
err
)
require
.
Equal
(
t
,
ErrExtraDataTooLarge
,
err
)
}
}
func
createPayloadWithWithdrawals
(
w
*
Withdrawals
)
*
ExecutionPayload
{
return
&
ExecutionPayload
{
ParentHash
:
common
.
HexToHash
(
"0x123"
),
FeeRecipient
:
common
.
HexToAddress
(
"0x456"
),
StateRoot
:
Bytes32
(
common
.
HexToHash
(
"0x789"
)),
ReceiptsRoot
:
Bytes32
(
common
.
HexToHash
(
"0xabc"
)),
LogsBloom
:
Bytes256
{
byte
(
13
),
byte
(
14
),
byte
(
15
)},
PrevRandao
:
Bytes32
(
common
.
HexToHash
(
"0x111"
)),
BlockNumber
:
Uint64Quantity
(
222
),
GasLimit
:
Uint64Quantity
(
333
),
GasUsed
:
Uint64Quantity
(
444
),
Timestamp
:
Uint64Quantity
(
555
),
ExtraData
:
common
.
Hex2Bytes
(
"0x666"
),
BaseFeePerGas
:
*
uint256
.
NewInt
(
777
),
BlockHash
:
common
.
HexToHash
(
"0x888"
),
Withdrawals
:
w
,
Transactions
:
[]
Data
{
common
.
Hex2Bytes
(
"0x999"
)},
}
}
func
TestMarshalUnmarshalWithdrawals
(
t
*
testing
.
T
)
{
emptyWithdrawal
:=
&
Withdrawals
{}
withdrawals
:=
&
Withdrawals
{
{
Index
:
987
,
Validator
:
654
,
Address
:
common
.
HexToAddress
(
"0x898"
),
Amount
:
321
,
},
}
maxWithdrawals
:=
make
(
Withdrawals
,
maxWithdrawalsPerPayload
)
for
i
:=
0
;
i
<
maxWithdrawalsPerPayload
;
i
++
{
maxWithdrawals
[
i
]
=
Withdrawal
{
Index
:
987
,
Validator
:
654
,
Address
:
common
.
HexToAddress
(
"0x898"
),
Amount
:
321
,
}
}
tooManyWithdrawals
:=
make
(
Withdrawals
,
maxWithdrawalsPerPayload
+
1
)
for
i
:=
0
;
i
<
maxWithdrawalsPerPayload
+
1
;
i
++
{
tooManyWithdrawals
[
i
]
=
Withdrawal
{
Index
:
987
,
Validator
:
654
,
Address
:
common
.
HexToAddress
(
"0x898"
),
Amount
:
321
,
}
}
tests
:=
[]
struct
{
name
string
version
BlockVersion
hasError
bool
withdrawals
*
Withdrawals
}{
{
"ZeroWithdrawalsSucceeds"
,
BlockV2
,
false
,
emptyWithdrawal
},
{
"ZeroWithdrawalsFailsToDeserialize"
,
BlockV1
,
true
,
emptyWithdrawal
},
{
"WithdrawalsSucceeds"
,
BlockV2
,
false
,
withdrawals
},
{
"WithdrawalsFailsToDeserialize"
,
BlockV1
,
true
,
withdrawals
},
{
"MaxWithdrawalsSucceeds"
,
BlockV2
,
false
,
&
maxWithdrawals
},
{
"TooManyWithdrawalsErrors"
,
BlockV2
,
true
,
&
tooManyWithdrawals
},
}
for
_
,
test
:=
range
tests
{
test
:=
test
t
.
Run
(
fmt
.
Sprintf
(
"TestWithdrawalUnmarshalMarshal_%s"
,
test
.
name
),
func
(
t
*
testing
.
T
)
{
input
:=
createPayloadWithWithdrawals
(
test
.
withdrawals
)
var
buf
bytes
.
Buffer
_
,
err
:=
input
.
MarshalSSZ
(
&
buf
)
require
.
NoError
(
t
,
err
)
data
:=
buf
.
Bytes
()
output
:=
&
ExecutionPayload
{}
err
=
output
.
UnmarshalSSZ
(
test
.
version
,
uint32
(
len
(
data
)),
bytes
.
NewReader
(
data
))
if
test
.
hasError
{
require
.
Error
(
t
,
err
)
}
else
{
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
input
,
output
)
if
test
.
withdrawals
!=
nil
{
require
.
Equal
(
t
,
len
(
*
test
.
withdrawals
),
len
(
*
output
.
Withdrawals
))
}
}
})
}
_
,
err
=
payload
.
MarshalSSZ
(
&
buf
)
require
.
Error
(
t
,
err
)
require
.
Equal
(
t
,
ErrExtraDataTooLarge
,
err
)
}
op-service/eth/types.go
View file @
f1cb42df
...
...
@@ -6,13 +6,13 @@ import (
"math/big"
"reflect"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/beacon/engine"
"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/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
)
type
ErrorCode
int
...
...
@@ -143,7 +143,7 @@ type ExecutionPayload struct {
ExtraData
BytesMax32
`json:"extraData"`
BaseFeePerGas
Uint256Quantity
`json:"baseFeePerGas"`
BlockHash
common
.
Hash
`json:"blockHash"`
Withdrawals
*
[]
Withdrawal
`json:"withdrawals,omitempty"`
Withdrawals
*
Withdrawals
`json:"withdrawals,omitempty"`
// Array of transaction objects, each object is a byte list (DATA) representing
// TransactionType || TransactionPayload or LegacyTransaction as defined in EIP-2718
Transactions
[]
Data
`json:"transactions"`
...
...
@@ -168,6 +168,10 @@ func (s rawTransactions) EncodeIndex(i int, w *bytes.Buffer) {
w
.
Write
(
s
[
i
])
}
func
(
payload
*
ExecutionPayload
)
CanyonBlock
()
bool
{
return
payload
.
Withdrawals
!=
nil
}
// CheckBlockHash recomputes the block hash and returns if the embedded block hash matches.
func
(
payload
*
ExecutionPayload
)
CheckBlockHash
()
(
actual
common
.
Hash
,
ok
bool
)
{
hasher
:=
trie
.
NewStackTrie
(
nil
)
...
...
@@ -191,11 +195,17 @@ func (payload *ExecutionPayload) CheckBlockHash() (actual common.Hash, ok bool)
Nonce
:
types
.
BlockNonce
{},
// zeroed, proof-of-work legacy
BaseFee
:
payload
.
BaseFeePerGas
.
ToBig
(),
}
if
payload
.
CanyonBlock
()
{
withdrawalHash
:=
types
.
DeriveSha
(
*
payload
.
Withdrawals
,
hasher
)
header
.
WithdrawalsHash
=
&
withdrawalHash
}
blockHash
:=
header
.
Hash
()
return
blockHash
,
blockHash
==
payload
.
BlockHash
}
func
BlockAsPayload
(
bl
*
types
.
Block
)
(
*
ExecutionPayload
,
error
)
{
func
BlockAsPayload
(
bl
*
types
.
Block
,
canyonForkTime
*
uint64
)
(
*
ExecutionPayload
,
error
)
{
baseFee
,
overflow
:=
uint256
.
FromBig
(
bl
.
BaseFee
())
if
overflow
{
return
nil
,
fmt
.
Errorf
(
"invalid base fee in block: %s"
,
bl
.
BaseFee
())
...
...
@@ -208,7 +218,8 @@ func BlockAsPayload(bl *types.Block) (*ExecutionPayload, error) {
}
opaqueTxs
[
i
]
=
otx
}
return
&
ExecutionPayload
{
payload
:=
&
ExecutionPayload
{
ParentHash
:
bl
.
ParentHash
(),
FeeRecipient
:
bl
.
Coinbase
(),
StateRoot
:
Bytes32
(
bl
.
Root
()),
...
...
@@ -220,10 +231,16 @@ func BlockAsPayload(bl *types.Block) (*ExecutionPayload, error) {
GasUsed
:
Uint64Quantity
(
bl
.
GasUsed
()),
Timestamp
:
Uint64Quantity
(
bl
.
Time
()),
ExtraData
:
bl
.
Extra
(),
BaseFeePerGas
:
Uint256Quantity
(
*
baseFee
)
,
BaseFeePerGas
:
*
baseFee
,
BlockHash
:
bl
.
Hash
(),
Transactions
:
opaqueTxs
,
},
nil
}
if
canyonForkTime
!=
nil
&&
uint64
(
payload
.
Timestamp
)
>=
*
canyonForkTime
{
payload
.
Withdrawals
=
&
Withdrawals
{}
}
return
payload
,
nil
}
type
PayloadAttributes
struct
{
...
...
@@ -234,7 +251,7 @@ type PayloadAttributes struct {
// suggested value for the coinbase field of the new payload
SuggestedFeeRecipient
common
.
Address
`json:"suggestedFeeRecipient"`
// Withdrawals to include into the block -- should be nil or empty depending on Shanghai enablement
Withdrawals
*
[]
Withdrawal
`json:"withdrawals,omitempty"`
Withdrawals
*
Withdrawals
`json:"withdrawals,omitempty"`
// Transactions to force into the block (always at the start of the transactions list).
Transactions
[]
Data
`json:"transactions,omitempty"`
// NoTxPool to disable adding any transactions from the transaction-pool.
...
...
@@ -302,9 +319,23 @@ type SystemConfig struct {
}
// Withdrawal represents a validator withdrawal from the consensus layer.
// https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#withdrawal
type
Withdrawal
struct
{
Index
uint64
`json:"index"`
// monotonically increasing identifier issued by consensus layer
Validator
uint64
`json:"validatorIndex"`
// index of validator associated with withdrawal
Address
common
.
Address
`json:"address"`
// target address for withdrawn ether
Amount
uint64
`json:"amount"`
// value of withdrawal in Gwei
}
// Withdrawals implements DerivableList for withdrawals.
type
Withdrawals
[]
Withdrawal
// Len returns the length of s.
func
(
s
Withdrawals
)
Len
()
int
{
return
len
(
s
)
}
// EncodeIndex encodes the i'th withdrawal to w. Note that this does not check for errors
// because we assume that *Withdrawal will only ever contain valid withdrawals that were either
// constructed by decoding or via public API in this package.
func
(
s
Withdrawals
)
EncodeIndex
(
i
int
,
w
*
bytes
.
Buffer
)
{
_
=
rlp
.
Encode
(
w
,
s
[
i
])
}
op-service/sources/types.go
View file @
f1cb42df
...
...
@@ -181,6 +181,7 @@ func (hdr *rpcHeader) Info(trustCache bool, mustBePostMerge bool) (eth.BlockInfo
type
rpcBlock
struct
{
rpcHeader
Transactions
[]
*
types
.
Transaction
`json:"transactions"`
Withdrawals
*
eth
.
Withdrawals
`json:"withdrawals,omitempty"`
}
func
(
block
*
rpcBlock
)
verify
()
error
{
...
...
@@ -252,6 +253,7 @@ func (block *rpcBlock) ExecutionPayload(trustCache bool) (*eth.ExecutionPayload,
BaseFeePerGas
:
baseFee
,
BlockHash
:
block
.
Hash
,
Transactions
:
opaqueTxs
,
Withdrawals
:
block
.
Withdrawals
,
},
nil
}
...
...
specs/rollup-node-p2p.md
View file @
f1cb42df
...
...
@@ -51,7 +51,8 @@ and are adopted by several other blockchains, most notably the [L1 consensus lay
-
[
Topic configuration
](
#topic-configuration
)
-
[
Topic validation
](
#topic-validation
)
-
[
Gossip Topics
](
#gossip-topics
)
-
[
`blocks`
](
#blocks
)
-
[
`blocksv1`
](
#blocksv1
)
-
[
`blocksv2`
](
#blocksv2
)
-
[
Block encoding
](
#block-encoding
)
-
[
Block signatures
](
#block-signatures
)
-
[
Block validation
](
#block-validation
)
...
...
@@ -247,9 +248,15 @@ The extended validator emits one of the following validation signals:
## Gossip Topics
### `blocks`
There are two topics for distributing blocks to other nodes faster than proxying through L1 would. These are:
The primary topic of the L2, to distribute blocks to other nodes faster than proxying through L1 would.
### `blocksv1`
Pre-Canyon/Shanghai blocks are broadcast on
`/optimism/<chainId>/0/blocks`
.
### `blocksv2`
Post-Canyon/Shanghai blocks are broadcast on
`/optimism/<chainId>/1/blocks`
.
#### Block encoding
...
...
@@ -282,6 +289,8 @@ An [extended-validator] checks the incoming messages as follows, in order of ope
(graceful boundary for worst-case propagation and clock skew)
-
`[REJECT]`
if the
`payload.timestamp`
is more than 5 seconds into the future
-
`[REJECT]`
if the
`block_hash`
in the
`payload`
is not valid
-
`[REJECT]`
if the block is on the V1 topic and has withdrawals
-
`[REJECT]`
if the block is on the V2 topic and does not have withdrawals
-
`[REJECT]`
if more than 5 different blocks have been seen with the same block height
-
`[IGNORE]`
if the block has already been seen
-
`[REJECT]`
if the signature by the sequencer is not valid
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment