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 }