validators

package
v0.0.0-...-f6bc33c Latest Latest
Warning

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

Go to latest
Published: Apr 28, 2023 License: MIT Imports: 39 Imported by: 0

README

Validators

This package manages the mapping of Tendermint nodes to Zeta nodes. This is important as it determines which nodes need to be involved in a multisig transacton (e.g. when a user wants to withdraw collateral).

Documentation

Index

Constants

View Source
const (
	ValidatorStatusPending = iota
	ValidatorStatusErsatz
	ValidatorStatusTendermint
)

Variables

View Source
var (
	ErrZetaNodeAlreadyRegisterForChain = errors.New("a zeta node is already registered with the blockchain node")
	ErrInvalidChainPubKey              = errors.New("invalid blockchain public key")
	ErrIssueSignaturesUnexpectedKind   = errors.New("unexpected node-signature kind")
)
View Source
var (
	ErrCurrentEthAddressDoesNotMatch = errors.New("current Ethereum address does not match")
	ErrCannotRotateToSameKey         = errors.New("new Ethereum address cannot be the same as the previous Ethereum address")
	ErrNodeHasUnresolvedRotation     = errors.New("ethereum keys from a previous rotation have not been resolved on the multisig control contract")
)
View Source
var (
	ErrTargetBlockHeightMustBeGreaterThanCurrentHeight       = errors.New("target block height must be greater then current block")
	ErrNewZetaPubKeyIndexMustBeGreaterThenCurrentPubKeyIndex = errors.New("a new zeta public key index must be greather then current public key index")
	ErrInvalidZetaPubKeyForNode                              = errors.New("current zeta public key is invalid for node")
	ErrNodeAlreadyHasPendingKeyRotation                      = errors.New("node already has a pending key rotation")
	ErrCurrentPubKeyHashDoesNotMatch                         = errors.New("current public key hash does not match")
)
View Source
var (
	ErrUnknownValidator            = errors.New("unknown validator ID")
	ErrUnexpectedSignedBlockHeight = errors.New("unexpected signed block height")

	PerformanceIncrement        = num.DecimalFromFloat(0.1)
	DecimalOne                  = num.DecimalFromFloat(1)
	VotingPowerScalingFactor, _ = num.DecimalFromString("10000")
)
View Source
var (
	ErrResourceDuplicate            = errors.New("resource duplicate")
	ErrCheckUntilInvalid            = errors.New("invalid time to check until")
	ErrInvalidResourceIDForNodeVote = errors.New("invalid resource ID")
	ErrVoteFromNonValidator         = errors.New("vote from non validator")
	ErrDuplicateVoteFromNode        = errors.New("duplicate vote from node")
)
View Source
var ErrHeartbeatHasExpired = errors.New("heartbeat received after expiry")
View Source
var ErrMissingRequiredAnnounceNodeFields = errors.New("missing required announce node fields")
View Source
var ErrNoPendingSignaturesForNodeID = errors.New("there are no pending signatures for the given nodeID")
View Source
var (
	ErrSnapshotKeyDoesNotExist = errors.New("unknown key for witness snapshot")
)
View Source
var ValidatorStatusToName = map[ValidatorStatus]string{
	ValidatorStatusPending:    "pending",
	ValidatorStatusErsatz:     "ersatz",
	ValidatorStatusTendermint: "tendermint",
}

Functions

func CalcAntiWhalingScore

func CalcAntiWhalingScore(delegationState []*types.ValidatorData, totalStakeD, optStake num.Decimal, stakeScoreParams types.StakeScoreParams) map[string]num.Decimal

CalcAntiWhalingScore calculates the anti-whaling stake score for the validators represented in the given delegation set.

func CalcDelegation

func CalcDelegation(validators map[string]struct{}, delegationState []*types.ValidatorData) ([]*types.ValidatorData, num.Decimal)

CalcDelegation extracts the delegation of the validator set from the delegation state slice and returns the total delegation.

func CalcValidatorScore

func CalcValidatorScore(valStake, totalStake, optStake num.Decimal, stakeScoreParams types.StakeScoreParams) num.Decimal

calcValidatorScore calculates the stake based raw validator score with anti whaling.

func GetOptimalStake

func GetOptimalStake(tmTotalDelegation num.Decimal, numValidators int, params types.StakeScoreParams) num.Decimal

