From 4b5512994485e92d2848dad02db9f33b3f9aa5f6 Mon Sep 17 00:00:00 2001 From: Sambhav Jain <136801346+DarkLord017@users.noreply.github.com> Date: Wed, 2 Oct 2024 01:38:24 +0530 Subject: [PATCH 1/2] Implement tests for execution/proof & /errors --- execution/errors_test.go | 85 +++++++++++++++++++ execution/proof_test.go | 173 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 execution/errors_test.go create mode 100644 execution/proof_test.go diff --git a/execution/errors_test.go b/execution/errors_test.go new file mode 100644 index 0000000..03ff6a6 --- /dev/null +++ b/execution/errors_test.go @@ -0,0 +1,85 @@ +package execution + +import ( + "errors" + "testing" + + "github.com/BlocSoc-iitr/selene/consensus/consensus_core" + "github.com/BlocSoc-iitr/selene/consensus/types" + "github.com/stretchr/testify/assert" +) + +func TestExecutionErrors(t *testing.T) { + // Test InvalidAccountProofError + address := types.Address{0x01, 0x02} + err := NewInvalidAccountProofError(address) + assert.EqualError(t, err, "invalid account proof for string: [1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]") + + // Test InvalidStorageProofError + slot := consensus_core.Bytes32{0x0a} + err = NewInvalidStorageProofError(address, slot) + assert.EqualError(t, err, "invalid storage proof for string: [1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], slot: [10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]") + + // Test CodeHashMismatchError + found := consensus_core.Bytes32{0x03} + expected := consensus_core.Bytes32{0x04} + err = NewCodeHashMismatchError(address, found, expected) + assert.EqualError(t, err, "code hash mismatch for string: [1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], found: [3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], expected: [4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]") + + // Test ReceiptRootMismatchError + tx := consensus_core.Bytes32{0x05} + err = NewReceiptRootMismatchError(tx) + assert.EqualError(t, err, "receipt root mismatch for tx: [5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]") + + // Test MissingTransactionError + err = NewMissingTransactionError(tx) + assert.EqualError(t, err, "missing transaction for tx: [5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]") + + // Test MissingLogError + err = NewMissingLogError(tx, 3) + assert.EqualError(t, err, "missing log for transaction: [5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], index: 3") + + // Test TooManyLogsToProveError + err = NewTooManyLogsToProveError(5000, 1000) + assert.EqualError(t, err, "too many logs to prove: 5000, current limit is: 1000") + + // Test InvalidBaseGasFeeError + err = NewInvalidBaseGasFeeError(1000, 2000, 123456) + assert.EqualError(t, err, "Invalid base gas fee selene 1000 vs rpc endpoint 2000 at block 123456") + + // Test BlockNotFoundError + err = NewBlockNotFoundError(123456) + assert.EqualError(t, err, "Block 123456 not found") + + // Test EmptyExecutionPayloadError + err = NewEmptyExecutionPayloadError() + assert.EqualError(t, err, "Selene Execution Payload is empty") +} + +func TestEvmErrors(t *testing.T) { + // Test RevertError + data := []byte{0x08, 0xc3, 0x79, 0xa0} + err := NewRevertError(data) + assert.EqualError(t, err, "execution reverted: [8 195 121 160]") + + // Test GenericError + err = NewGenericError("generic error") + assert.EqualError(t, err, "evm error: generic error") + + // Test RpcError + rpcErr := errors.New("rpc connection failed") + err = NewRpcError(rpcErr) + assert.EqualError(t, err, "rpc error: rpc connection failed") +} + +func TestDecodeRevertReason(t *testing.T) { + // Test successful revert reason decoding + reasonData := []byte{0x08, 0xc3, 0x79, 0xa0} + reason := DecodeRevertReason(reasonData) + assert.NotEmpty(t, reason, "Revert reason should be decoded") + + // Test invalid revert data + invalidData := []byte{0x00} + reason = DecodeRevertReason(invalidData) + assert.Contains(t, reason, "invalid data for unpacking") +} diff --git a/execution/proof_test.go b/execution/proof_test.go new file mode 100644 index 0000000..c265dd9 --- /dev/null +++ b/execution/proof_test.go @@ -0,0 +1,173 @@ +package execution + +import ( + "encoding/hex" + "fmt" + "io" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func (a *Account) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{a.Nonce, a.Balance, a.StorageHash, a.CodeHash}) +} + +func (a *Account) DecodeRLP(s *rlp.Stream) error { + var accountRLP struct { + Nonce uint64 + Balance *big.Int + StorageHash common.Hash + CodeHash common.Hash + } + if err := s.Decode(&accountRLP); err != nil { + return err + } + a.Nonce = accountRLP.Nonce + a.Balance = accountRLP.Balance + a.StorageHash = accountRLP.StorageHash + a.CodeHash = accountRLP.CodeHash + + return nil +} + +func TestSharedPrefixLength(t *testing.T) { + t.Run("Match first 5 nibbles", func(t *testing.T) { + path := []byte{0x12, 0x13, 0x14, 0x6f, 0x6c, 0x64, 0x21} + pathOffset := 6 + nodePath := []byte{0x6f, 0x6c, 0x63, 0x21} + + sharedLen := sharedPrefixLength(path, pathOffset, nodePath) + assert.Equal(t, 5, sharedLen) + }) + + t.Run("Match first 7 nibbles with skip", func(t *testing.T) { + path := []byte{0x12, 0x13, 0x14, 0x6f, 0x6c, 0x64, 0x21} + pathOffset := 5 + nodePath := []byte{0x14, 0x6f, 0x6c, 0x64, 0x11} + + sharedLen := sharedPrefixLength(path, pathOffset, nodePath) + assert.Equal(t, 7, sharedLen) + }) + + t.Run("No match", func(t *testing.T) { + path := []byte{0x12, 0x34, 0x56} + pathOffset := 0 + nodePath := []byte{0x78, 0x9a, 0xbc} + + sharedLen := sharedPrefixLength(path, pathOffset, nodePath) + assert.Equal(t, 0, sharedLen) + }) + + t.Run("Complete match", func(t *testing.T) { + path := []byte{0x12, 0x34, 0x56} + pathOffset := 0 + nodePath := []byte{0x00, 0x12, 0x34, 0x56} + + sharedLen := sharedPrefixLength(path, pathOffset, nodePath) + assert.Equal(t, 6, sharedLen) + }) +} + +func TestSkipLength(t *testing.T) { + testCases := []struct { + name string + node []byte + expected int + }{ + {"Empty node", []byte{}, 0}, + {"Nibble 0", []byte{0x00}, 2}, + {"Nibble 1", []byte{0x10}, 1}, + {"Nibble 2", []byte{0x20}, 2}, + {"Nibble 3", []byte{0x30}, 1}, + {"Nibble 4", []byte{0x40}, 0}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := skipLength(tc.node) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestGetNibble(t *testing.T) { + path := []byte{0x12, 0x34, 0x56} + + testCases := []struct { + offset int + expected byte + }{ + {0, 0x1}, + {1, 0x2}, + {2, 0x3}, + {3, 0x4}, + {4, 0x5}, + {5, 0x6}, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Offset %d", tc.offset), func(t *testing.T) { + result := getNibble(path, tc.offset) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestIsEmptyValue(t *testing.T) { + t.Run("Empty slot", func(t *testing.T) { + value := []byte{0x80} + assert.True(t, isEmptyValue(value)) + }) + + t.Run("Empty account", func(t *testing.T) { + emptyAccount := 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, err := rlp.EncodeToBytes(&emptyAccount) + require.NoError(t, err) + assert.True(t, isEmptyValue(encodedEmptyAccount)) + }) + + t.Run("Non-empty value", func(t *testing.T) { + value := []byte{0x01, 0x23, 0x45} + assert.False(t, isEmptyValue(value)) + }) +} + +func TestEncodeAccount(t *testing.T) { + proof := &EIP1186AccountProofResponse{ + Nonce: 1, + Balance: big.NewInt(1000), + StorageHash: [32]byte{1, 2, 3}, + CodeHash: [32]byte{4, 5, 6}, + } + + encoded, err := EncodeAccount(proof) + require.NoError(t, err) + + var decodedAccount Account + err = rlp.DecodeBytes(encoded, &decodedAccount) + require.NoError(t, err) + + // Assert the decoded values match the original + assert.Equal(t, uint64(1), decodedAccount.Nonce) + assert.Equal(t, big.NewInt(1000), decodedAccount.Balance) + assert.Equal(t, proof.StorageHash, decodedAccount.StorageHash) + assert.Equal(t, proof.CodeHash, decodedAccount.CodeHash) +} + +func TestKeccak256(t *testing.T) { + input := []byte("hello world") + expected, _ := hex.DecodeString("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad") + + result := keccak256(input) + assert.Equal(t, expected, result) +} From 518fca8057598462a07357a35a8f7a0e34c48ff0 Mon Sep 17 00:00:00 2001 From: Sambhav Jain <136801346+DarkLord017@users.noreply.github.com> Date: Wed, 2 Oct 2024 19:24:11 +0530 Subject: [PATCH 2/2] Update proof.go --- execution/proof.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/execution/proof.go b/execution/proof.go index 549e7ec..88873d6 100644 --- a/execution/proof.go +++ b/execution/proof.go @@ -5,6 +5,7 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/sha3" ) @@ -13,8 +14,8 @@ import ( type EIP1186AccountProofResponse struct { Nonce uint64 Balance *big.Int - StorageHash [32]byte - CodeHash [32]byte + StorageHash common.Hash + CodeHash common.Hash } func VerifyProof(proof [][]byte, root []byte, path []byte, value []byte) (bool, error) { @@ -110,7 +111,7 @@ func isEmptyValue(value []byte) bool { 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) + encodedEmptyAccount, _ := rlp.EncodeToBytes(&emptyAccount) isEmptySlot := len(value) == 1 && value[0] == 0x80 isEmptyAccount := bytes.Equal(value, encodedEmptyAccount) @@ -122,7 +123,7 @@ func sharedPrefixLength(path []byte, pathOffset int, nodePath []byte) int { skipLength := skipLength(nodePath) len1 := min(len(nodePath)*2-skipLength, len(path)*2-pathOffset) - prefixLen := 0 + var prefixLen int = 0 for i := 0; i < len1; i++ { pathNibble := getNibble(path, i+pathOffset) @@ -174,7 +175,7 @@ func EncodeAccount(proof *EIP1186AccountProofResponse) ([]byte, error) { CodeHash: proof.CodeHash, } - return rlp.EncodeToBytes(account) + return rlp.EncodeToBytes(&account) } // Make a generic function for it