chaincode

package
v0.0.0-...-ea48f79 Latest Latest
Warning

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

Go to latest
Published: Oct 3, 2019 License: Apache-2.0 Imports: 33 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// Mutual TLS auth client key and cert paths in the chaincode container
	TLSClientKeyPath      string = "/etc/hyperledger/fabric/client.key"
	TLSClientCertPath     string = "/etc/hyperledger/fabric/client.crt"
	TLSClientRootCertPath string = "/etc/hyperledger/fabric/peer.crt"
)
View Source
const DevModeUserRunsChaincode string = "dev"

DevModeUserRunsChaincode enables chaincode execution in a development environment

View Source
const (
	// InitializedKeyName is the reserved key in a chaincode's namespace which
	// records the ID of the chaincode which initialized the namespace.
	// In this way, we can enforce Init exactly once semantics, whenever
	// the backing chaincode bytes change (but not be required to re-initialize
	// the chaincode say, when endorsement policy changes).
	InitializedKeyName = "\x00" + string(utf8.MaxRune) + "initialized"
)

Variables

This section is empty.

Functions

func IsDevMode

func IsDevMode() bool

IsDevMode returns true if the peer was configured with development-mode enabled.

func NewTxKey

func NewTxKey(channelID, txID string) string

func ParseName

func ParseName(ccName string) *sysccprovider.ChaincodeInstance

ParseName parses a chaincode name into a ChaincodeInstance. The name should be of the form "chaincode-name:version/channel-name" with optional elements.

Types

type ACLProvider

type ACLProvider interface {
	CheckACL(resName string, channelID string, idinfo interface{}) error
}

An ACLProvider performs access control checks when invoking chaincode.

type ActiveTransactions

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

func NewActiveTransactions

func NewActiveTransactions() *ActiveTransactions

func (*ActiveTransactions) Add

func (a *ActiveTransactions) Add(channelID, txID string) bool

func (*ActiveTransactions) Remove

func (a *ActiveTransactions) Remove(channelID, txID string)

type ApplicationConfigRetriever

type ApplicationConfigRetriever interface {
	// GetApplicationConfig returns the channelconfig.Application for the channel
	// and whether the Application config exists
	GetApplicationConfig(cid string) (channelconfig.Application, bool)
}

ApplicationConfigRetriever to retrieve the application configuration for a channel

type CCProviderImpl

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

ccProviderImpl is an implementation of the ccprovider.ChaincodeProvider interface

func NewProvider

func NewProvider(cs *ChaincodeSupport) *CCProviderImpl

func (*CCProviderImpl) ExecuteLegacyInit

ExecuteLegacyInit executes a chaincode which is not in the LSCC table

func (*CCProviderImpl) Stop

Stop stops the chaincode given context and spec

type CertGenerator

type CertGenerator interface {
	// Generate returns a certificate and private key and associates
	// the hash of the certificates with the given chaincode name
	Generate(ccName string) (*accesscontrol.CertAndPrivKeyPair, error)
}

CertGenerator generates client certificates for chaincode.

type ChaincodeDefinitionGetter

type ChaincodeDefinitionGetter interface {
	ChaincodeDefinition(channelID, chaincodeName string, txSim ledger.SimpleQueryExecutor) (ccprovider.ChaincodeDefinition, error)
}

ChaincodeDefinitionGetter is responsible for retrieving a chaincode definition from the system. The definition is used by the InstantiationPolicyChecker.

type ChaincodeSupport

type ChaincodeSupport struct {
	Keepalive              time.Duration
	ExecuteTimeout         time.Duration
	UserRunsCC             bool
	Runtime                Runtime
	ACLProvider            ACLProvider
	HandlerRegistry        *HandlerRegistry
	Launcher               Launcher
	SystemCCProvider       sysccprovider.SystemChaincodeProvider
	Lifecycle              Lifecycle
	AppConfig              ApplicationConfigRetriever
	HandlerMetrics         *HandlerMetrics
	LaunchMetrics          *LaunchMetrics
	DeployedCCInfoProvider ledger.DeployedChaincodeInfoProvider
}

