├── .editorconfig ├── .github └── workflows │ └── erlang.yml ├── .gitignore ├── AUTHORS.TXT ├── LICENSE ├── LICENSE.TXT ├── Makefile ├── README.md ├── elvis.config ├── include └── antidote_crdt.hrl ├── rebar.config ├── rebar.config.script ├── rebar.lock ├── rebar3 ├── src ├── antidote_crdt.app.src ├── antidote_crdt.erl ├── antidote_crdt_counter_b.erl ├── antidote_crdt_counter_fat.erl ├── antidote_crdt_counter_pn.erl ├── antidote_crdt_flag_dw.erl ├── antidote_crdt_flag_ew.erl ├── antidote_crdt_flag_helper.erl ├── antidote_crdt_map_go.erl ├── antidote_crdt_map_rr.erl ├── antidote_crdt_register_lww.erl ├── antidote_crdt_register_mv.erl ├── antidote_crdt_secure_counter_pn.erl ├── antidote_crdt_set_aw.erl ├── antidote_crdt_set_go.erl └── antidote_crdt_set_rw.erl └── test ├── crdt_properties.erl ├── prop_counter_fat.erl ├── prop_counter_pn.erl ├── prop_flag_dw.erl ├── prop_flag_ew.erl ├── prop_map_go.erl ├── prop_map_rr.erl ├── prop_register_lww.erl ├── prop_register_mv.erl ├── prop_set_aw.erl ├── prop_set_go.erl └── prop_set_rw.erl /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig file: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | # 4 space indentation 14 | [*.{erl, src, hrl}] 15 | indent_style = space 16 | indent_size = 4 17 | -------------------------------------------------------------------------------- /.github/workflows/erlang.yml: -------------------------------------------------------------------------------- 1 | name: Erlang CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | erlang: [ 21,22,23,24 ] 16 | 17 | container: 18 | image: erlang:${{ matrix.erlang }} 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - run: make compile 23 | 24 | lint: 25 | runs-on: ubuntu-latest 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | erlang: [ 21,22,23,24 ] 30 | 31 | container: 32 | image: erlang:${{ matrix.erlang }} 33 | 34 | steps: 35 | - uses: actions/checkout@v2 36 | - run: make lint 37 | 38 | verify: 39 | runs-on: ubuntu-latest 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | erlang: [ 21,22,23,24 ] 44 | 45 | container: 46 | image: erlang:${{ matrix.erlang }} 47 | 48 | steps: 49 | - uses: actions/checkout@v2 50 | - run: make dialyzer 51 | #- run: make xref 52 | 53 | test: 54 | runs-on: ubuntu-latest 55 | strategy: 56 | fail-fast: false 57 | matrix: 58 | erlang: [ 21,22,23,24 ] 59 | 60 | container: 61 | image: erlang:${{ matrix.erlang }} 62 | 63 | steps: 64 | - uses: actions/checkout@v2 65 | - name: Unit Tests 66 | run: make test 67 | - name: Proper Tests 68 | run: make proper 69 | - name: Coverage 70 | run: make coverage 71 | - name: ls 72 | run: ls _build/test/cover 73 | - name: Send Coverage 74 | env: 75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 76 | run: ./rebar3 as test coveralls send 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | test/.rebar3/ 3 | TEST-*.xml 4 | *.swp 5 | src/*.swp 6 | test/*.swp 7 | *.beam 8 | doc/ -------------------------------------------------------------------------------- /AUTHORS.TXT: -------------------------------------------------------------------------------- 1 | Antidote software has been developed by: 2 | ===================================== 3 | 4 | Annette Bieniusa (Technische Universität Kaiserslautern, Germany) 5 | Deepthi Akkoorath (Technische Universität Kaiserslautern, Germany) 6 | Christopher S. MeikleJohn (Université catholique de Louvain (UCL), Belgique) 7 | Tyler Crain (Université Pierre et Marie Curie / Sorbonne-Université, France) 8 | Alejandro Tomsic (Université Pierre et Marie Curie / Sorbonne-Université, France) 9 | Zhongmiao Li (Université catholique de Louvain (UCL), Belgique) 10 | Manuel Bravo (Université catholique de Louvain (UCL), Belgique) 11 | Michał Jabczyński 12 | Albert Schimpf (Technische Universität Kaiserslautern, Germany) 13 | Peter Zeller (Technische Universität Kaiserslautern, Germany) 14 | Valter Balegas (Universidade NOVA de Lisboa, Portugal) 15 | Santiago Alvarez Colombo 16 | Mathias Weber (Technische Universität Kaiserslautern, Germany) 17 | Diogo Serra 18 | Mike Rudek 19 | Roger Pueyo 20 | Borja 21 | Shraddha Barke (Technische Universität Kaiserslautern) 22 | Vitor Enes (Universidade do Minho & INESC TEC, Portugal) 23 | Georges Younes (Universidade do Minho & INESC TEC, Portugal) 24 | Goncalo Tomas 25 | João Neto 26 | Ilyas Toumlilt 27 | Paolo Viotti 28 | Nuno Preguiça (Universidade NOVA de Lisboa, Portugal) 29 | Carlos Baquero (Universidade do Minho & INESC TEC, Portugal) 30 | Marc Shapiro (Université Pierre et Marie Curie / Sorbonne-Université, France) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | Antidote Licensing 2 | ============================================ 3 | 4 | This file attempts to include all licenses that apply within Antidote (this program) source trees AntidoteDB/antidote and AntidoteDB/antidote_crdt, in particular any that are supposed to be exposed to the end user for credit requirements for instance. 5 | 6 | Antidote :A planet scale, highly available, transactional database built on CRDT technology. 7 | 8 | 9 | Copyright <2013-2018> < 10 | Technische Universität Kaiserslautern, Germany 11 | Université Pierre et Marie Curie / Sorbonne-Université, France 12 | Universidade NOVA de Lisboa, Portugal 13 | Université catholique de Louvain (UCL), Belgique 14 | INESC TEC, Portugal 15 | > 16 | 17 | The list of the contributors to the development of Antidote is given in the AUTHORS file. 18 | 19 | Website of Antidote: https://www.antidotedb.eu 20 | Contact: info@antidotedb.com 21 | 22 | Antidote General 23 | ----------------------------------------- 24 | 25 | Licensed under the Apache License, Version 2.0 (the "License"); 26 | you may not use this file except in compliance with the License. 27 | You may obtain a copy of the License at 28 | 29 | http://www.apache.org/licenses/LICENSE-2.0 30 | 31 | Unless required by applicable law or agreed to in writing, software 32 | distributed under the License is distributed on an "AS IS" BASIS, 33 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | See the License for the specific language governing permissions and 35 | limitations under the License. 36 | ************************************************************************* 37 | 38 | [The documentation for this program is under a creative commons attribution share-alike 39 | 4.0 license. http://creativecommons.org/licenses/by-sa/4.0/ 40 | The documentation can be found here: https://antidotedb.gitbook.io/documentation/] 41 | ************************************************************************* 42 | 43 | This program has dependencies on other libraries and Website dependencies which are or not under Apache License, Version 2.0 and that are commonly distributed with this program core libraries. 44 | 45 | Dependencies are as follows: 46 | 47 | riak_core Apache 2.0 48 | erlzmq MIT 49 | elli MIT 50 | prometheus MIT 51 | prometheus_process_collector MIT 52 | elli_prometheus BSD-3 53 | rand_compat Apache 2.0 54 | lager Apache 2.0 55 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR = $(shell pwd)/rebar3 2 | COVERPATH = ./_build/test/cover 3 | 4 | .PHONY: compile clean lint test proper coverage shell docs xref dialyzer typer \ 5 | 6 | all: compile 7 | 8 | compile: 9 | ${REBAR} compile 10 | 11 | clean: 12 | ${REBAR} clean 13 | 14 | lint: 15 | ${REBAR} lint 16 | 17 | test: 18 | ${REBAR} eunit 19 | 20 | proper: 21 | ${REBAR} proper -n 1000 22 | 23 | coverage: 24 | cp _build/proper+test/cover/eunit.coverdata ${COVERPATH}/proper.coverdata ;\ 25 | ${REBAR} cover --verbose 26 | 27 | shell: 28 | ${REBAR} shell --apps antidote_crdt 29 | 30 | docs: 31 | ${REBAR} edoc 32 | 33 | xref: 34 | ${REBAR} xref 35 | 36 | dialyzer: 37 | ${REBAR} dialyzer 38 | 39 | typer: 40 | typer --annotate -I ../ --plt $(PLT) -r src 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archive of Antidote CRDT library 2 | 3 | Current version is included at https://github.com/AntidoteDB/antidote 4 | -------------------------------------------------------------------------------- /elvis.config: -------------------------------------------------------------------------------- 1 | %% https://github.com/inaka/elvis_core/wiki/Rules 2 | [{elvis, 3 | [{config, 4 | [ 5 | #{dirs => ["src", "src/**", "test", "test/**"], 6 | filter => "*.erl", 7 | rules => 8 | [ 9 | %% {elvis_style, line_length, 10 | %% #{ignore => [], 11 | %% limit => 100, 12 | %% skip_comments => false}}, 13 | {elvis_style, no_tabs}, 14 | {elvis_style, no_trailing_whitespace, #{ignore_empty_lines => false}}, 15 | {elvis_style, macro_names, #{ignore => []}}, 16 | %% {elvis_style, macro_module_names}, 17 | { 18 | elvis_style, 19 | operator_spaces, 20 | #{rules => 21 | [ 22 | {right, ","}, 23 | {right, "--"}, 24 | {left, "--"}, 25 | {right, "++"}, 26 | {left, "++"} 27 | ]} 28 | }, 29 | {elvis_style, nesting_level, #{level => 3, ignore => []}}, 30 | {elvis_style, god_modules, 31 | #{limit => 25, 32 | ignore => []}}, 33 | %% TODO Maybe consider removing if expressions in the future 34 | %% {elvis_style, no_if_expression}, 35 | {elvis_style, no_nested_try_catch, #{ignore => []}}, 36 | %% {elvis_style, invalid_dynamic_call, 37 | %% #{ignore => []}}, 38 | {elvis_style, used_ignored_variable, #{ignore => []}}, 39 | {elvis_style, no_behavior_info}, 40 | { 41 | elvis_style, 42 | module_naming_convention, 43 | #{regex => "^[a-z]([a-z0-9]*_?)*(_SUITE)?$", 44 | ignore => []} 45 | }, 46 | { 47 | elvis_style, 48 | function_naming_convention, 49 | #{regex => "^([a-z][a-z0-9]*_?)*$", 50 | %% TODO Can be added back once functions are renamed 51 | ignore => [antidote_crdt_counter_b, crdt_properties, prop_map_go, prop_map_rr, prop_register_lww, prop_set_aw, prop_set_rw]} 52 | }, 53 | { 54 | elvis_style, 55 | variable_naming_convention, 56 | %% TODO Can be added back once variables are renamed 57 | #{regex => "^_?([A-Z][0-9a-zA-Z]*)$", 58 | ignore => [antidote_crdt_set_aw]} 59 | }, 60 | {elvis_style, state_record_and_type}, 61 | {elvis_style, no_spec_with_records}, 62 | %% TODO Cound be a useful check in the future 63 | %% {elvis_style, dont_repeat_yourself, #{min_complexity => 10}}, 64 | %% {elvis_style, max_module_length, #{max_length => 500, ignore => [], count_comments => false, count_whitespace => false}}, 65 | %% {elvis_style, max_function_length, #{max_length => 30, 66 | %% count_comments => false, 67 | %% count_whitespace => false, 68 | %% ignore_functions => []}}, 69 | %% {elvis_style, no_call, #{ignore => [], no_call_functions => []}}, 70 | {elvis_style, no_common_caveats_call, #{ignore => [], 71 | caveat_functions => [{timer, send_after,2}, 72 | {timer, send_after,3}, 73 | {timer, send_interval,2}, 74 | {timer, send_interval,3}, 75 | {erlang, size, 1}]}}, 76 | {elvis_style, no_debug_call, #{ignore => [crdt_properties], 77 | debug_functions => [{ct, pal}, {ct, print}, {io, format, 1}, {io, format, 2}]}} 78 | ] 79 | }, 80 | #{dirs => ["."], 81 | filter => "Makefile", 82 | ruleset => makefiles 83 | }, 84 | #{dirs => ["."], 85 | filter => "rebar.config", 86 | ruleset => rebar_config 87 | }, 88 | #{dirs => ["."], 89 | filter => "elvis.config", 90 | ruleset => elvis_config 91 | } 92 | ]}, 93 | {verbose, true}] 94 | }]. 95 | -------------------------------------------------------------------------------- /include/antidote_crdt.hrl: -------------------------------------------------------------------------------- 1 | -type crdt() :: term(). 2 | -type update() :: {atom(), term()}. 3 | -type effect() :: term(). 4 | -type value() :: term(). 5 | -type reason() :: term(). 6 | 7 | -export_type([ crdt/0, 8 | update/0, 9 | effect/0, 10 | value/0 11 | ]). 12 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {dialyzer_base_plt_apps, [kernel, stdlib, erts, sasl, ssl, tools, os_mon, runtime_tools, inets, xmerl, webtool, eunit, syntax_tools, compiler, crypto, mnesia, public_key, snmp]}. 2 | {erl_opts, [debug_info, warnings_as_errors, {platform_define, "^[0-9]+", namespaced_types}]}. 3 | {eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}. 4 | {xref_checks, [undefined_function_calls]}. 5 | {edoc_opts, [{preprocess, true}]}. 6 | 7 | {project_plugins, 8 | [ 9 | rebar3_proper, 10 | {rebar3_lint, "0.1.10"} 11 | ]}. 12 | 13 | {profiles, [ 14 | {test, [ 15 | {deps, [{proper, "1.3.0"}]}, 16 | {erl_opts, [nowarn_export_all]}, 17 | {plugins, [coveralls]} 18 | ]} 19 | ]}. 20 | 21 | {cover_enabled, true}. 22 | {cover_export_enabled, true}. 23 | {coveralls_coverdata, "_build/test/cover/*.coverdata"}. 24 | {coveralls_service_name, "github"}. 25 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | case {os:getenv("GITHUB_ACTIONS"), os:getenv("GITHUB_TOKEN")} of 2 | {"true", Token} when is_list(Token) -> 3 | CONFIG1 = [{coveralls_repo_token, Token}, 4 | {coveralls_service_job_id, os:getenv("GITHUB_RUN_ID")}, 5 | {coveralls_commit_sha, os:getenv("GITHUB_SHA")}, 6 | {coveralls_service_number, os:getenv("GITHUB_RUN_NUMBER")} | CONFIG], 7 | case os:getenv("GITHUB_EVENT_NAME") =:= "pull_request" 8 | andalso string:tokens(os:getenv("GITHUB_REF"), "/") of 9 | [_, "pull", PRNO, _] -> 10 | [{coveralls_service_pull_request, PRNO} | CONFIG1]; 11 | _ -> 12 | CONFIG1 13 | end; 14 | _ -> 15 | CONFIG 16 | end. 17 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AntidoteDB/antidote_crdt/7afb16f830d615bca53b90b06d0b959ee4758552/rebar3 -------------------------------------------------------------------------------- /src/antidote_crdt.app.src: -------------------------------------------------------------------------------- 1 | {application, antidote_crdt, 2 | [ 3 | {description, "An Erlang CRDT library"}, 4 | {vsn, "0.1.3"}, 5 | {modules, [ 6 | antidote_crdt 7 | ]}, 8 | {registered, []}, 9 | {applications, [ 10 | kernel, 11 | stdlib 12 | ]}, 13 | {env, []}, 14 | {maintainers, ["Annette Bieniusa", "Goncalo Tomas", "Peter Zeller", "Albert Schimpf"]}, 15 | {licenses, ["Apache 2.0"]}, 16 | {links,[{"Github","https://github.com/AntidoteDB/antidote_crdt"}]} 17 | ]}. 18 | -------------------------------------------------------------------------------- /src/antidote_crdt.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% antidote_crdt.erl : behaviour for op-based CRDTs 30 | %% Naming pattern of antidote crdts: _ 31 | %% if there is only one kind of semantics implemented for a certain type 32 | %% only the type is used in the name e.g. rga 33 | %% counter_pn: PN-Counter aka Positive Negative Counter 34 | %% counter_b: Bounded Counter 35 | %% counter_fat: Fat Counter 36 | %% integer: Integer (Experimental) 37 | %% flag_ew: Enable Wins Flag aka EW-Flag 38 | %% flag_dw: Disable Wins Flag DW-Flag 39 | %% set_go: Grow Only Set aka G-Set 40 | %% set_aw: Add Wins Set aka AW-Set, previously OR-Set (Observed Remove Set) 41 | %% set_rw: Remove Wins Set aka RW-Set 42 | %% register_lww: Last Writer Wins Register aka LWW-Reg 43 | %% register_mv: MultiValue Register aka MV-Reg 44 | %% map_go: Grow Only Map aka G-Map 45 | %% map_aw: Add Wins Map aka AW-Map (Experimental) 46 | %% map_rr: Recursive Resets Map aka RR-Map 47 | %% rga: Replicated Growable Array (Experimental) 48 | 49 | 50 | 51 | -module(antidote_crdt). 52 | 53 | -include("antidote_crdt.hrl"). 54 | 55 | -export([to_binary/1, from_binary/1, dict_to_orddict/1]). 56 | 57 | % The CRDTs supported by Antidote: 58 | -type typ() :: 59 | antidote_crdt_counter_pn 60 | | antidote_crdt_counter_b 61 | | antidote_crdt_counter_fat 62 | | antidote_crdt_secure_counter_pn 63 | | antidote_crdt_flag_ew 64 | | antidote_crdt_flag_dw 65 | | antidote_crdt_set_go 66 | | antidote_crdt_set_aw 67 | | antidote_crdt_set_rw 68 | | antidote_crdt_register_lww 69 | | antidote_crdt_register_mv 70 | | antidote_crdt_map_go 71 | | antidote_crdt_map_rr. 72 | 73 | -type internal_crdt() :: term(). 74 | -type internal_effect() :: term(). 75 | 76 | -export([is_type/1, alias/1, new/1, value/2, downstream/3, update/3, require_state_downstream/2, is_operation/2]). 77 | 78 | % Callbacks implemented by each concrete CRDT implementation 79 | -callback new() -> internal_crdt(). 80 | -callback value(internal_crdt()) -> value(). 81 | -callback downstream(update(), internal_crdt()) -> {ok, internal_effect()} | {error, reason()}. 82 | -callback update(internal_effect(), internal_crdt()) -> {ok, internal_crdt()}. 83 | -callback require_state_downstream(update()) -> boolean(). 84 | -callback is_operation(update()) -> boolean(). %% Type check 85 | 86 | -callback equal(internal_crdt(), internal_crdt()) -> boolean(). 87 | -callback to_binary(internal_crdt()) -> binary(). 88 | -callback from_binary(binary()) -> {ok, internal_crdt()} | {error, reason()}. 89 | 90 | % Check if the given type is supported by Antidote 91 | -spec is_type(typ()) -> boolean(). 92 | is_type(antidote_crdt_counter_pn) -> true; 93 | is_type(antidote_crdt_counter_b) -> true; 94 | is_type(antidote_crdt_counter_fat) -> true; 95 | is_type(antidote_crdt_flag_ew) -> true; 96 | is_type(antidote_crdt_flag_dw) -> true; 97 | is_type(antidote_crdt_set_go) -> true; 98 | is_type(antidote_crdt_set_aw) -> true; 99 | is_type(antidote_crdt_set_rw) -> true; 100 | is_type(antidote_crdt_register_lww) -> true; 101 | is_type(antidote_crdt_register_mv) -> true; 102 | is_type(antidote_crdt_map_go) -> true; 103 | is_type(antidote_crdt_map_rr) -> true; 104 | is_type(antidote_crdt_secure_counter_pn) -> true; 105 | is_type(antidote_crdt_secure_set_go) -> true; 106 | is_type(antidote_crdt_secure_set_aw) -> true; 107 | is_type(antidote_crdt_secure_set_rw) -> true; 108 | is_type(antidote_crdt_secure_register_lww) -> true; 109 | is_type(antidote_crdt_secure_register_mv) -> true; 110 | is_type(antidote_crdt_secure_map_go) -> true; 111 | is_type(antidote_crdt_secure_map_rr) -> true; 112 | is_type(_) -> false. 113 | 114 | % Makes it possible to map multiple CRDT types to one CRDT implementation. 115 | -spec alias(typ()) -> typ(). 116 | alias(antidote_crdt_secure_set_go) -> antidote_crdt_set_go; 117 | alias(antidote_crdt_secure_set_aw) -> antidote_crdt_set_aw; 118 | alias(antidote_crdt_secure_set_rw) -> antidote_crdt_set_rw; 119 | alias(antidote_crdt_secure_register_lww) -> antidote_crdt_register_lww; 120 | alias(antidote_crdt_secure_register_mv) -> antidote_crdt_register_mv; 121 | alias(antidote_crdt_secure_map_go) -> antidote_crdt_map_go; 122 | alias(antidote_crdt_secure_map_rr) -> antidote_crdt_map_rr; 123 | alias(Type) -> Type. 124 | 125 | % Returns the initial CRDT state for the given Type 126 | -spec new(typ()) -> crdt(). 127 | new(Type) -> 128 | T = antidote_crdt:alias(Type), 129 | true = is_type(T), 130 | T:new(). 131 | 132 | % Reads the value from a CRDT state 133 | -spec value(typ(), crdt()) -> any(). 134 | value(Type, State) -> 135 | T = antidote_crdt:alias(Type), 136 | true = is_type(T), 137 | T:value(State). 138 | 139 | % Computes the downstream effect for a given update operation and current state. 140 | % This has to be called once at the source replica. 141 | % The effect must then be applied on all replicas using the update function. 142 | % For some update operation it is not necessary to provide the current state 143 | % and the atom 'ignore' can be passed instead (see function require_state_downstream). 144 | -spec downstream(typ(), update(), crdt() | ignore) -> {ok, effect()} | {error, reason()}. 145 | downstream(Type, Update, State) -> 146 | T = antidote_crdt:alias(Type), 147 | true = is_type(T), 148 | true = T:is_operation(Update), 149 | T:downstream(Update, State). 150 | 151 | % Updates the state of a CRDT by applying a downstream effect calculated 152 | % using the downstream function. 153 | % For most types the update function must be called in causal order: 154 | % if Eff2 was calculated on a state where Eff1 was already replied, 155 | % then Eff1 has to be applied before Eff2 on all replicas. 156 | -spec update(typ(), effect(), crdt()) -> {ok, crdt()}. 157 | update(Type, Effect, State) -> 158 | T = antidote_crdt:alias(Type), 159 | true = is_type(T), 160 | T:update(Effect, State). 161 | 162 | % Checks whether the current state is required by the downstream function 163 | % for a specific type and update operation 164 | -spec require_state_downstream(typ(), update()) -> boolean(). 165 | require_state_downstream(Type, Update) -> 166 | T = antidote_crdt:alias(Type), 167 | true = is_type(T), 168 | T:require_state_downstream(Update). 169 | 170 | % Checks whether the given update operation is valid for the given type 171 | -spec is_operation(typ(), update()) -> boolean(). 172 | is_operation(Type, Update) -> 173 | T = antidote_crdt:alias(Type), 174 | true = is_type(T), 175 | T:is_operation(Update). 176 | 177 | -spec to_binary(crdt()) -> binary(). 178 | to_binary(Term) -> 179 | Opts = case application:get_env(antidote_crdt, binary_compression, 1) of 180 | true -> [compressed]; 181 | N when N >= 0, N =< 9 -> [{compressed, N}]; 182 | _ -> [] 183 | end, 184 | term_to_binary(Term, Opts). 185 | 186 | -spec from_binary(binary()) -> crdt(). 187 | from_binary(Binary) -> 188 | binary_to_term(Binary). 189 | 190 | 191 | %% turns a dict into a sorted list of [{key, value}] 192 | -spec dict_to_orddict(dict:dict()) -> orddict:orddict(). 193 | dict_to_orddict(Dict) -> 194 | lists:sort(dict:to_list(Dict)). 195 | -------------------------------------------------------------------------------- /src/antidote_crdt_counter_b.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | 28 | %% @doc 29 | %% An operation based implementation of the bounded counter CRDT. 30 | %% This counter is able to maintain a non-negative value by 31 | %% explicitly exchanging permissions to execute decrement operations. 32 | %% All operations on this CRDT are monotonic and do not keep extra tombstones. 33 | %% 34 | %% In the code, the variable `V' is used for a positive integer. 35 | %% In the code, the variable `P' is used for `transfers()' which is a defined type. 36 | %% In the code, the variable `D' is used for `decrements()' which is a defined type. 37 | %% @end 38 | 39 | -module(antidote_crdt_counter_b). 40 | 41 | -behaviour(antidote_crdt). 42 | 43 | -include("antidote_crdt.hrl"). 44 | 45 | %% Call backs 46 | -export([new/0, 47 | value/1, 48 | downstream/2, 49 | update/2, 50 | equal/2, 51 | to_binary/1, 52 | from_binary/1, 53 | is_operation/1, 54 | require_state_downstream/1, 55 | generate_downstream_check/4 56 | ]). 57 | 58 | %% API 59 | -export([localPermissions/2, 60 | permissions/1 61 | ]). 62 | 63 | -ifdef(TEST). 64 | -include_lib("eunit/include/eunit.hrl"). 65 | -endif. 66 | 67 | -type id() :: term(). %% A replica's identifier. 68 | -type transfers() :: orddict:orddict({id(), id()}, pos_integer()). %% The orddict that maps 69 | -type decrements() :: orddict:orddict(id(), pos_integer()). 70 | -type antidote_crdt_counter_b() :: {transfers(), decrements()}. 71 | -type antidote_crdt_counter_b_op() :: {increment | decrement, {pos_integer(), id()}} | {transfer, {pos_integer(), id(), id()}}. 72 | -type antidote_crdt_counter_b_effect() :: {{increment | decrement, pos_integer()} | {transfer, pos_integer(), id()}, id()}. 73 | 74 | %% @doc Return a new, empty `antidote_crdt_counter_b()'. 75 | -spec new() -> antidote_crdt_counter_b(). 76 | new() -> 77 | {orddict:new(), orddict:new()}. 78 | 79 | %% @doc Return the available permissions of replica `Id' in a `antidote_crdt_counter_b()'. 80 | -spec localPermissions(id(), antidote_crdt_counter_b()) -> non_neg_integer(). 81 | localPermissions(Id, {P, D}) -> 82 | Received = orddict:fold( 83 | fun 84 | (_, V, Acc) -> 85 | Acc + V 86 | end, 87 | 0, orddict:filter( 88 | fun 89 | ({_, ToId}, _) when ToId == Id -> 90 | true; 91 | (_, _) -> 92 | false 93 | end, P)), 94 | Granted = orddict:fold( 95 | fun 96 | (_, V, Acc) -> 97 | Acc + V 98 | end, 0, orddict:filter( 99 | fun 100 | ({FromId, ToId}, _) when FromId == Id andalso ToId /= Id -> 101 | true; 102 | (_, _) -> 103 | false 104 | end, P)), 105 | case orddict:find(Id, D) of 106 | {ok, Decrements} -> 107 | Received - Granted - Decrements; 108 | error -> 109 | Received - Granted 110 | end. 111 | 112 | %% @doc Return the total available permissions in a `antidote_crdt_counter_b()'. 113 | -spec permissions(antidote_crdt_counter_b()) -> non_neg_integer(). 114 | permissions({P, D}) -> 115 | TotalIncrements = orddict:fold( 116 | fun 117 | ({K, K}, V, Acc) -> 118 | V + Acc; 119 | (_, _, Acc) -> 120 | Acc 121 | end, 0, P), 122 | TotalDecrements = orddict:fold( 123 | fun 124 | (_, V, Acc) -> 125 | V + Acc 126 | end, 0, D), 127 | TotalIncrements - TotalDecrements. 128 | 129 | %% @doc Return the read value of a given `antidote_crdt_counter_b()', itself. 130 | -spec value(antidote_crdt_counter_b()) -> antidote_crdt_counter_b(). 131 | value(Counter) -> Counter. 132 | 133 | %% @doc Generate a downstream operation. 134 | %% The first parameter is either `{increment, pos_integer()}' or `{decrement, pos_integer()}', 135 | %% which specify the operation and amount, or `{transfer, pos_integer(), id()}' 136 | %% that additionally specifies the target replica. 137 | %% The second parameter is an `actor()' who identifies the source replica, 138 | %% and the third parameter is a `antidote_crdt_counter_b()' which holds the current snapshot. 139 | %% 140 | %% Returns a tuple containing the operation and source replica. 141 | %% This operation fails and returns `{error, no_permissions}' 142 | %% if it tries to consume resources unavailable to the source replica 143 | %% (which prevents logging of forbidden attempts). 144 | -spec downstream(antidote_crdt_counter_b_op(), antidote_crdt_counter_b()) -> {ok, antidote_crdt_counter_b_effect()} | {error, no_permissions}. 145 | downstream({increment, {V, Actor}}, _Counter) when is_integer(V), V > 0 -> 146 | {ok, {{increment, V}, Actor}}; 147 | downstream({decrement, {V, Actor}}, Counter) when is_integer(V), V > 0 -> 148 | generate_downstream_check({decrement, V}, Actor, Counter, V); 149 | downstream({transfer, {V, ToId, Actor}}, Counter) when is_integer(V), V > 0 -> 150 | generate_downstream_check({transfer, V, ToId}, Actor, Counter, V). 151 | 152 | generate_downstream_check(Op, Actor, Counter, V) -> 153 | Available = localPermissions(Actor, Counter), 154 | if Available >= V -> {ok, {Op, Actor}}; 155 | Available < V -> {error, no_permissions} 156 | end. 157 | 158 | %% @doc Update a `antidote_crdt_counter_b()' with a downstream operation, 159 | %% usually created with `generate_downstream'. 160 | %% 161 | %% Return the resulting `antidote_crdt_counter_b()' after applying the operation. 162 | -spec update(antidote_crdt_counter_b_effect(), antidote_crdt_counter_b()) -> {ok, antidote_crdt_counter_b()}. 163 | update({{increment, V}, Id}, Counter) -> 164 | increment(Id, V, Counter); 165 | update({{decrement, V}, Id}, Counter) -> 166 | decrement(Id, V, Counter); 167 | update({{transfer, V, ToId}, FromId}, Counter) -> 168 | transfer(FromId, ToId, V, Counter). 169 | 170 | %% Add a given amount of permissions to a replica. 171 | -spec increment(id(), pos_integer(), antidote_crdt_counter_b()) -> {ok, antidote_crdt_counter_b()}. 172 | increment(Id, V, {P, D}) -> 173 | {ok, {orddict:update_counter({Id, Id}, V, P), D}}. 174 | 175 | %% Consume a given amount of permissions from a replica. 176 | -spec decrement(id(), pos_integer(), antidote_crdt_counter_b()) -> {ok, antidote_crdt_counter_b()}. 177 | decrement(Id, V, {P, D}) -> 178 | {ok, {P, orddict:update_counter(Id, V, D)}}. 179 | 180 | %% Transfer a given amount of permissions from one replica to another. 181 | -spec transfer(id(), id(), pos_integer(), antidote_crdt_counter_b()) -> {ok, antidote_crdt_counter_b()}. 182 | transfer(FromId, ToId, V, {P, D}) -> 183 | {ok, {orddict:update_counter({FromId, ToId}, V, P), D}}. 184 | 185 | %% doc Return the binary representation of a `antidote_crdt_counter_b()'. 186 | -spec to_binary(antidote_crdt_counter_b()) -> binary(). 187 | to_binary(C) -> term_to_binary(C). 188 | 189 | %% doc Return a `antidote_crdt_counter_b()' from its binary representation. 190 | -spec from_binary(binary()) -> {ok, antidote_crdt_counter_b()}. 191 | from_binary(<>) -> {ok, binary_to_term(B)}. 192 | 193 | %% @doc The following operation verifies 194 | %% that Operation is supported by this particular CRDT. 195 | -spec is_operation(term()) -> boolean(). 196 | is_operation({increment, {V, _Actor}}) -> is_pos_integer(V); 197 | is_operation({decrement, {V, _Actor}}) -> is_pos_integer(V); 198 | is_operation({transfer, {V, _, _Actor}}) -> is_pos_integer(V); 199 | is_operation(_) -> false. 200 | 201 | -spec is_pos_integer(term()) -> boolean(). 202 | is_pos_integer(V) -> is_integer(V) andalso (V > 0). 203 | 204 | %% The antidote_crdt_counter_b requires no state downstream for increment. 205 | -spec require_state_downstream(antidote_crdt_counter_b_op()) -> boolean(). 206 | require_state_downstream({increment, {_, _}}) -> 207 | false; 208 | require_state_downstream(_) -> 209 | true. 210 | 211 | %% Checks equality. 212 | %% Since all contents of the antidote_crdt_counter_b are ordered (two orddicts) 213 | %% they will be equal if the content is equal. 214 | -spec equal(antidote_crdt_counter_b(), antidote_crdt_counter_b()) -> boolean(). 215 | equal(BCounter1, BCounter2) -> 216 | BCounter1 == BCounter2. 217 | 218 | %% =================================================================== 219 | %% EUnit tests 220 | %% =================================================================== 221 | 222 | -ifdef(TEST). 223 | 224 | %% Utility to generate and apply downstream operations. 225 | apply_op(Op, Counter) -> 226 | {ok, OP_DS} = downstream(Op, Counter), 227 | {ok, NewCounter} = update(OP_DS, Counter), 228 | NewCounter. 229 | 230 | %% Tests creating a new `antidote_crdt_counter_b()'. 231 | new_test() -> 232 | ?assertEqual({orddict:new(), orddict:new()}, new()). 233 | 234 | %% Tests increment operations. 235 | increment_test() -> 236 | Counter0 = new(), 237 | Counter1 = apply_op({increment, {10, r1}}, Counter0), 238 | Counter2 = apply_op({increment, {5, r2}}, Counter1), 239 | %% Test replicas' values. 240 | ?assertEqual(5, localPermissions(r2, Counter2)), 241 | ?assertEqual(10, localPermissions(r1, Counter2)), 242 | %% Test total value. 243 | ?assertEqual(15, permissions(Counter2)). 244 | 245 | %% Tests the function `localPermissions()'. 246 | localPermisisons_test() -> 247 | Counter0 = new(), 248 | Counter1 = apply_op({increment, {10, r1}}, Counter0), 249 | %% Test replica with positive amount of permissions. 250 | ?assertEqual(10, localPermissions(r1, Counter1)), 251 | %% Test nonexistent replica. 252 | ?assertEqual(0, localPermissions(r2, Counter1)). 253 | 254 | %% Tests decrement operations. 255 | decrement_test() -> 256 | Counter0 = new(), 257 | Counter1 = apply_op({increment, {10, r1}}, Counter0), 258 | %% Test allowed decrement. 259 | Counter2 = apply_op({decrement, {6, r1}}, Counter1), 260 | ?assertEqual(4, permissions(Counter2)), 261 | %% Test nonexistent replica. 262 | ?assertEqual(0, localPermissions(r2, Counter1)), 263 | %% Test forbidden decrement. 264 | OP_DS = downstream({decrement, {6, r1}}, Counter2), 265 | ?assertEqual({error, no_permissions}, OP_DS). 266 | 267 | %% Tests a more complex chain of increment and decrement operations. 268 | decrement_increment_test() -> 269 | Counter0 = new(), 270 | Counter1 = apply_op({increment, {10, r1}}, Counter0), 271 | Counter2 = apply_op({decrement, {6, r1}}, Counter1), 272 | Counter3 = apply_op({increment, {6, r2}}, Counter2), 273 | %% Test several replicas (balance each other). 274 | ?assertEqual(10, permissions(Counter3)), 275 | %% Test forbidden permissions, when total is higher than consumed. 276 | OP_DS = downstream({decrement, {6, r1}}, Counter3), 277 | ?assertEqual({error, no_permissions}, OP_DS), 278 | %% Test the same operation is allowed on another replica with enough permissions. 279 | Counter4 = apply_op({decrement, {6, r2}}, Counter3), 280 | ?assertEqual(4, permissions(Counter4)). 281 | 282 | %% Tests transferring permissions. 283 | transfer_test() -> 284 | Counter0 = new(), 285 | Counter1 = apply_op({increment, {10, r1}}, Counter0), 286 | %% Test transferring permissions from one replica to another. 287 | Counter2 = apply_op({transfer, {6, r2, r1}}, Counter1), 288 | ?assertEqual(4, localPermissions(r1, Counter2)), 289 | ?assertEqual(6, localPermissions(r2, Counter2)), 290 | ?assertEqual(10, permissions(Counter2)), 291 | %% Test transference forbidden by lack of previously transfered resources. 292 | OP_DS = downstream({transfer, {5, r2, r1}}, Counter2), 293 | ?assertEqual({error, no_permissions}, OP_DS), 294 | %% Test transference enabled by previously transfered resources. 295 | Counter3 = apply_op({transfer, {5, r1, r2}}, Counter2), 296 | ?assertEqual(9, localPermissions(r1, Counter3)), 297 | ?assertEqual(1, localPermissions(r2, Counter3)), 298 | ?assertEqual(10, permissions(Counter3)). 299 | 300 | %% Tests the function `value()'. 301 | value_test() -> 302 | %% Test on `antidote_crdt_counter_b()' resulting from applying all kinds of operation. 303 | Counter0 = new(), 304 | Counter1 = apply_op({increment, {10, r1}}, Counter0), 305 | Counter2 = apply_op({decrement, {6, r1}}, Counter1), 306 | Counter3 = apply_op({transfer, {2, r2, r1}}, Counter2), 307 | %% Assert `value()' returns `antidote_crdt_counter_b()' itself. 308 | ?assertEqual(Counter3, value(Counter3)). 309 | 310 | transfer_to_self_is_is_not_allowed_if_not_enough_local_permissions_exist_test() -> 311 | Counter0 = new(), 312 | Counter1 = apply_op({increment, {8, r1}}, Counter0), 313 | DownstreamResult = downstream({transfer, {10, r1, r1}}, Counter1), 314 | ?assertEqual({error, no_permissions}, DownstreamResult). 315 | 316 | transfer_to_self_is_increment_if_enough_local_permissions_exist_test() -> 317 | Counter0 = new(), 318 | Counter1 = apply_op({increment, {10, r1}}, Counter0), 319 | Counter2 = apply_op({increment, {10, r1}}, Counter0), 320 | Counter3 = apply_op({increment, {10, r1}}, Counter1), 321 | Counter4 = apply_op({transfer, {10, r1, r1}}, Counter2), 322 | ?assertEqual(Counter3, Counter4). 323 | 324 | %% Tests serialization functions `to_binary()' and `from_binary()'. 325 | binary_test() -> 326 | %% Test on `antidote_crdt_counter_b()' resulting from applying all kinds of operation. 327 | Counter0 = new(), 328 | Counter1 = apply_op({increment, {10, r1}}, Counter0), 329 | Counter2 = apply_op({decrement, {6, r1}}, Counter1), 330 | Counter3 = apply_op({transfer, {2, r2, r1}}, Counter2), 331 | %% Assert marshaling and unmarshaling holds the same `antidote_crdt_counter_b()'. 332 | B = to_binary(Counter3), 333 | ?assertEqual({ok, Counter3}, from_binary(B)). 334 | 335 | %% Tests that operations are correctly detected. 336 | is_operation_test() -> 337 | ?assertEqual(true, is_operation({transfer, {2, r2, r1}})), 338 | ?assertEqual(true, is_operation({increment, {50, r1}})), 339 | ?assertEqual(false, is_operation(increment)), 340 | ?assertEqual(true, is_operation({decrement, {50, r1}})), 341 | ?assertEqual(false, is_operation(decrement)), 342 | ?assertEqual(false, is_operation({anything, [1, 2, 3]})). 343 | 344 | -endif. 345 | -------------------------------------------------------------------------------- /src/antidote_crdt_counter_fat.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% antidote_crdt_counter_fat: A convergent, replicated, operation based Fat Counter 30 | %% The state of this fat counter is list of pairs where each pair is an integer 31 | %% and a related token. 32 | %% Basically when the counter recieves {increment, N} or {decrement, N} it generates 33 | %% a pair {N, NewToken}. 34 | %% On update, all seen tokens are removed and the new pair is then added to the state. 35 | %% This token keeps growing ("Fat" Counter) but it useful as it allows the reset 36 | %% functionaility, On reset(), all seen tokens are removed. 37 | %% link to paper: http://haslab.uminho.pt/cbm/files/a3-younes.pdf 38 | 39 | -module(antidote_crdt_counter_fat). 40 | 41 | -behaviour(antidote_crdt). 42 | 43 | -ifdef(TEST). 44 | -include_lib("eunit/include/eunit.hrl"). 45 | -endif. 46 | 47 | -export([ new/0, 48 | value/1, 49 | downstream/2, 50 | update/2, 51 | equal/2, 52 | to_binary/1, 53 | from_binary/1, 54 | is_operation/1, 55 | is_bottom/1, 56 | require_state_downstream/1 57 | ]). 58 | 59 | -type uniqueToken() :: term(). 60 | -type state() :: orddict:orddict(uniqueToken(), integer()). 61 | -type op() :: 62 | {increment, integer()} 63 | | {decrement, integer()} 64 | | {reset, {}}. 65 | -type effect() :: 66 | {uniqueToken(), integer()} 67 | | [uniqueToken()]. 68 | 69 | %% @doc Create a new, empty fat counter 70 | -spec new() -> state(). 71 | new() -> 72 | orddict:new(). 73 | 74 | %% @doc The value of this counter is equal to the sum of all the values 75 | %% having tokens. 76 | -spec value(state()) -> integer(). 77 | value(FatCounter) -> 78 | lists:sum([V || {_, V} <- FatCounter]). 79 | 80 | 81 | -spec downstream(op(), state()) -> {ok, effect()}. 82 | downstream(Op, FatCtr) -> 83 | Token = unique(), 84 | case Op of 85 | {increment, Value} when is_integer(Value) -> 86 | {ok, {Token, Value}}; 87 | {decrement, Value} when is_integer(Value) -> 88 | {ok, {Token, -Value}}; 89 | {reset, {}} -> 90 | {ok, orddict:fetch_keys(FatCtr)} 91 | end. 92 | 93 | -spec unique() -> uniqueToken(). 94 | unique() -> 95 | crypto:strong_rand_bytes(20). 96 | 97 | 98 | -spec update(effect(), state()) -> {ok, state()}. 99 | update({Token, Value}, FatCtr) -> 100 | % insert new value 101 | {ok, orddict:store(Token, Value, FatCtr)}; 102 | update(Overridden, FatCtr) -> 103 | {ok, apply_downstreams(Overridden, FatCtr)}. 104 | 105 | %% @private apply a list of downstream ops to a given orset 106 | apply_downstreams([], FatCtr) -> 107 | FatCtr; 108 | apply_downstreams(_Tokens, []) -> 109 | []; 110 | apply_downstreams([Token1|TokensRest]=Tokens, [{Token2, Value2}|FatCtrRest]=FatCtr) -> 111 | if 112 | Token1 == Token2 -> 113 | apply_downstreams(TokensRest, FatCtrRest); 114 | Token1 > Token2 -> 115 | [{Token2, Value2} | apply_downstreams(Tokens, FatCtrRest)]; 116 | true -> 117 | apply_downstreams(TokensRest, FatCtr) 118 | end. 119 | 120 | -spec equal(state(), state()) -> boolean(). 121 | equal(FatCtr1, FatCtr2) -> 122 | FatCtr1 == FatCtr2. 123 | 124 | -define(TAG, 85). 125 | -define(V1_VERS, 1). 126 | 127 | -spec to_binary(state()) -> binary(). 128 | to_binary(FatCtr) -> 129 | <>. 130 | 131 | %% @doc Decode binary 132 | -spec from_binary(binary()) -> {ok, state()} | {error, term()}. 133 | from_binary(<>) -> 134 | {ok, antidote_crdt:from_binary(Bin)}. 135 | 136 | is_bottom(FatCtr) -> 137 | FatCtr == new(). 138 | 139 | %% @doc The following operation verifies 140 | %% that Operation is supported by this particular CRDT. 141 | -spec is_operation(term()) -> boolean(). 142 | is_operation({increment, Value}) when is_integer(Value) -> true; 143 | is_operation({decrement, Value}) when is_integer(Value)-> true; 144 | is_operation({reset, {}}) -> true; 145 | is_operation(_) -> false. 146 | 147 | require_state_downstream(Op) -> 148 | Op == {reset, {}}. 149 | 150 | 151 | 152 | %% =================================================================== 153 | %% EUnit tests 154 | %% =================================================================== 155 | -ifdef(TEST). 156 | 157 | new_test() -> 158 | ?assertEqual(0, value(new())). 159 | 160 | update_increment_test() -> 161 | FatCnt0 = new(), 162 | {ok, Increment1} = downstream({increment, 5}, FatCnt0), 163 | {ok, FatCnt1} = update(Increment1, FatCnt0), 164 | {ok, Decrement1} = downstream({decrement, 2}, FatCnt1), 165 | {ok, FatCnt2} = update(Decrement1, FatCnt1), 166 | {ok, Increment2} = downstream({increment, 1}, FatCnt2), 167 | {ok, FatCnt3} = update(Increment2, FatCnt2), 168 | {ok, Reset1} = downstream({reset, {}}, FatCnt3), 169 | {ok, FatCnt4} = update(Reset1, FatCnt3), 170 | {ok, Decrement2} = downstream({decrement, 2}, FatCnt4), 171 | {ok, FatCnt5} = update(Decrement2, FatCnt4), 172 | io:format("FatCnt0 = ~p~n", [FatCnt0]), 173 | io:format("Increment1 = ~p~n", [Increment1]), 174 | io:format("FatCnt1 = ~p~n", [FatCnt1]), 175 | io:format("Decrement1 = ~p~n", [Decrement1]), 176 | io:format("FatCnt2 = ~p~n", [FatCnt2]), 177 | ?assertEqual(0, value(FatCnt0)), 178 | ?assertEqual(5, value(FatCnt1)), 179 | ?assertEqual(3, value(FatCnt2)), 180 | ?assertEqual(4, value(FatCnt3)), 181 | ?assertEqual(0, value(FatCnt4)), 182 | ?assertEqual(-2, value(FatCnt5)). 183 | 184 | -endif. 185 | -------------------------------------------------------------------------------- /src/antidote_crdt_counter_pn.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% antidote_crdt_counter_pn: A convergent, replicated, operation 30 | %% based PN-Counter 31 | 32 | -module(antidote_crdt_counter_pn). 33 | 34 | -behaviour(antidote_crdt). 35 | 36 | -ifdef(TEST). 37 | -include_lib("eunit/include/eunit.hrl"). 38 | -endif. 39 | 40 | -export([ new/0, 41 | new/1, 42 | value/1, 43 | downstream/2, 44 | update/2, 45 | equal/2, 46 | to_binary/1, 47 | from_binary/1, 48 | is_operation/1, 49 | require_state_downstream/1 50 | ]). 51 | 52 | 53 | -type state() :: integer(). 54 | -type op() :: {increment, integer()} | 55 | {decrement, integer()}. 56 | -type effect() :: integer(). 57 | 58 | %% @doc Create a new, empty 'antidote_crdt_counter_pn' 59 | new() -> 60 | 0. 61 | 62 | %% @doc Create 'antidote_crdt_counter_pn' with initial value 63 | -spec new(integer()) -> state(). 64 | new(Value) when is_integer(Value) -> 65 | Value; 66 | new(_) -> 67 | new(). 68 | 69 | %% @doc The single, total value of a `pncounter()' 70 | -spec value(state()) -> integer(). 71 | value(PNCnt) when is_integer(PNCnt) -> 72 | PNCnt. 73 | 74 | %% @doc Generate a downstream operation. 75 | %% The first parameter is either `increment' or `decrement' or the two tuples 76 | %% `{increment, pos_integer()}' or `{decrement, pos_integer()}'. The second parameter 77 | %% is the pncounter (this parameter is not actually used). 78 | -spec downstream(op(), state()) -> {ok, effect()}. 79 | downstream(increment, _PNCnt) -> 80 | {ok, 1}; 81 | downstream(decrement, _PNCnt) -> 82 | {ok, -1}; 83 | downstream({increment, By}, _PNCnt) when is_integer(By) -> 84 | {ok, By}; 85 | downstream({decrement, By}, _PNCnt) when is_integer(By) -> 86 | {ok, -By}. 87 | 88 | %% @doc Update a `pncounter()'. The first argument is either the atom 89 | %% `increment' or `decrement' or the two tuples `{increment, pos_integer()}' or 90 | %% `{decrement, pos_integer()}'. 91 | %% In the case of the former, the operation's amount 92 | %% is `1'. Otherwise it is the value provided in the tuple's second element. 93 | %% The 2nd argument is the `pncounter()' to update. 94 | %% 95 | %% returns the updated `pncounter()' 96 | -spec update(effect(), state()) -> {ok, state()}. 97 | update(N, PNCnt) -> 98 | {ok, PNCnt + N}. 99 | 100 | %% @doc Compare if two `pncounter()' are equal. Only returns `true()' if both 101 | %% of their positive and negative entries are equal. 102 | -spec equal(state(), state()) -> boolean(). 103 | equal(PNCnt1, PNCnt2) -> 104 | PNCnt1 =:= PNCnt2. 105 | 106 | -spec to_binary(state()) -> binary(). 107 | to_binary(PNCounter) -> 108 | term_to_binary(PNCounter). 109 | 110 | from_binary(Bin) -> 111 | %% @TODO something smarter 112 | {ok, binary_to_term(Bin)}. 113 | 114 | %% @doc The following operation verifies 115 | %% that Operation is supported by this particular CRDT. 116 | -spec is_operation(term()) -> boolean(). 117 | is_operation(increment) -> true; 118 | is_operation(decrement) -> true; 119 | is_operation({increment, By}) when is_integer(By) -> true; 120 | is_operation({decrement, By}) when is_integer(By)-> true; 121 | is_operation(_) -> false. 122 | 123 | %% @doc Returns true if ?MODULE:downstream/2 needs the state of crdt 124 | %% to generate downstream effect 125 | require_state_downstream(_) -> 126 | false. 127 | 128 | %% =================================================================== 129 | %% EUnit tests 130 | %% =================================================================== 131 | -ifdef(TEST). 132 | 133 | prepare_and_effect(Op, PNCounter) -> 134 | {ok, Downstream} = downstream(Op, PNCounter), 135 | update(Downstream, PNCounter). 136 | 137 | new_test() -> 138 | ?assertEqual(0, new()). 139 | 140 | value_test() -> 141 | PNCnt = 4, 142 | ?assertEqual(4, value(PNCnt)). 143 | 144 | update_increment_test() -> 145 | PNCnt0 = new(), 146 | {ok, PNCnt1} = prepare_and_effect({increment, 1}, PNCnt0), 147 | {ok, PNCnt2} = prepare_and_effect({increment, 2}, PNCnt1), 148 | {ok, PNCnt3} = prepare_and_effect({increment, 1}, PNCnt2), 149 | ?assertEqual(4, value(PNCnt3)). 150 | 151 | update_increment_by_test() -> 152 | PNCnt0 = new(), 153 | {ok, PNCnt1} = prepare_and_effect({increment, 7}, PNCnt0), 154 | ?assertEqual(7, value(PNCnt1)). 155 | 156 | update_decrement_test() -> 157 | PNCnt0 = new(), 158 | {ok, PNCnt1} = prepare_and_effect({increment, 1}, PNCnt0), 159 | {ok, PNCnt2} = prepare_and_effect({increment, 2}, PNCnt1), 160 | {ok, PNCnt3} = prepare_and_effect({increment, 1}, PNCnt2), 161 | {ok, PNCnt4} = prepare_and_effect({decrement, 1}, PNCnt3), 162 | ?assertEqual(3, value(PNCnt4)). 163 | 164 | update_negative_params_test() -> 165 | PNCnt0 = new(), 166 | {ok, PNCnt1} = prepare_and_effect({increment, -7}, PNCnt0), 167 | {ok, PNCnt2} = prepare_and_effect({decrement, -5}, PNCnt1), 168 | ?assertEqual(-2, value(PNCnt2)). 169 | 170 | equal_test() -> 171 | PNCnt1 = 4, 172 | PNCnt2 = 2, 173 | PNCnt3 = 2, 174 | ?assertNot(equal(PNCnt1, PNCnt2)), 175 | ?assert(equal(PNCnt2, PNCnt3)). 176 | 177 | binary_test() -> 178 | PNCnt1 = 4, 179 | BinaryPNCnt1 = to_binary(PNCnt1), 180 | {ok, PNCnt2} = from_binary(BinaryPNCnt1), 181 | ?assert(equal(PNCnt1, PNCnt2)). 182 | 183 | -endif. 184 | -------------------------------------------------------------------------------- /src/antidote_crdt_flag_dw.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% @doc 30 | %% An operation-based Disable-wins Flag CRDT. 31 | 32 | %% @end 33 | -module(antidote_crdt_flag_dw). 34 | 35 | %% Callbacks 36 | -export([ new/0, 37 | value/1, 38 | downstream/2, 39 | update/2, 40 | equal/2, 41 | to_binary/1, 42 | from_binary/1, 43 | is_operation/1, 44 | is_bottom/1, 45 | require_state_downstream/1 46 | ]). 47 | 48 | -behaviour(antidote_crdt). 49 | 50 | -ifdef(TEST). 51 | -include_lib("eunit/include/eunit.hrl"). 52 | -endif. 53 | 54 | -define(TAG, 77). 55 | -define(V1_VERS, 1). 56 | 57 | -type flag_dw() :: {antidote_crdt_flag_helper:tokens(), antidote_crdt_flag_helper:tokens()}. 58 | 59 | %% SeenTokens, NewEnableTokens, NewDisableTokens 60 | -type downstream_op() :: {antidote_crdt_flag_helper:tokens(), antidote_crdt_flag_helper:tokens(), antidote_crdt_flag_helper:tokens()}. 61 | 62 | -spec new() -> flag_dw(). 63 | new() -> 64 | {[], []}. 65 | 66 | -spec value(flag_dw()) -> boolean(). 67 | value({EnableTokens, DisableTokens}) -> 68 | DisableTokens == [] andalso EnableTokens =/= []. 69 | 70 | -spec downstream(antidote_crdt_flag_helper:op(), flag_dw()) -> {ok, downstream_op()}. 71 | downstream({disable, {}}, {EnableTokens, DisableTokens}) -> 72 | {ok, {EnableTokens ++ DisableTokens, [], [antidote_crdt_flag_helper:unique()]}}; 73 | downstream({enable, {}}, {EnableTokens, DisableTokens}) -> 74 | {ok, {EnableTokens ++ DisableTokens, [antidote_crdt_flag_helper:unique()], []}}; 75 | downstream({reset, {}}, {EnableTokens, DisableTokens}) -> 76 | {ok, {EnableTokens ++ DisableTokens, [], []}}. 77 | 78 | -spec update(downstream_op(), flag_dw()) -> {ok, flag_dw()}. 79 | update({SeenTokens, NewEnableTokens, NewDisableTokens}, {CurrentEnableTokens, CurrentDisableTokens}) -> 80 | FinalEnableTokens = (CurrentEnableTokens ++ NewEnableTokens) -- SeenTokens, 81 | FinalDisableTokens = (CurrentDisableTokens ++ NewDisableTokens) -- SeenTokens, 82 | {ok, {FinalEnableTokens, FinalDisableTokens}}. 83 | 84 | -spec equal(flag_dw(), flag_dw()) -> boolean(). 85 | equal(Flag1, Flag2) -> 86 | Flag1 == Flag2. 87 | 88 | -spec to_binary(flag_dw()) -> antidote_crdt_flag_helper:binary_flag(). 89 | to_binary(Flag) -> 90 | %% @TODO something smarter 91 | <>. 92 | 93 | from_binary(<>) -> 94 | %% @TODO something smarter 95 | {ok, binary_to_term(Bin)}. 96 | 97 | is_operation(A) -> antidote_crdt_flag_helper:is_operation(A). 98 | 99 | is_bottom(Flag) -> 100 | Flag == new(). 101 | 102 | require_state_downstream(A) -> antidote_crdt_flag_helper:require_state_downstream(A). 103 | 104 | 105 | %% =================================================================== 106 | %% EUnit tests 107 | %% =================================================================== 108 | -ifdef(TEST). 109 | 110 | -endif. 111 | -------------------------------------------------------------------------------- /src/antidote_crdt_flag_ew.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% @doc 30 | %% An operation-based Enable-wins Flag CRDT. 31 | 32 | %% @end 33 | -module(antidote_crdt_flag_ew). 34 | 35 | %% Callbacks 36 | -export([ new/0, 37 | value/1, 38 | downstream/2, 39 | update/2, 40 | equal/2, 41 | to_binary/1, 42 | from_binary/1, 43 | is_operation/1, 44 | is_bottom/1, 45 | require_state_downstream/1 46 | ]). 47 | 48 | -behaviour(antidote_crdt). 49 | 50 | -ifdef(TEST). 51 | -include_lib("eunit/include/eunit.hrl"). 52 | -endif. 53 | 54 | -define(TAG, 77). 55 | -define(V1_VERS, 1). 56 | 57 | -type flag_ew() :: antidote_crdt_flag_helper:tokens(). 58 | 59 | %% SeenTokens, NewTokens 60 | -type downstream_op() :: {antidote_crdt_flag_helper:tokens(), antidote_crdt_flag_helper:tokens()}. 61 | 62 | -spec new() -> flag_ew(). 63 | new() -> 64 | []. 65 | 66 | -spec value(flag_ew()) -> boolean(). 67 | value(EnableTokens) -> 68 | EnableTokens =/= []. 69 | 70 | -spec downstream(antidote_crdt_flag_helper:op(), flag_ew()) -> {ok, downstream_op()}. 71 | downstream({disable, {}}, Tokens) -> 72 | {ok, {Tokens, []}}; 73 | downstream({enable, {}}, Tokens) -> 74 | {ok, {Tokens, [antidote_crdt_flag_helper:unique()]}}; 75 | downstream({reset, {}}, Tokens) -> 76 | {ok, {Tokens, []}}. 77 | 78 | -spec update(downstream_op(), flag_ew()) -> {ok, flag_ew()}. 79 | update({SeenTokens, NewTokens}, CurrentTokens) -> 80 | FinalTokens = (CurrentTokens ++ NewTokens) -- SeenTokens, 81 | {ok, FinalTokens}. 82 | 83 | -spec equal(flag_ew(), flag_ew()) -> boolean(). 84 | equal(Flag1, Flag2) -> 85 | Flag1 == Flag2. % Everything inside is ordered, so this should work 86 | 87 | -spec to_binary(flag_ew()) -> antidote_crdt_flag_helper:binary_flag(). 88 | to_binary(Flag) -> 89 | %% @TODO something smarter 90 | <>. 91 | 92 | from_binary(<>) -> 93 | %% @TODO something smarter 94 | {ok, binary_to_term(Bin)}. 95 | 96 | is_operation(A) -> antidote_crdt_flag_helper:is_operation(A). 97 | 98 | is_bottom(Flag) -> 99 | Flag == new(). 100 | 101 | require_state_downstream(A) -> antidote_crdt_flag_helper:require_state_downstream(A). 102 | 103 | %% =================================================================== 104 | %% EUnit tests 105 | %% =================================================================== 106 | -ifdef(TEST). 107 | 108 | prop1_test() -> 109 | Flag0 = new(), 110 | % DC1 add a 111 | {ok, Enable1Effect} = downstream({enable, {}}, Flag0), 112 | {ok, Flag1a} = update(Enable1Effect, Flag0), 113 | % DC1 reset 114 | {ok, Reset1Effect} = downstream({reset, {}}, Flag1a), 115 | {ok, Flag1b} = update(Reset1Effect, Flag1a), 116 | 117 | io:format("Reset1Effect = ~p~n", [Reset1Effect]), 118 | io:format("Enable1Effect = ~p~n", [Enable1Effect]), 119 | 120 | io:format("Flag1a = ~p~n", [Flag1a]), 121 | io:format("Flag1b = ~p~n", [Flag1b]), 122 | 123 | ?assertEqual(false, value(Flag0)), 124 | ?assertEqual(true, value(Flag1a)), 125 | ?assertEqual(false, value(Flag1b)). 126 | 127 | -endif. 128 | -------------------------------------------------------------------------------- /src/antidote_crdt_flag_helper.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% @doc 30 | %% A helper for operation-based flags, enable wins flag and disable wins flag. 31 | 32 | %% @end 33 | -module(antidote_crdt_flag_helper). 34 | 35 | 36 | %% Callbacks 37 | -export([ from_binary/1, 38 | is_operation/1, 39 | require_state_downstream/1, 40 | unique/0 41 | ]). 42 | 43 | -ifdef(TEST). 44 | -include_lib("eunit/include/eunit.hrl"). 45 | -endif. 46 | 47 | -export_type([tokens/0, binary_flag/0, op/0]). 48 | 49 | -type binary_flag() :: binary(). %% A binary that from_binary/1 will operate on. 50 | 51 | -type op() :: 52 | {enable, {}} 53 | | {disable, {}} 54 | | {reset, {}}. 55 | 56 | -type token() :: term(). 57 | -type tokens() :: [token()]. 58 | 59 | %% @doc generate a unique identifier (best-effort). 60 | unique() -> 61 | crypto:strong_rand_bytes(20). 62 | 63 | -define(TAG, 77). 64 | -define(V1_VERS, 1). 65 | 66 | from_binary(<>) -> 67 | %% @TODO something smarter 68 | {ok, binary_to_term(Bin)}. 69 | 70 | is_operation({enable, {}}) -> true; 71 | is_operation({disable, {}}) -> true; 72 | is_operation({reset, {}}) -> true; 73 | is_operation(_) -> false. 74 | 75 | require_state_downstream(_) -> true. 76 | 77 | %% =================================================================== 78 | %% EUnit tests 79 | %% =================================================================== 80 | -ifdef(TEST). 81 | 82 | -endif. 83 | -------------------------------------------------------------------------------- /src/antidote_crdt_map_go.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% @doc module antidote_crdt_map_go - A grow-only map 30 | %% 31 | %% This map simply forwards all operations to the embedded CRDTs. 32 | %% There is no real remove-operation. 33 | %% 34 | %% The reset operation, forwards the reset to all values in the map. 35 | 36 | -module(antidote_crdt_map_go). 37 | 38 | -behaviour(antidote_crdt). 39 | 40 | %% API 41 | -export([new/0, value/1, update/2, equal/2, 42 | to_binary/1, from_binary/1, is_operation/1, downstream/2, require_state_downstream/1]). 43 | 44 | -ifdef(TEST). 45 | -include_lib("eunit/include/eunit.hrl"). 46 | -endif. 47 | 48 | 49 | -type antidote_crdt_map_go() :: dict:dict({Key::term(), Type::atom()}, NestedState::term()). 50 | -type antidote_crdt_map_go_op() :: 51 | {update, nested_op()} 52 | | {update, [nested_op()]}. 53 | -type nested_op() :: {{Key::term(), Type::atom() }, Op::term()}. 54 | -type antidote_crdt_map_go_effect() :: 55 | {update, nested_downstream()} 56 | | {update, [nested_downstream()]}. 57 | -type nested_downstream() :: {{Key::term(), Type::atom() }, Op::term()}. 58 | 59 | -spec new() -> antidote_crdt_map_go(). 60 | new() -> 61 | dict:new(). 62 | 63 | -spec value(antidote_crdt_map_go()) -> [{{Key::term(), Type::atom()}, Value::term()}]. 64 | value(Map) -> 65 | lists:sort([{{Key, Type}, antidote_crdt:value(Type, Value)} || {{Key, Type}, Value} <- dict:to_list(Map)]). 66 | 67 | -spec require_state_downstream(antidote_crdt_map_go_op()) -> boolean(). 68 | require_state_downstream(_Op) -> 69 | true. 70 | 71 | 72 | -spec downstream(antidote_crdt_map_go_op(), antidote_crdt_map_go()) -> {ok, antidote_crdt_map_go_effect()}. 73 | downstream({update, {{Key, Type}, Op}}, CurrentMap) -> 74 | % TODO could be optimized for some types 75 | CurrentValue = case dict:is_key({Key, Type}, CurrentMap) of 76 | true -> dict:fetch({Key, Type}, CurrentMap); 77 | false -> antidote_crdt:new(Type) 78 | end, 79 | {ok, DownstreamOp} = antidote_crdt:downstream(Type, Op, CurrentValue), 80 | {ok, {update, {{Key, Type}, DownstreamOp}}}; 81 | downstream({update, Ops}, CurrentMap) when is_list(Ops) -> 82 | {ok, {update, lists:map(fun(Op) -> {ok, DSOp} = downstream({update, Op}, CurrentMap), DSOp end, Ops)}}. 83 | 84 | -spec update(antidote_crdt_map_go_effect(), antidote_crdt_map_go()) -> {ok, antidote_crdt_map_go()}. 85 | update({update, {{Key, Type}, Op}}, Map) -> 86 | case dict:is_key({Key, Type}, Map) of 87 | true -> {ok, dict:update({Key, Type}, fun(V) -> {ok, Value} = antidote_crdt:update(Type, Op, V), Value end, Map)}; 88 | false -> NewValue = antidote_crdt:new(Type), 89 | {ok, NewValueUpdated} = antidote_crdt:update(Type, Op, NewValue), 90 | {ok, dict:store({Key, Type}, NewValueUpdated, Map)} 91 | end; 92 | update({update, Ops}, Map) -> 93 | apply_ops(Ops, Map). 94 | 95 | apply_ops([], Map) -> 96 | {ok, Map}; 97 | apply_ops([Op | Rest], Map) -> 98 | {ok, ORDict1} = update(Op, Map), 99 | apply_ops(Rest, ORDict1). 100 | 101 | 102 | 103 | equal(Map1, Map2) -> 104 | Map1 == Map2. % TODO better implementation (recursive equals) 105 | 106 | 107 | -define(TAG, 101). 108 | -define(V1_VERS, 1). 109 | 110 | to_binary(Policy) -> 111 | %% @TODO something smarter 112 | <>. 113 | 114 | from_binary(<>) -> 115 | %% @TODO something smarter 116 | {ok, binary_to_term(Bin)}. 117 | 118 | is_operation(Operation) -> 119 | case Operation of 120 | {update, {{_Key, Type}, Op}} -> 121 | antidote_crdt:is_type(Type) andalso antidote_crdt:is_operation(Type, Op); 122 | {update, Ops} when is_list(Ops) -> 123 | distinct([Key || {Key, _} <- Ops]) 124 | andalso lists:all(fun(Op) -> is_operation({update, Op}) end, Ops); 125 | {reset, {}} -> false; 126 | _ -> 127 | false 128 | end. 129 | 130 | distinct([]) -> true; 131 | distinct([X|Xs]) -> 132 | not lists:member(X, Xs) andalso distinct(Xs). 133 | 134 | 135 | %% =================================================================== 136 | %% EUnit tests 137 | %% =================================================================== 138 | % TODO 139 | -ifdef(TEST). 140 | new_test() -> 141 | ?assertEqual(dict:new(), new()). 142 | 143 | update_test() -> 144 | Map1 = new(), 145 | {ok, DownstreamOp} = downstream({update, {{key1, antidote_crdt_register_lww}, {assign, <<"test">>}}}, Map1), 146 | ?assertMatch({update, {{key1, antidote_crdt_register_lww}, {_, <<"test">>}}}, DownstreamOp), 147 | {ok, Map2} = update(DownstreamOp, Map1), 148 | ?assertEqual([{{key1, antidote_crdt_register_lww}, <<"test">>}], value(Map2)). 149 | 150 | update2_test() -> 151 | Map1 = new(), 152 | {ok, Effect1} = downstream({update, [{{a, antidote_crdt_set_aw}, {add, a}}]}, Map1), 153 | {ok, Map2} = update(Effect1, Map1), 154 | ?assertEqual([{{a, antidote_crdt_set_aw}, [a]}], value(Map2)). 155 | 156 | -endif. 157 | -------------------------------------------------------------------------------- /src/antidote_crdt_map_rr.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% @doc module antidote_crdt_map_rr - A CRDT map datatype with a reset functionality 30 | %% 31 | %% Inserting a new element in the map: 32 | %% if element already there -> do nothing 33 | %% if not create a map entry where value is initial state of the embedded 34 | %% data type (a call to the create function of the embedded data type) 35 | %% 36 | %% Update operations on entries(embedded CRDTs) are calls to the update fucntions of the entries. 37 | %% 38 | %% Deleting an entry in the map: 39 | %% 1- calls the reset function of this entry (tries to reset entry to its initial state) 40 | %% As reset only affects operations that are locally (where reset was invoked) seen 41 | %% i.e. operations on the same entry that are concurrent to the reset operation are 42 | %% not affected and their effect should be observable once delivered. 43 | %% 44 | %% if there were no operations concurrent to the reset (all operations where in the causal past of the reset), 45 | %% then the state of the entry is bottom (the initial state of the entry) 46 | %% 47 | %% 2- checks if the state of the entry after the reset is bottom (its initial state) 48 | %% if bottom, delete the entry from the map 49 | %% if not bottom, keep the entry 50 | %% 51 | %% An entry exists in a map, if there is at least one update (inserts included) on the key, which is not followed by a remove 52 | %% 53 | %% Resetting the map means removing all the current entries 54 | %% 55 | 56 | -module(antidote_crdt_map_rr). 57 | 58 | -behaviour(antidote_crdt). 59 | 60 | %% API 61 | -export([new/0, value/1, update/2, equal/2, get/2, 62 | to_binary/1, from_binary/1, is_operation/1, downstream/2, require_state_downstream/1, is_bottom/1]). 63 | 64 | -ifdef(TEST). 65 | -include_lib("eunit/include/eunit.hrl"). 66 | -endif. 67 | 68 | 69 | -type typedKey() :: {Key::term(), Type::atom()}. 70 | -type state() :: dict:dict(typedKey(), {NestedState::term()}). 71 | -type op() :: 72 | {update, nested_op()} 73 | | {update, [nested_op()]} 74 | | {remove, typedKey()} 75 | | {remove, [typedKey()]} 76 | | {batch, {Updates::[nested_op()], Removes::[typedKey()]}} 77 | | {reset, {}}. 78 | -type nested_op() :: {typedKey(), Op::term()}. 79 | -type effect() :: 80 | {Adds::[nested_downstream()], Removed::[nested_downstream()]}. 81 | -type nested_downstream() :: {typedKey(), none | {ok, Effect::term()}}. 82 | -type value() :: orddict:orddict(typedKey(), term()). 83 | 84 | -spec new() -> state(). 85 | new() -> 86 | dict:new(). 87 | 88 | -spec value(state()) -> value(). 89 | value(Map) -> 90 | lists:sort([{{Key, Type}, antidote_crdt:value(Type, Value)} || {{Key, Type}, Value} <- dict:to_list(Map)]). 91 | 92 | % get a value from the map 93 | % returns empty value if the key is not present in the map 94 | -spec get(typedKey(), value()) -> term(). 95 | get({_K, Type}=Key, Map) -> 96 | case orddict:find(Key, Map) of 97 | {ok, Val} -> Val; 98 | error -> antidote_crdt:value(Type, antidote_crdt:new(Type)) 99 | end. 100 | 101 | 102 | -spec require_state_downstream(op()) -> boolean(). 103 | require_state_downstream(_Op) -> 104 | true. 105 | 106 | -spec downstream(op(), state()) -> {ok, effect()}. 107 | downstream({update, {{Key, Type}, Op}}, CurrentMap) -> 108 | downstream({update, [{{Key, Type}, Op}]}, CurrentMap); 109 | downstream({update, NestedOps}, CurrentMap) -> 110 | downstream({batch, {NestedOps, []}}, CurrentMap); 111 | downstream({remove, {Key, Type}}, CurrentMap) -> 112 | downstream({remove, [{Key, Type}]}, CurrentMap); 113 | downstream({remove, Keys}, CurrentMap) -> 114 | downstream({batch, {[], Keys}}, CurrentMap); 115 | downstream({batch, {Updates, Removes}}, CurrentMap) -> 116 | UpdateEffects = [generate_downstream_update(Op, CurrentMap) || Op <- Updates], 117 | RemoveEffects = [generate_downstream_remove(Key, CurrentMap) || Key <- Removes], 118 | {ok, {UpdateEffects, RemoveEffects}}; 119 | downstream({reset, {}}, CurrentMap) -> 120 | % reset removes all keys 121 | AllKeys = [Key || {Key, _Val} <- value(CurrentMap)], 122 | downstream({remove, AllKeys}, CurrentMap). 123 | 124 | 125 | -spec generate_downstream_update({typedKey(), Op::term()}, state()) -> nested_downstream(). 126 | generate_downstream_update({{Key, Type}, Op}, CurrentMap) -> 127 | CurrentState = 128 | case dict:is_key({Key, Type}, CurrentMap) of 129 | true -> dict:fetch({Key, Type}, CurrentMap); 130 | false -> antidote_crdt:new(Type) 131 | end, 132 | {ok, DownstreamEffect} = antidote_crdt:downstream(Type, Op, CurrentState), 133 | {{Key, Type}, {ok, DownstreamEffect}}. 134 | 135 | 136 | -spec generate_downstream_remove(typedKey(), state()) -> nested_downstream(). 137 | generate_downstream_remove({Key, Type}, CurrentMap) -> 138 | CurrentState = 139 | case dict:is_key({Key, Type}, CurrentMap) of 140 | true -> dict:fetch({Key, Type}, CurrentMap); 141 | false -> antidote_crdt:new(Type) 142 | end, 143 | DownstreamEffect = 144 | case antidote_crdt:is_operation(Type, {reset, {}}) of 145 | true -> 146 | {ok, _} = antidote_crdt:downstream(Type, {reset, {}}, CurrentState); 147 | false -> 148 | none 149 | end, 150 | {{Key, Type}, DownstreamEffect}. 151 | 152 | 153 | -spec update(effect(), state()) -> {ok, state()}. 154 | update({Updates, Removes}, State) -> 155 | State2 = lists:foldl(fun(E, S) -> update_entry(E, S) end, State, Updates), 156 | State3 = dict:fold(fun(K, V, S) -> remove_obsolete(K, V, S) end, new(), State2), 157 | State4 = lists:foldl(fun(E, S) -> remove_entry(E, S) end, State3, Removes), 158 | {ok, State4}. 159 | 160 | update_entry({{Key, Type}, {ok, Op}}, Map) -> 161 | case dict:find({Key, Type}, Map) of 162 | {ok, State} -> 163 | {ok, UpdatedState} = antidote_crdt:update(Type, Op, State), 164 | dict:store({Key, Type}, UpdatedState, Map); 165 | error -> 166 | NewValue = antidote_crdt:new(Type), 167 | {ok, NewValueUpdated} = antidote_crdt:update(Type, Op, NewValue), 168 | dict:store({Key, Type}, NewValueUpdated, Map) 169 | end. 170 | 171 | remove_entry({{Key, Type}, {ok, Op}}, Map) -> 172 | case dict:find({Key, Type}, Map) of 173 | {ok, State} -> 174 | {ok, UpdatedState} = antidote_crdt:update(Type, Op, State), 175 | case is_bottom(Type, UpdatedState) of 176 | true -> 177 | dict:erase({Key, Type}, Map); 178 | false -> 179 | dict:store({Key, Type}, UpdatedState, Map) 180 | end; 181 | error -> 182 | Map 183 | end; 184 | remove_entry({{_Key, _Type}, none}, Map) -> 185 | Map. 186 | 187 | remove_obsolete({Key, Type}, Val, Map) -> 188 | case is_bottom(Type, Val) of 189 | false -> 190 | dict:store({Key, Type}, Val, Map); 191 | true -> 192 | Map 193 | end. 194 | 195 | is_bottom(Type, State) -> 196 | T = antidote_crdt:alias(Type), 197 | erlang:function_exported(T, is_bottom, 1) andalso T:is_bottom(State). 198 | 199 | equal(Map1, Map2) -> 200 | Map1 == Map2. % TODO better implementation (recursive equals) 201 | 202 | 203 | -define(TAG, 101). 204 | -define(V1_VERS, 1). 205 | 206 | to_binary(Policy) -> 207 | <>. 208 | 209 | from_binary(<>) -> 210 | {ok, binary_to_term(Bin)}. 211 | 212 | is_operation(Operation) -> 213 | case Operation of 214 | {update, {{_Key, Type}, Op}} -> 215 | antidote_crdt:is_type(Type) andalso antidote_crdt:is_operation(Type, Op); 216 | {update, Ops} when is_list(Ops) -> 217 | distinct([Key || {Key, _} <- Ops]) 218 | andalso lists:all(fun(Op) -> is_operation({update, Op}) end, Ops); 219 | {remove, {_Key, Type}} -> 220 | antidote_crdt:is_type(Type); 221 | {remove, Keys} when is_list(Keys) -> 222 | distinct(Keys) 223 | andalso lists:all(fun(Key) -> is_operation({remove, Key}) end, Keys); 224 | {batch, {Updates, Removes}} -> 225 | is_list(Updates) 226 | andalso is_list(Removes) 227 | andalso distinct(Removes ++ [Key || {Key, _} <- Updates]) 228 | andalso lists:all(fun(Key) -> is_operation({remove, Key}) end, Removes) 229 | andalso lists:all(fun(Op) -> is_operation({update, Op}) end, Updates); 230 | {reset, {}} -> true; 231 | is_bottom -> true; 232 | _ -> 233 | false 234 | end. 235 | 236 | distinct([]) -> true; 237 | distinct([X|Xs]) -> 238 | not lists:member(X, Xs) andalso distinct(Xs). 239 | 240 | is_bottom(Map) -> 241 | dict:is_empty(Map). 242 | 243 | 244 | %% =================================================================== 245 | %% EUnit tests 246 | %% =================================================================== 247 | -ifdef(TEST). 248 | 249 | reset1_test() -> 250 | Map0 = new(), 251 | % DC1: a.incr 252 | {ok, Incr1} = downstream({update, {{a, antidote_crdt_counter_fat}, {increment, 1}}}, Map0), 253 | {ok, Map1a} = update(Incr1, Map0), 254 | % DC1 reset 255 | {ok, Reset1} = downstream({reset, {}}, Map1a), 256 | {ok, Map1b} = update(Reset1, Map1a), 257 | % DC2 a.remove 258 | {ok, Remove1} = downstream({remove, {a, antidote_crdt_counter_fat}}, Map0), 259 | {ok, Map2a} = update(Remove1, Map0), 260 | % DC2 --> DC1 261 | {ok, Map1c} = update(Remove1, Map1b), 262 | % DC1 reset 263 | {ok, Reset2} = downstream({reset, {}}, Map1c), 264 | {ok, Map1d} = update(Reset2, Map1c), 265 | % DC1: a.incr 266 | {ok, Incr2} = downstream({update, {{a, antidote_crdt_counter_fat}, {increment, 2}}}, Map1d), 267 | {ok, Map1e} = update(Incr2, Map1d), 268 | 269 | io:format("Map0 = ~p~n", [Map0]), 270 | io:format("Incr1 = ~p~n", [Incr1]), 271 | io:format("Map1a = ~p~n", [Map1a]), 272 | io:format("Reset1 = ~p~n", [Reset1]), 273 | io:format("Map1b = ~p~n", [Map1b]), 274 | io:format("Remove1 = ~p~n", [Remove1]), 275 | io:format("Map2a = ~p~n", [Map2a]), 276 | io:format("Map1c = ~p~n", [Map1c]), 277 | io:format("Reset2 = ~p~n", [Reset2]), 278 | io:format("Map1d = ~p~n", [Map1d]), 279 | io:format("Incr2 = ~p~n", [Incr2]), 280 | io:format("Map1e = ~p~n", [Map1e]), 281 | 282 | ?assertEqual([], value(Map0)), 283 | ?assertEqual([{{a, antidote_crdt_counter_fat}, 1}], value(Map1a)), 284 | ?assertEqual([], value(Map1b)), 285 | ?assertEqual([], value(Map2a)), 286 | ?assertEqual([], value(Map1c)), 287 | ?assertEqual([], value(Map1d)), 288 | ?assertEqual([{{a, antidote_crdt_counter_fat}, 2}], value(Map1e)). 289 | 290 | 291 | reset2_test() -> 292 | Map0 = new(), 293 | % DC1: s.add 294 | {ok, Add1} = downstream({update, {{s, antidote_crdt_set_rw}, {add, a}}}, Map0), 295 | {ok, Map1a} = update(Add1, Map0), 296 | % DC1 reset 297 | {ok, Reset1} = downstream({reset, {}}, Map1a), 298 | {ok, Map1b} = update(Reset1, Map1a), 299 | % DC2 s.remove 300 | {ok, Remove1} = downstream({remove, {s, antidote_crdt_set_rw}}, Map0), 301 | {ok, Map2a} = update(Remove1, Map0), 302 | % DC2 --> DC1 303 | {ok, Map1c} = update(Remove1, Map1b), 304 | % DC1 reset 305 | {ok, Reset2} = downstream({reset, {}}, Map1c), 306 | {ok, Map1d} = update(Reset2, Map1c), 307 | % DC1: s.add 308 | {ok, Add2} = downstream({update, {{s, antidote_crdt_set_rw}, {add, b}}}, Map1d), 309 | {ok, Map1e} = update(Add2, Map1d), 310 | 311 | io:format("Map0 = ~p~n" , [value(Map0)]), 312 | io:format("Add1 = ~p~n" , [Add1]), 313 | io:format("Map1a = ~p~n" , [value(Map1a)]), 314 | io:format("Reset1 = ~p~n" , [Reset1]), 315 | io:format("Map1b = ~p~n" , [value(Map1b)]), 316 | io:format("Remove1 = ~p~n", [Remove1]), 317 | io:format("Map2a = ~p~n" , [value(Map2a)]), 318 | io:format("Map1c = ~p~n" , [value(Map1c)]), 319 | io:format("Reset2 = ~p~n" , [Reset2]), 320 | io:format("Map1d = ~p~n" , [value(Map1d)]), 321 | io:format("Add2 = ~p~n" , [Add2]), 322 | io:format("Map1e = ~p~n" , [value(Map1e)]), 323 | 324 | ?assertEqual([], value(Map0)), 325 | ?assertEqual([{{s, antidote_crdt_set_rw}, [a]}], value(Map1a)), 326 | ?assertEqual([], value(Map1b)), 327 | ?assertEqual([], value(Map2a)), 328 | ?assertEqual([], value(Map1c)), 329 | ?assertEqual([], value(Map1d)), 330 | ?assertEqual([{{s, antidote_crdt_set_rw}, [b]}], value(Map1e)). 331 | 332 | prop1_test() -> 333 | Map0 = new(), 334 | % DC1: s.add 335 | {ok, Add1} = downstream({update, {{a, antidote_crdt_map_rr}, {update, {{a, antidote_crdt_set_rw}, {add, a}}}}}, Map0), 336 | {ok, Map1a} = update(Add1, Map0), 337 | 338 | % DC1 reset 339 | {ok, Reset1} = downstream({remove, {a, antidote_crdt_map_rr}}, Map1a), 340 | {ok, Map1b} = update(Reset1, Map1a), 341 | 342 | io:format("Map0 = ~p~n", [Map0]), 343 | io:format("Add1 = ~p~n", [Add1]), 344 | io:format("Map1a = ~p~n", [Map1a]), 345 | io:format("Reset1 = ~p~n", [Reset1]), 346 | io:format("Map1b = ~p~n", [Map1b]), 347 | 348 | ?assertEqual([], value(Map0)), 349 | ?assertEqual([{{a, antidote_crdt_map_rr}, [{{a, antidote_crdt_set_rw}, [a]}]}], value(Map1a)), 350 | ?assertEqual([], value(Map1b)). 351 | 352 | prop2_test() -> 353 | Map0 = new(), 354 | % DC1: update remove 355 | {ok, Add1} = downstream({update, [{{b, antidote_crdt_map_rr}, {remove, {a, antidote_crdt_set_rw}}}]}, Map0), 356 | {ok, Map1a} = update(Add1, Map0), 357 | 358 | % DC2 remove 359 | {ok, Remove2} = downstream({remove, {b, antidote_crdt_map_rr}}, Map0), 360 | {ok, Map2a} = update(Remove2, Map0), 361 | 362 | % pull DC2 -> DC1 363 | {ok, Map1b} = update(Remove2, Map1a), 364 | 365 | io:format("Map0 = ~p~n", [Map0]), 366 | io:format("Add1 = ~p~n", [Add1]), 367 | io:format("Map1a = ~p~n", [Map1a]), 368 | io:format("Remove2 = ~p~n", [Remove2]), 369 | io:format("Map1b = ~p~n", [Map1b]), 370 | 371 | ?assertEqual([], value(Map0)), 372 | ?assertEqual([], value(Map1a)), 373 | ?assertEqual([], value(Map2a)), 374 | ?assertEqual([], value(Map1b)). 375 | 376 | upd(Update, State) -> 377 | {ok, Downstream} = downstream(Update, State), 378 | {ok, Res} = update(Downstream, State), 379 | Res. 380 | 381 | remove_test() -> 382 | M1 = new(), 383 | ?assertEqual([], value(M1)), 384 | ?assertEqual(true, is_bottom(M1)), 385 | M2 = upd({update, [ 386 | {{<<"a">>, antidote_crdt_set_aw}, {add, <<"1">>}}, 387 | {{<<"b">>, antidote_crdt_register_mv}, {assign, <<"2">>}}, 388 | {{<<"c">>, antidote_crdt_counter_fat}, {increment, 1}} 389 | ]}, M1), 390 | ?assertEqual([ 391 | {{<<"a">>, antidote_crdt_set_aw}, [<<"1">>]}, 392 | {{<<"b">>, antidote_crdt_register_mv}, [<<"2">>]}, 393 | {{<<"c">>, antidote_crdt_counter_fat}, 1} 394 | ], value(M2)), 395 | ?assertEqual(false, is_bottom(M2)), 396 | M3 = upd({reset, {}}, M2), 397 | io:format("M3 state = ~p~n", [dict:to_list(M3)]), 398 | ?assertEqual([], value(M3)), 399 | ?assertEqual(true, is_bottom(M3)), 400 | ok. 401 | 402 | -endif. 403 | -------------------------------------------------------------------------------- /src/antidote_crdt_register_lww.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% @doc module antidote_crdt_register_lww - An operation based last-writer-wins register 30 | %% Each operation is assigned a timestamp, which is guaranteed to be greater than 31 | %% the current timestamp. 32 | %% The current value of the register is the value assigned with the greatest timestamp 33 | %% or the empty binary if there was no assignment yet. 34 | 35 | -module(antidote_crdt_register_lww). 36 | 37 | -behaviour(antidote_crdt). 38 | 39 | -ifdef(TEST). 40 | -include_lib("eunit/include/eunit.hrl"). 41 | -endif. 42 | 43 | -export([ new/0, 44 | value/1, 45 | downstream/2, 46 | update/2, 47 | equal/2, 48 | to_binary/1, 49 | from_binary/1, 50 | is_operation/1, 51 | require_state_downstream/1 52 | ]). 53 | 54 | -type antidote_crdt_register_lww() :: {non_neg_integer(), term()}. 55 | 56 | -type antidote_crdt_register_lww_op() :: {assign, term(), non_neg_integer()} | {assign, term()}. 57 | 58 | new() -> 59 | {0, <<>>}. 60 | 61 | value({_Time, Val}) -> 62 | Val. 63 | 64 | -spec downstream(antidote_crdt_register_lww_op(), antidote_crdt_register_lww()) -> {ok, term()}. 65 | downstream({assign, Value, Time}, {OldTime, _OldValue}) -> 66 | {ok, {max(Time, OldTime + 1), Value}}; 67 | downstream({assign, Value}, State) -> 68 | downstream({assign, Value, make_micro_epoch()}, State). 69 | 70 | make_micro_epoch() -> 71 | {Mega, Sec, Micro} = os:timestamp(), 72 | (Mega * 1000000 + Sec) * 1000000 + Micro. 73 | 74 | 75 | update(Effect, State) -> 76 | % take the state with maximum time, if times are equal use maximum state 77 | {ok, max(Effect, State)}. 78 | 79 | 80 | require_state_downstream(_Operation) -> true. 81 | 82 | is_operation({assign, _Value}) -> true; 83 | is_operation({assign, _Value, _Time}) -> true; 84 | is_operation(_Other) -> false. 85 | 86 | 87 | equal(CRDT1, CRDT2) -> 88 | CRDT1 == CRDT2. 89 | 90 | to_binary(CRDT) -> 91 | erlang:term_to_binary(CRDT). 92 | 93 | from_binary(Bin) -> 94 | {ok, erlang:binary_to_term(Bin)}. 95 | 96 | %% =================================================================== 97 | %% EUnit tests 98 | %% =================================================================== 99 | -ifdef(test). 100 | 101 | all_test() -> 102 | S0 = new(), 103 | {ok, Downstream} = downstream({assign, a}, S0), 104 | {ok, S1} = update(Downstream, S0), 105 | ?assertEqual(a, value(S1)). 106 | 107 | -endif. 108 | -------------------------------------------------------------------------------- /src/antidote_crdt_register_mv.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% @doc 30 | %% A Multi-Value Register CRDT. 31 | %% Read Returns a sorted list of all concurrently added values 32 | %% 33 | %% This is implemented, by assigning a unique token to every assign operation. 34 | %% The downstream effect carries the tokens of overridden values, so that 35 | %% only assignments, which happened before are really overridden and 36 | %% concurrent assignments are maintained 37 | %% 38 | %% @end 39 | 40 | -module(antidote_crdt_register_mv). 41 | 42 | -behaviour(antidote_crdt). 43 | 44 | %% Callbacks 45 | -export([ new/0, 46 | value/1, 47 | downstream/2, 48 | update/2, 49 | equal/2, 50 | to_binary/1, 51 | from_binary/1, 52 | is_operation/1, 53 | require_state_downstream/1, 54 | is_bottom/1 55 | ]). 56 | 57 | 58 | -ifdef(TEST). 59 | -include_lib("eunit/include/eunit.hrl"). 60 | -endif. 61 | 62 | -type antidote_crdt_register_mv() :: [{term(), uniqueToken()}]. 63 | -type uniqueToken() :: term(). 64 | -type antidote_crdt_register_mv_effect() :: 65 | {Value::term(), uniqueToken(), Overridden::[uniqueToken()]} 66 | | {reset, Overridden::[uniqueToken()]}. 67 | 68 | 69 | -type antidote_crdt_register_mv_op() :: {assign, term()}. 70 | 71 | 72 | %% @doc Create a new, empty `antidote_crdt_register_mv()' 73 | -spec new() -> antidote_crdt_register_mv(). 74 | new() -> 75 | []. 76 | 77 | 78 | 79 | %% @doc The values of this `antidote_crdt_register_mv()'. Multiple values can be returned, 80 | %% since there can be diverged value in this register. 81 | -spec value(antidote_crdt_register_mv()) -> [term()]. 82 | value(MVReg) -> 83 | [V || {V, _} <- MVReg]. 84 | 85 | 86 | -spec downstream(antidote_crdt_register_mv_op(), antidote_crdt_register_mv()) -> {ok, antidote_crdt_register_mv_effect()}. 87 | downstream({assign, Value}, MVReg) -> 88 | Token = unique(), 89 | Overridden = [Tok || {_, Tok} <- MVReg], 90 | {ok, {Value, Token, Overridden}}; 91 | downstream({reset, {}}, MVReg) -> 92 | Overridden = [Tok || {_, Tok} <- MVReg], 93 | {ok, {reset, Overridden}}. 94 | 95 | -spec unique() -> uniqueToken(). 96 | unique() -> 97 | crypto:strong_rand_bytes(20). 98 | 99 | 100 | -spec update(antidote_crdt_register_mv_effect(), antidote_crdt_register_mv()) -> {ok, antidote_crdt_register_mv()}. 101 | update({Value, Token, Overridden}, MVreg) -> 102 | % remove overridden values 103 | MVreg2 = [{V, T} || {V, T} <- MVreg, not lists:member(T, Overridden)], 104 | % insert new value 105 | {ok, insert_sorted({Value, Token}, MVreg2)}; 106 | update({reset, Overridden}, MVreg) -> 107 | MVreg2 = [{V, T} || {V, T} <- MVreg, not lists:member(T, Overridden)], 108 | {ok, MVreg2}. 109 | 110 | % insert value into sorted list 111 | insert_sorted(A, []) -> [A]; 112 | insert_sorted(A, [X|Xs]) when A < X -> [A, X|Xs]; 113 | insert_sorted(A, [X|Xs]) -> [X|insert_sorted(A, Xs)]. 114 | 115 | 116 | -spec equal(antidote_crdt_register_mv(), antidote_crdt_register_mv()) -> boolean(). 117 | equal(MVReg1, MVReg2) -> 118 | MVReg1 == MVReg2. 119 | 120 | -define(TAG, 85). 121 | -define(V1_VERS, 1). 122 | 123 | -spec to_binary(antidote_crdt_register_mv()) -> binary(). 124 | to_binary(MVReg) -> 125 | <>. 126 | 127 | %% @doc Decode binary `antidote_crdt_register_mv()' 128 | -spec from_binary(binary()) -> {ok, antidote_crdt_register_mv()} | {error, term()}. 129 | from_binary(<>) -> 130 | {ok, antidote_crdt:from_binary(Bin)}. 131 | 132 | 133 | %% @doc The following operation verifies 134 | %% that Operation is supported by this particular CRDT. 135 | -spec is_operation(term()) -> boolean(). 136 | is_operation({assign, _}) -> true; 137 | is_operation({reset, {}}) -> true; 138 | is_operation(_) -> false. 139 | 140 | require_state_downstream(_) -> 141 | true. 142 | 143 | is_bottom(State) -> State == new(). 144 | 145 | 146 | %% =================================================================== 147 | %% EUnit tests 148 | %% =================================================================== 149 | -ifdef(TEST). 150 | 151 | upd(Update, State) -> 152 | {ok, Downstream} = downstream(Update, State), 153 | {ok, Res} = update(Downstream, State), 154 | Res. 155 | 156 | reset_test() -> 157 | R1 = new(), 158 | ?assertEqual([], value(R1)), 159 | ?assertEqual(true, is_bottom(R1)), 160 | R2 = upd({assign, <<"a">>}, R1), 161 | ?assertEqual([<<"a">>], value(R2)), 162 | ?assertEqual(false, is_bottom(R2)), 163 | R3 = upd({reset, {}}, R2), 164 | ?assertEqual([], value(R3)), 165 | ?assertEqual(true, is_bottom(R3)). 166 | 167 | -endif. 168 | -------------------------------------------------------------------------------- /src/antidote_crdt_secure_counter_pn.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% Copyright <2020> < 3 | %% Technische Universität Kaiserslautern, Germany 4 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 5 | %% Universidade NOVA de Lisboa, Portugal 6 | %% Université catholique de Louvain (UCL), Belgique 7 | %% INESC TEC, Portugal 8 | %% > 9 | %% 10 | %% This file is provided to you under the Apache License, 11 | %% Version 2.0 (the "License"); you may not use this file 12 | %% except in compliance with the License. You may obtain 13 | %% a copy of the License at 14 | %% 15 | %% http://www.apache.org/licenses/LICENSE-2.0 16 | %% 17 | %% Unless required by applicable law or agreed to in writing, 18 | %% software distributed under the License is distributed on an 19 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20 | %% KIND, either expressed or implied. See the License for the 21 | %% specific language governing permissions and limitations 22 | %% under the License. 23 | %% 24 | %% List of the contributors to the development of Antidote: see AUTHORS file. 25 | %% Description and complete License: see LICENSE file. 26 | %% ------------------------------------------------------------------- 27 | 28 | %% antidote_crdt_secure_counter_pn: A convergent, replicated, operation 29 | %% based secure (using the Paillier cryptosystem) PN-Counter. 30 | 31 | -module(antidote_crdt_secure_counter_pn). 32 | 33 | -behaviour(antidote_crdt). 34 | 35 | -ifdef(TEST). 36 | -include_lib("eunit/include/eunit.hrl"). 37 | -endif. 38 | 39 | -export([ 40 | new/0, 41 | value/1, 42 | downstream/2, 43 | update/2, 44 | equal/2, 45 | to_binary/1, 46 | from_binary/1, 47 | is_operation/1, 48 | require_state_downstream/1 49 | ]). 50 | 51 | -type freshness() :: fresh | spoiled. 52 | -type delta() :: integer(). 53 | -type nsquare() :: pos_integer(). 54 | -type state() :: {freshness(), integer()}. 55 | -type op() :: {increment, {delta(), nsquare()}}. 56 | -type effect() :: {delta(), nsquare()}. 57 | 58 | %% @doc Create a new, empty 'antidote_crdt_secure_counter_pn'. 59 | %% 60 | %% The first element of the state tuple indicates whether the counter 61 | %% is newly created (no increments done yet) or not. The second element 62 | %% represents the encrypted (as specified by the Paillier scheme) total 63 | %% value of the counter. 64 | -spec new() -> state(). 65 | new() -> 66 | {fresh, 0}. 67 | 68 | %% @doc Returns the encrypted (as specified by the Paillier scheme) total 69 | %% value of a secure pn-counter. 70 | -spec value(state()) -> integer(). 71 | value({_, Value}) when is_integer(Value) -> 72 | Value. 73 | 74 | %% @doc Generate a downstream operation. 75 | %% 76 | %% The first parameter is a tuple of the form `{increment, {integer(), pos_integer()}}'. 77 | %% Where the first integer represents the encrypted delta value by which the counter 78 | %% will be incremented. The second integer represents the N squared value calculated 79 | %% during the key generation phase of the Paillier cryptosystem. 80 | %% 81 | %% The value of N is part of the user's public key, it is ok for the server to know this 82 | %% value. Invalid `NSquare' values (less than or equal to zero) are rejected, and a 83 | %% downstream effect is not generated. 84 | %% 85 | %% The second parameter is the secure pn-counter, this parameter is not actually used. 86 | -spec downstream(op(), state()) -> {ok, effect()}. 87 | downstream({increment, {Delta, NSquare}}, _SecurePNCounter) when 88 | is_integer(Delta) and is_integer(NSquare) and (NSquare > 0) 89 | -> 90 | {ok, {Delta, NSquare}}. 91 | 92 | %% @doc Updates a given secure pn-counter, incrementing it by a given 93 | %% encrypted `Delta'. By incrementing we mean performing the homomorphic 94 | %% addition of the counter value with the given delta. As described by 95 | %% the Paillier cryptosystem, the homomorphic addition of two plaintexts 96 | %% translates to the product of two ciphertexts modulo N squared. 97 | %% 98 | %% Returns the updated secure pn-counter. 99 | -spec update(effect(), state()) -> {ok, state()}. 100 | update({Delta, _NSquare}, {fresh, _Value}) -> 101 | {ok, {spoiled, Delta}}; 102 | update({Delta, NSquare}, {spoiled, Value}) -> 103 | {ok, {spoiled, (Value * Delta) rem NSquare}}. 104 | 105 | %% @doc Compare if two secure counters are equal. 106 | -spec equal(state(), state()) -> boolean(). 107 | equal(SecurePNCounter1, SecurePNCounter2) -> 108 | SecurePNCounter1 =:= SecurePNCounter2. 109 | 110 | -spec to_binary(state()) -> binary(). 111 | to_binary(SecurePNCounter) -> 112 | term_to_binary(SecurePNCounter). 113 | 114 | -spec from_binary(binary()) -> {ok, state()}. 115 | from_binary(Bin) -> 116 | {ok, binary_to_term(Bin)}. 117 | 118 | %% @doc The following function verifies that a given operation is supported by 119 | %% this particular CRDT. 120 | -spec is_operation(term()) -> boolean(). 121 | is_operation({increment, {Delta, NSquare}}) when 122 | is_integer(Delta) and is_integer(NSquare) and (NSquare > 0) 123 | -> 124 | true; 125 | is_operation(_) -> 126 | false. 127 | 128 | %% @doc Returns true if ?MODULE:downstream/2 needs the state of crdt 129 | %% to generate downstream effect. 130 | require_state_downstream(_) -> 131 | false. 132 | 133 | %% =================================================================== 134 | %% EUnit tests 135 | %% =================================================================== 136 | -ifdef(TEST). 137 | 138 | prepare_and_effect(Op, PNCounter) -> 139 | {ok, Downstream} = downstream(Op, PNCounter), 140 | update(Downstream, PNCounter). 141 | 142 | new_test() -> 143 | ?assertEqual({fresh, 0}, new()). 144 | 145 | value_test() -> 146 | ?assertEqual(0, value({fresh, 0})), 147 | ?assertEqual(4, value({spoiled, 4})). 148 | 149 | update_test() -> 150 | Counter = new(), 151 | % Fresh counter becomes spoiled. 152 | {ok, Counter1} = prepare_and_effect({increment, {2, 1}}, Counter), 153 | ?assertEqual({spoiled, 2}, Counter1), 154 | % Spoiled counter stays spoiled and correctly updates value. 155 | {ok, Counter2} = prepare_and_effect({increment, {3, 36}}, Counter1), 156 | ?assertEqual({spoiled, 6}, Counter2). 157 | 158 | reject_invalid_nsquare_test() -> 159 | Counter = new(), 160 | Operation1 = {increment, {1, 0}}, 161 | Operation2 = {increment, {1, -1}}, 162 | ?assertNot(is_operation(Operation1)), 163 | ?assertNot(is_operation(Operation2)), 164 | ?assertError(function_clause, downstream(Operation1, Counter)), 165 | ?assertError(function_clause, downstream(Operation2, Counter)). 166 | 167 | equal_test() -> 168 | Counter1 = {fresh, 4}, 169 | Counter2 = {fresh, 2}, 170 | Counter3 = {fresh, 2}, 171 | ?assertNot(equal(Counter1, Counter2)), 172 | ?assert(equal(Counter2, Counter3)), 173 | 174 | Counter4 = {spoiled, 5}, 175 | Counter5 = {spoiled, 3}, 176 | Counter6 = {spoiled, 3}, 177 | ?assertNot(equal(Counter4, Counter5)), 178 | ?assert(equal(Counter5, Counter6)), 179 | 180 | ?assertNot(equal(Counter1, Counter4)). 181 | 182 | binary_test() -> 183 | Counter1 = {spoiled, 4, 5}, 184 | BinaryCounter1 = to_binary(Counter1), 185 | {ok, Counter2} = from_binary(BinaryCounter1), 186 | ?assert(equal(Counter1, Counter2)). 187 | 188 | -endif. 189 | -------------------------------------------------------------------------------- /src/antidote_crdt_set_aw.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% @doc 30 | %% An operation-based Observed-Remove Set CRDT. 31 | %% As the data structure is operation-based, to issue an operation, one should 32 | %% firstly call `downstream/2' to get the downstream version of the 33 | %% operation and then call `update/2'. 34 | %% 35 | %% It provides five operations: add, which adds an element to a set; add_all, 36 | %% adds a list of elements to a set; remove, which removes an element from a set; 37 | %% remove_all that removes a list of elements from the set; update, that contains 38 | %% a list of previous four commands. 39 | %% 40 | %% This file is adapted from riak_dt_antidote_crdt_set_aw, a state-based implementation of 41 | %% Observed-Remove Set. 42 | %% The changes are as follows: 43 | %% 1. `generate_downstream/3' is added, as this is necessary for op-based CRDTs. 44 | %% 2. `merge/2' is removed. 45 | %% 3. There is no tombstone of removed elements. 46 | %% 47 | %% @reference Marc Shapiro, Nuno Preguiça, Carlos Baquero, Marek Zawirski (2011) A comprehensive study of 48 | %% Convergent and Commutative Replicated Data Types. http://hal.upmc.fr/inria-00555588/ 49 | %% 50 | %% @end 51 | -module(antidote_crdt_set_aw). 52 | 53 | -include("antidote_crdt.hrl"). 54 | 55 | %% Callbacks 56 | -export([ new/0, 57 | value/1, 58 | downstream/2, 59 | update/2, 60 | equal/2, 61 | to_binary/1, 62 | from_binary/1, 63 | is_operation/1, 64 | require_state_downstream/1, 65 | is_bottom/1 66 | ]). 67 | 68 | -behaviour(antidote_crdt). 69 | 70 | -ifdef(TEST). 71 | -include_lib("eunit/include/eunit.hrl"). 72 | -endif. 73 | 74 | -type antidote_crdt_set_aw() :: orddict:orddict(member(), tokens()). 75 | 76 | -type binary_antidote_crdt_set_aw() :: binary(). %% A binary that from_binary/1 will operate on. 77 | 78 | -type antidote_crdt_set_aw_op() :: 79 | {add, member()} 80 | | {remove, member()} 81 | | {add_all, [member()]} 82 | | {remove_all, [member()]} 83 | | {reset, {}}. 84 | 85 | %% The downstream op is a list of triples. 86 | %% In each triple: 87 | %% - the first component is the elem that was added or removed 88 | %% - the second component is the list of supporting tokens to be added 89 | %% - the third component is the list of supporting tokens to be removed 90 | -type downstream_op() :: [{member(), tokens(), tokens()}]. 91 | 92 | -type member() :: term(). 93 | -type token() :: binary(). 94 | -type tokens() :: [token()]. 95 | 96 | -spec new() -> antidote_crdt_set_aw(). 97 | new() -> 98 | orddict:new(). 99 | 100 | %% @doc return all existing elements in the `antidote_crdt_set_aw()'. 101 | -spec value(antidote_crdt_set_aw()) -> [member()]. 102 | value(Set_aw) -> 103 | orddict:fetch_keys(Set_aw). 104 | 105 | %% @doc generate downstream operations. 106 | %% If the operation is add or add_all, generate unique tokens for 107 | %% each element and fetches the current supporting tokens. 108 | %% If the operation is remove or remove_all, fetches current 109 | %% supporting tokens of these elements existing in the `antidote_crdt_set_aw()'. 110 | -spec downstream(antidote_crdt_set_aw_op(), antidote_crdt_set_aw()) -> {ok, downstream_op()}. 111 | downstream({add, Elem}, Set_aw) -> 112 | downstream({add_all, [Elem]}, Set_aw); 113 | downstream({add_all, Elems}, Set_aw) -> 114 | CreateDownstream = fun(Elem, CurrentTokens) -> 115 | Token = unique(), 116 | {Elem, [Token], CurrentTokens} 117 | end, 118 | DownstreamOps = create_downstreams(CreateDownstream, lists:usort(Elems), Set_aw, []), 119 | {ok, lists:reverse(DownstreamOps)}; 120 | downstream({remove, Elem}, Set_aw) -> 121 | downstream({remove_all, [Elem]}, Set_aw); 122 | downstream({remove_all, Elems}, Set_aw) -> 123 | CreateDownstream = fun(Elem, CurrentTokens) -> 124 | {Elem, [], CurrentTokens} 125 | end, 126 | DownstreamOps = create_downstreams(CreateDownstream, lists:usort(Elems), Set_aw, []), 127 | {ok, lists:reverse(DownstreamOps)}; 128 | downstream({reset, {}}, Set_aw) -> 129 | % reset is like removing all elements 130 | downstream({remove_all, value(Set_aw)}, Set_aw). 131 | 132 | %% @doc apply downstream operations and update an `antidote_crdt_set_aw()'. 133 | -spec update(downstream_op(), antidote_crdt_set_aw()) -> {ok, antidote_crdt_set_aw()}. 134 | update(DownstreamOp, Set_aw) -> 135 | {ok, apply_downstreams(DownstreamOp, Set_aw)}. 136 | 137 | -spec equal(antidote_crdt_set_aw(), antidote_crdt_set_aw()) -> boolean(). 138 | equal(Set_awA, Set_awB) -> 139 | % Everything inside is ordered, so this should work 140 | Set_awA == Set_awB. 141 | 142 | -define(TAG, 76). %?DT_ORSET_TAG from riak_dt 143 | -define(V1_VERS, 1). 144 | 145 | -spec to_binary(antidote_crdt_set_aw()) -> binary_antidote_crdt_set_aw(). 146 | to_binary(Set_aw) -> 147 | %% @TODO something smarter 148 | <>. 149 | 150 | from_binary(<>) -> 151 | %% @TODO something smarter 152 | {ok, binary_to_term(Bin)}. 153 | 154 | %% @doc generate a unique identifier (best-effort). 155 | -spec unique() -> token(). 156 | unique() -> 157 | crypto:strong_rand_bytes(20). 158 | 159 | %% @private generic downstream op creation for adds and removals 160 | create_downstreams(_CreateDownstream, [], _Set_aw, DownstreamOps) -> 161 | DownstreamOps; 162 | create_downstreams(CreateDownstream, Elems, [], DownstreamOps) -> 163 | lists:foldl( 164 | fun(Elem, Ops) -> 165 | DownstreamOp = CreateDownstream(Elem, []), 166 | [DownstreamOp|Ops] 167 | end, 168 | DownstreamOps, 169 | Elems 170 | ); 171 | create_downstreams(CreateDownstream, [Elem1|ElemsRest]=Elems, [{Elem2, Tokens}|Set_awRest]=Set_aw, DownstreamOps) -> 172 | if 173 | Elem1 == Elem2 -> 174 | DownstreamOp = CreateDownstream(Elem1, Tokens), 175 | create_downstreams(CreateDownstream, ElemsRest, Set_awRest, [DownstreamOp|DownstreamOps]); 176 | Elem1 > Elem2 -> 177 | create_downstreams(CreateDownstream, Elems, Set_awRest, DownstreamOps); 178 | true -> 179 | DownstreamOp = CreateDownstream(Elem1, []), 180 | create_downstreams(CreateDownstream, ElemsRest, Set_aw, [DownstreamOp|DownstreamOps]) 181 | end. 182 | 183 | %% @private apply a list of downstream ops to a given antidote_crdt_set_aw 184 | apply_downstreams([], Set_aw) -> 185 | Set_aw; 186 | apply_downstreams(Ops, []) -> 187 | lists:foldl( 188 | fun({Elem, ToAdd, ToRemove}, Set_aw) -> 189 | Set_aw ++ apply_downstream(Elem, [], ToAdd, ToRemove) 190 | end, 191 | [], 192 | Ops 193 | ); 194 | apply_downstreams([{Elem1, ToAdd, ToRemove}|OpsRest]=Ops, [{Elem2, CurrentTokens}|Set_awRest]=Set_aw) -> 195 | if 196 | Elem1 == Elem2 -> 197 | apply_downstream(Elem1, CurrentTokens, ToAdd, ToRemove) ++ apply_downstreams(OpsRest, Set_awRest); 198 | Elem1 > Elem2 -> 199 | [{Elem2, CurrentTokens} | apply_downstreams(Ops, Set_awRest)]; 200 | true -> 201 | apply_downstream(Elem1, [], ToAdd, ToRemove) ++ apply_downstreams(OpsRest, Set_aw) 202 | end. 203 | 204 | %% @private create an orddict entry from a downstream op 205 | apply_downstream(Elem, CurrentTokens, ToAdd, ToRemove) -> 206 | Tokens = (CurrentTokens ++ ToAdd) -- ToRemove, 207 | case Tokens of 208 | [] -> 209 | []; 210 | _ -> 211 | [{Elem, Tokens}] 212 | end. 213 | 214 | %% @doc The following operation verifies 215 | %% that Operation is supported by this particular CRDT. 216 | is_operation({add, _Elem}) -> true; 217 | is_operation({add_all, L}) when is_list(L) -> true; 218 | is_operation({remove, _Elem}) -> true; 219 | is_operation({remove_all, L}) when is_list(L) -> true; 220 | is_operation({reset, {}}) -> true; 221 | is_operation(_) -> false. 222 | 223 | require_state_downstream({add, _}) -> true; 224 | require_state_downstream({add_all, _}) -> true; 225 | require_state_downstream({remove, _}) -> true; 226 | require_state_downstream({remove_all, _}) -> true; 227 | require_state_downstream({reset, {}}) -> true. 228 | 229 | is_bottom(State) -> State == new(). 230 | 231 | %% =================================================================== 232 | %% EUnit tests 233 | %% =================================================================== 234 | -ifdef(TEST). 235 | 236 | new_test() -> 237 | ?assertEqual(orddict:new(), new()). 238 | 239 | add_test() -> 240 | Elem = <<"foo">>, 241 | Elems = [<<"li">>, <<"manu">>], 242 | Set1 = new(), 243 | {ok, DownstreamOp1} = downstream({add, Elem}, Set1), 244 | ?assertMatch([{Elem, _, _}], DownstreamOp1), 245 | {ok, DownstreamOp2} = downstream({add_all, Elems}, Set1), 246 | ?assertMatch([{<<"li">>, _, _}, {<<"manu">>, _, _}], DownstreamOp2), 247 | {ok, Set2} = update(DownstreamOp1, Set1), 248 | ?assertEqual([Elem], value(Set2)), 249 | {ok, Set3} = update(DownstreamOp2, Set1), 250 | ?assertEqual(Elems, value(Set3)). 251 | 252 | value_test() -> 253 | Set1 = new(), 254 | {ok, DownstreamOp1} = downstream({add, <<"foo">>}, Set1), 255 | ?assertEqual([], value(Set1)), 256 | {ok, Set2} = update(DownstreamOp1, Set1), 257 | ?assertEqual([<<"foo">>], value(Set2)), 258 | {ok, DownstreamOp2} = downstream({add_all, [<<"foo">>, <<"li">>, <<"manu">>]}, Set2), 259 | {ok, Set3} = update(DownstreamOp2, Set2), 260 | ?assertEqual([<<"foo">>, <<"li">>, <<"manu">>], value(Set3)). 261 | 262 | remove_test() -> 263 | Set1 = new(), 264 | %% Add an element then remove it 265 | {ok, Op1} = downstream({add, <<"foo">>}, Set1), 266 | {ok, Set2} = update(Op1, Set1), 267 | ?assertEqual([<<"foo">>], value(Set2)), 268 | {ok, Op2} = downstream({remove, <<"foo">>}, Set2), 269 | {ok, Set3} = update(Op2, Set2), 270 | ?assertEqual([], value(Set3)), 271 | 272 | %% Add many elements then remove part 273 | {ok, Op3} = downstream({add_all, [<<"foo">>, <<"li">>, <<"manu">>]}, Set1), 274 | {ok, Set4} = update(Op3, Set1), 275 | ?assertEqual([<<"foo">>, <<"li">>, <<"manu">>], value(Set4)), 276 | 277 | {ok, Op5} = downstream({remove_all, [<<"foo">>, <<"li">>]}, Set4), 278 | {ok, Set5} = update(Op5, Set4), 279 | ?assertEqual([<<"manu">>], value(Set5)), 280 | 281 | %% Remove more than current have 282 | {ok, Op6} = downstream({add_all, [<<"foo">>, <<"li">>, <<"manu">>]}, Set1), 283 | {ok, Set6} = update(Op6, Set1), 284 | {ok, Op7} = downstream({remove_all, [<<"manu">>, <<"test">>]}, Set6), 285 | Result = update(Op7, Set6), 286 | ?assertMatch({ok, _}, Result). 287 | 288 | 289 | remove2_test() -> 290 | Set1 = new(), 291 | %% Add an element then remove it 292 | {ok, Op1} = downstream({add, <<"foo">>}, Set1), 293 | {ok, Set2} = update(Op1, Set1), 294 | ?assertEqual([<<"foo">>], value(Set2)), 295 | {ok, Op2} = downstream({remove, <<"foo">>}, Set2), 296 | {ok, Set3} = update(Op2, Set2), 297 | ?assertEqual([], value(Set3)), 298 | 299 | %% Remove the element again (e.g. on a different replica) 300 | {ok, Op3} = downstream({remove, <<"foo">>}, Set2), 301 | {ok, Set4} = update(Op3, Set2), 302 | ?assertEqual([], value(Set4)), 303 | 304 | %% now execute Op3 on Set3, where the element was already removed locally 305 | {ok, Set5} = update(Op3, Set3), 306 | ?assertEqual([], value(Set5)). 307 | 308 | 309 | concurrent_add_test() -> 310 | Set1 = new(), 311 | %% Add an element then remove it 312 | {ok, Op1} = downstream({add, <<"foo">>}, Set1), 313 | {ok, Set2} = update(Op1, Set1), 314 | ?assertEqual([<<"foo">>], value(Set2)), 315 | 316 | %% If remove is concurrent with the second add, will not remove the second added 317 | {ok, Op2} = downstream({remove, <<"foo">>}, Set2), 318 | 319 | {ok, Op3} = downstream({add, <<"foo">>}, Set1), 320 | {ok, Set3} = update(Op3, Set2), 321 | ?assertEqual([<<"foo">>], value(Set3)), 322 | 323 | {ok, Set4} = update(Op2, Set3), 324 | ?assertEqual([<<"foo">>], value(Set4)), 325 | 326 | %% If remove follows two adds, remove will remove all 327 | {ok, Op4} = downstream({remove, <<"foo">>}, Set3), 328 | {ok, Set5} = update(Op4, Set3), 329 | ?assertEqual([], value(Set5)). 330 | 331 | binary_test() -> 332 | Set_aw1 = new(), 333 | BinarySet_aw1 = to_binary(Set_aw1), 334 | {ok, Set_aw2} = from_binary(BinarySet_aw1), 335 | ?assert(equal(Set_aw1, Set_aw2)), 336 | 337 | {ok, Op1} = downstream({add, <<"foo">>}, Set_aw1), 338 | {ok, Set_aw3} = update(Op1, Set_aw1), 339 | BinarySet_aw3 = to_binary(Set_aw3), 340 | {ok, Set_aw4} = from_binary(BinarySet_aw3), 341 | ?assert(equal(Set_aw3, Set_aw4)). 342 | 343 | -endif. 344 | -------------------------------------------------------------------------------- /src/antidote_crdt_set_go.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% @doc module antidote_crdt_set_go - An operation based grow-only set 30 | 31 | -module(antidote_crdt_set_go). 32 | 33 | -behaviour(antidote_crdt). 34 | 35 | -ifdef(TEST). 36 | -include_lib("eunit/include/eunit.hrl"). 37 | -endif. 38 | 39 | -export([ new/0, 40 | value/1, 41 | downstream/2, 42 | update/2, 43 | equal/2, 44 | to_binary/1, 45 | from_binary/1, 46 | is_operation/1, 47 | require_state_downstream/1 48 | ]). 49 | 50 | -type antidote_crdt_set_go() :: ordsets:ordset(member()). 51 | -type antidote_crdt_set_go_op() :: {add, member()} 52 | | {add_all, [member()]}. 53 | 54 | -type antidote_crdt_set_go_effect() :: antidote_crdt_set_go(). 55 | -type member() :: term(). 56 | 57 | new() -> 58 | ordsets:new(). 59 | 60 | value(Set) -> 61 | Set. 62 | 63 | -spec downstream(antidote_crdt_set_go_op(), antidote_crdt_set_go()) -> {ok, antidote_crdt_set_go_effect()}. 64 | downstream({add, Elem}, _State) -> 65 | {ok, ordsets:from_list([Elem])}; 66 | downstream({add_all, Elems}, _State) -> 67 | {ok, ordsets:from_list(Elems)}. 68 | 69 | update(Effect, State) -> 70 | {ok, ordsets:union(State, Effect)}. 71 | 72 | require_state_downstream(_Operation) -> false. 73 | 74 | is_operation({add, _}) -> true; 75 | is_operation({add_all, _}) -> true; 76 | is_operation(_) -> false. 77 | 78 | equal(CRDT1, CRDT2) -> 79 | CRDT1 == CRDT2. 80 | 81 | to_binary(CRDT) -> 82 | erlang:term_to_binary(CRDT). 83 | 84 | from_binary(Bin) -> 85 | {ok, erlang:binary_to_term(Bin)}. 86 | 87 | %% =================================================================== 88 | %% EUnit tests 89 | %% =================================================================== 90 | -ifdef(test). 91 | 92 | all_test() -> 93 | S0 = new(), 94 | {ok, Downstream} = downstream({add, a}, S0), 95 | {ok, S1} = update(Downstream, S0), 96 | ?assertEqual(1, antidote_crdt_set_go:stat(element_count, S1)). 97 | 98 | -endif. 99 | -------------------------------------------------------------------------------- /src/antidote_crdt_set_rw.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | %% @doc 30 | %% An operation-based Remove-wins Set CRDT. 31 | %% 32 | %% Semantics: 33 | %% Add-operations only affect the remove-operations, which they have observed. 34 | %% The concurrent remove-operations will win over an add-operation. 35 | %% A reset operation has the same effect as removing all possible elements (not only the ones currently in the set). 36 | %% 37 | %% Implementation: 38 | %% The implementation is not very efficient, in particular tombstones for removed elements are stored forever. 39 | %% 40 | %% The state is a pair of: 41 | %% a) the reset tokens, which store when the last resets were executed 42 | %% b) a mapping from elements to a set of tombstone-tokens 43 | %% 44 | %% An element is in the set, if its set of tombstone-tokens is empty. 45 | %% An add-operation will remove the current tombstones of an element. 46 | %% 47 | %% In this implementation of the RWSet, we remove entries for elements having no tokens 48 | %% (i.e. AddTokens == [] and RemoveTokens == []) 49 | 50 | %% @end 51 | -module(antidote_crdt_set_rw). 52 | 53 | 54 | %% Callbacks 55 | -export([ new/0, 56 | value/1, 57 | downstream/2, 58 | update/2, 59 | equal/2, 60 | to_binary/1, 61 | from_binary/1, 62 | is_operation/1, 63 | is_bottom/1, 64 | require_state_downstream/1 65 | ]). 66 | 67 | 68 | -behaviour(antidote_crdt). 69 | 70 | -ifdef(TEST). 71 | -include_lib("eunit/include/eunit.hrl"). 72 | -endif. 73 | 74 | -type antidote_crdt_set_rw() :: orddict:orddict(term(), {tokens(), tokens()}). 75 | 76 | -type binary_antidote_crdt_set_rw() :: binary(). %% A binary that from_binary/1 will operate on. 77 | 78 | -type antidote_crdt_set_rw_op() :: 79 | {add, member()} 80 | | {remove, member()} 81 | | {add_all, [member()]} 82 | | {remove_all, [member()]} 83 | | {reset, {}}. 84 | 85 | %% element, CurrentTokens, AddTokens, RemoveTokens 86 | -type downstream_op() :: [{member(), tokens(), tokens(), tokens()}]. 87 | 88 | -type member() :: term(). 89 | -type token() :: term(). 90 | -type tokens() :: [token()]. 91 | 92 | -spec new() -> antidote_crdt_set_rw(). 93 | new() -> 94 | orddict:new(). 95 | 96 | -spec value(antidote_crdt_set_rw()) -> [member()]. 97 | value(RWSet) -> 98 | [Elem || {Elem, {_, []}} <- RWSet]. 99 | 100 | -spec downstream(antidote_crdt_set_rw_op(), antidote_crdt_set_rw()) -> {ok, downstream_op()}. 101 | downstream({add, Elem}, RWSet) -> 102 | downstream({add_all, [Elem]}, RWSet); 103 | downstream({add_all, Elems}, RWSet) -> 104 | CreateDownstream = fun(Elem, CurrentTokens) -> 105 | Token = unique(), 106 | {Elem, CurrentTokens, [Token], []} 107 | end, 108 | DownstreamOps = create_downstreams(CreateDownstream, lists:usort(Elems), RWSet, []), 109 | {ok, lists:reverse(DownstreamOps)}; 110 | downstream({remove, Elem}, RWSet) -> 111 | downstream({remove_all, [Elem]}, RWSet); 112 | downstream({remove_all, Elems}, RWSet) -> 113 | CreateDownstream = fun(Elem, CurrentTokens) -> 114 | Token = unique(), 115 | {Elem, CurrentTokens, [], [Token]} 116 | end, 117 | DownstreamOps = create_downstreams(CreateDownstream, lists:usort(Elems), RWSet, []), 118 | {ok, lists:reverse(DownstreamOps)}; 119 | downstream({reset, {}}, RWSet) -> 120 | CreateDownstream = fun(Elem, CurrentTokens) -> 121 | {Elem, CurrentTokens, [], []} 122 | end, 123 | DownstreamOps = create_downstreams(CreateDownstream, lists:usort(orddict:fetch_keys(RWSet)), RWSet, []), 124 | {ok, lists:reverse(DownstreamOps)}. 125 | 126 | %% @private generic downstream op creation for adds and removals 127 | create_downstreams(_CreateDownstream, [], _RWSet, DownstreamOps) -> 128 | DownstreamOps; 129 | create_downstreams(CreateDownstream, Elems, [], DownstreamOps) -> 130 | lists:foldl( 131 | fun(Elem, Ops) -> 132 | DownstreamOp = CreateDownstream(Elem, []), 133 | [DownstreamOp|Ops] 134 | end, 135 | DownstreamOps, 136 | Elems 137 | ); 138 | create_downstreams(CreateDownstream, [Elem1|ElemsRest]=Elems, [{Elem2, {AddTokens, RemoveTokens}}|RWSetRest]=RWSet, DownstreamOps) -> 139 | if 140 | Elem1 == Elem2 -> 141 | DownstreamOp = CreateDownstream(Elem1, AddTokens ++ RemoveTokens), 142 | create_downstreams(CreateDownstream, ElemsRest, RWSetRest, [DownstreamOp|DownstreamOps]); 143 | Elem1 > Elem2 -> 144 | create_downstreams(CreateDownstream, Elems, RWSetRest, DownstreamOps); 145 | true -> 146 | DownstreamOp = CreateDownstream(Elem1, []), 147 | create_downstreams(CreateDownstream, ElemsRest, RWSet, [DownstreamOp|DownstreamOps]) 148 | end. 149 | 150 | %% @doc generate a unique identifier (best-effort). 151 | unique() -> 152 | crypto:strong_rand_bytes(20). 153 | 154 | -spec update(downstream_op(), antidote_crdt_set_rw()) -> {ok, antidote_crdt_set_rw()}. 155 | update(DownstreamOp, RWSet) -> 156 | RWSet1 = apply_downstreams(DownstreamOp, RWSet), 157 | RWSet2 = [Entry || {_, {AddTokens, RemoveTokens}} = Entry <- RWSet1, AddTokens =/= [] orelse RemoveTokens =/= []], 158 | {ok, RWSet2}. 159 | 160 | %% @private apply a list of downstream ops to a given orset 161 | apply_downstreams([], RWSet) -> 162 | RWSet; 163 | apply_downstreams(Ops, []) -> 164 | [apply_downstream(Elem, SeenTokens, ToAdd, ToRemove, [], []) || {Elem, SeenTokens, ToAdd, ToRemove} <- Ops]; 165 | apply_downstreams([{Elem1, SeenTokens, ToAdd, ToRemove}|OpsRest]=Ops, [{Elem2, {CurrentAddTokens, CurrentRemoveTokens}}|RWSetRest]=RWSet) -> 166 | if 167 | Elem1 == Elem2 -> 168 | [apply_downstream(Elem1, SeenTokens, ToAdd, ToRemove, CurrentAddTokens, CurrentRemoveTokens) | apply_downstreams(OpsRest, RWSetRest)]; 169 | Elem1 > Elem2 -> 170 | [{Elem2, {CurrentAddTokens, CurrentRemoveTokens}} | apply_downstreams(Ops, RWSetRest)]; 171 | true -> 172 | [apply_downstream(Elem1, SeenTokens, ToAdd, ToRemove, [], []) | apply_downstreams(OpsRest, RWSet)] 173 | end. 174 | 175 | %% @private create an orddict entry from a downstream op 176 | apply_downstream(Elem, SeenTokens, ToAdd, ToRemove, CurrentAddTokens, CurrentRemoveTokens) -> 177 | AddTokens = (CurrentAddTokens ++ ToAdd) -- SeenTokens, 178 | RemoveTokens = (CurrentRemoveTokens ++ ToRemove) -- SeenTokens, 179 | {Elem, {AddTokens, RemoveTokens}}. 180 | 181 | 182 | 183 | -spec equal(antidote_crdt_set_rw(), antidote_crdt_set_rw()) -> boolean(). 184 | equal(ORDictA, ORDictB) -> 185 | ORDictA == ORDictB. % Everything inside is ordered, so this should work 186 | 187 | -define(TAG, 77). 188 | -define(V1_VERS, 1). 189 | 190 | -spec to_binary(antidote_crdt_set_rw()) -> binary_antidote_crdt_set_rw(). 191 | to_binary(Set) -> 192 | %% @TODO something smarter 193 | <>. 194 | 195 | from_binary(<>) -> 196 | %% @TODO something smarter 197 | {ok, binary_to_term(Bin)}. 198 | 199 | is_operation({add, _Elem}) -> true; 200 | is_operation({add_all, L}) when is_list(L) -> true; 201 | is_operation({remove, _Elem}) -> 202 | true; 203 | is_operation({remove_all, L}) when is_list(L) -> true; 204 | is_operation({reset, {}}) -> true; 205 | is_operation(_) -> false. 206 | 207 | is_bottom(RWSet) -> 208 | RWSet == new(). 209 | 210 | require_state_downstream(_) -> true. 211 | 212 | %% =================================================================== 213 | %% EUnit tests 214 | %% =================================================================== 215 | -ifdef(TEST). 216 | 217 | reset1_test() -> 218 | Set0 = new(), 219 | % DC1 reset 220 | {ok, ResetEffect} = downstream({reset, {}}, Set0), 221 | {ok, Set1a} = update(ResetEffect, Set0), 222 | % DC1 add 223 | {ok, Add1Effect} = downstream({add, a}, Set1a), 224 | {ok, Set1b} = update(Add1Effect, Set1a), 225 | % DC2 add 226 | {ok, Add2Effect} = downstream({add, a}, Set0), 227 | {ok, Set2a} = update(Add2Effect, Set0), 228 | % pull 2 from DC1 to DC2 229 | {ok, Set2b} = update(ResetEffect, Set2a), 230 | {ok, Set2c} = update(Add1Effect, Set2b), 231 | 232 | io:format("ResetEffect = ~p~n", [ResetEffect]), 233 | io:format("Add1Effect = ~p~n", [Add1Effect]), 234 | io:format("Add2Effect = ~p~n", [Add2Effect]), 235 | 236 | io:format("Set1a = ~p~n", [Set1a]), 237 | io:format("Set1b = ~p~n", [Set1b]), 238 | io:format("Set2a = ~p~n", [Set2a]), 239 | io:format("Set2b = ~p~n", [Set2b]), 240 | io:format("Set2c = ~p~n", [Set2c]), 241 | 242 | ?assertEqual([], value(Set1a)), 243 | ?assertEqual([a], value(Set1b)), 244 | ?assertEqual([a], value(Set2a)), 245 | ?assertEqual([a], value(Set2b)), 246 | ?assertEqual([a], value(Set2c)). 247 | 248 | 249 | reset2_test() -> 250 | Set0 = new(), 251 | % DC1 reset 252 | {ok, Reset1Effect} = downstream({reset, {}}, Set0), 253 | {ok, Set1a} = update(Reset1Effect, Set0), 254 | % DC1 --> Dc2 255 | {ok, Set2a} = update(Reset1Effect, Set0), 256 | % DC2 reset 257 | {ok, Reset2Effect} = downstream({reset, {}}, Set2a), 258 | {ok, Set2b} = update(Reset2Effect, Set2a), 259 | % DC2 add 260 | {ok, Add2Effect} = downstream({add, a}, Set2b), 261 | {ok, Set2c} = update(Add2Effect, Set2b), 262 | % DC3 add 263 | {ok, Add3Effect} = downstream({add, a}, Set0), 264 | {ok, Set3a} = update(Add3Effect, Set0), 265 | % DC1 --> DC3 266 | {ok, Set3b} = update(Reset1Effect, Set3a), 267 | % DC2 --> DC3 268 | {ok, Set3c} = update(Reset2Effect, Set3b), 269 | % DC2 --> DC3 270 | {ok, Set3d} = update(Add2Effect, Set3c), 271 | 272 | 273 | ?assertEqual([], value(Set1a)), 274 | ?assertEqual([a], value(Set2c)), 275 | ?assertEqual([a], value(Set3d)). 276 | 277 | 278 | add_test() -> 279 | Set0 = new(), 280 | io:format("Set0 = ~p~n", [Set0]), 281 | % DC1 add 282 | {ok, Add1Effect} = downstream({add, a}, Set0), 283 | {ok, Set1} = update(Add1Effect, Set0), 284 | io:format("Set1 = ~p~n", [Set1]), 285 | % DC1 add 286 | {ok, Add2Effect} = downstream({add, b}, Set1), 287 | {ok, Set2} = update(Add2Effect, Set1), 288 | io:format("Set2 = ~p~n", [Set2]), 289 | 290 | io:format("Add1Effect = ~p~n", [Add1Effect]), 291 | io:format("Add2Effect = ~p~n", [Add2Effect]), 292 | 293 | ?assertEqual([], value(Set0)), 294 | ?assertEqual([a], value(Set1)), 295 | ?assertEqual([a, b], value(Set2)). 296 | 297 | prop1_test() -> 298 | Set0 = new(), 299 | % DC1 reset 300 | {ok, ResetEffect} = downstream({reset, {}}, Set0), 301 | {ok, Set1a} = update(ResetEffect, Set0), 302 | % DC3 add b 303 | {ok, Add3Effect} = downstream({add, b}, Set0), 304 | {ok, Set3a} = update(Add3Effect, Set0), 305 | % pull from DC3 to DC1 306 | {ok, Set1b} = update(Add3Effect, Set1a), 307 | 308 | io:format("ResetEffect = ~p~n", [ResetEffect]), 309 | io:format("Add3Effect = ~p~n", [Add3Effect]), 310 | 311 | io:format("Set1a = ~p~n", [Set1a]), 312 | io:format("Set3a = ~p~n", [Set3a]), 313 | io:format("Set1b = ~p~n", [Set1b]), 314 | 315 | ?assertEqual([], value(Set1a)), 316 | ?assertEqual([b], value(Set3a)), 317 | ?assertEqual([b], value(Set1b)). 318 | 319 | prop2_test() -> 320 | Set0 = new(), 321 | % DC1 remove b 322 | {ok, Remove1Effect} = downstream({remove, b}, Set0), 323 | {ok, Set1a} = update(Remove1Effect, Set0), 324 | % DC3 add a 325 | {ok, Add3Effect} = downstream({add, a}, Set0), 326 | {ok, Set3a} = update(Add3Effect, Set0), 327 | % pull from DC3 to DC1 328 | {ok, Set1b} = update(Add3Effect, Set1a), 329 | 330 | io:format("Remove1Effect = ~p~n", [Remove1Effect]), 331 | io:format("Add3Effect = ~p~n", [Add3Effect]), 332 | 333 | io:format("Set1a = ~p~n", [Set1a]), 334 | io:format("Set3a = ~p~n", [Set3a]), 335 | io:format("Set1b = ~p~n", [Set1b]), 336 | 337 | ?assertEqual([], value(Set1a)), 338 | ?assertEqual([a], value(Set3a)), 339 | ?assertEqual([a], value(Set1b)). 340 | 341 | 342 | prop3_test() -> 343 | Set0 = new(), 344 | % DC2 add a 345 | {ok, Add2Effect} = downstream({add, a}, Set0), 346 | {ok, Set2a} = update(Add2Effect, Set0), 347 | % DC3 reset 348 | {ok, Reset3Effect} = downstream({reset, {}}, Set0), 349 | {ok, Set3a} = update(Reset3Effect, Set0), 350 | % pull from DC2 to DC3 351 | {ok, Set3b} = update(Add2Effect, Set3a), 352 | 353 | io:format("Reset3Effect = ~p~n", [Reset3Effect]), 354 | io:format("Add2Effect = ~p~n", [Add2Effect]), 355 | 356 | io:format("Set2a = ~p~n", [Set2a]), 357 | io:format("Set3a = ~p~n", [Set3a]), 358 | io:format("Set3b = ~p~n", [Set3b]), 359 | 360 | ?assertEqual([a], value(Set2a)), 361 | ?assertEqual([], value(Set3a)), 362 | ?assertEqual([a], value(Set3b)). 363 | 364 | prop4_test() -> 365 | Set0 = new(), 366 | % DC2 add a 367 | {ok, Add2Effect} = downstream({add, a}, Set0), 368 | {ok, Set2a} = update(Add2Effect, Set0), 369 | % DC3 reset 370 | {ok, Reset3Effect} = downstream({reset, {}}, Set0), 371 | {ok, Set3a} = update(Reset3Effect, Set0), 372 | % pull from DC3 to DC2 373 | {ok, Set2b} = update(Reset3Effect, Set2a), 374 | 375 | io:format("Reset3Effect = ~p~n", [Reset3Effect]), 376 | io:format("Add2Effect = ~p~n", [Add2Effect]), 377 | 378 | io:format("Set2a = ~p~n", [Set2a]), 379 | io:format("Set3a = ~p~n", [Set3a]), 380 | io:format("Set3b = ~p~n", [Set2b]), 381 | 382 | ?assertEqual([a], value(Set2a)), 383 | ?assertEqual([], value(Set3a)), 384 | ?assertEqual([a], value(Set2b)). 385 | 386 | prop5_test() -> 387 | Set0 = new(), 388 | % DC2 add a 389 | {ok, Add2Effect} = downstream({add, a}, Set0), 390 | {ok, Set2a} = update(Add2Effect, Set0), 391 | % DC3 reset 392 | {ok, Reset2Effect} = downstream({reset, {}}, Set2a), 393 | {ok, Set2b} = update(Reset2Effect, Set2a), 394 | 395 | io:format("Reset2Effect = ~p~n", [Reset2Effect]), 396 | io:format("Add2Effect = ~p~n", [Add2Effect]), 397 | 398 | io:format("Set2a = ~p~n", [Set2a]), 399 | io:format("Set2b = ~p~n", [Set2b]), 400 | 401 | ?assertEqual([a], value(Set2a)), 402 | ?assertEqual([], Set2b), 403 | ?assertEqual([], value(Set2b)). 404 | 405 | -endif. 406 | -------------------------------------------------------------------------------- /test/crdt_properties.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(crdt_properties). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | 35 | 36 | -export([ 37 | crdt_satisfies_spec/3, 38 | crdt_satisfies_partial_spec/3, 39 | spec_to_partial/1, 40 | clock_le/2, 41 | subcontext/2, 42 | filter_resets/1, 43 | latest_operations/1 44 | ]). 45 | 46 | -export_type([clocked_operation/0]). 47 | 48 | 49 | 50 | -type clock() :: #{replica() => non_neg_integer()}. 51 | -type clocked_operation() :: {Clock :: clock(), Operation :: any()}. 52 | -type clocked_effect() :: {Clock :: clock(), Effect :: any()}. 53 | -type replica() :: dc1 | dc2 | dc3. 54 | 55 | -record(test_replica_state, { 56 | state :: any(), 57 | clock = #{} :: clock(), 58 | operations = [] :: [clocked_operation()], 59 | downstreamOps = [] :: [clocked_effect()] 60 | }). 61 | 62 | -type test_replica_state() :: #test_replica_state{}. 63 | -type test_state() :: #{replica() => test_replica_state()}. 64 | -type test_operation() :: {pull, replica(), replica()} | {exec, replica(), any()}. 65 | 66 | 67 | %% this checks whether the implementation satisfies a given CRDT specification 68 | %% Crdt: module name of the CRDT to test 69 | %% OperationGen: proper generator for generating a single random CRDT operation 70 | %% Spec: A function which takes a list of {Clock,Operation} pairs and returns the expected value of the CRDT. 71 | %% The clock can be used to determine the happens-before relation between operations 72 | -spec crdt_satisfies_spec(atom(), fun(() -> proper_types:raw_type()), fun(([clocked_operation()]) -> term())) -> proper:forall_clause(). 73 | crdt_satisfies_spec(Crdt, OperationGen, Spec) -> 74 | ?FORALL(Ops, generateOps(OperationGen), 75 | checkSpec(Crdt, Ops, Spec) 76 | ). 77 | 78 | %% this checks whether the implementation satisfies a given partial CRDT specification 79 | %% Crdt: module name of the CRDT to test 80 | %% OperationGen: proper generator for generating a single random CRDT operation 81 | %% Spec: A function which takes a list of {Clock,Operation} pairs and the value of the CRDT and checks some relation between them. 82 | -spec crdt_satisfies_partial_spec(atom(), fun(() -> proper_types:raw_type()), fun(([clocked_operation()], term()) -> proper:test())) -> proper:forall_clause(). 83 | crdt_satisfies_partial_spec(Crdt, OperationGen, Spec) -> 84 | ?FORALL(Ops, generateOps(OperationGen), 85 | checkPartialSpec(Crdt, Ops, Spec) 86 | ). 87 | 88 | 89 | 90 | % generates a list of operations 91 | generateOps(OpGen) -> 92 | list(oneof([ 93 | % pulls one operation from the first replica to the second 94 | {pull, replica(), replica()}, 95 | % execute operation on a given replica 96 | {exec, replica(), OpGen()} 97 | ])). 98 | 99 | replica() -> oneof([dc1, dc2, dc3]). 100 | 101 | clock_le(A, B) -> 102 | lists:all(fun(R) -> maps:get(R, A) =< maps:get(R, B, 0) end, maps:keys(A)). 103 | 104 | 105 | subcontext(Clock, Operations) -> 106 | [{OpClock, Op} || {OpClock, Op} <- Operations, clock_le(OpClock, Clock), not clock_le(Clock, OpClock)]. 107 | 108 | %% removes all operations which are followed by a reset-operation (including the resets) 109 | filter_resets(Operations) -> 110 | ResetClocks = [Clock || {Clock, {reset, {}}} <- Operations], 111 | % consider only operations, that are not invalidated by a reset: 112 | [{Clock, Op} || 113 | % all operations ... 114 | {Clock, Op} <- Operations, 115 | % such that no reset comes after the operation 116 | [] == [ResetClock || ResetClock <- ResetClocks, clock_le(Clock, ResetClock)]]. 117 | 118 | %% returns only those operations, which are not followed by another operation 119 | %% also removes all reset operations 120 | latest_operations(Operations) -> 121 | [Op || 122 | {Clock, Op} <- Operations, 123 | Op =/= {reset, {}}, 124 | [] == [C || {C, _} <- Operations, C =/= Clock, clock_le(Clock, C)]]. 125 | 126 | 127 | % executes/checks the specification 128 | checkSpec(Crdt, Ops, Spec) -> 129 | checkPartialSpec(Crdt, Ops, spec_to_partial(Spec)). 130 | 131 | % converts a full specification (returning a CRDT value) to a partial specification 132 | -spec spec_to_partial(fun(([clocked_operation()]) -> term())) -> fun(([clocked_operation()], term()) -> proper:test()). 133 | spec_to_partial(Spec) -> 134 | fun(Operations, RValue) -> 135 | SpecValue = Spec(Operations), 136 | ?WHENFAIL( 137 | begin 138 | io:format("Expected value: ~p~n", [SpecValue]), 139 | io:format("Actual value : ~p~n", [RValue]) 140 | end, 141 | SpecValue == RValue 142 | ) 143 | end. 144 | 145 | % executes/checks the specification 146 | checkPartialSpec(Crdt, Ops, Spec) -> 147 | % check that the CRDT is registered: 148 | true = antidote_crdt:is_type(Crdt), 149 | % check that all generated operatiosn are valid: 150 | _ = [case Crdt:is_operation(Op) of 151 | true -> true; 152 | false -> throw({invalid_operation, Op}) 153 | end || {exec, _, Op} <- Ops], 154 | 155 | InitialState = maps:from_list( 156 | [{Dc, #test_replica_state{state = Crdt:new()}} || Dc <- [dc1, dc2, dc3]]), 157 | EndState = execSystem(Crdt, Ops, InitialState), 158 | conjunction( 159 | [{binary_encoding, checkBinaryEncoding(Crdt, EndState)}] ++ 160 | [{R, 161 | checkSpecEnd(Crdt, Spec, EndState, R)} 162 | || R <- maps:keys(EndState)]). 163 | 164 | checkSpecEnd(Crdt, Spec, EndState, R) -> 165 | RState = maps:get(R, EndState), 166 | RClock = RState#test_replica_state.clock, 167 | RValue = Crdt:value(RState#test_replica_state.state), 168 | 169 | % get the visible operations: 170 | VisibleOperations = [{Clock, Op} || 171 | Replica <- maps:keys(EndState), 172 | {Clock, Op} <- (maps:get(Replica, EndState))#test_replica_state.operations, 173 | clock_le(Clock, RClock)], 174 | 175 | ?WHENFAIL( 176 | begin 177 | io:format("Checking value on ~p~n", [R]) 178 | end, 179 | Spec(VisibleOperations, RValue) 180 | ). 181 | 182 | 183 | -spec execSystem(atom(), [test_operation()], test_state()) -> test_state(). 184 | execSystem(_Crdt, [], State) -> 185 | State; 186 | execSystem(Crdt, [{pull, Source, Target}|RemainingOps], State) -> 187 | TargetState = maps:get(Target, State), 188 | TargetClock = TargetState#test_replica_state.clock, 189 | SourceState = maps:get(Source, State), 190 | % get all downstream operations at the source, which are not yet delivered to the target, 191 | % and which have all dependencies already delivered at the target 192 | Effects = [{Clock, Effect} || 193 | {Clock, Effect} <- SourceState#test_replica_state.downstreamOps, 194 | not clock_le(Clock, TargetClock), 195 | clock_le(Clock#{Source => 0}, TargetClock) 196 | ], 197 | NewState = 198 | case Effects of 199 | [] -> State; 200 | [{Clock, Op}|_] -> 201 | {ok, NewCrdtState} = Crdt:update(Op, TargetState#test_replica_state.state), 202 | NewTargetState = TargetState#test_replica_state{ 203 | state = NewCrdtState, 204 | clock = TargetClock#{Source => maps:get(Source, Clock)} 205 | }, 206 | State#{Target => NewTargetState} 207 | end, 208 | 209 | 210 | execSystem(Crdt, RemainingOps, NewState); 211 | execSystem(Crdt, [{exec, Replica, Op}|RemainingOps], State) -> 212 | ReplicaState = maps:get(Replica, State), 213 | CrdtState = ReplicaState#test_replica_state.state, 214 | CrdtStateForDownstream = 215 | case Crdt:require_state_downstream(Op) of 216 | true -> CrdtState; 217 | false -> no_state 218 | end, 219 | {ok, Effect} = Crdt:downstream(Op, CrdtStateForDownstream), 220 | {ok, NewCrdtState} = Crdt:update(Effect, CrdtState), 221 | 222 | ReplicaClock = ReplicaState#test_replica_state.clock, 223 | NewReplicaClock = ReplicaClock#{Replica => maps:get(Replica, ReplicaClock, 0) + 1}, 224 | 225 | NewReplicaState = ReplicaState#test_replica_state{ 226 | state = NewCrdtState, 227 | clock = NewReplicaClock, 228 | operations = ReplicaState#test_replica_state.operations ++ [{NewReplicaClock, Op}], 229 | downstreamOps = ReplicaState#test_replica_state.downstreamOps ++ [{NewReplicaClock, Effect}] 230 | }, 231 | NewState = State#{Replica => NewReplicaState}, 232 | execSystem(Crdt, RemainingOps, NewState). 233 | 234 | 235 | 236 | checkBinaryEncoding(Crdt, EndState) -> 237 | conjunction([{R, checkBinaryEncoding(Crdt, EndState, R)} || R <- maps:keys(EndState)]). 238 | 239 | checkBinaryEncoding(Crdt, EndState, R) -> 240 | RState = maps:get(R, EndState), 241 | CrdtState = RState#test_replica_state.state, 242 | BinState = Crdt:to_binary(CrdtState), 243 | true = is_binary(BinState), 244 | {ok, CrdtState2} = Crdt:from_binary(BinState), 245 | 246 | conjunction([ 247 | {equal_state, ?WHENFAIL( 248 | begin 249 | io:format("CRDT state before: ~p~n", [CrdtState]), 250 | io:format("CRDT state after: ~p~n", [CrdtState2]) 251 | end, 252 | Crdt:equal(CrdtState, CrdtState2) 253 | )}, 254 | {equal_value, ?WHENFAIL( 255 | begin 256 | io:format("CRDT value before: ~p~n", [Crdt:value(CrdtState)]), 257 | io:format("CRDT value after: ~p~n", [rdt:value(CrdtState2)]) 258 | end, 259 | Crdt:value(CrdtState) == Crdt:value(CrdtState2) 260 | )} 261 | ]). 262 | 263 | 264 | %%printState(State) -> 265 | %% [printReplicaState(R, ReplicaState) || {R, ReplicaState} <- maps:to_list(State)]. 266 | %% 267 | %%printReplicaState(R, S) -> 268 | %% io:format("Replica ~p : ~n", [R]), 269 | %% io:format(" State ~p : ~n", [S#test_replica_state.state]), 270 | %% io:format(" operations ~p : ~n", [S#test_replica_state.operations]), 271 | %% io:format(" downstreamOps ~p : ~n", [S#test_replica_state.downstreamOps]), 272 | %% ok. 273 | -------------------------------------------------------------------------------- /test/prop_counter_fat.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(prop_counter_fat). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | %% API 35 | -export([prop_counter_fat_spec/0, op/0, spec/1]). 36 | 37 | 38 | prop_counter_fat_spec() -> 39 | crdt_properties:crdt_satisfies_spec(antidote_crdt_counter_fat, fun op/0, fun spec/1). 40 | 41 | 42 | spec(Operations1) -> 43 | Operations = crdt_properties:filter_resets(Operations1), 44 | lists:sum([X || {_, {increment, X}} <- Operations]) 45 | - lists:sum([X || {_, {decrement, X}} <- Operations]). 46 | 47 | % generates a random counter operation 48 | op() -> 49 | oneof([ 50 | {increment, integer()}, 51 | {decrement, integer()}, 52 | {reset, {}} 53 | ]). 54 | 55 | -------------------------------------------------------------------------------- /test/prop_counter_pn.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(prop_counter_pn). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | %% API 35 | -export([prop_counter_pn_spec/0, op/0, spec/1]). 36 | 37 | 38 | prop_counter_pn_spec() -> 39 | crdt_properties:crdt_satisfies_spec(antidote_crdt_counter_pn, fun op/0, fun spec/1). 40 | 41 | 42 | spec(Operations) -> 43 | lists:sum([X || {_, {increment, X}} <- Operations]) 44 | + lists:sum([1 || {_, increment} <- Operations]) 45 | - lists:sum([X || {_, {decrement, X}} <- Operations]) 46 | - lists:sum([1 || {_, decrement} <- Operations]). 47 | 48 | % generates a random counter operation 49 | op() -> 50 | oneof([ 51 | increment, 52 | decrement, 53 | {increment, integer()}, 54 | {decrement, integer()} 55 | ]). 56 | 57 | -------------------------------------------------------------------------------- /test/prop_flag_dw.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(prop_flag_dw). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | %% API 35 | -export([prop_flag_dw_spec/0, op/0, spec/1]). 36 | 37 | prop_flag_dw_spec() -> 38 | crdt_properties:crdt_satisfies_spec(antidote_crdt_flag_dw, fun op/0, fun spec/1). 39 | 40 | 41 | spec(Operations) -> 42 | LatestOps = crdt_properties:latest_operations(Operations), 43 | Enables = [enable || {enable, {}} <- LatestOps], 44 | Disables = [disable || {disable, {}} <- LatestOps], 45 | % returns true, when there is an enable-operation and no disable operation among the latest operations: 46 | Enables =/= [] andalso Disables == []. 47 | 48 | % generates a random operation 49 | op() -> 50 | oneof([ 51 | {enable, {}}, 52 | {disable, {}}, 53 | {reset, {}} 54 | ]). 55 | 56 | -------------------------------------------------------------------------------- /test/prop_flag_ew.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(prop_flag_ew). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | %% API 35 | -export([prop_flag_ew_spec/0, op/0, spec/1]). 36 | 37 | prop_flag_ew_spec() -> 38 | crdt_properties:crdt_satisfies_spec(antidote_crdt_flag_ew, fun op/0, fun spec/1). 39 | 40 | 41 | spec(Operations) -> 42 | LatestOps = crdt_properties:latest_operations(Operations), 43 | Enables = [enable || {enable, {}} <- LatestOps], 44 | % returns true, when there is an enable-operation among the latest operations: 45 | Enables =/= []. 46 | 47 | % generates a random operation 48 | op() -> 49 | oneof([ 50 | {enable, {}}, 51 | {disable, {}}, 52 | {reset, {}} 53 | ]). 54 | 55 | -------------------------------------------------------------------------------- /test/prop_map_go.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(prop_map_go). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | %% API 35 | -export([prop_map_go_spec/0]). 36 | 37 | 38 | prop_map_go_spec() -> 39 | crdt_properties:crdt_satisfies_spec(antidote_crdt_map_go, fun op/0, fun spec/1). 40 | 41 | 42 | spec(Operations1) -> 43 | Operations = lists:flatmap(fun normalizeOp/1, Operations1), 44 | Keys = lists:usort([Key || {_, {update, {Key, _}}} <- Operations]), 45 | GroupedByKey = [{Key, nestedOps(Operations, Key)} || Key <- Keys], 46 | NestedSpec = [{{Key, Type}, nestedSpec(Type, Ops)} || {{Key, Type}, Ops} <- GroupedByKey], 47 | lists:sort(NestedSpec). 48 | 49 | nestedOps(Operations, Key) -> 50 | [{Clock, NestedOp} || {Clock, {update, {Key2, NestedOp}}} <- Operations, Key == Key2]. 51 | 52 | nestedSpec(antidote_crdt_map_go, Ops) -> spec(Ops); 53 | nestedSpec(antidote_crdt_set_aw, Ops) -> prop_set_aw:spec(Ops); 54 | nestedSpec(antidote_crdt_counter_pn, Ops) -> prop_counter_pn:spec(Ops). 55 | 56 | 57 | normalizeOp({Clock, {update, List}}) when is_list(List) -> 58 | [{Clock, {update, X}} || X <- List]; 59 | normalizeOp(X) -> [X]. 60 | 61 | 62 | % generates a random operation 63 | op() -> ?SIZED(Size, op(Size)). 64 | op(Size) -> 65 | {update, oneof([ 66 | nestedOp(Size), 67 | ?LET(L, list(nestedOp(Size div 2)), removeDuplicateKeys(L, [])) 68 | ])}. 69 | 70 | removeDuplicateKeys([], _) -> []; 71 | removeDuplicateKeys([{Key, Op}|Rest], Keys) -> 72 | case lists:member(Key, Keys) of 73 | true -> removeDuplicateKeys(Rest, Keys); 74 | false -> [{Key, Op}|removeDuplicateKeys(Rest, [Key|Keys])] 75 | end. 76 | 77 | nestedOp(Size) -> 78 | oneof( 79 | [ 80 | % TODO add other type (orset) and recursive maps 81 | % TODO make sure that keys are unique here and in the is_operation check 82 | {{key(), antidote_crdt_set_aw}, prop_set_aw:op()}, 83 | {{key(), antidote_crdt_counter_pn}, prop_counter_pn:op()} 84 | ] 85 | ++ 86 | if 87 | Size > 1 -> 88 | [{{key(), antidote_crdt_map_go}, ?LAZY(op(Size div 2))}]; 89 | true -> [] 90 | end 91 | ). 92 | 93 | 94 | key() -> 95 | oneof([a, b, c, d]). 96 | -------------------------------------------------------------------------------- /test/prop_map_rr.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(prop_map_rr). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | %% API 35 | -export([prop_map_rr_spec/0]). 36 | 37 | prop_map_rr_spec() -> 38 | crdt_properties:crdt_satisfies_partial_spec(antidote_crdt_map_rr, fun op/0, fun spec/2). 39 | 40 | 41 | spec(Operations1, Value) -> 42 | Operations = normalize(Operations1), 43 | % Collect all keys from all all updates 44 | Keys = allKeys(Operations), 45 | GroupedByKey = [{Key, nestedOps(Operations, Key)} || Key <- Keys], 46 | KeyCheck = 47 | fun({{Key, Type}, Ops}) -> 48 | ?WHENFAIL( 49 | begin 50 | io:format("~n~nOperations1 = ~p~n", [Operations1]), 51 | io:format("Operations = ~p~n", [Operations]), 52 | io:format("GroupedByKey = ~p~n", [GroupedByKey]) 53 | end, 54 | nestedSpec(Type, Ops, antidote_crdt_map_rr:get({Key, Type}, Value)) 55 | ) 56 | end, 57 | conjunction( 58 | [{Key, KeyCheck({{Key, Type}, Ops})} 59 | || {{Key, Type}, Ops} <- GroupedByKey]). 60 | 61 | normalize(Operations) -> 62 | lists:flatmap(fun(Op) -> normalizeOp(Op, Operations) end, Operations). 63 | 64 | allKeys(Operations) -> 65 | lists:usort([Key || {_AddClock, {update, {Key, _}}} <- Operations]). 66 | 67 | nestedOps(Operations, {_, Type}=Key) -> 68 | Resets = 69 | case Type:is_operation({reset, {}}) of 70 | true -> 71 | [{Clock, {reset, {}}} || {Clock, {remove, Key2}} <- Operations, Key == Key2]; 72 | false -> [] 73 | end, 74 | Resets ++ [{Clock, NestedOp} || {Clock, {update, {Key2, NestedOp}}} <- Operations, Key == Key2]. 75 | 76 | nestedSpec(antidote_crdt_map_rr, Ops, Value) -> spec(Ops, Value); 77 | % nestedSpec(antidote_crdt_set_aw, Ops) -> prop_set_aw:spec(Ops); 78 | % nestedSpec(antidote_crdt_counter_fat, Ops) -> prop_counter_fat:spec(Ops); 79 | nestedSpec(antidote_crdt_set_rw, Ops, Value) -> 80 | (crdt_properties:spec_to_partial(fun prop_set_rw:spec/1))(Ops, Value). 81 | 82 | % normalizes operations (update-lists into single update-operations) 83 | normalizeOp({Clock, {update, List}}, _) when is_list(List) -> 84 | [{Clock, {update, X}} || X <- List]; 85 | normalizeOp({Clock, {remove, List}}, _) when is_list(List) -> 86 | [{Clock, {remove, X}} || X <- List]; 87 | normalizeOp({Clock, {batch, {Updates, Removes}}}, _) -> 88 | [{Clock, {update, X}} || X <- Updates] 89 | ++ [{Clock, {remove, X}} || X <- Removes]; 90 | normalizeOp({Clock, {reset, {}}}, Operations) -> 91 | % reset is like remove on all keys 92 | Keys = allKeys(normalize(crdt_properties:subcontext(Clock, Operations))), 93 | [{Clock, {remove, X}} || X <- Keys]; 94 | normalizeOp(X, _) -> [X]. 95 | 96 | 97 | % generates a random operation 98 | op() -> ?SIZED(Size, op(Size)). 99 | op(Size) -> 100 | oneof([ 101 | {update, nestedOp(Size)}, 102 | {update, ?LET(L, list(nestedOp(Size div 2)), removeDuplicateKeys(L, []))}, 103 | {remove, typed_key()}, 104 | {remove, ?LET(L, list(typed_key()), lists:usort(L))}, 105 | ?LET({Updates, Removes}, 106 | {list(nestedOp(Size div 2)), list(typed_key())}, 107 | begin 108 | Removes2 = lists:usort(Removes), 109 | Updates2 = removeDuplicateKeys(Updates, Removes2), 110 | {batch, {Updates2, Removes2}} 111 | end), 112 | {reset, {}} 113 | ]). 114 | 115 | removeDuplicateKeys([], _) -> []; 116 | removeDuplicateKeys([{Key, Op}|Rest], Keys) -> 117 | case lists:member(Key, Keys) of 118 | true -> removeDuplicateKeys(Rest, Keys); 119 | false -> [{Key, Op}|removeDuplicateKeys(Rest, [Key|Keys])] 120 | end. 121 | 122 | nestedOp(Size) -> 123 | oneof( 124 | [ 125 | % {{key(), antidote_crdt_counter_fat}, prop_counter_fat:op()}, 126 | % {{key(), antidote_crdt_set_aw}, prop_set_aw:op()}, 127 | {{key(), antidote_crdt_set_rw}, prop_set_rw:op()} 128 | ] 129 | ++ 130 | if 131 | Size > 1 -> 132 | [{{key(), antidote_crdt_map_rr}, ?LAZY(op(Size div 2))}]; 133 | true -> [] 134 | end 135 | ). 136 | 137 | typed_key() -> {key(), crdt_type()}. 138 | 139 | crdt_type() -> 140 | oneof([antidote_crdt_set_rw, antidote_crdt_map_rr]). 141 | 142 | key() -> 143 | oneof([key1, key2, key3, key4]). 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /test/prop_register_lww.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(prop_register_lww). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | %% API 35 | -export([prop_register_lww_spec/0]). 36 | 37 | 38 | prop_register_lww_spec() -> 39 | crdt_properties:crdt_satisfies_spec(antidote_crdt_register_lww, fun op/0, fun spec/1). 40 | 41 | 42 | spec(Operations) -> 43 | case Operations of 44 | [] -> 45 | % initial value is empty binary 46 | <<>>; 47 | _ -> 48 | % times are corrected, so that old values are always overridden 49 | Operations2 = [correctTime(Operations, Op) || Op <- Operations], 50 | % select the value based on the assign with maximum timestamp (or max. value if timestamps are equal) 51 | {_MaxTime, MaxVal} = lists:max([{Time, Val} || {_, {assign, Val, Time}} <- Operations2]), 52 | MaxVal 53 | end. 54 | 55 | correctTime(Operations, {OperationVc, {assign, Value, Timestamp}}) -> 56 | Before = [{Vc, Op} || {Vc, Op} <- Operations, Vc =/= OperationVc, crdt_properties:clock_le(Vc, OperationVc)], 57 | Before2 = [correctTime(Before, Op) || Op <- Before], 58 | BeforeTimestampMax = lists:max([0] ++ [T || {_, {assign, _, T}} <- Before2]), 59 | {OperationVc, {assign, Value, max(Timestamp, BeforeTimestampMax+1) }}. 60 | 61 | 62 | % generates a random counter operation 63 | op() -> 64 | {assign, oneof([a, b, c, d, e, f, g, h, i]), non_neg_integer()}. 65 | 66 | -------------------------------------------------------------------------------- /test/prop_register_mv.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(prop_register_mv). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | %% API 35 | -export([prop_register_mv_spec/0]). 36 | 37 | 38 | prop_register_mv_spec() -> 39 | crdt_properties:crdt_satisfies_spec(antidote_crdt_register_mv, fun op/0, fun spec/1). 40 | 41 | 42 | spec(Operations) -> 43 | lists:sort([Val || {assign, Val} <- crdt_properties:latest_operations(Operations)]). 44 | 45 | 46 | 47 | % generates a random operation 48 | op() -> 49 | frequency([ 50 | {5, {assign, oneof([a, b, c, d, e, f, g, h, i])}}, 51 | {1, {reset, {}}} 52 | ]). 53 | 54 | -------------------------------------------------------------------------------- /test/prop_set_aw.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(prop_set_aw). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | %% API 35 | -export([prop_set_aw_spec/0, op/0, spec/1]). 36 | 37 | 38 | prop_set_aw_spec() -> 39 | crdt_properties:crdt_satisfies_spec(antidote_crdt_set_aw, fun op/0, fun spec/1). 40 | 41 | 42 | spec(Operations1) -> 43 | Operations = lists:flatmap(fun normalizeOperation/1, Operations1), 44 | lists:usort( 45 | % all X, 46 | [X || 47 | % such that there is an add operation for X 48 | {AddClock, {add, X}} <- Operations, 49 | % and there is no remove operation after the add 50 | [] == [Y || {RemoveClock, {remove, Y}} <- Operations, X == Y, crdt_properties:clock_le(AddClock, RemoveClock)], 51 | % and there is no reset operation after the add 52 | [] == [{reset, {}} || {ResetClock, {reset, {}}} <- Operations, crdt_properties:clock_le(AddClock, ResetClock)] 53 | ]). 54 | 55 | % transforms add_all and remove_all into single operations 56 | normalizeOperation({Clock, {add_all, Elems}}) -> 57 | [{Clock, {add, Elem}} || Elem <- Elems]; 58 | normalizeOperation({Clock, {remove_all, Elems}}) -> 59 | [{Clock, {remove, Elem}} || Elem <- Elems]; 60 | normalizeOperation(X) -> 61 | [X]. 62 | 63 | % generates a random operation 64 | op() -> 65 | oneof([ 66 | {add, set_element()}, 67 | {add_all, list(set_element())}, 68 | {remove, set_element()}, 69 | {remove_all, list(set_element())}, 70 | {reset, {}} 71 | ]). 72 | 73 | set_element() -> 74 | oneof([a, b]). 75 | -------------------------------------------------------------------------------- /test/prop_set_go.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(prop_set_go). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | %% API 35 | -export([prop_set_go_spec/0]). 36 | 37 | 38 | prop_set_go_spec() -> 39 | crdt_properties:crdt_satisfies_spec(antidote_crdt_set_go, fun op/0, fun spec/1). 40 | 41 | 42 | spec(Operations) -> 43 | lists:usort( 44 | [X || {_, {add, X}} <- Operations] 45 | ++ [X || {_, {add_all, Xs}} <- Operations, X <- Xs] 46 | ). 47 | 48 | % generates a random counter operation 49 | op() -> 50 | oneof([ 51 | {add, set_element()}, 52 | {add_all, list(set_element())} 53 | ]). 54 | 55 | set_element() -> 56 | oneof([a, b, c, d]). 57 | -------------------------------------------------------------------------------- /test/prop_set_rw.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright <2013-2018> < 4 | %% Technische Universität Kaiserslautern, Germany 5 | %% Université Pierre et Marie Curie / Sorbonne-Université, France 6 | %% Universidade NOVA de Lisboa, Portugal 7 | %% Université catholique de Louvain (UCL), Belgique 8 | %% INESC TEC, Portugal 9 | %% > 10 | %% 11 | %% This file is provided to you under the Apache License, 12 | %% Version 2.0 (the "License"); you may not use this file 13 | %% except in compliance with the License. You may obtain 14 | %% a copy of the License at 15 | %% 16 | %% http://www.apache.org/licenses/LICENSE-2.0 17 | %% 18 | %% Unless required by applicable law or agreed to in writing, 19 | %% software distributed under the License is distributed on an 20 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | %% KIND, either expressed or implied. See the License for the 22 | %% specific language governing permissions and limitations 23 | %% under the License. 24 | %% 25 | %% List of the contributors to the development of Antidote: see AUTHORS file. 26 | %% Description and complete License: see LICENSE file. 27 | %% ------------------------------------------------------------------- 28 | 29 | -module(prop_set_rw). 30 | 31 | -define(PROPER_NO_TRANS, true). 32 | -include_lib("proper/include/proper.hrl"). 33 | 34 | %% API 35 | -export([prop_set_rw_spec/0, op/0, spec/1]). 36 | 37 | 38 | prop_set_rw_spec() -> 39 | crdt_properties:crdt_satisfies_spec(antidote_crdt_set_rw, fun op/0, fun spec/1). 40 | 41 | 42 | spec(Operations1) -> 43 | Operations2 = crdt_properties:filter_resets(Operations1), 44 | Operations = lists:flatmap(fun normalizeOperation/1, Operations2), 45 | RemoveClocks = 46 | fun(X) -> 47 | [Clock || {Clock, {remove, Y}} <- Operations, X == Y] 48 | end, 49 | Removed = 50 | fun(X) -> 51 | % there is a remove for X which is not followed by an add 52 | lists:any(fun(RemClock) -> [] == [Y || {AddClock, {add, Y}} <- Operations, X == Y, crdt_properties:clock_le(RemClock, AddClock)] end, RemoveClocks(X)) 53 | end, 54 | Added = [X || {_, {add, X}} <- Operations], 55 | [X || X <- lists:usort(Added), not Removed(X)]. 56 | 57 | % transforms add_all and remove_all into single operations 58 | normalizeOperation({Clock, {add_all, Elems}}) -> 59 | [{Clock, {add, Elem}} || Elem <- Elems]; 60 | normalizeOperation({Clock, {remove_all, Elems}}) -> 61 | [{Clock, {remove, Elem}} || Elem <- Elems]; 62 | normalizeOperation(X) -> 63 | [X]. 64 | 65 | % generates a random counter operation 66 | op() -> 67 | oneof([ 68 | {add, set_element()}, 69 | {add_all, list(set_element())}, 70 | {remove, set_element()}, 71 | {remove_all, list(set_element())}, 72 | {reset, {}} 73 | ]). 74 | 75 | set_element() -> 76 | oneof([a, b]). 77 | 78 | --------------------------------------------------------------------------------