Skip to content

Commit

Permalink
feat: get header by block hash
Browse files Browse the repository at this point in the history
  • Loading branch information
fearlessfe authored and GrapeBaBa committed Jan 20, 2024
1 parent 534b834 commit baf8e8a
Show file tree
Hide file tree
Showing 6 changed files with 502 additions and 4 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ require (
github.com/mattn/go-sqlite3 v1.14.18
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
github.com/olekukonko/tablewriter v0.0.5
github.com/optimism-java/utp-go v0.0.0-20231225095152-5a9690d82b58
github.com/optimism-java/utp-go v0.0.0-20240117090415-3a5aad17f644
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7
Expand All @@ -71,7 +71,7 @@ require (
golang.org/x/crypto v0.17.0
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
golang.org/x/sync v0.5.0
golang.org/x/sys v0.15.0
golang.org/x/sys v0.16.0
golang.org/x/text v0.14.0
golang.org/x/time v0.3.0
golang.org/x/tools v0.15.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,8 @@ github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsq
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/optimism-java/utp-go v0.0.0-20231225095152-5a9690d82b58 h1:EZfd3NpJV+CL5vORquJ2O6eAUjkpI7+ge9a9n9HMyYE=
github.com/optimism-java/utp-go v0.0.0-20231225095152-5a9690d82b58/go.mod h1:DZ0jYzLzt4ZsCmhI/iqYgGFoNx45OfpEoKzXB8HVALQ=
github.com/optimism-java/utp-go v0.0.0-20240117090415-3a5aad17f644 h1:vrYEqCVnDS/Z3lLQa+GBrXRtIHN948TWy+aw04O9dpQ=
github.com/optimism-java/utp-go v0.0.0-20240117090415-3a5aad17f644/go.mod h1:DZ0jYzLzt4ZsCmhI/iqYgGFoNx45OfpEoKzXB8HVALQ=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
Expand Down Expand Up @@ -797,6 +799,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand Down
24 changes: 22 additions & 2 deletions p2p/discover/portal_protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ func (p *PortalProtocol) setupUDPListening() (*net.UDPConn, error) {
})

// TODO: ZAP PRODUCTION LOG
logger, err := zap.NewDevelopmentConfig().Build()
logger, err := zap.NewProductionConfig().Build()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1355,7 +1355,7 @@ func (p *PortalProtocol) ContentLookup(contentKey []byte) ([]byte, error) {
lookupContext, cancel := context.WithCancel(context.Background())
defer cancel()
resChan := make(chan []byte, 1)

defer close(resChan)
newLookup(lookupContext, p.table, p.Self().ID(), func(n *node) ([]*node, error) {
return p.contentLookupWorker(unwrapNode(n), contentKey, resChan)
}).run()
Expand Down Expand Up @@ -1390,6 +1390,26 @@ func (p *PortalProtocol) contentLookupWorker(n *enode.Node, contentKey []byte, r
return wrapedNode, nil
}

func (p *PortalProtocol) ToContentId(contentKey []byte) []byte {
return p.toContentId(contentKey)
}

func (p *PortalProtocol) InRange(contentId []byte) bool {
return inRange(p.Self().ID(), p.nodeRadius, contentId)
}

func (p *PortalProtocol) Get(contentId []byte) ([]byte, error) {
return p.storage.Get(contentId)
}

func (p *PortalProtocol) Put(contentId []byte, content []byte) error {
return p.storage.Put(contentId, content)
}

func (p *PortalProtocol) GetContent() <-chan *ContentElement {
return p.contentQueue
}

func inRange(nodeId enode.ID, nodeRadius *uint256.Int, contentId []byte) bool {
distance := enode.LogDist(nodeId, enode.ID(contentId))
disBig := new(big.Int).SetInt64(int64(distance))
Expand Down
203 changes: 203 additions & 0 deletions portalnetwork/history/history_network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package history

import (
"bytes"
"errors"
"fmt"

"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/portalnetwork/storage"
"github.com/ethereum/go-ethereum/rlp"
)

type ContentType byte

const (
BlockHeaderType ContentType = 0x00
BlockBodyType ContentType = 0x01
ReceiptsType ContentType = 0x02
EpochAccumulatorType ContentType = 0x03
)

var (
ErrWithdrawalHashIsNotEqual = errors.New("withdrawals hash is not equal")
ErrTxHashIsNotEqual = errors.New("tx hash is not equal")
ErrUnclesHashIsNotEqual = errors.New("uncles hash is not equal")
ErrReceiptsHashIsNotEqual = errors.New("receipts hash is not equal")
ErrContentOutOfRange = errors.New("content out of range")
ErrHeaderWithProofIsInvalid = errors.New("header proof is invalid")
ErrInvalidBlockHash = errors.New("invalid block hash")
)

type ContentKey struct {
selector ContentType
data []byte
}

func newContentKey(selector ContentType, hash []byte) *ContentKey {
return &ContentKey{
selector: selector,
data: hash,
}
}

func (c *ContentKey) encode() []byte {
res := make([]byte, 0, len(c.data)+1)
res = append(res, byte(c.selector))
res = append(res, c.data...)
return res
}

type HistoryNetwork struct {
portalProtocol *discover.PortalProtocol
masterAccumulator *MasterAccumulator
}

func NewHistoryNetwork(portalProtocol *discover.PortalProtocol, accu *MasterAccumulator) *HistoryNetwork {
return &HistoryNetwork{
portalProtocol: portalProtocol,
masterAccumulator: accu,
}
}

func (h *HistoryNetwork) Start() error {
err := h.portalProtocol.Start()
if err != nil {
return err
}
go h.processContentLoop()
return nil
}

// Currently doing 4 retries on lookups but only when the validation fails.
const requestRetries = 4

func (h *HistoryNetwork) GetBlockHeader(blockHash []byte) (*types.Header, error) {
contentKey := newContentKey(BlockHeaderType, 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 {
blockHeaderWithProof, err := DecodeBlockHeaderWithProof(res)
if err != nil {
return nil, err
}
header := new(types.Header)
err = rlp.DecodeBytes(blockHeaderWithProof.Header, header)
return header, err
}
// no content in local storage
for retries := 0; retries < requestRetries; retries++ {
// TODO log the err and continue
content, err := h.portalProtocol.ContentLookup(contentKey)
if err != nil {
continue
}

headerWithProof, err := DecodeBlockHeaderWithProof(content)
if err != nil {
continue
}

header, err := ValidateBlockHeaderBytes(headerWithProof.Header, blockHash)
if err != nil {
continue
}
valid, err := h.verifyHeader(header, *headerWithProof.Proof)
if err != nil || !valid {
continue
}
// TODO handle the error
_ = h.portalProtocol.Put(contentId, content)
return header, nil
}
return nil, storage.ErrContentNotFound
}

func (h *HistoryNetwork) verifyHeader(header *types.Header, proof BlockHeaderProof) (bool, error) {
return h.masterAccumulator.VerifyHeader(*header, proof)
}

func (h *HistoryNetwork) processContentLoop() {
contentChan := h.portalProtocol.GetContent()
for contentElement := range contentChan {
err := h.validateContents(contentElement.ContentKeys, contentElement.Contents)
if err != nil {
continue
}
// TODO gossip the validate content
}
}

func (h *HistoryNetwork) validateContent(contentKey []byte, content []byte) error {
switch ContentType(contentKey[0]) {
case BlockHeaderType:
headerWithProof, err := DecodeBlockHeaderWithProof(content)
if err != nil {
return err
}
header, err := ValidateBlockHeaderBytes(headerWithProof.Header, contentKey[1:])
if err != nil {
return err
}
valid, err := h.verifyHeader(header, *headerWithProof.Proof)
if err != nil {
return err
}
if !valid {
return ErrHeaderWithProofIsInvalid
}
return err
case BlockBodyType:
// TODO
case ReceiptsType:
// TODO
case EpochAccumulatorType:
// TODO
}
return errors.New("unknown content type")
}

func (h *HistoryNetwork) validateContents(contentKeys [][]byte, contents [][]byte) error {
for i, content := range contents {
contentKey := contentKeys[i]
err := h.validateContent(contentKey, content)
if err != nil {
return fmt.Errorf("content validate failed with content key %v", contentKey)
}
contentId := h.portalProtocol.ToContentId(contentKey)
_ = h.portalProtocol.Put(contentId, content)
}
return nil
}

func ValidateBlockHeaderBytes(headerBytes []byte, blockHash []byte) (*types.Header, error) {
header := new(types.Header)
err := rlp.DecodeBytes(headerBytes, header)
if err != nil {
return nil, err
}
if header.ExcessBlobGas != nil {
return nil, errors.New("EIP-4844 not yet implemented")
}
hash := header.Hash()
if !bytes.Equal(hash[:], blockHash) {
return nil, ErrInvalidBlockHash
}
return header, nil
}

func DecodeBlockHeaderWithProof(content []byte) (*BlockHeaderWithProof, error) {
headerWithProof := new(BlockHeaderWithProof)
err := headerWithProof.UnmarshalSSZ(content)
return headerWithProof, err
}
Loading

0 comments on commit baf8e8a

Please sign in to comment.