ChaincodeSupport responsible for providing interfacing with chaincodes from the Peer.

func NewChaincodeSupport

func NewChaincodeSupport(
	config *Config,
	peerAddress string,
	userRunsCC bool,
	caCert []byte,
	certGenerator CertGenerator,
	packageProvider PackageProvider,
	lifecycle Lifecycle,
	aclProvider ACLProvider,
	processor Processor,
	SystemCCProvider sysccprovider.SystemChaincodeProvider,
	platformRegistry *platforms.Registry,
	appConfig ApplicationConfigRetriever,
	metricsProvider metrics.Provider,
	deployedCCInfoProvider ledger.DeployedChaincodeInfoProvider,
) *ChaincodeSupport

NewChaincodeSupport creates a new ChaincodeSupport instance.

func (*ChaincodeSupport) CheckInit

func (cs *ChaincodeSupport) CheckInit(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput) (bool, error)

func (*ChaincodeSupport) Execute

Execute invokes chaincode and returns the original response.

func (*ChaincodeSupport) ExecuteLegacyInit

ExecuteLegacyInit is a temporary method which should be removed once the old style lifecycle is entirely deprecated. Ideally one release after the introduction of the new lifecycle. It does not attempt to start the chaincode based on the information from lifecycle, but instead accepts the container information directly in the form of a ChaincodeDeploymentSpec.

func (*ChaincodeSupport) HandleChaincodeStream

func (cs *ChaincodeSupport) HandleChaincodeStream(stream ccintf.ChaincodeStream) error

HandleChaincodeStream implements ccintf.HandleChaincodeStream for all vms to call with appropriate stream

func (*ChaincodeSupport) Invoke

Invoke will invoke chaincode and return the message containing the response. The chaincode will be launched if it is not already running.

func (*ChaincodeSupport) Launch

func (cs *ChaincodeSupport) Launch(chainID string, ccci *ccprovider.ChaincodeContainerInfo) (*Handler, error)

Launch starts executing chaincode if it is not already running. This method blocks until the peer side handler gets into ready state or encounters a fatal error. If the chaincode is already running, it simply returns.

func (*ChaincodeSupport) LaunchInit

LaunchInit bypasses getting the chaincode spec from the LSCC table as in the case of v1.0-v1.2 lifecycle, the chaincode will not yet be defined in the LSCC table

func (*ChaincodeSupport) Register

Register the bidi stream entry point called by chaincode to register with the Peer.

func (*ChaincodeSupport) Stop

Stop stops a chaincode if running.

type CheckInstantiationPolicyFunc

type CheckInstantiationPolicyFunc func(name, version string, cd *ccprovider.ChaincodeData) error

Adapter from function to InstantiationPolicyChecker interface.

func (CheckInstantiationPolicyFunc) CheckInstantiationPolicy

func (c CheckInstantiationPolicyFunc) CheckInstantiationPolicy(name, version string, cd *ccprovider.ChaincodeData) error

type CollectionACLCache

type CollectionACLCache map[string]*readWritePermission

CollectionACLCache encapsulates a cache that stores read write permission on a collection

type Config

type Config struct {
	TLSEnabled     bool
	Keepalive      time.Duration
	ExecuteTimeout time.Duration
	StartupTimeout time.Duration
	LogFormat      string
	LogLevel       string
	ShimLogLevel   string
}

func GlobalConfig

func GlobalConfig() *Config

type ContainerRuntime

type ContainerRuntime struct {
	CertGenerator    CertGenerator
	Processor        Processor
	CACert           []byte
	CommonEnv        []string
	PeerAddress      string
	PlatformRegistry *platforms.Registry
}

ContainerRuntime is responsible for managing containerized chaincode.

func (*ContainerRuntime) LaunchConfig

func (c *ContainerRuntime) LaunchConfig(packageID string, ccType string) (*LaunchConfig, error)

