diff --git a/.gitignore b/.gitignore index efc568f..2cb9fa1 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ .vscode .idea/ -*.log \ No newline at end of file +*.log +*.db \ No newline at end of file diff --git a/core/verkletrie/database/database.go b/core/verkletrie/database/database.go new file mode 100644 index 0000000..227d992 --- /dev/null +++ b/core/verkletrie/database/database.go @@ -0,0 +1,82 @@ +package database + +import ( + "errors" +) + +const IdealBatchSize = 100 * 1024 + +var ErrMissingNode = errors.New("missing node") + +type BatchWriter interface { + Put(key []byte, value []byte) error + Write() error + Reset() + Size() int +} + +type DB interface { + Set(key []byte, value []byte) error + Get(key []byte) ([]byte, error) + NewBatch() BatchWriter + Close() +} + +type InMemoryDB struct { + store map[string][]byte +} + +func NewInMemoryVerkleDB() *InMemoryDB { + return &InMemoryDB{ + store: make(map[string][]byte), + } +} + +func (m *InMemoryDB) Set(key []byte, value []byte) error { + m.store[string(key)] = value + return nil +} + +func (m *InMemoryDB) Get(key []byte) ([]byte, error) { + v, ok := m.store[string(key)] + if !ok { + return nil, ErrMissingNode + } + + return v, nil +} + +func (m *InMemoryDB) NewBatch() BatchWriter { + return &InMemoryBatch{ + store: make(map[string][]byte), + db: m, + } +} + +func (m *InMemoryDB) Close() { +} + +type InMemoryBatch struct { + store map[string][]byte + db *InMemoryDB +} + +func (m *InMemoryBatch) Put(key []byte, value []byte) error { + m.store[string(key)] = value + return nil +} + +func (m *InMemoryBatch) Write() error { + for k, v := range m.store { + m.db.Set([]byte(k), v) + } + return nil +} + +func (m *InMemoryBatch) Reset() { + m.store = make(map[string][]byte) +} + +func (m *InMemoryBatch) Size() int { + return len(m.store) +} diff --git a/core/verkletrie/database/rocksdb/rocksdb.go b/core/verkletrie/database/rocksdb/rocksdb.go new file mode 100644 index 0000000..910d3f2 --- /dev/null +++ b/core/verkletrie/database/rocksdb/rocksdb.go @@ -0,0 +1,141 @@ +// implements the database interface for verkle tree to store nodes +package rocksdb + +// type BatchWriter interface { +// Put(key []byte, value []byte) error +// Write() error +// Reset() +// Size() int +// } + +// type DB interface { +// Set(key []byte, value []byte) error +// Get(key []byte) ([]byte, error) +// NewBatch() BatchWriter +// } + +// package main + +import ( + "errors" + + "github.com/0chain/common/core/verkletrie/database" + + "github.com/linxGnu/grocksdb" +) + +// BatchWriter interface definition +// type BatchWriter interface { +// Put(key []byte, value []byte) error +// Write() error +// Reset() +// Size() int +// } + +// // DB interface definition +// type DB interface { +// Set(key []byte, value []byte) error +// Get(key []byte) ([]byte, error) +// NewBatch() BatchWriter +// } + +// rocksBatchWriter implements the BatchWriter interface using RocksDB +type rocksBatchWriter struct { + db *grocksdb.DB + wo *grocksdb.WriteOptions + batch *grocksdb.WriteBatch + size int +} + +func (w *rocksBatchWriter) Put(key []byte, value []byte) error { + w.batch.Put(key, value) + w.size++ + return nil +} + +func (w *rocksBatchWriter) Write() error { + if w.size == 0 { + return errors.New("batch is empty") + } + return w.db.Write(w.wo, w.batch) +} + +func (w *rocksBatchWriter) Reset() { + w.batch.Clear() + w.size = 0 +} + +func (w *rocksBatchWriter) Size() int { + return w.size +} + +// rocksDB implements the DB interface +type rocksDB struct { + db *grocksdb.DB + wo *grocksdb.WriteOptions + ro *grocksdb.ReadOptions +} + +func (r *rocksDB) Set(key []byte, value []byte) error { + return r.db.Put(r.wo, key, value) +} + +func (r *rocksDB) Get(key []byte) ([]byte, error) { + slice, err := r.db.Get(r.ro, key) + if err != nil { + return nil, database.ErrMissingNode + } + + if !slice.Exists() { + return nil, database.ErrMissingNode + } + + data := slice.Data() + if len(data) == 0 { + return nil, database.ErrMissingNode + } + + ret := make([]byte, len(data)) + copy(ret, data) + defer slice.Free() + return ret, nil +} + +func (r *rocksDB) NewBatch() database.BatchWriter { + return &rocksBatchWriter{ + db: r.db, + wo: r.wo, + batch: grocksdb.NewWriteBatch(), + size: 0, + } +} + +func (r *rocksDB) Close() { + r.db.Close() +} + +func defaultDBOptions() *grocksdb.Options { + bbto := grocksdb.NewDefaultBlockBasedTableOptions() + bbto.SetBlockCache(grocksdb.NewLRUCache(3 << 30)) + opts := grocksdb.NewDefaultOptions() + opts.SetKeepLogFileNum(5) + opts.SetBlockBasedTableFactory(bbto) + opts.SetCreateIfMissing(true) + return opts +} + +func NewRocksDB(path string) (database.DB, error) { + // opts := grocksdb.NewDefaultOptions() + // opts.SetCreateIfMissing(true) + opts := defaultDBOptions() + db, err := grocksdb.OpenDb(opts, path) + if err != nil { + return nil, err + } + + return &rocksDB{ + db: db, + wo: grocksdb.NewDefaultWriteOptions(), + ro: grocksdb.NewDefaultReadOptions(), + }, nil +} diff --git a/core/verkletrie/treekey.go b/core/verkletrie/treekey.go new file mode 100644 index 0000000..c603ddd --- /dev/null +++ b/core/verkletrie/treekey.go @@ -0,0 +1,117 @@ +package verkletrie + +import ( + "github.com/crate-crypto/go-ipa/bandersnatch/fr" + "github.com/ethereum/go-verkle" + "github.com/holiman/uint256" +) + +const ( + // the position between 2 and 63 are researved in case we need to define new fields + VERSION_LEAF_KEY = 0 // keys ending in 00: version (could be allocation version), which is always set to 0 + FILE_HASH_LEAF_KEY = 1 // keys ending in 01: file hash, could be the root hash of the file content + STORAGE_SIZE_LEAF_KEY = 2 // keys ending in 10: the size of the storage + HEADER_STORAGE_OFFSET = 16 // the offset of the storage in the tree + VERKLE_NODE_WIDTH = 256 // the width of the verkle node + // MAIN_STORAGE_OFFSET = 256 ^ 31 // the offset of the main storage in the tree +) + +var ( + getTreePolyIndex0Point *verkle.Point +) + +var ( + zero = uint256.NewInt(0) + headerStorageOffset = uint256.NewInt(HEADER_STORAGE_OFFSET) + verkleNodeWidth = uint256.NewInt(VERKLE_NODE_WIDTH) + headerStorageCap = VERKLE_NODE_WIDTH - HEADER_STORAGE_OFFSET // the capacity of the header storage, which is (256-16)*32=7168 Bytes(7KB) + mainStorageOffset = new(uint256.Int).Lsh(uint256.NewInt(1), 248 /* 8 * 31*/) +) + +// GetTreeKeyForFileHash returns file hash +func GetTreeKeyForFileHash(filepathHash []byte) []byte { + return GetTreeKey(filepathHash, zero, FILE_HASH_LEAF_KEY) +} + +func GetTreeKeyForStorageSize(filepathHash []byte) []byte { + return GetTreeKey(filepathHash, zero, STORAGE_SIZE_LEAF_KEY) +} + +func GetTreeKeyForStorageSlot(filepathHash []byte, storageKey uint64) []byte { + pos := uint256.NewInt(storageKey) + if storageKey < uint64(headerStorageCap) { + // storage in the header + pos.Add(headerStorageOffset, pos) + } else { + // stroage in the main storage + pos.Add(mainStorageOffset, pos) + } + + subIdx := uint256.NewInt(0) + pos.DivMod(pos, verkleNodeWidth, subIdx) + if subIdx.Eq(zero) { + return GetTreeKey(filepathHash, pos, 0) + } else { + return GetTreeKey(filepathHash, pos, subIdx.Bytes()[0]) + } +} + +func init() { + // The byte array is the Marshalled output of the point computed as such: + //cfg, _ := verkle.GetConfig() + //verkle.FromLEBytes(&getTreePolyIndex0Fr[0], []byte{2, 64}) + //= cfg.CommitToPoly(getTreePolyIndex0Fr[:], 1) + getTreePolyIndex0Point = new(verkle.Point) + err := getTreePolyIndex0Point.SetBytes([]byte{34, 25, 109, 242, 193, 5, 144, 224, 76, 52, 189, 92, 197, 126, 9, 145, 27, 152, 199, 130, 165, 3, 210, 27, 193, 131, 142, 28, 110, 26, 16, 191}) + if err != nil { + panic(err) + } +} + +// https://github.com/gballet/go-ethereum/blob/a586f0d253bdaec8bc0ee5849fe5f3137ef0ab43/trie/utils/verkle.go#L95 +// GetTreeKey performs both the work of the spec's get_tree_key function, and that +// of pedersen_hash: it builds the polynomial in pedersen_hash without having to +// create a mostly zero-filled buffer and "type cast" it to a 128-long 16-byte +// array. Since at most the first 5 coefficients of the polynomial will be non-zero, +// these 5 coefficients are created directly. +func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte { + if len(address) < 32 { + var aligned [32]byte + address = append(aligned[:32-len(address)], address...) + } + + // poly = [2+256*64, address_le_low, address_le_high, tree_index_le_low, tree_index_le_high] + var poly [5]fr.Element + + // 32-byte address, interpreted as two little endian + // 16-byte numbers. + verkle.FromLEBytes(&poly[1], address[:16]) + verkle.FromLEBytes(&poly[2], address[16:]) + + // treeIndex must be interpreted as a 32-byte aligned little-endian integer. + // e.g: if treeIndex is 0xAABBCC, we need the byte representation to be 0xCCBBAA00...00. + // poly[3] = LE({CC,BB,AA,00...0}) (16 bytes), poly[4]=LE({00,00,...}) (16 bytes). + // + // To avoid unnecessary endianness conversions for go-ipa, we do some trick: + // - poly[3]'s byte representation is the same as the *top* 16 bytes (trieIndexBytes[16:]) of + // 32-byte aligned big-endian representation (BE({00,...,AA,BB,CC})). + // - poly[4]'s byte representation is the same as the *low* 16 bytes (trieIndexBytes[:16]) of + // the 32-byte aligned big-endian representation (BE({00,00,...}). + trieIndexBytes := treeIndex.Bytes32() + verkle.FromBytes(&poly[3], trieIndexBytes[16:]) + verkle.FromBytes(&poly[4], trieIndexBytes[:16]) + + cfg := verkle.GetConfig() + ret := cfg.CommitToPoly(poly[:], 0) + + // add a constant point corresponding to poly[0]=[2+256*64]. + ret.Add(ret, getTreePolyIndex0Point) + + return pointToHash(ret, subIndex) +} + +func pointToHash(evaluated *verkle.Point, suffix byte) []byte { + retb := verkle.HashPointToBytes(evaluated) + retb[31] = suffix + return retb[:] +} diff --git a/core/verkletrie/verkletrie.go b/core/verkletrie/verkletrie.go new file mode 100644 index 0000000..08789b3 --- /dev/null +++ b/core/verkletrie/verkletrie.go @@ -0,0 +1,410 @@ +package verkletrie + +import ( + "encoding/hex" + "fmt" + "sync/atomic" + + "github.com/pkg/errors" + + "github.com/0chain/common/core/verkletrie/database" + "github.com/ethereum/go-verkle" + "github.com/holiman/uint256" +) + +const defaultMaxRollbacks = 5 + +var DBVerkleNodeKeyPrefix = []byte("verkle_node_") + +var ChunkSize = uint256.NewInt(32) // 32 + +type Hash [32]byte + +type VerkleTrie struct { + db database.DB + rootKey []byte // the key in db where the whole serialized verkle trie to be persisted. Could be the allocation id + root verkle.VerkleNode + prevRoots []verkle.VerkleNode // previous root nodes for rollback + maxRollbacks int +} + +func rootKey(key string) []byte { + return append(DBVerkleNodeKeyPrefix, key...) +} + +type OptionFunc func(*VerkleTrie) + +func WithMaxRollbacks(maxRollbacks int) OptionFunc { + return func(vt *VerkleTrie) { + vt.maxRollbacks = maxRollbacks + } +} + +func New(key string, db database.DB, options ...OptionFunc) *VerkleTrie { + var root verkle.VerkleNode + rootKey := rootKey(key) + + vt := &VerkleTrie{ + db: db, + rootKey: rootKey, + maxRollbacks: defaultMaxRollbacks, + } + + for _, option := range options { + option(vt) + } + + payload, err := db.Get([]byte(rootKey)) + if err != nil { + if err == database.ErrMissingNode { + root := verkle.New() + vt.root = root + vt.prevRoots = []verkle.VerkleNode{root.Copy()} + return vt + } + panic(err) + } + + root, err = verkle.ParseNode(payload, 0) + if err != nil { + panic(err) + } + + vt.root = root + vt.prevRoots = []verkle.VerkleNode{root.Copy()} + return vt +} + +func (m *VerkleTrie) dbKey(key []byte) []byte { + return append([]byte(m.rootKey), key...) +} + +func (m *VerkleTrie) nodeResolver(key []byte) ([]byte, error) { + return m.db.Get(append(m.rootKey, key...)) +} + +func (m *VerkleTrie) getWithHashedKey(key []byte) ([]byte, error) { + return m.root.Get(key, m.nodeResolver) +} + +func (m *VerkleTrie) getFileRootHash(filepathHash []byte) ([]byte, error) { + key := GetTreeKeyForFileHash(filepathHash) + return m.getWithHashedKey(key) +} + +func (m *VerkleTrie) deleteFileRootHash(filepathHash []byte) (bool, error) { + key := GetTreeKeyForFileHash(filepathHash) + return m.deleteWithHashedKey(key) +} + +func (m *VerkleTrie) insertFileRootHash(filepathHash []byte, rootHash []byte) error { + key := GetTreeKeyForFileHash(filepathHash) + return m.insert(key, rootHash) +} + +func (m *VerkleTrie) insertValue(filepathHash []byte, data []byte) error { + // insert the value size + storageSizeKey := GetTreeKeyForStorageSize(filepathHash) + vb := uint256.NewInt(uint64(len(data))).Bytes32() + if err := m.insert(storageSizeKey, vb[:]); err != nil { + return errors.Wrap(err, "insert storage size") + } + + chunks := getStorageDataChunks(data) + for i, chunk := range chunks { + chunkKey := GetTreeKeyForStorageSlot(filepathHash, uint64(i)) + if err := m.insert(chunkKey, chunk); err != nil { + return errors.Wrap(err, "insert storage chunk") + } + } + return nil +} + +func (m *VerkleTrie) deleteValue(filepathHash []byte) error { + storageSizeKey := GetTreeKeyForStorageSize(filepathHash) + sizeBytes, err := m.getWithHashedKey(storageSizeKey) + if err != nil { + return errors.Wrap(err, "delete value error on getting storage size") + } + + size := new(uint256.Int).SetBytes(sizeBytes) + // remove all chunks nodes + chunkNum := getChunkNum(*size) + for i := 0; i < int(chunkNum); i++ { + chunkKey := GetTreeKeyForStorageSlot(filepathHash, uint64(i)) + _, err = m.deleteWithHashedKey(chunkKey) + if err != nil { + return errors.Wrap(err, "delete value error on deleting storage chunk") + } + } + + // delete the storage size node + if _, err := m.deleteWithHashedKey(storageSizeKey); err != nil { + return errors.Wrap(err, "delete storage size") + } + + return nil +} + +func getChunkNum(size uint256.Int) uint64 { + mod := new(uint256.Int) + size.DivMod(&size, ChunkSize, mod) + if mod.CmpUint64(0) > 0 { + return size.Uint64() + 1 + } + return size.Uint64() +} + +func getStorageDataChunks(data []byte) [][]byte { + size := len(data) + chunkSize := int(ChunkSize.Uint64()) + chunks := make([][]byte, 0, size/chunkSize+1) + + chunkNum := size / chunkSize + for i := 0; i < chunkNum; i++ { + chunks = append(chunks, data[i*chunkSize:(i+1)*chunkSize]) + } + if size%chunkSize > 0 { + chunks = append(chunks, data[chunkNum*chunkSize:]) + } + return chunks +} + +func (m *VerkleTrie) getValue(filepathHash []byte) ([]byte, error) { + storageSizeKey := GetTreeKeyForStorageSize(filepathHash) + sizeBytes, err := m.getWithHashedKey(storageSizeKey) + if err != nil { + return nil, err + } + + size := new(uint256.Int).SetBytes(sizeBytes) + if size.Uint64() == 0 { + return nil, nil + } + + mod := new(uint256.Int) + chunkNum := new(uint256.Int) + chunkNum, mod = size.DivMod(size, ChunkSize, mod) + + valueBytes := make([]byte, 0, size.Uint64()) + for i := uint64(0); i < chunkNum.Uint64(); i++ { + chunkKey := GetTreeKeyForStorageSlot(filepathHash, i) + chunk, err := m.getWithHashedKey(chunkKey) + if err != nil { + return nil, err + } + + valueBytes = append(valueBytes, chunk...) + } + if mod.Uint64() > 0 { + chunkKey := GetTreeKeyForStorageSlot(filepathHash, chunkNum.Uint64()) + chunk, err := m.getWithHashedKey(chunkKey) + if err != nil { + return nil, err + } + valueBytes = append(valueBytes, chunk[:mod.Uint64()]...) + } + return valueBytes, nil +} + +func (m *VerkleTrie) insert(key []byte, value []byte) error { + return m.root.Insert(key, value, m.nodeResolver) +} + +func (m *VerkleTrie) deleteWithHashedKey(key []byte) (bool, error) { + return m.root.Delete(key, m.nodeResolver) +} + +func (m *VerkleTrie) Hash() Hash { + return m.root.Commit().Bytes() +} + +func (m *VerkleTrie) Commit() Hash { + return m.root.Commit().Bytes() +} + +var flushCount int32 + +func (m *VerkleTrie) flushFunc(p []byte, node verkle.VerkleNode) { + nodeBytes, err := node.Serialize() + if err != nil { + panic(fmt.Errorf("serializing node: %s", err)) + } + + rootKeyLen := len(m.rootKey) + path := make([]byte, 0, rootKeyLen+32) + path = append(path, []byte(m.rootKey)...) + path = append(path[:rootKeyLen], p...) + if err := m.db.Set(path, nodeBytes[:]); err != nil { + panic(fmt.Errorf("put node to disk: %s", err)) + } + + atomic.AddInt32(&flushCount, 1) +} + +func (m *VerkleTrie) Flush() { + m.root.Commit() + newRoot := m.root.Copy() + if m.prevRoots[len(m.prevRoots)-1].Hash().Equal(newRoot.Hash()) { + // do nothing as there's no changes happened + return + } + + m.prevRoots = append(m.prevRoots, newRoot) + if len(m.prevRoots) > m.maxRollbacks+1 { // +1 for because the init root will take a place in the prevRoots + m.prevRoots = m.prevRoots[1:] + } + + m.root.(*verkle.InternalNode).Flush(m.flushFunc) +} + +func (m *VerkleTrie) Rollback() error { + prevRoot := m.prevRoots[len(m.prevRoots)-1] + if prevRoot.Hash().Equal(m.root.Hash()) { + if len(m.prevRoots) <= 1 { + // current node is the oldest nodes, do nothing + return errors.New("nothing to rollback") + } + + // move to previous root + m.root = m.prevRoots[len(m.prevRoots)-2].Copy() + m.prevRoots = m.prevRoots[:len(m.prevRoots)-1] + m.root.(*verkle.InternalNode).Flush(m.flushFunc) + // fmt.Printf("rollback to %x\n", m.root.Hash().Bytes()) + return nil + } + + m.root = m.prevRoots[len(m.prevRoots)-1].Copy() + m.prevRoots = m.prevRoots[:len(m.prevRoots)-1] + fmt.Println("prev root num:", len(m.prevRoots)) + m.root.(*verkle.InternalNode).Flush(m.flushFunc) + return nil +} + +func (m *VerkleTrie) InsertFileMeta(filepathHash []byte, rootHash, metaData []byte) error { + if err := m.insertFileRootHash(filepathHash, rootHash); err != nil { + return err + } + + // insert metaData + return m.insertValue(filepathHash, metaData) +} + +func (m *VerkleTrie) DeleteFileMeta(filepathHash []byte) error { + _, err := m.deleteWithHashedKey(GetTreeKeyForFileHash(filepathHash)) + if err != nil { + return err + } + + return m.deleteValue(filepathHash) +} + +func (m *VerkleTrie) GetFileMetaRootHash(filepathHash []byte) ([]byte, error) { + return m.getFileRootHash(filepathHash) +} + +func (m *VerkleTrie) GetFileMeta(filepathHash []byte) ([]byte, error) { + return m.getValue(filepathHash) +} + +type Keylist [][]byte + +func makeProof(trie *VerkleTrie, keys Keylist) (*verkle.VerkleProof, verkle.StateDiff, error) { + proof, _, _, _, err := verkle.MakeVerkleMultiProof(trie.root, nil, keys, trie.nodeResolver) + if err != nil { + return nil, nil, err + } + + return verkle.SerializeProof(proof) +} + +func MakeProofFileMeta(trie *VerkleTrie, files [][]byte) (*verkle.VerkleProof, verkle.StateDiff, error) { + keys := make([][]byte, 0, len(files)) + for _, file := range files { + keys = append(keys, GetTreeKeyForFileHash(file)) + } + proof, _, _, _, err := verkle.MakeVerkleMultiProof(trie.root, nil, keys, trie.nodeResolver) + if err != nil { + return nil, nil, err + } + + return verkle.SerializeProof(proof) +} + +func VerifyProofPresenceFileMeta(vp *verkle.VerkleProof, stateDiff verkle.StateDiff, stateRoot []byte, files Keylist) error { + keys := make([][]byte, 0, len(files)) + for _, file := range files { + keys = append(keys, GetTreeKeyForFileHash(file)) + } + return verifyProofPresence(vp, stateDiff, stateRoot, keys) +} + +func verifyProof(vp *verkle.VerkleProof, stateDiff verkle.StateDiff, stateRoot []byte) (*verkle.Proof, error) { + dproof, err := verkle.DeserializeProof(vp, stateDiff) + if err != nil { + return nil, fmt.Errorf("verkle proof deserialization error: %w", err) + } + + root := new(verkle.Point) + if err := root.SetBytes(stateRoot); err != nil { + return nil, fmt.Errorf("verkle root deserialization error: %w", err) + } + + tree, err := verkle.PreStateTreeFromProof(dproof, root) + if err != nil { + return nil, fmt.Errorf("error rebuilding the pre-tree from proof: %w", err) + } + + if err := verkle.VerifyVerkleProofWithPreState(dproof, tree); err != nil { + return nil, fmt.Errorf("verkle proof verification error: %w", err) + } + + return dproof, nil +} + +// verifyProofPresence verifies that the verkle proof is valid and keys are presence in the state tree +func verifyProofPresence(vp *verkle.VerkleProof, stateDiff verkle.StateDiff, stateRoot []byte, keys Keylist) error { + if _, err := verifyProof(vp, stateDiff, stateRoot); err != nil { + return err + } + + // v, _ := json.MarshalIndent(stateDiff, "", " ") + // fmt.Println(string(v)) + + sdMap := make(map[string][32]byte, len(stateDiff)) + for _, sd := range stateDiff { + for _, su := range sd.SuffixDiffs { + path := append(sd.Stem[:], su.Suffix) + if su.CurrentValue != nil { + sdMap[string(path)] = *su.CurrentValue + } + } + } + + for _, key := range keys { + if _, ok := sdMap[string(key)]; !ok { + return fmt.Errorf("verkle proof could not find key: %x", key) + } + } + + return nil +} + +func VerifyProofAbsence(vp *verkle.VerkleProof, stateDiff verkle.StateDiff, stateRoot []byte, keys Keylist) error { + dproof, err := verifyProof(vp, stateDiff, stateRoot) + if err != nil { + return err + } + + for _, v := range dproof.PreValues { + if len(v) != 0 { + return errors.New("verkle proof contains value") + } + } + return nil +} + +func HexToBytes(s string) []byte { + v, _ := hex.DecodeString(s) + return v +} diff --git a/core/verkletrie/verkletrie_test.go b/core/verkletrie/verkletrie_test.go new file mode 100644 index 0000000..a4921cc --- /dev/null +++ b/core/verkletrie/verkletrie_test.go @@ -0,0 +1,869 @@ +package verkletrie + +import ( + "crypto/rand" + "encoding/json" + "flag" + "fmt" + "os" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/0chain/common/core/verkletrie/database" + "github.com/0chain/common/core/verkletrie/database/rocksdb" + "github.com/ethereum/go-verkle" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var keys = [][]byte{ + HexToBytes("a3b45d6e7f890123456789abcdef0123456789abcdef0123456789abcdef0123"), + HexToBytes("123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0"), + HexToBytes("abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"), + HexToBytes("f10a5b26c19e3d94b6a87fe0c41269abd4e29a935fb7e4cd9a51f8b1272d3a68"), + HexToBytes("43e1b7c9f20d5e76b4a1c8f62e9a03d57b9c16e3fa12b7a589d20c4f7e5a8c3b"), + HexToBytes("e6d2a1f94c10b3e87f5d2b9c4a3ea62f709b52d68e13c4d7a8b61f9eb3c2475a"), + HexToBytes("a58c3e7b12f4d96a3b8e27f0a1c76e5d49f2b6a13d0c5e28f7b19d4a2c07b831"), + HexToBytes("f094e7a3b8d2c5f1093b4e76a8d2b5c4e1f7a3965d2c0e4b8a5d19f2b3c7e8b4"), + HexToBytes("1a9f4c2d3b6e7a5f8d29c3b14e07a6d5c8b3f2d7a1e4b9c5d702b8f1c3a9d4e6"), + HexToBytes("7e8c3b2f6a1d5e0c8b29f4a713d6e5c8f7a2b1d0e9c5a4b8f36e7d12c8b0a4f9"), +} + +var benchKeys = [][]byte{ + HexToBytes("de34efbdcb37c44397e5213f295ce973edfd6e3ebd586ff4be20dd308d875635"), + HexToBytes("9d79cd3523ad43ca5849a3c93c9aeb2a2a1e09e7b129a24583eb79ee914ca685"), + HexToBytes("8e0b76196671d514b7b26e5478e459c34339a965ad168424184b33e11c7503c3"), + HexToBytes("a2e17cc6bedd7c8079a25ef6f7ee2f1da3f773ae8d993e7836b2d22cd1697dfc"), + HexToBytes("dc52d23c1940f342f228aa307cb436bde167a68fca3d005a9cbfca8648c23a63"), + HexToBytes("a262530eb4e165305ce7e70f5480a9f6f74d18b67467c3a6ce5259a2e5f79c21"), + HexToBytes("28d75ae78e0ee004ec8ac7691129916d5aac021b80b247712b4816cb5b2245ed"), + HexToBytes("887658a80146b0cd5fd87c3c5e01699432025425cc70be24a134331484db8bd2"), + HexToBytes("62046f78350e648a3dab7747222b9e4822f44800432ef83b929c62748bcd3212"), + HexToBytes("357c0778187bce682a78331d7e8496838c241345635327b53335ea1dfa69e938"), +} + +var ( + mainStorageLargeValue = []byte{} + once sync.Once + // dbType can be "inmemory" or "rocksdb" + // var dbType = "rocksdb" + dbType = "rocksdb" + // dbType = "inmemory" +) + +var generate bool + +func TestMain(m *testing.M) { + flag.BoolVar(&generate, "gen", false, "generate test data") + flag.Parse() + + mainStorageLargeValue = make([]byte, 0, 128) + for i := 0; i < 128; i++ { + mainStorageLargeValue = append(mainStorageLargeValue, keys[0][:]...) + } + + if generate && dbType == "rocksdb" { + // generate the ./testdata/bench.db if it's the first time to run the benchmark + makeBenchRocksDB1MSmall() + makeBenchRocksDBLargeValue() + makeBenchRocksDB1KNodes() + makeBenchRocksDB1KLargeNodes() + } + + os.Exit(m.Run()) +} + +func testPrepareDB(t testing.TB) (database.DB, func()) { + switch { + case dbType == "inmemory": + return database.NewInMemoryVerkleDB(), func() {} + case dbType == "rocksdb": + return makeRocksDB(t) + } + return nil, nil +} + +func makeBenchRocksDB1MSmall() { + dbPath := "./testdata/bench.db" + fmt.Println("dbPath:", dbPath) + db, err := rocksdb.NewRocksDB(dbPath) + if err != nil { + panic(err) + } + defer db.Close() + vt := New("alloc_1", db) + for i := 0; i < 1000000; i++ { + var key []byte + if i < len(benchKeys) { + key = benchKeys[i] + } else { + randBytes := make([]byte, 32) + rand.Read(randBytes) + key = randBytes[:] + } + // fmt.Printf("%x\n", key) + err := vt.insertValue(key[:], key[:]) + if err != nil { + panic(err) + } + } + vt.Flush() +} + +func makeBenchRocksDBLargeValue() { + dbPath := fmt.Sprintf("./testdata/bench_large.db") + db, err := rocksdb.NewRocksDB(dbPath) + if err != nil { + panic(err) + } + defer db.Close() + vt := New("alloc_1", db) + for i := 0; i < 10000; i++ { + key := []byte{} + if i < len(benchKeys) { + key = benchKeys[i] + } else { + randBytes := make([]byte, 32) + rand.Read(randBytes) + key = randBytes[:] + } + err := vt.insertValue(key[:], mainStorageLargeValue) + if err != nil { + panic(err) + } + } + vt.Flush() +} +func makeBenchRocksDB1KNodes() { + dbPath := fmt.Sprintf("./testdata/bench_1k.db") + db, err := rocksdb.NewRocksDB(dbPath) + if err != nil { + panic(err) + } + defer db.Close() + vt := New("alloc_1", db) + for i := 0; i < 1000; i++ { + key := []byte{} + if i < len(benchKeys) { + key = benchKeys[i] + } else { + randBytes := make([]byte, 32) + rand.Read(randBytes) + key = randBytes[:] + } + err := vt.insertValue(key[:], mainStorageLargeValue) + if err != nil { + panic(err) + } + } + vt.Flush() +} + +func makeBenchRocksDB1KLargeNodes() { + dbPath := fmt.Sprintf("./testdata/bench_1k_large.db") + db, err := rocksdb.NewRocksDB(dbPath) + if err != nil { + panic(err) + } + defer db.Close() + vt := New("alloc_1", db) + for i := 0; i < 1000; i++ { + key := []byte{} + if i < len(benchKeys) { + key = benchKeys[i] + } else { + randBytes := make([]byte, 32) + rand.Read(randBytes) + key = randBytes[:] + } + err := vt.insertValue(key[:], mainStorageLargeValue) + if err != nil { + panic(err) + } + } + vt.Flush() +} + +func getBenchRocksDB1KLargeDB() database.DB { + dbPath := "./testdata/bench_1k_large.db" + db, err := rocksdb.NewRocksDB(dbPath) + if err != nil { + panic(err) + } + return db +} + +func getBenchRocksDB1MSmall() database.DB { + dbPath := "./testdata/bench.db" + db, err := rocksdb.NewRocksDB(dbPath) + if err != nil { + panic(err) + } + + return db +} + +func getBenchRocksDBLarge() database.DB { + dbPath := "./testdata/bench_large.db" + db, err := rocksdb.NewRocksDB(dbPath) + if err != nil { + panic(err) + } + return db +} + +func getBenchRocksDB1K() database.DB { + dbPath := "./testdata/bench_1k.db" + db, err := rocksdb.NewRocksDB(dbPath) + if err != nil { + panic(err) + } + return db +} + +func makeRocksDB(t testing.TB) (db database.DB, clean func()) { + dbPath := fmt.Sprintf("./testdata/%s_%d.db", t.Name(), time.Now().Nanosecond()) + dbDir := filepath.Dir(dbPath) + os.MkdirAll(dbDir, os.ModePerm) + + var err error + db, err = rocksdb.NewRocksDB(dbPath) + if err != nil { + t.Fatal(err) + } + return db, func() { + db.Close() + if err := os.RemoveAll(dbPath); err != nil { + t.Fatal(err) + } + } +} + +func TestVerkleTrie_Insert(t *testing.T) { + // t.Parallel() + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + + // Insert some data + err := vt.insert(keys[0], []byte("value1")) + assert.Nil(t, err) + err = vt.insert(keys[1], []byte("value2")) + assert.Nil(t, err) + + // Check that the data is there + value, err := vt.getWithHashedKey(keys[0]) + assert.Nil(t, err) + assert.Equal(t, []byte("value1"), value) + + value, err = vt.getWithHashedKey(keys[1]) + assert.Nil(t, err) + assert.Equal(t, []byte("value2"), value) +} + +func TestVerkleTrie_Delete(t *testing.T) { + // t.Parallel() + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + + // Insert some data + err := vt.insert(keys[0], []byte("value1")) + assert.Nil(t, err) + err = vt.insert(keys[1], []byte("value2")) + assert.Nil(t, err) + + // Delete some data + _, err = vt.deleteWithHashedKey(keys[0]) + assert.Nil(t, err) + + // Check that the data is no longer there + value, err := vt.getWithHashedKey(keys[0]) + assert.Nil(t, err) + assert.Nil(t, value) + + value, err = vt.getWithHashedKey(keys[1]) + assert.Nil(t, err) + assert.Equal(t, []byte("value2"), value) +} + +func TestVerkleTrie_Flush(t *testing.T) { + // t.Parallel() + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + + err := vt.insert(keys[0], keys[0]) + assert.Nil(t, err) + + err = vt.insert(keys[1], keys[1]) + assert.Nil(t, err) + + // Commit the tree + vt.Flush() + fmt.Println("flush count:", flushCount) + oldFlushCount := flushCount + + // fmt.Println(string(verkle.ToDot(vt.root))) + + // Create a new tree with the db + newVt := New("alloc_1", db) + // Check if the data can be acquired + value, err := newVt.getWithHashedKey(keys[0]) + assert.Nil(t, err) + assert.Equal(t, keys[0], value) + + value, err = newVt.getWithHashedKey(keys[1]) + assert.Nil(t, err) + assert.Equal(t, keys[1], value) + + err = newVt.insert(keys[2], keys[2]) + assert.Nil(t, err) + newVt.Flush() + fmt.Println("new flush count:", flushCount-oldFlushCount) +} + +func TestTreeKeyStorage(t *testing.T) { + // t.Parallel() + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + + filepathHash := keys[0] + rootHash := keys[1] + // insert file: alloc1/testfile.txt + key := GetTreeKeyForFileHash(filepathHash) + err := vt.insert(key, rootHash) + assert.Nil(t, err) + + vt.Flush() + + v, err := vt.getWithHashedKey(key) + assert.Nil(t, err) + + assert.Equal(t, rootHash, v) + + bigValue := append(keys[0], keys[1]...) + err = vt.insertValue(filepathHash, bigValue) + assert.Nil(t, err) + + vt.Flush() + + vv, err := vt.getValue(filepathHash) + assert.Nil(t, err) + + assert.Equal(t, bigValue, vv) +} + +func TestTreeStorageLargeData(t *testing.T) { + // t.Parallel() + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + filepathHash := keys[0] + + mainStoreChunkNum := 1000 + totalChunkNum := headerStorageCap + mainStoreChunkNum + + values := make([]byte, 0, totalChunkNum*int(ChunkSize.Uint64())) + // test to use out all header spaces for storage + for i := 0; i < totalChunkNum; i++ { + values = append(values, keys[0][:]...) + } + + err := vt.insertValue(filepathHash, values) + assert.Nil(t, err) + + vv, err := vt.getValue(filepathHash) + assert.Nil(t, err) + require.Equal(t, values, vv) + + vt.Flush() + + vt = New("alloc_1", db) + fmt.Println("-----------------------------------") + + v, err := vt.getValue(filepathHash) + assert.Nil(t, err) + + assert.Equal(t, values, v) +} + +func TestInsertsNodeChanges(t *testing.T) { + // t.Parallel() + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + for i := 0; i < len(keys[:7]); i++ { + err := vt.insertValue(keys[i], keys[i]) + assert.Nil(t, err) + } + + vt.Flush() + oldC := flushCount + fmt.Println("flush count:", flushCount) + + vt.insert(keys[7], keys[7]) + vt.Flush() + fmt.Println("new flush count:", flushCount-oldC) +} + +func TestProof(t *testing.T) { + // t.Parallel() + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + for i := 0; i < len(keys[:3]); i++ { + err := vt.insert(keys[i], keys[i]) + assert.Nil(t, err) + } + + root := vt.Commit() + dproof, stateDiff, err := makeProof(vt, keys[:3]) + assert.Nil(t, err) + + dproofBytes, err := json.Marshal(dproof) + assert.Nil(t, err) + + stateDiffBytes, err := json.Marshal(stateDiff) + assert.Nil(t, err) + + // deserialize dproof + dproof2 := &verkle.VerkleProof{} + err = json.Unmarshal(dproofBytes, dproof2) + assert.Nil(t, err) + + // deserialize stateDiff + stateDiff2 := verkle.StateDiff{} + err = json.Unmarshal(stateDiffBytes, &stateDiff2) + assert.Nil(t, err) + + err = verifyProofPresence(dproof2, stateDiff2, root[:], keys[:3]) + assert.Nil(t, err) +} + +func TestProofNotExistKey(t *testing.T) { + // t.Parallel() + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + for i := 0; i < len(keys[:3]); i++ { + err := vt.insert(keys[i], keys[i]) + assert.Nil(t, err) + } + + root := vt.Commit() + + t.Run("proof no key exists", func(t *testing.T) { + dp, sdiff, err := makeProof(vt, keys[3:]) + assert.Nil(t, err) + + err = VerifyProofAbsence(dp, sdiff, root[:], keys[3:]) + assert.Nil(t, err) + }) + + t.Run("proof absence of exist key - should fail", func(t *testing.T) { + dp, sdiff, err := makeProof(vt, keys[2:]) + assert.Nil(t, err) + + err = VerifyProofAbsence(dp, sdiff, root[:], keys[2:]) + assert.EqualError(t, err, "verkle proof contains value") + }) +} + +func TestDeleteFileRootHash(t *testing.T) { + // t.Parallel() + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + for i := 0; i < len(keys[:3]); i++ { + err := vt.insertFileRootHash(keys[i], keys[i]) + assert.Nil(t, err) + } + + vt.Commit() + _, err := vt.deleteFileRootHash(keys[2]) + assert.Nil(t, err) + vt.Commit() + + // Verify that the root hash of the file is deleted + v2, err := vt.getFileRootHash(keys[2]) + assert.Nil(t, err) + assert.Nil(t, v2) + + v1, err := vt.getFileRootHash(keys[1]) + assert.Nil(t, err) + assert.NotNil(t, v1) +} + +func TestDeleteValue(t *testing.T) { + // t.Parallel() + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + for i := 0; i < len(keys[:3]); i++ { + err := vt.insertValue(keys[i], mainStorageLargeValue[:]) + assert.Nil(t, err) + } + + vt.Commit() + + vb, err := vt.getValue(keys[0]) + assert.Nil(t, err) + assert.Equal(t, mainStorageLargeValue[:], vb) + + err = vt.deleteValue(keys[0]) + assert.Nil(t, err) + + // verify that the value is deleted + vv, err := vt.getValue(keys[0]) + assert.Nil(t, err) + assert.Nil(t, vv) + + // assert that all related nodes are deleted + storageSizeKey := GetTreeKeyForStorageSize(keys[0]) + sv, err := vt.getWithHashedKey(storageSizeKey) + assert.Nil(t, err) + assert.Nil(t, sv) + + // assert that all chunks are deleted + size := len(mainStorageLargeValue) + chunkNum := size / int(ChunkSize.Uint64()) + if size%int(ChunkSize.Uint64()) > 0 { + chunkNum++ + } + for i := 0; i < chunkNum; i++ { + chunkKey := GetTreeKeyForStorageSlot(keys[0], uint64(i)) + cv, err := vt.getWithHashedKey(chunkKey) + assert.Nil(t, err) + assert.Nil(t, cv) + } +} + +func BenchmarkInsertSmallValue(b *testing.B) { + db := getBenchRocksDB1MSmall() + // db := getBenchRocksDBLarge() + defer db.Close() + + vt := New("alloc_1", db) + for i := 0; i < b.N; i++ { + randBytes := make([]byte, 32) + rand.Read(randBytes) + key := randBytes[:] + + err := vt.insertValue(key, key[:]) + assert.Nil(b, err) + vt.Flush() + } +} + +func BenchmarkInsertLargeValue(b *testing.B) { + db := getBenchRocksDBLarge() + defer db.Close() + + vt := New("alloc_1", db) + for i := 0; i < b.N; i++ { + randBytes := make([]byte, 32) + rand.Read(randBytes) + key := randBytes[:] + + err := vt.insertValue(key, mainStorageLargeValue) + assert.Nil(b, err) + vt.Flush() + } +} + +// func BenchmarkMakeProof2(b *testing.B) { +// db := getBenchRocksDB() +// defer db.Close() + +// vt := New("alloc_1", db) +// for i := 0; i < b.N; i++ { +// // randBytes := make([]byte, 32) +// // rand.Read(randBytes) +// key := smallValueKeys[i%len(smallValueKeys)] +// // err := vt.InsertValue(key, key[:]) +// // assert.Nil(b, err) +// // vt.Flush() +// MakeProof(vt, Keylist{key}) +// } +// } + +// var largeValue1KNodeKeys = [][]byte{ +// HexToBytes("cdb8b26ac25ececa8bd26aa67a6dbb11af4a75b303fb17bf644e08ba4276ea07"), +// HexToBytes("dc5a39e1e3494e87e06a9647b3e0c33657226410a06a2d61c530c68e2cbbe1ab"), +// HexToBytes("199ffb5ce1205c8a60e1b905c7730154d2812113224b9321a9362da61745ee73"), +// HexToBytes("ceb9d23653fd5163ac8409ce8878e0c82e46ee4d281f0e23b96e9f89ed5091d5"), +// HexToBytes("bfb6896cbbb07b273b6616a5ba9c4928e17e4bf812b7804a1c93baa0ca4f48bf"), +// HexToBytes("3e16ee78e18c62c7c9fb52535e4fe10958c519b549039a62621cf9f32cea203d"), +// HexToBytes("aeac233ad49984e3c96d53caf0e0f4b760a15bab01c21a233e4f904578a096f5"), +// HexToBytes("88eb70d652ef57e7f873e4b03f38a2144be905ae90ff54111b2ac62e7b93829a"), +// HexToBytes("c653552478a06c7075e21f64f166fc4a687499843bdc9e521fa106a5bb012d15"), +// HexToBytes("37d3367def3a1b8d75237c7099d44ff38a6b1a70250ac4fde7dc6f0378c08856"), +// } + +func BenchmarkMakeProof(b *testing.B) { + b.Run("1k small nodes", func(b *testing.B) { + db := getBenchRocksDB1K() + // db := getBenchRocksDB1KLargeDB() + // db := getBenchRocksDBLarge() + defer db.Close() + // db, clean := testPrepareDB(b) + // defer clean() + vt := New("alloc_1", db) + for i := 0; i < b.N; i++ { + key := benchKeys[i%len(benchKeys)] + makeProof(vt, Keylist{key}) + } + }) + + b.Run("1k large nodes", func(b *testing.B) { + db := getBenchRocksDB1KLargeDB() + defer db.Close() + vt := New("alloc_1", db) + for i := 0; i < b.N; i++ { + key := benchKeys[i%len(benchKeys)] + makeProof(vt, Keylist{key}) + } + }) + + b.Run("10k large nodes", func(b *testing.B) { + db := getBenchRocksDBLarge() + defer db.Close() + vt := New("alloc_1", db) + for i := 0; i < b.N; i++ { + key := benchKeys[i%len(benchKeys)] + makeProof(vt, Keylist{key}) + } + }) + + b.Run("1M small nodes", func(b *testing.B) { + db := getBenchRocksDB1MSmall() + defer db.Close() + vt := New("alloc_1", db) + for i := 0; i < b.N; i++ { + key := benchKeys[i%len(benchKeys)] + makeProof(vt, Keylist{key}) + } + + }) + + b.Run("2 small nodes", func(b *testing.B) { + db, clean := testPrepareDB(b) + defer clean() + vt := New("alloc_1", db) + // Insert some data + err := vt.insert(keys[0], []byte("value1")) + assert.Nil(b, err) + err = vt.insert(keys[1], []byte("value2")) + assert.Nil(b, err) + vt.Commit() + + for i := 0; i < b.N; i++ { + key := keys[i%2] + makeProof(vt, Keylist{key}) + } + }) +} + +func BenchmarkVerifyProof(b *testing.B) { + db, clean := testPrepareDB(b) + defer clean() + + vt := New("alloc_1", db) + for i := 0; i < len(keys); i++ { + err := vt.insertFileRootHash(keys[i], keys[i]) + assert.Nil(b, err) + } + + vt.Flush() + root := vt.Hash() + + key := GetTreeKeyForFileHash(keys[0]) + vp, sd, err := makeProof(vt, Keylist{key}) + assert.Nil(b, err) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + err = verifyProofPresence(vp, sd, root[:], Keylist{key}) + assert.Nil(b, err) + } +} + +func TestFileMeta(t *testing.T) { + db, clean := testPrepareDB(t) + defer clean() + + vt := New("alloc_1", db) + + // insert + err := vt.InsertFileMeta(keys[0], keys[1], keys[2]) + assert.Nil(t, err) + vt.Flush() + + // get file root hash + rootHash, err := vt.GetFileMetaRootHash(keys[0]) + assert.Nil(t, err) + assert.Equal(t, keys[1], rootHash) + + // get file meta + meta, err := vt.GetFileMeta(keys[0]) + assert.Nil(t, err) + assert.Equal(t, keys[2], meta) + + // delete file meta + err = vt.DeleteFileMeta(keys[0]) + assert.Nil(t, err) + + rootHash, err = vt.getFileRootHash(keys[0]) + assert.Nil(t, err) + assert.Nil(t, rootHash) + + meta, err = vt.getValue(keys[0]) + assert.Nil(t, err) + assert.Nil(t, meta) +} + +func TestProofFileMeta(t *testing.T) { + db, clean := testPrepareDB(t) + defer clean() + + vt := New("alloc_1", db) + for i := 0; i < len(keys)-1; i++ { + err := vt.InsertFileMeta(keys[i], keys[i], keys[i]) + assert.Nil(t, err) + } + + vt.Flush() + root := vt.Hash() + vp, sd, err := MakeProofFileMeta(vt, keys) + assert.Nil(t, err) + + t.Run("proof key exists", func(t *testing.T) { + err = VerifyProofPresenceFileMeta(vp, sd, root[:], Keylist{keys[0]}) + assert.Nil(t, err) + }) + + t.Run("proof key does not exist", func(t *testing.T) { + noneExistKey := keys[len(keys)-1] + err = VerifyProofPresenceFileMeta(vp, sd, root[:], Keylist{noneExistKey}) + assert.NotNil(t, err) + }) +} +func TestRollback(t *testing.T) { + + // t.Run("rollback", func(t *testing.T) { + // db, clean := testPrepareDB(t) + // defer clean() + // vt := New("alloc_1", db) + // vt.InsertFileMeta(keys[0], keys[1], keys[1]) + // vt.Flush() + + // vt.InsertFileMeta(keys[0], keys[2], keys[2]) + // vt.Flush() + + // vold, err := vt.GetFileMeta(keys[0]) + // assert.Nil(t, err) + // assert.Equal(t, keys[2], vold) + // // fmt.Println(verkle.ToDot(vt.root)) + + // vt.Rollback() + // vv, _ := vt.GetFileMeta(keys[0]) + // assert.Equal(t, keys[1], vv) + // }) + + // t.Run("rollback 2 times", func(t *testing.T) { + // db, clean := testPrepareDB(t) + // defer clean() + // vt := New("alloc_1", db) + // vt.InsertFileMeta(keys[0], keys[1], keys[1]) + // vt.Flush() + + // vt.InsertFileMeta(keys[0], keys[2], keys[2]) + // vt.Flush() + + // vt.InsertFileMeta(keys[0], keys[3], keys[3]) + // vt.Flush() + + // err := vt.Rollback() + // assert.Nil(t, err) + // err = vt.Rollback() + // assert.Nil(t, err) + + // vold, err := vt.GetFileMeta(keys[0]) + // assert.Nil(t, err) + // assert.Equal(t, keys[1], vold) + // }) + + t.Run("rollback err", func(t *testing.T) { + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + vt.InsertFileMeta(keys[0], keys[1], keys[1]) + vt.Flush() + + err := vt.Rollback() + assert.Nil(t, err) + + err = vt.Rollback() + require.Error(t, err) + + vold, err := vt.GetFileMeta(keys[0]) + assert.Nil(t, err) + assert.Nil(t, vold) + }) + + t.Run("rollback max depth", func(t *testing.T) { + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db) + vt.InsertFileMeta(keys[0], keys[9], keys[9]) + vt.Flush() + + for i := 0; i < defaultMaxRollbacks; i++ { + vt.InsertFileMeta(keys[0], keys[i], keys[i]) + vt.Flush() + } + + for i := 0; i < defaultMaxRollbacks; i++ { + err := vt.Rollback() + assert.Nil(t, err) + } + + err := vt.Rollback() + assert.NotNil(t, err) + + vold, err := vt.GetFileMeta(keys[0]) + assert.Nil(t, err) + assert.Equal(t, keys[9], vold) + }) + + t.Run("rollback custom max rollback", func(t *testing.T) { + db, clean := testPrepareDB(t) + defer clean() + vt := New("alloc_1", db, WithMaxRollbacks(2)) + vt.InsertFileMeta(keys[0], keys[9], keys[9]) + vt.Flush() + + for i := 0; i < 2; i++ { + vt.InsertFileMeta(keys[0], keys[i], keys[i]) + vt.Flush() + } + + for i := 0; i < 2; i++ { + err := vt.Rollback() + assert.Nil(t, err) + } + + err := vt.Rollback() + assert.NotNil(t, err) + + vold, err := vt.GetFileMeta(keys[0]) + assert.Nil(t, err) + assert.Equal(t, keys[9], vold) + }) +} diff --git a/go.mod b/go.mod index 77a397a..c80bb00 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,12 @@ module github.com/0chain/common go 1.18 require ( + github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c + github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 github.com/hashicorp/golang-lru v0.5.4 + github.com/holiman/uint256 v1.2.4 github.com/linxGnu/grocksdb v1.8.0 + github.com/pkg/errors v0.9.1 github.com/shopspring/decimal v1.3.1 github.com/spf13/cast v1.5.0 github.com/spf13/pflag v1.0.5 @@ -13,16 +17,20 @@ require ( github.com/tinylib/msgp v1.1.6 go.uber.org/atomic v1.7.0 go.uber.org/zap v1.21.0 - golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d + golang.org/x/crypto v0.10.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) require ( + github.com/bits-and-blooms/bitset v1.13.0 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/philhofer/fwd v1.1.2-0.20210722190033-5c56ac6d0bb9 // indirect @@ -32,11 +40,13 @@ require ( github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.10.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect ) replace github.com/tinylib/msgp => github.com/0chain/msgp v1.1.62 diff --git a/go.sum b/go.sum index e4702ae..404885a 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= +github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -51,6 +53,12 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -60,6 +68,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -117,6 +127,7 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -127,6 +138,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -134,16 +147,20 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/linxGnu/grocksdb v1.8.0 h1:H4L/LhP7GOMf1j17oQAElHgVlbEje2h14A8Tz9cM2BE= github.com/linxGnu/grocksdb v1.8.0/go.mod h1:09CeBborffXhXdNpEcOeZrLKEnRtrZFEpFdPNI9Zjjg= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= @@ -212,8 +229,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -300,6 +317,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -337,8 +356,8 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -346,8 +365,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -493,8 +512,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -518,3 +537,5 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=