Skip to content

Reconfig#360

Draft
yacovm wants to merge 5 commits intomainfrom
reconfig
Draft

Reconfig#360
yacovm wants to merge 5 commits intomainfrom
reconfig

Conversation

@yacovm
Copy link
Copy Markdown
Collaborator

@yacovm yacovm commented Mar 23, 2026

No description provided.

@yacovm yacovm marked this pull request as draft March 23, 2026 14:20
@yacovm yacovm force-pushed the reconfig branch 6 times, most recently from 4aa113e to ef4c05f Compare March 30, 2026 14:33
}
require.Greater(t, node.Epoch(), uint64(1))

return
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we return here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, artifact from debugging 🤦‍♂️

Fixed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +20 to +25
type outerBlock struct {
finalization *simplex.Finalization
block metadata.StateMachineBlock
}

type blockStore map[uint64]*outerBlock
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we store helper structs like this in their own files? alongside functions like newStateMachine()?

it's hard to review since im bouncing around these large files trying to separate/review the helpers from the tests

Comment on lines +27 to +33
func (bs blockStore) clone() blockStore {
newStore := make(blockStore)
for k, v := range bs {
newStore[k] = v
}
return newStore
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (bs blockStore) clone() blockStore {
newStore := make(blockStore)
for k, v := range bs {
newStore[k] = v
}
return newStore
}
func (bs blockStore) clone() blockStore {
newStore := make(blockStore)
maps.Copy(newStore, bs)
return newStore
}

return sm.wrapBlock(parentBlock, childBlock, simplexEpochInfo, pChainHeight, simplexMetadata, simplexBlacklist), nil
}

func (sm *StateMachine) createSealingBlock(ctx context.Context, parentBlock StateMachineBlock, simplexMetadata []byte, simplexBlacklist []byte, simplexEpochInfo SimplexEpochInfo, newApprovals *approvals, pChainHeight uint64) (*StateMachineBlock, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newApprovals is unused

})
}

func (nbms NodeBLSMappings) ForEach(selector func(int, NodeBLSMapping)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why can't we just do

for i, nbm := range NodeBLSMappings {
		
}

// GetPChainHeight returns the latest known P-chain height.
GetPChainHeight func() uint64
// GetUpgrades returns the current upgrade configuration.
GetUpgrades func() UpgradeConfig
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im confused by this function. The only time we set it is in a test, and it is set to a noop. If it is not necessary now, can we remove it from this pr?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's needed by ICM epochs

return
}
if selector(i, nbm) {
total, err = safeAdd(total, nbm.Weight)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just return the error right away?

this function feels so ai generated 😅 so much unnecessary complexity for a simple for loop + sum counter. Unless i am missing something?


type ValidatorSetApprovals []ValidatorSetApproval

func (vsa ValidatorSetApprovals) ForEach(f func(int, ValidatorSetApproval)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

}
})
return result
} No newline at end of file
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs a new line at the end

Comment on lines +286 to +308
func (nbms NodeBLSMappings) Equal(other NodeBLSMappings) bool {
if len(nbms) != len(other) {
return false
}

nbmsClone := nbms.Clone()
otherClone := other.Clone()

slices.SortFunc(nbmsClone, func(a, b NodeBLSMapping) int {
return slices.Compare(a.NodeID[:], b.NodeID[:])
})

slices.SortFunc(otherClone, func(a, b NodeBLSMapping) int {
return slices.Compare(a.NodeID[:], b.NodeID[:])
})

for i := range nbmsClone {
if !nbmsClone[i].Equals(&otherClone[i]) {
return false
}
}
return true
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (nbms NodeBLSMappings) Equal(other NodeBLSMappings) bool {
if len(nbms) != len(other) {
return false
}
nbmsClone := nbms.Clone()
otherClone := other.Clone()
slices.SortFunc(nbmsClone, func(a, b NodeBLSMapping) int {
return slices.Compare(a.NodeID[:], b.NodeID[:])
})
slices.SortFunc(otherClone, func(a, b NodeBLSMapping) int {
return slices.Compare(a.NodeID[:], b.NodeID[:])
})
for i := range nbmsClone {
if !nbmsClone[i].Equals(&otherClone[i]) {
return false
}
}
return true
}
func (nbms NodeBLSMappings) Equal(other NodeBLSMappings) bool {
if len(nbms) != len(other) {
return false
}
sortByNodeID := func(a, b NodeBLSMapping) int {
return slices.Compare(a.NodeID[:], b.NodeID[:])
}
nbmsClone := nbms.Clone()
otherClone := other.Clone()
slices.SortFunc(nbmsClone, sortByNodeID)
slices.SortFunc(otherClone, sortByNodeID)
return slices.EqualFunc(nbmsClone, otherClone, func(a, b NodeBLSMapping) bool {
return a.Equals(&b)
})
}

also why don't we just keep these sets sorted? potentially we could require GetValidatorSet to return a sorted array, or create a wrapper like GetSortedValidatorSet which would save us a bunch of slices.SortFunc() calls as equal can be called multiple times

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also we could just store the NodeBLSMappings as a map and then assert equality in linear time rather than sorting both functions and then iterating(O(nlogn) * 2 + n) ?

canotoData canotoData_ValidatorSetApproval
}