LaunchConfig creates the LaunchConfig for chaincode running in a container.

func (*ContainerRuntime) Start

func (c *ContainerRuntime) Start(ccci *ccprovider.ChaincodeContainerInfo, codePackage []byte) error

Start launches chaincode in a runtime environment.

func (*ContainerRuntime) Stop

Stop terminates chaincode and its container runtime environment.

func (*ContainerRuntime) Wait

Wait waits for the container runtime to terminate.

type ContextRegistry

type ContextRegistry interface {
	Create(txParams *ccprovider.TransactionParams) (*TransactionContext, error)
	Get(chainID, txID string) *TransactionContext
	Delete(chainID, txID string)
	Close()
}

A ContextRegistry is responsible for managing transaction contexts.

type Handler

type Handler struct {
	// Keepalive specifies the interval at which keep-alive messages are sent.
	Keepalive time.Duration
	// SystemCCVersion specifies the current system chaincode version
	SystemCCVersion string
	// DefinitionGetter is used to retrieve the chaincode definition from the
	// Lifecycle System Chaincode.
	DefinitionGetter ChaincodeDefinitionGetter
	// Invoker is used to invoke chaincode.
	Invoker Invoker
	// Registry is used to track active handlers.
	Registry Registry
	// ACLProvider is used to check if a chaincode invocation should be allowed.
	ACLProvider ACLProvider
	// TXContexts is a collection of TransactionContext instances
	// that are accessed by channel name and transaction ID.
	TXContexts ContextRegistry
	// activeTransactions holds active transaction identifiers.
	ActiveTransactions TransactionRegistry
	// SystemCCProvider provides access to system chaincode metadata
	SystemCCProvider SystemCCProvider
	// InstantiationPolicyChecker is used to evaluate the chaincode instantiation policies.
	InstantiationPolicyChecker InstantiationPolicyChecker
	// QueryResponeBuilder is used to build query responses
	QueryResponseBuilder QueryResponseBuilder
	// LedgerGetter is used to get the ledger associated with a channel
	LedgerGetter LedgerGetter
	// DeployedCCInfoProvider is used to initialize the Collection Store
	DeployedCCInfoProvider ledger.DeployedChaincodeInfoProvider
	// UUIDGenerator is used to generate UUIDs
	UUIDGenerator UUIDGenerator
	// AppConfig is used to retrieve the application config for a channel
	AppConfig ApplicationConfigRetriever

	// Metrics holds chaincode handler metrics
	Metrics *HandlerMetrics
	// contains filtered or unexported fields
}

Handler implements the peer side of the chaincode stream.

func (*Handler) Close

func (h *Handler) Close()

func (*Handler) Execute

func (*Handler) HandleDelState

