chain

package
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Mar 4, 2024 License: MIT Imports: 19 Imported by: 0

README

Chain Validator Design

Chain validators are organized by transaction type. The executor handles mundane tasks that are common to all chain validators, such as authentication and authorization.

In general, every transaction requires an origin record. Thus, the executor validates and loads the origin before delegating to the chain validator. However, certain transaction types, specifically synthetic transactions that create records, may not need an extant origin. The executor has a specific clause for these special cases.

Chain Validator Implementation

Chain validators must satisfy the TxExecutor interface:

type TxExecutor interface {
	Type() protocol.TransactionType
	Validate(*StateManager, *messaging.Envelope) error
}

All state manipulation (mutating and loading) must go through the state manager. There are three methods that can be used to modify records and/or create synthetic transactions:

  • Implementing a user transaction executor
    • Update(record) - Update one or more existing records. Cannot be used to create records.
    • Create(record) - Create one or more new records. Produces a synthetic chain create transaction.
    • Submit(url, body) - Submit a synthetic transaction.
  • Implementing a synthetic transaction executor
    • Update(record) - Create or update one or more existing records.
    • Create(record) - Cannot be used by synthetic transactions.
    • Submit(url, body) - Cannot be used by synthetic transactions.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ActivateProtocolVersion

type ActivateProtocolVersion struct{}

func (ActivateProtocolVersion) Execute

func (ActivateProtocolVersion) Type

func (ActivateProtocolVersion) Validate

type AddCredits

type AddCredits struct{}

func (AddCredits) Execute

func (AddCredits) Type

func (AddCredits) Validate

type AuthDelegate

type AuthDelegate interface {
	// GetActiveGlobals returns the active network global values.
	GetActiveGlobals() *core.GlobalValues

	// GetAccountAuthoritySet returns the authority set of an account.
	GetAccountAuthoritySet(batch *database.Batch, account protocol.Account) (*protocol.AccountAuth, error)

	// GetMessageAs retrieves a signature by hash from the bundle or database.
	GetSignatureAs(batch *database.Batch, hash [32]byte) (protocol.Signature, error)

	// TransactionIsInitiated verifies the transaction has been paid for and the
	// initiator signature has been processed.
	TransactionIsInitiated(batch *database.Batch, transaction *protocol.Transaction) (bool, *messaging.CreditPayment, error)

	// SignerCanSign returns an error if the signer is not authorized to sign
	// the transaction (e.g. a key page's transaction blacklist).
	SignerCanSign(batch *database.Batch, transaction *protocol.Transaction, signer protocol.Signer) error

	// AuthorityDidVote verifies the authority is ready to send an authority
	// signature. For most transactions, this succeeds if at least one of the
	// authority's signers is satisfied.
	AuthorityDidVote(batch *database.Batch, transaction *protocol.Transaction, authUrl *url.URL) (bool, protocol.VoteType, error)

	// AuthorityWillVote verifies that an authority signature (aka vote)
	// approving the transaction has been received from the authority.
	AuthorityWillVote(batch *database.Batch, block uint64, transaction *protocol.Transaction, authUrl *url.URL) (*AuthVote, error)
}

type AuthVote added in v1.2.0

type AuthVote struct {
	Source *url.URL
	Vote   protocol.VoteType
}

type AuthorityValidator

type AuthorityValidator interface {
	// AuthorityWillVote checks if the authority is ready to vote. MUST NOT
	// MODIFY STATE.
	AuthorityWillVote(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, authority *url.URL) (fallback bool, vote *AuthVote, err error)
}

type BurnCredits

type BurnCredits struct{}

func (BurnCredits) Execute

func (BurnCredits) Type

func (BurnCredits) Validate

type BurnTokens

type BurnTokens struct{}

func (BurnTokens) Execute

func (BurnTokens) Type

func (BurnTokens) Validate

type ChainUpdates

type ChainUpdates struct {
	Entries      []*protocol.BlockEntry
	SynthEntries []*database.BlockStateSynthTxnEntry
}

func (*ChainUpdates) AddChainEntry

func (u *ChainUpdates) AddChainEntry(batch *database.Batch, chain *database.Chain2, entry []byte, sourceIndex, sourceBlock uint64) error

AddChainEntry adds an entry to a chain and records the chain update in the block state.

func (*ChainUpdates) AddChainEntry2

func (u *ChainUpdates) AddChainEntry2(batch *database.Batch, chain *database.Chain2, entry []byte, sourceIndex, sourceBlock uint64, unique bool) (int64, error)

func (*ChainUpdates) DidAddChainEntry

