istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/ledger/ledger.go (about) 1 // Copyright 2019 Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package ledger implements a modified map with three unique characteristics: 16 // 1. every unique state of the map is given a unique hash 17 // 2. prior states of the map are retained for a fixed period of time 18 // 2. given a previous hash, we can retrieve a previous state from the map, if it is still retained. 19 package ledger 20 21 import ( 22 "encoding/base64" 23 "time" 24 25 "github.com/spaolacci/murmur3" 26 ) 27 28 // Ledger exposes a modified map with three unique characteristics: 29 // 1. every unique state of the map is given a unique hash 30 // 2. prior states of the map are retained for a fixed period of time 31 // 2. given a previous hash, we can retrieve a previous state from the map, if it is still retained. 32 type Ledger interface { 33 // Put adds or overwrites a key in the Ledger 34 Put(key, value string) (string, error) 35 // Delete removes a key from the Ledger, which may still be read using GetPreviousValue 36 Delete(key string) error 37 // Get returns a the value of the key from the Ledger's current state 38 Get(key string) (string, error) 39 // RootHash is the hash of all keys and values currently in the Ledger 40 RootHash() string 41 // GetPreviousValue executes a get against a previous version of the ledger, using that version's root hash. 42 GetPreviousValue(previousRootHash, key string) (result string, err error) 43 } 44 45 type smtLedger struct { 46 tree *smt 47 } 48 49 // Make returns a Ledger which will retain previous nodes after they are deleted. 50 func Make(retention time.Duration) Ledger { 51 return smtLedger{tree: newSMT(hasher, nil, retention)} 52 } 53 54 // Put adds a key value pair to the ledger, overwriting previous values and marking them for 55 // removal after the retention specified in Make() 56 func (s smtLedger) Put(key, value string) (result string, err error) { 57 b, err := s.tree.Update([][]byte{coerceKeyToHashLen(key)}, [][]byte{coerceToHashLen(value)}) 58 result = string(b) 59 return 60 } 61 62 // Delete removes a key value pair from the ledger, marking it for removal after the retention specified in Make() 63 func (s smtLedger) Delete(key string) (err error) { 64 _, err = s.tree.Update([][]byte{[]byte(key)}, [][]byte{defaultLeaf}) 65 return 66 } 67 68 // GetPreviousValue returns the value of key when the ledger's RootHash was previousHash, if it is still retained. 69 func (s smtLedger) GetPreviousValue(previousRootHash, key string) (result string, err error) { 70 prevBytes, err := base64.StdEncoding.DecodeString(previousRootHash) 71 if err != nil { 72 return "", err 73 } 74 b, err := s.tree.GetPreviousValue(prevBytes, coerceKeyToHashLen(key)) 75 var i int 76 // trim leading 0's from b 77 for i = range b { 78 if b[i] != 0 { 79 break 80 } 81 } 82 result = string(b[i:]) 83 return 84 } 85 86 // Get returns the current value of key. 87 func (s smtLedger) Get(key string) (result string, err error) { 88 return s.GetPreviousValue(s.RootHash(), key) 89 } 90 91 // RootHash represents the hash of the current state of the ledger. 92 func (s smtLedger) RootHash() string { 93 return base64.StdEncoding.EncodeToString(s.tree.Root()) 94 } 95 96 func coerceKeyToHashLen(val string) []byte { 97 hasher := murmur3.New64() 98 _, _ = hasher.Write([]byte(val)) 99 return hasher.Sum(nil) 100 } 101 102 func coerceToHashLen(val string) []byte { 103 // hash length is fixed at 64 bits until generic support is added 104 const hashLen = 64 105 byteVal := []byte(val) 106 if len(byteVal) < hashLen/8 { 107 // zero fill the left side of the slice 108 zerofill := make([]byte, hashLen/8) 109 byteVal = append(zerofill[:hashLen/8-len(byteVal)], byteVal...) 110 } 111 return byteVal[:hashLen/8] 112 }