diff --git a/balloon/hyper/cache.go b/balloon/hyper/cache.go deleted file mode 100644 index bd4056167..000000000 --- a/balloon/hyper/cache.go +++ /dev/null @@ -1,71 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package hyper - -import "github.com/bbva/qed/balloon/navigator" - -type CacheResolver interface { - ShouldBeInCache(pos navigator.Position) bool - ShouldCache(pos navigator.Position) bool - ShouldCollect(pos navigator.Position) bool - IsOnPath(pos navigator.Position) bool -} - -type SingleTargetedCacheResolver struct { - numBits uint16 - cacheLevel uint16 - targetKey []byte -} - -func NewSingleTargetedCacheResolver(numBits, cacheLevel uint16, targetKey []byte) *SingleTargetedCacheResolver { - return &SingleTargetedCacheResolver{numBits, cacheLevel, targetKey} -} - -func (r SingleTargetedCacheResolver) ShouldBeInCache(pos navigator.Position) bool { - return pos.Height() >= r.cacheLevel && !r.IsOnPath(pos) -} - -func (r SingleTargetedCacheResolver) ShouldCache(pos navigator.Position) bool { - return pos.Height() == r.cacheLevel -} - -func (r SingleTargetedCacheResolver) ShouldCollect(pos navigator.Position) bool { - return pos.Height() == r.cacheLevel -} - -/* - This method does not reach leafs. Goes from root (bit := 0) to height=1 (bit := numbits - 1) -*/ -func (r SingleTargetedCacheResolver) IsOnPath(pos navigator.Position) bool { - height := pos.Height() - if height == r.numBits { - return true - } - bit := r.numBits - height - 1 - return bitIsSet(r.targetKey, bit) == bitIsSet(pos.Index(), bit) -} - -/* - Is bit in position 'i' set to 1? - i : 2 3 - bits: [00101011] [00101011] - mask: [00100000] [00010000] - true false -*/ -func bitIsSet(bits []byte, i uint16) bool { - return bits[i/8]&(1< 1 && p.navigator.IsLeaf(pos) { - return nil, ErrLeavesSlice - } - - // we do a post-order traversal - - // split leaves - rightPos := p.navigator.GoToRight(pos) - leftSlice, rightSlice := leaves.Split(rightPos.Index()) - left, err := p.traverseWithoutCache(p.navigator.GoToLeft(pos), leftSlice) - if err != nil { - return nil, ErrLeavesSlice - } - - right, err := p.traverseWithoutCache(rightPos, rightSlice) - if err != nil { - return nil, ErrLeavesSlice - } - - if p.navigator.IsRoot(pos) { - return visitor.NewRoot(pos, left, right), nil - } - return visitor.NewNode(pos, left, right), nil -} - -type SearchPruner struct { - key []byte - PruningContext -} - -func NewSearchPruner(key []byte, context PruningContext) *SearchPruner { - return &SearchPruner{key, context} -} - -func (p *SearchPruner) Prune() (visitor.Visitable, error) { - return p.traverseCache(p.navigator.Root(), storage.NewKVRange()) -} - -func (p *SearchPruner) traverseCache(pos navigator.Position, leaves storage.KVRange) (visitor.Visitable, error) { - if p.cacheResolver.ShouldBeInCache(pos) { - digest, ok := p.cache.Get(pos) - if !ok { - cached := visitor.NewCached(pos, p.defaultHashes[pos.Height()]) - return visitor.NewCollectable(cached), nil - } - return visitor.NewCollectable(visitor.NewCached(pos, digest)), nil - } - - // if we are over the cache level, we need to do a range query to get the leaves - var atLastLevel bool - if atLastLevel = p.cacheResolver.ShouldCache(pos); atLastLevel { - first := p.navigator.DescendToFirst(pos) - last := p.navigator.DescendToLast(pos) - kvRange, _ := p.store.GetRange(storage.IndexPrefix, first.Index(), last.Index()) - - // replace leaves with new slice and append the previous to the new one - for _, l := range leaves { - kvRange = kvRange.InsertSorted(l) - } - leaves = kvRange - } - - rightPos := p.navigator.GoToRight(pos) - leftPos := p.navigator.GoToLeft(pos) - leftSlice, rightSlice := leaves.Split(rightPos.Index()) - - var left, right visitor.Visitable - var err error - if atLastLevel { - left, err = p.traverse(leftPos, leftSlice) - if err != nil { - return nil, err - } - right, err = p.traverse(rightPos, rightSlice) - } else { - left, err = p.traverseCache(leftPos, leftSlice) - if err != nil { - return nil, err - } - right, err = p.traverseCache(rightPos, rightSlice) - } - if err != nil { - return nil, err - } - - if p.navigator.IsRoot(pos) { - return visitor.NewRoot(pos, left, right), nil - } - - return visitor.NewNode(pos, left, right), nil -} - -func (p *SearchPruner) traverse(pos navigator.Position, leaves storage.KVRange) (visitor.Visitable, error) { - if p.navigator.IsLeaf(pos) && len(leaves) == 1 { - leaf := visitor.NewLeaf(pos, leaves[0].Value) - if !p.cacheResolver.IsOnPath(pos) { - return visitor.NewCollectable(leaf), nil - } - return leaf, nil - } - if !p.navigator.IsRoot(pos) && len(leaves) == 0 { - cached := visitor.NewCached(pos, p.defaultHashes[pos.Height()]) - return visitor.NewCollectable(cached), nil - } - if len(leaves) > 1 && p.navigator.IsLeaf(pos) { - return nil, ErrLeavesSlice - } - - // we do a post-order traversal - - // split leaves - rightPos := p.navigator.GoToRight(pos) - leftSlice, rightSlice := leaves.Split(rightPos.Index()) - - if !p.cacheResolver.IsOnPath(pos) { - left, err := p.traverseWithoutCollecting(p.navigator.GoToLeft(pos), leftSlice) - if err != nil { - return nil, err - } - - right, err := p.traverseWithoutCollecting(rightPos, rightSlice) - if err != nil { - return nil, err - } - - if p.navigator.IsRoot(pos) { - return visitor.NewRoot(pos, left, right), nil - } - return visitor.NewCollectable(visitor.NewNode(pos, left, right)), nil - } - - left, err := p.traverse(p.navigator.GoToLeft(pos), leftSlice) - if err != nil { - return nil, err - } - - right, err := p.traverse(rightPos, rightSlice) - if err != nil { - return nil, err - } - - if p.navigator.IsRoot(pos) { - return visitor.NewRoot(pos, left, right), nil - } - return visitor.NewNode(pos, left, right), nil -} - -func (p *SearchPruner) traverseWithoutCollecting(pos navigator.Position, leaves storage.KVRange) (visitor.Visitable, error) { - if p.navigator.IsLeaf(pos) && len(leaves) == 1 { - return visitor.NewLeaf(pos, leaves[0].Value), nil - } - if !p.navigator.IsRoot(pos) && len(leaves) == 0 { - return visitor.NewCached(pos, p.defaultHashes[pos.Height()]), nil - } - if len(leaves) > 1 && p.navigator.IsLeaf(pos) { - return nil, ErrLeavesSlice - } - - // we do a post-order traversal - - // split leaves - rightPos := p.navigator.GoToRight(pos) - leftSlice, rightSlice := leaves.Split(rightPos.Index()) - left, err := p.traverseWithoutCollecting(p.navigator.GoToLeft(pos), leftSlice) - if err != nil { - return nil, ErrLeavesSlice - } - right, err := p.traverseWithoutCollecting(rightPos, rightSlice) - if err != nil { - return nil, ErrLeavesSlice - } - - if p.navigator.IsRoot(pos) { - return visitor.NewRoot(pos, left, right), nil - } - return visitor.NewNode(pos, left, right), nil -} - -type VerifyPruner struct { - key hashing.Digest - value []byte - PruningContext -} - -func NewVerifyPruner(key, value []byte, context PruningContext) *VerifyPruner { - return &VerifyPruner{key, value, context} -} - -func (p *VerifyPruner) Prune() (visitor.Visitable, error) { - leaves := storage.KVRange{storage.NewKVPair(p.key, p.value)} - return p.traverse(p.navigator.Root(), leaves) -} - -func (p *VerifyPruner) traverse(pos navigator.Position, leaves storage.KVRange) (visitor.Visitable, error) { - if p.navigator.IsLeaf(pos) && len(leaves) == 1 { - return visitor.NewLeaf(pos, leaves[0].Value), nil - } - if !p.navigator.IsRoot(pos) && len(leaves) == 0 { - digest, ok := p.cache.Get(pos) - if !ok { - return nil, ErrWrongAuditPath - } - return visitor.NewCached(pos, digest), nil - } - if len(leaves) > 1 && p.navigator.IsLeaf(pos) { - return nil, ErrLeavesSlice - } - - // we do a post-order traversal - - // split leaves - rightPos := p.navigator.GoToRight(pos) - leftSlice, rightSlice := leaves.Split(rightPos.Index()) - left, err := p.traverse(p.navigator.GoToLeft(pos), leftSlice) - if err != nil { - return nil, err - } - right, err := p.traverse(rightPos, rightSlice) - if err != nil { - return nil, err - } - if p.navigator.IsRoot(pos) { - return visitor.NewRoot(pos, left, right), nil - } - return visitor.NewNode(pos, left, right), nil -} diff --git a/balloon/hyper/pruner_test.go b/balloon/hyper/pruner_test.go deleted file mode 100644 index f1a941303..000000000 --- a/balloon/hyper/pruner_test.go +++ /dev/null @@ -1,309 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package hyper - -import ( - "testing" - - assert "github.com/stretchr/testify/require" - - "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/balloon/visitor" - "github.com/bbva/qed/hashing" - "github.com/bbva/qed/storage" - storage_utils "github.com/bbva/qed/testutils/storage" -) - -var ( - FixedDigest = make([]byte, 8) -) - -func pos(index byte, height uint16) navigator.Position { - return NewPosition([]byte{index}, height) -} - -func root(pos navigator.Position, left, right visitor.Visitable) *visitor.Root { - return visitor.NewRoot(pos, left, right) -} - -func node(pos navigator.Position, left, right visitor.Visitable) *visitor.Node { - return visitor.NewNode(pos, left, right) -} - -func leaf(pos navigator.Position, value byte) *visitor.Leaf { - return visitor.NewLeaf(pos, []byte{value}) -} - -func cached(pos navigator.Position) *visitor.Cached { - return visitor.NewCached(pos, hashing.Digest{0}) -} - -func collectable(underlying visitor.Visitable) *visitor.Collectable { - return visitor.NewCollectable(underlying) -} - -func cacheable(underlying visitor.Visitable) *visitor.Cacheable { - return visitor.NewCacheable(underlying) -} - -func TestInsertPruner(t *testing.T) { - - numBits := uint16(8) - cacheLevel := uint16(4) - - testCases := []struct { - key, value []byte - storeMutations []*storage.Mutation - expectedPruned visitor.Visitable - }{ - { - key: []byte{0}, - value: []byte{0}, - storeMutations: []*storage.Mutation{}, - expectedPruned: root(pos(0, 8), - cacheable(node(pos(0, 7), - cacheable(node(pos(0, 6), - cacheable(node(pos(0, 5), - collectable(cacheable(node(pos(0, 4), - node(pos(0, 3), - node(pos(0, 2), - node(pos(0, 1), - leaf(pos(0, 0), 0), - cached(pos(1, 0))), - cached(pos(2, 1))), - cached(pos(4, 2))), - cached(pos(8, 3))))), - cached(pos(16, 4)))), - cached(pos(32, 5)))), - cached(pos(64, 6)))), - cached(pos(128, 7)), - ), - }, - { - key: []byte{2}, - value: []byte{1}, - storeMutations: []*storage.Mutation{ - {storage.IndexPrefix, []byte{0}, []byte{0}}, - }, - expectedPruned: root(pos(0, 8), - cacheable(node(pos(0, 7), - cacheable(node(pos(0, 6), - cacheable(node(pos(0, 5), - collectable(cacheable(node(pos(0, 4), - node(pos(0, 3), - node(pos(0, 2), - node(pos(0, 1), - leaf(pos(0, 0), 0), - cached(pos(1, 0))), - node(pos(2, 1), - leaf(pos(2, 0), 1), - cached(pos(3, 0)))), - cached(pos(4, 2))), - cached(pos(8, 3))))), - cached(pos(16, 4)))), - cached(pos(32, 5)))), - cached(pos(64, 6)))), - cached(pos(128, 7)), - ), - }, - { - key: []byte{255}, - value: []byte{2}, - storeMutations: []*storage.Mutation{ - {storage.IndexPrefix, []byte{0}, []byte{0}}, - {storage.IndexPrefix, []byte{2}, []byte{1}}, - }, - expectedPruned: root(pos(0, 8), - cached(pos(0, 7)), - cacheable(node(pos(128, 7), - cached(pos(128, 6)), - cacheable(node(pos(192, 6), - cached(pos(192, 5)), - cacheable(node(pos(224, 5), - cached(pos(224, 4)), - collectable(cacheable(node(pos(240, 4), - cached(pos(240, 3)), - node(pos(248, 3), - cached(pos(248, 2)), - node(pos(252, 2), - cached(pos(252, 1)), - node(pos(254, 1), - cached(pos(254, 0)), - leaf(pos(255, 0), 2))))))))))), - )), - ), - }, - } - - for i, c := range testCases { - store, closeF := storage_utils.OpenBPlusTreeStore() - defer closeF() - store.Mutate(c.storeMutations) - - cache := cache.NewSimpleCache(4) - - context := PruningContext{ - navigator: NewHyperTreeNavigator(numBits), - cacheResolver: NewSingleTargetedCacheResolver(numBits, cacheLevel, c.key), - cache: cache, - store: store, - defaultHashes: []hashing.Digest{{0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}, - } - - pruned, _ := NewInsertPruner(c.key, c.value, context).Prune() - assert.Equalf(t, c.expectedPruned, pruned, "The pruned trees should match for test case %d", i) - } -} - -func TestSearchPruner(t *testing.T) { - - numBits := uint16(8) - cacheLevel := uint16(4) - - testCases := []struct { - key []byte - storeMutations []*storage.Mutation - expectedPruned visitor.Visitable - }{ - { - key: []byte{0}, - storeMutations: []*storage.Mutation{ - {storage.IndexPrefix, []byte{0}, []byte{0}}, - }, - expectedPruned: root(pos(0, 8), - node(pos(0, 7), - node(pos(0, 6), - node(pos(0, 5), - node(pos(0, 4), - node(pos(0, 3), - node(pos(0, 2), - node(pos(0, 1), - leaf(pos(0, 0), 0), - collectable(cached(pos(1, 0)))), - collectable(cached(pos(2, 1)))), - collectable(cached(pos(4, 2)))), - collectable(cached(pos(8, 3)))), - collectable(cached(pos(16, 4)))), - collectable(cached(pos(32, 5)))), - collectable(cached(pos(64, 6)))), - collectable(cached(pos(128, 7))), - ), - }, - { - key: []byte{6}, - storeMutations: []*storage.Mutation{ - {storage.IndexPrefix, []byte{1}, []byte{1}}, - {storage.IndexPrefix, []byte{6}, []byte{6}}, - }, - expectedPruned: root(pos(0, 8), - node(pos(0, 7), - node(pos(0, 6), - node(pos(0, 5), - node(pos(0, 4), - node(pos(0, 3), - collectable(node(pos(0, 2), - node(pos(0, 1), - cached(pos(0, 0)), - leaf(pos(1, 0), 1)), - cached(pos(2, 1)))), - node(pos(4, 2), - collectable(cached(pos(4, 1))), - node(pos(6, 1), - leaf(pos(6, 0), 6), - collectable(cached(pos(7, 0)))))), - collectable(cached(pos(8, 3)))), - collectable(cached(pos(16, 4)))), - collectable(cached(pos(32, 5)))), - collectable(cached(pos(64, 6)))), - collectable(cached(pos(128, 7)))), - }, - } - - for i, c := range testCases { - store, closeF := storage_utils.OpenBPlusTreeStore() - defer closeF() - store.Mutate(c.storeMutations) - - cache := cache.NewSimpleCache(4) - - context := PruningContext{ - navigator: NewHyperTreeNavigator(numBits), - cacheResolver: NewSingleTargetedCacheResolver(numBits, cacheLevel, c.key), - cache: cache, - store: store, - defaultHashes: []hashing.Digest{{0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}, - } - - pruned, _ := NewSearchPruner(c.key, context).Prune() - assert.Equalf(t, c.expectedPruned, pruned, "The pruned trees should match for test case %d", i) - } -} -func TestVerifyPruner(t *testing.T) { - - numBits := uint16(8) - cacheLevel := uint16(4) - - fakeCache := cache.NewFakeCache(hashing.Digest{0}) // Always return hashing.Digest{0} - // Add element before verifying. - store, closeF := storage_utils.OpenBPlusTreeStore() - defer closeF() - mutations := []*storage.Mutation{ - {storage.IndexPrefix, []byte{0}, []byte{0}}, - } - store.Mutate(mutations) - - testCases := []struct { - key, value []byte - expectedPruned visitor.Visitable - }{ - { - key: []byte{0}, - value: []byte{0}, - expectedPruned: root(pos(0, 8), - node(pos(0, 7), - node(pos(0, 6), - node(pos(0, 5), - node(pos(0, 4), - node(pos(0, 3), - node(pos(0, 2), - node(pos(0, 1), - leaf(pos(0, 0), 0), - cached(pos(1, 0))), - cached(pos(2, 1))), - cached(pos(4, 2))), - cached(pos(8, 3))), - cached(pos(16, 4))), - cached(pos(32, 5))), - cached(pos(64, 6))), - cached(pos(128, 7))), - }, - } - - for i, c := range testCases { - context := PruningContext{ - navigator: NewHyperTreeNavigator(numBits), - cacheResolver: NewSingleTargetedCacheResolver(numBits, cacheLevel, c.key), - cache: fakeCache, - store: store, - defaultHashes: []hashing.Digest{{0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}, - } - - pruned, _ := NewVerifyPruner(c.key, c.value, context).Prune() - assert.Equalf(t, c.expectedPruned, pruned, "The pruned trees should match for test case %d", i) - } -} diff --git a/balloon/hyper2/pruning/batch.go b/balloon/hyper/pruning/batch.go similarity index 100% rename from balloon/hyper2/pruning/batch.go rename to balloon/hyper/pruning/batch.go diff --git a/balloon/hyper2/pruning/insert.go b/balloon/hyper/pruning/insert.go similarity index 99% rename from balloon/hyper2/pruning/insert.go rename to balloon/hyper/pruning/insert.go index 1811ac9d5..457cc78ca 100644 --- a/balloon/hyper2/pruning/insert.go +++ b/balloon/hyper/pruning/insert.go @@ -20,7 +20,7 @@ import ( "bytes" "sort" - "github.com/bbva/qed/balloon/hyper2/navigation" + "github.com/bbva/qed/balloon/hyper/navigation" ) type Leaf struct { diff --git a/balloon/hyper2/pruning/insert_test.go b/balloon/hyper/pruning/insert_test.go similarity index 100% rename from balloon/hyper2/pruning/insert_test.go rename to balloon/hyper/pruning/insert_test.go diff --git a/balloon/hyper2/pruning/loader.go b/balloon/hyper/pruning/loader.go similarity index 97% rename from balloon/hyper2/pruning/loader.go rename to balloon/hyper/pruning/loader.go index 51810aa62..90034af67 100644 --- a/balloon/hyper2/pruning/loader.go +++ b/balloon/hyper/pruning/loader.go @@ -20,7 +20,7 @@ import ( "github.com/bbva/qed/log" "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/hyper2/navigation" + "github.com/bbva/qed/balloon/hyper/navigation" "github.com/bbva/qed/storage" ) diff --git a/balloon/hyper2/pruning/operation.go b/balloon/hyper/pruning/operation.go similarity index 99% rename from balloon/hyper2/pruning/operation.go rename to balloon/hyper/pruning/operation.go index 5533c1341..2eba36f32 100644 --- a/balloon/hyper2/pruning/operation.go +++ b/balloon/hyper/pruning/operation.go @@ -18,7 +18,7 @@ package pruning import ( "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/hyper2/navigation" + "github.com/bbva/qed/balloon/hyper/navigation" "github.com/bbva/qed/hashing" "github.com/bbva/qed/log" "github.com/bbva/qed/storage" diff --git a/balloon/hyper2/pruning/rebuild.go b/balloon/hyper/pruning/rebuild.go similarity index 97% rename from balloon/hyper2/pruning/rebuild.go rename to balloon/hyper/pruning/rebuild.go index ea6de9c12..c337913b4 100644 --- a/balloon/hyper2/pruning/rebuild.go +++ b/balloon/hyper/pruning/rebuild.go @@ -19,7 +19,7 @@ package pruning import ( "bytes" - "github.com/bbva/qed/balloon/hyper2/navigation" + "github.com/bbva/qed/balloon/hyper/navigation" ) func PruneToRebuild(index, serializedBatch []byte, cacheHeightLimit uint16, batches BatchLoader) *OperationsStack { diff --git a/balloon/navigator/position.go b/balloon/hyper/pruning/rebuild_test.go similarity index 80% rename from balloon/navigator/position.go rename to balloon/hyper/pruning/rebuild_test.go index 5e5efdf93..ac398ddd6 100644 --- a/balloon/navigator/position.go +++ b/balloon/hyper/pruning/rebuild_test.go @@ -14,13 +14,10 @@ limitations under the License. */ -package navigator +package pruning + +import "testing" + +func TestPruneToRebuild(t *testing.T) { -type Position interface { - Index() []byte - Height() uint16 - Bytes() []byte - String() string - StringId() string - IndexAsUint64() uint64 } diff --git a/balloon/hyper2/pruning/search.go b/balloon/hyper/pruning/search.go similarity index 98% rename from balloon/hyper2/pruning/search.go rename to balloon/hyper/pruning/search.go index 5b3844d70..3deeab016 100644 --- a/balloon/hyper2/pruning/search.go +++ b/balloon/hyper/pruning/search.go @@ -19,7 +19,7 @@ package pruning import ( "bytes" - "github.com/bbva/qed/balloon/hyper2/navigation" + "github.com/bbva/qed/balloon/hyper/navigation" ) func PruneToFind(index []byte, batches BatchLoader) *OperationsStack { diff --git a/balloon/hyper2/pruning/search_test.go b/balloon/hyper/pruning/search_test.go similarity index 100% rename from balloon/hyper2/pruning/search_test.go rename to balloon/hyper/pruning/search_test.go diff --git a/balloon/hyper2/pruning/stack.go b/balloon/hyper/pruning/stack.go similarity index 100% rename from balloon/hyper2/pruning/stack.go rename to balloon/hyper/pruning/stack.go diff --git a/balloon/hyper2/pruning/test_util.go b/balloon/hyper/pruning/test_util.go similarity index 97% rename from balloon/hyper2/pruning/test_util.go rename to balloon/hyper/pruning/test_util.go index 7d4447b6a..488bfa89d 100644 --- a/balloon/hyper2/pruning/test_util.go +++ b/balloon/hyper/pruning/test_util.go @@ -17,7 +17,7 @@ package pruning import ( - "github.com/bbva/qed/balloon/hyper2/navigation" + "github.com/bbva/qed/balloon/hyper/navigation" ) func pos(index byte, height uint16) navigation.Position { diff --git a/balloon/hyper2/pruning/verify.go b/balloon/hyper/pruning/verify.go similarity index 96% rename from balloon/hyper2/pruning/verify.go rename to balloon/hyper/pruning/verify.go index 46d01fd6e..828e4de07 100644 --- a/balloon/hyper2/pruning/verify.go +++ b/balloon/hyper/pruning/verify.go @@ -19,7 +19,7 @@ package pruning import ( "bytes" - "github.com/bbva/qed/balloon/hyper2/navigation" + "github.com/bbva/qed/balloon/hyper/navigation" ) func PruneToVerify(index, value []byte, auditPathHeight uint16) *OperationsStack { diff --git a/balloon/hyper2/pruning/verify_test.go b/balloon/hyper/pruning/verify_test.go similarity index 100% rename from balloon/hyper2/pruning/verify_test.go rename to balloon/hyper/pruning/verify_test.go diff --git a/balloon/hyper/tree.go b/balloon/hyper/tree.go index 7c3f4f269..e7b5bde6c 100644 --- a/balloon/hyper/tree.go +++ b/balloon/hyper/tree.go @@ -17,45 +17,45 @@ package hyper import ( - "bytes" - "math" "sync" + "github.com/bbva/qed/balloon/hyper/navigation" + "github.com/bbva/qed/log" + "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/balloon/visitor" + "github.com/bbva/qed/balloon/hyper/pruning" "github.com/bbva/qed/hashing" - "github.com/bbva/qed/log" - "github.com/bbva/qed/metrics" "github.com/bbva/qed/storage" "github.com/bbva/qed/util" ) -const ( - CacheSize int64 = (1 << 26) * 68 // 2^26 elements * 68 bytes for entry -) - type HyperTree struct { - store storage.Store - cache cache.ModifiableCache - hasherF func() hashing.Hasher - cacheLevel uint16 - defaultHashes []hashing.Digest - hasher hashing.Hasher + store storage.Store + cache cache.ModifiableCache + hasherF func() hashing.Hasher + + hasher hashing.Hasher + cacheHeightLimit uint16 + defaultHashes []hashing.Digest + batchLoader pruning.BatchLoader sync.RWMutex } func NewHyperTree(hasherF func() hashing.Hasher, store storage.Store, cache cache.ModifiableCache) *HyperTree { + hasher := hasherF() - cacheLevel := hasher.Len() - uint16(math.Max(float64(2), math.Floor(float64(hasher.Len())/10))) + numBits := hasher.Len() + cacheHeightLimit := numBits - min(24, (numBits/8)*4) + tree := &HyperTree{ - store: store, - cache: cache, - hasherF: hasherF, - cacheLevel: cacheLevel, - defaultHashes: make([]hashing.Digest, hasher.Len()), - hasher: hasher, + store: store, + cache: cache, + hasherF: hasherF, + hasher: hasher, + cacheHeightLimit: cacheHeightLimit, + defaultHashes: make([]hashing.Digest, numBits), + batchLoader: pruning.NewDefaultBatchLoader(store, cache, cacheHeightLimit), } tree.defaultHashes[0] = tree.hasher.Do([]byte{0x0}, []byte{0x0}) @@ -73,107 +73,78 @@ func (t *HyperTree) Add(eventDigest hashing.Digest, version uint64) (hashing.Dig t.Lock() defer t.Unlock() - // Activate metrics gathering - stats := metrics.Hyper - - // visitors - computeHash := visitor.NewComputeHashVisitor(t.hasher) - caching := visitor.NewCachingVisitor(computeHash, t.cache) - collect := visitor.NewCollectMutationsVisitor(caching, storage.HyperCachePrefix) - - // build pruning context - versionAsBytes := util.Uint64AsBytes(version) - context := PruningContext{ - navigator: NewHyperTreeNavigator(t.hasher.Len()), - cacheResolver: NewSingleTargetedCacheResolver(t.hasher.Len(), t.cacheLevel, eventDigest), - cache: t.cache, - store: t.store, - defaultHashes: t.defaultHashes, - } + //log.Debugf("Adding new event digest %x with version %d", eventDigest, version) - // traverse from root and generate a visitable pruned tree - pruned, err := NewInsertPruner(eventDigest, versionAsBytes, context).Prune() - if err != nil { - return nil, nil, err + versionAsBytes := util.Uint64AsPaddedBytes(version, len(eventDigest)) + versionAsBytes = versionAsBytes[len(versionAsBytes)-len(eventDigest):] + + // build a stack of operations and then interpret it to generate the root hash + ops := pruning.PruneToInsert(eventDigest, versionAsBytes, t.cacheHeightLimit, t.batchLoader) + ctx := &pruning.Context{ + Hasher: t.hasher, + Cache: t.cache, + DefaultHashes: t.defaultHashes, + Mutations: make([]*storage.Mutation, 0), } - // visit the pruned tree - rootHash := pruned.PostOrder(collect).(hashing.Digest) + rh := ops.Pop().Interpret(ops, ctx) // create a mutation for the new leaf leafMutation := storage.NewMutation(storage.IndexPrefix, eventDigest, versionAsBytes) // collect mutations - mutations := append(collect.Result(), leafMutation) - - // Increment add hits - stats.Add("add_hits", 1) + mutations := append(ctx.Mutations, leafMutation) - return rootHash, mutations, nil + return rh, mutations, nil } func (t *HyperTree) QueryMembership(eventDigest hashing.Digest, version []byte) (proof *QueryProof, err error) { t.Lock() defer t.Unlock() - stats := metrics.Hyper - stats.Add("QueryMembership_hits", 1) - - // visitors - computeHash := visitor.NewComputeHashVisitor(t.hasher) - calcAuditPath := visitor.NewAuditPathVisitor(computeHash) - - // build pruning context - context := PruningContext{ - navigator: NewHyperTreeNavigator(t.hasher.Len()), - cacheResolver: NewSingleTargetedCacheResolver(t.hasher.Len(), t.cacheLevel, eventDigest), - cache: t.cache, - store: t.store, - defaultHashes: t.defaultHashes, - } + //log.Debugf("Proving membership for index %d with version %d", eventDigest, version) - // traverse from root and generate a visitable pruned tree - pruned, err := NewSearchPruner(eventDigest, context).Prune() - if err != nil { - return nil, err + // build a stack of operations and then interpret it to generate the audit path + ops := pruning.PruneToFind(eventDigest, t.batchLoader) + ctx := &pruning.Context{ + Hasher: t.hasher, + Cache: t.cache, + DefaultHashes: t.defaultHashes, + AuditPath: make(navigation.AuditPath, 0), } - // visit the pruned tree - pruned.PostOrder(calcAuditPath) + ops.Pop().Interpret(ops, ctx) - // TODO include version in audit path visitor - return NewQueryProof(eventDigest, version, calcAuditPath.Result(), t.hasherF()), nil + return NewQueryProof(eventDigest, version, ctx.AuditPath, t.hasherF()), nil } -func (t *HyperTree) VerifyMembership(proof *QueryProof, version uint64, eventDigest, expectedDigest hashing.Digest) bool { +func (t *HyperTree) RebuildCache() { t.Lock() defer t.Unlock() - log.Debugf("Verifying membership for eventDigest %x", eventDigest) - stats := metrics.Hyper - stats.Add("VerifyMembership_hits", 1) - // visitors - computeHash := visitor.NewComputeHashVisitor(t.hasher) - - // build pruning context - versionAsBytes := util.Uint64AsBytes(version) - context := PruningContext{ - navigator: NewHyperTreeNavigator(t.hasher.Len()), - cacheResolver: NewSingleTargetedCacheResolver(t.hasher.Len(), t.cacheLevel, eventDigest), - cache: proof.AuditPath(), - store: t.store, - defaultHashes: t.defaultHashes, - } + // warm up cache + log.Info("Warming up hyper cache...") - // traverse from root and generate a visitable pruned tree - pruned, err := NewVerifyPruner(eventDigest, versionAsBytes, context).Prune() + // get all nodes at cache limit height + start := make([]byte, 2+t.hasher.Len()/8) + end := make([]byte, 2+t.hasher.Len()/8) + start[1] = byte(t.cacheHeightLimit) + end[1] = byte(t.cacheHeightLimit + 1) + nodes, err := t.store.GetRange(storage.HyperCachePrefix, start, end) if err != nil { - return false + log.Fatalf("Oops, something went wrong: %v", err) } - // visit the pruned tree - recomputed := pruned.PostOrder(computeHash).(hashing.Digest) - return bytes.Equal(recomputed, expectedDigest) + // insert every node into cache + for _, node := range nodes { + ops := pruning.PruneToRebuild(node.Key[2:], node.Value, t.cacheHeightLimit, t.batchLoader) + ctx := &pruning.Context{ + Hasher: t.hasher, + Cache: t.cache, + DefaultHashes: t.defaultHashes, + } + ops.Pop().Interpret(ops, ctx) + } } func (t *HyperTree) Close() { @@ -181,62 +152,12 @@ func (t *HyperTree) Close() { t.hasher = nil t.defaultHashes = nil t.store = nil + t.batchLoader = nil } -func (t *HyperTree) RebuildCache() error { - t.Lock() - defer t.Unlock() - - // warm up cache - log.Info("Warming up hyper cache...") - - // Fill last cache level with stored data - err := t.cache.Fill(t.store.GetAll(storage.HyperCachePrefix)) - if err != nil { - return err - } - - if t.cache.Size() == 0 { // nothing to recompute - log.Infof("Warming up done, elements cached: %d", t.cache.Size()) - return nil - } - - // Recompute and fill the rest of the cache - navigator := NewHyperTreeNavigator(t.hasher.Len()) - root := navigator.Root() - // skip root - t.populateCache(navigator.GoToLeft(root), navigator) - t.populateCache(navigator.GoToRight(root), navigator) - log.Infof("Warming up done, elements cached: %d", t.cache.Size()) - return nil -} - -func (t *HyperTree) populateCache(pos navigator.Position, nav navigator.TreeNavigator) hashing.Digest { - stats := metrics.Hyper - stats.Add("populateCache_hits", 1) - if pos.Height() == t.cacheLevel { - cached, ok := t.cache.Get(pos) - if !ok { - return nil - } - return cached - } - leftPos := nav.GoToLeft(pos) - rightPos := nav.GoToRight(pos) - left := t.populateCache(leftPos, nav) - right := t.populateCache(rightPos, nav) - - if left == nil && right == nil { - return nil - } - if left == nil { - left = t.defaultHashes[leftPos.Height()] +func min(x, y uint16) uint16 { + if x < y { + return x } - if right == nil { - right = t.defaultHashes[rightPos.Height()] - } - - digest := t.hasher.Salted(pos.Bytes(), left, right) - t.cache.Put(pos, digest) - return digest + return y } diff --git a/balloon/hyper/tree_test.go b/balloon/hyper/tree_test.go index 4c757255c..2f67148c7 100644 --- a/balloon/hyper/tree_test.go +++ b/balloon/hyper/tree_test.go @@ -17,19 +17,19 @@ package hyper import ( + "encoding/binary" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/visitor" + "github.com/bbva/qed/balloon/hyper/navigation" "github.com/bbva/qed/hashing" "github.com/bbva/qed/log" "github.com/bbva/qed/storage" "github.com/bbva/qed/testutils/rand" storage_utils "github.com/bbva/qed/testutils/storage" "github.com/bbva/qed/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestAdd(t *testing.T) { @@ -54,16 +54,15 @@ func TestAdd(t *testing.T) { store, closeF := storage_utils.OpenBPlusTreeStore() defer closeF() - simpleCache := cache.NewSimpleCache(10) - tree := NewHyperTree(hashing.NewFakeXorHasher, store, simpleCache) + + tree := NewHyperTree(hashing.NewFakeXorHasher, store, cache.NewSimpleCache(10)) for i, c := range testCases { - index := uint64(i) - snapshot, mutations, err := tree.Add(c.eventDigest, index) + version := uint64(i) + rootHash, mutations, err := tree.Add(c.eventDigest, version) + require.NoErrorf(t, err, "This should not fail for version %d", i) tree.store.Mutate(mutations) - require.NoErrorf(t, err, "This should not fail for index %d", i) - assert.Equalf(t, c.expectedRootHash, snapshot, "Incorrect root hash for index %d", i) - + assert.Equalf(t, c.expectedRootHash, rootHash, "Incorrect root hash for index %d", i) } } @@ -71,65 +70,63 @@ func TestProveMembership(t *testing.T) { log.SetLogger("TestProveMembership", log.SILENT) - hasher := hashing.NewFakeXorHasher() - digest := hasher.Do(hashing.Digest{0x0}) - testCases := []struct { - addOps map[uint64]hashing.Digest - expectedAuditPath visitor.AuditPath + addedKeys map[uint64]hashing.Digest + expectedAuditPath navigation.AuditPath }{ { - addOps: map[uint64]hashing.Digest{ - uint64(0): {0}, + addedKeys: map[uint64]hashing.Digest{ + uint64(0): {0x0}, }, - expectedAuditPath: visitor.AuditPath{ - "10|4": hashing.Digest{0x0}, - "04|2": hashing.Digest{0x0}, - "80|7": hashing.Digest{0x0}, - "40|6": hashing.Digest{0x0}, - "20|5": hashing.Digest{0x0}, - "08|3": hashing.Digest{0x0}, - "02|1": hashing.Digest{0x0}, - "01|0": hashing.Digest{0x0}, + expectedAuditPath: navigation.AuditPath{ + "0x80|7": hashing.Digest{0x0}, + "0x40|6": hashing.Digest{0x0}, + "0x20|5": hashing.Digest{0x0}, + "0x10|4": hashing.Digest{0x0}, }, }, { - addOps: map[uint64]hashing.Digest{ - uint64(0): {0}, - uint64(1): {1}, - uint64(2): {2}, + addedKeys: map[uint64]hashing.Digest{ + uint64(0): {0x0}, + uint64(1): {0x1}, + uint64(2): {0x2}, }, - expectedAuditPath: visitor.AuditPath{ - "10|4": hashing.Digest{0x0}, - "04|2": hashing.Digest{0x0}, - "80|7": hashing.Digest{0x0}, - "40|6": hashing.Digest{0x0}, - "20|5": hashing.Digest{0x0}, - "08|3": hashing.Digest{0x0}, - "02|1": hashing.Digest{0x2}, - "01|0": hashing.Digest{0x1}, + expectedAuditPath: navigation.AuditPath{ + "0x80|7": hashing.Digest{0x0}, + "0x40|6": hashing.Digest{0x0}, + "0x20|5": hashing.Digest{0x0}, + "0x10|4": hashing.Digest{0x0}, + "0x08|3": hashing.Digest{0x0}, + "0x04|2": hashing.Digest{0x0}, + "0x02|1": hashing.Digest{0x2}, + "0x01|0": hashing.Digest{0x1}, }, }, } + hasher := hashing.NewFakeXorHasher() + searchedDigest := hasher.Do(hashing.Digest{0x0}) + for i, c := range testCases { store, closeF := storage_utils.OpenBPlusTreeStore() defer closeF() simpleCache := cache.NewSimpleCache(10) tree := NewHyperTree(hashing.NewFakeXorHasher, store, simpleCache) - for index, digest := range c.addOps { + for index, digest := range c.addedKeys { _, mutations, err := tree.Add(digest, index) tree.store.Mutate(mutations) require.NoErrorf(t, err, "This should not fail for index %d", i) } - leaf, err := store.Get(storage.IndexPrefix, digest) + + leaf, err := store.Get(storage.IndexPrefix, searchedDigest) require.NoErrorf(t, err, "No leaf with digest %v", err) - pf, err := tree.QueryMembership(leaf.Key, leaf.Value) + proof, err := tree.QueryMembership(leaf.Key, leaf.Value) require.NoErrorf(t, err, "Error adding to the tree: %v for index %d", err, i) - assert.Equalf(t, c.expectedAuditPath, pf.AuditPath(), "Incorrect audit path for index %d", i) + assert.Equalf(t, c.expectedAuditPath, proof.AuditPath, "Incorrect audit path for index %d", i) } + } func TestAddAndVerify(t *testing.T) { @@ -147,6 +144,7 @@ func TestAddAndVerify(t *testing.T) { } for i, c := range testCases { + hasher := c.hasherF() store, closeF := storage_utils.OpenBPlusTreeStore() defer closeF() @@ -154,20 +152,24 @@ func TestAddAndVerify(t *testing.T) { tree := NewHyperTree(c.hasherF, store, simpleCache) key := hasher.Do(hashing.Digest("a test event")) - snapshot, mutations, err := tree.Add(key, value) + valueBytes := util.Uint64AsPaddedBytes(value, int(hasher.Len()/8)) + valueBytes = valueBytes[len(valueBytes)-len(key):] // adjust lenght to hasher len + + rootHash, mutations, err := tree.Add(key, value) + require.NoErrorf(t, err, "Add operation should not fail for index %d", i) tree.store.Mutate(mutations) - require.NoErrorf(t, err, "This should not fail for index %d", i) leaf, err := store.Get(storage.IndexPrefix, key) - require.NoErrorf(t, err, "No leaf with digest %v", err) + require.NoErrorf(t, err, "No leaf with key %d: %v", key, err) proof, err := tree.QueryMembership(leaf.Key, leaf.Value) - require.Nilf(t, err, "Error must be nil for index %d", i) - assert.Equalf(t, util.Uint64AsBytes(value), proof.Value, "Incorrect actual value for index %d", i) + require.Nilf(t, err, "The membership query should not fail for index %d", i) + assert.Equalf(t, valueBytes, proof.Value, "Incorrect actual value for index %d", i) - correct := tree.VerifyMembership(proof, value, key, snapshot) + correct := proof.Verify(key, rootHash) assert.Truef(t, correct, "Key %x should be a member for index %d", key, i) } + } func TestDeterministicAdd(t *testing.T) { @@ -249,6 +251,7 @@ func TestDeterministicAdd(t *testing.T) { // check cache equality require.True(t, cache1.Equal(cache2), "Both caches should be equal") + } func TestRebuildCache(t *testing.T) { @@ -289,15 +292,20 @@ func BenchmarkAdd(b *testing.B) { defer closeF() hasher := hashing.NewSha256Hasher() - fastCache := cache.NewFastCache(CacheSize) - tree := NewHyperTree(hashing.NewSha256Hasher, store, fastCache) + freeCache := cache.NewFreeCache(2000 * (1 << 20)) + + tree := NewHyperTree(hashing.NewSha256Hasher, store, freeCache) b.ResetTimer() b.N = 100000 for i := 0; i < b.N; i++ { - key := hasher.Do(rand.Bytes(32)) - _, mutations, _ := tree.Add(key, uint64(i)) + index := make([]byte, 8) + binary.LittleEndian.PutUint64(index, uint64(i)) + elem := append(rand.Bytes(32), index...) + _, mutations, err := tree.Add(hasher.Do(elem), uint64(i)) + if err != nil { + b.Fatal(err) + } store.Mutate(mutations) } - } diff --git a/balloon/hyper2/proof.go b/balloon/hyper2/proof.go deleted file mode 100644 index 9463b5f44..000000000 --- a/balloon/hyper2/proof.go +++ /dev/null @@ -1,65 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package hyper2 - -import ( - "bytes" - - "github.com/bbva/qed/balloon/hyper2/navigation" - "github.com/bbva/qed/balloon/hyper2/pruning" - "github.com/bbva/qed/hashing" - "github.com/bbva/qed/log" -) - -type QueryProof struct { - AuditPath navigation.AuditPath - Key, Value []byte - hasher hashing.Hasher -} - -func NewQueryProof(key, value []byte, auditPath navigation.AuditPath, hasher hashing.Hasher) *QueryProof { - return &QueryProof{ - Key: key, - Value: value, - AuditPath: auditPath, - hasher: hasher, - } -} - -// Verify verifies a membership query for a provided key from an expected -// root hash that fixes the hyper tree. Returns true if the proof is valid, -// false otherwise. -func (p QueryProof) Verify(key []byte, expectedRootHash hashing.Digest) (valid bool) { - - log.Debugf("Verifying query proof for key %d", p.Key) - - if len(p.AuditPath) == 0 { - // an empty audit path (empty tree) shows non-membersip for any key - return false - } - - // build a stack of operations and then interpret it to recompute the root hash - ops := pruning.PruneToVerify(key, p.Value, p.hasher.Len()-uint16(len(p.AuditPath))) - ctx := &pruning.Context{ - Hasher: p.hasher, - AuditPath: p.AuditPath, - } - recomputed := ops.Pop().Interpret(ops, ctx) - - return bytes.Equal(key, p.Key) && bytes.Equal(recomputed, expectedRootHash) - -} diff --git a/balloon/hyper2/proof_test.go b/balloon/hyper2/proof_test.go deleted file mode 100644 index 4b6c99679..000000000 --- a/balloon/hyper2/proof_test.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package hyper2 - -import ( - "testing" - - "github.com/bbva/qed/hashing" - "github.com/stretchr/testify/assert" - - "github.com/bbva/qed/balloon/hyper2/navigation" -) - -func TestProofVerify(t *testing.T) { - - testCases := []struct { - key, value []byte - auditPath navigation.AuditPath - rootHash hashing.Digest - verifyResult bool - }{ - { - // verify key=0 with empty audit path - key: []byte{0}, - value: []byte{0}, - auditPath: navigation.AuditPath{}, - rootHash: hashing.Digest{0x0}, - verifyResult: false, - }, - { - // verify key=0 with empty audit path - key: []byte{0}, - value: []byte{0}, - auditPath: navigation.AuditPath{ - "0x80|7": hashing.Digest{0x0}, - "0x40|6": hashing.Digest{0x0}, - "0x20|5": hashing.Digest{0x0}, - "0x10|4": hashing.Digest{0x0}, - }, - rootHash: hashing.Digest{0x0}, - verifyResult: true, - }, - { - // verify key=0 with empty audit path - key: []byte{0}, - value: []byte{0}, - auditPath: navigation.AuditPath{ - "0x80|7": hashing.Digest{0x0}, - "0x40|6": hashing.Digest{0x0}, - "0x20|5": hashing.Digest{0x0}, - "0x10|4": hashing.Digest{0x0}, - }, - rootHash: hashing.Digest{0x1}, - verifyResult: false, - }, - } - - for i, c := range testCases { - proof := NewQueryProof(c.key, c.value, c.auditPath, hashing.NewFakeXorHasher()) - correct := proof.Verify(c.key, c.rootHash) - assert.Equalf(t, c.verifyResult, correct, "The verification result should match for test case %d", i) - } - -} diff --git a/balloon/hyper2/tree.go b/balloon/hyper2/tree.go deleted file mode 100644 index 7e59b1e08..000000000 --- a/balloon/hyper2/tree.go +++ /dev/null @@ -1,163 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package hyper2 - -import ( - "sync" - - "github.com/bbva/qed/balloon/hyper2/navigation" - "github.com/bbva/qed/log" - - "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/hyper2/pruning" - "github.com/bbva/qed/hashing" - "github.com/bbva/qed/storage" - "github.com/bbva/qed/util" -) - -type HyperTree struct { - store storage.Store - cache cache.ModifiableCache - hasherF func() hashing.Hasher - - hasher hashing.Hasher - cacheHeightLimit uint16 - defaultHashes []hashing.Digest - batchLoader pruning.BatchLoader - - sync.RWMutex -} - -func NewHyperTree(hasherF func() hashing.Hasher, store storage.Store, cache cache.ModifiableCache) *HyperTree { - - hasher := hasherF() - numBits := hasher.Len() - cacheHeightLimit := numBits - min(24, (numBits/8)*4) - - tree := &HyperTree{ - store: store, - cache: cache, - hasherF: hasherF, - hasher: hasher, - cacheHeightLimit: cacheHeightLimit, - defaultHashes: make([]hashing.Digest, numBits), - batchLoader: pruning.NewDefaultBatchLoader(store, cache, cacheHeightLimit), - } - - tree.defaultHashes[0] = tree.hasher.Do([]byte{0x0}, []byte{0x0}) - for i := uint16(1); i < hasher.Len(); i++ { - tree.defaultHashes[i] = tree.hasher.Do(tree.defaultHashes[i-1], tree.defaultHashes[i-1]) - } - - // warm-up cache - tree.RebuildCache() - - return tree -} - -func (t *HyperTree) Add(eventDigest hashing.Digest, version uint64) (hashing.Digest, []*storage.Mutation, error) { - t.Lock() - defer t.Unlock() - - //log.Debugf("Adding new event digest %x with version %d", eventDigest, version) - - versionAsBytes := util.Uint64AsPaddedBytes(version, len(eventDigest)) - versionAsBytes = versionAsBytes[len(versionAsBytes)-len(eventDigest):] - - // build a stack of operations and then interpret it to generate the root hash - ops := pruning.PruneToInsert(eventDigest, versionAsBytes, t.cacheHeightLimit, t.batchLoader) - ctx := &pruning.Context{ - Hasher: t.hasher, - Cache: t.cache, - DefaultHashes: t.defaultHashes, - Mutations: make([]*storage.Mutation, 0), - } - - rh := ops.Pop().Interpret(ops, ctx) - - // create a mutation for the new leaf - leafMutation := storage.NewMutation(storage.IndexPrefix, eventDigest, versionAsBytes) - - // collect mutations - mutations := append(ctx.Mutations, leafMutation) - - return rh, mutations, nil -} - -func (t *HyperTree) QueryMembership(eventDigest hashing.Digest, version []byte) (proof *QueryProof, err error) { - t.Lock() - defer t.Unlock() - - //log.Debugf("Proving membership for index %d with version %d", eventDigest, version) - - // build a stack of operations and then interpret it to generate the audit path - ops := pruning.PruneToFind(eventDigest, t.batchLoader) - ctx := &pruning.Context{ - Hasher: t.hasher, - Cache: t.cache, - DefaultHashes: t.defaultHashes, - AuditPath: make(navigation.AuditPath, 0), - } - - ops.Pop().Interpret(ops, ctx) - - return NewQueryProof(eventDigest, version, ctx.AuditPath, t.hasherF()), nil -} - -func (t *HyperTree) RebuildCache() { - t.Lock() - defer t.Unlock() - - // warm up cache - log.Info("Warming up hyper cache...") - - // get all nodes at cache limit height - start := make([]byte, 2+t.hasher.Len()/8) - end := make([]byte, 2+t.hasher.Len()/8) - start[1] = byte(t.cacheHeightLimit) - end[1] = byte(t.cacheHeightLimit + 1) - nodes, err := t.store.GetRange(storage.HyperCachePrefix, start, end) - if err != nil { - log.Fatalf("Oops, something went wrong: %v", err) - } - - // insert every node into cache - for _, node := range nodes { - ops := pruning.PruneToRebuild(node.Key[2:], node.Value, t.cacheHeightLimit, t.batchLoader) - ctx := &pruning.Context{ - Hasher: t.hasher, - Cache: t.cache, - DefaultHashes: t.defaultHashes, - } - ops.Pop().Interpret(ops, ctx) - } -} - -func (t *HyperTree) Close() { - t.cache = nil - t.hasher = nil - t.defaultHashes = nil - t.store = nil - t.batchLoader = nil -} - -func min(x, y uint16) uint16 { - if x < y { - return x - } - return y -} diff --git a/balloon/hyper2/tree_test.go b/balloon/hyper2/tree_test.go deleted file mode 100644 index ecfc356fc..000000000 --- a/balloon/hyper2/tree_test.go +++ /dev/null @@ -1,311 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package hyper2 - -import ( - "encoding/binary" - "testing" - - "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/hyper2/navigation" - "github.com/bbva/qed/hashing" - "github.com/bbva/qed/log" - "github.com/bbva/qed/storage" - "github.com/bbva/qed/testutils/rand" - storage_utils "github.com/bbva/qed/testutils/storage" - "github.com/bbva/qed/util" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAdd(t *testing.T) { - - log.SetLogger("TestAdd", log.SILENT) - - testCases := []struct { - eventDigest hashing.Digest - expectedRootHash hashing.Digest - }{ - {hashing.Digest{0x0}, hashing.Digest{0x0}}, - {hashing.Digest{0x1}, hashing.Digest{0x1}}, - {hashing.Digest{0x2}, hashing.Digest{0x3}}, - {hashing.Digest{0x3}, hashing.Digest{0x0}}, - {hashing.Digest{0x4}, hashing.Digest{0x4}}, - {hashing.Digest{0x5}, hashing.Digest{0x1}}, - {hashing.Digest{0x6}, hashing.Digest{0x7}}, - {hashing.Digest{0x7}, hashing.Digest{0x0}}, - {hashing.Digest{0x8}, hashing.Digest{0x8}}, - {hashing.Digest{0x9}, hashing.Digest{0x1}}, - } - - store, closeF := storage_utils.OpenBPlusTreeStore() - defer closeF() - - tree := NewHyperTree(hashing.NewFakeXorHasher, store, cache.NewSimpleCache(10)) - - for i, c := range testCases { - version := uint64(i) - rootHash, mutations, err := tree.Add(c.eventDigest, version) - require.NoErrorf(t, err, "This should not fail for version %d", i) - tree.store.Mutate(mutations) - assert.Equalf(t, c.expectedRootHash, rootHash, "Incorrect root hash for index %d", i) - } -} - -func TestProveMembership(t *testing.T) { - - log.SetLogger("TestProveMembership", log.SILENT) - - testCases := []struct { - addedKeys map[uint64]hashing.Digest - expectedAuditPath navigation.AuditPath - }{ - { - addedKeys: map[uint64]hashing.Digest{ - uint64(0): {0x0}, - }, - expectedAuditPath: navigation.AuditPath{ - "0x80|7": hashing.Digest{0x0}, - "0x40|6": hashing.Digest{0x0}, - "0x20|5": hashing.Digest{0x0}, - "0x10|4": hashing.Digest{0x0}, - }, - }, - { - addedKeys: map[uint64]hashing.Digest{ - uint64(0): {0x0}, - uint64(1): {0x1}, - uint64(2): {0x2}, - }, - expectedAuditPath: navigation.AuditPath{ - "0x80|7": hashing.Digest{0x0}, - "0x40|6": hashing.Digest{0x0}, - "0x20|5": hashing.Digest{0x0}, - "0x10|4": hashing.Digest{0x0}, - "0x08|3": hashing.Digest{0x0}, - "0x04|2": hashing.Digest{0x0}, - "0x02|1": hashing.Digest{0x2}, - "0x01|0": hashing.Digest{0x1}, - }, - }, - } - - hasher := hashing.NewFakeXorHasher() - searchedDigest := hasher.Do(hashing.Digest{0x0}) - - for i, c := range testCases { - store, closeF := storage_utils.OpenBPlusTreeStore() - defer closeF() - simpleCache := cache.NewSimpleCache(10) - tree := NewHyperTree(hashing.NewFakeXorHasher, store, simpleCache) - - for index, digest := range c.addedKeys { - _, mutations, err := tree.Add(digest, index) - tree.store.Mutate(mutations) - require.NoErrorf(t, err, "This should not fail for index %d", i) - } - - leaf, err := store.Get(storage.IndexPrefix, searchedDigest) - require.NoErrorf(t, err, "No leaf with digest %v", err) - - proof, err := tree.QueryMembership(leaf.Key, leaf.Value) - require.NoErrorf(t, err, "Error adding to the tree: %v for index %d", err, i) - assert.Equalf(t, c.expectedAuditPath, proof.AuditPath, "Incorrect audit path for index %d", i) - } - -} - -func TestAddAndVerify(t *testing.T) { - - log.SetLogger("TestAddAndVerify", log.SILENT) - - value := uint64(0) - - testCases := []struct { - hasherF func() hashing.Hasher - }{ - {hasherF: hashing.NewXorHasher}, - {hasherF: hashing.NewSha256Hasher}, - {hasherF: hashing.NewPearsonHasher}, - } - - for i, c := range testCases { - - hasher := c.hasherF() - store, closeF := storage_utils.OpenBPlusTreeStore() - defer closeF() - simpleCache := cache.NewSimpleCache(10) - tree := NewHyperTree(c.hasherF, store, simpleCache) - - key := hasher.Do(hashing.Digest("a test event")) - valueBytes := util.Uint64AsPaddedBytes(value, int(hasher.Len()/8)) - valueBytes = valueBytes[len(valueBytes)-len(key):] // adjust lenght to hasher len - - rootHash, mutations, err := tree.Add(key, value) - require.NoErrorf(t, err, "Add operation should not fail for index %d", i) - tree.store.Mutate(mutations) - - leaf, err := store.Get(storage.IndexPrefix, key) - require.NoErrorf(t, err, "No leaf with key %d: %v", key, err) - - proof, err := tree.QueryMembership(leaf.Key, leaf.Value) - require.Nilf(t, err, "The membership query should not fail for index %d", i) - assert.Equalf(t, valueBytes, proof.Value, "Incorrect actual value for index %d", i) - - correct := proof.Verify(key, rootHash) - assert.Truef(t, correct, "Key %x should be a member for index %d", key, i) - } - -} - -func TestDeterministicAdd(t *testing.T) { - - log.SetLogger("TestDeterministicAdd", log.SILENT) - - hasher := hashing.NewSha256Hasher() - - // create two trees - cache1 := cache.NewSimpleCache(0) - cache2 := cache.NewSimpleCache(0) - store1, closeF1 := storage_utils.OpenBPlusTreeStore() - store2, closeF2 := storage_utils.OpenBPlusTreeStore() - defer closeF1() - defer closeF2() - tree1 := NewHyperTree(hashing.NewSha256Hasher, store1, cache1) - tree2 := NewHyperTree(hashing.NewSha256Hasher, store2, cache2) - - // insert a bunch of events in both trees - for i := 0; i < 100; i++ { - event := rand.Bytes(32) - eventDigest := hasher.Do(event) - version := uint64(i) - _, m1, _ := tree1.Add(eventDigest, version) - store1.Mutate(m1) - _, m2, _ := tree2.Add(eventDigest, version) - store2.Mutate(m2) - } - - // check index store equality - reader11 := store1.GetAll(storage.IndexPrefix) - reader21 := store2.GetAll(storage.IndexPrefix) - defer reader11.Close() - defer reader21.Close() - buff11 := make([]*storage.KVPair, 0) - buff21 := make([]*storage.KVPair, 0) - for { - b := make([]*storage.KVPair, 100) - n, err := reader11.Read(b) - if err != nil || n == 0 { - break - } - buff11 = append(buff11, b...) - } - for { - b := make([]*storage.KVPair, 100) - n, err := reader21.Read(b) - if err != nil || n == 0 { - break - } - buff21 = append(buff21, b...) - } - require.Equalf(t, buff11, buff21, "The stored indexes should be equal") - - // check cache store equality - reader12 := store1.GetAll(storage.HyperCachePrefix) - reader22 := store2.GetAll(storage.HyperCachePrefix) - defer reader12.Close() - defer reader22.Close() - buff12 := make([]*storage.KVPair, 0) - buff22 := make([]*storage.KVPair, 0) - for { - b := make([]*storage.KVPair, 100) - n, err := reader12.Read(b) - if err != nil || n == 0 { - break - } - buff12 = append(buff12, b...) - } - for { - b := make([]*storage.KVPair, 100) - n, err := reader22.Read(b) - if err != nil || n == 0 { - break - } - buff22 = append(buff22, b...) - } - require.Equalf(t, buff12, buff22, "The stored cached digests should be equal") - - // check cache equality - require.True(t, cache1.Equal(cache2), "Both caches should be equal") - -} - -func TestRebuildCache(t *testing.T) { - - log.SetLogger("TestRebuildCache", log.SILENT) - - store, closeF := storage_utils.OpenBPlusTreeStore() - defer closeF() - hasherF := hashing.NewSha256Hasher - hasher := hasherF() - - firstCache := cache.NewSimpleCache(10) - tree := NewHyperTree(hasherF, store, firstCache) - require.True(t, firstCache.Size() == 0, "The cache should be empty") - - // store multiple elements - for i := 0; i < 1000; i++ { - key := hasher.Do(rand.Bytes(32)) - _, mutations, _ := tree.Add(key, uint64(i)) - store.Mutate(mutations) - } - expectedSize := firstCache.Size() - - // Close tree and reopen with a new fresh cache - tree.Close() - secondCache := cache.NewSimpleCache(10) - tree = NewHyperTree(hasherF, store, secondCache) - - require.Equal(t, expectedSize, secondCache.Size(), "The size of the caches should match") - require.True(t, firstCache.Equal(secondCache), "The caches should be equal") -} - -func BenchmarkAdd(b *testing.B) { - - log.SetLogger("BenchmarkAdd", log.SILENT) - - store, closeF := storage_utils.OpenBadgerStore(b, "/var/tmp/hyper_tree_test.db") - defer closeF() - - hasher := hashing.NewSha256Hasher() - freeCache := cache.NewFreeCache(2000 * (1 << 20)) - - tree := NewHyperTree(hashing.NewSha256Hasher, store, freeCache) - - b.ResetTimer() - b.N = 100000 - for i := 0; i < b.N; i++ { - index := make([]byte, 8) - binary.LittleEndian.PutUint64(index, uint64(i)) - elem := append(rand.Bytes(32), index...) - _, mutations, err := tree.Add(hasher.Do(elem), uint64(i)) - if err != nil { - b.Fatal(err) - } - store.Mutate(mutations) - } -} diff --git a/balloon/navigator/navigator.go b/balloon/navigator/navigator.go deleted file mode 100644 index a69203659..000000000 --- a/balloon/navigator/navigator.go +++ /dev/null @@ -1,27 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package navigator - -type TreeNavigator interface { - Root() Position - IsLeaf(Position) bool - IsRoot(Position) bool - GoToLeft(Position) Position - GoToRight(Position) Position - DescendToFirst(Position) Position - DescendToLast(Position) Position -} diff --git a/balloon/navigator/test_util.go b/balloon/navigator/test_util.go deleted file mode 100644 index d77352b45..000000000 --- a/balloon/navigator/test_util.go +++ /dev/null @@ -1,63 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package navigator - -import ( - "encoding/binary" - "fmt" - - "github.com/bbva/qed/util" -) - -type FakePosition struct { - Idx []byte - Hght uint16 -} - -func NewFakePosition(index []byte, height uint16) *FakePosition { - return &FakePosition{ - Idx: index, - Hght: height, - } -} - -func (p FakePosition) Index() []byte { - return p.Idx -} - -func (p FakePosition) Height() uint16 { - return p.Hght -} - -func (p FakePosition) Bytes() []byte { - b := make([]byte, 34) // Size of the index plus 2 bytes for the height - copy(b, p.Idx) - copy(b[len(p.Idx):], util.Uint16AsBytes(p.Hght)) - return b -} - -func (p FakePosition) String() string { - return fmt.Sprintf("Pos(%x, %d)", p.Idx, p.Hght) -} - -func (p FakePosition) StringId() string { - return fmt.Sprintf("%x|%d", p.Idx, p.Hght) -} - -func (p FakePosition) IndexAsUint64() uint64 { - return binary.BigEndian.Uint64(p.Idx) -} diff --git a/balloon/visitor/audit.go b/balloon/visitor/audit.go deleted file mode 100644 index 8e3f1649f..000000000 --- a/balloon/visitor/audit.go +++ /dev/null @@ -1,70 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package visitor - -import ( - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/hashing" -) - -type AuditPath map[string]hashing.Digest - -func (p AuditPath) Get(pos navigator.Position) (hashing.Digest, bool) { - digest, ok := p[pos.StringId()] - return digest, ok -} - -type Verifiable interface { - Verify(key []byte, expectedDigest hashing.Digest) bool - AuditPath() AuditPath -} - -type FakeVerifiable struct { - result bool -} - -func NewFakeVerifiable(result bool) *FakeVerifiable { - return &FakeVerifiable{result} -} - -func (f FakeVerifiable) Verify(key []byte, snapshot hashing.Digest) bool { - return f.result -} - -func (f FakeVerifiable) AuditPath() AuditPath { - return make(AuditPath) -} - -type AuditPathVisitor struct { - auditPath AuditPath - - *ComputeHashVisitor -} - -func NewAuditPathVisitor(decorated *ComputeHashVisitor) *AuditPathVisitor { - return &AuditPathVisitor{ComputeHashVisitor: decorated, auditPath: make(AuditPath)} -} - -func (v AuditPathVisitor) Result() AuditPath { - return v.auditPath -} - -func (v *AuditPathVisitor) VisitCollectable(pos navigator.Position, result interface{}) interface{} { - digest := v.ComputeHashVisitor.VisitCollectable(pos, result).(hashing.Digest) - v.auditPath[pos.StringId()] = digest - return digest -} diff --git a/balloon/visitor/audit_test.go b/balloon/visitor/audit_test.go deleted file mode 100644 index 2fa18810c..000000000 --- a/balloon/visitor/audit_test.go +++ /dev/null @@ -1,101 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package visitor - -import ( - "testing" - - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/hashing" - "github.com/stretchr/testify/require" -) - -func TestAuditPathVisitor(t *testing.T) { - - testCases := []struct { - visitable Visitable - expectedAuditPath AuditPath - }{ - { - visitable: NewLeaf(&navigator.FakePosition{[]byte{0x0}, 0}, []byte{0x1}), - expectedAuditPath: AuditPath{}, - }, - { - visitable: NewRoot( - &navigator.FakePosition{[]byte{0x0}, 1}, - NewCached(&navigator.FakePosition{[]byte{0x0}, 0}, hashing.Digest{0x0}), - NewLeaf(&navigator.FakePosition{[]byte{0x1}, 0}, hashing.Digest{0x1}), - ), - expectedAuditPath: AuditPath{}, - }, - { - visitable: NewRoot( - &navigator.FakePosition{[]byte{0x0}, 2}, - NewCached(&navigator.FakePosition{[]byte{0x0}, 1}, hashing.Digest{0x1}), - NewPartialNode(&navigator.FakePosition{[]byte{0x1}, 1}, - NewLeaf(&navigator.FakePosition{[]byte{0x2}, 0}, hashing.Digest{0x2}), - ), - ), - expectedAuditPath: AuditPath{}, - }, - { - visitable: NewRoot( - &navigator.FakePosition{[]byte{0x0}, 2}, - NewCached(&navigator.FakePosition{[]byte{0x0}, 1}, hashing.Digest{0x1}), - NewNode(&navigator.FakePosition{[]byte{0x1}, 1}, - NewCached(&navigator.FakePosition{[]byte{0x2}, 0}, hashing.Digest{0x2}), - NewLeaf(&navigator.FakePosition{[]byte{0x3}, 0}, hashing.Digest{0x3}), - ), - ), - expectedAuditPath: AuditPath{}, - }, - { - visitable: NewRoot( - &navigator.FakePosition{[]byte{0x0}, 2}, - NewCollectable(NewCached(&navigator.FakePosition{[]byte{0x0}, 1}, hashing.Digest{0x1})), - NewPartialNode(&navigator.FakePosition{[]byte{0x1}, 1}, - NewLeaf(&navigator.FakePosition{[]byte{0x2}, 0}, hashing.Digest{0x2}), - ), - ), - expectedAuditPath: AuditPath{ - "00|1": hashing.Digest{0x1}, - }, - }, - { - visitable: NewRoot( - &navigator.FakePosition{[]byte{0x0}, 2}, - NewCollectable(NewCached(&navigator.FakePosition{[]byte{0x0}, 1}, hashing.Digest{0x1})), - NewNode(&navigator.FakePosition{[]byte{0x1}, 1}, - NewCollectable(NewCached(&navigator.FakePosition{[]byte{0x2}, 0}, hashing.Digest{0x2})), - NewLeaf(&navigator.FakePosition{[]byte{0x3}, 0}, hashing.Digest{0x3}), - ), - ), - expectedAuditPath: AuditPath{ - "00|1": hashing.Digest{0x1}, - "02|0": hashing.Digest{0x2}, - }, - }, - } - - for i, c := range testCases { - visitor := NewAuditPathVisitor(NewComputeHashVisitor(hashing.NewFakeXorHasher())) - c.visitable.PostOrder(visitor) - auditPath := visitor.Result() - require.Equalf(t, c.expectedAuditPath, auditPath, "The audit path %v should be equal to the expected %v in test case %d", auditPath, c.expectedAuditPath, i) - } - -} diff --git a/balloon/visitor/caching.go b/balloon/visitor/caching.go deleted file mode 100644 index 8aa480757..000000000 --- a/balloon/visitor/caching.go +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package visitor - -import ( - "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/hashing" -) - -type CachingVisitor struct { - cache cache.ModifiableCache - - *ComputeHashVisitor -} - -func NewCachingVisitor(decorated *ComputeHashVisitor, cache cache.ModifiableCache) *CachingVisitor { - return &CachingVisitor{ - ComputeHashVisitor: decorated, - cache: cache, - } -} - -func (v *CachingVisitor) VisitCacheable(pos navigator.Position, result interface{}) interface{} { - v.cache.Put(pos.Bytes(), result.(hashing.Digest)) - return result -} diff --git a/balloon/visitor/caching_test.go b/balloon/visitor/caching_test.go deleted file mode 100644 index 5d3c30a52..000000000 --- a/balloon/visitor/caching_test.go +++ /dev/null @@ -1,157 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package visitor - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/hashing" -) - -func TestCachingVisitor(t *testing.T) { - - testCases := []struct { - visitable Visitable - expectedElements []CachedElement - }{ - { - visitable: NewCollectable( - NewCacheable( - NewLeaf( - &navigator.FakePosition{[]byte{0x0}, 0}, - []byte{0x0}, - ), - )), - expectedElements: []CachedElement{ - *NewCachedElement( - &navigator.FakePosition{[]byte{0x0}, 0}, - hashing.Digest{0x0}, - ), - }, - }, - { - visitable: NewCollectable( - NewCacheable( - NewRoot(&navigator.FakePosition{[]byte{0x0}, 1}, - NewCached(&navigator.FakePosition{[]byte{0x0}, 0}, hashing.Digest{0x0}), - NewCollectable( - NewCacheable( - NewLeaf(&navigator.FakePosition{[]byte{0x1}, 0}, hashing.Digest{0x1}))), - ))), - expectedElements: []CachedElement{ - *NewCachedElement( - &navigator.FakePosition{[]byte{0x1}, 0}, - hashing.Digest{0x1}, - ), - *NewCachedElement( - &navigator.FakePosition{[]byte{0x0}, 1}, - hashing.Digest{0x1}, - ), - }, - }, - { - visitable: NewRoot( - &navigator.FakePosition{[]byte{0x0}, 2}, - NewCached(&navigator.FakePosition{[]byte{0x0}, 1}, hashing.Digest{0x1}), - NewPartialNode(&navigator.FakePosition{[]byte{0x1}, 1}, - NewCollectable( - NewCacheable( - NewLeaf(&navigator.FakePosition{[]byte{0x2}, 0}, hashing.Digest{0x2}), - )), - ), - ), - expectedElements: []CachedElement{ - *NewCachedElement( - &navigator.FakePosition{[]byte{0x2}, 0}, - hashing.Digest{0x2}, - ), - }, - }, - { - visitable: NewCollectable( - NewCacheable( - NewRoot( - &navigator.FakePosition{[]byte{0x0}, 2}, - NewCached(&navigator.FakePosition{[]byte{0x0}, 1}, hashing.Digest{0x1}), - NewCollectable( - NewCacheable( - NewNode(&navigator.FakePosition{[]byte{0x2}, 1}, - NewCached(&navigator.FakePosition{[]byte{0x2}, 0}, hashing.Digest{0x2}), - NewCollectable( - NewCacheable( - NewLeaf(&navigator.FakePosition{[]byte{0x3}, 0}, hashing.Digest{0x3}), - ))))), - ))), - expectedElements: []CachedElement{ - *NewCachedElement( - &navigator.FakePosition{[]byte{0x3}, 0}, - hashing.Digest{0x3}, - ), - *NewCachedElement( - &navigator.FakePosition{[]byte{0x2}, 1}, - hashing.Digest{0x1}, - ), - *NewCachedElement( - &navigator.FakePosition{[]byte{0x0}, 2}, - hashing.Digest{0x0}, - ), - }, - }, - { - visitable: NewRoot( - &navigator.FakePosition{[]byte{0x0}, 3}, - NewCached(&navigator.FakePosition{[]byte{0x0}, 2}, hashing.Digest{0x0}), - NewPartialNode(&navigator.FakePosition{[]byte{0x4}, 2}, - NewPartialNode(&navigator.FakePosition{[]byte{0x4}, 1}, - NewCollectable( - NewCacheable( - NewLeaf(&navigator.FakePosition{[]byte{0x4}, 0}, hashing.Digest{0x4})))), - ), - ), - expectedElements: []CachedElement{ - *NewCachedElement( - &navigator.FakePosition{[]byte{0x4}, 0}, - hashing.Digest{0x4}, - ), - }, - }, - } - - for i, c := range testCases { - cache := cache.NewSimpleCache(0) - visitor := NewCachingVisitor(NewComputeHashVisitor(hashing.NewFakeXorHasher()), cache) - c.visitable.PostOrder(visitor) - for _, e := range c.expectedElements { - v, _ := cache.Get(e.Pos) - require.Equalf(t, e.Digest, v, "The cached element %v should be cached in test case %d", e, i) - } - } - -} - -type CachedElement struct { - Pos navigator.Position - Digest hashing.Digest -} - -func NewCachedElement(pos navigator.Position, digest hashing.Digest) *CachedElement { - return &CachedElement{pos, digest} -} diff --git a/balloon/visitor/collect.go b/balloon/visitor/collect.go deleted file mode 100644 index a556a57ee..000000000 --- a/balloon/visitor/collect.go +++ /dev/null @@ -1,31 +0,0 @@ -package visitor - -import ( - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/hashing" - "github.com/bbva/qed/storage" -) - -type CollectMutationsVisitor struct { - storagePrefix byte - mutations []*storage.Mutation - - PostOrderVisitor -} - -func NewCollectMutationsVisitor(decorated PostOrderVisitor, storagePrefix byte) *CollectMutationsVisitor { - return &CollectMutationsVisitor{ - PostOrderVisitor: decorated, - storagePrefix: storagePrefix, - mutations: make([]*storage.Mutation, 0), - } -} - -func (v CollectMutationsVisitor) Result() []*storage.Mutation { - return v.mutations -} - -func (v *CollectMutationsVisitor) VisitCollectable(pos navigator.Position, result interface{}) interface{} { - v.mutations = append(v.mutations, storage.NewMutation(v.storagePrefix, pos.Bytes(), result.(hashing.Digest))) - return result -} diff --git a/balloon/visitor/collect_test.go b/balloon/visitor/collect_test.go deleted file mode 100644 index 28c615f1f..000000000 --- a/balloon/visitor/collect_test.go +++ /dev/null @@ -1,73 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package visitor - -import ( - "testing" - - assert "github.com/stretchr/testify/require" - - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/hashing" - "github.com/bbva/qed/storage" -) - -var fakePos = navigator.NewFakePosition - -func TestCollect(t *testing.T) { - - testCases := []struct { - visitable Visitable - expectedMutations []*storage.Mutation - }{ - { - visitable: NewRoot(fakePos([]byte{0}, 8), - NewNode(fakePos([]byte{0}, 9), - NewCollectable(NewCached(fakePos([]byte{0}, 7), hashing.Digest{0})), - NewNode(fakePos([]byte{0}, 9), - NewCollectable(NewCached(fakePos([]byte{0}, 6), hashing.Digest{1})), - NewLeaf(fakePos([]byte{0}, 1), hashing.Digest{0}), - ), - ), - NewNode(fakePos([]byte{0}, 9), - NewLeaf(fakePos([]byte{0}, 1), hashing.Digest{0}), - NewCollectable(NewCached(fakePos([]byte{0}, 8), hashing.Digest{2})), - ), - ), - expectedMutations: []*storage.Mutation{ - {storage.HyperCachePrefix, fakePos([]byte{0}, 7).Bytes(), hashing.Digest{0}}, - {storage.HyperCachePrefix, fakePos([]byte{0}, 6).Bytes(), hashing.Digest{1}}, - {storage.HyperCachePrefix, fakePos([]byte{0}, 8).Bytes(), hashing.Digest{2}}, - }, - }, - } - - for i, c := range testCases { - decorated := NewComputeHashVisitor(hashing.NewFakeXorHasher()) - visitor := NewCollectMutationsVisitor(decorated, storage.HyperCachePrefix) - c.visitable.PostOrder(visitor) - - mutations := visitor.Result() - assert.ElementsMatchf( - t, - mutations, - c.expectedMutations, - "Mutation error in test case %d", - i, - ) - } -} diff --git a/balloon/visitor/compute.go b/balloon/visitor/compute.go deleted file mode 100644 index ef6ee3e48..000000000 --- a/balloon/visitor/compute.go +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package visitor - -import ( - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/hashing" -) - -type ComputeHashVisitor struct { - hasher hashing.Hasher -} - -func NewComputeHashVisitor(hasher hashing.Hasher) *ComputeHashVisitor { - return &ComputeHashVisitor{hasher} -} - -func (v *ComputeHashVisitor) VisitRoot(pos navigator.Position, leftResult, rightResult interface{}) interface{} { - return v.hasher.Salted(pos.Bytes(), leftResult.(hashing.Digest), rightResult.(hashing.Digest)) -} - -func (v *ComputeHashVisitor) VisitNode(pos navigator.Position, leftResult, rightResult interface{}) interface{} { - return v.hasher.Salted(pos.Bytes(), leftResult.(hashing.Digest), rightResult.(hashing.Digest)) -} - -func (v *ComputeHashVisitor) VisitPartialNode(pos navigator.Position, leftResult interface{}) interface{} { - return v.hasher.Salted(pos.Bytes(), leftResult.(hashing.Digest)) -} - -func (v *ComputeHashVisitor) VisitLeaf(pos navigator.Position, value []byte) interface{} { - return v.hasher.Salted(pos.Bytes(), value) -} - -func (v *ComputeHashVisitor) VisitCached(pos navigator.Position, cachedDigest hashing.Digest) interface{} { - return cachedDigest -} - -func (v *ComputeHashVisitor) VisitCollectable(pos navigator.Position, result interface{}) interface{} { - return result -} - -func (v *ComputeHashVisitor) VisitCacheable(pos navigator.Position, result interface{}) interface{} { - return result -} diff --git a/balloon/visitor/compute_test.go b/balloon/visitor/compute_test.go deleted file mode 100644 index 7cc0e7d5e..000000000 --- a/balloon/visitor/compute_test.go +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package visitor - -import ( - "testing" - - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/hashing" - "github.com/stretchr/testify/require" -) - -func TestComputeHashVisitor(t *testing.T) { - - testCases := []struct { - visitable Visitable - expectedDigest hashing.Digest - }{ - { - visitable: NewLeaf(&navigator.FakePosition{[]byte{0x0}, 0}, []byte{0x1}), - expectedDigest: hashing.Digest{0x1}, - }, - { - visitable: NewRoot( - &navigator.FakePosition{[]byte{0x0}, 1}, - NewCached(&navigator.FakePosition{[]byte{0x0}, 0}, hashing.Digest{0x0}), - NewLeaf(&navigator.FakePosition{[]byte{0x1}, 0}, hashing.Digest{0x1}), - ), - expectedDigest: hashing.Digest{0x1}, - }, - { - visitable: NewRoot( - &navigator.FakePosition{[]byte{0x0}, 2}, - NewCached(&navigator.FakePosition{[]byte{0x0}, 1}, hashing.Digest{0x1}), - NewPartialNode(&navigator.FakePosition{[]byte{0x1}, 1}, - NewLeaf(&navigator.FakePosition{[]byte{0x2}, 0}, hashing.Digest{0x2}), - ), - ), - expectedDigest: hashing.Digest{0x3}, - }, - { - visitable: NewRoot( - &navigator.FakePosition{[]byte{0x0}, 2}, - NewCached(&navigator.FakePosition{[]byte{0x0}, 1}, hashing.Digest{0x1}), - NewNode(&navigator.FakePosition{[]byte{0x1}, 1}, - NewCached(&navigator.FakePosition{[]byte{0x2}, 0}, hashing.Digest{0x2}), - NewLeaf(&navigator.FakePosition{[]byte{0x3}, 0}, hashing.Digest{0x3}), - ), - ), - expectedDigest: hashing.Digest{0x0}, - }, - { - visitable: NewRoot( - &navigator.FakePosition{[]byte{0x0}, 2}, - NewCached(&navigator.FakePosition{[]byte{0x0}, 1}, hashing.Digest{0x1}), - NewNode(&navigator.FakePosition{[]byte{0x1}, 1}, - NewCached(&navigator.FakePosition{[]byte{0x2}, 0}, hashing.Digest{0x2}), - NewCollectable(NewLeaf(&navigator.FakePosition{[]byte{0x3}, 0}, hashing.Digest{0x3})), - ), - ), - expectedDigest: hashing.Digest{0x0}, - }, - } - - visitor := NewComputeHashVisitor(hashing.NewFakeXorHasher()) - - for i, c := range testCases { - digest := c.visitable.PostOrder(visitor) - require.Equalf(t, c.expectedDigest, digest, "The computed digest %x should be equal to the expected %x in test case %d", digest, c.expectedDigest, i) - } -} diff --git a/balloon/visitor/print.go b/balloon/visitor/print.go deleted file mode 100644 index 698f9dc2b..000000000 --- a/balloon/visitor/print.go +++ /dev/null @@ -1,74 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package visitor - -import ( - "fmt" - "strings" - - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/hashing" -) - -type PrintVisitor struct { - tokens []string - height uint16 -} - -func NewPrintVisitor(height uint16) *PrintVisitor { - return &PrintVisitor{tokens: make([]string, 1), height: height} -} - -func (v PrintVisitor) Result() string { - return fmt.Sprintf("\n%s", strings.Join(v.tokens[:], "\n")) -} - -func (v *PrintVisitor) VisitRoot(pos navigator.Position) { - v.tokens = append(v.tokens, fmt.Sprintf("Root(%v)", pos)) -} - -func (v *PrintVisitor) VisitNode(pos navigator.Position) { - v.tokens = append(v.tokens, fmt.Sprintf("%sNode(%v)", v.indent(pos.Height()), pos)) -} - -func (v *PrintVisitor) VisitPartialNode(pos navigator.Position) { - v.tokens = append(v.tokens, fmt.Sprintf("%sPartialNode(%v)", v.indent(pos.Height()), pos)) -} - -func (v *PrintVisitor) VisitLeaf(pos navigator.Position, value []byte) { - v.tokens = append(v.tokens, fmt.Sprintf("%sLeaf(%v)[%x]", v.indent(pos.Height()), pos, value)) -} - -func (v *PrintVisitor) VisitCached(pos navigator.Position, cachedDigest hashing.Digest) { - v.tokens = append(v.tokens, fmt.Sprintf("%sCached(%v)[%x]", v.indent(pos.Height()), pos, cachedDigest)) -} - -func (v *PrintVisitor) VisitCollectable(pos navigator.Position) { - v.tokens = append(v.tokens, fmt.Sprintf("%sCollectable(%v)", v.indent(pos.Height()), pos)) -} - -func (v *PrintVisitor) VisitCacheable(pos navigator.Position) { - v.tokens = append(v.tokens, fmt.Sprintf("%sCacheable(%v)", v.indent(pos.Height()), pos)) -} - -func (v PrintVisitor) indent(height uint16) string { - indents := make([]string, 0) - for i := height; i < v.height; i++ { - indents = append(indents, "\t") - } - return strings.Join(indents, "") -} diff --git a/balloon/visitor/visitor.go b/balloon/visitor/visitor.go deleted file mode 100644 index 00e493200..000000000 --- a/balloon/visitor/visitor.go +++ /dev/null @@ -1,230 +0,0 @@ -/* - Copyright 2018 Banco Bilbao Vizcaya Argentaria, S.A. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package visitor - -import ( - "fmt" - - "github.com/bbva/qed/balloon/navigator" - "github.com/bbva/qed/hashing" -) - -type PostOrderVisitor interface { - VisitRoot(pos navigator.Position, leftResult, rightResult interface{}) interface{} - VisitNode(pos navigator.Position, leftResult, rightResult interface{}) interface{} - VisitPartialNode(pos navigator.Position, leftResult interface{}) interface{} - VisitLeaf(pos navigator.Position, value []byte) interface{} - VisitCached(pos navigator.Position, cachedDigest hashing.Digest) interface{} - VisitCollectable(pos navigator.Position, result interface{}) interface{} - VisitCacheable(pos navigator.Position, result interface{}) interface{} -} - -type PreOrderVisitor interface { - VisitRoot(pos navigator.Position) - VisitNode(pos navigator.Position) - VisitPartialNode(pos navigator.Position) - VisitLeaf(pos navigator.Position, value []byte) - VisitCached(pos navigator.Position, cachedDigest hashing.Digest) - VisitCollectable(pos navigator.Position) - VisitCacheable(pos navigator.Position) -} - -type Visitable interface { - PostOrder(visitor PostOrderVisitor) interface{} - PreOrder(visitor PreOrderVisitor) - String() string - Position() navigator.Position -} - -type Root struct { - pos navigator.Position - left, right Visitable -} - -type Node struct { - pos navigator.Position - left, right Visitable -} - -type PartialNode struct { - pos navigator.Position - left Visitable -} - -type Leaf struct { - pos navigator.Position - value []byte -} - -type Cached struct { - pos navigator.Position - digest hashing.Digest -} - -type Cacheable struct { - Visitable -} - -type Collectable struct { - Visitable -} - -func NewRoot(pos navigator.Position, left, right Visitable) *Root { - return &Root{pos, left, right} -} - -func (r Root) PostOrder(visitor PostOrderVisitor) interface{} { - leftResult := r.left.PostOrder(visitor) - rightResult := r.right.PostOrder(visitor) - return visitor.VisitRoot(r.pos, leftResult, rightResult) -} - -func (r Root) PreOrder(visitor PreOrderVisitor) { - visitor.VisitRoot(r.pos) - r.left.PreOrder(visitor) - r.right.PreOrder(visitor) -} - -func (r Root) Position() navigator.Position { - return r.pos -} - -func (r Root) String() string { - return fmt.Sprintf("Root(%v)[ %v | %v ]", r.pos, r.left, r.right) -} - -func NewNode(pos navigator.Position, left, right Visitable) *Node { - return &Node{pos, left, right} -} - -func (n Node) PostOrder(visitor PostOrderVisitor) interface{} { - leftResult := n.left.PostOrder(visitor) - rightResult := n.right.PostOrder(visitor) - return visitor.VisitNode(n.pos, leftResult, rightResult) -} - -func (n Node) PreOrder(visitor PreOrderVisitor) { - visitor.VisitNode(n.pos) - n.left.PreOrder(visitor) - n.right.PreOrder(visitor) -} - -func (n Node) Position() navigator.Position { - return n.pos -} - -func (n Node) String() string { - return fmt.Sprintf("Node(%v)[ %v | %v ]", n.pos, n.left, n.right) -} - -func NewPartialNode(pos navigator.Position, left Visitable) *PartialNode { - return &PartialNode{pos, left} -} - -func (p PartialNode) PostOrder(visitor PostOrderVisitor) interface{} { - leftResult := p.left.PostOrder(visitor) - return visitor.VisitPartialNode(p.pos, leftResult) -} - -func (p PartialNode) PreOrder(visitor PreOrderVisitor) { - visitor.VisitPartialNode(p.pos) - p.left.PreOrder(visitor) -} - -func (p PartialNode) Position() navigator.Position { - return p.pos -} - -func (p PartialNode) String() string { - return fmt.Sprintf("PartialNode(%v)[ %v ]", p.pos, p.left) -} - -func NewLeaf(pos navigator.Position, value []byte) *Leaf { - return &Leaf{pos, value} -} - -func (l Leaf) PostOrder(visitor PostOrderVisitor) interface{} { - return visitor.VisitLeaf(l.pos, l.value) -} - -func (l Leaf) PreOrder(visitor PreOrderVisitor) { - visitor.VisitLeaf(l.pos, l.value) -} - -func (l Leaf) Position() navigator.Position { - return l.pos -} - -func (l Leaf) String() string { - return fmt.Sprintf("Leaf(%v)[ %x ]", l.pos, l.value) -} - -func NewCached(pos navigator.Position, digest hashing.Digest) *Cached { - return &Cached{pos, digest} -} - -func (c Cached) PostOrder(visitor PostOrderVisitor) interface{} { - return visitor.VisitCached(c.pos, c.digest) -} - -func (c Cached) PreOrder(visitor PreOrderVisitor) { - visitor.VisitCached(c.pos, c.digest) -} - -func (c Cached) Position() navigator.Position { - return c.pos -} - -func (c Cached) String() string { - return fmt.Sprintf("Cached(%v)[ %x ]", c.pos, c.digest) -} - -func NewCacheable(underlying Visitable) *Cacheable { - return &Cacheable{Visitable: underlying} -} - -func (c Cacheable) PostOrder(visitor PostOrderVisitor) interface{} { - result := c.Visitable.PostOrder(visitor) - return visitor.VisitCacheable(c.Position(), result) -} - -func (c Cacheable) PreOrder(visitor PreOrderVisitor) { - visitor.VisitCacheable(c.Position()) - c.Visitable.PreOrder(visitor) -} - -func (c Cacheable) String() string { - return fmt.Sprintf("Cacheable[ %v ]", c.Visitable) -} - -func NewCollectable(underlying Visitable) *Collectable { - return &Collectable{Visitable: underlying} -} - -func (c Collectable) PostOrder(visitor PostOrderVisitor) interface{} { - result := c.Visitable.PostOrder(visitor) - return visitor.VisitCollectable(c.Position(), result) -} - -func (c Collectable) PreOrder(visitor PreOrderVisitor) { - visitor.VisitCollectable(c.Position()) - c.Visitable.PreOrder(visitor) -} - -func (c Collectable) String() string { - return fmt.Sprintf("Collectable[ %v ]", c.Visitable) -}