Skip to content

MigrationRegistry cannot record a migration that already ran: appliedAt is always block.timestamp #127

Description

@thedavidmeister

MigrationRegistry.applyMigration stamps block.timestamp and offers no way to say otherwise:

sApplied[msg.sender][migration] = block.timestamp;

So a writer can only record a migration as having been applied now. A deploy script that already ran — before this registry existed on the chain, or before the writer started recording at all — has no way to register itself with the time it actually ran. Its options are to record a time that is wrong, or not to appear in the record at all.

Both of those defeat what the registry is for. applied(writer, migration) is read to answer "when did this writer apply this migration", and a backfilled record that says "today" answers it wrongly for every historical migration. Leaving it out instead makes the namespace's head wrong too, because applyMigration refuses anything not applied onto the current head — so a writer that skipped its past migrations cannot record the next one either, and the whole namespace is stuck at genesis.

Ask

Let the caller supply the applied-at timestamp when registering a migration that already ran.

What this has to settle, and does not yet

  • Which guarantee an author-supplied timestamp actually removes. The record is namespaced by msg.sender and there is no authority, so every entry is already only as trustworthy as the writer that wrote it — a reader asking about a namespace it trusts is reading something only that authority could have written. What a reader can rely on today, and would stop being able to, is that appliedAt is a block time and therefore monotonic and bounded by the block it landed in.
  • Whether a supplied timestamp may be in the future, and whether it must be greater than the namespace's previous record. Append-only per writer is currently a property of the head, not of the times; a settable timestamp makes "the record goes forwards" a separate thing to decide.
  • Whether this is the same entry point or a second one. applyMigration(expectedHead, migration) is the live signature; a appliedAt parameter changes it, and an overload leaves two ways to write one record.
  • ZeroTimestamp becomes a caller-input check, not an environment one. Zero is currently refused because applied reads it as "never applied" while the head has moved — that reasoning is unchanged, but the check moves from guarding against a zero-genesis chain to guarding against a caller passing zero, which is the ordinary case rather than the exotic one.

Cost of the change

None of the kind first assumed here. This section originally claimed the
registry was deployed and pinned by consumers, so a signature change meant
IMigrationRegistryV2 and a consumer migration. That is false: no release has
ever been cut. src/generated/ holds only candidate/, and
LibMigrationRegistryReleased.releasedSuites() returns an empty array. The
sol-v* tags are soldeer package releases and pin no deployed address.

With an empty release record there is nothing to stay compatible with, so the
change lands in place on MigrationRegistry / IMigrationRegistryV1 /
LibMigrationRegistry. See the correction comment below for the full evidence
and the settled answers to the four questions above.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions