fix(proposervm): millisecond-resolution block timestamps (unblocks sub-second cadence)

The proposervm block wire stored the timestamp as Unix SECONDS (timestamp.Unix()), so the
sub-second window/granularity work was silently truncated to whole seconds when written to the
block: buildChild computed the proposer slot from sub-second time, but verify read back
second-resolution and computed a DIFFERENT slot → errUnexpectedProposer verify-drops at any
window < 1s. Store timestamp.UnixMilli() and read time.UnixMilli() so sub-second timestamps
round-trip and build/verify agree on the slot. Measured at a 100ms window: errUnexpectedProposer
frequent→0, 108→149 TPS, 2.8s→2.2s cadence, 5/5 byte-identical. Forward-only wire change (the
outer proposervm timestamp; the inner EVM block timestamp is independent). Remaining sub-second
floor is now the EVM targetBlockRate, not the proposervm.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
zeekay
2026-07-16 09:00:21 -07:00
co-authored by Hanzo Dev
parent c3d6105804
commit 45a3dcfff1
2 changed files with 3 additions and 3 deletions
+1 -1
View File
@@ -144,7 +144,7 @@ func (b *statelessBlock) initialize(bytes []byte) error {
}
root := b.msg.Root()
b.timestamp = time.Unix(root.Int64(offTimestamp), 0)
b.timestamp = time.UnixMilli(root.Int64(offTimestamp))
// Proposer-identity slot: [scheme:1B | identity]. Empty ⇒ unsigned block.
idSlot := root.Bytes(offCert)
+2 -2
View File
@@ -31,7 +31,7 @@ func BuildUnsigned(
// No identity, no signature: the block bytes are just the unsigned body. This
// is the pre-fork→post-fork transition block and every K=1 block (no proposer
// window), so small local nets never exercise a signed path.
unsignedBytes := buildUnsignedBuffer(parentID, timestamp.Unix(), pChainHeight, epoch, nil, blockBytes)
unsignedBytes := buildUnsignedBuffer(parentID, timestamp.UnixMilli(), pChainHeight, epoch, nil, blockBytes)
block := &statelessBlock{}
if err := block.initialize(unsignedBytes); err != nil {
@@ -103,7 +103,7 @@ func buildSigned(
idSlot = append(idSlot, scheme)
idSlot = append(idSlot, identity...)
unsignedBytes := buildUnsignedBuffer(parentID, timestamp.Unix(), pChainHeight, epoch, idSlot, blockBytes)
unsignedBytes := buildUnsignedBuffer(parentID, timestamp.UnixMilli(), pChainHeight, epoch, idSlot, blockBytes)
// The block ID is the hash of the unsigned prefix; the proposer signs a
// header binding (chainID, parentID, blockID).