Documentation
¶
Overview ¶
Package blocklag is a Go companion library for failnext that detects relative block-height lag across a fixed set of Ethereum HTTP JSON-RPC providers.
Call Monitor.Check to query providers directly with eth_blockNumber, then pass Monitor.Eligible to failnext.Config.Eligible.
blocklag determines provider eligibility from relative block-height lag; failnext handles endpoint selection and HTTP failover. failnext itself does not detect blockchain lag or stale state.
Checks are initiated by the caller; no background polling occurs. Eligibility reflects the last published comparison, not verified canonical-chain freshness.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Endpoints is the fixed set of Ethereum HTTP JSON-RPC destinations.
// Each endpoint needs a nonempty, unique ID and an absolute HTTP or HTTPS
// URL with a host and no fragment. Endpoints should be on the same
// Ethereum-compatible chain.
Endpoints []failnext.Endpoint
// MaxLag is the largest permitted difference from the highest successfully
// observed block in each Check. The limit is inclusive; zero is valid.
MaxLag uint64
// Base performs blocklag's direct HTTP probes, not failnext's application
// requests. If nil, http.DefaultTransport is used. A custom transport must
// preserve the requested destination, honor request cancellation, and must
// not route probes through failnext.
Base http.RoundTripper
}
Config configures a Monitor.
type Monitor ¶
type Monitor struct {
// contains filtered or unexported fields
}
Monitor maintains eligibility based on the last published comparison of block heights for a fixed set of Ethereum RPC endpoints. Construct it with New; the zero value is not usable.
Example ¶
// Local test servers stand in for two Ethereum HTTP RPC providers.
provider := func(block, balance string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var request struct {
Method string `json:"method"`
}
if r.Method != http.MethodPost || json.NewDecoder(r.Body).Decode(&request) != nil {
http.Error(w, "invalid RPC request", http.StatusBadRequest)
return
}
var result string
switch request.Method {
case "eth_blockNumber":
result = block
case "eth_getBalance":
result = balance
default:
http.Error(w, "unexpected RPC method", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = fmt.Fprintf(w, `{"jsonrpc":"2.0","id":1,"result":%q}`, result)
})
}
primary := httptest.NewServer(provider("0x64", "0x0")) // Block 100.
defer primary.Close()
backup := httptest.NewServer(provider("0x6e", "0x2a")) // Block 110.
defer backup.Close()
endpoints := []failnext.Endpoint{
{ID: "primary", URL: primary.URL},
{ID: "backup", URL: backup.URL},
}
monitor, err := blocklag.New(blocklag.Config{
Endpoints: endpoints,
MaxLag: 3,
})
if err != nil {
panic(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := monitor.Check(ctx); err != nil {
panic(err)
}
fmt.Println("primary eligible:", monitor.Eligible("primary"))
fmt.Println("backup eligible:", monitor.Eligible("backup"))
tr, err := failnext.New(failnext.Config{
Endpoints: endpoints,
Eligible: monitor.Eligible,
})
if err != nil {
panic(err)
}
client := &http.Client{Transport: tr}
defer client.CloseIdleConnections()
// Ethereum JSON-RPC reads use POST. Allow failover only when the
// application knows the operation is safe to repeat.
readCtx := failnext.WithFailoverAllowed(ctx)
req, err := http.NewRequestWithContext(readCtx, http.MethodPost, endpoints[0].URL,
strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"eth_getBalance","params":["0x0000000000000000000000000000000000000000","latest"]}`))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var rpcResult struct {
Result string `json:"result"`
}
if err := json.NewDecoder(resp.Body).Decode(&rpcResult); err != nil {
panic(err)
}
fmt.Println("balance:", rpcResult.Result)
Output: primary eligible: false backup eligible: true balance: 0x2a
func New ¶
New validates the configuration and constructs a Monitor. It copies the endpoint slice so later caller modifications do not affect it. All configured endpoints are initially eligible.
func (*Monitor) Check ¶
Check probes all configured providers directly with eth_blockNumber, outside failnext, and publishes their relative eligibility. Provider probes run concurrently, but overlapping calls to Check are serialized.
A successful probe is eligible when its height is within MaxLag blocks of the highest successfully observed height. If at least one probe succeeds, Check publishes a complete new snapshot; failed probes are ineligible. Partial probe failure still returns an error, even though state is updated. Probe errors identify failed endpoint IDs; multiple errors are joined.
If every probe fails or cancellation is observed before publication, the previous snapshot is retained. A waiting Check can be canceled without starting probes. The caller owns the checking schedule; published eligibility never expires.
func (*Monitor) Eligible ¶
func (m *Monitor) Eligible(id failnext.EndpointID) bool
Eligible reports whether id is eligible according to the latest published snapshot. All configured endpoints are initially eligible; unknown IDs are always ineligible. It performs no network I/O and is safe for concurrent use. Separate calls may observe different snapshots if Check publishes between them. It can be used directly as failnext.Config.Eligible. It only supplies eligibility; failnext still performs endpoint selection and HTTP failover.