check.go 3 KB
Newer Older
1 2
package trie

3
import (
tavit ohanian's avatar
tavit ohanian committed
4
	"gitlab.dms3.io/p2p/go-p2p-xor/key"
5 6
)

7 8 9 10 11 12
type InvariantDiscrepancy struct {
	Reason            string
	PathToDiscrepancy string
	KeyAtDiscrepancy  string
}

13
// CheckInvariant panics of the trie does not meet its invariant.
14 15
func (trie *Trie) CheckInvariant() *InvariantDiscrepancy {
	return trie.checkInvariant(0, nil)
16 17
}

18
func (trie *Trie) checkInvariant(depth int, pathSoFar *triePath) *InvariantDiscrepancy {
19
	switch {
20 21
	case trie.IsEmptyLeaf():
		return nil
22 23
	case trie.IsNonEmptyLeaf():
		if !pathSoFar.matchesKey(trie.Key) {
24 25 26 27 28
			return &InvariantDiscrepancy{
				Reason:            "key found at invalid location in trie",
				PathToDiscrepancy: pathSoFar.BitString(),
				KeyAtDiscrepancy:  trie.Key.BitString(),
			}
29
		}
30
		return nil
31 32 33
	default:
		if trie.IsEmpty() {
			b0, b1 := trie.Branch[0], trie.Branch[1]
34 35 36 37 38 39
			if d0 := b0.checkInvariant(depth+1, pathSoFar.Push(0)); d0 != nil {
				return d0
			}
			if d1 := b1.checkInvariant(depth+1, pathSoFar.Push(1)); d1 != nil {
				return d1
			}
40 41
			switch {
			case b0.IsEmptyLeaf() && b1.IsEmptyLeaf():
42 43 44 45 46
				return &InvariantDiscrepancy{
					Reason:            "intermediate node with two empty leaves",
					PathToDiscrepancy: pathSoFar.BitString(),
					KeyAtDiscrepancy:  "none",
				}
47
			case b0.IsEmptyLeaf() && b1.IsNonEmptyLeaf():
48 49 50 51 52
				return &InvariantDiscrepancy{
					Reason:            "intermediate node with one empty leaf/0",
					PathToDiscrepancy: pathSoFar.BitString(),
					KeyAtDiscrepancy:  "none",
				}
53
			case b0.IsNonEmptyLeaf() && b1.IsEmptyLeaf():
54 55 56 57 58 59 60
				return &InvariantDiscrepancy{
					Reason:            "intermediate node with one empty leaf/1",
					PathToDiscrepancy: pathSoFar.BitString(),
					KeyAtDiscrepancy:  "none",
				}
			default:
				return nil
61 62
			}
		} else {
63 64 65 66 67
			return &InvariantDiscrepancy{
				Reason:            "intermediate node with a key",
				PathToDiscrepancy: pathSoFar.BitString(),
				KeyAtDiscrepancy:  trie.Key.BitString(),
			}
68 69 70
		}
	}
}
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

type triePath struct {
	parent *triePath
	bit    byte
}

func (p *triePath) Push(bit byte) *triePath {
	return &triePath{parent: p, bit: bit}
}

func (p *triePath) RootPath() []byte {
	if p == nil {
		return nil
	} else {
		return append(p.parent.RootPath(), p.bit)
	}
}

func (p *triePath) matchesKey(k key.Key) bool {
	// Slower, but more explicit:
91 92 93 94 95 96 97 98
	// for i, b := range p.RootPath() {
	// 	if k.BitAt(i) != b {
	// 		return false
	// 	}
	// }
	// return true
	ok, _ := p.walk(k, 0)
	return ok
99 100
}

101
func (p *triePath) walk(k key.Key, depthToLeaf int) (ok bool, depthToRoot int) {
102 103 104
	if p == nil {
		return true, 0
	} else {
105 106
		parOk, parDepthToRoot := p.parent.walk(k, depthToLeaf+1)
		return k.BitAt(parDepthToRoot) == p.bit && parOk, parDepthToRoot + 1
107 108 109
	}
}

110 111
func (p *triePath) BitString() string {
	return p.bitString(0)
112 113
}

114
func (p *triePath) bitString(depthToLeaf int) string {
115 116 117 118 119
	if p == nil {
		return ""
	} else {
		switch {
		case p.bit == 0:
120
			return p.parent.bitString(depthToLeaf+1) + "0"
121
		case p.bit == 1:
122
			return p.parent.bitString(depthToLeaf+1) + "1"
123 124 125 126 127
		default:
			panic("bit digit > 1")
		}
	}
}