func NewValidatorPerformance

func NewValidatorPerformance(log *logging.Logger) *validatorPerformance

func SignAnnounceNode

func SignAnnounceNode(
	an *commandspb.AnnounceNode,
	zetaSigner Signer,
	ethSigner Signer,
) error

SignAnnounceNode adds the signature for the ethereum and Zeta address / pubkeys.

func SignEthereumKeyRotation

func SignEthereumKeyRotation(
	kr *commandspb.EthereumKeyRotateSubmission,
	ethSigner Signer,
) error

func VerifyAnnounceNode

func VerifyAnnounceNode(an *commandspb.AnnounceNode) error

SignAnnounceNode adds the signature for the ethereum and Zeta address / pubkeys.

func VerifyEthereumKeyRotation

func VerifyEthereumKeyRotation(kr *commandspb.EthereumKeyRotateSubmission, verify func(message, signature []byte, hexAddress string) error) error

func VerifyTendermintKey

func VerifyTendermintKey(tmKey string) error

Types

type Broker

type Broker interface {
	Send(event events.Event)
	SendBatch(events []events.Event)
}

Broker needs no mocks.

type Commander

type Commander interface {
	Command(ctx context.Context, cmd txn.Command, payload proto.Message, f func(string, error), bo *backoff.ExponentialBackOff)
	CommandSync(ctx context.Context, cmd txn.Command, payload proto.Message, f func(string, error), bo *backoff.ExponentialBackOff)
}

type Config

type Config struct {
	// logging level
	Level encoding.LogLevel `long:"log-level"`
}

Config represents governance specific configuration.

func NewDefaultConfig

func NewDefaultConfig() Config

NewDefaultConfig creates an instance of the package specific configuration.

type ERC20Signatures

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

func NewSignatures

func NewSignatures(
	log *logging.Logger,
	multiSigTopology MultiSigTopology,
	notary Notary,
	nw NodeWallets,
	broker Broker,
	isValidatorSetup bool,
) *ERC20Signatures

func (*ERC20Signatures) ClearStaleSignatures

func (s *ERC20Signatures) ClearStaleSignatures()

ClearStaleSignatures checks core's view of who is an isn't on the multisig contract and remove any pending signatures that have been resolve e.g if a pending sig to add an address X exists but X is on the contract we can remove the pending sig.

func (*ERC20Signatures) EmitValidatorAddedSignatures

func (s *ERC20Signatures) EmitValidatorAddedSignatures(ctx context.Context, submitter, nodeID string, currentTime time.Time) error

EmitValidatorAddedSignatures emit signatures to add nodeID's ethereum address onto that can be submitter to the contract by submitter.

func (*ERC20Signatures) EmitValidatorRemovedSignatures

func (s *ERC20Signatures) EmitValidatorRemovedSignatures(ctx context.Context, submitter, nodeID string, currentTime time.Time) error

EmitValidatorRemovedSignatures emit signatures to remove nodeID's ethereum address onto that can be submitter to the contract by submitter.

func (*ERC20Signatures) OfferSignatures

func (s *ERC20Signatures) OfferSignatures()

func (*ERC20Signatures) PreparePromotionsSignatures

func (s *ERC20Signatures) PreparePromotionsSignatures(
	ctx context.Context,
	currentTime time.Time,
	epochSeq uint64,
	previousState map[string]StatusAddress,
	newState map[string]StatusAddress,
)

func (*ERC20Signatures) PrepareValidatorSignatures

func (s *ERC20Signatures) PrepareValidatorSignatures(ctx context.Context, validators []NodeIDAddress, epochSeq uint64, added bool)

PrepareValidatorSignatures make nonces and store the data needed to generate signatures to add/remove from the multisig control contract.

func (*ERC20Signatures) RestorePendingSignatures

func (s *ERC20Signatures) RestorePendingSignatures(sigs *snapshot.ToplogySignatures)

func (*ERC20Signatures) SerialisePendingSignatures

func (s *ERC20Signatures) SerialisePendingSignatures() *snapshot.ToplogySignatures

func (*ERC20Signatures) SetNonce

func (s *ERC20Signatures) SetNonce(t time.Time)

type GenesisState

type GenesisState ValidatorMapping

func DefaultGenesisState