func (h *Handler) HandleDelState(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

func (*Handler) HandleGetHistoryForKey

func (h *Handler) HandleGetHistoryForKey(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

Handles query to ledger history db

func (*Handler) HandleGetPrivateDataHash

func (h *Handler) HandleGetPrivateDataHash(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

func (*Handler) HandleGetQueryResult

func (h *Handler) HandleGetQueryResult(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

Handles query to ledger to execute query state

func (*Handler) HandleGetState

func (h *Handler) HandleGetState(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

Handles query to ledger to get state

func (*Handler) HandleGetStateByRange

func (h *Handler) HandleGetStateByRange(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

Handles query to ledger to rage query state

func (*Handler) HandleGetStateMetadata

func (h *Handler) HandleGetStateMetadata(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

Handles query to ledger to get state metadata

func (*Handler) HandleInvokeChaincode

func (h *Handler) HandleInvokeChaincode(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

Handles requests that modify ledger state

func (*Handler) HandlePutState

func (h *Handler) HandlePutState(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

func (*Handler) HandlePutStateMetadata

func (h *Handler) HandlePutStateMetadata(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

func (*Handler) HandleQueryStateClose

func (h *Handler) HandleQueryStateClose(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

Handles the closing of a state iterator

func (*Handler) HandleQueryStateNext

func (h *Handler) HandleQueryStateNext(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error)

Handles query to ledger for query state next

func (*Handler) HandleRegister

func (h *Handler) HandleRegister(msg *pb.ChaincodeMessage)

handleRegister is invoked when chaincode tries to register.

func (*Handler) HandleTransaction

func (h *Handler) HandleTransaction(msg *pb.ChaincodeMessage, delegate handleFunc)

HandleTransaction is a middleware function that obtains and verifies a transaction context prior to forwarding the message to the provided delegate. Response messages returned by the delegate are sent to the chat stream. Any errors returned by the delegate are packaged as chaincode error messages.

func (*Handler) Notify

func (h *Handler) Notify(msg *pb.ChaincodeMessage)

func (*Handler) ProcessStream

func (h *Handler) ProcessStream(stream ccintf.ChaincodeStream) error

func (*Handler) State

func (h *Handler) State() State

type HandlerMetrics

type HandlerMetrics struct {
	ShimRequestsReceived  metrics.Counter
	ShimRequestsCompleted metrics.Counter
	ShimRequestDuration   metrics.Histogram
	ExecuteTimeouts       metrics.Counter
}

func NewHandlerMetrics

func NewHandlerMetrics(p metrics.Provider) *HandlerMetrics

type HandlerRegistry

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

HandlerRegistry maintains chaincode Handler instances.

func NewHandlerRegistry

func NewHandlerRegistry(allowUnsolicitedRegistration bool) *HandlerRegistry

NewHandlerRegistry constructs a HandlerRegistry.

func (*HandlerRegistry) Deregister

func (r *HandlerRegistry) Deregister(packageID ccintf.CCID) error

Deregister clears references to state associated specified chaincode. As part of the cleanup, it closes the handler so it can cleanup any state. If the registry does not contain the provided handler, an error is returned.

func (*HandlerRegistry) Failed

func (r *HandlerRegistry) Failed(packageID ccintf.CCID, err error)

Failed indicates that registration of a launched chaincode has failed.

func (*HandlerRegistry) Handler

func (r *HandlerRegistry) Handler(packageID ccintf.CCID) *Handler

Handler retrieves the handler for a chaincode instance.

func (*HandlerRegistry) Launching

func (r *HandlerRegistry) Launching(packageID ccintf.CCID) (*LaunchState, bool)

Launching indicates that chaincode is being launched. The LaunchState that is returned provides mechanisms to determine when the operation has completed and whether or not it failed. The bool indicates whether or not the chaincode has already been started.

func (*HandlerRegistry) Ready

func (r *HandlerRegistry) Ready(packageID ccintf.CCID)

Ready indicates that the chaincode registration has completed and the READY response has been sent to the chaincode.

func (*HandlerRegistry) Register

func (r *HandlerRegistry) Register(h *Handler) error

Register adds a chaincode handler to the registry. An error will be returned if a handler is already registered for the chaincode. An error will also be returned if the chaincode has not already been "launched", and unsolicited registration is not allowed.

type InstantiationPolicyChecker

type InstantiationPolicyChecker interface {
	CheckInstantiationPolicy(name, version string, cd *ccprovider.ChaincodeData) error
}

InstantiationPolicyChecker is used to evaluate instantiation policies.

type Invoker

type Invoker interface {
	Invoke(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, spec *pb.ChaincodeInput) (*pb.ChaincodeMessage, error)
}

An Invoker invokes chaincode.

type LaunchConfig

type LaunchConfig struct {
	Args  []string
	Envs  []string
	Files map[string][]byte
}

LaunchConfig holds chaincode launch arguments, environment variables, and files.

func (*LaunchConfig) String

func (lc *LaunchConfig) String() string

type LaunchMetrics

type LaunchMetrics struct {
	LaunchDuration metrics.Histogram
	LaunchFailures metrics.Counter
	LaunchTimeouts metrics.Counter
}

func NewLaunchMetrics

func NewLaunchMetrics(p metrics.Provider) *LaunchMetrics

type LaunchRegistry

type LaunchRegistry interface {
	Launching(packageID ccintf.CCID) (launchState *LaunchState, started bool)
	Deregister(packageID ccintf.CCID) error
}

LaunchRegistry tracks launching chaincode instances.

type LaunchState

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

func NewLaunchState

func NewLaunchState() *LaunchState

func (*LaunchState) Done

func (l *LaunchState) Done() <-chan struct{}

func (*LaunchState) Err

func (l *LaunchState) Err() error

func (*LaunchState) Notify

func (l *LaunchState) Notify(err error)

type Launcher

type Launcher interface {
	Launch(ccci *ccprovider.ChaincodeContainerInfo) error
}

Launcher is used to launch chaincode runtimes.

type LedgerGetter

type LedgerGetter interface {
	GetLedger(cid string) ledger.PeerLedger
}

LedgerGetter is used to get ledgers for chaincode.

type Lifecycle

type Lifecycle interface {
	// ChaincodeDefinition returns the details for a chaincode by name
	ChaincodeDefinition(channelID, chaincodeName string, qe ledger.SimpleQueryExecutor) (ccprovider.ChaincodeDefinition, error)

	// ChaincodeContainerInfo returns the package necessary to launch a chaincode
	ChaincodeContainerInfo(channelID, chaincodeName string, qe ledger.SimpleQueryExecutor) (*ccprovider.ChaincodeContainerInfo, error)
}

Lifecycle provides a way to retrieve chaincode definitions and the packages necessary to run them

type MessageHandler

type MessageHandler interface {
	Handle(*pb.ChaincodeMessage, *TransactionContext) (*pb.ChaincodeMessage, error)
}

type PackageProvider

type PackageProvider interface {
	GetChaincodeCodePackage(ccci *ccprovider.ChaincodeContainerInfo) ([]byte, error)
}

PackageProvider gets chaincode packages from the filesystem.

type PendingQueryResult

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

func (*PendingQueryResult) Add

func (p *PendingQueryResult) Add(queryResult commonledger.QueryResult) error

func (*PendingQueryResult) Cut

func (*PendingQueryResult) Size

func (p *PendingQueryResult) Size() int

type Processor

type Processor interface {
	Process(vmtype string, req container.VMCReq) error
}

Processor processes vm and container requests.

type QueryResponseBuilder

type QueryResponseBuilder interface {
	BuildQueryResponse(txContext *TransactionContext, iter commonledger.ResultsIterator,
		iterID string, isPaginated bool, totalReturnLimit int32) (*pb.QueryResponse, error)
}

QueryResponseBuilder is responsible for building QueryResponse messages for query transactions initiated by chaincode.

type QueryResponseGenerator

type QueryResponseGenerator struct {
	MaxResultLimit int
}

func (*QueryResponseGenerator) BuildQueryResponse

func (q *QueryResponseGenerator) BuildQueryResponse(txContext *TransactionContext, iter commonledger.ResultsIterator,
	iterID string, isPaginated bool, totalReturnLimit int32) (*pb.QueryResponse, error)

BuildQueryResponse takes an iterator and fetch state to construct QueryResponse

type Registry

type Registry interface {
	Register(*Handler) error
	Ready(ccintf.CCID)
	Failed(ccintf.CCID, error)
	Deregister(ccintf.CCID) error
}

A Registry is responsible for tracking handlers.

type Runtime

type Runtime interface {
	Start(ccci *ccprovider.ChaincodeContainerInfo, codePackage []byte) error
	Stop(ccci *ccprovider.ChaincodeContainerInfo) error
	Wait(ccci *ccprovider.ChaincodeContainerInfo) (int, error)
}

Runtime is used to manage chaincode runtime instances.

type RuntimeLauncher

type RuntimeLauncher struct {
	Runtime         Runtime
	Registry        LaunchRegistry
	PackageProvider PackageProvider
	StartupTimeout  time.Duration
	Metrics         *LaunchMetrics
}

RuntimeLauncher is responsible for launching chaincode runtimes.

func (*RuntimeLauncher) Launch

type State

type State int
const (
	Created State = iota
	Established
	Ready
)

func (State) String

func (s State) String() string

type SystemCCProvider

type SystemCCProvider interface {
	IsSysCC(name string) bool
	IsSysCCAndNotInvokableCC2CC(name string) bool
}

SystemCCProvider provides system chaincode metadata.

type TransactionContext

type TransactionContext struct {
	ChainID              string
	NamespaceID          string
	SignedProp           *pb.SignedProposal
	Proposal             *pb.Proposal
	ResponseNotifier     chan *pb.ChaincodeMessage
	TXSimulator          ledger.TxSimulator
	HistoryQueryExecutor ledger.HistoryQueryExecutor
	CollectionStore      privdata.CollectionStore
	IsInitTransaction    bool

	// cache used to save the result of collection acl
	// as a transactionContext is created for every chaincode
	// invoke (even in case of chaincode-calling-chaincode,
	// we do not need to store the namespace in the map and
	// collection alone is sufficient.
	CollectionACLCache CollectionACLCache
	// contains filtered or unexported fields
}

func (*TransactionContext) CleanupQueryContext

func (t *TransactionContext) CleanupQueryContext(queryID string)

func (*TransactionContext) CleanupQueryContextWithBookmark

func (t *TransactionContext) CleanupQueryContextWithBookmark(queryID string) string

func (*TransactionContext) CloseQueryIterators

func (t *TransactionContext) CloseQueryIterators()

func (*TransactionContext) GetPendingQueryResult

func (t *TransactionContext) GetPendingQueryResult(queryID string) *PendingQueryResult

func (*TransactionContext) GetQueryIterator

func (t *TransactionContext) GetQueryIterator(queryID string) commonledger.ResultsIterator

func (*TransactionContext) GetTotalReturnCount

func (t *TransactionContext) GetTotalReturnCount(queryID string) *int32

func (*TransactionContext) InitializeCollectionACLCache

func (t *TransactionContext) InitializeCollectionACLCache()

func (*TransactionContext) InitializeQueryContext

func (t *TransactionContext) InitializeQueryContext(queryID string, iter commonledger.ResultsIterator)

type TransactionContexts

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

TransactionContexts maintains active transaction contexts for a Handler.

func NewTransactionContexts

func NewTransactionContexts() *TransactionContexts

NewTransactionContexts creates a registry for active transaction contexts.

func (*TransactionContexts) Close

func (c *TransactionContexts) Close()

Close closes all query iterators assocated with the context.

func (*TransactionContexts) Create

Create creates a new TransactionContext for the specified chain and transaction ID. An error is returned when a transaction context has already been created for the specified chain and transaction ID.

func (*TransactionContexts) Delete

func (c *TransactionContexts) Delete(chainID, txID string)

Delete removes the transaction context associated with the specified chain and transaction ID.

func (*TransactionContexts) Get

func (c *TransactionContexts) Get(chainID, txID string) *TransactionContext

Get retrieves the transaction context associated with the chain and transaction ID.

type TransactionRegistry

type TransactionRegistry interface {
	Add(channelID, txID string) bool
	Remove(channelID, txID string)
}

TransactionRegistry tracks active transactions for each channel.

type UUIDGenerator

type UUIDGenerator interface {
	New() string
}

UUIDGenerator is responsible for creating unique query identifiers.

type UUIDGeneratorFunc

type UUIDGeneratorFunc func() string

func (UUIDGeneratorFunc) New

func (u UUIDGeneratorFunc) New() string

Directories

Path Synopsis
Code generated by counterfeiter.
Code generated by counterfeiter.
mock
Code generated by counterfeiter.
Code generated by counterfeiter.
Code generated by counterfeiter.
Code generated by counterfeiter.
mock
Code generated by counterfeiter.
Code generated by counterfeiter.
mock
Code generated by counterfeiter.
Code generated by counterfeiter.

Jump to

Keyboard shortcuts

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