Skip to content

Commit

Permalink
getProof rpc method
Browse files Browse the repository at this point in the history
  • Loading branch information
pnowosie committed Oct 3, 2024
1 parent 0c0700c commit d383531
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 101 deletions.
12 changes: 12 additions & 0 deletions core/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,18 @@ func (s *State) storage() (*trie.Trie, func() error, error) {
return s.globalTrie(db.StateTrie, trie.NewTriePedersen)
}

func (s *State) StorageTrie() (*trie.Trie, func() error, error) {
return s.storage()
}

func (s *State) ClassTrie() (*trie.Trie, func() error, error) {
return s.classesTrie()
}

func (s *State) StorageTrieForAddr(addr *felt.Felt) (*trie.Trie, error) {
return storage(addr, s.txn)
}

func (s *State) classesTrie() (*trie.Trie, func() error, error) {
return s.globalTrie(db.ClassesTrie, trie.NewTriePoseidon)
}
Expand Down
19 changes: 0 additions & 19 deletions rpc/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,3 @@ func (h *Handler) Nonce(id BlockID, address felt.Felt) (*felt.Felt, *jsonrpc.Err

return nonce, nil
}

// StorageAt gets the value of the storage at the given address and key.
//
// It follows the specification defined here:
// https://github.com/starkware-libs/starknet-specs/blob/a789ccc3432c57777beceaa53a34a7ae2f25fda0/api/starknet_api_openrpc.json#L110
func (h *Handler) StorageAt(address, key felt.Felt, id BlockID) (*felt.Felt, *jsonrpc.Error) {
stateReader, stateCloser, rpcErr := h.stateByBlockID(&id)
if rpcErr != nil {
return nil, rpcErr
}
defer h.callAndLogErr(stateCloser, "Error closing state reader in getStorageAt")

value, err := stateReader.ContractStorage(&address, &key)
if err != nil {
return nil, ErrContractNotFound
}

return value, nil
}
82 changes: 0 additions & 82 deletions rpc/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,85 +86,3 @@ func TestNonce(t *testing.T) {
assert.Equal(t, expectedNonce, nonce)
})
}

func TestStorageAt(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)

mockReader := mocks.NewMockReader(mockCtrl)
log := utils.NewNopZapLogger()
handler := rpc.New(mockReader, nil, nil, "", log)

t.Run("empty blockchain", func(t *testing.T) {
mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Latest: true})
require.Nil(t, storage)
assert.Equal(t, rpc.ErrBlockNotFound, rpcErr)
})

t.Run("non-existent block hash", func(t *testing.T) {
mockReader.EXPECT().StateAtBlockHash(&felt.Zero).Return(nil, nil, db.ErrKeyNotFound)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Hash: &felt.Zero})
require.Nil(t, storage)
assert.Equal(t, rpc.ErrBlockNotFound, rpcErr)
})

t.Run("non-existent block number", func(t *testing.T) {
mockReader.EXPECT().StateAtBlockNumber(uint64(0)).Return(nil, nil, db.ErrKeyNotFound)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Number: 0})
require.Nil(t, storage)
assert.Equal(t, rpc.ErrBlockNotFound, rpcErr)
})

mockState := mocks.NewMockStateHistoryReader(mockCtrl)

t.Run("non-existent contract", func(t *testing.T) {
mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil)
mockState.EXPECT().ContractStorage(gomock.Any(), gomock.Any()).Return(nil, errors.New("non-existent contract"))

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Latest: true})
require.Nil(t, storage)
assert.Equal(t, rpc.ErrContractNotFound, rpcErr)
})

t.Run("non-existent key", func(t *testing.T) {
mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil)
mockState.EXPECT().ContractStorage(gomock.Any(), gomock.Any()).Return(&felt.Zero, errors.New("non-existent key"))

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Latest: true})
require.Nil(t, storage)
assert.Equal(t, rpc.ErrContractNotFound, rpcErr)
})

expectedStorage := new(felt.Felt).SetUint64(1)

t.Run("blockID - latest", func(t *testing.T) {
mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil)
mockState.EXPECT().ContractStorage(gomock.Any(), gomock.Any()).Return(expectedStorage, nil)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Latest: true})
require.Nil(t, rpcErr)
assert.Equal(t, expectedStorage, storage)
})

t.Run("blockID - hash", func(t *testing.T) {
mockReader.EXPECT().StateAtBlockHash(&felt.Zero).Return(mockState, nopCloser, nil)
mockState.EXPECT().ContractStorage(gomock.Any(), gomock.Any()).Return(expectedStorage, nil)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Hash: &felt.Zero})
require.Nil(t, rpcErr)
assert.Equal(t, expectedStorage, storage)
})

