Prioritized and injected transaction sequences - #1605
Open
elopez wants to merge 3 commits into
Open
Conversation
genTx does three things in sequence: resolve the deployed contracts to the ABIs Echidna knows how to call, pick one of them and generate a call to it, then wrap that call in a transaction with a random sender, value and delay. Only the middle step is about choosing what to call; the two ends are the same for any generator that has already decided. Split them into callableContracts, genRandomCall and toTx, leaving genTx as the composition of the three. No behaviour changes -- this is the groundwork for a generator that picks its own call and reuses both ends rather than repeating them. Co-authored-by: gustavo-grieco <gustavo.grieco+github@gmail.com>
A prototype is a SolCall with holes: a function name and an argument list where Nothing marks an argument left for the fuzzer to fill in. It is how something outside the campaign says "call transfer, I don't care with what", or "call transfer with this recipient and any amount". genTxFromPrototype resolves one against the deployed contracts and hands the result to the same tail genTx uses. Matching is on name and arity only: an argument left open carries no type to compare against, so same-arity overloads all qualify and one is picked at random. matchingContracts cuts each contract down to just the signatures that match, which keeps the subsequent pick total -- there is no filtered list that can turn out to be empty after a contract was already chosen. When nothing deployed exposes such a function it falls back to a fully random transaction, so a prototype naming a function that isn't there costs diversity rather than stalling the worker. Nothing produces prototypes yet; the command that injects them follows. Co-authored-by: gustavo-grieco <gustavo.grieco+github@gmail.com>
Add the other half of prototypes: a worker can be handed a sequence of them together with a probability, and will fuzz that sequence instead of a corpus-mutated one that often. This is what lets something outside the campaign spend part of a worker's budget on a specific ordering -- an ordering it has reason to believe is interesting, but that the corpus mutators are unlikely to stumble into. FuzzSequence and ClearPrioritization join the commands a fuzzing worker accepts over the bus, and what they inject accumulates in WorkerState.prioritizedSequences. randseq now chooses between the prioritized path and the standard one; its previous body moves to genStandardSeq unchanged. genPrioritizedSeq turns a sequence of prototypes into a real one. The calls are generated in order with up to maxInterleavedTxs random transactions between consecutive ones, so what gets pinned is the ordering rather than the whole sequence. It is then prefixed with the start of a corpus entry, so the ordering also runs from a state the campaign has already reached -- except on worker 0, which always takes the empty prefix so the initial state stays covered. The result is padded with random transactions, or truncated, to respect seqLen. Nothing sends these commands yet; the client that does arrives with the MCP server. Co-authored-by: gustavo-grieco <gustavo.grieco+github@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A prototype is a
SolCallwith holes: a function name plus an argumentlist where
Nothingmarks an argument the fuzzer should fill in. This PRteaches the generator to build transactions from prototypes, and teaches a
fuzzing worker to accept a sequence of them over the inter-worker bus and
spend part of its budget fuzzing that ordering instead of a corpus-mutated one.
The point is to let something outside the campaign say "this ordering looks
interesting" — an ordering the corpus mutators are unlikely to stumble into on
their own — without pinning everything else about the sequence.
Nothing sends these commands yet; the client that does arrives with the MCP
server. Until then the entry points are
genTxFromPrototypeandgenPrioritizedSeq.Commits
Worth reviewing in order — the first is a pure refactor and carries no
behaviour change:
refactor: factor the shared tail out of genTx—genTxresolved thedeployed contracts, picked one and generated a call, then wrapped it in a
Tx. Only the middle step is about choosing what to call, so the two endsbecome
callableContractsandtoTx, withgenRandomCallin between.feat: generate a transaction from a call prototype—SolCallPrototype,genTxFromPrototype,matchingContracts,genPrototypeCall.feat: prioritize injected call sequences during fuzzing—genPrioritizedSeq,WorkerState.prioritizedSequences, theFuzzSequenceand
ClearPrioritizationcommands, and therandseqsplit.Notes on the design
genPrioritizedSeqgenerates the prototype calls in order with up tomaxInterleavedTxsrandom transactions between consecutive ones, prefixes theresult with the start of a corpus entry so the ordering also runs from a state
the campaign has already reached, then pads or truncates to
seqLen. Worker 0always takes the empty prefix, so the ordering stays covered from the initial
state too.
Matching a prototype against the deployed contracts is on name and arity
only — an argument left open carries no type to compare against, so
same-arity overloads all qualify and one is picked at random. This is
documented at
matchingContracts. When nothing exposes the named function thegenerator falls back to a fully random transaction, so a prototype naming a
function that isn't there costs diversity rather than stalling the worker.
SolCallPrototypelives inTypes.Signaturenext toSolCall, soTransaction,CampaignandInterWorkershare one name for it.genPrioritizedSeqsits next togenTxFromPrototypeinEchidna.Transaction,and the two new command handlers go in
Worker/Command.hsalongside thesampling ones rather than back into
runFuzzWorker'swhereblock.Two things to flag
prioritizedSequencesis uncapped, unlikesampledFunctionswhichmaxSampledFunctionsbounds. It only grows via bus commands andrandseqwalks it with
rElemon the hot path, so a client injecting repeatedly growsit without bound;
ClearPrioritizationis the only reset. Happy to add a capfor symmetry with the sampling handler if reviewers prefer.
seqLen,take seqLendrops its tail, so a prototype longer thanseqLenisonly partly exercised. Pre-existing behaviour, preserved here.
Verification
cabal buildis warning-free,hlint lib srcis clean (the one remaining hintis pre-existing in
SourceMapping.hs), and all 190 tests pass. Each commitbuilds on its own, so the branch stays bisectable.
Since nothing enables the feature until the MCP server lands, the new path was
also driven directly:
seqLen, with the prototype calls presentin order and with the fixed arguments they were given;
call, still at
seqLen;FuzzSequencepushed over the bus breaks anordering-dependent property that a 20,000-tx baseline run does not.