github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/scripts/go-ffi/trie.go (about)

     1  package main
     2  
     3  import (
     4  	"crypto/rand"
     5  	"fmt"
     6  	"log"
     7  	"math/big"
     8  	"os"
     9  
    10  	"github.com/ethereum/go-ethereum/accounts/abi"
    11  	"github.com/ethereum/go-ethereum/common"
    12  	"github.com/ethereum/go-ethereum/common/hexutil"
    13  	"github.com/ethereum/go-ethereum/core/rawdb"
    14  	"github.com/ethereum/go-ethereum/rlp"
    15  	"github.com/ethereum/go-ethereum/trie"
    16  )
    17  
    18  // Variant enum
    19  const (
    20  	// Generate a test case with a valid proof of inclusion for the k/v pair in the trie.
    21  	valid string = "valid"
    22  	// Generate an invalid test case with an extra proof element attached to an otherwise
    23  	// valid proof of inclusion for the passed k/v.
    24  	extraProofElems = "extra_proof_elems"
    25  	// Generate an invalid test case where the proof is malformed.
    26  	corruptedProof = "corrupted_proof"
    27  	// Generate an invalid test case where a random element of the proof has more bytes than the
    28  	// length designates within the RLP list encoding.
    29  	invalidDataRemainder = "invalid_data_remainder"
    30  	// Generate an invalid test case where a long proof element is incorrect for the root.
    31  	invalidLargeInternalHash = "invalid_large_internal_hash"
    32  	// Generate an invalid test case where a small proof element is incorrect for the root.
    33  	invalidInternalNodeHash = "invalid_internal_node_hash"
    34  	// Generate a valid test case with a key that has been given a random prefix
    35  	prefixedValidKey = "prefixed_valid_key"
    36  	// Generate a valid test case with a proof of inclusion for an empty key.
    37  	emptyKey = "empty_key"
    38  	// Generate an invalid test case with a partially correct proof
    39  	partialProof = "partial_proof"
    40  )
    41  
    42  // Generate an abi-encoded `trieTestCase` of a specified variant
    43  func FuzzTrie() {
    44  	variant := os.Args[2]
    45  	if len(variant) < 1 {
    46  		log.Fatal("Must pass a variant to the trie fuzzer!")
    47  	}
    48  
    49  	var testCase trieTestCase
    50  	switch variant {
    51  	case valid:
    52  		testCase = genTrieTestCase(false)
    53  	case extraProofElems:
    54  		testCase = genTrieTestCase(false)
    55  		// Duplicate the last element of the proof
    56  		testCase.Proof = append(testCase.Proof, [][]byte{testCase.Proof[len(testCase.Proof)-1]}...)
    57  	case corruptedProof:
    58  		testCase = genTrieTestCase(false)
    59  
    60  		// Re-encode a random element within the proof
    61  		idx := randRange(0, int64(len(testCase.Proof)))
    62  		encoded, _ := rlp.EncodeToBytes(testCase.Proof[idx])
    63  		testCase.Proof[idx] = encoded
    64  	case invalidDataRemainder:
    65  		testCase = genTrieTestCase(false)
    66  
    67  		// Alter true length of random proof element by appending random bytes
    68  		// Do not update the encoded length
    69  		idx := randRange(0, int64(len(testCase.Proof)))
    70  		b := make([]byte, randRange(1, 512))
    71  		if _, err := rand.Read(b); err != nil {
    72  			log.Fatal("Error generating random bytes")
    73  		}
    74  		testCase.Proof[idx] = append(testCase.Proof[idx], b...)
    75  	case invalidLargeInternalHash:
    76  		testCase = genTrieTestCase(false)
    77  
    78  		// Clobber 4 bytes within a list element of a random proof element
    79  		// TODO: Improve this by decoding the proof elem and choosing random
    80  		// bytes to overwrite.
    81  		idx := randRange(1, int64(len(testCase.Proof)))
    82  		b := make([]byte, 4)
    83  		if _, err := rand.Read(b); err != nil {
    84  			log.Fatal("Error generating random bytes")
    85  		}
    86  		testCase.Proof[idx] = append(
    87  			testCase.Proof[idx][:20],
    88  			append(
    89  				b,
    90  				testCase.Proof[idx][24:]...,
    91  			)...,
    92  		)
    93  	case invalidInternalNodeHash:
    94  		testCase = genTrieTestCase(false)
    95  		// Assign the last proof element to an encoded list containing a
    96  		// random 29 byte value
    97  		b := make([]byte, 29)
    98  		if _, err := rand.Read(b); err != nil {
    99  			log.Fatal("Error generating random bytes")
   100  		}
   101  		e, _ := rlp.EncodeToBytes(b)
   102  		testCase.Proof[len(testCase.Proof)-1] = append([]byte{0xc0 + 30}, e...)
   103  	case prefixedValidKey:
   104  		testCase = genTrieTestCase(false)
   105  
   106  		b := make([]byte, randRange(1, 16))
   107  		if _, err := rand.Read(b); err != nil {
   108  			log.Fatal("Error generating random bytes")
   109  		}
   110  		testCase.Key = append(b, testCase.Key...)
   111  	case emptyKey:
   112  		testCase = genTrieTestCase(true)
   113  	case partialProof:
   114  		testCase = genTrieTestCase(false)
   115  
   116  		// Cut the proof in half
   117  		proofLen := len(testCase.Proof)
   118  		newProof := make([][]byte, proofLen/2)
   119  		for i := 0; i < proofLen/2; i++ {
   120  			newProof[i] = testCase.Proof[i]
   121  		}
   122  
   123  		testCase.Proof = newProof
   124  	default:
   125  		log.Fatal("Invalid variant passed to trie fuzzer!")
   126  	}
   127  
   128  	// Print encoded test case with no newline so that foundry's FFI can read the output
   129  	fmt.Print(testCase.AbiEncode())
   130  }
   131  
   132  // Generate a random test case for Bedrock's MerkleTrie verifier.
   133  func genTrieTestCase(selectEmptyKey bool) trieTestCase {
   134  	// Create an empty merkle trie
   135  	memdb := rawdb.NewMemoryDatabase()
   136  	randTrie := trie.NewEmpty(trie.NewDatabase(memdb, nil))
   137  
   138  	// Get a random number of elements to put into the trie
   139  	randN := randRange(2, 1024)
   140  	// Get a random key/value pair to generate a proof of inclusion for
   141  	randSelect := randRange(0, randN)
   142  
   143  	// Create a fixed-length key as well as a randomly-sized value
   144  	// We create these out of the loop to reduce mem allocations.
   145  	randKey := make([]byte, 32)
   146  	randValue := make([]byte, randRange(2, 1024))
   147  
   148  	// Randomly selected key/value for proof generation
   149  	var key []byte
   150  	var value []byte
   151  
   152  	// Add `randN` elements to the trie
   153  	for i := int64(0); i < randN; i++ {
   154  		// Randomize the contents of `randKey` and `randValue`
   155  		if _, err := rand.Read(randKey); err != nil {
   156  			log.Fatal("Error generating random bytes")
   157  		}
   158  		if _, err := rand.Read(randValue); err != nil {
   159  			log.Fatal("Error generating random bytes")
   160  		}
   161  
   162  		// Clear the selected key if `selectEmptyKey` is true
   163  		if i == randSelect && selectEmptyKey {
   164  			randKey = make([]byte, 0)
   165  		}
   166  
   167  		// Insert the random k/v pair into the trie
   168  		if err := randTrie.Update(randKey, randValue); err != nil {
   169  			log.Fatal("Error adding key-value pair to trie")
   170  		}
   171  
   172  		// If this is our randomly selected k/v pair, store it in `key` & `value`
   173  		if i == randSelect {
   174  			key = randKey
   175  			value = randValue
   176  		}
   177  	}
   178  
   179  	// Generate proof for `key`'s inclusion in our trie
   180  	var proof proofList
   181  	if err := randTrie.Prove(key, &proof); err != nil {
   182  		log.Fatal("Error creating proof for randomly selected key's inclusion in generated trie")
   183  	}
   184  
   185  	// Create our test case with the data collected
   186  	testCase := trieTestCase{
   187  		Root:  randTrie.Hash(),
   188  		Key:   key,
   189  		Value: value,
   190  		Proof: proof,
   191  	}
   192  
   193  	return testCase
   194  }
   195  
   196  // Represents a test case for bedrock's `MerkleTrie.sol`
   197  type trieTestCase struct {
   198  	Root  common.Hash
   199  	Key   []byte
   200  	Value []byte
   201  	Proof [][]byte
   202  }
   203  
   204  // Tuple type to encode `TrieTestCase`
   205  var (
   206  	trieTestCaseTuple, _ = abi.NewType("tuple", "TrieTestCase", []abi.ArgumentMarshaling{
   207  		{Name: "root", Type: "bytes32"},
   208  		{Name: "key", Type: "bytes"},
   209  		{Name: "value", Type: "bytes"},
   210  		{Name: "proof", Type: "bytes[]"},
   211  	})
   212  
   213  	encoder = abi.Arguments{
   214  		{Type: trieTestCaseTuple},
   215  	}
   216  )
   217  
   218  // Encodes the trieTestCase as the `trieTestCaseTuple`.
   219  func (t *trieTestCase) AbiEncode() string {
   220  	// Encode the contents of the struct as a tuple
   221  	packed, err := encoder.Pack(&t)
   222  	if err != nil {
   223  		log.Fatalf("Error packing TrieTestCase: %v", err)
   224  	}
   225  
   226  	// Remove the pointer and encode the packed bytes as a hex string
   227  	return hexutil.Encode(packed[32:])
   228  }
   229  
   230  // Helper that generates a cryptographically secure random 64-bit integer
   231  // between the range [min, max)
   232  func randRange(min int64, max int64) int64 {
   233  	r, err := rand.Int(rand.Reader, new(big.Int).Sub(new(big.Int).SetInt64(max), new(big.Int).SetInt64(min)))
   234  	if err != nil {
   235  		log.Fatal("Failed to generate random number within bounds")
   236  	}
   237  
   238  	return (new(big.Int).Add(r, new(big.Int).SetInt64(min))).Int64()
   239  }