mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-01 17:43:45 +00:00
Merge pull request #146 from fearlessfe/light
light cleint and history summaries validation
This commit is contained in:
commit
17308df173
5 changed files with 255 additions and 47 deletions
|
|
@ -14,6 +14,7 @@ import (
|
|||
ssz "github.com/ferranbt/fastssz"
|
||||
"github.com/protolambda/zrnt/eth2/beacon/common"
|
||||
"github.com/protolambda/zrnt/eth2/configs"
|
||||
"github.com/protolambda/zrnt/eth2/util/merkle"
|
||||
"github.com/protolambda/ztyp/codec"
|
||||
"github.com/protolambda/ztyp/tree"
|
||||
)
|
||||
|
|
@ -32,6 +33,7 @@ type BeaconNetwork struct {
|
|||
log log.Logger
|
||||
closeCtx context.Context
|
||||
closeFunc context.CancelFunc
|
||||
lightClient *ConsensusLightClient
|
||||
}
|
||||
|
||||
func NewBeaconNetwork(portalProtocol *discover.PortalProtocol) *BeaconNetwork {
|
||||
|
|
@ -251,8 +253,29 @@ func (bn *BeaconNetwork) validateContent(contentKey []byte, content []byte) erro
|
|||
return nil
|
||||
// TODO: VERIFY
|
||||
case HistoricalSummaries:
|
||||
var historicalSummaries HistoricalSummariesProof
|
||||
return historicalSummaries.Deserialize(codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
|
||||
key := &HistoricalSummariesWithProofKey{}
|
||||
err := key.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey[1:]), uint64(len(contentKey[1:]))))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
forkedHistoricalSummariesWithProof := &ForkedHistoricalSummariesWithProof{}
|
||||
err = forkedHistoricalSummariesWithProof.Deserialize(bn.spec, codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if forkedHistoricalSummariesWithProof.HistoricalSummariesWithProof.EPOCH != common.Epoch(key.Epoch) {
|
||||
return fmt.Errorf("historical summaries with proof epoch does not match the content key epoch: %d != %d", forkedHistoricalSummariesWithProof.HistoricalSummariesWithProof.EPOCH, key.Epoch)
|
||||
}
|
||||
|
||||
// TODO get root from light client
|
||||
header := bn.lightClient.GetFinalityHeader()
|
||||
latestFinalizedRoot := header.StateRoot
|
||||
|
||||
valid := bn.stateSummariesValidation(*forkedHistoricalSummariesWithProof, latestFinalizedRoot)
|
||||
if !valid {
|
||||
return errors.New("merkle proof validation failed for HistoricalSummariesProof")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unknown content type %v", contentKey[0])
|
||||
}
|
||||
|
|
@ -305,3 +328,11 @@ func (bn *BeaconNetwork) processContentLoop(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bn *BeaconNetwork) stateSummariesValidation(f ForkedHistoricalSummariesWithProof, latestFinalizedRoot common.Root) bool {
|
||||
proof := f.HistoricalSummariesWithProof.Proof
|
||||
summariesRoot := f.HistoricalSummariesWithProof.HistoricalSummaries.HashTreeRoot(bn.spec, tree.GetHashFn())
|
||||
|
||||
gIndex := 59
|
||||
return merkle.VerifyMerkleBranch(summariesRoot, proof.Proof[:], 5, uint64(gIndex), latestFinalizedRoot)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,10 +30,10 @@ var (
|
|||
)
|
||||
|
||||
type ConsensusAPI interface {
|
||||
GetBootstrap(blockRoot common.Root) (common.SpecObj, error)
|
||||
GetUpdates(firstPeriod, count uint64) ([]common.SpecObj, error)
|
||||
GetCheckpointData(checkpointHash common.Root) (common.SpecObj, error)
|
||||
GetFinalityData() (common.SpecObj, error)
|
||||
GetOptimisticData() (common.SpecObj, error)
|
||||
GetFinalityUpdate() (common.SpecObj, error)
|
||||
GetOptimisticUpdate() (common.SpecObj, error)
|
||||
ChainID() uint64
|
||||
Name() string
|
||||
}
|
||||
|
|
@ -174,7 +174,7 @@ func (c *ConsensusLightClient) Sync() error {
|
|||
c.ApplyUpdate(update)
|
||||
}
|
||||
|
||||
finalityUpdate, err := c.API.GetFinalityData()
|
||||
finalityUpdate, err := c.API.GetFinalityUpdate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -184,7 +184,7 @@ func (c *ConsensusLightClient) Sync() error {
|
|||
}
|
||||
c.ApplyFinalityUpdate(finalityUpdate)
|
||||
|
||||
optimisticUpdate, err := c.API.GetOptimisticData()
|
||||
optimisticUpdate, err := c.API.GetOptimisticUpdate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -199,7 +199,7 @@ func (c *ConsensusLightClient) Sync() error {
|
|||
}
|
||||
|
||||
func (c *ConsensusLightClient) Advance() error {
|
||||
finalityUpdate, err := c.API.GetFinalityData()
|
||||
finalityUpdate, err := c.API.GetFinalityUpdate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -209,7 +209,7 @@ func (c *ConsensusLightClient) Advance() error {
|
|||
}
|
||||
c.ApplyFinalityUpdate(finalityUpdate)
|
||||
|
||||
optimisticUpdate, err := c.API.GetOptimisticData()
|
||||
optimisticUpdate, err := c.API.GetOptimisticUpdate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -240,7 +240,7 @@ func (c *ConsensusLightClient) Advance() error {
|
|||
}
|
||||
|
||||
func (c *ConsensusLightClient) bootstrap() error {
|
||||
forkedBootstrap, err := c.API.GetCheckpointData(c.InitialCheckpoint)
|
||||
forkedBootstrap, err := c.API.GetBootstrap(c.InitialCheckpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func (m MockConsensusAPI) GetUpdates(_, _ uint64) ([]common.SpecObj, error) {
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (m MockConsensusAPI) GetCheckpointData(_ common.Root) (common.SpecObj, error) {
|
||||
func (m MockConsensusAPI) GetBootstrap(_ common.Root) (common.SpecObj, error) {
|
||||
jsonStr, _ := os.ReadFile(m.testdataDir + "/bootstrap.json")
|
||||
|
||||
bootstrap := &capella.LightClientBootstrap{}
|
||||
|
|
@ -47,7 +47,7 @@ func (m MockConsensusAPI) GetCheckpointData(_ common.Root) (common.SpecObj, erro
|
|||
return bootstrap, nil
|
||||
}
|
||||
|
||||
func (m MockConsensusAPI) GetFinalityData() (common.SpecObj, error) {
|
||||
func (m MockConsensusAPI) GetFinalityUpdate() (common.SpecObj, error) {
|
||||
jsonStr, _ := os.ReadFile(m.testdataDir + "/finality.json")
|
||||
|
||||
finality := &capella.LightClientFinalityUpdate{}
|
||||
|
|
@ -56,7 +56,7 @@ func (m MockConsensusAPI) GetFinalityData() (common.SpecObj, error) {
|
|||
return finality, nil
|
||||
}
|
||||
|
||||
func (m MockConsensusAPI) GetOptimisticData() (common.SpecObj, error) {
|
||||
func (m MockConsensusAPI) GetOptimisticUpdate() (common.SpecObj, error) {
|
||||
jsonStr, _ := os.ReadFile(m.testdataDir + "/optimistic.json")
|
||||
|
||||
optimistic := &capella.LightClientOptimisticUpdate{}
|
||||
|
|
@ -140,7 +140,7 @@ func TestVerifyFinalityUpdate(t *testing.T) {
|
|||
client, err := getClient(false, t)
|
||||
require.NoError(t, err)
|
||||
|
||||
update, err := client.API.GetFinalityData()
|
||||
update, err := client.API.GetFinalityUpdate()
|
||||
require.NoError(t, err)
|
||||
|
||||
// normal
|
||||
|
|
@ -154,7 +154,7 @@ func TestVerifyFinalityUpdate(t *testing.T) {
|
|||
err = client.VerifyGenericUpdate(genericUpdate)
|
||||
require.Equal(t, ErrInvalidFinalityProof, err)
|
||||
// ErrInvalidSignature
|
||||
update, err = client.API.GetFinalityData()
|
||||
update, err = client.API.GetFinalityUpdate()
|
||||
require.NoError(t, err)
|
||||
|
||||
genericUpdate, err = FromLightClientFinalityUpdate(update)
|
||||
|
|
@ -168,7 +168,7 @@ func TestVerifyOptimisticUpdate(t *testing.T) {
|
|||
client, err := getClient(false, t)
|
||||
require.NoError(t, err)
|
||||
|
||||
update, err := client.API.GetOptimisticData()
|
||||
update, err := client.API.GetOptimisticUpdate()
|
||||
require.NoError(t, err)
|
||||
|
||||
// normal
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
package beacon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/portalnetwork/storage"
|
||||
"github.com/protolambda/zrnt/eth2/beacon/common"
|
||||
"github.com/protolambda/ztyp/codec"
|
||||
"github.com/protolambda/ztyp/tree"
|
||||
)
|
||||
|
||||
|
|
@ -12,40 +17,142 @@ const BeaconGenesisTime uint64 = 1606824023
|
|||
var _ ConsensusAPI = &PortalLightApi{}
|
||||
|
||||
type PortalLightApi struct {
|
||||
bn *BeaconNetwork
|
||||
portalProtocol *discover.PortalProtocol
|
||||
spec *common.Spec
|
||||
}
|
||||
|
||||
func NewPortalLightApi() *PortalLightApi {
|
||||
return &PortalLightApi{}
|
||||
}
|
||||
|
||||
func (api *PortalLightApi) GetUpdates(firstPeriod, count uint64) ([]zrntcommon.SpecObj, error) {
|
||||
return api.bn.GetUpdates(firstPeriod, count)
|
||||
}
|
||||
|
||||
func (api *PortalLightApi) GetCheckpointData(checkpointHash tree.Root) (zrntcommon.SpecObj, error) {
|
||||
return api.bn.GetCheckpointData(checkpointHash)
|
||||
}
|
||||
|
||||
func (api *PortalLightApi) GetFinalityData() (zrntcommon.SpecObj, error) {
|
||||
expectedCurrentSlot := api.bn.spec.TimeToSlot(zrntcommon.Timestamp(time.Now().Unix()), zrntcommon.Timestamp(BeaconGenesisTime))
|
||||
recentEpochStart := expectedCurrentSlot - (expectedCurrentSlot % api.bn.spec.SLOTS_PER_EPOCH) + 1
|
||||
return api.bn.GetFinalityUpdate(uint64(recentEpochStart))
|
||||
}
|
||||
|
||||
func (api *PortalLightApi) GetOptimisticData() (zrntcommon.SpecObj, error) {
|
||||
expectedCurrentSlot := api.bn.spec.TimeToSlot(zrntcommon.Timestamp(time.Now().Unix()), zrntcommon.Timestamp(BeaconGenesisTime))
|
||||
return api.bn.GetOptimisticUpdate(uint64(expectedCurrentSlot))
|
||||
}
|
||||
|
||||
func (api *PortalLightApi) ChainID() uint64 {
|
||||
// ChainID implements ConsensusAPI.
|
||||
func (p *PortalLightApi) ChainID() uint64 {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (api *PortalLightApi) Name() string {
|
||||
// GetCheckpointData implements ConsensusAPI.
|
||||
func (p *PortalLightApi) GetBootstrap(blockRoot tree.Root) (common.SpecObj, error) {
|
||||
bootstrapKey := &LightClientBootstrapKey{
|
||||
BlockHash: blockRoot[:],
|
||||
}
|
||||
contentKeyBytes, err := bootstrapKey.MarshalSSZ()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contentKey := storage.NewContentKey(LightClientBootstrap, contentKeyBytes).Encode()
|
||||
// Get from local
|
||||
contentId := p.portalProtocol.ToContentId(contentKey)
|
||||
res, err := p.getContent(contentKey, contentId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
forkedLightClientBootstrap := &ForkedLightClientBootstrap{}
|
||||
err = forkedLightClientBootstrap.Deserialize(p.spec, codec.NewDecodingReader(bytes.NewReader(res), uint64(len(res))))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return forkedLightClientBootstrap.Bootstrap, nil
|
||||
}
|
||||
|
||||
// GetFinalityData implements ConsensusAPI.
|
||||
func (p *PortalLightApi) GetFinalityUpdate() (common.SpecObj, error) {
|
||||
// Get the finality update for the most recent finalized epoch. We use 0 as the finalized
|
||||
// slot because the finalized slot is not known at this point and the protocol is
|
||||
// designed to return the most recent which is > 0
|
||||
finUpdateKey := &LightClientFinalityUpdateKey{
|
||||
FinalizedSlot: 0,
|
||||
}
|
||||
contentKeyBytes, err := finUpdateKey.MarshalSSZ()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contentKey := storage.NewContentKey(LightClientFinalityUpdate, contentKeyBytes).Encode()
|
||||
// Get from local
|
||||
contentId := p.portalProtocol.ToContentId(contentKey)
|
||||
res, err := p.getContent(contentKey, contentId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalityUpdate := &ForkedLightClientFinalityUpdate{}
|
||||
err = finalityUpdate.Deserialize(p.spec, codec.NewDecodingReader(bytes.NewReader(res), uint64(len(res))))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return finalityUpdate.LightClientFinalityUpdate, nil
|
||||
}
|
||||
|
||||
// GetOptimisticData implements ConsensusAPI.
|
||||
func (p *PortalLightApi) GetOptimisticUpdate() (common.SpecObj, error) {
|
||||
currentSlot := p.spec.TimeToSlot(common.Timestamp(time.Now().Unix()), common.Timestamp(BeaconGenesisTime))
|
||||
optimisticUpdateKey := &LightClientOptimisticUpdateKey{
|
||||
OptimisticSlot: uint64(currentSlot),
|
||||
}
|
||||
contentKeyBytes, err := optimisticUpdateKey.MarshalSSZ()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contentKey := storage.NewContentKey(LightClientOptimisticUpdate, contentKeyBytes).Encode()
|
||||
// Get from local
|
||||
contentId := p.portalProtocol.ToContentId(contentKey)
|
||||
res, err := p.getContent(contentKey, contentId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optimisticUpdate := &ForkedLightClientOptimisticUpdate{}
|
||||
err = optimisticUpdate.Deserialize(p.spec, codec.NewDecodingReader(bytes.NewReader(res), uint64(len(res))))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return optimisticUpdate.LightClientOptimisticUpdate, nil
|
||||
}
|
||||
|
||||
// GetUpdates implements ConsensusAPI.
|
||||
func (p *PortalLightApi) GetUpdates(firstPeriod uint64, count uint64) ([]common.SpecObj, error) {
|
||||
lightClientUpdateKey := &LightClientUpdateKey{
|
||||
StartPeriod: firstPeriod,
|
||||
Count: count,
|
||||
}
|
||||
contentKeyBytes, err := lightClientUpdateKey.MarshalSSZ()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contentKey := storage.NewContentKey(LightClientUpdate, contentKeyBytes).Encode()
|
||||
// Get from local
|
||||
contentId := p.portalProtocol.ToContentId(contentKey)
|
||||
data, err := p.getContent(contentKey, contentId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var lightClientUpdateRange LightClientUpdateRange = make([]ForkedLightClientUpdate, 0)
|
||||
err = lightClientUpdateRange.Deserialize(p.spec, codec.NewDecodingReader(bytes.NewReader(data), uint64(len(data))))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]common.SpecObj, len(lightClientUpdateRange))
|
||||
|
||||
for i, item := range lightClientUpdateRange {
|
||||
res[i] = item.LightClientUpdate
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Name implements ConsensusAPI.
|
||||
func (p *PortalLightApi) Name() string {
|
||||
return "portal"
|
||||
}
|
||||
|
||||
// func CurrentSlot() uint64 {
|
||||
|
||||
// }
|
||||
func (p *PortalLightApi) getContent(contentKey, contentId []byte) ([]byte, error) {
|
||||
res, err := p.portalProtocol.Get(contentKey, contentId)
|
||||
// other error
|
||||
if err != nil && !errors.Is(err, storage.ErrContentNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
if res == nil {
|
||||
// Get from remote
|
||||
res, _, err = p.portalProtocol.ContentLookup(contentKey, contentId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package beacon
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/protolambda/zrnt/eth2/beacon/altair"
|
||||
|
|
@ -38,6 +39,37 @@ type LightClientOptimisticUpdateKey struct {
|
|||
OptimisticSlot uint64
|
||||
}
|
||||
|
||||
type HistoricalSummariesWithProofKey struct {
|
||||
Epoch uint64
|
||||
}
|
||||
|
||||
func (v HistoricalSummariesWithProofKey) ByteLength() uint64 {
|
||||
return 8
|
||||
}
|
||||
|
||||
func (v HistoricalSummariesWithProofKey) FixedLength() uint64 {
|
||||
return 8
|
||||
}
|
||||
|
||||
func (v HistoricalSummariesWithProofKey) Serialize(w *codec.EncodingWriter) error {
|
||||
return w.WriteUint64(v.Epoch)
|
||||
}
|
||||
|
||||
func (v *HistoricalSummariesWithProofKey) Deserialize(r *codec.DecodingReader) error {
|
||||
d, err := r.ReadUint64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Epoch = d
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v HistoricalSummariesWithProofKey) HashTreeRoot(h tree.HashFn) common.Root {
|
||||
newRoot := common.Root{}
|
||||
binary.LittleEndian.PutUint64(newRoot[:], v.Epoch)
|
||||
return newRoot
|
||||
}
|
||||
|
||||
type ForkedLightClientBootstrap struct {
|
||||
ForkDigest common.ForkDigest
|
||||
Bootstrap common.SpecObj
|
||||
|
|
@ -305,22 +337,60 @@ type HistoricalSummariesWithProof struct {
|
|||
Proof *HistoricalSummariesProof
|
||||
}
|
||||
|
||||
func (hswp *HistoricalSummariesWithProof) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error {
|
||||
func (hswp HistoricalSummariesWithProof) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error {
|
||||
return dr.Container(&hswp.EPOCH, spec.Wrap(&hswp.HistoricalSummaries), hswp.Proof)
|
||||
}
|
||||
|
||||
func (hswp *HistoricalSummariesWithProof) Serialize(spec *common.Spec, w *codec.EncodingWriter) error {
|
||||
func (hswp HistoricalSummariesWithProof) Serialize(spec *common.Spec, w *codec.EncodingWriter) error {
|
||||
return w.Container(hswp.EPOCH, spec.Wrap(&hswp.HistoricalSummaries), hswp.Proof)
|
||||
}
|
||||
|
||||
func (hswp *HistoricalSummariesWithProof) ByteLength(spec *common.Spec) uint64 {
|
||||
func (hswp HistoricalSummariesWithProof) ByteLength(spec *common.Spec) uint64 {
|
||||
return codec.ContainerLength(hswp.EPOCH, spec.Wrap(&hswp.HistoricalSummaries), hswp.Proof)
|
||||
}
|
||||
|
||||
func (hswp *HistoricalSummariesWithProof) FixedLength(_ *common.Spec) uint64 {
|
||||
func (hswp HistoricalSummariesWithProof) FixedLength(_ *common.Spec) uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (hswp *HistoricalSummariesWithProof) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root {
|
||||
func (hswp HistoricalSummariesWithProof) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) common.Root {
|
||||
return hFn.HashTreeRoot(hswp.EPOCH, spec.Wrap(&hswp.HistoricalSummaries), hswp.Proof)
|
||||
}
|
||||
|
||||
type ForkedHistoricalSummariesWithProof struct {
|
||||
ForkDigest common.ForkDigest
|
||||
HistoricalSummariesWithProof HistoricalSummariesWithProof
|
||||
}
|
||||
|
||||
func (fhswp *ForkedHistoricalSummariesWithProof) Deserialize(spec *common.Spec, dr *codec.DecodingReader) error {
|
||||
_, err := dr.Read(fhswp.ForkDigest[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = fhswp.HistoricalSummariesWithProof.Deserialize(spec, dr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fhswp *ForkedHistoricalSummariesWithProof) Serialize(spec *common.Spec, w *codec.EncodingWriter) error {
|
||||
if err := w.Write(fhswp.ForkDigest[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
return fhswp.HistoricalSummariesWithProof.Serialize(spec, w)
|
||||
}
|
||||
|
||||
func (fhswp ForkedHistoricalSummariesWithProof) FixedLength(_ *common.Spec) uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (fhswp ForkedHistoricalSummariesWithProof) ByteLength(spec *common.Spec) uint64 {
|
||||
return 4 + fhswp.HistoricalSummariesWithProof.ByteLength(spec)
|
||||
}
|
||||
|
||||
func (fhswp ForkedHistoricalSummariesWithProof) HashTreeRoot(spec *common.Spec, h tree.HashFn) common.Root {
|
||||
return h.HashTreeRoot(fhswp.ForkDigest, spec.Wrap(common.SpecObj(fhswp.HistoricalSummariesWithProof)))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue