wtdb

package
v0.12.2 Latest Latest
Warning

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

Go to latest
Published: Mar 18, 2021 License: MIT Imports: 23 Imported by: 0

Documentation

Index

Constants

View Source
const SessionIDSize = 33

SessionIDSize is 33-bytes; it is a serialized, compressed public key.

Variables

View Source
var (

	// ErrTowerNotFound signals that the target tower was not found in the
	// database.
	ErrTowerNotFound = errors.New("tower not found")

	// ErrTowerUnackedUpdates is an error returned when we attempt to mark a
	// tower's sessions as inactive, but one of its sessions has unacked
	// updates.
	ErrTowerUnackedUpdates = errors.New("tower has unacked updates")

	// ErrCorruptClientSession signals that the client session's on-disk
	// structure deviates from what is expected.
	ErrCorruptClientSession = errors.New("client session corrupted")

	// ErrClientSessionAlreadyExists signals an attempt to reinsert a client
	// session that has already been created.
	ErrClientSessionAlreadyExists = errors.New(
		"client session already exists",
	)

	// ErrChannelAlreadyRegistered signals a duplicate attempt to register a
	// channel with the client database.
	ErrChannelAlreadyRegistered = errors.New("channel already registered")

	// ErrChannelNotRegistered signals a channel has not yet been registered
	// in the client database.
	ErrChannelNotRegistered = errors.New("channel not registered")

	// ErrClientSessionNotFound signals that the requested client session
	// was not found in the database.
	ErrClientSessionNotFound = errors.New("client session not found")

	// ErrUpdateAlreadyCommitted signals that the chosen sequence number has
	// already been committed to an update with a different breach hint.
	ErrUpdateAlreadyCommitted = errors.New("update already committed")

	// ErrCommitUnorderedUpdate signals the client tried to commit a
	// sequence number other than the next unallocated sequence number.
	ErrCommitUnorderedUpdate = errors.New("update seqnum not monotonic")

	// ErrCommittedUpdateNotFound signals that the tower tried to ACK a
	// sequence number that has not yet been allocated by the client.
	ErrCommittedUpdateNotFound = errors.New("committed update not found")

	// ErrUnallocatedLastApplied signals that the tower tried to provide a
	// LastApplied value greater than any allocated sequence number.
	ErrUnallocatedLastApplied = errors.New("tower echoed last appiled " +
		"greater than allocated seqnum")

	// ErrNoReservedKeyIndex signals that a client session could not be
	// created because no session key index was reserved.
	ErrNoReservedKeyIndex = errors.New("key index not reserved")

	// ErrIncorrectKeyIndex signals that the client session could not be
	// created because session key index differs from the reserved key
	// index.
	ErrIncorrectKeyIndex = errors.New("incorrect key index")

	// ErrLastTowerAddr is an error returned when the last address of a
	// watchtower is attempted to be removed.
	ErrLastTowerAddr = errors.New("cannot remove last tower address")
)
View Source
var (

	// ErrUninitializedDB signals that top-level buckets for the database
	// have not been initialized.
	ErrUninitializedDB = errors.New("db not initialized")

	// ErrNoDBVersion signals that the database contains no version info.
	ErrNoDBVersion = errors.New("db has no version")
)
View Source
var (
	// ErrSessionNotFound is returned when querying by session id for a
	// session that does not exist.
	ErrSessionNotFound = errors.New("session not found in db")

	// ErrSessionAlreadyExists signals that a session creation failed
	// because a session with the same session id already exists.
	ErrSessionAlreadyExists = errors.New("session already exists")

	// ErrUpdateOutOfOrder is returned when the sequence number is not equal
	// to the server's LastApplied+1.
	ErrUpdateOutOfOrder = errors.New("update sequence number is not " +
		"sequential")

	// ErrLastAppliedReversion is returned when the client echos a
	// last-applied value that is less than it claimed in a prior update.
	ErrLastAppliedReversion = errors.New("update last applied must be " +
		"non-decreasing")

	// ErrSeqNumAlreadyApplied is returned when the client sends a sequence
	// number for which they already claim to have an ACK.
	ErrSeqNumAlreadyApplied = errors.New("update sequence number has " +
		"already been applied")

	// ErrSessionConsumed is returned if the client tries to send a sequence
	// number larger than the session's max number of updates.
	ErrSessionConsumed = errors.New("all session updates have been " +
		"consumed")
)
View Source
var (

	// ErrNoSessionHintIndex signals that an active session does not have an
	// initialized index for tracking its own state updates.
	ErrNoSessionHintIndex = errors.New("session hint index missing")

	// ErrInvalidBlobSize indicates that the encrypted blob provided by the
	// client is not valid according to the blob type of the session.
	ErrInvalidBlobSize = errors.New("invalid blob size")
)

