mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 05:47:59 -05:00
Compare commits
9 Commits
fix-earlie
...
cp-sync-sn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d6b566918 | ||
|
|
fa53836413 | ||
|
|
b52bbb1322 | ||
|
|
a7f195287e | ||
|
|
11976c4406 | ||
|
|
c7f55c969c | ||
|
|
f500ffbce0 | ||
|
|
26a957b4cd | ||
|
|
f955a88c9e |
13
api/client/openapi/BUILD.bazel
Normal file
13
api/client/openapi/BUILD.bazel
Normal file
@@ -0,0 +1,13 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["client.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/api/client/openapi",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
278
api/client/openapi/client.go
Normal file
278
api/client/openapi/client.go
Normal file
@@ -0,0 +1,278 @@
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const GET_WEAK_SUBJECTIVITY_CHECKPOINT_EPOCH_PATH = "/eth/v1alpha1/beacon/weak_subjectivity_checkpoint_epoch"
|
||||
const GET_WEAK_SUBJECTIVITY_CHECKPOINT_PATH = "/eth/v1alpha1/beacon/weak_subjectivity_checkpoint"
|
||||
const GET_SIGNED_BLOCK_PATH = "/eth/v2/beacon/blocks"
|
||||
const GET_STATE_PATH = "/eth/v2/debug/beacon/states"
|
||||
|
||||
// ClientOpt is a functional option for the Client type (http.Client wrapper)
|
||||
type ClientOpt func(*Client)
|
||||
|
||||
// WithTimeout sets the .Timeout attribute of the wrapped http.Client
|
||||
func WithTimeout(timeout time.Duration) ClientOpt {
|
||||
return func(c *Client) {
|
||||
c.c.Timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// Client provides a collection of helper methods for calling the beacon node OpenAPI endpoints
|
||||
type Client struct {
|
||||
c *http.Client
|
||||
host string
|
||||
scheme string
|
||||
}
|
||||
|
||||
func (c *Client) urlForPath(methodPath string) *url.URL {
|
||||
u := &url.URL{
|
||||
Scheme: c.scheme,
|
||||
Host: c.host,
|
||||
}
|
||||
u.Path = path.Join(u.Path, methodPath)
|
||||
return u
|
||||
}
|
||||
|
||||
// NewClient constructs a new client with the provided options (ex WithTimeout).
|
||||
// host is the base host + port used to construct request urls. This value can be
|
||||
// a URL string, or NewClient will assume an http endpoint if just `host:port` is used.
|
||||
func NewClient(host string, opts ...ClientOpt) (*Client, error) {
|
||||
host, err := validHostname(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Client{
|
||||
c: &http.Client{},
|
||||
scheme: "http",
|
||||
host: host,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(c)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func validHostname(h string) (string, error) {
|
||||
// try to parse as url (being permissive)
|
||||
u, err := url.Parse(h)
|
||||
if err == nil && u.Host != "" {
|
||||
return u.Host, nil
|
||||
}
|
||||
// try to parse as host:port
|
||||
host, port, err := net.SplitHostPort(h)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", host, port), nil
|
||||
}
|
||||
|
||||
type checkpointEpochResponse struct {
|
||||
Epoch string
|
||||
}
|
||||
|
||||
// GetWeakSubjectivityCheckpointEpoch retrieves the epoch for a finalized block within the weak subjectivity period.
|
||||
// The main use case for method is to find the slot that can be used to fetch a block within the subjectivity period
|
||||
// which can be used to sync (as an alternative to syncing from genesis).
|
||||
func (c *Client) GetWeakSubjectivityCheckpointEpoch() (uint64, error) {
|
||||
u := c.urlForPath(GET_WEAK_SUBJECTIVITY_CHECKPOINT_EPOCH_PATH)
|
||||
r, err := c.c.Get(u.String())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return 0, non200Err(r)
|
||||
}
|
||||
jsonr := &checkpointEpochResponse{}
|
||||
err = json.NewDecoder(r.Body).Decode(jsonr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseUint(jsonr.Epoch, 10, 64)
|
||||
}
|
||||
|
||||
type wscResponse struct {
|
||||
BlockRoot string
|
||||
StateRoot string
|
||||
Epoch string
|
||||
}
|
||||
|
||||
// GetWeakSubjectivityCheckpoint calls an entirely different rpc method than GetWeakSubjectivityCheckpointEpoch
|
||||
// This endpoint is much slower, because it uses stategen to generate the BeaconState at the beginning of the
|
||||
// weak subjectivity epoch. This also results in a different set of state+block roots, because this endpoint currently
|
||||
// uses the state's latest_block_header for the block hash, while the checkpoint sync code assumes that the block
|
||||
// is from the first slot in the epoch and the state is from the subesequent slot.
|
||||
func (c *Client) GetWeakSubjectivityCheckpoint() (*ethpb.WeakSubjectivityCheckpoint, error) {
|
||||
u := c.urlForPath(GET_WEAK_SUBJECTIVITY_CHECKPOINT_PATH)
|
||||
r, err := c.c.Get(u.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, non200Err(r)
|
||||
}
|
||||
v := &wscResponse{}
|
||||
b := bytes.NewBuffer(nil)
|
||||
bodyReader := io.TeeReader(r.Body, b)
|
||||
err = json.NewDecoder(bodyReader).Decode(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
epoch, err := strconv.ParseUint(v.Epoch, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockRoot, err := base64.StdEncoding.DecodeString(v.BlockRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stateRoot, err := base64.StdEncoding.DecodeString(v.StateRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ðpb.WeakSubjectivityCheckpoint{
|
||||
Epoch: types.Epoch(epoch),
|
||||
BlockRoot: blockRoot,
|
||||
StateRoot: stateRoot,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: fix hardcoded pb type using sniff code
|
||||
// GetBlockBySlot queries the beacon node API for the SignedBeaconBlockAltair for the given slot
|
||||
func (c *Client) GetBlockBySlot(slot uint64) (*ethpb.SignedBeaconBlockAltair, error) {
|
||||
blockPath := path.Join(GET_SIGNED_BLOCK_PATH, strconv.FormatUint(slot, 10))
|
||||
u := c.urlForPath(blockPath)
|
||||
log.Printf("requesting %s", u.String())
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/octet-stream")
|
||||
r, err := c.c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, non200Err(r)
|
||||
}
|
||||
|
||||
v := ðpb.SignedBeaconBlockAltair{}
|
||||
b := new(bytes.Buffer)
|
||||
_, err = b.ReadFrom(r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = v.UnmarshalSSZ(b.Bytes())
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetBlockByRoot retrieves a SignedBeaconBlockAltair with the given root via the beacon node API
|
||||
func (c *Client) GetBlockByRoot(blockHex string) (*ethpb.SignedBeaconBlockAltair, error) {
|
||||
blockPath := path.Join(GET_SIGNED_BLOCK_PATH, blockHex)
|
||||
u := c.urlForPath(blockPath)
|
||||
log.Printf("requesting %s", u.String())
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/octet-stream")
|
||||
r, err := c.c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, non200Err(r)
|
||||
}
|
||||
|
||||
v := ðpb.SignedBeaconBlockAltair{}
|
||||
b := new(bytes.Buffer)
|
||||
_, err = b.ReadFrom(r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = v.UnmarshalSSZ(b.Bytes())
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetStateByRoot retrieves a BeaconStateAltair with the given root via the beacon node API
|
||||
func (c *Client) GetStateByRoot(stateHex string) (*ethpb.BeaconStateAltair, error) {
|
||||
statePath := path.Join(GET_STATE_PATH, stateHex)
|
||||
u := c.urlForPath(statePath)
|
||||
log.Printf("requesting %s", u.String())
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/octet-stream")
|
||||
r, err := c.c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, non200Err(r)
|
||||
}
|
||||
|
||||
v := ðpb.BeaconStateAltair{}
|
||||
b := new(bytes.Buffer)
|
||||
_, err = b.ReadFrom(r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = v.UnmarshalSSZ(b.Bytes())
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetStateBySlot retrieves a BeaconStateAltair at the given slot via the beacon node API
|
||||
func (c *Client) GetStateBySlot(slot uint64) (*ethpb.BeaconStateAltair, error) {
|
||||
statePath := path.Join(GET_STATE_PATH, strconv.FormatUint(slot, 10))
|
||||
u := c.urlForPath(statePath)
|
||||
log.Printf("requesting %s", u.String())
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/octet-stream")
|
||||
r, err := c.c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, non200Err(r)
|
||||
}
|
||||
|
||||
v := ðpb.BeaconStateAltair{}
|
||||
b := new(bytes.Buffer)
|
||||
_, err = b.ReadFrom(r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = v.UnmarshalSSZ(b.Bytes())
|
||||
return v, err
|
||||
}
|
||||
|
||||
func non200Err(response *http.Response) error {
|
||||
bodyBytes, err := ioutil.ReadAll(response.Body)
|
||||
var body string
|
||||
if err != nil {
|
||||
body = "(Unable to read response body.)"
|
||||
} else {
|
||||
body = "response body:\n" + string(bodyBytes)
|
||||
}
|
||||
return fmt.Errorf("Got non-200 status code = %d requesting %s. %s", response.StatusCode, response.Request.URL, body)
|
||||
}
|
||||
@@ -74,10 +74,22 @@ func (m *ApiProxyMiddleware) PrepareRequestForProxying(endpoint Endpoint, req *h
|
||||
return nil
|
||||
}
|
||||
|
||||
type ClientOption func(*http.Client)
|
||||
|
||||
func WithTimeout(t time.Duration) ClientOption {
|
||||
return func(c *http.Client) {
|
||||
c.Timeout = t
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyRequest proxies the request to grpc-gateway.
|
||||
func ProxyRequest(req *http.Request) (*http.Response, ErrorJson) {
|
||||
func ProxyRequest(req *http.Request, opts ...ClientOption) (*http.Response, ErrorJson) {
|
||||
// We do not use http.DefaultClient because it does not have any timeout.
|
||||
// TODO: think about exposing this as a flag, or based on endpoint
|
||||
netClient := &http.Client{Timeout: time.Minute * 2}
|
||||
for _, o := range opts {
|
||||
o(netClient)
|
||||
}
|
||||
grpcResp, err := netClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, InternalServerErrorWithMessage(err, "could not proxy request")
|
||||
|
||||
@@ -56,8 +56,8 @@ func TestFinalizedCheckpt_GenesisRootOk(t *testing.T) {
|
||||
cp := ðpb.Checkpoint{Root: genesisRoot[:]}
|
||||
c := setupBeaconChain(t, beaconDB)
|
||||
c.finalizedCheckpt = cp
|
||||
c.genesisRoot = genesisRoot
|
||||
assert.DeepEqual(t, c.genesisRoot[:], c.FinalizedCheckpt().Root)
|
||||
c.originBlockRoot = genesisRoot
|
||||
assert.DeepEqual(t, c.originBlockRoot[:], c.FinalizedCheckpt().Root)
|
||||
}
|
||||
|
||||
func TestCurrentJustifiedCheckpt_CanRetrieve(t *testing.T) {
|
||||
@@ -77,8 +77,8 @@ func TestJustifiedCheckpt_GenesisRootOk(t *testing.T) {
|
||||
genesisRoot := [32]byte{'B'}
|
||||
cp := ðpb.Checkpoint{Root: genesisRoot[:]}
|
||||
c.justifiedCheckpt = cp
|
||||
c.genesisRoot = genesisRoot
|
||||
assert.DeepEqual(t, c.genesisRoot[:], c.CurrentJustifiedCheckpt().Root)
|
||||
c.originBlockRoot = genesisRoot
|
||||
assert.DeepEqual(t, c.originBlockRoot[:], c.CurrentJustifiedCheckpt().Root)
|
||||
}
|
||||
|
||||
func TestPreviousJustifiedCheckpt_CanRetrieve(t *testing.T) {
|
||||
@@ -98,8 +98,8 @@ func TestPrevJustifiedCheckpt_GenesisRootOk(t *testing.T) {
|
||||
cp := ðpb.Checkpoint{Root: genesisRoot[:]}
|
||||
c := setupBeaconChain(t, beaconDB)
|
||||
c.prevJustifiedCheckpt = cp
|
||||
c.genesisRoot = genesisRoot
|
||||
assert.DeepEqual(t, c.genesisRoot[:], c.PreviousJustifiedCheckpt().Root)
|
||||
c.originBlockRoot = genesisRoot
|
||||
assert.DeepEqual(t, c.originBlockRoot[:], c.PreviousJustifiedCheckpt().Root)
|
||||
}
|
||||
|
||||
func TestHeadSlot_CanRetrieve(t *testing.T) {
|
||||
|
||||
@@ -46,11 +46,11 @@ func (s *Service) updateHead(ctx context.Context, balances []uint64) error {
|
||||
// Get head from the fork choice service.
|
||||
f := s.finalizedCheckpt
|
||||
j := s.justifiedCheckpt
|
||||
// To get head before the first justified epoch, the fork choice will start with genesis root
|
||||
// To get head before the first justified epoch, the fork choice will start with origin root
|
||||
// instead of zero hashes.
|
||||
headStartRoot := bytesutil.ToBytes32(j.Root)
|
||||
if headStartRoot == params.BeaconConfig().ZeroHash {
|
||||
headStartRoot = s.genesisRoot
|
||||
headStartRoot = s.originBlockRoot
|
||||
}
|
||||
|
||||
// In order to process head, fork choice store requires justified info.
|
||||
@@ -277,8 +277,8 @@ func (s *Service) notifyNewHeadEvent(
|
||||
newHeadStateRoot,
|
||||
newHeadRoot []byte,
|
||||
) error {
|
||||
previousDutyDependentRoot := s.genesisRoot[:]
|
||||
currentDutyDependentRoot := s.genesisRoot[:]
|
||||
previousDutyDependentRoot := s.originBlockRoot[:]
|
||||
currentDutyDependentRoot := s.originBlockRoot[:]
|
||||
|
||||
var previousDutyEpoch types.Epoch
|
||||
currentDutyEpoch := slots.ToEpoch(newHeadSlot)
|
||||
|
||||
@@ -158,7 +158,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
cfg: &config{
|
||||
StateNotifier: notifier,
|
||||
},
|
||||
genesisRoot: [32]byte{1},
|
||||
originBlockRoot: [32]byte{1},
|
||||
}
|
||||
newHeadStateRoot := [32]byte{2}
|
||||
newHeadRoot := [32]byte{3}
|
||||
@@ -174,8 +174,8 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
Block: newHeadRoot[:],
|
||||
State: newHeadStateRoot[:],
|
||||
EpochTransition: false,
|
||||
PreviousDutyDependentRoot: srv.genesisRoot[:],
|
||||
CurrentDutyDependentRoot: srv.genesisRoot[:],
|
||||
PreviousDutyDependentRoot: srv.originBlockRoot[:],
|
||||
CurrentDutyDependentRoot: srv.originBlockRoot[:],
|
||||
}
|
||||
require.DeepSSZEqual(t, wanted, eventHead)
|
||||
})
|
||||
@@ -187,7 +187,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
cfg: &config{
|
||||
StateNotifier: notifier,
|
||||
},
|
||||
genesisRoot: genesisRoot,
|
||||
originBlockRoot: genesisRoot,
|
||||
}
|
||||
epoch1Start, err := slots.EpochStart(1)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -439,7 +439,7 @@ func (s *Service) deletePoolAtts(atts []*ethpb.Attestation) error {
|
||||
// fork choice justification routine.
|
||||
func (s *Service) ensureRootNotZeros(root [32]byte) [32]byte {
|
||||
if root == params.BeaconConfig().ZeroHash {
|
||||
return s.genesisRoot
|
||||
return s.originBlockRoot
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
@@ -733,10 +733,10 @@ func TestEnsureRootNotZeroHashes(t *testing.T) {
|
||||
opts := testServiceOptsNoDB()
|
||||
service, err := NewService(ctx, opts...)
|
||||
require.NoError(t, err)
|
||||
service.genesisRoot = [32]byte{'a'}
|
||||
service.originBlockRoot = [32]byte{'a'}
|
||||
|
||||
r := service.ensureRootNotZeros(params.BeaconConfig().ZeroHash)
|
||||
assert.Equal(t, service.genesisRoot, r, "Did not get wanted justified root")
|
||||
assert.Equal(t, service.originBlockRoot, r, "Did not get wanted justified root")
|
||||
root := [32]byte{'b'}
|
||||
r = service.ensureRootNotZeros(root)
|
||||
assert.Equal(t, root, r, "Did not get wanted justified root")
|
||||
@@ -917,7 +917,7 @@ func TestUpdateJustifiedInitSync(t *testing.T) {
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: gRoot[:]}))
|
||||
beaconState, _ := util.DeterministicGenesisState(t, 32)
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
service.genesisRoot = gRoot
|
||||
service.originBlockRoot = gRoot
|
||||
currentCp := ðpb.Checkpoint{Epoch: 1}
|
||||
service.justifiedCheckpt = currentCp
|
||||
newCp := ðpb.Checkpoint{Epoch: 2, Root: gRoot[:]}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/async/event"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
@@ -103,45 +104,52 @@ func (s *Service) VerifyFinalizedConsistency(ctx context.Context, root []byte) e
|
||||
}
|
||||
|
||||
// This routine processes fork choice attestations from the pool to account for validator votes and fork choice.
|
||||
func (s *Service) processAttestationsRoutine(subscribedToStateEvents chan<- struct{}) {
|
||||
func (s *Service) spawnProcessAttestationsRoutine(stateFeed *event.Feed) {
|
||||
// Wait for state to be initialized.
|
||||
stateChannel := make(chan *feed.Event, 1)
|
||||
stateSub := s.cfg.StateNotifier.StateFeed().Subscribe(stateChannel)
|
||||
subscribedToStateEvents <- struct{}{}
|
||||
<-stateChannel
|
||||
stateSub.Unsubscribe()
|
||||
|
||||
if s.genesisTime.IsZero() {
|
||||
log.Warn("ProcessAttestations routine waiting for genesis time")
|
||||
for s.genesisTime.IsZero() {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
log.Warn("Genesis time received, now available to process attestations")
|
||||
}
|
||||
|
||||
st := slots.NewSlotTicker(s.genesisTime, params.BeaconConfig().SecondsPerSlot)
|
||||
for {
|
||||
stateSub := stateFeed.Subscribe(stateChannel)
|
||||
go func() {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
stateSub.Unsubscribe()
|
||||
return
|
||||
case <-st.C():
|
||||
// Continue when there's no fork choice attestation, there's nothing to process and update head.
|
||||
// This covers the condition when the node is still initial syncing to the head of the chain.
|
||||
if s.cfg.AttPool.ForkchoiceAttestationCount() == 0 {
|
||||
continue
|
||||
}
|
||||
s.processAttestations(s.ctx)
|
||||
case <-stateChannel:
|
||||
stateSub.Unsubscribe()
|
||||
break
|
||||
}
|
||||
|
||||
balances, err := s.justifiedBalances.get(s.ctx, bytesutil.ToBytes32(s.justifiedCheckpt.Root))
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get justified balances for root %v w/ error %s", s.justifiedCheckpt.Root, err)
|
||||
continue
|
||||
if s.genesisTime.IsZero() {
|
||||
log.Warn("ProcessAttestations routine waiting for genesis time")
|
||||
for s.genesisTime.IsZero() {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
if err := s.updateHead(s.ctx, balances); err != nil {
|
||||
log.Warnf("Resolving fork due to new attestation: %v", err)
|
||||
log.Warn("Genesis time received, now available to process attestations")
|
||||
}
|
||||
|
||||
st := slots.NewSlotTicker(s.genesisTime, params.BeaconConfig().SecondsPerSlot)
|
||||
for {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
case <-st.C():
|
||||
// Continue when there's no fork choice attestation, there's nothing to process and update head.
|
||||
// This covers the condition when the node is still initial syncing to the head of the chain.
|
||||
if s.cfg.AttPool.ForkchoiceAttestationCount() == 0 {
|
||||
continue
|
||||
}
|
||||
s.processAttestations(s.ctx)
|
||||
|
||||
balances, err := s.justifiedBalances.get(s.ctx, bytesutil.ToBytes32(s.justifiedCheckpt.Root))
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get justified balances for root %v w/ error %s", s.justifiedCheckpt.Root, err)
|
||||
continue
|
||||
}
|
||||
if err := s.updateHead(s.ctx, balances); err != nil {
|
||||
log.Warnf("Resolving fork due to new attestation: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// This processes fork choice attestations from the pool to account for validator votes and fork choice.
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/time"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
@@ -45,13 +46,14 @@ const headSyncMinEpochsAfterCheckpoint = 128
|
||||
// Service represents a service that handles the internal
|
||||
// logic of managing the full PoS beacon chain.
|
||||
type Service struct {
|
||||
cfg *config
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
genesisTime time.Time
|
||||
head *head
|
||||
headLock sync.RWMutex
|
||||
genesisRoot [32]byte
|
||||
cfg *config
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
genesisTime time.Time
|
||||
head *head
|
||||
headLock sync.RWMutex
|
||||
// originBlockRoot is the genesis root, or weak subjectivity checkpoint root, depending on how the node is initialized
|
||||
originBlockRoot [32]byte
|
||||
justifiedCheckpt *ethpb.Checkpoint
|
||||
prevJustifiedCheckpt *ethpb.Checkpoint
|
||||
bestJustifiedCheckpt *ethpb.Checkpoint
|
||||
@@ -120,179 +122,17 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
|
||||
// Start a blockchain service's main event loop.
|
||||
func (s *Service) Start() {
|
||||
beaconState := s.cfg.FinalizedStateAtStartUp
|
||||
saved := s.cfg.FinalizedStateAtStartUp
|
||||
|
||||
// Make sure that attestation processor is subscribed and ready for state initializing event.
|
||||
attestationProcessorSubscribed := make(chan struct{}, 1)
|
||||
|
||||
// If the chain has already been initialized, simply start the block processing routine.
|
||||
if beaconState != nil && !beaconState.IsNil() {
|
||||
log.Info("Blockchain data already exists in DB, initializing...")
|
||||
s.genesisTime = time.Unix(int64(beaconState.GenesisTime()), 0)
|
||||
s.cfg.AttService.SetGenesisTime(beaconState.GenesisTime())
|
||||
if err := s.initializeChainInfo(s.ctx); err != nil {
|
||||
log.Fatalf("Could not set up chain info: %v", err)
|
||||
if saved != nil && !saved.IsNil() {
|
||||
if err := s.startFromSavedState(saved); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// We start a counter to genesis, if needed.
|
||||
gState, err := s.cfg.BeaconDB.GenesisState(s.ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not retrieve genesis state: %v", err)
|
||||
}
|
||||
gRoot, err := gState.HashTreeRoot(s.ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not hash tree root genesis state: %v", err)
|
||||
}
|
||||
go slots.CountdownToGenesis(s.ctx, s.genesisTime, uint64(gState.NumValidators()), gRoot)
|
||||
|
||||
justifiedCheckpoint, err := s.cfg.BeaconDB.JustifiedCheckpoint(s.ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not get justified checkpoint: %v", err)
|
||||
}
|
||||
finalizedCheckpoint, err := s.cfg.BeaconDB.FinalizedCheckpoint(s.ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not get finalized checkpoint: %v", err)
|
||||
}
|
||||
|
||||
// Resume fork choice.
|
||||
s.justifiedCheckpt = ethpb.CopyCheckpoint(justifiedCheckpoint)
|
||||
s.prevJustifiedCheckpt = ethpb.CopyCheckpoint(justifiedCheckpoint)
|
||||
s.bestJustifiedCheckpt = ethpb.CopyCheckpoint(justifiedCheckpoint)
|
||||
s.finalizedCheckpt = ethpb.CopyCheckpoint(finalizedCheckpoint)
|
||||
s.prevFinalizedCheckpt = ethpb.CopyCheckpoint(finalizedCheckpoint)
|
||||
s.resumeForkChoice(justifiedCheckpoint, finalizedCheckpoint)
|
||||
|
||||
ss, err := slots.EpochStart(s.finalizedCheckpt.Epoch)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not get start slot of finalized epoch: %v", err)
|
||||
}
|
||||
h := s.headBlock().Block()
|
||||
if h.Slot() > ss {
|
||||
log.WithFields(logrus.Fields{
|
||||
"startSlot": ss,
|
||||
"endSlot": h.Slot(),
|
||||
}).Info("Loading blocks to fork choice store, this may take a while.")
|
||||
if err := s.fillInForkChoiceMissingBlocks(s.ctx, h, s.finalizedCheckpt, s.justifiedCheckpt); err != nil {
|
||||
log.Fatalf("Could not fill in fork choice store missing blocks: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// not attempting to save initial sync blocks here, because there shouldn't be until
|
||||
// after the statefeed.Initialized event is fired (below)
|
||||
if err := s.wsVerifier.VerifyWeakSubjectivity(s.ctx, s.finalizedCheckpt.Epoch); err != nil {
|
||||
// Exit run time if the node failed to verify weak subjectivity checkpoint.
|
||||
log.Fatalf("could not verify initial checkpoint provided for chain sync, with err=: %v", err)
|
||||
}
|
||||
|
||||
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.Initialized,
|
||||
Data: &statefeed.InitializedData{
|
||||
StartTime: s.genesisTime,
|
||||
GenesisValidatorsRoot: beaconState.GenesisValidatorRoot(),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
log.Info("Waiting to reach the validator deposit threshold to start the beacon chain...")
|
||||
if s.cfg.ChainStartFetcher == nil {
|
||||
log.Fatal("Not configured web3Service for POW chain")
|
||||
return // return need for TestStartUninitializedChainWithoutConfigPOWChain.
|
||||
if err := s.startFromPOWChain(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
go func() {
|
||||
stateChannel := make(chan *feed.Event, 1)
|
||||
stateSub := s.cfg.StateNotifier.StateFeed().Subscribe(stateChannel)
|
||||
defer stateSub.Unsubscribe()
|
||||
<-attestationProcessorSubscribed
|
||||
for {
|
||||
select {
|
||||
case event := <-stateChannel:
|
||||
if event.Type == statefeed.ChainStarted {
|
||||
data, ok := event.Data.(*statefeed.ChainStartedData)
|
||||
if !ok {
|
||||
log.Error("event data is not type *statefeed.ChainStartedData")
|
||||
return
|
||||
}
|
||||
log.WithField("starttime", data.StartTime).Debug("Received chain start event")
|
||||
s.processChainStartTime(s.ctx, data.StartTime)
|
||||
return
|
||||
}
|
||||
case <-s.ctx.Done():
|
||||
log.Debug("Context closed, exiting goroutine")
|
||||
return
|
||||
case err := <-stateSub.Err():
|
||||
log.WithError(err).Error("Subscription to state notifier failed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
go s.processAttestationsRoutine(attestationProcessorSubscribed)
|
||||
}
|
||||
|
||||
// processChainStartTime initializes a series of deposits from the ChainStart deposits in the eth1
|
||||
// deposit contract, initializes the beacon chain's state, and kicks off the beacon chain.
|
||||
func (s *Service) processChainStartTime(ctx context.Context, genesisTime time.Time) {
|
||||
preGenesisState := s.cfg.ChainStartFetcher.PreGenesisState()
|
||||
initializedState, err := s.initializeBeaconChain(ctx, genesisTime, preGenesisState, s.cfg.ChainStartFetcher.ChainStartEth1Data())
|
||||
if err != nil {
|
||||
log.Fatalf("Could not initialize beacon chain: %v", err)
|
||||
}
|
||||
// We start a counter to genesis, if needed.
|
||||
gRoot, err := initializedState.HashTreeRoot(s.ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not hash tree root genesis state: %v", err)
|
||||
}
|
||||
go slots.CountdownToGenesis(ctx, genesisTime, uint64(initializedState.NumValidators()), gRoot)
|
||||
|
||||
// We send out a state initialized event to the rest of the services
|
||||
// running in the beacon node.
|
||||
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.Initialized,
|
||||
Data: &statefeed.InitializedData{
|
||||
StartTime: genesisTime,
|
||||
GenesisValidatorsRoot: initializedState.GenesisValidatorRoot(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// initializes the state and genesis block of the beacon chain to persistent storage
|
||||
// based on a genesis timestamp value obtained from the ChainStart event emitted
|
||||
// by the ETH1.0 Deposit Contract and the POWChain service of the node.
|
||||
func (s *Service) initializeBeaconChain(
|
||||
ctx context.Context,
|
||||
genesisTime time.Time,
|
||||
preGenesisState state.BeaconState,
|
||||
eth1data *ethpb.Eth1Data) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon-chain.Service.initializeBeaconChain")
|
||||
defer span.End()
|
||||
s.genesisTime = genesisTime
|
||||
unixTime := uint64(genesisTime.Unix())
|
||||
|
||||
genesisState, err := transition.OptimizedGenesisBeaconState(unixTime, preGenesisState, eth1data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize genesis state")
|
||||
}
|
||||
|
||||
if err := s.saveGenesisData(ctx, genesisState); err != nil {
|
||||
return nil, errors.Wrap(err, "could not save genesis data")
|
||||
}
|
||||
|
||||
log.Info("Initialized beacon chain genesis state")
|
||||
|
||||
// Clear out all pre-genesis data now that the state is initialized.
|
||||
s.cfg.ChainStartFetcher.ClearPreGenesisData()
|
||||
|
||||
// Update committee shuffled indices for genesis epoch.
|
||||
if err := helpers.UpdateCommitteeCache(genesisState, 0 /* genesis epoch */); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := helpers.UpdateProposerIndicesInCache(ctx, genesisState); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.cfg.AttService.SetGenesisTime(genesisState.GenesisTime())
|
||||
|
||||
return genesisState, nil
|
||||
}
|
||||
|
||||
// Stop the blockchain service's main event loop and associated goroutines.
|
||||
@@ -312,7 +152,7 @@ func (s *Service) Stop() error {
|
||||
// Status always returns nil unless there is an error condition that causes
|
||||
// this service to be unhealthy.
|
||||
func (s *Service) Status() error {
|
||||
if s.genesisRoot == params.BeaconConfig().ZeroHash {
|
||||
if s.originBlockRoot == params.BeaconConfig().ZeroHash {
|
||||
return errors.New("genesis state has not been created")
|
||||
}
|
||||
if runtime.NumGoroutine() > s.cfg.MaxRoutines {
|
||||
@@ -321,61 +161,105 @@ func (s *Service) Status() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This gets called when beacon chain is first initialized to save genesis data (state, block, and more) in db.
|
||||
func (s *Service) saveGenesisData(ctx context.Context, genesisState state.BeaconState) error {
|
||||
if err := s.cfg.BeaconDB.SaveGenesisData(ctx, genesisState); err != nil {
|
||||
return errors.Wrap(err, "could not save genesis data")
|
||||
}
|
||||
genesisBlk, err := s.cfg.BeaconDB.GenesisBlock(ctx)
|
||||
if err != nil || genesisBlk == nil || genesisBlk.IsNil() {
|
||||
return fmt.Errorf("could not load genesis block: %v", err)
|
||||
}
|
||||
genesisBlkRoot, err := genesisBlk.Block().HashTreeRoot()
|
||||
func (s *Service) startFromSavedState(saved state.BeaconState) error {
|
||||
log.Info("Blockchain data already exists in DB, initializing...")
|
||||
s.genesisTime = time.Unix(int64(saved.GenesisTime()), 0)
|
||||
s.cfg.AttService.SetGenesisTime(saved.GenesisTime())
|
||||
|
||||
originRoot, err := s.originRootFromSavedState(s.ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get genesis block root")
|
||||
return err
|
||||
}
|
||||
s.originBlockRoot = originRoot
|
||||
|
||||
if err := s.initializeHeadFromDB(s.ctx); err != nil {
|
||||
return errors.Wrap(err, "could not set up chain info")
|
||||
}
|
||||
spawnCountdownIfPreGenesis(s.ctx, s.genesisTime, s.cfg.BeaconDB)
|
||||
|
||||
justified, err := s.cfg.BeaconDB.JustifiedCheckpoint(s.ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get justified checkpoint")
|
||||
}
|
||||
s.prevJustifiedCheckpt = ethpb.CopyCheckpoint(justified)
|
||||
s.bestJustifiedCheckpt = ethpb.CopyCheckpoint(justified)
|
||||
s.justifiedCheckpt = ethpb.CopyCheckpoint(justified)
|
||||
|
||||
finalized, err := s.cfg.BeaconDB.FinalizedCheckpoint(s.ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get finalized checkpoint")
|
||||
}
|
||||
s.prevFinalizedCheckpt = ethpb.CopyCheckpoint(finalized)
|
||||
s.finalizedCheckpt = ethpb.CopyCheckpoint(finalized)
|
||||
|
||||
store := protoarray.New(justified.Epoch, finalized.Epoch, bytesutil.ToBytes32(finalized.Root))
|
||||
s.cfg.ForkChoiceStore = store
|
||||
|
||||
ss, err := slots.EpochStart(s.finalizedCheckpt.Epoch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get start slot of finalized epoch")
|
||||
}
|
||||
h := s.headBlock().Block()
|
||||
if h.Slot() > ss {
|
||||
log.WithFields(logrus.Fields{
|
||||
"startSlot": ss,
|
||||
"endSlot": h.Slot(),
|
||||
}).Info("Loading blocks to fork choice store, this may take a while.")
|
||||
if err := s.fillInForkChoiceMissingBlocks(s.ctx, h, s.finalizedCheckpt, s.justifiedCheckpt); err != nil {
|
||||
return errors.Wrap(err, "could not fill in fork choice store missing blocks")
|
||||
}
|
||||
}
|
||||
|
||||
s.genesisRoot = genesisBlkRoot
|
||||
s.cfg.StateGen.SaveFinalizedState(0 /*slot*/, genesisBlkRoot, genesisState)
|
||||
|
||||
// Finalized checkpoint at genesis is a zero hash.
|
||||
genesisCheckpoint := genesisState.FinalizedCheckpoint()
|
||||
|
||||
s.justifiedCheckpt = ethpb.CopyCheckpoint(genesisCheckpoint)
|
||||
s.prevJustifiedCheckpt = ethpb.CopyCheckpoint(genesisCheckpoint)
|
||||
s.bestJustifiedCheckpt = ethpb.CopyCheckpoint(genesisCheckpoint)
|
||||
s.finalizedCheckpt = ethpb.CopyCheckpoint(genesisCheckpoint)
|
||||
s.prevFinalizedCheckpt = ethpb.CopyCheckpoint(genesisCheckpoint)
|
||||
|
||||
if err := s.cfg.ForkChoiceStore.ProcessBlock(ctx,
|
||||
genesisBlk.Block().Slot(),
|
||||
genesisBlkRoot,
|
||||
params.BeaconConfig().ZeroHash,
|
||||
[32]byte{},
|
||||
genesisCheckpoint.Epoch,
|
||||
genesisCheckpoint.Epoch); err != nil {
|
||||
log.Fatalf("Could not process genesis block for fork choice: %v", err)
|
||||
// not attempting to save initial sync blocks here, because there shouldn't be any until
|
||||
// after the statefeed.Initialized event is fired (below)
|
||||
if err := s.wsVerifier.VerifyWeakSubjectivity(s.ctx, s.finalizedCheckpt.Epoch); err != nil {
|
||||
// Exit run time if the node failed to verify weak subjectivity checkpoint.
|
||||
return errors.Wrap(err, "could not verify initial checkpoint provided for chain sync")
|
||||
}
|
||||
|
||||
s.setHead(genesisBlkRoot, genesisBlk, genesisState)
|
||||
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.Initialized,
|
||||
Data: &statefeed.InitializedData{
|
||||
StartTime: s.genesisTime,
|
||||
GenesisValidatorsRoot: saved.GenesisValidatorRoot(),
|
||||
},
|
||||
})
|
||||
|
||||
s.spawnProcessAttestationsRoutine(s.cfg.StateNotifier.StateFeed())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This gets called to initialize chain info variables using the finalized checkpoint stored in DB
|
||||
func (s *Service) initializeChainInfo(ctx context.Context) error {
|
||||
func (s *Service) originRootFromSavedState(ctx context.Context) ([32]byte, error) {
|
||||
// first check if we have started from checkpoint sync and have a root
|
||||
originRoot, err := s.cfg.BeaconDB.OriginBlockRoot(ctx)
|
||||
if err == nil {
|
||||
return originRoot, nil
|
||||
}
|
||||
if !errors.Is(err, db.ErrNotFound) {
|
||||
return originRoot, errors.Wrap(err, "could not retrieve checkpoint sync chain origin data from db")
|
||||
}
|
||||
|
||||
// we got here because OriginBlockRoot gave us an ErrNotFound. this means the node was started from a genesis state,
|
||||
// so we should have a value for GenesisBlock
|
||||
genesisBlock, err := s.cfg.BeaconDB.GenesisBlock(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get genesis block from db")
|
||||
return originRoot, errors.Wrap(err, "could not get genesis block from db")
|
||||
}
|
||||
if err := helpers.BeaconBlockIsNil(genesisBlock); err != nil {
|
||||
return err
|
||||
return originRoot, err
|
||||
}
|
||||
genesisBlkRoot, err := genesisBlock.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get signing root of genesis block")
|
||||
return genesisBlkRoot, errors.Wrap(err, "could not get signing root of genesis block")
|
||||
}
|
||||
s.genesisRoot = genesisBlkRoot
|
||||
return genesisBlkRoot, nil
|
||||
}
|
||||
|
||||
// initializeHeadFromDB uses the finalized checkpoint and head block found in the database to set the current head
|
||||
// note that this may block until stategen replays blocks between the finalized and head blocks
|
||||
// if the head sync flag was specified and the gap between the finalized and head blocks is at least 128 epochs long
|
||||
func (s *Service) initializeHeadFromDB(ctx context.Context) error {
|
||||
finalized, err := s.cfg.BeaconDB.FinalizedCheckpoint(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get finalized checkpoint from db")
|
||||
@@ -442,11 +326,146 @@ func (s *Service) initializeChainInfo(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is called when a client starts from non-genesis slot. This passes last justified and finalized
|
||||
// information to fork choice service to initializes fork choice store.
|
||||
func (s *Service) resumeForkChoice(justifiedCheckpoint, finalizedCheckpoint *ethpb.Checkpoint) {
|
||||
store := protoarray.New(justifiedCheckpoint.Epoch, finalizedCheckpoint.Epoch, bytesutil.ToBytes32(finalizedCheckpoint.Root))
|
||||
s.cfg.ForkChoiceStore = store
|
||||
func (s *Service) startFromPOWChain() error {
|
||||
log.Info("Waiting to reach the validator deposit threshold to start the beacon chain...")
|
||||
if s.cfg.ChainStartFetcher == nil {
|
||||
return errors.New("not configured web3Service for POW chain")
|
||||
}
|
||||
go func() {
|
||||
stateChannel := make(chan *feed.Event, 1)
|
||||
stateSub := s.cfg.StateNotifier.StateFeed().Subscribe(stateChannel)
|
||||
defer stateSub.Unsubscribe()
|
||||
s.spawnProcessAttestationsRoutine(s.cfg.StateNotifier.StateFeed())
|
||||
for {
|
||||
select {
|
||||
case event := <-stateChannel:
|
||||
if event.Type == statefeed.ChainStarted {
|
||||
data, ok := event.Data.(*statefeed.ChainStartedData)
|
||||
if !ok {
|
||||
log.Error("event data is not type *statefeed.ChainStartedData")
|
||||
return
|
||||
}
|
||||
log.WithField("starttime", data.StartTime).Debug("Received chain start event")
|
||||
s.onPowchainStart(s.ctx, data.StartTime)
|
||||
return
|
||||
}
|
||||
case <-s.ctx.Done():
|
||||
log.Debug("Context closed, exiting goroutine")
|
||||
return
|
||||
case err := <-stateSub.Err():
|
||||
log.WithError(err).Error("Subscription to state notifier failed")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// onPowchainStart initializes a series of deposits from the ChainStart deposits in the eth1
|
||||
// deposit contract, initializes the beacon chain's state, and kicks off the beacon chain.
|
||||
func (s *Service) onPowchainStart(ctx context.Context, genesisTime time.Time) {
|
||||
preGenesisState := s.cfg.ChainStartFetcher.PreGenesisState()
|
||||
initializedState, err := s.initializeBeaconChain(ctx, genesisTime, preGenesisState, s.cfg.ChainStartFetcher.ChainStartEth1Data())
|
||||
if err != nil {
|
||||
log.Fatalf("Could not initialize beacon chain: %v", err)
|
||||
}
|
||||
// We start a counter to genesis, if needed.
|
||||
gRoot, err := initializedState.HashTreeRoot(s.ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not hash tree root genesis state: %v", err)
|
||||
}
|
||||
go slots.CountdownToGenesis(ctx, genesisTime, uint64(initializedState.NumValidators()), gRoot)
|
||||
|
||||
// We send out a state initialized event to the rest of the services
|
||||
// running in the beacon node.
|
||||
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.Initialized,
|
||||
Data: &statefeed.InitializedData{
|
||||
StartTime: genesisTime,
|
||||
GenesisValidatorsRoot: initializedState.GenesisValidatorRoot(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// initializes the state and genesis block of the beacon chain to persistent storage
|
||||
// based on a genesis timestamp value obtained from the ChainStart event emitted
|
||||
// by the ETH1.0 Deposit Contract and the POWChain service of the node.
|
||||
func (s *Service) initializeBeaconChain(
|
||||
ctx context.Context,
|
||||
genesisTime time.Time,
|
||||
preGenesisState state.BeaconState,
|
||||
eth1data *ethpb.Eth1Data) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon-chain.Service.initializeBeaconChain")
|
||||
defer span.End()
|
||||
s.genesisTime = genesisTime
|
||||
unixTime := uint64(genesisTime.Unix())
|
||||
|
||||
genesisState, err := transition.OptimizedGenesisBeaconState(unixTime, preGenesisState, eth1data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize genesis state")
|
||||
}
|
||||
|
||||
if err := s.saveGenesisData(ctx, genesisState); err != nil {
|
||||
return nil, errors.Wrap(err, "could not save genesis data")
|
||||
}
|
||||
|
||||
log.Info("Initialized beacon chain genesis state")
|
||||
|
||||
// Clear out all pre-genesis data now that the state is initialized.
|
||||
s.cfg.ChainStartFetcher.ClearPreGenesisData()
|
||||
|
||||
// Update committee shuffled indices for genesis epoch.
|
||||
if err := helpers.UpdateCommitteeCache(genesisState, 0 /* genesis epoch */); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := helpers.UpdateProposerIndicesInCache(ctx, genesisState); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.cfg.AttService.SetGenesisTime(genesisState.GenesisTime())
|
||||
|
||||
return genesisState, nil
|
||||
}
|
||||
|
||||
// This gets called when beacon chain is first initialized to save genesis data (state, block, and more) in db.
|
||||
func (s *Service) saveGenesisData(ctx context.Context, genesisState state.BeaconState) error {
|
||||
if err := s.cfg.BeaconDB.SaveGenesisData(ctx, genesisState); err != nil {
|
||||
return errors.Wrap(err, "could not save genesis data")
|
||||
}
|
||||
genesisBlk, err := s.cfg.BeaconDB.GenesisBlock(ctx)
|
||||
if err != nil || genesisBlk == nil || genesisBlk.IsNil() {
|
||||
return fmt.Errorf("could not load genesis block: %v", err)
|
||||
}
|
||||
genesisBlkRoot, err := genesisBlk.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get genesis block root")
|
||||
}
|
||||
|
||||
s.originBlockRoot = genesisBlkRoot
|
||||
s.cfg.StateGen.SaveFinalizedState(0 /*slot*/, genesisBlkRoot, genesisState)
|
||||
|
||||
// Finalized checkpoint at genesis is a zero hash.
|
||||
genesisCheckpoint := genesisState.FinalizedCheckpoint()
|
||||
|
||||
s.justifiedCheckpt = ethpb.CopyCheckpoint(genesisCheckpoint)
|
||||
s.prevJustifiedCheckpt = ethpb.CopyCheckpoint(genesisCheckpoint)
|
||||
s.bestJustifiedCheckpt = ethpb.CopyCheckpoint(genesisCheckpoint)
|
||||
s.finalizedCheckpt = ethpb.CopyCheckpoint(genesisCheckpoint)
|
||||
s.prevFinalizedCheckpt = ethpb.CopyCheckpoint(genesisCheckpoint)
|
||||
|
||||
if err := s.cfg.ForkChoiceStore.ProcessBlock(ctx,
|
||||
genesisBlk.Block().Slot(),
|
||||
genesisBlkRoot,
|
||||
params.BeaconConfig().ZeroHash,
|
||||
[32]byte{},
|
||||
genesisCheckpoint.Epoch,
|
||||
genesisCheckpoint.Epoch); err != nil {
|
||||
log.Fatalf("Could not process genesis block for fork choice: %v", err)
|
||||
}
|
||||
|
||||
s.setHead(genesisBlkRoot, genesisBlk, genesisState)
|
||||
return nil
|
||||
}
|
||||
|
||||
// This returns true if block has been processed before. Two ways to verify the block has been processed:
|
||||
@@ -460,3 +479,20 @@ func (s *Service) hasBlock(ctx context.Context, root [32]byte) bool {
|
||||
|
||||
return s.cfg.BeaconDB.HasBlock(ctx, root)
|
||||
}
|
||||
|
||||
func spawnCountdownIfPreGenesis(ctx context.Context, genesisTime time.Time, db db.HeadAccessDatabase) {
|
||||
currentTime := prysmTime.Now()
|
||||
if currentTime.After(genesisTime) {
|
||||
return
|
||||
}
|
||||
|
||||
gState, err := db.GenesisState(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not retrieve genesis state: %v", err)
|
||||
}
|
||||
gRoot, err := gState.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not hash tree root genesis state: %v", err)
|
||||
}
|
||||
go slots.CountdownToGenesis(ctx, genesisTime, uint64(gState.NumValidators()), gRoot)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/prysmaticlabs/prysm/async/event"
|
||||
mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache"
|
||||
b "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
|
||||
@@ -283,9 +284,11 @@ func TestChainService_InitializeChainInfo(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, genesisRoot))
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(headBlock)))
|
||||
require.NoError(t, beaconDB.SaveFinalizedCheckpoint(ctx, ðpb.Checkpoint{Epoch: slots.ToEpoch(finalizedSlot), Root: headRoot[:]}))
|
||||
c := &Service{cfg: &config{BeaconDB: beaconDB, StateGen: stategen.New(beaconDB)}}
|
||||
c.cfg.FinalizedStateAtStartUp = headState
|
||||
require.NoError(t, c.initializeChainInfo(ctx))
|
||||
attSrv, err := attestations.NewService(ctx, &attestations.Config{})
|
||||
require.NoError(t, err)
|
||||
c, err := NewService(ctx, WithDatabase(beaconDB), WithStateGen(stategen.New(beaconDB)), WithAttestationService(attSrv), WithStateNotifier(&mock.MockStateNotifier{}), WithFinalizedStateAtStartUp(headState))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.startFromSavedState(headState))
|
||||
headBlk, err := c.HeadBlock(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, headBlock, headBlk.Proto(), "Head block incorrect")
|
||||
@@ -298,7 +301,7 @@ func TestChainService_InitializeChainInfo(t *testing.T) {
|
||||
if !bytes.Equal(headRoot[:], r) {
|
||||
t.Error("head slot incorrect")
|
||||
}
|
||||
assert.Equal(t, genesisRoot, c.genesisRoot, "Genesis block root incorrect")
|
||||
assert.Equal(t, genesisRoot, c.originBlockRoot, "Genesis block root incorrect")
|
||||
}
|
||||
|
||||
func TestChainService_InitializeChainInfo_SetHeadAtGenesis(t *testing.T) {
|
||||
@@ -324,12 +327,15 @@ func TestChainService_InitializeChainInfo_SetHeadAtGenesis(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, headRoot))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, genesisRoot))
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(headBlock)))
|
||||
c := &Service{cfg: &config{BeaconDB: beaconDB, StateGen: stategen.New(beaconDB)}}
|
||||
require.NoError(t, c.initializeChainInfo(ctx))
|
||||
attSrv, err := attestations.NewService(ctx, &attestations.Config{})
|
||||
require.NoError(t, err)
|
||||
c, err := NewService(ctx, WithDatabase(beaconDB), WithStateGen(stategen.New(beaconDB)), WithAttestationService(attSrv), WithStateNotifier(&mock.MockStateNotifier{}))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.startFromSavedState(headState))
|
||||
s, err := c.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.DeepSSZEqual(t, headState.InnerStateUnsafe(), s.InnerStateUnsafe(), "Head state incorrect")
|
||||
assert.Equal(t, genesisRoot, c.genesisRoot, "Genesis block root incorrect")
|
||||
assert.Equal(t, genesisRoot, c.originBlockRoot, "Genesis block root incorrect")
|
||||
assert.DeepEqual(t, genesis, c.head.block.Proto())
|
||||
}
|
||||
|
||||
@@ -381,13 +387,15 @@ func TestChainService_InitializeChainInfo_HeadSync(t *testing.T) {
|
||||
Root: finalizedRoot[:],
|
||||
}))
|
||||
|
||||
c := &Service{cfg: &config{BeaconDB: beaconDB, StateGen: stategen.New(beaconDB)}}
|
||||
c.cfg.FinalizedStateAtStartUp = headState
|
||||
require.NoError(t, c.initializeChainInfo(ctx))
|
||||
attSrv, err := attestations.NewService(ctx, &attestations.Config{})
|
||||
require.NoError(t, err)
|
||||
c, err := NewService(ctx, WithDatabase(beaconDB), WithStateGen(stategen.New(beaconDB)), WithAttestationService(attSrv), WithStateNotifier(&mock.MockStateNotifier{}), WithFinalizedStateAtStartUp(headState))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.startFromSavedState(headState))
|
||||
s, err := c.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.DeepSSZEqual(t, headState.InnerStateUnsafe(), s.InnerStateUnsafe(), "Head state incorrect")
|
||||
assert.Equal(t, genesisRoot, c.genesisRoot, "Genesis block root incorrect")
|
||||
assert.Equal(t, genesisRoot, c.originBlockRoot, "Genesis block root incorrect")
|
||||
// Since head sync is not triggered, chain is initialized to the last finalization checkpoint.
|
||||
assert.DeepEqual(t, finalizedBlock, c.head.block.Proto())
|
||||
assert.LogsContain(t, hook, "resetting head from the checkpoint ('--head-sync' flag is ignored)")
|
||||
@@ -404,11 +412,11 @@ func TestChainService_InitializeChainInfo_HeadSync(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveHeadBlockRoot(ctx, headRoot))
|
||||
|
||||
hook.Reset()
|
||||
require.NoError(t, c.initializeChainInfo(ctx))
|
||||
require.NoError(t, c.initializeHeadFromDB(ctx))
|
||||
s, err = c.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.DeepSSZEqual(t, headState.InnerStateUnsafe(), s.InnerStateUnsafe(), "Head state incorrect")
|
||||
assert.Equal(t, genesisRoot, c.genesisRoot, "Genesis block root incorrect")
|
||||
assert.Equal(t, genesisRoot, c.originBlockRoot, "Genesis block root incorrect")
|
||||
// Head slot is far beyond the latest finalized checkpoint, head sync is triggered.
|
||||
assert.DeepEqual(t, headBlock, c.head.block.Proto())
|
||||
assert.LogsContain(t, hook, "Regenerating state from the last checkpoint at slot 225")
|
||||
@@ -478,7 +486,7 @@ func TestProcessChainStartTime_ReceivedFeed(t *testing.T) {
|
||||
stateChannel := make(chan *feed.Event, 1)
|
||||
stateSub := service.cfg.StateNotifier.StateFeed().Subscribe(stateChannel)
|
||||
defer stateSub.Unsubscribe()
|
||||
service.processChainStartTime(context.Background(), time.Now())
|
||||
service.onPowchainStart(context.Background(), time.Now())
|
||||
|
||||
stateEvent := <-stateChannel
|
||||
require.Equal(t, int(stateEvent.Type), statefeed.Initialized)
|
||||
|
||||
@@ -36,6 +36,7 @@ func NewWeakSubjectivityVerifier(wsc *ethpb.Checkpoint, db weakSubjectivityDB) (
|
||||
// per 7342, a nil checkpoint, zero-root or zero-epoch should all fail validation
|
||||
// and return an error instead of creating a WeakSubjectivityVerifier that permits any chain history.
|
||||
if wsc == nil || len(wsc.Root) == 0 || wsc.Epoch == 0 {
|
||||
log.Warn("no valid weak subjectivity checkpoint specified, running without weak subjectivity verification")
|
||||
return &WeakSubjectivityVerifier{
|
||||
enabled: false,
|
||||
}, nil
|
||||
@@ -81,6 +82,7 @@ func (v *WeakSubjectivityVerifier) VerifyWeakSubjectivity(ctx context.Context, f
|
||||
}
|
||||
filter := filters.NewFilter().SetStartSlot(v.slot).SetEndSlot(v.slot + params.BeaconConfig().SlotsPerEpoch)
|
||||
// A node should have the weak subjectivity block corresponds to the correct epoch in the DB.
|
||||
log.Infof("searching block roots index for weak subjectivity root=%#x", v.root)
|
||||
roots, err := v.db.BlockRoots(ctx, filter)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error while retrieving block roots to verify weak subjectivity")
|
||||
|
||||
@@ -2,12 +2,13 @@ package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
@@ -18,52 +19,55 @@ func TestService_VerifyWeakSubjectivityRoot(t *testing.T) {
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = 32
|
||||
b.Block.Slot = 1792480
|
||||
blockEpoch := slots.ToEpoch(b.Block.Slot)
|
||||
require.NoError(t, beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(b)))
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
tests := []struct {
|
||||
wsVerified bool
|
||||
disabled bool
|
||||
wantErr error
|
||||
checkpt *ethpb.Checkpoint
|
||||
finalizedEpoch types.Epoch
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "nil root and epoch",
|
||||
},
|
||||
{
|
||||
name: "already verified",
|
||||
checkpt: ðpb.Checkpoint{Epoch: 2},
|
||||
finalizedEpoch: 2,
|
||||
wsVerified: true,
|
||||
name: "nil root and epoch",
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
name: "not yet to verify, ws epoch higher than finalized epoch",
|
||||
checkpt: ðpb.Checkpoint{Epoch: 2},
|
||||
finalizedEpoch: 1,
|
||||
checkpt: ðpb.Checkpoint{Root: bytesutil.PadTo([]byte{'a'}, 32), Epoch: blockEpoch},
|
||||
finalizedEpoch: blockEpoch - 1,
|
||||
},
|
||||
{
|
||||
name: "can't find the block in DB",
|
||||
checkpt: ðpb.Checkpoint{Root: bytesutil.PadTo([]byte{'a'}, 32), Epoch: 1},
|
||||
finalizedEpoch: 3,
|
||||
finalizedEpoch: blockEpoch + 1,
|
||||
wantErr: errWSBlockNotFound,
|
||||
},
|
||||
{
|
||||
name: "can't find the block corresponds to ws epoch in DB",
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: 2}, // Root belongs in epoch 1.
|
||||
finalizedEpoch: 3,
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: blockEpoch - 2}, // Root belongs in epoch 1.
|
||||
finalizedEpoch: blockEpoch - 1,
|
||||
wantErr: errWSBlockNotFoundInEpoch,
|
||||
},
|
||||
{
|
||||
name: "can verify and pass",
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: 1},
|
||||
finalizedEpoch: 3,
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: blockEpoch},
|
||||
finalizedEpoch: blockEpoch + 1,
|
||||
},
|
||||
{
|
||||
name: "equal epoch",
|
||||
checkpt: ðpb.Checkpoint{Root: r[:], Epoch: blockEpoch},
|
||||
finalizedEpoch: blockEpoch,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
wv, err := NewWeakSubjectivityVerifier(tt.checkpt, beaconDB)
|
||||
require.Equal(t, !tt.disabled, wv.enabled)
|
||||
require.NoError(t, err)
|
||||
s := &Service{
|
||||
cfg: &config{BeaconDB: beaconDB, WeakSubjectivityCheckpt: tt.checkpt},
|
||||
|
||||
@@ -5,6 +5,7 @@ go_library(
|
||||
srcs = [
|
||||
"alias.go",
|
||||
"db.go",
|
||||
"errors.go",
|
||||
"log.go",
|
||||
"restore.go",
|
||||
],
|
||||
|
||||
9
beacon-chain/db/errors.go
Normal file
9
beacon-chain/db/errors.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package db
|
||||
|
||||
import "github.com/prysmaticlabs/prysm/beacon-chain/db/kv"
|
||||
|
||||
// ErrNotFound can be used to determine if an error from a method in the database package
|
||||
// represents a "not found" error. These often require different handling than a low-level
|
||||
// i/o error. This variable copies the value in the kv package to the same scope as the Database interfaces,
|
||||
// so that it is available to code paths that do not interact directly with the kv package.
|
||||
var ErrNotFound = kv.ErrNotFound
|
||||
@@ -48,6 +48,9 @@ type ReadOnlyDatabase interface {
|
||||
DepositContractAddress(ctx context.Context) ([]byte, error)
|
||||
// Powchain operations.
|
||||
PowchainData(ctx context.Context) (*ethpb.ETH1ChainData, error)
|
||||
|
||||
// origin checkpoint sync support
|
||||
OriginBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
}
|
||||
|
||||
// NoHeadAccessDatabase defines a struct without access to chain head data.
|
||||
@@ -90,6 +93,9 @@ type HeadAccessDatabase interface {
|
||||
LoadGenesis(ctx context.Context, r io.Reader) error
|
||||
SaveGenesisData(ctx context.Context, state state.BeaconState) error
|
||||
EnsureEmbeddedGenesis(ctx context.Context) error
|
||||
|
||||
// initialization method needed for origin checkpoint sync
|
||||
SaveOrigin(ctx context.Context, state io.Reader, block io.Reader) error
|
||||
}
|
||||
|
||||
// SlasherDatabase interface for persisting data related to detecting slashable offenses on Ethereum.
|
||||
|
||||
@@ -9,6 +9,7 @@ go_library(
|
||||
"checkpoint.go",
|
||||
"deposit_contract.go",
|
||||
"encoding.go",
|
||||
"error.go",
|
||||
"finalized_block_roots.go",
|
||||
"genesis.go",
|
||||
"key.go",
|
||||
@@ -24,6 +25,7 @@ go_library(
|
||||
"state_summary.go",
|
||||
"state_summary_cache.go",
|
||||
"utils.go",
|
||||
"wss.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/db/kv",
|
||||
visibility = [
|
||||
@@ -51,6 +53,7 @@ go_library(
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"//proto/prysm/v1alpha1/wrapper:go_default_library",
|
||||
"//proto/sniff:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_dgraph_io_ristretto//:go_default_library",
|
||||
@@ -81,6 +84,7 @@ go_test(
|
||||
"checkpoint_test.go",
|
||||
"deposit_contract_test.go",
|
||||
"encoding_test.go",
|
||||
"error_test.go",
|
||||
"finalized_block_roots_test.go",
|
||||
"genesis_test.go",
|
||||
"init_test.go",
|
||||
|
||||
@@ -46,6 +46,28 @@ func (s *Store) Block(ctx context.Context, blockRoot [32]byte) (block.SignedBeac
|
||||
return blk, err
|
||||
}
|
||||
|
||||
// OriginBlockRoot returns the value written to the db in SaveOriginBlockRoot
|
||||
// This is the root of a finalized block within the weak subjectivity period
|
||||
// at the time the chain was started, used to initialize the database and chain
|
||||
// without syncing from genesis.
|
||||
func (s *Store) OriginBlockRoot(ctx context.Context) ([32]byte, error) {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.OriginBlockRoot")
|
||||
defer span.End()
|
||||
|
||||
var root [32]byte
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(blocksBucket)
|
||||
rootSlice := bkt.Get(originBlockRootKey)
|
||||
if rootSlice == nil {
|
||||
return ErrNotFoundOriginBlockRoot
|
||||
}
|
||||
copy(root[:], rootSlice)
|
||||
return nil
|
||||
})
|
||||
|
||||
return root, err
|
||||
}
|
||||
|
||||
// HeadBlock returns the latest canonical block in the Ethereum Beacon Chain.
|
||||
func (s *Store) HeadBlock(ctx context.Context) (block.SignedBeaconBlock, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "BeaconDB.HeadBlock")
|
||||
@@ -336,6 +358,19 @@ func (s *Store) SaveGenesisBlockRoot(ctx context.Context, blockRoot [32]byte) er
|
||||
})
|
||||
}
|
||||
|
||||
// SaveOriginBlockRoot is used to keep track of the block root used for origin sync.
|
||||
// This should be a finalized block from within the current weak subjectivity period.
|
||||
// This value is used by a running beacon chain node to locate the state at the beginning
|
||||
// of the chain history, in places where genesis would typically be used.
|
||||
func (s *Store) SaveOriginBlockRoot(ctx context.Context, blockRoot [32]byte) error {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.SaveOriginBlockRoot")
|
||||
defer span.End()
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(blocksBucket)
|
||||
return bucket.Put(originBlockRootKey, blockRoot[:])
|
||||
})
|
||||
}
|
||||
|
||||
// HighestSlotBlocksBelow returns the block with the highest slot below the input slot from the db.
|
||||
func (s *Store) HighestSlotBlocksBelow(ctx context.Context, slot types.Slot) ([]block.SignedBeaconBlock, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "BeaconDB.HighestSlotBlocksBelow")
|
||||
|
||||
46
beacon-chain/db/kv/error.go
Normal file
46
beacon-chain/db/kv/error.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package kv
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrNotFound can be used directly, or as a wrapped DBError, whenever a db method needs to
|
||||
// indicate that a value couldn't be found.
|
||||
var ErrNotFound = errors.New("not found in db")
|
||||
|
||||
// ErrNotFoundOriginBlockRoot is an error specifically for the origin block root getter
|
||||
var ErrNotFoundOriginBlockRoot = WrapDBError(ErrNotFound, "OriginBlockRoot")
|
||||
|
||||
// WrapDBError wraps an error in a DBError. See commentary on DBError for more context.
|
||||
func WrapDBError(e error, outer string) error {
|
||||
return DBError{
|
||||
Wraps: e,
|
||||
Outer: errors.New(outer),
|
||||
}
|
||||
}
|
||||
|
||||
// DBError implements the Error method so that it can be asserted as an error.
|
||||
// The Unwrap method supports error wrapping, enabling it to be used with errors.Is/As.
|
||||
// The primary use case is to make it simple for database methods to return errors
|
||||
// that wrap ErrNotFound, allowing calling code to check for "not found" errors
|
||||
// like: `error.Is(err, ErrNotFound)`. This is intended to improve error handling
|
||||
// in db lookup methods that need to differentiate between a missing value and some
|
||||
// other database error. for more background see:
|
||||
// https://go.dev/blog/go1.13-errors
|
||||
type DBError struct {
|
||||
Wraps error
|
||||
Outer error
|
||||
}
|
||||
|
||||
// Error satisfies the error interface, so that DBErrors can be used anywhere that
|
||||
// expects an `error`.
|
||||
func (e DBError) Error() string {
|
||||
es := e.Outer.Error()
|
||||
if e.Wraps != nil {
|
||||
es += ": " + e.Wraps.Error()
|
||||
}
|
||||
return es
|
||||
}
|
||||
|
||||
// Unwrap is used by the errors package Is and As methods.
|
||||
func (e DBError) Unwrap() error {
|
||||
return e.Wraps
|
||||
}
|
||||
24
beacon-chain/db/kv/error_test.go
Normal file
24
beacon-chain/db/kv/error_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package kv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWrappedSentinelError(t *testing.T) {
|
||||
e := ErrNotFoundOriginBlockRoot
|
||||
if !errors.Is(e, ErrNotFoundOriginBlockRoot) {
|
||||
t.Error("expected that a copy of ErrNotFoundOriginBlockRoot would have an is-a relationship")
|
||||
}
|
||||
|
||||
outer := errors.New("wrapped error")
|
||||
e2 := DBError{Wraps: ErrNotFoundOriginBlockRoot, Outer: outer}
|
||||
if !errors.Is(e2, ErrNotFoundOriginBlockRoot) {
|
||||
t.Error("expected that errors.Is would know DBError wraps ErrNotFoundOriginBlockRoot")
|
||||
}
|
||||
|
||||
// test that the innermost not found error is detected
|
||||
if !errors.Is(e2, ErrNotFound) {
|
||||
t.Error("expected that errors.Is would know ErrNotFoundOriginBlockRoot wraps ErrNotFound")
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ func (s *Store) updateFinalizedBlockRoots(ctx context.Context, tx *bolt.Tx, chec
|
||||
root := checkpoint.Root
|
||||
var previousRoot []byte
|
||||
genesisRoot := tx.Bucket(blocksBucket).Get(genesisBlockRootKey)
|
||||
initCheckpointRoot := tx.Bucket(blocksBucket).Get(originBlockRootKey)
|
||||
|
||||
// De-index recent finalized block roots, to be re-indexed.
|
||||
previousFinalizedCheckpoint := ðpb.Checkpoint{}
|
||||
@@ -74,7 +75,7 @@ func (s *Store) updateFinalizedBlockRoots(ctx context.Context, tx *bolt.Tx, chec
|
||||
// Walk up the ancestry chain until we reach a block root present in the finalized block roots
|
||||
// index bucket or genesis block root.
|
||||
for {
|
||||
if bytes.Equal(root, genesisRoot) {
|
||||
if bytes.Equal(root, genesisRoot) || bytes.Equal(root, initCheckpointRoot) {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ var (
|
||||
// Objects that are only compatible with specific forks should be prefixed with such keys.
|
||||
altairKey = []byte("altair")
|
||||
mergeKey = []byte("merge")
|
||||
// block root included in the beacon state used by weak subjectivity initial sync
|
||||
originBlockRootKey = []byte("origin-block-root")
|
||||
|
||||
// Deprecated: This index key was migrated in PR 6461. Do not use, except for migrations.
|
||||
lastArchivedIndexKey = []byte("last-archived")
|
||||
|
||||
97
beacon-chain/db/kv/wss.go
Normal file
97
beacon-chain/db/kv/wss.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package kv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/sniff"
|
||||
)
|
||||
|
||||
// SaveOrigin loads an ssz serialized Block & BeaconState from an io.Reader
|
||||
// (ex: an open file) prepares the database so that the beacon node can begin
|
||||
// syncing, using the provided values as their point of origin. This is an alternative
|
||||
// to syncing from genesis, and should only be run on an empty database.
|
||||
func (s *Store) SaveOrigin(ctx context.Context, stateReader, blockReader io.Reader) error {
|
||||
// unmarshal both block and state before trying to save anything
|
||||
// so that we fail early if there is any issue with the ssz data
|
||||
sb, err := ioutil.ReadAll(stateReader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not read origin state bytes from reader")
|
||||
}
|
||||
|
||||
cf, err := sniff.ConfigForkForState(sb)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not sniff config+fork for origin state bytes")
|
||||
}
|
||||
state, err := sniff.BeaconStateForConfigFork(sb, cf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize origin state w/ bytes + config+fork")
|
||||
}
|
||||
|
||||
bb, err := ioutil.ReadAll(blockReader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not read origin block bytes from reader")
|
||||
}
|
||||
wblk, err := sniff.BlockForConfigFork(bb, cf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to initialize origin block w/ bytes + config+fork")
|
||||
}
|
||||
blk := wblk.Block()
|
||||
|
||||
// save block
|
||||
blockRoot, err := blk.HashTreeRoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute HashTreeRoot of checkpoint block")
|
||||
}
|
||||
log.Infof("saving checkpoint block to db, w/ root=%#x", blockRoot)
|
||||
if err := s.SaveBlock(ctx, wblk); err != nil {
|
||||
return errors.Wrap(err, "could not save checkpoint block")
|
||||
}
|
||||
|
||||
// save state
|
||||
log.Infof("calling SaveState w/ blockRoot=%x", blockRoot)
|
||||
if err = s.SaveState(ctx, state, blockRoot); err != nil {
|
||||
return errors.Wrap(err, "could not save state")
|
||||
}
|
||||
if err = s.SaveStateSummary(ctx, ðpb.StateSummary{
|
||||
Slot: state.Slot(),
|
||||
Root: blockRoot[:],
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "could not save state summary")
|
||||
}
|
||||
|
||||
// save origin block root in special key, to be used when the canonical
|
||||
// origin (start of chain, ie alternative to genesis) block or state is needed
|
||||
if err = s.SaveOriginBlockRoot(ctx, blockRoot); err != nil {
|
||||
return errors.Wrap(err, "could not save origin block root")
|
||||
}
|
||||
|
||||
// mark block as head of chain, so that processing will pick up from this point
|
||||
if err = s.SaveHeadBlockRoot(ctx, blockRoot); err != nil {
|
||||
return errors.Wrap(err, "could not save head block root")
|
||||
}
|
||||
|
||||
// rebuild the checkpoint from the block
|
||||
// use it to mark the block as justified and finalized
|
||||
slotEpoch, err := blk.Slot().SafeDivSlot(params.BeaconConfig().SlotsPerEpoch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chkpt := ðpb.Checkpoint{
|
||||
Epoch: types.Epoch(slotEpoch),
|
||||
Root: blockRoot[:],
|
||||
}
|
||||
if err = s.SaveJustifiedCheckpoint(ctx, chkpt); err != nil {
|
||||
return errors.Wrap(err, "could not mark checkpoint sync block as justified")
|
||||
}
|
||||
if err = s.SaveFinalizedCheckpoint(ctx, chkpt); err != nil {
|
||||
return errors.Wrap(err, "could not mark checkpoint sync block as finalized")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -40,6 +40,7 @@ go_library(
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//beacon-chain/sync/checkpoint:go_default_library",
|
||||
"//beacon-chain/sync/initial-sync:go_default_library",
|
||||
"//cmd:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/sync/checkpoint"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pkg/errors"
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
@@ -100,6 +102,8 @@ type BeaconNode struct {
|
||||
slasherAttestationsFeed *event.Feed
|
||||
finalizedStateAtStartUp state.BeaconState
|
||||
serviceFlagOpts *serviceFlagOpts
|
||||
blockchainFlagOpts []blockchain.Option
|
||||
CheckpointInitializer *checkpoint.Initializer
|
||||
}
|
||||
|
||||
// New creates a new node instance, sets up configuration options, and registers
|
||||
@@ -219,7 +223,7 @@ func New(cliCtx *cli.Context, opts ...Option) (*BeaconNode, error) {
|
||||
|
||||
// db.DatabasePath is the path to the containing directory
|
||||
// db.NewDBFilename expands that to the canonical full path using
|
||||
// the same constuction as NewDB()
|
||||
// the same construction as NewDB()
|
||||
c, err := newBeaconNodePromCollector(db.NewDBFilename(beacon.db.DatabasePath()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -373,6 +377,13 @@ func (b *BeaconNode) startDB(cliCtx *cli.Context, depositAddress string) error {
|
||||
if err := b.db.EnsureEmbeddedGenesis(b.ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.CheckpointInitializer != nil {
|
||||
if err := b.CheckpointInitializer.Initialize(b.ctx, d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
knownContract, err := b.db.DepositContractAddress(b.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/api/gateway/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/api/grpc"
|
||||
@@ -21,6 +22,7 @@ type sszConfig struct {
|
||||
sszPath string
|
||||
fileName string
|
||||
responseJson sszResponseJson
|
||||
clientOpts []apimiddleware.ClientOption
|
||||
}
|
||||
|
||||
func handleGetBeaconStateSSZ(m *apimiddleware.ApiProxyMiddleware, endpoint apimiddleware.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) {
|
||||
@@ -28,6 +30,7 @@ func handleGetBeaconStateSSZ(m *apimiddleware.ApiProxyMiddleware, endpoint apimi
|
||||
sszPath: "/eth/v1/debug/beacon/states/{state_id}/ssz",
|
||||
fileName: "beacon_state.ssz",
|
||||
responseJson: &beaconStateSSZResponseJson{},
|
||||
clientOpts: []apimiddleware.ClientOption{apimiddleware.WithTimeout(time.Minute * 4)},
|
||||
}
|
||||
return handleGetSSZ(m, endpoint, w, req, config)
|
||||
}
|
||||
@@ -46,6 +49,7 @@ func handleGetBeaconStateSSZV2(m *apimiddleware.ApiProxyMiddleware, endpoint api
|
||||
sszPath: "/eth/v2/debug/beacon/states/{state_id}/ssz",
|
||||
fileName: "beacon_state.ssz",
|
||||
responseJson: &beaconStateSSZResponseV2Json{},
|
||||
clientOpts: []apimiddleware.ClientOption{apimiddleware.WithTimeout(time.Minute * 4)},
|
||||
}
|
||||
return handleGetSSZ(m, endpoint, w, req, config)
|
||||
}
|
||||
@@ -74,7 +78,7 @@ func handleGetSSZ(
|
||||
apimiddleware.WriteError(w, errJson, nil)
|
||||
return true
|
||||
}
|
||||
grpcResponse, errJson := apimiddleware.ProxyRequest(req)
|
||||
grpcResponse, errJson := apimiddleware.ProxyRequest(req, config.clientOpts...)
|
||||
if errJson != nil {
|
||||
apimiddleware.WriteError(w, errJson, nil)
|
||||
return true
|
||||
|
||||
@@ -550,6 +550,22 @@ func (bs *Server) chainHeadRetrieval(ctx context.Context) (*ethpb.ChainHead, err
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetWeakSubjectivityCheckpointEpoch only computes the epoch for the weak subjectivity checkpoint.
|
||||
func (bs *Server) GetWeakSubjectivityCheckpointEpoch(ctx context.Context, _ *emptypb.Empty) (*ethpb.WeakSubjectivityCheckpointEpoch, error) {
|
||||
hs, err := bs.HeadFetcher.HeadState(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not get head state")
|
||||
}
|
||||
wsEpoch, err := helpers.LatestWeakSubjectivityEpoch(ctx, hs)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not get weak subjectivity epoch")
|
||||
}
|
||||
|
||||
return ðpb.WeakSubjectivityCheckpointEpoch{
|
||||
Epoch: uint64(wsEpoch),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetWeakSubjectivityCheckpoint retrieves weak subjectivity state root, block root, and epoch.
|
||||
func (bs *Server) GetWeakSubjectivityCheckpoint(ctx context.Context, _ *emptypb.Empty) (*ethpb.WeakSubjectivityCheckpoint, error) {
|
||||
hs, err := bs.HeadFetcher.HeadState(ctx)
|
||||
|
||||
@@ -7,21 +7,7 @@ go_library(
|
||||
"phase0.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state",
|
||||
visibility = [
|
||||
"//beacon-chain:__subpackages__",
|
||||
"//contracts/deposit:__subpackages__",
|
||||
"//proto/prysm/v1alpha1:__subpackages__",
|
||||
"//proto/testing:__subpackages__",
|
||||
"//slasher/rpc:__subpackages__",
|
||||
"//testing/benchmark:__pkg__",
|
||||
"//testing/fuzz:__pkg__",
|
||||
"//testing/slasher/simulator:__pkg__",
|
||||
"//testing/spectest:__subpackages__",
|
||||
"//testing/util:__pkg__",
|
||||
"//tools/benchmark-files-gen:__pkg__",
|
||||
"//tools/exploredb:__pkg__",
|
||||
"//tools/pcli:__pkg__",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
|
||||
@@ -28,21 +28,7 @@ go_library(
|
||||
"unsupported_setters.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/v1",
|
||||
visibility = [
|
||||
"//beacon-chain:__subpackages__",
|
||||
"//contracts/deposit:__subpackages__",
|
||||
"//proto/migration:__subpackages__",
|
||||
"//proto/prysm/v1alpha1:__subpackages__",
|
||||
"//proto/testing:__subpackages__",
|
||||
"//runtime/interop:__subpackages__",
|
||||
"//slasher/rpc:__subpackages__",
|
||||
"//testing/benchmark:__pkg__",
|
||||
"//testing/fuzz:__pkg__",
|
||||
"//testing/spectest:__subpackages__",
|
||||
"//testing/util:__pkg__",
|
||||
"//tools/benchmark-files-gen:__pkg__",
|
||||
"//tools/pcli:__pkg__",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/fieldtrie:go_default_library",
|
||||
|
||||
@@ -35,6 +35,16 @@ func InitializeFromProto(st *ethpb.BeaconState) (*BeaconState, error) {
|
||||
return InitializeFromProtoUnsafe(proto.Clone(st).(*ethpb.BeaconState))
|
||||
}
|
||||
|
||||
// InitializeFromSSZBytes is a convenience method to obtain a BeaconState by unmarshaling
|
||||
// a slice of bytes containing the ssz-serialized representation of the state.
|
||||
func InitializeFromSSZBytes(marshaled []byte) (*BeaconState, error) {
|
||||
st := ðpb.BeaconState{}
|
||||
if err := st.UnmarshalSSZ(marshaled); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return InitializeFromProtoUnsafe(st)
|
||||
}
|
||||
|
||||
// InitializeFromProtoUnsafe directly uses the beacon state protobuf pointer
|
||||
// and sets it as the inner state of the BeaconState type.
|
||||
func InitializeFromProtoUnsafe(st *ethpb.BeaconState) (*BeaconState, error) {
|
||||
|
||||
@@ -28,12 +28,7 @@ go_library(
|
||||
"types.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/v2",
|
||||
visibility = [
|
||||
"//beacon-chain:__subpackages__",
|
||||
"//proto/migration:__subpackages__",
|
||||
"//testing/spectest:__subpackages__",
|
||||
"//testing/util:__pkg__",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/fieldtrie:go_default_library",
|
||||
|
||||
@@ -35,6 +35,16 @@ func InitializeFromProto(st *ethpb.BeaconStateAltair) (*BeaconState, error) {
|
||||
return InitializeFromProtoUnsafe(proto.Clone(st).(*ethpb.BeaconStateAltair))
|
||||
}
|
||||
|
||||
// InitializeFromSSZBytes is a convenience method to obtain a BeaconState by unmarshaling
|
||||
// a slice of bytes containing the ssz-serialized representation of the state.
|
||||
func InitializeFromSSZBytes(marshaled []byte) (*BeaconState, error) {
|
||||
st := ðpb.BeaconStateAltair{}
|
||||
if err := st.UnmarshalSSZ(marshaled); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return InitializeFromProtoUnsafe(st)
|
||||
}
|
||||
|
||||
// InitializeFromProtoUnsafe directly uses the beacon state protobuf pointer
|
||||
// and sets it as the inner state of the BeaconState type.
|
||||
func InitializeFromProtoUnsafe(st *ethpb.BeaconStateAltair) (*BeaconState, error) {
|
||||
|
||||
@@ -30,10 +30,7 @@ go_library(
|
||||
"types.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/state/v3",
|
||||
visibility = [
|
||||
"//beacon-chain:__subpackages__",
|
||||
"//testing/util:__pkg__",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/fieldtrie:go_default_library",
|
||||
|
||||
@@ -34,6 +34,16 @@ func InitializeFromProto(st *ethpb.BeaconStateMerge) (*BeaconState, error) {
|
||||
return InitializeFromProtoUnsafe(proto.Clone(st).(*ethpb.BeaconStateMerge))
|
||||
}
|
||||
|
||||
// InitializeFromSSZBytes is a convenience method to obtain a BeaconState by unmarshaling
|
||||
// a slice of bytes containing the ssz-serialized representation of the state.
|
||||
func InitializeFromSSZBytes(marshaled []byte) (*BeaconState, error) {
|
||||
st := ðpb.BeaconStateMerge{}
|
||||
if err := st.UnmarshalSSZ(marshaled); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return InitializeFromProtoUnsafe(st)
|
||||
}
|
||||
|
||||
// InitializeFromProtoUnsafe directly uses the beacon state protobuf pointer
|
||||
// and sets it as the inner state of the BeaconState type.
|
||||
func InitializeFromProtoUnsafe(st *ethpb.BeaconStateMerge) (*BeaconState, error) {
|
||||
|
||||
12
beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
12
beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
@@ -0,0 +1,12 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["initialize.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/sync/checkpoint",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
29
beacon-chain/sync/checkpoint/initialize.go
Normal file
29
beacon-chain/sync/checkpoint/initialize.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Initializer struct {
|
||||
BlockReadCloser io.ReadCloser
|
||||
StateReadCloser io.ReadCloser
|
||||
}
|
||||
|
||||
func (ini *Initializer) Initialize(ctx context.Context, d db.Database) error {
|
||||
defer func() {
|
||||
err := ini.BlockReadCloser.Close()
|
||||
if err != nil {
|
||||
log.Errorf("error while closing checkpoint block input stream: %s", err)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
err := ini.StateReadCloser.Close()
|
||||
if err != nil {
|
||||
log.Errorf("error while closing checkpoint state input stream: %s", err)
|
||||
}
|
||||
}()
|
||||
return d.SaveOrigin(ctx, ini.StateReadCloser, ini.BlockReadCloser)
|
||||
}
|
||||
@@ -234,7 +234,7 @@ func (s *Service) processBlock(
|
||||
s.logSyncStatus(genesis, blk.Block(), blkRoot)
|
||||
parentRoot := bytesutil.ToBytes32(blk.Block().ParentRoot())
|
||||
if !s.cfg.DB.HasBlock(ctx, parentRoot) && !s.cfg.Chain.HasInitSyncBlock(parentRoot) {
|
||||
return fmt.Errorf("%w: %#x", errParentDoesNotExist, blk.Block().ParentRoot())
|
||||
return fmt.Errorf("%w: (in processBlock @ slot %d) %#x", errParentDoesNotExist, blk.Block().Slot(), blk.Block().ParentRoot())
|
||||
}
|
||||
return blockReceiver(ctx, blk, blkRoot)
|
||||
}
|
||||
@@ -264,7 +264,7 @@ func (s *Service) processBatchedBlocks(ctx context.Context, genesis time.Time,
|
||||
s.logBatchSyncStatus(genesis, blks, blkRoot)
|
||||
parentRoot := bytesutil.ToBytes32(firstBlock.Block().ParentRoot())
|
||||
if !s.cfg.DB.HasBlock(ctx, parentRoot) && !s.cfg.Chain.HasInitSyncBlock(parentRoot) {
|
||||
return fmt.Errorf("%w: %#x", errParentDoesNotExist, firstBlock.Block().ParentRoot())
|
||||
return fmt.Errorf("%w: %#x (in processBatchedBlocks @ slot %d)", errParentDoesNotExist, firstBlock.Block().ParentRoot(), firstBlock.Block().Slot())
|
||||
}
|
||||
blockRoots := make([][32]byte, len(blks))
|
||||
blockRoots[0] = blkRoot
|
||||
|
||||
@@ -21,6 +21,7 @@ go_library(
|
||||
"//cmd/beacon-chain/db:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//cmd/beacon-chain/powchain:go_default_library",
|
||||
"//cmd/beacon-chain/sync/checkpoint:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//io/logs:go_default_library",
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
// FlagOptions for blockchain service flag configurations.
|
||||
func FlagOptions(c *cli.Context) ([]blockchain.Option, error) {
|
||||
wsp := c.String(flags.WeakSubjectivityCheckpt.Name)
|
||||
wsp := c.String(flags.WeakSubjectivityCheckpoint.Name)
|
||||
wsCheckpt, err := helpers.ParseWeakSubjectivityInputString(wsp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -165,13 +165,6 @@ var (
|
||||
Name: "network-id",
|
||||
Usage: "Sets the network id of the beacon chain.",
|
||||
}
|
||||
// WeakSubjectivityCheckpt defines the weak subjectivity checkpoint the node must sync through to defend against long range attacks.
|
||||
WeakSubjectivityCheckpt = &cli.StringFlag{
|
||||
Name: "weak-subjectivity-checkpoint",
|
||||
Usage: "Input in `block_root:epoch_number` format. This guarantees that syncing leads to the given Weak Subjectivity Checkpoint along the canonical chain. " +
|
||||
"If such a sync is not possible, the node will treat it a critical and irrecoverable failure",
|
||||
Value: "",
|
||||
}
|
||||
// Eth1HeaderReqLimit defines a flag to set the maximum number of headers that a deposit log query can fetch. If none is set, 1000 will be the limit.
|
||||
Eth1HeaderReqLimit = &cli.Uint64Flag{
|
||||
Name: "eth1-header-req-limit",
|
||||
@@ -184,6 +177,14 @@ var (
|
||||
Usage: "Load a genesis state from ssz file. Testnet genesis files can be found in the " +
|
||||
"eth2-clients/eth2-testnets repository on github.",
|
||||
}
|
||||
// WeakSubjectivityCheckpoint defines the weak subjectivity checkpoint the node must sync through to defend against long range attacks.
|
||||
WeakSubjectivityCheckpoint = &cli.StringFlag{
|
||||
Name: "weak-subjectivity-checkpoint",
|
||||
Usage: "Input in `block_root:epoch_number` format." +
|
||||
" This guarantees that syncing leads to the given Weak Subjectivity Checkpoint along the canonical chain. " +
|
||||
"If such a sync is not possible, the node will treat it as a critical and irrecoverable failure",
|
||||
Value: "",
|
||||
}
|
||||
// MinPeersPerSubnet defines a flag to set the minimum number of peers that a node will attempt to peer with for a subnet.
|
||||
MinPeersPerSubnet = &cli.Uint64Flag{
|
||||
Name: "minimum-peers-per-subnet",
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"runtime"
|
||||
runtimeDebug "runtime/debug"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/sync/checkpoint"
|
||||
|
||||
gethlog "github.com/ethereum/go-ethereum/log"
|
||||
golog "github.com/ipfs/go-log/v2"
|
||||
joonix "github.com/joonix/log"
|
||||
@@ -61,7 +63,7 @@ var appFlags = []cli.Flag{
|
||||
flags.HistoricalSlasherNode,
|
||||
flags.ChainID,
|
||||
flags.NetworkID,
|
||||
flags.WeakSubjectivityCheckpt,
|
||||
flags.WeakSubjectivityCheckpoint,
|
||||
flags.Eth1HeaderReqLimit,
|
||||
flags.GenesisStatePath,
|
||||
flags.MinPeersPerSubnet,
|
||||
@@ -119,6 +121,8 @@ var appFlags = []cli.Flag{
|
||||
cmd.RestoreTargetDirFlag,
|
||||
cmd.BoltMMapInitialSizeFlag,
|
||||
cmd.ValidatorMonitorIndicesFlag,
|
||||
checkpoint.BlockPath,
|
||||
checkpoint.StatePath,
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -243,6 +247,13 @@ func startNode(ctx *cli.Context) error {
|
||||
node.WithBlockchainFlagOptions(blockchainFlagOpts),
|
||||
node.WithPowchainFlagOptions(powchainFlagOpts),
|
||||
}
|
||||
cptOpts, err := checkpoint.BeaconNodeOptions(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cptOpts != nil {
|
||||
opts = append(opts, cptOpts)
|
||||
}
|
||||
beacon, err := node.New(ctx, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
13
cmd/beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
13
cmd/beacon-chain/sync/checkpoint/BUILD.bazel
Normal file
@@ -0,0 +1,13 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["options.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/cmd/beacon-chain/sync/checkpoint",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/node:go_default_library",
|
||||
"//beacon-chain/sync/checkpoint:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
55
cmd/beacon-chain/sync/checkpoint/options.go
Normal file
55
cmd/beacon-chain/sync/checkpoint/options.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/node"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/sync/checkpoint"
|
||||
"github.com/urfave/cli/v2"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// StatePath defines a flag to start the beacon chain from a give genesis state file.
|
||||
StatePath = &cli.PathFlag{
|
||||
Name: "checkpoint-state",
|
||||
Usage: "Rather than syncing from genesis, you can start processing from a ssz-serialized BeaconState+Block." +
|
||||
" This flag allows you to specify a local file containing the checkpoint BeaconState to load.",
|
||||
}
|
||||
// BlockPath is required when using StatePath to also provide the latest integrated block.
|
||||
BlockPath = &cli.PathFlag{
|
||||
Name: "checkpoint-block",
|
||||
Usage: "Rather than syncing from genesis, you can start processing from a ssz-serialized BeaconState+Block." +
|
||||
" This flag allows you to specify a local file containing the checkpoint Block to load.",
|
||||
}
|
||||
)
|
||||
|
||||
func BeaconNodeOptions(c *cli.Context) (node.Option, error) {
|
||||
blockPath := c.Path(BlockPath.Name)
|
||||
statePath := c.Path(StatePath.Name)
|
||||
if blockPath == "" && statePath == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if blockPath != "" && statePath == "" {
|
||||
return nil, fmt.Errorf("--checkpoint-block specified, but not --checkpoint-state. both are required")
|
||||
}
|
||||
if blockPath == "" && statePath != "" {
|
||||
return nil, fmt.Errorf("--checkpoint-state specified, but not --checkpoint-block. both are required")
|
||||
}
|
||||
|
||||
return func(node *node.BeaconNode) error {
|
||||
blockFH, err := os.Open(blockPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stateFH, err := os.Open(statePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
node.CheckpointInitializer = &checkpoint.Initializer{
|
||||
BlockReadCloser: blockFH,
|
||||
StateReadCloser: stateFH,
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
@@ -119,7 +119,7 @@ var appHelpFlagGroups = []flagGroup{
|
||||
flags.HistoricalSlasherNode,
|
||||
flags.ChainID,
|
||||
flags.NetworkID,
|
||||
flags.WeakSubjectivityCheckpt,
|
||||
flags.WeakSubjectivityCheckpoint,
|
||||
flags.Eth1HeaderReqLimit,
|
||||
flags.GenesisStatePath,
|
||||
flags.MinPeersPerSubnet,
|
||||
|
||||
22
cmd/prysmctl/BUILD.bazel
Normal file
22
cmd/prysmctl/BUILD.bazel
Normal file
@@ -0,0 +1,22 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary")
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["main.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/cmd/prysmctl",
|
||||
visibility = ["//visibility:private"],
|
||||
deps = [
|
||||
"//cmd/prysmctl/checkpoint:go_default_library",
|
||||
"//cmd/prysmctl/get:go_default_library",
|
||||
"//cmd/prysmctl/ssz:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "prysmctl",
|
||||
embed = [":go_default_library"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
18
cmd/prysmctl/checkpoint/BUILD.bazel
Normal file
18
cmd/prysmctl/checkpoint/BUILD.bazel
Normal file
@@ -0,0 +1,18 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"checkpoint.go",
|
||||
"latest.go",
|
||||
"save.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/cmd/prysmctl/checkpoint",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api/client/openapi:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
15
cmd/prysmctl/checkpoint/checkpoint.go
Normal file
15
cmd/prysmctl/checkpoint/checkpoint.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package checkpoint
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
var Commands = []*cli.Command{
|
||||
{
|
||||
Name: "checkpoint",
|
||||
Aliases: []string{"cpt"},
|
||||
Usage: "commands for managing checkpoint syncing",
|
||||
Subcommands: []*cli.Command{
|
||||
latestCmd,
|
||||
saveCmd,
|
||||
},
|
||||
},
|
||||
}
|
||||
81
cmd/prysmctl/checkpoint/latest.go
Normal file
81
cmd/prysmctl/checkpoint/latest.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/api/client/openapi"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var latestFlags = struct {
|
||||
BeaconNodeHost string
|
||||
Timeout string
|
||||
}{}
|
||||
|
||||
var latestCmd = &cli.Command{
|
||||
Name: "latest",
|
||||
Usage: "Connect to a beacon-node server and print the block_root:epoch for the latest checkpoint.",
|
||||
Action: cliActionLatest,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "beacon-node-host",
|
||||
Usage: "host:port for beacon node to query",
|
||||
Destination: &latestFlags.BeaconNodeHost,
|
||||
Value: "localhost:3500",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "http-timeout",
|
||||
Usage: "timeout for http requests made to beacon-node-url (uses duration format, ex: 2m31s). default: 2m",
|
||||
Destination: &latestFlags.Timeout,
|
||||
Value: "2m",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func cliActionLatest(c *cli.Context) error {
|
||||
f := latestFlags
|
||||
opts := make([]openapi.ClientOpt, 0)
|
||||
log.Printf("--beacon-node-url=%s", f.BeaconNodeHost)
|
||||
timeout, err := time.ParseDuration(f.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, openapi.WithTimeout(timeout))
|
||||
validatedHost, err := validHostname(latestFlags.BeaconNodeHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("host:port=%s", validatedHost)
|
||||
client, err := openapi.NewClient(validatedHost, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wsc, err := client.GetWeakSubjectivityCheckpoint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Print("writing weak subjectivity results to stdout")
|
||||
fmt.Printf("epoch: %d\nblock_root: %s\nstate_root: %s\n", int(wsc.Epoch), hexutil.Encode(wsc.BlockRoot), hexutil.Encode(wsc.StateRoot))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validHostname(h string) (string, error) {
|
||||
// try to parse as url (being permissive)
|
||||
u, err := url.Parse(h)
|
||||
if err == nil && u.Host != "" {
|
||||
return u.Host, nil
|
||||
}
|
||||
// try to parse as host:port
|
||||
host, port, err := net.SplitHostPort(h)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", host, port), nil
|
||||
}
|
||||
147
cmd/prysmctl/checkpoint/save.go
Normal file
147
cmd/prysmctl/checkpoint/save.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package checkpoint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/api/client/openapi"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const SLOTS_PER_EPOCH = 32
|
||||
|
||||
var saveFlags = struct {
|
||||
BeaconNodeHost string
|
||||
Timeout string
|
||||
BlockHex string
|
||||
BlockSavePath string
|
||||
StateHex string
|
||||
Epoch int
|
||||
}{}
|
||||
|
||||
var saveCmd = &cli.Command{
|
||||
Name: "save",
|
||||
Usage: "query for the current weak subjectivity period epoch, then download the corresponding state and block. To be used for checkpoint sync.",
|
||||
Action: cliActionSave,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "beacon-node-host",
|
||||
Usage: "host:port for beacon node connection",
|
||||
Destination: &saveFlags.BeaconNodeHost,
|
||||
Value: "localhost:3500",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "http-timeout",
|
||||
Usage: "timeout for http requests made to beacon-node-url (uses duration format, ex: 2m31s). default: 2m",
|
||||
Destination: &saveFlags.Timeout,
|
||||
Value: "4m",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "epoch",
|
||||
Usage: "instead of state-root, epoch can be used to find the BeaconState for the slot at the epoch boundary.",
|
||||
Destination: &saveFlags.Epoch,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func cliActionSave(c *cli.Context) error {
|
||||
f := saveFlags
|
||||
opts := make([]openapi.ClientOpt, 0)
|
||||
log.Printf("--beacon-node-url=%s", f.BeaconNodeHost)
|
||||
timeout, err := time.ParseDuration(f.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, openapi.WithTimeout(timeout))
|
||||
client, err := openapi.NewClient(saveFlags.BeaconNodeHost, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if saveFlags.Epoch > 0 {
|
||||
return saveCheckpointByEpoch(client, uint64(saveFlags.Epoch))
|
||||
}
|
||||
|
||||
return saveCheckpoint(client)
|
||||
}
|
||||
|
||||
func saveCheckpoint(client *openapi.Client) error {
|
||||
epoch, err := client.GetWeakSubjectivityCheckpointEpoch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Beacon node computes the current weak subjectivity checkpoint as epoch = %d", epoch)
|
||||
return saveCheckpointByEpoch(client, epoch)
|
||||
}
|
||||
|
||||
func saveCheckpointByEpoch(client *openapi.Client, epoch uint64) error {
|
||||
slot := epoch * SLOTS_PER_EPOCH
|
||||
|
||||
block, err := client.GetBlockBySlot(slot)
|
||||
blockRoot, err := block.Block.HashTreeRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockRootHex := fmt.Sprintf("%#x", blockRoot)
|
||||
log.Printf("retrieved block at slot %d with root=%s", slot, fmt.Sprintf("%#x", blockRoot))
|
||||
blockStateRoot := block.Block.StateRoot
|
||||
log.Printf("retrieved block has state root %s", fmt.Sprintf("%#x", blockStateRoot))
|
||||
|
||||
// assigning this variable to make it extra obvious that the state slot is different
|
||||
stateSlot := slot + 1
|
||||
// using the state at (slot % 32 = 1) instead of the epoch boundary ensures the
|
||||
// next block applied to the state will have the block at the weak subjectivity checkpoint
|
||||
// as its parent, satisfying prysm's sync code current verification that the parent block is present in the db
|
||||
state, err := client.GetStateBySlot(stateSlot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stateRoot, err := state.HashTreeRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("retrieved state for checkpoint at slot %d, w/ root=%s", slot, fmt.Sprintf("%#x", stateRoot))
|
||||
latestBlockRoot, err := state.LatestBlockHeader.HashTreeRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// we only want to provide checkpoints+state pairs where the state integrates the checkpoint block as its latest root
|
||||
// this ensures that when syncing begins from the provided state, the next block in the chain can find the
|
||||
// latest block in the db.
|
||||
if blockRoot == latestBlockRoot {
|
||||
log.Printf("State latest_block_header root matches block root=%#x", latestBlockRoot)
|
||||
} else {
|
||||
return fmt.Errorf("fatal error, state latest_block_header root=%#x, does not match block root=%#x", latestBlockRoot, blockRoot)
|
||||
}
|
||||
|
||||
bb, err := block.MarshalSSZ()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockPath := fmt.Sprintf("block-%s.ssz", blockRootHex)
|
||||
log.Printf("saving ssz-encoded block to to %s", blockPath)
|
||||
err = os.WriteFile(blockPath, bb, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sb, err := state.MarshalSSZ()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
statePath := fmt.Sprintf("state-%s.ssz", fmt.Sprintf("%#x", stateRoot))
|
||||
log.Printf("saving ssz-encoded state to to %s", statePath)
|
||||
err = os.WriteFile(statePath, sb, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("To validate that your client is using this checkpoint, specify the following flag when starting prysm:")
|
||||
fmt.Printf("--weak-subjectivity-checkpoint=%s:%d\n\n", blockRootHex, epoch)
|
||||
fmt.Println("To sync a new beacon node starting from the checkpoint state, you may specify the following flags (assuming the files are in your current working directory)")
|
||||
fmt.Printf("--checkpoint-state=%s --checkpoint-block=%s\n", statePath, blockPath)
|
||||
return nil
|
||||
}
|
||||
18
cmd/prysmctl/get/BUILD.bazel
Normal file
18
cmd/prysmctl/get/BUILD.bazel
Normal file
@@ -0,0 +1,18 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"block.go",
|
||||
"get.go",
|
||||
"state.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/cmd/prysmctl/get",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api/client/openapi:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
90
cmd/prysmctl/get/block.go
Normal file
90
cmd/prysmctl/get/block.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package get
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/prysm/api/client/openapi"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var getBlockFlags = struct {
|
||||
BeaconNodeHost string
|
||||
Timeout string
|
||||
BlockHex string
|
||||
BlockSavePath string
|
||||
}{}
|
||||
|
||||
var getBlockCmd = &cli.Command{
|
||||
Name: "block",
|
||||
Usage: "Retrieve ssz-encoded block data from a beacon node.",
|
||||
Action: cliActionGetBlock,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "beacon-node-host",
|
||||
Usage: "host:port for beacon node connection",
|
||||
Destination: &getBlockFlags.BeaconNodeHost,
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "http-timeout",
|
||||
Usage: "timeout for http requests made to beacon-node-url (uses duration format, ex: 2m31s). default: 2m",
|
||||
Destination: &getBlockFlags.Timeout,
|
||||
Value: "2m",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "block-root",
|
||||
Usage: "block root (in 0x hex string format) used to retrieve the SignedBeaconBlock for checkpoint state.",
|
||||
Destination: &getBlockFlags.BlockHex,
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "block-save-path",
|
||||
Usage: "path to file where block root should be saved. defaults to `block-<block_root>.ssz`",
|
||||
Destination: &getBlockFlags.BlockSavePath,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func cliActionGetBlock(c *cli.Context) error {
|
||||
f := getBlockFlags
|
||||
if f.BlockHex != "" {
|
||||
}
|
||||
opts := make([]openapi.ClientOpt, 0)
|
||||
log.Printf("beacon-node-url=%s", f.BeaconNodeHost)
|
||||
timeout, err := time.ParseDuration(f.Timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts = append(opts, openapi.WithTimeout(timeout))
|
||||
client, err := openapi.NewClient(f.BeaconNodeHost, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return saveBlock(client, f.BlockHex, f.BlockSavePath)
|
||||
}
|
||||
|
||||
func saveBlock(client *openapi.Client, root, path string) error {
|
||||
block, err := client.GetBlockByRoot(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockRoot, err := block.Block.HashTreeRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("retrieved block for checkpoint, w/ block (header) root=%s", hexutil.Encode(blockRoot[:]))
|
||||
if path == "" {
|
||||
path = fmt.Sprintf("block-%s.ssz", root)
|
||||
}
|
||||
log.Printf("saving to %s...", path)
|
||||
blockBytes, err := block.MarshalSSZ()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, blockBytes, 0644)
|
||||
}
|
||||
14
cmd/prysmctl/get/get.go
Normal file
14
cmd/prysmctl/get/get.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package get
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
var Commands = []*cli.Command{
|
||||
{
|
||||
Name: "get",
|
||||
Usage: "commands for retrieving objects from a running beacon node",
|
||||
Subcommands: []*cli.Command{
|
||||
getBlockCmd,
|
||||
getStateCmd,
|
||||
},
|
||||
},
|
||||
}
|
||||
73
cmd/prysmctl/get/state.go
Normal file
73
cmd/prysmctl/get/state.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package get
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/prysm/api/client/openapi"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var getStateFlags = struct {
|
||||
BeaconNodeHost string
|
||||
Timeout string
|
||||
StateHex string
|
||||
StateSavePath string
|
||||
}{}
|
||||
|
||||
var getStateCmd = &cli.Command{
|
||||
Name: "state",
|
||||
Usage: "Download a state identified by slot or epoch",
|
||||
Action: cliActionGetState,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "beacon-node-host",
|
||||
Usage: "host:port for beacon node connection",
|
||||
Destination: &getStateFlags.BeaconNodeHost,
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "http-timeout",
|
||||
Usage: "timeout for http requests made to beacon-node-url (uses duration format, ex: 2m31s). default: 2m",
|
||||
Destination: &getStateFlags.Timeout,
|
||||
Value: "2m",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "state-root",
|
||||
Usage: "instead of epoch, state root (in 0x hex string format) can be used to retrieve from the beacon-node and save locally.",
|
||||
Destination: &getStateFlags.StateHex,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "state-save-path",
|
||||
Usage: "path to file where state root should be saved if specified. defaults to `state-<state_root>.ssz`",
|
||||
Destination: &getStateFlags.StateSavePath,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func saveStateByRoot(client *openapi.Client, root, path string) error {
|
||||
state, err := client.GetStateByRoot(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stateRoot, err := state.HashTreeRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("retrieved state for checkpoint, w/ root=%s", hexutil.Encode(stateRoot[:]))
|
||||
if path == "" {
|
||||
path = fmt.Sprintf("state-%s.ssz", root)
|
||||
}
|
||||
log.Printf("saving to %s...", path)
|
||||
blockBytes, err := state.MarshalSSZ()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, blockBytes, 0644)
|
||||
}
|
||||
|
||||
func cliActionGetState(c *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
31
cmd/prysmctl/main.go
Normal file
31
cmd/prysmctl/main.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/cmd/prysmctl/get"
|
||||
"github.com/prysmaticlabs/prysm/cmd/prysmctl/ssz"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/cmd/prysmctl/checkpoint"
|
||||
)
|
||||
|
||||
var prysmctlCommands []*cli.Command
|
||||
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Commands: prysmctlCommands,
|
||||
}
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
prysmctlCommands = append(prysmctlCommands, checkpoint.Commands...)
|
||||
prysmctlCommands = append(prysmctlCommands, get.Commands...)
|
||||
prysmctlCommands = append(prysmctlCommands, ssz.Commands...)
|
||||
}
|
||||
23
cmd/prysmctl/ssz/BUILD.bazel
Normal file
23
cmd/prysmctl/ssz/BUILD.bazel
Normal file
@@ -0,0 +1,23 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"sniff.go",
|
||||
"ssz.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/cmd/prysmctl/ssz",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//config/params:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["sniff_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = ["//testing/require:go_default_library"],
|
||||
)
|
||||
264
cmd/prysmctl/ssz/sniff.go
Normal file
264
cmd/prysmctl/ssz/sniff.go
Normal file
@@ -0,0 +1,264 @@
|
||||
package ssz
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var sniffFlags = struct {
|
||||
Path string
|
||||
}{}
|
||||
|
||||
var inspectCmd = &cli.Command{
|
||||
Name: "sniff",
|
||||
Usage: "Extract state metadata from a serialized ssz file",
|
||||
Action: sniff,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "state-path",
|
||||
Usage: "path to file with ssz-serialized state root.`",
|
||||
Destination: &sniffFlags.Path,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/*
|
||||
class BeaconState(Container):
|
||||
# Versioning
|
||||
genesis_time: uint64
|
||||
genesis_validators_root: Root // [32]byte
|
||||
slot: Slot // uint64
|
||||
fork: Fork // [16]byte, nested
|
||||
|
||||
class Fork(Container):
|
||||
previous_version: Version // [4]byte
|
||||
current_version: Version // [4]byte
|
||||
epoch: Epoch # Epoch of latest fork // uint64
|
||||
*/
|
||||
|
||||
type fieldType int
|
||||
|
||||
const (
|
||||
TypeUint64 fieldType = iota
|
||||
TypeByteSlice
|
||||
TypeRoot
|
||||
TypeContainer
|
||||
)
|
||||
|
||||
type field struct {
|
||||
name string
|
||||
size int
|
||||
nested []*field
|
||||
fieldType fieldType
|
||||
slice []byte
|
||||
}
|
||||
|
||||
var forkFields = []*field{
|
||||
{size: 4, fieldType: TypeByteSlice, name: "previous_version"},
|
||||
{size: 4, fieldType: TypeByteSlice, name: "current_version"},
|
||||
{size: 8, fieldType: TypeUint64, name: "epoch"},
|
||||
}
|
||||
|
||||
var beaconState = &field{fieldType: TypeContainer, name: "BeaconState", nested: []*field{
|
||||
{size: 8, fieldType: TypeUint64, name: "genesis_time"},
|
||||
{size: 32, fieldType: TypeRoot, name: "genesis_validators_root"},
|
||||
{size: 8, fieldType: TypeUint64, name: "slot"},
|
||||
{fieldType: TypeContainer, name: "fork", nested: forkFields},
|
||||
}}
|
||||
|
||||
func (f *field) ByteSlice() []byte {
|
||||
if f.fieldType != TypeByteSlice {
|
||||
panic(fmt.Sprintf("ByteSlice called on non-byte slice field: %v", f))
|
||||
}
|
||||
return f.slice
|
||||
}
|
||||
|
||||
func (f *field) Root() [32]byte {
|
||||
if f.fieldType != TypeRoot {
|
||||
panic(fmt.Sprintf("Root called on non-byte slice field: %v", f))
|
||||
}
|
||||
var r [32]byte
|
||||
copy(r[:], f.slice)
|
||||
return r
|
||||
}
|
||||
|
||||
func (f *field) Uint64() uint64 {
|
||||
if f.fieldType != TypeUint64 {
|
||||
panic(fmt.Sprintf("Uint64 called on non-uint64 field: %v", f))
|
||||
}
|
||||
if len(f.slice) != 8 {
|
||||
panic(fmt.Sprintf("Invalid data (len of .slice s %d, expected 8), for uint64 field: %v", len(f.slice), f))
|
||||
}
|
||||
return binary.LittleEndian.Uint64(f.slice[0:8])
|
||||
}
|
||||
|
||||
func sniff(c *cli.Context) error {
|
||||
fh, err := os.Open(sniffFlags.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf := make([]byte, beaconState.Size())
|
||||
_, err = io.ReadFull(fh, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := beaconState.index(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
dumpField(beaconState)
|
||||
cf, err := ConfigForkFromBeaconState(beaconState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("\nfork metadata:\n%s\n", cf.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpField(f *field, indent ...string) {
|
||||
for _, s := range indent {
|
||||
fmt.Print(s)
|
||||
}
|
||||
switch f.fieldType {
|
||||
case TypeContainer:
|
||||
fmt.Printf("%s(container):\n", f.name)
|
||||
for _, n := range f.nested {
|
||||
dumpField(n, append(indent, "\t")...)
|
||||
}
|
||||
case TypeByteSlice:
|
||||
fmt.Printf("%s(bytes): %#x\n", f.name, f.ByteSlice())
|
||||
case TypeRoot:
|
||||
fmt.Printf("%s(root): %#x\n", f.name, f.Root())
|
||||
case TypeUint64:
|
||||
fmt.Printf("%s(uint64): %d\n", f.name, f.Uint64())
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled field type for field=%v", f))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *field) Size() int {
|
||||
if len(f.nested) > 0 {
|
||||
size := 0
|
||||
for _, n := range f.nested {
|
||||
size += n.Size()
|
||||
}
|
||||
return size
|
||||
}
|
||||
return f.size
|
||||
}
|
||||
|
||||
func (f *field) index(buf []byte) error {
|
||||
// always copy the buf to slice; containers will have a slice to their contents, primitives to their values
|
||||
f.slice = buf
|
||||
// if the field is a primitive, stop here
|
||||
if len(f.nested) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getting this far means we're looking at a container of some kind, recursively index it
|
||||
// we're always working with a sliced view, so index math is simple
|
||||
offset := 0
|
||||
for _, fld := range f.nested {
|
||||
if err := fld.index(buf[offset : offset+fld.Size()]); err != nil {
|
||||
return err
|
||||
}
|
||||
offset += fld.Size()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type path string
|
||||
|
||||
func (p path) head() path {
|
||||
parts := strings.SplitN(string(p), ".", 2)
|
||||
return path(parts[0])
|
||||
}
|
||||
|
||||
func (p path) tail() path {
|
||||
parts := strings.SplitN(string(p), ".", 2)
|
||||
if len(parts) == 2 {
|
||||
return path(parts[1])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p path) leaf() bool {
|
||||
return !strings.Contains(string(p), ".")
|
||||
}
|
||||
|
||||
func (f *field) get(p path) (*field, error) {
|
||||
name := string(p.head())
|
||||
if f.name != name {
|
||||
return nil, fmt.Errorf("could not match path = %s", p)
|
||||
}
|
||||
if p.leaf() {
|
||||
return f, nil
|
||||
}
|
||||
childName := string(p.tail().head())
|
||||
for _, n := range f.nested {
|
||||
if n.name != childName {
|
||||
continue
|
||||
}
|
||||
return n.get(p.tail())
|
||||
}
|
||||
return nil, fmt.Errorf("could not match path = %s", p.tail())
|
||||
}
|
||||
|
||||
type ConfigFork struct {
|
||||
Config params.ConfigName
|
||||
Fork params.ForkName
|
||||
Epoch types.Epoch
|
||||
}
|
||||
|
||||
func (cf ConfigFork) String() string {
|
||||
return fmt.Sprintf("config=%s, fork=%s, epoch=%d", cf.Config.String(), cf.Fork.String(), cf.Epoch)
|
||||
}
|
||||
|
||||
func ConfigForkFromBeaconState(f *field) (ConfigFork, error) {
|
||||
cf := ConfigFork{}
|
||||
currentVersion, err := f.get("BeaconState.fork.current_version")
|
||||
if err != nil {
|
||||
return cf, err
|
||||
}
|
||||
var cv [4]byte
|
||||
copy(cv[:], currentVersion.ByteSlice())
|
||||
e, err := f.get("BeaconState.fork.epoch")
|
||||
if err != nil {
|
||||
return cf, err
|
||||
}
|
||||
cf.Epoch = types.Epoch(e.Uint64())
|
||||
for name, cfg := range params.AllConfigs() {
|
||||
genesis := tobyte4(cfg.GenesisForkVersion)
|
||||
altair := tobyte4(cfg.AltairForkVersion)
|
||||
merge := tobyte4(cfg.MergeForkVersion)
|
||||
for id := range cfg.ForkVersionSchedule {
|
||||
if id == cv {
|
||||
cf.Config = name
|
||||
switch id {
|
||||
case genesis:
|
||||
cf.Fork = params.ForkGenesis
|
||||
case altair:
|
||||
cf.Fork = params.ForkAltair
|
||||
case merge:
|
||||
cf.Fork = params.ForkMerge
|
||||
default:
|
||||
return cf, fmt.Errorf("unrecognized fork for config name=%s, BeaconState.fork.current_version=%#x", name.String(), cv)
|
||||
}
|
||||
return cf, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return cf, fmt.Errorf("Could not find a match for BeaconState.fork.current_version=%#x", cv)
|
||||
}
|
||||
|
||||
func tobyte4(slice []byte) [4]byte {
|
||||
var b4 [4]byte
|
||||
copy(b4[:], slice)
|
||||
return b4
|
||||
}
|
||||
54
cmd/prysmctl/ssz/sniff_test.go
Normal file
54
cmd/prysmctl/ssz/sniff_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package ssz
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
)
|
||||
|
||||
func TestPathHead(t *testing.T) {
|
||||
p := path("a.b.c")
|
||||
head := p.head()
|
||||
tail := p.tail()
|
||||
|
||||
require.Equal(t, path("a"), head)
|
||||
require.Equal(t, path("b.c"), tail)
|
||||
}
|
||||
|
||||
func TestPathLeaf(t *testing.T) {
|
||||
p := path("a.b")
|
||||
l := path("a")
|
||||
require.Equal(t, false, p.leaf())
|
||||
require.Equal(t, true, l.leaf())
|
||||
}
|
||||
|
||||
func fixtureField() *field {
|
||||
return &field{fieldType: TypeContainer, name: "BeaconState", nested: []*field{
|
||||
{size: 8, fieldType: TypeUint64, name: "genesis_time"},
|
||||
{size: 32, fieldType: TypeRoot, name: "genesis_validators_root"},
|
||||
{size: 8, fieldType: TypeUint64, name: "slot"},
|
||||
{fieldType: TypeContainer, name: "fork", nested: []*field{
|
||||
{size: 4, fieldType: TypeByteSlice, name: "previous_version"},
|
||||
{size: 4, fieldType: TypeByteSlice, name: "current_version"},
|
||||
{size: 8, fieldType: TypeUint64, name: "epoch"},
|
||||
}},
|
||||
}}
|
||||
}
|
||||
|
||||
func TestFieldGet(t *testing.T) {
|
||||
testField := fixtureField()
|
||||
f, err := testField.get("BeaconState.fork.current_version")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 4, f.size)
|
||||
}
|
||||
|
||||
func TestFieldGetContainer(t *testing.T) {
|
||||
testField := fixtureField()
|
||||
f, err := testField.get("BeaconState.fork")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(f.nested))
|
||||
require.Equal(t, "fork", f.name)
|
||||
epoch, err := f.get("fork.epoch")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 8, epoch.size)
|
||||
}
|
||||
13
cmd/prysmctl/ssz/ssz.go
Normal file
13
cmd/prysmctl/ssz/ssz.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package ssz
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
var Commands = []*cli.Command{
|
||||
{
|
||||
Name: "ssz",
|
||||
Usage: "commands for interacting with ssz values",
|
||||
Subcommands: []*cli.Command{
|
||||
inspectCmd,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -30,6 +30,8 @@ func WrapFlags(flags []cli.Flag) []cli.Flag {
|
||||
f = altsrc.NewUint64Flag(t)
|
||||
case *cli.UintFlag:
|
||||
f = altsrc.NewUintFlag(t)
|
||||
case *cli.PathFlag:
|
||||
f = altsrc.NewPathFlag(t)
|
||||
case *cli.Int64Flag:
|
||||
// Int64Flag does not work. See https://github.com/prysmaticlabs/prysm/issues/6478
|
||||
panic(fmt.Sprintf("unsupported flag type type %T", f))
|
||||
|
||||
@@ -25,6 +25,7 @@ go_library(
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//params:go_default_library",
|
||||
"@com_github_mohae_deepcopy//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@in_gopkg_yaml_v2//:go_default_library",
|
||||
@@ -53,6 +54,7 @@ go_test(
|
||||
":go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@in_gopkg_yaml_v2//:go_default_library",
|
||||
"@io_bazel_rules_go//go/tools/bazel:go_default_library",
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -196,3 +199,45 @@ func (b *BeaconChainConfig) InitializeForkSchedule() {
|
||||
// Set Altair fork data.
|
||||
b.ForkVersionSchedule[bytesutil.ToBytes4(b.AltairForkVersion)] = b.AltairForkEpoch
|
||||
}
|
||||
|
||||
type ForkScheduleEntry struct {
|
||||
Version [4]byte
|
||||
Epoch types.Epoch
|
||||
}
|
||||
|
||||
type OrderedForkSchedule []ForkScheduleEntry
|
||||
|
||||
func (o OrderedForkSchedule) Len() int { return len(o) }
|
||||
func (o OrderedForkSchedule) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
|
||||
func (o OrderedForkSchedule) Less(i, j int) bool { return o[i].Epoch < o[j].Epoch }
|
||||
|
||||
|
||||
var VersionForEpochNotFound = errors.New("could not find an entry in the ForkVersionSchedule")
|
||||
func (o OrderedForkSchedule) VersionForEpoch(epoch types.Epoch) ([4]byte, error) {
|
||||
for i := range o {
|
||||
// moving from lowest to highest, only consider epochs that start at or before the slot
|
||||
if epoch < o[i].Epoch {
|
||||
continue
|
||||
}
|
||||
// `i+1 == len(o)`: at last element, don't need to check if there is another fork schedule entry in between
|
||||
// `o[i+1].Epoch > fse.Epoch`: next version's epoch is past the epoch in question, so `i` is correct
|
||||
if i+1 == len(o) || epoch < o[i+1].Epoch {
|
||||
return o[i].Version, nil
|
||||
}
|
||||
}
|
||||
var nope [4]byte
|
||||
return nope, errors.Wrap(VersionForEpochNotFound, fmt.Sprintf("no epoch in list <= %d", epoch))
|
||||
}
|
||||
|
||||
func (b *BeaconChainConfig) OrderedForkSchedule() OrderedForkSchedule {
|
||||
ofs := make(OrderedForkSchedule, 0)
|
||||
for version, epoch := range b.ForkVersionSchedule {
|
||||
fse := ForkScheduleEntry{
|
||||
Version: version,
|
||||
Epoch: epoch,
|
||||
}
|
||||
ofs = append(ofs, fse)
|
||||
}
|
||||
sort.Sort(ofs)
|
||||
return ofs
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package params_test
|
||||
|
||||
import (
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
)
|
||||
|
||||
@@ -44,3 +47,95 @@ func TestConfig_DataRace(t *testing.T) {
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedConfigSchedule(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
for name, cfg := range params.AllConfigs() {
|
||||
t.Run(name.String(), func(t *testing.T) {
|
||||
prevVersion := [4]byte{0,0,0,0}
|
||||
// epoch 0 is genesis, and it's a uint so can't make it -1
|
||||
// so we use a pointer to detect the boundary condition and skip it
|
||||
var prevEpoch *types.Epoch
|
||||
for _, fse := range cfg.OrderedForkSchedule() {
|
||||
if prevEpoch == nil {
|
||||
prevEpoch = &fse.Epoch
|
||||
prevVersion = fse.Version
|
||||
continue
|
||||
}
|
||||
if *prevEpoch > fse.Epoch {
|
||||
t.Errorf("Epochs out of order! %#x/%d before %#x/%d", fse.Version, fse.Epoch, prevVersion, prevEpoch)
|
||||
}
|
||||
prevEpoch = &fse.Epoch
|
||||
prevVersion = fse.Version
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
bc := testForkVersionScheduleBCC()
|
||||
ofs := bc.OrderedForkSchedule()
|
||||
for i := range ofs {
|
||||
if ofs[i].Epoch != types.Epoch(math.Pow(2, float64(i))) {
|
||||
t.Errorf("expected %dth element of list w/ epoch=%d, got=%d. list=%v", i, types.Epoch(2^i), ofs[i].Epoch, ofs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionForEpoch(t *testing.T) {
|
||||
bc := testForkVersionScheduleBCC()
|
||||
ofs := bc.OrderedForkSchedule()
|
||||
testCases := []struct{
|
||||
name string
|
||||
version [4]byte
|
||||
epoch types.Epoch
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "found between versions",
|
||||
version: [4]byte{2,1,2,3},
|
||||
epoch: types.Epoch(7),
|
||||
},
|
||||
{
|
||||
name: "found at end",
|
||||
version: [4]byte{4,1,2,3},
|
||||
epoch: types.Epoch(100),
|
||||
},
|
||||
{
|
||||
name: "found at start",
|
||||
version: [4]byte{0,1,2,3},
|
||||
epoch: types.Epoch(1),
|
||||
},
|
||||
{
|
||||
name: "found at boundary",
|
||||
version: [4]byte{1,1,2,3},
|
||||
epoch: types.Epoch(2),
|
||||
},
|
||||
{
|
||||
name: "not found before",
|
||||
epoch: types.Epoch(0),
|
||||
err: params.VersionForEpochNotFound,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
v, err := ofs.VersionForEpoch(tc.epoch)
|
||||
if tc.err == nil {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorIs(t, err, tc.err)
|
||||
}
|
||||
require.Equal(t, tc.version, v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testForkVersionScheduleBCC() *params.BeaconChainConfig {
|
||||
return ¶ms.BeaconChainConfig{
|
||||
ForkVersionSchedule: map[[4]byte]types.Epoch{
|
||||
[4]byte{1,1,2,3}: types.Epoch(2),
|
||||
[4]byte{0,1,2,3}: types.Epoch(1),
|
||||
[4]byte{4,1,2,3}: types.Epoch(16),
|
||||
[4]byte{3,1,2,3}: types.Epoch(8),
|
||||
[4]byte{2,1,2,3}: types.Epoch(4),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,54 @@ var ConfigNames = map[ConfigName]string{
|
||||
}
|
||||
|
||||
// ConfigName enum describes the type of known network in use.
|
||||
type ConfigName = int
|
||||
type ConfigName int
|
||||
|
||||
func (n ConfigName) String() string {
|
||||
s, ok := ConfigNames[n]
|
||||
if !ok {
|
||||
return "undefined"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func AllConfigs() map[ConfigName]*BeaconChainConfig {
|
||||
all := make(map[ConfigName]*BeaconChainConfig)
|
||||
for name := range ConfigNames {
|
||||
var cfg *BeaconChainConfig
|
||||
switch name {
|
||||
case Mainnet:
|
||||
cfg = MainnetConfig()
|
||||
case Prater:
|
||||
cfg = PraterConfig()
|
||||
case Pyrmont:
|
||||
cfg = PyrmontConfig()
|
||||
case Minimal:
|
||||
cfg = MinimalSpecConfig()
|
||||
case EndToEnd:
|
||||
cfg = E2ETestConfig()
|
||||
}
|
||||
cfg = cfg.Copy()
|
||||
cfg.InitializeForkSchedule()
|
||||
all[name] = cfg
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
type ForkName int
|
||||
|
||||
const (
|
||||
ForkGenesis ForkName = iota
|
||||
ForkAltair
|
||||
ForkMerge
|
||||
)
|
||||
|
||||
func (n ForkName) String() string {
|
||||
switch n {
|
||||
case ForkGenesis:
|
||||
return "genesis"
|
||||
case ForkAltair:
|
||||
return "altair"
|
||||
}
|
||||
|
||||
return "undefined"
|
||||
}
|
||||
|
||||
813
proto/prysm/v1alpha1/beacon_chain.pb.go
generated
813
proto/prysm/v1alpha1/beacon_chain.pb.go
generated
File diff suppressed because it is too large
Load Diff
@@ -332,6 +332,24 @@ func local_request_BeaconChain_GetWeakSubjectivityCheckpoint_0(ctx context.Conte
|
||||
|
||||
}
|
||||
|
||||
func request_BeaconChain_GetWeakSubjectivityCheckpointEpoch_0(ctx context.Context, marshaler runtime.Marshaler, client BeaconChainClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq emptypb.Empty
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := client.GetWeakSubjectivityCheckpointEpoch(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_BeaconChain_GetWeakSubjectivityCheckpointEpoch_0(ctx context.Context, marshaler runtime.Marshaler, server BeaconChainServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq emptypb.Empty
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := server.GetWeakSubjectivityCheckpointEpoch(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_BeaconChain_ListBeaconCommittees_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
@@ -1011,6 +1029,29 @@ func RegisterBeaconChainHandlerServer(ctx context.Context, mux *runtime.ServeMux
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_BeaconChain_GetWeakSubjectivityCheckpointEpoch_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/ethereum.eth.v1alpha1.BeaconChain/GetWeakSubjectivityCheckpointEpoch")
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_BeaconChain_GetWeakSubjectivityCheckpointEpoch_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_BeaconChain_GetWeakSubjectivityCheckpointEpoch_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_BeaconChain_ListBeaconCommittees_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
@@ -1578,6 +1619,26 @@ func RegisterBeaconChainHandlerClient(ctx context.Context, mux *runtime.ServeMux
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_BeaconChain_GetWeakSubjectivityCheckpointEpoch_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/ethereum.eth.v1alpha1.BeaconChain/GetWeakSubjectivityCheckpointEpoch")
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_BeaconChain_GetWeakSubjectivityCheckpointEpoch_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_BeaconChain_GetWeakSubjectivityCheckpointEpoch_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_BeaconChain_ListBeaconCommittees_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
@@ -1884,6 +1945,8 @@ var (
|
||||
|
||||
pattern_BeaconChain_GetWeakSubjectivityCheckpoint_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"eth", "v1alpha1", "beacon", "weak_subjectivity_checkpoint"}, ""))
|
||||
|
||||
pattern_BeaconChain_GetWeakSubjectivityCheckpointEpoch_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"eth", "v1alpha1", "beacon", "weak_subjectivity_checkpoint_epoch"}, ""))
|
||||
|
||||
pattern_BeaconChain_ListBeaconCommittees_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"eth", "v1alpha1", "beacon", "committees"}, ""))
|
||||
|
||||
pattern_BeaconChain_ListValidatorBalances_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"eth", "v1alpha1", "validators", "balances"}, ""))
|
||||
@@ -1936,6 +1999,8 @@ var (
|
||||
|
||||
forward_BeaconChain_GetWeakSubjectivityCheckpoint_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_BeaconChain_GetWeakSubjectivityCheckpointEpoch_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_BeaconChain_ListBeaconCommittees_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_BeaconChain_ListValidatorBalances_0 = runtime.ForwardResponseMessage
|
||||
|
||||
@@ -160,6 +160,15 @@ service BeaconChain {
|
||||
};
|
||||
}
|
||||
|
||||
// Retrieve the epoch that the server believes should be used as a weak subjectivity checkpoint.
|
||||
// With the epoch, an rpc/api client can then download the BeaconState for the first slot, and then
|
||||
// also download the latest block observed in the unmarshaled state for a complete checkpoint.
|
||||
rpc GetWeakSubjectivityCheckpointEpoch(google.protobuf.Empty) returns (WeakSubjectivityCheckpointEpoch) {
|
||||
option (google.api.http) = {
|
||||
get: "/eth/v1alpha1/beacon/weak_subjectivity_checkpoint_epoch"
|
||||
};
|
||||
}
|
||||
|
||||
// Retrieve the beacon chain committees for a given epoch.
|
||||
//
|
||||
// If no filter criteria is specified, the response returns
|
||||
@@ -914,3 +923,8 @@ message WeakSubjectivityCheckpoint {
|
||||
// The epoch of weak subjectivity checkpoint.
|
||||
uint64 epoch = 3 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/eth2-types.Epoch"];
|
||||
}
|
||||
|
||||
message WeakSubjectivityCheckpointEpoch {
|
||||
// The epoch of the weak subjectivity checkpoint.
|
||||
uint64 epoch = 1;
|
||||
}
|
||||
36
proto/sniff/BUILD.bazel
Normal file
36
proto/sniff/BUILD.bazel
Normal file
@@ -0,0 +1,36 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"initialize.go",
|
||||
"sniffer.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/proto/sniff",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/v1:go_default_library",
|
||||
"//beacon-chain/state/v2:go_default_library",
|
||||
"//beacon-chain/state/v3:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/block:go_default_library",
|
||||
"//proto/prysm/v1alpha1/wrapper:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["sniffer_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
],
|
||||
)
|
||||
79
proto/sniff/initialize.go
Normal file
79
proto/sniff/initialize.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package sniff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
||||
v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1"
|
||||
v2 "github.com/prysmaticlabs/prysm/beacon-chain/state/v2"
|
||||
v3 "github.com/prysmaticlabs/prysm/beacon-chain/state/v3"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
v1alpha1 "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
|
||||
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
)
|
||||
|
||||
// is an io.Reader. This allows client code to remain agnostic about whether the data comes
|
||||
// from the network or a file without needing to read the entire state into mem as a large byte slice.
|
||||
func BeaconStateForConfigFork(b []byte, cf *ConfigFork) (state.BeaconState, error) {
|
||||
switch cf.Fork {
|
||||
case params.ForkGenesis:
|
||||
return v1.InitializeFromSSZBytes(b)
|
||||
case params.ForkAltair:
|
||||
return v2.InitializeFromSSZBytes(b)
|
||||
case params.ForkMerge:
|
||||
return v3.InitializeFromSSZBytes(b)
|
||||
}
|
||||
return nil, fmt.Errorf("unable to initialize BeaconState for fork version=%s", cf.Fork.String())
|
||||
}
|
||||
|
||||
// BlockForConfigFork attempts to unmarshal a block from a marshaled byte slice into the correct block type.
|
||||
// In order to do this it needs to know what fork the block is from using ConfigFork, which can be obtained
|
||||
// by using ConfigForkForState.
|
||||
func BlockForConfigFork(b []byte, cf *ConfigFork) (block.SignedBeaconBlock, error) {
|
||||
slot, err := SlotFromBlock(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// heuristic to make sure block is from the same version as the state
|
||||
// based on the fork schedule for the Config detected from the state
|
||||
// get the version that corresponds to the epoch the block is from according to the fork choice schedule
|
||||
// and make sure that the version is the same one that was pulled from the state
|
||||
epoch := slots.ToEpoch(slot)
|
||||
fs := cf.Config.OrderedForkSchedule()
|
||||
ver, err := fs.VersionForEpoch(epoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ver != cf.Version {
|
||||
return nil, fmt.Errorf("cannot sniff block schema, block (slot=%d, epoch=%d) is on a different fork", slot, epoch)
|
||||
}
|
||||
|
||||
switch cf.Fork {
|
||||
case params.ForkGenesis:
|
||||
blk := &v1alpha1.SignedBeaconBlock{}
|
||||
err := blk.UnmarshalSSZ(b)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal SignedBeaconBlock in BlockFromSSZReader")
|
||||
}
|
||||
return wrapper.WrappedPhase0SignedBeaconBlock(blk), nil
|
||||
case params.ForkAltair:
|
||||
blk := &v1alpha1.SignedBeaconBlockAltair{}
|
||||
err := blk.UnmarshalSSZ(b)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal SignedBeaconBlockAltair in BlockFromSSZReader")
|
||||
}
|
||||
return wrapper.WrappedAltairSignedBeaconBlock(blk)
|
||||
case params.ForkMerge:
|
||||
blk := &v1alpha1.SignedBeaconBlockMerge{}
|
||||
err := blk.UnmarshalSSZ(b)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal SignedBeaconBlockMerge in BlockFromSSZReader")
|
||||
}
|
||||
return wrapper.WrappedMergeSignedBeaconBlock(blk)
|
||||
}
|
||||
return nil, fmt.Errorf("unable to initialize BeaconBlock for fork version=%s at slot=%d", cf.Fork.String(), slot)
|
||||
}
|
||||
140
proto/sniff/sniffer.go
Normal file
140
proto/sniff/sniffer.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package sniff
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/config/params"
|
||||
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
||||
)
|
||||
|
||||
type fieldType int
|
||||
|
||||
const (
|
||||
TypeUint64 fieldType = iota
|
||||
TypeBytes4
|
||||
TypeByteSlice
|
||||
TypeRoot
|
||||
TypeContainer
|
||||
)
|
||||
|
||||
type fieldSpec struct {
|
||||
offset int
|
||||
size int
|
||||
t fieldType
|
||||
}
|
||||
|
||||
func (f *fieldSpec) Uint64(state []byte) (uint64, error) {
|
||||
if f.t != TypeUint64 {
|
||||
return 0, fmt.Errorf("Uint64 called on non-uint64 field: %v", f)
|
||||
}
|
||||
s, err := f.slice(state)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.LittleEndian.Uint64(s), nil
|
||||
}
|
||||
|
||||
func (f *fieldSpec) Bytes4(state []byte) ([4]byte, error) {
|
||||
var b4 [4]byte
|
||||
if f.t != TypeBytes4 {
|
||||
return b4, fmt.Errorf("Bytes4 called on non-bytes4 field %v", f)
|
||||
}
|
||||
if f.size != 4 {
|
||||
return b4, fmt.Errorf("Bytes4 types must have a size of 4, invalid fieldSpec %v", f)
|
||||
}
|
||||
val, err := f.slice(state)
|
||||
if err != nil {
|
||||
return b4, err
|
||||
}
|
||||
return bytesutil.ToBytes4(val), nil
|
||||
}
|
||||
|
||||
func (f *fieldSpec) slice(value []byte) ([]byte, error) {
|
||||
if len(value) < f.offset+f.size {
|
||||
return nil, fmt.Errorf("cannot pull bytes from value; offset=%d, size=%d, so value must be at least %d bytes (actual=%d)", f.offset, f.size, f.offset+f.size, len(value))
|
||||
}
|
||||
return value[f.offset : f.offset+f.size], nil
|
||||
}
|
||||
|
||||
var beaconStateCurrentVersion = fieldSpec{
|
||||
// 52 = 8 (genesis_time) + 32 (genesis_validators_root) + 8 (slot) + 4 (previous_version)
|
||||
offset: 52,
|
||||
size: 4,
|
||||
t: TypeBytes4,
|
||||
}
|
||||
|
||||
var beaconStateEpoch = fieldSpec{
|
||||
// 52 = 8 (genesis_time) + 32 (genesis_validators_root) + 8 (slot) + 4 (previous_version) + 4 (current_version)
|
||||
offset: 56,
|
||||
size: 8,
|
||||
t: TypeUint64,
|
||||
}
|
||||
|
||||
type ConfigFork struct {
|
||||
ConfigName params.ConfigName
|
||||
Fork params.ForkName
|
||||
Config *params.BeaconChainConfig
|
||||
Version [4]byte
|
||||
Epoch types.Epoch
|
||||
}
|
||||
|
||||
func ConfigForkForState(marshaled []byte) (*ConfigFork, error) {
|
||||
epoch, err := beaconStateEpoch.Uint64(marshaled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cv, err := beaconStateCurrentVersion.Bytes4(marshaled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FindConfigFork(types.Epoch(epoch), cv)
|
||||
}
|
||||
|
||||
func FindConfigFork(epoch types.Epoch, cv [4]byte) (*ConfigFork, error) {
|
||||
cf := &ConfigFork{
|
||||
Epoch: epoch,
|
||||
Version: cv,
|
||||
}
|
||||
for name, cfg := range params.AllConfigs() {
|
||||
genesis := bytesutil.ToBytes4(cfg.GenesisForkVersion)
|
||||
altair := bytesutil.ToBytes4(cfg.AltairForkVersion)
|
||||
merge := bytesutil.ToBytes4(cfg.MergeForkVersion)
|
||||
for v := range cfg.ForkVersionSchedule {
|
||||
if v == cv {
|
||||
cf.ConfigName = name
|
||||
cf.Config = cfg
|
||||
switch v {
|
||||
case genesis:
|
||||
cf.Fork = params.ForkGenesis
|
||||
case altair:
|
||||
cf.Fork = params.ForkAltair
|
||||
case merge:
|
||||
cf.Fork = params.ForkMerge
|
||||
default:
|
||||
return cf, fmt.Errorf("unrecognized fork for config name=%s, BeaconState.fork.current_version=%#x", name.String(), cv)
|
||||
}
|
||||
return cf, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return cf, fmt.Errorf("could not find a config+fork match for epoch=%d, current_version=%#x", epoch, cv)
|
||||
}
|
||||
|
||||
var beaconBlockSlot = fieldSpec{
|
||||
// ssz variable length offset (not to be confused with the fieldSpec offest) is a uint32
|
||||
// variable length offsets come before fixed length data, so that's 4 bytes at the beginning
|
||||
// then signature is 96 bytes, 4+96 = 100
|
||||
offset: 100,
|
||||
size: 8,
|
||||
t: TypeUint64,
|
||||
}
|
||||
|
||||
func SlotFromBlock(marshaled []byte) (types.Slot, error) {
|
||||
slot, err := beaconBlockSlot.Uint64(marshaled)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return types.Slot(slot), nil
|
||||
}
|
||||
135
proto/sniff/sniffer_test.go
Normal file
135
proto/sniff/sniffer_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package sniff
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
)
|
||||
|
||||
func TestSlotFromBlock(t *testing.T) {
|
||||
b := testBlock()
|
||||
var slot types.Slot = 3
|
||||
b.Block.Slot = slot
|
||||
bb, err := b.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
sfb, err := SlotFromBlock(bb)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot, sfb)
|
||||
|
||||
ba := testBlockAltair()
|
||||
ba.Block.Slot = slot
|
||||
bab, err := ba.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
sfba, err := SlotFromBlock(bab)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot, sfba)
|
||||
|
||||
bm := testBlockMerge()
|
||||
bm.Block.Slot = slot
|
||||
bmb, err := ba.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
sfbm, err := SlotFromBlock(bmb)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot, sfbm)
|
||||
}
|
||||
|
||||
func testBlock() *ethpb.SignedBeaconBlock {
|
||||
return ðpb.SignedBeaconBlock{
|
||||
Block: ðpb.BeaconBlock{
|
||||
ProposerIndex: types.ValidatorIndex(0),
|
||||
ParentRoot: make([]byte, 32),
|
||||
StateRoot: make([]byte, 32),
|
||||
Body: ðpb.BeaconBlockBody{
|
||||
RandaoReveal: make([]byte, 96),
|
||||
Graffiti: make([]byte, 32),
|
||||
ProposerSlashings: []*ethpb.ProposerSlashing{},
|
||||
AttesterSlashings: []*ethpb.AttesterSlashing{},
|
||||
Attestations: []*ethpb.Attestation{},
|
||||
Deposits: []*ethpb.Deposit{},
|
||||
VoluntaryExits: []*ethpb.SignedVoluntaryExit{},
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
DepositCount: 0,
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
}
|
||||
|
||||
func testBlockAltair() *ethpb.SignedBeaconBlockAltair {
|
||||
return ðpb.SignedBeaconBlockAltair{
|
||||
Block: ðpb.BeaconBlockAltair{
|
||||
ProposerIndex: types.ValidatorIndex(0),
|
||||
ParentRoot: make([]byte, 32),
|
||||
StateRoot: make([]byte, 32),
|
||||
Body: ðpb.BeaconBlockBodyAltair{
|
||||
RandaoReveal: make([]byte, 96),
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
DepositCount: 0,
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
Graffiti: make([]byte, 32),
|
||||
ProposerSlashings: []*ethpb.ProposerSlashing{},
|
||||
AttesterSlashings: []*ethpb.AttesterSlashing{},
|
||||
Attestations: []*ethpb.Attestation{},
|
||||
Deposits: []*ethpb.Deposit{},
|
||||
VoluntaryExits: []*ethpb.SignedVoluntaryExit{},
|
||||
SyncAggregate: ðpb.SyncAggregate{
|
||||
SyncCommitteeBits: make([]byte, 64),
|
||||
SyncCommitteeSignature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
}
|
||||
|
||||
func testBlockMerge() *ethpb.SignedBeaconBlockMerge {
|
||||
return ðpb.SignedBeaconBlockMerge{
|
||||
Block: ðpb.BeaconBlockMerge{
|
||||
ProposerIndex: types.ValidatorIndex(0),
|
||||
ParentRoot: make([]byte, 32),
|
||||
StateRoot: make([]byte, 32),
|
||||
Body: ðpb.BeaconBlockBodyMerge{
|
||||
RandaoReveal: make([]byte, 96),
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
DepositCount: 0,
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
Graffiti: make([]byte, 32),
|
||||
ProposerSlashings: []*ethpb.ProposerSlashing{},
|
||||
AttesterSlashings: []*ethpb.AttesterSlashing{},
|
||||
Attestations: []*ethpb.Attestation{},
|
||||
Deposits: []*ethpb.Deposit{},
|
||||
VoluntaryExits: []*ethpb.SignedVoluntaryExit{},
|
||||
SyncAggregate: ðpb.SyncAggregate{
|
||||
SyncCommitteeBits: make([]byte, 64),
|
||||
SyncCommitteeSignature: make([]byte, 96),
|
||||
},
|
||||
ExecutionPayload: ðpb.ExecutionPayload{
|
||||
ParentHash: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, 32),
|
||||
ReceiptRoot: make([]byte, 32),
|
||||
LogsBloom: make([]byte, 256),
|
||||
Random: make([]byte, 32),
|
||||
BlockNumber: 0,
|
||||
GasLimit: 0,
|
||||
GasUsed: 0,
|
||||
Timestamp: 0,
|
||||
ExtraData: make([]byte, 32),
|
||||
BaseFeePerGas: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
Transactions: make([][]byte, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
}
|
||||
20
testing/mock/beacon_service_mock.go
generated
20
testing/mock/beacon_service_mock.go
generated
@@ -238,6 +238,26 @@ func (mr *MockBeaconChainClientMockRecorder) GetWeakSubjectivityCheckpoint(arg0,
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWeakSubjectivityCheckpoint", reflect.TypeOf((*MockBeaconChainClient)(nil).GetWeakSubjectivityCheckpoint), varargs...)
|
||||
}
|
||||
|
||||
// GetWeakSubjectivityCheckpointEpoch mock implementation
|
||||
func (m *MockBeaconChainClient) GetWeakSubjectivityCheckpointEpoch(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*eth.WeakSubjectivityCheckpointEpoch, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "GetWeakSubjectivityCheckpointEpoch", varargs...)
|
||||
ret0, _ := ret[0].(*eth.WeakSubjectivityCheckpointEpoch)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetWeakSubjectivityCheckpointEpoch impersonates the GetWeakSubjectivityCheckpointEpoch and records the parameters
|
||||
func (mr *MockBeaconChainClientMockRecorder) GetWeakSubjectivityCheckpointEpoch(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWeakSubjectivityCheckpointEpoch", reflect.TypeOf((*MockBeaconChainClient)(nil).GetWeakSubjectivityCheckpointEpoch), varargs...)
|
||||
}
|
||||
|
||||
// ListAttestations mocks base method
|
||||
func (m *MockBeaconChainClient) ListAttestations(arg0 context.Context, arg1 *eth.ListAttestationsRequest, arg2 ...grpc.CallOption) (*eth.ListAttestationsResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Reference in New Issue
Block a user