├── .gitignore ├── CHANGELOG ├── LICENSE ├── README.md ├── contracts ├── Grove.sol └── api.sol ├── docs ├── Makefile ├── api.rst ├── conf.py ├── index.rst ├── intro.rst ├── library.rst └── solidity_lexer.py ├── libraries └── GroveLib.sol └── tests ├── balancing ├── test_auto_balancing.py └── test_node_deletion.py ├── navigation └── test_get_next_node.py └── query ├── test_exists_querying.py └── test_querying.py /.gitignore: -------------------------------------------------------------------------------- 1 | .password 2 | .genesis.json 3 | build/ 4 | dist/ 5 | chains/ 6 | **/_build 7 | **/.cache/** 8 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 0.3.0 2 | ----- 3 | 4 | - Get rid of `Index.name` in library representation of an index. 5 | - Get rid of `Node.nodeId` in library representation of a node. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Piper Merriam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grove 2 | 3 | [![Join the chat at https://gitter.im/pipermerriam/ethereum-grove](https://badges.gitter.im/pipermerriam/ethereum-grove.svg)](https://gitter.im/pipermerriam/ethereum-grove?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Grove provides indexed storage for ordered data such that it can be efficiently 6 | queried. 7 | 8 | You can use grove as a library either by deploying the `GroveLib` contract 9 | yourself, or using the one deployed at 10 | `0xd07ce4329b27eb8896c51458468d98a0e4c0394c`. 11 | 12 | Alternatively, you can interact with grove as a standalone service via the `Grove` contract either by deploying it yourself, or using the one deployed at `0x8017f24a47c889b1ee80501ff84beb3c017edf0b`. 13 | 14 | If you would like to verify either of the contract's source, they were compiled using version 15 | `0.1.5-23865e39` of the `solc` compiler with the `--optimize` flag turned. 16 | 17 | More information available in the [Documentation](http://ethereum-grove.readthedocs.org/en/latest/) 18 | -------------------------------------------------------------------------------- /contracts/Grove.sol: -------------------------------------------------------------------------------- 1 | // Grove v0.3 2 | import "libraries/GroveLib.sol"; 3 | 4 | 5 | /// @title Grove - queryable indexes for ordered data. 6 | /// @author Piper Merriam 7 | contract Grove { 8 | /* 9 | * Indexes for ordered data 10 | * 11 | * Address: 0x8017f24a47c889b1ee80501ff84beb3c017edf0b 12 | */ 13 | // Map index_id to index 14 | mapping (bytes32 => GroveLib.Index) index_lookup; 15 | 16 | // Map node_id to index_id. 17 | mapping (bytes32 => bytes32) node_to_index; 18 | mapping (bytes32 => bytes32) node_id_lookup; 19 | 20 | /// @notice Computes the id for a Grove index which is sha3(owner, indexName) 21 | /// @param owner The address of the index owner. 22 | /// @param indexName The name of the index. 23 | function computeIndexId(address owner, bytes32 indexName) constant returns (bytes32) { 24 | return sha3(owner, indexName); 25 | } 26 | 27 | /// @notice Computes the id for a node in a given Grove index which is sha3(indexId, id) 28 | /// @param indexId The id for the index the node belongs to. 29 | /// @param id The unique identifier for the data this node represents. 30 | function computeNodeId(bytes32 indexId, bytes32 id) constant returns (bytes32) { 31 | return sha3(indexId, id); 32 | } 33 | 34 | /* 35 | * Node getters 36 | */ 37 | /// @notice Retrieves the id of the root node for this index. 38 | /// @param indexId The id of the index. 39 | function getIndexRoot(bytes32 indexId) constant returns (bytes32) { 40 | return index_lookup[indexId].root; 41 | } 42 | 43 | /// @dev Retrieve the index id for the node. 44 | /// @param nodeId The id for the node 45 | function getNodeIndexId(bytes32 nodeId) constant returns (bytes32) { 46 | return node_to_index[nodeId]; 47 | } 48 | 49 | /// @dev Retrieve the value of the node. 50 | /// @param nodeId The id for the node 51 | function getNodeValue(bytes32 nodeId) constant returns (int) { 52 | return GroveLib.getNodeValue(index_lookup[node_to_index[nodeId]], node_id_lookup[nodeId]); 53 | } 54 | 55 | /// @dev Retrieve the height of the node. 56 | /// @param nodeId The id for the node 57 | function getNodeHeight(bytes32 nodeId) constant returns (uint) { 58 | return GroveLib.getNodeHeight(index_lookup[node_to_index[nodeId]], node_id_lookup[nodeId]); 59 | } 60 | 61 | /// @dev Retrieve the parent id of the node. 62 | /// @param nodeId The id for the node 63 | function getNodeParent(bytes32 nodeId) constant returns (bytes32) { 64 | return GroveLib.getNodeParent(index_lookup[node_to_index[nodeId]], node_id_lookup[nodeId]); 65 | } 66 | 67 | /// @dev Retrieve the left child id of the node. 68 | /// @param nodeId The id for the node 69 | function getNodeLeftChild(bytes32 nodeId) constant returns (bytes32) { 70 | return GroveLib.getNodeLeftChild(index_lookup[node_to_index[nodeId]], node_id_lookup[nodeId]); 71 | } 72 | 73 | /// @dev Retrieve the right child id of the node. 74 | /// @param nodeId The id for the node 75 | function getNodeRightChild(bytes32 nodeId) constant returns (bytes32) { 76 | return GroveLib.getNodeRightChild(index_lookup[node_to_index[nodeId]], node_id_lookup[nodeId]); 77 | } 78 | 79 | /** @dev Retrieve the id of the node that comes immediately before this 80 | * one. Returns 0x0 if there is no previous node. 81 | */ 82 | /// @param nodeId The id for the node 83 | function getPreviousNode(bytes32 nodeId) constant returns (bytes32) { 84 | return GroveLib.getPreviousNode(index_lookup[node_to_index[nodeId]], node_id_lookup[nodeId]); 85 | } 86 | 87 | /** @dev Retrieve the id of the node that comes immediately after this 88 | * one. Returns 0x0 if there is no previous node. 89 | */ 90 | /// @param nodeId The id for the node 91 | function getNextNode(bytes32 nodeId) constant returns (bytes32) { 92 | return GroveLib.getNextNode(index_lookup[node_to_index[nodeId]], node_id_lookup[nodeId]); 93 | } 94 | 95 | /** @dev Update or Insert a data element represented by the unique 96 | * identifier `id` into the index. 97 | */ 98 | /// @param indexName The human readable name for the index that the node should be upserted into. 99 | /// @param id The unique identifier that the index node represents. 100 | /// @param value The number which represents this data elements total ordering. 101 | function insert(bytes32 indexName, bytes32 id, int value) public { 102 | bytes32 indexId = computeIndexId(msg.sender, indexName); 103 | GroveLib.Index storage index = index_lookup[indexId]; 104 | 105 | bytes32 nodeId = computeNodeId(indexId, id); 106 | // Store the mapping from nodeId to the indexId 107 | node_to_index[nodeId] = indexId; 108 | node_id_lookup[nodeId] = id; 109 | 110 | GroveLib.insert(index, id, value); 111 | } 112 | 113 | /// @dev Query whether a node exists within the specified index for the unique identifier. 114 | /// @param indexId The id for the index. 115 | /// @param id The unique identifier of the data element. 116 | function exists(bytes32 indexId, bytes32 id) constant returns (bool) { 117 | return GroveLib.exists(index_lookup[indexId], id); 118 | } 119 | 120 | /// @dev Remove the index node for the given unique identifier. 121 | /// @param indexName The name of the index. 122 | /// @param id The unique identifier of the data element. 123 | function remove(bytes32 indexName, bytes32 id) public { 124 | GroveLib.remove(index_lookup[computeIndexId(msg.sender, indexName)], id); 125 | } 126 | 127 | /** @dev Query the index for the edge-most node that satisfies the 128 | * given query. For >, >=, and ==, this will be the left-most node 129 | * that satisfies the comparison. For < and <= this will be the 130 | * right-most node that satisfies the comparison. 131 | */ 132 | /// @param indexId The id of the index that should be queried 133 | /** @param operator One of '>', '>=', '<', '<=', '==' to specify what 134 | * type of comparison operator should be used. 135 | */ 136 | function query(bytes32 indexId, bytes2 operator, int value) constant returns (bytes32) { 137 | return GroveLib.query(index_lookup[indexId], operator, value); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /contracts/api.sol: -------------------------------------------------------------------------------- 1 | contract GroveAPI { 2 | /* 3 | * Shortcuts 4 | */ 5 | function getIndexId(address ownerAddress, bytes32 indexName) constant returns (bytes32); 6 | function getNodeId(bytes32 indexId, bytes32 id) constant returns (bytes32); 7 | 8 | /* 9 | * Node and Index API 10 | */ 11 | function getIndexName(bytes32 indexId) constant returns (bytes32); 12 | function getIndexRoot(bytes32 indexId) constant returns (bytes32); 13 | function getNodeId(bytes32 nodeId) constant returns (bytes32); 14 | function getNodeIndexId(bytes32 nodeId) constant returns (bytes32); 15 | function getNodeValue(bytes32 nodeId) constant returns (int); 16 | function getNodeHeight(bytes32 nodeId) constant returns (uint); 17 | function getNodeParent(bytes32 nodeId) constant returns (bytes32); 18 | function getNodeLeftChild(bytes32 nodeId) constant returns (bytes32); 19 | function getNodeRightChild(bytes32 nodeId) constant returns (bytes32); 20 | 21 | /* 22 | * Traversal 23 | */ 24 | function getNextNode(bytes32 nodeId) constant returns (bytes32); 25 | function getPreviousNode(bytes32 nodeId) constant returns (bytes32); 26 | 27 | /* 28 | * Insert and Query API 29 | */ 30 | function insert(bytes32 indexName, bytes32 id, int value) public; 31 | function query(bytes32 indexId, bytes2 operator, int value) public returns (bytes32); 32 | function exists(bytes32 indexId, bytes32 id) constant returns (bool); 33 | function remove(bytes32 indexName, bytes32 id) public; 34 | } 35 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Grove.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Grove.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Grove" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Grove" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | Grove API 2 | ========= 3 | 4 | 5 | Indexes 6 | ------- 7 | 8 | In grove, an index represents one set of ordered data, such as the ages of a 9 | set of people. 10 | 11 | Indexes are namespaced by ethereum address, meaning that any write operations 12 | to an index by different addresses will write to different indexes. Each index 13 | has an id which can be computed as ``sha3(ownerAddress, indexName)``. 14 | 15 | You can also use the ``computeIndexId(address ownerAddress, bytes32 indexName)`` 16 | function to compute this for you. 17 | 18 | 19 | Nodes 20 | ----- 21 | 22 | Within each index, there is a binary tree that stores the data. The tree is 23 | comprised of nodes which have the following attributes. 24 | 25 | * **indexId (bytes32):** the index this node belongs to. 26 | * **id (bytes32):** A unique identifier for the data element that this node 27 | represents. Within an index, there is a 1:1 relationship between id and a 28 | node in the index. 29 | * **nodeId (bytes32):** a unique identifier for the node, computed as ``sha3(indexId, id)`` 30 | * **value (int):** an integer that can be used to compare this node with other 31 | nodes to determine ordering. 32 | * **parent (bytes32):** the nodeId of this node's parent (0x0 if no parent). 33 | * **left (bytes32):** the nodeId of this node's left child (0x0 if no left 34 | child). 35 | * **right (bytes32):** the nodeId of this node's right child (0x0 if no right 36 | child). 37 | * **height (bytes32):** the longest path from this node to a child leaf node. 38 | 39 | For a given piece of data that you wish to track and query the ordering using 40 | Grove, you must be able to provide a unique identifier for that data element, 41 | as well as an integer value that can be used to order the data element with 42 | respect to the other elements. 43 | 44 | 45 | Node Properties 46 | ^^^^^^^^^^^^^^^ 47 | 48 | * **function getIndexName(bytes32 indexId) constant returns (bytes32)** 49 | 50 | Returns the name for the given index id. 51 | 52 | * **function getIndexRoot(bytes32 indexId) constant returns (bytes32)** 53 | 54 | Returns the root node for a given index. 55 | 56 | * **function getNodeId(bytes32 nodeId) constant returns (bytes32)** 57 | 58 | Returns the id of the node. 59 | 60 | * **function getNodeIndexId(bytes32 nodeId) constant returns (bytes32)** 61 | 62 | Returns the indexId of the node. 63 | 64 | * **function getNodeValue(bytes32 nodeId) constant returns (int)** 65 | 66 | Returns the value for the node. 67 | 68 | * **function getNodeHeight(bytes32 nodeId) constant returns (uint)** 69 | 70 | Returns the height for the node. 71 | 72 | * **function getNodeParent(bytes32 nodeId) constant returns (bytes32)** 73 | 74 | Returns the parent of the node. 75 | 76 | * **function getNodeLeftChild(bytes32 nodeId) constant returns (bytes32)** 77 | 78 | Returns the left child of the node. 79 | 80 | * **function getNodeRightChild(bytes32 nodeId) constant returns (bytes32)** 81 | 82 | Returns the right child of the node. 83 | 84 | 85 | Tree Traversal 86 | ^^^^^^^^^^^^^^ 87 | 88 | * **function getNextNode(bytes32 nodeId) constant returns (bytes32)** 89 | 90 | Returns the node ID for the next sequential node in the index. Returns 0x0 91 | if there is not next node. 92 | 93 | * **function getPreviousNode(bytes32 nodeId) constant returns (bytes32)** 94 | 95 | Returns the node ID for the previous sequential node in the index. Returns 96 | 0x0 if there is no previous node. 97 | 98 | 99 | Insertion 100 | ^^^^^^^^^ 101 | 102 | **function insert(bytes32 indexName, bytes32 id, int value) public** 103 | 104 | You can use the ``insert`` function insert a new piece of data into the index. 105 | 106 | * **indexName:** The name of this index. The index id is automatically 107 | computed based on ``msg.sender``. 108 | * **id:** The unique identifier for this data. 109 | * **value (int):** The value that can be used to order this node with respect 110 | to the other nodes. 111 | 112 | .. note:: 113 | 114 | If a node with the given **id** is already present in the index, then the 115 | 116 | 117 | Removal (deletion) 118 | ^^^^^^^^^^^^^^^^^^ 119 | 120 | **function remove(bytes32 indexName, bytes32 id) public** 121 | 122 | You can use the ``remove`` function to remove an **id** from the index. 123 | 124 | 125 | Existence 126 | ^^^^^^^^^ 127 | 128 | **function exists(bytes32 indexId, bytes32 id) public returns (bool)** 129 | 130 | You can use the ``exists`` function to query whether an **id** is present 131 | within a given index. 132 | 133 | 134 | Querying 135 | ^^^^^^^^ 136 | 137 | **function query(bytes32 indexId, bytes2 operator, int value) public returns (bytes32)** 138 | 139 | Each index can be queried using the ``query`` function. 140 | 141 | * **indexId:** The id of the index that should be queried. Note that this is 142 | **not** the name of the index. 143 | * **operator:** The comparison operator that should be used. Supported 144 | operators are ``<``, ``<=``, ``>``, ``>=``, ``==``. 145 | * **value:** The value that each node in the index should be compared againt. 146 | 147 | For ``>``, ``>=`` and ``==`` the left-most node that satisfies the comparison 148 | is returned. 149 | 150 | For ``<`` and ``<=`` the right-most node that satisfies the comparison is 151 | returned. 152 | 153 | If no nodes satisfy the comparison, then 0x0 is returned. 154 | 155 | 156 | Abstract Solidity Contract 157 | -------------------------- 158 | 159 | This abstract contract can be used to let your contracts access the Grove API 160 | natively. 161 | 162 | .. code-block:: solidity 163 | 164 | contract GroveAPI { 165 | /* 166 | * Shortcuts 167 | */ 168 | function computeIndexId(address ownerAddress, bytes32 indexName) constant returns (bytes32); 169 | function computeNodeId(bytes32 indexId, bytes32 id) constant returns (bytes32); 170 | 171 | /* 172 | * Node and Index API 173 | */ 174 | function getIndexName(bytes32 indexId) constant returns (bytes32); 175 | function getIndexRoot(bytes32 indexId) constant returns (bytes32); 176 | function getNodeId(bytes32 nodeId) constant returns (bytes32); 177 | function getNodeIndexId(bytes32 nodeId) constant returns (bytes32); 178 | function getNodeValue(bytes32 nodeId) constant returns (int); 179 | function getNodeHeight(bytes32 nodeId) constant returns (uint); 180 | function getNodeParent(bytes32 nodeId) constant returns (bytes32); 181 | function getNodeLeftChild(bytes32 nodeId) constant returns (bytes32); 182 | function getNodeRightChild(bytes32 nodeId) constant returns (bytes32); 183 | 184 | /* 185 | * Traversal 186 | */ 187 | function getNextNode(bytes32 nodeId) constant returns (bytes32); 188 | function getPreviousNode(bytes32 nodeId) constant returns (bytes32); 189 | 190 | /* 191 | * Insert and Query API 192 | */ 193 | function insert(bytes32 indexName, bytes32 id, int value) public; 194 | function query(bytes32 indexId, bytes2 operator, int value) public returns (bytes32); 195 | function exists(bytes32 indexId, bytes32 id) constant returns (bool); 196 | function remove(bytes32 indexName, bytes32 id) public; 197 | } 198 | 199 | Contract ABI 200 | ------------ 201 | 202 | The contract can be accessed via web3.js with 203 | 204 | .. code-block:: javascript 205 | 206 | var Grove = web3.eth.contract([{"constant":true,"inputs":[{"name":"indexId","type":"bytes32"},{"name":"id","type":"bytes32"}],"name":"computeNodeId","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"indexName","type":"bytes32"}],"name":"computeIndexId","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"nodeId","type":"bytes32"}],"name":"getNodeLeftChild","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"nodeId","type":"bytes32"}],"name":"getPreviousNode","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"nodeId","type":"bytes32"}],"name":"getNodeValue","outputs":[{"name":"","type":"int256"}],"type":"function"},{"constant":true,"inputs":[{"name":"nodeId","type":"bytes32"}],"name":"getNodeRightChild","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"indexId","type":"bytes32"},{"name":"id","type":"bytes32"}],"name":"exists","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"indexName","type":"bytes32"},{"name":"id","type":"bytes32"},{"name":"value","type":"int256"}],"name":"insert","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"nodeId","type":"bytes32"}],"name":"getNodeParent","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"indexId","type":"bytes32"}],"name":"getIndexName","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"nodeId","type":"bytes32"}],"name":"getNodeIndexId","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"nodeId","type":"bytes32"}],"name":"getNextNode","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"indexName","type":"bytes32"},{"name":"id","type":"bytes32"}],"name":"remove","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"nodeId","type":"bytes32"}],"name":"getNodeHeight","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"nodeId","type":"bytes32"}],"name":"getNodeId","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"indexId","type":"bytes32"}],"name":"getIndexRoot","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"indexId","type":"bytes32"},{"name":"operator","type":"bytes2"},{"name":"value","type":"int256"}],"name":"query","outputs":[{"name":"","type":"bytes32"}],"type":"function"}]).at(0x7d7ce4e2cdfea812b33f48f419860b91cf9a141d); 207 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Grove documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Sep 30 12:46:03 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix(es) of source filenames. 38 | # You can specify multiple suffix as a list of string: 39 | # source_suffix = ['.rst', '.md'] 40 | source_suffix = '.rst' 41 | 42 | # The encoding of source files. 43 | #source_encoding = 'utf-8-sig' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = u'Grove' 50 | copyright = u'2015, Piper Merriam' 51 | author = u'Piper Merriam' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = '0.1.0' 59 | # The full version, including alpha/beta/rc tags. 60 | release = '0.1.0' 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # There are two options for replacing |today|: either, you set today to some 70 | # non-false value, then it is used: 71 | #today = '' 72 | # Else, today_fmt is used as the format for a strftime call. 73 | #today_fmt = '%B %d, %Y' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | exclude_patterns = ['_build'] 78 | 79 | # The reST default role (used for this markup: `text`) to use for all 80 | # documents. 81 | #default_role = None 82 | 83 | # If true, '()' will be appended to :func: etc. cross-reference text. 84 | #add_function_parentheses = True 85 | 86 | # If true, the current module name will be prepended to all description 87 | # unit titles (such as .. function::). 88 | #add_module_names = True 89 | 90 | # If true, sectionauthor and moduleauthor directives will be shown in the 91 | # output. They are ignored by default. 92 | #show_authors = False 93 | 94 | # The name of the Pygments (syntax highlighting) style to use. 95 | pygments_style = 'sphinx' 96 | 97 | # Custom solidity lexer 98 | sys.path.append(os.path.dirname(__file__)) 99 | from pygments.lexer import __all__ as pygments_lexers 100 | from solidity_lexer import SolidityLexer 101 | 102 | pygments_lexers.append(SolidityLexer) 103 | 104 | # A list of ignored prefixes for module index sorting. 105 | #modindex_common_prefix = [] 106 | 107 | # If true, keep warnings as "system message" paragraphs in the built documents. 108 | #keep_warnings = False 109 | 110 | # If true, `todo` and `todoList` produce output, else they produce nothing. 111 | todo_include_todos = False 112 | 113 | 114 | # -- Options for HTML output ---------------------------------------------- 115 | 116 | # The theme to use for HTML and HTML Help pages. See the documentation for 117 | # a list of builtin themes. 118 | html_theme = 'alabaster' 119 | 120 | # Theme options are theme-specific and customize the look and feel of a theme 121 | # further. For a list of options available for each theme, see the 122 | # documentation. 123 | #html_theme_options = {} 124 | 125 | # Add any paths that contain custom themes here, relative to this directory. 126 | #html_theme_path = [] 127 | 128 | # The name for this set of Sphinx documents. If None, it defaults to 129 | # " v documentation". 130 | #html_title = None 131 | 132 | # A shorter title for the navigation bar. Default is the same as html_title. 133 | #html_short_title = None 134 | 135 | # The name of an image file (relative to this directory) to place at the top 136 | # of the sidebar. 137 | #html_logo = None 138 | 139 | # The name of an image file (within the static path) to use as favicon of the 140 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 141 | # pixels large. 142 | #html_favicon = None 143 | 144 | # Add any paths that contain custom static files (such as style sheets) here, 145 | # relative to this directory. They are copied after the builtin static files, 146 | # so a file named "default.css" will overwrite the builtin "default.css". 147 | html_static_path = ['_static'] 148 | 149 | # Add any extra paths that contain custom files (such as robots.txt or 150 | # .htaccess) here, relative to this directory. These files are copied 151 | # directly to the root of the documentation. 152 | #html_extra_path = [] 153 | 154 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 155 | # using the given strftime format. 156 | #html_last_updated_fmt = '%b %d, %Y' 157 | 158 | # If true, SmartyPants will be used to convert quotes and dashes to 159 | # typographically correct entities. 160 | #html_use_smartypants = True 161 | 162 | # Custom sidebar templates, maps document names to template names. 163 | #html_sidebars = {} 164 | 165 | # Additional templates that should be rendered to pages, maps page names to 166 | # template names. 167 | #html_additional_pages = {} 168 | 169 | # If false, no module index is generated. 170 | #html_domain_indices = True 171 | 172 | # If false, no index is generated. 173 | #html_use_index = True 174 | 175 | # If true, the index is split into individual pages for each letter. 176 | #html_split_index = False 177 | 178 | # If true, links to the reST sources are added to the pages. 179 | #html_show_sourcelink = True 180 | 181 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 182 | #html_show_sphinx = True 183 | 184 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 185 | #html_show_copyright = True 186 | 187 | # If true, an OpenSearch description file will be output, and all pages will 188 | # contain a tag referring to it. The value of this option must be the 189 | # base URL from which the finished HTML is served. 190 | #html_use_opensearch = '' 191 | 192 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 193 | #html_file_suffix = None 194 | 195 | # Language to be used for generating the HTML full-text search index. 196 | # Sphinx supports the following languages: 197 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 198 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 199 | #html_search_language = 'en' 200 | 201 | # A dictionary with options for the search language support, empty by default. 202 | # Now only 'ja' uses this config value 203 | #html_search_options = {'type': 'default'} 204 | 205 | # The name of a javascript file (relative to the configuration directory) that 206 | # implements a search results scorer. If empty, the default will be used. 207 | #html_search_scorer = 'scorer.js' 208 | 209 | # Output file base name for HTML help builder. 210 | htmlhelp_basename = 'Grovedoc' 211 | 212 | # -- Options for LaTeX output --------------------------------------------- 213 | 214 | latex_elements = { 215 | # The paper size ('letterpaper' or 'a4paper'). 216 | #'papersize': 'letterpaper', 217 | 218 | # The font size ('10pt', '11pt' or '12pt'). 219 | #'pointsize': '10pt', 220 | 221 | # Additional stuff for the LaTeX preamble. 222 | #'preamble': '', 223 | 224 | # Latex figure (float) alignment 225 | #'figure_align': 'htbp', 226 | } 227 | 228 | # Grouping the document tree into LaTeX files. List of tuples 229 | # (source start file, target name, title, 230 | # author, documentclass [howto, manual, or own class]). 231 | latex_documents = [ 232 | (master_doc, 'Grove.tex', u'Grove Documentation', 233 | u'Piper Merriam', 'manual'), 234 | ] 235 | 236 | # The name of an image file (relative to this directory) to place at the top of 237 | # the title page. 238 | #latex_logo = None 239 | 240 | # For "manual" documents, if this is true, then toplevel headings are parts, 241 | # not chapters. 242 | #latex_use_parts = False 243 | 244 | # If true, show page references after internal links. 245 | #latex_show_pagerefs = False 246 | 247 | # If true, show URL addresses after external links. 248 | #latex_show_urls = False 249 | 250 | # Documents to append as an appendix to all manuals. 251 | #latex_appendices = [] 252 | 253 | # If false, no module index is generated. 254 | #latex_domain_indices = True 255 | 256 | 257 | # -- Options for manual page output --------------------------------------- 258 | 259 | # One entry per manual page. List of tuples 260 | # (source start file, name, description, authors, manual section). 261 | man_pages = [ 262 | (master_doc, 'grove', u'Grove Documentation', 263 | [author], 1) 264 | ] 265 | 266 | # If true, show URL addresses after external links. 267 | #man_show_urls = False 268 | 269 | 270 | # -- Options for Texinfo output ------------------------------------------- 271 | 272 | # Grouping the document tree into Texinfo files. List of tuples 273 | # (source start file, target name, title, author, 274 | # dir menu entry, description, category) 275 | texinfo_documents = [ 276 | (master_doc, 'Grove', u'Grove Documentation', 277 | author, 'Grove', 'One line description of project.', 278 | 'Miscellaneous'), 279 | ] 280 | 281 | # Documents to append as an appendix to all manuals. 282 | #texinfo_appendices = [] 283 | 284 | # If false, no module index is generated. 285 | #texinfo_domain_indices = True 286 | 287 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 288 | #texinfo_show_urls = 'footnote' 289 | 290 | # If true, do not generate a @detailmenu in the "Top" node's menu. 291 | #texinfo_no_detailmenu = False 292 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Grove documentation master file, created by 2 | sphinx-quickstart on Wed Sep 30 12:46:03 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Grove's documentation! 7 | ================================= 8 | 9 | Grove is an ethereum contract that provides an API for storing and retrieving 10 | ordered data in a fast manner. 11 | 12 | Grove indexes use an AVL tree for storage which has on average 13 | ``O(log n)`` time complexity for inserts and lookups. 14 | 15 | Grove can be used as a library within your own contract, or as a service by 16 | interacting with the publicly deployed Grove contract. 17 | 18 | The Grove Library can be used at the address 19 | ``0xd07ce4329b27eb8896c51458468d98a0e4c0394c``. 20 | 21 | The Grove contract can be used at 22 | ``0x8017f24a47c889b1ee80501ff84beb3c017edf0b``. 23 | 24 | If you would like to verify the source, it was compiled using version 25 | ``0.1.5-23865e39`` of the ``solc`` compiler with the ``--optimize`` flag turned 26 | on. 27 | 28 | 29 | .. toctree:: 30 | intro 31 | api 32 | library 33 | :maxdepth: 2 34 | 35 | Indices and tables 36 | ================== 37 | 38 | * :ref:`genindex` 39 | * :ref:`modindex` 40 | * :ref:`search` 41 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | Introduction to Grove 2 | ===================== 3 | 4 | To understand how Grove can be used, we need a situation where we care about 5 | the ordering of our data. Lets explore this in the context of a name 6 | registrar. Suppose we have a contract which holds some set of key/value 7 | mappings. 8 | 9 | .. code-block:: solidity 10 | 11 | contract namereg { 12 | struct Record { 13 | address owner; 14 | bytes32 key; 15 | bytes32 value; 16 | uint expires; 17 | } 18 | ... 19 | } 20 | 21 | One question that someone might want to ask this data is *"Which record will 22 | expire next"*. To enable this, the namereg contract would need to feed 23 | registrations into and index in grove. 24 | 25 | .. code-block:: javascript 26 | 27 | contract namereg { 28 | // Here, Grove might be an abstract solidity contract that allows our 29 | // namereg contract to interact with it's public API. 30 | Grove grove = Grove('0x6b07cb54be50bc040cca0360ec792d7b5609f4db'); 31 | 32 | struct Record { 33 | address owner; 34 | bytes32 key; 35 | bytes32 value; 36 | uint expires; 37 | } 38 | function register(bytes32 key, bytes32 value) { 39 | ... // Do the actual registration 40 | 41 | grove.insert("recordExpiration", key, record.expires); 42 | } 43 | } 44 | 45 | So, each time someone registers a key, it tells grove about the record, using 46 | the ``key`` as the ``id``, and the expiration time of the record as the 47 | ``value`` that grove will use for ordering. Grove will store these records in 48 | the index ``recordExpiration``. 49 | 50 | Someone who wished to query for the next record to expire would do something 51 | like the following. We'll assume that we've loaded the Grove ABI into a 52 | contract object in web3. 53 | 54 | .. code-block:: javascript 55 | 56 | // Compute the ID for the index we want to query 57 | > index_id = Grove.getIndexId(namereg.address, "recordExpiration") 58 | // Query grove for the first record that is greater than 'now'. 59 | > node_id = Grove.query(index_id, ">", now_timestamp) 60 | > record_id = Grove.getNodeId(node_id) 61 | 62 | 63 | In this example, ``node_id`` would either be ``0x0`` if there is no record 64 | who's expiration is greater than the ``now_timestamp`` value, or the 65 | ``bytes32`` of the first node in the index that is greater than 66 | ``now_timestamp``. 67 | 68 | Assuming that ``node_id`` is not ``0x0``, we can then fetch the ``key`` for the 69 | record using the ``grove.getNodeId`` function. 70 | -------------------------------------------------------------------------------- /docs/library.rst: -------------------------------------------------------------------------------- 1 | Library Usage 2 | ============= 3 | 4 | Grove can be used as a `solidity library `_, allowing use of all of Groves functionality using your own contracts storage. 5 | 6 | The primary Grove contract itself is actually just a wrapper around the 7 | deployed library contract. 8 | 9 | 10 | Example 11 | ------- 12 | 13 | In this example, we will use a fictional contract ``Members`` which tracks 14 | information about some set of members for an organization. 15 | 16 | 17 | .. code-block:: solidity 18 | 19 | library GroveAPI { 20 | struct Index { 21 | bytes32 id; 22 | bytes32 name; 23 | bytes32 root; 24 | mapping (bytes32 => Node) nodes; 25 | } 26 | 27 | struct Node { 28 | bytes32 nodeId; 29 | bytes32 indexId; 30 | bytes32 id; 31 | int value; 32 | bytes32 parent; 33 | bytes32 left; 34 | bytes32 right; 35 | uint height; 36 | } 37 | 38 | function insert(Index storage index, bytes32 id, int value) public; 39 | function query(Index storage index, bytes2 operator, int value) public returns (bytes32); 40 | } 41 | 42 | contract Members { 43 | Grove.Index ageIndex; 44 | 45 | function addMember(bytes32 name, uint age) { 46 | ... // Do whatever needs to be done to add the member. 47 | 48 | // Update the ageIndex with this new member's age. 49 | Grove.insert(ageIndex, name, age); 50 | } 51 | 52 | function isAnyoneThisOld(uint age) constant returns (bool) { 53 | return Grove.query("==", age) != 0x0; 54 | } 55 | } 56 | 57 | 58 | The ``Members`` contract above interfaces with the Grove library in two places. 59 | 60 | First, within the ``addMember`` function, everytime a member is added to the 61 | organization, their age is tracked in the ``ageIndex``. 62 | 63 | Second, the ``isAnyoneThisOld`` allows checking whether any member is a 64 | specific age. 65 | -------------------------------------------------------------------------------- /docs/solidity_lexer.py: -------------------------------------------------------------------------------- 1 | from pygments.lexers.c_cpp import CFamilyLexer 2 | from pygments.lexer import ( 3 | words, 4 | ) 5 | from pygments.token import ( 6 | Keyword, 7 | ) 8 | 9 | 10 | class SolidityLexer(CFamilyLexer): 11 | name = "Solidity" 12 | aliases = ["solidity", "sol"] 13 | filenames = ["*.sol"] 14 | 15 | tokens = { 16 | 'address': [ 17 | (r'address', Keyword.Type), 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libraries/GroveLib.sol: -------------------------------------------------------------------------------- 1 | // Grove v0.3 2 | 3 | 4 | /// @title GroveLib - Library for queriable indexed ordered data. 5 | /// @author PiperMerriam - 6 | library GroveLib { 7 | /* 8 | * Indexes for ordered data 9 | * 10 | * Address: 0x7c1eb207c07e7ab13cf245585bd03d0fa478d034 11 | */ 12 | struct Index { 13 | bytes32 root; 14 | mapping (bytes32 => Node) nodes; 15 | } 16 | 17 | struct Node { 18 | bytes32 id; 19 | int value; 20 | bytes32 parent; 21 | bytes32 left; 22 | bytes32 right; 23 | uint height; 24 | } 25 | 26 | function max(uint a, uint b) internal returns (uint) { 27 | if (a >= b) { 28 | return a; 29 | } 30 | return b; 31 | } 32 | 33 | /* 34 | * Node getters 35 | */ 36 | /// @dev Retrieve the unique identifier for the node. 37 | /// @param index The index that the node is part of. 38 | /// @param id The id for the node to be looked up. 39 | function getNodeId(Index storage index, bytes32 id) constant returns (bytes32) { 40 | return index.nodes[id].id; 41 | } 42 | 43 | /// @dev Retrieve the value for the node. 44 | /// @param index The index that the node is part of. 45 | /// @param id The id for the node to be looked up. 46 | function getNodeValue(Index storage index, bytes32 id) constant returns (int) { 47 | return index.nodes[id].value; 48 | } 49 | 50 | /// @dev Retrieve the height of the node. 51 | /// @param index The index that the node is part of. 52 | /// @param id The id for the node to be looked up. 53 | function getNodeHeight(Index storage index, bytes32 id) constant returns (uint) { 54 | return index.nodes[id].height; 55 | } 56 | 57 | /// @dev Retrieve the parent id of the node. 58 | /// @param index The index that the node is part of. 59 | /// @param id The id for the node to be looked up. 60 | function getNodeParent(Index storage index, bytes32 id) constant returns (bytes32) { 61 | return index.nodes[id].parent; 62 | } 63 | 64 | /// @dev Retrieve the left child id of the node. 65 | /// @param index The index that the node is part of. 66 | /// @param id The id for the node to be looked up. 67 | function getNodeLeftChild(Index storage index, bytes32 id) constant returns (bytes32) { 68 | return index.nodes[id].left; 69 | } 70 | 71 | /// @dev Retrieve the right child id of the node. 72 | /// @param index The index that the node is part of. 73 | /// @param id The id for the node to be looked up. 74 | function getNodeRightChild(Index storage index, bytes32 id) constant returns (bytes32) { 75 | return index.nodes[id].right; 76 | } 77 | 78 | /// @dev Retrieve the node id of the next node in the tree. 79 | /// @param index The index that the node is part of. 80 | /// @param id The id for the node to be looked up. 81 | function getPreviousNode(Index storage index, bytes32 id) constant returns (bytes32) { 82 | Node storage currentNode = index.nodes[id]; 83 | 84 | if (currentNode.id == 0x0) { 85 | // Unknown node, just return 0x0; 86 | return 0x0; 87 | } 88 | 89 | Node memory child; 90 | 91 | if (currentNode.left != 0x0) { 92 | // Trace left to latest child in left tree. 93 | child = index.nodes[currentNode.left]; 94 | 95 | while (child.right != 0) { 96 | child = index.nodes[child.right]; 97 | } 98 | return child.id; 99 | } 100 | 101 | if (currentNode.parent != 0x0) { 102 | // Now we trace back up through parent relationships, looking 103 | // for a link where the child is the right child of it's 104 | // parent. 105 | Node storage parent = index.nodes[currentNode.parent]; 106 | child = currentNode; 107 | 108 | while (true) { 109 | if (parent.right == child.id) { 110 | return parent.id; 111 | } 112 | 113 | if (parent.parent == 0x0) { 114 | break; 115 | } 116 | child = parent; 117 | parent = index.nodes[parent.parent]; 118 | } 119 | } 120 | 121 | // This is the first node, and has no previous node. 122 | return 0x0; 123 | } 124 | 125 | /// @dev Retrieve the node id of the previous node in the tree. 126 | /// @param index The index that the node is part of. 127 | /// @param id The id for the node to be looked up. 128 | function getNextNode(Index storage index, bytes32 id) constant returns (bytes32) { 129 | Node storage currentNode = index.nodes[id]; 130 | 131 | if (currentNode.id == 0x0) { 132 | // Unknown node, just return 0x0; 133 | return 0x0; 134 | } 135 | 136 | Node memory child; 137 | 138 | if (currentNode.right != 0x0) { 139 | // Trace right to earliest child in right tree. 140 | child = index.nodes[currentNode.right]; 141 | 142 | while (child.left != 0) { 143 | child = index.nodes[child.left]; 144 | } 145 | return child.id; 146 | } 147 | 148 | if (currentNode.parent != 0x0) { 149 | // if the node is the left child of it's parent, then the 150 | // parent is the next one. 151 | Node storage parent = index.nodes[currentNode.parent]; 152 | child = currentNode; 153 | 154 | while (true) { 155 | if (parent.left == child.id) { 156 | return parent.id; 157 | } 158 | 159 | if (parent.parent == 0x0) { 160 | break; 161 | } 162 | child = parent; 163 | parent = index.nodes[parent.parent]; 164 | } 165 | 166 | // Now we need to trace all the way up checking to see if any parent is the 167 | } 168 | 169 | // This is the final node. 170 | return 0x0; 171 | } 172 | 173 | 174 | /// @dev Updates or Inserts the id into the index at its appropriate location based on the value provided. 175 | /// @param index The index that the node is part of. 176 | /// @param id The unique identifier of the data element the index node will represent. 177 | /// @param value The value of the data element that represents it's total ordering with respect to other elementes. 178 | function insert(Index storage index, bytes32 id, int value) public { 179 | if (index.nodes[id].id == id) { 180 | // A node with this id already exists. If the value is 181 | // the same, then just return early, otherwise, remove it 182 | // and reinsert it. 183 | if (index.nodes[id].value == value) { 184 | return; 185 | } 186 | remove(index, id); 187 | } 188 | 189 | uint leftHeight; 190 | uint rightHeight; 191 | 192 | bytes32 previousNodeId = 0x0; 193 | 194 | if (index.root == 0x0) { 195 | index.root = id; 196 | } 197 | Node storage currentNode = index.nodes[index.root]; 198 | 199 | // Do insertion 200 | while (true) { 201 | if (currentNode.id == 0x0) { 202 | // This is a new unpopulated node. 203 | currentNode.id = id; 204 | currentNode.parent = previousNodeId; 205 | currentNode.value = value; 206 | break; 207 | } 208 | 209 | // Set the previous node id. 210 | previousNodeId = currentNode.id; 211 | 212 | // The new node belongs in the right subtree 213 | if (value >= currentNode.value) { 214 | if (currentNode.right == 0x0) { 215 | currentNode.right = id; 216 | } 217 | currentNode = index.nodes[currentNode.right]; 218 | continue; 219 | } 220 | 221 | // The new node belongs in the left subtree. 222 | if (currentNode.left == 0x0) { 223 | currentNode.left = id; 224 | } 225 | currentNode = index.nodes[currentNode.left]; 226 | } 227 | 228 | // Rebalance the tree 229 | _rebalanceTree(index, currentNode.id); 230 | } 231 | 232 | /// @dev Checks whether a node for the given unique identifier exists within the given index. 233 | /// @param index The index that should be searched 234 | /// @param id The unique identifier of the data element to check for. 235 | function exists(Index storage index, bytes32 id) constant returns (bool) { 236 | return (index.nodes[id].height > 0); 237 | } 238 | 239 | /// @dev Remove the node for the given unique identifier from the index. 240 | /// @param index The index that should be removed 241 | /// @param id The unique identifier of the data element to remove. 242 | function remove(Index storage index, bytes32 id) public { 243 | Node storage replacementNode; 244 | Node storage parent; 245 | Node storage child; 246 | bytes32 rebalanceOrigin; 247 | 248 | Node storage nodeToDelete = index.nodes[id]; 249 | 250 | if (nodeToDelete.id != id) { 251 | // The id does not exist in the tree. 252 | return; 253 | } 254 | 255 | if (nodeToDelete.left != 0x0 || nodeToDelete.right != 0x0) { 256 | // This node is not a leaf node and thus must replace itself in 257 | // it's tree by either the previous or next node. 258 | if (nodeToDelete.left != 0x0) { 259 | // This node is guaranteed to not have a right child. 260 | replacementNode = index.nodes[getPreviousNode(index, nodeToDelete.id)]; 261 | } 262 | else { 263 | // This node is guaranteed to not have a left child. 264 | replacementNode = index.nodes[getNextNode(index, nodeToDelete.id)]; 265 | } 266 | // The replacementNode is guaranteed to have a parent. 267 | parent = index.nodes[replacementNode.parent]; 268 | 269 | // Keep note of the location that our tree rebalancing should 270 | // start at. 271 | rebalanceOrigin = replacementNode.id; 272 | 273 | // Join the parent of the replacement node with any subtree of 274 | // the replacement node. We can guarantee that the replacement 275 | // node has at most one subtree because of how getNextNode and 276 | // getPreviousNode are used. 277 | if (parent.left == replacementNode.id) { 278 | parent.left = replacementNode.right; 279 | if (replacementNode.right != 0x0) { 280 | child = index.nodes[replacementNode.right]; 281 | child.parent = parent.id; 282 | } 283 | } 284 | if (parent.right == replacementNode.id) { 285 | parent.right = replacementNode.left; 286 | if (replacementNode.left != 0x0) { 287 | child = index.nodes[replacementNode.left]; 288 | child.parent = parent.id; 289 | } 290 | } 291 | 292 | // Now we replace the nodeToDelete with the replacementNode. 293 | // This includes parent/child relationships for all of the 294 | // parent, the left child, and the right child. 295 | replacementNode.parent = nodeToDelete.parent; 296 | if (nodeToDelete.parent != 0x0) { 297 | parent = index.nodes[nodeToDelete.parent]; 298 | if (parent.left == nodeToDelete.id) { 299 | parent.left = replacementNode.id; 300 | } 301 | if (parent.right == nodeToDelete.id) { 302 | parent.right = replacementNode.id; 303 | } 304 | } 305 | else { 306 | // If the node we are deleting is the root node update the 307 | // index root node pointer. 308 | index.root = replacementNode.id; 309 | } 310 | 311 | replacementNode.left = nodeToDelete.left; 312 | if (nodeToDelete.left != 0x0) { 313 | child = index.nodes[nodeToDelete.left]; 314 | child.parent = replacementNode.id; 315 | } 316 | 317 | replacementNode.right = nodeToDelete.right; 318 | if (nodeToDelete.right != 0x0) { 319 | child = index.nodes[nodeToDelete.right]; 320 | child.parent = replacementNode.id; 321 | } 322 | } 323 | else if (nodeToDelete.parent != 0x0) { 324 | // The node being deleted is a leaf node so we only erase it's 325 | // parent linkage. 326 | parent = index.nodes[nodeToDelete.parent]; 327 | 328 | if (parent.left == nodeToDelete.id) { 329 | parent.left = 0x0; 330 | } 331 | if (parent.right == nodeToDelete.id) { 332 | parent.right = 0x0; 333 | } 334 | 335 | // keep note of where the rebalancing should begin. 336 | rebalanceOrigin = parent.id; 337 | } 338 | else { 339 | // This is both a leaf node and the root node, so we need to 340 | // unset the root node pointer. 341 | index.root = 0x0; 342 | } 343 | 344 | // Now we zero out all of the fields on the nodeToDelete. 345 | nodeToDelete.id = 0x0; 346 | nodeToDelete.value = 0; 347 | nodeToDelete.parent = 0x0; 348 | nodeToDelete.left = 0x0; 349 | nodeToDelete.right = 0x0; 350 | nodeToDelete.height = 0; 351 | 352 | // Walk back up the tree rebalancing 353 | if (rebalanceOrigin != 0x0) { 354 | _rebalanceTree(index, rebalanceOrigin); 355 | } 356 | } 357 | 358 | bytes2 constant GT = ">"; 359 | bytes2 constant LT = "<"; 360 | bytes2 constant GTE = ">="; 361 | bytes2 constant LTE = "<="; 362 | bytes2 constant EQ = "=="; 363 | 364 | function _compare(int left, bytes2 operator, int right) internal returns (bool) { 365 | if (operator == GT) { 366 | return (left > right); 367 | } 368 | if (operator == LT) { 369 | return (left < right); 370 | } 371 | if (operator == GTE) { 372 | return (left >= right); 373 | } 374 | if (operator == LTE) { 375 | return (left <= right); 376 | } 377 | if (operator == EQ) { 378 | return (left == right); 379 | } 380 | 381 | // Invalid operator. 382 | throw; 383 | } 384 | 385 | function _getMaximum(Index storage index, bytes32 id) internal returns (int) { 386 | Node storage currentNode = index.nodes[id]; 387 | 388 | while (true) { 389 | if (currentNode.right == 0x0) { 390 | return currentNode.value; 391 | } 392 | currentNode = index.nodes[currentNode.right]; 393 | } 394 | } 395 | 396 | function _getMinimum(Index storage index, bytes32 id) internal returns (int) { 397 | Node storage currentNode = index.nodes[id]; 398 | 399 | while (true) { 400 | if (currentNode.left == 0x0) { 401 | return currentNode.value; 402 | } 403 | currentNode = index.nodes[currentNode.left]; 404 | } 405 | } 406 | 407 | 408 | /** @dev Query the index for the edge-most node that satisfies the 409 | * given query. For >, >=, and ==, this will be the left-most node 410 | * that satisfies the comparison. For < and <= this will be the 411 | * right-most node that satisfies the comparison. 412 | */ 413 | /// @param index The index that should be queried 414 | /** @param operator One of '>', '>=', '<', '<=', '==' to specify what 415 | * type of comparison operator should be used. 416 | */ 417 | function query(Index storage index, bytes2 operator, int value) public returns (bytes32) { 418 | bytes32 rootNodeId = index.root; 419 | 420 | if (rootNodeId == 0x0) { 421 | // Empty tree. 422 | return 0x0; 423 | } 424 | 425 | Node storage currentNode = index.nodes[rootNodeId]; 426 | 427 | while (true) { 428 | if (_compare(currentNode.value, operator, value)) { 429 | // We have found a match but it might not be the 430 | // *correct* match. 431 | if ((operator == LT) || (operator == LTE)) { 432 | // Need to keep traversing right until this is no 433 | // longer true. 434 | if (currentNode.right == 0x0) { 435 | return currentNode.id; 436 | } 437 | if (_compare(_getMinimum(index, currentNode.right), operator, value)) { 438 | // There are still nodes to the right that 439 | // match. 440 | currentNode = index.nodes[currentNode.right]; 441 | continue; 442 | } 443 | return currentNode.id; 444 | } 445 | 446 | if ((operator == GT) || (operator == GTE) || (operator == EQ)) { 447 | // Need to keep traversing left until this is no 448 | // longer true. 449 | if (currentNode.left == 0x0) { 450 | return currentNode.id; 451 | } 452 | if (_compare(_getMaximum(index, currentNode.left), operator, value)) { 453 | currentNode = index.nodes[currentNode.left]; 454 | continue; 455 | } 456 | return currentNode.id; 457 | } 458 | } 459 | 460 | if ((operator == LT) || (operator == LTE)) { 461 | if (currentNode.left == 0x0) { 462 | // There are no nodes that are less than the value 463 | // so return null. 464 | return 0x0; 465 | } 466 | currentNode = index.nodes[currentNode.left]; 467 | continue; 468 | } 469 | 470 | if ((operator == GT) || (operator == GTE)) { 471 | if (currentNode.right == 0x0) { 472 | // There are no nodes that are greater than the value 473 | // so return null. 474 | return 0x0; 475 | } 476 | currentNode = index.nodes[currentNode.right]; 477 | continue; 478 | } 479 | 480 | if (operator == EQ) { 481 | if (currentNode.value < value) { 482 | if (currentNode.right == 0x0) { 483 | return 0x0; 484 | } 485 | currentNode = index.nodes[currentNode.right]; 486 | continue; 487 | } 488 | 489 | if (currentNode.value > value) { 490 | if (currentNode.left == 0x0) { 491 | return 0x0; 492 | } 493 | currentNode = index.nodes[currentNode.left]; 494 | continue; 495 | } 496 | } 497 | } 498 | } 499 | 500 | function _rebalanceTree(Index storage index, bytes32 id) internal { 501 | // Trace back up rebalancing the tree and updating heights as 502 | // needed.. 503 | Node storage currentNode = index.nodes[id]; 504 | 505 | while (true) { 506 | int balanceFactor = _getBalanceFactor(index, currentNode.id); 507 | 508 | if (balanceFactor == 2) { 509 | // Right rotation (tree is heavy on the left) 510 | if (_getBalanceFactor(index, currentNode.left) == -1) { 511 | // The subtree is leaning right so it need to be 512 | // rotated left before the current node is rotated 513 | // right. 514 | _rotateLeft(index, currentNode.left); 515 | } 516 | _rotateRight(index, currentNode.id); 517 | } 518 | 519 | if (balanceFactor == -2) { 520 | // Left rotation (tree is heavy on the right) 521 | if (_getBalanceFactor(index, currentNode.right) == 1) { 522 | // The subtree is leaning left so it need to be 523 | // rotated right before the current node is rotated 524 | // left. 525 | _rotateRight(index, currentNode.right); 526 | } 527 | _rotateLeft(index, currentNode.id); 528 | } 529 | 530 | if ((-1 <= balanceFactor) && (balanceFactor <= 1)) { 531 | _updateNodeHeight(index, currentNode.id); 532 | } 533 | 534 | if (currentNode.parent == 0x0) { 535 | // Reached the root which may be new due to tree 536 | // rotation, so set it as the root and then break. 537 | break; 538 | } 539 | 540 | currentNode = index.nodes[currentNode.parent]; 541 | } 542 | } 543 | 544 | function _getBalanceFactor(Index storage index, bytes32 id) internal returns (int) { 545 | Node storage node = index.nodes[id]; 546 | 547 | return int(index.nodes[node.left].height) - int(index.nodes[node.right].height); 548 | } 549 | 550 | function _updateNodeHeight(Index storage index, bytes32 id) internal { 551 | Node storage node = index.nodes[id]; 552 | 553 | node.height = max(index.nodes[node.left].height, index.nodes[node.right].height) + 1; 554 | } 555 | 556 | function _rotateLeft(Index storage index, bytes32 id) internal { 557 | Node storage originalRoot = index.nodes[id]; 558 | 559 | if (originalRoot.right == 0x0) { 560 | // Cannot rotate left if there is no right originalRoot to rotate into 561 | // place. 562 | throw; 563 | } 564 | 565 | // The right child is the new root, so it gets the original 566 | // `originalRoot.parent` as it's parent. 567 | Node storage newRoot = index.nodes[originalRoot.right]; 568 | newRoot.parent = originalRoot.parent; 569 | 570 | // The original root needs to have it's right child nulled out. 571 | originalRoot.right = 0x0; 572 | 573 | if (originalRoot.parent != 0x0) { 574 | // If there is a parent node, it needs to now point downward at 575 | // the newRoot which is rotating into the place where `node` was. 576 | Node storage parent = index.nodes[originalRoot.parent]; 577 | 578 | // figure out if we're a left or right child and have the 579 | // parent point to the new node. 580 | if (parent.left == originalRoot.id) { 581 | parent.left = newRoot.id; 582 | } 583 | if (parent.right == originalRoot.id) { 584 | parent.right = newRoot.id; 585 | } 586 | } 587 | 588 | 589 | if (newRoot.left != 0) { 590 | // If the new root had a left child, that moves to be the 591 | // new right child of the original root node 592 | Node storage leftChild = index.nodes[newRoot.left]; 593 | originalRoot.right = leftChild.id; 594 | leftChild.parent = originalRoot.id; 595 | } 596 | 597 | // Update the newRoot's left node to point at the original node. 598 | originalRoot.parent = newRoot.id; 599 | newRoot.left = originalRoot.id; 600 | 601 | if (newRoot.parent == 0x0) { 602 | index.root = newRoot.id; 603 | } 604 | 605 | // TODO: are both of these updates necessary? 606 | _updateNodeHeight(index, originalRoot.id); 607 | _updateNodeHeight(index, newRoot.id); 608 | } 609 | 610 | function _rotateRight(Index storage index, bytes32 id) internal { 611 | Node storage originalRoot = index.nodes[id]; 612 | 613 | if (originalRoot.left == 0x0) { 614 | // Cannot rotate right if there is no left node to rotate into 615 | // place. 616 | throw; 617 | } 618 | 619 | // The left child is taking the place of node, so we update it's 620 | // parent to be the original parent of the node. 621 | Node storage newRoot = index.nodes[originalRoot.left]; 622 | newRoot.parent = originalRoot.parent; 623 | 624 | // Null out the originalRoot.left 625 | originalRoot.left = 0x0; 626 | 627 | if (originalRoot.parent != 0x0) { 628 | // If the node has a parent, update the correct child to point 629 | // at the newRoot now. 630 | Node storage parent = index.nodes[originalRoot.parent]; 631 | 632 | if (parent.left == originalRoot.id) { 633 | parent.left = newRoot.id; 634 | } 635 | if (parent.right == originalRoot.id) { 636 | parent.right = newRoot.id; 637 | } 638 | } 639 | 640 | if (newRoot.right != 0x0) { 641 | Node storage rightChild = index.nodes[newRoot.right]; 642 | originalRoot.left = newRoot.right; 643 | rightChild.parent = originalRoot.id; 644 | } 645 | 646 | // Update the new root's right node to point to the original node. 647 | originalRoot.parent = newRoot.id; 648 | newRoot.right = originalRoot.id; 649 | 650 | if (newRoot.parent == 0x0) { 651 | index.root = newRoot.id; 652 | } 653 | 654 | // Recompute heights. 655 | _updateNodeHeight(index, originalRoot.id); 656 | _updateNodeHeight(index, newRoot.id); 657 | } 658 | } 659 | -------------------------------------------------------------------------------- /tests/balancing/test_auto_balancing.py: -------------------------------------------------------------------------------- 1 | def test_single_node_tree(deploy_coinbase, deployed_contracts): 2 | grove = deployed_contracts.Grove 3 | 4 | index_id = grove.computeIndexId(deploy_coinbase, "test-a") 5 | 6 | grove.insert('test-a', 'a', 20) 7 | node_id = grove.computeNodeId(index_id, 'a') 8 | 9 | assert grove.getNodeParent(node_id) is None 10 | assert grove.getNodeLeftChild(node_id) is None 11 | assert grove.getNodeRightChild(node_id) is None 12 | assert grove.getNodeHeight(node_id) == 1 13 | assert grove.getNodeValue(node_id) == 20 14 | 15 | 16 | def test_two_node_tree(deploy_coinbase, deployed_contracts): 17 | grove = deployed_contracts.Grove 18 | 19 | index_id = grove.computeIndexId(deploy_coinbase, "test-b") 20 | 21 | grove.insert('test-b', 'a', 20) 22 | node_a_id = grove.computeNodeId(index_id, 'a') 23 | 24 | grove.insert('test-b', 'b', 25) 25 | node_b_id = grove.computeNodeId(index_id, 'b') 26 | 27 | root = grove.getIndexRoot.call(index_id) 28 | assert root == 'a' 29 | 30 | assert grove.getNodeParent(node_a_id) is None 31 | assert grove.getNodeLeftChild(node_a_id) is None 32 | assert grove.getNodeRightChild(node_a_id) == 'b' 33 | assert grove.getNodeHeight(node_a_id) == 2 34 | assert grove.getNodeValue(node_a_id) == 20 35 | 36 | assert grove.getNodeParent(node_b_id) == 'a' 37 | assert grove.getNodeLeftChild(node_b_id) is None 38 | assert grove.getNodeRightChild(node_b_id) is None 39 | assert grove.getNodeHeight(node_b_id) == 1 40 | assert grove.getNodeValue(node_b_id) == 25 41 | 42 | 43 | values_a = ( 44 | ('a', 1), 45 | ('b', 2), 46 | ('c', 3), 47 | ) 48 | 49 | # value, parent, left, right, height 50 | tree_a = { 51 | ('a', 1, 'b', None, None, 1), 52 | ('b', 2, None, 'a', 'c', 2), 53 | ('c', 3, 'b', None, None, 1), 54 | } 55 | 56 | 57 | def test_right_heavy_three_node_tree(deploy_coinbase, deployed_contracts): 58 | grove = deployed_contracts.Grove 59 | 60 | for id, node_value in values_a: 61 | grove.insert('test-c', id, node_value) 62 | 63 | index_id = grove.computeIndexId(deploy_coinbase, "test-c") 64 | 65 | root = grove.getIndexRoot.call(index_id) 66 | assert root == 'b' 67 | 68 | actual = set() 69 | 70 | for id, _ in values_a: 71 | node_id = grove.computeNodeId.call(index_id, id) 72 | value = grove.getNodeValue.call(node_id) 73 | parent = grove.getNodeParent.call(node_id) 74 | left = grove.getNodeLeftChild.call(node_id) 75 | right = grove.getNodeRightChild.call(node_id) 76 | height = grove.getNodeHeight.call(node_id) 77 | 78 | actual.add((id, value, parent, left, right, height)) 79 | 80 | assert actual == tree_a 81 | 82 | 83 | values_b = ( 84 | ('a', 3), 85 | ('b', 2), 86 | ('c', 1), 87 | ) 88 | 89 | # value, parent, left, right, height 90 | tree_b = { 91 | ('a', 3, 'b', None, None, 1), 92 | ('b', 2, None, 'c', 'a', 2), 93 | ('c', 1, 'b', None, None, 1), 94 | } 95 | 96 | 97 | def test_left_heavy_three_node_tree(deploy_coinbase, deployed_contracts): 98 | grove = deployed_contracts.Grove 99 | 100 | for id, node_value in values_b: 101 | grove.insert('test-d', id, node_value) 102 | 103 | index_id = grove.computeIndexId(deploy_coinbase, "test-d") 104 | 105 | root = grove.getIndexRoot.call(index_id) 106 | assert root == 'b' 107 | 108 | actual = set() 109 | 110 | for id, _ in values_b: 111 | node_id = grove.computeNodeId.call(index_id, id) 112 | value = grove.getNodeValue.call(node_id) 113 | parent = grove.getNodeParent.call(node_id) 114 | left = grove.getNodeLeftChild.call(node_id) 115 | right = grove.getNodeRightChild.call(node_id) 116 | height = grove.getNodeHeight.call(node_id) 117 | 118 | actual.add((id, value, parent, left, right, height)) 119 | 120 | from pprint import pprint 121 | 122 | assert actual == tree_b 123 | 124 | 125 | values_c = ( 126 | ('a', 25), 127 | ('b', 30), 128 | ('c', 35), 129 | ('d', 33), 130 | ('e', 40), 131 | ('f', 38), 132 | ) 133 | 134 | # value, parent, left, right, height 135 | tree_c = { 136 | ('a', 25, 'b', None, None, 1), 137 | ('b', 30, 'c', 'a', 'd', 2), 138 | ('c', 35, None, 'b', 'e', 3), 139 | ('d', 33, 'b', None, None, 1), 140 | ('e', 40, 'c', 'f', None, 2), 141 | ('f', 38, 'e', None, None, 1), 142 | } 143 | 144 | 145 | def test_right_heavy_six_node_tree(deploy_coinbase, deployed_contracts): 146 | grove = deployed_contracts.Grove 147 | 148 | for id, node_value in values_c: 149 | grove.insert('test-e', id, node_value) 150 | 151 | index_id = grove.computeIndexId(deploy_coinbase, "test-e") 152 | 153 | root = grove.getIndexRoot.call(index_id) 154 | assert root == 'c' 155 | 156 | actual = set() 157 | 158 | for id, _ in values_c: 159 | node_id = grove.computeNodeId.call(index_id, id) 160 | value = grove.getNodeValue.call(node_id) 161 | parent = grove.getNodeParent.call(node_id) 162 | left = grove.getNodeLeftChild.call(node_id) 163 | right = grove.getNodeRightChild.call(node_id) 164 | height = grove.getNodeHeight.call(node_id) 165 | 166 | actual.add((id, value, parent, left, right, height)) 167 | 168 | from pprint import pprint 169 | 170 | assert actual == tree_c 171 | -------------------------------------------------------------------------------- /tests/balancing/test_node_deletion.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import random 4 | 5 | 6 | tree_nodes = ( 7 | ('a', 8), 8 | ('b', 6), 9 | ('c', 7), 10 | ('d', 10), 11 | ('e', 2), 12 | ('f', 12), 13 | ('g', 3), 14 | ('h', 15), 15 | ('i', 13), 16 | ('j', 5), 17 | ('k', 19), 18 | ('l', 11), 19 | ('m', 17), 20 | ('n', 0), 21 | ('o', 4), 22 | ('p', 14), 23 | ('q', 18), 24 | ('r', 9), 25 | ('s', 16), 26 | ('t', 1), 27 | ) 28 | 29 | 30 | @pytest.mark.parametrize( 31 | 'ids_to_remove,expected_states', 32 | ( 33 | # id, value, parent, left, right, height 34 | # Leaf Nodes 35 | ( 36 | ['n'], { 37 | ('t', 1, 'g', None, 'e', 2), 38 | } 39 | ), 40 | ( 41 | ['l'], { 42 | ('f', 12, 'd', None, None, 1), 43 | } 44 | ), 45 | ( 46 | ['q'], { 47 | ('k', 19, 'm', None, None, 1), 48 | } 49 | ), 50 | ( 51 | ['b'], { 52 | ('j', 5, 'g', 'o', None, 2), 53 | } 54 | ), 55 | # One from bottom. 56 | ( 57 | ['t'], { 58 | ('n', 0, 'g', None, 'e', 2), 59 | ('e', 2, 'n', None, None, 1), 60 | ('g', 3, 'c', 'n', 'j', 3), 61 | } 62 | ), 63 | ( 64 | ['a'], { 65 | ('r', 9, 'd', None, None, 1), 66 | ('d', 10, 'i', 'r', 'f', 3), 67 | } 68 | ), 69 | ( 70 | ['a', 'r'], { 71 | ('l', 11, 'i', 'd', 'f', 2), 72 | ('d', 10, 'l', None, None, 1), 73 | ('f', 12, 'l', None, None, 1), 74 | ('i', 13, 'c', 'l', 'm', 4), 75 | } 76 | ), 77 | ( 78 | ['f'], { 79 | ('l', 11, 'd', None, None, 1), 80 | ('d', 10, 'i', 'a', 'l', 3), 81 | } 82 | ), 83 | ( 84 | ['f', 'l'], { 85 | ('r', 9, 'i', 'a', 'd', 2), 86 | ('a', 8, 'r', None, None, 1), 87 | ('d', 10, 'r', None, None, 1), 88 | ('i', 13, 'c', 'r', 'm', 4), 89 | } 90 | ), 91 | # Mid tree 92 | ( 93 | ['g'], { 94 | ('e', 2, 'c', 't', 'j', 3), 95 | ('t', 1, 'e', 'n', None, 2), 96 | ('j', 5, 'e', 'o', 'b', 2), 97 | ('c', 7, None, 'e', 'i', 5), 98 | } 99 | ), 100 | ( 101 | ['i'], { 102 | ('f', 12, 'c', 'd', 'm', 4), 103 | ('d', 10, 'f', 'a', 'l', 3), 104 | ('l', 11, 'd', None, None, 1), 105 | ('m', 17, 'f', 'h', 'k', 3), 106 | ('c', 7, None, 'g', 'f', 5), 107 | } 108 | ), 109 | # Root 110 | ( 111 | ['c'], { 112 | ('b', 6, None, 'g', 'i', 5), 113 | ('j', 5, 'g', 'o', None, 2), 114 | ('g', 3, 'b', 't', 'j', 3), 115 | ('i', 13, 'b', 'd', 'm', 4), 116 | } 117 | ), 118 | ) 119 | #tree_nodes = ( 120 | # ('a', 8), 121 | # ('b', 6), 122 | # ('c', 7), 123 | # ('d', 10), 124 | # ('e', 2), 125 | # ('f', 12), 126 | # ('g', 3), 127 | # ('h', 15), 128 | # ('i', 13), 129 | # ('j', 5), 130 | # ('k', 19), 131 | # ('l', 11), 132 | # ('m', 17), 133 | # ('n', 0), 134 | # ('o', 4), 135 | # ('p', 14), 136 | # ('q', 18), 137 | # ('r', 9), 138 | # ('s', 16), 139 | # ('t', 1), 140 | #) 141 | ) 142 | def test_removing_nodes(deploy_coinbase, deployed_contracts, ids_to_remove, expected_states): 143 | index_name = "test-{0}".format(''.join(ids_to_remove)) 144 | 145 | grove = deployed_contracts.Grove 146 | 147 | for _id, value in tree_nodes: 148 | grove.insert(index_name, _id, value) 149 | 150 | index_id = grove.computeIndexId(deploy_coinbase, index_name) 151 | 152 | for _id in ids_to_remove: 153 | node_id = grove.computeNodeId(index_id, _id) 154 | assert grove.exists(index_id, _id) is True 155 | grove.remove(index_name, _id) 156 | assert grove.exists(index_id, _id) is False 157 | 158 | actual_states = set() 159 | 160 | for _id, _, _, _, _, _ in expected_states: 161 | node_id = grove.computeNodeId(index_id, _id) 162 | value = grove.getNodeValue(node_id) 163 | parent = grove.getNodeParent(node_id) 164 | left = grove.getNodeLeftChild(node_id) 165 | right = grove.getNodeRightChild(node_id) 166 | height = grove.getNodeHeight(node_id) 167 | 168 | actual_states.add((_id, value, parent, left, right, height)) 169 | 170 | assert expected_states == actual_states 171 | 172 | 173 | def test_deleting_sets_new_root(deploy_coinbase, deployed_contracts): 174 | grove = deployed_contracts.Grove 175 | 176 | index_name = "test-root_deletion" 177 | index_id = grove.computeIndexId(deploy_coinbase, index_name) 178 | 179 | grove.insert(index_name, 'a', 2) 180 | grove.insert(index_name, 'b', 1) 181 | grove.insert(index_name, 'c', 3) 182 | 183 | node_a_id = grove.computeNodeId(index_id, 'a') 184 | node_b_id = grove.computeNodeId(index_id, 'b') 185 | 186 | assert grove.getIndexRoot(index_id) == 'a' 187 | 188 | grove.remove(index_name, 'a') 189 | 190 | assert grove.getIndexRoot(index_id) == 'b' 191 | -------------------------------------------------------------------------------- /tests/navigation/test_get_next_node.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | tree_nodes = ( 5 | ('a', 18), 6 | ('b', 0), 7 | ('c', 7), 8 | ('d', 11), 9 | ('e', 16), 10 | ('f', 3), 11 | ('g', 16), 12 | ('h', 17), 13 | ('i', 17), 14 | ('j', 18), 15 | ('k', 12), 16 | ('l', 3), 17 | ('m', 4), 18 | ('n', 6), 19 | ('o', 11), 20 | ('p', 5), 21 | ('q', 12), 22 | ('r', 1), 23 | ('s', 1), 24 | ('t', 16), 25 | ('u', 14), 26 | ('v', 3), 27 | ('w', 7), 28 | ('x', 13), 29 | ('y', 6), 30 | ('z', 17), 31 | ) 32 | 33 | @pytest.fixture(scope="module") 34 | def big_tree(deployed_contracts): 35 | grove = deployed_contracts.Grove 36 | 37 | for _id, value in tree_nodes: 38 | grove.insert('test', _id, value) 39 | return grove 40 | 41 | 42 | @pytest.mark.parametrize( 43 | '_id,next_id', 44 | ( 45 | ('b', 'r'), 46 | ('r', 's'), 47 | ('s', 'f'), 48 | ('f', 'l'), 49 | ('l', 'v'), 50 | ('v', 'm'), 51 | ('m', 'p'), 52 | ('p', 'n'), 53 | ('n', 'y'), 54 | ('y', 'c'), 55 | ('c', 'w'), 56 | ('w', 'd'), 57 | ('d', 'o'), 58 | ('o', 'k'), 59 | ('k', 'q'), 60 | ('q', 'x'), 61 | ('x', 'u'), 62 | ('u', 'e'), 63 | ('e', 'g'), 64 | ('g', 't'), 65 | ('t', 'h'), 66 | ('h', 'i'), 67 | ('i', 'z'), 68 | ('z', 'a'), 69 | ('a', 'j'), 70 | ('j', None), 71 | ) 72 | ) 73 | def test_getting_next_node(deploy_coinbase, big_tree, _id, next_id): 74 | def get_val(node_id): 75 | if node_id is None: 76 | return None 77 | return big_tree.getNodeValue(node_id) 78 | 79 | index_id = big_tree.computeIndexId(deploy_coinbase, "test") 80 | 81 | node_id = big_tree.computeNodeId(index_id, _id) 82 | actual_next_id = big_tree.getNextNode(node_id) 83 | assert actual_next_id == next_id 84 | 85 | 86 | @pytest.mark.parametrize( 87 | '_id,previous_id', 88 | ( 89 | ('b', None), 90 | ('r', 'b'), 91 | ('s', 'r'), 92 | ('f', 's'), 93 | ('l', 'f'), 94 | ('v', 'l'), 95 | ('m', 'v'), 96 | ('p', 'm'), 97 | ('n', 'p'), 98 | ('y', 'n'), 99 | ('c', 'y'), 100 | ('w', 'c'), 101 | ('d', 'w'), 102 | ('o', 'd'), 103 | ('k', 'o'), 104 | ('q', 'k'), 105 | ('x', 'q'), 106 | ('u', 'x'), 107 | ('e', 'u'), 108 | ('g', 'e'), 109 | ('t', 'g'), 110 | ('h', 't'), 111 | ('i', 'h'), 112 | ('z', 'i'), 113 | ('a', 'z'), 114 | ('j', 'a'), 115 | ) 116 | ) 117 | def test_getting_previous_node(deploy_coinbase, big_tree, _id, previous_id): 118 | def get_val(node_id): 119 | if node_id is None: 120 | return None 121 | return big_tree.getNodeValue(node_id) 122 | 123 | index_id = big_tree.computeIndexId(deploy_coinbase, "test") 124 | 125 | node_id = big_tree.computeNodeId(index_id, _id) 126 | actual_previous_id = big_tree.getPreviousNode(node_id) 127 | assert actual_previous_id == previous_id 128 | -------------------------------------------------------------------------------- /tests/query/test_exists_querying.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | tree_nodes = ( 5 | ('a', 18), 6 | ('b', 0), 7 | ('c', 7), 8 | ('d', 11), 9 | ('e', 16), 10 | ('f', 3), 11 | ('g', 16), 12 | ('h', 17), 13 | ('i', 17), 14 | ('j', 18), 15 | ('k', 12), 16 | ('l', 3), 17 | ('m', 4), 18 | ('n', 6), 19 | ('o', 11), 20 | ('p', 5), 21 | ('q', 12), 22 | ('r', 1), 23 | ('s', 1), 24 | ('t', 16), 25 | ('u', 14), 26 | ('v', 3), 27 | ('w', 7), 28 | ('x', 13), 29 | ('y', 6), 30 | ('z', 17), 31 | ) 32 | 33 | 34 | def test_exists_query(deploy_coinbase, deployed_contracts): 35 | grove = deployed_contracts.Grove 36 | index_id = grove.computeIndexId(deploy_coinbase, "test-a") 37 | 38 | for _id, value in tree_nodes: 39 | assert grove.exists(index_id, _id) is False 40 | 41 | grove.insert('test-a', _id, value) 42 | 43 | assert grove.exists(index_id, _id) is True 44 | 45 | # Make sure it still registers as having all of the nodes. 46 | for _id, _ in tree_nodes: 47 | assert grove.exists(index_id, _id) is True 48 | 49 | # Sanity check 50 | for _id in ('aa', 'tsra', 'arst', 'bb', 'cc'): 51 | assert grove.exists(index_id, _id) is False 52 | 53 | 54 | def test_exists_query_special_case(deploy_coinbase, deployed_contracts): 55 | grove = deployed_contracts.Grove 56 | index_id = grove.computeIndexId(deploy_coinbase, "test-b") 57 | 58 | grove.insert('test-b', 'key', 1234) 59 | 60 | assert grove.exists(index_id, '') is False 61 | -------------------------------------------------------------------------------- /tests/query/test_querying.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | tree_nodes = ( 5 | ('a', 18), 6 | ('b', 0), 7 | ('c', 7), 8 | ('d', 11), 9 | ('e', 16), 10 | ('f', 3), 11 | ('g', 16), 12 | ('h', 17), 13 | ('i', 17), 14 | ('j', 18), 15 | ('k', 12), 16 | ('l', 3), 17 | ('m', 4), 18 | ('n', 6), 19 | ('o', 11), 20 | ('p', 5), 21 | ('q', 12), 22 | ('r', 1), 23 | ('s', 1), 24 | ('t', 16), 25 | ('u', 14), 26 | ('v', 3), 27 | ('w', 7), 28 | ('x', 13), 29 | ('y', 6), 30 | ('z', 17), 31 | ) 32 | 33 | @pytest.fixture(scope="module") 34 | def big_tree(deployed_contracts): 35 | grove = deployed_contracts.Grove 36 | 37 | for _id, value in tree_nodes: 38 | grove.insert('test-querying', _id, value) 39 | return grove 40 | 41 | 42 | @pytest.mark.parametrize( 43 | 'operator,value,expected', 44 | ( 45 | # parent, left, right, height 46 | ("==", 7, (7, None, 4, 16, 6)), 47 | ("==", 6, (6, 4, 5, 6, 2)), 48 | ("==", 11, (11, 11, 7, None, 2)), 49 | ("==", 17, (17, 16, 16, 18, 4)), 50 | ("==", 15, None), 51 | ("==", 2, None), 52 | ("==", 8, None), 53 | # LT 54 | ("<", 4, (3, 3, None, None, 1)), 55 | ("<", 5, (4, 7, 3, 6, 4)), 56 | ("<", 8, (7, 11, None, None, 1)), 57 | ("<", 14, (13, 14, None, None, 1)), 58 | ("<", 16, (14, 12, 13, None, 2)), 59 | ("<", 17, (16, 16, None, None, 1)), 60 | ("<", 18, (17, 17, None, None, 1)), 61 | ("<", -1, None), 62 | ("<", 0, None), 63 | # GT 64 | (">", 6, (7, None, 4, 16, 6)), 65 | (">", 3, (4, 7, 3, 6, 4)), 66 | (">", 16, (17, 16, 16, 18, 4)), 67 | (">", 18, None), 68 | (">", 19, None), 69 | # LTE 70 | ("<=", 4, (4, 7, 3, 6, 4)), 71 | ("<=", 5, (5, 6, None, None, 1)), 72 | ("<=", 8, (7, 11, None, None, 1)), 73 | ("<=", 7, (7, 11, None, None, 1)), 74 | ("<=", 6, (6, 6, None, None, 1)), 75 | ("<=", 16, (16, 16, None, None, 1)), 76 | ("<=", 15, (14, 12, 13, None, 2)), 77 | ("<=", 0, (0, 1, None, None, 1)), 78 | ("<=", 18, (18, 18, None, None, 1)), 79 | ("<=", 19, (18, 18, None, None, 1)), 80 | ("<=", -1, None), 81 | # GTE 82 | (">=", 0, (0, 1, None, None, 1)), 83 | (">=", 3, (3, 4, 1, 3, 3)), 84 | (">=", 13, (13, 14, None, None, 1)), 85 | (">=", 14, (14, 12, 13, None, 2)), 86 | (">=", 15, (16, 7, 11, 17, 5)), 87 | (">=", 17, (17, 16, 16, 18, 4)), 88 | (">=", 18, (18, 17, 17, 18, 3)), 89 | (">=", 19, None), 90 | ) 91 | ) 92 | def test_tree_querying(deploy_coinbase, big_tree, operator, value, expected): 93 | index_id = big_tree.computeIndexId(deploy_coinbase, "test-querying") 94 | 95 | def get_val(node_id): 96 | if node_id is None: 97 | return None 98 | return big_tree.getNodeValue(big_tree.computeNodeId(index_id, node_id)) 99 | 100 | _id = big_tree.query(index_id, operator, value) 101 | 102 | if _id is None: 103 | assert expected is None 104 | else: 105 | node_id = big_tree.computeNodeId(index_id, _id) 106 | 107 | val = big_tree.getNodeValue(node_id) 108 | parent = get_val(big_tree.getNodeParent(node_id)) 109 | left = get_val(big_tree.getNodeLeftChild(node_id)) 110 | right = get_val(big_tree.getNodeRightChild(node_id)) 111 | height = big_tree.getNodeHeight(node_id) 112 | 113 | actual = (val, parent, left, right, height) 114 | assert actual == expected 115 | --------------------------------------------------------------------------------