github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/merkler/stacked.go (about) 1 // Copyright 2020 Insolar Network Ltd. 2 // All rights reserved. 3 // This material is licensed under the Insolar License version 1.0, 4 // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md. 5 6 package merkler 7 8 import ( 9 "github.com/insolar/vanilla/args" 10 "github.com/insolar/vanilla/cryptkit" 11 "github.com/insolar/vanilla/longbits" 12 ) 13 14 var _ cryptkit.SequenceDigester = &StackedCalculator{} 15 16 type TraceFunc func(v longbits.FoldableReader, isLeaf bool) 17 18 // Creates a sequence digester that does merkle tree calculation. 19 // When (unbalancedStub) is empty, then the resulting merkle-tree will be closed without stubbing, otherwise with per-level stubbing with (unbalancedStub) on the right side. 20 // When (traceFn) is not nil, then StackedCalculator will call it each time when a leaf or node is added. 21 // 22 // NB! For every AddNext() call the (traceFn) is always called twice - first time with (leafX, true) value, and then (nodeX, false), where nodeX can be nil. 23 // And during FinishSequence() - the (traceFn) can be called N < 2*log(leafCount) times with (nodeX, false), where nodeX can not be nil. 24 // 25 func NewStackedCalculator(digester cryptkit.PairDigester, unbalancedStub cryptkit.Digest, traceFn TraceFunc) StackedCalculator { 26 if digester == nil { 27 panic("illegal value") 28 } 29 return StackedCalculator{digester: digester, unbalancedStub: unbalancedStub, traceFn: traceFn} 30 } 31 32 // A calculator to do streaming calculation of Merkle-tree by using provided PairDigester. 33 // The resulting merkle will have same depth for all branches except for the rightmost branch. 34 // 35 // When unbalancedStub == nil, then FinishSequence() will create the rightmost branch by recursively 36 // applying the same rule - all sub-branches will have same depth except for the rightmost sub-branch. 37 // 38 // When unbalancedStub != nil, then FinishSequence() will create a perfect tree by using unbalancedStub 39 // once per level when a value for the rightmost sub-branch is missing. 40 // 41 // When AddNext() was never called then FinishSequence() will return a non-nil unbalancedStub otherwise will panic. 42 // 43 // Complexity (n - a number of added hashes): 44 // - AddNext() is O(1), it does only upto 2 calls to PairDigester.DigestPair() 45 // - FinishSequence() is O(log n), it does k*log(n) calls to PairDigester.DigestPair() where k ~ 1 when unbalancedStub == nil and k ~ 2 otherwise 46 // - ForkSequence() is O(log n), but only copies memory 47 // - Memory is O(log n) 48 // 49 type StackedCalculator struct { 50 digester cryptkit.PairDigester 51 unbalancedStub cryptkit.Digest 52 prevAdded longbits.FoldableReader 53 traceFn func(v longbits.FoldableReader, isLeaf bool) 54 count uint 55 treeLevels []treeLevel 56 finished bool 57 } 58 59 type treeLevel struct { 60 digest0 cryptkit.Digest 61 digest1 cryptkit.Digest 62 } 63 64 func (p *StackedCalculator) GetDigestMethod() cryptkit.DigestMethod { 65 return p.digester.GetDigestMethod() + "/merkle" 66 } 67 68 func (p *StackedCalculator) GetDigestSize() int { 69 return p.digester.GetDigestSize() 70 } 71 72 func (p *StackedCalculator) digest(v0, v1 longbits.FoldableReader) cryptkit.Digest { 73 d := p.digester.DigestPair(v0, v1) 74 if p.traceFn != nil { 75 p.traceFn(d, false) 76 } 77 return d 78 } 79 80 func (p *StackedCalculator) AddNext(addDigest longbits.FoldableReader) { 81 82 if p.finished || p.digester == nil { 83 panic("illegal state") 84 } 85 86 if p.traceFn != nil { 87 p.traceFn(addDigest, true) 88 } 89 90 /* 91 Here we use position of grey-code transition bit to choose the level that requires pair-hashing 92 Tree level is counted from a leaf to the root, leaf level is considered as -1 93 */ 94 95 p.count++ 96 pairPosition := args.GreyIncBit(p.count) // level + 1 97 98 var bottomDigest cryptkit.Digest 99 100 if pairPosition == 0 { 101 // Level -1 (leafs) is special - it only stores a previous value 102 bottomDigest = p.digest(p.prevAdded, addDigest) 103 p.prevAdded = nil 104 } else { 105 if p.prevAdded != nil { 106 panic("illegal state") 107 } 108 p.prevAdded = addDigest 109 110 if int(pairPosition) > len(p.treeLevels) { 111 if p.traceFn != nil { 112 p.traceFn(nil, false) 113 } 114 return 115 } 116 pairLevel := &p.treeLevels[pairPosition-1] 117 bottomDigest = p.digest(pairLevel.digest0, pairLevel.digest1) 118 pairLevel.digest0, pairLevel.digest1 = cryptkit.Digest{}, cryptkit.Digest{} 119 } 120 121 if int(pairPosition) == len(p.treeLevels) { 122 p.treeLevels = append(p.treeLevels, treeLevel{digest0: bottomDigest}) 123 return 124 } 125 126 var d *cryptkit.Digest 127 if p.count&(uint(2)<<pairPosition) != 0 { 128 d = &p.treeLevels[pairPosition].digest0 129 } else { 130 d = &p.treeLevels[pairPosition].digest1 131 } 132 if !d.IsEmpty() { 133 panic("illegal state") 134 } 135 *d = bottomDigest 136 } 137 138 func (p *StackedCalculator) Count() int { 139 return int(p.count) 140 } 141 142 func (p *StackedCalculator) FinishSequence() cryptkit.Digest { 143 144 if p.finished || p.digester == nil { 145 panic("illegal state") 146 } 147 p.finished = true 148 149 hasStub := !p.unbalancedStub.IsEmpty() 150 if p.count == 0 { 151 if hasStub { 152 return p.unbalancedStub 153 } 154 panic("illegal state - empty") 155 } 156 157 var bottomDigest cryptkit.Digest 158 if p.prevAdded != nil { 159 if hasStub { 160 bottomDigest = p.digest(p.prevAdded, p.unbalancedStub) 161 } else { 162 bottomDigest = cryptkit.NewDigest(p.prevAdded, p.digester.GetDigestMethod()) 163 } 164 } 165 166 // TODO refactor/simplify 167 for i := 0; i < len(p.treeLevels); i++ { // DONT USE range as treeLevels can be appended inside the loop! 168 curLevel := &p.treeLevels[i] 169 170 switch { 171 case !curLevel.digest1.IsEmpty(): 172 // both are present 173 if curLevel.digest0.IsEmpty() { 174 panic("illegal state") 175 } 176 levelDigest := p.digest(curLevel.digest0, curLevel.digest1) 177 178 if bottomDigest.IsEmpty() { 179 bottomDigest = levelDigest 180 continue 181 } 182 183 if i+1 == len(p.treeLevels) { 184 p.treeLevels = append(p.treeLevels, treeLevel{digest0: levelDigest}) 185 } else { 186 nextLevel := &p.treeLevels[i+1] 187 switch { 188 case nextLevel.digest0.IsEmpty(): 189 if !nextLevel.digest1.IsEmpty() { 190 panic("illegal state") 191 } 192 nextLevel.digest0 = levelDigest 193 case nextLevel.digest1.IsEmpty(): 194 nextLevel.digest1 = levelDigest 195 default: 196 panic("illegal state") 197 } 198 } 199 if hasStub { 200 bottomDigest = p.digest(bottomDigest, p.unbalancedStub) 201 } 202 // or leave as is 203 case !curLevel.digest0.IsEmpty(): 204 switch { 205 case !bottomDigest.IsEmpty(): 206 bottomDigest = p.digest(curLevel.digest0, bottomDigest) 207 case hasStub && i < len(p.treeLevels)-1: // never stub the last level 208 bottomDigest = p.digest(curLevel.digest0, p.unbalancedStub) 209 default: 210 bottomDigest = curLevel.digest0 211 } 212 case !bottomDigest.IsEmpty() && hasStub: 213 bottomDigest = p.digest(bottomDigest, p.unbalancedStub) 214 } 215 } 216 217 // return p.treeLevels[len(p.treeLevels)-1].digest0 218 return bottomDigest 219 }