func DefaultGenesisState() GenesisState

func LoadGenesisState

func LoadGenesisState(bytes []byte) (GenesisState, error)

type MultiSigTopology

type MultiSigTopology interface {
	IsSigner(address string) bool
	ExcessSigners(addresses []string) bool
	GetSigners() []string
	GetThreshold() uint32
}

type NodeIDAddress

type NodeIDAddress struct {
	NodeID           string
	EthAddress       string
	SubmitterAddress string
}

type NodeWallets

type NodeWallets interface {
	GetZeta() Wallet
	GetTendermintPubkey() string
	GetEthereumAddress() string
	GetEthereum() Signer
}

type NodeWalletsWrapper

type NodeWalletsWrapper struct {
	*nodewallets.NodeWallets
}

func WrapNodeWallets

func WrapNodeWallets(nw *nodewallets.NodeWallets) *NodeWalletsWrapper

func (*NodeWalletsWrapper) GetEthereum

func (w *NodeWalletsWrapper) GetEthereum() Signer

func (*NodeWalletsWrapper) GetEthereumAddress

func (w *NodeWalletsWrapper) GetEthereumAddress() string

func (*NodeWalletsWrapper) GetTendermintPubkey

func (w *NodeWalletsWrapper) GetTendermintPubkey() string

func (*NodeWalletsWrapper) GetZeta

func (w *NodeWalletsWrapper) GetZeta() Wallet

type Notary

type Notary interface {
	StartAggregate(resID string, kind types.NodeSignatureKind, signature []byte)
	IsSigned(ctx context.Context, id string, kind types.NodeSignatureKind) ([]types.NodeSignature, bool)
	OfferSignatures(kind types.NodeSignatureKind, f func(resources string) []byte)
}

Notary ...

type PendingEthereumKeyRotation

type PendingEthereumKeyRotation struct {
	NodeID           string
	NewAddress       string
	OldAddress       string
	SubmitterAddress string
}

type PendingKeyRotation

type PendingKeyRotation struct {
	BlockHeight uint64
	NodeID      string
	NewPubKey   string
	NewKeyIndex uint32
}

type Resource

type Resource interface {
	GetID() string
	GetType() commandspb.NodeVote_Type
	Check() error
}

type Signatures

type Signatures interface {
	PreparePromotionsSignatures(
		ctx context.Context,
		currentTime time.Time,
		epochSeq uint64,
		previousState map[string]StatusAddress,
		newState map[string]StatusAddress,
	)
	SetNonce(currentTime time.Time)
	PrepareValidatorSignatures(ctx context.Context, validators []NodeIDAddress, epochSeq uint64, added bool)
	EmitValidatorAddedSignatures(ctx context.Context, submitter, nodeID string, currentTime time.Time) error
	EmitValidatorRemovedSignatures(ctx context.Context, submitter, nodeID string, currentTime time.Time) error
	ClearStaleSignatures()
	SerialisePendingSignatures() *snapshot.ToplogySignatures
	RestorePendingSignatures(*snapshot.ToplogySignatures)
	OfferSignatures()
}

type Signer

type Signer interface {
	Sign([]byte) ([]byte, error)
	Algo() string
}

type StatusAddress

type StatusAddress struct {
	Status           ValidatorStatus
	EthAddress       string
	SubmitterAddress string
}

type TimeService

type TimeService interface {
	GetTimeNow() time.Time
}

type Topology

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

func NewTopology

func NewTopology(
	log *logging.Logger, cfg Config, wallets NodeWallets, broker Broker, isValidatorSetup bool, cmd Commander, msTopology MultiSigTopology, timeService TimeService,
) *Topology

func (*Topology) AddForwarder

func (t *Topology) AddForwarder(pubKey string)

AddForwarder records the times that a validator fowards an eth event.

func (*Topology) AddKeyRotate

func (t *Topology) AddKeyRotate(ctx context.Context, nodeID string, currentBlockHeight uint64, kr *commandspb.KeyRotateSubmission) error

func (*Topology) AddNewNode

func (t *Topology) AddNewNode(ctx context.Context, nr *commandspb.AnnounceNode, status ValidatorStatus) error

func (*Topology) AllNodeIDs

func (t *Topology) AllNodeIDs() []string