Functions

func DisableLog

func DisableLog()

DisableLog disables all library log output. Logging output is disabled by default until UseLogger is called.

func ReadElement

func ReadElement(r io.Reader, element interface{}) error

ReadElement deserializes a single element from the provided io.Reader.

func ReadElements

func ReadElements(r io.Reader, elements ...interface{}) error

ReadElements deserializes the provided io.Reader into a variadic list of target elements.

func UseLogger

func UseLogger(logger btclog.Logger)

UseLogger uses a specified Logger to output package logging info. This should be used in preference to SetLogWriter if the caller is also using btclog.

func WriteElement

func WriteElement(w io.Writer, element interface{}) error

WriteElement serializes a single element into the provided io.Writer.

func WriteElements

func WriteElements(w io.Writer, elements ...interface{}) error

WriteElements serializes a variadic list of elements into the given io.Writer.

Types

type BackupID

type BackupID struct {
	// ChanID is the channel id of the revoked commitment.
	ChanID lnwire.ChannelID

	// CommitHeight is the commitment height of the revoked commitment.
	CommitHeight uint64
}

BackupID identifies a particular revoked, remote commitment by channel id and commitment height.

func (*BackupID) Decode

func (b *BackupID) Decode(r io.Reader) error

Decode reads a BackupID from the passed io.Reader.

func (*BackupID) Encode

func (b *BackupID) Encode(w io.Writer) error

Encode writes the BackupID from the passed io.Writer.

func (BackupID) String

func (b BackupID) String() string

String returns a human-readable encoding of a BackupID.

type CSessionStatus

type CSessionStatus uint8

CSessionStatus is a bit-field representing the possible statuses of ClientSessions.

const (
	// CSessionActive indicates that the ClientSession is active and can be
	// used for backups.
	CSessionActive CSessionStatus = 0

	// CSessionInactive indicates that the ClientSession is inactive and
	// cannot be used for backups.
	CSessionInactive CSessionStatus = 1
)

type ChannelSummaries

type ChannelSummaries map[lnwire.ChannelID]ClientChanSummary

ChannelSummaries is a map for a given channel id to it's ClientChanSummary.

type ClientChanSummary

type ClientChanSummary struct {
	// SweepPkScript is the pkscript to which all justice transactions will
	// deposit recovered funds for this particular channel.
	SweepPkScript []byte
}

ClientChanSummary tracks channel-specific information. A new ClientChanSummary is inserted in the database the first time the client encounters a particular channel.

func (*ClientChanSummary) Decode

func (s *ClientChanSummary) Decode(r io.Reader) error

Decode reads a ClientChanSummary form the passed io.Reader.

func (*ClientChanSummary) Encode

func (s *ClientChanSummary) Encode(w io.Writer) error

Encode writes the ClientChanSummary to the passed io.Writer.

type ClientDB

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

ClientDB is single database providing a persistent storage engine for the wtclient.

func OpenClientDB

func OpenClientDB(dbPath string, dbTimeout time.Duration) (*ClientDB, error)

OpenClientDB opens the client database given the path to the database's directory. If no such database exists, this method will initialize a fresh one using the latest version number and bucket structure. If a database exists but has a lower version number than the current version, any necessary migrations will be applied before returning. Any attempt to open a database with a version number higher that the latest version will fail to prevent accidental reversion.

func (*ClientDB) AckUpdate

func (c *ClientDB) AckUpdate(id *SessionID, seqNum uint16,
	lastApplied uint16) error

AckUpdate persists an acknowledgment for a given (session, seqnum) pair. This removes the update from the set of committed updates, and validates the lastApplied value returned from the tower.

func (*ClientDB) Close

func (c *ClientDB) Close() error

Close closes the underlying database.

func (*ClientDB) CommitUpdate

func (c *ClientDB) CommitUpdate(id *SessionID,
	update *CommittedUpdate) (uint16, error)

CommitUpdate persists the CommittedUpdate provided in the slot for (session, seqNum). This allows the client to retransmit this update on startup.

func (*ClientDB) CreateClientSession

func (c *ClientDB) CreateClientSession(session *ClientSession) error

CreateClientSession records a newly negotiated client session in the set of active sessions. The session can be identified by its SessionID.

func (*ClientDB) CreateTower

func (c *ClientDB) CreateTower(lnAddr *lnwire.NetAddress) (*Tower, error)

CreateTower initialize an address record used to communicate with a watchtower. Each Tower is assigned a unique ID, that is used to amortize storage costs of the public key when used by multiple sessions. If the tower already exists, the address is appended to the list of all addresses used to that tower previously and its corresponding sessions are marked as active.

func (*ClientDB) FetchChanSummaries

func (c *ClientDB) FetchChanSummaries() (ChannelSummaries, error)

FetchChanSummaries loads a mapping from all registered channels to their channel summaries.

func (*ClientDB) ListClientSessions

func (c *ClientDB) ListClientSessions(id *TowerID) (map[SessionID]*ClientSession, error)

ListClientSessions returns the set of all client sessions known to the db. An optional tower ID can be used to filter out any client sessions in the response that do not correspond to this tower.

func (*ClientDB) ListTowers

func (c *ClientDB) ListTowers() ([]*Tower, error)

ListTowers retrieves the list of towers available within the database.

func (*ClientDB) LoadTower

func (c *ClientDB) LoadTower(pubKey *btcec.PublicKey) (*Tower, error)

LoadTower retrieves a tower by its public key.

func (*ClientDB) LoadTowerByID

func (c *ClientDB) LoadTowerByID(towerID TowerID) (*Tower, error)

LoadTowerByID retrieves a tower by its tower ID.

func (*ClientDB) MarkBackupIneligible

func (c *ClientDB) MarkBackupIneligible(chanID lnwire.ChannelID,
	commitHeight uint64) error

MarkBackupIneligible records that the state identified by the (channel id, commit height) tuple was ineligible for being backed up under the current policy. This state can be retried later under a different policy.

func (*ClientDB) NextSessionKeyIndex

func (c *ClientDB) NextSessionKeyIndex(towerID TowerID,
	blobType blob.Type) (uint32, error)

NextSessionKeyIndex reserves a new session key derivation index for a particular tower id. The index is reserved for that tower until CreateClientSession is invoked for that tower and index, at which point a new index for that tower can be reserved. Multiple calls to this method before CreateClientSession is invoked should return the same index.

func (*ClientDB) RegisterChannel

func (c *ClientDB) RegisterChannel(chanID lnwire.ChannelID,
	sweepPkScript []byte) error

RegisterChannel registers a channel for use within the client database. For now, all that is stored in the channel summary is the sweep pkscript that we'd like any tower sweeps to pay into. In the future, this will be extended to contain more info to allow the client efficiently request historical states to be backed up under the client's active policy.

func (*ClientDB) RemoveTower

func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error

RemoveTower modifies a tower's record within the database. If an address is provided, then _only_ the address record should be removed from the tower's persisted state. Otherwise, we'll attempt to mark the tower as inactive by marking all of its sessions inactive. If any of its sessions has unacked updates, then ErrTowerUnackedUpdates is returned. If the tower doesn't have any sessions at all, it'll be completely removed from the database.

NOTE: An error is not returned if the tower doesn't exist.

func (*ClientDB) Version

func (c *ClientDB) Version() (uint32, error)

Version returns the database's current version number.

NOTE: Part of the versionedDB interface.

type ClientSession