t.Run("blockID - number", func(t *testing.T) {
mockReader.EXPECT().StateAtBlockNumber(uint64(0)).Return(mockState, nopCloser, nil)
mockState.EXPECT().ContractStorage(gomock.Any(), gomock.Any()).Return(expectedStorage, nil)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Number: 0})
require.Nil(t, rpcErr)
assert.Equal(t, expectedStorage, storage)
})
}
5 changes: 5 additions & 0 deletions rpc/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,11 @@ func (h *Handler) MethodsV0_6() ([]jsonrpc.Method, string) { //nolint: funlen
Params: []jsonrpc.Parameter{{Name: "contract_address"}, {Name: "key"}, {Name: "block_id"}},
Handler: h.StorageAt,
},
{
Name: "starknet_getStorageProof",
Params: []jsonrpc.Parameter{},
Handler: h.StorageProof,
},
{
Name: "starknet_getClassHashAt",
Params: []jsonrpc.Parameter{{Name: "block_id"}, {Name: "contract_address"}},
Expand Down
64 changes: 64 additions & 0 deletions rpc/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package rpc

import (
"github.com/NethermindEth/juno/core"
"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/juno/core/trie"
"github.com/NethermindEth/juno/jsonrpc"
)

/****************************************************
Storage Handlers
*****************************************************/

// StorageAt gets the value of the storage at the given address and key.
//
// It follows the specification defined here:
// https://github.com/starkware-libs/starknet-specs/blob/a789ccc3432c57777beceaa53a34a7ae2f25fda0/api/starknet_api_openrpc.json#L110
func (h *Handler) StorageAt(address, key felt.Felt, id BlockID) (*felt.Felt, *jsonrpc.Error) {
stateReader, stateCloser, rpcErr := h.stateByBlockID(&id)
if rpcErr != nil {
return nil, rpcErr
}
defer h.callAndLogErr(stateCloser, "Error closing state reader in getStorageAt")

value, err := stateReader.ContractStorage(&address, &key)
if err != nil {
return nil, ErrContractNotFound
}

return value, nil
}

// StorageProof returns the merkle paths in one of the state tries: global state, classes, individual contract
//
// It follows the specification defined here:
// https://github.com/starkware-libs/starknet-specs/blob/647caa00c0223e1daab1b2f3acc4e613ba2138aa/api/starknet_api_openrpc.json#L910
func (h *Handler) StorageProof(classes, contracts []felt.Felt, storageKeys []StorageKeys) (*felt.Felt, *jsonrpc.Error) {
stateReader, stateCloser, err := h.bcReader.HeadState()
if err != nil {
return nil, ErrInternal.CloneWithData(err)
}
defer h.callAndLogErr(stateCloser, "Error closing state reader in getStorageProof")

// TODO: Extend state reader interface?
s := stateReader.(*core.State)
clt, _, err := s.ClassTrie()
if err != nil {
return nil, ErrInternal.CloneWithData(err)
}
for _, elt := range classes {
feltBytes := elt.Bytes()
key := trie.NewKey(core.ContractStorageTrieHeight, feltBytes[:])
nodes, err := trie.GetProof(&key, clt)

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / lint

declared and not used: nodes

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / lint

declared and not used: err (typecheck)

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / lint

declared and not used: nodes

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / lint

declared and not used: err) (typecheck)

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / lint

declared and not used: nodes

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / lint

declared and not used: err) (typecheck)

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / lint

declared and not used: nodes

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / Run Tests (ubuntu-latest)

declared and not used: nodes

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / Run Tests (ubuntu-latest)

declared and not used: err

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / Run Tests (macos-latest)

declared and not used: nodes

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / Run Tests (macos-latest)

declared and not used: err

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / Run Tests (ubuntu-arm64-4-core)

declared and not used: nodes

Check failure on line 53 in rpc/storage.go

View workflow job for this annotation

GitHub Actions / Run Tests (ubuntu-arm64-4-core)

declared and not used: err
// adapt proofs to the expected format
}

return nil, ErrUnexpectedError
}

// https://github.com/starkware-libs/starknet-specs/blob/647caa00c0223e1daab1b2f3acc4e613ba2138aa/api/starknet_api_openrpc.json#L938
type StorageKeys struct {
Contract felt.Felt `json:"contract_address"`
Keys []felt.Felt `json:"storage_keys"`
}
178 changes: 178 additions & 0 deletions rpc/storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package rpc_test

import (
"errors"
"github.com/NethermindEth/juno/core/felt"
"github.com/NethermindEth/juno/db"
"github.com/NethermindEth/juno/mocks"
"github.com/NethermindEth/juno/rpc"
"github.com/NethermindEth/juno/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"testing"
)

func TestStorageAt(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)

mockReader := mocks.NewMockReader(mockCtrl)
log := utils.NewNopZapLogger()
handler := rpc.New(mockReader, nil, nil, "", log)

t.Run("empty blockchain", func(t *testing.T) {
mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Latest: true})
require.Nil(t, storage)
assert.Equal(t, rpc.ErrBlockNotFound, rpcErr)
})

t.Run("non-existent block hash", func(t *testing.T) {
mockReader.EXPECT().StateAtBlockHash(&felt.Zero).Return(nil, nil, db.ErrKeyNotFound)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Hash: &felt.Zero})
require.Nil(t, storage)
assert.Equal(t, rpc.ErrBlockNotFound, rpcErr)
})

t.Run("non-existent block number", func(t *testing.T) {
mockReader.EXPECT().StateAtBlockNumber(uint64(0)).Return(nil, nil, db.ErrKeyNotFound)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Number: 0})
require.Nil(t, storage)
assert.Equal(t, rpc.ErrBlockNotFound, rpcErr)
})

mockState := mocks.NewMockStateHistoryReader(mockCtrl)

t.Run("non-existent contract", func(t *testing.T) {
mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil)
mockState.EXPECT().ContractStorage(gomock.Any(), gomock.Any()).Return(nil, errors.New("non-existent contract"))

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Latest: true})
require.Nil(t, storage)
assert.Equal(t, rpc.ErrContractNotFound, rpcErr)
})

t.Run("non-existent key", func(t *testing.T) {
mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil)
mockState.EXPECT().ContractStorage(gomock.Any(), gomock.Any()).Return(&felt.Zero, errors.New("non-existent key"))

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Latest: true})
require.Nil(t, storage)
assert.Equal(t, rpc.ErrContractNotFound, rpcErr)
})

expectedStorage := new(felt.Felt).SetUint64(1)

t.Run("blockID - latest", func(t *testing.T) {
mockReader.EXPECT().HeadState().Return(mockState, nopCloser, nil)
mockState.EXPECT().ContractStorage(gomock.Any(), gomock.Any()).Return(expectedStorage, nil)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Latest: true})
require.Nil(t, rpcErr)
assert.Equal(t, expectedStorage, storage)
})

t.Run("blockID - hash", func(t *testing.T) {
mockReader.EXPECT().StateAtBlockHash(&felt.Zero).Return(mockState, nopCloser, nil)
mockState.EXPECT().ContractStorage(gomock.Any(), gomock.Any()).Return(expectedStorage, nil)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Hash: &felt.Zero})
require.Nil(t, rpcErr)
assert.Equal(t, expectedStorage, storage)
})

t.Run("blockID - number", func(t *testing.T) {
mockReader.EXPECT().StateAtBlockNumber(uint64(0)).Return(mockState, nopCloser, nil)
mockState.EXPECT().ContractStorage(gomock.Any(), gomock.Any()).Return(expectedStorage, nil)

storage, rpcErr := handler.StorageAt(felt.Zero, felt.Zero, rpc.BlockID{Number: 0})
require.Nil(t, rpcErr)
assert.Equal(t, expectedStorage, storage)
})
}

func TestStorageProof(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)

mockReader := mocks.NewMockReader(mockCtrl)
log := utils.NewNopZapLogger()
handler := rpc.New(mockReader, nil, nil, "", log)

t.Run("empty blockchain", func(t *testing.T) {
//mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound)

proof, rpcErr := handler.StorageProof(nil, nil, nil)
require.Nil(t, proof)

assert.Equal(t, rpc.ErrUnexpectedError, rpcErr)
})
t.Run("class trie hash does not exist in a trie", func(t *testing.T) {
//mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound)

proof, rpcErr := handler.StorageProof(nil, nil, nil)
require.Nil(t, proof)

assert.Equal(t, rpc.ErrUnexpectedError, rpcErr)
})
t.Run("class trie hash exists in a trie", func(t *testing.T) {
//mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound)

proof, rpcErr := handler.StorageProof(nil, nil, nil)
require.Nil(t, proof)

assert.Equal(t, rpc.ErrUnexpectedError, rpcErr)
})
t.Run("storage trie address does not exist in a trie", func(t *testing.T) {
//mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound)

proof, rpcErr := handler.StorageProof(nil, nil, nil)
require.Nil(t, proof)

assert.Equal(t, rpc.ErrUnexpectedError, rpcErr)
})
t.Run("storage trie address exists in a trie", func(t *testing.T) {
//mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound)

proof, rpcErr := handler.StorageProof(nil, nil, nil)
require.Nil(t, proof)

assert.Equal(t, rpc.ErrUnexpectedError, rpcErr)
})
t.Run("contract storage trie address does not exist in a trie", func(t *testing.T) {
//mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound)

proof, rpcErr := handler.StorageProof(nil, nil, nil)
require.Nil(t, proof)

assert.Equal(t, rpc.ErrUnexpectedError, rpcErr)
})
t.Run("contract storage trie key slot does not exist in a trie", func(t *testing.T) {
//mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound)

proof, rpcErr := handler.StorageProof(nil, nil, nil)
require.Nil(t, proof)

assert.Equal(t, rpc.ErrUnexpectedError, rpcErr)
})
t.Run("contract storage trie address/key exists in a trie", func(t *testing.T) {
//mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound)

proof, rpcErr := handler.StorageProof(nil, nil, nil)
require.Nil(t, proof)

assert.Equal(t, rpc.ErrUnexpectedError, rpcErr)
})
t.Run("class & storage tries proofs requested", func(t *testing.T) {
//mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound)

proof, rpcErr := handler.StorageProof(nil, nil, nil)
require.Nil(t, proof)

assert.Equal(t, rpc.ErrUnexpectedError, rpcErr)
})
}

0 comments on commit d383531

Please sign in to comment.