bvm

package
v1.22.22 Latest Latest
Warning

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

Go to latest
Published: Dec 21, 2025 License: BSD-3-Clause Imports: 28 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var VMID = ids.ID{'b', 'r', 'i', 'd', 'g', 'e', 'v', 'm', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

VMID is the unique identifier for BridgeVM (B-Chain)

View Source
var (
	Version = &version.Semantic{
		Major: 1,
		Minor: 0,
		Patch: 0,
	}
)

Functions

This section is empty.

Types

type Block

type Block struct {
	ParentID_      ids.ID           `json:"parentId"` // Field renamed to avoid method collision
	BlockHeight    uint64           `json:"height"`
	BlockTimestamp int64            `json:"timestamp"`
	BridgeRequests []*BridgeRequest `json:"bridgeRequests"`

	// MPC signatures for this block (NodeID -> signature bytes)
	MPCSignatures map[ids.NodeID][]byte `json:"mpcSignatures"`

	// Cached values
	ID_ ids.ID
	// contains filtered or unexported fields
}

Block represents a block in the Bridge chain

func (*Block) Accept

func (b *Block) Accept(ctx context.Context) error

Accept marks the block as accepted

func (*Block) Bytes

func (b *Block) Bytes() []byte

Bytes returns the block bytes

func (*Block) Height

func (b *Block) Height() uint64

Height returns the block height

func (*Block) ID

func (b *Block) ID() ids.ID

ID returns the block ID

func (*Block) Parent

func (b *Block) Parent() ids.ID

Parent returns the parent block (for block.Block interface compatibility)

func (*Block) ParentID added in v1.16.56

func (b *Block) ParentID() ids.ID

ParentID returns the parent block ID

func (*Block) Reject

func (b *Block) Reject(ctx context.Context) error

Reject marks the block as rejected

func (*Block) Status

func (b *Block) Status() uint8

Status returns the block's status

func (*Block) Timestamp added in v1.16.56

func (b *Block) Timestamp() time.Time

Timestamp returns the block timestamp

func (*Block) Verify

func (b *Block) Verify(ctx context.Context) error

Verify verifies the block

type BridgeConfig added in v1.16.56

type BridgeConfig struct {
	// MPC configuration for secure cross-chain operations
	MPCThreshold    int `json:"mpcThreshold"`    // t: Threshold (t+1 parties needed)
	MPCTotalParties int `json:"mpcTotalParties"` // n: Total number of MPC nodes

	// Bridge parameters
	MinConfirmations uint32 `json:"minConfirmations"` // Confirmations required before bridging
	BridgeFee        uint64 `json:"bridgeFee"`        // Fee in LUX for bridge operations

	// Supported chains
	SupportedChains []string `json:"supportedChains"` // Chain IDs that can be bridged

	// Security settings
	MaxBridgeAmount      uint64 `json:"maxBridgeAmount"`      // Maximum amount per bridge transaction
	DailyBridgeLimit     uint64 `json:"dailyBridgeLimit"`     // Daily limit for bridge operations
	RequireValidatorBond uint64 `json:"requireValidatorBond"` // 100M LUX bond required (slashable, NOT staked)

	// LP-333: Opt-in Signer Set Management
	MaxSigners     int     `json:"maxSigners"`     // Maximum signers before set is frozen (default: 100)
	ThresholdRatio float64 `json:"thresholdRatio"` // Threshold as ratio of signers (default: 0.67 = 2/3)
}

BridgeConfig contains VM configuration

type BridgeRegistry added in v1.16.56

type BridgeRegistry struct {
	Validators       map[ids.NodeID]*BridgeValidator
	CompletedBridges map[ids.ID]*CompletedBridge
	DailyVolume      map[string]uint64 // chainID -> volume
	// contains filtered or unexported fields
}

BridgeRegistry tracks bridge operations and validators

type BridgeRequest added in v1.16.56

type BridgeRequest struct {
	ID            ids.ID    `json:"id"`
	SourceChain   string    `json:"sourceChain"`
	DestChain     string    `json:"destChain"`
	Asset         ids.ID    `json:"asset"`
	Amount        uint64    `json:"amount"`
	Recipient     []byte    `json:"recipient"`
	SourceTxID    ids.ID    `json:"sourceTxId"`
	Confirmations uint32    `json:"confirmations"`
	Status        string    `json:"status"` // pending, signing, completed, failed
	MPCSignatures [][]byte  `json:"mpcSignatures"`
	CreatedAt     time.Time `json:"createdAt"`
}

BridgeRequest represents a cross-chain bridge request

type BridgeValidator added in v1.16.56

type BridgeValidator struct {
	NodeID       ids.NodeID
	StakeAmount  uint64
	MPCPublicKey []byte
	Active       bool
	TotalBridged uint64
	SuccessRate  float64
}

BridgeValidator represents a bridge validator node

type ChainClient added in v1.16.56

type ChainClient interface {
	GetTransaction(ctx context.Context, txID ids.ID) (interface{}, error)
	GetConfirmations(ctx context.Context, txID ids.ID) (uint32, error)
	SendTransaction(ctx context.Context, tx interface{}) (ids.ID, error)
	ValidateAddress(address []byte) error
}

ChainClient interface for interacting with different chains

type CompletedBridge added in v1.16.56

type CompletedBridge struct {
	RequestID    ids.ID
	SourceTxID   ids.ID
	DestTxID     ids.ID
	CompletedAt  time.Time
	MPCSignature []byte
}

CompletedBridge represents a completed bridge operation

type Factory

type Factory struct{}

Factory creates new BridgeVM instances

func (*Factory) New

func (f *Factory) New(logger log.Logger) (interface{}, error)

New returns a new instance of the BridgeVM

type Genesis added in v1.16.56

type Genesis struct {
	Timestamp int64 `json:"timestamp"`
}

Genesis represents the genesis state

type GetCurrentEpochArgs added in v1.16.56

type GetCurrentEpochArgs struct{}

GetCurrentEpochArgs are the arguments for bridge_getCurrentEpoch (empty)

type GetCurrentEpochReply added in v1.16.56

type GetCurrentEpochReply struct {
	Epoch        uint64 `json:"epoch"`
	TotalSigners int    `json:"totalSigners"`
	Threshold    int    `json:"threshold"`
	SetFrozen    bool   `json:"setFrozen"`
}

GetCurrentEpochReply is the reply for bridge_getCurrentEpoch

type GetSignerSetInfoArgs added in v1.16.56

type GetSignerSetInfoArgs struct{}

GetSignerSetInfoArgs are the arguments for bridge_getSignerSetInfo (empty)

type GetSignerSetInfoReply added in v1.16.56

type GetSignerSetInfoReply struct {
	TotalSigners   int               `json:"totalSigners"`
	Threshold      int               `json:"threshold"`
	MaxSigners     int               `json:"maxSigners"`
	CurrentEpoch   uint64            `json:"currentEpoch"`
	SetFrozen      bool              `json:"setFrozen"`
	RemainingSlots int               `json:"remainingSlots"`
	WaitlistSize   int               `json:"waitlistSize"`
	Signers        []SignerInfoReply `json:"signers"`
	PublicKey      string            `json:"publicKey,omitempty"`
}

GetSignerSetInfoReply is the reply for bridge_getSignerSetInfo

type GetWaitlistArgs added in v1.16.56

type GetWaitlistArgs struct{}

GetWaitlistArgs are the arguments for bridge_getWaitlist (empty)

type GetWaitlistReply added in v1.16.56

type GetWaitlistReply struct {
	WaitlistSize int      `json:"waitlistSize"`
	NodeIDs      []string `json:"nodeIds"`
}

GetWaitlistReply is the reply for bridge_getWaitlist

type HasSignerArgs added in v1.16.56

type HasSignerArgs struct {
	NodeID string `json:"nodeId"`
}

HasSignerArgs are the arguments for bridge_hasSigner

type HasSignerReply added in v1.16.56

type HasSignerReply struct {
	IsSigner bool `json:"isSigner"`
}

HasSignerReply is the reply for bridge_hasSigner

type RegisterValidatorArgs added in v1.16.56

type RegisterValidatorArgs struct {
	NodeID     string `json:"nodeId"`
	BondAmount string `json:"bondAmount,omitempty"` // 100M LUX bond (slashable)
	MPCPubKey  string `json:"mpcPubKey,omitempty"`
}

RegisterValidatorArgs are the arguments for bridge_registerValidator

type RegisterValidatorInput added in v1.16.56

type RegisterValidatorInput struct {
	NodeID     string `json:"nodeId"`
	BondAmount string `json:"bondAmount,omitempty"` // 100M LUX bond (slashable)
	MPCPubKey  string `json:"mpcPubKey,omitempty"`
}

RegisterValidatorInput is the input for registering as a bridge signer

type RegisterValidatorReply added in v1.16.56

type RegisterValidatorReply struct {
	Success        bool   `json:"success"`
	NodeID         string `json:"nodeId"`
	Registered     bool   `json:"registered"`
	Waitlisted     bool   `json:"waitlisted"`
	SignerIndex    int    `json:"signerIndex"`
	WaitlistIndex  int    `json:"waitlistIndex,omitempty"`
	TotalSigners   int    `json:"totalSigners"`
	Threshold      int    `json:"threshold"`
	ReshareNeeded  bool   `json:"reshareNeeded"`
	CurrentEpoch   uint64 `json:"currentEpoch"`
	SetFrozen      bool   `json:"setFrozen"`
	RemainingSlots int    `json:"remainingSlots"`
	Message        string `json:"message"`
}

RegisterValidatorReply is the reply for bridge_registerValidator

type RegisterValidatorResult added in v1.16.56

type RegisterValidatorResult struct {
	Success        bool   `json:"success"`
	NodeID         string `json:"nodeId"`
	Registered     bool   `json:"registered"`
	Waitlisted     bool   `json:"waitlisted"`
	SignerIndex    int    `json:"signerIndex"`
	WaitlistIndex  int    `json:"waitlistIndex,omitempty"`
	TotalSigners   int    `json:"totalSigners"`
	Threshold      int    `json:"threshold"`
	ReshareNeeded  bool   `json:"reshareNeeded"` // Always false for opt-in (LP-333)
	CurrentEpoch   uint64 `json:"currentEpoch"`
	SetFrozen      bool   `json:"setFrozen"`
	RemainingSlots int    `json:"remainingSlots"`
	Message        string `json:"message"`
}

RegisterValidatorResult is the result of registering as a bridge signer

type ReplaceSignerArgs added in v1.16.56

type ReplaceSignerArgs struct {
	NodeID            string `json:"nodeId"`            // Signer to remove
	ReplacementNodeID string `json:"replacementNodeId"` // Explicit replacement (optional, uses waitlist if empty)
}

ReplaceSignerArgs are the arguments for bridge_replaceSigner

type ReplaceSignerReply added in v1.16.56

type ReplaceSignerReply struct {
	Success           bool   `json:"success"`
	RemovedNodeID     string `json:"removedNodeId,omitempty"`
	ReplacementNodeID string `json:"replacementNodeId,omitempty"`
	ReshareSession    string `json:"reshareSession,omitempty"`
	NewEpoch          uint64 `json:"newEpoch"`
	ActiveSigners     int    `json:"activeSigners"`
	Threshold         int    `json:"threshold"`
	Message           string `json:"message"`
}

ReplaceSignerReply is the reply for bridge_replaceSigner

type Service added in v1.16.56

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

Service provides JSON-RPC endpoints for BridgeVM LP-333 signer management

func NewService added in v1.16.56

func NewService(vm *VM) *Service

NewService returns a new Service instance

func (*Service) GetCurrentEpoch added in v1.16.56

func (s *Service) GetCurrentEpoch(_ *http.Request, _ *GetCurrentEpochArgs, reply *GetCurrentEpochReply) error

GetCurrentEpoch returns the current epoch (incremented only on reshare)

func (*Service) GetSignerSetInfo added in v1.16.56

func (s *Service) GetSignerSetInfo(_ *http.Request, _ *GetSignerSetInfoArgs, reply *GetSignerSetInfoReply) error

GetSignerSetInfo returns information about the current signer set (LP-333)

func (*Service) GetWaitlist added in v1.16.56

func (s *Service) GetWaitlist(_ *http.Request, _ *GetWaitlistArgs, reply *GetWaitlistReply) error

GetWaitlist returns the current waitlist of validators waiting for signer slots

func (*Service) HasSigner added in v1.16.56

func (s *Service) HasSigner(_ *http.Request, args *HasSignerArgs, reply *HasSignerReply) error

HasSigner checks if a node is in the active signer set

func (*Service) RegisterValidator added in v1.16.56

func (s *Service) RegisterValidator(_ *http.Request, args *RegisterValidatorArgs, reply *RegisterValidatorReply) error

RegisterValidator registers a validator as a bridge signer (LP-333 opt-in model) First 100 validators are accepted directly without reshare. After 100 signers, new validators go to waitlist.

func (*Service) ReplaceSigner added in v1.16.56

func (s *Service) ReplaceSigner(_ *http.Request, args *ReplaceSignerArgs, reply *ReplaceSignerReply) error

ReplaceSigner removes a failed signer and triggers reshare (LP-333) This is the ONLY operation that triggers a reshare.

func (*Service) SlashSigner added in v1.16.56

func (s *Service) SlashSigner(_ *http.Request, args *SlashSignerArgs, reply *SlashSignerReply) error

SlashSigner slashes a misbehaving bridge signer's bond The bond is NOT stake - it's a slashable deposit that can be partially or fully seized

type SignerInfo added in v1.16.56

type SignerInfo struct {
	NodeID     ids.NodeID `json:"nodeId"`
	PartyID    party.ID   `json:"partyId"`
	BondAmount uint64     `json:"bondAmount"` // 100M LUX bond (slashable, NOT staked)
	MPCPubKey  []byte     `json:"mpcPubKey"`
	Active     bool       `json:"active"`
	JoinedAt   time.Time  `json:"joinedAt"`
	SlotIndex  int        `json:"slotIndex"`
	Slashed    bool       `json:"slashed"`    // True if this signer has been slashed
	SlashCount int        `json:"slashCount"` // Number of times slashed
}

SignerInfo contains information about a signer in the set

type SignerInfoReply added in v1.16.56

type SignerInfoReply struct {
	NodeID     string `json:"nodeId"`
	PartyID    string `json:"partyId"`
	BondAmount uint64 `json:"bondAmount"` // 100M LUX bond (slashable)
	Active     bool   `json:"active"`
	JoinedAt   string `json:"joinedAt"`
	SlotIndex  int    `json:"slotIndex"`
	Slashed    bool   `json:"slashed"`
	SlashCount int    `json:"slashCount"`
}

SignerInfoReply contains signer information for RPC replies

type SignerReplacementResult added in v1.16.56

type SignerReplacementResult struct {
	Success           bool   `json:"success"`
	RemovedNodeID     string `json:"removedNodeId,omitempty"`
	ReplacementNodeID string `json:"replacementNodeId,omitempty"`
	ReshareSession    string `json:"reshareSession,omitempty"`
	NewEpoch          uint64 `json:"newEpoch"`
	ActiveSigners     int    `json:"activeSigners"`
	Threshold         int    `json:"threshold"`
	Message           string `json:"message"`
}

SignerReplacementResult is the result of replacing a failed signer

type SignerSet added in v1.16.56

type SignerSet struct {
	Signers      []*SignerInfo `json:"signers"`      // Active signers (max 100)
	Waitlist     []ids.NodeID  `json:"waitlist"`     // Validators waiting for a slot
	CurrentEpoch uint64        `json:"currentEpoch"` // Increments ONLY on reshare (slot replacement)
	SetFrozen    bool          `json:"setFrozen"`    // True when len(Signers) >= MaxSigners
	ThresholdT   int           `json:"thresholdT"`   // Current t value (T+1 required to sign)
	PublicKey    []byte        `json:"publicKey"`    // Combined threshold public key
}

SignerSet tracks the current MPC signer set (LP-333) First 100 validators opt-in without reshare. Reshare ONLY on slot replacement.

type SignerSetInfo added in v1.16.56

type SignerSetInfo struct {
	TotalSigners   int           `json:"totalSigners"`
	Threshold      int           `json:"threshold"`
	MaxSigners     int           `json:"maxSigners"`
	CurrentEpoch   uint64        `json:"currentEpoch"`
	SetFrozen      bool          `json:"setFrozen"`
	RemainingSlots int           `json:"remainingSlots"`
	WaitlistSize   int           `json:"waitlistSize"`
	Signers        []*SignerInfo `json:"signers"`
	PublicKey      string        `json:"publicKey,omitempty"`
}

SignerSetInfo is the result of getting signer set information

type SlashSignerArgs added in v1.16.56

type SlashSignerArgs struct {
	NodeID       string `json:"nodeId"`
	Reason       string `json:"reason"`
	SlashPercent int    `json:"slashPercent"` // Percentage of bond to slash (1-100)
	Evidence     string `json:"evidence"`     // Hex-encoded proof of misbehavior
}

SlashSignerArgs are the arguments for bridge_slashSigner

type SlashSignerInput added in v1.16.56

type SlashSignerInput struct {
	NodeID       ids.NodeID `json:"nodeId"`
	Reason       string     `json:"reason"`
	SlashPercent int        `json:"slashPercent"` // Percentage of bond to slash (1-100)
	Evidence     []byte     `json:"evidence"`     // Proof of misbehavior
}

SlashSignerInput is the input for slashing a bridge signer

type SlashSignerReply added in v1.16.56

type SlashSignerReply struct {
	Success         bool   `json:"success"`
	NodeID          string `json:"nodeId"`
	SlashedAmount   uint64 `json:"slashedAmount"`
	RemainingBond   uint64 `json:"remainingBond"`
	TotalSlashCount int    `json:"totalSlashCount"`
	RemovedFromSet  bool   `json:"removedFromSet"`
	Message         string `json:"message"`
}

SlashSignerReply is the reply for bridge_slashSigner

type SlashSignerResult added in v1.16.56

type SlashSignerResult struct {
	Success         bool   `json:"success"`
	NodeID          string `json:"nodeId"`
	SlashedAmount   uint64 `json:"slashedAmount"`
	RemainingBond   uint64 `json:"remainingBond"`
	TotalSlashCount int    `json:"totalSlashCount"`
	RemovedFromSet  bool   `json:"removedFromSet"`
	Message         string `json:"message"`
}

SlashSignerResult is the result of slashing a bridge signer

type VM

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

VM implements the Bridge VM for cross-chain interoperability

func (*VM) AppGossip

func (vm *VM) AppGossip(ctx context.Context, nodeID ids.NodeID, msg []byte) error

AppGossip implements the common.VM interface

func (*VM) AppRequest

func (vm *VM) AppRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, deadline time.Time, request []byte) error

