It took a few attempts, but I managed to come up with a merge implementation that worked for both AckSync and SyncRegister, with more than 2 nodes:
Merge(left, right) ==
LET to_keep == {name \in DOMAIN left : (name \notin DOMAIN right \/ left[name] > right[name])}
IN [name \in to_keep |-> left[name]]
RECURSIVE Flatten(_, _, _)
Flatten(keys, struct, acc) ==
IF keys = {} THEN acc
ELSE
LET k == CHOOSE k \in keys: TRUE
IN Flatten(keys \ {k}, struct, acc @@ struct[k])
MergeRegistries(local, remote, remote_node) ==
LET all_registered == Flatten(DOMAIN local, local, << >>)
IN [r \in DOMAIN local |-> CASE
(r = remote_node) -> local[r] @@ Merge(remote, all_registered)
[] OTHER -> Merge(local[r], remote)
]
SyncRegister(n) ==
...
l == MergeRegistries(locally_registered[n], [r \in {message.name} |-> message.time], message.from)
AckSync(n) ==
...
l == MergeRegistries(locally_registered[n], message.local_data, message.from)
and now I can run simulation node (with 5 nodes & more keys), for hours; without failure. I have no idea how much of the search space has been covered though, I suspect not a lot.
And on that note, I think I’m done with this topic 🎉