diff --git a/internal/trie/node/branch.go b/internal/trie/node/branch.go index f4d66397468..dae232292b2 100644 --- a/internal/trie/node/branch.go +++ b/internal/trie/node/branch.go @@ -20,7 +20,7 @@ type Branch struct { // Dirty is true when the branch differs // from the node stored in the database. Dirty bool - hashDigest []byte + HashDigest []byte Encoding []byte // Generation is incremented on every trie Snapshot() call. // Each node also contain a certain Generation number, diff --git a/internal/trie/node/copy.go b/internal/trie/node/copy.go index cbdbb874cd8..eff6e8c9698 100644 --- a/internal/trie/node/copy.go +++ b/internal/trie/node/copy.go @@ -37,9 +37,9 @@ func (b *Branch) Copy(copyChildren bool) Node { copy(cpy.Value, b.Value) } - if b.hashDigest != nil { - cpy.hashDigest = make([]byte, len(b.hashDigest)) - copy(cpy.hashDigest, b.hashDigest) + if b.HashDigest != nil { + cpy.HashDigest = make([]byte, len(b.HashDigest)) + copy(cpy.HashDigest, b.HashDigest) } if b.Encoding != nil { @@ -74,9 +74,9 @@ func (l *Leaf) Copy(_ bool) Node { copy(cpy.Value, l.Value) } - if l.hashDigest != nil { - cpy.hashDigest = make([]byte, len(l.hashDigest)) - copy(cpy.hashDigest, l.hashDigest) + if l.HashDigest != nil { + cpy.HashDigest = make([]byte, len(l.HashDigest)) + copy(cpy.HashDigest, l.HashDigest) } if l.Encoding != nil { diff --git a/internal/trie/node/copy_test.go b/internal/trie/node/copy_test.go index 91d6b04485d..8210547c61c 100644 --- a/internal/trie/node/copy_test.go +++ b/internal/trie/node/copy_test.go @@ -41,7 +41,7 @@ func Test_Branch_Copy(t *testing.T) { nil, nil, &Leaf{Key: []byte{9}}, }, Dirty: true, - hashDigest: []byte{5}, + HashDigest: []byte{5}, Encoding: []byte{6}, }, expectedBranch: &Branch{ @@ -51,7 +51,7 @@ func Test_Branch_Copy(t *testing.T) { nil, nil, &Leaf{Key: []byte{9}}, }, Dirty: true, - hashDigest: []byte{5}, + HashDigest: []byte{5}, Encoding: []byte{6}, }, }, @@ -83,7 +83,7 @@ func Test_Branch_Copy(t *testing.T) { assert.Equal(t, testCase.expectedBranch, branchCopy) testForSliceModif(t, testCase.branch.Key, branchCopy.Key) testForSliceModif(t, testCase.branch.Value, branchCopy.Value) - testForSliceModif(t, testCase.branch.hashDigest, branchCopy.hashDigest) + testForSliceModif(t, testCase.branch.HashDigest, branchCopy.HashDigest) testForSliceModif(t, testCase.branch.Encoding, branchCopy.Encoding) testCase.branch.Children[15] = &Leaf{Key: []byte("modified")} @@ -108,14 +108,14 @@ func Test_Leaf_Copy(t *testing.T) { Key: []byte{1, 2}, Value: []byte{3, 4}, Dirty: true, - hashDigest: []byte{5}, + HashDigest: []byte{5}, Encoding: []byte{6}, }, expectedLeaf: &Leaf{ Key: []byte{1, 2}, Value: []byte{3, 4}, Dirty: true, - hashDigest: []byte{5}, + HashDigest: []byte{5}, Encoding: []byte{6}, }, }, @@ -135,7 +135,7 @@ func Test_Leaf_Copy(t *testing.T) { assert.Equal(t, testCase.expectedLeaf, leafCopy) testForSliceModif(t, testCase.leaf.Key, leafCopy.Key) testForSliceModif(t, testCase.leaf.Value, leafCopy.Value) - testForSliceModif(t, testCase.leaf.hashDigest, leafCopy.hashDigest) + testForSliceModif(t, testCase.leaf.HashDigest, leafCopy.HashDigest) testForSliceModif(t, testCase.leaf.Encoding, leafCopy.Encoding) }) } diff --git a/internal/trie/node/decode.go b/internal/trie/node/decode.go index 86d9e788667..c67e84e49af 100644 --- a/internal/trie/node/decode.go +++ b/internal/trie/node/decode.go @@ -106,7 +106,7 @@ func decodeBranch(reader io.Reader, header byte) (branch *Branch, err error) { } branch.Children[i] = &Leaf{ - hashDigest: hash, + HashDigest: hash, } } diff --git a/internal/trie/node/decode_test.go b/internal/trie/node/decode_test.go index 2be148c3227..c6f683aece3 100644 --- a/internal/trie/node/decode_test.go +++ b/internal/trie/node/decode_test.go @@ -173,7 +173,7 @@ func Test_decodeBranch(t *testing.T) { nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &Leaf{ - hashDigest: []byte{1, 2, 3, 4, 5}, + HashDigest: []byte{1, 2, 3, 4, 5}, }, }, Dirty: true, @@ -208,7 +208,7 @@ func Test_decodeBranch(t *testing.T) { nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &Leaf{ - hashDigest: []byte{1, 2, 3, 4, 5}, + HashDigest: []byte{1, 2, 3, 4, 5}, }, }, Dirty: true, diff --git a/internal/trie/node/encode_decode_test.go b/internal/trie/node/encode_decode_test.go index 9c3b9af2c81..f4bb1686971 100644 --- a/internal/trie/node/encode_decode_test.go +++ b/internal/trie/node/encode_decode_test.go @@ -57,7 +57,7 @@ func Test_Branch_Encode_Decode(t *testing.T) { Key: []byte{5}, Children: [16]Node{ &Leaf{ - hashDigest: []byte{0x41, 0x9, 0x4, 0xa}, + HashDigest: []byte{0x41, 0x9, 0x4, 0xa}, }, }, Dirty: true, diff --git a/internal/trie/node/hash.go b/internal/trie/node/hash.go index f7ae910266d..218fb293d30 100644 --- a/internal/trie/node/hash.go +++ b/internal/trie/node/hash.go @@ -14,7 +14,7 @@ import ( // given to the branch. Note it does not copy them, so beware. func (b *Branch) SetEncodingAndHash(enc, hash []byte) { b.Encoding = enc - b.hashDigest = hash + b.HashDigest = hash } // GetHash returns the hash of the branch. @@ -22,7 +22,7 @@ func (b *Branch) SetEncodingAndHash(enc, hash []byte) { // the returned hash will modify the hash // of the branch. func (b *Branch) GetHash() []byte { - return b.hashDigest + return b.HashDigest } // EncodeAndHash returns the encoding of the branch and @@ -30,8 +30,8 @@ func (b *Branch) GetHash() []byte { // If the encoding is less than 32 bytes, the hash returned // is the encoding and not the hash of the encoding. func (b *Branch) EncodeAndHash() (encoding, hash []byte, err error) { - if !b.Dirty && b.Encoding != nil && b.hashDigest != nil { - return b.Encoding, b.hashDigest, nil + if !b.Dirty && b.Encoding != nil && b.HashDigest != nil { + return b.Encoding, b.HashDigest, nil } buffer := pools.EncodingBuffers.Get().(*bytes.Buffer) @@ -50,9 +50,9 @@ func (b *Branch) EncodeAndHash() (encoding, hash []byte, err error) { encoding = b.Encoding // no need to copy if buffer.Len() < 32 { - b.hashDigest = make([]byte, len(bufferBytes)) - copy(b.hashDigest, bufferBytes) - hash = b.hashDigest // no need to copy + b.HashDigest = make([]byte, len(bufferBytes)) + copy(b.HashDigest, bufferBytes) + hash = b.HashDigest // no need to copy return encoding, hash, nil } @@ -61,8 +61,8 @@ func (b *Branch) EncodeAndHash() (encoding, hash []byte, err error) { if err != nil { return nil, nil, err } - b.hashDigest = hashArray[:] - hash = b.hashDigest // no need to copy + b.HashDigest = hashArray[:] + hash = b.HashDigest // no need to copy return encoding, hash, nil } @@ -73,7 +73,7 @@ func (l *Leaf) SetEncodingAndHash(enc, hash []byte) { l.encodingMu.Lock() l.Encoding = enc l.encodingMu.Unlock() - l.hashDigest = hash + l.HashDigest = hash } // GetHash returns the hash of the leaf. @@ -81,7 +81,7 @@ func (l *Leaf) SetEncodingAndHash(enc, hash []byte) { // the returned hash will modify the hash // of the branch. func (l *Leaf) GetHash() []byte { - return l.hashDigest + return l.HashDigest } // EncodeAndHash returns the encoding of the leaf and @@ -90,9 +90,9 @@ func (l *Leaf) GetHash() []byte { // is the encoding and not the hash of the encoding. func (l *Leaf) EncodeAndHash() (encoding, hash []byte, err error) { l.encodingMu.RLock() - if !l.IsDirty() && l.Encoding != nil && l.hashDigest != nil { + if !l.IsDirty() && l.Encoding != nil && l.HashDigest != nil { l.encodingMu.RUnlock() - return l.Encoding, l.hashDigest, nil + return l.Encoding, l.HashDigest, nil } l.encodingMu.RUnlock() @@ -116,9 +116,9 @@ func (l *Leaf) EncodeAndHash() (encoding, hash []byte, err error) { encoding = l.Encoding // no need to copy if len(bufferBytes) < 32 { - l.hashDigest = make([]byte, len(bufferBytes)) - copy(l.hashDigest, bufferBytes) - hash = l.hashDigest // no need to copy + l.HashDigest = make([]byte, len(bufferBytes)) + copy(l.HashDigest, bufferBytes) + hash = l.HashDigest // no need to copy return encoding, hash, nil } @@ -128,8 +128,8 @@ func (l *Leaf) EncodeAndHash() (encoding, hash []byte, err error) { return nil, nil, err } - l.hashDigest = hashArray[:] - hash = l.hashDigest // no need to copy + l.HashDigest = hashArray[:] + hash = l.HashDigest // no need to copy return encoding, hash, nil } diff --git a/internal/trie/node/hash_test.go b/internal/trie/node/hash_test.go index 7bc59de4640..32532d05b17 100644 --- a/internal/trie/node/hash_test.go +++ b/internal/trie/node/hash_test.go @@ -14,13 +14,13 @@ func Test_Branch_SetEncodingAndHash(t *testing.T) { branch := &Branch{ Encoding: []byte{2}, - hashDigest: []byte{3}, + HashDigest: []byte{3}, } branch.SetEncodingAndHash([]byte{4}, []byte{5}) expectedBranch := &Branch{ Encoding: []byte{4}, - hashDigest: []byte{5}, + HashDigest: []byte{5}, } assert.Equal(t, expectedBranch, branch) } @@ -29,7 +29,7 @@ func Test_Branch_GetHash(t *testing.T) { t.Parallel() branch := &Branch{ - hashDigest: []byte{3}, + HashDigest: []byte{3}, } hash := branch.GetHash() @@ -52,7 +52,7 @@ func Test_Branch_EncodeAndHash(t *testing.T) { branch: &Branch{}, expectedBranch: &Branch{ Encoding: []byte{0x80, 0x0, 0x0}, - hashDigest: []byte{0x80, 0x0, 0x0}, + HashDigest: []byte{0x80, 0x0, 0x0}, }, encoding: []byte{0x80, 0x0, 0x0}, hash: []byte{0x80, 0x0, 0x0}, @@ -64,7 +64,7 @@ func Test_Branch_EncodeAndHash(t *testing.T) { }, expectedBranch: &Branch{ Encoding: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, - hashDigest: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, + HashDigest: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, }, encoding: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, hash: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, @@ -75,11 +75,11 @@ func Test_Branch_EncodeAndHash(t *testing.T) { Value: []byte{2}, Dirty: true, Encoding: []byte{3}, - hashDigest: []byte{4}, + HashDigest: []byte{4}, }, expectedBranch: &Branch{ Encoding: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, - hashDigest: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, + HashDigest: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, }, encoding: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, hash: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, @@ -90,13 +90,13 @@ func Test_Branch_EncodeAndHash(t *testing.T) { Value: []byte{2}, Dirty: false, Encoding: []byte{3}, - hashDigest: []byte{4}, + HashDigest: []byte{4}, }, expectedBranch: &Branch{ Key: []byte{1}, Value: []byte{2}, Encoding: []byte{3}, - hashDigest: []byte{4}, + HashDigest: []byte{4}, }, encoding: []byte{3}, hash: []byte{4}, @@ -107,7 +107,7 @@ func Test_Branch_EncodeAndHash(t *testing.T) { }, expectedBranch: &Branch{ Encoding: []byte{0xbf, 0x2, 0x7, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x0, 0x0}, //nolint:lll - hashDigest: []byte{0x6b, 0xd8, 0xcc, 0xac, 0x71, 0x77, 0x44, 0x17, 0xfe, 0xe0, 0xde, 0xda, 0xd5, 0x97, 0x6e, 0x69, 0xeb, 0xe9, 0xdd, 0x80, 0x1d, 0x4b, 0x51, 0xf1, 0x5b, 0xf3, 0x4a, 0x93, 0x27, 0x32, 0x2c, 0xb0}, //nolint:lll + HashDigest: []byte{0x6b, 0xd8, 0xcc, 0xac, 0x71, 0x77, 0x44, 0x17, 0xfe, 0xe0, 0xde, 0xda, 0xd5, 0x97, 0x6e, 0x69, 0xeb, 0xe9, 0xdd, 0x80, 0x1d, 0x4b, 0x51, 0xf1, 0x5b, 0xf3, 0x4a, 0x93, 0x27, 0x32, 0x2c, 0xb0}, //nolint:lll }, encoding: []byte{0xbf, 0x2, 0x7, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x0, 0x0}, //nolint:lll hash: []byte{0x6b, 0xd8, 0xcc, 0xac, 0x71, 0x77, 0x44, 0x17, 0xfe, 0xe0, 0xde, 0xda, 0xd5, 0x97, 0x6e, 0x69, 0xeb, 0xe9, 0xdd, 0x80, 0x1d, 0x4b, 0x51, 0xf1, 0x5b, 0xf3, 0x4a, 0x93, 0x27, 0x32, 0x2c, 0xb0}, //nolint:lll @@ -136,13 +136,13 @@ func Test_Leaf_SetEncodingAndHash(t *testing.T) { leaf := &Leaf{ Encoding: []byte{2}, - hashDigest: []byte{3}, + HashDigest: []byte{3}, } leaf.SetEncodingAndHash([]byte{4}, []byte{5}) expectedLeaf := &Leaf{ Encoding: []byte{4}, - hashDigest: []byte{5}, + HashDigest: []byte{5}, } assert.Equal(t, expectedLeaf, leaf) } @@ -151,7 +151,7 @@ func Test_Leaf_GetHash(t *testing.T) { t.Parallel() leaf := &Leaf{ - hashDigest: []byte{3}, + HashDigest: []byte{3}, } hash := leaf.GetHash() @@ -174,7 +174,7 @@ func Test_Leaf_EncodeAndHash(t *testing.T) { leaf: &Leaf{}, expectedLeaf: &Leaf{ Encoding: []byte{0x40, 0x0}, - hashDigest: []byte{0x40, 0x0}, + HashDigest: []byte{0x40, 0x0}, }, encoding: []byte{0x40, 0x0}, hash: []byte{0x40, 0x0}, @@ -186,7 +186,7 @@ func Test_Leaf_EncodeAndHash(t *testing.T) { }, expectedLeaf: &Leaf{ Encoding: []byte{0x41, 0x1, 0x4, 0x2}, - hashDigest: []byte{0x41, 0x1, 0x4, 0x2}, + HashDigest: []byte{0x41, 0x1, 0x4, 0x2}, }, encoding: []byte{0x41, 0x1, 0x4, 0x2}, hash: []byte{0x41, 0x1, 0x4, 0x2}, @@ -197,11 +197,11 @@ func Test_Leaf_EncodeAndHash(t *testing.T) { Value: []byte{2}, Dirty: true, Encoding: []byte{3}, - hashDigest: []byte{4}, + HashDigest: []byte{4}, }, expectedLeaf: &Leaf{ Encoding: []byte{0x41, 0x1, 0x4, 0x2}, - hashDigest: []byte{0x41, 0x1, 0x4, 0x2}, + HashDigest: []byte{0x41, 0x1, 0x4, 0x2}, }, encoding: []byte{0x41, 0x1, 0x4, 0x2}, hash: []byte{0x41, 0x1, 0x4, 0x2}, @@ -212,13 +212,13 @@ func Test_Leaf_EncodeAndHash(t *testing.T) { Value: []byte{2}, Dirty: false, Encoding: []byte{3}, - hashDigest: []byte{4}, + HashDigest: []byte{4}, }, expectedLeaf: &Leaf{ Key: []byte{1}, Value: []byte{2}, Encoding: []byte{3}, - hashDigest: []byte{4}, + HashDigest: []byte{4}, }, encoding: []byte{3}, hash: []byte{4}, @@ -229,7 +229,7 @@ func Test_Leaf_EncodeAndHash(t *testing.T) { }, expectedLeaf: &Leaf{ Encoding: []byte{0x7f, 0x2, 0x7, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x0}, //nolint:lll - hashDigest: []byte{0xfb, 0xae, 0x31, 0x4b, 0xef, 0x31, 0x9, 0xc7, 0x62, 0x99, 0x9d, 0x40, 0x9b, 0xd4, 0xdc, 0x64, 0xe7, 0x39, 0x46, 0x8b, 0xd3, 0xaf, 0xe8, 0x63, 0x9d, 0xf9, 0x41, 0x40, 0x76, 0x40, 0x10, 0xa3}, //nolint:lll + HashDigest: []byte{0xfb, 0xae, 0x31, 0x4b, 0xef, 0x31, 0x9, 0xc7, 0x62, 0x99, 0x9d, 0x40, 0x9b, 0xd4, 0xdc, 0x64, 0xe7, 0x39, 0x46, 0x8b, 0xd3, 0xaf, 0xe8, 0x63, 0x9d, 0xf9, 0x41, 0x40, 0x76, 0x40, 0x10, 0xa3}, //nolint:lll }, encoding: []byte{0x7f, 0x2, 0x7, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x0}, //nolint:lll hash: []byte{0xfb, 0xae, 0x31, 0x4b, 0xef, 0x31, 0x9, 0xc7, 0x62, 0x99, 0x9d, 0x40, 0x9b, 0xd4, 0xdc, 0x64, 0xe7, 0x39, 0x46, 0x8b, 0xd3, 0xaf, 0xe8, 0x63, 0x9d, 0xf9, 0x41, 0x40, 0x76, 0x40, 0x10, 0xa3}, //nolint:lll diff --git a/internal/trie/node/leaf.go b/internal/trie/node/leaf.go index e25406f69d9..b5a5e02b2a8 100644 --- a/internal/trie/node/leaf.go +++ b/internal/trie/node/leaf.go @@ -20,7 +20,7 @@ type Leaf struct { // Dirty is true when the branch differs // from the node stored in the database. Dirty bool - hashDigest []byte + HashDigest []byte Encoding []byte encodingMu sync.RWMutex // Generation is incremented on every trie Snapshot() call. @@ -58,7 +58,7 @@ func (l *Leaf) StringNode() (stringNode *gotree.Node) { stringNode.Appendf("Key: " + bytesToString(l.Key)) stringNode.Appendf("Value: " + bytesToString(l.Value)) stringNode.Appendf("Calculated encoding: " + bytesToString(l.Encoding)) - stringNode.Appendf("Calculated digest: " + bytesToString(l.hashDigest)) + stringNode.Appendf("Calculated digest: " + bytesToString(l.HashDigest)) return stringNode } diff --git a/lib/trie/buffer_mock_test.go b/lib/trie/buffer_mock_test.go new file mode 100644 index 00000000000..6f76b676c41 --- /dev/null +++ b/lib/trie/buffer_mock_test.go @@ -0,0 +1,77 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/internal/trie/node (interfaces: Buffer) + +// Package trie is a generated GoMock package. +package trie + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockBuffer is a mock of Buffer interface. +type MockBuffer struct { + ctrl *gomock.Controller + recorder *MockBufferMockRecorder +} + +// MockBufferMockRecorder is the mock recorder for MockBuffer. +type MockBufferMockRecorder struct { + mock *MockBuffer +} + +// NewMockBuffer creates a new mock instance. +func NewMockBuffer(ctrl *gomock.Controller) *MockBuffer { + mock := &MockBuffer{ctrl: ctrl} + mock.recorder = &MockBufferMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBuffer) EXPECT() *MockBufferMockRecorder { + return m.recorder +} + +// Bytes mocks base method. +func (m *MockBuffer) Bytes() []byte { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Bytes") + ret0, _ := ret[0].([]byte) + return ret0 +} + +// Bytes indicates an expected call of Bytes. +func (mr *MockBufferMockRecorder) Bytes() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bytes", reflect.TypeOf((*MockBuffer)(nil).Bytes)) +} + +// Len mocks base method. +func (m *MockBuffer) Len() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Len") + ret0, _ := ret[0].(int) + return ret0 +} + +// Len indicates an expected call of Len. +func (mr *MockBufferMockRecorder) Len() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Len", reflect.TypeOf((*MockBuffer)(nil).Len)) +} + +// Write mocks base method. +func (m *MockBuffer) Write(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Write indicates an expected call of Write. +func (mr *MockBufferMockRecorder) Write(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockBuffer)(nil).Write), arg0) +} diff --git a/lib/trie/helpers_test.go b/lib/trie/helpers_test.go new file mode 100644 index 00000000000..9e8881d6e8f --- /dev/null +++ b/lib/trie/helpers_test.go @@ -0,0 +1,16 @@ +// Copyright 2022 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package trie + +import ( + "errors" +) + +type writeCall struct { + written []byte + n int + err error +} + +var errTest = errors.New("test error") diff --git a/lib/trie/trie.go b/lib/trie/trie.go index f0bb4b54631..bcfbd7e860a 100644 --- a/lib/trie/trie.go +++ b/lib/trie/trie.go @@ -273,13 +273,12 @@ func nextKey(curr Node, prefix, key []byte) []byte { // Put inserts a key with value into the trie func (t *Trie) Put(key, value []byte) { - t.tryPut(key, value) + nibblesKey := codec.KeyLEToNibbles(key) + t.tryPut(nibblesKey, value) } func (t *Trie) tryPut(key, value []byte) { - k := codec.KeyLEToNibbles(key) - - t.root = t.insert(t.root, k, node.NewLeaf(nil, value, true, t.generation)) + t.root = t.insert(t.root, key, node.NewLeaf(nil, value, true, t.generation)) } // insert attempts to insert a key with value into the trie @@ -303,7 +302,7 @@ func (t *Trie) insert(parent Node, key []byte, value Node) Node { p := newParent.(*node.Leaf) // if a value already exists in the trie at this key, overwrite it with the new value // if the values are the same, don't mark node dirty - if p.Value != nil && bytes.Equal(p.Key, key) { + if bytes.Equal(p.Key, key) { if !bytes.Equal(value.(*node.Leaf).Value, p.Value) { p.Value = value.(*node.Leaf).Value p.SetDirty(true) @@ -559,6 +558,12 @@ func (t *Trie) clearPrefixLimit(cn Node, prefix []byte, limit *uint32) (Node, bo if len(prefix) == len(c.Key)+1 && length == len(prefix)-1 { i := prefix[len(c.Key)] + + if c.Children[i] == nil { + // child is already nil at the child index + return c, false, true + } + c.Children[i] = t.deleteNodes(c.Children[i], []byte{}, limit) c.SetDirty(true) @@ -677,6 +682,12 @@ func (t *Trie) clearPrefix(cn Node, prefix []byte) (Node, bool) { if len(prefix) == len(c.Key)+1 && length == len(prefix)-1 { // found prefix at child index, delete child i := prefix[len(c.Key)] + + if c.Children[i] == nil { + // child is already nil at the child index + return c, false + } + c.Children[i] = nil c.SetDirty(true) curr = handleDeletion(c, prefix) @@ -733,7 +744,8 @@ func (t *Trie) delete(parent Node, key []byte) (Node, bool) { n, del := t.delete(p.Children[key[length]], key[length+1:]) if !del { // If nothing was deleted then don't copy the path. - return p, false + // Return the parent without its generation updated. + return parent, false } p.Children[key[length]] = n diff --git a/lib/trie/trie_endtoend_test.go b/lib/trie/trie_endtoend_test.go index d33c856027a..f39a184cab9 100644 --- a/lib/trie/trie_endtoend_test.go +++ b/lib/trie/trie_endtoend_test.go @@ -25,34 +25,6 @@ import ( "github.com/ChainSafe/gossamer/lib/common" ) -type commonPrefixTest struct { - a, b []byte - output int -} - -var commonPrefixTests = []commonPrefixTest{ - {a: []byte{}, b: []byte{}, output: 0}, - {a: []byte{0x00}, b: []byte{}, output: 0}, - {a: []byte{0x00}, b: []byte{0x00}, output: 1}, - {a: []byte{0x00}, b: []byte{0x00, 0x01}, output: 1}, - {a: []byte{0x01}, b: []byte{0x00, 0x01, 0x02}, output: 0}, - {a: []byte{0x00, 0x01, 0x02, 0x00}, b: []byte{0x00, 0x01, 0x02}, output: 3}, - {a: []byte{0x00, 0x01, 0x02, 0x00, 0xff}, b: []byte{0x00, 0x01, 0x02, 0x00}, output: 4}, - {a: []byte{0x00, 0x01, 0x02, 0x00, 0xff}, b: []byte{0x00, 0x01, 0x02, 0x00, 0xff, 0x00}, output: 5}, -} - -func TestCommonPrefix(t *testing.T) { - for i, test := range commonPrefixTests { - test := test - t.Run(strconv.Itoa(i), func(t *testing.T) { - output := lenCommonPrefix(test.a, test.b) - if output != test.output { - t.Errorf("Fail: got %d expected %d", output, test.output) - } - }) - } -} - //nolint:revive const ( PUT = 0 @@ -62,46 +34,6 @@ const ( GETLEAF = 4 ) -func TestNewEmptyTrie(t *testing.T) { - trie := NewEmptyTrie() - if trie == nil { - t.Error("did not initialise trie") - } -} - -func TestNewTrie(t *testing.T) { - trie := NewTrie(&node.Leaf{Key: []byte{0}, Value: []byte{17}}) - if trie == nil { - t.Error("did not initialise trie") - } -} - -func TestEntries(t *testing.T) { - trie := NewEmptyTrie() - - tests := []Test{ - {key: []byte{0x01, 0x35}, value: []byte("pen")}, - {key: []byte{0x01, 0x35, 0x79}, value: []byte("penguin")}, - {key: []byte{0xf2}, value: []byte("feather")}, - {key: []byte{0x09, 0xd3}, value: []byte("noot")}, - } - - for _, test := range tests { - trie.Put(test.key, test.value) - } - - entries := trie.Entries() - if len(entries) != len(tests) { - t.Fatal("length of trie.Entries does not equal length of values put into trie") - } - - for _, test := range tests { - if entries[string(test.key)] == nil { - t.Fatal("did not get entry in trie") - } - } -} - func hexDecode(in string) []byte { out, _ := hex.DecodeString(in) return out @@ -170,43 +102,6 @@ func runTests(t *testing.T, trie *Trie, tests []Test) { } } -func TestLoadTrieFromMap(t *testing.T) { - data := map[string]string{"0x1234": "0x5678", "0xaabbcc": "0xddeeff"} - testTrie := &Trie{} - - err := testTrie.LoadFromMap(data) - if err != nil { - t.Fatal(err) - } - - expectedTrie := &Trie{} - var keyBytes, valueBytes []byte - for key, value := range data { - keyBytes, err = common.HexToBytes(key) - if err != nil { - t.Fatal(err) - } - valueBytes, err = common.HexToBytes(value) - if err != nil { - t.Fatal(err) - } - expectedTrie.Put(keyBytes, valueBytes) - } - - testhash, err := testTrie.Hash() - if err != nil { - t.Fatal(err) - } - expectedhash, err := expectedTrie.Hash() - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(testhash[:], expectedhash[:]) { - t.Fatalf("Fail: got %x expected %x", testhash, expectedhash) - } -} - func TestPutAndGetBranch(t *testing.T) { trie := NewEmptyTrie() @@ -583,230 +478,6 @@ func TestDelete(t *testing.T) { require.Equal(t, dcTrieHash, tHash) } -func TestGetKeysWithPrefix(t *testing.T) { - trie := NewEmptyTrie() - - tests := []Test{ - {key: []byte{0x01, 0x35}, value: []byte("spaghetti"), op: PUT}, - {key: []byte{0x01, 0x35, 0x79}, value: []byte("gnocchi"), op: PUT}, - {key: []byte{0x07, 0x3a}, value: []byte("ramen"), op: PUT}, - {key: []byte{0x07, 0x3b}, value: []byte("noodles"), op: PUT}, - {key: []byte{0xf2}, value: []byte("pho"), op: PUT}, - {key: []byte(":key1"), value: []byte("value1"), op: PUT}, - {key: []byte(":key2"), value: []byte("value2"), op: PUT}, - {key: []byte{0xff, 0xee, 0xdd, 0xcc, 0xbb, 0x11}, value: []byte("asd"), op: PUT}, - {key: []byte{0xff, 0xee, 0xdd, 0xcc, 0xaa, 0x11}, value: []byte("fgh"), op: PUT}, - } - - for _, test := range tests { - trie.Put(test.key, test.value) - } - - expected := [][]byte{{0x01, 0x35}, {0x01, 0x35, 0x79}} - keys := trie.GetKeysWithPrefix([]byte{0x01}) - require.Equal(t, expected, keys) - - expected = [][]byte{{0x01, 0x35}, {0x01, 0x35, 0x79}, {0x07, 0x3a}, {0x07, 0x3b}} - keys = trie.GetKeysWithPrefix([]byte{0x0}) - require.Equal(t, expected, keys) - - expected = [][]byte{{0x07, 0x3a}, {0x07, 0x3b}} - keys = trie.GetKeysWithPrefix([]byte{0x07, 0x30}) - require.Equal(t, expected, keys) - - expected = [][]byte{[]byte(":key1")} - keys = trie.GetKeysWithPrefix([]byte(":key1")) - require.Equal(t, expected, keys) - - expected = [][]byte{} - keys = trie.GetKeysWithPrefix([]byte{0xff, 0xee, 0xbb, 0xcc, 0xbb, 0x11}) - require.Equal(t, expected, keys) -} - -func TestNextKey(t *testing.T) { - trie := NewEmptyTrie() - - tests := []Test{ - {key: []byte{0x01, 0x35}, value: []byte("spaghetti"), op: PUT}, - {key: []byte{0x01, 0x35, 0x79}, value: []byte("gnocchi"), op: PUT}, - {key: []byte{0x01, 0x35, 0x7a}, value: []byte("gnocchi"), op: PUT}, - {key: []byte{0x07, 0x3a}, value: []byte("ramen"), op: PUT}, - {key: []byte{0x07, 0x3b}, value: []byte("noodles"), op: PUT}, - {key: []byte{0xf2}, value: []byte("pho"), op: PUT}, - } - - for _, test := range tests { - trie.Put(test.key, test.value) - } - - testCases := []struct { - input []byte - expected []byte - }{ - { - tests[0].key, - tests[1].key, - }, - { - tests[1].key, - tests[2].key, - }, - { - tests[2].key, - tests[3].key, - }, - { - tests[3].key, - tests[4].key, - }, - { - tests[4].key, - tests[5].key, - }, - { - tests[5].key, - nil, - }, - } - - for _, tc := range testCases { - next := trie.NextKey(tc.input) - require.Equal(t, tc.expected, next) - } -} - -func TestNextKey_MoreAncestors(t *testing.T) { - trie := NewEmptyTrie() - - tests := []Test{ - {key: []byte{0x01, 0x35}, value: []byte("spaghetti"), op: PUT}, - {key: []byte{0x01, 0x35, 0x79}, value: []byte("gnocchi"), op: PUT}, - {key: []byte{0x01, 0x35, 0x79, 0xab}, value: []byte("spaghetti"), op: PUT}, - {key: []byte{0x01, 0x35, 0x79, 0xab, 0x9}, value: []byte("gnocchi"), op: PUT}, - {key: []byte{0x01, 0x35, 0x79, 0xab, 0xf}, value: []byte("gnocchi"), op: PUT}, - {key: []byte{0x07, 0x3a}, value: []byte("ramen"), op: PUT}, - {key: []byte{0x07, 0x3b}, value: []byte("noodles"), op: PUT}, - {key: []byte{0xf2}, value: []byte("pho"), op: PUT}, - } - - for _, test := range tests { - trie.Put(test.key, test.value) - } - - testCases := []struct { - input []byte - expected []byte - }{ - { - tests[0].key, - tests[1].key, - }, - { - tests[1].key, - tests[2].key, - }, - { - tests[2].key, - tests[3].key, - }, - { - tests[3].key, - tests[4].key, - }, - { - tests[4].key, - tests[5].key, - }, - { - tests[5].key, - tests[6].key, - }, - { - tests[6].key, - tests[7].key, - }, - { - tests[7].key, - nil, - }, - { - []byte{}, - tests[0].key, - }, - { - []byte{0}, - tests[0].key, - }, - { - []byte{0x01}, - tests[0].key, - }, - { - []byte{0x02}, - tests[5].key, - }, - { - []byte{0x05, 0x12, 0x34}, - tests[5].key, - }, - { - []byte{0xf}, - tests[7].key, - }, - } - - for _, tc := range testCases { - next := trie.NextKey(tc.input) - require.Equal(t, tc.expected, next, common.BytesToHex(tc.input)) - } -} - -func TestNextKey_Again(t *testing.T) { - trie := NewEmptyTrie() - - var testCases = []string{ - "asdf", - "bnm", - "ghjk", - "qwerty", - "uiopl", - "zxcv", - } - - for _, tc := range testCases { - trie.Put([]byte(tc), []byte(tc)) - } - - for i, tc := range testCases { - next := trie.NextKey([]byte(tc)) - if i == len(testCases)-1 { - require.Nil(t, next) - } else { - require.Equal(t, []byte(testCases[i+1]), next, common.BytesToHex([]byte(tc))) - } - } -} - -func TestNextKey_HostAPI(t *testing.T) { - trie := NewEmptyTrie() - - var testCases = []string{ - ":code", - ":heappages", - } - - for _, tc := range testCases { - trie.Put([]byte(tc), []byte(tc)) - } - - nextCases := []string{"Opti", "Option"} - - for _, tc := range nextCases { - next := trie.NextKey([]byte(tc)) - require.Nil(t, next) - } -} - func TestClearPrefix(t *testing.T) { tests := []Test{ {key: []byte{0x01, 0x35}, value: []byte("spaghetti"), op: PUT}, diff --git a/lib/trie/trie_test.go b/lib/trie/trie_test.go new file mode 100644 index 00000000000..049ee7870ac --- /dev/null +++ b/lib/trie/trie_test.go @@ -0,0 +1,3308 @@ +// Copyright 2022 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package trie + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/ChainSafe/gossamer/internal/trie/node" + "github.com/ChainSafe/gossamer/lib/common" + gomock "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func Test_NewEmptyTrie(t *testing.T) { + expectedTrie := &Trie{ + childTries: make(map[common.Hash]*Trie), + deletedKeys: map[common.Hash]struct{}{}, + } + trie := NewEmptyTrie() + assert.Equal(t, expectedTrie, trie) +} + +func Test_NewTrie(t *testing.T) { + root := &node.Leaf{ + Key: []byte{0}, + Value: []byte{17}, + } + expectedTrie := &Trie{ + root: &node.Leaf{ + Key: []byte{0}, + Value: []byte{17}, + }, + childTries: make(map[common.Hash]*Trie), + deletedKeys: map[common.Hash]struct{}{}, + } + trie := NewTrie(root) + assert.Equal(t, expectedTrie, trie) +} + +func Test_Trie_Snapshot(t *testing.T) { + t.Parallel() + + trie := &Trie{ + generation: 8, + root: &node.Leaf{Key: []byte{8}}, + childTries: map[common.Hash]*Trie{ + {1}: { + generation: 1, + root: &node.Leaf{Key: []byte{1}}, + deletedKeys: map[common.Hash]struct{}{ + {1}: {}, + }, + }, + {2}: { + generation: 2, + root: &node.Leaf{Key: []byte{2}}, + deletedKeys: map[common.Hash]struct{}{ + {2}: {}, + }, + }, + }, + deletedKeys: map[common.Hash]struct{}{ + {1}: {}, + {2}: {}, + }, + } + + expectedTrie := &Trie{ + generation: 9, + root: &node.Leaf{Key: []byte{8}}, + childTries: map[common.Hash]*Trie{ + {1}: { + generation: 2, + root: &node.Leaf{Key: []byte{1}}, + deletedKeys: map[common.Hash]struct{}{}, + }, + {2}: { + generation: 3, + root: &node.Leaf{Key: []byte{2}}, + deletedKeys: map[common.Hash]struct{}{}, + }, + }, + deletedKeys: map[common.Hash]struct{}{}, + } + + newTrie := trie.Snapshot() + + assert.Equal(t, expectedTrie, newTrie) +} + +func Test_Trie_maybeUpdateGeneration(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie *Trie + node Node + newNode Node + copied bool + expectedTrie *Trie + }{ + "nil node": {}, + "same generation": { + trie: &Trie{ + generation: 1, + }, + node: &node.Leaf{ + Generation: 1, + Key: []byte{1}, + }, + newNode: &node.Leaf{ + Generation: 1, + Key: []byte{1}, + }, + expectedTrie: &Trie{ + generation: 1, + }, + }, + "trie generation higher and empty hash": { + trie: &Trie{ + generation: 2, + }, + node: &node.Leaf{ + Generation: 1, + Key: []byte{1}, + }, + newNode: &node.Leaf{ + Generation: 2, + Key: []byte{1}, + }, + copied: true, + expectedTrie: &Trie{ + generation: 2, + }, + }, + "trie generation higher and hash": { + trie: &Trie{ + generation: 2, + deletedKeys: map[common.Hash]struct{}{}, + }, + node: &node.Leaf{ + Generation: 1, + Key: []byte{1}, + HashDigest: []byte{1, 2, 3}, + }, + newNode: &node.Leaf{ + Generation: 2, + Key: []byte{1}, + HashDigest: []byte{1, 2, 3}, + }, + copied: true, + expectedTrie: &Trie{ + generation: 2, + deletedKeys: map[common.Hash]struct{}{ + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, + }: {}, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + trie := testCase.trie + + newNode := trie.maybeUpdateGeneration(testCase.node) + + assert.Equal(t, testCase.newNode, newNode) + assert.Equal(t, testCase.expectedTrie, trie) + + // Check for deep copy + if newNode != nil && testCase.copied { + newNode.SetDirty(!newNode.IsDirty()) + assert.NotEqual(t, testCase.node, newNode) + } + }) + } +} + +func Test_Trie_RootNode(t *testing.T) { + t.Parallel() + + trie := Trie{ + root: &node.Leaf{ + Key: []byte{1, 2, 3}, + }, + } + expectedRoot := &node.Leaf{ + Key: []byte{1, 2, 3}, + } + + root := trie.RootNode() + + assert.Equal(t, expectedRoot, root) +} + +//go:generate mockgen -destination=buffer_mock_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/internal/trie/node Buffer + +func Test_Trie_encodeRoot(t *testing.T) { + t.Parallel() + + type bufferCalls struct { + writeCalls []writeCall + lenCall bool + lenReturn int + bytesCall bool + bytesReturn []byte + } + + testCases := map[string]struct { + root node.Node + bufferCalls bufferCalls + errWrapped error + errMessage string + expectedRoot node.Node + }{ + "nil root and no error": { + bufferCalls: bufferCalls{ + writeCalls: []writeCall{ + {written: []byte{0}}, + }, + }, + }, + "nil root and write error": { + bufferCalls: bufferCalls{ + writeCalls: []writeCall{ + { + written: []byte{0}, + err: errTest, + }, + }, + }, + errWrapped: errTest, + errMessage: "cannot write nil root node to buffer: test error", + }, + "root encoding error": { + root: &node.Leaf{ + Key: []byte{1, 2}, + }, + bufferCalls: bufferCalls{ + writeCalls: []writeCall{ + { + written: []byte{66}, + err: errTest, + }, + }, + }, + errWrapped: errTest, + errMessage: "cannot encode header: test error", + expectedRoot: &node.Leaf{ + Key: []byte{1, 2}, + }, + }, + "root encoding success": { + root: &node.Leaf{ + Key: []byte{1, 2}, + }, + bufferCalls: bufferCalls{ + writeCalls: []writeCall{ + {written: []byte{66}}, + {written: []byte{18}}, + {written: []byte{0}}, + }, + lenCall: true, + lenReturn: 3, + bytesCall: true, + bytesReturn: []byte{66, 18, 0}, + }, + expectedRoot: &node.Leaf{ + Key: []byte{1, 2}, + Encoding: []byte{66, 18, 0}, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + buffer := NewMockBuffer(ctrl) + + var previousCall *gomock.Call + for _, write := range testCase.bufferCalls.writeCalls { + call := buffer.EXPECT(). + Write(write.written). + Return(write.n, write.err) + + if previousCall != nil { + call.After(previousCall) + } + previousCall = call + } + if testCase.bufferCalls.lenCall { + buffer.EXPECT().Len(). + Return(testCase.bufferCalls.lenReturn) + } + if testCase.bufferCalls.bytesCall { + buffer.EXPECT().Bytes(). + Return(testCase.bufferCalls.bytesReturn) + } + + err := encodeRoot(testCase.root, buffer) + + assert.ErrorIs(t, err, testCase.errWrapped) + if testCase.errWrapped != nil { + assert.EqualError(t, err, testCase.errMessage) + } + assert.Equal(t, testCase.expectedRoot, testCase.root) + }) + } +} + +func Test_Trie_MustHash(t *testing.T) { + t.Parallel() + + t.Run("success", func(t *testing.T) { + t.Parallel() + + var trie Trie + + hash := trie.MustHash() + + expectedHash := common.Hash{ + 0x3, 0x17, 0xa, 0x2e, 0x75, 0x97, 0xb7, 0xb7, + 0xe3, 0xd8, 0x4c, 0x5, 0x39, 0x1d, 0x13, 0x9a, + 0x62, 0xb1, 0x57, 0xe7, 0x87, 0x86, 0xd8, 0xc0, + 0x82, 0xf2, 0x9d, 0xcf, 0x4c, 0x11, 0x13, 0x14} + assert.Equal(t, expectedHash, hash) + }) +} + +func Test_Trie_Hash(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + hash common.Hash + errWrapped error + errMessage string + expectedTrie Trie + }{ + "nil root": { + hash: common.Hash{ + 0x3, 0x17, 0xa, 0x2e, 0x75, 0x97, 0xb7, 0xb7, + 0xe3, 0xd8, 0x4c, 0x5, 0x39, 0x1d, 0x13, 0x9a, + 0x62, 0xb1, 0x57, 0xe7, 0x87, 0x86, 0xd8, 0xc0, + 0x82, 0xf2, 0x9d, 0xcf, 0x4c, 0x11, 0x13, 0x14}, + }, + "leaf root": { + trie: Trie{ + root: &node.Leaf{ + Key: []byte{1, 2, 3}, + }, + }, + hash: common.Hash{ + 0x84, 0x7c, 0x95, 0x42, 0x8d, 0x9c, 0xcf, 0xce, + 0xa7, 0x27, 0x15, 0x33, 0x48, 0x74, 0x99, 0x11, + 0x83, 0xb8, 0xe8, 0xc4, 0x80, 0x88, 0xea, 0x4d, + 0x9f, 0x57, 0x82, 0x94, 0xc9, 0x76, 0xf4, 0x6f}, + expectedTrie: Trie{ + root: &node.Leaf{ + Key: []byte{1, 2, 3}, + Encoding: []byte{67, 1, 35, 0}, + }, + }, + }, + "branch root": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{1, 2, 3}, + Value: []byte("branch"), + Children: [16]node.Node{ + &node.Leaf{Key: []byte{9}}, + }, + }, + }, + hash: common.Hash{ + 0xbc, 0x4b, 0x90, 0x4c, 0x65, 0xb1, 0x3b, 0x9b, + 0xcf, 0xe2, 0x32, 0xe3, 0xe6, 0x50, 0x20, 0xd8, + 0x21, 0x96, 0xce, 0xbf, 0x4c, 0xa4, 0xd, 0xaa, + 0xbe, 0x27, 0xab, 0x13, 0xcb, 0xf0, 0xfd, 0xd7}, + expectedTrie: Trie{ + root: &node.Branch{ + Key: []byte{1, 2, 3}, + Value: []byte("branch"), + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{9}, + Encoding: []byte{0x41, 0x09, 0x00}, + }, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + hash, err := testCase.trie.Hash() + + assert.ErrorIs(t, err, testCase.errWrapped) + if testCase.errWrapped != nil { + assert.EqualError(t, err, testCase.errMessage) + } + assert.Equal(t, testCase.hash, hash) + assert.Equal(t, testCase.expectedTrie, testCase.trie) + }) + } +} + +func entriesMatch(t *testing.T, expected, actual map[string][]byte) { + t.Helper() + + for expectedKeyLEString, expectedValue := range expected { + expectedKeyLE := []byte(expectedKeyLEString) + actualValue, ok := actual[expectedKeyLEString] + if !ok { + t.Errorf("key 0x%x is missing from entries", expectedKeyLE) + continue + } + + if !bytes.Equal(expectedValue, actualValue) { + t.Errorf("for key 0x%x, expected value 0x%x but got actual value 0x%x", + expectedKeyLE, expectedValue, actualValue) + } + } + + for actualKeyLEString, actualValue := range actual { + actualKeyLE := []byte(actualKeyLEString) + _, ok := expected[actualKeyLEString] + if !ok { + t.Errorf("actual key 0x%x with value 0x%x was not expected", + actualKeyLE, actualValue) + } + } +} + +func Test_Trie_Entries(t *testing.T) { + t.Parallel() + + t.Run("simple root", func(t *testing.T) { + t.Parallel() + + root := &node.Branch{ + Key: []byte{0xa}, + Value: []byte("root"), + Children: [16]node.Node{ + &node.Leaf{ // index 0 + Key: []byte{2, 0xb}, + Value: []byte("leaf"), + }, + nil, + &node.Leaf{ // index 2 + Key: []byte{0xb}, + Value: []byte("leaf"), + }, + }, + } + + trie := NewTrie(root) + + entries := trie.Entries() + + expectedEntries := map[string][]byte{ + string([]byte{0x0a}): []byte("root"), + string([]byte{0xa0, 0x2b}): []byte("leaf"), + string([]byte{0x0a, 0x2b}): []byte("leaf"), + } + + entriesMatch(t, expectedEntries, entries) + }) + + t.Run("custom root", func(t *testing.T) { + t.Parallel() + + root := &node.Branch{ + Key: []byte{0xa, 0xb}, + Value: []byte("root"), + Children: [16]node.Node{ + nil, nil, nil, + &node.Branch{ // branch with value at child index 3 + Key: []byte{0xb}, + Value: []byte("branch 1"), + Children: [16]node.Node{ + nil, nil, nil, + &node.Leaf{ // leaf at child index 3 + Key: []byte{0xc}, + Value: []byte("bottom leaf"), + }, + }, + }, + nil, nil, nil, + &node.Leaf{ // leaf at child index 7 + Key: []byte{0xd}, + Value: []byte("top leaf"), + }, + nil, + &node.Branch{ // branch without value at child index 9 + Key: []byte{0xe}, + Value: []byte("branch 2"), + Children: [16]node.Node{ + &node.Leaf{ // leaf at child index 0 + Key: []byte{0xf}, + Value: []byte("bottom leaf 2"), + }, nil, nil, + }, + }, + }, + } + + trie := NewTrie(root) + + entries := trie.Entries() + + expectedEntries := map[string][]byte{ + string([]byte{0xab}): []byte("root"), + string([]byte{0xab, 0x7d}): []byte("top leaf"), + string([]byte{0xab, 0x3b}): []byte("branch 1"), + string([]byte{0xab, 0x3b, 0x3c}): []byte("bottom leaf"), + string([]byte{0xab, 0x9e}): []byte("branch 2"), + string([]byte{0xab, 0x9e, 0x0f}): []byte("bottom leaf 2"), + } + + entriesMatch(t, expectedEntries, entries) + }) + + t.Run("end to end", func(t *testing.T) { + t.Parallel() + + trie := Trie{ + root: nil, + childTries: make(map[common.Hash]*Trie), + deletedKeys: make(map[common.Hash]struct{}), + } + + kv := map[string][]byte{ + "ab": []byte("pen"), + "abc": []byte("penguin"), + "hy": []byte("feather"), + "cb": []byte("noot"), + } + + for k, v := range kv { + trie.Put([]byte(k), v) + } + + entries := trie.Entries() + + assert.Equal(t, kv, entries) + }) +} + +func Test_Trie_NextKey(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + key []byte + nextKey []byte + }{ + "nil root and nil key returns nil": {}, + "nil root returns nil": { + key: []byte{2}, + }, + "nil key returns root leaf": { + trie: Trie{ + root: &node.Leaf{ + Key: []byte{2}, + }, + }, + nextKey: []byte{2}, + }, + "key smaller than root leaf full key": { + trie: Trie{ + root: &node.Leaf{ + Key: []byte{2}, + }, + }, + key: []byte{0x10}, // 10 => [1, 0] in nibbles + nextKey: []byte{2}, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + nextKey := testCase.trie.NextKey(testCase.key) + + assert.Equal(t, testCase.nextKey, nextKey) + }) + } +} + +func Test_nextKey(t *testing.T) { + // Note this test is basically testing trie.NextKey without + // the headaches associated with converting nibbles and + // LE keys back and forth + t.Parallel() + + testCases := map[string]struct { + trie Trie + key []byte + nextKey []byte + }{ + "nil root and nil key returns nil": {}, + "nil root returns nil": { + key: []byte{2}, + }, + "nil key returns root leaf": { + trie: Trie{ + root: &node.Leaf{ + Key: []byte{2}, + }, + }, + nextKey: []byte{2}, + }, + "key smaller than root leaf full key": { + trie: Trie{ + root: &node.Leaf{ + Key: []byte{2}, + }, + }, + key: []byte{1}, + nextKey: []byte{2}, + }, + "key equal to root leaf full key": { + trie: Trie{ + root: &node.Leaf{ + Key: []byte{2}, + }, + }, + key: []byte{2}, + }, + "key greater than root leaf full key": { + trie: Trie{ + root: &node.Leaf{ + Key: []byte{2}, + }, + }, + key: []byte{3}, + }, + "key smaller than root branch full key": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{2}, + Value: []byte("branch"), + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{1}, + }, + }, + }, + }, + key: []byte{1}, + nextKey: []byte{2}, + }, + "key equal to root branch full key": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{2}, + Value: []byte("branch"), + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{1}, + }, + }, + }, + }, + key: []byte{2, 0, 1}, + }, + "key smaller than leaf full key": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{1}, + Value: []byte("branch"), + Children: [16]node.Node{ + nil, nil, + &node.Leaf{ + // full key [1, 2, 3] + Key: []byte{3}, + }, + }, + }, + }, + key: []byte{1, 2, 2}, + nextKey: []byte{1, 2, 3}, + }, + "key equal to leaf full key": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{1}, + Value: []byte("branch"), + Children: [16]node.Node{ + nil, nil, + &node.Leaf{ + // full key [1, 2, 3] + Key: []byte{3}, + }, + }, + }, + }, + key: []byte{1, 2, 3}, + }, + "key greater than leaf full key": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{1}, + Value: []byte("branch"), + Children: [16]node.Node{ + nil, nil, + &node.Leaf{ + // full key [1, 2, 3] + Key: []byte{3}, + }, + }, + }, + }, + key: []byte{1, 2, 4}, + }, + "next key branch with value": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{1}, + Value: []byte("top branch"), + Children: [16]node.Node{ + nil, nil, + &node.Branch{ + // full key [1, 2, 3] + Key: []byte{3}, + Value: []byte("branch 1"), + Children: [16]node.Node{ + nil, nil, nil, nil, + &node.Leaf{ + // full key [1, 2, 3, 4, 5] + Key: []byte{0x5}, + Value: []byte("bottom leaf"), + }, + }, + }, + }, + }, + }, + key: []byte{1}, + nextKey: []byte{1, 2, 3}, + }, + "next key go through branch without value": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{1}, + Children: [16]node.Node{ + nil, nil, + &node.Branch{ + // full key [1, 2, 3] + Key: []byte{3}, + Children: [16]node.Node{ + nil, nil, nil, nil, + &node.Leaf{ + // full key [1, 2, 3, 4, 5] + Key: []byte{0x5}, + Value: []byte("bottom leaf"), + }, + }, + }, + }, + }, + }, + key: []byte{0}, + nextKey: []byte{1, 2, 3, 4, 5}, + }, + "next key leaf from bottom branch": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{1}, + Children: [16]node.Node{ + nil, nil, + &node.Branch{ + // full key [1, 2, 3] + Key: []byte{3}, + Value: []byte("bottom branch"), + Children: [16]node.Node{ + nil, nil, nil, nil, + &node.Leaf{ + // full key [1, 2, 3, 4, 5] + Key: []byte{0x5}, + Value: []byte("bottom leaf"), + }, + }, + }, + }, + }, + }, + key: []byte{1, 2, 3}, + nextKey: []byte{1, 2, 3, 4, 5}, + }, + "next key greater than branch": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{1}, + Children: [16]node.Node{ + nil, nil, + &node.Branch{ + // full key [1, 2, 3] + Key: []byte{3}, + Value: []byte("bottom branch"), + Children: [16]node.Node{ + nil, nil, nil, nil, + &node.Leaf{ + // full key [1, 2, 3, 4, 5] + Key: []byte{0x5}, + Value: []byte("bottom leaf"), + }, + }, + }, + }, + }, + }, + key: []byte{1, 2, 3}, + nextKey: []byte{1, 2, 3, 4, 5}, + }, + "key smaller length and greater than root branch full key": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{2, 0}, + Value: []byte("branch"), + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + }, + key: []byte{3}, + }, + "key smaller length and greater than root leaf full key": { + trie: Trie{ + root: &node.Leaf{ + Key: []byte{2, 0}, + Value: []byte("leaf"), + }, + }, + key: []byte{3}, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + originalTrie := testCase.trie.DeepCopy() + + nextKey := nextKey(testCase.trie.root, nil, testCase.key) + + assert.Equal(t, testCase.nextKey, nextKey) + assert.Equal(t, *originalTrie, testCase.trie) // ensure no mutation + }) + } +} + +func Test_Trie_Put(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + key []byte + value []byte + expectedTrie Trie + }{ + "trie with key and value": { + trie: Trie{ + generation: 1, + root: &node.Leaf{ + Key: []byte{1, 2, 0, 5}, + Value: []byte{1}, + }, + }, + key: []byte{0x12, 0x16}, + value: []byte{2}, + expectedTrie: Trie{ + generation: 1, + root: &node.Branch{ + Key: []byte{1, 2}, + Generation: 1, + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{5}, + Value: []byte{1}, + Generation: 1, + Dirty: true, + }, + &node.Leaf{ + Key: []byte{6}, + Value: []byte{2}, + Generation: 1, + Dirty: true, + }, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + trie := testCase.trie + trie.Put(testCase.key, testCase.value) + + assert.Equal(t, testCase.expectedTrie, trie) + }) + } +} + +func Test_Trie_tryPut(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + key []byte + value []byte + expectedTrie Trie + }{ + "nil everything": { + trie: Trie{ + generation: 1, + }, + expectedTrie: Trie{ + generation: 1, + root: &node.Leaf{ + Generation: 1, + Dirty: true, + }, + }, + }, + "empty trie with nil key and value": { + trie: Trie{ + generation: 1, + }, + value: []byte{3, 4}, + expectedTrie: Trie{ + generation: 1, + root: &node.Leaf{ + Value: []byte{3, 4}, + Generation: 1, + Dirty: true, + }, + }, + }, + "empty trie with key and value": { + trie: Trie{ + generation: 1, + }, + key: []byte{1, 2}, + value: []byte{3, 4}, + expectedTrie: Trie{ + generation: 1, + root: &node.Leaf{ + Key: []byte{1, 2}, + Value: []byte{3, 4}, + Generation: 1, + Dirty: true, + }, + }, + }, + "trie with key and value": { + trie: Trie{ + generation: 1, + root: &node.Leaf{ + Key: []byte{1, 0, 5}, + Value: []byte{1}, + }, + }, + key: []byte{1, 1, 6}, + value: []byte{2}, + expectedTrie: Trie{ + generation: 1, + root: &node.Branch{ + Key: []byte{1}, + Generation: 1, + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{5}, + Value: []byte{1}, + Generation: 1, + Dirty: true, + }, + &node.Leaf{ + Key: []byte{6}, + Value: []byte{2}, + Generation: 1, + Dirty: true, + }, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + trie := testCase.trie + trie.tryPut(testCase.key, testCase.value) + + assert.Equal(t, testCase.expectedTrie, trie) + }) + } +} + +func Test_Trie_insert(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + parent Node + key []byte + value Node + newNode Node + expectedTrie Trie + }{ + "nil parent": { + key: []byte{1}, + value: &node.Leaf{}, + newNode: &node.Leaf{ + Key: []byte{1}, + }, + }, + "branch parent": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte("branch"), + Children: [16]node.Node{ + nil, + &node.Leaf{Key: []byte{2}}, + }, + }, + key: []byte{1, 0}, + value: &node.Leaf{ + Value: []byte("leaf"), + }, + newNode: &node.Branch{ + Key: []byte{1}, + Value: []byte("branch"), + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{}, + Value: []byte("leaf"), + }, + &node.Leaf{Key: []byte{2}}, + }, + }, + }, + "override leaf parent": { + parent: &node.Leaf{ + Key: []byte{1}, + Value: []byte("original leaf"), + }, + key: []byte{1}, + value: &node.Leaf{ + Value: []byte("new leaf"), + }, + newNode: &node.Leaf{ + Key: []byte{1}, + Value: []byte("new leaf"), + Dirty: true, + }, + }, + "write same leaf value as child to parent leaf": { + parent: &node.Leaf{ + Key: []byte{1}, + Value: []byte("same"), + }, + key: []byte{1}, + value: &node.Leaf{ + Value: []byte("same"), + }, + newNode: &node.Leaf{ + Key: []byte{1}, + Value: []byte("same"), + }, + }, + "write leaf as child to parent leaf": { + parent: &node.Leaf{ + Key: []byte{1}, + Value: []byte("original leaf"), + }, + key: []byte{1, 0}, + value: &node.Leaf{ + Value: []byte("leaf"), + }, + newNode: &node.Branch{ + Key: []byte{1}, + Value: []byte("original leaf"), + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{}, + Value: []byte("leaf"), + }, + }, + }, + }, + "write leaf as divergent child next to parent leaf": { + parent: &node.Leaf{ + Key: []byte{1, 2}, + Value: []byte("original leaf"), + }, + key: []byte{2, 3}, + value: &node.Leaf{ + Value: []byte("leaf"), + }, + newNode: &node.Branch{ + Key: []byte{}, + Dirty: true, + Children: [16]node.Node{ + nil, + &node.Leaf{ + Key: []byte{2}, + Value: []byte("original leaf"), + Dirty: true, + }, + &node.Leaf{ + Key: []byte{3}, + Value: []byte("leaf"), + }, + }, + }, + }, + "write leaf into nil leaf": { + parent: &node.Leaf{ + Key: []byte{1}, + }, + key: []byte{1}, + value: &node.Leaf{ + Value: []byte("leaf"), + }, + newNode: &node.Leaf{ + Key: []byte{1}, + Value: []byte("leaf"), + Dirty: true, + }, + }, + "write leaf as child to nil value leaf": { + parent: &node.Leaf{ + Key: []byte{1, 2}, + }, + key: []byte{1}, + value: &node.Leaf{ + Value: []byte("leaf"), + }, + newNode: &node.Branch{ + Key: []byte{1}, + Value: []byte("leaf"), + Dirty: true, + Children: [16]node.Node{ + nil, nil, + &node.Leaf{ + Key: []byte{}, + Dirty: true, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + trie := testCase.trie + newNode := trie.insert(testCase.parent, testCase.key, testCase.value) + + assert.Equal(t, testCase.newNode, newNode) + assert.Equal(t, testCase.expectedTrie, trie) + }) + } +} + +func Test_Trie_updateBranch(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + parent *node.Branch + key []byte + value Node + newNode Node + }{ + "update with branch": { + parent: &node.Branch{ + Key: []byte{2}, + Value: []byte("old"), + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + key: []byte{2}, + value: &node.Branch{ + Value: []byte("new"), + }, + newNode: &node.Branch{ + Key: []byte{2}, + Value: []byte("new"), + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + }, + "update with leaf": { + parent: &node.Branch{ + Key: []byte{2}, + Value: []byte("old"), + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + key: []byte{2}, + value: &node.Leaf{ + Value: []byte("new"), + }, + newNode: &node.Branch{ + Key: []byte{2}, + Value: []byte("new"), + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + }, + "add leaf as direct child": { + parent: &node.Branch{ + Key: []byte{2}, + Value: []byte{5}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + key: []byte{2, 3, 4, 5}, + value: &node.Leaf{ + Value: []byte{6}, + }, + newNode: &node.Branch{ + Key: []byte{2}, + Value: []byte{5}, + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + nil, nil, + &node.Leaf{ + Key: []byte{4, 5}, + Value: []byte{6}, + }, + }, + }, + }, + "add leaf as nested child": { + parent: &node.Branch{ + Key: []byte{2}, + Value: []byte{5}, + Children: [16]node.Node{ + nil, nil, nil, + &node.Branch{ + Key: []byte{4}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + }, + }, + key: []byte{2, 3, 4, 5, 6}, + value: &node.Leaf{ + Value: []byte{6}, + }, + newNode: &node.Branch{ + Key: []byte{2}, + Value: []byte{5}, + Dirty: true, + Children: [16]node.Node{ + nil, nil, nil, + &node.Branch{ + Key: []byte{4}, + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + nil, nil, nil, nil, + &node.Leaf{ + Key: []byte{6}, + Value: []byte{6}, + }, + }, + }, + }, + }, + }, + "split branch for longer key": { + parent: &node.Branch{ + Key: []byte{2, 3}, + Value: []byte{5}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + key: []byte{2, 4, 5, 6}, + value: &node.Leaf{ + Value: []byte{6}, + }, + newNode: &node.Branch{ + Key: []byte{2}, + Dirty: true, + Children: [16]node.Node{ + nil, nil, nil, + &node.Branch{ + Key: []byte{}, + Value: []byte{5}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + &node.Leaf{ + Key: []byte{5, 6}, + Value: []byte{6}, + }, + }, + }, + }, + "split root branch": { + parent: &node.Branch{ + Key: []byte{2, 3}, + Value: []byte{5}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + key: []byte{3}, + value: &node.Leaf{ + Value: []byte{6}, + }, + newNode: &node.Branch{ + Key: []byte{}, + Dirty: true, + Children: [16]node.Node{ + nil, nil, + &node.Branch{ + Key: []byte{3}, + Value: []byte{5}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + &node.Leaf{ + Key: []byte{}, + Value: []byte{6}, + }, + }, + }, + }, + "update with leaf at empty key": { + parent: &node.Branch{ + Key: []byte{2}, + Value: []byte{5}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + key: []byte{}, + value: &node.Leaf{ + Value: []byte{6}, + }, + newNode: &node.Branch{ + Key: []byte{}, + Value: []byte{6}, + Dirty: true, + Children: [16]node.Node{ + nil, nil, + &node.Branch{ + Key: []byte{}, + Value: []byte{5}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + trie := new(Trie) + + newNode := trie.updateBranch(testCase.parent, testCase.key, testCase.value) + + assert.Equal(t, testCase.newNode, newNode) + assert.Equal(t, new(Trie), trie) // check no mutation + }) + } +} + +func Test_Trie_LoadFromMap(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + data map[string]string + expectedTrie Trie + errWrapped error + errMessage string + }{ + "nil data": {}, + "empty data": { + data: map[string]string{}, + }, + "bad key": { + data: map[string]string{ + "0xa": "0x01", + }, + errWrapped: hex.ErrLength, + errMessage: "encoding/hex: odd length hex string: 0xa", + }, + "bad value": { + data: map[string]string{ + "0x01": "0xa", + }, + errWrapped: hex.ErrLength, + errMessage: "encoding/hex: odd length hex string: 0xa", + }, + "load into empty trie": { + data: map[string]string{ + "0x01": "0x06", + "0x0120": "0x07", + "0x0130": "0x08", + }, + expectedTrie: Trie{ + root: &node.Branch{ + Key: []byte{00, 01}, + Value: []byte{6}, + Dirty: true, + Children: [16]node.Node{ + nil, nil, + &node.Leaf{ + Key: []byte{0}, + Value: []byte{7}, + Dirty: true, + }, + &node.Leaf{ + Key: []byte{0}, + Value: []byte{8}, + Dirty: true, + }, + }, + }, + }, + }, + "override trie": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{00, 01}, + Value: []byte{106}, + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{ + Value: []byte{9}, + }, + nil, + &node.Leaf{ + Key: []byte{0}, + Value: []byte{107}, + Dirty: true, + }, + }, + }, + }, + data: map[string]string{ + "0x01": "0x06", + "0x0120": "0x07", + "0x0130": "0x08", + }, + expectedTrie: Trie{ + root: &node.Branch{ + Key: []byte{00, 01}, + Value: []byte{6}, + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{ + Value: []byte{9}, + }, + nil, + &node.Leaf{ + Key: []byte{0}, + Value: []byte{7}, + Dirty: true, + }, + &node.Leaf{ + Key: []byte{0}, + Value: []byte{8}, + Dirty: true, + }, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := testCase.trie.LoadFromMap(testCase.data) + + assert.ErrorIs(t, err, testCase.errWrapped) + if testCase.errWrapped != nil { + assert.EqualError(t, err, testCase.errMessage) + } + + assert.Equal(t, testCase.expectedTrie, testCase.trie) + }) + } +} + +func Test_Trie_GetKeysWithPrefix(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + prefix []byte + keys [][]byte + }{ + "some trie": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{0, 1}, + Children: [16]node.Node{ + &node.Branch{ // full key 0, 1, 0, 3 + Key: []byte{3}, + Children: [16]node.Node{ + &node.Leaf{ // full key 0, 1, 0, 0, 4 + Key: []byte{4}, + }, + &node.Leaf{ // full key 0, 1, 0, 1, 5 + Key: []byte{5}, + }, + }, + }, + &node.Leaf{ // full key 0, 1, 1, 9 + Key: []byte{9}, + }, + }, + }, + }, + prefix: []byte{1}, + keys: [][]byte{ + {1, 3, 4}, + {1, 3, 0x15}, + {1, 0x19}, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + keys := testCase.trie.GetKeysWithPrefix(testCase.prefix) + + assert.Equal(t, testCase.keys, keys) + }) + } +} + +func Test_getKeysWithPrefix(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + parent Node + prefix []byte + key []byte + keys [][]byte + expectedKeys [][]byte + }{ + "nil parent returns keys passed": { + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}}, + }, + "common prefix for parent branch and search key": { + parent: &node.Branch{ + Key: []byte{1, 2, 3}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{4}}, + &node.Leaf{Key: []byte{5}}, + }, + }, + prefix: []byte{9, 8, 7}, + key: []byte{1, 2}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}, + {0x98, 0x71, 0x23, 0x04}, + {0x98, 0x71, 0x23, 0x15}}, + }, + "parent branch and empty key": { + parent: &node.Branch{ + Key: []byte{1, 2, 3}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{4}}, + &node.Leaf{Key: []byte{5}}, + }, + }, + prefix: []byte{9, 8, 7}, + key: []byte{}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}, + {0x98, 0x71, 0x23, 0x04}, + {0x98, 0x71, 0x23, 0x15}}, + }, + "search key smaller than branch key with no full common prefix": { + parent: &node.Branch{ + Key: []byte{1, 2, 3}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{4}}, + &node.Leaf{Key: []byte{5}}, + }, + }, + key: []byte{1, 3}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}}, + }, + "common prefix smaller tan search key": { + parent: &node.Branch{ + Key: []byte{1, 2}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{4}}, + &node.Leaf{Key: []byte{5}}, + }, + }, + key: []byte{1, 2, 3}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}}, + }, + "recursive call": { + parent: &node.Branch{ + Key: []byte{1, 2, 3}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{4}}, + &node.Leaf{Key: []byte{5}}, + }, + }, + prefix: []byte{9, 8, 7}, + key: []byte{1, 2, 3, 0}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}, + {0x98, 0x71, 0x23, 0x04}}, + }, + "parent leaf with search key equal to common prefix": { + parent: &node.Leaf{ + Key: []byte{1, 2, 3}, + }, + prefix: []byte{9, 8, 7}, + key: []byte{1, 2, 3}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}, + {0x98, 0x71, 0x23}}, + }, + "parent leaf with empty search key": { + parent: &node.Leaf{ + Key: []byte{1, 2, 3}, + }, + prefix: []byte{9, 8, 7}, + key: []byte{}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}, + {0x98, 0x71, 0x23}}, + }, + "parent leaf with too deep search key": { + parent: &node.Leaf{ + Key: []byte{1, 2, 3}, + }, + prefix: []byte{9, 8, 7}, + key: []byte{1, 2, 3, 4}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}}, + }, + "parent leaf with shorter matching search key": { + parent: &node.Leaf{ + Key: []byte{1, 2, 3}, + }, + prefix: []byte{9, 8, 7}, + key: []byte{1, 2}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}, + {0x98, 0x71, 0x23}}, + }, + "parent leaf with not matching search key": { + parent: &node.Leaf{ + Key: []byte{1, 2, 3}, + }, + prefix: []byte{9, 8, 7}, + key: []byte{1, 3, 3}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}}, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + keys := getKeysWithPrefix(testCase.parent, + testCase.prefix, testCase.key, testCase.keys) + + assert.Equal(t, testCase.expectedKeys, keys) + }) + } +} + +func Test_addAllKeys(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + parent Node + prefix []byte + keys [][]byte + expectedKeys [][]byte + }{ + "nil parent returns keys passed": { + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}}, + }, + "leaf parent": { + parent: &node.Leaf{ + Key: []byte{1, 2, 3}, + }, + prefix: []byte{9, 8, 7}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}, + {0x98, 0x71, 0x23}}, + }, + "parent branch without value": { + parent: &node.Branch{ + Key: []byte{1, 2, 3}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{4}}, + &node.Leaf{Key: []byte{5}}, + }, + }, + prefix: []byte{9, 8, 7}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}, + {0x98, 0x71, 0x23, 0x04}, + {0x98, 0x71, 0x23, 0x15}}, + }, + "parent branch with empty value": { + parent: &node.Branch{ + Key: []byte{1, 2, 3}, + Value: []byte{}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{4}}, + &node.Leaf{Key: []byte{5}}, + }, + }, + prefix: []byte{9, 8, 7}, + keys: [][]byte{{1}, {2}}, + expectedKeys: [][]byte{{1}, {2}, + {0x98, 0x71, 0x23}, + {0x98, 0x71, 0x23, 0x04}, + {0x98, 0x71, 0x23, 0x15}}, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + keys := addAllKeys(testCase.parent, + testCase.prefix, testCase.keys) + + assert.Equal(t, testCase.expectedKeys, keys) + }) + } +} + +func Test_Trie_Get(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + key []byte + value []byte + }{ + "some trie": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{0, 1}, + Value: []byte{1, 3}, + Children: [16]node.Node{ + &node.Branch{ // full key 0, 1, 0, 3 + Key: []byte{3}, + Value: []byte{1, 2}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + &node.Leaf{ // full key 0, 1, 1, 9 + Key: []byte{9}, + Value: []byte{1, 2, 3, 4, 5}, + }, + }, + }, + }, + key: []byte{0x01, 0x19}, + value: []byte{1, 2, 3, 4, 5}, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + value := testCase.trie.Get(testCase.key) + + assert.Equal(t, testCase.value, value) + }) + } +} + +func Test_retrieve(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + parent Node + key []byte + value []byte + }{ + "nil parent": { + key: []byte{1}, + }, + "leaf key match": { + parent: &node.Leaf{ + Key: []byte{1}, + Value: []byte{2}, + }, + key: []byte{1}, + value: []byte{2}, + }, + "leaf key mismatch": { + parent: &node.Leaf{ + Key: []byte{1, 2}, + Value: []byte{2}, + }, + key: []byte{1}, + }, + "branch key match": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{2}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + key: []byte{1}, + value: []byte{2}, + }, + "branch key with empty search key": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{2}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + value: []byte{2}, + }, + "branch key mismatch with shorter search key": { + parent: &node.Branch{ + Key: []byte{1, 2}, + Value: []byte{2}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + key: []byte{1}, + }, + "bottom leaf in branch": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + nil, nil, + &node.Branch{ // full key 1, 2, 3 + Key: []byte{3}, + Value: []byte{2}, + Children: [16]node.Node{ + nil, nil, nil, nil, + &node.Leaf{ // full key 1, 2, 3, 4, 5 + Key: []byte{5}, + Value: []byte{3}, + }, + }, + }, + }, + }, + key: []byte{1, 2, 3, 4, 5}, + value: []byte{3}, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + // Check no mutation was done + const copyChildren = true + var expectedParent Node + if testCase.parent != nil { + expectedParent = testCase.parent.Copy(copyChildren) + } + + value := retrieve(testCase.parent, testCase.key) + + assert.Equal(t, testCase.value, value) + assert.Equal(t, expectedParent, testCase.parent) + }) + } +} + +func Test_Trie_ClearPrefixLimit(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + prefix []byte + limit uint32 + deleted uint32 + allDeleted bool + expectedTrie Trie + }{ + "limit is zero": {}, + "clear prefix limit": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{1, 2}, + Value: []byte{1}, + Children: [16]node.Node{ + nil, nil, nil, + &node.Leaf{ + Key: []byte{4}, + }, + }, + }, + }, + prefix: []byte{0x12}, + limit: 5, + deleted: 2, + allDeleted: true, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + trie := testCase.trie + + deleted, allDeleted := trie.ClearPrefixLimit(testCase.prefix, testCase.limit) + + assert.Equal(t, testCase.deleted, deleted) + assert.Equal(t, testCase.allDeleted, allDeleted) + assert.Equal(t, testCase.expectedTrie, trie) + }) + } +} + +func Test_Trie_clearPrefixLimit(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + parent Node + prefix []byte + limit uint32 + expectedLimit uint32 + newParent Node + updated bool + allDeleted bool + expectedTrie Trie + }{ + "limit is zero": { + allDeleted: true, + }, + "nil parent": { + limit: 1, + expectedLimit: 1, + allDeleted: true, + }, + "leaf parent with common prefix": { + parent: &node.Leaf{ + Key: []byte{1, 2}, + }, + prefix: []byte{1}, + limit: 1, + updated: true, + allDeleted: true, + }, + "leaf parent with key equal prefix": { + parent: &node.Leaf{ + Key: []byte{1}, + }, + prefix: []byte{1}, + limit: 1, + updated: true, + allDeleted: true, + }, + "leaf parent with key no common prefix": { + parent: &node.Leaf{ + Key: []byte{1, 2}, + }, + prefix: []byte{1, 3}, + limit: 1, + expectedLimit: 1, + newParent: &node.Leaf{ + Key: []byte{1, 2}, + }, + allDeleted: true, + }, + "leaf parent with key smaller than prefix": { + parent: &node.Leaf{ + Key: []byte{1}, + }, + prefix: []byte{1, 2}, + limit: 1, + expectedLimit: 1, + newParent: &node.Leaf{ + Key: []byte{1}, + }, + allDeleted: true, + }, + "branch without value parent with common prefix": { + parent: &node.Branch{ + Key: []byte{1, 2}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + &node.Leaf{Key: []byte{2}}, + }, + }, + prefix: []byte{1}, + limit: 3, + expectedLimit: 1, + updated: true, + allDeleted: true, + }, + "branch without value with key equal prefix": { + parent: &node.Branch{ + Key: []byte{1, 2}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + &node.Leaf{Key: []byte{2}}, + }, + }, + prefix: []byte{1, 2}, + limit: 3, + expectedLimit: 1, + updated: true, + allDeleted: true, + }, + "branch without value with no common prefix": { + parent: &node.Branch{ + Key: []byte{1, 2}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + &node.Leaf{Key: []byte{2}}, + }, + }, + prefix: []byte{1, 3}, + limit: 1, + expectedLimit: 1, + newParent: &node.Branch{ + Key: []byte{1, 2}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + &node.Leaf{Key: []byte{2}}, + }, + }, + allDeleted: true, + }, + "branch without value with key smaller than prefix by more than one": { + parent: &node.Branch{ + Key: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + &node.Leaf{Key: []byte{2}}, + }, + }, + prefix: []byte{1, 2, 3}, + limit: 1, + expectedLimit: 1, + newParent: &node.Branch{ + Key: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + &node.Leaf{Key: []byte{2}}, + }, + }, + allDeleted: true, + }, + "branch without value with key smaller than prefix by one": { + parent: &node.Branch{ + Key: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + &node.Leaf{Key: []byte{2}}, + }, + }, + prefix: []byte{1, 2}, + limit: 1, + expectedLimit: 1, + newParent: &node.Branch{ + Key: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + &node.Leaf{Key: []byte{2}}, + }, + }, + allDeleted: true, + }, + "branch with value with common prefix": { + parent: &node.Branch{ + Key: []byte{1, 2}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + prefix: []byte{1}, + limit: 2, + updated: true, + allDeleted: true, + }, + "branch with value with key equal prefix": { + parent: &node.Branch{ + Key: []byte{1, 2}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + prefix: []byte{1, 2}, + limit: 2, + updated: true, + allDeleted: true, + }, + "branch with value with no common prefix": { + parent: &node.Branch{ + Key: []byte{1, 2}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + prefix: []byte{1, 3}, + limit: 1, + expectedLimit: 1, + newParent: &node.Branch{ + Key: []byte{1, 2}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + allDeleted: true, + }, + "branch with value with key smaller than prefix by more than one": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + prefix: []byte{1, 2, 3}, + limit: 1, + expectedLimit: 1, + newParent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + allDeleted: true, + }, + "branch with value with key smaller than prefix by one": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + prefix: []byte{1, 2}, + limit: 1, + expectedLimit: 1, + newParent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + allDeleted: true, + }, + "delete one child of branch": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{3}}, + &node.Leaf{Key: []byte{4}}, + }, + }, + prefix: []byte{1}, + limit: 1, + newParent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Dirty: true, + Children: [16]node.Node{ + nil, + &node.Leaf{Key: []byte{4}}, + }, + }, + updated: true, + }, + "delete only child of branch": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{3}}, + }, + }, + prefix: []byte{1, 0}, + limit: 1, + newParent: &node.Leaf{ + Key: []byte{1}, + Value: []byte{1}, + Dirty: true, + }, + updated: true, + allDeleted: true, + }, + "fully delete children of branch with value": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{3}}, + &node.Leaf{Key: []byte{4}}, + }, + }, + prefix: []byte{1}, + limit: 2, + newParent: &node.Leaf{ + Key: []byte{1}, + Value: []byte{1}, + Dirty: true, + }, + updated: true, + }, + "fully delete children of branch without value": { + parent: &node.Branch{ + Key: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{3}}, + &node.Leaf{Key: []byte{4}}, + }, + }, + prefix: []byte{1}, + limit: 2, + updated: true, + allDeleted: true, + }, + "partially delete child of branch": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Branch{ // full key 1, 0, 3 + Key: []byte{3}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{ // full key 1, 0, 3, 0, 5 + Key: []byte{5}, + }, + }, + }, + &node.Leaf{ + Key: []byte{6}, + }, + }, + }, + prefix: []byte{1, 0}, + limit: 1, + newParent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{ // full key 1, 0, 3 + Key: []byte{3}, + Value: []byte{1}, + Dirty: true, + }, + &node.Leaf{ + Key: []byte{6}, + }, + }, + }, + updated: true, + }, + "update child of branch": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Branch{ // full key 1, 0, 2 + Key: []byte{2}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + }, + }, + }, + }, + prefix: []byte{1, 0, 2}, + limit: 2, + newParent: &node.Leaf{ + Key: []byte{1}, + Value: []byte{1}, + Dirty: true, + }, + updated: true, + allDeleted: true, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + trie := testCase.trie + + newParent, updated, allDeleted := trie.clearPrefixLimit(testCase.parent, + testCase.prefix, &testCase.limit) + + assert.Equal(t, testCase.newParent, newParent) + assert.Equal(t, testCase.expectedLimit, testCase.limit) + assert.Equal(t, testCase.updated, updated) + assert.Equal(t, testCase.allDeleted, allDeleted) + assert.Equal(t, testCase.expectedTrie, trie) + }) + } +} + +func Test_Trie_deleteNodes(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + parent Node + prefix []byte + limit uint32 + newLimit uint32 + newNode Node + oneDeletion bool + expectedTrie Trie + }{ + "zero limit": { + parent: &node.Leaf{ + Key: []byte{1}, + }, + newNode: &node.Leaf{ + Key: []byte{1}, + }, + }, + "nil parent": { + limit: 1, + newLimit: 1, + }, + "update leaf generation": { + trie: Trie{ + generation: 1, + }, + parent: &node.Leaf{}, + newNode: &node.Leaf{ + Generation: 1, + }, + expectedTrie: Trie{ + generation: 1, + }, + }, + "update branch generation": { + trie: Trie{ + generation: 1, + }, + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + newNode: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Generation: 1, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + expectedTrie: Trie{ + generation: 1, + }, + }, + "delete leaf": { + parent: &node.Leaf{}, + limit: 2, + newLimit: 1, + }, + "delete branch without value": { + parent: &node.Branch{ + Children: [16]node.Node{ + &node.Leaf{}, + &node.Leaf{}, + }, + }, + limit: 3, + newLimit: 1, + }, + "delete branch with value": { + parent: &node.Branch{ + Key: []byte{3}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + limit: 3, + newLimit: 1, + }, + "delete branch and all children": { + parent: &node.Branch{ + Key: []byte{3}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + &node.Leaf{Key: []byte{2}}, + }, + }, + limit: 10, + newLimit: 8, + }, + "delete branch one child only": { + trie: Trie{ + generation: 1, + }, + parent: &node.Branch{ + Key: []byte{3}, + Value: []byte{1, 2, 3}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + &node.Leaf{Key: []byte{2}}, + }, + }, + limit: 1, + newNode: &node.Branch{ + Key: []byte{3}, + Value: []byte{1, 2, 3}, + Dirty: true, + Generation: 1, + Children: [16]node.Node{ + nil, + &node.Leaf{Key: []byte{2}}, + }, + }, + expectedTrie: Trie{ + generation: 1, + }, + }, + "delete branch children only": { + trie: Trie{ + generation: 1, + }, + parent: &node.Branch{ + Key: []byte{3}, + Value: []byte{1, 2, 3}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{1}}, + &node.Leaf{Key: []byte{2}}, + }, + }, + limit: 2, + newLimit: 0, + newNode: &node.Leaf{ + Key: []byte{3}, + Value: []byte{1, 2, 3}, + Dirty: true, + Generation: 1, + }, + expectedTrie: Trie{ + generation: 1, + }, + }, + "delete branch all children except one": { + trie: Trie{ + generation: 1, + }, + parent: &node.Branch{ + Key: []byte{3}, + Children: [16]node.Node{ + nil, + &node.Leaf{Key: []byte{1}}, + nil, + &node.Leaf{Key: []byte{2}}, + nil, + &node.Leaf{Key: []byte{3}}, + }, + }, + prefix: []byte{1, 2}, + limit: 2, + newLimit: 0, + newNode: &node.Leaf{ + Key: []byte{3, 5, 3}, + Generation: 1, + Dirty: true, + }, + expectedTrie: Trie{ + generation: 1, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + trie := testCase.trie + + newNode := trie.deleteNodes(testCase.parent, testCase.prefix, &testCase.limit) + + assert.Equal(t, testCase.limit, testCase.limit) + assert.Equal(t, testCase.newNode, newNode) + assert.Equal(t, testCase.expectedTrie, trie) + }) + } +} + +func Test_Trie_ClearPrefix(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + prefix []byte + expectedTrie Trie + }{ + "nil prefix": { + trie: Trie{ + root: &node.Leaf{}, + }, + }, + "empty prefix": { + trie: Trie{ + root: &node.Leaf{}, + }, + prefix: []byte{}, + }, + "empty trie": { + prefix: []byte{0x12}, + }, + "clear prefix": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{1, 2}, + Children: [16]node.Node{ + &node.Leaf{ // full key in nibbles 1, 2, 0, 5 + Key: []byte{5}, + }, + &node.Branch{ // full key in nibbles 1, 2, 1, 6 + Key: []byte{6}, + Value: []byte("bottom branch"), + Children: [16]node.Node{ + &node.Leaf{ // full key in nibbles 1, 2, 1, 6, 0, 7 + Key: []byte{7}, + }, + }, + }, + }, + }, + }, + prefix: []byte{0x12, 0x16}, + expectedTrie: Trie{ + root: &node.Leaf{ + Key: []byte{1, 2, 0, 5}, + Dirty: true, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + // Check for no mutation + var expectedPrefix []byte + if testCase.prefix != nil { + expectedPrefix = make([]byte, len(testCase.prefix)) + copy(expectedPrefix, testCase.prefix) + } + + testCase.trie.ClearPrefix(testCase.prefix) + + assert.Equal(t, testCase.expectedTrie, testCase.trie) + assert.Equal(t, expectedPrefix, testCase.prefix) + }) + } +} + +func Test_Trie_clearPrefix(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + parent Node + prefix []byte + newParent Node + updated bool + expectedTrie Trie + }{ + "nil parent": {}, + "leaf parent with common prefix": { + parent: &node.Leaf{ + Key: []byte{1, 2}, + }, + prefix: []byte{1}, + updated: true, + }, + "leaf parent with key equal prefix": { + parent: &node.Leaf{ + Key: []byte{1}, + }, + prefix: []byte{1}, + updated: true, + }, + "leaf parent with key no common prefix": { + parent: &node.Leaf{ + Key: []byte{1, 2}, + }, + prefix: []byte{1, 3}, + newParent: &node.Leaf{ + Key: []byte{1, 2}, + }, + }, + "leaf parent with key smaller than prefix": { + parent: &node.Leaf{ + Key: []byte{1}, + }, + prefix: []byte{1, 2}, + newParent: &node.Leaf{ + Key: []byte{1}, + }, + }, + "branch parent with common prefix": { + parent: &node.Branch{ + Key: []byte{1, 2}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + prefix: []byte{1}, + updated: true, + }, + "branch with key equal prefix": { + parent: &node.Branch{ + Key: []byte{1, 2}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + prefix: []byte{1, 2}, + updated: true, + }, + "branch with no common prefix": { + parent: &node.Branch{ + Key: []byte{1, 2}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + prefix: []byte{1, 3}, + newParent: &node.Branch{ + Key: []byte{1, 2}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + }, + "branch with key smaller than prefix by more than one": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + prefix: []byte{1, 2, 3}, + newParent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + }, + "branch with key smaller than prefix by one": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + prefix: []byte{1, 2}, + newParent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + }, + "delete one child of branch": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{3}}, + &node.Leaf{Key: []byte{4}}, + }, + }, + prefix: []byte{1, 0, 3}, + newParent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Dirty: true, + Children: [16]node.Node{ + nil, + &node.Leaf{Key: []byte{4}}, + }, + }, + updated: true, + }, + "fully delete child of branch": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{3}}, + }, + }, + prefix: []byte{1, 0}, + newParent: &node.Leaf{ + Key: []byte{1}, + Value: []byte{1}, + Dirty: true, + }, + updated: true, + }, + "partially delete child of branch": { + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Branch{ // full key 1, 0, 3 + Key: []byte{3}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{ // full key 1, 0, 3, 0, 5 + Key: []byte{5}, + }, + }, + }, + }, + }, + prefix: []byte{1, 0, 3, 0}, + newParent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{ // full key 1, 0, 3 + Key: []byte{3}, + Value: []byte{1}, + Dirty: true, + }, + }, + }, + updated: true, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + trie := testCase.trie + + newParent, updated := trie.clearPrefix(testCase.parent, + testCase.prefix) + + assert.Equal(t, testCase.newParent, newParent) + assert.Equal(t, testCase.updated, updated) + assert.Equal(t, testCase.expectedTrie, trie) + }) + } +} + +func Test_Trie_Delete(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + key []byte + expectedTrie Trie + }{ + "nil key": { + trie: Trie{ + root: &node.Leaf{}, + }, + }, + "empty key": { + trie: Trie{ + root: &node.Leaf{}, + }, + }, + "empty trie": { + key: []byte{0x12}, + }, + "delete branch node": { + trie: Trie{ + root: &node.Branch{ + Key: []byte{1, 2}, + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{5}, + Value: []byte{97}, + }, + &node.Branch{ // full key in nibbles 1, 2, 1, 6 + Key: []byte{6}, + Value: []byte{98}, + Children: [16]node.Node{ + &node.Leaf{ // full key in nibbles 1, 2, 1, 6, 0, 7 + Key: []byte{7}, + Value: []byte{99}, + }, + }, + }, + }, + }, + }, + key: []byte{0x12, 0x16}, + expectedTrie: Trie{ + root: &node.Branch{ + Key: []byte{1, 2}, + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{5}, + Value: []byte{97}, + }, + &node.Leaf{ // full key in nibbles 1, 2, 1, 6 + Key: []byte{6, 0, 7}, + Value: []byte{99}, + Dirty: true, + }, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + // Check for no mutation + var expectedKey []byte + if testCase.key != nil { + expectedKey = make([]byte, len(testCase.key)) + copy(expectedKey, testCase.key) + } + + testCase.trie.Delete(testCase.key) + + assert.Equal(t, testCase.expectedTrie, testCase.trie) + assert.Equal(t, expectedKey, testCase.key) + }) + } +} + +func Test_Trie_delete(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + trie Trie + parent Node + key []byte + newParent Node + updated bool + }{ + "nil parent": { + key: []byte{1}, + }, + "leaf parent and nil key": { + parent: &node.Leaf{ + Key: []byte{1}, + }, + updated: true, + }, + "leaf parent and empty key": { + parent: &node.Leaf{ + Key: []byte{1}, + }, + key: []byte{}, + updated: true, + }, + "leaf parent matches key": { + parent: &node.Leaf{ + Key: []byte{1}, + }, + key: []byte{1}, + updated: true, + }, + "leaf parent mismatches key": { + parent: &node.Leaf{ + Key: []byte{1}, + }, + key: []byte{2}, + newParent: &node.Leaf{ + Key: []byte{1}, + }, + }, + "branch parent and nil key": { + trie: Trie{ + generation: 1, + }, + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{2}, + }, + }, + }, + newParent: &node.Leaf{ + Key: []byte{1, 0, 2}, + Dirty: true, + Generation: 1, + }, + updated: true, + }, + "branch parent and empty key": { + trie: Trie{ + generation: 1, + }, + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{2}, + }, + }, + }, + key: []byte{}, + newParent: &node.Leaf{ + Key: []byte{1, 0, 2}, + Dirty: true, + Generation: 1, + }, + updated: true, + }, + "branch parent matches key": { + trie: Trie{ + generation: 1, + }, + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{ + Key: []byte{2}, + }, + }, + }, + key: []byte{1}, + newParent: &node.Leaf{ + Key: []byte{1, 0, 2}, + Dirty: true, + Generation: 1, + }, + updated: true, + }, + "branch parent child matches key": { + trie: Trie{ + generation: 1, + }, + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{ // full key 1, 0, 2 + Key: []byte{2}, + }, + }, + }, + key: []byte{1, 0, 2}, + newParent: &node.Leaf{ + Key: []byte{1}, + Value: []byte{1}, + Dirty: true, + Generation: 1, + }, + updated: true, + }, + "branch parent mismatches key": { + trie: Trie{ + generation: 1, + }, + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + key: []byte{2}, + newParent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{}, + }, + }, + }, + "branch parent child mismatches key": { + trie: Trie{ + generation: 1, + }, + parent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{ // full key 1, 0, 2 + Key: []byte{2}, + }, + }, + }, + key: []byte{1, 0, 3}, + newParent: &node.Branch{ + Key: []byte{1}, + Value: []byte{1}, + Children: [16]node.Node{ + &node.Leaf{ // full key 1, 0, 2 + Key: []byte{2}, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + // Check for no mutation + var expectedKey []byte + if testCase.key != nil { + expectedKey = make([]byte, len(testCase.key)) + copy(expectedKey, testCase.key) + } + expectedTrie := *testCase.trie.DeepCopy() + + newParent, updated := testCase.trie.delete(testCase.parent, testCase.key) + + assert.Equal(t, testCase.newParent, newParent) + assert.Equal(t, testCase.updated, updated) + assert.Equal(t, expectedTrie, testCase.trie) + assert.Equal(t, expectedKey, testCase.key) + }) + } +} + +func Test_handleDeletion(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + branch *node.Branch + deletedKey []byte + newNode Node + }{ + "branch with value and without children": { + branch: &node.Branch{ + Key: []byte{1, 2, 3}, + Value: []byte{5, 6, 7}, + Generation: 1, + }, + deletedKey: []byte{1, 2, 3, 4}, + newNode: &node.Leaf{ + Key: []byte{1, 2, 3}, + Value: []byte{5, 6, 7}, + Generation: 1, + Dirty: true, + }, + }, + // branch without value and without children cannot happen + // since it would be turned into a leaf when it only has one child + // remaining. + "branch with value and a single child": { + branch: &node.Branch{ + Key: []byte{1, 2, 3}, + Value: []byte{5, 6, 7}, + Generation: 1, + Children: [16]node.Node{ + nil, + &node.Leaf{Key: []byte{9}}, + }, + }, + newNode: &node.Branch{ + Key: []byte{1, 2, 3}, + Value: []byte{5, 6, 7}, + Generation: 1, + Children: [16]node.Node{ + nil, + &node.Leaf{Key: []byte{9}}, + }, + }, + }, + "branch without value and a single leaf child": { + branch: &node.Branch{ + Key: []byte{1, 2, 3}, + Generation: 1, + Children: [16]node.Node{ + nil, + &node.Leaf{ // full key 1,2,3,1,9 + Key: []byte{9}, + Value: []byte{10}, + }, + }, + }, + deletedKey: []byte{1, 2, 3, 4}, + newNode: &node.Leaf{ + Key: []byte{1, 2, 3, 1, 9}, + Value: []byte{10}, + Generation: 1, + Dirty: true, + }, + }, + "branch without value and a single branch child": { + branch: &node.Branch{ + Key: []byte{1, 2, 3}, + Generation: 1, + Children: [16]node.Node{ + nil, + &node.Branch{ + Key: []byte{9}, + Value: []byte{10}, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{7}}, + nil, + &node.Leaf{Key: []byte{8}}, + }, + }, + }, + }, + newNode: &node.Branch{ + Key: []byte{1, 2, 3, 1, 9}, + Value: []byte{10}, + Generation: 1, + Dirty: true, + Children: [16]node.Node{ + &node.Leaf{Key: []byte{7}}, + nil, + &node.Leaf{Key: []byte{8}}, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + // Check for no mutation + var expectedKey []byte + if testCase.deletedKey != nil { + expectedKey = make([]byte, len(testCase.deletedKey)) + copy(expectedKey, testCase.deletedKey) + } + + newNode := handleDeletion(testCase.branch, testCase.deletedKey) + + assert.Equal(t, testCase.newNode, newNode) + assert.Equal(t, expectedKey, testCase.deletedKey) + }) + } +} + +func Test_lenCommonPrefix(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + a []byte + b []byte + length int + }{ + "nil slices": {}, + "empty slices": { + a: []byte{}, + b: []byte{}, + }, + "fully different": { + a: []byte{1, 2, 3}, + b: []byte{4, 5, 6}, + }, + "fully same": { + a: []byte{1, 2, 3}, + b: []byte{1, 2, 3}, + length: 3, + }, + "different and common prefix": { + a: []byte{1, 2, 3, 4}, + b: []byte{1, 2, 4, 4}, + length: 2, + }, + "first bigger than second": { + a: []byte{1, 2, 3}, + b: []byte{1, 2}, + length: 2, + }, + "first smaller than second": { + a: []byte{1, 2}, + b: []byte{1, 2, 3}, + length: 2, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + length := lenCommonPrefix(testCase.a, testCase.b) + + assert.Equal(t, testCase.length, length) + }) + } +}