AllNodeIDs returns all the validators node IDs keys.

func (*Topology) AllZetaPubKeys

func (t *Topology) AllZetaPubKeys() []string

AllZetaPubKeys returns all the validators zeta public keys.

func (*Topology) BeginBlock

func (t *Topology) BeginBlock(ctx context.Context, req abcitypes.RequestBeginBlock)

func (*Topology) Checkpoint

func (t *Topology) Checkpoint() ([]byte, error)

func (*Topology) Get

func (t *Topology) Get(key string) *ValidatorData

Get returns validator data based on validator master public key.

func (*Topology) GetAllPendingKeyRotations

func (t *Topology) GetAllPendingKeyRotations() []*PendingKeyRotation

func (*Topology) GetPendingEthereumKeyRotation

func (t *Topology) GetPendingEthereumKeyRotation(blockHeight uint64, nodeID string) *PendingEthereumKeyRotation

func (*Topology) GetPendingKeyRotation

func (t *Topology) GetPendingKeyRotation(blockHeight uint64, nodeID string) *PendingKeyRotation

func (*Topology) GetRewardsScores

func (t *Topology) GetRewardsScores(ctx context.Context, epochSeq string, delegationState []*types.ValidatorData, stakeScoreParams types.StakeScoreParams) (*types.ScoreData, *types.ScoreData)

GetRewardsScores returns the reward scores (raw, performance, multisig, validator_score, and normalised) for tm and ersatz validaor sets.

func (*Topology) GetState

func (t *Topology) GetState(k string) ([]byte, []types.StateProvider, error)

func (*Topology) GetTotalVotingPower

func (t *Topology) GetTotalVotingPower() int64

func (*Topology) GetValidatorPowerUpdates

func (t *Topology) GetValidatorPowerUpdates() []tmtypes.ValidatorUpdate

GetValidatorPowerUpdates returns the voting power changes if this is the first block of an epoch.

func (*Topology) GetVotingPower

func (t *Topology) GetVotingPower(pubkey string) int64

func (*Topology) IsSelfTendermintValidator

func (t *Topology) IsSelfTendermintValidator() bool

func (*Topology) IsTendermintValidator

func (t *Topology) IsTendermintValidator(pubkey string) (ok bool)

IsValidatorZetaPubKey returns true if the given key is a Zeta validator public key and the validators is of status Tendermint.

func (*Topology) IsValidator

func (t *Topology) IsValidator() bool

func (*Topology) IsValidatorNodeID

func (t *Topology) IsValidatorNodeID(nodeID string) bool

IsValidatorNodeID takes a nodeID and returns true if the node is a validator node.

func (*Topology) IsValidatorZetaPubKey

func (t *Topology) IsValidatorZetaPubKey(pubkey string) (ok bool)

IsValidatorZetaPubKey returns true if the given key is a Zeta validator public key.

func (*Topology) IssueSignatures

func (t *Topology) IssueSignatures(ctx context.Context, submitter, nodeID string, kind types.NodeSignatureKind) error

func (*Topology) Keys

func (t *Topology) Keys() []string

func (*Topology) Len

func (t *Topology) Len() int

Len return the number of validators with status Tendermint, the only validators that matter.

func (*Topology) Load

func (t *Topology) Load(ctx context.Context, data []byte) error

func (*Topology) LoadState

func (t *Topology) LoadState(ctx context.Context, p *types.Payload) ([]types.StateProvider, error)

func (*Topology) LoadValidatorsOnGenesis

func (t *Topology) LoadValidatorsOnGenesis(ctx context.Context, rawstate []byte) (err error)

func (*Topology) Name

func (t *Topology) Name() types.CheckpointName

func (*Topology) Namespace

func (t *Topology) Namespace() types.SnapshotNamespace

func (*Topology) NotifyOnKeyChange

func (t *Topology) NotifyOnKeyChange(fns ...func(ctx context.Context, oldPubKey, newPubKey string))

func (*Topology) NumberOfTendermintValidators

func (t *Topology) NumberOfTendermintValidators() uint

func (*Topology) OnEpochEvent

func (t *Topology) OnEpochEvent(ctx context.Context, epoch types.Epoch)

func (*Topology) OnEpochLengthUpdate

func (t *Topology) OnEpochLengthUpdate(ctx context.Context, l time.Duration) error

