network

package
v0.33.17 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 17, 2024 License: AGPL-3.0 Imports: 17 Imported by: 47

Documentation

Overview

(c) 2019 Dapper Labs - ALL RIGHTS RESERVED

Index

Constants

This section is empty.

Variables

View Source
var (
	EmptyTargetList = errors.New("target list empty")
)
View Source
var ErrBlobNotFound = errors.New("blobservice: key not found")

Functions

func AllPeerUnreachableError

func AllPeerUnreachableError(errs ...error) bool

AllPeerUnreachableError returns whether all errors are PeerUnreachableError

func IsErrConnectionStatus added in v0.30.0

func IsErrConnectionStatus(err error) bool

IsErrConnectionStatus returns whether an error is ErrIllegalConnectionState

func IsPeerUnreachableError

func IsPeerUnreachableError(e error) bool

IsPeerUnreachableError returns whether the given error is PeerUnreachableError

func IsTransientError added in v0.29.0

func IsTransientError(err error) bool

func NewPeerUnreachableError

func NewPeerUnreachableError(err error) error

NewPeerUnreachableError creates a PeerUnreachableError instance with an error

Types

type AllowListingUpdate added in v0.32.0

type AllowListingUpdate struct {
	FlowIds flow.IdentifierList
	Cause   DisallowListedCause
}

AllowListingUpdate is a notification of a new allow list update, it contains a list of Flow identities that are now allow listed for a specific reason, i.e., their disallow list entry for that reason is removed.

type BasicResolver added in v0.21.0

type BasicResolver interface {
	LookupIPAddr(context.Context, string) ([]net.IPAddr, error)
	LookupTXT(context.Context, string) ([]string, error)
}

BasicResolver is a low level interface for DNS resolution Note: this is the resolver interface that libp2p expects. We keep a copy of it here for mock generation. https://github.com/multiformats/go-multiaddr-dns/blob/master/resolve.go

type BlobGetter added in v0.23.9

type BlobGetter interface {
	// GetBlob gets the requested blob.
	GetBlob(ctx context.Context, c cid.Cid) (blobs.Blob, error)

	// GetBlobs does a batch request for the given cids, returning blobs as
	// they are found, in no particular order.
	//
	// It may not be able to find all requested blobs (or the context may
	// be canceled). In that case, it will close the channel early. It is up
	// to the consumer to detect this situation and keep track which blobs
	// it has received and which it hasn't.
	GetBlobs(ctx context.Context, ks []cid.Cid) <-chan blobs.Blob
}

BlobGetter is the common interface shared between blobservice sessions and the blobservice.

type BlobService added in v0.23.9

type BlobService interface {
	component.Component
	BlobGetter

	// AddBlob puts a given blob to the underlying datastore
	AddBlob(ctx context.Context, b blobs.Blob) error

	// AddBlobs adds a slice of blobs at the same time using batching
	// capabilities of the underlying datastore whenever possible.
	AddBlobs(ctx context.Context, bs []blobs.Blob) error

	// DeleteBlob deletes the given blob from the blobservice.
	DeleteBlob(ctx context.Context, c cid.Cid) error

	// GetSession creates a new session that allows for controlled exchange of wantlists to decrease the bandwidth overhead.
	GetSession(ctx context.Context) BlobGetter

	// TriggerReprovide updates the BlobService's provider entries in the DHT
	TriggerReprovide(ctx context.Context) error
}

BlobService is a hybrid blob datastore. It stores data in a local datastore and may retrieve data from a remote Exchange. It uses an internal `datastore.Datastore` instance to store values.

type BlobServiceOption added in v0.23.9

type BlobServiceOption func(BlobService)

type Codec

type Codec interface {
	NewEncoder(w io.Writer) Encoder
	NewDecoder(r io.Reader) Decoder
	Encode(v interface{}) ([]byte, error)

	// Decode decodes a message.
	// Expected error returns during normal operations:
	//  - codec.ErrInvalidEncoding if message encoding is invalid.
	//  - codec.ErrUnknownMsgCode if message code byte does not match any of the configured message codes.
	//  - codec.ErrMsgUnmarshal if the codec fails to unmarshal the data to the message type denoted by the message code.
	Decode(data []byte) (interface{}, error)
}

Codec provides factory functions for encoders and decoders.

