diff --git a/relayer/cmd/generate_beacon_data.go b/relayer/cmd/generate_beacon_data.go index c2300df2ae..c0a1aa8023 100644 --- a/relayer/cmd/generate_beacon_data.go +++ b/relayer/cmd/generate_beacon_data.go @@ -19,6 +19,7 @@ import ( "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" beaconjson "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/json" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/scale" + "github.com/snowfork/snowbridge/relayer/relays/beacon/protocol" "github.com/snowfork/snowbridge/relayer/relays/beacon/store" executionConf "github.com/snowfork/snowbridge/relayer/relays/execution" @@ -128,12 +129,13 @@ func generateBeaconCheckpoint(cmd *cobra.Command, _ []string) error { return err } - store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries) + p := protocol.New(conf.Source.Beacon.Spec) + store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries, *p) store.Connect() defer store.Close() - client := api.NewBeaconClient(endpoint, conf.Source.Beacon.Spec.SlotsInEpoch) - s := syncer.New(client, conf.Source.Beacon.Spec, &store) + client := api.NewBeaconClient(endpoint) + s := syncer.New(client, &store, p) checkPointScale, err := s.GetCheckpoint() if err != nil { @@ -182,13 +184,15 @@ func generateBeaconTestFixture(cmd *cobra.Command, _ []string) error { return err } - store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries) + p := protocol.New(conf.Source.Beacon.Spec) + + store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries, *p) store.Connect() defer store.Close() log.WithFields(log.Fields{"endpoint": endpoint}).Info("connecting to beacon API") - client := api.NewBeaconClient(endpoint, conf.Source.Beacon.Spec.SlotsInEpoch) - s := syncer.New(client, conf.Source.Beacon.Spec, &store) + client := api.NewBeaconClient(endpoint) + s := syncer.New(client, &store, p) viper.SetConfigFile("/tmp/snowbridge/execution-relay-asset-hub.json") @@ -226,11 +230,11 @@ func generateBeaconTestFixture(cmd *cobra.Command, _ []string) error { return err } initialSyncHeaderSlot := initialSync.Header.Slot - initialSyncPeriod := s.ComputeSyncPeriodAtSlot(initialSyncHeaderSlot) - initialEpoch := s.ComputeEpochAtSlot(initialSyncHeaderSlot) + initialSyncPeriod := p.ComputeSyncPeriodAtSlot(initialSyncHeaderSlot) + initialEpoch := p.ComputeEpochAtSlot(initialSyncHeaderSlot) // generate SyncCommitteeUpdate for filling the missing NextSyncCommittee in initial checkpoint - syncCommitteeUpdateScale, err := s.GetSyncCommitteePeriodUpdate(initialSyncPeriod) + syncCommitteeUpdateScale, err := s.GetSyncCommitteePeriodUpdate(initialSyncPeriod, 0) if err != nil { return fmt.Errorf("get sync committee update: %w", err) } @@ -333,11 +337,11 @@ func generateBeaconTestFixture(cmd *cobra.Command, _ []string) error { if finalizedUpdate.AttestedHeader.Slot <= initialSyncHeaderSlot { return fmt.Errorf("AttestedHeader slot should be greater than initialSyncHeaderSlot") } - finalizedEpoch := s.ComputeEpochAtSlot(finalizedUpdate.AttestedHeader.Slot) + finalizedEpoch := p.ComputeEpochAtSlot(finalizedUpdate.AttestedHeader.Slot) if finalizedEpoch <= initialEpoch { return fmt.Errorf("epoch in FinalizedUpdate should be greater than initialEpoch") } - finalizedPeriod := s.ComputeSyncPeriodAtSlot(finalizedUpdate.FinalizedHeader.Slot) + finalizedPeriod := p.ComputeSyncPeriodAtSlot(finalizedUpdate.FinalizedHeader.Slot) if initialSyncPeriod != finalizedPeriod { return fmt.Errorf("initialSyncPeriod should be consistent with finalizedUpdatePeriod") } @@ -389,7 +393,7 @@ func generateBeaconTestFixture(cmd *cobra.Command, _ []string) error { return fmt.Errorf("get next finalized header update: %w", err) } nextFinalizedUpdate := nextFinalizedUpdateScale.Payload.ToJSON() - nextFinalizedUpdatePeriod := s.ComputeSyncPeriodAtSlot(nextFinalizedUpdate.FinalizedHeader.Slot) + nextFinalizedUpdatePeriod := p.ComputeSyncPeriodAtSlot(nextFinalizedUpdate.FinalizedHeader.Slot) if initialSyncPeriod+1 == nextFinalizedUpdatePeriod { err := writeJSONToFile(nextFinalizedUpdate, fmt.Sprintf("%s/%s", pathToBeaconTestFixtureFiles, "next-finalized-header-update.json")) if err != nil { @@ -398,7 +402,7 @@ func generateBeaconTestFixture(cmd *cobra.Command, _ []string) error { log.Info("created next finalized header update file") // generate nextSyncCommitteeUpdate - nextSyncCommitteeUpdateScale, err := s.GetSyncCommitteePeriodUpdate(initialSyncPeriod + 1) + nextSyncCommitteeUpdateScale, err := s.GetSyncCommitteePeriodUpdate(initialSyncPeriod+1, 0) if err != nil { return fmt.Errorf("get sync committee update: %w", err) } @@ -480,16 +484,17 @@ func generateExecutionUpdate(cmd *cobra.Command, _ []string) error { if err != nil { return err } - specSettings := conf.Source.Beacon.Spec log.WithFields(log.Fields{"endpoint": endpoint}).Info("connecting to beacon API") - store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries) + p := protocol.New(conf.Source.Beacon.Spec) + + store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries, *p) store.Connect() defer store.Close() // generate executionUpdate - client := api.NewBeaconClient(endpoint, specSettings.SlotsInEpoch) - s := syncer.New(client, specSettings, &store) + client := api.NewBeaconClient(endpoint) + s := syncer.New(client, &store, p) blockRoot, err := s.Client.GetBeaconBlockRoot(uint64(beaconSlot)) if err != nil { return fmt.Errorf("fetch block: %w", err) @@ -667,13 +672,15 @@ func generateInboundFixture(cmd *cobra.Command, _ []string) error { return err } - store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries) + p := protocol.New(conf.Source.Beacon.Spec) + + store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries, *p) store.Connect() defer store.Close() log.WithFields(log.Fields{"endpoint": endpoint}).Info("connecting to beacon API") - client := api.NewBeaconClient(endpoint, conf.Source.Beacon.Spec.SlotsInEpoch) - s := syncer.New(client, conf.Source.Beacon.Spec, &store) + client := api.NewBeaconClient(endpoint) + s := syncer.New(client, &store, p) viper.SetConfigFile("/tmp/snowbridge/execution-relay-asset-hub.json") diff --git a/relayer/cmd/import_execution_header.go b/relayer/cmd/import_execution_header.go index 02693134ec..ed7e20b0fd 100644 --- a/relayer/cmd/import_execution_header.go +++ b/relayer/cmd/import_execution_header.go @@ -11,6 +11,7 @@ import ( "github.com/snowfork/snowbridge/relayer/relays/beacon/config" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" + "github.com/snowfork/snowbridge/relayer/relays/beacon/protocol" "github.com/snowfork/snowbridge/relayer/relays/beacon/store" "github.com/ethereum/go-ethereum/common" @@ -89,8 +90,6 @@ func importExecutionHeaderFn(cmd *cobra.Command, _ []string) error { return err } - specSettings := conf.Source.Beacon.Spec - keypair, err := getKeyPair(privateKeyFile) if err != nil { return fmt.Errorf("get keypair from file: %w", err) @@ -110,12 +109,13 @@ func importExecutionHeaderFn(cmd *cobra.Command, _ []string) error { log.WithField("hash", beaconHeader).Info("will be syncing execution header for beacon hash") - store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries) + p := protocol.New(conf.Source.Beacon.Spec) + store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries, *p) store.Connect() defer store.Close() - client := api.NewBeaconClient(lodestarEndpoint, specSettings.SlotsInEpoch) - syncer := syncer.New(client, specSettings, &store) + client := api.NewBeaconClient(lodestarEndpoint) + syncer := syncer.New(client, &store, p) beaconHeaderHash := common.HexToHash(finalizedHeader) diff --git a/relayer/cmd/store_beacon_state.go b/relayer/cmd/store_beacon_state.go index 9e5468cce3..3e05132992 100644 --- a/relayer/cmd/store_beacon_state.go +++ b/relayer/cmd/store_beacon_state.go @@ -2,18 +2,18 @@ package cmd import ( "fmt" - log "github.com/sirupsen/logrus" "strconv" "github.com/snowfork/snowbridge/relayer/relays/beacon/config" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" + "github.com/snowfork/snowbridge/relayer/relays/beacon/protocol" "github.com/snowfork/snowbridge/relayer/relays/beacon/store" + _ "github.com/mattn/go-sqlite3" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" - - _ "github.com/mattn/go-sqlite3" ) func storeBeaconState() *cobra.Command { @@ -49,11 +49,10 @@ func storeBeaconStateInDB(cmd *cobra.Command, _ []string) error { return err } - store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries) - - specSettings := conf.Source.Beacon.Spec - beaconClient := api.NewBeaconClient(conf.Source.Beacon.Endpoint, specSettings.SlotsInEpoch) - syncer := syncer.New(beaconClient, specSettings, &store) + p := protocol.New(conf.Source.Beacon.Spec) + store := store.New(conf.Source.Beacon.DataStore.Location, conf.Source.Beacon.DataStore.MaxEntries, *p) + beaconClient := api.NewBeaconClient(conf.Source.Beacon.Endpoint) + syncer := syncer.New(beaconClient, &store, p) err = store.Connect() if err != nil { @@ -69,8 +68,6 @@ func storeBeaconStateInDB(cmd *cobra.Command, _ []string) error { attestedHeaderSlot := uint64(update.Payload.AttestedHeader.Slot) finalizedHeaderSlot := uint64(update.Payload.FinalizedHeader.Slot) - attestedSyncPeriod := syncer.ComputeSyncPeriodAtSlot(attestedHeaderSlot) - finalizedSyncPeriod := syncer.ComputeSyncPeriodAtSlot(finalizedHeaderSlot) attestedBeaconData, err := syncer.Client.GetBeaconState(strconv.FormatUint(attestedHeaderSlot, 10)) if err != nil { @@ -81,19 +78,7 @@ func storeBeaconStateInDB(cmd *cobra.Command, _ []string) error { return fmt.Errorf("download finalized beacon state at slot %d: %w", finalizedHeaderSlot, err) } - err = store.WriteStateFile(attestedHeaderSlot, attestedBeaconData) - if err != nil { - return err - } - err = store.WriteStateFile(finalizedHeaderSlot, finalizedBeaconData) - if err != nil { - return err - } - - err = store.StoreUpdate(attestedHeaderSlot, finalizedHeaderSlot, attestedSyncPeriod, finalizedSyncPeriod) - if err != nil { - return fmt.Errorf("store beacon update: %w", err) - } + err = store.WriteEntry(attestedHeaderSlot, finalizedHeaderSlot, attestedBeaconData, finalizedBeaconData) deletedSlots, err := store.PruneOldStates() log.WithField("deletedSlots", deletedSlots).Info("deleted old beacon states") diff --git a/relayer/relays/beacon/config/config.go b/relayer/relays/beacon/config/config.go index 21776b2f88..c8ab4a4807 100644 --- a/relayer/relays/beacon/config/config.go +++ b/relayer/relays/beacon/config/config.go @@ -10,6 +10,7 @@ type Config struct { } type SpecSettings struct { + SyncCommitteeSize uint64 `mapstructure:"syncCommitteeSize"` SlotsInEpoch uint64 `mapstructure:"slotsInEpoch"` EpochsPerSyncCommitteePeriod uint64 `mapstructure:"epochsPerSyncCommitteePeriod"` DenebForkEpoch uint64 `mapstructure:"denebForkedEpoch"` diff --git a/relayer/relays/beacon/header/header.go b/relayer/relays/beacon/header/header.go index 1ed83b14a6..c13e9cbc94 100644 --- a/relayer/relays/beacon/header/header.go +++ b/relayer/relays/beacon/header/header.go @@ -12,6 +12,7 @@ import ( "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/scale" + "github.com/snowfork/snowbridge/relayer/relays/beacon/protocol" "github.com/snowfork/snowbridge/relayer/relays/beacon/state" "github.com/snowfork/snowbridge/relayer/relays/beacon/store" @@ -28,22 +29,18 @@ var ErrExecutionHeaderNotImported = errors.New("execution header not imported") var ErrBeaconHeaderNotFinalized = errors.New("beacon header not finalized") type Header struct { - cache *cache.BeaconCache - writer parachain.ChainWriter - syncer *syncer.Syncer - store store.BeaconStore - slotsInEpoch uint64 - epochsPerSyncCommitteePeriod uint64 + cache *cache.BeaconCache + writer parachain.ChainWriter + syncer *syncer.Syncer + protocol *protocol.Protocol } -func New(writer parachain.ChainWriter, client api.BeaconAPI, setting config.SpecSettings, store store.BeaconStore) Header { +func New(writer parachain.ChainWriter, client api.BeaconAPI, setting config.SpecSettings, store store.BeaconStore, protocol *protocol.Protocol) Header { return Header{ - cache: cache.New(setting.SlotsInEpoch, setting.EpochsPerSyncCommitteePeriod), - writer: writer, - syncer: syncer.New(client, setting, store), - store: store, - slotsInEpoch: setting.SlotsInEpoch, - epochsPerSyncCommitteePeriod: setting.EpochsPerSyncCommitteePeriod, + cache: cache.New(setting.SlotsInEpoch, setting.EpochsPerSyncCommitteePeriod), + writer: writer, + syncer: syncer.New(client, store, protocol), + protocol: protocol, } } @@ -52,7 +49,7 @@ func (h *Header) Sync(ctx context.Context, eg *errgroup.Group) error { if err != nil { return fmt.Errorf("fetch parachain last finalized header state: %w", err) } - latestSyncedPeriod := h.syncer.ComputeSyncPeriodAtSlot(lastFinalizedHeaderState.BeaconSlot) + latestSyncedPeriod := h.protocol.ComputeSyncPeriodAtSlot(lastFinalizedHeaderState.BeaconSlot) log.WithFields(log.Fields{ "last_finalized_hash": lastFinalizedHeaderState.BeaconBlockRoot, @@ -63,6 +60,15 @@ func (h *Header) Sync(ctx context.Context, eg *errgroup.Group) error { h.cache.SetInitialCheckpointSlot(lastFinalizedHeaderState.InitialCheckpointSlot) h.cache.AddCheckPointSlots([]uint64{lastFinalizedHeaderState.BeaconSlot}) + // Special handling here for the initial checkpoint to sync the next sync committee which is not included in initial + // checkpoint. + if h.isInitialSyncPeriod() { + err = h.SyncCommitteePeriodUpdate(ctx, latestSyncedPeriod) + if err != nil { + return fmt.Errorf("sync next committee for initial sync period: %w", err) + } + } + log.Info("starting to sync finalized headers") ticker := time.NewTicker(time.Second * 10) @@ -104,8 +110,7 @@ func (h *Header) Sync(ctx context.Context, eg *errgroup.Group) error { } func (h *Header) SyncCommitteePeriodUpdate(ctx context.Context, period uint64) error { - update, err := h.syncer.GetSyncCommitteePeriodUpdate(period) - + update, err := h.syncer.GetSyncCommitteePeriodUpdate(period, h.cache.Finalized.LastSyncedSlot) switch { case errors.Is(err, syncer.ErrCommitteeUpdateHeaderInDifferentSyncPeriod): { @@ -121,7 +126,7 @@ func (h *Header) SyncCommitteePeriodUpdate(ctx context.Context, period uint64) e // If the gap between the last two finalized headers is more than the sync committee period, sync an interim // finalized header - maxLatency := h.cache.Finalized.LastSyncedSlot + (h.slotsInEpoch * h.epochsPerSyncCommitteePeriod) + maxLatency := h.cache.Finalized.LastSyncedSlot + (h.protocol.Settings.SlotsInEpoch * h.protocol.Settings.EpochsPerSyncCommitteePeriod) if maxLatency < uint64(update.Payload.FinalizedHeader.Slot) { err = h.syncInterimFinalizedUpdate(ctx, h.cache.Finalized.LastSyncedSlot) if err != nil { @@ -144,7 +149,7 @@ func (h *Header) SyncCommitteePeriodUpdate(ctx context.Context, period uint64) e if err != nil { return fmt.Errorf("fetch last finalized header state: %w", err) } - lastUpdatedPeriod := h.syncer.ComputeSyncPeriodAtSlot(lastFinalizedHeaderState.BeaconSlot) + lastUpdatedPeriod := h.protocol.ComputeSyncPeriodAtSlot(lastFinalizedHeaderState.BeaconSlot) if period != lastUpdatedPeriod { return ErrSyncCommitteeNotImported } @@ -166,8 +171,8 @@ func (h *Header) SyncFinalizedHeader(ctx context.Context) error { "blockRoot": update.FinalizedHeaderBlockRoot, }).Info("syncing finalized header from Ethereum beacon client") - currentSyncPeriod := h.syncer.ComputeSyncPeriodAtSlot(uint64(update.Payload.AttestedHeader.Slot)) - lastSyncedPeriod := h.syncer.ComputeSyncPeriodAtSlot(h.cache.Finalized.LastSyncedSlot) + currentSyncPeriod := h.protocol.ComputeSyncPeriodAtSlot(uint64(update.Payload.AttestedHeader.Slot)) + lastSyncedPeriod := h.protocol.ComputeSyncPeriodAtSlot(h.cache.Finalized.LastSyncedSlot) if lastSyncedPeriod < currentSyncPeriod { err = h.syncLaggingSyncCommitteePeriods(ctx, lastSyncedPeriod, currentSyncPeriod) @@ -233,8 +238,9 @@ func (h *Header) SyncHeaders(ctx context.Context) error { } func (h *Header) syncInterimFinalizedUpdate(ctx context.Context, lastSyncedSlot uint64) error { - checkpointSlot := h.syncer.CalculateNextCheckpointSlot(lastSyncedSlot) - finalizedUpdate, err := h.syncer.GetFinalizedUpdateAtAttestedSlot(checkpointSlot, lastSyncedSlot) + checkpointSlot := h.protocol.CalculateNextCheckpointSlot(lastSyncedSlot) + + finalizedUpdate, err := h.syncer.GetLatestPossibleFinalizedUpdate(checkpointSlot, lastSyncedSlot) if err != nil { return fmt.Errorf("get interim checkpoint to update chain (checkpoint slot %d, original slot: %d): %w", checkpointSlot, lastSyncedSlot, err) } @@ -254,12 +260,6 @@ func (h *Header) syncLaggingSyncCommitteePeriods(ctx context.Context, latestSync periodsToSync = append(periodsToSync, i) } - // Special handling here for the initial checkpoint to sync the next sync committee which is not included in initial - // checkpoint. - if h.isInitialSyncPeriod() { - periodsToSync = append([]uint64{latestSyncedPeriod}, periodsToSync...) - } - log.WithFields(log.Fields{ "periods": periodsToSync, }).Info("sync committee periods to be synced") @@ -273,7 +273,7 @@ func (h *Header) syncLaggingSyncCommitteePeriods(ctx context.Context, latestSync // If Latency found between LastSyncedSyncCommitteePeriod and currentSyncPeriod in Ethereum beacon client // just return error so to exit ASAP to allow ExecutionUpdate to catch up - lastSyncedPeriod := h.syncer.ComputeSyncPeriodAtSlot(h.cache.Finalized.LastSyncedSlot) + lastSyncedPeriod := h.protocol.ComputeSyncPeriodAtSlot(h.cache.Finalized.LastSyncedSlot) if lastSyncedPeriod < currentSyncPeriod { return ErrSyncCommitteeLatency } @@ -327,13 +327,10 @@ func (h *Header) populateClosestCheckpoint(slot uint64) (cache.Proof, error) { switch { case errors.Is(cache.FinalizedCheckPointNotAvailable, err) || errors.Is(cache.FinalizedCheckPointNotPopulated, err): - checkpointSlot := checkpoint.Slot - if checkpointSlot == 0 { - checkpointSlot, err = h.populateCheckPointCacheWithDataFromChain(slot) - if err != nil { - // There should always be a checkpoint onchain with the range of the sync committee period slots - return checkpoint, fmt.Errorf("find checkpoint on-chain: %w", err) - } + err = h.populateCheckPointCacheWithDataFromChain(slot) + if err != nil { + // There should always be a checkpoint onchain with the range of the sync committee period slots + return checkpoint, fmt.Errorf("find checkpoint on-chain for slot %d: %w", slot, err) } checkpoint, err = h.cache.GetClosestCheckpoint(slot) @@ -351,22 +348,27 @@ func (h *Header) populateClosestCheckpoint(slot uint64) (cache.Proof, error) { return checkpoint, nil } -func (h *Header) populateCheckPointCacheWithDataFromChain(slot uint64) (uint64, error) { - checkpointSlot := h.syncer.CalculateNextCheckpointSlot(slot) +func (h *Header) getNextHeaderUpdateBySlot(slot uint64) (scale.HeaderUpdatePayload, error) { + slot = slot + 1 + return h.getHeaderUpdateBySlot(slot) +} + +func (h *Header) populateCheckPointCacheWithDataFromChain(slot uint64) error { + checkpointSlot := h.protocol.CalculateNextCheckpointSlot(slot) lastFinalizedHeaderState, err := h.writer.GetLastFinalizedHeaderState() if err != nil { - return 0, fmt.Errorf("get last finalized header for the checkpoint: %w", err) + return fmt.Errorf("get last finalized header for the checkpoint: %w", err) } if slot > lastFinalizedHeaderState.BeaconSlot { - return 0, ErrBeaconHeaderNotFinalized + return ErrBeaconHeaderNotFinalized } if checkpointSlot < lastFinalizedHeaderState.BeaconSlot { historicState, err := h.findLatestCheckPoint(slot) if err != nil { - return 0, fmt.Errorf("get history finalized header for the checkpoint: %w", err) + return fmt.Errorf("get history finalized header for the checkpoint: %w", err) } checkpointSlot = historicState.BeaconSlot } else { @@ -377,10 +379,10 @@ func (h *Header) populateCheckPointCacheWithDataFromChain(slot uint64) (uint64, err = h.populateFinalizedCheckpoint(checkpointSlot) if err != nil { - return 0, fmt.Errorf("populated local cache with finalized header found on-chain: %w", err) + return fmt.Errorf("populated local cache with finalized header found on-chain: %w", err) } - return 0, nil + return nil } func (h *Header) getHeaderUpdateBySlot(slot uint64) (scale.HeaderUpdatePayload, error) { @@ -401,7 +403,7 @@ func (h *Header) getHeaderUpdateBySlot(slot uint64) (scale.HeaderUpdatePayload, func (h *Header) FetchExecutionProof(blockRoot common.Hash) (scale.HeaderUpdatePayload, error) { var headerUpdate scale.HeaderUpdatePayload - header, err := h.syncer.Client.GetHeader(blockRoot) + header, err := h.syncer.Client.GetHeaderByBlockRoot(blockRoot) if err != nil { return headerUpdate, fmt.Errorf("get beacon header by blockRoot: %w", err) } @@ -421,8 +423,8 @@ func (h *Header) FetchExecutionProof(blockRoot common.Hash) (scale.HeaderUpdateP } func (h *Header) isInitialSyncPeriod() bool { - initialPeriod := h.syncer.ComputeSyncPeriodAtSlot(h.cache.InitialCheckpointSlot) - lastFinalizedPeriod := h.syncer.ComputeSyncPeriodAtSlot(h.cache.Finalized.LastSyncedSlot) + initialPeriod := h.protocol.ComputeSyncPeriodAtSlot(h.cache.InitialCheckpointSlot) + lastFinalizedPeriod := h.protocol.ComputeSyncPeriodAtSlot(h.cache.Finalized.LastSyncedSlot) return initialPeriod == lastFinalizedPeriod } @@ -434,11 +436,11 @@ func (h *Header) findLatestCheckPoint(slot uint64) (state.FinalizedHeader, error } startIndex := uint64(lastIndex) endIndex := uint64(0) - if uint64(lastIndex) > h.epochsPerSyncCommitteePeriod { - endIndex = endIndex - h.epochsPerSyncCommitteePeriod + if uint64(lastIndex) > h.protocol.Settings.EpochsPerSyncCommitteePeriod { + endIndex = endIndex - h.protocol.Settings.EpochsPerSyncCommitteePeriod } - syncCommitteePeriod := h.slotsInEpoch * h.epochsPerSyncCommitteePeriod + syncCommitteePeriod := h.protocol.Settings.SlotsInEpoch * h.protocol.Settings.EpochsPerSyncCommitteePeriod for index := startIndex; index >= endIndex; index-- { beaconRoot, err := h.writer.GetFinalizedBeaconRootByIndex(uint32(index)) diff --git a/relayer/relays/beacon/header/header_test.go b/relayer/relays/beacon/header/header_test.go index 26e108fe93..aaccca130d 100644 --- a/relayer/relays/beacon/header/header_test.go +++ b/relayer/relays/beacon/header/header_test.go @@ -3,10 +3,10 @@ package header import ( "context" "github.com/ethereum/go-ethereum/common" - "github.com/snowfork/snowbridge/relayer/relays/beacon/cache" "github.com/snowfork/snowbridge/relayer/relays/beacon/config" - "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" + "github.com/snowfork/snowbridge/relayer/relays/beacon/mock" + "github.com/snowfork/snowbridge/relayer/relays/beacon/protocol" "github.com/snowfork/snowbridge/relayer/relays/beacon/state" "github.com/snowfork/snowbridge/relayer/relays/beacon/store" "github.com/snowfork/snowbridge/relayer/relays/testutil" @@ -21,9 +21,9 @@ func TestSyncInterimFinalizedUpdate_WithDataFromAPI(t *testing.T) { EpochsPerSyncCommitteePeriod: 256, DenebForkEpoch: 0, } - client := testutil.MockAPI{} - beaconStore := testutil.MockStore{} - beaconSyncer := syncer.New(&client, settings, &beaconStore) + p := protocol.New(settings) + client := mock.API{} + beaconStore := mock.Store{} headerAtSlot4571072, err := testutil.GetHeaderAtSlot(4571072) require.NoError(t, err) @@ -52,9 +52,8 @@ func TestSyncInterimFinalizedUpdate_WithDataFromAPI(t *testing.T) { } client.BeaconStates = beaconStates - h := Header{ - cache: cache.New(settings.SlotsInEpoch, settings.EpochsPerSyncCommitteePeriod), - writer: &testutil.MockWriter{ + h := New( + &mock.Writer{ LastFinalizedState: state.FinalizedHeader{ BeaconBlockRoot: common.Hash{}, BeaconSlot: 4562496, @@ -62,10 +61,11 @@ func TestSyncInterimFinalizedUpdate_WithDataFromAPI(t *testing.T) { InitialCheckpointSlot: 0, }, }, - syncer: beaconSyncer, - slotsInEpoch: settings.SlotsInEpoch, - epochsPerSyncCommitteePeriod: settings.EpochsPerSyncCommitteePeriod, - } + &client, + settings, + &beaconStore, + p, + ) // Find a checkpoint for a slot that is just out of the on-chain synced finalized header block roots range err = h.syncInterimFinalizedUpdate(context.Background(), 4570722) @@ -78,9 +78,9 @@ func TestSyncInterimFinalizedUpdate_WithDataFromStore(t *testing.T) { EpochsPerSyncCommitteePeriod: 256, DenebForkEpoch: 0, } - client := testutil.MockAPI{} - beaconStore := testutil.MockStore{} - beaconSyncer := syncer.New(&client, settings, &beaconStore) + p := protocol.New(settings) + client := mock.API{} + beaconStore := mock.Store{} headerAtSlot4571072, err := testutil.GetHeaderAtSlot(4571072) require.NoError(t, err) @@ -108,16 +108,15 @@ func TestSyncInterimFinalizedUpdate_WithDataFromStore(t *testing.T) { finalizedState, err := testutil.LoadFile("4571072.ssz") require.NoError(t, err) // Return the beacon state from the stpore - beaconStore.BeaconStateData = store.StoredBeaconData{ + beaconStore.StoredBeaconStateData = store.StoredBeaconData{ AttestedSlot: 4571136, FinalizedSlot: 4571072, AttestedBeaconState: attestedState, FinalizedBeaconState: finalizedState, } - h := Header{ - cache: cache.New(settings.SlotsInEpoch, settings.EpochsPerSyncCommitteePeriod), - writer: &testutil.MockWriter{ + h := New( + &mock.Writer{ LastFinalizedState: state.FinalizedHeader{ BeaconBlockRoot: common.Hash{}, BeaconSlot: 4562496, @@ -125,10 +124,11 @@ func TestSyncInterimFinalizedUpdate_WithDataFromStore(t *testing.T) { InitialCheckpointSlot: 0, }, }, - syncer: beaconSyncer, - slotsInEpoch: settings.SlotsInEpoch, - epochsPerSyncCommitteePeriod: settings.EpochsPerSyncCommitteePeriod, - } + &client, + settings, + &beaconStore, + p, + ) // Find a checkpoint for a slot that is just out of the on-chain synced finalized header block roots range err = h.syncInterimFinalizedUpdate(context.Background(), 4570722) @@ -143,9 +143,9 @@ func TestSyncInterimFinalizedUpdate_WithDataFromStoreWithDifferentBlocks(t *test EpochsPerSyncCommitteePeriod: 256, DenebForkEpoch: 0, } - client := testutil.MockAPI{} - beaconStore := testutil.MockStore{} - beaconSyncer := syncer.New(&client, settings, &beaconStore) + p := protocol.New(settings) + client := mock.API{} + beaconStore := mock.Store{} headerAtSlot4570752, err := testutil.GetHeaderAtSlot(4570752) require.NoError(t, err) @@ -173,16 +173,15 @@ func TestSyncInterimFinalizedUpdate_WithDataFromStoreWithDifferentBlocks(t *test finalizedState, err := testutil.LoadFile("4570752.ssz") require.NoError(t, err) // Return the beacon state from the store - beaconStore.BeaconStateData = store.StoredBeaconData{ + beaconStore.StoredBeaconStateData = store.StoredBeaconData{ AttestedSlot: 4570816, FinalizedSlot: 4570752, AttestedBeaconState: attestedState, FinalizedBeaconState: finalizedState, } - h := Header{ - cache: cache.New(settings.SlotsInEpoch, settings.EpochsPerSyncCommitteePeriod), - writer: &testutil.MockWriter{ + h := New( + &mock.Writer{ LastFinalizedState: state.FinalizedHeader{ BeaconBlockRoot: common.Hash{}, BeaconSlot: 4562496, @@ -190,10 +189,11 @@ func TestSyncInterimFinalizedUpdate_WithDataFromStoreWithDifferentBlocks(t *test InitialCheckpointSlot: 0, }, }, - syncer: beaconSyncer, - slotsInEpoch: settings.SlotsInEpoch, - epochsPerSyncCommitteePeriod: settings.EpochsPerSyncCommitteePeriod, - } + &client, + settings, + &beaconStore, + p, + ) // Find a checkpoint for a slot that is just out of the on-chain synced finalized header block roots range err = h.syncInterimFinalizedUpdate(context.Background(), 4570722) @@ -208,10 +208,9 @@ func TestSyncInterimFinalizedUpdate_BeaconStateNotAvailableInAPIAndStore(t *test EpochsPerSyncCommitteePeriod: 256, DenebForkEpoch: 0, } - - client := testutil.MockAPI{} - beaconStore := testutil.MockStore{} - beaconSyncer := syncer.New(&client, settings, &beaconStore) + p := protocol.New(settings) + client := mock.API{} + beaconStore := mock.Store{} headerAtSlot4571072, err := testutil.GetHeaderAtSlot(4571072) require.NoError(t, err) @@ -226,9 +225,8 @@ func TestSyncInterimFinalizedUpdate_BeaconStateNotAvailableInAPIAndStore(t *test 4571137: headerAtSlot4571137, } - h := Header{ - cache: cache.New(settings.SlotsInEpoch, settings.EpochsPerSyncCommitteePeriod), - writer: &testutil.MockWriter{ + h := New( + &mock.Writer{ LastFinalizedState: state.FinalizedHeader{ BeaconBlockRoot: common.Hash{}, BeaconSlot: 4562496, @@ -236,10 +234,11 @@ func TestSyncInterimFinalizedUpdate_BeaconStateNotAvailableInAPIAndStore(t *test InitialCheckpointSlot: 0, }, }, - syncer: beaconSyncer, - slotsInEpoch: settings.SlotsInEpoch, - epochsPerSyncCommitteePeriod: settings.EpochsPerSyncCommitteePeriod, - } + &client, + settings, + &beaconStore, + p, + ) // Find a checkpoint for a slot that is just out of the on-chain synced finalized header block roots range err = h.syncInterimFinalizedUpdate(context.Background(), 4570722) @@ -252,10 +251,9 @@ func TestSyncInterimFinalizedUpdate_NoValidBlocksFound(t *testing.T) { EpochsPerSyncCommitteePeriod: 256, DenebForkEpoch: 0, } - - client := testutil.MockAPI{} - beaconStore := testutil.MockStore{} - beaconSyncer := syncer.New(&client, settings, &beaconStore) + p := protocol.New(settings) + client := mock.API{} + beaconStore := mock.Store{} headerAtSlot4571072, err := testutil.GetHeaderAtSlot(4571072) require.NoError(t, err) @@ -265,9 +263,8 @@ func TestSyncInterimFinalizedUpdate_NoValidBlocksFound(t *testing.T) { 4571072: headerAtSlot4571072, } - h := Header{ - cache: cache.New(settings.SlotsInEpoch, settings.EpochsPerSyncCommitteePeriod), - writer: &testutil.MockWriter{ + h := New( + &mock.Writer{ LastFinalizedState: state.FinalizedHeader{ BeaconBlockRoot: common.Hash{}, BeaconSlot: 4562496, @@ -275,10 +272,11 @@ func TestSyncInterimFinalizedUpdate_NoValidBlocksFound(t *testing.T) { InitialCheckpointSlot: 0, }, }, - syncer: beaconSyncer, - slotsInEpoch: settings.SlotsInEpoch, - epochsPerSyncCommitteePeriod: settings.EpochsPerSyncCommitteePeriod, - } + &client, + settings, + &beaconStore, + p, + ) // Find a checkpoint for a slot that is just out of the on-chain synced finalized header block roots range err = h.syncInterimFinalizedUpdate(context.Background(), 4570722) diff --git a/relayer/relays/beacon/header/syncer/api/api.go b/relayer/relays/beacon/header/syncer/api/api.go index 1345f58062..fe21b5e0bd 100644 --- a/relayer/relays/beacon/header/syncer/api/api.go +++ b/relayer/relays/beacon/header/syncer/api/api.go @@ -29,7 +29,8 @@ type BeaconAPI interface { GetGenesis() (Genesis, error) GetFinalizedCheckpoint() (FinalizedCheckpoint, error) GetHeaderBySlot(slot uint64) (BeaconHeader, error) - GetHeader(blockRoot common.Hash) (BeaconHeader, error) + GetHeaderAtHead() (BeaconHeader, error) + GetHeaderByBlockRoot(blockRoot common.Hash) (BeaconHeader, error) GetBeaconBlockBySlot(slot uint64) (BeaconBlockResponse, error) GetBeaconBlockRoot(slot uint64) (common.Hash, error) GetBeaconBlock(blockID common.Hash) (BeaconBlockResponse, error) @@ -39,16 +40,14 @@ type BeaconAPI interface { } type BeaconClient struct { - httpClient http.Client - endpoint string - slotsInEpoch uint64 + httpClient http.Client + endpoint string } -func NewBeaconClient(endpoint string, slotsInEpoch uint64) *BeaconClient { +func NewBeaconClient(endpoint string) *BeaconClient { return &BeaconClient{ http.Client{}, endpoint, - slotsInEpoch, } } @@ -153,58 +152,19 @@ func (b *BeaconClient) GetFinalizedCheckpoint() (FinalizedCheckpoint, error) { } func (b *BeaconClient) GetHeaderBySlot(slot uint64) (BeaconHeader, error) { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/eth/v1/beacon/headers/%d", b.endpoint, slot), nil) - if err != nil { - return BeaconHeader{}, fmt.Errorf("%s: %w", ConstructRequestErrorMessage, err) - } - - req.Header.Set("accept", "application/json") - res, err := b.httpClient.Do(req) - if err != nil { - return BeaconHeader{}, fmt.Errorf("%s: %w", DoHTTPRequestErrorMessage, err) - } - - if res.StatusCode != http.StatusOK { - if res.StatusCode == 404 { - return BeaconHeader{}, ErrNotFound - } - - return BeaconHeader{}, fmt.Errorf("%s: %d", HTTPStatusNotOKErrorMessage, res.StatusCode) - } - - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - return BeaconHeader{}, fmt.Errorf("%s: %w", ReadResponseBodyErrorMessage, err) - } - - var response BeaconHeaderResponse - - err = json.Unmarshal(bodyBytes, &response) - if err != nil { - return BeaconHeader{}, fmt.Errorf("%s: %w", UnmarshalBodyErrorMessage, err) - } - - slotFromResponse, err := strconv.ParseUint(response.Data.Header.Message.Slot, 10, 64) - if err != nil { - return BeaconHeader{}, fmt.Errorf("parse slot as int: %w", err) - } + return b.GetHeader(fmt.Sprintf("%d", slot)) +} - proposerIndex, err := strconv.ParseUint(response.Data.Header.Message.ProposerIndex, 10, 64) - if err != nil { - return BeaconHeader{}, fmt.Errorf("parse proposerIndex as int: %w", err) - } +func (b *BeaconClient) GetHeaderAtHead() (BeaconHeader, error) { + return b.GetHeader("head") +} - return BeaconHeader{ - Slot: slotFromResponse, - ProposerIndex: proposerIndex, - ParentRoot: common.HexToHash(response.Data.Header.Message.ParentRoot), - StateRoot: common.HexToHash(response.Data.Header.Message.StateRoot), - BodyRoot: common.HexToHash(response.Data.Header.Message.BodyRoot), - }, nil +func (b *BeaconClient) GetHeaderByBlockRoot(blockRoot common.Hash) (BeaconHeader, error) { + return b.GetHeader(blockRoot.Hex()) } -func (b *BeaconClient) GetHeader(blockRoot common.Hash) (BeaconHeader, error) { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/eth/v1/beacon/headers/%s", b.endpoint, blockRoot), nil) +func (b *BeaconClient) GetHeader(qualifier string) (BeaconHeader, error) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/eth/v1/beacon/headers/%s", b.endpoint, qualifier), nil) if err != nil { return BeaconHeader{}, fmt.Errorf("%s: %w", ConstructRequestErrorMessage, err) } diff --git a/relayer/relays/beacon/header/syncer/api/api_response.go b/relayer/relays/beacon/header/syncer/api/api_response.go index 00fb06ed87..1ba06034b7 100644 --- a/relayer/relays/beacon/header/syncer/api/api_response.go +++ b/relayer/relays/beacon/header/syncer/api/api_response.go @@ -29,51 +29,57 @@ type SyncCommitteePeriodUpdateResponse struct { } `json:"data"` } +type BeaconBlockResponseData struct { + Message BeaconBlockResponseMessage `json:"message"` +} + +type BeaconBlockResponseMessage struct { + Slot string `json:"slot"` + ProposerIndex string `json:"proposer_index"` + ParentRoot string `json:"parent_root"` + StateRoot string `json:"state_root"` + Body BeaconBlockResponseBody `json:"body"` +} + +type BeaconBlockResponseBody struct { + RandaoReveal string `json:"randao_reveal"` + Eth1Data struct { + DepositRoot string `json:"deposit_root"` + DepositCount string `json:"deposit_count"` + BlockHash string `json:"block_hash"` + } `json:"eth1_data"` + Graffiti string `json:"graffiti"` + ProposerSlashings []ProposerSlashingResponse `json:"proposer_slashings"` + AttesterSlashings []AttesterSlashingResponse `json:"attester_slashings"` + Attestations []AttestationResponse `json:"attestations"` + Deposits []DepositResponse `json:"deposits"` + VoluntaryExits []SignedVoluntaryExitResponse `json:"voluntary_exits"` + SyncAggregate SyncAggregateResponse `json:"sync_aggregate"` + ExecutionPayload struct { + ParentHash string `json:"parent_hash"` + FeeRecipient string `json:"fee_recipient"` + StateRoot string `json:"state_root"` + ReceiptsRoot string `json:"receipts_root"` + LogsBloom string `json:"logs_bloom"` + PrevRandao string `json:"prev_randao"` + BlockNumber string `json:"block_number"` + GasLimit string `json:"gas_limit"` + GasUsed string `json:"gas_used"` + Timestamp string `json:"timestamp"` + ExtraData string `json:"extra_data"` + BaseFeePerGas string `json:"base_fee_per_gas"` + BlockHash string `json:"block_hash"` + Transactions []string `json:"transactions"` + Withdrawals []WithdrawalResponse `json:"withdrawals"` + BlobGasUsed string `json:"blob_gas_used,omitempty"` + ExcessBlobGas string `json:"excess_blob_gas,omitempty"` + } `json:"execution_payload"` + BlsToExecutionChanges []SignedBLSToExecutionChangeResponse `json:"bls_to_execution_changes"` + BlobKzgCommitments []string `json:"blob_kzg_commitments"` +} + type BeaconBlockResponse struct { - Data struct { - Message struct { - Slot string `json:"slot"` - ProposerIndex string `json:"proposer_index"` - ParentRoot string `json:"parent_root"` - StateRoot string `json:"state_root"` - Body struct { - RandaoReveal string `json:"randao_reveal"` - Eth1Data struct { - DepositRoot string `json:"deposit_root"` - DepositCount string `json:"deposit_count"` - BlockHash string `json:"block_hash"` - } `json:"eth1_data"` - Graffiti string `json:"graffiti"` - ProposerSlashings []ProposerSlashingResponse `json:"proposer_slashings"` - AttesterSlashings []AttesterSlashingResponse `json:"attester_slashings"` - Attestations []AttestationResponse `json:"attestations"` - Deposits []DepositResponse `json:"deposits"` - VoluntaryExits []SignedVoluntaryExitResponse `json:"voluntary_exits"` - SyncAggregate SyncAggregateResponse `json:"sync_aggregate"` - ExecutionPayload struct { - ParentHash string `json:"parent_hash"` - FeeRecipient string `json:"fee_recipient"` - StateRoot string `json:"state_root"` - ReceiptsRoot string `json:"receipts_root"` - LogsBloom string `json:"logs_bloom"` - PrevRandao string `json:"prev_randao"` - BlockNumber string `json:"block_number"` - GasLimit string `json:"gas_limit"` - GasUsed string `json:"gas_used"` - Timestamp string `json:"timestamp"` - ExtraData string `json:"extra_data"` - BaseFeePerGas string `json:"base_fee_per_gas"` - BlockHash string `json:"block_hash"` - Transactions []string `json:"transactions"` - Withdrawals []WithdrawalResponse `json:"withdrawals"` - BlobGasUsed string `json:"blob_gas_used,omitempty"` - ExcessBlobGas string `json:"excess_blob_gas,omitempty"` - } `json:"execution_payload"` - BlsToExecutionChanges []SignedBLSToExecutionChangeResponse `json:"bls_to_execution_changes"` - BlobKzgCommitments []string `json:"blob_kzg_commitments"` - } `json:"body"` - } `json:"message"` - } `json:"data"` + Data BeaconBlockResponseData `json:"data"` } type BootstrapResponse struct { diff --git a/relayer/relays/beacon/header/syncer/sync_protocol.go b/relayer/relays/beacon/header/syncer/sync_protocol.go deleted file mode 100644 index 085c5718ea..0000000000 --- a/relayer/relays/beacon/header/syncer/sync_protocol.go +++ /dev/null @@ -1,32 +0,0 @@ -package syncer - -func (s *Syncer) ComputeSyncPeriodAtSlot(slot uint64) uint64 { - return slot / (s.setting.SlotsInEpoch * s.setting.EpochsPerSyncCommitteePeriod) -} - -func (s *Syncer) ComputeEpochAtSlot(slot uint64) uint64 { - return slot / s.setting.SlotsInEpoch -} - -func (s *Syncer) IsStartOfEpoch(slot uint64) bool { - return slot%s.setting.SlotsInEpoch == 0 -} - -func (s *Syncer) CalculateNextCheckpointSlot(slot uint64) uint64 { - syncPeriod := s.ComputeSyncPeriodAtSlot(slot) - - // on new period boundary - if syncPeriod*s.setting.SlotsInEpoch*s.setting.EpochsPerSyncCommitteePeriod == slot { - return slot - } - - return (syncPeriod + 1) * s.setting.SlotsInEpoch * s.setting.EpochsPerSyncCommitteePeriod -} - -func (s *Syncer) DenebForked(slot uint64) bool { - return s.ComputeEpochAtSlot(slot) >= s.setting.DenebForkEpoch -} - -func (s *Syncer) SyncPeriodLength() uint64 { - return s.setting.SlotsInEpoch * s.setting.EpochsPerSyncCommitteePeriod -} diff --git a/relayer/relays/beacon/header/syncer/syncer.go b/relayer/relays/beacon/header/syncer/syncer.go index 223d85c78c..bea7ee8f96 100644 --- a/relayer/relays/beacon/header/syncer/syncer.go +++ b/relayer/relays/beacon/header/syncer/syncer.go @@ -3,15 +3,15 @@ package syncer import ( "errors" "fmt" - "github.com/snowfork/snowbridge/relayer/relays/beacon/store" "strconv" "github.com/snowfork/go-substrate-rpc-client/v4/types" "github.com/snowfork/snowbridge/relayer/relays/beacon/cache" - "github.com/snowfork/snowbridge/relayer/relays/beacon/config" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/scale" + "github.com/snowfork/snowbridge/relayer/relays/beacon/protocol" "github.com/snowfork/snowbridge/relayer/relays/beacon/state" + "github.com/snowfork/snowbridge/relayer/relays/beacon/store" "github.com/snowfork/snowbridge/relayer/relays/util" "github.com/ethereum/go-ethereum/common" @@ -23,6 +23,7 @@ import ( const ( BlockRootGeneralizedIndex = 37 FinalizedCheckpointGeneralizedIndex = 105 + NextSyncCommitteeGeneralizedIndex = 55 ExecutionPayloadGeneralizedIndex = 25 ) @@ -32,16 +33,16 @@ var ( ) type Syncer struct { - Client api.BeaconAPI - setting config.SpecSettings - store store.BeaconStore + Client api.BeaconAPI + store store.BeaconStore + protocol *protocol.Protocol } -func New(client api.BeaconAPI, setting config.SpecSettings, store store.BeaconStore) *Syncer { +func New(client api.BeaconAPI, store store.BeaconStore, protocol *protocol.Protocol) *Syncer { return &Syncer{ - Client: client, - setting: setting, - store: store, + Client: client, + store: store, + protocol: protocol, } } @@ -98,7 +99,27 @@ func (s *Syncer) GetCheckpoint() (scale.BeaconCheckpoint, error) { }, nil } -func (s *Syncer) GetSyncCommitteePeriodUpdate(from uint64) (scale.Update, error) { +// GetSyncCommitteePeriodUpdate fetches a sync committee update from the light client API endpoint. If it fails +// (typically because it cannot download the finalized header beacon state because the slot does not fall on a 32 +// slot interval, due to a missed block), it will construct an update manually from data download from the beacon +// API, or if that is unavailable, use a stored beacon state. +func (s *Syncer) GetSyncCommitteePeriodUpdate(period uint64, lastFinalizedSlot uint64) (scale.Update, error) { + update, err := s.GetSyncCommitteePeriodUpdateFromEndpoint(period) + if err != nil { + log.WithFields(log.Fields{"period": period, "err": err}).Warn("fetch sync committee update period light client failed, trying building update manually") + update, err = s.GetFinalizedUpdateWithSyncCommittee(period, lastFinalizedSlot) + if err != nil { + return update, fmt.Errorf("build sync committee update: %w", err) + } + } + + return update, nil +} + +// GetSyncCommitteePeriodUpdateFromEndpoint fetches a sync committee update from the light client API endpoint. If +// it cannot download the required beacon state from the API, it will look in the data store if the state is stored. +// If not, it returns an error. +func (s *Syncer) GetSyncCommitteePeriodUpdateFromEndpoint(from uint64) (scale.Update, error) { committeeUpdateContainer, err := s.Client.GetSyncCommitteePeriodUpdate(from) if err != nil { return scale.Update{}, fmt.Errorf("fetch sync committee period update: %w", err) @@ -133,7 +154,16 @@ func (s *Syncer) GetSyncCommitteePeriodUpdate(from uint64) (scale.Update, error) blockRootsProof, err := s.GetBlockRoots(uint64(finalizedHeader.Slot)) if err != nil { - return scale.Update{}, fmt.Errorf("fetch block roots: %w", err) + beaconStateData, err := s.store.GetBeaconStateData(uint64(finalizedHeader.Slot)) + if err != nil { + return scale.Update{}, fmt.Errorf("fetch beacon state for block roots proof: %w", err) + } + beaconState, err := s.unmarshalBeaconState(uint64(finalizedHeader.Slot), beaconStateData) + + blockRootsProof, err = s.GetBlockRootsFromState(beaconState) + if err != nil { + return scale.Update{}, fmt.Errorf("fetch block roots: %w", err) + } } finalizedHeaderBlockRoot, err := finalizedHeader.ToSSZ().HashTreeRoot() @@ -162,7 +192,7 @@ func (s *Syncer) GetSyncCommitteePeriodUpdate(from uint64) (scale.Update, error) BlockRootsTree: blockRootsProof.Tree, } - finalizedPeriod := s.ComputeSyncPeriodAtSlot(uint64(finalizedHeader.Slot)) + finalizedPeriod := s.protocol.ComputeSyncPeriodAtSlot(uint64(finalizedHeader.Slot)) if finalizedPeriod != from { return syncCommitteePeriodUpdate, ErrCommitteeUpdateHeaderInDifferentSyncPeriod @@ -180,7 +210,7 @@ func (s *Syncer) GetBlockRoots(slot uint64) (scale.BlockRootProof, error) { if err != nil { return blockRootProof, fmt.Errorf("download beacon state (at slot %d) failed: %w", slot, err) } - isDeneb := s.DenebForked(slot) + isDeneb := s.protocol.DenebForked(slot) blockRootsContainer = &state.BlockRootsContainerMainnet{} if isDeneb { @@ -333,7 +363,7 @@ func (s *Syncer) FindBeaconHeaderWithBlockIncluded(slot uint64) (state.BeaconBlo err := api.ErrNotFound var header api.BeaconHeader tries := 0 - maxSlotsMissed := int(s.setting.SlotsInEpoch) + maxSlotsMissed := int(s.protocol.Settings.SlotsInEpoch) startSlot := slot for errors.Is(err, api.ErrNotFound) && tries < maxSlotsMissed { // Need to use GetHeaderBySlot instead of GetBeaconBlockRoot here because GetBeaconBlockRoot @@ -393,7 +423,7 @@ func (s *Syncer) GetHeaderUpdate(blockRoot common.Hash, checkpoint *cache.Proof) return update, err } - sszBlock, err := blockResponse.ToFastSSZ(s.DenebForked(slot)) + sszBlock, err := blockResponse.ToFastSSZ(s.protocol.DenebForked(slot)) if err != nil { return update, err } @@ -414,7 +444,7 @@ func (s *Syncer) GetHeaderUpdate(blockRoot common.Hash, checkpoint *cache.Proof) } var versionedExecutionPayloadHeader scale.VersionedExecutionPayloadHeader - if s.DenebForked(slot) { + if s.protocol.DenebForked(slot) { executionPayloadScale, err := api.DenebExecutionPayloadToScale(sszBlock.ExecutionPayloadDeneb()) if err != nil { return scale.HeaderUpdatePayload{}, err @@ -477,7 +507,7 @@ func (s *Syncer) getBeaconStateAtSlot(slot uint64) (state.BeaconState, error) { func (s *Syncer) unmarshalBeaconState(slot uint64, data []byte) (state.BeaconState, error) { var beaconState state.BeaconState - isDeneb := s.DenebForked(slot) + isDeneb := s.protocol.DenebForked(slot) if isDeneb { beaconState = &state.BeaconStateDenebMainnet{} @@ -493,55 +523,120 @@ func (s *Syncer) unmarshalBeaconState(slot uint64, data []byte) (state.BeaconSta return beaconState, nil } -// Sanity check the finalized and attested header are at 32 boundary blocks so we can download the beacon state -func (s *Syncer) findAttestedAndFinalizedHeadersAtBoundary(initialSlot, lowestSlot uint64) (uint64, error) { - var headers []uint64 +// Sanity check the finalized and attested header are at 32 boundary blocks, so we can download the beacon state +func (s *Syncer) FindLatestAttestedHeadersAtInterval(initialSlot, lowestSlot uint64) (uint64, error) { slot := initialSlot for { - if len(headers) == 2 { - break - } - - header, err := s.Client.GetHeaderBySlot(slot) + finalizedSlot, attestedSlot, err := s.findValidUpdatePair(slot) if err != nil { - slot -= s.setting.SlotsInEpoch if lowestSlot > slot { return 0, fmt.Errorf("unable to find valid slot") } + slot -= s.protocol.Settings.SlotsInEpoch + continue } - finalizedSlot := header.Slot - (s.setting.SlotsInEpoch * 2) - finalizedHeader, err := s.Client.GetHeaderBySlot(finalizedSlot) + log.WithFields(log.Fields{"attested": attestedSlot, "finalized": finalizedSlot}).Info("found boundary headers") + return attestedSlot, nil + } +} + +// FindOldestAttestedHeaderAtInterval finds a set of headers (finalized and attested headers) that are at 32 boundary +// blocks (with a sync committee super majority signature), so we can download the beacon state. +func (s *Syncer) FindOldestAttestedHeaderAtInterval(initialSlot, highestSlot uint64) (uint64, error) { + // special case where the finalized beacon state is not set at genesis + if initialSlot == 0 { + initialSlot = 2 * s.protocol.Settings.SlotsInEpoch + } + slot := initialSlot + + head, err := s.Client.GetHeaderAtHead() + if err != nil { + return 0, fmt.Errorf("get chain head: %w", err) + } + + for { + finalizedSlot, attestedSlot, err := s.findValidUpdatePair(slot) if err != nil { - slot -= s.setting.SlotsInEpoch + if highestSlot < slot || head.Slot < slot { + return 0, fmt.Errorf("unable to find valid slot") + } + + slot += s.protocol.Settings.SlotsInEpoch continue } - headers = append(headers, header.Slot) - headers = append(headers, finalizedHeader.Slot) + log.WithFields(log.Fields{"attested": attestedSlot, "finalized": finalizedSlot}).Info("found boundary headers") + return attestedSlot, nil + } +} + +func (s *Syncer) findValidUpdatePair(slot uint64) (uint64, uint64, error) { + finalizedHeader, err := s.Client.GetHeaderBySlot(slot) + if err != nil { + return 0, 0, fmt.Errorf("get finalized slot: %d err: %w", slot, err) + } + + attestedSlot := finalizedHeader.Slot + (s.protocol.Settings.SlotsInEpoch * 2) + attestedHeader, err := s.Client.GetHeaderBySlot(attestedSlot) + if err != nil { + return 0, 0, fmt.Errorf("get attested slot: %d err: %w", attestedSlot, err) + } + + nextHeader, err := s.FindBeaconHeaderWithBlockIncluded(attestedSlot + 1) + if err != nil { + return 0, 0, fmt.Errorf("get next header: %d err: %w", attestedSlot+1, err) + } + nextBlock, err := s.Client.GetBeaconBlockBySlot(nextHeader.Slot) + if err != nil { + return 0, 0, fmt.Errorf("get next block: %d err: %w", nextHeader.Slot, err) + } + + superMajority, err := s.protocol.SyncCommitteeSuperMajority(nextBlock.Data.Message.Body.SyncAggregate.SyncCommitteeBits) + if err != nil { + return 0, 0, fmt.Errorf("compute sync committee supermajority: %d err: %w", nextHeader.Slot, err) + } + if !superMajority { + return 0, 0, fmt.Errorf("sync committee at slot not supermajority: %d", nextHeader.Slot) + } + + return finalizedHeader.Slot, attestedHeader.Slot, nil +} + +func (s *Syncer) GetLatestPossibleFinalizedUpdate(attestedSlot uint64, boundary uint64) (scale.Update, error) { + attestedSlot, err := s.FindLatestAttestedHeadersAtInterval(attestedSlot, boundary) + if err != nil { + return scale.Update{}, fmt.Errorf("cannot find blocks at boundaries: %w", err) } - log.WithField("headers_found", headers).Info("found boundary headers") - return headers[0], nil + return s.GetFinalizedUpdateAtAttestedSlot(attestedSlot, boundary, false) } -func (s *Syncer) GetFinalizedUpdateAtAttestedSlot(attestedSlot uint64, lastSyncedFinalizedSlot uint64) (scale.Update, error) { - var update scale.Update +func (s *Syncer) GetFinalizedUpdateWithSyncCommittee(syncCommitteePeriod, lastFinalizedSlot uint64) (scale.Update, error) { + slot := (syncCommitteePeriod) * s.protocol.Settings.SlotsInEpoch * s.protocol.Settings.EpochsPerSyncCommitteePeriod + + boundary := (syncCommitteePeriod + 1) * s.protocol.Settings.SlotsInEpoch * s.protocol.Settings.EpochsPerSyncCommitteePeriod - attestedSlot, err := s.findAttestedAndFinalizedHeadersAtBoundary(attestedSlot, lastSyncedFinalizedSlot) + attestedSlot, err := s.FindOldestAttestedHeaderAtInterval(slot, boundary) if err != nil { - return update, fmt.Errorf("cannot find blocks at boundaries: %w", err) + return scale.Update{}, fmt.Errorf("cannot find blocks at boundaries: %w", err) } + return s.GetFinalizedUpdateAtAttestedSlot(attestedSlot, boundary, true) +} + +func (s *Syncer) GetFinalizedUpdateAtAttestedSlot(attestedSlot uint64, boundary uint64, fetchNextSyncCommittee bool) (scale.Update, error) { + var update scale.Update + // Try getting beacon data from the API first data, err := s.getBeaconDataFromClient(attestedSlot) if err != nil { // If it fails, using the beacon store and look for a relevant finalized update - data, err = s.getBeaconDataFromStore(lastSyncedFinalizedSlot) + data, err = s.getBeaconDataFromStore(attestedSlot, boundary, fetchNextSyncCommittee) if err != nil { return update, fmt.Errorf("fetch beacon data from api and data store failure: %w", err) } @@ -561,6 +656,32 @@ func (s *Syncer) GetFinalizedUpdateAtAttestedSlot(attestedSlot uint64, lastSynce return update, fmt.Errorf("get finalized header proof: %w", err) } + var nextSyncCommitteeScale scale.OptionNextSyncCommitteeUpdatePayload + if fetchNextSyncCommittee { + nextSyncCommitteeProof, err := stateTree.Prove(NextSyncCommitteeGeneralizedIndex) + if err != nil { + return update, fmt.Errorf("get finalized header proof: %w", err) + } + + nextSyncCommittee := data.AttestedState.GetSyncSyncCommittee() + + syncCommitteePubKeys, err := util.ByteArrayToPublicKeyArray(nextSyncCommittee.PubKeys) + nextSyncCommitteeScale = scale.OptionNextSyncCommitteeUpdatePayload{ + HasValue: true, + Value: scale.NextSyncCommitteeUpdatePayload{ + NextSyncCommittee: scale.SyncCommittee{ + Pubkeys: syncCommitteePubKeys, + AggregatePubkey: nextSyncCommittee.AggregatePubKey, + }, + NextSyncCommitteeBranch: util.BytesBranchToScale(nextSyncCommitteeProof.Hashes), + }, + } + } else { + nextSyncCommitteeScale = scale.OptionNextSyncCommitteeUpdatePayload{ + HasValue: false, + } + } + blockRootsProof, err := s.GetBlockRootsFromState(data.FinalizedState) if err != nil { return scale.Update{}, fmt.Errorf("fetch block roots: %w", err) @@ -606,16 +727,14 @@ func (s *Syncer) GetFinalizedUpdateAtAttestedSlot(attestedSlot uint64, lastSynce } payload := scale.UpdatePayload{ - AttestedHeader: scaleHeader, - SyncAggregate: scaleSyncAggregate, - SignatureSlot: types.U64(nextBlockSlot), - NextSyncCommitteeUpdate: scale.OptionNextSyncCommitteeUpdatePayload{ - HasValue: false, - }, - FinalizedHeader: scaleFinalizedHeader, - FinalityBranch: util.BytesBranchToScale(finalizedHeaderProof.Hashes), - BlockRootsRoot: blockRootsProof.Leaf, - BlockRootsBranch: blockRootsProof.Proof, + AttestedHeader: scaleHeader, + SyncAggregate: scaleSyncAggregate, + SignatureSlot: types.U64(nextBlockSlot), + NextSyncCommitteeUpdate: nextSyncCommitteeScale, + FinalizedHeader: scaleFinalizedHeader, + FinalityBranch: util.BytesBranchToScale(finalizedHeaderProof.Hashes), + BlockRootsRoot: blockRootsProof.Leaf, + BlockRootsBranch: blockRootsProof.Proof, } return scale.Update{ @@ -626,7 +745,7 @@ func (s *Syncer) GetFinalizedUpdateAtAttestedSlot(attestedSlot uint64, lastSynce } func (s *Syncer) getBlockHeaderAncestryProof(slot int, blockRoot common.Hash, blockRootTree *ssz.Node) ([]types.H256, error) { - maxSlotsPerHistoricalRoot := int(s.setting.SlotsInEpoch * s.setting.EpochsPerSyncCommitteePeriod) + maxSlotsPerHistoricalRoot := int(s.protocol.Settings.SlotsInEpoch * s.protocol.Settings.EpochsPerSyncCommitteePeriod) indexInArray := slot % maxSlotsPerHistoricalRoot leafIndex := maxSlotsPerHistoricalRoot + indexInArray @@ -674,7 +793,7 @@ func (s *Syncer) getBeaconDataFromClient(attestedSlot uint64) (finalizedUpdateCo response.FinalizedCheckPoint = *response.AttestedState.GetFinalizedCheckpoint() // Get the finalized header at the given slot state - response.FinalizedHeader, err = s.Client.GetHeader(common.BytesToHash(response.FinalizedCheckPoint.Root)) + response.FinalizedHeader, err = s.Client.GetHeaderByBlockRoot(common.BytesToHash(response.FinalizedCheckPoint.Root)) if err != nil { return response, fmt.Errorf("fetch header: %w", err) } @@ -689,12 +808,23 @@ func (s *Syncer) getBeaconDataFromClient(attestedSlot uint64) (finalizedUpdateCo // Get the best, latest finalized and attested beacon states including the slot provided in the finalized state block // roots, from the Beacon store. -func (s *Syncer) getBeaconDataFromStore(originalSlot uint64) (finalizedUpdateContainer, error) { +func (s *Syncer) getBeaconDataFromStore(slot, boundary uint64, findMin bool) (finalizedUpdateContainer, error) { + response, err := s.getExactMatchFromStore(slot) + if err != nil { + response, err = s.getBestMatchBeaconDataFromStore(slot, boundary, findMin) + if err != nil { + return finalizedUpdateContainer{}, fmt.Errorf("unable to find exact slot or best other slot beacon data") + } + } + + return response, nil +} + +func (s *Syncer) getBestMatchBeaconDataFromStore(slot, boundary uint64, findMin bool) (finalizedUpdateContainer, error) { var response finalizedUpdateContainer var err error - checkpointSlot := s.CalculateNextCheckpointSlot(originalSlot) - data, err := s.store.FindBeaconStateWithinSyncPeriodRange(originalSlot, checkpointSlot) + data, err := s.store.FindBeaconStateWithinSyncPeriod(slot, boundary, findMin) if err != nil { return finalizedUpdateContainer{}, err } @@ -711,10 +841,43 @@ func (s *Syncer) getBeaconDataFromStore(originalSlot uint64) (finalizedUpdateCon response.FinalizedCheckPoint = *response.AttestedState.GetFinalizedCheckpoint() - response.FinalizedHeader, err = s.Client.GetHeader(common.BytesToHash(response.FinalizedCheckPoint.Root)) + response.FinalizedHeader, err = s.Client.GetHeaderByBlockRoot(common.BytesToHash(response.FinalizedCheckPoint.Root)) + if err != nil { + return response, fmt.Errorf("fetch header: %w", err) + } + + return response, nil +} + +func (s *Syncer) getExactMatchFromStore(slot uint64) (finalizedUpdateContainer, error) { + var response finalizedUpdateContainer + attestedStateData, err := s.store.GetBeaconStateData(slot) + if err != nil { + return finalizedUpdateContainer{}, err + } + + response.AttestedSlot = slot + response.AttestedState, err = s.unmarshalBeaconState(slot, attestedStateData) + if err != nil { + return finalizedUpdateContainer{}, err + } + + response.FinalizedCheckPoint = *response.AttestedState.GetFinalizedCheckpoint() + + response.FinalizedHeader, err = s.Client.GetHeaderByBlockRoot(common.BytesToHash(response.FinalizedCheckPoint.Root)) if err != nil { return response, fmt.Errorf("fetch header: %w", err) } + finalizedStateData, err := s.store.GetBeaconStateData(response.FinalizedHeader.Slot) + if err != nil { + return finalizedUpdateContainer{}, err + } + + response.FinalizedState, err = s.unmarshalBeaconState(response.FinalizedHeader.Slot, finalizedStateData) + if err != nil { + return finalizedUpdateContainer{}, err + } + return response, nil } diff --git a/relayer/relays/beacon/header/syncer/syncer_test.go b/relayer/relays/beacon/header/syncer/syncer_test.go index da4c5e805f..46851e9b7e 100644 --- a/relayer/relays/beacon/header/syncer/syncer_test.go +++ b/relayer/relays/beacon/header/syncer/syncer_test.go @@ -3,13 +3,17 @@ package syncer import ( "encoding/json" "fmt" - "github.com/snowfork/snowbridge/relayer/relays/testutil" "strconv" "testing" "github.com/snowfork/snowbridge/relayer/relays/beacon/config" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" + "github.com/snowfork/snowbridge/relayer/relays/beacon/mock" + "github.com/snowfork/snowbridge/relayer/relays/beacon/protocol" + "github.com/snowfork/snowbridge/relayer/relays/beacon/store" + "github.com/snowfork/snowbridge/relayer/relays/testutil" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -17,11 +21,11 @@ import ( const TestUrl = "https://lodestar-sepolia.chainsafe.io" func newTestRunner() *Syncer { - return New(api.NewBeaconClient(TestUrl, 32), config.SpecSettings{ + return New(api.NewBeaconClient(TestUrl), &mock.Store{}, protocol.New(config.SpecSettings{ SlotsInEpoch: 32, EpochsPerSyncCommitteePeriod: 256, DenebForkEpoch: 0, - }, &testutil.MockStore{}) + })) } // Verifies that the Lodestar provided finalized endpoint matches the manually constructed finalized endpoint @@ -35,8 +39,11 @@ func TestGetFinalizedUpdateAtSlot(t *testing.T) { require.NoError(t, err) lodestarUpdateJSON := lodestarUpdate.Payload.ToJSON() + attestedSlot, err := syncer.FindLatestAttestedHeadersAtInterval(uint64(lodestarUpdate.Payload.AttestedHeader.Slot), 9331) + require.NoError(t, err) + // Manually construct the finalized update for the same block - manualUpdate, err := syncer.GetFinalizedUpdateAtAttestedSlot(uint64(lodestarUpdate.Payload.AttestedHeader.Slot), 9331) + manualUpdate, err := syncer.GetLatestPossibleFinalizedUpdate(attestedSlot, 9331) require.NoError(t, err) manualUpdateJSON := manualUpdate.Payload.ToJSON() @@ -49,6 +56,75 @@ func TestGetFinalizedUpdateAtSlot(t *testing.T) { require.JSONEq(t, string(lodestarPayload), string(manualPayload)) } +// Verifies that the Lodestar provided finalized endpoint matches the manually constructed finalized endpoint +func TestGetFinalizedUpdateWithSyncCommitteeUpdateAtSlot(t *testing.T) { + t.Skip("skip testing utility test") + + beaconData64, err := testutil.LoadFile("64.ssz") + require.NoError(t, err) + beaconData129, err := testutil.LoadFile("129.ssz") + require.NoError(t, err) + + headerAtSlot64, err := testutil.GetHeaderAtSlot(64) + require.NoError(t, err) + headerAtSlot129, err := testutil.GetHeaderAtSlot(129) + require.NoError(t, err) + headerAtSlot130, err := testutil.GetHeaderAtSlot(130) + require.NoError(t, err) + + blockAtSlot, err := testutil.GetBlockAtSlot(130) + require.NoError(t, err) + + syncCommitteeUpdate, err := testutil.GetSyncCommitteeUpdate(0) + require.NoError(t, err) + + mockAPI := mock.API{ + LatestFinalisedUpdateResponse: api.LatestFinalisedUpdateResponse{}, + SyncCommitteePeriodUpdateResponse: syncCommitteeUpdate, + HeadersBySlot: map[uint64]api.BeaconHeader{ + 64: headerAtSlot64, + 129: headerAtSlot129, + 130: headerAtSlot130, + }, + BlocksAtSlot: map[uint64]api.BeaconBlockResponse{ + 130: blockAtSlot, + }, + Header: map[common.Hash]api.BeaconHeader{ + common.HexToHash("0x3d0145a0f4565ac6fde12d4a4e7f5df35bec009ee9cb30abaac2eaab8de0d6c5"): headerAtSlot64, + }, + BeaconStates: nil, + } + + syncer := New(&mockAPI, &mock.Store{ + BeaconStateData: map[uint64][]byte{ + 64: beaconData64, + 129: beaconData129, + }, + StoredBeaconStateData: store.StoredBeaconData{ + AttestedSlot: 129, + FinalizedSlot: 64, + AttestedBeaconState: beaconData129, + FinalizedBeaconState: beaconData64, + }, + }, protocol.New(config.SpecSettings{ + SlotsInEpoch: 32, + EpochsPerSyncCommitteePeriod: 256, + DenebForkEpoch: 0, + })) + + // Manually construct a finalized update + manualUpdate, err := syncer.GetFinalizedUpdateAtAttestedSlot(129, 0, true) + require.NoError(t, err) + manualUpdateJSON := manualUpdate.Payload.ToJSON() + + lodestarPayload, err := testutil.LoadFile("sync_committee_comp.json") + require.NoError(t, err) + manualPayload, err := json.Marshal(manualUpdateJSON) + require.NoError(t, err) + + require.Equal(t, lodestarPayload, manualPayload) +} + func TestGetInitialCheckpoint(t *testing.T) { t.Skip("skip testing utility test") @@ -64,24 +140,34 @@ func TestGetInitialCheckpoint(t *testing.T) { } func TestFindAttestedAndFinalizedHeadersAtBoundary(t *testing.T) { - mockAPI := testutil.MockAPI{} + mockAPI := mock.API{} mockAPI.HeadersBySlot = map[uint64]api.BeaconHeader{ 8160: {Slot: 8160}, // skip 8128 // skip 8096 8064: {Slot: 8064}, // this should be the first valid attested header + 8065: {Slot: 8065}, // next header so that we can get the sync aggregate // skip 8032 8000: {Slot: 8000}, } - syncer := New(&mockAPI, config.SpecSettings{ + mockAPI.BlocksAtSlot = map[uint64]api.BeaconBlockResponse{ + 8065: { + Data: api.BeaconBlockResponseData{Message: api.BeaconBlockResponseMessage{Body: api.BeaconBlockResponseBody{SyncAggregate: api.SyncAggregateResponse{ + SyncCommitteeBits: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000", + SyncCommitteeSignature: "0x946646f0dacd480ecb8878709e7632037fd1adc7c99f15cf725ecd9f3710aa848de8f9fa9595479547065e76bb018d75077fc1912908c9d50e254e99db192b1a76ed1b2cfffafb92742334230787cb94447897148cee37053d4e682c85149b27", + }}}}, + }, + } + + syncer := New(&mockAPI, &mock.Store{}, protocol.New(config.SpecSettings{ SlotsInEpoch: 32, EpochsPerSyncCommitteePeriod: 256, DenebForkEpoch: 0, - }, &testutil.MockStore{}) + })) - attested, err := syncer.findAttestedAndFinalizedHeadersAtBoundary(8192, 100) + attested, err := syncer.FindLatestAttestedHeadersAtInterval(8192, 100) assert.NoError(t, err) assert.Equal(t, "8064", strconv.FormatUint(attested, 10)) @@ -89,19 +175,29 @@ func TestFindAttestedAndFinalizedHeadersAtBoundary(t *testing.T) { // skip 32768 32736: {Slot: 32736}, 32704: {Slot: 32704}, + 32705: {Slot: 32705}, // next header so that we can get the sync aggregate // skip 32672 32640: {Slot: 32640}, 32608: {Slot: 32608}, 32576: {Slot: 32576}, } - syncer = New(&mockAPI, config.SpecSettings{ + mockAPI.BlocksAtSlot = map[uint64]api.BeaconBlockResponse{ + 32705: { + Data: api.BeaconBlockResponseData{Message: api.BeaconBlockResponseMessage{Body: api.BeaconBlockResponseBody{SyncAggregate: api.SyncAggregateResponse{ + SyncCommitteeBits: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000", + SyncCommitteeSignature: "0x946646f0dacd480ecb8878709e7632037fd1adc7c99f15cf725ecd9f3710aa848de8f9fa9595479547065e76bb018d75077fc1912908c9d50e254e99db192b1a76ed1b2cfffafb92742334230787cb94447897148cee37053d4e682c85149b27", + }}}}, + }, + } + + syncer = New(&mockAPI, &mock.Store{}, protocol.New(config.SpecSettings{ SlotsInEpoch: 32, EpochsPerSyncCommitteePeriod: 256, DenebForkEpoch: 0, - }, &testutil.MockStore{}) + })) - attested, err = syncer.findAttestedAndFinalizedHeadersAtBoundary(32768, 25076) + attested, err = syncer.FindLatestAttestedHeadersAtInterval(32768, 25076) assert.NoError(t, err) assert.Equal(t, "32704", strconv.FormatUint(attested, 10)) @@ -109,19 +205,29 @@ func TestFindAttestedAndFinalizedHeadersAtBoundary(t *testing.T) { // skip 32768 32736: {Slot: 32736}, 32704: {Slot: 32704}, + 32705: {Slot: 32705}, // next header so that we can get the sync aggregate // skip 32672 32640: {Slot: 32640}, // skip 32608 32576: {Slot: 32576}, } - syncer = New(&mockAPI, config.SpecSettings{ + mockAPI.BlocksAtSlot = map[uint64]api.BeaconBlockResponse{ + 32705: { + Data: api.BeaconBlockResponseData{Message: api.BeaconBlockResponseMessage{Body: api.BeaconBlockResponseBody{SyncAggregate: api.SyncAggregateResponse{ + SyncCommitteeBits: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000", + SyncCommitteeSignature: "0x946646f0dacd480ecb8878709e7632037fd1adc7c99f15cf725ecd9f3710aa848de8f9fa9595479547065e76bb018d75077fc1912908c9d50e254e99db192b1a76ed1b2cfffafb92742334230787cb94447897148cee37053d4e682c85149b27", + }}}}, + }, + } + + syncer = New(&mockAPI, &mock.Store{}, protocol.New(config.SpecSettings{ SlotsInEpoch: 32, EpochsPerSyncCommitteePeriod: 256, DenebForkEpoch: 0, - }, &testutil.MockStore{}) + })) - attested, err = syncer.findAttestedAndFinalizedHeadersAtBoundary(32768, 25076) + attested, err = syncer.FindLatestAttestedHeadersAtInterval(32768, 25076) assert.NoError(t, err) assert.Equal(t, "32704", strconv.FormatUint(attested, 10)) @@ -139,12 +245,12 @@ func TestFindAttestedAndFinalizedHeadersAtBoundary(t *testing.T) { 32448: {Slot: 32448}, } - syncer = New(&mockAPI, config.SpecSettings{ + syncer = New(&mockAPI, &mock.Store{}, protocol.New(config.SpecSettings{ SlotsInEpoch: 32, EpochsPerSyncCommitteePeriod: 256, DenebForkEpoch: 0, - }, &testutil.MockStore{}) + })) - attested, err = syncer.findAttestedAndFinalizedHeadersAtBoundary(32768, 32540) + attested, err = syncer.FindLatestAttestedHeadersAtInterval(32768, 32540) assert.Error(t, err) } diff --git a/relayer/relays/beacon/main.go b/relayer/relays/beacon/main.go index f2ba493bbf..9ca801fed1 100644 --- a/relayer/relays/beacon/main.go +++ b/relayer/relays/beacon/main.go @@ -8,6 +8,7 @@ import ( "github.com/snowfork/snowbridge/relayer/relays/beacon/config" "github.com/snowfork/snowbridge/relayer/relays/beacon/header" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" + "github.com/snowfork/snowbridge/relayer/relays/beacon/protocol" "github.com/snowfork/snowbridge/relayer/relays/beacon/store" log "github.com/sirupsen/logrus" @@ -46,21 +47,27 @@ func (r *Relay) Start(ctx context.Context, eg *errgroup.Group) error { r.config.Sink.Parachain.MaxBatchCallSize, ) + p := protocol.New(specSettings) + err = writer.Start(ctx, eg) if err != nil { return err } - store := store.New(r.config.Source.Beacon.DataStore.Location, r.config.Source.Beacon.DataStore.MaxEntries) - store.Connect() - defer store.Close() + s := store.New(r.config.Source.Beacon.DataStore.Location, r.config.Source.Beacon.DataStore.MaxEntries, *p) + err = s.Connect() + if err != nil { + return err + } + defer s.Close() - beaconAPI := api.NewBeaconClient(r.config.Source.Beacon.Endpoint, specSettings.SlotsInEpoch) + beaconAPI := api.NewBeaconClient(r.config.Source.Beacon.Endpoint) headers := header.New( writer, beaconAPI, specSettings, - &store, + &s, + p, ) return headers.Sync(ctx, eg) diff --git a/relayer/relays/testutil/mock_api.go b/relayer/relays/beacon/mock/mock_api.go similarity index 54% rename from relayer/relays/testutil/mock_api.go rename to relayer/relays/beacon/mock/mock_api.go index 3044dffb42..5fbc9746dd 100644 --- a/relayer/relays/testutil/mock_api.go +++ b/relayer/relays/beacon/mock/mock_api.go @@ -1,13 +1,14 @@ -package testutil +package mock import ( "fmt" "github.com/ethereum/go-ethereum/common" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" + "github.com/snowfork/snowbridge/relayer/relays/testutil" "github.com/snowfork/snowbridge/relayer/relays/util" ) -type MockAPI struct { +type API struct { LatestFinalisedUpdateResponse api.LatestFinalisedUpdateResponse SyncCommitteePeriodUpdateResponse api.SyncCommitteePeriodUpdateResponse HeadersBySlot map[uint64]api.BeaconHeader @@ -16,19 +17,23 @@ type MockAPI struct { BeaconStates map[uint64]bool } -func (m *MockAPI) GetBootstrap(blockRoot common.Hash) (api.BootstrapResponse, error) { +func (m *API) GetHeaderAtHead() (api.BeaconHeader, error) { + return api.BeaconHeader{}, nil +} + +func (m *API) GetBootstrap(blockRoot common.Hash) (api.BootstrapResponse, error) { return api.BootstrapResponse{}, nil } -func (m *MockAPI) GetGenesis() (api.Genesis, error) { +func (m *API) GetGenesis() (api.Genesis, error) { return api.Genesis{}, nil } -func (m *MockAPI) GetFinalizedCheckpoint() (api.FinalizedCheckpoint, error) { +func (m *API) GetFinalizedCheckpoint() (api.FinalizedCheckpoint, error) { return api.FinalizedCheckpoint{}, nil } -func (m *MockAPI) GetHeaderBySlot(slot uint64) (api.BeaconHeader, error) { +func (m *API) GetHeaderBySlot(slot uint64) (api.BeaconHeader, error) { value, ok := m.HeadersBySlot[slot] if !ok { return api.BeaconHeader{}, api.ErrNotFound @@ -36,11 +41,11 @@ func (m *MockAPI) GetHeaderBySlot(slot uint64) (api.BeaconHeader, error) { return value, nil } -func (m *MockAPI) GetHeader(blockRoot common.Hash) (api.BeaconHeader, error) { +func (m *API) GetHeaderByBlockRoot(blockRoot common.Hash) (api.BeaconHeader, error) { return m.Header[blockRoot], nil } -func (m *MockAPI) GetBeaconBlockBySlot(slot uint64) (api.BeaconBlockResponse, error) { +func (m *API) GetBeaconBlockBySlot(slot uint64) (api.BeaconBlockResponse, error) { value, ok := m.BlocksAtSlot[slot] if !ok { return api.BeaconBlockResponse{}, api.ErrNotFound @@ -48,23 +53,23 @@ func (m *MockAPI) GetBeaconBlockBySlot(slot uint64) (api.BeaconBlockResponse, er return value, nil } -func (m *MockAPI) GetBeaconBlockRoot(slot uint64) (common.Hash, error) { +func (m *API) GetBeaconBlockRoot(slot uint64) (common.Hash, error) { return common.Hash{}, nil } -func (m *MockAPI) GetBeaconBlock(blockID common.Hash) (api.BeaconBlockResponse, error) { +func (m *API) GetBeaconBlock(blockID common.Hash) (api.BeaconBlockResponse, error) { return api.BeaconBlockResponse{}, nil } -func (m *MockAPI) GetSyncCommitteePeriodUpdate(from uint64) (api.SyncCommitteePeriodUpdateResponse, error) { - return api.SyncCommitteePeriodUpdateResponse{}, nil +func (m *API) GetSyncCommitteePeriodUpdate(from uint64) (api.SyncCommitteePeriodUpdateResponse, error) { + return m.SyncCommitteePeriodUpdateResponse, nil } -func (m *MockAPI) GetLatestFinalizedUpdate() (api.LatestFinalisedUpdateResponse, error) { +func (m *API) GetLatestFinalizedUpdate() (api.LatestFinalisedUpdateResponse, error) { return m.LatestFinalisedUpdateResponse, nil } -func (m *MockAPI) GetBeaconState(stateIdOrSlot string) ([]byte, error) { +func (m *API) GetBeaconState(stateIdOrSlot string) ([]byte, error) { slot, err := util.ToUint64(stateIdOrSlot) if err != nil { return nil, fmt.Errorf("invalid beacon state slot: %w", err) @@ -75,7 +80,7 @@ func (m *MockAPI) GetBeaconState(stateIdOrSlot string) ([]byte, error) { return nil, api.ErrNotFound } - data, err := LoadFile(stateIdOrSlot + ".ssz") + data, err := testutil.LoadFile(stateIdOrSlot + ".ssz") if err != nil { return nil, fmt.Errorf("error reading file: %w", err) } diff --git a/relayer/relays/beacon/mock/mock_store.go b/relayer/relays/beacon/mock/mock_store.go new file mode 100644 index 0000000000..a935e524da --- /dev/null +++ b/relayer/relays/beacon/mock/mock_store.go @@ -0,0 +1,35 @@ +package mock + +import ( + "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" + "github.com/snowfork/snowbridge/relayer/relays/beacon/store" +) + +type Store struct { + StoredBeaconStateData store.StoredBeaconData + BeaconStateData map[uint64][]byte +} + +func (m *Store) WriteEntry(attestedSlot, finalizedSlot uint64, attestedStateData, finalizedStateData []byte) error { + return nil +} + +func (m *Store) GetBeaconStateData(slot uint64) ([]byte, error) { + value, ok := m.BeaconStateData[slot] + if !ok { + return nil, api.ErrNotFound + } + return value, nil +} + +func (m *Store) Connect() error { + return nil +} + +func (m *Store) Close() { + +} + +func (m *Store) FindBeaconStateWithinSyncPeriod(slot, boundary uint64, findMax bool) (store.StoredBeaconData, error) { + return m.StoredBeaconStateData, nil +} diff --git a/relayer/relays/beacon/mock/mock_writer.go b/relayer/relays/beacon/mock/mock_writer.go new file mode 100644 index 0000000000..20b7156162 --- /dev/null +++ b/relayer/relays/beacon/mock/mock_writer.go @@ -0,0 +1,70 @@ +package mock + +import ( + "context" + "fmt" + + "github.com/snowfork/go-substrate-rpc-client/v4/types" + "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/scale" + "github.com/snowfork/snowbridge/relayer/relays/beacon/state" + + "github.com/ethereum/go-ethereum/common" +) + +type Writer struct { + LastFinalizedState state.FinalizedHeader +} + +func (m *Writer) GetLastFinalizedStateIndex() (types.U32, error) { + return 0, nil +} + +func (m *Writer) GetFinalizedBeaconRootByIndex(index uint32) (types.H256, error) { + return types.H256{}, nil +} + +func (m *Writer) BatchCall(ctx context.Context, extrinsic string, calls []interface{}) error { + return nil +} + +func (m *Writer) WriteToParachainAndRateLimit(ctx context.Context, extrinsicName string, payload ...interface{}) error { + return nil +} +func (m *Writer) WriteToParachainAndWatch(ctx context.Context, extrinsicName string, payload ...interface{}) error { + update, ok := payload[0].(scale.UpdatePayload) + if ok { + m.LastFinalizedState.BeaconSlot = uint64(update.FinalizedHeader.Slot) + htr, err := update.FinalizedHeader.ToSSZ().HashTreeRoot() + if err != nil { + return fmt.Errorf("hash tree root error") + } + m.LastFinalizedState.BeaconBlockRoot = htr + } else { + return fmt.Errorf("type conversion error") + } + return nil +} + +func (m *Writer) GetLastFinalizedHeaderState() (state.FinalizedHeader, error) { + return m.LastFinalizedState, nil +} + +func (m *Writer) GetFinalizedStateByStorageKey(key string) (scale.BeaconState, error) { + return scale.BeaconState{}, nil +} + +func (m *Writer) GetLastBasicChannelBlockNumber() (uint64, error) { + return 0, nil +} + +func (m *Writer) GetLastBasicChannelNonceByAddress(address common.Address) (uint64, error) { + return 0, nil + +} +func (m *Writer) GetFinalizedHeaderStateByBlockRoot(blockRoot types.H256) (state.FinalizedHeader, error) { + return state.FinalizedHeader{}, nil +} + +func (m *Writer) FindCheckPointBackward(slot uint64) (state.FinalizedHeader, error) { + return state.FinalizedHeader{}, nil +} diff --git a/relayer/relays/beacon/protocol/protocol.go b/relayer/relays/beacon/protocol/protocol.go new file mode 100644 index 0000000000..9e853c736c --- /dev/null +++ b/relayer/relays/beacon/protocol/protocol.go @@ -0,0 +1,72 @@ +package protocol + +import ( + "encoding/hex" + "strings" + + "github.com/snowfork/snowbridge/relayer/relays/beacon/config" +) + +type Protocol struct { + Settings config.SpecSettings +} + +func New(setting config.SpecSettings) *Protocol { + return &Protocol{Settings: setting} +} + +func (p *Protocol) ComputeSyncPeriodAtSlot(slot uint64) uint64 { + return slot / (p.Settings.SlotsInEpoch * p.Settings.EpochsPerSyncCommitteePeriod) +} + +func (p *Protocol) ComputeEpochAtSlot(slot uint64) uint64 { + return slot / p.Settings.SlotsInEpoch +} + +func (p *Protocol) IsStartOfEpoch(slot uint64) bool { + return slot%p.Settings.SlotsInEpoch == 0 +} + +func (p *Protocol) CalculateNextCheckpointSlot(slot uint64) uint64 { + syncPeriod := p.ComputeSyncPeriodAtSlot(slot) + + // on new period boundary + if syncPeriod*p.Settings.SlotsInEpoch*p.Settings.EpochsPerSyncCommitteePeriod == slot { + return slot + } + + return (syncPeriod + 1) * p.Settings.SlotsInEpoch * p.Settings.EpochsPerSyncCommitteePeriod +} + +func (p *Protocol) DenebForked(slot uint64) bool { + return p.ComputeEpochAtSlot(slot) >= p.Settings.DenebForkEpoch +} + +func (p *Protocol) SyncPeriodLength() uint64 { + return p.Settings.SlotsInEpoch * p.Settings.EpochsPerSyncCommitteePeriod +} + +func (p *Protocol) SyncCommitteeSuperMajority(syncCommitteeHex string) (bool, error) { + bytes, err := hex.DecodeString(strings.Replace(syncCommitteeHex, "0x", "", 1)) + if err != nil { + return false, err + } + + var bits []int + + // Convert each byte to bits + for _, b := range bytes { + for i := 7; i >= 0; i-- { + bit := (b >> i) & 1 + bits = append(bits, int(bit)) + } + } + sum := 0 + for _, bit := range bits { + sum += bit + } + if sum*3 < int(p.Settings.SyncCommitteeSize)*2 { + return false, nil + } + return true, nil +} diff --git a/relayer/relays/beacon/header/syncer/sync_protocol_test.go b/relayer/relays/beacon/protocol/protocol_test.go similarity index 80% rename from relayer/relays/beacon/header/syncer/sync_protocol_test.go rename to relayer/relays/beacon/protocol/protocol_test.go index f70b36497c..2ad82608c3 100644 --- a/relayer/relays/beacon/header/syncer/sync_protocol_test.go +++ b/relayer/relays/beacon/protocol/protocol_test.go @@ -1,4 +1,4 @@ -package syncer +package protocol import ( "testing" @@ -34,11 +34,11 @@ func TestIsStartOfEpoch(t *testing.T) { }, } - syncer := Syncer{} - syncer.setting.SlotsInEpoch = 32 + p := Protocol{} + p.Settings.SlotsInEpoch = 32 for _, tt := range values { - result := syncer.IsStartOfEpoch(tt.slot) + result := p.IsStartOfEpoch(tt.slot) assert.Equal(t, tt.expected, result, "expected %t but found %t for slot %d", tt.expected, result, tt.slot) } } @@ -66,12 +66,12 @@ func TestCalculateNextCheckpointSlot(t *testing.T) { }, } - syncer := Syncer{} - syncer.setting.SlotsInEpoch = 8 - syncer.setting.EpochsPerSyncCommitteePeriod = 8 + p := Protocol{} + p.Settings.SlotsInEpoch = 8 + p.Settings.EpochsPerSyncCommitteePeriod = 8 for _, tt := range values { - result := syncer.CalculateNextCheckpointSlot(tt.slot) + result := p.CalculateNextCheckpointSlot(tt.slot) assert.Equal(t, tt.expected, result, "expected %t but found %t for slot %d", tt.expected, result, tt.slot) } } diff --git a/relayer/relays/beacon/state/beacon.go b/relayer/relays/beacon/state/beacon.go index 1e8ad2449f..ef0ff75393 100644 --- a/relayer/relays/beacon/state/beacon.go +++ b/relayer/relays/beacon/state/beacon.go @@ -164,6 +164,7 @@ type BeaconState interface { GetBlockRoots() [][]byte GetTree() (*ssz.Node, error) GetFinalizedCheckpoint() *Checkpoint + GetSyncSyncCommittee() *SyncCommittee } type SyncAggregate interface { @@ -316,3 +317,7 @@ func (b *BeaconStateCapellaMainnet) SetBlockRoots(blockRoots [][]byte) { func (b *BeaconStateCapellaMainnet) GetFinalizedCheckpoint() *Checkpoint { return b.FinalizedCheckpoint } + +func (b *BeaconStateCapellaMainnet) GetSyncSyncCommittee() *SyncCommittee { + return b.NextSyncCommittee +} diff --git a/relayer/relays/beacon/state/beacon_deneb.go b/relayer/relays/beacon/state/beacon_deneb.go index 4113d38bae..bd889c8b18 100644 --- a/relayer/relays/beacon/state/beacon_deneb.go +++ b/relayer/relays/beacon/state/beacon_deneb.go @@ -133,3 +133,7 @@ func (b *BeaconStateDenebMainnet) SetBlockRoots(blockRoots [][]byte) { func (b *BeaconStateDenebMainnet) GetFinalizedCheckpoint() *Checkpoint { return b.FinalizedCheckpoint } + +func (b *BeaconStateDenebMainnet) GetSyncSyncCommittee() *SyncCommittee { + return b.NextSyncCommittee +} diff --git a/relayer/relays/beacon/state/beacon_deneb_encoding.go b/relayer/relays/beacon/state/beacon_deneb_encoding.go index f6cb833308..2ad6ca4f25 100644 --- a/relayer/relays/beacon/state/beacon_deneb_encoding.go +++ b/relayer/relays/beacon/state/beacon_deneb_encoding.go @@ -1,5 +1,5 @@ // Code generated by fastssz. DO NOT EDIT. -// Hash: 1a8d5eb2ef2434a8f5b5e6a4c9c58acf26171d5098f7d503f860ba54667ba92a +// Hash: 2d1815cffaa3bda65721acc72bdfc0e47fdeb4193ba7500d237e58f2369c3628 // Version: 0.1.3 package state diff --git a/relayer/relays/beacon/state/beacon_encoding.go b/relayer/relays/beacon/state/beacon_encoding.go index 9d943d4e94..ce50a6e58e 100644 --- a/relayer/relays/beacon/state/beacon_encoding.go +++ b/relayer/relays/beacon/state/beacon_encoding.go @@ -1,5 +1,5 @@ // Code generated by fastssz. DO NOT EDIT. -// Hash: 1a8d5eb2ef2434a8f5b5e6a4c9c58acf26171d5098f7d503f860ba54667ba92a +// Hash: 2d1815cffaa3bda65721acc72bdfc0e47fdeb4193ba7500d237e58f2369c3628 // Version: 0.1.3 package state diff --git a/relayer/relays/beacon/store/datastore.go b/relayer/relays/beacon/store/datastore.go index c29e4e7b2b..9cb0cd96a8 100644 --- a/relayer/relays/beacon/store/datastore.go +++ b/relayer/relays/beacon/store/datastore.go @@ -2,20 +2,26 @@ package store import ( "database/sql" + "errors" "fmt" "os" - "strings" "time" + + "github.com/snowfork/snowbridge/relayer/relays/beacon/protocol" + + _ "github.com/mattn/go-sqlite3" ) const BeaconStateDir = "states" const BeaconStateFilename = "beacon_state_%d.ssz" +const BeaconStoreName = "beacon-state" type BeaconStore interface { Connect() error Close() - StoreUpdate(attestedSlot, finalizedSlot, attestedSyncPeriod, finalizedSyncPeriod uint64) error - FindBeaconStateWithinSyncPeriodRange(baseSlot, slotRange uint64) (StoredBeaconData, error) + FindBeaconStateWithinSyncPeriod(slot, boundary uint64, findMin bool) (StoredBeaconData, error) + GetBeaconStateData(slot uint64) ([]byte, error) + WriteEntry(attestedSlot, finalizedSlot uint64, attestedStateData, finalizedStateData []byte) error } type BeaconState struct { @@ -40,19 +46,21 @@ type Store struct { location string maxEntries uint64 db *sql.DB + protocol protocol.Protocol } -func New(location string, maxEntries uint64) Store { +func New(location string, maxEntries uint64, protocol protocol.Protocol) Store { return Store{ location, maxEntries, nil, + protocol, } } func (s *Store) Connect() error { var err error - s.db, err = sql.Open("sqlite3", s.location+"beacon-state") + s.db, err = sql.Open("sqlite3", s.location+BeaconStoreName) if err != nil { return err } @@ -74,41 +82,28 @@ func (s *Store) Close() { _ = s.db.Close() } -func (s *Store) StoreUpdate(attestedSlot, finalizedSlot, attestedSyncPeriod, finalizedSyncPeriod uint64) error { - attestedStateFileName := fmt.Sprintf(BeaconStateFilename, attestedSlot) - finalizedStateFileName := fmt.Sprintf(BeaconStateFilename, finalizedSlot) - - insertStmt := `INSERT INTO beacon_state (attested_slot, finalized_slot, attested_sync_period, finalized_sync_period, attested_state_filename, finalized_state_filename) VALUES (?, ?, ?, ?, ?, ?)` - stmt, err := s.db.Prepare(insertStmt) - if err != nil { - return err - } - defer stmt.Close() - - _, err = stmt.Exec(attestedSlot, finalizedSlot, attestedSyncPeriod, finalizedSyncPeriod, attestedStateFileName, finalizedStateFileName) - if err != nil { - return err - } - - return nil -} - -// Find the latest finalized header within the same sync committee. -func (s *Store) FindBeaconStateWithinSyncPeriodRange(baseSlot, checkPointSlot uint64) (StoredBeaconData, error) { +// FindBeaconStateWithinSyncPeriod finds an attested and a finalized header pair within the same sync period. +// bool findMin specifies whether the largest or smallest slot should be found. if findMin = true, the earliest +// block in the sync committee will be returned, otherwise the largest. This is used for FinalizedUpdates, where +// the latest block ideally wants to be synced, and for SyncCommitteeUpdates, where the earliest slot with the +// next sync committee wants to be located. +func (s *Store) FindBeaconStateWithinSyncPeriod(slot, boundary uint64, findMin bool) (StoredBeaconData, error) { var data StoredBeaconData - query := `SELECT MAX(attested_slot), finalized_slot, attested_state_filename, finalized_state_filename FROM beacon_state WHERE attested_slot >= ? AND attested_slot <= ?` + var query string + if findMin { + query = `SELECT MIN(attested_slot), attested_slot, finalized_slot, attested_state_filename, finalized_state_filename FROM beacon_state WHERE attested_slot >= ? AND attested_slot < ?` + } else { + query = `SELECT MAX(attested_slot), attested_slot, finalized_slot, attested_state_filename, finalized_state_filename FROM beacon_state WHERE attested_slot >= ? AND attested_slot < ?` + } + var min uint64 var attestedSlot uint64 var finalizedSlot uint64 var attestedStateFilename string var finalizedStateFilename string - err := s.db.QueryRow(query, baseSlot, checkPointSlot).Scan(&attestedSlot, &finalizedSlot, &attestedStateFilename, &finalizedStateFilename) + err := s.db.QueryRow(query, slot, boundary).Scan(&min, &attestedSlot, &finalizedSlot, &attestedStateFilename, &finalizedStateFilename) if err != nil { - if err == sql.ErrNoRows { - // No finalized slots found within the range - return data, fmt.Errorf("no match found") - } - return data, err + return data, fmt.Errorf("no match found") } attestedState, err := s.ReadStateFile(attestedStateFilename) @@ -131,13 +126,47 @@ func (s *Store) FindBeaconStateWithinSyncPeriodRange(baseSlot, checkPointSlot ui return data, nil } -func (s *Store) WriteStateFile(slot uint64, data []byte) error { - err := os.WriteFile(s.location+BeaconStateDir+"/"+fmt.Sprintf(BeaconStateFilename, slot), data, 0644) +// GetBeaconStateData finds a beacon state at a slot. +func (s *Store) GetBeaconStateData(slot uint64) ([]byte, error) { + query := `SELECT attested_slot, finalized_slot, attested_state_filename, finalized_state_filename FROM beacon_state WHERE attested_slot = ? OR finalized_slot = ? LIMIT 1` + var attestedSlot uint64 + var finalizedSlot uint64 + var attestedStateFilename string + var finalizedStateFilename string + err := s.db.QueryRow(query, slot, slot).Scan(&attestedSlot, &finalizedSlot, &attestedStateFilename, &finalizedStateFilename) if err != nil { - return fmt.Errorf("write to file: %w", err) + if errors.Is(err, sql.ErrNoRows) { + // No finalized slots found within the range + return nil, fmt.Errorf("no match found") + } + return nil, err } - return nil + if attestedSlot == slot { + return s.ReadStateFile(attestedStateFilename) + } + + if finalizedSlot == slot { + return s.ReadStateFile(finalizedStateFilename) + } + + return nil, fmt.Errorf("no beacon state found") +} + +func (s *Store) WriteEntry(attestedSlot, finalizedSlot uint64, attestedStateData, finalizedStateData []byte) error { + err := s.writeStateFile(attestedSlot, attestedStateData) + if err != nil { + return err + } + err = s.writeStateFile(finalizedSlot, finalizedStateData) + if err != nil { + return err + } + + attestedSyncPeriod := s.protocol.ComputeSyncPeriodAtSlot(attestedSlot) + finalizedSyncPeriod := s.protocol.ComputeSyncPeriodAtSlot(finalizedSlot) + + return s.storeUpdate(attestedSlot, finalizedSlot, attestedSyncPeriod, finalizedSyncPeriod) } func (s *Store) DeleteStateFile(filename string) error { @@ -160,11 +189,11 @@ func (s *Store) ReadStateFile(filename string) ([]byte, error) { func (s *Store) PruneOldStates() ([]uint64, error) { selectSQL := fmt.Sprintf(` - SELECT id, attested_slot, finalized_slot, attested_sync_period, finalized_sync_period, attested_state_filename, finalized_state_filename, timestamp + SELECT id, attested_slot, finalized_slot, attested_sync_period, finalized_sync_period, attested_state_filename, finalized_state_filename FROM beacon_state WHERE id NOT IN ( SELECT id FROM beacon_state - ORDER BY timestamp DESC + ORDER BY attested_slot DESC LIMIT %d )`, s.maxEntries) @@ -177,8 +206,7 @@ func (s *Store) PruneOldStates() ([]uint64, error) { var deleteSlots []uint64 for rows.Next() { var entry BeaconState - var timestampInt64 int64 - if err := rows.Scan(&entry.ID, &entry.AttestedSlot, &entry.FinalizedSlot, &entry.AttestedSyncPeriod, &entry.FinalizedSyncPeriod, &entry.AttestedStateFilename, &entry.FinalizedStateFilename, ×tampInt64); err != nil { + if err := rows.Scan(&entry.ID, &entry.AttestedSlot, &entry.FinalizedSlot, &entry.AttestedSyncPeriod, &entry.FinalizedSyncPeriod, &entry.AttestedStateFilename, &entry.FinalizedStateFilename); err != nil { return nil, fmt.Errorf("failed to scan row: %w", err) } deleteSlots = append(deleteSlots, entry.AttestedSlot) @@ -188,52 +216,24 @@ func (s *Store) PruneOldStates() ([]uint64, error) { return nil, fmt.Errorf("error iterating rows: %w", err) } - var slotsForQuery []string for _, slot := range deleteSlots { - slotsForQuery = append(slotsForQuery, fmt.Sprintf("%d", slot)) - } - slotsStr := "(" + strings.Join(slotsForQuery, ",") + ")" - // Query to find any matching AttestedSlot or FinalizedSlot - query := fmt.Sprintf(`SELECT DISTINCT attested_slot FROM beacon_state WHERE attested_slot IN %s - UNION - SELECT DISTINCT finalized_slot FROM beacon_state WHERE finalized_slot IN %s`, slotsStr, slotsStr) - - existingRows, err := s.db.Query(query) - if err != nil { - return nil, err - } - defer existingRows.Close() - - // Create a map of found slots to efficiently check for existence - foundSlots := make(map[uint64]bool) - for existingRows.Next() { - var slot uint64 - if err := existingRows.Scan(&slot); err != nil { + err := s.DeleteStateFile(fmt.Sprintf(BeaconStateFilename, slot)) + if err != nil { return nil, err } - foundSlots[slot] = true - } - - for _, slot := range deleteSlots { - if !foundSlots[slot] { - err := s.DeleteStateFile(fmt.Sprintf(BeaconStateFilename, slot)) - if err != nil { - return nil, err - } - } } // Then, delete those rows pruneSQL := fmt.Sprintf(` - DELETE FROM beacon_state - WHERE id IN ( - SELECT id FROM beacon_state - WHERE id NOT IN ( + DELETE FROM beacon_state + WHERE id IN ( SELECT id FROM beacon_state - ORDER BY timestamp DESC - LIMIT %d - ) - )`, s.maxEntries) + WHERE id NOT IN ( + SELECT id FROM beacon_state + ORDER BY timestamp DESC + LIMIT %d + ) + )`, s.maxEntries) _, err = s.db.Exec(pruneSQL) if err != nil { return nil, fmt.Errorf("failed to prune oldest entries: %w", err) @@ -267,3 +267,31 @@ func (s *Store) createTable() error { return nil } + +func (s *Store) writeStateFile(slot uint64, data []byte) error { + err := os.WriteFile(s.location+BeaconStateDir+"/"+fmt.Sprintf(BeaconStateFilename, slot), data, 0644) + if err != nil { + return fmt.Errorf("write to file: %w", err) + } + + return nil +} + +func (s *Store) storeUpdate(attestedSlot, finalizedSlot, attestedSyncPeriod, finalizedSyncPeriod uint64) error { + attestedStateFileName := fmt.Sprintf(BeaconStateFilename, attestedSlot) + finalizedStateFileName := fmt.Sprintf(BeaconStateFilename, finalizedSlot) + + insertStmt := `INSERT INTO beacon_state (attested_slot, finalized_slot, attested_sync_period, finalized_sync_period, attested_state_filename, finalized_state_filename) VALUES (?, ?, ?, ?, ?, ?)` + stmt, err := s.db.Prepare(insertStmt) + if err != nil { + return err + } + defer stmt.Close() + + _, err = stmt.Exec(attestedSlot, finalizedSlot, attestedSyncPeriod, finalizedSyncPeriod, attestedStateFileName, finalizedStateFileName) + if err != nil { + return err + } + + return nil +} diff --git a/relayer/relays/beacon/store/datastore_test.go b/relayer/relays/beacon/store/datastore_test.go new file mode 100644 index 0000000000..1c2384a080 --- /dev/null +++ b/relayer/relays/beacon/store/datastore_test.go @@ -0,0 +1,208 @@ +package store + +import ( + "fmt" + "github.com/snowfork/snowbridge/relayer/relays/beacon/config" + "github.com/snowfork/snowbridge/relayer/relays/beacon/protocol" + "github.com/snowfork/snowbridge/relayer/relays/testutil" + "github.com/stretchr/testify/require" + "os" + "testing" + + _ "github.com/mattn/go-sqlite3" +) + +const TestDataStoreFile = "./" + +func TestGetBeaconState(t *testing.T) { + _ = os.RemoveAll(TestDataStoreFile + BeaconStateDir) + _ = os.Remove(TestDataStoreFile + BeaconStoreName) + + store := New(TestDataStoreFile, 100, *protocol.New(config.SpecSettings{ + SlotsInEpoch: 32, + EpochsPerSyncCommitteePeriod: 256, + DenebForkEpoch: 0, + })) + err := store.Connect() + require.NoError(t, err) + defer func() { + err := os.RemoveAll(TestDataStoreFile + BeaconStateDir) + require.NoError(t, err) + err = os.Remove(TestDataStoreFile + BeaconStoreName) + require.NoError(t, err) + store.Close() + }() + + attestedSlot := uint64(4570816) + finalizedSlot := uint64(4570752) + + attestedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", attestedSlot)) + require.NoError(t, err) + finalizedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", finalizedSlot)) + require.NoError(t, err) + + err = store.WriteEntry(attestedSlot, finalizedSlot, attestedState, finalizedState) + require.NoError(t, err) + + // Check that we can get the beacon states + attestedStateStore, err := store.GetBeaconStateData(attestedSlot) + require.NoError(t, err) + require.Equal(t, attestedState, attestedStateStore) + + finalizedStateStore, err := store.GetBeaconStateData(finalizedSlot) + require.NoError(t, err) + require.Equal(t, finalizedState, finalizedStateStore) + + // Check that a non-existent state returns an error + _, err = store.GetBeaconStateData(35345345) + require.Error(t, err) +} + +func TestPruneOldStates(t *testing.T) { + _ = os.RemoveAll(TestDataStoreFile + BeaconStateDir) + _ = os.Remove(TestDataStoreFile + BeaconStoreName) + + store := New(TestDataStoreFile, 2, *protocol.New(config.SpecSettings{ + SlotsInEpoch: 32, + EpochsPerSyncCommitteePeriod: 256, + DenebForkEpoch: 0, + })) + err := store.Connect() + require.NoError(t, err) + defer func() { + err := os.RemoveAll(TestDataStoreFile + BeaconStateDir) + require.NoError(t, err) + err = os.Remove(TestDataStoreFile + BeaconStoreName) + require.NoError(t, err) + store.Close() + }() + + // entry 1 + pair1FinalizedSlot := uint64(4570816) + pair1AttestedSlot := uint64(4570752) + + pair1AttestedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair1AttestedSlot)) + require.NoError(t, err) + pair1FinalizedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair1FinalizedSlot)) + require.NoError(t, err) + + err = store.WriteEntry(pair1AttestedSlot, pair1FinalizedSlot, pair1AttestedState, pair1FinalizedState) + require.NoError(t, err) + + // entry 2 + pair2FinalizedSlot := uint64(4571072) + pair2AttestedSlot := uint64(4571136) + + pair2AttestedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair2AttestedSlot)) + require.NoError(t, err) + pair2FinalizedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair2FinalizedSlot)) + require.NoError(t, err) + + err = store.WriteEntry(pair2AttestedSlot, pair2FinalizedSlot, pair2AttestedState, pair2FinalizedState) + require.NoError(t, err) + + // entry 3 + pair3FinalizedSlot := uint64(4644864) + pair3AttestedSlot := uint64(4644928) + + pair3AttestedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair3AttestedSlot)) + require.NoError(t, err) + pair3FinalizedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair3FinalizedSlot)) + require.NoError(t, err) + + err = store.WriteEntry(pair3AttestedSlot, pair3FinalizedSlot, pair3AttestedState, pair3FinalizedState) + require.NoError(t, err) + + _, err = store.GetBeaconStateData(pair1FinalizedSlot) + require.NoError(t, err) + _, err = store.GetBeaconStateData(pair1AttestedSlot) + require.NoError(t, err) + + deleted, err := store.PruneOldStates() + require.NoError(t, err) + require.Equal(t, []uint64{pair1AttestedSlot, pair1FinalizedSlot}, deleted) // Check the oldest slots were deleted + + // Check the files were also deleted + _, err = store.GetBeaconStateData(pair1FinalizedSlot) + require.Error(t, err) + _, err = store.GetBeaconStateData(pair1AttestedSlot) + require.Error(t, err) +} + +func TestFindBeaconStateWithinSyncPeriodRange(t *testing.T) { + _ = os.RemoveAll(TestDataStoreFile + BeaconStateDir) + _ = os.Remove(TestDataStoreFile + BeaconStoreName) + + p := protocol.New(config.SpecSettings{ + SlotsInEpoch: 32, + EpochsPerSyncCommitteePeriod: 256, + DenebForkEpoch: 0, + }) + store := New(TestDataStoreFile, 2, *p) + err := store.Connect() + require.NoError(t, err) + defer func() { + err := os.RemoveAll(TestDataStoreFile + BeaconStateDir) + require.NoError(t, err) + err = os.Remove(TestDataStoreFile + BeaconStoreName) + require.NoError(t, err) + store.Close() + }() + + // entry 1 + pair1FinalizedSlot := uint64(4570816) + pair1AttestedSlot := uint64(4570752) + + pair1AttestedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair1AttestedSlot)) + require.NoError(t, err) + pair1FinalizedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair1FinalizedSlot)) + require.NoError(t, err) + + err = store.WriteEntry(pair1AttestedSlot, pair1FinalizedSlot, pair1AttestedState, pair1FinalizedState) + require.NoError(t, err) + + // entry 2 + pair2FinalizedSlot := uint64(4571072) + pair2AttestedSlot := uint64(4571136) + + pair2AttestedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair2AttestedSlot)) + require.NoError(t, err) + pair2FinalizedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair2FinalizedSlot)) + require.NoError(t, err) + + err = store.WriteEntry(pair2AttestedSlot, pair2FinalizedSlot, pair2AttestedState, pair2FinalizedState) + require.NoError(t, err) + + // entry 3 + pair3FinalizedSlot := uint64(4644864) + pair3AttestedSlot := uint64(4644928) + + pair3AttestedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair3AttestedSlot)) + require.NoError(t, err) + pair3FinalizedState, err := testutil.LoadFile(fmt.Sprintf("%d.ssz", pair3FinalizedSlot)) + require.NoError(t, err) + + err = store.WriteEntry(pair3AttestedSlot, pair3FinalizedSlot, pair3AttestedState, pair3FinalizedState) + require.NoError(t, err) + + period568Start := uint64(568 * 256 * 32) + beaconData, err := store.FindBeaconStateWithinSyncPeriod(4644864, period568Start, true) + require.NoError(t, err) + + require.Equal(t, pair3AttestedSlot, beaconData.AttestedSlot) + require.Equal(t, pair3FinalizedSlot, beaconData.FinalizedSlot) + + period558Start := uint64(558 * 256 * 32) + beaconData, err = store.FindBeaconStateWithinSyncPeriod(4570003, period558Start, false) + require.NoError(t, err) + + require.Equal(t, pair1AttestedSlot, beaconData.AttestedSlot) + require.Equal(t, pair1FinalizedSlot, beaconData.FinalizedSlot) + + period559Start := uint64(559 * 256 * 32) + beaconData, err = store.FindBeaconStateWithinSyncPeriod(4570800, period559Start, false) + require.NoError(t, err) + + require.Equal(t, pair2AttestedSlot, beaconData.AttestedSlot) + require.Equal(t, pair2FinalizedSlot, beaconData.FinalizedSlot) +} diff --git a/relayer/relays/execution/main.go b/relayer/relays/execution/main.go index 1181dba895..70971e3db7 100644 --- a/relayer/relays/execution/main.go +++ b/relayer/relays/execution/main.go @@ -3,8 +3,6 @@ package execution import ( "context" "fmt" - "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" - "github.com/snowfork/snowbridge/relayer/relays/beacon/store" "math/big" "sort" "time" @@ -19,6 +17,9 @@ import ( "github.com/snowfork/snowbridge/relayer/contracts" "github.com/snowfork/snowbridge/relayer/crypto/sr25519" "github.com/snowfork/snowbridge/relayer/relays/beacon/header" + "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" + "github.com/snowfork/snowbridge/relayer/relays/beacon/protocol" + "github.com/snowfork/snowbridge/relayer/relays/beacon/store" "golang.org/x/sync/errgroup" ) @@ -82,16 +83,19 @@ func (r *Relay) Start(ctx context.Context, eg *errgroup.Group) error { } r.gatewayContract = contract - store := store.New(r.config.Source.Beacon.DataStore.Location, r.config.Source.Beacon.DataStore.MaxEntries) + p := protocol.New(r.config.Source.Beacon.Spec) + + store := store.New(r.config.Source.Beacon.DataStore.Location, r.config.Source.Beacon.DataStore.MaxEntries, *p) store.Connect() defer store.Close() - beaconAPI := api.NewBeaconClient(r.config.Source.Beacon.Endpoint, r.config.Source.Beacon.Spec.SlotsInEpoch) + beaconAPI := api.NewBeaconClient(r.config.Source.Beacon.Endpoint) beaconHeader := header.New( writer, beaconAPI, r.config.Source.Beacon.Spec, &store, + p, ) r.beaconHeader = &beaconHeader diff --git a/relayer/relays/testutil/fixtures.go b/relayer/relays/testutil/fixtures.go index ad7dfc6e95..80f298354f 100644 --- a/relayer/relays/testutil/fixtures.go +++ b/relayer/relays/testutil/fixtures.go @@ -9,10 +9,10 @@ import ( "runtime" ) -func GetSyncCommitteeUpdate() (api.SyncCommitteePeriodUpdateResponse, error) { +func GetSyncCommitteeUpdate(period uint64) (api.SyncCommitteePeriodUpdateResponse, error) { var update api.SyncCommitteePeriodUpdateResponse - data, err := LoadFile("older_sync_committee_update.json") + data, err := LoadFile(fmt.Sprintf("sync_committee_update_%d.json", period)) if err != nil { return update, fmt.Errorf("error reading file: %w", err) } @@ -82,5 +82,4 @@ func LoadFile(filename string) ([]byte, error) { } return jsonData, nil - } diff --git a/relayer/relays/testutil/fixtures/4644864.ssz b/relayer/relays/testutil/fixtures/4644864.ssz new file mode 100644 index 0000000000..ae39c5e422 Binary files /dev/null and b/relayer/relays/testutil/fixtures/4644864.ssz differ diff --git a/relayer/relays/testutil/fixtures/4644928.ssz b/relayer/relays/testutil/fixtures/4644928.ssz new file mode 100644 index 0000000000..c5542f1ca6 Binary files /dev/null and b/relayer/relays/testutil/fixtures/4644928.ssz differ diff --git a/relayer/relays/testutil/fixtures/4645280.ssz b/relayer/relays/testutil/fixtures/4645280.ssz new file mode 100644 index 0000000000..27dd995850 Binary files /dev/null and b/relayer/relays/testutil/fixtures/4645280.ssz differ diff --git a/relayer/relays/testutil/fixtures/header_at_slot_129.json b/relayer/relays/testutil/fixtures/header_at_slot_129.json new file mode 100644 index 0000000000..7de8dcb89c --- /dev/null +++ b/relayer/relays/testutil/fixtures/header_at_slot_129.json @@ -0,0 +1 @@ +{"slot":129,"proposer_index":5,"parent_root":"0x5042902843e7028011fd44920e40306bb9e737c3840abd479156fa045e67029d","state_root":"0x149a79d6c9a734406fb177c8a9aec16829bbdc3f438b3cf0ca2985e352c276be","body_root":"0x1bb18ce4c75754da60179daeaace247949c7b64222480695991ee0e582e60b86"} diff --git a/relayer/relays/testutil/fixtures/header_at_slot_130.json b/relayer/relays/testutil/fixtures/header_at_slot_130.json new file mode 100644 index 0000000000..68e382603b --- /dev/null +++ b/relayer/relays/testutil/fixtures/header_at_slot_130.json @@ -0,0 +1 @@ +{"slot":130,"proposer_index":6,"parent_root":"0xf244a1a19fa53466e9ea53f9e4526227fc74ba7940a2b995d7486cf6dc2d31bc","state_root":"0x394b2b79d80a6da2615fa3b855793b124e6e11b0f29725dbc6c9a9665f49faa4","body_root":"0xc4e4a874a09a323943abd9f7859718be37ef736945480936ec08be925da5342b"} diff --git a/relayer/relays/testutil/fixtures/header_at_slot_64.json b/relayer/relays/testutil/fixtures/header_at_slot_64.json new file mode 100644 index 0000000000..06bbfb2f6c --- /dev/null +++ b/relayer/relays/testutil/fixtures/header_at_slot_64.json @@ -0,0 +1 @@ +{"slot":64,"proposer_index":4,"parent_root":"0xa30468e014329f29425a3655e840b09554b75087bdd02872233d20bf46499acc","state_root":"0x1f8560fd92808e86051e9fb5bc180107a511c46fb859f86ef1190a4f0ee5ecc1","body_root":"0xa811f0c7c34e7a991ace860474f1894c54e2b14dc10fc968a70bd34e0a7c0068"} diff --git a/relayer/relays/testutil/fixtures/sync_committee_update_0.json b/relayer/relays/testutil/fixtures/sync_committee_update_0.json new file mode 100644 index 0000000000..c45ef9fd96 --- /dev/null +++ b/relayer/relays/testutil/fixtures/sync_committee_update_0.json @@ -0,0 +1 @@ +{"data":{"attested_header":{"beacon":{"slot":"129","proposer_index":"5","parent_root":"0x5042902843e7028011fd44920e40306bb9e737c3840abd479156fa045e67029d","state_root":"0x149a79d6c9a734406fb177c8a9aec16829bbdc3f438b3cf0ca2985e352c276be","body_root":"0x1bb18ce4c75754da60179daeaace247949c7b64222480695991ee0e582e60b86"},"execution":{"parent_hash":"0xfc24a496b9a6adc756bba2e988e171d76ffd7afb76cd267783773f6a3bcbcfd7","fee_recipient":"0x0000000000000000000000000000000000000000","state_root":"0xb82b748d23de8b4f8da21f9e672dea23f7b197045d1cd33e401f9a807ababd38","receipts_root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logs_bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prev_randao":"0xd232bdfe5a5ff2bcb16174ebbe866aa90554446f9a9c16ee85a83777bf3ece7d","block_number":"129","gas_limit":"70526681","gas_used":"0","timestamp":"1711631286","extra_data":"0xd983010d0b846765746888676f312e32312e368664617277696e","base_fee_per_gas":"38","block_hash":"0xb36a6e770d26c220f6767e42a648c92c71349c9534d0ab82f408cf91014f78c1","transactions_root":"0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1","withdrawals_root":"0x792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535","blob_gas_used":"0","excess_blob_gas":"0"},"execution_branch":["0x50dbcb1fd7c53f50bb3bd1a30640f38eec60300722977902a47779cae66d448c","0xb46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x0f7d1e8ad6f53d0b0765f8aaa1da53b93693e2e127c4c490ad160c9e16d39dfc"]},"next_sync_committee":{"pubkeys":["0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b","0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34","0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac","0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373","0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e","0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e","0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b"],"aggregate_pubkey":"0x8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c"},"next_sync_committee_branch":["0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59","0x9bf0322f1ee94ad990a942bc5d6cfe499121f537ac5c6cc938707dab3d4682e1","0x51837f50a320eab48848ff9c7d1171871872a3d9192b67c1eef959bf0dc02828","0x35fa77753c61d63b9eb822049fe3ec9d299933004d9a76f5532a6556c382e280","0x3e49cb19b7b2faba1d89fb35ed6ce3d3d63915f5cc851a9b34bd04362054dabf"],"finalized_header":{"beacon":{"slot":"64","proposer_index":"4","parent_root":"0xa30468e014329f29425a3655e840b09554b75087bdd02872233d20bf46499acc","state_root":"0x1f8560fd92808e86051e9fb5bc180107a511c46fb859f86ef1190a4f0ee5ecc1","body_root":"0xa811f0c7c34e7a991ace860474f1894c54e2b14dc10fc968a70bd34e0a7c0068"},"execution":{"parent_hash":"0xe57a711cc099579baa05a1687af9a7b32907738cd97787e6dd5e7d0c9e5118b3","fee_recipient":"0x0000000000000000000000000000000000000000","state_root":"0xb82b748d23de8b4f8da21f9e672dea23f7b197045d1cd33e401f9a807ababd38","receipts_root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logs_bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prev_randao":"0xfb2d4f65540fceef0cea5fc6213dddec29b1c7cdad4183ad20cc97d91a2ede9b","block_number":"64","gas_limit":"75150842","gas_used":"0","timestamp":"1711631221","extra_data":"0xd983010d0b846765746888676f312e32312e368664617277696e","base_fee_per_gas":"202816","block_hash":"0xb03086c1df5cae3bfd50715788e3934a3c48691e6723d8da89958cec4355ec5a","transactions_root":"0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1","withdrawals_root":"0x792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535","blob_gas_used":"0","excess_blob_gas":"0"},"execution_branch":["0x009c48baf412ec9a1cb99c20a13db8c4a63460e865da1a95f8d8b55e9c2a9ae9","0xb46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb","0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x8ab089426e150163fe8c84b9d23fc2befc594fbe2c5e9f299b049e2562b5dc82"]},"finality_branch":["0x0200000000000000000000000000000000000000000000000000000000000000","0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7","0x98e9116c6bb7f20de18800dc63e73e689d06d6a47d35b5e2b32cf093d475840d","0x51837f50a320eab48848ff9c7d1171871872a3d9192b67c1eef959bf0dc02828","0x35fa77753c61d63b9eb822049fe3ec9d299933004d9a76f5532a6556c382e280","0x3e49cb19b7b2faba1d89fb35ed6ce3d3d63915f5cc851a9b34bd04362054dabf"],"sync_aggregate":{"sync_committee_bits":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","sync_committee_signature":"0x816a7ea48dd784ae28d1e30b32a72fe3597295bee9b23b503fed03b673e1dd222239b1c676473f532a27289f8798f46207d007b2de53830b1585dd29633cd39ea336c4f90ce002966fb2d3807cb1570b9cc110ea6470d8d86b4c87bf3ffd9bb4"},"signature_slot":"130"},"version":"deneb"} diff --git a/relayer/relays/testutil/mock_store.go b/relayer/relays/testutil/mock_store.go deleted file mode 100644 index 4e05525457..0000000000 --- a/relayer/relays/testutil/mock_store.go +++ /dev/null @@ -1,25 +0,0 @@ -package testutil - -import ( - "github.com/snowfork/snowbridge/relayer/relays/beacon/store" -) - -type MockStore struct { - BeaconStateData store.StoredBeaconData -} - -func (m *MockStore) Connect() error { - return nil -} - -func (m *MockStore) Close() { - -} - -func (m *MockStore) StoreUpdate(attestedSlot, finalizedSlot, attestedSyncPeriod, finalizedSyncPeriod uint64) error { - return nil -} - -func (m *MockStore) FindBeaconStateWithinSyncPeriodRange(baseSlot, slotRange uint64) (store.StoredBeaconData, error) { - return m.BeaconStateData, nil -} diff --git a/relayer/relays/testutil/mock_writer.go b/relayer/relays/testutil/mock_writer.go deleted file mode 100644 index 5532521e7c..0000000000 --- a/relayer/relays/testutil/mock_writer.go +++ /dev/null @@ -1,70 +0,0 @@ -package testutil - -import ( - "context" - "fmt" - - "github.com/snowfork/go-substrate-rpc-client/v4/types" - "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/scale" - "github.com/snowfork/snowbridge/relayer/relays/beacon/state" - - "github.com/ethereum/go-ethereum/common" -) - -type MockWriter struct { - LastFinalizedState state.FinalizedHeader -} - -func (m *MockWriter) GetLastFinalizedStateIndex() (types.U32, error) { - return 0, nil -} - -func (m *MockWriter) GetFinalizedBeaconRootByIndex(index uint32) (types.H256, error) { - return types.H256{}, nil -} - -func (m *MockWriter) BatchCall(ctx context.Context, extrinsic string, calls []interface{}) error { - return nil -} - -func (m *MockWriter) WriteToParachainAndRateLimit(ctx context.Context, extrinsicName string, payload ...interface{}) error { - return nil -} -func (m *MockWriter) WriteToParachainAndWatch(ctx context.Context, extrinsicName string, payload ...interface{}) error { - update, ok := payload[0].(scale.UpdatePayload) - if ok { - m.LastFinalizedState.BeaconSlot = uint64(update.FinalizedHeader.Slot) - htr, err := update.FinalizedHeader.ToSSZ().HashTreeRoot() - if err != nil { - return fmt.Errorf("hash tree root error") - } - m.LastFinalizedState.BeaconBlockRoot = htr - } else { - return fmt.Errorf("type conversion error") - } - return nil -} - -func (m *MockWriter) GetLastFinalizedHeaderState() (state.FinalizedHeader, error) { - return m.LastFinalizedState, nil -} - -func (m *MockWriter) GetFinalizedStateByStorageKey(key string) (scale.BeaconState, error) { - return scale.BeaconState{}, nil -} - -func (m *MockWriter) GetLastBasicChannelBlockNumber() (uint64, error) { - return 0, nil -} - -func (m *MockWriter) GetLastBasicChannelNonceByAddress(address common.Address) (uint64, error) { - return 0, nil - -} -func (m *MockWriter) GetFinalizedHeaderStateByBlockRoot(blockRoot types.H256) (state.FinalizedHeader, error) { - return state.FinalizedHeader{}, nil -} - -func (m *MockWriter) FindCheckPointBackward(slot uint64) (state.FinalizedHeader, error) { - return state.FinalizedHeader{}, nil -} diff --git a/relayer/relays/util/util.go b/relayer/relays/util/util.go index 9e94b7ef1c..eb0cf9b3a8 100644 --- a/relayer/relays/util/util.go +++ b/relayer/relays/util/util.go @@ -2,6 +2,7 @@ package util import ( "encoding/hex" + "fmt" "strconv" "strings" @@ -164,3 +165,16 @@ func ChangeByteOrder(b []byte) []byte { return b } + +func ByteArrayToPublicKeyArray(pubkeys [][]byte) ([][48]byte, error) { + result := [][48]byte{} + for _, pubkey := range pubkeys { + var tmpPubkey [48]byte + if len(pubkey) > 48 { + return nil, fmt.Errorf("slice is longer than 48 bytes") + } + copy(tmpPubkey[:], pubkey) + result = append(result, tmpPubkey) + } + return result, nil +} diff --git a/web/packages/test/config/beacon-relay.json b/web/packages/test/config/beacon-relay.json index 8b708ba0d8..b51189e87c 100644 --- a/web/packages/test/config/beacon-relay.json +++ b/web/packages/test/config/beacon-relay.json @@ -3,6 +3,7 @@ "beacon": { "endpoint": "http://127.0.0.1:9596", "spec": { + "syncCommitteeSize": 512, "slotsInEpoch": 32, "epochsPerSyncCommitteePeriod": 256, "denebForkedEpoch": 0