type ClientSession struct {
	// ID is the client's public key used when authenticating with the
	// tower.
	//
	// NOTE: This value is not serialized with the body of the struct, it
	// should be set and recovered as the ClientSession's key.
	ID SessionID

	ClientSessionBody

	// CommittedUpdates is a sorted list of unacked updates. These updates
	// can be resent after a restart if the updates failed to send or
	// receive an acknowledgment.
	//
	// NOTE: This list is serialized in it's own bucket, separate from the
	// body of the ClientSession. The representation on disk is a key value
	// map from sequence number to CommittedUpdateBody to allow efficient
	// insertion and retrieval.
	CommittedUpdates []CommittedUpdate

	// AckedUpdates is a map from sequence number to backup id to record
	// which revoked states were uploaded via this session.
	//
	// NOTE: This map is serialized in it's own bucket, separate from the
	// body of the ClientSession.
	AckedUpdates map[uint16]BackupID

	// Tower holds the pubkey and address of the watchtower.
	//
	// NOTE: This value is not serialized. It is recovered by looking up the
	// tower with TowerID.
	Tower *Tower

	// SessionKeyECDH is the ECDH capable wrapper of the ephemeral secret
	// key used to connect to the watchtower.
	//
	// NOTE: This value is not serialized. It is derived using the KeyIndex
	// on startup to avoid storing private keys on disk.
	SessionKeyECDH keychain.SingleKeyECDH
}

ClientSession encapsulates a SessionInfo returned from a successful session negotiation, and also records the tower and ephemeral secret used for communicating with the tower.

type ClientSessionBody

type ClientSessionBody struct {
	// SeqNum is the next unallocated sequence number that can be sent to
	// the tower.
	SeqNum uint16

	// TowerLastApplied the last last-applied the tower has echoed back.
	TowerLastApplied uint16

	// TowerID is the unique, db-assigned identifier that references the
	// Tower with which the session is negotiated.
	TowerID TowerID

	// KeyIndex is the index of key locator used to derive the client's
	// session key so that it can authenticate with the tower to update its
	// session. In order to rederive the private key, the key locator should
	// use the keychain.KeyFamilyTowerSession key family.
	KeyIndex uint32

	// Policy holds the negotiated session parameters.
	Policy wtpolicy.Policy

	// Status indicates the current state of the ClientSession.
	Status CSessionStatus

	// RewardPkScript is the pkscript that the tower's reward will be
	// deposited to if a sweep transaction confirms and the sessions
	// specifies a reward output.
	RewardPkScript []byte
}

ClientSessionBody represents the primary components of a ClientSession that are serialized together within the database. The CommittedUpdates and AckedUpdates are serialized in buckets separate from the body.

func (*ClientSessionBody) Decode

func (s *ClientSessionBody) Decode(r io.Reader) error

Decode reads a ClientSessionBody from the passed io.Reader.

func (*ClientSessionBody) Encode

func (s *ClientSessionBody) Encode(w io.Writer) error

Encode writes a ClientSessionBody to the passed io.Writer.

type CommittedUpdate

type CommittedUpdate struct {
	// SeqNum is the unique sequence number allocated by the session to this
	// update.
	SeqNum uint16

	CommittedUpdateBody
}

CommittedUpdate holds a state update sent by a client along with its allocated sequence number and the exact remote commitment the encrypted justice transaction can rectify.

type CommittedUpdateBody

type CommittedUpdateBody struct {
	// BackupID identifies the breached commitment that the encrypted blob
	// can spend from.
	BackupID BackupID

	// Hint is the 16-byte prefix of the revoked commitment transaction ID.
	Hint blob.BreachHint

	// EncryptedBlob is a ciphertext containing the sweep information for
	// exacting justice if the commitment transaction matching the breach
	// hint is broadcast.
	EncryptedBlob []byte
}

CommittedUpdateBody represents the primary components of a CommittedUpdate. On disk, this is stored under the sequence number, which acts as its key.

func (*CommittedUpdateBody) Decode

func (u *CommittedUpdateBody) Decode(r io.Reader) error

Decode reads a CommittedUpdateBody from the passed io.Reader.

func (*CommittedUpdateBody) Encode

func (u *CommittedUpdateBody) Encode(w io.Writer) error

Encode writes the CommittedUpdateBody to the passed io.Writer.

type Match

type Match struct {
	// ID is the session id of the client who uploaded the state update.
	ID SessionID

	// SeqNum is the session sequence number occupied by the client's state
	// update. Together with ID, this allows the tower to derive the
	// appropriate nonce for decryption.
	SeqNum uint16

	// Hint is the breach hint that triggered the match.
	Hint blob.BreachHint

	// EncryptedBlob is the encrypted payload containing the justice kit
	// uploaded by the client.
	EncryptedBlob []byte

	// SessionInfo is the contract negotiated between tower and client, that
	// provides input parameters such as fee rate, reward rate, and reward
	// address when attempting to reconstruct the justice transaction.
	SessionInfo *SessionInfo
}