AppRequest implements the common.VM interface

func (*VM) AppRequestFailed

func (vm *VM) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *warp.Error) error

AppRequestFailed implements the common.VM interface

func (*VM) AppResponse

func (vm *VM) AppResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error

AppResponse implements the common.VM interface

func (*VM) BuildBlock

func (vm *VM) BuildBlock(ctx context.Context) (block.Block, error)

BuildBlock implements the block.ChainVM interface

func (*VM) Connected

func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, nodeVersion interface{}) error

Connected implements the common.VM interface

func (*VM) CreateHandlers

func (vm *VM) CreateHandlers(ctx context.Context) (map[string]http.Handler, error)

CreateHandlers implements the common.VM interface

func (*VM) CreateRPCHandlers added in v1.16.56

func (vm *VM) CreateRPCHandlers() (map[string]http.Handler, error)

CreateRPCHandlers creates HTTP handlers for JSON-RPC endpoints

func (*VM) CreateStaticHandlers

func (vm *VM) CreateStaticHandlers(ctx context.Context) (map[string]http.Handler, error)

CreateStaticHandlers implements the common.VM interface

func (*VM) CrossChainAppRequest

func (vm *VM) CrossChainAppRequest(ctx context.Context, chainID ids.ID, requestID uint32, deadline time.Time, request []byte) error

