mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 23:18:15 -05:00
Checkpoint Sync 1/5 - fork/version detection and unmarshaling support (#10380)
* fork/version detection and unmarshaling support * Update config/params/config.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update proto/detect/configfork.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * PR feedback * move ssz initialization into the detect package * clarify comment * VersionForEpoch is much simpler/clearer in reverse * simpler VersionForEpoch; build AllConfigs in init * use fieldparams for Version * Update proto/detect/configfork_test.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * remove custom ForkName type, use runtime/version * pr cleanup * random fix from bad gh ui suggestion; privatize * privatize fieldSpec methods; + unit tests * Update proto/detect/configfork.go Co-authored-by: Potuz <potuz@prysmaticlabs.com> * fix bad github ui suggestion * ensure unique versions for simpler config match * fmt & adding unit test for ByState() * table-driven unit test for ByState * TestUnmarshalState * OrderedSchedule -> network/forks per PR feedback * goimports * lint fixes * move proto/detect -> ssz/encoding/detect * use typeUndefined in String * backport config tests from e2e PR * fix config parity test; make debugging it easier * lint * fix fork schedule initialization * cleanup * fix build * fix big ole derp * anything for you, deep source * goimportsss * InitializeForkSchedule in LoadChainConfigFile * PR feedback Co-authored-by: kasey <kasey@users.noreply.github.com> Co-authored-by: Radosław Kapka <rkapka@wp.pl> Co-authored-by: Potuz <potuz@prysmaticlabs.com>
This commit is contained in:
52
encoding/ssz/detect/BUILD.bazel
Normal file
52
encoding/ssz/detect/BUILD.bazel
Normal file
@@ -0,0 +1,52 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"configfork.go",
|
||||
"fieldspec.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/encoding/ssz/detect",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/v1:go_default_library",
|
||||
"//beacon-chain/state/v2:go_default_library",
|
||||
"//beacon-chain/state/v3:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//network/forks:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"//proto/prysm/v1alpha1/wrapper:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ferranbt_fastssz//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"configfork_test.go",
|
||||
"fieldspec_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"//proto/prysm/v1alpha1/wrapper:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
],
|
||||
)
|
||||
179
encoding/ssz/detect/configfork.go
Normal file
179
encoding/ssz/detect/configfork.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package detect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/network/forks"
|
||||
|
||||
ssz "github.com/ferranbt/fastssz"
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1"
|
||||
v2 "github.com/prysmaticlabs/prysm/beacon-chain/state/v2"
|
||||
v3 "github.com/prysmaticlabs/prysm/beacon-chain/state/v3"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
)
|
||||
|
||||
// VersionedUnmarshaler represents the intersection of Configuration (eg mainnet, testnet) and Fork (eg phase0, altair).
|
||||
// Using a detected VersionedUnmarshaler, a BeaconState or SignedBeaconBlock can be correctly unmarshaled without the need to
|
||||
// hard code a concrete type in paths where only the marshaled bytes, or marshaled bytes and a version, are available.
|
||||
type VersionedUnmarshaler struct {
|
||||
Config *params.BeaconChainConfig
|
||||
// Fork aligns with the fork names in config/params/values.go
|
||||
Fork int
|
||||
// Version corresponds to the Version type defined in the beacon-chain spec, aka a "fork version number":
|
||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types
|
||||
Version [fieldparams.VersionLength]byte
|
||||
}
|
||||
|
||||
var beaconStateCurrentVersion = fieldSpec{
|
||||
// 52 = 8 (genesis_time) + 32 (genesis_validators_root) + 8 (slot) + 4 (previous_version)
|
||||
offset: 52,
|
||||
t: typeBytes4,
|
||||
}
|
||||
|
||||
// FromState exploits the fixed-size lower-order bytes in a BeaconState as a heuristic to obtain the value of the
|
||||
// state.version field without first unmarshaling the BeaconState. The Version is then internally used to lookup
|
||||
// the correct ConfigVersion.
|
||||
func FromState(marshaled []byte) (*VersionedUnmarshaler, error) {
|
||||
cv, err := beaconStateCurrentVersion.bytes4(marshaled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FromForkVersion(cv)
|
||||
}
|
||||
|
||||
var ErrForkNotFound = errors.New("version found in fork schedule but can't be matched to a named fork")
|
||||
|
||||
// FromForkVersion uses a lookup table to resolve a Version (from a beacon node api for instance, or obtained by peeking at
|
||||
// the bytes of a marshaled BeaconState) to a VersionedUnmarshaler.
|
||||
func FromForkVersion(cv [fieldparams.VersionLength]byte) (*VersionedUnmarshaler, error) {
|
||||
cfg, err := params.ConfigForVersion(cv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var fork int
|
||||
switch cv {
|
||||
case bytesutil.ToBytes4(cfg.GenesisForkVersion):
|
||||
fork = version.Phase0
|
||||
case bytesutil.ToBytes4(cfg.AltairForkVersion):
|
||||
fork = version.Altair
|
||||
case bytesutil.ToBytes4(cfg.BellatrixForkVersion):
|
||||
fork = version.Bellatrix
|
||||
default:
|
||||
return nil, errors.Wrapf(ErrForkNotFound, "version=%#x", cv)
|
||||
}
|
||||
return &VersionedUnmarshaler{
|
||||
Config: cfg,
|
||||
Fork: fork,
|
||||
Version: cv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UnmarshalBeaconState uses internal knowledge in the VersionedUnmarshaler to pick the right concrete BeaconState type,
|
||||
// then Unmarshal()s the type and returns an instance of state.BeaconState if successful.
|
||||
func (cf *VersionedUnmarshaler) UnmarshalBeaconState(marshaled []byte) (s state.BeaconState, err error) {
|
||||
forkName := version.String(cf.Fork)
|
||||
switch fork := cf.Fork; fork {
|
||||
case version.Phase0:
|
||||
st := ðpb.BeaconState{}
|
||||
err = st.UnmarshalSSZ(marshaled)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal state, detected fork=%s", forkName)
|
||||
}
|
||||
s, err = v1.InitializeFromProtoUnsafe(st)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to init state trie from state, detected fork=%s", forkName)
|
||||
}
|
||||
case version.Altair:
|
||||
st := ðpb.BeaconStateAltair{}
|
||||
err = st.UnmarshalSSZ(marshaled)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal state, detected fork=%s", forkName)
|
||||
}
|
||||
s, err = v2.InitializeFromProtoUnsafe(st)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to init state trie from state, detected fork=%s", forkName)
|
||||
}
|
||||
case version.Bellatrix:
|
||||
st := ðpb.BeaconStateBellatrix{}
|
||||
err = st.UnmarshalSSZ(marshaled)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal state, detected fork=%s", forkName)
|
||||
}
|
||||
s, err = v3.InitializeFromProtoUnsafe(st)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to init state trie from state, detected fork=%s", forkName)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unable to initialize BeaconState for fork version=%s", forkName)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
var beaconBlockSlot = fieldSpec{
|
||||
// ssz variable length offset (not to be confused with the fieldSpec offest) is a uint32
|
||||
// variable length. Offsets come before fixed length data, so that's 4 bytes at the beginning
|
||||
// then signature is 96 bytes, 4+96 = 100
|
||||
offset: 100,
|
||||
t: typeUint64,
|
||||
}
|
||||
|
||||
func slotFromBlock(marshaled []byte) (types.Slot, error) {
|
||||
slot, err := beaconBlockSlot.uint64(marshaled)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return types.Slot(slot), nil
|
||||
}
|
||||
|
||||
var errBlockForkMismatch = errors.New("fork or config detected from state is different than block")
|
||||
|
||||
// UnmarshalBeaconBlock uses internal knowledge in the VersionedUnmarshaler to pick the right concrete SignedBeaconBlock type,
|
||||
// then Unmarshal()s the type and returns an instance of block.SignedBeaconBlock if successful.
|
||||
func (cf *VersionedUnmarshaler) UnmarshalBeaconBlock(marshaled []byte) (block.SignedBeaconBlock, error) {
|
||||
slot, err := slotFromBlock(marshaled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// heuristic to make sure block is from the same version as the VersionedUnmarshaler.
|
||||
// Look up the version for the epoch that the block is from, then ensure that it matches the Version in the
|
||||
// VersionedUnmarshaler.
|
||||
epoch := slots.ToEpoch(slot)
|
||||
fs := forks.NewOrderedSchedule(cf.Config)
|
||||
ver, err := fs.VersionForEpoch(epoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ver != cf.Version {
|
||||
return nil, errors.Wrapf(errBlockForkMismatch, "slot=%d, epoch=%d, version=%#x", slot, epoch, ver)
|
||||
}
|
||||
|
||||
var blk ssz.Unmarshaler
|
||||
switch cf.Fork {
|
||||
case version.Phase0:
|
||||
blk = ðpb.SignedBeaconBlock{}
|
||||
case version.Altair:
|
||||
blk = ðpb.SignedBeaconBlockAltair{}
|
||||
case version.Bellatrix:
|
||||
blk = ðpb.SignedBeaconBlockBellatrix{}
|
||||
default:
|
||||
forkName := version.String(cf.Fork)
|
||||
return nil, fmt.Errorf("unable to initialize BeaconBlock for fork version=%s at slot=%d", forkName, slot)
|
||||
}
|
||||
err = blk.UnmarshalSSZ(marshaled)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal SignedBeaconBlock in UnmarshalSSZ")
|
||||
}
|
||||
return wrapper.WrappedSignedBeaconBlock(blk)
|
||||
}
|
||||
401
encoding/ssz/detect/configfork_test.go
Normal file
401
encoding/ssz/detect/configfork_test.go
Normal file
@@ -0,0 +1,401 @@
|
||||
package detect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
v1 "github.com/prysmaticlabs/prysm/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
)
|
||||
|
||||
func TestSlotFromBlock(t *testing.T) {
|
||||
b := testBlockGenesis()
|
||||
var slot types.Slot = 3
|
||||
b.Block.Slot = slot
|
||||
bb, err := b.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
sfb, err := slotFromBlock(bb)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot, sfb)
|
||||
|
||||
ba := testBlockAltair()
|
||||
ba.Block.Slot = slot
|
||||
bab, err := ba.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
sfba, err := slotFromBlock(bab)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot, sfba)
|
||||
|
||||
bm := testBlockBellatrix()
|
||||
bm.Block.Slot = slot
|
||||
bmb, err := ba.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
sfbm, err := slotFromBlock(bmb)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot, sfbm)
|
||||
}
|
||||
|
||||
func TestByState(t *testing.T) {
|
||||
bc, cleanup := hackBellatrixMaxuint()
|
||||
defer cleanup()
|
||||
altairSlot, err := slots.EpochStart(bc.AltairForkEpoch)
|
||||
bellaSlot, err := slots.EpochStart(bc.BellatrixForkEpoch)
|
||||
require.NoError(t, err)
|
||||
cases := []struct {
|
||||
name string
|
||||
version int
|
||||
slot types.Slot
|
||||
forkversion [4]byte
|
||||
}{
|
||||
{
|
||||
name: "genesis",
|
||||
version: version.Phase0,
|
||||
slot: 0,
|
||||
forkversion: bytesutil.ToBytes4(bc.GenesisForkVersion),
|
||||
},
|
||||
{
|
||||
name: "altair",
|
||||
version: version.Altair,
|
||||
slot: altairSlot,
|
||||
forkversion: bytesutil.ToBytes4(bc.AltairForkVersion),
|
||||
},
|
||||
{
|
||||
name: "bellatrix",
|
||||
version: version.Bellatrix,
|
||||
slot: bellaSlot,
|
||||
forkversion: bytesutil.ToBytes4(bc.BellatrixForkVersion),
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
st, err := stateForVersion(c.version)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, st.SetFork(ðpb.Fork{
|
||||
PreviousVersion: make([]byte, 4),
|
||||
CurrentVersion: c.forkversion[:],
|
||||
Epoch: 0,
|
||||
}))
|
||||
require.NoError(t, st.SetSlot(c.slot))
|
||||
m, err := st.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
cf, err := FromState(m)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.version, cf.Fork)
|
||||
require.Equal(t, c.forkversion, cf.Version)
|
||||
require.Equal(t, bc.ConfigName, cf.Config.ConfigName)
|
||||
}
|
||||
}
|
||||
|
||||
func stateForVersion(v int) (state.BeaconState, error) {
|
||||
switch v {
|
||||
case version.Phase0:
|
||||
return util.NewBeaconState()
|
||||
case version.Altair:
|
||||
return util.NewBeaconStateAltair()
|
||||
case version.Bellatrix:
|
||||
return util.NewBeaconStateBellatrix()
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognoized version %d", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalState(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
bc, cleanup := hackBellatrixMaxuint()
|
||||
defer cleanup()
|
||||
altairSlot, err := slots.EpochStart(bc.AltairForkEpoch)
|
||||
bellaSlot, err := slots.EpochStart(bc.BellatrixForkEpoch)
|
||||
require.NoError(t, err)
|
||||
cases := []struct {
|
||||
name string
|
||||
version int
|
||||
slot types.Slot
|
||||
forkversion [4]byte
|
||||
}{
|
||||
{
|
||||
name: "genesis",
|
||||
version: version.Phase0,
|
||||
slot: 0,
|
||||
forkversion: bytesutil.ToBytes4(bc.GenesisForkVersion),
|
||||
},
|
||||
{
|
||||
name: "altair",
|
||||
version: version.Altair,
|
||||
slot: altairSlot,
|
||||
forkversion: bytesutil.ToBytes4(bc.AltairForkVersion),
|
||||
},
|
||||
{
|
||||
name: "bellatrix",
|
||||
version: version.Bellatrix,
|
||||
slot: bellaSlot,
|
||||
forkversion: bytesutil.ToBytes4(bc.BellatrixForkVersion),
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
st, err := stateForVersion(c.version)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, st.SetFork(ðpb.Fork{
|
||||
PreviousVersion: make([]byte, 4),
|
||||
CurrentVersion: c.forkversion[:],
|
||||
Epoch: 0,
|
||||
}))
|
||||
require.NoError(t, st.SetSlot(c.slot))
|
||||
m, err := st.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
cf, err := FromState(m)
|
||||
require.NoError(t, err)
|
||||
s, err := cf.UnmarshalBeaconState(m)
|
||||
require.NoError(t, err)
|
||||
expected, err := st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
actual, err := s.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func hackBellatrixMaxuint() (*params.BeaconChainConfig, func()) {
|
||||
// We monkey patch the config to use a smaller value for the bellatrix fork epoch.
|
||||
// Upstream configs use MaxUint64, which leads to a multiplication overflow when converting epoch->slot.
|
||||
// Unfortunately we have unit tests that assert our config matches the upstream config, so we have to choose between
|
||||
// breaking conformance, adding a special case to the conformance unit test, or patch it here.
|
||||
previous := params.BeaconConfig()
|
||||
bc := params.MainnetConfig().Copy()
|
||||
bc.BellatrixForkEpoch = math.MaxUint32
|
||||
bc.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(bc)
|
||||
// override the param used for mainnet with the patched version
|
||||
params.KnownConfigs[params.Mainnet] = func() *params.BeaconChainConfig {
|
||||
return bc
|
||||
}
|
||||
return bc, func() {
|
||||
// put the previous BeaconChainConfig back in place at the end of the test
|
||||
params.OverrideBeaconConfig(previous)
|
||||
// restore the normal MainnetConfig func in the KnownConfigs mapping
|
||||
params.KnownConfigs[params.Mainnet] = params.MainnetConfig
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalBlock(t *testing.T) {
|
||||
bc, cleanup := hackBellatrixMaxuint()
|
||||
defer cleanup()
|
||||
require.Equal(t, types.Epoch(math.MaxUint32), params.KnownConfigs[params.Mainnet]().BellatrixForkEpoch)
|
||||
genv := bytesutil.ToBytes4(bc.GenesisForkVersion)
|
||||
altairv := bytesutil.ToBytes4(bc.AltairForkVersion)
|
||||
bellav := bytesutil.ToBytes4(bc.BellatrixForkVersion)
|
||||
altairS, err := slots.EpochStart(bc.AltairForkEpoch)
|
||||
bellaS, err := slots.EpochStart(bc.BellatrixForkEpoch)
|
||||
require.NoError(t, err)
|
||||
cases := []struct {
|
||||
b func(*testing.T, types.Slot) block.SignedBeaconBlock
|
||||
name string
|
||||
version [4]byte
|
||||
slot types.Slot
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "genesis - slot 0",
|
||||
b: signedTestBlockGenesis,
|
||||
version: genv,
|
||||
},
|
||||
{
|
||||
name: "last slot of phase 0",
|
||||
b: signedTestBlockGenesis,
|
||||
version: genv,
|
||||
slot: altairS - 1,
|
||||
},
|
||||
{
|
||||
name: "first slot of altair",
|
||||
b: signedTestBlockAltair,
|
||||
version: altairv,
|
||||
slot: altairS,
|
||||
},
|
||||
{
|
||||
name: "last slot of altair",
|
||||
b: signedTestBlockAltair,
|
||||
version: altairv,
|
||||
slot: bellaS - 1,
|
||||
},
|
||||
{
|
||||
name: "first slot of bellatrix",
|
||||
b: signedTestBlockBellatrix,
|
||||
version: bellav,
|
||||
slot: bellaS,
|
||||
},
|
||||
{
|
||||
name: "bellatrix block in altair slot",
|
||||
b: signedTestBlockBellatrix,
|
||||
version: bellav,
|
||||
slot: bellaS - 1,
|
||||
err: errBlockForkMismatch,
|
||||
},
|
||||
{
|
||||
name: "genesis block in altair slot",
|
||||
b: signedTestBlockGenesis,
|
||||
version: genv,
|
||||
slot: bellaS - 1,
|
||||
err: errBlockForkMismatch,
|
||||
},
|
||||
{
|
||||
name: "altair block in genesis slot",
|
||||
b: signedTestBlockAltair,
|
||||
version: altairv,
|
||||
err: errBlockForkMismatch,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
b := c.b(t, c.slot)
|
||||
marshaled, err := b.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
cf, err := FromForkVersion(c.version)
|
||||
require.NoError(t, err)
|
||||
bcf, err := cf.UnmarshalBeaconBlock(marshaled)
|
||||
if c.err != nil {
|
||||
require.ErrorIs(t, err, c.err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
expected, err := b.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
actual, err := bcf.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func signedTestBlockGenesis(t *testing.T, slot types.Slot) block.SignedBeaconBlock {
|
||||
b := testBlockGenesis()
|
||||
b.Block.Slot = slot
|
||||
s, err := wrapper.WrappedSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}
|
||||
|
||||
func testBlockGenesis() *ethpb.SignedBeaconBlock {
|
||||
return ðpb.SignedBeaconBlock{
|
||||
Block: ðpb.BeaconBlock{
|
||||
ProposerIndex: types.ValidatorIndex(0),
|
||||
ParentRoot: make([]byte, 32),
|
||||
StateRoot: make([]byte, 32),
|
||||
Body: ðpb.BeaconBlockBody{
|
||||
RandaoReveal: make([]byte, 96),
|
||||
Graffiti: make([]byte, 32),
|
||||
ProposerSlashings: []*ethpb.ProposerSlashing{},
|
||||
AttesterSlashings: []*ethpb.AttesterSlashing{},
|
||||
Attestations: []*ethpb.Attestation{},
|
||||
Deposits: []*ethpb.Deposit{},
|
||||
VoluntaryExits: []*ethpb.SignedVoluntaryExit{},
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
DepositCount: 0,
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
}
|
||||
|
||||
func signedTestBlockAltair(t *testing.T, slot types.Slot) block.SignedBeaconBlock {
|
||||
b := testBlockAltair()
|
||||
b.Block.Slot = slot
|
||||
s, err := wrapper.WrappedSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}
|
||||
|
||||
func testBlockAltair() *ethpb.SignedBeaconBlockAltair {
|
||||
return ðpb.SignedBeaconBlockAltair{
|
||||
Block: ðpb.BeaconBlockAltair{
|
||||
ProposerIndex: types.ValidatorIndex(0),
|
||||
ParentRoot: make([]byte, 32),
|
||||
StateRoot: make([]byte, 32),
|
||||
Body: ðpb.BeaconBlockBodyAltair{
|
||||
RandaoReveal: make([]byte, 96),
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
DepositCount: 0,
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
Graffiti: make([]byte, 32),
|
||||
ProposerSlashings: []*ethpb.ProposerSlashing{},
|
||||
AttesterSlashings: []*ethpb.AttesterSlashing{},
|
||||
Attestations: []*ethpb.Attestation{},
|
||||
Deposits: []*ethpb.Deposit{},
|
||||
VoluntaryExits: []*ethpb.SignedVoluntaryExit{},
|
||||
SyncAggregate: ðpb.SyncAggregate{
|
||||
SyncCommitteeBits: make([]byte, 64),
|
||||
SyncCommitteeSignature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
}
|
||||
|
||||
func signedTestBlockBellatrix(t *testing.T, slot types.Slot) block.SignedBeaconBlock {
|
||||
b := testBlockBellatrix()
|
||||
b.Block.Slot = slot
|
||||
s, err := wrapper.WrappedSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}
|
||||
|
||||
func testBlockBellatrix() *ethpb.SignedBeaconBlockBellatrix {
|
||||
return ðpb.SignedBeaconBlockBellatrix{
|
||||
Block: ðpb.BeaconBlockBellatrix{
|
||||
ProposerIndex: types.ValidatorIndex(0),
|
||||
ParentRoot: make([]byte, 32),
|
||||
StateRoot: make([]byte, 32),
|
||||
Body: ðpb.BeaconBlockBodyBellatrix{
|
||||
RandaoReveal: make([]byte, 96),
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
DepositCount: 0,
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
Graffiti: make([]byte, 32),
|
||||
ProposerSlashings: []*ethpb.ProposerSlashing{},
|
||||
AttesterSlashings: []*ethpb.AttesterSlashing{},
|
||||
Attestations: []*ethpb.Attestation{},
|
||||
Deposits: []*ethpb.Deposit{},
|
||||
VoluntaryExits: []*ethpb.SignedVoluntaryExit{},
|
||||
SyncAggregate: ðpb.SyncAggregate{
|
||||
SyncCommitteeBits: make([]byte, 64),
|
||||
SyncCommitteeSignature: make([]byte, 96),
|
||||
},
|
||||
ExecutionPayload: &v1.ExecutionPayload{
|
||||
ParentHash: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, 32),
|
||||
ReceiptsRoot: make([]byte, 32),
|
||||
LogsBloom: make([]byte, 256),
|
||||
BlockNumber: 0,
|
||||
GasLimit: 0,
|
||||
GasUsed: 0,
|
||||
Timestamp: 0,
|
||||
ExtraData: make([]byte, 32),
|
||||
BaseFeePerGas: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
Transactions: make([][]byte, 0),
|
||||
PrevRandao: make([]byte, 32),
|
||||
},
|
||||
},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
}
|
||||
79
encoding/ssz/detect/fieldspec.go
Normal file
79
encoding/ssz/detect/fieldspec.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package detect
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
)
|
||||
|
||||
type fieldType int
|
||||
|
||||
const (
|
||||
typeUndefined fieldType = iota
|
||||
typeUint64
|
||||
typeBytes4
|
||||
)
|
||||
|
||||
func (f fieldType) String() string {
|
||||
switch f {
|
||||
case typeUint64:
|
||||
return "uint64"
|
||||
case typeBytes4:
|
||||
return "bytes4"
|
||||
case typeUndefined:
|
||||
return "undefined"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
func (f fieldType) Size() int {
|
||||
switch f {
|
||||
case typeUint64:
|
||||
return 8
|
||||
case typeBytes4:
|
||||
return 4
|
||||
default:
|
||||
panic("can't determine size for unrecognizedtype ")
|
||||
}
|
||||
}
|
||||
|
||||
var errWrongMethodForType = errors.New("wrong fieldSpec method for type")
|
||||
var errIndexOutOfRange = errors.New("value index would exceed byte length")
|
||||
|
||||
type fieldSpec struct {
|
||||
offset int
|
||||
t fieldType
|
||||
}
|
||||
|
||||
func (f *fieldSpec) uint64(state []byte) (uint64, error) {
|
||||
if f.t != typeUint64 {
|
||||
return 0, errors.Wrapf(errWrongMethodForType, "called uint64() for type=%s", f.t)
|
||||
}
|
||||
s, err := f.slice(state)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.LittleEndian.Uint64(s), nil
|
||||
}
|
||||
|
||||
func (f *fieldSpec) bytes4(state []byte) ([4]byte, error) {
|
||||
var b4 [4]byte
|
||||
if f.t != typeBytes4 {
|
||||
return b4, errors.Wrapf(errWrongMethodForType, "called bytes4() with fieldType=%s", f.t)
|
||||
}
|
||||
val, err := f.slice(state)
|
||||
if err != nil {
|
||||
return b4, err
|
||||
}
|
||||
return bytesutil.ToBytes4(val), nil
|
||||
}
|
||||
|
||||
func (f *fieldSpec) slice(value []byte) ([]byte, error) {
|
||||
size := f.t.Size()
|
||||
if len(value) < f.offset+size {
|
||||
return nil, errors.Wrapf(errIndexOutOfRange, "offset=%d, size=%d, byte len=%d", f.offset, size, len(value))
|
||||
}
|
||||
return value[f.offset : f.offset+size], nil
|
||||
}
|
||||
104
encoding/ssz/detect/fieldspec_test.go
Normal file
104
encoding/ssz/detect/fieldspec_test.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package detect
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
)
|
||||
|
||||
func TestTypeMismatch(t *testing.T) {
|
||||
wrong := fieldSpec{
|
||||
offset: 52,
|
||||
t: typeBytes4,
|
||||
}
|
||||
_, err := wrong.uint64([]byte{})
|
||||
require.ErrorIs(t, err, errWrongMethodForType)
|
||||
|
||||
wrong = fieldSpec{
|
||||
offset: 100,
|
||||
t: typeUint64,
|
||||
}
|
||||
_, err = wrong.bytes4([]byte{})
|
||||
require.ErrorIs(t, err, errWrongMethodForType)
|
||||
}
|
||||
|
||||
func TestFieldSpecUint(t *testing.T) {
|
||||
var expectedUint uint64 = 23
|
||||
buf := make([]byte, binary.MaxVarintLen64)
|
||||
uv := binary.PutUvarint(buf, expectedUint)
|
||||
require.Equal(t, 1, uv)
|
||||
padded := make([]byte, 100)
|
||||
uintOffset := 10
|
||||
copy(padded[uintOffset:], buf)
|
||||
fs := fieldSpec{offset: uintOffset, t: typeUint64}
|
||||
u, err := fs.uint64(padded)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedUint, u)
|
||||
}
|
||||
|
||||
func TestFieldSpecBytes4(t *testing.T) {
|
||||
expectedBytes := []byte("cafe")
|
||||
padded := make([]byte, 100)
|
||||
byteOffset := 42
|
||||
copy(padded[byteOffset:], expectedBytes)
|
||||
fs := fieldSpec{offset: byteOffset, t: typeBytes4}
|
||||
b, err := fs.bytes4(padded)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, expectedBytes, b[:])
|
||||
}
|
||||
|
||||
func TestFieldSpecSlice(t *testing.T) {
|
||||
cases := []struct {
|
||||
offset int
|
||||
fieldType fieldType
|
||||
slice []byte
|
||||
err error
|
||||
name string
|
||||
expected []byte
|
||||
}{
|
||||
{
|
||||
offset: 0,
|
||||
fieldType: typeBytes4,
|
||||
slice: []byte{},
|
||||
err: errIndexOutOfRange,
|
||||
name: "zero length, out of range",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
fieldType: typeBytes4,
|
||||
slice: []byte("1234"),
|
||||
err: errIndexOutOfRange,
|
||||
name: "non-zero length, out of range",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
fieldType: typeBytes4,
|
||||
slice: []byte("12345"),
|
||||
expected: []byte("2345"),
|
||||
name: "success",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
fieldType: typeUint64,
|
||||
slice: []byte("123456789"),
|
||||
expected: []byte("23456789"),
|
||||
name: "uint success",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
s := fieldSpec{
|
||||
offset: c.offset,
|
||||
t: c.fieldType,
|
||||
}
|
||||
b, err := s.slice(c.slice)
|
||||
if c.err == nil {
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, c.expected, b)
|
||||
} else {
|
||||
require.ErrorIs(t, err, c.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user