-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from DarkLord017/Implemented_execution_proof_g…
…o_execution_errors_go_#30 Implemented_execution_proof_go_execution_errors_go_#30
- Loading branch information
Showing
2 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package execution | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/BlocSoc-iitr/selene/consensus/consensus_core" | ||
"github.com/BlocSoc-iitr/selene/consensus/types" | ||
"github.com/ethereum/go-ethereum/accounts/abi" | ||
) | ||
|
||
// ExecutionError represents various execution-related errors | ||
type ExecutionError struct { | ||
Kind string | ||
Details interface{} | ||
} | ||
|
||
func (e *ExecutionError) Error() string { | ||
switch e.Kind { | ||
case "InvalidAccountProof": | ||
return fmt.Sprintf("invalid account proof for string: %v", e.Details) | ||
case "InvalidStorageProof": | ||
details := e.Details.([]interface{}) | ||
return fmt.Sprintf("invalid storage proof for string: %v, slot: %v", details[0], details[1]) | ||
case "CodeHashMismatch": | ||
details := e.Details.([]interface{}) | ||
return fmt.Sprintf("code hash mismatch for string: %v, found: %v, expected: %v", details[0], details[1], details[2]) | ||
case "ReceiptRootMismatch": | ||
return fmt.Sprintf("receipt root mismatch for tx: %v", e.Details) | ||
case "MissingTransaction": | ||
return fmt.Sprintf("missing transaction for tx: %v", e.Details) | ||
case "NoReceiptForTransaction": | ||
return fmt.Sprintf("could not prove receipt for tx: %v", e.Details) | ||
case "MissingLog": | ||
details := e.Details.([]interface{}) | ||
return fmt.Sprintf("missing log for transaction: %v, index: %v", details[0], details[1]) | ||
case "TooManyLogsToProve": | ||
details := e.Details.([]interface{}) | ||
return fmt.Sprintf("too many logs to prove: %v, current limit is: %v", details[0], details[1]) | ||
case "IncorrectRpcNetwork": | ||
return "execution RPC is for the incorrect network" | ||
case "InvalidBaseGasFee": | ||
details := e.Details.([]interface{}) | ||
return fmt.Sprintf("Invalid base gas fee selene %v vs rpc endpoint %v at block %v", details[0], details[1], details[2]) | ||
case "InvalidGasUsedRatio": | ||
details := e.Details.([]interface{}) | ||
return fmt.Sprintf("Invalid gas used ratio of selene %v vs rpc endpoint %v at block %v", details[0], details[1], details[2]) | ||
case "BlockNotFoundError": | ||
return fmt.Sprintf("Block %v not found", e.Details) | ||
case "EmptyExecutionPayload": | ||
return "Selene Execution Payload is empty" | ||
case "InvalidBlockRange": | ||
details := e.Details.([]interface{}) | ||
return fmt.Sprintf("User query for block %v but selene oldest block is %v", details[0], details[1]) | ||
default: | ||
return "unknown execution error" | ||
} | ||
} | ||
|
||
// Helper functions to create specific ExecutionError instances | ||
func NewInvalidAccountProofError(address types.Address) error { | ||
return &ExecutionError{"InvalidAccountProof", address} | ||
} | ||
|
||
func NewInvalidStorageProofError(address types.Address, slot consensus_core.Bytes32) error { | ||
return &ExecutionError{"InvalidStorageProof", []interface{}{address, slot}} | ||
} | ||
|
||
func NewCodeHashMismatchError(address types.Address, found consensus_core.Bytes32, expected consensus_core.Bytes32) error { | ||
return &ExecutionError{"CodeHashMismatch", []interface{}{address, found, expected}} | ||
} | ||
|
||
func NewReceiptRootMismatchError(tx consensus_core.Bytes32) error { | ||
return &ExecutionError{"ReceiptRootMismatch", tx} | ||
} | ||
|
||
func NewMissingTransactionError(tx consensus_core.Bytes32) error { | ||
return &ExecutionError{"MissingTransaction", tx} | ||
} | ||
|
||
func NewNoReceiptForTransactionError(tx consensus_core.Bytes32) error { | ||
return &ExecutionError{"NoReceiptForTransaction", tx} | ||
} | ||
|
||
func NewMissingLogError(tx consensus_core.Bytes32, index uint64) error { | ||
return &ExecutionError{"MissingLog", []interface{}{tx, index}} | ||
} | ||
|
||
func NewTooManyLogsToProveError(count int, limit int) error { | ||
return &ExecutionError{"TooManyLogsToProve", []interface{}{count, limit}} | ||
} | ||
|
||
func NewIncorrectRpcNetworkError() error { | ||
return &ExecutionError{"IncorrectRpcNetwork", nil} | ||
} | ||
|
||
func NewInvalidBaseGasFeeError(selene uint64, rpc uint64, block uint64) error { | ||
return &ExecutionError{"InvalidBaseGasFee", []interface{}{selene, rpc, block}} | ||
} | ||
|
||
func NewInvalidGasUsedRatioError(seleneRatio float64, rpcRatio float64, block uint64) error { | ||
return &ExecutionError{"InvalidGasUsedRatio", []interface{}{seleneRatio, rpcRatio, block}} | ||
} | ||
|
||
func NewBlockNotFoundError(block uint64) error { | ||
return &ExecutionError{"BlockNotFoundError", block} | ||
} | ||
|
||
func NewEmptyExecutionPayloadError() error { | ||
return &ExecutionError{"EmptyExecutionPayload", nil} | ||
} | ||
|
||
func NewInvalidBlockRangeError(queryBlock uint64, oldestBlock uint64) error { | ||
return &ExecutionError{"InvalidBlockRange", []interface{}{queryBlock, oldestBlock}} | ||
} | ||
|
||
// EvmError represents EVM-related errors | ||
type EvmError struct { | ||
Kind string | ||
Details interface{} | ||
} | ||
|
||
func (e *EvmError) Error() string { | ||
switch e.Kind { | ||
case "Revert": | ||
return fmt.Sprintf("execution reverted: %v", e.Details) | ||
case "Generic": | ||
return fmt.Sprintf("evm error: %v", e.Details) | ||
case "RpcError": | ||
return fmt.Sprintf("rpc error: %v", e.Details) | ||
default: | ||
return "unknown evm error" | ||
} | ||
} | ||
|
||
// Helper functions for creating specific EVM errors | ||
func NewRevertError(data []byte) error { | ||
return &EvmError{"Revert", data} | ||
} | ||
|
||
func NewGenericError(message string) error { | ||
return &EvmError{"Generic", message} | ||
} | ||
|
||
func NewRpcError(report error) error { | ||
return &EvmError{"RpcError", report} | ||
} | ||
|
||
func DecodeRevertReason(data []byte) string { | ||
reason, err := abi.UnpackRevert(data) | ||
if err != nil { | ||
reason = string(err.Error()) | ||
} | ||
return reason | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
package execution | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"math/big" | ||
|
||
"github.com/ethereum/go-ethereum/rlp" | ||
"golang.org/x/crypto/sha3" | ||
) | ||
|
||
// EIP1186AccountProofResponse for account proof encoding | ||
type EIP1186AccountProofResponse struct { | ||
Nonce uint64 | ||
Balance *big.Int | ||
StorageHash [32]byte | ||
CodeHash [32]byte | ||
} | ||
|
||
func VerifyProof(proof [][]byte, root []byte, path []byte, value []byte) (bool, error) { | ||
expectedHash := root | ||
pathOffset := 0 | ||
|
||
for i, node := range proof { | ||
if !bytes.Equal(expectedHash, keccak256(node)) { | ||
return false, nil | ||
} | ||
|
||
var nodeList [][]byte | ||
if err := rlp.DecodeBytes(node, &nodeList); err != nil { | ||
fmt.Println("Error decoding node:", err) | ||
return false, err | ||
} | ||
|
||
if len(nodeList) == 17 { | ||
if i == len(proof)-1 { | ||
// exclusion proof | ||
nibble := getNibble(path, pathOffset) | ||
if len(nodeList[nibble]) == 0 && isEmptyValue(value) { | ||
return true, nil | ||
} | ||
} else { | ||
nibble := getNibble(path, pathOffset) | ||
expectedHash = nodeList[nibble] | ||
pathOffset++ | ||
} | ||
} else if len(nodeList) == 2 { | ||
if i == len(proof)-1 { | ||
// exclusion proof | ||
if !pathsMatch(nodeList[0], skipLength(nodeList[0]), path, pathOffset) && isEmptyValue(value) { | ||
return true, nil | ||
} | ||
|
||
// inclusion proof | ||
if bytes.Equal(nodeList[1], value) { | ||
return pathsMatch(nodeList[0], skipLength(nodeList[0]), path, pathOffset), nil | ||
} | ||
} else { | ||
nodePath := nodeList[0] | ||
prefixLength := sharedPrefixLength(path, pathOffset, nodePath) | ||
if prefixLength < len(nodePath)*2-skipLength(nodePath) { | ||
// Proof shows a divergent path , but we're not at the leaf yet | ||
return false, nil | ||
} | ||
pathOffset += prefixLength | ||
expectedHash = nodeList[1] | ||
} | ||
} else { | ||
return false, nil | ||
} | ||
} | ||
|
||
return false, nil | ||
} | ||
|
||
func pathsMatch(p1 []byte, s1 int, p2 []byte, s2 int) bool { | ||
len1 := len(p1)*2 - s1 | ||
len2 := len(p2)*2 - s2 | ||
|
||
if len1 != len2 { | ||
return false | ||
} | ||
|
||
for offset := 0; offset < len1; offset++ { | ||
n1 := getNibble(p1, s1+offset) | ||
n2 := getNibble(p2, s2+offset) | ||
if n1 != n2 { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
|
||
// dead code | ||
func GetRestPath(p []byte, s int) string { | ||
var ret string | ||
for i := s; i < len(p)*2; i++ { | ||
n := getNibble(p, i) | ||
ret += fmt.Sprintf("%01x", n) | ||
} | ||
return ret | ||
} | ||
|
||
func isEmptyValue(value []byte) bool { | ||
emptyAccount := Account{ | ||
Check failure on line 106 in execution/proof.go GitHub Actions / test
Check failure on line 106 in execution/proof.go GitHub Actions / build (1.22.3)
|
||
Nonce: 0, | ||
Balance: big.NewInt(0), | ||
StorageHash: [32]byte{0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e, 0x5b, 0x48, 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x01, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21}, | ||
CodeHash: [32]byte{0xc5, 0xd2, 0x46, 0x01, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7, 0x03, 0xc0, 0xe5, 0x00, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x04, 0x5d, 0x85, 0xa4, 0x70}, | ||
} | ||
|
||
encodedEmptyAccount, _ := rlp.EncodeToBytes(emptyAccount) | ||
|
||
isEmptySlot := len(value) == 1 && value[0] == 0x80 | ||
isEmptyAccount := bytes.Equal(value, encodedEmptyAccount) | ||
|
||
return isEmptySlot || isEmptyAccount | ||
} | ||
|
||
func sharedPrefixLength(path []byte, pathOffset int, nodePath []byte) int { | ||
skipLength := skipLength(nodePath) | ||
|
||
len1 := min(len(nodePath)*2-skipLength, len(path)*2-pathOffset) | ||
prefixLen := 0 | ||
|
||
for i := 0; i < len1; i++ { | ||
pathNibble := getNibble(path, i+pathOffset) | ||
nodePathNibble := getNibble(nodePath, i+skipLength) | ||
if pathNibble != nodePathNibble { | ||
break | ||
} | ||
prefixLen++ | ||
} | ||
|
||
return prefixLen | ||
} | ||
|
||
func skipLength(node []byte) int { | ||
if len(node) == 0 { | ||
return 0 | ||
} | ||
|
||
nibble := getNibble(node, 0) | ||
switch nibble { | ||
case 0, 2: | ||
return 2 | ||
case 1, 3: | ||
return 1 | ||
default: | ||
return 0 | ||
} | ||
} | ||
|
||
func getNibble(path []byte, offset int) byte { | ||
byteVal := path[offset/2] | ||
if offset%2 == 0 { | ||
return byteVal >> 4 | ||
} | ||
return byteVal & 0xF | ||
} | ||
|
||
func keccak256(data []byte) []byte { | ||
hash := sha3.NewLegacyKeccak256() | ||
hash.Write(data) | ||
return hash.Sum(nil) | ||
} | ||
|
||
func EncodeAccount(proof *EIP1186AccountProofResponse) ([]byte, error) { | ||
account := Account{ | ||
Check failure on line 170 in execution/proof.go GitHub Actions / test
Check failure on line 170 in execution/proof.go GitHub Actions / build (1.22.3)
|
||
Nonce: proof.Nonce, | ||
Balance: proof.Balance, | ||
StorageHash: proof.StorageHash, | ||
CodeHash: proof.CodeHash, | ||
} | ||
|
||
return rlp.EncodeToBytes(account) | ||
} | ||
|
||
// Make a generic function for it | ||
func min(a, b int) int { | ||
if a < b { | ||
return a | ||
} | ||
return b | ||
} |