Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented_execution_proof_go_execution_errors_go_#30 #34

154 changes: 154 additions & 0 deletions execution/errors.go
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)
gerceboss marked this conversation as resolved.
Show resolved Hide resolved
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
}
186 changes: 186 additions & 0 deletions execution/proof.go
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

View workflow job for this annotation

GitHub Actions / test

undefined: Account

Check failure on line 106 in execution/proof.go

View workflow job for this annotation

GitHub Actions / build (1.22.3)

undefined: Account

Check failure on line 106 in execution/proof.go

View workflow job for this annotation

GitHub Actions / golangci-lint (/home/runner/work/selene/selene)

undefined: Account
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

View workflow job for this annotation

GitHub Actions / test

undefined: Account

Check failure on line 170 in execution/proof.go

View workflow job for this annotation

GitHub Actions / build (1.22.3)

undefined: Account

Check failure on line 170 in execution/proof.go

View workflow job for this annotation

GitHub Actions / golangci-lint (/home/runner/work/selene/selene)

undefined: Account (typecheck)
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 {
gerceboss marked this conversation as resolved.
Show resolved Hide resolved
if a < b {
return a
}
return b
}
Loading