Skip to content

Commit

Permalink
Merge pull request #5 from pipermerriam/piper/library-usage
Browse files Browse the repository at this point in the history
Conversion of Grove into a library.
  • Loading branch information
pipermerriam committed Oct 9, 2015
2 parents cecebf8 + 829fe5b commit 985fd07
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 182 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
Grove provides indexed storage for ordered data such that it can be efficiently
queried.

You can deploy grove yourself, or use the version deployed at `0xfe9d4e5717ec0e16f8301240df5c3f7d3e9effef`
You can use grove as a library either by deploying the `GroveLib` contract
yourself, or using the one deployed at
`0xd07ce4329b27eb8896c51458468d98a0e4c0394c`.

If you would like to verify the source, it was compiled using version
`0.1.3-1736fe80` of the `solc` compiler with the `--optimize` flag turned
on.
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`.

If you would like to verify either of the contract's source, they were compiled using version
`0.1.5-23865e39` of the `solc` compiler with the `--optimize` flag turned.

[Documentation](http://ethereum-grove.readthedocs.org/en/latest/)
More information available in the [Documentation](http://ethereum-grove.readthedocs.org/en/latest/)
478 changes: 335 additions & 143 deletions contracts/Grove.sol

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Indexes are namespaced by ethereum address, meaning that any write operations
to an index by different addresses will write to different indexes. Each index
has an id which can be computed as ``sha3(ownerAddress, indexName)``.

You can also use the ``getIndexId(address ownerAddress, bytes32 indexName)``
You can also use the ``computeIndexId(address ownerAddress, bytes32 indexName)``
function to compute this for you.


Expand Down Expand Up @@ -165,8 +165,8 @@ natively.
/*
* Shortcuts
*/
function getIndexId(address ownerAddress, bytes32 indexName) constant returns (bytes32);
function getNodeId(bytes32 indexId, bytes32 id) constant returns (bytes32);
function computeIndexId(address ownerAddress, bytes32 indexName) constant returns (bytes32);
function computeNodeId(bytes32 indexId, bytes32 id) constant returns (bytes32);
/*
* Node and Index API
Expand Down Expand Up @@ -203,4 +203,4 @@ The contract can be accessed via web3.js with

.. code-block:: javascript
var Grove = web3.eth.contract([{"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":"indexId","type":"bytes32"},{"name":"id","type":"bytes32"}],"name":"getNodeId","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":true,"inputs":[{"name":"owner","type":"address"},{"name":"indexName","type":"bytes32"}],"name":"getIndexId","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);
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);
19 changes: 14 additions & 5 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,27 @@ Welcome to Grove's documentation!
Grove is an ethereum contract that provides an API for storing and retrieving
ordered data in a fast manner.

The current implementation uses an AVL tree for storage which has on average
Grove indexes use an AVL tree for storage which has on average
``O(log n)`` time complexity for inserts and lookups.

The Grove contract can be accessed at
``0x7d7ce4e2cdfea812b33f48f419860b91cf9a141d``. If you would like to verify
the source, it was compiled using version ``0.1.3-1736fe80`` of the ``solc``
compiler with the ``--optimize`` flag turned on.
Grove can be used as a library within your own contract, or as a service by
interacting with the publicly deployed Grove contract.

The Grove Library can be used at the address
``0xd07ce4329b27eb8896c51458468d98a0e4c0394c``.

The Grove contract can be used at
``0x8017f24a47c889b1ee80501ff84beb3c017edf0b``.

If you would like to verify the source, it was compiled using version
``0.1.5-23865e39`` of the ``solc`` compiler with the ``--optimize`` flag turned
on.


.. toctree::
intro
api
library
:maxdepth: 2

Indices and tables
Expand Down
64 changes: 64 additions & 0 deletions docs/library.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
Library Usage
=============

Grove can be used as a `solidity library <https://github.com/ethereum/wiki/wiki/Solidity-Tutorial#libraries>`_, allowing use of all of Groves functionality using your own contracts storage.

The primary Grove contract itself is actually just a wrapper around the
deployed library contract.


Example
-------

In this example, we will use a fictional contract ``Members`` which tracks
information about some set of members for an organization.