Match is returned in response to a database query for a breach hints contained in a particular block. The match encapsulates all data required to properly decrypt a client's encrypted blob, and pursue action on behalf of the victim by reconstructing the justice transaction and broadcasting it to the network.

NOTE: It is possible for a match to cause a false positive, since they are matched on a prefix of the txid. In such an event, the likely behavior is that the payload will fail to decrypt.

type SessionID

type SessionID [SessionIDSize]byte

SessionID is created from the remote public key of a client, and serves as a unique identifier and authentication for sending state updates.

func NewSessionIDFromPubKey

func NewSessionIDFromPubKey(pubKey *btcec.PublicKey) SessionID

NewSessionIDFromPubKey creates a new SessionID from a public key.

func (SessionID) String

func (s SessionID) String() string

String returns a hex encoding of the session id.

type SessionInfo

type SessionInfo struct {
	// ID is the remote public key of the watchtower client.
	ID SessionID

	// Policy holds the negotiated session parameters.
	Policy wtpolicy.Policy

	// LastApplied the sequence number of the last successful state update.
	LastApplied uint16

	// ClientLastApplied the last last-applied the client has echoed back.
	ClientLastApplied uint16

	// RewardAddress the address that the tower's reward will be deposited
	// to if a sweep transaction confirms.
	RewardAddress []byte
}

SessionInfo holds the negotiated session parameters for single session id, and handles the acceptance and validation of state updates sent by the client.

func (*SessionInfo) AcceptUpdateSequence

func (s *SessionInfo) AcceptUpdateSequence(seqNum, lastApplied uint16) error

AcceptUpdateSequence validates that a state update's sequence number and last applied are valid given our past history with the client. These checks ensure that clients are properly in sync and following the update protocol properly. If validation is successful, the receiver's LastApplied and ClientLastApplied are updated with the latest values presented by the client. Any errors returned from this method are converted into an appropriate wtwire.StateUpdateCode.

func (*SessionInfo) Decode

func (s *SessionInfo) Decode(r io.Reader) error

Decode deserializes the session infor from the given io.Reader.

func (*SessionInfo) Encode

func (s *SessionInfo) Encode(w io.Writer) error

Encode serializes the session info to the given io.Writer.

type SessionStateUpdate

type SessionStateUpdate struct {
	// ID the session id of the client who sent the state update.
	ID SessionID

	// SeqNum the sequence number of the update within the session.
	SeqNum uint16

	// LastApplied the highest index that client has acknowledged is
	// committed
	LastApplied uint16

	// Hint is the 16-byte prefix of the revoked commitment transaction.
	Hint blob.BreachHint

	// EncryptedBlob is a ciphertext containing the sweep information for
	// exacting justice if the commitment transaction matching the breach
	// hint is broadcast.
	EncryptedBlob []byte
}

SessionStateUpdate holds a state update sent by a client along with its SessionID.

func (*SessionStateUpdate) Decode

func (u *SessionStateUpdate) Decode(r io.Reader) error

Decode deserializes the target state update from the provided io.Reader.

func (*SessionStateUpdate) Encode

func (u *SessionStateUpdate) Encode(w io.Writer) error

Encode serializes the state update into the provided io.Writer.

type Tower

type Tower struct {
	// ID is a unique ID for this record assigned by the database.
	ID TowerID

	// IdentityKey is the public key of the remote node, used to
	// authenticate the brontide transport.
	IdentityKey *btcec.PublicKey

	// Addresses is a list of possible addresses to reach the tower.
	Addresses []net.Addr
}

Tower holds the necessary components required to connect to a remote tower. Communication is handled by brontide, and requires both a public key and an address.

func (*Tower) AddAddress

func (t *Tower) AddAddress(addr net.Addr)

AddAddress adds the given address to the tower's in-memory list of addresses. If the address's string is already present, the Tower will be left unmodified. Otherwise, the adddress is prepended to the beginning of the Tower's addresses, on the assumption that it is fresher than the others.