type ValidatorSetApprovals []ValidatorSetApproval
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this an array and not a map with the nodeIDs being keys? seems like UniqueByNodeID would benefit from this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not unique. A node may send several approvals, for different payloads.

That's why we filter them:

	newApprovals = newApprovals.Filter(func(i int, approval ValidatorSetApproval) bool {
		// Pick only approvals that agree with our candidate auxiliary info digest and P-Chain height
		return approval.PChainHeight == pChainHeight && approval.AuxInfoSeqDigest == candidateAuxInfoDigest
	})

- The genesis block does not contain the Simplex epoch information, and it is implicitly set to be the zero values of the fields above.


### Auxiliary information encoding
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alongside separating out the ICM stuff from this PR, I think we should also remove the auxillary information to reduce the diff of this PR. thoughts?

Comment on lines +273 to +280
func (fn *fakeNode) tryFinalizeNextBlock() {
nextIndex := len(fn.finalizedBlocks)

if fn.isNextBlockTelock() {
return
}

block := fn.notarizedBlocks[nextIndex]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems like we can some index out of bounds exceptions/nil panics if notarizedBlocks & finalizedBlocks are out of sync. For example this test panics

func TestTryFinalize(t *testing.T) {
	fn := newFakeNode(t)

	fn.tryFinalizeNextBlock()
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any benefit to having the block in two places? can we have something like

 type blockState struct {
      block     metadata.StateMachineBlock
      finalized bool
  }

and then remove the two arrays and just have a singular array in the node state?

Prev [32]byte
}

type fakeNode struct {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im not a fan of the name fakeNode, it is too generic and non-descriptive as it doesn't really tell me much about what the node actually does, i.e. the part being faked.

we already have BasicNode, LongRunningNode, ControlledNode. Maybe MultiEpochNode?

"github.com/stretchr/testify/require"
)

func TestFakeNode(t *testing.T) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test was initially hard to follow. TestFakeNode makes it seem like we are trying to test the fake node implementation, even though we don't really care about testing the fake node implementation. Maybe TestStateMachineEpochTransition is better?

Comment on lines +176 to +177
notarizedBlocks []metadata.StateMachineBlock
finalizedBlocks []metadata.StateMachineBlock
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we consider consolidating these into a single data structure?

sm.maybeInit()

// The zero sequence number is reserved for the genesis block, which should never be built.
if simplexMetadata.Seq == 0 {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add a test for this case?

return block.InnerBlock.Verify(ctx)
}

func (sm *StateMachine) identifyCurrentState(prevBlockSimplexEpochInfo SimplexEpochInfo) (state, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (sm *StateMachine) identifyCurrentState(prevBlockSimplexEpochInfo SimplexEpochInfo) (state, error) {
func epochState(epochInfo SimplexEpochInfo) (state, error) {

i dont think this function should be associated with StateMachine.

return block.InnerBlock.Verify(ctx)
}

func (sm *StateMachine) identifyCurrentState(prevBlockSimplexEpochInfo SimplexEpochInfo) (state, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this suggestion is even better than the previous. Also we don't need to return error regardless

Suggested change
func (sm *StateMachine) identifyCurrentState(prevBlockSimplexEpochInfo SimplexEpochInfo) (state, error) {
func (i *SimplexEpochInfo) CurrentState() state {

we should also probably write tests for this function

}

func computePrevVMBlockSeq(parentBlock StateMachineBlock, prevBlockSeq uint64) uint64 {
// Either our parent block has no inner block, in which case we just inherit its previous VM block sequence,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Either our parent block has no inner block, in which case we just inherit its previous VM block sequence,
// Either our parent block has no inner block, in which case we just inherit its previous VM block sequence.

Comment on lines +225 to +232
case stateFirstSimplexBlock:
return sm.buildBlockZero(ctx, parentBlock, simplexMetadataBytes, simplexBlacklistBytes)
case stateBuildBlockNormalOp:
return sm.buildBlockNormalOp(ctx, parentBlock, simplexMetadataBytes, simplexBlacklistBytes, prevBlockSeq)
case stateBuildCollectingApprovals:
return sm.buildBlockCollectingApprovals(ctx, parentBlock, simplexMetadataBytes, simplexBlacklistBytes, prevBlockSeq)
case stateBuildBlockEpochSealed:
return sm.buildBlockEpochSealed(ctx, parentBlock, simplexMetadataBytes, simplexBlacklistBytes, prevBlockSeq)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we pass in the simplexMetadata as simplex.Metadata instead of bytes, we can remove prevBlockSeq from all the function headers and just use simplexMetadata.Seq - 1. Then when we need the bytes, we should just call simplexMetadata.Bytes()

yacovm added 5 commits April 6, 2026 18:02
This commit adds an implementation of the Metadata State Machine (MSM).
It intercepts block building and verification from the Simplex instance,
and performs or verifies metadata state transitions according to the logic defined in the README.md.

Contains:

 - MSM block building and verification logic
 - Epoch transition (sealing blocks, Telocks, approval collection)
 - Block encoding with canoto
 - Unit and integration tests (fake node test and full epoch lifecycle test)

Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants