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<