func (c *ChainUpdates) DidAddChainEntry(batch *database.Batch, u *url.URL, name string, typ protocol.ChainType, entry []byte, index, sourceIndex, sourceBlock uint64) error

DidAddChainEntry records a chain update in the block state.

func (*ChainUpdates) DidUpdateChain

func (c *ChainUpdates) DidUpdateChain(update *protocol.BlockEntry)

DidUpdateChain records a chain update.

func (*ChainUpdates) Merge

func (c *ChainUpdates) Merge(d *ChainUpdates)

type CreateDataAccount

type CreateDataAccount struct{}

func (CreateDataAccount) AuthorityIsAccepted added in v1.2.0

func (CreateDataAccount) AuthorityIsAccepted(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, sig *protocol.AuthoritySignature) (fallback bool, err error)

func (CreateDataAccount) Execute

func (CreateDataAccount) TransactionIsReady

func (CreateDataAccount) TransactionIsReady(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)

func (CreateDataAccount) Type

func (CreateDataAccount) Validate

type CreateIdentity

type CreateIdentity struct{}

func (CreateIdentity) AllowMissingPrincipal

func (CreateIdentity) AllowMissingPrincipal(transaction *protocol.Transaction) bool

func (CreateIdentity) AuthorityIsAccepted added in v1.2.0

func (CreateIdentity) AuthorityIsAccepted(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, sig *protocol.AuthoritySignature) (fallback bool, err error)

func (CreateIdentity) Execute

func (CreateIdentity) SignerCanSign added in v1.2.0

func (CreateIdentity) SignerCanSign(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, signer protocol.Signer) (fallback bool, err error)

func (CreateIdentity) TransactionIsReady

func (CreateIdentity) TransactionIsReady(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)

func (CreateIdentity) Type

func (CreateIdentity) Validate

type CreateKeyBook

type CreateKeyBook struct{}

func (CreateKeyBook) AuthorityIsAccepted added in v1.2.0

func (CreateKeyBook) AuthorityIsAccepted(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, sig *protocol.AuthoritySignature) (fallback bool, err error)

func (CreateKeyBook) Execute

func (CreateKeyBook) TransactionIsReady

func (CreateKeyBook) TransactionIsReady(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)

func (CreateKeyBook) Type

func (CreateKeyBook) Validate

type CreateKeyPage

type CreateKeyPage struct{}

func (CreateKeyPage) Execute

func (CreateKeyPage) Type

func (CreateKeyPage) Validate

type CreateLiteTokenAccount

type CreateLiteTokenAccount struct{}

func (CreateLiteTokenAccount) AllowMissingPrincipal

func (CreateLiteTokenAccount) AllowMissingPrincipal(transaction *protocol.Transaction) bool

func (CreateLiteTokenAccount) AuthorityIsAccepted added in v1.2.0

func (CreateLiteTokenAccount) Execute

func (CreateLiteTokenAccount) SignerCanSign added in v1.2.0

func (CreateLiteTokenAccount) SignerCanSign(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, signer protocol.Signer) (fallback bool, err error)

func (CreateLiteTokenAccount) TransactionIsReady

func (CreateLiteTokenAccount) TransactionIsReady(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)

func (CreateLiteTokenAccount) Type

func (CreateLiteTokenAccount) Validate

type CreateToken

type CreateToken struct{}

func (CreateToken) AuthorityIsAccepted added in v1.2.0

func (CreateToken) AuthorityIsAccepted(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, sig *protocol.AuthoritySignature) (fallback bool, err error)

func (CreateToken) Execute

func (CreateToken) TransactionIsReady

func (CreateToken) TransactionIsReady(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)

func (CreateToken) Type

func (CreateToken) Validate

type CreateTokenAccount

type CreateTokenAccount struct{}

func (CreateTokenAccount) AuthorityIsAccepted added in v1.2.0

func (CreateTokenAccount) AuthorityIsAccepted(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, sig *protocol.AuthoritySignature) (fallback bool, err error)

func (CreateTokenAccount) Execute

func (CreateTokenAccount) TransactionIsReady

func (CreateTokenAccount) TransactionIsReady(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)

func (CreateTokenAccount) Type

func (CreateTokenAccount) Validate

type Delivery

type Delivery struct {
	Internal  bool
	Forwarded bool

	Signatures  []protocol.Signature
	Transaction *protocol.Transaction
	State       ProcessTransactionState
}

func (*Delivery) IsForwarded

func (d *Delivery) IsForwarded() bool

IsForwarded returns true if the transaction was delivered within a SyntheticForwardedTransaction.

func (*Delivery) LoadTransaction

func (d *Delivery) LoadTransaction(batch *database.Batch) (*protocol.TransactionStatus, error)

