Skip to content

Commit

Permalink
Merge pull request #34 from DarkLord017/Implemented_execution_proof_g…
Browse files Browse the repository at this point in the history
…o_execution_errors_go_#30

Implemented_execution_proof_go_execution_errors_go_#30
  • Loading branch information
gerceboss authored Sep 28, 2024
2 parents 987f84f + 394539b commit a24a843
Show file tree
Hide file tree
Showing 2 changed files with 340 additions and 0 deletions.
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)
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 {
if a < b {
return a
}
return b
}

0 comments on commit a24a843

Please sign in to comment.