Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions portalnetwork/beacon/beacon_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type BeaconNetwork struct {
Spec *common.Spec
}

func (bn *BeaconNetwork) GetUpdates(firstPeriod, count uint64) (LightClientUpdateRange, error) {
func (bn *BeaconNetwork) GetUpdates(firstPeriod, count uint64) ([]*capella.LightClientUpdate, error) {
lightClientUpdateKey := &LightClientUpdateKey{
StartPeriod: firstPeriod,
Count: count,
Expand All @@ -44,7 +44,14 @@ func (bn *BeaconNetwork) GetUpdates(firstPeriod, count uint64) (LightClientUpdat
return nil, err
}

return lightClientUpdateRange, nil
updates := make([]*capella.LightClientUpdate, len(lightClientUpdateRange))
for i, update := range lightClientUpdateRange {
if update.ForkDigest != Capella {
return nil, errors.New("unknown fork digest")
}
updates[i] = update.LightClientUpdate.(*capella.LightClientUpdate)
}
return updates, nil
}

func (bn *BeaconNetwork) GetCheckpointData(checkpointHash tree.Root) (*capella.LightClientBootstrap, error) {
Expand Down
52 changes: 34 additions & 18 deletions portalnetwork/beacon/light_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var (
)

type ConsensusAPI interface {
GetUpdates(firstPeriod, count uint64) (LightClientUpdateRange, error)
GetUpdates(firstPeriod, count uint64) ([]*capella.LightClientUpdate, error)
GetCheckpointData(checkpointHash common.Root) (*capella.LightClientBootstrap, error)
GetFinalityData() (*capella.LightClientFinalityUpdate, error)
GetOptimisticData() (*capella.LightClientOptimisticUpdate, error)
Expand All @@ -52,7 +52,7 @@ type ConsensusLightClient struct {
API ConsensusAPI
InitialCheckpoint common.Root
LastCheckpoint common.Root
Config Config
Config *Config
Logger log.Logger
}

Expand All @@ -62,7 +62,7 @@ type Config struct {
DefaultCheckpoint common.Root
Checkpoint common.Root
DataDir string
ChainConfig ChainConfig
Chain ChainConfig
Spec *common.Spec
MaxCheckpointAge uint64
Fallback string
Expand All @@ -79,13 +79,29 @@ type ChainConfig struct {
type GenericUpdate struct {
AttestedHeader *common.BeaconBlockHeader
SyncAggregate *altair.SyncAggregate
SingnatureSlot common.Slot
SignatureSlot common.Slot
NextSyncCommittee *common.SyncCommittee
NextSyncCommitteeBranch *altair.SyncCommitteeProofBranch
FinalizedHeader *common.BeaconBlockHeader
FinalityBranch *altair.FinalizedRootProofBranch
}

func NewConsensusLightClient(api ConsensusAPI, config *Config, checkpointBlockRoot common.Root, logger log.Logger) (*ConsensusLightClient, error) {
client := &ConsensusLightClient{
API: api,
Config: config,
Logger: logger,
InitialCheckpoint: checkpointBlockRoot,
}

err := client.bootstrap()
if err != nil {
return nil, err
}

return client, nil
}

//lint:ignore U1000 placeholder function
func (c *ConsensusLightClient) bootstrap() error {
bootstrap, err := c.API.GetCheckpointData(c.InitialCheckpoint)
Expand Down Expand Up @@ -150,25 +166,25 @@ func (c *ConsensusLightClient) VerifyGenericUpdate(update *GenericUpdate) error
return ErrInsufficientParticipation
}
updateFinalizedSlot := update.FinalizedHeader.Slot
validTime := uint64(c.expectedCurrentSlot()) >= uint64(update.SingnatureSlot) && update.SingnatureSlot > update.AttestedHeader.Slot && update.AttestedHeader.Slot >= updateFinalizedSlot
validTime := uint64(c.expectedCurrentSlot()) >= uint64(update.SignatureSlot) && update.SignatureSlot > update.AttestedHeader.Slot && update.AttestedHeader.Slot >= updateFinalizedSlot
if !validTime {
return ErrInvalidTimestamp
}

storePeriod := CalcSyncPeriod(uint64(c.Store.FinalizedHeader.Slot))
updateSigPeriod := CalcSyncPeriod(uint64(update.SingnatureSlot))
updateSigPeriod := CalcSyncPeriod(uint64(update.SignatureSlot))
validPeriod := false
if c.Store.NextSyncCommittee != nil {
validPeriod = (updateSigPeriod == storePeriod || updateSigPeriod == storePeriod+1)
validPeriod = updateSigPeriod == storePeriod || updateSigPeriod == storePeriod+1
} else {
validPeriod = (updateSigPeriod == storePeriod)
validPeriod = updateSigPeriod == storePeriod
}
if !validPeriod {
return ErrInvalidPeriod
}

updateAttestedPeriod := CalcSyncPeriod(uint64(update.AttestedHeader.Slot))
updateHasNextCommittee := (c.Store.NextSyncCommittee == nil && update.NextSyncCommittee != nil && updateAttestedPeriod == storePeriod)
updateHasNextCommittee := c.Store.NextSyncCommittee == nil && update.NextSyncCommittee != nil && updateAttestedPeriod == storePeriod

if update.AttestedHeader.Slot <= c.Store.FinalizedHeader.Slot && !updateHasNextCommittee {
return ErrNotRelevant
Expand All @@ -180,7 +196,7 @@ func (c *ConsensusLightClient) VerifyGenericUpdate(update *GenericUpdate) error
}
}
if update.NextSyncCommittee != nil && update.NextSyncCommitteeBranch != nil {
isValid := IsNextCommitteeProofValid(c.Config.Spec, *update.AttestedHeader, *update.NextSyncCommittee, *update.NextSyncCommitteeBranch)
isValid := IsNextCommitteeProofValid(*update.AttestedHeader, *update.NextSyncCommittee, *update.NextSyncCommitteeBranch)
if !isValid {
return ErrInvalidNextSyncCommitteeProof
}
Expand All @@ -195,7 +211,7 @@ func (c *ConsensusLightClient) VerifyGenericUpdate(update *GenericUpdate) error

pks := GetParticipatingKeys(*syncCommittee, update.SyncAggregate.SyncCommitteeBits)

isValidSig, err := c.VerifySyncCommitteeSignature(pks, *update.AttestedHeader, update.SyncAggregate.SyncCommitteeSignature, update.SingnatureSlot)
isValidSig, err := c.VerifySyncCommitteeSignature(pks, *update.AttestedHeader, update.SyncAggregate.SyncCommitteeSignature, update.SignatureSlot)
if err != nil {
return err
}
Expand Down Expand Up @@ -239,19 +255,19 @@ func (c *ConsensusLightClient) VerifySyncCommitteeSignature(pks []common.BLSPubk
}

func (c *ConsensusLightClient) ComputeCommitteeSignRoot(headerRoot tree.Root, slot common.Slot) common.Root {
genesisRoot := c.Config.ChainConfig.GenesisRoot
genesisRoot := c.Config.Chain.GenesisRoot
domainType := hexutil.MustDecode("0x07000000")
forkVersion := c.Config.Spec.ForkVersion(slot)
domain := common.ComputeDomain(common.BLSDomainType(domainType), forkVersion, genesisRoot)
return ComputeSigningRoot(headerRoot, domain)
}

func (c *ConsensusLightClient) expectedCurrentSlot() common.Slot {
return c.Config.Spec.TimeToSlot(common.Timestamp(time.Now().Unix()), common.Timestamp(c.Config.ChainConfig.GenesisTime))
return c.Config.Spec.TimeToSlot(common.Timestamp(time.Now().Unix()), common.Timestamp(c.Config.Chain.GenesisTime))
}

func (c *ConsensusLightClient) slotTimestamp(slot common.Slot) (common.Timestamp, error) {
atSlot, err := c.Config.Spec.TimeAtSlot(slot, common.Timestamp(c.Config.ChainConfig.GenesisTime))
atSlot, err := c.Config.Spec.TimeAtSlot(slot, common.Timestamp(c.Config.Chain.GenesisTime))
if err != nil {
return 0, err
}
Expand All @@ -267,7 +283,7 @@ func FromLightClientUpdate(update *capella.LightClientUpdate) *GenericUpdate {
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SingnatureSlot: update.SignatureSlot,
SignatureSlot: update.SignatureSlot,
NextSyncCommittee: &update.NextSyncCommittee,
NextSyncCommitteeBranch: &update.NextSyncCommitteeBranch,
FinalizedHeader: &update.FinalizedHeader.Beacon,
Expand All @@ -279,7 +295,7 @@ func FromLightClientFinalityUpdate(update *capella.LightClientFinalityUpdate) *G
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SingnatureSlot: update.SignatureSlot,
SignatureSlot: update.SignatureSlot,
FinalizedHeader: &update.FinalizedHeader.Beacon,
FinalityBranch: &update.FinalityBranch,
}
Expand All @@ -289,7 +305,7 @@ func FromLightClientOptimisticUpdate(update *capella.LightClientOptimisticUpdate
return &GenericUpdate{
AttestedHeader: &update.AttestedHeader.Beacon,
SyncAggregate: &update.SyncAggregate,
SingnatureSlot: update.SignatureSlot,
SignatureSlot: update.SignatureSlot,
}
}

Expand All @@ -312,7 +328,7 @@ func IsFinalityProofValid(attestedHeader common.BeaconBlockHeader, finalityHeade
return merkle.VerifyMerkleBranch(leaf, finalityBranch[:], 6, 41, root)
}

func IsNextCommitteeProofValid(spec *common.Spec, attestedHeader common.BeaconBlockHeader, nextCommittee common.SyncCommittee, nextCommitteeBranch altair.SyncCommitteeProofBranch) bool {
func IsNextCommitteeProofValid(attestedHeader common.BeaconBlockHeader, nextCommittee common.SyncCommittee, nextCommitteeBranch altair.SyncCommitteeProofBranch) bool {
leaf := nextCommittee.HashTreeRoot(configs.Mainnet, tree.GetHashFn())
root := attestedHeader.StateRoot
return merkle.VerifyMerkleBranch(leaf, nextCommitteeBranch[:], 5, 23, root)
Expand Down
95 changes: 95 additions & 0 deletions portalnetwork/beacon/light_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package beacon

import (
"encoding/json"
"os"
"testing"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/internal/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/protolambda/zrnt/eth2/beacon/capella"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/stretchr/testify/assert"
)

var _ ConsensusAPI = (*MockConsensusAPI)(nil)

type MockConsensusAPI struct {
testdataDir string
}

func NewMockConsensusAPI(path string) (ConsensusAPI, error) {
return &MockConsensusAPI{testdataDir: path}, nil
}

func (m MockConsensusAPI) GetUpdates(_, _ uint64) ([]*capella.LightClientUpdate, error) {
jsonStr, _ := os.ReadFile(m.testdataDir + "/updates.json")

updates := make([]*capella.LightClientUpdate, 0)
_ = json.Unmarshal(jsonStr, &updates)

return updates, nil
}

func (m MockConsensusAPI) GetCheckpointData(_ common.Root) (*capella.LightClientBootstrap, error) {
jsonStr, _ := os.ReadFile(m.testdataDir + "/bootstrap.json")

bootstrap := &capella.LightClientBootstrap{}
_ = json.Unmarshal(jsonStr, &bootstrap)

return bootstrap, nil
}

func (m MockConsensusAPI) GetFinalityData() (*capella.LightClientFinalityUpdate, error) {
jsonStr, _ := os.ReadFile(m.testdataDir + "/finality.json")

finality := &capella.LightClientFinalityUpdate{}
_ = json.Unmarshal(jsonStr, &finality)

return finality, nil
}

func (m MockConsensusAPI) GetOptimisticData() (*capella.LightClientOptimisticUpdate, error) {
jsonStr, _ := os.ReadFile(m.testdataDir + "/optimistic.json")

optimistic := &capella.LightClientOptimisticUpdate{}
_ = json.Unmarshal(jsonStr, &optimistic)

return optimistic, nil
}

func (m MockConsensusAPI) ChainID() uint64 {
panic("implement me")
}

func (m MockConsensusAPI) Name() string {
return "mock"
}

func getClient(strictCheckpointAge bool, t *testing.T) (*ConsensusLightClient, error) {
baseConfig := Mainnet()
api, err := NewMockConsensusAPI("testdata/mockdata")
assert.NoError(t, err)

config := &Config{
ConsensusAPI: api.Name(),
Chain: baseConfig.Chain,
Spec: baseConfig.Spec,
StrictCheckpointAge: strictCheckpointAge,
}

checkpoint := common.Root(hexutil.MustDecode("0xc62aa0de55e6f21230fa63713715e1a6c13e73005e89f6389da271955d819bde"))

client, err := NewConsensusLightClient(api, config, checkpoint, testlog.Logger(t, log.LvlTrace))
if err != nil {
return nil, err
}

return client, nil
}

func TestVerifyCheckpointAgeInvalid(t *testing.T) {
_, err := getClient(true, t)
assert.ErrorContains(t, err, "checkpoint is too old")
}
44 changes: 44 additions & 0 deletions portalnetwork/beacon/networks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package beacon

import (
"errors"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/configs"
)

const MainnetType = iota

type BaseConfig struct {
APIPort uint64
API string
DefaultCheckpoint common.Root
Chain ChainConfig
Spec *common.Spec
MaxCheckpointAge uint64
}

func Mainnet() *BaseConfig {
return &BaseConfig{
APIPort: 8545,
API: "https://www.lightclientdata.org",
DefaultCheckpoint: common.Root(hexutil.MustDecode("0x766647f3c4e1fc91c0db9a9374032ae038778411fbff222974e11f2e3ce7dadf")),
Chain: ChainConfig{
ChainID: 1,
GenesisTime: 1606824023,
GenesisRoot: common.Root(hexutil.MustDecode("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95")),
},
Spec: configs.Mainnet,
MaxCheckpointAge: 1_209_600,
}
}

func ToBaseConfig(networkType int) (*BaseConfig, error) {
switch networkType {
case MainnetType:
return Mainnet(), nil
default:
return nil, errors.New("unknown network type")
}
}
2 changes: 1 addition & 1 deletion portalnetwork/beacon/portal_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func NewPortalLightApi() *PortalLightApi {
return &PortalLightApi{}
}

func (api *PortalLightApi) GetUpdates(firstPeriod, count uint64) (LightClientUpdateRange, error) {
func (api *PortalLightApi) GetUpdates(firstPeriod, count uint64) ([]*capella.LightClientUpdate, error) {
return api.bn.GetUpdates(firstPeriod, count)
}

Expand Down
1 change: 1 addition & 0 deletions portalnetwork/beacon/testdata/mockdata/bootstrap.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions portalnetwork/beacon/testdata/mockdata/finality.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"attested_header":{"beacon":{"slot":"7358726","proposer_index":"427162","parent_root":"0x1d7b8baa34c28a3e0d2230f8d459b92203324d1e1ec48e762c527b5ea7055612","state_root":"0x3b7be385013f0f43c12d2b5d42cedb6d7d18f854ce3b04853f526d0024034171","body_root":"0xb0bc8ce3ec58d241366aca78630803572d36ae24f9e9d52a4e21c6370585f60e"},"execution":{"parent_hash":"0x4ba3d43cc285b7774aca41a1cee5c4d4fda49e4d599065dc789c5e8d17d289ec","fee_recipient":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5","state_root":"0x7dfc406a2c6b07312e554a3a34b75ba153196559c83b60556b01362e0bf190f0","receipts_root":"0x8e4a047603c7618479f2c243c4cb88fe6329cc7c552e53bbf79c02114a2eda48","logs_bloom":"0x0fedfc2d69e98b6a31ea1f7fd9fa9a70bfb96841daa484d4c7fb60c8c610f5f6ba6541cc2124c3e157185f3362dc0b18f2338e12ac357e661c57f959f9ee2b27531d5018d6d8b9fbacebffabd694c7bde11929f04e7c28eceb7dbe47887c8ec4fe24ef2613b67998e72453c1f621a877338225703e18b73fbf24091b3a596c647fdec25df39615a342fdf06c4392ee3667b0e38309dfc7b9c77b1b4669f6f13a8e2b05573bc070842c1a70e9dd183faecc8ecd8ef93adaafc1472a4fd8faf5711e50856a32a10ad92e8db429dd6f10c14e6e6933c07daf14f804e17219a1e8071df964883b22a6f53d56e4a8ea05a94147e79682ab0d8a7e2778d9e16cddb547","prev_randao":"0xe7a8aa63bfd300810ebb9fb0389f25b2f83ad3f859a2b6d6e8d10b5e9e8a9f14","block_number":"18170142","gas_limit":"30000000","gas_used":"19938147","timestamp":"1695128735","extra_data":"0x6265617665726275696c642e6f7267","base_fee_per_gas":"18458742656","block_hash":"0x91a4a0d4a27a88f264320a82cf87743a6dc1ad724a1ebe74db8169ceb314a1d3","transactions_root":"0x094cfda3b6d80ffc84dd5ba017ef5b30a9e2e94125fcee4d42c9cc92bc2238e1","withdrawals_root":"0x4a865418c1b6acbc014b0545e4af40fa7e3ca51b44f8afd9f12225edc5a3f121"},"execution_branch":["0xa632d98f70c821de565a89b080491cfd28905c68c6bb9230ec337d3f9d255d65","0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0xbe0654721b70c4f82c3844f3877a123db4a49f8bcb417d6165faaccfbac17e99"]},"finalized_header":{"beacon":{"slot":"7358656","proposer_index":"584647","parent_root":"0x51bf31832358ee618a84c74193fdc1e58c810cdb006eaf82318edb4f2cfbc899","state_root":"0xca3dda0a7bcfade21f508a62562047dbfaccbf3b7b1e41e28177ff2f8ff6ae3f","body_root":"0xe5a8735e10bd19059a5af294fed39638eeb7344abf49d113bb25a2215d050f07"},"execution":{"parent_hash":"0x1a56b9cb321be8608d892a731fe44c829578888e3e67b38459e06d4b3885974f","fee_recipient":"0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97","state_root":"0x2f3491f8eb43e0b0f1c526e53b29df6889ad90ad2d4b72d3201ec91d2d21cf24","receipts_root":"0x753f312a70a37fa8d1e55960c5b8be9f044db12faffe6549732b4f55699ff334","logs_bloom":"0x49bd937641fd2a48b2de33e0a228ec2013b84fb4309c98ad604d0702681081901012286fc320ded140110f24449c159426252041a8a26a395d10d98ec368a9707e6b430e4f64b9796806760fd417182e95980df60c6908b4913358069074025c9f0c800c1686830b640450a603603e63e73221602a9976846fc00d98a01a281400e62345ca80811a04e5b08216981fd398408a81ed28810d141920e190b2c72f8ba00054513a2a523e0a44ec599a94e04cba54245429b18b8044a0f804022423cd04118a00222a0a24491a40d4a21204007000d0040820d42b23940205d0ec5f1c18ac48940022aa400500e469264c618020b2925f3f1a422c9018712bb11c43","prev_randao":"0x0374fa31d1b5afaba63b7acd2de8352da04cab41e493193958672f96bcda04b3","block_number":"18170072","gas_limit":"30000000","gas_used":"15671422","timestamp":"1695127895","extra_data":"0x546974616e2028746974616e6275696c6465722e78797a29","base_fee_per_gas":"13939250676","block_hash":"0xca87151eee53057062520f13077a9d11185ad4a9d0cce2a8b4a3aea71a6d2426","transactions_root":"0x00ea0f91f1f7e41b0cf9b65bb0be1d8f4efa66fbc1ede6e84b603f645aed94d4","withdrawals_root":"0xb4aa6ed2229d997ca83f077c745165294afe093126915e439a32644921cc9038"},"execution_branch":["0x728b804a08a074ccfb89211fb48537ad0d033139c6e85031e818560bba0101bc","0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x6ef4b335fac95fd2c133a0c7f54f730fb05c60eda8003ecbb62f99b5225c5d19"]},"finality_branch":["0x4682030000000000000000000000000000000000000000000000000000000000","0x43b3c8708ddb8f57a75a131a951a4369a869a3b157ee7b8436196cf18fceb182","0x09e07670197a4694452b40964340029be53e70cedc772b1403943bb170b9e723","0x2b8a2b2c9383dd30341d90baa00d5e111de55a64a5fb8a33efcd43a07d3ffdb1","0xc58b9ac49bfe4b0f9776e91e3c29a18545a214a3da57fe7fae0d1949f4377878","0x863f1e7fff7ab79747f064586436cb3010817ef1e50a70b7fd7ef773b2af701b"],"sync_aggregate":{"sync_committee_bits":"0xfffffffffffff7dfffffbfdfffffff7ff7fffffffffffffffffffffffff6fffff7effffffffffffff7ffff7ffffff7fffe7fffffffffff7bffffff7fffffff7f","sync_committee_signature":"0x933b06c2d41a4e89b20dcc2d3313d420e9d1b975de79f4b89f0dffde5cf76f028356583ab5f6fa886a4b138b5855367c13c88d1d6851e2820296a800ec1b6d7bd5c6dd41037d11df3c160c96e843460d911f07f06b938a4e373e8f02f40e873b"},"signature_slot":"7358727"}
1 change: 1 addition & 0 deletions portalnetwork/beacon/testdata/mockdata/optimistic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"attested_header":{"beacon":{"slot":"7358726","proposer_index":"427162","parent_root":"0x1d7b8baa34c28a3e0d2230f8d459b92203324d1e1ec48e762c527b5ea7055612","state_root":"0x3b7be385013f0f43c12d2b5d42cedb6d7d18f854ce3b04853f526d0024034171","body_root":"0xb0bc8ce3ec58d241366aca78630803572d36ae24f9e9d52a4e21c6370585f60e"},"execution":{"parent_hash":"0x4ba3d43cc285b7774aca41a1cee5c4d4fda49e4d599065dc789c5e8d17d289ec","fee_recipient":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5","state_root":"0x7dfc406a2c6b07312e554a3a34b75ba153196559c83b60556b01362e0bf190f0","receipts_root":"0x8e4a047603c7618479f2c243c4cb88fe6329cc7c552e53bbf79c02114a2eda48","logs_bloom":"0x0fedfc2d69e98b6a31ea1f7fd9fa9a70bfb96841daa484d4c7fb60c8c610f5f6ba6541cc2124c3e157185f3362dc0b18f2338e12ac357e661c57f959f9ee2b27531d5018d6d8b9fbacebffabd694c7bde11929f04e7c28eceb7dbe47887c8ec4fe24ef2613b67998e72453c1f621a877338225703e18b73fbf24091b3a596c647fdec25df39615a342fdf06c4392ee3667b0e38309dfc7b9c77b1b4669f6f13a8e2b05573bc070842c1a70e9dd183faecc8ecd8ef93adaafc1472a4fd8faf5711e50856a32a10ad92e8db429dd6f10c14e6e6933c07daf14f804e17219a1e8071df964883b22a6f53d56e4a8ea05a94147e79682ab0d8a7e2778d9e16cddb547","prev_randao":"0xe7a8aa63bfd300810ebb9fb0389f25b2f83ad3f859a2b6d6e8d10b5e9e8a9f14","block_number":"18170142","gas_limit":"30000000","gas_used":"19938147","timestamp":"1695128735","extra_data":"0x6265617665726275696c642e6f7267","base_fee_per_gas":"18458742656","block_hash":"0x91a4a0d4a27a88f264320a82cf87743a6dc1ad724a1ebe74db8169ceb314a1d3","transactions_root":"0x094cfda3b6d80ffc84dd5ba017ef5b30a9e2e94125fcee4d42c9cc92bc2238e1","withdrawals_root":"0x4a865418c1b6acbc014b0545e4af40fa7e3ca51b44f8afd9f12225edc5a3f121"},"execution_branch":["0xa632d98f70c821de565a89b080491cfd28905c68c6bb9230ec337d3f9d255d65","0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0xbe0654721b70c4f82c3844f3877a123db4a49f8bcb417d6165faaccfbac17e99"]},"sync_aggregate":{"sync_committee_bits":"0xfffffffffffff7dfffffbfdfffffff7ff7fffffffffffffffffffffffff6fffff7effffffffffffff7ffff7ffffff7fffe7fffffffffff7bffffff7fffffff7f","sync_committee_signature":"0x933b06c2d41a4e89b20dcc2d3313d420e9d1b975de79f4b89f0dffde5cf76f028356583ab5f6fa886a4b138b5855367c13c88d1d6851e2820296a800ec1b6d7bd5c6dd41037d11df3c160c96e843460d911f07f06b938a4e373e8f02f40e873b"},"signature_slot":"7358727"}
1 change: 1 addition & 0 deletions portalnetwork/beacon/testdata/mockdata/updates.json

Large diffs are not rendered by default.