Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: type paramed Header interface #96

Merged
merged 5 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions header.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import (
)

// Header abstracts all methods required to perform header sync.
type Header interface {
// TODO: Ideally, this should be Header[H Header[H]], but GO does not support recursive type
// definitions (yet?)
type Header[H any] interface {
// New creates new instance of a header.
// It exists to overcome limitation of Go's type system.
// See:
//https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#pointer-method-example
New() Header
New() H
// IsZero reports whether Header is a zero value of it's concrete type.
IsZero() bool
// ChainID returns identifier of the chain.
Expand All @@ -25,7 +27,7 @@ type Header interface {
// Time returns time when header was created.
Time() time.Time
// Verify validates given untrusted Header against trusted Header.
Verify(Header) error
Verify(H) error
// Validate performs stateless validation to check for missed/incorrect fields.
Validate() error

Expand All @@ -34,6 +36,6 @@ type Header interface {
}

// New is a generic Header constructor.
func New[H Header]() (h H) {
return h.New().(H)
func New[H Header[H]]() (h H) {
return h.New()
}
6 changes: 3 additions & 3 deletions headertest/dummy_header.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func RandDummyHeader(t *testing.T) *DummyHeader {
return dh
}

func (d *DummyHeader) New() header.Header {
func (d *DummyHeader) New() *DummyHeader {
return new(DummyHeader)
}

Expand Down Expand Up @@ -96,8 +96,8 @@ func (d *DummyHeader) IsExpired(period time.Duration) bool {
return expirationTime.Before(time.Now())
}

func (d *DummyHeader) Verify(header header.Header) error {
if dummy, _ := header.(*DummyHeader); dummy.VerifyFailure {
func (d *DummyHeader) Verify(header *DummyHeader) error {
if header.VerifyFailure {
return ErrDummyVerify
}

Expand Down
8 changes: 4 additions & 4 deletions headertest/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (
"github.com/celestiaorg/go-header"
)

type Generator[H header.Header] interface {
type Generator[H header.Header[H]] interface {
NextHeader() H
}

type Store[H header.Header] struct {
type Store[H header.Header[H]] struct {
Headers map[int64]H
HeadHeight int64
}
Expand All @@ -23,7 +23,7 @@ func NewDummyStore(t *testing.T) *Store[*DummyHeader] {
}

// NewStore creates a generic mock store supporting different type of Headers based on Generator.
func NewStore[H header.Header](t *testing.T, gen Generator[H], numHeaders int) *Store[H] {
func NewStore[H header.Header[H]](t *testing.T, gen Generator[H], numHeaders int) *Store[H] {
store := &Store[H]{
Headers: make(map[int64]H),
HeadHeight: 0,
Expand All @@ -48,7 +48,7 @@ func (m *Store[H]) Height() uint64 {
return uint64(m.HeadHeight)
}

func (m *Store[H]) Head(context.Context, ...header.HeadOption) (H, error) {
func (m *Store[H]) Head(context.Context, ...header.HeadOption[H]) (H, error) {
return m.Headers[m.HeadHeight], nil
}

Expand Down
2 changes: 1 addition & 1 deletion headertest/subscriber.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/celestiaorg/go-header"
)

type Subscriber[H header.Header] struct {
type Subscriber[H header.Header[H]] struct {
Headers []H
}

Expand Down
16 changes: 8 additions & 8 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const (
// Subscriber encompasses the behavior necessary to
// subscribe/unsubscribe from new Header events from the
// network.
type Subscriber[H Header] interface {
type Subscriber[H Header[H]] interface {
// Subscribe creates long-living Subscription for validated Headers.
// Multiple Subscriptions can be created.
Subscribe() (Subscription[H], error)
Expand All @@ -28,7 +28,7 @@ type Subscriber[H Header] interface {
}

// Subscription listens for new Headers.
type Subscription[H Header] interface {
type Subscription[H Header[H]] interface {
// NextHeader returns the newest verified and valid Header
// in the network.
NextHeader(ctx context.Context) (H, error)
Expand All @@ -37,13 +37,13 @@ type Subscription[H Header] interface {
}

// Broadcaster broadcasts a Header to the network.
type Broadcaster[H Header] interface {
type Broadcaster[H Header[H]] interface {
Broadcast(ctx context.Context, header H, opts ...pubsub.PubOpt) error
}

// Exchange encompasses the behavior necessary to request Headers
// from the network.
type Exchange[H Header] interface {
type Exchange[H Header[H]] interface {
Getter[H]
}

Expand Down Expand Up @@ -71,7 +71,7 @@ func (ena *ErrNonAdjacent) Error() string {

// Store encompasses the behavior necessary to store and retrieve Headers
// from a node's local storage.
type Store[H Header] interface {
type Store[H Header[H]] interface {
// Start starts the store.
Start(context.Context) error

Expand Down Expand Up @@ -104,7 +104,7 @@ type Store[H Header] interface {

// Getter contains the behavior necessary for a component to retrieve
// headers that have been processed during header sync.
type Getter[H Header] interface {
type Getter[H Header[H]] interface {
Head[H]

// Get returns the Header corresponding to the given hash.
Expand All @@ -124,7 +124,7 @@ type Getter[H Header] interface {
// Head contains the behavior necessary for a component to retrieve
// the chain head. Note that "chain head" is subjective to the component
// reporting it.
type Head[H Header] interface {
type Head[H Header[H]] interface {
// Head returns the latest known header.
Head(context.Context, ...HeadOption) (H, error)
Head(context.Context, ...HeadOption[H]) (H, error)
}
6 changes: 3 additions & 3 deletions local/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import (
)

// Exchange is a simple Exchange that reads Headers from Store without any networking.
type Exchange[H header.Header] struct {
type Exchange[H header.Header[H]] struct {
store header.Store[H]
}

// NewExchange creates a new local Exchange.
func NewExchange[H header.Header](store header.Store[H]) header.Exchange[H] {
func NewExchange[H header.Header[H]](store header.Store[H]) header.Exchange[H] {
return &Exchange[H]{
store: store,
}
Expand All @@ -26,7 +26,7 @@ func (l *Exchange[H]) Stop(context.Context) error {
return nil
}

func (l *Exchange[H]) Head(ctx context.Context, _ ...header.HeadOption) (H, error) {
func (l *Exchange[H]) Head(ctx context.Context, _ ...header.HeadOption[H]) (H, error) {
return l.store.Head(ctx)
}

Expand Down
2 changes: 1 addition & 1 deletion metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
var meter = otel.Meter("header")

// WithMetrics enables Otel metrics to monitor head and total amount of synced headers.
func WithMetrics[H Header](store Store[H]) error {
func WithMetrics[H Header[H]](store Store[H]) error {
headC, _ := meter.Int64ObservableCounter(
"head",
metric.WithDescription("Subjective head of the node"),
Expand Down
10 changes: 5 additions & 5 deletions opts.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package header

type HeadOption func(opts *HeadParams)
type HeadOption[H Header[H]] func(opts *HeadParams[H])

// HeadParams contains options to be used for Head interface methods
type HeadParams struct {
type HeadParams[H Header[H]] struct {
// TrustedHead allows the caller of Head to specify a trusted header
// against which the underlying implementation of Head can verify against.
TrustedHead Header
TrustedHead Header[H]
}

// WithTrustedHead sets the TrustedHead parameter to the given header.
func WithTrustedHead(verified Header) func(opts *HeadParams) {
return func(opts *HeadParams) {
func WithTrustedHead[H Header[H]](verified Header[H]) func(opts *HeadParams[H]) {
return func(opts *HeadParams[H]) {
opts.TrustedHead = verified
}
}
10 changes: 5 additions & 5 deletions p2p/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var maxUntrustedHeadRequests = 4

// Exchange enables sending outbound HeaderRequests to the network as well as
// handling inbound HeaderRequests from the network.
type Exchange[H header.Header] struct {
type Exchange[H header.Header[H]] struct {
ctx context.Context
cancel context.CancelFunc

Expand All @@ -47,7 +47,7 @@ type Exchange[H header.Header] struct {
metrics *metrics
}

func NewExchange[H header.Header](
func NewExchange[H header.Header[H]](
host host.Host,
peers peer.IDSlice,
gater *conngater.BasicConnectionGater,
Expand Down Expand Up @@ -100,7 +100,7 @@ func (ex *Exchange[H]) Stop(ctx context.Context) error {
// The Head must be verified thereafter where possible.
// We request in parallel all the trusted peers, compare their response
// and return the highest one.
func (ex *Exchange[H]) Head(ctx context.Context, opts ...header.HeadOption) (H, error) {
func (ex *Exchange[H]) Head(ctx context.Context, opts ...header.HeadOption[H]) (H, error) {
log.Debug("requesting head")

reqCtx := ctx
Expand All @@ -115,7 +115,7 @@ func (ex *Exchange[H]) Head(ctx context.Context, opts ...header.HeadOption) (H,
defer cancel()
}

reqParams := header.HeadParams{}
reqParams := header.HeadParams[H]{}
for _, opt := range opts {
opt(&reqParams)
}
Expand Down Expand Up @@ -344,7 +344,7 @@ func shufflePeers(peers peer.IDSlice) peer.IDSlice {
// * should be received at least from 2 peers;
// If neither condition is met, then latest Header will be returned (header of the highest
// height).
func bestHead[H header.Header](result []H) (H, error) {
func bestHead[H header.Header[H]](result []H) (H, error) {
if len(result) == 0 {
var zero H
return zero, header.ErrNotFound
Expand Down
6 changes: 3 additions & 3 deletions p2p/exchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestExchange_RequestHead(t *testing.T) {

tests := []struct {
requestFromTrusted bool
lastHeader header.Header
lastHeader header.Header[*headertest.DummyHeader]
expectedHeight int64
expectedHash header.Hash
}{
Expand All @@ -74,7 +74,7 @@ func TestExchange_RequestHead(t *testing.T) {

for i, tt := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
var opts []header.HeadOption
var opts []header.HeadOption[*headertest.DummyHeader]
if !tt.requestFromTrusted {
opts = append(opts, header.WithTrustedHead(tt.lastHeader))
}
Expand Down Expand Up @@ -583,7 +583,7 @@ func (t *timedOutStore) HasAt(_ context.Context, _ uint64) bool {
return true
}

func (t *timedOutStore) Head(context.Context, ...header.HeadOption) (*headertest.DummyHeader, error) {
func (t *timedOutStore) Head(context.Context, ...header.HeadOption[*headertest.DummyHeader]) (*headertest.DummyHeader, error) {
time.Sleep(t.timeout)
return nil, header.ErrNoHead
}
4 changes: 2 additions & 2 deletions p2p/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var (

// ExchangeServer represents the server-side component for
// responding to inbound header-related requests.
type ExchangeServer[H header.Header] struct {
type ExchangeServer[H header.Header[H]] struct {
protocolID protocol.ID

host host.Host
Expand All @@ -39,7 +39,7 @@ type ExchangeServer[H header.Header] struct {

// NewExchangeServer returns a new P2P server that handles inbound
// header-related requests.
func NewExchangeServer[H header.Header](
func NewExchangeServer[H header.Header[H]](
host host.Host,
store header.Store[H],
opts ...Option[ServerParameters],
Expand Down
10 changes: 5 additions & 5 deletions p2p/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ import (
// response.
var errEmptyResponse = errors.New("empty response")

type option[H header.Header] func(*session[H])
type option[H header.Header[H]] func(*session[H])

func withValidation[H header.Header](from H) option[H] {
func withValidation[H header.Header[H]](from H) option[H] {
return func(s *session[H]) {
s.from = from
}
}

// session aims to divide a range of headers
// into several smaller requests among different peers.
type session[H header.Header] struct {
type session[H header.Header[H]] struct {
host host.Host
protocolID protocol.ID
queue *peerQueue
Expand All @@ -45,7 +45,7 @@ type session[H header.Header] struct {
reqCh chan *p2p_pb.HeaderRequest
}

func newSession[H header.Header](
func newSession[H header.Header[H]](
ctx context.Context,
h host.Host,
peerTracker *peerTracker,
Expand Down Expand Up @@ -284,7 +284,7 @@ func prepareRequests(from, amount, headersPerPeer uint64) []*p2p_pb.HeaderReques
}

// processResponses converts HeaderResponses to Headers
func processResponses[H header.Header](resps []*p2p_pb.HeaderResponse) ([]H, error) {
func processResponses[H header.Header[H]](resps []*p2p_pb.HeaderResponse) ([]H, error) {
if len(resps) == 0 {
return nil, errEmptyResponse
}
Expand Down
4 changes: 2 additions & 2 deletions p2p/subscriber.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

// Subscriber manages the lifecycle and relationship of header Module
// with the "header-sub" gossipsub topic.
type Subscriber[H header.Header] struct {
type Subscriber[H header.Header[H]] struct {
pubsubTopicID string

pubsub *pubsub.PubSub
Expand All @@ -23,7 +23,7 @@ type Subscriber[H header.Header] struct {

// NewSubscriber returns a Subscriber that manages the header Module's
// relationship with the "header-sub" gossipsub topic.
func NewSubscriber[H header.Header](
func NewSubscriber[H header.Header[H]](
ps *pubsub.PubSub,
msgID pubsub.MsgIdFunction,
networkID string,
Expand Down
4 changes: 2 additions & 2 deletions p2p/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import (
)

// subscription handles retrieving Headers from the header pubsub topic.
type subscription[H header.Header] struct {
type subscription[H header.Header[H]] struct {
topic *pubsub.Topic
subscription *pubsub.Subscription
}

// newSubscription creates a new Header event subscription
// on the given host.
func newSubscription[H header.Header](topic *pubsub.Topic) (*subscription[H], error) {
func newSubscription[H header.Header[H]](topic *pubsub.Topic) (*subscription[H], error) {
sub, err := topic.Subscribe()
if err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions store/batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import (
// unlike the Store which keeps 'hash -> header' and 'height -> hash'.
// The approach simplifies implementation for the batch and
// makes it better optimized for the GetByHeight case which is what we need.
type batch[H header.Header] struct {
type batch[H header.Header[H]] struct {
lk sync.RWMutex
heights map[string]uint64
headers []H
}

// newBatch creates the batch with the given pre-allocated size.
func newBatch[H header.Header](size int) *batch[H] {
func newBatch[H header.Header[H]](size int) *batch[H] {
return &batch[H]{
heights: make(map[string]uint64, size),
headers: make([]H, 0, size),
Expand Down
Loading