fix: the disjoint leaf/branch sync case

This commit is contained in:
Cassandra Heart 2026-01-30 17:54:46 -06:00
parent 97069eafa9
commit 0b8fba1b07
No known key found for this signature in database
GPG Key ID: 371083BFA6C240AA

View File

@ -689,6 +689,17 @@ func (hg *HypergraphCRDT) syncSubtree(
// Log divergence for global prover sync
isGlobalProver := isGlobalProverShardBytes(shardKey)
var localNodeType string
switch localNode.(type) {
case *tries.LazyVectorCommitmentBranchNode:
localNodeType = "branch"
case *tries.LazyVectorCommitmentLeafNode:
localNodeType = "leaf"
case nil:
localNodeType = "nil"
default:
localNodeType = "unknown"
}
if isGlobalProver {
logger.Info("global prover sync: commitment divergence",
zap.String("phase", phaseSet.String()),
@ -697,6 +708,7 @@ func (hg *HypergraphCRDT) syncSubtree(
zap.String("local_commitment", hex.EncodeToString(localCommitment)),
zap.String("server_commitment", hex.EncodeToString(serverBranch.Commitment)),
zap.Bool("local_has_data", localNode != nil),
zap.String("local_node_type", localNodeType),
zap.Int("server_children", len(serverBranch.Children)),
zap.Bool("server_is_leaf", serverBranch.IsLeaf),
)
@ -713,6 +725,18 @@ func (hg *HypergraphCRDT) syncSubtree(
return hg.fetchAndIntegrateLeaves(stream, shardKey, phaseSet, expectedRoot, serverBranch.FullPath, localSet, logger)
}
// Structural mismatch: local is a leaf but server is a branch with children.
// We can't compare children because local has none - fetch all server leaves.
if _, isLeaf := localNode.(*tries.LazyVectorCommitmentLeafNode); isLeaf {
if isGlobalProver {
logger.Info("global prover sync: structural mismatch - local leaf vs server branch, fetching leaves",
zap.Int("path_depth", len(serverBranch.FullPath)),
zap.Int("server_children", len(serverBranch.Children)),
)
}
return hg.fetchAndIntegrateLeaves(stream, shardKey, phaseSet, expectedRoot, serverBranch.FullPath, localSet, logger)
}
// Compare children and recurse
localChildren := make(map[int32][]byte)
if tree != nil && tree.Root != nil {
@ -742,14 +766,37 @@ func (hg *HypergraphCRDT) syncSubtree(
}
}
if isGlobalProver {
logger.Info("global prover sync: comparing children",
zap.Int("path_depth", len(serverBranch.FullPath)),
zap.Int("local_children_count", len(localChildren)),
zap.Int("server_children_count", len(serverBranch.Children)),
)
}
childrenMatched := 0
childrenToSync := 0
for _, serverChild := range serverBranch.Children {
localChildCommit := localChildren[serverChild.Index]
if bytes.Equal(localChildCommit, serverChild.Commitment) {
// Child matches, skip
// Both nil/empty means we have no data on either side - skip
// But if server has a commitment and we don't (or vice versa), we need to sync
localEmpty := len(localChildCommit) == 0
serverEmpty := len(serverChild.Commitment) == 0
if localEmpty && serverEmpty {
// Neither side has data, skip
childrenMatched++
continue
}
if bytes.Equal(localChildCommit, serverChild.Commitment) {
// Child matches, skip
childrenMatched++
continue
}
childrenToSync++
// Need to sync this child
childPath := append(slices.Clone(serverBranch.FullPath), serverChild.Index)
@ -792,6 +839,14 @@ func (hg *HypergraphCRDT) syncSubtree(
}
}
if isGlobalProver {
logger.Info("global prover sync: children comparison complete",
zap.Int("path_depth", len(serverBranch.FullPath)),
zap.Int("matched", childrenMatched),
zap.Int("synced", childrenToSync),
)
}
return nil
}
@ -804,9 +859,17 @@ func (hg *HypergraphCRDT) fetchAndIntegrateLeaves(
localSet hypergraph.IdSet,
logger *zap.Logger,
) error {
logger.Debug("fetching leaves",
zap.String("path", hex.EncodeToString(packPath(path))),
)
isGlobalProver := isGlobalProverShardBytes(shardKey)
if isGlobalProver {
logger.Info("global prover sync: fetching leaves",
zap.String("path", hex.EncodeToString(packPath(path))),
zap.Int("path_depth", len(path)),
)
} else {
logger.Debug("fetching leaves",
zap.String("path", hex.EncodeToString(packPath(path))),
)
}
var continuationToken []byte
totalFetched := 0
@ -887,6 +950,13 @@ func (hg *HypergraphCRDT) fetchAndIntegrateLeaves(
continuationToken = leavesResp.ContinuationToken
}
if isGlobalProver {
logger.Info("global prover sync: leaves integrated",
zap.String("path", hex.EncodeToString(packPath(path))),
zap.Int("total_fetched", totalFetched),
)
}
return nil
}