.. code-block:: solidity
library GroveAPI {
struct Index {
bytes32 id;
bytes32 name;
bytes32 root;
mapping (bytes32 => Node) nodes;
}
struct Node {
bytes32 nodeId;
bytes32 indexId;
bytes32 id;
int value;
bytes32 parent;
bytes32 left;
bytes32 right;
uint height;
}
function insert(Index storage index, bytes32 id, int value) public;
function query(Index storage index, bytes2 operator, int value) public returns (bytes32);
}
contract Members {
Grove.Index ageIndex;
function addMember(bytes32 name, uint age) {
... // Do whatever needs to be done to add the member.
// Update the ageIndex with this new member's age.
Grove.insert(ageIndex, name, age);
}
function isAnyoneThisOld(uint age) constant returns (bool) {
return Grove.query("==", age) != 0x0;
}
}
The ``Members`` contract above interfaces with the Grove library in two places.

First, within the ``addMember`` function, everytime a member is added to the
organization, their age is tracked in the ``ageIndex``.

Second, the ``isAnyoneThisOld`` allows checking whether any member is a
specific age.
1 change: 0 additions & 1 deletion docs/solidity_lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@ class SolidityLexer(CFamilyLexer):
tokens = {
'address': [
(r'address', Keyword.Type),
#(words(('address',)), Keyword.Type),
]
}
22 changes: 11 additions & 11 deletions tests/balancing/test_auto_balancing.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
def test_single_node_tree(deployed_contracts):
grove = deployed_contracts.Grove

index_id = grove.getIndexId(grove._meta.rpc_client.get_coinbase(), "test-a")
index_id = grove.computeIndexId(grove._meta.rpc_client.get_coinbase(), "test-a")

grove.insert('test-a', 'a', 20)
node_id = grove.getNodeId(index_id, 'a')
node_id = grove.computeNodeId(index_id, 'a')

assert grove.getNodeId(node_id) == 'a'
assert grove.getNodeParent(node_id) is None
Expand All @@ -22,13 +22,13 @@ def get_id(node_id):
return None
return grove.getNodeId.call(node_id)

index_id = grove.getIndexId(grove._meta.rpc_client.get_coinbase(), "test-b")
index_id = grove.computeIndexId(grove._meta.rpc_client.get_coinbase(), "test-b")

grove.insert('test-b', 'a', 20)
node_a_id = grove.getNodeId(index_id, 'a')
node_a_id = grove.computeNodeId(index_id, 'a')

grove.insert('test-b', 'b', 25)
node_b_id = grove.getNodeId(index_id, 'b')
node_b_id = grove.computeNodeId(index_id, 'b')

root = get_id(grove.getIndexRoot.call(index_id))
assert root == 'a'
Expand Down Expand Up @@ -73,15 +73,15 @@ def get_id(node_id):
for id, node_value in values_a:
grove.insert('test-c', id, node_value)

index_id = grove.getIndexId(grove._meta.rpc_client.get_coinbase(), "test-c")
index_id = grove.computeIndexId(grove._meta.rpc_client.get_coinbase(), "test-c")

root = get_id(grove.getIndexRoot.call(index_id))
assert root == 'b'

actual = set()

for id, _ in values_a:
node_id = grove.getNodeId.call(index_id, id)
node_id = grove.computeNodeId.call(index_id, id)
value = grove.getNodeValue.call(node_id)
_parent = grove.getNodeParent.call(node_id)
parent = get_id(_parent)
Expand Down Expand Up @@ -121,15 +121,15 @@ def get_id(node_id):
for id, node_value in values_b:
grove.insert('test-d', id, node_value)

index_id = grove.getIndexId(grove._meta.rpc_client.get_coinbase(), "test-d")
index_id = grove.computeIndexId(grove._meta.rpc_client.get_coinbase(), "test-d")

root = get_id(grove.getIndexRoot.call(index_id))
assert root == 'b'

actual = set()

for id, _ in values_b:
node_id = grove.getNodeId.call(index_id, id)
node_id = grove.computeNodeId.call(index_id, id)
value = grove.getNodeValue.call(node_id)
_parent = grove.getNodeParent.call(node_id)
parent = get_id(_parent)
Expand Down Expand Up @@ -177,15 +177,15 @@ def get_id(node_id):
for id, node_value in values_c:
grove.insert('test-e', id, node_value)

index_id = grove.getIndexId(grove._meta.rpc_client.get_coinbase(), "test-e")
index_id = grove.computeIndexId(grove._meta.rpc_client.get_coinbase(), "test-e")

root = get_id(grove.getIndexRoot.call(index_id))
assert root == 'c'

actual = set()

