A node killed while admin_importChain was accepting a round could not start
its C-Chain again: "required historical state unavailable (reexec=2)". The
import wrote the acceptor tip for the whole round first and then committed the
round's tries one block at a time, so a kill in between left a tip on disk
above the accepted pointer, naming blocks whose state was never written.
reprocessState re-executes from the acceptor tip, which it takes to be at or
below the block it recovers, and searches back from there only 2x the commit
interval for a state to start from.
The import now accepts each block the way the acceptor does, through the one
acceptDurably both call: tip, then trie, a block at a time. And reprocessState
moves an acceptor tip that is above the accepted block back to it, which is
what recovers a database an earlier release left that way.
TestImportArchive_KilledAnywhereRestarts kills an import at every write it
makes, reopens from the accepted pointer the VM would read, resumes, and
restarts once more. On the previous code, at commit interval 1, the kill after
write 11 of 48 does not start again.
Refs #152
Co-authored-by: Hanzo Dev <[email protected]>
admin_importChain and --import-chain-data validate blocks with InsertChain
and then move the accepted tip with SetLastAcceptedBlockDirect. Neither step
does what the acceptor does for a block consensus accepts: index its
transactions and hand its trie to the state manager. An archive node therefore
committed only the roots the import force-committed at round ends and indexed
only each round's last block. After a restart every other height's state was a
missing trie node, eth_getTransactionByHash found one transaction per round,
and with commit-interval 1 the startup head repair could not re-execute the tip
from the height before it, so the C-Chain did not start.
AcceptImported does the acceptor's durable work for each inserted block, in
order, through acceptState, which the acceptor now calls too.
TestImportArchive_HistorySurvivesRestart imports in rounds shorter than the
chain, reopens it on the same database, and checks every height's state and
every transaction's lookup. On the previous code it fails at height 1, and with
commit-interval 1 the chain does not reopen.
A database an earlier release imported keeps the gaps; only a fresh import
under this one fills them.
Fixes#152
Co-authored-by: Hanzo Dev <[email protected]>
Build and vet are clean on the go1.27.1 toolchain. Five packages carry
pre-existing test failures -- core, eth/gasprice, internal/ethapi and the
two params/extras suites -- and they fail identically with this change
reverted, including the same invalid chainID 0 panic.
Co-authored-by: Hanzo Dev <[email protected]>
typescript 7.0.2 is the native compiler and exports no JavaScript API, so hardhat,
ts-node and next cannot read it. Both projects take it aliased as tsgo for the
typechecking and keep typescript on the 6 line for the tools; next 16.0.1 in
particular does not recognize a bare 7 pin and runs an unsolicited
`pnpm add -D typescript` mid-build.
contracts/ reports 0 diagnostics under both compilers with strict spelled out —
TS7 turns it on by default and this config never opted in. docs/ reports the same 5
under both, once the side-effect CSS import gets the ambient declaration TS7 now
demands. Neither project had a typecheck script.
Co-authored-by: Hanzo Dev <[email protected]>
One label per runner, spelled <os>-<arch>. The aliases these jobs named
were removed, and a job naming a label no runner carries waits instead of
failing.
Co-authored-by: Hanzo Dev <[email protected]>
One label per runner, spelled <os>-<arch>. The aliases these jobs named
were removed, and a job naming a label no runner carries waits instead of
failing.
Co-authored-by: Hanzo Dev <[email protected]>
precompile v0.19.22 renames the eleven modules that keyed off Verify, Threshold or
Encapsulate, so the fifty-six this registry links now answer to one convention.
Co-authored-by: Hanzo Dev <[email protected]>
This file named 41 suite packages one by one so their init() would run, because
luxd rejects an upgrade.json activation for a configKey it has never seen with
"unknown precompile config". That made it a second record of what the suite
contains, and it had drifted: aivmbridge, swap and v3 registered in the suite and
were absent here, so no Lux chain could activate them however its genesis was
written.
Importing the suite's registry instead cannot drift — that package derives its set
from the packages that actually register rather than from a list. One record, kept
in the repository that owns the thing recorded.
The list was also pinned to suite v0.19.8 while the suite had moved to v0.19.21,
so even the import alone left 14 of 56 modules linked. With the bump the surface
a luxd built from this module dispatches is 56, which is exactly what the suite
holds — measurable rather than asserted: cmd/surface prints it, and it is the
list an upgrade.json is allowed to name.
Co-authored-by: Hanzo Dev <[email protected]>
luxfi/genesis 336c946 removed warpConfig from the mainnet upgrade schedule
and moved requirePrimaryNetworkSigners into the block-zero declaration,
because the EVM refuses to initialise when a live precompile is re-declared
with a different rule — a second declaration is admitted only to disable one.
These tests still asserted the arrangement that commit abandoned, so both
failed against a correct repository: one looked for a warpConfig entry that
is deliberately gone, the other compared a fixture still carrying it.
Red-review finding #8 is unchanged and now checked at its source: warp is
read from configs/mainnet/cchain.json and must require primary-network
signers, and the upgrade schedule must re-declare it nowhere. The mainnet
and testnet fixtures are re-vendored (48 entries each) and the hardcoded
canonical count follows.
The C++ and Rust EVMs are separate luxd plugins. The host spawns every VM with
VM_TRANSPORT=zap (node/vms/rpcchainvm/factory.go) and the cevm binary is
exercised that way cross-process today. Choosing an EVM is choosing which plugin
binary runs -- not a switch inside the Go EVM.
This tree carried a second mechanism for the same choice: an in-process cgo
bridge that linked C++ into the Go binary behind -tags cevm/revm, with a
per-transaction executor registry, an evm_setBackend RPC to flip between
backends at runtime, a resident-store config knob, and a startup log naming the
chosen backend. Its executor returned nil, nil; its tagged build did not compile
(cevm_resident_store.go referenced symbols cevmbridge never defined); and no
build produced a binary that used it. 3,961 lines, gone.
What stays is what the Go EVM actually has: the BlockExecutor seam and
Block-STM behind it (blockstm.go), and the GPU accelerator seam with its
darwin/cgo bridge into luxfi/gpu (gpu_bridge.go). The state processor consults
both and falls through to sequential execution when neither is registered.
Note for the record: nothing in the shipped binary registers a BlockExecutor,
so blocks run sequentially on the Go interpreter. The removed startup log said
"Block-STM across all cores"; it was not describing this binary. Wiring
Block-STM into the production path changes consensus execution and is a
separate decision, made deliberately, with a parity gate.
Co-authored-by: Hanzo Dev <[email protected]>
Three things, all in the import path.
The tree did not build. plugin/evm/vm_database.go names AncientShared and
FreezeThreshold on rawdb.OpenOptions; luxfi/geth v1.20.2 has neither and v1.20.3
has both, so a clean checkout failed to compile before any change. Pinned to
v1.20.3.
The blocks-per-round was the literal 2500 inside importBlocksFromFile, so the
only way to change it was to edit and rebuild. It is a parameter now:
admin_importChain takes an optional count, --import-chain-batch sets it for the
startup path, and defaultImportBatch stands in when neither says. The right
number is a property of the machine -- a host with memory to spare amortises one
InsertChain call over far more blocks -- so it does not belong in the source.
Both importers wrote their own copy of the same closure to advance the accepted
marker and commit. There is one persistAcceptedBlock now and both hand it to the
importer. What genuinely differs between them -- the startup path reconciles the
export frontier and syncs consensus state afterwards, because it runs before the
engine is live -- stays where it belongs.
Co-authored-by: Hanzo Dev <[email protected]>
github.com/luxfi/utils is now github.com/luxfi/util. Import paths and the
module requirement follow the rename, and the luxUtils alias becomes luxUtil.
The old module stays as an indirect requirement that published dependencies
still pull in.
Co-authored-by: Hanzo Dev <[email protected]>
constants.BlackholeAddr is a plain [20]byte, so constants no longer imports
geth. The fourteen sites here all want a geth common.Address, and each file
already imports geth/common, so each takes the free conversion itself.
Only one site was a compile error: the .Bytes() method call in
rewardmanager.DisableFeeRewards. Everywhere else [20]byte is assignable to
common.Address, so the change was invisible to the compiler, and the two
require.Equal assertions in rewardmanager would have compiled and then failed
at run time, because reflect.DeepEqual compares types before bytes. Those two
are wrapped for that reason, not for style.
The two failures in core, TestHeadPin_Across4096Boundary_NoLeak and the
invalid-chainID-0 panic in TestCreateThenDeletePostByzantium, reproduce
identically with these lines reverted and are unrelated.
Co-authored-by: Hanzo Dev <[email protected]>
One family. Font stacks name Zen and fall back to the native faces, tokens
name the role rather than a face, and the packages that used to supply a
face now point at @hanzo/font.
Self-hosted @font-face blocks are removed rather than renamed: rewriting the
family in one would leave a src pointing at a file this repo no longer ships.
@hanzo/font carries the binaries and declares the faces, so the local copies
go with the declaration that used them.
Comments naming a face we no longer use are deleted, not reworded.
luxd serves chain routes under /v1/chain, and the node, cli and sdk releases
already ship it. The deployed gateways still answer the old prefix, so remote
reads break until they are redeployed; the estate moves together rather than
sitting half converted.
Lines that record an observation - a probe, a status code, a past release -
keep the prefix they were written against, as do URLs on hosts that are not
ours. Avalanche endpoints move to /ext/bc, which is the path that host serves.
Co-authored-by: Hanzo Dev <[email protected]>
The rename to /v1/chain is real in luxd source, but the deployed fleet has not
taken it yet: probed just now, api.lux.network, api.lux-test.network,
api.zoo.network and api.hanzo.network each answer 200 on /v1/bc/C/rpc and 404 on
/v1/chain/C/rpc. Composing /v1/chain against those hosts reaches nothing, which
is the failure the rename set out to prevent.
The move belongs with the node redeploy, not ahead of it.
Co-authored-by: Hanzo Dev <[email protected]>
luxd serves chain routes under /v1/chain; the /v1/bc prefix it replaced is
gone, so anything composing it reaches nothing. Lines that record an
observation - a probe, a status code, a past release - keep the prefix they
were written against, as do URLs on hosts that are not ours.
Co-authored-by: Hanzo Dev <[email protected]>
luxd serves chain APIs under one segment from constants.ChainAliasPrefix
(constants v1.6.4, node v1.36.181):
/v1/bc/<alias>/... -> /v1/chain/<alias>/...
/ext/bc/<alias>/... -> /v1/chain/<alias>/...
Observations keep their original addresses: a dated note or a recorded status
code is a fact about when it was written, not an instruction to follow.
A NOTICE was added where the repository forks or republishes upstream work
and carried no repository-level credit for it. MIT requires the copyright
notice to travel with every copy; GPL-3.0 §5(a) requires a modified work to
say that it is modified.
Where LICENSE changed: the FSF's copyright line on the GNU licence text had
been replaced with ours, immediately above the sentence "Everyone is
permitted to copy and distribute verbatim copies of this license document,
but changing it is not allowed." The sibling COPYING.LESSER in the same
repository still carried the correct line, which is how it was found.
This fleet does not publish to GitHub Pages, and the plan does not offer it
for a private repository — creating a Pages site answers 422 — so the job
could not have succeeded on any push it ran for.
Every job in this workflow was the Pages deploy, so the file goes with it.
Sites publish from their hanzo.yml declaration onto the static plane.
This is a tracked file in a public repository. It carried a deployment analysis
naming a cluster, namespace and statefulset, a recovery narrative, transaction
hashes and balances from a running network, an address described as one the
author controls, and a run of dated Problem/Status-FIXED postmortems. That is
operational record, and it belongs to the private one.
The RewardManager section keeps the mechanism, the config that activates it, the
in-process proof, and the per-chain DAO addresses a reader needs to configure it;
what it exercised on a running network is now stated as what was verified rather
than as where and on which pods. The durable facts inside the postmortems are
kept as properties: no beacon chain, so the Cancun blob fields are not part of a
Lux EVM block; a genesis file has to reproduce the chain's block 0; importing
history moves the database under anything reading it.
Also drops "subnet" from two lines describing our own chains.
Co-authored-by: Hanzo Dev <[email protected]>
A receipt root that disagrees stops the node where it stands — that is the
property the placement of this rule exists to prevent, and it holds whether or
not it has ever been reached.
Comment only.
Co-authored-by: Hanzo Dev <[email protected]>
The comment explained the placement by recounting what a node hit when the rule
was enforced during execution. The reason it belongs here does not depend on
that having happened: execution has to be a function of the block alone, and
reading an object out of shared memory while the matching Remove lands at accept
makes it a function of when the block is executed instead.
Behaviour unchanged; comment only.
Co-authored-by: Hanzo Dev <[email protected]>
The ancient store existed in geth and its flags existed on the node, and nothing
joined them: the VM always opened its chain database straight over the key-value
store, so --cchain-ancient reached the C-Chain config and stopped there.
openChainDatabase is that join. Without an ancient directory the key-value store
is the database, as before, and every block the node ever accepted stays in it.
With one, blocks past the freeze threshold live in an append-only store on that
path: the node that owns the directory writes them there and drops its own
copies, and nodes that share the directory read history from it instead of each
keeping a copy. On a machine running many nodes that is the difference between
one copy of the chain and one per node.
The config test was written against an earlier shape, where ancient-read-only
and ancient-shared each required the other — a pair that named one arrangement
twice and was removed. It now pins what is actually enforced: sharing needs a
directory to share, and a store needs a hot window for the block being built to
find its parent. A comment about two writers interleaving one file had been left
sitting above the threshold check, which is not what it describes.
Co-authored-by: Hanzo Dev <[email protected]>
The two settings each required the other, so neither could be given alone and
the pair named one arrangement twice. Reading a store another node writes
already means not writing to it. Nothing branched on ancient-read-only.
Co-authored-by: Hanzo Dev <[email protected]>
parseGenesis names each genesis config key it copies into the extras
config. feeSplitTimestamp was not on that list, so a configured split was
silently dropped and every fee kept burning whole. Name it beside the
other fork timestamps. verifyFeeSplit already refuses a split with no
governed destination, so a misconfigured node fails at parse rather than
at a validator — TestParseGenesis{ReadsFeeSplitTimestamp,RefusesSplit
WithoutRewardManager} pin both halves.
Point the fee-governance test's DAO address at the deployed CREATE2 DAO
Safe 0xF66B025b so the reward destination has one source of truth.
Co-authored-by: Hanzo Dev <[email protected]>
admin_importChain rewrites the chain from a file on disk. It defaulted to
enabled, with a comment saying it was on so that importChain would work, so
every node carried the ability to have its history replaced by anything that
could reach its RPC port -- to spare an operator one line of config during a
restore that happens rarely and is supervised when it does.
A node that is restoring sets admin-api-enabled in its chain config for as long
as the restore takes. Nothing else needs it, and nothing else should offer it.
The tests set the field explicitly and are unaffected.
Co-authored-by: Hanzo Dev <[email protected]>
State sync is skipped when the chain is not far enough ahead of local state,
because bootstrapping would be faster. That trade only exists while
bootstrapping can run at all. It replays blocks against the state beneath them,
so a node missing the state at its own head cannot replay a single block — it
fails to materialize the next one and stops there, whatever the gap is.
On such a node the threshold declines the one mechanism that could repair it,
and small-gap corruption becomes permanent: it cannot execute forward, and it is
not far enough behind to be allowed to sync. The threshold now applies only when
the node holds the state its head is built on. A chain that cannot be read
answers no — the cost of being wrong that way is a sync that was not strictly
needed, against a node that could otherwise never come back.
Co-authored-by: Hanzo Dev <[email protected]>
It was the zero value, so a chain got it only by carrying a per-chain config
file that said so — and most deployments have no chain-config path at all, which
made the one mechanism for rejoining after a deep gap unreachable on whole
fleets. There is no posture where refusing it and re-executing instead is the
right answer, so it is not a decision to hand an operator. The bound is
StateSyncMinBlocks, which lives inside the mechanism: a smaller gap replays and
never reaches this path.
Co-authored-by: Hanzo Dev <[email protected]>