mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-16 00:43:46 +00:00
feat: get body and receipts
This commit is contained in:
parent
baf8e8aac7
commit
577a61c863
5 changed files with 465 additions and 22 deletions
|
|
@ -1373,7 +1373,7 @@ func (p *PortalProtocol) contentLookupWorker(n *enode.Node, contentKey []byte, r
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
switch flag {
|
switch flag {
|
||||||
case portalwire.ContentRawSelector:
|
case portalwire.ContentRawSelector, portalwire.ContentConnIdSelector:
|
||||||
content, ok := content.([]byte)
|
content, ok := content.([]byte)
|
||||||
if !ok {
|
if !ok {
|
||||||
return wrapedNode, fmt.Errorf("failed to assert to raw content, value is: %v", content)
|
return wrapedNode, fmt.Errorf("failed to assert to raw content, value is: %v", content)
|
||||||
|
|
|
||||||
|
|
@ -318,24 +318,6 @@ func TestCancel(t *testing.T) {
|
||||||
time.Sleep(time.Second * 3)
|
time.Sleep(time.Second * 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// func TestInsertWithDistance(t *testing.T) {
|
|
||||||
// targetId := uint256.NewInt(0).Bytes32()
|
|
||||||
// nodes := make([]*node, 0, 10)
|
|
||||||
// for i := 0; i < 10; i++ {
|
|
||||||
// enode := &node{
|
|
||||||
// Node: enode.Node{
|
|
||||||
// id: enode.ID(uint256.NewInt(uint64(i)).Bytes32()),
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// nodes = append(nodes, enode)
|
|
||||||
// }
|
|
||||||
// newNode := &node{
|
|
||||||
// Node: &enode.Node{
|
|
||||||
// id: uint256.NewInt(20).Bytes32(),
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
func TestContentLookup(t *testing.T) {
|
func TestContentLookup(t *testing.T) {
|
||||||
node1, err := setupLocalPortalNode(":17777", nil)
|
node1, err := setupLocalPortalNode(":17777", nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/portalnetwork/storage"
|
"github.com/ethereum/go-ethereum/portalnetwork/storage"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContentType byte
|
type ContentType byte
|
||||||
|
|
@ -123,10 +124,299 @@ func (h *HistoryNetwork) GetBlockHeader(blockHash []byte) (*types.Header, error)
|
||||||
return nil, storage.ErrContentNotFound
|
return nil, storage.ErrContentNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *HistoryNetwork) GetBlockBody(blockHash []byte) (*types.Body, error) {
|
||||||
|
header, err := h.GetBlockHeader(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
contentKey := newContentKey(BlockBodyType, blockHash).encode()
|
||||||
|
contentId := h.portalProtocol.ToContentId(contentKey)
|
||||||
|
|
||||||
|
if !h.portalProtocol.InRange(contentId) {
|
||||||
|
return nil, ErrContentOutOfRange
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.portalProtocol.Get(contentId)
|
||||||
|
// other error
|
||||||
|
// TODO maybe use nil res to replace the ErrContentNotFound
|
||||||
|
if err != nil && err != storage.ErrContentNotFound {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// no error
|
||||||
|
if err == nil {
|
||||||
|
body, err := DecodePortalBlockBodyBytes(res)
|
||||||
|
return body, err
|
||||||
|
}
|
||||||
|
// no content in local storage
|
||||||
|
|
||||||
|
for retries := 0; retries < requestRetries; retries++ {
|
||||||
|
content, err := h.portalProtocol.ContentLookup(contentKey)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
body, err := DecodePortalBlockBodyBytes(content)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateBlockBody(body, header)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO handle the error
|
||||||
|
_ = h.portalProtocol.Put(contentId, content)
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
return nil, storage.ErrContentNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HistoryNetwork) GetReceipts(blockHash []byte) ([]*types.Receipt, error) {
|
||||||
|
header, err := h.GetBlockHeader(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
contentKey := newContentKey(ReceiptsType, blockHash).encode()
|
||||||
|
contentId := h.portalProtocol.ToContentId(contentKey)
|
||||||
|
|
||||||
|
if !h.portalProtocol.InRange(contentId) {
|
||||||
|
return nil, ErrContentOutOfRange
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.portalProtocol.Get(contentId)
|
||||||
|
// other error
|
||||||
|
if err != nil && err != storage.ErrContentNotFound {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// no error
|
||||||
|
if err == nil {
|
||||||
|
portalReceipte := new(PortalReceipts)
|
||||||
|
err := portalReceipte.UnmarshalSSZ(res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
receipts, err := FromPortalReceipts(portalReceipte)
|
||||||
|
return receipts, err
|
||||||
|
}
|
||||||
|
// no content in local storage
|
||||||
|
|
||||||
|
for retries := 0; retries < requestRetries; retries++ {
|
||||||
|
content, err := h.portalProtocol.ContentLookup(contentKey)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
receipts, err := ValidatePortalReceiptsBytes(content, header.ReceiptHash.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO handle the error
|
||||||
|
_ = h.portalProtocol.Put(contentId, content)
|
||||||
|
return receipts, nil
|
||||||
|
}
|
||||||
|
return nil, storage.ErrContentNotFound
|
||||||
|
}
|
||||||
|
|
||||||
func (h *HistoryNetwork) verifyHeader(header *types.Header, proof BlockHeaderProof) (bool, error) {
|
func (h *HistoryNetwork) verifyHeader(header *types.Header, proof BlockHeaderProof) (bool, error) {
|
||||||
return h.masterAccumulator.VerifyHeader(*header, proof)
|
return h.masterAccumulator.VerifyHeader(*header, proof)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidateBlockBodyBytes(bodyBytes []byte, header *types.Header) (*types.Body, error) {
|
||||||
|
// TODO check shanghai, pos and legacy block
|
||||||
|
body, err := DecodePortalBlockBodyBytes(bodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = validateBlockBody(body, header)
|
||||||
|
return body, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodePortalBlockBodyBytes(bodyBytes []byte) (*types.Body, error) {
|
||||||
|
blockBodyShanghai := new(PortalBlockBodyShanghai)
|
||||||
|
err := blockBodyShanghai.UnmarshalSSZ(bodyBytes)
|
||||||
|
if err == nil {
|
||||||
|
return FromPortalBlockBodyShanghai(blockBodyShanghai)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockBodyLegacy := new(BlockBodyLegacy)
|
||||||
|
err = blockBodyLegacy.UnmarshalSSZ(bodyBytes)
|
||||||
|
if err == nil {
|
||||||
|
return FromBlockBodyLegacy(blockBodyLegacy)
|
||||||
|
}
|
||||||
|
return nil, errors.New("all portal block body decodings failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateBlockBody(body *types.Body, header *types.Header) error {
|
||||||
|
if hash := types.CalcUncleHash(body.Uncles); !bytes.Equal(hash[:], header.UncleHash.Bytes()) {
|
||||||
|
return ErrUnclesHashIsNotEqual
|
||||||
|
}
|
||||||
|
|
||||||
|
if hash := types.DeriveSha(types.Transactions(body.Transactions), trie.NewStackTrie(nil)); !bytes.Equal(hash[:], header.TxHash.Bytes()) {
|
||||||
|
return ErrTxHashIsNotEqual
|
||||||
|
}
|
||||||
|
if body.Withdrawals == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if hash := types.DeriveSha(types.Withdrawals(body.Withdrawals), trie.NewStackTrie(nil)); !bytes.Equal(hash[:], header.WithdrawalsHash.Bytes()) {
|
||||||
|
return ErrWithdrawalHashIsNotEqual
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBlockBody encode types.Body to ssz bytes
|
||||||
|
func EncodeBlockBody(body *types.Body) ([]byte, error) {
|
||||||
|
if body.Withdrawals != nil && len(body.Withdrawals) > 0 {
|
||||||
|
blockShanghai, err := toPortalBlockBodyShanghai(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return blockShanghai.MarshalSSZ()
|
||||||
|
} else {
|
||||||
|
legacyBlock, err := toBlockBodyLegacy(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return legacyBlock.MarshalSSZ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// toPortalBlockBodyShanghai convert types.Body to PortalBlockBodyShanghai
|
||||||
|
func toPortalBlockBodyShanghai(b *types.Body) (*PortalBlockBodyShanghai, error) {
|
||||||
|
legacy, err := toBlockBodyLegacy(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawals := make([][]byte, 0, len(b.Withdrawals))
|
||||||
|
for _, w := range b.Withdrawals {
|
||||||
|
b, err := rlp.EncodeToBytes(w)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawals = append(withdrawals, b)
|
||||||
|
}
|
||||||
|
return &PortalBlockBodyShanghai{Transactions: legacy.Transactions, Uncles: legacy.Uncles, Withdrawals: withdrawals}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toBlockBodyLegacy convert types.Body to BlockBodyLegacy
|
||||||
|
func toBlockBodyLegacy(b *types.Body) (*BlockBodyLegacy, error) {
|
||||||
|
txs := make([][]byte, 0, len(b.Transactions))
|
||||||
|
|
||||||
|
for _, tx := range b.Transactions {
|
||||||
|
txBytes, err := rlp.EncodeToBytes(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txs = append(txs, txBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
uncleBytes, err := rlp.EncodeToBytes(b.Uncles)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &BlockBodyLegacy{Uncles: uncleBytes, Transactions: txs}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromPortalBlockBodyShanghai convert PortalBlockBodyShanghai to types.Body
|
||||||
|
func FromPortalBlockBodyShanghai(b *PortalBlockBodyShanghai) (*types.Body, error) {
|
||||||
|
transactions := make([]*types.Transaction, 0, len(b.Transactions))
|
||||||
|
for _, t := range b.Transactions {
|
||||||
|
tran := new(types.Transaction)
|
||||||
|
err := tran.UnmarshalBinary(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transactions = append(transactions, tran)
|
||||||
|
}
|
||||||
|
uncles := make([]*types.Header, 0, len(b.Uncles))
|
||||||
|
err := rlp.DecodeBytes(b.Uncles, &uncles)
|
||||||
|
withdrawals := make([]*types.Withdrawal, 0, len(b.Withdrawals))
|
||||||
|
for _, w := range b.Withdrawals {
|
||||||
|
withdrawal := new(types.Withdrawal)
|
||||||
|
err := rlp.DecodeBytes(w, withdrawal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withdrawals = append(withdrawals, withdrawal)
|
||||||
|
}
|
||||||
|
return &types.Body{
|
||||||
|
Uncles: uncles,
|
||||||
|
Transactions: transactions,
|
||||||
|
Withdrawals: withdrawals,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromBlockBodyLegacy convert BlockBodyLegacy to types.Body
|
||||||
|
func FromBlockBodyLegacy(b *BlockBodyLegacy) (*types.Body, error) {
|
||||||
|
transactions := make([]*types.Transaction, 0, len(b.Transactions))
|
||||||
|
for _, t := range b.Transactions {
|
||||||
|
tran := new(types.Transaction)
|
||||||
|
err := tran.UnmarshalBinary(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transactions = append(transactions, tran)
|
||||||
|
}
|
||||||
|
uncles := make([]*types.Header, 0, len(b.Uncles))
|
||||||
|
err := rlp.DecodeBytes(b.Uncles, &uncles)
|
||||||
|
return &types.Body{
|
||||||
|
Uncles: uncles,
|
||||||
|
Transactions: transactions,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromPortalReceipts convert PortalReceipts to types.Receipt
|
||||||
|
func FromPortalReceipts(r *PortalReceipts) ([]*types.Receipt, error) {
|
||||||
|
res := make([]*types.Receipt, 0, len(r.Receipts))
|
||||||
|
for _, reci := range r.Receipts {
|
||||||
|
recipt := new(types.Receipt)
|
||||||
|
err := recipt.UnmarshalBinary(reci)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = append(res, recipt)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidatePortalReceiptsBytes(receiptBytes, receiptsRoot []byte) ([]*types.Receipt, error) {
|
||||||
|
portalReceipts := new(PortalReceipts)
|
||||||
|
err := portalReceipts.UnmarshalSSZ(receiptBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
receipts, err := FromPortalReceipts(portalReceipts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
root := types.DeriveSha(types.Receipts(receipts), trie.NewStackTrie(nil))
|
||||||
|
|
||||||
|
if !bytes.Equal(root[:], receiptsRoot) {
|
||||||
|
return nil, errors.New("receipt root is not equal to the header.ReceiptHash")
|
||||||
|
}
|
||||||
|
return receipts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncodeReceipts(receipts []*types.Receipt) ([]byte, error) {
|
||||||
|
portalReceipts, err := ToPortalReceipts(receipts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return portalReceipts.MarshalSSZ()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToPortalReceipts convert types.Receipt to PortalReceipts
|
||||||
|
func ToPortalReceipts(receipts []*types.Receipt) (*PortalReceipts, error) {
|
||||||
|
res := make([][]byte, 0, len(receipts))
|
||||||
|
for _, r := range receipts {
|
||||||
|
b, err := r.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = append(res, b)
|
||||||
|
}
|
||||||
|
return &PortalReceipts{Receipts: res}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *HistoryNetwork) processContentLoop() {
|
func (h *HistoryNetwork) processContentLoop() {
|
||||||
contentChan := h.portalProtocol.GetContent()
|
contentChan := h.portalProtocol.GetContent()
|
||||||
for contentElement := range contentChan {
|
for contentElement := range contentChan {
|
||||||
|
|
@ -158,9 +448,19 @@ func (h *HistoryNetwork) validateContent(contentKey []byte, content []byte) erro
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
case BlockBodyType:
|
case BlockBodyType:
|
||||||
// TODO
|
header, err := h.GetBlockHeader(contentKey[1:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = ValidateBlockBodyBytes(content, header)
|
||||||
|
return err
|
||||||
case ReceiptsType:
|
case ReceiptsType:
|
||||||
// TODO
|
header, err := h.GetBlockHeader(contentKey[1:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = ValidatePortalReceiptsBytes(content, header.ReceiptHash.Bytes())
|
||||||
|
return err
|
||||||
case EpochAccumulatorType:
|
case EpochAccumulatorType:
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -6,7 +6,7 @@ import (
|
||||||
ssz "github.com/ferranbt/fastssz"
|
ssz "github.com/ferranbt/fastssz"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate sszgen --path types.go --exclude-objs BlockHeaderProof
|
//go:generate sszgen --path types.go --exclude-objs BlockHeaderProof,PortalReceipts
|
||||||
|
|
||||||
type BlockHeaderProofType uint8
|
type BlockHeaderProofType uint8
|
||||||
|
|
||||||
|
|
@ -106,3 +106,87 @@ func (p *BlockHeaderProof) SizeSSZ() (size int) {
|
||||||
func (p *BlockHeaderProof) HashTreeRootWith(hh ssz.HashWalker) (err error) {
|
func (p *BlockHeaderProof) HashTreeRootWith(hh ssz.HashWalker) (err error) {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PortalReceipts struct {
|
||||||
|
Receipts [][]byte `ssz-max:"134217728,16384"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalSSZ ssz marshals the PortalReceipts object
|
||||||
|
func (p *PortalReceipts) MarshalSSZ() ([]byte, error) {
|
||||||
|
return ssz.MarshalSSZ(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalSSZTo ssz marshals the PortalReceipts object to a target array
|
||||||
|
func (p *PortalReceipts) MarshalSSZTo(buf []byte) (dst []byte, err error) {
|
||||||
|
dst = buf
|
||||||
|
|
||||||
|
if size := len(p.Receipts); size > 134217728 {
|
||||||
|
err = ssz.ErrListTooBigFn("PortalReceipts.Receipts", size, 134217728)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
{
|
||||||
|
offset := 4 * len(p.Receipts)
|
||||||
|
for ii := 0; ii < len(p.Receipts); ii++ {
|
||||||
|
dst = ssz.WriteOffset(dst, offset)
|
||||||
|
offset += len(p.Receipts[ii])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ii := 0; ii < len(p.Receipts); ii++ {
|
||||||
|
if size := len(p.Receipts[ii]); size > 16384 {
|
||||||
|
err = ssz.ErrBytesLengthFn("PortalReceipts.Receipts[ii]", size, 16384)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dst = append(dst, p.Receipts[ii]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalSSZ ssz unmarshals the PortalReceipts object
|
||||||
|
func (p *PortalReceipts) UnmarshalSSZ(buf []byte) error {
|
||||||
|
var err error
|
||||||
|
size := uint64(len(buf))
|
||||||
|
if size < 4 {
|
||||||
|
return ssz.ErrSize
|
||||||
|
}
|
||||||
|
// Field (0) 'Receipts'
|
||||||
|
{
|
||||||
|
num, err := ssz.DecodeDynamicLength(buf, 134217728)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Receipts = make([][]byte, num)
|
||||||
|
err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) {
|
||||||
|
if len(buf) > 16384 {
|
||||||
|
return ssz.ErrBytesLength
|
||||||
|
}
|
||||||
|
if cap(p.Receipts[indx]) == 0 {
|
||||||
|
p.Receipts[indx] = make([]byte, 0, len(buf))
|
||||||
|
}
|
||||||
|
p.Receipts[indx] = append(p.Receipts[indx], buf...)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SizeSSZ returns the ssz encoded size in bytes for the PortalReceipts object
|
||||||
|
func (p *PortalReceipts) SizeSSZ() (size int) {
|
||||||
|
size = 0
|
||||||
|
|
||||||
|
// Field (0) 'Receipts'
|
||||||
|
for ii := 0; ii < len(p.Receipts); ii++ {
|
||||||
|
size += 4
|
||||||
|
size += len(p.Receipts[ii])
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashTreeRootWith ssz hashes the PortalReceipts object with a hasher
|
||||||
|
func (p *PortalReceipts) HashTreeRootWith(hh ssz.HashWalker) (err error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue