github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/ledger/partial/ptrie/partialTrie.go (about)

     1  package ptrie
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/onflow/flow-go/ledger"
     7  	"github.com/onflow/flow-go/ledger/common/bitutils"
     8  	"github.com/onflow/flow-go/ledger/common/hash"
     9  )
    10  
    11  // PSMT (Partial Sparse Merkle Tree) holds a subset of an sparse merkle tree at specific
    12  // state (no historic views). Instead of keeping any unneeded branch, it only keeps
    13  // the hash of subtree. This implementation is fully stored in memory and doesn't use
    14  // a database.
    15  //
    16  // DEFINITIONS and CONVENTIONS:
    17  //   - HEIGHT of a node v in a tree is the number of edges on the longest downward path
    18  //     between v and a tree leaf. The height of a tree is the heights of its root.
    19  //     The height of a Trie is always the height of the fully-expanded tree.
    20  type PSMT struct {
    21  	root       *node // Root
    22  	pathLookUp map[ledger.Path]*node
    23  }
    24  
    25  // RootHash returns the rootNode hash value of the SMT
    26  func (p *PSMT) RootHash() ledger.RootHash {
    27  	return ledger.RootHash(p.root.Hash())
    28  }
    29  
    30  // GetSinglePayload returns payload of a given path
    31  func (p *PSMT) GetSinglePayload(path ledger.Path) (*ledger.Payload, error) {
    32  	node, found := p.pathLookUp[path]
    33  	if !found {
    34  		return nil, &ErrMissingPath{Paths: []ledger.Path{path}}
    35  	}
    36  	return node.payload, nil
    37  }
    38  
    39  // Get returns an slice of payloads (same order), an slice of failed paths and errors (if any)
    40  // TODO return list of indecies instead of paths
    41  func (p *PSMT) Get(paths []ledger.Path) ([]*ledger.Payload, error) {
    42  	var failedPaths []ledger.Path
    43  	payloads := make([]*ledger.Payload, len(paths))
    44  	for i, path := range paths {
    45  		// lookup the path for the payload
    46  		node, found := p.pathLookUp[path]
    47  		if !found {
    48  			failedPaths = append(failedPaths, path)
    49  			continue
    50  		}
    51  		payloads[i] = node.payload
    52  	}
    53  	if len(failedPaths) > 0 {
    54  		return nil, &ErrMissingPath{Paths: failedPaths}
    55  	}
    56  	return payloads, nil
    57  }
    58  
    59  // Update updates registers and returns rootValue after updates
    60  // in case of error, it returns a list of paths for which update failed
    61  func (p *PSMT) Update(paths []ledger.Path, payloads []*ledger.Payload) (ledger.RootHash, error) {
    62  	var failedPaths []ledger.Path
    63  	for i, path := range paths {
    64  		payload := payloads[i]
    65  		// lookup the path and update the value
    66  		node, found := p.pathLookUp[path]
    67  		if !found {
    68  			failedPaths = append(failedPaths, path)
    69  			continue
    70  		}
    71  		node.hashValue = ledger.ComputeCompactValue(hash.Hash(path), payload.Value(), node.height)
    72  	}
    73  	if len(failedPaths) > 0 {
    74  		return ledger.RootHash(hash.DummyHash), &ErrMissingPath{Paths: failedPaths}
    75  	}
    76  	// after updating all the nodes, compute the value recursively only once
    77  	return ledger.RootHash(p.root.forceComputeHash()), nil
    78  }
    79  
    80  // NewPSMT builds a Partial Sparse Merkle Tree (PSMT) given a chunkdatapack registertouches
    81  // TODO just accept batch proof as input
    82  func NewPSMT(
    83  	rootValue ledger.RootHash,
    84  	batchProof *ledger.TrieBatchProof,
    85  ) (*PSMT, error) {
    86  	height := ledger.NodeMaxHeight
    87  	psmt := PSMT{newNode(ledger.GetDefaultHashForHeight(height), height), make(map[ledger.Path]*node)}
    88  
    89  	// iterating over proofs for building the tree
    90  	for i, pr := range batchProof.Proofs {
    91  		if pr == nil {
    92  			return nil, fmt.Errorf("proof at index %d is nil", i)
    93  		}
    94  		path := pr.Path
    95  		payload := pr.Payload
    96  
    97  		// we process the path, bit by bit, until we reach the end of the proof (due to compactness)
    98  		prValueIndex := 0        // we keep track of our progress through proofs by prValueIndex
    99  		currentNode := psmt.root // start from the rootNode and walk down the tree
   100  		for j := 0; j < int(pr.Steps); j++ {
   101  			// if a flag (bit j in flags) is false, the value is a default value
   102  			// otherwise the value is stored in the proofs
   103  			defaultHash := ledger.GetDefaultHashForHeight(currentNode.height - 1)
   104  			v := defaultHash
   105  			flag := bitutils.ReadBit(pr.Flags, j)
   106  			if flag == 1 {
   107  				// use the proof at index prValueIndex
   108  				v = pr.Interims[prValueIndex]
   109  				prValueIndex++
   110  			}
   111  			bit := bitutils.ReadBit(path[:], j)
   112  			// look at the bit number j (left to right) for branching
   113  			if bit == 1 { // right branching
   114  				if currentNode.lChild == nil { // check left child
   115  					currentNode.lChild = newNode(v, currentNode.height-1)
   116  				}
   117  				if currentNode.rChild == nil { // create the right child if not exist
   118  					// Caution: we are temporarily initializing the node with default hash, which will later get updated to the
   119  					// proper value (if this is an interim node, its hash will be set when computing the root hash of the PTrie
   120  					// in the end; if this is a leaf, we'll set the hash at the end of processing the proof)
   121  					currentNode.rChild = newNode(defaultHash, currentNode.height-1)
   122  				}
   123  				currentNode = currentNode.rChild
   124  			} else { // left branching
   125  				if currentNode.rChild == nil { // check right child
   126  					currentNode.rChild = newNode(v, currentNode.height-1)
   127  				}
   128  				if currentNode.lChild == nil { // create the left child if not exist
   129  					// Caution: we are temporarily initializing the node with default hash, which will later get updated to the
   130  					// proper value (if this is an interim node, its hash will be set when computing the root hash of the PTrie
   131  					// in the end; if this is a leaf, we'll set the hash at the end of processing the proof)
   132  					currentNode.lChild = newNode(defaultHash, currentNode.height-1)
   133  				}
   134  				currentNode = currentNode.lChild
   135  			}
   136  		}
   137  
   138  		currentNode.payload = payload
   139  		// update node's hash value only for inclusion proofs (for others we assume default value)
   140  		if pr.Inclusion {
   141  			currentNode.hashValue = ledger.ComputeCompactValue(hash.Hash(path), payload.Value(), currentNode.height)
   142  		}
   143  		// keep a reference to this node by path (for update purpose)
   144  		psmt.pathLookUp[path] = currentNode
   145  	}
   146  
   147  	// check if the rootHash matches the root node's hash value of the partial trie
   148  	if ledger.RootHash(psmt.root.forceComputeHash()) != rootValue {
   149  		return nil, fmt.Errorf("rootNode hash doesn't match the proofs expected [%x], got [%x]", psmt.root.Hash(), rootValue)
   150  	}
   151  	return &psmt, nil
   152  }