CrossChainAppRequest implements the common.VM interface

func (*VM) CrossChainAppRequestFailed

func (vm *VM) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32, appErr *warp.Error) error

CrossChainAppRequestFailed implements the common.VM interface

func (*VM) CrossChainAppResponse

func (vm *VM) CrossChainAppResponse(ctx context.Context, chainID ids.ID, requestID uint32, response []byte) error

CrossChainAppResponse implements the common.VM interface

func (*VM) Disconnected

func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error

Disconnected implements the common.VM interface

func (*VM) GetBlock

func (vm *VM) GetBlock(ctx context.Context, id ids.ID) (block.Block, error)

GetBlock implements the block.ChainVM interface

func (*VM) GetBlockIDAtHeight

func (vm *VM) GetBlockIDAtHeight(ctx context.Context, height uint64) (ids.ID, error)

GetBlockIDAtHeight implements the consensusman.HeightIndexedChainVM interface

func (*VM) GetSignerSetInfo added in v1.16.56

func (vm *VM) GetSignerSetInfo() *SignerSetInfo

GetSignerSetInfo returns information about the current signer set

func (*VM) HasSigner added in v1.16.56

func (vm *VM) HasSigner(nodeID ids.NodeID) bool

HasSigner checks if a node ID is in the active signer set