OnEpochLengthUpdate updates the duration of an epoch - which is used to calculate the number of blocks to keep a malperforming validators. The number of blocks is calculated as 10 epochs x duration of epoch in seconds, assuming block time is 1s.

func (*Topology) OnEpochRestore

func (t *Topology) OnEpochRestore(_ context.Context, epoch types.Epoch)

OnEpochRestore is the epochtime service telling us the restored epoch data.

func (*Topology) OnPerformanceScalingChanged

func (t *Topology) OnPerformanceScalingChanged(ctx context.Context, scalingFactor num.Decimal) error

OnPerformanceScalingChanged updates the network parameter for performance scaling factor.

func (*Topology) ProcessAnnounceNode

func (t *Topology) ProcessAnnounceNode(
	ctx context.Context, an *commandspb.AnnounceNode,
) error

func (*Topology) ProcessEthereumKeyRotation

func (t *Topology) ProcessEthereumKeyRotation(
	ctx context.Context,
	publicKey string,
	kr *commandspb.EthereumKeyRotateSubmission,
	verify func(message, signature []byte, hexAddress string) error,
) error

func (*Topology) ProcessValidatorHeartbeat

func (t *Topology) ProcessValidatorHeartbeat(ctx context.Context, vh *commandspb.ValidatorHeartbeat,
	verifyZetaSig func(message, signature, pubkey []byte) error,
	verifyEthSig func(message, signature []byte, hexAddress string) error,
) error

ProcessValidatorHeartbeat is verifying the signatures from a validator's transaction and records the status.

func (*Topology) RecalcValidatorSet

func (t *Topology) RecalcValidatorSet(ctx context.Context, epochSeq string, delegationState []*types.ValidatorData, stakeScoreParams types.StakeScoreParams)

RecalcValidatorSet is called at the before a new epoch is started to update the validator sets. the delegation state corresponds to the epoch about to begin.

func (*Topology) ReloadConf

func (t *Topology) ReloadConf(cfg Config)

ReloadConf updates the internal configuration.

func (*Topology) SelfNodeID

func (t *Topology) SelfNodeID() string

func (*Topology) SelfZetaPubKey

func (t *Topology) SelfZetaPubKey() string

func (*Topology) SetIsValidator

func (t *Topology) SetIsValidator()

SetIsValidator will set the flag for `self` so that it is considered a real validator for example, when a node has announced itself and is accepted as a PENDING validator.

func (*Topology) SetNotary

func (t *Topology) SetNotary(notary Notary)

SetNotary this is not good, the topology depends on the notary which in return also depends on the topology... Luckily they do not require recursive calls as for each calls are one offs... anyway we may want to extract the code requiring the notary somewhere else or have different pattern somehow...

func (*Topology) SetSignatures

func (t *Topology) SetSignatures(signatures Signatures)

SetSignatures this is not good, same issue as for SetNotary method. This is only used as a helper for testing..

func (*Topology) Stopped

func (t *Topology) Stopped() bool

func (*Topology) UpdateErsatzValidatorsFactor

func (t *Topology) UpdateErsatzValidatorsFactor(_ context.Context, ersatzFactor num.Decimal) error

UpdateErsatzValidatorsFactor updates the ratio between the tendermint validators list and the ersatz validators list.

func (*Topology) UpdateMinimumEthereumEventsForNewValidator

func (t *Topology) UpdateMinimumEthereumEventsForNewValidator(_ context.Context, minimumEthereumEventsForNewValidator *num.Uint) error

UpdateMinimumEthereumEventsForNewValidator updates the minimum number of events forwarded by / voted for by the joining validator.

func (*Topology) UpdateMinimumRequireSelfStake

func (t *Topology) UpdateMinimumRequireSelfStake(_ context.Context, minStake num.Decimal) error

UpdateMinimumRequireSelfStake updates the minimum requires stake for a validator.

func (*Topology) UpdateNumberEthMultisigSigners

func (t *Topology) UpdateNumberEthMultisigSigners(_ context.Context, numberEthMultisigSigners *num.Uint) error

UpdateNumberEthMultisigSigners updates the required number of multisig signers.

func (*Topology) UpdateNumberOfTendermintValidators

func (t *Topology) UpdateNumberOfTendermintValidators(_ context.Context, noValidators *num.Uint) error

