diff --git a/api/apihttp/apihttp_test.go b/api/apihttp/apihttp_test.go index aa4df0c35..e0dc7f7f7 100644 --- a/api/apihttp/apihttp_test.go +++ b/api/apihttp/apihttp_test.go @@ -28,7 +28,6 @@ import ( "github.com/bbva/qed/balloon" "github.com/bbva/qed/balloon/history" - "github.com/bbva/qed/balloon/history/navigation" "github.com/bbva/qed/balloon/hyper" "github.com/bbva/qed/hashing" "github.com/bbva/qed/protocol" @@ -57,7 +56,7 @@ func (b fakeRaftBalloon) QueryDigestMembership(keyDigest hashing.Digest, version return &balloon.MembershipProof{ Exists: true, HyperProof: hyper.NewQueryProof([]byte{0x0}, []byte{0x0}, hyper.AuditPath{}, nil), - HistoryProof: history.NewMembershipProof(0, 0, navigation.AuditPath{}, nil), + HistoryProof: history.NewMembershipProof(0, 0, history.AuditPath{}, nil), CurrentVersion: 1, QueryVersion: 1, ActualVersion: 2, @@ -71,7 +70,7 @@ func (b fakeRaftBalloon) QueryMembership(event []byte, version uint64) (*balloon return &balloon.MembershipProof{ Exists: true, HyperProof: hyper.NewQueryProof([]byte{0x0}, []byte{0x0}, hyper.AuditPath{}, nil), - HistoryProof: history.NewMembershipProof(0, 0, navigation.AuditPath{}, nil), + HistoryProof: history.NewMembershipProof(0, 0, history.AuditPath{}, nil), CurrentVersion: 1, QueryVersion: 1, ActualVersion: 2, @@ -85,7 +84,7 @@ func (b fakeRaftBalloon) QueryConsistency(start, end uint64) (*balloon.Increment ip := balloon.IncrementalProof{ Start: 2, End: 8, - AuditPath: navigation.AuditPath{pathKey: hashing.Digest{0x00}}, + AuditPath: history.AuditPath{pathKey: hashing.Digest{0x00}}, Hasher: hashing.NewFakeXorHasher(), } return &ip, nil diff --git a/balloon/balloon.go b/balloon/balloon.go index d55fdc534..232c16f59 100644 --- a/balloon/balloon.go +++ b/balloon/balloon.go @@ -22,7 +22,6 @@ import ( "github.com/bbva/qed/balloon/cache" "github.com/bbva/qed/balloon/history" - historynav "github.com/bbva/qed/balloon/history/navigation" "github.com/bbva/qed/balloon/hyper" "github.com/bbva/qed/hashing" "github.com/bbva/qed/metrics" @@ -146,13 +145,13 @@ func (p MembershipProof) Verify(event []byte, snapshot *Snapshot) bool { type IncrementalProof struct { Start, End uint64 - AuditPath historynav.AuditPath + AuditPath history.AuditPath Hasher hashing.Hasher } func NewIncrementalProof( start, end uint64, - auditPath historynav.AuditPath, + auditPath history.AuditPath, hasher hashing.Hasher, ) *IncrementalProof { return &IncrementalProof{ diff --git a/balloon/history/audit_visitor.go b/balloon/history/audit_visitor.go new file mode 100644 index 000000000..a9a756be5 --- /dev/null +++ b/balloon/history/audit_visitor.go @@ -0,0 +1,80 @@ +/* + 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 history + +import ( + "fmt" + + "github.com/bbva/qed/balloon/cache" + "github.com/bbva/qed/hashing" +) + +type auditPathVisitor struct { + hasher hashing.Hasher + cache cache.Cache + + auditPath AuditPath +} + +func newAuditPathVisitor(hasher hashing.Hasher, cache cache.Cache) *auditPathVisitor { + return &auditPathVisitor{ + hasher: hasher, + cache: cache, + auditPath: make(AuditPath), + } +} + +func (v auditPathVisitor) Result() AuditPath { + return v.auditPath +} + +func (v *auditPathVisitor) VisitLeafHashOp(op leafHashOp) hashing.Digest { + return v.hasher.Salted(op.Position().Bytes(), op.Value) +} + +func (v *auditPathVisitor) VisitInnerHashOp(op innerHashOp) hashing.Digest { + leftHash := op.Left.Accept(v) + rightHash := op.Right.Accept(v) + return v.hasher.Salted(op.Position().Bytes(), leftHash, rightHash) +} + +func (v *auditPathVisitor) VisitPartialInnerHashOp(op partialInnerHashOp) hashing.Digest { + leftHash := op.Left.Accept(v) + return v.hasher.Salted(op.Position().Bytes(), leftHash) +} + +func (v *auditPathVisitor) VisitGetCacheOp(op getCacheOp) hashing.Digest { + hash, ok := v.cache.Get(op.Position().Bytes()) + if !ok { + panic(fmt.Sprintf("Oops, something went wrong. There should be a cached element at position %v", op.Position())) + } + return hash +} + +func (v *auditPathVisitor) VisitPutCacheOp(op putCacheOp) hashing.Digest { + return op.operation.Accept(v) +} + +func (v *auditPathVisitor) VisitMutateOp(op mutateOp) hashing.Digest { + return op.operation.Accept(v) +} + +func (v *auditPathVisitor) VisitCollectOp(op collectOp) hashing.Digest { + hash := op.operation.Accept(v) + v.auditPath[op.Position().FixedBytes()] = hash + return hash +} diff --git a/balloon/history/pruning/audit_visitor_test.go b/balloon/history/audit_visitor_test.go similarity index 63% rename from balloon/history/pruning/audit_visitor_test.go rename to balloon/history/audit_visitor_test.go index 902d88070..66168f8d2 100644 --- a/balloon/history/pruning/audit_visitor_test.go +++ b/balloon/history/audit_visitor_test.go @@ -1,10 +1,25 @@ -package pruning +/* + 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 history import ( "testing" "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/history/navigation" "github.com/bbva/qed/hashing" "github.com/stretchr/testify/require" ) @@ -12,19 +27,19 @@ import ( func TestAuditPathVisitor(t *testing.T) { testCases := []struct { - op Operation - expectedAuditPath navigation.AuditPath + op operation + expectedAuditPath AuditPath }{ { op: leafnil(pos(0, 0)), - expectedAuditPath: navigation.AuditPath{}, + expectedAuditPath: AuditPath{}, }, { op: inner(pos(0, 1), collect(getCache(pos(0, 0))), leafnil(pos(1, 0)), ), - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 0).FixedBytes(): hashing.Digest{0x0}, }, }, @@ -35,7 +50,7 @@ func TestAuditPathVisitor(t *testing.T) { leafnil(pos(2, 0)), ), ), - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 1).FixedBytes(): hashing.Digest{0x0}, }, }, @@ -47,7 +62,7 @@ func TestAuditPathVisitor(t *testing.T) { leafnil(pos(3, 0)), ), ), - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 1).FixedBytes(): hashing.Digest{0x0}, pos(2, 0).FixedBytes(): hashing.Digest{0x0}, }, @@ -61,7 +76,7 @@ func TestAuditPathVisitor(t *testing.T) { ), ), ), - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 2).FixedBytes(): hashing.Digest{0x0}, }, }, @@ -75,7 +90,7 @@ func TestAuditPathVisitor(t *testing.T) { ), ), ), - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 2).FixedBytes(): hashing.Digest{0x0}, pos(4, 0).FixedBytes(): hashing.Digest{0x0}, }, @@ -83,7 +98,7 @@ func TestAuditPathVisitor(t *testing.T) { } for i, c := range testCases { - visitor := NewAuditPathVisitor(hashing.NewFakeXorHasher(), cache.NewFakeCache([]byte{0x0})) + visitor := newAuditPathVisitor(hashing.NewFakeXorHasher(), cache.NewFakeCache([]byte{0x0})) c.op.Accept(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/history/compute_visitor.go b/balloon/history/compute_visitor.go new file mode 100644 index 000000000..81c3b8364 --- /dev/null +++ b/balloon/history/compute_visitor.go @@ -0,0 +1,71 @@ +/* + 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 history + +import ( + "fmt" + + "github.com/bbva/qed/balloon/cache" + "github.com/bbva/qed/hashing" +) + +type computeHashVisitor struct { + hasher hashing.Hasher + cache cache.Cache +} + +func newComputeHashVisitor(hasher hashing.Hasher, cache cache.Cache) *computeHashVisitor { + return &computeHashVisitor{ + hasher: hasher, + cache: cache, + } +} + +func (v *computeHashVisitor) VisitLeafHashOp(op leafHashOp) hashing.Digest { + return v.hasher.Salted(op.Position().Bytes(), op.Value) +} + +func (v *computeHashVisitor) VisitInnerHashOp(op innerHashOp) hashing.Digest { + leftHash := op.Left.Accept(v) + rightHash := op.Right.Accept(v) + return v.hasher.Salted(op.Position().Bytes(), leftHash, rightHash) +} + +func (v *computeHashVisitor) VisitPartialInnerHashOp(op partialInnerHashOp) hashing.Digest { + leftHash := op.Left.Accept(v) + return v.hasher.Salted(op.Position().Bytes(), leftHash) +} + +func (v *computeHashVisitor) VisitGetCacheOp(op getCacheOp) hashing.Digest { + hash, ok := v.cache.Get(op.Position().Bytes()) + if !ok { // TODO maybe we should return an error + panic(fmt.Sprintf("Oops, something went wrong. There should be a cached element at position %v", op.Position())) + } + return hash +} + +func (v *computeHashVisitor) VisitPutCacheOp(op putCacheOp) hashing.Digest { + return op.operation.Accept(v) +} + +func (v *computeHashVisitor) VisitMutateOp(op mutateOp) hashing.Digest { + return op.operation.Accept(v) +} + +func (v *computeHashVisitor) VisitCollectOp(op collectOp) hashing.Digest { + return op.operation.Accept(v) +} diff --git a/balloon/history/pruning/compute_visitor_test.go b/balloon/history/compute_visitor_test.go similarity index 63% rename from balloon/history/pruning/compute_visitor_test.go rename to balloon/history/compute_visitor_test.go index 73de1c311..ed01b89d9 100644 --- a/balloon/history/pruning/compute_visitor_test.go +++ b/balloon/history/compute_visitor_test.go @@ -1,4 +1,20 @@ -package pruning +/* + 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 history import ( "testing" @@ -11,7 +27,7 @@ import ( func TestComputeHashVisitor(t *testing.T) { testCases := []struct { - op Operation + op operation expectedDigest hashing.Digest }{ { @@ -56,7 +72,7 @@ func TestComputeHashVisitor(t *testing.T) { }, } - visitor := NewComputeHashVisitor(hashing.NewFakeXorHasher(), cache.NewFakeCache([]byte{0x0})) + visitor := newComputeHashVisitor(hashing.NewFakeXorHasher(), cache.NewFakeCache([]byte{0x0})) for i, c := range testCases { digest := c.op.Accept(visitor) diff --git a/balloon/history/consistency.go b/balloon/history/consistency.go new file mode 100644 index 000000000..15c96b9ce --- /dev/null +++ b/balloon/history/consistency.go @@ -0,0 +1,136 @@ +/* + 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 history + +import ( + "sort" +) + +type targets []uint64 + +func (t targets) InsertSorted(version uint64) targets { + + if len(t) == 0 { + t = append(t, version) + return t + } + + index := sort.Search(len(t), func(i int) bool { + return t[i] > version + }) + + if index > 0 && t[index-1] == version { + return t + } + + t = append(t, version) + copy(t[index+1:], t[index:]) + t[index] = version + return t + +} + +func (t targets) Split(version uint64) (left, right targets) { + // the smallest index i where t[i] >= version + index := sort.Search(len(t), func(i int) bool { + return t[i] >= version //bytes.Compare(r[i].Key, key) >= 0 + }) + return t[:index], t[index:] +} + +func pruneToFindConsistent(index, version uint64) operation { + + var traverse func(pos *position, targets targets, shortcut bool) operation + + traverse = func(pos *position, targets targets, shortcut bool) operation { + + if len(targets) == 0 { + if !shortcut { + return newCollectOp(newGetCacheOp(pos)) + } + return newGetCacheOp(pos) + } + + if pos.IsLeaf() { + if pos.Index == index { + return newLeafHashOp(pos, nil) + } + if !shortcut { + return newCollectOp(newGetCacheOp(pos)) + } + return newGetCacheOp(pos) + } + + if len(targets) == 1 && targets[0] != index { + if !shortcut { + return newCollectOp(traverse(pos, targets, true)) + } + } + + rightPos := pos.Right() + leftTargets, rightTargets := targets.Split(rightPos.Index) + + left := traverse(pos.Left(), leftTargets, shortcut) + right := traverse(rightPos, rightTargets, shortcut) + + if version < rightPos.Index { + return newPartialInnerHashOp(pos, left) + } + + return newInnerHashOp(pos, left, right) + } + + targets := make(targets, 0) + targets = targets.InsertSorted(index) + targets = targets.InsertSorted(version) + return traverse(newRootPosition(version), targets, false) + +} + +func pruneToCheckConsistency(start, end uint64) operation { + + var traverse func(pos *position, targets targets) operation + + traverse = func(pos *position, targets targets) operation { + + if len(targets) == 0 { + return newCollectOp(newGetCacheOp(pos)) + } + + if pos.IsLeaf() { + return newCollectOp(newGetCacheOp(pos)) + } + + rightPos := pos.Right() + leftTargets, rightTargets := targets.Split(rightPos.Index) + + left := traverse(pos.Left(), leftTargets) + right := traverse(rightPos, rightTargets) + + if end < rightPos.Index { + return newPartialInnerHashOp(pos, left) + } + + return newInnerHashOp(pos, left, right) + } + + targets := make(targets, 0) + targets = targets.InsertSorted(start) + targets = targets.InsertSorted(end) + return traverse(newRootPosition(end), targets) + +} diff --git a/balloon/history/pruning/consistency_test.go b/balloon/history/consistency_test.go similarity index 87% rename from balloon/history/pruning/consistency_test.go rename to balloon/history/consistency_test.go index 01d9e14a6..3f8fde42b 100644 --- a/balloon/history/pruning/consistency_test.go +++ b/balloon/history/consistency_test.go @@ -1,4 +1,20 @@ -package pruning +/* + 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 history import ( "testing" @@ -11,7 +27,7 @@ func TestPruneToFindConsistent(t *testing.T) { testCases := []struct { index, version uint64 - expectedOp Operation + expectedOp operation }{ { index: 0, @@ -128,7 +144,7 @@ func TestPruneToFindConsistent(t *testing.T) { } for i, c := range testCases { - prunedOp := PruneToFindConsistent(c.index, c.version) + prunedOp := pruneToFindConsistent(c.index, c.version) assert.Equalf(t, c.expectedOp, prunedOp, "The pruned operation should match for test case %d", i) } @@ -138,7 +154,7 @@ func TestPruneToFindConsistentSameVersion(t *testing.T) { testCases := []struct { version uint64 - expectedOp Operation + expectedOp operation }{ { version: 0, @@ -211,7 +227,7 @@ func TestPruneToFindConsistentSameVersion(t *testing.T) { } for i, c := range testCases { - prunedOp := PruneToFindConsistent(c.version, c.version) + prunedOp := pruneToFindConsistent(c.version, c.version) assert.Equalf(t, c.expectedOp, prunedOp, "The pruned operation should match for test case %d", i) } } @@ -220,7 +236,7 @@ func TestPruneToCheckConsistency(t *testing.T) { testCases := []struct { start, end uint64 - expectedOp Operation + expectedOp operation }{ { start: 0, @@ -337,7 +353,7 @@ func TestPruneToCheckConsistency(t *testing.T) { } for i, c := range testCases { - prunedOp := PruneToCheckConsistency(c.start, c.end) + prunedOp := pruneToCheckConsistency(c.start, c.end) assert.Equalf(t, c.expectedOp, prunedOp, "The pruned operation should match for test case %d", i) } @@ -349,7 +365,7 @@ func BenchmarkPruneToFindConsistent(b *testing.B) { b.ResetTimer() for i := uint64(0); i < uint64(b.N); i++ { - pruned := PruneToFindConsistent(0, i) + pruned := pruneToFindConsistent(0, i) assert.NotNil(b, pruned) } @@ -361,7 +377,7 @@ func BenchmarkPruneToCheckConsistency(b *testing.B) { b.ResetTimer() for i := uint64(0); i < uint64(b.N); i++ { - pruned := PruneToCheckConsistency(0, i) + pruned := pruneToCheckConsistency(0, i) assert.NotNil(b, pruned) } diff --git a/balloon/history/insert.go b/balloon/history/insert.go new file mode 100644 index 000000000..f6f57e1d1 --- /dev/null +++ b/balloon/history/insert.go @@ -0,0 +1,55 @@ +/* + 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 history + +import ( + "github.com/bbva/qed/hashing" +) + +func pruneToInsert(version uint64, eventDigest hashing.Digest) operation { + + var traverse func(*position) operation + traverse = func(pos *position) operation { + + if pos.IsLeaf() { + return newMutateOp(newPutCacheOp(newLeafHashOp(pos, eventDigest))) + } + + var left, right operation + + rightPos := pos.Right() + if version < rightPos.Index { // go to left + left = traverse(pos.Left()) + right = newGetCacheOp(rightPos) + } else { // go to right + left = newGetCacheOp(pos.Left()) + right = traverse(rightPos) + } + + if rightPos.Index > version { // partial + return newPartialInnerHashOp(pos, left) + } + + if pos.LastDescendant().Index <= version { // freeze + return newMutateOp(newPutCacheOp(newInnerHashOp(pos, left, right))) + } + return newInnerHashOp(pos, left, right) + + } + + return traverse(newRootPosition(version)) +} diff --git a/balloon/history/pruning/insert_test.go b/balloon/history/insert_test.go similarity index 76% rename from balloon/history/pruning/insert_test.go rename to balloon/history/insert_test.go index c57abd7e4..30c41eefd 100644 --- a/balloon/history/pruning/insert_test.go +++ b/balloon/history/insert_test.go @@ -1,4 +1,20 @@ -package pruning +/* + 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 history import ( "testing" @@ -14,7 +30,7 @@ func TestPruneToInsert(t *testing.T) { testCases := []struct { version uint64 eventDigest hashing.Digest - expectedOp Operation + expectedOp operation }{ { version: 0, @@ -105,7 +121,7 @@ func TestPruneToInsert(t *testing.T) { } for i, c := range testCases { - prunedOp := PruneToInsert(c.version, c.eventDigest) + prunedOp := pruneToInsert(c.version, c.eventDigest) assert.Equalf(t, c.expectedOp, prunedOp, "The pruned operation should match for test case %d", i) } @@ -117,7 +133,7 @@ func BenchmarkPruneToInsert(b *testing.B) { b.ResetTimer() for i := uint64(0); i < uint64(b.N); i++ { - pruned := PruneToInsert(i, rand.Bytes(32)) + pruned := pruneToInsert(i, rand.Bytes(32)) assert.NotNil(b, pruned) } diff --git a/balloon/history/insert_visitor.go b/balloon/history/insert_visitor.go new file mode 100644 index 000000000..7e5f0bb24 --- /dev/null +++ b/balloon/history/insert_visitor.go @@ -0,0 +1,85 @@ +/* + 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 history + +import ( + "fmt" + + "github.com/bbva/qed/balloon/cache" + "github.com/bbva/qed/hashing" + "github.com/bbva/qed/storage" +) + +type insertVisitor struct { + hasher hashing.Hasher + cache cache.ModifiableCache + storagePrefix byte // TODO shall i remove this? + + mutations []*storage.Mutation +} + +func newInsertVisitor(hasher hashing.Hasher, cache cache.ModifiableCache, storagePrefix byte) *insertVisitor { + return &insertVisitor{ + hasher: hasher, + cache: cache, + storagePrefix: storagePrefix, + mutations: make([]*storage.Mutation, 0), + } +} + +func (v insertVisitor) Result() []*storage.Mutation { + return v.mutations +} + +func (v *insertVisitor) VisitLeafHashOp(op leafHashOp) hashing.Digest { + return v.hasher.Salted(op.Position().Bytes(), op.Value) +} + +func (v *insertVisitor) VisitInnerHashOp(op innerHashOp) hashing.Digest { + leftHash := op.Left.Accept(v) + rightHash := op.Right.Accept(v) + return v.hasher.Salted(op.Position().Bytes(), leftHash, rightHash) +} + +func (v *insertVisitor) VisitPartialInnerHashOp(op partialInnerHashOp) hashing.Digest { + leftHash := op.Left.Accept(v) + return v.hasher.Salted(op.Position().Bytes(), leftHash) +} + +func (v *insertVisitor) VisitGetCacheOp(op getCacheOp) hashing.Digest { + hash, ok := v.cache.Get(op.Position().Bytes()) + if !ok { + panic(fmt.Sprintf("Oops, something went wrong. There should be a cached element at position %v", op.Position())) + } + return hash +} + +func (v *insertVisitor) VisitPutCacheOp(op putCacheOp) hashing.Digest { + hash := op.operation.Accept(v) + v.cache.Put(op.Position().Bytes(), hash) + return hash +} + +func (v *insertVisitor) VisitMutateOp(op mutateOp) hashing.Digest { + hash := op.operation.Accept(v) + v.mutations = append(v.mutations, storage.NewMutation(v.storagePrefix, op.Position().Bytes(), hash)) + return hash +} + +func (v *insertVisitor) VisitCollectOp(op collectOp) hashing.Digest { + return op.operation.Accept(v) +} diff --git a/balloon/history/pruning/insert_visitor_test.go b/balloon/history/insert_visitor_test.go similarity index 69% rename from balloon/history/pruning/insert_visitor_test.go rename to balloon/history/insert_visitor_test.go index e05631834..a43915a96 100644 --- a/balloon/history/pruning/insert_visitor_test.go +++ b/balloon/history/insert_visitor_test.go @@ -1,10 +1,24 @@ -package pruning +/* + 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 history import ( "testing" "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/history/navigation" "github.com/bbva/qed/hashing" "github.com/bbva/qed/storage" "github.com/stretchr/testify/assert" @@ -13,7 +27,7 @@ import ( func TestInsertVisitor(t *testing.T) { testCases := []struct { - op Operation + op operation expectedMutations []*storage.Mutation expectedElements []*cachedElement }{ @@ -61,7 +75,7 @@ func TestInsertVisitor(t *testing.T) { for i, c := range testCases { cache := cache.NewFakeCache([]byte{0x0}) - visitor := NewInsertVisitor(hashing.NewFakeXorHasher(), cache, storage.HistoryCachePrefix) + visitor := newInsertVisitor(hashing.NewFakeXorHasher(), cache, storage.HistoryCachePrefix) c.op.Accept(visitor) @@ -75,10 +89,10 @@ func TestInsertVisitor(t *testing.T) { } type cachedElement struct { - Pos *navigation.Position + Pos *position Digest []byte } -func newCachedElement(pos *navigation.Position, digest []byte) *cachedElement { +func newCachedElement(pos *position, digest []byte) *cachedElement { return &cachedElement{pos, digest} } diff --git a/balloon/history/navigation/audit.go b/balloon/history/navigation/audit.go deleted file mode 100644 index 7d27d36b6..000000000 --- a/balloon/history/navigation/audit.go +++ /dev/null @@ -1,57 +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 navigation - -import ( - "fmt" - "strconv" - "strings" - - "github.com/bbva/qed/hashing" - "github.com/bbva/qed/util" -) - -type AuditPath map[[KeySize]byte]hashing.Digest - -func (p AuditPath) Get(pos []byte) ([]byte, bool) { - var buff [KeySize]byte - copy(buff[:], pos) - digest, ok := p[buff] - return digest, ok -} - -func (p AuditPath) Serialize() map[string]hashing.Digest { - s := make(map[string]hashing.Digest, len(p)) - for k, v := range p { - s[fmt.Sprintf("%d|%d", util.BytesAsUint64(k[:8]), util.BytesAsUint16(k[8:]))] = v - } - return s -} - -func ParseAuditPath(serialized map[string]hashing.Digest) AuditPath { - parsed := make(AuditPath, len(serialized)) - for k, v := range serialized { - tokens := strings.Split(k, "|") - index, _ := strconv.Atoi(tokens[0]) - height, _ := strconv.Atoi(tokens[1]) - var key [KeySize]byte - copy(key[:8], util.Uint64AsBytes(uint64(index))) - copy(key[8:], util.Uint16AsBytes(uint16(height))) - parsed[key] = v - } - return parsed -} diff --git a/balloon/history/navigation/audit_test.go b/balloon/history/navigation/audit_test.go deleted file mode 100644 index 97aab9e4f..000000000 --- a/balloon/history/navigation/audit_test.go +++ /dev/null @@ -1,102 +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 navigation - -import ( - "testing" - - "github.com/bbva/qed/hashing" - "github.com/stretchr/testify/assert" -) - -func TestAuditPathSerialization(t *testing.T) { - - testCases := []struct { - path AuditPath - expected map[string]hashing.Digest - }{ - { - AuditPath{}, - map[string]hashing.Digest{}, - }, - { - AuditPath{ - NewPosition(0, 0).FixedBytes(): []byte{0x0}, - }, - map[string]hashing.Digest{ - "0|0": []byte{0x0}, - }, - }, - { - AuditPath{ - NewPosition(0, 0).FixedBytes(): []byte{0x0}, - NewPosition(2, 1).FixedBytes(): []byte{0x1}, - NewPosition(4, 2).FixedBytes(): []byte{0x2}, - }, - map[string]hashing.Digest{ - "0|0": []byte{0x0}, - "2|1": []byte{0x1}, - "4|2": []byte{0x2}, - }, - }, - } - - for i, c := range testCases { - serialized := c.path.Serialize() - assert.Equalf(t, c.expected, serialized, "The serialized paths should match for test case %d", i) - } - -} - -func TestParseAuditPath(t *testing.T) { - - testCases := []struct { - path map[string]hashing.Digest - expected AuditPath - }{ - { - map[string]hashing.Digest{}, - AuditPath{}, - }, - { - map[string]hashing.Digest{ - "0|0": []byte{0x0}, - }, - AuditPath{ - NewPosition(0, 0).FixedBytes(): []byte{0x0}, - }, - }, - { - map[string]hashing.Digest{ - "0|0": []byte{0x0}, - "2|1": []byte{0x1}, - "4|2": []byte{0x2}, - }, - AuditPath{ - NewPosition(0, 0).FixedBytes(): []byte{0x0}, - NewPosition(2, 1).FixedBytes(): []byte{0x1}, - NewPosition(4, 2).FixedBytes(): []byte{0x2}, - }, - }, - } - - for i, c := range testCases { - parsed := ParseAuditPath(c.path) - assert.Equalf(t, c.expected, parsed, "The parsed paths should match for test case %d", i) - } - -} diff --git a/balloon/history/navigation/position_test.go b/balloon/history/navigation/position_test.go deleted file mode 100644 index c4776b8b6..000000000 --- a/balloon/history/navigation/position_test.go +++ /dev/null @@ -1,166 +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 navigation - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestRoot(t *testing.T) { - - testCases := []struct { - version uint64 - expectedPos *Position - }{ - {0, NewPosition(0, 0)}, - {1, NewPosition(0, 1)}, - {2, NewPosition(0, 2)}, - {3, NewPosition(0, 2)}, - {4, NewPosition(0, 3)}, - {5, NewPosition(0, 3)}, - {6, NewPosition(0, 3)}, - {7, NewPosition(0, 3)}, - {8, NewPosition(0, 4)}, - } - - for i, c := range testCases { - rootPos := NewRootPosition(c.version) - require.Equalf(t, c.expectedPos, rootPos, "The root position should match in test case %d", i) - } - -} - -func TestIsLeaf(t *testing.T) { - - testCases := []struct { - position *Position - ok bool - }{ - {NewPosition(0, 0), true}, - {NewPosition(0, 1), false}, - {NewPosition(1, 1), false}, - {NewPosition(2, 0), true}, - } - - for i, c := range testCases { - result := c.position.IsLeaf() - require.Equalf(t, c.ok, result, "The leaf checking should match for test case %d", i) - } - -} - -func TestLeft(t *testing.T) { - - testCases := []struct { - position *Position - expectedLeft *Position - }{ - {NewPosition(0, 0), nil}, - {NewPosition(0, 0), nil}, - {NewPosition(1, 0), nil}, - {NewPosition(0, 1), NewPosition(0, 0)}, - {NewPosition(0, 0), nil}, - {NewPosition(1, 0), nil}, - {NewPosition(2, 0), nil}, - {NewPosition(0, 1), NewPosition(0, 0)}, - {NewPosition(2, 1), NewPosition(2, 0)}, // TODO check invalid positions like (1,1)? - {NewPosition(0, 0), nil}, - {NewPosition(1, 0), nil}, - {NewPosition(2, 0), nil}, - {NewPosition(0, 1), NewPosition(0, 0)}, - {NewPosition(2, 1), NewPosition(2, 0)}, - {NewPosition(0, 2), NewPosition(0, 1)}, - } - - for i, c := range testCases { - left := c.position.Left() - require.Equalf(t, c.expectedLeft, left, "The left positions should match for test case %d", i) - } -} - -func TestRight(t *testing.T) { - - testCases := []struct { - position *Position - expectedRight *Position - }{ - {NewPosition(0, 0), nil}, - {NewPosition(0, 0), nil}, - {NewPosition(1, 0), nil}, - {NewPosition(0, 1), NewPosition(1, 0)}, - {NewPosition(0, 0), nil}, - {NewPosition(1, 0), nil}, - {NewPosition(2, 0), nil}, - {NewPosition(0, 1), NewPosition(1, 0)}, - {NewPosition(2, 1), NewPosition(3, 0)}, - {NewPosition(0, 0), nil}, - {NewPosition(1, 0), nil}, - {NewPosition(2, 0), nil}, - {NewPosition(0, 1), NewPosition(1, 0)}, - {NewPosition(2, 1), NewPosition(3, 0)}, - {NewPosition(0, 2), NewPosition(2, 1)}, - } - - for i, c := range testCases { - right := c.position.Right() - require.Equalf(t, c.expectedRight, right, "The right positions should match for test case %d", i) - } -} - -func TestFirstDescendant(t *testing.T) { - - testCases := []struct { - position *Position - expectedPos *Position - }{ - {NewPosition(0, 0), NewPosition(0, 0)}, - {NewPosition(1, 0), NewPosition(1, 0)}, - {NewPosition(0, 1), NewPosition(0, 0)}, - {NewPosition(2, 0), NewPosition(2, 0)}, - {NewPosition(2, 1), NewPosition(2, 0)}, - {NewPosition(0, 2), NewPosition(0, 0)}, - } - - for i, c := range testCases { - first := c.position.FirstDescendant() - require.Equalf(t, c.expectedPos, first, "The first descentant position should match for test case %d", i) - } - -} - -func TestLastDescendant(t *testing.T) { - - testCases := []struct { - position *Position - expectedPos *Position - }{ - {NewPosition(0, 0), NewPosition(0, 0)}, - {NewPosition(1, 0), NewPosition(1, 0)}, - {NewPosition(0, 1), NewPosition(1, 0)}, - {NewPosition(2, 0), NewPosition(2, 0)}, - {NewPosition(2, 1), NewPosition(3, 0)}, - {NewPosition(0, 2), NewPosition(3, 0)}, - } - - for i, c := range testCases { - last := c.position.LastDescendant() - require.Equalf(t, c.expectedPos, last, "The first descentant position should match for test case %d", i) - } - -} diff --git a/balloon/history/navigation/test_util.go b/balloon/history/navigation/test_util.go deleted file mode 100644 index 1aaffe1c8..000000000 --- a/balloon/history/navigation/test_util.go +++ /dev/null @@ -1,21 +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 navigation - -func pos(index uint64, height uint16) *Position { - return NewPosition(index, height) -} diff --git a/balloon/history/operation.go b/balloon/history/operation.go new file mode 100644 index 000000000..5da24db31 --- /dev/null +++ b/balloon/history/operation.go @@ -0,0 +1,200 @@ +/* + 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 history + +import ( + "fmt" + + "github.com/bbva/qed/hashing" +) + +type operation interface { + Accept(visitor opVisitor) hashing.Digest + String() string + Position() *position +} + +type opVisitor interface { // THROW ERRORS? + VisitLeafHashOp(op leafHashOp) hashing.Digest + VisitInnerHashOp(op innerHashOp) hashing.Digest + VisitPartialInnerHashOp(op partialInnerHashOp) hashing.Digest + VisitGetCacheOp(op getCacheOp) hashing.Digest + VisitPutCacheOp(op putCacheOp) hashing.Digest + VisitMutateOp(op mutateOp) hashing.Digest + VisitCollectOp(op collectOp) hashing.Digest +} + +type leafHashOp struct { + pos *position + Value []byte +} + +type innerHashOp struct { + pos *position + Left, Right operation +} + +type partialInnerHashOp struct { + pos *position + Left operation +} + +type getCacheOp struct { + pos *position +} + +type putCacheOp struct { + operation +} + +type mutateOp struct { + operation +} + +type collectOp struct { + operation +} + +func newLeafHashOp(pos *position, value []byte) *leafHashOp { + return &leafHashOp{ + pos: pos, + Value: value, + } +} + +func (o leafHashOp) Accept(visitor opVisitor) hashing.Digest { + return visitor.VisitLeafHashOp(o) +} + +func (o leafHashOp) Position() *position { + return o.pos +} + +func (o leafHashOp) String() string { + return fmt.Sprintf("leafHashOp(%v)[ %x ]", o.pos, o.Value) +} + +func newInnerHashOp(pos *position, left, right operation) *innerHashOp { + return &innerHashOp{ + pos: pos, + Left: left, + Right: right, + } +} + +func (o innerHashOp) Accept(visitor opVisitor) hashing.Digest { + return visitor.VisitInnerHashOp(o) +} + +func (o innerHashOp) Position() *position { + return o.pos +} + +func (o innerHashOp) String() string { + return fmt.Sprintf("innerHashOp(%v)[ %v | %v ]", o.pos, o.Left, o.Right) +} + +func newPartialInnerHashOp(pos *position, left operation) *partialInnerHashOp { + return &partialInnerHashOp{ + pos: pos, + Left: left, + } +} + +func (o partialInnerHashOp) Accept(visitor opVisitor) hashing.Digest { + return visitor.VisitPartialInnerHashOp(o) +} + +func (o partialInnerHashOp) Position() *position { + return o.pos +} + +func (o partialInnerHashOp) String() string { + return fmt.Sprintf("partialInnerHashOp(%v)[ %v ]", o.pos, o.Left) +} + +func newGetCacheOp(pos *position) *getCacheOp { + return &getCacheOp{ + pos: pos, + } +} + +func (o getCacheOp) Accept(visitor opVisitor) hashing.Digest { + return visitor.VisitGetCacheOp(o) +} + +func (o getCacheOp) Position() *position { + return o.pos +} + +func (o getCacheOp) String() string { + return fmt.Sprintf("getCacheOp(%v)", o.pos) +} + +func newPutCacheOp(op operation) *putCacheOp { + return &putCacheOp{ + operation: op, + } +} + +func (o putCacheOp) Accept(visitor opVisitor) hashing.Digest { + return visitor.VisitPutCacheOp(o) +} + +func (o putCacheOp) Position() *position { + return o.operation.Position() +} + +func (o putCacheOp) String() string { + return fmt.Sprintf("putCacheOp( %v )", o.operation) +} + +func newMutateOp(op operation) *mutateOp { + return &mutateOp{ + operation: op, + } +} + +func (o mutateOp) Accept(visitor opVisitor) hashing.Digest { + return visitor.VisitMutateOp(o) +} + +func (o mutateOp) Position() *position { + return o.operation.Position() +} + +func (o mutateOp) String() string { + return fmt.Sprintf("mutateOp( %v )", o.operation) +} + +func newCollectOp(op operation) *collectOp { + return &collectOp{ + operation: op, + } +} + +func (o collectOp) Accept(visitor opVisitor) hashing.Digest { + return visitor.VisitCollectOp(o) +} + +func (o collectOp) Position() *position { + return o.operation.Position() +} + +func (o collectOp) String() string { + return fmt.Sprintf("collectOp( %v )", o.operation) +} diff --git a/balloon/history/navigation/position.go b/balloon/history/position.go similarity index 59% rename from balloon/history/navigation/position.go rename to balloon/history/position.go index 4fbccfde6..d06b8b2b1 100644 --- a/balloon/history/navigation/position.go +++ b/balloon/history/position.go @@ -14,7 +14,7 @@ limitations under the License. */ -package navigation +package history import ( "fmt" @@ -23,75 +23,75 @@ import ( "github.com/bbva/qed/util" ) -const KeySize = 10 +const keySize = 10 -type Position struct { +type position struct { Index uint64 Height uint16 - serialized [KeySize]byte + serialized [keySize]byte } -func NewPosition(index uint64, height uint16) *Position { - var b [KeySize]byte // Size of the index plus 2 bytes for the height +func newPosition(index uint64, height uint16) *position { + var b [keySize]byte // Size of the index plus 2 bytes for the height indexAsBytes := util.Uint64AsBytes(index) copy(b[:], indexAsBytes[:len(indexAsBytes)]) copy(b[len(indexAsBytes):], util.Uint16AsBytes(height)) - return &Position{ + return &position{ Index: index, Height: height, serialized: b, // memoized } } -func NewRootPosition(version uint64) *Position { - return NewPosition(0, uint16(bits.Len64(version))) +func newRootPosition(version uint64) *position { + return newPosition(0, uint16(bits.Len64(version))) } -func (p Position) Bytes() []byte { +func (p position) Bytes() []byte { return p.serialized[:] } -func (p Position) FixedBytes() [KeySize]byte { +func (p position) FixedBytes() [keySize]byte { return p.serialized } -func (p Position) String() string { +func (p position) String() string { return fmt.Sprintf("Pos(%d, %d)", p.Index, p.Height) } -func (p Position) StringId() string { +func (p position) StringId() string { return fmt.Sprintf("%d|%d", p.Index, p.Height) } -func (p Position) Left() *Position { +func (p position) Left() *position { if p.IsLeaf() { return nil } - return NewPosition(p.Index, p.Height-1) + return newPosition(p.Index, p.Height-1) } -func (p Position) Right() *Position { +func (p position) Right() *position { if p.IsLeaf() { return nil } - return NewPosition(p.Index+1<<(p.Height-1), p.Height-1) + return newPosition(p.Index+1<<(p.Height-1), p.Height-1) } -func (p Position) IsLeaf() bool { +func (p position) IsLeaf() bool { return p.Height == 0 } -func (p Position) FirstDescendant() *Position { +func (p position) FirstDescendant() *position { if p.IsLeaf() { return &p } - return NewPosition(p.Index, 0) + return newPosition(p.Index, 0) } -func (p Position) LastDescendant() *Position { +func (p position) LastDescendant() *position { if p.IsLeaf() { return &p } - return NewPosition(p.Index+1< version - }) - - if index > 0 && t[index-1] == version { - return t - } - - t = append(t, version) - copy(t[index+1:], t[index:]) - t[index] = version - return t - -} - -func (t Targets) Split(version uint64) (left, right Targets) { - // the smallest index i where t[i] >= version - index := sort.Search(len(t), func(i int) bool { - return t[i] >= version //bytes.Compare(r[i].Key, key) >= 0 - }) - return t[:index], t[index:] -} - -func PruneToFindConsistent(index, version uint64) Operation { - - var traverse func(pos *navigation.Position, targets Targets, shortcut bool) Operation - - traverse = func(pos *navigation.Position, targets Targets, shortcut bool) Operation { - - if len(targets) == 0 { - if !shortcut { - return NewCollectOp(NewGetCacheOp(pos)) - } - return NewGetCacheOp(pos) - } - - if pos.IsLeaf() { - if pos.Index == index { - return NewLeafHashOp(pos, nil) - } - if !shortcut { - return NewCollectOp(NewGetCacheOp(pos)) - } - return NewGetCacheOp(pos) - } - - if len(targets) == 1 && targets[0] != index { - if !shortcut { - return NewCollectOp(traverse(pos, targets, true)) - } - } - - rightPos := pos.Right() - leftTargets, rightTargets := targets.Split(rightPos.Index) - - left := traverse(pos.Left(), leftTargets, shortcut) - right := traverse(rightPos, rightTargets, shortcut) - - if version < rightPos.Index { - return NewPartialInnerHashOp(pos, left) - } - - return NewInnerHashOp(pos, left, right) - } - - targets := make(Targets, 0) - targets = targets.InsertSorted(index) - targets = targets.InsertSorted(version) - return traverse(navigation.NewRootPosition(version), targets, false) - -} - -func PruneToCheckConsistency(start, end uint64) Operation { - - var traverse func(pos *navigation.Position, targets Targets) Operation - - traverse = func(pos *navigation.Position, targets Targets) Operation { - - if len(targets) == 0 { - return NewCollectOp(NewGetCacheOp(pos)) - } - - if pos.IsLeaf() { - return NewCollectOp(NewGetCacheOp(pos)) - } - - rightPos := pos.Right() - leftTargets, rightTargets := targets.Split(rightPos.Index) - - left := traverse(pos.Left(), leftTargets) - right := traverse(rightPos, rightTargets) - - if end < rightPos.Index { - return NewPartialInnerHashOp(pos, left) - } - - return NewInnerHashOp(pos, left, right) - } - - targets := make(Targets, 0) - targets = targets.InsertSorted(start) - targets = targets.InsertSorted(end) - return traverse(navigation.NewRootPosition(end), targets) - -} diff --git a/balloon/history/pruning/insert.go b/balloon/history/pruning/insert.go deleted file mode 100644 index a51e69bb5..000000000 --- a/balloon/history/pruning/insert.go +++ /dev/null @@ -1,42 +0,0 @@ -package pruning - -import ( - "github.com/bbva/qed/balloon/history/navigation" - "github.com/bbva/qed/hashing" -) - -type Traverse func(*navigation.Position) Operation - -func PruneToInsert(version uint64, eventDigest hashing.Digest) Operation { - - var traverse Traverse - traverse = func(pos *navigation.Position) Operation { - - if pos.IsLeaf() { - return NewMutateOp(NewPutCacheOp(NewLeafHashOp(pos, eventDigest))) - } - - var left, right Operation - - rightPos := pos.Right() - if version < rightPos.Index { // go to left - left = traverse(pos.Left()) - right = NewGetCacheOp(rightPos) - } else { // go to right - left = NewGetCacheOp(pos.Left()) - right = traverse(rightPos) - } - - if rightPos.Index > version { // partial - return NewPartialInnerHashOp(pos, left) - } - - if pos.LastDescendant().Index <= version { // freeze - return NewMutateOp(NewPutCacheOp(NewInnerHashOp(pos, left, right))) - } - return NewInnerHashOp(pos, left, right) - - } - - return traverse(navigation.NewRootPosition(version)) -} diff --git a/balloon/history/pruning/insert_visitor.go b/balloon/history/pruning/insert_visitor.go deleted file mode 100644 index d422490d6..000000000 --- a/balloon/history/pruning/insert_visitor.go +++ /dev/null @@ -1,69 +0,0 @@ -package pruning - -import ( - "fmt" - - "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/hashing" - "github.com/bbva/qed/storage" -) - -type InsertVisitor struct { - hasher hashing.Hasher - cache cache.ModifiableCache - storagePrefix byte // TODO shall i remove this? - - mutations []*storage.Mutation -} - -func NewInsertVisitor(hasher hashing.Hasher, cache cache.ModifiableCache, storagePrefix byte) *InsertVisitor { - return &InsertVisitor{ - hasher: hasher, - cache: cache, - storagePrefix: storagePrefix, - mutations: make([]*storage.Mutation, 0), - } -} - -func (v InsertVisitor) Result() []*storage.Mutation { - return v.mutations -} - -func (v *InsertVisitor) VisitLeafHashOp(op LeafHashOp) hashing.Digest { - return v.hasher.Salted(op.Position().Bytes(), op.Value) -} - -func (v *InsertVisitor) VisitInnerHashOp(op InnerHashOp) hashing.Digest { - leftHash := op.Left.Accept(v) - rightHash := op.Right.Accept(v) - return v.hasher.Salted(op.Position().Bytes(), leftHash, rightHash) -} - -func (v *InsertVisitor) VisitPartialInnerHashOp(op PartialInnerHashOp) hashing.Digest { - leftHash := op.Left.Accept(v) - return v.hasher.Salted(op.Position().Bytes(), leftHash) -} - -func (v *InsertVisitor) VisitGetCacheOp(op GetCacheOp) hashing.Digest { - hash, ok := v.cache.Get(op.Position().Bytes()) - if !ok { - panic(fmt.Sprintf("Oops, something went wrong. There should be a cached element at position %v", op.Position())) - } - return hash -} - -func (v *InsertVisitor) VisitPutCacheOp(op PutCacheOp) hashing.Digest { - hash := op.Operation.Accept(v) - v.cache.Put(op.Position().Bytes(), hash) - return hash -} - -func (v *InsertVisitor) VisitMutateOp(op MutateOp) hashing.Digest { - hash := op.Operation.Accept(v) - v.mutations = append(v.mutations, storage.NewMutation(v.storagePrefix, op.Position().Bytes(), hash)) - return hash -} - -func (v *InsertVisitor) VisitCollectOp(op CollectOp) hashing.Digest { - return op.Operation.Accept(v) -} diff --git a/balloon/history/pruning/operation.go b/balloon/history/pruning/operation.go deleted file mode 100644 index 59583eb43..000000000 --- a/balloon/history/pruning/operation.go +++ /dev/null @@ -1,185 +0,0 @@ -package pruning - -import ( - "fmt" - - "github.com/bbva/qed/balloon/history/navigation" - "github.com/bbva/qed/hashing" -) - -type Operation interface { - Accept(visitor OpVisitor) hashing.Digest - String() string - Position() *navigation.Position -} - -type OpVisitor interface { // THROW ERRORS? - VisitLeafHashOp(op LeafHashOp) hashing.Digest - VisitInnerHashOp(op InnerHashOp) hashing.Digest - VisitPartialInnerHashOp(op PartialInnerHashOp) hashing.Digest - VisitGetCacheOp(op GetCacheOp) hashing.Digest - VisitPutCacheOp(op PutCacheOp) hashing.Digest - VisitMutateOp(op MutateOp) hashing.Digest - VisitCollectOp(op CollectOp) hashing.Digest -} - -type LeafHashOp struct { - pos *navigation.Position - Value []byte -} - -type InnerHashOp struct { - pos *navigation.Position - Left, Right Operation -} - -type PartialInnerHashOp struct { - pos *navigation.Position - Left Operation -} - -type GetCacheOp struct { - pos *navigation.Position -} - -type PutCacheOp struct { - Operation -} - -type MutateOp struct { - Operation -} - -type CollectOp struct { - Operation -} - -func NewLeafHashOp(pos *navigation.Position, value []byte) *LeafHashOp { - return &LeafHashOp{ - pos: pos, - Value: value, - } -} - -func (o LeafHashOp) Accept(visitor OpVisitor) hashing.Digest { - return visitor.VisitLeafHashOp(o) -} - -func (o LeafHashOp) Position() *navigation.Position { - return o.pos -} - -func (o LeafHashOp) String() string { - return fmt.Sprintf("LeafHashOp(%v)[ %x ]", o.pos, o.Value) -} - -func NewInnerHashOp(pos *navigation.Position, left, right Operation) *InnerHashOp { - return &InnerHashOp{ - pos: pos, - Left: left, - Right: right, - } -} - -func (o InnerHashOp) Accept(visitor OpVisitor) hashing.Digest { - return visitor.VisitInnerHashOp(o) -} - -func (o InnerHashOp) Position() *navigation.Position { - return o.pos -} - -func (o InnerHashOp) String() string { - return fmt.Sprintf("InnerHashOp(%v)[ %v | %v ]", o.pos, o.Left, o.Right) -} - -func NewPartialInnerHashOp(pos *navigation.Position, left Operation) *PartialInnerHashOp { - return &PartialInnerHashOp{ - pos: pos, - Left: left, - } -} - -func (o PartialInnerHashOp) Accept(visitor OpVisitor) hashing.Digest { - return visitor.VisitPartialInnerHashOp(o) -} - -func (o PartialInnerHashOp) Position() *navigation.Position { - return o.pos -} - -func (o PartialInnerHashOp) String() string { - return fmt.Sprintf("PartialInnerHashOp(%v)[ %v ]", o.pos, o.Left) -} - -func NewGetCacheOp(pos *navigation.Position) *GetCacheOp { - return &GetCacheOp{ - pos: pos, - } -} - -func (o GetCacheOp) Accept(visitor OpVisitor) hashing.Digest { - return visitor.VisitGetCacheOp(o) -} - -func (o GetCacheOp) Position() *navigation.Position { - return o.pos -} - -func (o GetCacheOp) String() string { - return fmt.Sprintf("GetCacheOp(%v)", o.pos) -} - -func NewPutCacheOp(op Operation) *PutCacheOp { - return &PutCacheOp{ - Operation: op, - } -} - -func (o PutCacheOp) Accept(visitor OpVisitor) hashing.Digest { - return visitor.VisitPutCacheOp(o) -} - -func (o PutCacheOp) Position() *navigation.Position { - return o.Operation.Position() -} - -func (o PutCacheOp) String() string { - return fmt.Sprintf("PutCacheOp( %v )", o.Operation) -} - -func NewMutateOp(op Operation) *MutateOp { - return &MutateOp{ - Operation: op, - } -} - -func (o MutateOp) Accept(visitor OpVisitor) hashing.Digest { - return visitor.VisitMutateOp(o) -} - -func (o MutateOp) Position() *navigation.Position { - return o.Operation.Position() -} - -func (o MutateOp) String() string { - return fmt.Sprintf("MutateOp( %v )", o.Operation) -} - -func NewCollectOp(op Operation) *CollectOp { - return &CollectOp{ - Operation: op, - } -} - -func (o CollectOp) Accept(visitor OpVisitor) hashing.Digest { - return visitor.VisitCollectOp(o) -} - -func (o CollectOp) Position() *navigation.Position { - return o.Operation.Position() -} - -func (o CollectOp) String() string { - return fmt.Sprintf("CollectOp( %v )", o.Operation) -} diff --git a/balloon/history/pruning/print_visitor.go b/balloon/history/pruning/print_visitor.go deleted file mode 100644 index 65ca93c60..000000000 --- a/balloon/history/pruning/print_visitor.go +++ /dev/null @@ -1,67 +0,0 @@ -package pruning - -import ( - "fmt" - "strings" - - "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) VisitLeafHashOp(op LeafHashOp) hashing.Digest { - v.tokens = append(v.tokens, fmt.Sprintf("%sLeafHashOp(%v)[%x]", v.indent(op.Position().Height), op.Position(), op.Value)) - return nil -} - -func (v *PrintVisitor) VisitInnerHashOp(op InnerHashOp) hashing.Digest { - v.tokens = append(v.tokens, fmt.Sprintf("%sInnerHashOp(%v)", v.indent(op.Position().Height), op.Position())) - op.Left.Accept(v) - op.Right.Accept(v) - return nil -} - -func (v *PrintVisitor) VisitPartialInnerHashOp(op PartialInnerHashOp) hashing.Digest { - v.tokens = append(v.tokens, fmt.Sprintf("%sPartialInnerHashOp(%v)", v.indent(op.Position().Height), op.Position())) - op.Left.Accept(v) - return nil -} - -func (v *PrintVisitor) VisitGetCacheOp(op GetCacheOp) hashing.Digest { - v.tokens = append(v.tokens, fmt.Sprintf("%sGetCacheOp(%v)", v.indent(op.Position().Height), op.Position())) - return nil -} - -func (v *PrintVisitor) VisitPutCacheOp(op PutCacheOp) hashing.Digest { - v.tokens = append(v.tokens, fmt.Sprintf("%sPutCacheOp(%v)", v.indent(op.Position().Height), op.Position())) - return op.Operation.Accept(v) -} - -func (v *PrintVisitor) VisitMutateOp(op MutateOp) hashing.Digest { - v.tokens = append(v.tokens, fmt.Sprintf("%sMutateOp(%v)", v.indent(op.Position().Height), op.Position())) - return op.Operation.Accept(v) -} - -func (v *PrintVisitor) VisitCollectOp(op CollectOp) hashing.Digest { - v.tokens = append(v.tokens, fmt.Sprintf("%sCollectOp(%v)", v.indent(op.Position().Height), op.Position())) - return op.Operation.Accept(v) -} - -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/history/pruning/search.go b/balloon/history/pruning/search.go deleted file mode 100644 index c92830f23..000000000 --- a/balloon/history/pruning/search.go +++ /dev/null @@ -1,35 +0,0 @@ -package pruning - -import ( - "github.com/bbva/qed/balloon/history/navigation" -) - -func PruneToFind(version uint64) Operation { - - var traverse Traverse - traverse = func(pos *navigation.Position) Operation { - - if pos.IsLeaf() { - return NewLeafHashOp(pos, nil) - } - - var left, right Operation - - rightPos := pos.Right() - if version < rightPos.Index { // go to left - left = traverse(pos.Left()) - right = NewCollectOp(NewGetCacheOp(rightPos)) - } else { // go to right - left = NewCollectOp(NewGetCacheOp(pos.Left())) - right = traverse(rightPos) - } - - if rightPos.Index > version { // partial - return NewPartialInnerHashOp(pos, left) - } - return NewInnerHashOp(pos, left, right) - - } - - return traverse(navigation.NewRootPosition(version)) -} diff --git a/balloon/history/pruning/test_util.go b/balloon/history/pruning/test_util.go deleted file mode 100644 index 1f901fadf..000000000 --- a/balloon/history/pruning/test_util.go +++ /dev/null @@ -1,57 +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 pruning - -import ( - "github.com/bbva/qed/balloon/history/navigation" -) - -func pos(index uint64, height uint16) *navigation.Position { - return navigation.NewPosition(index, height) -} - -func inner(pos *navigation.Position, left, right Operation) *InnerHashOp { - return NewInnerHashOp(pos, left, right) -} - -func partial(pos *navigation.Position, left Operation) *PartialInnerHashOp { - return NewPartialInnerHashOp(pos, left) -} - -func leaf(pos *navigation.Position, value byte) *LeafHashOp { - return NewLeafHashOp(pos, []byte{value}) -} - -func leafnil(pos *navigation.Position) *LeafHashOp { - return NewLeafHashOp(pos, nil) -} - -func getCache(pos *navigation.Position) *GetCacheOp { - return NewGetCacheOp(pos) -} - -func putCache(op Operation) *PutCacheOp { - return NewPutCacheOp(op) -} - -func mutate(op Operation) *MutateOp { - return NewMutateOp(op) -} - -func collect(op Operation) *CollectOp { - return NewCollectOp(op) -} diff --git a/balloon/history/pruning/verify.go b/balloon/history/pruning/verify.go deleted file mode 100644 index 0f15de296..000000000 --- a/balloon/history/pruning/verify.go +++ /dev/null @@ -1,101 +0,0 @@ -package pruning - -import ( - "github.com/bbva/qed/balloon/history/navigation" - "github.com/bbva/qed/hashing" -) - -func PruneToVerify(index, version uint64, eventDigest hashing.Digest) Operation { - - var traverse Traverse - traverse = func(pos *navigation.Position) Operation { - - if pos.IsLeaf() { - return NewLeafHashOp(pos, eventDigest) - } - - var left, right Operation - - rightPos := pos.Right() - if index < rightPos.Index { // go to left - left = traverse(pos.Left()) - right = NewGetCacheOp(rightPos) - } else { // go to right - left = NewGetCacheOp(pos.Left()) - right = traverse(rightPos) - } - - if rightPos.Index > version { // partial - return NewPartialInnerHashOp(pos, left) - } - - return NewInnerHashOp(pos, left, right) - - } - - return traverse(navigation.NewRootPosition(version)) -} - -func PruneToVerifyIncrementalStart(version uint64) Operation { - - var traverse Traverse - traverse = func(pos *navigation.Position) Operation { - - if pos.IsLeaf() { - return NewGetCacheOp(pos) - } - - var left, right Operation - - rightPos := pos.Right() - if version < rightPos.Index { // go to left - left = traverse(pos.Left()) - right = NewGetCacheOp(rightPos) - } else { // go to right - left = NewGetCacheOp(pos.Left()) - right = traverse(rightPos) - } - - if rightPos.Index > version { // partial - return NewPartialInnerHashOp(pos, left) - } - return NewInnerHashOp(pos, left, right) - - } - - return traverse(navigation.NewRootPosition(version)) -} - -func PruneToVerifyIncrementalEnd(start, end uint64) Operation { - - var traverse func(pos *navigation.Position, targets Targets) Operation - - traverse = func(pos *navigation.Position, targets Targets) Operation { - - if len(targets) == 0 { - return NewGetCacheOp(pos) - } - - if pos.IsLeaf() { - return NewGetCacheOp(pos) - } - - rightPos := pos.Right() - leftTargets, rightTargets := targets.Split(rightPos.Index) - - left := traverse(pos.Left(), leftTargets) - right := traverse(rightPos, rightTargets) - - if end < rightPos.Index { - return NewPartialInnerHashOp(pos, left) - } - - return NewInnerHashOp(pos, left, right) - - } - - targets := make(Targets, 0) - targets = targets.InsertSorted(start) - targets = targets.InsertSorted(end) - return traverse(navigation.NewRootPosition(end), targets) -} diff --git a/balloon/history/search.go b/balloon/history/search.go new file mode 100644 index 000000000..8ab6a4e9e --- /dev/null +++ b/balloon/history/search.go @@ -0,0 +1,47 @@ +/* + 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 history + +func pruneToFind(version uint64) operation { + + var traverse func(pos *position) operation + traverse = func(pos *position) operation { + + if pos.IsLeaf() { + return newLeafHashOp(pos, nil) + } + + var left, right operation + + rightPos := pos.Right() + if version < rightPos.Index { // go to left + left = traverse(pos.Left()) + right = newCollectOp(newGetCacheOp(rightPos)) + } else { // go to right + left = newCollectOp(newGetCacheOp(pos.Left())) + right = traverse(rightPos) + } + + if rightPos.Index > version { // partial + return newPartialInnerHashOp(pos, left) + } + return newInnerHashOp(pos, left, right) + + } + + return traverse(newRootPosition(version)) +} diff --git a/balloon/history/pruning/search_test.go b/balloon/history/search_test.go similarity index 72% rename from balloon/history/pruning/search_test.go rename to balloon/history/search_test.go index 449d10206..4117dd604 100644 --- a/balloon/history/pruning/search_test.go +++ b/balloon/history/search_test.go @@ -1,4 +1,20 @@ -package pruning +/* + 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 history import ( "testing" @@ -11,7 +27,7 @@ func TestPruneToFind(t *testing.T) { testCases := []struct { version uint64 - expectedOp Operation + expectedOp operation }{ { version: 0, @@ -94,7 +110,7 @@ func TestPruneToFind(t *testing.T) { } for i, c := range testCases { - prunedOp := PruneToFind(c.version) + prunedOp := pruneToFind(c.version) assert.Equalf(t, c.expectedOp, prunedOp, "The pruned operation should match for test case %d", i) } @@ -106,7 +122,7 @@ func BenchmarkPruneToFind(b *testing.B) { b.ResetTimer() for i := uint64(0); i < uint64(b.N); i++ { - pruned := PruneToFind(i) + pruned := pruneToFind(i) assert.NotNil(b, pruned) } diff --git a/balloon/history/test_util.go b/balloon/history/test_util.go index 06d6b8ea7..9d28a04e5 100644 --- a/balloon/history/test_util.go +++ b/balloon/history/test_util.go @@ -16,8 +16,38 @@ package history -import "github.com/bbva/qed/balloon/history/navigation" +func pos(index uint64, height uint16) *position { + return newPosition(index, height) +} + +func inner(pos *position, left, right operation) *innerHashOp { + return newInnerHashOp(pos, left, right) +} + +func partial(pos *position, left operation) *partialInnerHashOp { + return newPartialInnerHashOp(pos, left) +} + +func leaf(pos *position, value byte) *leafHashOp { + return newLeafHashOp(pos, []byte{value}) +} + +func leafnil(pos *position) *leafHashOp { + return newLeafHashOp(pos, nil) +} + +func getCache(pos *position) *getCacheOp { + return newGetCacheOp(pos) +} + +func putCache(op operation) *putCacheOp { + return newPutCacheOp(op) +} + +func mutate(op operation) *mutateOp { + return newMutateOp(op) +} -func pos(index uint64, height uint16) *navigation.Position { - return navigation.NewPosition(index, height) +func collect(op operation) *collectOp { + return newCollectOp(op) } diff --git a/balloon/history/tree.go b/balloon/history/tree.go index 59e94531d..4b22e9972 100644 --- a/balloon/history/tree.go +++ b/balloon/history/tree.go @@ -18,7 +18,6 @@ package history import ( "github.com/bbva/qed/balloon/cache" - "github.com/bbva/qed/balloon/history/pruning" "github.com/bbva/qed/hashing" "github.com/bbva/qed/log" "github.com/bbva/qed/storage" @@ -52,8 +51,8 @@ func (t *HistoryTree) Add(eventDigest hashing.Digest, version uint64) (hashing.D log.Debugf("Adding new event digest %x with version %d", eventDigest, version) // build a visitable pruned tree and then visit it to generate the root hash - visitor := pruning.NewInsertVisitor(t.hasher, t.writeCache, storage.HistoryCachePrefix) - rh := pruning.PruneToInsert(version, eventDigest).Accept(visitor) + visitor := newInsertVisitor(t.hasher, t.writeCache, storage.HistoryCachePrefix) + rh := pruneToInsert(version, eventDigest).Accept(visitor) return rh, visitor.Result(), nil } @@ -63,11 +62,11 @@ func (t *HistoryTree) ProveMembership(index, version uint64) (*MembershipProof, log.Debugf("Proving membership for index %d with version %d", index, version) // build a visitable pruned tree and then visit it to collect the audit path - visitor := pruning.NewAuditPathVisitor(t.hasherF(), t.readCache) + visitor := newAuditPathVisitor(t.hasherF(), t.readCache) if index == version { - pruning.PruneToFind(index).Accept(visitor) // faster pruning + pruneToFind(index).Accept(visitor) // faster pruning } else { - pruning.PruneToFindConsistent(index, version).Accept(visitor) + pruneToFindConsistent(index, version).Accept(visitor) } proof := NewMembershipProof(index, version, visitor.Result(), t.hasherF()) @@ -79,8 +78,8 @@ func (t *HistoryTree) ProveConsistency(start, end uint64) (*IncrementalProof, er log.Debugf("Proving consistency between versions %d and %d", start, end) // build a visitable pruned tree and then visit it to collect the audit path - visitor := pruning.NewAuditPathVisitor(t.hasherF(), t.readCache) - pruning.PruneToCheckConsistency(start, end).Accept(visitor) + visitor := newAuditPathVisitor(t.hasherF(), t.readCache) + pruneToCheckConsistency(start, end).Accept(visitor) proof := NewIncrementalProof(start, end, visitor.Result(), t.hasherF()) diff --git a/balloon/history/tree_test.go b/balloon/history/tree_test.go index c3ce91ec2..20014c562 100644 --- a/balloon/history/tree_test.go +++ b/balloon/history/tree_test.go @@ -19,7 +19,6 @@ package history import ( "testing" - "github.com/bbva/qed/balloon/history/navigation" "github.com/bbva/qed/hashing" "github.com/bbva/qed/log" "github.com/bbva/qed/storage/bplus" @@ -111,19 +110,19 @@ func TestProveMembership(t *testing.T) { testCases := []struct { index, version uint64 eventDigest hashing.Digest - expectedAuditPath navigation.AuditPath + expectedAuditPath AuditPath }{ { index: 0, version: 0, eventDigest: hashing.Digest{0x0}, - expectedAuditPath: navigation.AuditPath{}, + expectedAuditPath: AuditPath{}, }, { index: 1, version: 1, eventDigest: hashing.Digest{0x1}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 0).FixedBytes(): hashing.Digest{0x0}, }, }, @@ -131,7 +130,7 @@ func TestProveMembership(t *testing.T) { index: 2, version: 2, eventDigest: hashing.Digest{0x2}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 1).FixedBytes(): hashing.Digest{0x1}, }, }, @@ -139,7 +138,7 @@ func TestProveMembership(t *testing.T) { index: 3, version: 3, eventDigest: hashing.Digest{0x3}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 1).FixedBytes(): hashing.Digest{0x1}, pos(2, 0).FixedBytes(): hashing.Digest{0x2}, }, @@ -148,7 +147,7 @@ func TestProveMembership(t *testing.T) { index: 4, version: 4, eventDigest: hashing.Digest{0x4}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 2).FixedBytes(): hashing.Digest{0x0}, }, }, @@ -156,7 +155,7 @@ func TestProveMembership(t *testing.T) { index: 5, version: 5, eventDigest: hashing.Digest{0x5}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 2).FixedBytes(): hashing.Digest{0x0}, pos(4, 0).FixedBytes(): hashing.Digest{0x4}, }, @@ -165,7 +164,7 @@ func TestProveMembership(t *testing.T) { index: 6, version: 6, eventDigest: hashing.Digest{0x6}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 2).FixedBytes(): hashing.Digest{0x0}, pos(4, 1).FixedBytes(): hashing.Digest{0x1}, }, @@ -174,7 +173,7 @@ func TestProveMembership(t *testing.T) { index: 7, version: 7, eventDigest: hashing.Digest{0x7}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 2).FixedBytes(): hashing.Digest{0x0}, pos(4, 1).FixedBytes(): hashing.Digest{0x1}, pos(6, 0).FixedBytes(): hashing.Digest{0x6}, @@ -184,7 +183,7 @@ func TestProveMembership(t *testing.T) { index: 8, version: 8, eventDigest: hashing.Digest{0x8}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 3).FixedBytes(): hashing.Digest{0x0}, }, }, @@ -192,7 +191,7 @@ func TestProveMembership(t *testing.T) { index: 9, version: 9, eventDigest: hashing.Digest{0x9}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 3).FixedBytes(): hashing.Digest{0x0}, pos(8, 0).FixedBytes(): hashing.Digest{0x8}, }, @@ -201,7 +200,7 @@ func TestProveMembership(t *testing.T) { index: 0, version: 1, eventDigest: hashing.Digest{0x0}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(1, 0).FixedBytes(): hashing.Digest{0x1}, }, }, @@ -209,7 +208,7 @@ func TestProveMembership(t *testing.T) { index: 0, version: 2, eventDigest: hashing.Digest{0x0}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(1, 0).FixedBytes(): hashing.Digest{0x1}, pos(2, 1).FixedBytes(): hashing.Digest{0x2}, }, @@ -218,7 +217,7 @@ func TestProveMembership(t *testing.T) { index: 0, version: 3, eventDigest: hashing.Digest{0x0}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(1, 0).FixedBytes(): hashing.Digest{0x1}, pos(2, 1).FixedBytes(): hashing.Digest{0x1}, }, @@ -227,7 +226,7 @@ func TestProveMembership(t *testing.T) { index: 0, version: 4, eventDigest: hashing.Digest{0x0}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(1, 0).FixedBytes(): hashing.Digest{0x1}, pos(2, 1).FixedBytes(): hashing.Digest{0x1}, pos(4, 2).FixedBytes(): hashing.Digest{0x4}, @@ -237,7 +236,7 @@ func TestProveMembership(t *testing.T) { index: 0, version: 5, eventDigest: hashing.Digest{0x0}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(1, 0).FixedBytes(): hashing.Digest{0x1}, pos(2, 1).FixedBytes(): hashing.Digest{0x1}, pos(4, 2).FixedBytes(): hashing.Digest{0x1}, @@ -247,7 +246,7 @@ func TestProveMembership(t *testing.T) { index: 0, version: 6, eventDigest: hashing.Digest{0x0}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(1, 0).FixedBytes(): hashing.Digest{0x1}, pos(2, 1).FixedBytes(): hashing.Digest{0x1}, pos(4, 2).FixedBytes(): hashing.Digest{0x7}, @@ -257,7 +256,7 @@ func TestProveMembership(t *testing.T) { index: 0, version: 7, eventDigest: hashing.Digest{0x0}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(1, 0).FixedBytes(): hashing.Digest{0x1}, pos(2, 1).FixedBytes(): hashing.Digest{0x1}, pos(4, 2).FixedBytes(): hashing.Digest{0x0}, @@ -290,24 +289,24 @@ func TestProveConsistency(t *testing.T) { testCases := []struct { eventDigest hashing.Digest - expectedAuditPath navigation.AuditPath + expectedAuditPath AuditPath }{ { eventDigest: hashing.Digest{0x0}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 0).FixedBytes(): hashing.Digest{0x0}, }, }, { eventDigest: hashing.Digest{0x1}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 0).FixedBytes(): hashing.Digest{0x0}, pos(1, 0).FixedBytes(): hashing.Digest{0x1}, }, }, { eventDigest: hashing.Digest{0x2}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 0).FixedBytes(): hashing.Digest{0x0}, pos(1, 0).FixedBytes(): hashing.Digest{0x1}, pos(2, 0).FixedBytes(): hashing.Digest{0x2}, @@ -315,7 +314,7 @@ func TestProveConsistency(t *testing.T) { }, { eventDigest: hashing.Digest{0x3}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 1).FixedBytes(): hashing.Digest{0x1}, pos(2, 0).FixedBytes(): hashing.Digest{0x2}, pos(3, 0).FixedBytes(): hashing.Digest{0x3}, @@ -323,7 +322,7 @@ func TestProveConsistency(t *testing.T) { }, { eventDigest: hashing.Digest{0x4}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 1).FixedBytes(): hashing.Digest{0x1}, pos(2, 0).FixedBytes(): hashing.Digest{0x2}, pos(3, 0).FixedBytes(): hashing.Digest{0x3}, @@ -332,7 +331,7 @@ func TestProveConsistency(t *testing.T) { }, { eventDigest: hashing.Digest{0x5}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 2).FixedBytes(): hashing.Digest{0x0}, pos(4, 0).FixedBytes(): hashing.Digest{0x4}, pos(5, 0).FixedBytes(): hashing.Digest{0x5}, @@ -340,7 +339,7 @@ func TestProveConsistency(t *testing.T) { }, { eventDigest: hashing.Digest{0x6}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 2).FixedBytes(): hashing.Digest{0x0}, pos(4, 0).FixedBytes(): hashing.Digest{0x4}, pos(5, 0).FixedBytes(): hashing.Digest{0x5}, @@ -349,7 +348,7 @@ func TestProveConsistency(t *testing.T) { }, { eventDigest: hashing.Digest{0x7}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 2).FixedBytes(): hashing.Digest{0x0}, pos(4, 1).FixedBytes(): hashing.Digest{0x1}, pos(6, 0).FixedBytes(): hashing.Digest{0x6}, @@ -358,7 +357,7 @@ func TestProveConsistency(t *testing.T) { }, { eventDigest: hashing.Digest{0x8}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 2).FixedBytes(): hashing.Digest{0x0}, pos(4, 1).FixedBytes(): hashing.Digest{0x1}, pos(6, 0).FixedBytes(): hashing.Digest{0x6}, @@ -368,7 +367,7 @@ func TestProveConsistency(t *testing.T) { }, { eventDigest: hashing.Digest{0x9}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 3).FixedBytes(): hashing.Digest{0x0}, pos(8, 0).FixedBytes(): hashing.Digest{0x8}, pos(9, 0).FixedBytes(): hashing.Digest{0x9}, @@ -403,19 +402,19 @@ func TestProveConsistencySameVersions(t *testing.T) { testCases := []struct { index uint64 eventDigest hashing.Digest - expectedAuditPath navigation.AuditPath + expectedAuditPath AuditPath }{ { index: 0, eventDigest: hashing.Digest{0x0}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 0).FixedBytes(): hashing.Digest{0x0}, }, }, { index: 1, eventDigest: hashing.Digest{0x1}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 0).FixedBytes(): hashing.Digest{0x0}, pos(1, 0).FixedBytes(): hashing.Digest{0x1}, }, @@ -423,7 +422,7 @@ func TestProveConsistencySameVersions(t *testing.T) { { index: 2, eventDigest: hashing.Digest{0x2}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 1).FixedBytes(): hashing.Digest{0x1}, pos(2, 0).FixedBytes(): hashing.Digest{0x2}, }, @@ -431,7 +430,7 @@ func TestProveConsistencySameVersions(t *testing.T) { { index: 3, eventDigest: hashing.Digest{0x3}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 1).FixedBytes(): hashing.Digest{0x1}, pos(2, 0).FixedBytes(): hashing.Digest{0x2}, pos(3, 0).FixedBytes(): hashing.Digest{0x3}, @@ -440,7 +439,7 @@ func TestProveConsistencySameVersions(t *testing.T) { { index: 4, eventDigest: hashing.Digest{0x4}, - expectedAuditPath: navigation.AuditPath{ + expectedAuditPath: AuditPath{ pos(0, 2).FixedBytes(): hashing.Digest{0x0}, pos(4, 0).FixedBytes(): hashing.Digest{0x4}, }, diff --git a/balloon/history/verify.go b/balloon/history/verify.go new file mode 100644 index 000000000..818b85413 --- /dev/null +++ b/balloon/history/verify.go @@ -0,0 +1,115 @@ +/* + 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 history + +import ( + "github.com/bbva/qed/hashing" +) + +func pruneToVerify(index, version uint64, eventDigest hashing.Digest) operation { + + var traverse func(pos *position) operation + traverse = func(pos *position) operation { + + if pos.IsLeaf() { + return newLeafHashOp(pos, eventDigest) + } + + var left, right operation + + rightPos := pos.Right() + if index < rightPos.Index { // go to left + left = traverse(pos.Left()) + right = newGetCacheOp(rightPos) + } else { // go to right + left = newGetCacheOp(pos.Left()) + right = traverse(rightPos) + } + + if rightPos.Index > version { // partial + return newPartialInnerHashOp(pos, left) + } + + return newInnerHashOp(pos, left, right) + + } + + return traverse(newRootPosition(version)) +} + +func pruneToVerifyIncrementalStart(version uint64) operation { + + var traverse func(pos *position) operation + traverse = func(pos *position) operation { + + if pos.IsLeaf() { + return newGetCacheOp(pos) + } + + var left, right operation + + rightPos := pos.Right() + if version < rightPos.Index { // go to left + left = traverse(pos.Left()) + right = newGetCacheOp(rightPos) + } else { // go to right + left = newGetCacheOp(pos.Left()) + right = traverse(rightPos) + } + + if rightPos.Index > version { // partial + return newPartialInnerHashOp(pos, left) + } + return newInnerHashOp(pos, left, right) + + } + + return traverse(newRootPosition(version)) +} + +func pruneToVerifyIncrementalEnd(start, end uint64) operation { + + var traverse func(pos *position, targets targets) operation + traverse = func(pos *position, targets targets) operation { + + if len(targets) == 0 { + return newGetCacheOp(pos) + } + + if pos.IsLeaf() { + return newGetCacheOp(pos) + } + + rightPos := pos.Right() + leftTargets, rightTargets := targets.Split(rightPos.Index) + + left := traverse(pos.Left(), leftTargets) + right := traverse(rightPos, rightTargets) + + if end < rightPos.Index { + return newPartialInnerHashOp(pos, left) + } + + return newInnerHashOp(pos, left, right) + + } + + targets := make(targets, 0) + targets = targets.InsertSorted(start) + targets = targets.InsertSorted(end) + return traverse(newRootPosition(end), targets) +} diff --git a/balloon/history/pruning/verify_test.go b/balloon/history/verify_test.go similarity index 82% rename from balloon/history/pruning/verify_test.go rename to balloon/history/verify_test.go index fc7b5faab..3ee35e6d0 100644 --- a/balloon/history/pruning/verify_test.go +++ b/balloon/history/verify_test.go @@ -1,4 +1,20 @@ -package pruning +/* + 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 history import ( "testing" @@ -14,7 +30,7 @@ func TestPruneToVerify(t *testing.T) { testCases := []struct { index, version uint64 eventDigest hashing.Digest - expectedOp Operation + expectedOp operation }{ { index: 0, @@ -75,7 +91,7 @@ func TestPruneToVerify(t *testing.T) { } for _, c := range testCases { - prunedOp := PruneToVerify(c.index, c.version, c.eventDigest) + prunedOp := pruneToVerify(c.index, c.version, c.eventDigest) assert.Equalf(t, c.expectedOp, prunedOp, "The pruned operation should match for test case with index %d and version %d", c.index, c.version) } @@ -85,7 +101,7 @@ func TestPruneToVerifyIncrementalEnd(t *testing.T) { testCases := []struct { index, version uint64 - expectedOp Operation + expectedOp operation }{ { index: 0, @@ -197,7 +213,7 @@ func TestPruneToVerifyIncrementalEnd(t *testing.T) { } for _, c := range testCases { - prunedOp := PruneToVerifyIncrementalEnd(c.index, c.version) + prunedOp := pruneToVerifyIncrementalEnd(c.index, c.version) assert.Equalf(t, c.expectedOp, prunedOp, "The pruned operation should match for test case with index %d and version %d", c.index, c.version) } @@ -209,7 +225,7 @@ func BenchmarkPruneToVerify(b *testing.B) { b.ResetTimer() for i := uint64(0); i < uint64(b.N); i++ { - pruned := PruneToVerify(0, i, rand.Bytes(32)) + pruned := pruneToVerify(0, i, rand.Bytes(32)) assert.NotNil(b, pruned) } @@ -221,7 +237,7 @@ func BenchmarkPruneToVerifyConsistent(b *testing.B) { b.ResetTimer() for i := uint64(0); i < uint64(b.N); i++ { - pruned := PruneToVerify(i, i, rand.Bytes(32)) + pruned := pruneToVerify(i, i, rand.Bytes(32)) assert.NotNil(b, pruned) } @@ -233,7 +249,7 @@ func BenchmarkPruneToVerifyIncrementalEnd(b *testing.B) { b.ResetTimer() for i := uint64(0); i < uint64(b.N); i++ { - pruned := PruneToVerifyIncrementalEnd(0, i) + pruned := pruneToVerifyIncrementalEnd(0, i) assert.NotNil(b, pruned) } @@ -245,7 +261,7 @@ func BenchmarkPruneToVerifyIncrementalStart(b *testing.B) { b.ResetTimer() for i := uint64(0); i < uint64(b.N); i++ { - pruned := PruneToVerifyIncrementalStart(i) + pruned := pruneToVerifyIncrementalStart(i) assert.NotNil(b, pruned) } diff --git a/balloon/test_util.go b/balloon/test_util.go index 1bd7e6ead..1007fd692 100644 --- a/balloon/test_util.go +++ b/balloon/test_util.go @@ -18,7 +18,6 @@ package balloon import ( "github.com/bbva/qed/balloon/history" - "github.com/bbva/qed/balloon/history/navigation" "github.com/bbva/qed/balloon/hyper" "github.com/bbva/qed/hashing" ) @@ -32,7 +31,7 @@ func NewFakeQueryProof(shouldVerify bool, value []byte, hasher hashing.Hasher) * func NewFakeMembershipProof(shouldVerify bool, hasher hashing.Hasher) *history.MembershipProof { if shouldVerify { - return history.NewMembershipProof(0, 0, navigation.AuditPath{}, hasher) + return history.NewMembershipProof(0, 0, history.AuditPath{}, hasher) } - return history.NewMembershipProof(1, 1, navigation.AuditPath{}, hasher) + return history.NewMembershipProof(1, 1, history.AuditPath{}, hasher) } diff --git a/protocol/protocol.go b/protocol/protocol.go index aaea66271..592c9f565 100644 --- a/protocol/protocol.go +++ b/protocol/protocol.go @@ -22,7 +22,6 @@ import ( "github.com/bbva/qed/balloon" "github.com/bbva/qed/balloon/history" - "github.com/bbva/qed/balloon/history/navigation" "github.com/bbva/qed/balloon/hyper" "github.com/bbva/qed/gossip/member" "github.com/bbva/qed/hashing" @@ -145,7 +144,7 @@ func ToBalloonProof(mr *MembershipResult, hasherF func() hashing.Hasher) *balloo historyProof := history.NewMembershipProof( mr.ActualVersion, mr.QueryVersion, - navigation.ParseAuditPath(mr.History), + history.ParseAuditPath(mr.History), hasherF(), ) @@ -179,5 +178,5 @@ func ToIncrementalResponse(proof *balloon.IncrementalProof) *IncrementalResponse } func ToIncrementalProof(ir *IncrementalResponse, hasher hashing.Hasher) *balloon.IncrementalProof { - return balloon.NewIncrementalProof(ir.Start, ir.End, navigation.ParseAuditPath(ir.AuditPath), hasher) + return balloon.NewIncrementalProof(ir.Start, ir.End, history.ParseAuditPath(ir.AuditPath), hasher) }