type Compressor added in v0.23.0

type Compressor interface {
	NewReader(io.Reader) (io.ReadCloser, error)
	NewWriter(io.Writer) (WriteCloseFlusher, error)
}

Compressor offers compressing and decompressing services for sending and receiving a byte slice at network layer.

type Conduit

type Conduit interface {
	MisbehaviorReporter
	// Publish submits an event to the network layer for unreliable delivery
	// to subscribers of the given event on the network layer. It uses a
	// publish-subscribe layer and can thus not guarantee that the specified
	// recipients received the event.
	// The event is published on the channels of this Conduit and will be received
	// by the nodes specified as part of the targetIDs.
	// TODO: function errors must be documented.
	Publish(event interface{}, targetIDs ...flow.Identifier) error

	// Unicast sends the event in a reliable way to the given recipient.
	// It uses 1-1 direct messaging over the underlying network to deliver the event.
	// It returns an error if the unicast fails.
	// TODO: function errors must be documented.
	Unicast(event interface{}, targetID flow.Identifier) error

	// Multicast unreliably sends the specified event over the channel
	// to the specified number of recipients selected from the specified subset.
	// The recipients are selected randomly from the targetIDs.
	// TODO: function errors must be documented.
	Multicast(event interface{}, num uint, targetIDs ...flow.Identifier) error

	// Close unsubscribes from the channels of this conduit. After calling close,
	// the conduit can no longer be used to send a message.
	Close() error
}

Conduit represents the interface for engines to communicate over the peer-to-peer network. Upon registration with the network, each engine is assigned a conduit, which it can use to communicate across the network in a network-agnostic way. In the background, the network layer connects all engines with the same ID over a shared bus, accessible through the conduit.

type ConduitAdapter added in v0.32.0

type ConduitAdapter interface {
	MisbehaviorReportConsumer
	// UnicastOnChannel sends the message in a reliable way to the given recipient.
	UnicastOnChannel(channels.Channel, interface{}, flow.Identifier) error

	// PublishOnChannel sends the message in an unreliable way to all the given recipients.
	PublishOnChannel(channels.Channel, interface{}, ...flow.Identifier) error

	// MulticastOnChannel unreliably sends the specified event over the channel to randomly selected number of recipients
	// selected from the specified targetIDs.
	MulticastOnChannel(channels.Channel, interface{}, uint, ...flow.Identifier) error

	// UnRegisterChannel unregisters the engine for the specified channel. The engine will no longer be able to send or
	// receive messages from that channel.
	UnRegisterChannel(channel channels.Channel) error
}

ConduitAdapter is one of the networking layer interfaces in Flow (i.e., EngineRegistry, ConduitAdapter, and Underlay). It represents the interface that networking layer offers to a single conduit which enables the conduit to send different types of messages i.e., unicast, multicast, and publish, to other conduits on the network.

type ConduitFactory added in v0.25.0

type ConduitFactory interface {
	// RegisterAdapter sets the ConduitAdapter component of the factory.
	// The ConduitAdapter is a wrapper around the Network layer that only exposes the set of methods
	// that are needed by a conduit.
	RegisterAdapter(ConduitAdapter) error

	// NewConduit creates a conduit on the specified channel.
	// Prior to creating any conduit, the factory requires an ConduitAdapter to be registered with it.
	NewConduit(context.Context, channels.Channel) (Conduit, error)
}

ConduitFactory is an interface type that is utilized by the Network to create conduits for the channels.

type Connection added in v0.12.3

type Connection interface {
	Send(msg interface{}) error
	Receive() (interface{}, error)
}

Connection represents an interface to read from & write to a connection.

type Decoder

type Decoder interface {
	Decode() (interface{}, error)
}

Decoder decodes from the underlying reader into the given message. Expected error returns during normal operations:

  • codec.ErrInvalidEncoding if message encoding is invalid.
  • codec.ErrUnknownMsgCode if message code byte does not match any of the configured message codes.
  • codec.ErrMsgUnmarshal if the codec fails to unmarshal the data to the message type denoted by the message code.

type DisallowListNotificationConsumer added in v0.32.0