LoadTransaction attempts to load the transaction from the database.

func (*Delivery) WasProducedInternally

func (d *Delivery) WasProducedInternally() bool

type DirectoryAnchor

type DirectoryAnchor struct{}

func (DirectoryAnchor) Execute

func (DirectoryAnchor) Type

func (DirectoryAnchor) Validate

type IssueTokens

type IssueTokens struct{}

func (IssueTokens) Execute

func (IssueTokens) Type

func (IssueTokens) Validate

type LockAccount

type LockAccount struct{}

func (LockAccount) Execute

func (LockAccount) Type

func (LockAccount) Validate

type NetworkMaintenance added in v1.3.0

type NetworkMaintenance struct{}

func (NetworkMaintenance) AuthorityIsAccepted added in v1.3.0

func (x NetworkMaintenance) AuthorityIsAccepted(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, sig *protocol.AuthoritySignature) (fallback bool, err error)

func (NetworkMaintenance) Execute added in v1.3.0

func (NetworkMaintenance) TransactionIsReady added in v1.3.0

func (x NetworkMaintenance) TransactionIsReady(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)

func (NetworkMaintenance) Type added in v1.3.0

func (NetworkMaintenance) Validate added in v1.3.0

type PartitionAnchor

type PartitionAnchor struct{}

func (PartitionAnchor) Execute

func (PartitionAnchor) Type

func (PartitionAnchor) Validate

type PrincipalValidator

type PrincipalValidator interface {
	TransactionExecutor

	AllowMissingPrincipal(*protocol.Transaction) bool
}

PrincipalValidator validates the principal for a specific type of transaction.

type ProcessTransactionState

type ProcessTransactionState struct {
	ProducedTxns       []*protocol.Transaction
	AdditionalMessages []messaging.Message
	ChainUpdates       ChainUpdates
	MakeMajorBlock     uint64
	MakeMajorBlockTime time.Time
	ReceivedAnchors    []*ReceivedAnchor
}

func (*ProcessTransactionState) DidProduceTxn

func (s *ProcessTransactionState) DidProduceTxn(url *url.URL, body protocol.TransactionBody)

DidProduceTxn records a produced transaction.

func (*ProcessTransactionState) DidReceiveAnchor

func (s *ProcessTransactionState) DidReceiveAnchor(partition string, body protocol.AnchorBody, index int64)

func (*ProcessTransactionState) Merge

func (*ProcessTransactionState) ProcessNetworkMaintenanceOp added in v1.3.0

func (s *ProcessTransactionState) ProcessNetworkMaintenanceOp(cause *url.TxID, op protocol.NetworkMaintenanceOperation)

ProcessNetworkMaintenanceOp queues a internal.NetworkMaintenanceOp message for processing after the current bundle.

func (*ProcessTransactionState) ProcessNetworkUpdate

func (s *ProcessTransactionState) ProcessNetworkUpdate(cause [32]byte, account *url.URL, body protocol.TransactionBody)

ProcessNetworkUpdate queues a internal.NetworkUpdate message for processing after the current bundle.

func (*ProcessTransactionState) ProcessTransaction

func (s *ProcessTransactionState) ProcessTransaction(txid *url.TxID)

ProcessTransaction queues a transaction for processing after the current bundle.

type ReceivedAnchor

type ReceivedAnchor struct {
	Partition string
	Body      protocol.AnchorBody
	Index     int64
}

type SendTokens

type SendTokens struct{}

func (SendTokens) Execute

func (SendTokens) Type

func (SendTokens) Validate

type SignatureValidationMetadata

type SignatureValidationMetadata struct {
	Location    *url.URL
	IsInitiator bool
	Delegated   bool
	Forwarded   bool
}

func (SignatureValidationMetadata) Nested

func (d SignatureValidationMetadata) Nested() bool

func (SignatureValidationMetadata) SetDelegated

func (SignatureValidationMetadata) SetForwarded

type SignerCanSignValidator added in v1.2.0

type SignerCanSignValidator interface {
	SignerCanSign(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, signer protocol.Signer) (fallback bool, err error)
}

