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  }