UpdateNumberOfTendermintValidators updates with the quota for tendermint validators. It updates accordingly the number of slots for ersatzvalidators.

func (*Topology) UpdateValidatorIncumbentBonusFactor

func (t *Topology) UpdateValidatorIncumbentBonusFactor(_ context.Context, incumbentBonusFactor num.Decimal) error

UpdateValidatorIncumbentBonusFactor updates with the net param for incumbent bonus, saved as incumbentBonusFactor + 1.

type ValidatorData

type ValidatorData struct {
	ID               string `json:"id"`
	ZetaPubKey       string `json:"zeta_pub_key"`
	ZetaPubKeyIndex  uint32 `json:"zeta_pub_key_index"`
	EthereumAddress  string `json:"ethereum_address"`
	TmPubKey         string `json:"tm_pub_key"`
	InfoURL          string `json:"info_url"`
	Country          string `json:"country"`
	Name             string `json:"name"`
	AvatarURL        string `json:"avatar_url"`
	FromEpoch        uint64 `json:"from_epoch"`
	SubmitterAddress string `json:"submitter_address"`
}

func (ValidatorData) HashZetaPubKey

func (v ValidatorData) HashZetaPubKey() (string, error)

HashZetaPubKey returns hash ZetaPubKey encoded as hex string.

func (ValidatorData) IsValid

func (v ValidatorData) IsValid() bool

type ValidatorMapping

type ValidatorMapping map[string]ValidatorData

ValidatorMapping maps a tendermint pubkey with a zeta pubkey.

type ValidatorPerformance

type ValidatorPerformance interface {
	ValidatorPerformanceScore(address string, votingPower, totalPower int64, performanceScalingFactor num.Decimal) num.Decimal
	BeginBlock(ctx context.Context, proposer string)
	Serialize() *v1.ValidatorPerformance
	Deserialize(*v1.ValidatorPerformance)
	Reset()
}

type ValidatorStatus

type ValidatorStatus int32

type ValidatorTopology

type ValidatorTopology interface {
	IsValidator() bool
	SelfZetaPubKey() string
	AllZetaPubKeys() []string
	IsValidatorZetaPubKey(string) bool
	IsTendermintValidator(string) bool
	GetVotingPower(pubkey string) int64
	GetTotalVotingPower() int64
}

type Verifier

type Verifier interface {
	Verify([]byte, []byte) error
}

type Wallet

type Wallet interface {
	PubKey() crypto.PublicKey
	ID() crypto.PublicKey
	Signer
}

type Witness

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

func NewWitness

func NewWitness(log *logging.Logger, cfg Config, top ValidatorTopology, cmd Commander, tsvc TimeService) (w *Witness)

func (*Witness) AddNodeCheck

func (w *Witness) AddNodeCheck(_ context.Context, nv *commandspb.NodeVote, key crypto.PublicKey) error

AddNodeCheck registers a vote from a validator node for a given resource.

func (*Witness) GetState

func (w *Witness) GetState(k string) ([]byte, []types.StateProvider, error)

func (*Witness) Keys

func (w *Witness) Keys() []string

func (*Witness) LoadState

func (w *Witness) LoadState(_ context.Context, p *types.Payload) ([]types.StateProvider, error)

func (*Witness) Namespace

func (w *Witness) Namespace() types.SnapshotNamespace

func (*Witness) OnDefaultValidatorsVoteRequiredUpdate

func (w *Witness) OnDefaultValidatorsVoteRequiredUpdate(ctx context.Context, d num.Decimal) error

func (*Witness) OnTick

func (w *Witness) OnTick(ctx context.Context, t time.Time)

func (*Witness) ReloadConf

func (w *Witness) ReloadConf(cfg Config)

ReloadConf updates the internal configuration.

func (*Witness) RestoreResource

func (w *Witness) RestoreResource(r Resource, cb func(interface{}, bool)) error

func (*Witness) StartCheck

func (w *Witness) StartCheck(
	r Resource,
	cb func(interface{}, bool),
	checkUntil time.Time,
) error

func (*Witness) Stop

func (w *Witness) Stop()

func (*Witness) Stopped

func (w *Witness) Stopped() bool

Directories

Path Synopsis
mocks
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

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