SignerCanSignValidator validates that a signer is authorized to sign a transaction (e.g a key page's black list).

type SignerValidator

type SignerValidator interface {
	TransactionExecutor

	// AuthorityIsAccepted checks if the authority signature is accepted for the
	// transaction.
	AuthorityIsAccepted(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, sig *protocol.AuthoritySignature) (fallback bool, err error)

	// TransactionIsReady checks if the transaction is ready to be executed.
	TransactionIsReady(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)
}

SignerValidator validates signatures for a specific type of transaction.

type StateManager

type StateManager struct {
	AuthDelegate AuthDelegate
	Origin       protocol.Account
	OriginUrl    *url.URL
	// contains filtered or unexported fields
}

func NewStateManager

func NewStateManager(net execute.DescribeShim, globals *core.GlobalValues, authDelegate AuthDelegate, batch *database.Batch, principal protocol.Account, transaction *protocol.Transaction, logger log.Logger) *StateManager

NewStateManager creates a new state manager and loads the transaction's origin. If the origin is not found, NewStateManager returns a valid state manager along with a not-found error.

func NewStatelessManager

func NewStatelessManager(net execute.DescribeShim, globals *core.GlobalValues, transaction *protocol.Transaction, logger log.Logger) *StateManager

NewStatelessManager creates a new state manager and does *not* hold a reference to any state such as the transaction's principal or the current network variables.

func (*StateManager) AddAuthority

func (m *StateManager) AddAuthority(account protocol.FullAccount, authority *url.URL) error

func (*StateManager) AddDirectoryEntry

func (c *StateManager) AddDirectoryEntry(directory *url.URL, u ...*url.URL) error

func (*StateManager) Commit

func (m *StateManager) Commit() (*ProcessTransactionState, error)

commit writes pending records to the database.

func (*StateManager) Create

func (st *StateManager) Create(accounts ...protocol.Account) error

func (*StateManager) Discard

func (m *StateManager) Discard()

func (*StateManager) GetBatch

func (m *StateManager) GetBatch() *database.Batch

func (*StateManager) GetHash

func (m *StateManager) GetHash() []byte

func (*StateManager) GetHeight

func (c *StateManager) GetHeight(u *url.URL) (uint64, error)

GetHeight loads the height of the chain

func (*StateManager) InheritAuth

func (m *StateManager) InheritAuth(account protocol.FullAccount) error

func (*StateManager) LoadTxn

func (c *StateManager) LoadTxn(txid [32]byte) (*protocol.Transaction, error)

LoadTxn loads and unmarshals a saved transaction

func (*StateManager) LoadUrl

func (c *StateManager) LoadUrl(account *url.URL) (protocol.Account, error)

LoadUrl loads a chain by URL and unmarshals it.

func (*StateManager) LoadUrlAs

func (c *StateManager) LoadUrlAs(account *url.URL, target interface{}) error

LoadUrlAs loads a chain by URL and unmarshals it as a specific type.

func (*StateManager) SetAuth

func (m *StateManager) SetAuth(account protocol.FullAccount, authorities []*url.URL) error

func (*StateManager) Submit

func (m *StateManager) Submit(url *url.URL, body protocol.TransactionBody)

Submit queues a synthetic transaction for submission.

func (*StateManager) Update

func (st *StateManager) Update(accounts ...protocol.Account) error

Update queues a record for storage in the database. The queued update will fail if the record does not already exist, unless it is created by a synthetic transaction, or the record is a transaction.

func (*StateManager) UpdateData

func (m *StateManager) UpdateData(record protocol.Account, entryHash []byte, dataEntry protocol.DataEntry)

UpdateData will cache a data associated with a DataAccount chain. the cache data will not be stored directly in the state but can be used upstream for storing a chain in the state database.

type SyntheticBurnTokens

type SyntheticBurnTokens struct{}

func (SyntheticBurnTokens) Execute

func (SyntheticBurnTokens) Type

func (SyntheticBurnTokens) Validate

type SyntheticCreateIdentity

type SyntheticCreateIdentity struct{}

func (SyntheticCreateIdentity) AllowMissingPrincipal

func (SyntheticCreateIdentity) AllowMissingPrincipal(transaction *protocol.Transaction) bool

func (SyntheticCreateIdentity) Execute

func (SyntheticCreateIdentity) Type

func (SyntheticCreateIdentity) Validate

type SyntheticDepositCredits

type SyntheticDepositCredits struct{}

func (SyntheticDepositCredits) AllowMissingPrincipal

func (SyntheticDepositCredits) AllowMissingPrincipal(transaction *protocol.Transaction) bool

func (SyntheticDepositCredits) DidFail

func (SyntheticDepositCredits) Execute

func (SyntheticDepositCredits) Type

func (SyntheticDepositCredits) Validate

type SyntheticDepositTokens

type SyntheticDepositTokens struct{}

func (SyntheticDepositTokens) AllowMissingPrincipal

func (SyntheticDepositTokens) AllowMissingPrincipal(transaction *protocol.Transaction) bool

func (SyntheticDepositTokens) DidFail

func (SyntheticDepositTokens) Execute

func (SyntheticDepositTokens) Type

func (SyntheticDepositTokens) Validate

type SyntheticWriteData

type SyntheticWriteData struct{}

func (SyntheticWriteData) AllowMissingPrincipal

func (SyntheticWriteData) AllowMissingPrincipal(transaction *protocol.Transaction) bool

func (SyntheticWriteData) Execute

func (SyntheticWriteData) Type

func (SyntheticWriteData) Validate

type SystemWriteData

type SystemWriteData struct{}

func (SystemWriteData) Execute

func (SystemWriteData) Type

func (SystemWriteData) Validate

type TransactionExecutor

type TransactionExecutor interface {
	// Type is the transaction type the executor can execute.
	Type() protocol.TransactionType

	// Validate validates the transaction for acceptance.
	Validate(*StateManager, *Delivery) (protocol.TransactionResult, error)

	// Execute fully validates and executes the transaction.
	Execute(*StateManager, *Delivery) (protocol.TransactionResult, error)
}

TransactionExecutor executes a specific type of transaction.

type TransactionExecutorCleanup

type TransactionExecutorCleanup interface {
	// DidFail is called if the transaction failed.
	DidFail(*ProcessTransactionState, *protocol.Transaction) error
}

TransactionExecutorCleanup cleans up after a failed transaction.

type TransferCredits

type TransferCredits struct{}

func (TransferCredits) Execute

func (TransferCredits) Type

func (TransferCredits) Validate

type UpdateAccountAuth

type UpdateAccountAuth struct{}

func (UpdateAccountAuth) AuthorityIsAccepted added in v1.2.0

func (UpdateAccountAuth) AuthorityIsAccepted(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, sig *protocol.AuthoritySignature) (fallback bool, err error)

func (UpdateAccountAuth) Execute

func (UpdateAccountAuth) TransactionIsReady

func (UpdateAccountAuth) TransactionIsReady(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)

func (UpdateAccountAuth) Type

func (UpdateAccountAuth) Validate

type UpdateKey

type UpdateKey struct{}

func (UpdateKey) AuthorityIsAccepted added in v1.2.0

func (UpdateKey) AuthorityIsAccepted(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, sig *protocol.AuthoritySignature) (fallback bool, err error)

func (UpdateKey) AuthorityWillVote added in v1.2.0

func (x UpdateKey) AuthorityWillVote(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, authority *url.URL) (fallback bool, vote *AuthVote, err error)

func (UpdateKey) Execute

func (UpdateKey) TransactionIsReady

func (x UpdateKey) TransactionIsReady(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)

func (UpdateKey) Type

func (UpdateKey) Validate

type UpdateKeyPage

type UpdateKeyPage struct{}

func (UpdateKeyPage) AuthorityIsAccepted added in v1.2.0

func (UpdateKeyPage) AuthorityIsAccepted(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, sig *protocol.AuthoritySignature) (fallback bool, err error)

func (UpdateKeyPage) Execute

func (UpdateKeyPage) SignerCanSign added in v1.2.0

func (UpdateKeyPage) SignerCanSign(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, signer protocol.Signer) (fallback bool, err error)

func (UpdateKeyPage) TransactionIsReady

func (UpdateKeyPage) TransactionIsReady(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)

func (UpdateKeyPage) Type

func (UpdateKeyPage) Validate

type WriteData

type WriteData struct{}

func (WriteData) AllowMissingPrincipal

func (WriteData) AllowMissingPrincipal(transaction *protocol.Transaction) bool

func (WriteData) AuthorityIsAccepted added in v1.2.0

func (WriteData) AuthorityIsAccepted(_ AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, _ *protocol.AuthoritySignature) (fallback bool, err error)

AuthorityIsAccepted returns nil if the transaction is writing to a lite data account.

func (WriteData) Execute

func (WriteData) SignerCanSign added in v1.2.0

func (WriteData) SignerCanSign(delegate AuthDelegate, batch *database.Batch, transaction *protocol.Transaction, signer protocol.Signer) (fallback bool, err error)

SignerCanSign returns nil if the transaction is writing to a lite data account.

func (WriteData) TransactionIsReady

func (WriteData) TransactionIsReady(_ AuthDelegate, batch *database.Batch, transaction *protocol.Transaction) (ready, fallback bool, err error)

TransactionIsReady returns true if the transaction is writing to a lite data account.

func (WriteData) Type

func (WriteData) Validate

type WriteDataTo

type WriteDataTo struct{}

func (WriteDataTo) Execute

func (WriteDataTo) Type

func (WriteDataTo) Validate

Jump to

Keyboard shortcuts

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