for id, _ in values_c:
node_id = grove.getNodeId.call(index_id, id)
node_id = grove.computeNodeId.call(index_id, id)
value = grove.getNodeValue.call(node_id)
_parent = grove.getNodeParent.call(node_id)
parent = get_id(_parent)
Expand Down
12 changes: 6 additions & 6 deletions tests/balancing/test_node_deletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,18 @@ def get_id(node_id):
return None
return grove.getNodeId.call(node_id)

index_id = grove.getIndexId(grove._meta.rpc_client.get_coinbase(), index_name)
index_id = grove.computeIndexId(grove._meta.rpc_client.get_coinbase(), index_name)

for _id in ids_to_remove:
node_id = grove.getNodeId(index_id, _id)
node_id = grove.computeNodeId(index_id, _id)
assert grove.exists(index_id, _id) is True
grove.remove(index_name, _id)
assert grove.exists(index_id, _id) is False

actual_states = set()

for _id, _, _, _, _, _ in expected_states:
node_id = grove.getNodeId(index_id, _id)
node_id = grove.computeNodeId(index_id, _id)
value = grove.getNodeValue(node_id)
parent = get_id(grove.getNodeParent(node_id))
left = get_id(grove.getNodeLeftChild(node_id))
Expand All @@ -179,14 +179,14 @@ def test_deleting_sets_new_root(deployed_contracts):
grove = deployed_contracts.Grove

index_name = "test-root_deletion"
index_id = grove.getIndexId(grove._meta.rpc_client.get_coinbase(), index_name)
index_id = grove.computeIndexId(grove._meta.rpc_client.get_coinbase(), index_name)

grove.insert(index_name, 'a', 2)
grove.insert(index_name, 'b', 1)
grove.insert(index_name, 'c', 3)

node_a_id = grove.getNodeId(index_id, 'a')
node_b_id = grove.getNodeId(index_id, 'b')
node_a_id = grove.computeNodeId(index_id, 'a')
node_b_id = grove.computeNodeId(index_id, 'b')

assert grove.getIndexRoot(index_id) == node_a_id

Expand Down
8 changes: 4 additions & 4 deletions tests/navigation/test_get_next_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ def get_val(node_id):
return None
return big_tree.getNodeValue.call(node_id)

index_id = big_tree.getIndexId(big_tree._meta.rpc_client.get_coinbase(), "test")
index_id = big_tree.computeIndexId(big_tree._meta.rpc_client.get_coinbase(), "test")

node_id = big_tree.getNodeId(index_id, _id)
node_id = big_tree.computeNodeId(index_id, _id)
next_node_id = big_tree.getNextNode.call(node_id)
if next_id is None:
assert next_node_id is None
Expand Down Expand Up @@ -125,9 +125,9 @@ def get_val(node_id):
return None
return big_tree.getNodeValue.call(node_id)

index_id = big_tree.getIndexId(big_tree._meta.rpc_client.get_coinbase(), "test")
index_id = big_tree.computeIndexId(big_tree._meta.rpc_client.get_coinbase(), "test")

node_id = big_tree.getNodeId(index_id, _id)
node_id = big_tree.computeNodeId(index_id, _id)
previous_node_id = big_tree.getPreviousNode.call(node_id)
if previous_id is None:
assert previous_node_id is None
Expand Down
4 changes: 2 additions & 2 deletions tests/query/test_exists_querying.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

def test_exists_query(deployed_contracts):
grove = deployed_contracts.Grove
index_id = grove.getIndexId(grove._meta.rpc_client.get_coinbase(), "test")
index_id = grove.computeIndexId(grove._meta.rpc_client.get_coinbase(), "test")

for _id, value in tree_nodes:
assert grove.exists(index_id, _id) is False
Expand All @@ -53,7 +53,7 @@ def test_exists_query(deployed_contracts):

def test_exists_query_special_case(deployed_contracts):
grove = deployed_contracts.Grove
index_id = grove.getIndexId(grove._meta.rpc_client.get_coinbase(), "test")
index_id = grove.computeIndexId(grove._meta.rpc_client.get_coinbase(), "test")

grove.insert('test', 'key', 1234)

Expand Down
2 changes: 1 addition & 1 deletion tests/query/test_querying.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def get_val(node_id):
return None
return big_tree.getNodeValue.call(node_id)

index_id = big_tree.getIndexId(big_tree._meta.rpc_client.get_coinbase(), "test")
index_id = big_tree.computeIndexId(big_tree._meta.rpc_client.get_coinbase(), "test")

node_id = big_tree.query.call(index_id, operator, value)

Expand Down

0 comments on commit 985fd07

Please sign in to comment.