{"id":17155185,"url":"https://github.com/davidrusu/hashseq","last_synced_at":"2025-07-10T07:04:10.867Z","repository":{"id":43072763,"uuid":"467190232","full_name":"davidrusu/hashseq","owner":"davidrusu","description":"A BFT Sequence CRDT suitable for Permisonless Networks with Unbounded Number of participants","archived":false,"fork":false,"pushed_at":"2023-11-28T18:40:31.000Z","size":175,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-20T13:41:07.904Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidrusu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-07T17:19:35.000Z","updated_at":"2023-12-13T16:07:40.000Z","dependencies_parsed_at":"2023-11-28T19:49:54.265Z","dependency_job_id":null,"html_url":"https://github.com/davidrusu/hashseq","commit_stats":{"total_commits":88,"total_committers":1,"mean_commits":88.0,"dds":0.0,"last_synced_commit":"60b0625d3057738467099bef77e0a601452d1dd3"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/davidrusu/hashseq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidrusu%2Fhashseq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidrusu%2Fhashseq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidrusu%2Fhashseq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidrusu%2Fhashseq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidrusu","download_url":"https://codeload.github.com/davidrusu/hashseq/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidrusu%2Fhashseq/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264545017,"owners_count":23625387,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-14T21:50:53.243Z","updated_at":"2025-07-10T07:04:10.807Z","avatar_url":"https://github.com/davidrusu.png","language":"Rust","readme":"\u003ca href=\"https://crates.io/crates/hashseq\"\u003e\u003cimg src=\"https://img.shields.io/crates/v/hashseq.svg\"\u003e\u003c/a\u003e\n\n# HashSeq\n\nA Byzantine-Fault-Tolerant (BFT) Sequence CRDT suitable for unpermissioned networks with unbounded number of collaborators.\n\n## Merge Semantics\n\n### Concurrent Inserts are not interleaved:\n\n| Site 1 | Site 2  |\n|--------|---------|\n|  hello | goodbye |\n\nOn merge we see:\n\n`hellogoodbye` OR `goodbyehello`\n\n\n### Common Prefix is Deduplicated:\n\n| Site 1 | Site 2  |\n|--------|---------|\n|  hello earth | hello mars |\n\nOn merge we see:\n\n`hello earthmars` OR `hello marsearth`\n\n(i.e. hello is not duplicated even though Site 1 and Site 2 both inserted it.)\n\n### Stable Ordering\nlet _S_,_R_ be HashSeq instances on Site 1, Site 2 respectively.\n\nBoth _S_ and _R_ form a montonic sub-sequence of _Q_ = merge(_S_, _R_).\n\nStated differently, for sequence elements _a_,_b_ ∈ _S_, if _a_ comes before _b_ in _S_, and _a_,_b_ ∈ _R_, then _a_ comes before _b_ in _R_.\n\n## Current Complexity:\n\nAssuming you are using the Cursor interface:\n\n|   op   | time | space |\n|--------|------|-------|\n| insert | O(1) | O(1)  |\n| remove | O(n) | O(n)  |\n| seek   | O(n) | O(n)  |\n\nThese are still WIP, we should be able to get `remove` and `seek` down to O(log(n)) once we have a secondary position index into the ordering tree.\n\n## Design\n\n\nEach edit produces a HashNode containing an Op and some extra dependencies:\n\n```rust\npub enum Op {\n    InsertRoot(char),\n    InsertAfter(Id, char),\n    InsertBefore(Id, char),\n    Remove(Id),\n}\n\npub struct HashNode {\n    extra_dependenciess: BTreeSet\u003cId\u003e,\n    op: Op,\n}\n\nimpl HashNode {\n    fn id(\u0026self) -\u003e Id;\n}\n```\n\n* `InsertRoot` is used when the HashSeq is empty.\n* `InsertAfter(id, char) is used to constrain this `HashNode` to appear after the node with id `id`.\n* `InsertBefore(id, char)` is used to constrain this HashNode to appear before the node with id `id`.\n* `Remove(id)` is used to removing the node with id `id`.\n\n#### Example 1. Writing \"hello\" by appending end\n\n```\nInsertRoot('h')       -- id = 0x0\nInsertAfter(0x0, 'e') -- id = 0x1\nInsertAfter(0x1, 'l') -- id = 0x2\nInsertAfter(0x2, 'l') -- id = 0x3\nInsertAfter(0x3, 'o') -- id = 0x4\n\n  h \u003c- e \u003c- l \u003c- l \u003c- o\n\n-- \"hello\"\n```\n\n\n\n\nEach insert produces a Node holding a value, the hashes of the immediate nodes to the left, and the immediate nodes to the right:\ns\n```\nstruct Node\u003cV\u003e {\n   value: V,\n   lefts: Set\u003cHash\u003e,\n   rights: Set\u003cHash\u003e,\n}\n```\nE.g.\n\nInserting 'a', 'b', 'c' in sequential order produces the graph:\n```\n a \u003c- b \u003c- c\n```\n\nInserting 'd' between 'a' and 'b'\n```\na \u003c- d -\u003e b \u003c- c\n   \\_____/\n```\n\nWe linearize these Hash Graphs by performing a biased topological sort.\n\nThe bias is used to decide a canonical ordering in cases where multiple linearizations satisfy the left/right constraints.\n\nE.g.\n```\n            s - a - m\n           /         \\\nh - i - ' '           ! - !\n           \\         /\n            d - a - n\n\n```\n\nThe above hash-graph can serialize to `hi samdan!!` or `hi dansam` or even any interleaving of sam/dan: `hi sdaamn`, `hi sdanam`, ... . We need a canonical ordering that preserves some semantic information, (i.e. no interleaving of concurrent runs)\n\nThe choice we make is: in a fork, we choose the branch whose starting element has the smaller hash, then to avoid interleaving of concurrent runs, our topological sort runs depth first rather than the traditional breadth first.\n\nSo in the above example, assuming `hash(s)` \u003c `hash(d)`, we'd get is: `hi samdan!!`.\n\n\n## Optimizations:\n\n\nIf we detect hash-chains, we can collabse them to just the first left hashes and the right hashes:\n\n```rust\nstruct Run\u003cT\u003e {\n   run: Vec\u003cT\u003e\n   lefts: Set\u003cHash\u003e\n   rights: Set\u003cHash\u003e\n}\n```\n\ni.e. in the first example, a,b,c are sequential, they all have a common right hand (empty set), and their left hand is the previous element in the sequence.\n\nSo we could represent this as:\n\n```rust\n\n// a \u003c- b \u003c- c == RUN(\"abc\")\n\nRun {\n  run: \"abc\",\n  lefts: {},\n  rights: {}\n}\n\n```\n\nInserting 'd' splits the run:\n\n```\na \u003c- d -\u003e RUN(\"bc\")\n   \\_____/\n```\n\nAnd the fork example:\n\n```\n           RUN(\"sam\")\n          /          \\\nRUN(\"hi \")            RUN(\"!!\")\n          \\          /\n           RUN(\"dan\")\n```\n\nThis way we only store hashes at forks, the rest can be recomputed when necessary.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidrusu%2Fhashseq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidrusu%2Fhashseq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidrusu%2Fhashseq/lists"}