type DisallowListNotificationConsumer interface {
	// OnDisallowListNotification is called when a new disallow list update notification is distributed.
	// Any error on consuming an event must be handled internally.
	// The implementation must be concurrency safe.
	OnDisallowListNotification(*DisallowListingUpdate)

	// OnAllowListNotification is called when a new allow list update notification is distributed.
	// Any error on consuming an event must be handled internally.
	// The implementation must be concurrency safe.
	OnAllowListNotification(*AllowListingUpdate)
}

DisallowListNotificationConsumer is an interface for consuming disallow/allow list update notifications.

type DisallowListedCause added in v0.32.0

type DisallowListedCause string

DisallowListedCause is a type representing the cause of disallow listing. A remote node may be disallow-listed by the current node for a variety of reasons. This type is used to represent the reason for disallow-listing, so that if a node is disallow-listed for reasons X and Y, allow-listing it back for reason X does not automatically allow-list it for reason Y.

const (
	// DisallowListedCauseAdmin is the cause of disallow-listing a node by an admin command.
	DisallowListedCauseAdmin DisallowListedCause = "disallow-listed-admin"
	// DisallowListedCauseAlsp is the cause of disallow-listing a node by the ALSP (Application Layer Spam Prevention).
	DisallowListedCauseAlsp DisallowListedCause = "disallow-listed-alsp"
)

func (DisallowListedCause) String added in v0.32.0

func (c DisallowListedCause) String() string

type DisallowListingUpdate added in v0.32.0

type DisallowListingUpdate struct {
	FlowIds flow.IdentifierList
	Cause   DisallowListedCause
}

DisallowListingUpdate is a notification of a new disallow list update, it contains a list of Flow identities that are now disallow listed for a specific reason.

type Encoder

type Encoder interface {
	Encode(v interface{}) error
}

Encoder encodes the given message into the underlying writer.

type Engine

type Engine interface {
	module.ReadyDoneAware

	// SubmitLocal submits an event originating on the local node.
	// Deprecated: To asynchronously communicate a local message between components:
	// * Define a message queue on the component receiving the message
	// * Define a function (with a concrete argument type) on the component receiving
	//   the message, which adds the message to the message queue
	SubmitLocal(event interface{})

	// Submit submits the given event from the node with the given origin ID
	// for processing in a non-blocking manner. It returns instantly and logs
	// a potential processing error internally when done.
	// Deprecated: Only applicable for use by the networking layer, which should use MessageProcessor instead
	Submit(channel channels.Channel, originID flow.Identifier, event interface{})

	// ProcessLocal processes an event originating on the local node.
	// Deprecated: To synchronously process a local message:
	// * Define a function (with a concrete argument type) on the component receiving
	//   the message, which blocks until the message is processed
	ProcessLocal(event interface{}) error

	// Process processes the given event from the node with the given origin ID
	// in a blocking manner. It returns the potential processing error when
	// done.
	// Deprecated: Only applicable for use by the networking layer, which should use MessageProcessor instead
	Process(channel channels.Channel, originID flow.Identifier, event interface{}) error
}

Engine represents an isolated process running across the peer-to-peer network as part of the node business logic. It provides the network layer with the necessary interface to forward events to engines for processing. Deprecated: Use MessageProcessor instead

type EngineRegistry added in v0.32.0

type EngineRegistry interface {
	component.Component
	// Register will subscribe to the channel with the given engine and
	// the engine will be notified with incoming messages on the channel.
	// The returned Conduit can be used to send messages to engines on other nodes subscribed to the same channel
	// On a single node, only one engine can be subscribed to a channel at any given time.
	Register(channel channels.Channel, messageProcessor MessageProcessor) (Conduit, error)

	// RegisterBlobService registers a BlobService on the given channel, using the given datastore to retrieve values.
	// The returned BlobService can be used to request blocks from the network.
	// RegisterBlobService starts the BlobService component using the network's context.
	// TODO: We should return a function that can be called to unregister / close the BlobService
	RegisterBlobService(channel channels.Channel, store datastore.Batching, opts ...BlobServiceOption) (BlobService, error)

	// RegisterPingService registers a ping protocol handler for the given protocol ID
	RegisterPingService(pingProtocolID protocol.ID, pingInfoProvider PingInfoProvider) (PingService, error)
}

EngineRegistry is one of the networking layer interfaces in Flow (i.e., EngineRegistry, ConduitAdapter, and Underlay). It represents the interface that networking layer offers to the Flow protocol layer, i.e., engines. It is responsible for creating conduits through which engines can send and receive messages to and from other engines on the network, as well as registering other services such as BlobService and PingService.

