diff --git a/hypergraph/sync_client_driven.go b/hypergraph/sync_client_driven.go index 54a0930..051d44e 100644 --- a/hypergraph/sync_client_driven.go +++ b/hypergraph/sync_client_driven.go @@ -648,12 +648,11 @@ func (hg *HypergraphCRDT) syncSubtree( ) error { tree := localSet.GetTree() - // Get local node at same path + // Get local commitment at same path to check if subtrees match var localCommitment []byte - var localNode tries.LazyVectorCommitmentNode if tree != nil && tree.Root != nil { path := toIntSlice(serverBranch.FullPath) - localNode = getNodeAtPath( + localNode := getNodeAtPath( logger, tree.SetType, tree.PhaseType, @@ -673,7 +672,7 @@ func (hg *HypergraphCRDT) syncSubtree( } } - // If commitments match, subtrees are identical + // If commitments match, subtrees are identical - no sync needed if bytes.Equal(localCommitment, serverBranch.Commitment) { logger.Debug("subtree matches", zap.String("path", hex.EncodeToString(packPath(serverBranch.FullPath))), @@ -681,102 +680,15 @@ func (hg *HypergraphCRDT) syncSubtree( return nil } - // If server node is a leaf or has no children, fetch all leaves - if serverBranch.IsLeaf || len(serverBranch.Children) == 0 { - return hg.fetchAndIntegrateLeaves(stream, shardKey, phaseSet, expectedRoot, serverBranch.FullPath, localSet, logger) - } - - // OPTIMIZATION: If we have NO local data at this path, skip the branch-by-branch - // traversal and just fetch all leaves directly. This avoids N round trips for N - // children when we know we need all of them anyway. - if localNode == nil { - logger.Debug("no local data at path, fetching all leaves directly", - zap.String("path", hex.EncodeToString(packPath(serverBranch.FullPath))), - zap.Int("serverChildren", 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 { - path := toIntSlice(serverBranch.FullPath) - if branch, ok := localNode.(*tries.LazyVectorCommitmentBranchNode); ok { - for i := 0; i < 64; i++ { - child := branch.Children[i] - if child == nil { - child, _ = branch.Store.GetNodeByPath( - tree.SetType, - tree.PhaseType, - tree.ShardKey, - slices.Concat(path, []int{i}), - ) - } - if child != nil { - childPath := slices.Concat(path, []int{i}) - child = ensureCommittedNode(logger, tree, childPath, child) - switch c := child.(type) { - case *tries.LazyVectorCommitmentBranchNode: - localChildren[int32(i)] = c.Commitment - case *tries.LazyVectorCommitmentLeafNode: - localChildren[int32(i)] = c.Commitment - } - } - } - } - } - - for _, serverChild := range serverBranch.Children { - localChildCommit := localChildren[serverChild.Index] - - if bytes.Equal(localChildCommit, serverChild.Commitment) { - // Child matches, skip - continue - } - - // Need to sync this child - childPath := append(slices.Clone(serverBranch.FullPath), serverChild.Index) - - // Query for child branch - err := stream.Send(&protobufs.HypergraphSyncQuery{ - Request: &protobufs.HypergraphSyncQuery_GetBranch{ - GetBranch: &protobufs.HypergraphSyncGetBranchRequest{ - ShardKey: shardKey, - PhaseSet: phaseSet, - Path: childPath, - ExpectedRoot: expectedRoot, - }, - }, - }) - if err != nil { - return errors.Wrap(err, "send GetBranch for child") - } - - resp, err := stream.Recv() - if err != nil { - return errors.Wrap(err, "receive GetBranch response for child") - } - - if errResp := resp.GetError(); errResp != nil { - logger.Warn("error getting child branch", - zap.String("error", errResp.Message), - zap.String("path", hex.EncodeToString(packPath(childPath))), - ) - continue - } - - childBranch := resp.GetBranch() - if childBranch == nil { - continue - } - - // Recurse - if err := hg.syncSubtree(stream, shardKey, phaseSet, expectedRoot, childBranch, localSet, logger); err != nil { - return err - } - } - - return nil + // Commitments don't match - fetch all leaves from server. + // This is simpler and more reliable than branch-by-branch comparison, + // ensuring we get the complete correct state from the server. + logger.Debug("subtree mismatch, fetching all leaves from server", + zap.String("path", hex.EncodeToString(packPath(serverBranch.FullPath))), + zap.String("localCommitment", hex.EncodeToString(localCommitment)), + zap.String("serverCommitment", hex.EncodeToString(serverBranch.Commitment)), + ) + return hg.fetchAndIntegrateLeaves(stream, shardKey, phaseSet, expectedRoot, serverBranch.FullPath, localSet, logger) } func (hg *HypergraphCRDT) fetchAndIntegrateLeaves( diff --git a/node/execution/engines/global_execution_engine.go b/node/execution/engines/global_execution_engine.go index a31e564..422f5a7 100644 --- a/node/execution/engines/global_execution_engine.go +++ b/node/execution/engines/global_execution_engine.go @@ -207,7 +207,8 @@ func (e *GlobalExecutionEngine) validateBundle( op.GetReject() != nil || op.GetKick() != nil || op.GetUpdate() != nil || - op.GetShard() != nil + op.GetShard() != nil || + op.GetSeniorityMerge() != nil if !isGlobalOp { if e.config.Network == 0 &&