func (*VM) HealthCheck

func (vm *VM) HealthCheck(ctx context.Context) (interface{}, error)

HealthCheck implements the common.VM interface

func (*VM) Initialize

func (vm *VM) Initialize(
	ctx context.Context,
	chainCtx interface{},
	db interface{},
	genesisBytes []byte,
	upgradeBytes []byte,
	configBytes []byte,
	msgChan interface{},
	fxs []interface{},
	appSender interface{},
) error

Initialize implements the block.ChainVM interface

func (*VM) LastAccepted

func (vm *VM) LastAccepted(ctx context.Context) (ids.ID, error)

LastAccepted implements the chain.ChainVM interface

func (*VM) NewHTTPHandler added in v1.16.56

func (vm *VM) NewHTTPHandler(ctx context.Context) (interface{}, error)

NewHTTPHandler returns HTTP handlers for the VM

func (*VM) ParseBlock

func (vm *VM) ParseBlock(ctx context.Context, bytes []byte) (block.Block, error)

ParseBlock implements the block.ChainVM interface

func (*VM) RegisterService added in v1.16.56

func (vm *VM) RegisterService(server *rpc.Server) error

RegisterService registers the BridgeVM RPC handlers

func (*VM) RegisterValidator added in v1.16.56

func (vm *VM) RegisterValidator(input *RegisterValidatorInput) (*RegisterValidatorResult, error)