type ErrIllegalConnectionState added in v0.30.0

type ErrIllegalConnectionState struct {
	// contains filtered or unexported fields
}

ErrIllegalConnectionState indicates connection status to node is NotConnected but connections to node > 0

func NewConnectionStatusErr added in v0.30.0

func NewConnectionStatusErr(pid peer.ID, numOfConns int) ErrIllegalConnectionState

NewConnectionStatusErr returns a new ErrIllegalConnectionState.

func (ErrIllegalConnectionState) Error added in v0.30.0

type IncomingMessageScope added in v0.29.1

type IncomingMessageScope interface {
	// OriginId returns the origin node ID.
	OriginId() flow.Identifier

	// Proto returns the raw message received.
	Proto() *message.Message

	// DecodedPayload returns the decoded payload of the message.
	DecodedPayload() interface{}

	// Protocol returns the type of protocol used to receive the message.
	Protocol() message.ProtocolType

	// Channel returns the channel of the message.
	Channel() channels.Channel

	// Size returns the size of the message.
	Size() int

	// TargetIDs returns the target node IDs, i.e., the intended recipients.
	TargetIDs() flow.IdentifierList

	// EventID returns the hash of the payload and channel.
	EventID() []byte

	// PayloadType returns the type of the decoded payload.
	PayloadType() string
}

IncomingMessageScope defines the interface for incoming message scopes, i.e., self-contained messages that have been received on the wire and are ready to be processed.

type MessageProcessor added in v0.17.2

type MessageProcessor interface {
	// Process is exposed by engines to accept messages from the networking layer.
	// Implementations of Process should be non-blocking. In general, Process should
	// only queue the message internally by the engine for later async processing.
	//
	// TODO: This function should not return an error.
	//  The networking layer's responsibility is fulfilled once it delivers a message to an engine.
	//  It does not possess the context required to handle errors that may arise during an engine's processing
	//  of the message, as error handling for message processing falls outside the domain of the networking layer.
	//  Consequently, it is reasonable to remove the error from the Process function's signature,
	//  since returning an error to the networking layer would not be useful in this context.
	Process(channel channels.Channel, originID flow.Identifier, message interface{}) error
}

MessageProcessor represents a component which receives messages from the networking layer. Since these messages come from other nodes, which may be Byzantine, implementations must expect and handle arbitrary message inputs (including invalid message types, malformed messages, etc.). Because of this, node-internal messages should NEVER be submitted to a component using Process.

type MessageQueue added in v0.12.3

type MessageQueue interface {
	// Insert inserts the message in queue
	Insert(message interface{}) error
	// Remove removes the message from the queue in priority order. If no message is found, this call blocks.
	// If two messages have the same priority, items are de-queued in insertion order
	Remove() interface{}
	// Len gives the current length of the queue
	Len() int
}

MessageQueue is the interface of the inbound message queue

type MessageValidator added in v0.12.3

type MessageValidator interface {
	// Validate validates the message and returns true if the message is to be retained and false if it needs to be dropped
	Validate(msg IncomingMessageScope) bool
}

MessageValidator validates the incoming message. Message validation happens in the network right before it is delivered to the network.

type Misbehavior added in v0.31.0

type Misbehavior string

Misbehavior is the type of malicious action concerning a message dissemination that can be reported by the engines. The misbehavior is used to penalize the misbehaving node at the protocol level concerning the messages that the current node has received from the misbehaving node.

func (Misbehavior) String added in v0.31.0

func (m Misbehavior) String() string

type MisbehaviorReport added in v0.31.0

type MisbehaviorReport interface {
	// OriginId returns the ID of the misbehaving node.
	OriginId() flow.Identifier

	// Reason returns the reason of the misbehavior.
	Reason() Misbehavior

	// Penalty returns the penalty value of the misbehavior.
	Penalty() float64
}

MisbehaviorReport abstracts the semantics of a misbehavior report. The misbehavior report is generated by the engine that detects a misbehavior on a delivered message to it. The engine crafts a misbehavior report and sends it to the networking layer to penalize the misbehaving node.

type MisbehaviorReportConsumer added in v0.32.0

