Skip to content

Commit fdb7edd

Browse files
authored
op-service: Define the SuperRoot type (#13670)
* op-service: Define the SuperRoot type * op-program: Add chain ID to super chain output roots * Rename
1 parent 3a97042 commit fdb7edd

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

op-service/eth/super_root.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package eth
2+
3+
import (
4+
"encoding/binary"
5+
"errors"
6+
7+
"github.com/ethereum/go-ethereum/crypto"
8+
)
9+
10+
var (
11+
ErrInvalidSuperRoot = errors.New("invalid super root")
12+
ErrInvalidSuperRootVersion = errors.New("invalid super root version")
13+
SuperRootVersionV1 = byte(1)
14+
)
15+
16+
const (
17+
// SuperRootVersionV1MinLen is the minimum length of a V1 super root prior to hashing
18+
// Must contain a 1 byte version, uint64 timestamp and at least one chain's output root hash
19+
SuperRootVersionV1MinLen = 1 + 8 + 32
20+
)
21+
22+
type Super interface {
23+
Version() byte
24+
Marshal() []byte
25+
}
26+
27+
func SuperRoot(super Super) Bytes32 {
28+
marshaled := super.Marshal()
29+
return Bytes32(crypto.Keccak256Hash(marshaled))
30+
}
31+
32+
type ChainIDAndOutput struct {
33+
ChainID uint64
34+
Output Bytes32
35+
}
36+
37+
func (c *ChainIDAndOutput) Marshal() []byte {
38+
d := make([]byte, 64)
39+
binary.BigEndian.PutUint64(d[24:32], c.ChainID)
40+
copy(d[32:], c.Output[:])
41+
return d
42+
}
43+
44+
type SuperV1 struct {
45+
Timestamp uint64
46+
Chains []ChainIDAndOutput
47+
}
48+
49+
func (o *SuperV1) Version() byte {
50+
return SuperRootVersionV1
51+
}
52+
53+
func (o *SuperV1) Marshal() []byte {
54+
buf := make([]byte, 0, 9+len(o.Chains)*64)
55+
version := o.Version()
56+
buf = append(buf, version)
57+
buf = binary.BigEndian.AppendUint64(buf, o.Timestamp)
58+
for _, o := range o.Chains {
59+
buf = append(buf, o.Marshal()...)
60+
}
61+
return buf
62+
}
63+
64+
func UnmarshalSuperRoot(data []byte) (Super, error) {
65+
if len(data) < 1 {
66+
return nil, ErrInvalidSuperRoot
67+
}
68+
ver := data[0]
69+
switch ver {
70+
case SuperRootVersionV1:
71+
return unmarshalSuperRootV1(data)
72+
default:
73+
return nil, ErrInvalidSuperRootVersion
74+
}
75+
}
76+
77+
func unmarshalSuperRootV1(data []byte) (*SuperV1, error) {
78+
// Must contain the version, timestamp and at least one output root.
79+
if len(data) < SuperRootVersionV1MinLen {
80+
return nil, ErrInvalidSuperRoot
81+
}
82+
// Must contain complete chain output roots
83+
if (len(data)-9)%32 != 0 {
84+
return nil, ErrInvalidSuperRoot
85+
}
86+
var output SuperV1
87+
// data[:1] is the version
88+
output.Timestamp = binary.BigEndian.Uint64(data[1:9])
89+
for i := 9; i < len(data); i += 64 {
90+
chainOutput := ChainIDAndOutput{
91+
ChainID: binary.BigEndian.Uint64(data[i+24 : i+32]),
92+
Output: Bytes32(data[i+32 : i+64]),
93+
}
94+
output.Chains = append(output.Chains, chainOutput)
95+
}
96+
return &output, nil
97+
}

op-service/eth/super_root_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package eth
2+
3+
import (
4+
"encoding/binary"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestUnmarshalSuperRoot_UnknownVersion(t *testing.T) {
11+
_, err := UnmarshalSuperRoot([]byte{0: 0xA, 32: 0xA})
12+
require.ErrorIs(t, err, ErrInvalidSuperRootVersion)
13+
}
14+
15+
func TestUnmarshalSuperRoot_TooShortForVersion(t *testing.T) {
16+
_, err := UnmarshalSuperRoot([]byte{})
17+
require.ErrorIs(t, err, ErrInvalidSuperRoot)
18+
}
19+
20+
func TestSuperRootV1Codec(t *testing.T) {
21+
t.Run("Valid", func(t *testing.T) {
22+
chainA := ChainIDAndOutput{ChainID: 11, Output: Bytes32{0x01}}
23+
chainB := ChainIDAndOutput{ChainID: 12, Output: Bytes32{0x02}}
24+
chainC := ChainIDAndOutput{ChainID: 13, Output: Bytes32{0x03}}
25+
superRoot := SuperV1{
26+
Timestamp: 7000,
27+
Chains: []ChainIDAndOutput{chainA, chainB, chainC},
28+
}
29+
marshaled := superRoot.Marshal()
30+
unmarshaled, err := UnmarshalSuperRoot(marshaled)
31+
require.NoError(t, err)
32+
unmarshaledV1 := unmarshaled.(*SuperV1)
33+
require.Equal(t, superRoot, *unmarshaledV1)
34+
})
35+
36+
t.Run("BelowMinLength", func(t *testing.T) {
37+
_, err := UnmarshalSuperRoot(append([]byte{SuperRootVersionV1}, 0x01))
38+
require.ErrorIs(t, err, ErrInvalidSuperRoot)
39+
})
40+
41+
t.Run("NoChainsIncluded", func(t *testing.T) {
42+
_, err := UnmarshalSuperRoot(binary.BigEndian.AppendUint64([]byte{SuperRootVersionV1}, 134058))
43+
require.ErrorIs(t, err, ErrInvalidSuperRoot)
44+
})
45+
46+
t.Run("PartialChainSuperRoot", func(t *testing.T) {
47+
input := binary.BigEndian.AppendUint64([]byte{SuperRootVersionV1}, 134058)
48+
input = append(input, 0x01, 0x02, 0x03)
49+
_, err := UnmarshalSuperRoot(input)
50+
require.ErrorIs(t, err, ErrInvalidSuperRoot)
51+
})
52+
}

0 commit comments

Comments
 (0)