From 50bd637733aa29d3782dd4b6b3376b7770fccc09 Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Fri, 6 Oct 2023 10:27:12 -0500 Subject: [PATCH 01/13] initial specs --- p2p/p2p.md | 198 ++++++++++++++++++++++++++++++++++++++++++++ specs/src/README.md | 53 +++++++++++- store/store.md | 104 +++++++++++++++++++++++ sync/sync.md | 14 ++++ 4 files changed, 368 insertions(+), 1 deletion(-) diff --git a/p2p/p2p.md b/p2p/p2p.md index e69de29b..c26c263f 100644 --- a/p2p/p2p.md +++ b/p2p/p2p.md @@ -0,0 +1,198 @@ +# P2P + +The p2p package mainly contains two services: + +1) Subscriber +2) Exchange + +## Subscriber + +Subscriber is a service that manages gossip of headers among the nodes in the p2p network by using [libp2p][libp2p] and its [pubsub][pubsub] modules. The pubsub topic used for gossip (`//header-sub/v0.0.1`) is configurable based on `networkID` parameter used to initialize the subscriber service. + +The Subscriber implements the following interface: + +``` +// Subscriber encompasses the behavior necessary to +// subscribe/unsubscribe from new Header events from the +// network. +type Subscriber[H Header[H]] interface { + // Subscribe creates long-living Subscription for validated Headers. + // Multiple Subscriptions can be created. + Subscribe() (Subscription[H], error) + // SetVerifier registers verification func for all Subscriptions. + // Registered func screens incoming headers + // before they are forwarded to Subscriptions. + // Only one func can be set. + SetVerifier(func(context.Context, H) error) error +} +``` + +The `Subscribe()` method allows listening to any new headers that are published to the p2p network. The `SetVerifier()` method allows for setting a custom verifier that will be executed upon receiving any new headers from the p2p network. This is a very useful customization for the consumers of go-header library to pass any custom logic as part of the pubsub. + +## Exchange + +An exchange is a combination of: +* Exchange: a client for requesting headers from the p2p network (outbound) +* ExchangeServer: a p2p server for handling inbound header requests + +### Exchange Client + +Exchange defines a client for requesting headers from the p2p network. An exchange client is initialized using self [host.Host][host], list of peers in the form of slice [peer.IDSlice][peer], and [connection gater][gater] for blocking and allowing nodes. Optional parameters like `ChainID` and `NetworkID` can also be passed. The exchange client also maintains a list of trusted peers via a peer tracker. + +#### Peer Tracker + +//TODO + +#### Trusted Peers vs Tracked Peers + +//TODO + +The exchange client implements the following interface: + +``` +// Getter contains the behavior necessary for a component to retrieve +// headers that have been processed during header sync. +type Getter[H Header[H]] interface { + Head[H] + + // Get returns the Header corresponding to the given hash. + Get(context.Context, Hash) (H, error) + + // GetByHeight returns the Header corresponding to the given block height. + GetByHeight(context.Context, uint64) (H, error) + + // GetRangeByHeight returns the given range of Headers. + GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) + + // GetVerifiedRange requests the header range from the provided Header and + // verifies that the returned headers are adjacent to each other. + GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) +} + +// 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[H]] interface { + // Head returns the latest known header. + Head(context.Context, ...HeadOption[H]) (H, error) +} +``` + +`Head()` method requests the latest header from trusted peers. The `Head()` requests utilizes 90% of the set deadline (in the form of context deadline) for requests and remaining for determining the best head from gathered responses. The `Head()` call also allows passing an option `TrustedHead` which allows the caller to specify a trusted header against which the untrusted headers received from a list of tracked peers (limited to `maxUntrustedHeadRequests` of 4) can be verified against, in the absence of trusted peers. Upon receiving headers from peers (either trusted or tracked), the best head is determined as the head: +* with max height among the received +* which is received from at least `minHeadResponses` of 2 peers +* when neither or both conditions meet, the head with highest height is used + +Apart from requesting the latest header, any arbitrary header(s) can be requested (with 3 retries) using height (`GetByHeight`), hash (`Get`), range (`GetRangeByHeight` and `GetVerifiedRange`) from trusted peers as defined in the request proto message: + +``` +message HeaderRequest { + oneof data { + uint64 origin = 1; + bytes hash = 2; + } + uint64 amount = 3; +} +``` +The `GetVerifiedRange` differs from `GetRangeByHeight` as it ensures that the returned headers are correct against another header (passed as parameter to the call). + +### Exchange Server + +ExchangeServer represents the server-side component (p2p server) for responding to inbound header requests. The exchange server needs to be initialized using self [host.Host][host] and a [store](../specs/src/specs/store.md). Optional `ServerParameters` as shown below, can be set during the server initialization. + +``` +// ServerParameters is the set of parameters that must be configured for the exchange. +type ServerParameters struct { + // WriteDeadline sets the timeout for sending messages to the stream + WriteDeadline time.Duration + // ReadDeadline sets the timeout for reading messages from the stream + ReadDeadline time.Duration + // RangeRequestTimeout defines a timeout after which the session will try to re-request headers + // from another peer. + RangeRequestTimeout time.Duration + // networkID is a network that will be used to create a protocol.ID + // Is empty by default + networkID string +} +``` + +The default values for `ServerParameters` are as described below. + +``` +// DefaultServerParameters returns the default params to configure the store. +func DefaultServerParameters() ServerParameters { + return ServerParameters{ + WriteDeadline: time.Second * 8, + ReadDeadline: time.Minute, + RangeRequestTimeout: time.Second * 10, + } +} +``` + +During the server start, a request handler for the `protocolID` (`/networkID/header-ex/v0.0.3`) which defined using the `networkID` configurable parameter is setup to serve the inbound header requests. + +The request handler returns a response which contains bytes of the requested header(s) and a status code as shown below. + +``` +message HeaderResponse { + bytes body = 1; + StatusCode statusCode = 2; +} +``` + +The `OK` status code for success, `NOT_FOUND` for requested headers not found, and `INVALID` for error (default). +``` +enum StatusCode { + INVALID = 0; + OK = 1; + NOT_FOUND = 2; +} +``` + +The request handler utilizes its local [store](../specs/src/specs/store.md) for serving the header requests and only up to `MaxRangeRequestSize` of 512 headers can be requested while requesting headers by range. If the requested range is not available, the range is reset to whatever is available. + +### Session + +Session aims to divide a header range requests into several smaller requests among different peers. This service is used by the exchange client for making the `GetRangeByHeight` and `GetVerifiedRange` calls. + +``` +// ClientParameters is the set of parameters that must be configured for the exchange. +type ClientParameters struct { + // MaxHeadersPerRangeRequest defines the max amount of headers that can be requested per 1 request. + MaxHeadersPerRangeRequest uint64 + // RangeRequestTimeout defines a timeout after which the session will try to re-request headers + // from another peer. + RangeRequestTimeout time.Duration + // networkID is a network that will be used to create a protocol.ID + networkID string + // chainID is an identifier of the chain. + chainID string + + pidstore PeerIDStore +} +``` + +The default values for `ClientParameters` are as described below. + +``` +// DefaultClientParameters returns the default params to configure the store. +func DefaultClientParameters() ClientParameters { + return ClientParameters{ + MaxHeadersPerRangeRequest: 64, + RangeRequestTimeout: time.Second * 8, + } +} +``` + + +## Metrics + +//TODO + +# References + +[libp2p]: https://github.com/libp2p/go-libp2p +[pubsub]: https://github.com/libp2p/go-libp2p-pubsub +[host]: https://github.com/libp2p/go-libp2p/core/host +[peer]: https://github.com/libp2p/go-libp2p/core/peer +[gater]: https://github.com/libp2p/go-libp2p/p2p/net/conngater \ No newline at end of file diff --git a/specs/src/README.md b/specs/src/README.md index fea080d0..1605bcad 100644 --- a/specs/src/README.md +++ b/specs/src/README.md @@ -1,3 +1,54 @@ # Welcome -Welcome to the go-header Specifications. +Welcome to the go-header Specifications. + +go-header is a library for syncing blockchain data such as block headers over the p2p network. It contains services for requesting and receiving headers from the p2p network, serving header requests from other nodes in the p2p network, storing headers, and syncing historical headers in case of fallbacks. + +|Component|Description| +|---|---| +|[p2p.Subscriber](specs/p2p.md)|listens for new headers from the p2p network| +|[p2p.ExchangeServer](specs/p2p.md)|serve header requests from other nodes in the p2p network| +|[p2p.Exchange](specs/p2p.md)|client that requests headers from other nodes in the p2p network| +|[store.Store](specs/store.md)|storing headers and making them available for access by other services such as exchange and syncer| +|[sync.Syncer](specs/sync.md)|syncing of historical and new headers from the p2p network| + +The go-header library makes it easy to consume by other projects by defining a clear interface (as described below). An example consumer is defined in [headertest/dummy_header.go][dummy header] + +``` +type Header[H any] interface { + // New creates new instance of a header. + New() H + + // IsZero reports whether Header is a zero value of it's concrete type. + IsZero() bool + + // ChainID returns identifier of the chain. + ChainID() string + + // Hash returns hash of a header. + Hash() Hash + + // Height returns the height of a header. + Height() uint64 + + // LastHeader returns the hash of last header before this header (aka. previous header hash). + LastHeader() Hash + + // Time returns time when header was created. + Time() time.Time + + // Verify validates given untrusted Header against trusted Header. + Verify(H) error + + // Validate performs stateless validation to check for missed/incorrect fields. + Validate() error + + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler +} +``` + +# References +[1] [Dummy Header][dummy header] + +[dummy header]: https://github.com/celestiaorg/go-header/blob/main/headertest/dummy_header.go \ No newline at end of file diff --git a/store/store.md b/store/store.md index e69de29b..2b03c217 100644 --- a/store/store.md +++ b/store/store.md @@ -0,0 +1,104 @@ +# Store + +Store implements the Store interface (shown below) for headers over [datastore][go-datastore]. + +``` +// Store encompasses the behavior necessary to store and retrieve Headers +// from a node's local storage. +type Store[H Header[H]] interface { + // Getter encompasses all getter methods for headers. + Getter[H] + + // Init initializes Store with the given head, meaning it is initialized with the genesis header. + Init(context.Context, H) error + + // Height reports current height of the chain head. + Height() uint64 + + // Has checks whether Header is already stored. + Has(context.Context, Hash) (bool, error) + + // HasAt checks whether Header at the given height is already stored. + HasAt(context.Context, uint64) bool + + // Append stores and verifies the given Header(s). + // It requires them to be adjacent and in ascending order, + // as it applies them contiguously on top of the current head height. + // It returns the amount of successfully applied headers, + // so caller can understand what given header was invalid, if any. + Append(context.Context, ...H) error +} + +// Getter contains the behavior necessary for a component to retrieve +// headers that have been processed during header sync. +type Getter[H Header[H]] interface { + Head[H] + + // Get returns the Header corresponding to the given hash. + Get(context.Context, Hash) (H, error) + + // GetByHeight returns the Header corresponding to the given block height. + GetByHeight(context.Context, uint64) (H, error) + + // GetRangeByHeight returns the given range of Headers. + GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) + + // GetVerifiedRange requests the header range from the provided Header and + // verifies that the returned headers are adjacent to each other. + GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) +} + +// 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[H]] interface { + // Head returns the latest known header. + Head(context.Context, ...HeadOption[H]) (H, error) +} +``` + +A new store is created by passing a [datastore][go-datastore] instance and an optional head. If the head is not passed while creating a new store, `Init` method can be used to later initialize the store with head. The store must have a head before start. The head is considered trusted header and generally it is the genesis header. A custom store prefix can be passed during the store initialization. Further, a set of parameters can be passed during the store initialization to configure the store as described below. + +``` +// Parameters is the set of parameters that must be configured for the store. +type Parameters struct { + // StoreCacheSize defines the maximum amount of entries in the Header Store cache. + StoreCacheSize int + + // IndexCacheSize defines the maximum amount of entries in the Height to Hash index cache. + IndexCacheSize int + + // WriteBatchSize defines the size of the batched header write. + // Headers are written in batches not to thrash the underlying Datastore with writes. + WriteBatchSize int + + // storePrefix defines the prefix used to wrap the store + // OPTIONAL + storePrefix datastore.Key +} +``` +The default values for store `Parameters` are as described below. + +``` +// DefaultParameters returns the default params to configure the store. +func DefaultParameters() Parameters { + return Parameters{ + StoreCacheSize: 4096, + IndexCacheSize: 16384, + WriteBatchSize: 2048, + } +} +``` + +The store runs a flush loop during the start which performs writing task to the underlying datastore in a separate routine. This way writes are controlled and manageable from one place allowing: +* `Append`s not blocked on long disk IO writes and underlying DB compactions +* Helps with batching header writes + +`Append` appends a list of headers to the store head. It requires that all headers to be appended are adjacent to each other (sequential). Also, append invokes adjacency header verification by calling the `Verify` header interface method to ensure that only verified headers are appended. As described above, append does not directly writes to the underlying datastore, which is taken care by the flush loop. + +`Has` method checks if a header with a given hash exists in the store. The check is performed on a cache ([lru.ARCCache][lru.ARCCache]) first, followed by the pending queue which contains headers that are not flushed (written to disk), and finally the datastore. The `Get` method works similar to `Has`, where the retrieval first checks cache, followed by the pending queue, and finally the datastore (disk access). + +# References + +[go-datastore]: https://github.com/ipfs/go-datastore +[lru.ARCCache]: https://github.com/hashicorp/golang-lru \ No newline at end of file diff --git a/sync/sync.md b/sync/sync.md index e69de29b..f83ea707 100644 --- a/sync/sync.md +++ b/sync/sync.md @@ -0,0 +1,14 @@ +# Sync + +Syncer implements efficient synchronization for headers. + +There are two main processes running in Syncer: +* Main syncing loop(`syncLoop`) + * Performs syncing from the latest stored header up to the latest known Subjective Head + * Syncs by requesting missing headers from Exchange or + * By accessing cache of pending headers +* Receives every new Network Head from PubSub gossip subnetwork (`incomingNetworkHead`) + * Validates against the latest known Subjective Head, is so + * Sets as the new Subjective Head, which + * If there is a gap between the previous and the new Subjective Head + * Triggers s.syncLoop and saves the Subjective Head in the pending so s.syncLoop can access it \ No newline at end of file From 7acecd7c23cc6b8cc28d520fd3623e8697c3896c Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Wed, 11 Oct 2023 09:36:42 -0500 Subject: [PATCH 02/13] update network vs subjective head description --- sync/sync.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sync/sync.md b/sync/sync.md index f83ea707..566ee84c 100644 --- a/sync/sync.md +++ b/sync/sync.md @@ -5,6 +5,8 @@ Syncer implements efficient synchronization for headers. There are two main processes running in Syncer: * Main syncing loop(`syncLoop`) * Performs syncing from the latest stored header up to the latest known Subjective Head + * Subjective head: the latest known local valid header and a sync target. + * Network head: the latest valid network-wide header. Becomes subjective once applied locally. * Syncs by requesting missing headers from Exchange or * By accessing cache of pending headers * Receives every new Network Head from PubSub gossip subnetwork (`incomingNetworkHead`) From e6f5ecc20e950cc5f86a6f43592aba21a33afce6 Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Wed, 11 Oct 2023 15:40:40 -0500 Subject: [PATCH 03/13] run markdownlint and finish syncer --- p2p/p2p.md | 111 +++++++++++++++++++++++--------------------- specs/src/README.md | 45 +++++++++--------- store/store.md | 90 +++++++++++++++++------------------ sync/sync.md | 95 +++++++++++++++++++++++++++++++++---- 4 files changed, 211 insertions(+), 130 deletions(-) diff --git a/p2p/p2p.md b/p2p/p2p.md index c26c263f..d1d52081 100644 --- a/p2p/p2p.md +++ b/p2p/p2p.md @@ -2,8 +2,8 @@ The p2p package mainly contains two services: -1) Subscriber -2) Exchange +1) Subscriber +2) Exchange ## Subscriber @@ -16,14 +16,14 @@ The Subscriber implements the following interface: // subscribe/unsubscribe from new Header events from the // network. type Subscriber[H Header[H]] interface { - // Subscribe creates long-living Subscription for validated Headers. - // Multiple Subscriptions can be created. - Subscribe() (Subscription[H], error) - // SetVerifier registers verification func for all Subscriptions. - // Registered func screens incoming headers - // before they are forwarded to Subscriptions. - // Only one func can be set. - SetVerifier(func(context.Context, H) error) error + // Subscribe creates long-living Subscription for validated Headers. + // Multiple Subscriptions can be created. + Subscribe() (Subscription[H], error) + // SetVerifier registers verification func for all Subscriptions. + // Registered func screens incoming headers + // before they are forwarded to Subscriptions. + // Only one func can be set. + SetVerifier(func(context.Context, H) error) error } ``` @@ -32,6 +32,7 @@ The `Subscribe()` method allows listening to any new headers that are published ## Exchange An exchange is a combination of: + * Exchange: a client for requesting headers from the p2p network (outbound) * ExchangeServer: a p2p server for handling inbound header requests @@ -53,32 +54,33 @@ The exchange client implements the following interface: // Getter contains the behavior necessary for a component to retrieve // headers that have been processed during header sync. type Getter[H Header[H]] interface { - Head[H] + Head[H] - // Get returns the Header corresponding to the given hash. - Get(context.Context, Hash) (H, error) + // Get returns the Header corresponding to the given hash. + Get(context.Context, Hash) (H, error) - // GetByHeight returns the Header corresponding to the given block height. - GetByHeight(context.Context, uint64) (H, error) + // GetByHeight returns the Header corresponding to the given block height. + GetByHeight(context.Context, uint64) (H, error) - // GetRangeByHeight returns the given range of Headers. - GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) + // GetRangeByHeight returns the given range of Headers. + GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) - // GetVerifiedRange requests the header range from the provided Header and - // verifies that the returned headers are adjacent to each other. - GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) + // GetVerifiedRange requests the header range from the provided Header and + // verifies that the returned headers are adjacent to each other. + GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) } // 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[H]] interface { - // Head returns the latest known header. - Head(context.Context, ...HeadOption[H]) (H, error) + // Head returns the latest known header. + Head(context.Context, ...HeadOption[H]) (H, error) } ``` `Head()` method requests the latest header from trusted peers. The `Head()` requests utilizes 90% of the set deadline (in the form of context deadline) for requests and remaining for determining the best head from gathered responses. The `Head()` call also allows passing an option `TrustedHead` which allows the caller to specify a trusted header against which the untrusted headers received from a list of tracked peers (limited to `maxUntrustedHeadRequests` of 4) can be verified against, in the absence of trusted peers. Upon receiving headers from peers (either trusted or tracked), the best head is determined as the head: + * with max height among the received * which is received from at least `minHeadResponses` of 2 peers * when neither or both conditions meet, the head with highest height is used @@ -94,6 +96,7 @@ message HeaderRequest { uint64 amount = 3; } ``` + The `GetVerifiedRange` differs from `GetRangeByHeight` as it ensures that the returned headers are correct against another header (passed as parameter to the call). ### Exchange Server @@ -103,16 +106,16 @@ ExchangeServer represents the server-side component (p2p server) for responding ``` // ServerParameters is the set of parameters that must be configured for the exchange. type ServerParameters struct { - // WriteDeadline sets the timeout for sending messages to the stream - WriteDeadline time.Duration - // ReadDeadline sets the timeout for reading messages from the stream - ReadDeadline time.Duration - // RangeRequestTimeout defines a timeout after which the session will try to re-request headers - // from another peer. - RangeRequestTimeout time.Duration - // networkID is a network that will be used to create a protocol.ID - // Is empty by default - networkID string + // WriteDeadline sets the timeout for sending messages to the stream + WriteDeadline time.Duration + // ReadDeadline sets the timeout for reading messages from the stream + ReadDeadline time.Duration + // RangeRequestTimeout defines a timeout after which the session will try to re-request headers + // from another peer. + RangeRequestTimeout time.Duration + // networkID is a network that will be used to create a protocol.ID + // Is empty by default + networkID string } ``` @@ -121,11 +124,11 @@ The default values for `ServerParameters` are as described below. ``` // DefaultServerParameters returns the default params to configure the store. func DefaultServerParameters() ServerParameters { - return ServerParameters{ - WriteDeadline: time.Second * 8, - ReadDeadline: time.Minute, - RangeRequestTimeout: time.Second * 10, - } + return ServerParameters{ + WriteDeadline: time.Second * 8, + ReadDeadline: time.Minute, + RangeRequestTimeout: time.Second * 10, + } } ``` @@ -141,6 +144,7 @@ message HeaderResponse { ``` The `OK` status code for success, `NOT_FOUND` for requested headers not found, and `INVALID` for error (default). + ``` enum StatusCode { INVALID = 0; @@ -158,17 +162,17 @@ Session aims to divide a header range requests into several smaller requests amo ``` // ClientParameters is the set of parameters that must be configured for the exchange. type ClientParameters struct { - // MaxHeadersPerRangeRequest defines the max amount of headers that can be requested per 1 request. - MaxHeadersPerRangeRequest uint64 - // RangeRequestTimeout defines a timeout after which the session will try to re-request headers - // from another peer. - RangeRequestTimeout time.Duration - // networkID is a network that will be used to create a protocol.ID - networkID string - // chainID is an identifier of the chain. - chainID string - - pidstore PeerIDStore + // MaxHeadersPerRangeRequest defines the max amount of headers that can be requested per 1 request. + MaxHeadersPerRangeRequest uint64 + // RangeRequestTimeout defines a timeout after which the session will try to re-request headers + // from another peer. + RangeRequestTimeout time.Duration + // networkID is a network that will be used to create a protocol.ID + networkID string + // chainID is an identifier of the chain. + chainID string + + pidstore PeerIDStore } ``` @@ -177,14 +181,13 @@ The default values for `ClientParameters` are as described below. ``` // DefaultClientParameters returns the default params to configure the store. func DefaultClientParameters() ClientParameters { - return ClientParameters{ - MaxHeadersPerRangeRequest: 64, - RangeRequestTimeout: time.Second * 8, - } + return ClientParameters{ + MaxHeadersPerRangeRequest: 64, + RangeRequestTimeout: time.Second * 8, + } } ``` - ## Metrics //TODO @@ -195,4 +198,4 @@ func DefaultClientParameters() ClientParameters { [pubsub]: https://github.com/libp2p/go-libp2p-pubsub [host]: https://github.com/libp2p/go-libp2p/core/host [peer]: https://github.com/libp2p/go-libp2p/core/peer -[gater]: https://github.com/libp2p/go-libp2p/p2p/net/conngater \ No newline at end of file +[gater]: https://github.com/libp2p/go-libp2p/p2p/net/conngater diff --git a/specs/src/README.md b/specs/src/README.md index 1605bcad..26dd5d7c 100644 --- a/specs/src/README.md +++ b/specs/src/README.md @@ -1,6 +1,6 @@ # Welcome -Welcome to the go-header Specifications. +Welcome to the go-header Specifications. go-header is a library for syncing blockchain data such as block headers over the p2p network. It contains services for requesting and receiving headers from the p2p network, serving header requests from other nodes in the p2p network, storing headers, and syncing historical headers in case of fallbacks. @@ -16,39 +16,40 @@ The go-header library makes it easy to consume by other projects by defining a c ``` type Header[H any] interface { - // New creates new instance of a header. - New() H - + // New creates new instance of a header. + New() H + // IsZero reports whether Header is a zero value of it's concrete type. - IsZero() bool - + IsZero() bool + // ChainID returns identifier of the chain. - ChainID() string - + ChainID() string + // Hash returns hash of a header. - Hash() Hash - + Hash() Hash + // Height returns the height of a header. - Height() uint64 - + Height() uint64 + // LastHeader returns the hash of last header before this header (aka. previous header hash). - LastHeader() Hash - + LastHeader() Hash + // Time returns time when header was created. - Time() time.Time - + Time() time.Time + // Verify validates given untrusted Header against trusted Header. - Verify(H) error + Verify(H) error - // Validate performs stateless validation to check for missed/incorrect fields. - Validate() error + // Validate performs stateless validation to check for missed/incorrect fields. + Validate() error - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler } ``` # References + [1] [Dummy Header][dummy header] -[dummy header]: https://github.com/celestiaorg/go-header/blob/main/headertest/dummy_header.go \ No newline at end of file +[dummy header]: https://github.com/celestiaorg/go-header/blob/main/headertest/dummy_header.go diff --git a/store/store.md b/store/store.md index 2b03c217..b2169362 100644 --- a/store/store.md +++ b/store/store.md @@ -6,54 +6,54 @@ Store implements the Store interface (shown below) for headers over [datastore][ // Store encompasses the behavior necessary to store and retrieve Headers // from a node's local storage. type Store[H Header[H]] interface { - // Getter encompasses all getter methods for headers. - Getter[H] + // Getter encompasses all getter methods for headers. + Getter[H] - // Init initializes Store with the given head, meaning it is initialized with the genesis header. - Init(context.Context, H) error + // Init initializes Store with the given head, meaning it is initialized with the genesis header. + Init(context.Context, H) error - // Height reports current height of the chain head. - Height() uint64 + // Height reports current height of the chain head. + Height() uint64 - // Has checks whether Header is already stored. - Has(context.Context, Hash) (bool, error) + // Has checks whether Header is already stored. + Has(context.Context, Hash) (bool, error) - // HasAt checks whether Header at the given height is already stored. - HasAt(context.Context, uint64) bool + // HasAt checks whether Header at the given height is already stored. + HasAt(context.Context, uint64) bool - // Append stores and verifies the given Header(s). - // It requires them to be adjacent and in ascending order, - // as it applies them contiguously on top of the current head height. - // It returns the amount of successfully applied headers, - // so caller can understand what given header was invalid, if any. - Append(context.Context, ...H) error + // Append stores and verifies the given Header(s). + // It requires them to be adjacent and in ascending order, + // as it applies them contiguously on top of the current head height. + // It returns the amount of successfully applied headers, + // so caller can understand what given header was invalid, if any. + Append(context.Context, ...H) error } // Getter contains the behavior necessary for a component to retrieve // headers that have been processed during header sync. type Getter[H Header[H]] interface { - Head[H] + Head[H] - // Get returns the Header corresponding to the given hash. - Get(context.Context, Hash) (H, error) + // Get returns the Header corresponding to the given hash. + Get(context.Context, Hash) (H, error) - // GetByHeight returns the Header corresponding to the given block height. - GetByHeight(context.Context, uint64) (H, error) + // GetByHeight returns the Header corresponding to the given block height. + GetByHeight(context.Context, uint64) (H, error) - // GetRangeByHeight returns the given range of Headers. - GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) + // GetRangeByHeight returns the given range of Headers. + GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) - // GetVerifiedRange requests the header range from the provided Header and - // verifies that the returned headers are adjacent to each other. - GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) + // GetVerifiedRange requests the header range from the provided Header and + // verifies that the returned headers are adjacent to each other. + GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) } // 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[H]] interface { - // Head returns the latest known header. - Head(context.Context, ...HeadOption[H]) (H, error) + // Head returns the latest known header. + Head(context.Context, ...HeadOption[H]) (H, error) } ``` @@ -62,35 +62,37 @@ A new store is created by passing a [datastore][go-datastore] instance and an op ``` // Parameters is the set of parameters that must be configured for the store. type Parameters struct { - // StoreCacheSize defines the maximum amount of entries in the Header Store cache. - StoreCacheSize int + // StoreCacheSize defines the maximum amount of entries in the Header Store cache. + StoreCacheSize int - // IndexCacheSize defines the maximum amount of entries in the Height to Hash index cache. - IndexCacheSize int + // IndexCacheSize defines the maximum amount of entries in the Height to Hash index cache. + IndexCacheSize int - // WriteBatchSize defines the size of the batched header write. - // Headers are written in batches not to thrash the underlying Datastore with writes. - WriteBatchSize int + // WriteBatchSize defines the size of the batched header write. + // Headers are written in batches not to thrash the underlying Datastore with writes. + WriteBatchSize int - // storePrefix defines the prefix used to wrap the store - // OPTIONAL - storePrefix datastore.Key + // storePrefix defines the prefix used to wrap the store + // OPTIONAL + storePrefix datastore.Key } ``` + The default values for store `Parameters` are as described below. ``` // DefaultParameters returns the default params to configure the store. func DefaultParameters() Parameters { - return Parameters{ - StoreCacheSize: 4096, - IndexCacheSize: 16384, - WriteBatchSize: 2048, - } + return Parameters{ + StoreCacheSize: 4096, + IndexCacheSize: 16384, + WriteBatchSize: 2048, + } } ``` The store runs a flush loop during the start which performs writing task to the underlying datastore in a separate routine. This way writes are controlled and manageable from one place allowing: + * `Append`s not blocked on long disk IO writes and underlying DB compactions * Helps with batching header writes @@ -101,4 +103,4 @@ The store runs a flush loop during the start which performs writing task to the # References [go-datastore]: https://github.com/ipfs/go-datastore -[lru.ARCCache]: https://github.com/hashicorp/golang-lru \ No newline at end of file +[lru.ARCCache]: https://github.com/hashicorp/golang-lru diff --git a/sync/sync.md b/sync/sync.md index 566ee84c..7b029293 100644 --- a/sync/sync.md +++ b/sync/sync.md @@ -1,16 +1,91 @@ # Sync -Syncer implements efficient synchronization for headers. +Syncer implements efficient synchronization for headers. There are two main processes running in Syncer: + * Main syncing loop(`syncLoop`) - * Performs syncing from the latest stored header up to the latest known Subjective Head - * Subjective head: the latest known local valid header and a sync target. - * Network head: the latest valid network-wide header. Becomes subjective once applied locally. - * Syncs by requesting missing headers from Exchange or - * By accessing cache of pending headers + * Performs syncing from the latest stored header up to the latest known Subjective Head + * Subjective head: the latest known local valid header and a sync target. + * Network head: the latest valid network-wide header. Becomes subjective once applied locally. + * Syncs by requesting missing headers from Exchange or + * By accessing cache of pending headers * Receives every new Network Head from PubSub gossip subnetwork (`incomingNetworkHead`) - * Validates against the latest known Subjective Head, is so - * Sets as the new Subjective Head, which - * If there is a gap between the previous and the new Subjective Head - * Triggers s.syncLoop and saves the Subjective Head in the pending so s.syncLoop can access it \ No newline at end of file + * Validates against the latest known Subjective Head, is so + * Sets as the new Subjective Head, which + * If there is a gap between the previous and the new Subjective Head + * Triggers s.syncLoop and saves the Subjective Head in the pending so s.syncLoop can access it + +For creating a new instance of the Syncer following components are needed: + +* A getter, e.g., [Exchange][exchange] +* A [Store](../../../store/store.md) +* A [Subscriber][subscriber] +* Additional options such as block time. More options as described below. + +Options for configuring the syncer: + +``` +// Parameters is the set of parameters that must be configured for the syncer. +type Parameters struct { + // TrustingPeriod is period through which we can trust a header's validators set. + // + // Should be significantly less than the unbonding period (e.g. unbonding + // period = 3 weeks, trusting period = 2 weeks). + // + // More specifically, trusting period + time needed to check headers + time + // needed to report and punish misbehavior should be less than the unbonding + // period. + TrustingPeriod time.Duration + // blockTime provides a reference point for the Syncer to determine + // whether its subjective head is outdated. + // Keeping it private to disable serialization for it. + // default value is set to 0 so syncer will constantly request networking head. + blockTime time.Duration + // recencyThreshold describes the time period for which a header is + // considered "recent". The default is blockTime + 5 seconds. + recencyThreshold time.Duration +} +``` + +The default parameters used to configure the syncer are: + +``` +// DefaultParameters returns the default params to configure the syncer. +func DefaultParameters() Parameters { + return Parameters{ + TrustingPeriod: 336 * time.Hour, // tendermint's default trusting period + } +} +``` + +When the syncer is started: + +* `incomingNetworkHead` is set as validator for any incoming header over the subscriber +* Retrieve the latest head to kick off syncing. Note that, syncer cannot be started without head. +* `syncLoop` is started, which listens to sync trigger + +## Fetching the Head to Start Syncing + +Known subjective head is considered network head if it is recent (`now - timestamp <= blocktime`). Otherwise, a head is requested from a trusted peer and set as the new subjective head, assuming that trusted peer is always fully synced. + +* If the event of network not able to be retrieved and subjective head is not recent, as fallback, the subjective head is used as head. +* The network head retrieved is subjected to validation (via `incomingNetworkHead`) before setting as the new subjective head. + +## Verify + +The header interface defines a `Verify` method which gets invoked when any new header is received via `incomingNetworkHead`. + +``` +// Verify validates given untrusted Header against trusted Header. +Verify(H) error +``` + +## syncLoop + +When a new network head is received which gets validated and set as subjective head, it triggers the `syncLoop` which tries to sync headers from old subjective head till new network head (the sync target) by utilizing the `getter`(the `Exchange` client). + +# References + +[exchange]: https://github.com/celestiaorg/go-header/blob/main/p2p/exchange.go +[subscriber]: https://github.com/celestiaorg/go-header/blob/main/p2p/subscriber.go From a889ccc06844abc7d70c69de7f9393c0c6cdff6f Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Wed, 11 Oct 2023 16:05:13 -0500 Subject: [PATCH 04/13] add peer tracker and metrics --- p2p/p2p.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/p2p/p2p.md b/p2p/p2p.md index d1d52081..ca4aa3e9 100644 --- a/p2p/p2p.md +++ b/p2p/p2p.md @@ -42,11 +42,18 @@ Exchange defines a client for requesting headers from the p2p network. An exchan #### Peer Tracker -//TODO +The three main functionalities of the peer tracker are: +* bootstrap +* track +* gc -#### Trusted Peers vs Tracked Peers +When the exchange client is started, it bootstraps the peer tracker using the set of trusted peers used to initialize the exchange client. -//TODO +The new peers are tracked by subscribing to `event.EvtPeerConnectednessChanged{}`. + +The peer tracker also runs garbage collector (gc) that removes the disconnected peers (determined as disconnected for more than `maxAwaitingTime` or connected peers whose scores are less than or equal to `defaultScore`) from the tracked peers list once every `gcPeriod`. + +The peer tracker also provides a block peer functionality which is used to block peers that send invalid network headers. Invalid header is a header that fails when `Verify` method of the header interface is invoked. The exchange client implements the following interface: @@ -190,7 +197,11 @@ func DefaultClientParameters() ClientParameters { ## Metrics -//TODO +Currently only following metrics are collected: +* P2P header exchange response size +* Duration of the get headers request in seconds +* Total synced headers + # References From f7995c3ea0e1f50d2dc99014bcded613f5a63af4 Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Tue, 17 Oct 2023 19:05:12 -0500 Subject: [PATCH 05/13] fix links and references --- p2p/p2p.md | 14 ++++++++++++-- specs/src/README.md | 26 +++++++++++++------------- store/store.md | 4 ++++ sync/sync.md | 4 ++++ 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/p2p/p2p.md b/p2p/p2p.md index ca4aa3e9..c84de748 100644 --- a/p2p/p2p.md +++ b/p2p/p2p.md @@ -38,7 +38,7 @@ An exchange is a combination of: ### Exchange Client -Exchange defines a client for requesting headers from the p2p network. An exchange client is initialized using self [host.Host][host], list of peers in the form of slice [peer.IDSlice][peer], and [connection gater][gater] for blocking and allowing nodes. Optional parameters like `ChainID` and `NetworkID` can also be passed. The exchange client also maintains a list of trusted peers via a peer tracker. +Exchange defines a client for requesting headers from the p2p network. An exchange client is initialized using self [host.Host][host], a list of peers in the form of slice [peer.IDSlice][peer], and a [connection gater][gater] for blocking and allowing nodes. Optional parameters like `ChainID` and `NetworkID` can also be passed. The exchange client also maintains a list of trusted peers via a peer tracker. #### Peer Tracker @@ -86,7 +86,7 @@ type Head[H Header[H]] interface { } ``` -`Head()` method requests the latest header from trusted peers. The `Head()` requests utilizes 90% of the set deadline (in the form of context deadline) for requests and remaining for determining the best head from gathered responses. The `Head()` call also allows passing an option `TrustedHead` which allows the caller to specify a trusted header against which the untrusted headers received from a list of tracked peers (limited to `maxUntrustedHeadRequests` of 4) can be verified against, in the absence of trusted peers. Upon receiving headers from peers (either trusted or tracked), the best head is determined as the head: +`Head()` method requests the latest header from trusted peers. The `Head()` requests utilizes 90% of the set deadline (in the form of context deadline) for requests and remaining for determining the best head from gathered responses. The `Head()` call also allows passing an optional `TrustedHead` which allows the caller to specify a trusted header against which the untrusted headers received from a list of tracked peers (limited to `maxUntrustedHeadRequests` of 4) can be verified against, in the absence of trusted peers. Upon receiving headers from peers (either trusted or tracked), the best head is determined as the head: * with max height among the received * which is received from at least `minHeadResponses` of 2 peers @@ -205,6 +205,16 @@ Currently only following metrics are collected: # References +[1] [libp2p][libp2p] + +[2] [pubsub][pubsub] + +[3] [host.Host][host] + +[4] [peer.IDSlice][peer] + +[5] [connection gater][gater] + [libp2p]: https://github.com/libp2p/go-libp2p [pubsub]: https://github.com/libp2p/go-libp2p-pubsub [host]: https://github.com/libp2p/go-libp2p/core/host diff --git a/specs/src/README.md b/specs/src/README.md index 26dd5d7c..1585cf2b 100644 --- a/specs/src/README.md +++ b/specs/src/README.md @@ -6,38 +6,38 @@ go-header is a library for syncing blockchain data such as block headers over th |Component|Description| |---|---| -|[p2p.Subscriber](specs/p2p.md)|listens for new headers from the p2p network| -|[p2p.ExchangeServer](specs/p2p.md)|serve header requests from other nodes in the p2p network| -|[p2p.Exchange](specs/p2p.md)|client that requests headers from other nodes in the p2p network| -|[store.Store](specs/store.md)|storing headers and making them available for access by other services such as exchange and syncer| -|[sync.Syncer](specs/sync.md)|syncing of historical and new headers from the p2p network| +|[p2p.Subscriber](../../p2p/p2p.md)|listens for new headers from the p2p network| +|[p2p.ExchangeServer](../../p2p/p2p.md)|serve header requests from other nodes in the p2p network| +|[p2p.Exchange](../../p2p/p2p.md)|client that requests headers from other nodes in the p2p network| +|[store.Store](../../store/store.md)|storing headers and making them available for access by other services such as exchange and syncer| +|[sync.Syncer](../../sync/sync.md)|syncing of historical and new headers from the p2p network| -The go-header library makes it easy to consume by other projects by defining a clear interface (as described below). An example consumer is defined in [headertest/dummy_header.go][dummy header] +The go-header library makes it easy to be used by other projects by defining a clear interface (as described below). An example usage is defined in [headertest/dummy_header.go][dummy header] ``` type Header[H any] interface { // New creates new instance of a header. New() H - // IsZero reports whether Header is a zero value of it's concrete type. + // IsZero reports whether Header is a zero value of it's concrete type. IsZero() bool - // ChainID returns identifier of the chain. + // ChainID returns identifier of the chain. ChainID() string - // Hash returns hash of a header. + // Hash returns hash of a header. Hash() Hash - // Height returns the height of a header. + // Height returns the height of a header. Height() uint64 - // LastHeader returns the hash of last header before this header (aka. previous header hash). + // LastHeader returns the hash of last header before this header (aka. previous header hash). LastHeader() Hash - // Time returns time when header was created. + // Time returns time when header was created. Time() time.Time - // Verify validates given untrusted Header against trusted Header. + // Verify validates given untrusted Header against trusted Header. Verify(H) error // Validate performs stateless validation to check for missed/incorrect fields. diff --git a/store/store.md b/store/store.md index b2169362..bb1a8dc9 100644 --- a/store/store.md +++ b/store/store.md @@ -102,5 +102,9 @@ The store runs a flush loop during the start which performs writing task to the # References +[1] [datastore][go-datastore] + +[2] [lru.ARCCache][lru.ARCCache] + [go-datastore]: https://github.com/ipfs/go-datastore [lru.ARCCache]: https://github.com/hashicorp/golang-lru diff --git a/sync/sync.md b/sync/sync.md index 7b029293..05ead750 100644 --- a/sync/sync.md +++ b/sync/sync.md @@ -87,5 +87,9 @@ When a new network head is received which gets validated and set as subjective h # References +[1] [Exchange][exchange] + +[2] [Subscriber][subscriber] + [exchange]: https://github.com/celestiaorg/go-header/blob/main/p2p/exchange.go [subscriber]: https://github.com/celestiaorg/go-header/blob/main/p2p/subscriber.go From ba4f2559d394392416e59f012602e8eac2fc731f Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Fri, 20 Oct 2023 10:22:21 -0500 Subject: [PATCH 06/13] add peer tracking condition --- p2p/p2p.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/p2p.md b/p2p/p2p.md index c84de748..8501a8de 100644 --- a/p2p/p2p.md +++ b/p2p/p2p.md @@ -38,7 +38,7 @@ An exchange is a combination of: ### Exchange Client -Exchange defines a client for requesting headers from the p2p network. An exchange client is initialized using self [host.Host][host], a list of peers in the form of slice [peer.IDSlice][peer], and a [connection gater][gater] for blocking and allowing nodes. Optional parameters like `ChainID` and `NetworkID` can also be passed. The exchange client also maintains a list of trusted peers via a peer tracker. +Exchange defines a client for requesting headers from the p2p network. An exchange client is initialized using self [host.Host][host], a list of peers in the form of slice [peer.IDSlice][peer], and a [connection gater][gater] for blocking and allowing nodes. Optional parameters like `ChainID` and `NetworkID` can also be passed. The exchange client also maintains a list of trusted peers via a peer tracker. The peer tracker discovers peers until `len(connected)+len(disconnected)` will not reach the limit(`maxPeerTrackerSize` for now it is 100) and `len(connected)>len(disconnected)`. #### Peer Tracker From 046d0b2ee4905c5e1006e50091b5c03778d6396f Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Fri, 20 Oct 2023 10:33:45 -0500 Subject: [PATCH 07/13] add peer tracker condition, p2p to P2P, use absolute links instead of relative --- p2p/p2p.md | 22 ++++++++++++---------- specs/src/README.md | 15 +++++++++------ sync/sync.md | 3 ++- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/p2p/p2p.md b/p2p/p2p.md index 8501a8de..9b47750e 100644 --- a/p2p/p2p.md +++ b/p2p/p2p.md @@ -1,13 +1,13 @@ # P2P -The p2p package mainly contains two services: +The P2P package mainly contains two services: 1) Subscriber 2) Exchange ## Subscriber -Subscriber is a service that manages gossip of headers among the nodes in the p2p network by using [libp2p][libp2p] and its [pubsub][pubsub] modules. The pubsub topic used for gossip (`//header-sub/v0.0.1`) is configurable based on `networkID` parameter used to initialize the subscriber service. +Subscriber is a service that manages gossip of headers among the nodes in the P2P network by using [libp2p][libp2p] and its [pubsub][pubsub] modules. The pubsub topic used for gossip (`//header-sub/v0.0.1`) is configurable based on `networkID` parameter used to initialize the subscriber service. The Subscriber implements the following interface: @@ -27,25 +27,26 @@ type Subscriber[H Header[H]] interface { } ``` -The `Subscribe()` method allows listening to any new headers that are published to the p2p network. The `SetVerifier()` method allows for setting a custom verifier that will be executed upon receiving any new headers from the p2p network. This is a very useful customization for the consumers of go-header library to pass any custom logic as part of the pubsub. +The `Subscribe()` method allows listening to any new headers that are published to the P2P network. The `SetVerifier()` method allows for setting a custom verifier that will be executed upon receiving any new headers from the P2P network. This is a very useful customization for the consumers of go-header library to pass any custom logic as part of the pubsub. ## Exchange An exchange is a combination of: -* Exchange: a client for requesting headers from the p2p network (outbound) -* ExchangeServer: a p2p server for handling inbound header requests +* Exchange: a client for requesting headers from the P2P network (outbound) +* ExchangeServer: a P2P server for handling inbound header requests ### Exchange Client -Exchange defines a client for requesting headers from the p2p network. An exchange client is initialized using self [host.Host][host], a list of peers in the form of slice [peer.IDSlice][peer], and a [connection gater][gater] for blocking and allowing nodes. Optional parameters like `ChainID` and `NetworkID` can also be passed. The exchange client also maintains a list of trusted peers via a peer tracker. The peer tracker discovers peers until `len(connected)+len(disconnected)` will not reach the limit(`maxPeerTrackerSize` for now it is 100) and `len(connected)>len(disconnected)`. +Exchange defines a client for requesting headers from the P2P network. An exchange client is initialized using self [host.Host][host], a list of peers in the form of slice [peer.IDSlice][peer], and a [connection gater][gater] for blocking and allowing nodes. Optional parameters like `ChainID` and `NetworkID` can also be passed. The exchange client also maintains a list of trusted peers via a peer tracker. The peer tracker discovers peers until `len(connected)+len(disconnected)` will not reach the limit(`maxPeerTrackerSize` for now it is 100) and `len(connected)>len(disconnected)`. #### Peer Tracker The three main functionalities of the peer tracker are: + * bootstrap * track -* gc +* garbage collection (gc) When the exchange client is started, it bootstraps the peer tracker using the set of trusted peers used to initialize the exchange client. @@ -108,7 +109,7 @@ The `GetVerifiedRange` differs from `GetRangeByHeight` as it ensures that the re ### Exchange Server -ExchangeServer represents the server-side component (p2p server) for responding to inbound header requests. The exchange server needs to be initialized using self [host.Host][host] and a [store](../specs/src/specs/store.md). Optional `ServerParameters` as shown below, can be set during the server initialization. +ExchangeServer represents the server-side component (P2P server) for responding to inbound header requests. The exchange server needs to be initialized using self [host.Host][host] and a [store][store]. Optional `ServerParameters` as shown below, can be set during the server initialization. ``` // ServerParameters is the set of parameters that must be configured for the exchange. @@ -160,7 +161,7 @@ enum StatusCode { } ``` -The request handler utilizes its local [store](../specs/src/specs/store.md) for serving the header requests and only up to `MaxRangeRequestSize` of 512 headers can be requested while requesting headers by range. If the requested range is not available, the range is reset to whatever is available. +The request handler utilizes its local [store][store] for serving the header requests and only up to `MaxRangeRequestSize` of 512 headers can be requested while requesting headers by range. If the requested range is not available, the range is reset to whatever is available. ### Session @@ -198,11 +199,11 @@ func DefaultClientParameters() ClientParameters { ## Metrics Currently only following metrics are collected: + * P2P header exchange response size * Duration of the get headers request in seconds * Total synced headers - # References [1] [libp2p][libp2p] @@ -220,3 +221,4 @@ Currently only following metrics are collected: [host]: https://github.com/libp2p/go-libp2p/core/host [peer]: https://github.com/libp2p/go-libp2p/core/peer [gater]: https://github.com/libp2p/go-libp2p/p2p/net/conngater +[store]: https://github.com/celestiaorg/go-header/blob/main/store/store.md diff --git a/specs/src/README.md b/specs/src/README.md index 1585cf2b..285d47eb 100644 --- a/specs/src/README.md +++ b/specs/src/README.md @@ -2,15 +2,15 @@ Welcome to the go-header Specifications. -go-header is a library for syncing blockchain data such as block headers over the p2p network. It contains services for requesting and receiving headers from the p2p network, serving header requests from other nodes in the p2p network, storing headers, and syncing historical headers in case of fallbacks. +go-header is a library for syncing blockchain data such as block headers over the P2P network. It contains services for requesting and receiving headers from the P2P network, serving header requests from other nodes in the P2P network, storing headers, and syncing historical headers in case of fallbacks. |Component|Description| |---|---| -|[p2p.Subscriber](../../p2p/p2p.md)|listens for new headers from the p2p network| -|[p2p.ExchangeServer](../../p2p/p2p.md)|serve header requests from other nodes in the p2p network| -|[p2p.Exchange](../../p2p/p2p.md)|client that requests headers from other nodes in the p2p network| -|[store.Store](../../store/store.md)|storing headers and making them available for access by other services such as exchange and syncer| -|[sync.Syncer](../../sync/sync.md)|syncing of historical and new headers from the p2p network| +|[p2p.Subscriber][p2p]|listens for new headers from the P2P network| +|[p2p.ExchangeServer][p2p]|serve header requests from other nodes in the P2P network| +|[p2p.Exchange][p2p]|client that requests headers from other nodes in the P2P network| +|[store.Store][store]|storing headers and making them available for access by other services such as exchange and syncer| +|[sync.Syncer][sync]|syncing of historical and new headers from the P2P network| The go-header library makes it easy to be used by other projects by defining a clear interface (as described below). An example usage is defined in [headertest/dummy_header.go][dummy header] @@ -53,3 +53,6 @@ type Header[H any] interface { [1] [Dummy Header][dummy header] [dummy header]: https://github.com/celestiaorg/go-header/blob/main/headertest/dummy_header.go +[p2p]: https://github.com/celestiaorg/go-header/blob/main/p2p/p2p.md +[store]: https://github.com/celestiaorg/go-header/blob/main/store/store.md +[sync]: https://github.com/celestiaorg/go-header/blob/main/sync/sync.md diff --git a/sync/sync.md b/sync/sync.md index 05ead750..6ffc6d00 100644 --- a/sync/sync.md +++ b/sync/sync.md @@ -19,7 +19,7 @@ There are two main processes running in Syncer: For creating a new instance of the Syncer following components are needed: * A getter, e.g., [Exchange][exchange] -* A [Store](../../../store/store.md) +* A [Store][store] * A [Subscriber][subscriber] * Additional options such as block time. More options as described below. @@ -93,3 +93,4 @@ When a new network head is received which gets validated and set as subjective h [exchange]: https://github.com/celestiaorg/go-header/blob/main/p2p/exchange.go [subscriber]: https://github.com/celestiaorg/go-header/blob/main/p2p/subscriber.go +[store]: https://github.com/celestiaorg/go-header/blob/main/store/store.md From 501157d2c9213c803d563c4b7c1fb619ec10aea7 Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Fri, 20 Oct 2023 10:36:29 -0500 Subject: [PATCH 08/13] minor fix --- p2p/p2p.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/p2p.md b/p2p/p2p.md index 9b47750e..4f207d06 100644 --- a/p2p/p2p.md +++ b/p2p/p2p.md @@ -7,7 +7,7 @@ The P2P package mainly contains two services: ## Subscriber -Subscriber is a service that manages gossip of headers among the nodes in the P2P network by using [libp2p][libp2p] and its [pubsub][pubsub] modules. The pubsub topic used for gossip (`//header-sub/v0.0.1`) is configurable based on `networkID` parameter used to initialize the subscriber service. +Subscriber is a service that manages the gossip of headers among the nodes in the P2P network by using [libp2p][libp2p] and its [pubsub][pubsub] modules. The pubsub topic is used for gossip (`//header-sub/v0.0.1`) and is configurable based on the `networkID` parameter used to initialize the subscriber service. The Subscriber implements the following interface: From 56dcce1ed1428274ab114e6c389bd4755ce4dd1a Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Tue, 24 Oct 2023 09:32:49 -0500 Subject: [PATCH 09/13] Apply suggestions from code review Co-authored-by: Hlib Kanunnikov --- p2p/p2p.md | 2 +- specs/src/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/p2p.md b/p2p/p2p.md index 4f207d06..c1e6a830 100644 --- a/p2p/p2p.md +++ b/p2p/p2p.md @@ -87,7 +87,7 @@ type Head[H Header[H]] interface { } ``` -`Head()` method requests the latest header from trusted peers. The `Head()` requests utilizes 90% of the set deadline (in the form of context deadline) for requests and remaining for determining the best head from gathered responses. The `Head()` call also allows passing an optional `TrustedHead` which allows the caller to specify a trusted header against which the untrusted headers received from a list of tracked peers (limited to `maxUntrustedHeadRequests` of 4) can be verified against, in the absence of trusted peers. Upon receiving headers from peers (either trusted or tracked), the best head is determined as the head: +`Head()` method requests the latest header from trusted or tracked peers. The `Head()` call also allows passing an optional `TrustedHead`, which allows the caller to specify a trusted head against which the untrusted head is verified. By default, `Head()` requests only trusted peers and if `TrustedHead` is provided untrusted tracked peers are also requested (limited to `maxUntrustedHeadRequests` of 4). The `Head()` requests utilize 90% of the set deadline (in the form of context deadline) for requests and the remaining for determining the best head from gathered responses. Upon receiving headers from peers (either trusted or tracked), the best head is determined as the head: * with max height among the received * which is received from at least `minHeadResponses` of 2 peers diff --git a/specs/src/README.md b/specs/src/README.md index 285d47eb..017e76c3 100644 --- a/specs/src/README.md +++ b/specs/src/README.md @@ -2,7 +2,7 @@ Welcome to the go-header Specifications. -go-header is a library for syncing blockchain data such as block headers over the P2P network. It contains services for requesting and receiving headers from the P2P network, serving header requests from other nodes in the P2P network, storing headers, and syncing historical headers in case of fallbacks. +go-header is a library for syncing blockchain data, such as block headers, over the P2P network in a trust-minimized way. It contains services for requesting and receiving headers from the P2P network, serving header requests from other nodes in the P2P network, storing headers, and syncing historical headers in case of fallbacks. |Component|Description| |---|---| From 28447787f7d50f266ad258d0b306c0bbb4af9dbb Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Thu, 26 Oct 2023 21:34:32 -0500 Subject: [PATCH 10/13] generalize and remove golang as much as possible --- p2p/p2p.md | 166 ++++++++++++++------------------------------ specs/src/README.md | 2 +- store/store.md | 100 ++++---------------------- sync/sync.md | 38 ++-------- 4 files changed, 72 insertions(+), 234 deletions(-) diff --git a/p2p/p2p.md b/p2p/p2p.md index c1e6a830..707174d8 100644 --- a/p2p/p2p.md +++ b/p2p/p2p.md @@ -2,43 +2,43 @@ The P2P package mainly contains two services: -1) Subscriber -2) Exchange +1) [Subscriber](#subscriber) +2) [Exchange](#exchange) ## Subscriber Subscriber is a service that manages the gossip of headers among the nodes in the P2P network by using [libp2p][libp2p] and its [pubsub][pubsub] modules. The pubsub topic is used for gossip (`//header-sub/v0.0.1`) and is configurable based on the `networkID` parameter used to initialize the subscriber service. -The Subscriber implements the following interface: +The Subscriber encompasses the behavior necessary to subscribe/unsubscribe from new Header events from the network. The Subscriber interface consists of: -``` -// Subscriber encompasses the behavior necessary to -// subscribe/unsubscribe from new Header events from the -// network. -type Subscriber[H Header[H]] interface { - // Subscribe creates long-living Subscription for validated Headers. - // Multiple Subscriptions can be created. - Subscribe() (Subscription[H], error) - // SetVerifier registers verification func for all Subscriptions. - // Registered func screens incoming headers - // before they are forwarded to Subscriptions. - // Only one func can be set. - SetVerifier(func(context.Context, H) error) error -} -``` +|Method|Input|Output|Description| +|--|--|--|--| +| Subscribe | | `Subscription[H], error` | Subscribe creates long-living Subscription for validated Headers. Multiple Subscriptions can be created. | +| SetVerifier | `func(context.Context, H) error` | error | SetVerifier registers verification func for all Subscriptions. Registered func screens incoming headers before they are forwarded to Subscriptions. Only one func can be set.| -The `Subscribe()` method allows listening to any new headers that are published to the P2P network. The `SetVerifier()` method allows for setting a custom verifier that will be executed upon receiving any new headers from the P2P network. This is a very useful customization for the consumers of go-header library to pass any custom logic as part of the pubsub. +The `Subscribe()` method allows listening to any new headers that are published to the P2P network. The `SetVerifier()` method allows for setting a custom verifier that will be executed upon receiving any new headers from the P2P network. This is a very useful customization for the consumers of go-header library to pass any custom logic as part of the pubsub. While multiple simultaneous subscriptions are possible via `Subscribe()` interface, only a single verifier can be set using the `SetVerifier` interface method. ## Exchange An exchange is a combination of: -* Exchange: a client for requesting headers from the P2P network (outbound) -* ExchangeServer: a P2P server for handling inbound header requests +* [Exchange](#exchange-client): a client for requesting headers from the P2P network (outbound) +* [ExchangeServer](#exchange-server): a P2P server for handling inbound header requests ### Exchange Client -Exchange defines a client for requesting headers from the P2P network. An exchange client is initialized using self [host.Host][host], a list of peers in the form of slice [peer.IDSlice][peer], and a [connection gater][gater] for blocking and allowing nodes. Optional parameters like `ChainID` and `NetworkID` can also be passed. The exchange client also maintains a list of trusted peers via a peer tracker. The peer tracker discovers peers until `len(connected)+len(disconnected)` will not reach the limit(`maxPeerTrackerSize` for now it is 100) and `len(connected)>len(disconnected)`. +Exchange defines a client for requesting headers from the P2P network. An exchange client is initialized using self [host.Host][host], a list of peers in the form of slice [peer.IDSlice][peer], and a [connection gater][gater] for blocking and allowing nodes. Optional parameters like `ChainID` and `NetworkID` can also be passed. The exchange client also maintains a list of trusted peers via a peer tracker. The peer tracker will continue to discover peers until: + +* the total peers (connected and disconnected) does not exceed [`maxPeerTrackerSize`][maxPeerTrackerSize] or +* connected peer count does not exceed disconnected peer count. + +A set of client parameters (shown in the table below) can be passed while initializing an exchange client. + +|Parameter|Type|Description|Default| +|--|--|--|--| +| MaxHeadersPerRangeRequest | uint64 | MaxHeadersPerRangeRequest defines the max amount of headers that can be requested per 1 request. | 64 | +| RangeRequestTimeout | time.Duration | RangeRequestTimeout defines a timeout after which the session will try to re-request headers from another peer. | 8s | +| chainID | string | chainID is an identifier of the chain. | "" | #### Peer Tracker @@ -52,48 +52,28 @@ When the exchange client is started, it bootstraps the peer tracker using the se The new peers are tracked by subscribing to `event.EvtPeerConnectednessChanged{}`. -The peer tracker also runs garbage collector (gc) that removes the disconnected peers (determined as disconnected for more than `maxAwaitingTime` or connected peers whose scores are less than or equal to `defaultScore`) from the tracked peers list once every `gcPeriod`. +The peer tracker also runs garbage collector (gc) that removes the disconnected peers (determined as disconnected for more than [maxAwaitingTime][maxAwaitingTime] or connected peers whose scores are less than or equal to [defaultScore][defaultScore]) from the tracked peers list once every [gcCycle][gcCycle]. The peer tracker also provides a block peer functionality which is used to block peers that send invalid network headers. Invalid header is a header that fails when `Verify` method of the header interface is invoked. -The exchange client implements the following interface: - -``` -// Getter contains the behavior necessary for a component to retrieve -// headers that have been processed during header sync. -type Getter[H Header[H]] interface { - Head[H] - - // Get returns the Header corresponding to the given hash. - Get(context.Context, Hash) (H, error) - - // GetByHeight returns the Header corresponding to the given block height. - GetByHeight(context.Context, uint64) (H, error) +#### Getter Interface - // GetRangeByHeight returns the given range of Headers. - GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) - - // GetVerifiedRange requests the header range from the provided Header and - // verifies that the returned headers are adjacent to each other. - GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) -} +The exchange client implements the following `Getter` interface which contains the behavior necessary for a component to retrieve headers that have been processed during header sync. The `Getter` interface consists of: -// 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[H]] interface { - // Head returns the latest known header. - Head(context.Context, ...HeadOption[H]) (H, error) -} -``` +|Method|Input|Output|Description| +|--|--|--|--| +| Head | `context.Context, ...HeadOption[H]` | `H, error` | Head returns the latest known chain header. Note that "chain head" is subjective to the component reporting it. | +| Get | `context.Context, Hash` | `H, error` | Get returns the Header corresponding to the given hash. | +| GetByHeight | `context.Context, uint64` | `H, error` | GetByHeight returns the Header corresponding to the given block height. | +| GetRangeByHeight | `ctx context.Context, from H, to uint64` | `[]H, error` | GetRangeByHeight requests the header range from the provided Header and verifies that the returned headers are adjacent to each other. Expected to return the range [from.Height()+1:to).| -`Head()` method requests the latest header from trusted or tracked peers. The `Head()` call also allows passing an optional `TrustedHead`, which allows the caller to specify a trusted head against which the untrusted head is verified. By default, `Head()` requests only trusted peers and if `TrustedHead` is provided untrusted tracked peers are also requested (limited to `maxUntrustedHeadRequests` of 4). The `Head()` requests utilize 90% of the set deadline (in the form of context deadline) for requests and the remaining for determining the best head from gathered responses. Upon receiving headers from peers (either trusted or tracked), the best head is determined as the head: +`Head()` method requests the latest header from trusted or tracked peers. The `Head()` call also allows passing an optional `TrustedHead`, which allows the caller to specify a trusted head against which the untrusted head is verified. By default, `Head()` requests only trusted peers and if `TrustedHead` is provided untrusted tracked peers are also requested, limited to [maxUntrustedHeadRequests][maxUntrustedHeadRequests]. The `Head()` requests utilize 90% of the set deadline (in the form of context deadline) for requests and the remaining for determining the best head from gathered responses. Upon receiving headers from peers (either trusted or tracked), the best head is determined as the head: * with max height among the received -* which is received from at least `minHeadResponses` of 2 peers +* which is received from at least [minHeadResponses][minHeadResponses] peers * when neither or both conditions meet, the head with highest height is used -Apart from requesting the latest header, any arbitrary header(s) can be requested (with 3 retries) using height (`GetByHeight`), hash (`Get`), range (`GetRangeByHeight` and `GetVerifiedRange`) from trusted peers as defined in the request proto message: +Apart from requesting the latest header, any arbitrary header(s) can be requested (with 3 retries) using height (`GetByHeight`), hash (`Get`), or range (`GetRangeByHeight`) from trusted peers as defined in the request proto message which encapsulates all three kinds of header requests: ``` message HeaderRequest { @@ -105,40 +85,18 @@ message HeaderRequest { } ``` -The `GetVerifiedRange` differs from `GetRangeByHeight` as it ensures that the returned headers are correct against another header (passed as parameter to the call). +Note that, `GetRangeByHeight` as it ensures that the returned headers are correct against the begin header of the range. ### Exchange Server -ExchangeServer represents the server-side component (P2P server) for responding to inbound header requests. The exchange server needs to be initialized using self [host.Host][host] and a [store][store]. Optional `ServerParameters` as shown below, can be set during the server initialization. +ExchangeServer represents the server-side component of the exchange (a P2P server) for responding to inbound header requests. The exchange server needs to be initialized using self [host.Host][host] and a [store][store]. Optional `ServerParameters` as shown below, can be set during the server initialization. -``` -// ServerParameters is the set of parameters that must be configured for the exchange. -type ServerParameters struct { - // WriteDeadline sets the timeout for sending messages to the stream - WriteDeadline time.Duration - // ReadDeadline sets the timeout for reading messages from the stream - ReadDeadline time.Duration - // RangeRequestTimeout defines a timeout after which the session will try to re-request headers - // from another peer. - RangeRequestTimeout time.Duration - // networkID is a network that will be used to create a protocol.ID - // Is empty by default - networkID string -} -``` - -The default values for `ServerParameters` are as described below. - -``` -// DefaultServerParameters returns the default params to configure the store. -func DefaultServerParameters() ServerParameters { - return ServerParameters{ - WriteDeadline: time.Second * 8, - ReadDeadline: time.Minute, - RangeRequestTimeout: time.Second * 10, - } -} -``` +|Parameter|Type|Description|Default| +|--|--|--|--| +| WriteDeadline | time.Duration | WriteDeadline sets the timeout for sending messages to the stream | 8s | +| ReadDeadline | time.Duration | ReadDeadline sets the timeout for reading messages from the stream | 60s | +| RangeRequestTimeout | time.Duration | RangeRequestTimeout defines a timeout after which the session will try to re-request headers from another peer | 10s | +| networkID | string | networkID is a network that will be used to create a protocol.ID | "" | During the server start, a request handler for the `protocolID` (`/networkID/header-ex/v0.0.3`) which defined using the `networkID` configurable parameter is setup to serve the inbound header requests. @@ -161,40 +119,11 @@ enum StatusCode { } ``` -The request handler utilizes its local [store][store] for serving the header requests and only up to `MaxRangeRequestSize` of 512 headers can be requested while requesting headers by range. If the requested range is not available, the range is reset to whatever is available. +The request handler utilizes its local [store][store] for serving the header requests and only up to [MaxRangeRequestSize][MaxRangeRequestSize] of 512 headers can be requested while requesting headers by range. If the requested range is not available, the range is reset to whatever is available. ### Session -Session aims to divide a header range requests into several smaller requests among different peers. This service is used by the exchange client for making the `GetRangeByHeight` and `GetVerifiedRange` calls. - -``` -// ClientParameters is the set of parameters that must be configured for the exchange. -type ClientParameters struct { - // MaxHeadersPerRangeRequest defines the max amount of headers that can be requested per 1 request. - MaxHeadersPerRangeRequest uint64 - // RangeRequestTimeout defines a timeout after which the session will try to re-request headers - // from another peer. - RangeRequestTimeout time.Duration - // networkID is a network that will be used to create a protocol.ID - networkID string - // chainID is an identifier of the chain. - chainID string - - pidstore PeerIDStore -} -``` - -The default values for `ClientParameters` are as described below. - -``` -// DefaultClientParameters returns the default params to configure the store. -func DefaultClientParameters() ClientParameters { - return ClientParameters{ - MaxHeadersPerRangeRequest: 64, - RangeRequestTimeout: time.Second * 8, - } -} -``` +Session aims to divide a header range requests into several smaller requests among different peers. This service is used by the exchange client for making the `GetRangeByHeight`. ## Metrics @@ -222,3 +151,10 @@ Currently only following metrics are collected: [peer]: https://github.com/libp2p/go-libp2p/core/peer [gater]: https://github.com/libp2p/go-libp2p/p2p/net/conngater [store]: https://github.com/celestiaorg/go-header/blob/main/store/store.md +[maxPeerTrackerSize]: https://github.com/celestiaorg/go-header/blob/main/p2p/peer_tracker.go#L19 +[maxAwaitingTime]: https://github.com/celestiaorg/go-header/blob/main/p2p/peer_tracker.go#L25 +[defaultScore]: https://github.com/celestiaorg/go-header/blob/main/p2p/peer_tracker.go#L17 +[gcCycle]: https://github.com/celestiaorg/go-header/blob/main/p2p/peer_tracker.go#L27 +[maxUntrustedHeadRequests]: https://github.com/celestiaorg/go-header/blob/main/p2p/exchange.go#L32 +[minHeadResponses]: https://github.com/celestiaorg/go-header/blob/main/p2p/exchange.go#L28 +[MaxRangeRequestSize]: https://github.com/celestiaorg/go-header/blob/main/interface.go#L13 diff --git a/specs/src/README.md b/specs/src/README.md index 017e76c3..92088a58 100644 --- a/specs/src/README.md +++ b/specs/src/README.md @@ -1,6 +1,6 @@ # Welcome -Welcome to the go-header Specifications. +Welcome to the go-header specifications. go-header is a library for syncing blockchain data, such as block headers, over the P2P network in a trust-minimized way. It contains services for requesting and receiving headers from the P2P network, serving header requests from other nodes in the P2P network, storing headers, and syncing historical headers in case of fallbacks. diff --git a/store/store.md b/store/store.md index bb1a8dc9..dfb23a81 100644 --- a/store/store.md +++ b/store/store.md @@ -1,95 +1,25 @@ # Store -Store implements the Store interface (shown below) for headers over [datastore][go-datastore]. +Store implements the Store interface (shown below) for headers over . -``` -// Store encompasses the behavior necessary to store and retrieve Headers -// from a node's local storage. -type Store[H Header[H]] interface { - // Getter encompasses all getter methods for headers. - Getter[H] +Store encompasses the behavior necessary to store and retrieve headers from a node's local storage ([datastore][go-datastore]). The Store interface includes checker and append methods on top of [Getter](../p2p/p2p.md#getter-interface) methods as shown in the table below. - // Init initializes Store with the given head, meaning it is initialized with the genesis header. - Init(context.Context, H) error - - // Height reports current height of the chain head. - Height() uint64 - - // Has checks whether Header is already stored. - Has(context.Context, Hash) (bool, error) - - // HasAt checks whether Header at the given height is already stored. - HasAt(context.Context, uint64) bool - - // Append stores and verifies the given Header(s). - // It requires them to be adjacent and in ascending order, - // as it applies them contiguously on top of the current head height. - // It returns the amount of successfully applied headers, - // so caller can understand what given header was invalid, if any. - Append(context.Context, ...H) error -} - -// Getter contains the behavior necessary for a component to retrieve -// headers that have been processed during header sync. -type Getter[H Header[H]] interface { - Head[H] - - // Get returns the Header corresponding to the given hash. - Get(context.Context, Hash) (H, error) - - // GetByHeight returns the Header corresponding to the given block height. - GetByHeight(context.Context, uint64) (H, error) - - // GetRangeByHeight returns the given range of Headers. - GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) - - // GetVerifiedRange requests the header range from the provided Header and - // verifies that the returned headers are adjacent to each other. - GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) -} - -// 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[H]] interface { - // Head returns the latest known header. - Head(context.Context, ...HeadOption[H]) (H, error) -} -``` +|Method|Input|Output|Description| +|--|--|--|--| +| Init | `context.Context, H` | `error` | Init initializes Store with the given head, meaning it is initialized with the genesis header. | +| Height | | `uint64` | Height reports current height of the chain head. | +| Has | `context.Context, Hash` | `bool, error` | Has checks whether Header is already stored. | +| HasAt | `context.Context, uint64` | `bool` | HasAt checks whether Header at the given height is already stored. | +| Append | `context.Context, ...H` | `error` | Append stores and verifies the given Header(s). It requires them to be adjacent and in ascending order, as it applies them contiguously on top of the current head height. It returns the amount of successfully applied headers, so caller can understand what given header was invalid, if any. | A new store is created by passing a [datastore][go-datastore] instance and an optional head. If the head is not passed while creating a new store, `Init` method can be used to later initialize the store with head. The store must have a head before start. The head is considered trusted header and generally it is the genesis header. A custom store prefix can be passed during the store initialization. Further, a set of parameters can be passed during the store initialization to configure the store as described below. -``` -// Parameters is the set of parameters that must be configured for the store. -type Parameters struct { - // StoreCacheSize defines the maximum amount of entries in the Header Store cache. - StoreCacheSize int - - // IndexCacheSize defines the maximum amount of entries in the Height to Hash index cache. - IndexCacheSize int - - // WriteBatchSize defines the size of the batched header write. - // Headers are written in batches not to thrash the underlying Datastore with writes. - WriteBatchSize int - - // storePrefix defines the prefix used to wrap the store - // OPTIONAL - storePrefix datastore.Key -} -``` - -The default values for store `Parameters` are as described below. - -``` -// DefaultParameters returns the default params to configure the store. -func DefaultParameters() Parameters { - return Parameters{ - StoreCacheSize: 4096, - IndexCacheSize: 16384, - WriteBatchSize: 2048, - } -} -``` +|Parameter|Type|Description|Default| +|--|--|--|--| +| StoreCacheSize | int | StoreCacheSize defines the maximum amount of entries in the Header Store cache. | 4096 | +| IndexCacheSize | int | IndexCacheSize defines the maximum amount of entries in the Height to Hash index cache. | 16384 | +| WriteBatchSize | int | WriteBatchSize defines the size of the batched header write. Headers are written in batches not to thrash the underlying Datastore with writes. | 2048 | +| storePrefix | datastore.Key | storePrefix defines the prefix used to wrap the store | nil | The store runs a flush loop during the start which performs writing task to the underlying datastore in a separate routine. This way writes are controlled and manageable from one place allowing: diff --git a/sync/sync.md b/sync/sync.md index 6ffc6d00..9561ece2 100644 --- a/sync/sync.md +++ b/sync/sync.md @@ -25,39 +25,11 @@ For creating a new instance of the Syncer following components are needed: Options for configuring the syncer: -``` -// Parameters is the set of parameters that must be configured for the syncer. -type Parameters struct { - // TrustingPeriod is period through which we can trust a header's validators set. - // - // Should be significantly less than the unbonding period (e.g. unbonding - // period = 3 weeks, trusting period = 2 weeks). - // - // More specifically, trusting period + time needed to check headers + time - // needed to report and punish misbehavior should be less than the unbonding - // period. - TrustingPeriod time.Duration - // blockTime provides a reference point for the Syncer to determine - // whether its subjective head is outdated. - // Keeping it private to disable serialization for it. - // default value is set to 0 so syncer will constantly request networking head. - blockTime time.Duration - // recencyThreshold describes the time period for which a header is - // considered "recent". The default is blockTime + 5 seconds. - recencyThreshold time.Duration -} -``` - -The default parameters used to configure the syncer are: - -``` -// DefaultParameters returns the default params to configure the syncer. -func DefaultParameters() Parameters { - return Parameters{ - TrustingPeriod: 336 * time.Hour, // tendermint's default trusting period - } -} -``` +|Parameter|Type|Description|Default| +|--|--|--|--| +| TrustingPeriod | time.Duration | TrustingPeriod is period through which we can trust a header's validators set. Should be significantly less than the unbonding period (e.g. unbonding period = 3 weeks, trusting period = 2 weeks). More specifically, trusting period + time needed to check headers + time needed to report and punish misbehavior should be less than the unbonding period. | 336 hours (tendermint's default trusting period) | +| blockTime | time.Duration | blockTime provides a reference point for the Syncer to determine whether its subjective head is outdated. Keeping it private to disable serialization for it. | 0 (reason: syncer will constantly request networking head.) | +| recencyThreshold | time.Duration | recencyThreshold describes the time period for which a header is considered "recent". | blockTime + 5 seconds | When the syncer is started: From baef0b94fa62a520bf8e11aad9ccd4e069ab05b1 Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Thu, 26 Oct 2023 21:46:57 -0500 Subject: [PATCH 11/13] generalize header interface --- p2p/p2p.md | 12 ++++++------ specs/src/README.md | 46 +++++++++++++-------------------------------- store/store.md | 10 +++++----- sync/sync.md | 7 +++---- 4 files changed, 27 insertions(+), 48 deletions(-) diff --git a/p2p/p2p.md b/p2p/p2p.md index 707174d8..589016f6 100644 --- a/p2p/p2p.md +++ b/p2p/p2p.md @@ -13,8 +13,8 @@ The Subscriber encompasses the behavior necessary to subscribe/unsubscribe from |Method|Input|Output|Description| |--|--|--|--| -| Subscribe | | `Subscription[H], error` | Subscribe creates long-living Subscription for validated Headers. Multiple Subscriptions can be created. | -| SetVerifier | `func(context.Context, H) error` | error | SetVerifier registers verification func for all Subscriptions. Registered func screens incoming headers before they are forwarded to Subscriptions. Only one func can be set.| +| Subscribe | | Subscription[H], error | Subscribe creates long-living Subscription for validated Headers. Multiple Subscriptions can be created. | +| SetVerifier | func(context.Context, H) error | error | SetVerifier registers verification func for all Subscriptions. Registered func screens incoming headers before they are forwarded to Subscriptions. Only one func can be set.| The `Subscribe()` method allows listening to any new headers that are published to the P2P network. The `SetVerifier()` method allows for setting a custom verifier that will be executed upon receiving any new headers from the P2P network. This is a very useful customization for the consumers of go-header library to pass any custom logic as part of the pubsub. While multiple simultaneous subscriptions are possible via `Subscribe()` interface, only a single verifier can be set using the `SetVerifier` interface method. @@ -62,10 +62,10 @@ The exchange client implements the following `Getter` interface which contains t |Method|Input|Output|Description| |--|--|--|--| -| Head | `context.Context, ...HeadOption[H]` | `H, error` | Head returns the latest known chain header. Note that "chain head" is subjective to the component reporting it. | -| Get | `context.Context, Hash` | `H, error` | Get returns the Header corresponding to the given hash. | -| GetByHeight | `context.Context, uint64` | `H, error` | GetByHeight returns the Header corresponding to the given block height. | -| GetRangeByHeight | `ctx context.Context, from H, to uint64` | `[]H, error` | GetRangeByHeight requests the header range from the provided Header and verifies that the returned headers are adjacent to each other. Expected to return the range [from.Height()+1:to).| +| Head | context.Context, ...HeadOption[H] | H, error | Head returns the latest known chain header. Note that "chain head" is subjective to the component reporting it. | +| Get | context.Context, Hash | H, error | Get returns the Header corresponding to the given hash. | +| GetByHeight | context.Context, uint64 | H, error | GetByHeight returns the Header corresponding to the given block height. | +| GetRangeByHeight | ctx context.Context, from H, to uint64 | []H, error | GetRangeByHeight requests the header range from the provided Header and verifies that the returned headers are adjacent to each other. Expected to return the range [from.Height()+1:to).| `Head()` method requests the latest header from trusted or tracked peers. The `Head()` call also allows passing an optional `TrustedHead`, which allows the caller to specify a trusted head against which the untrusted head is verified. By default, `Head()` requests only trusted peers and if `TrustedHead` is provided untrusted tracked peers are also requested, limited to [maxUntrustedHeadRequests][maxUntrustedHeadRequests]. The `Head()` requests utilize 90% of the set deadline (in the form of context deadline) for requests and the remaining for determining the best head from gathered responses. Upon receiving headers from peers (either trusted or tracked), the best head is determined as the head: diff --git a/specs/src/README.md b/specs/src/README.md index 92088a58..63c0d078 100644 --- a/specs/src/README.md +++ b/specs/src/README.md @@ -14,39 +14,19 @@ go-header is a library for syncing blockchain data, such as block headers, over The go-header library makes it easy to be used by other projects by defining a clear interface (as described below). An example usage is defined in [headertest/dummy_header.go][dummy header] -``` -type Header[H any] interface { - // New creates new instance of a header. - New() H - - // IsZero reports whether Header is a zero value of it's concrete type. - IsZero() bool - - // ChainID returns identifier of the chain. - ChainID() string - - // Hash returns hash of a header. - Hash() Hash - - // Height returns the height of a header. - Height() uint64 - - // LastHeader returns the hash of last header before this header (aka. previous header hash). - LastHeader() Hash - - // Time returns time when header was created. - Time() time.Time - - // Verify validates given untrusted Header against trusted Header. - Verify(H) error - - // Validate performs stateless validation to check for missed/incorrect fields. - Validate() error - - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler -} -``` +|Method|Input|Output|Description| +|--|--|--|--| +| New | | H | New creates new instance of a header. | +| IsZero | | bool | IsZero reports whether Header is a zero value of it's concrete type. | +| ChainID | | string | ChainID returns identifier of the chain. | +| Hash | | Hash | Hash returns hash of a header. | +| Height | | uint64 | Height returns the height of a header. | +| LastHeader | | Hash | LastHeader returns the hash of last header before this header (aka. previous header hash). | +| Time | | time.Time | Time returns time when header was created. | +| Verify | H | error | Verify validates given untrusted Header against trusted Header. | +| Validate | | error | Validate performs stateless validation to check for missed/incorrect fields. | +| MarshalBinary | | []byte, error| MarshalBinary encodes the receiver into a binary form and returns the result. | +| UnmarshalBinary | []byte | error | UnmarshalBinary must be able to decode the form generated by MarshalBinary. UnmarshalBinary must copy the data if it wishes to retain the data after returning.| # References diff --git a/store/store.md b/store/store.md index dfb23a81..7a0bb842 100644 --- a/store/store.md +++ b/store/store.md @@ -6,11 +6,11 @@ Store encompasses the behavior necessary to store and retrieve headers from a no |Method|Input|Output|Description| |--|--|--|--| -| Init | `context.Context, H` | `error` | Init initializes Store with the given head, meaning it is initialized with the genesis header. | -| Height | | `uint64` | Height reports current height of the chain head. | -| Has | `context.Context, Hash` | `bool, error` | Has checks whether Header is already stored. | -| HasAt | `context.Context, uint64` | `bool` | HasAt checks whether Header at the given height is already stored. | -| Append | `context.Context, ...H` | `error` | Append stores and verifies the given Header(s). It requires them to be adjacent and in ascending order, as it applies them contiguously on top of the current head height. It returns the amount of successfully applied headers, so caller can understand what given header was invalid, if any. | +| Init | context.Context, H | error | Init initializes Store with the given head, meaning it is initialized with the genesis header. | +| Height | | uint64 | Height reports current height of the chain head. | +| Has | context.Context, Hash | bool, error | Has checks whether Header is already stored. | +| HasAt | context.Context, uint64 | bool | HasAt checks whether Header at the given height is already stored. | +| Append | context.Context, ...H | error | Append stores and verifies the given Header(s). It requires them to be adjacent and in ascending order, as it applies them contiguously on top of the current head height. It returns the amount of successfully applied headers, so caller can understand what given header was invalid, if any. | A new store is created by passing a [datastore][go-datastore] instance and an optional head. If the head is not passed while creating a new store, `Init` method can be used to later initialize the store with head. The store must have a head before start. The head is considered trusted header and generally it is the genesis header. A custom store prefix can be passed during the store initialization. Further, a set of parameters can be passed during the store initialization to configure the store as described below. diff --git a/sync/sync.md b/sync/sync.md index 9561ece2..0887b64e 100644 --- a/sync/sync.md +++ b/sync/sync.md @@ -48,10 +48,9 @@ Known subjective head is considered network head if it is recent (`now - timesta The header interface defines a `Verify` method which gets invoked when any new header is received via `incomingNetworkHead`. -``` -// Verify validates given untrusted Header against trusted Header. -Verify(H) error -``` +|Method|Input|Output|Description| +|--|--|--|--| +| Verify | H | error | Verify validates given untrusted Header against trusted Header. | ## syncLoop From 390e75f62873e0140ccad4613bd5ebaf7dadc967 Mon Sep 17 00:00:00 2001 From: Ganesha Upadhyaya Date: Fri, 27 Oct 2023 07:04:26 -0500 Subject: [PATCH 12/13] further generalize README --- specs/src/README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/specs/src/README.md b/specs/src/README.md index 63c0d078..26ba2789 100644 --- a/specs/src/README.md +++ b/specs/src/README.md @@ -6,13 +6,13 @@ go-header is a library for syncing blockchain data, such as block headers, over |Component|Description| |---|---| -|[p2p.Subscriber][p2p]|listens for new headers from the P2P network| -|[p2p.ExchangeServer][p2p]|serve header requests from other nodes in the P2P network| -|[p2p.Exchange][p2p]|client that requests headers from other nodes in the P2P network| -|[store.Store][store]|storing headers and making them available for access by other services such as exchange and syncer| -|[sync.Syncer][sync]|syncing of historical and new headers from the P2P network| +|[Subscriber](specs/p2p.md#subscriber)|listens for new headers from the P2P network| +|[ExchangeServer](specs/p2p.md#exchange-server)|serve header requests from other nodes in the P2P network| +|[Exchange](specs/p2p.md#exchange-client)|client that requests headers from other nodes in the P2P network| +|[Store](specs/store.md)|storing headers and making them available for access by other services such as exchange and syncer| +|[Syncer](specs/sync.md)|syncing of historical and new headers from the P2P network| -The go-header library makes it easy to be used by other projects by defining a clear interface (as described below). An example usage is defined in [headertest/dummy_header.go][dummy header] +The go-header library defines a clear interface (as described in the table below) for consumption. Any blockchain data implementing this interface can utilize go-header's P2P services. An example is defined in [headertest/dummy_header.go][dummy header] |Method|Input|Output|Description| |--|--|--|--| @@ -33,6 +33,3 @@ The go-header library makes it easy to be used by other projects by defining a c [1] [Dummy Header][dummy header] [dummy header]: https://github.com/celestiaorg/go-header/blob/main/headertest/dummy_header.go -[p2p]: https://github.com/celestiaorg/go-header/blob/main/p2p/p2p.md -[store]: https://github.com/celestiaorg/go-header/blob/main/store/store.md -[sync]: https://github.com/celestiaorg/go-header/blob/main/sync/sync.md From 9bd3962db3f57251dc16e9b9ba3f3fdc341e0591 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 8 Dec 2023 15:02:55 +0100 Subject: [PATCH 13/13] try fixing links --- specs/src/README.md | 48 ++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/specs/src/README.md b/specs/src/README.md index 26ba2789..ccf2967c 100644 --- a/specs/src/README.md +++ b/specs/src/README.md @@ -4,32 +4,36 @@ Welcome to the go-header specifications. go-header is a library for syncing blockchain data, such as block headers, over the P2P network in a trust-minimized way. It contains services for requesting and receiving headers from the P2P network, serving header requests from other nodes in the P2P network, storing headers, and syncing historical headers in case of fallbacks. -|Component|Description| -|---|---| -|[Subscriber](specs/p2p.md#subscriber)|listens for new headers from the P2P network| -|[ExchangeServer](specs/p2p.md#exchange-server)|serve header requests from other nodes in the P2P network| -|[Exchange](specs/p2p.md#exchange-client)|client that requests headers from other nodes in the P2P network| -|[Store](specs/store.md)|storing headers and making them available for access by other services such as exchange and syncer| -|[Syncer](specs/sync.md)|syncing of historical and new headers from the P2P network| +| Component | Description | +|----------------------------------|----------------------------------------------------------------------------------------------------| +| [Subscriber][Subscriber] | listens for new headers from the P2P network | +| [ExchangeServer][ExchangeServer] | serve header requests from other nodes in the P2P network | +| [Exchange][Exchange] | client that requests headers from other nodes in the P2P network | +| [Store][Store] | storing headers and making them available for access by other services such as exchange and syncer | +| [Syncer][Syncer] | syncing of historical and new headers from the P2P network | The go-header library defines a clear interface (as described in the table below) for consumption. Any blockchain data implementing this interface can utilize go-header's P2P services. An example is defined in [headertest/dummy_header.go][dummy header] -|Method|Input|Output|Description| -|--|--|--|--| -| New | | H | New creates new instance of a header. | -| IsZero | | bool | IsZero reports whether Header is a zero value of it's concrete type. | -| ChainID | | string | ChainID returns identifier of the chain. | -| Hash | | Hash | Hash returns hash of a header. | -| Height | | uint64 | Height returns the height of a header. | -| LastHeader | | Hash | LastHeader returns the hash of last header before this header (aka. previous header hash). | -| Time | | time.Time | Time returns time when header was created. | -| Verify | H | error | Verify validates given untrusted Header against trusted Header. | -| Validate | | error | Validate performs stateless validation to check for missed/incorrect fields. | -| MarshalBinary | | []byte, error| MarshalBinary encodes the receiver into a binary form and returns the result. | -| UnmarshalBinary | []byte | error | UnmarshalBinary must be able to decode the form generated by MarshalBinary. UnmarshalBinary must copy the data if it wishes to retain the data after returning.| +| Method | Input | Output | Description | +|-----------------|--------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| New | | H | New creates new instance of a header. | +| IsZero | | bool | IsZero reports whether Header is a zero value of it's concrete type. | +| ChainID | | string | ChainID returns identifier of the chain. | +| Hash | | Hash | Hash returns hash of a header. | +| Height | | uint64 | Height returns the height of a header. | +| LastHeader | | Hash | LastHeader returns the hash of last header before this header (aka. previous header hash). | +| Time | | time.Time | Time returns time when header was created. | +| Verify | H | error | Verify validates given untrusted Header against trusted Header. | +| Validate | | error | Validate performs stateless validation to check for missed/incorrect fields. | +| MarshalBinary | | []byte, error | MarshalBinary encodes the receiver into a binary form and returns the result. | +| UnmarshalBinary | []byte | error | UnmarshalBinary must be able to decode the form generated by MarshalBinary. UnmarshalBinary must copy the data if it wishes to retain the data after returning. | # References -[1] [Dummy Header][dummy header] +[1] [Dummy Header][https://github.com/celestiaorg/go-header/blob/main/headertest/dummy_header.go] -[dummy header]: https://github.com/celestiaorg/go-header/blob/main/headertest/dummy_header.go +[Subscriber]: ../../p2p/p2p.md#subscriber +[ExchangeServer]: ../../p2p/p2p.md#exchange-server +[Exchange]: ../../p2p/p2p.md#exchange-client +[Store]: ../../store/store.md +[Syncer]: ../../sync/sync.md \ No newline at end of file