type MisbehaviorReportConsumer interface {
	// ReportMisbehaviorOnChannel reports the misbehavior of a node on sending a message to the current node that appears
	// valid based on the networking layer but is considered invalid by the current node based on the Flow protocol.
	// The misbehavior report is sent to the current node's networking layer on the given channel to be processed.
	// Args:
	// - channel: The channel on which the misbehavior report is sent.
	// - report: The misbehavior report to be sent.
	// Returns:
	// none
	ReportMisbehaviorOnChannel(channel channels.Channel, report MisbehaviorReport)
}

MisbehaviorReportConsumer set of funcs used to handle MisbehaviorReport disseminated from misbehavior reporters.

type MisbehaviorReportManager added in v0.31.0

type MisbehaviorReportManager interface {
	component.Component
	// HandleMisbehaviorReport handles the misbehavior report that is sent by the engine.
	// The implementation of this function should penalize the misbehaving node and report the node to be
	// disallow-listed if the overall penalty of the misbehaving node drops below the disallow-listing threshold.
	// The implementation of this function should be thread-safe and non-blocking.
	HandleMisbehaviorReport(channels.Channel, MisbehaviorReport)
}

MisbehaviorReportManager abstracts the semantics of handling misbehavior reports. The misbehavior report manager is responsible for handling misbehavior reports that are sent by the engines. The misbehavior report manager is responsible for penalizing the misbehaving node and disallow-listing the node if the overall penalty of the misbehaving node drops below the disallow-listing threshold.

type MisbehaviorReporter added in v0.31.0

type MisbehaviorReporter interface {
	// ReportMisbehavior reports the misbehavior of a node on sending a message to the current node that appears valid
	// based on the networking layer but is considered invalid by the current node based on the Flow protocol.
	// The misbehavior is reported to the networking layer to penalize the misbehaving node.
	// Implementation must be thread-safe and non-blocking.
	ReportMisbehavior(MisbehaviorReport)
}

MisbehaviorReporter is an interface that is used to report misbehavior of a remote node. The misbehavior is reported to the networking layer to penalize the misbehaving node.

type NetworkingType added in v0.31.0

type NetworkingType uint8

NetworkingType is the type of the Flow networking layer. It is used to differentiate between the public (i.e., unstaked) and private (i.e., staked) networks.

const (
	// PrivateNetwork indicates that the staked private-side of the Flow blockchain that nodes can only join and leave
	// with a staking requirement.
	PrivateNetwork NetworkingType = iota + 1
	// PublicNetwork indicates that the unstaked public-side of the Flow blockchain that nodes can join and leave at will
	// with no staking requirement.
	PublicNetwork
)

func (NetworkingType) String added in v0.32.0

func (t NetworkingType) String() string

type OutgoingMessageScope added in v0.29.1

type OutgoingMessageScope interface {
	// TargetIds returns the target node IDs.
	TargetIds() flow.IdentifierList

	// Size returns the size of the message.
	Size() int

	// PayloadType returns the type of the payload to be sent.
	PayloadType() string

	// Topic returns the topic, i.e., channel-id/spork-id.
	Topic() channels.Topic

	// Proto returns the raw proto message sent on the wire.
	Proto() *message.Message
}

OutgoingMessageScope defines the interface for building outgoing message scopes, i.e., self-contained messages that are ready to be sent on the wire.

type PeerUnreachableError

type PeerUnreachableError struct {
	Err error
}

PeerUnreachableError is the error when submitting events to target fails due to the target peer is unreachable

func (PeerUnreachableError) Error

func (e PeerUnreachableError) Error() string

func (PeerUnreachableError) Unwrap

func (e PeerUnreachableError) Unwrap() error

Unwrap returns the wrapped error value

type PingInfoProvider added in v0.23.9

type PingInfoProvider interface {
	SoftwareVersion() string
	SealedBlockHeight() uint64
	HotstuffView() uint64
}

PingInfoProvider is the interface used by the PingService to respond to incoming PingRequest with a PingResponse populated with the necessary details

type PingService added in v0.23.9

type PingService interface {
	Ping(ctx context.Context, peerID peer.ID) (message.PingResponse, time.Duration, error)
}

type SubscriptionManager added in v0.12.3