NOTE: This method is NOT safe for concurrent use.

func (*Tower) Decode

func (t *Tower) Decode(r io.Reader) error

Decode reads a Tower from the passed io.Reader. The TowerID is meant to be decoded from the key.

func (*Tower) Encode

func (t *Tower) Encode(w io.Writer) error

Encode writes the Tower to the passed io.Writer. The TowerID is not serialized, since it acts as the key.

func (*Tower) LNAddrs

func (t *Tower) LNAddrs() []*lnwire.NetAddress

LNAddrs generates a list of lnwire.NetAddress from a Tower instance's addresses. This can be used to have a client try multiple addresses for the same Tower.

NOTE: This method is NOT safe for concurrent use.

func (*Tower) RemoveAddress

func (t *Tower) RemoveAddress(addr net.Addr)

RemoveAddress removes the given address from the tower's in-memory list of addresses. If the address doesn't exist, then this will act as a NOP.

func (*Tower) String

func (t *Tower) String() string

String returns a user-friendly identifier of the tower.

type TowerDB

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

TowerDB is single database providing a persistent storage engine for the wtserver and lookout subsystems.

func OpenTowerDB

func OpenTowerDB(dbPath string, dbTimeout time.Duration) (*TowerDB, error)

OpenTowerDB opens the tower database given the path to the database's directory. If no such database exists, this method will initialize a fresh one using the latest version number and bucket structure. If a database exists but has a lower version number than the current version, any necessary migrations will be applied before returning. Any attempt to open a database with a version number higher that the latest version will fail to prevent accidental reversion.

func (*TowerDB) Close

func (t *TowerDB) Close() error

Close closes the underlying database.

func (*TowerDB) DeleteSession

func (t *TowerDB) DeleteSession(target SessionID) error

DeleteSession removes all data associated with a particular session id from the tower's database.

func (*TowerDB) GetLookoutTip

func (t *TowerDB) GetLookoutTip() (*chainntnfs.BlockEpoch, error)

GetLookoutTip retrieves the current lookout tip block epoch from the tower database.

func (*TowerDB) GetSessionInfo

func (t *TowerDB) GetSessionInfo(id *SessionID) (*SessionInfo, error)

GetSessionInfo retrieves the session for the passed session id. An error is returned if the session could not be found.

func (*TowerDB) InsertSessionInfo

func (t *TowerDB) InsertSessionInfo(session *SessionInfo) error

InsertSessionInfo records a negotiated session in the tower database. An error is returned if the session already exists.

func (*TowerDB) InsertStateUpdate

func (t *TowerDB) InsertStateUpdate(update *SessionStateUpdate) (uint16, error)

InsertStateUpdate stores an update sent by the client after validating that the update is well-formed in the context of other updates sent for the same session. This include verifying that the sequence number is incremented properly and the last applied values echoed by the client are sane.

func (*TowerDB) QueryMatches

func (t *TowerDB) QueryMatches(breachHints []blob.BreachHint) ([]Match, error)

QueryMatches searches against all known state updates for any that match the passed breachHints. More than one Match will be returned for a given hint if they exist in the database.

func (*TowerDB) SetLookoutTip

func (t *TowerDB) SetLookoutTip(epoch *chainntnfs.BlockEpoch) error

SetLookoutTip stores the provided epoch as the latest lookout tip epoch in the tower database.

func (*TowerDB) Version

func (t *TowerDB) Version() (uint32, error)

Version returns the database's current version number.

NOTE: Part of the versionedDB interface.

type TowerID

type TowerID uint64

TowerID is a unique 64-bit identifier allocated to each unique watchtower. This allows the client to conserve on-disk space by not needing to always reference towers by their pubkey.

func TowerIDFromBytes

func TowerIDFromBytes(towerIDBytes []byte) TowerID

TowerIDFromBytes constructs a TowerID from the provided byte slice. The argument must have at least 8 bytes, and should contain the TowerID in big-endian byte order.

func (TowerID) Bytes

func (id TowerID) Bytes() []byte

Bytes encodes a TowerID into an 8-byte slice in big-endian byte order.

type UnknownElementType

type UnknownElementType = channeldb.UnknownElementType

UnknownElementType is an alias for channeldb.UnknownElementType.

Jump to

Keyboard shortcuts

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