RegisterValidator registers a new validator as a bridge signer (opt-in model) LP-333: First 100 validators are accepted directly - NO reshare on join. After 100 signers, new validators go to waitlist until a slot opens.

func (*VM) RemoveSigner added in v1.16.56

func (vm *VM) RemoveSigner(nodeID ids.NodeID, replacementNodeID *ids.NodeID) (*SignerReplacementResult, error)

RemoveSigner removes a failed/stopped signer and triggers replacement LP-333: This is the ONLY operation that triggers a reshare. Epoch increments only when a signer is replaced.

func (*VM) SetPreference

func (vm *VM) SetPreference(ctx context.Context, id ids.ID) error

SetPreference implements the chain.ChainVM interface

func (*VM) SetState

func (vm *VM) SetState(ctx context.Context, state uint32) error

SetState implements the common.VM interface

func (*VM) Shutdown

func (vm *VM) Shutdown(ctx context.Context) error

Shutdown implements the common.VM interface

func (*VM) SlashSigner added in v1.16.56

func (vm *VM) SlashSigner(input *SlashSignerInput) (*SlashSignerResult, error)

SlashSigner slashes a misbehaving bridge signer's bond The bond is NOT stake - it's a slashable deposit that can be partially or fully seized

func (*VM) Version

func (vm *VM) Version(ctx context.Context) (string, error)

Version implements the common.VM interface

func (*VM) WaitForEvent added in v1.16.56

func (vm *VM) WaitForEvent(ctx context.Context) (interface{}, error)

WaitForEvent blocks until an event occurs that should trigger block building

Jump to

Keyboard shortcuts

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