type SubscriptionManager interface {
	// Register registers an engine on the channel into the subscription manager.
	Register(channel channels.Channel, engine MessageProcessor) error

	// Unregister removes the engine associated with a channel.
	Unregister(channel channels.Channel) error

	// GetEngine returns engine associated with a channel.
	GetEngine(channel channels.Channel) (MessageProcessor, error)

	// Channels returns all the channels registered in this subscription manager.
	Channels() channels.ChannelList
}

type Topology added in v0.12.3

type Topology interface {
	// Fanout receives IdentityList of entire network and constructs the fanout IdentityList of the node.
	// A node directly communicates with its fanout IdentityList on epidemic dissemination
	// of the messages (i.e., publish and multicast).
	//
	// Fanout is not concurrency safe. It is responsibility of caller to lock for it (if needed).
	Fanout(ids flow.IdentityList) flow.IdentityList
}

Topology provides a subset of nodes which a given node should directly connect in order to form a connected mesh for epidemic message dissemination (e.g., publisher and subscriber model).

type TransientError added in v0.29.0

type TransientError struct {
	Err error
}

TransientError represents an error returned from a network layer function call which may be interpreted as non-critical. In general, we desire that all expected error return values are enumerated in a function's documentation - any undocumented errors are considered fatal. However, 3rd party libraries don't always conform to this standard, including the networking libraries we use. This error type can be used to wrap these 3rd party errors on the boundary into flow-go, to explicitly mark them as non-critical.

func NewTransientErrorf added in v0.29.0

func NewTransientErrorf(msg string, args ...interface{}) TransientError

func (TransientError) Error added in v0.29.0

func (err TransientError) Error() string

func (TransientError) Unwrap added in v0.29.0

func (err TransientError) Unwrap() error

type Underlay added in v0.32.0

type Underlay interface {
	module.ReadyDoneAware
	DisallowListNotificationConsumer

	// Subscribe subscribes the network Underlay to a channel.
	// No errors are expected during normal operation.
	Subscribe(channel channels.Channel) error

	// Unsubscribe unsubscribes the network Underlay from a channel.
	// All errors returned from this function can be considered benign.
	Unsubscribe(channel channels.Channel) error

	// UpdateNodeAddresses fetches and updates the addresses of all the authorized participants
	// in the Flow protocol.
	UpdateNodeAddresses()
}

Underlay is one of the networking layer interfaces in Flow (i.e., EngineRegistry, ConduitAdapter, and Underlay). It represents the interface that networking layer offers to lower level networking components such as libp2p. It is responsible for subscribing to and unsubscribing from channels, as well as updating the addresses of all the authorized participants in the Flow protocol.

type Violation added in v0.32.0

type Violation struct {
	Identity *flow.Identity
	PeerID   string
	OriginID flow.Identifier
	MsgType  string
	Channel  channels.Channel
	Protocol message.ProtocolType
	Err      error
}

type ViolationsConsumer added in v0.32.0

type ViolationsConsumer interface {
	// OnUnAuthorizedSenderError logs an error for unauthorized sender error.
	OnUnAuthorizedSenderError(violation *Violation)

	// OnUnknownMsgTypeError logs an error for unknown message type error.
	OnUnknownMsgTypeError(violation *Violation)

	// OnInvalidMsgError logs an error for messages that contained payloads that could not
	// be unmarshalled into the message type denoted by message code byte.
	OnInvalidMsgError(violation *Violation)

	// OnSenderEjectedError logs an error for sender ejected error.
	OnSenderEjectedError(violation *Violation)

	// OnUnauthorizedUnicastOnChannel logs an error for messages unauthorized to be sent via unicast.
	OnUnauthorizedUnicastOnChannel(violation *Violation)

	// OnUnauthorizedPublishOnChannel logs an error for messages unauthorized to be sent via pubsub.
	OnUnauthorizedPublishOnChannel(violation *Violation)

	// OnUnexpectedError logs an error for unknown errors.
	OnUnexpectedError(violation *Violation)
}

ViolationsConsumer logs reported slashing violation errors and reports those violations as misbehavior's to the ALSP misbehavior report manager. Any errors encountered while reporting the misbehavior are considered irrecoverable and will result in a fatal level log.

type WriteCloseFlusher added in v0.23.0

type WriteCloseFlusher interface {
	io.WriteCloser
	Flush() error
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL