diff --git a/portalnetwork/state/network.go b/portalnetwork/state/network.go index 193be375bf..be3c191d68 100644 --- a/portalnetwork/state/network.go +++ b/portalnetwork/state/network.go @@ -107,9 +107,9 @@ func (h *StateNetwork) validateContent(contentKey []byte, content []byte) error case AccountTrieNodeType: return h.validateAccountTrieNode(contentKey[1:], content) case ContractStorageTrieNodeType: - return validateContractStorageTrieNode(h.spec, contentKey[1:], content) + return h.validateContractStorageTrieNode(contentKey[1:], content) case ContractByteCodeType: - return validateContractByteCode(h.spec, contentKey[1:], content) + return h.validateContractByteCode(contentKey[1:], content) } return errors.New("unknown content type") } @@ -135,11 +135,53 @@ func (h *StateNetwork) validateAccountTrieNode(contentKey []byte, content []byte return err } -func validateContractStorageTrieNode(spec *common.Spec, contentKey []byte, content []byte) error { - return nil +func (h *StateNetwork) validateContractStorageTrieNode(contentKey []byte, content []byte) error { + contractStorageKey := &ContractStorageTrieNodeKey{} + err := contractStorageKey.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey), uint64(len(contentKey)))) + if err != nil { + return err + } + contractProof := &ContractStorageTrieNodeWithProof{} + err = contractProof.Deserialize(codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content)))) + if err != nil { + return err + } + stateRoot, err := h.getStateRoot(contractProof.BlockHash) + + if err != nil { + return err + } + + accountState, err := validateAccountState(stateRoot, contractStorageKey.AddressHash, &contractProof.AccountProof) + if err != nil { + return err + } + err = validateNodeTrieProof(common.Bytes32(accountState.Root), contractStorageKey.NodeHash, &contractStorageKey.Path, &contractProof.StoregeProof) + return err } -func validateContractByteCode(spec *common.Spec, contentKey []byte, content []byte) error { +func (h *StateNetwork) validateContractByteCode(contentKey []byte, content []byte) error { + contractByteCodeKey := &ContractBytecodeKey{} + err := contractByteCodeKey.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey), uint64(len(contentKey)))) + if err != nil { + return err + } + contractBytecodeWithProof := &ContractBytecodeWithProof{} + err = contractBytecodeWithProof.Deserialize(codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content)))) + if err != nil { + return err + } + stateRoot, err := h.getStateRoot(contractBytecodeWithProof.BlockHash) + if err != nil { + return err + } + accountState, err := validateAccountState(stateRoot, contractByteCodeKey.AddressHash, &contractBytecodeWithProof.AccountProof) + if err != nil { + return err + } + if !bytes.Equal(accountState.CodeHash, contractByteCodeKey.CodeHash[:]) { + return errors.New("account state is invalid") + } return nil } @@ -187,6 +229,27 @@ func validateNodeTrieProof(rootHash common.Bytes32, nodeHash common.Bytes32, pat return nil } +func validateAccountState(rootHash common.Bytes32, addrrssHash common.Bytes32, proof *TrieProof) (*types.StateAccount, error) { + path := make([]byte, 0, len(addrrssHash)*2) + for _, item := range addrrssHash { + before, after := unpackNibblePair(item) + path = append(path, before, after) + } + lastProof, p, err := validateTrieProof(rootHash, path, proof) + if err != nil { + return nil, err + } + n, err := trie.DecodeTrieNode(nil, lastProof) + if err != nil { + return nil, err + } + stateBytes, _, err := trie.TraverseTrieNode(n, p) + if err != nil { + return nil, err + } + return types.FullAccount(stateBytes) +} + func validateTrieProof(rootHash common.Bytes32, path []byte, proof *TrieProof) (EncodedTrieNode, []byte, error) { if len(*proof) == 0 { return nil, nil, errors.New("proof should be empty") diff --git a/portalnetwork/state/network_test.go b/portalnetwork/state/network_test.go index 1aec2e5c13..ee1371d0d3 100644 --- a/portalnetwork/state/network_test.go +++ b/portalnetwork/state/network_test.go @@ -71,3 +71,35 @@ func TestValidateAccountTrieNode(t *testing.T) { require.NoError(t, err) } } + +func TestValidateContractStorage(t *testing.T) { + cases, err := getTestCases("contract_storage_trie_node.yaml") + require.NoError(t, err) + + for _, tt := range cases { + server := rpc.NewServer() + api := &MockAPI{ + header: tt.BlockHeader, + } + server.RegisterName("portal", api) + bn := NewStateNetwork(nil, server) + err = bn.validateContent(hexutil.MustDecode(tt.ContentKey), hexutil.MustDecode(tt.ContentValueOffer)) + require.NoError(t, err) + } +} + +func TestValidateContractByte(t *testing.T) { + cases, err := getTestCases("contract_bytecode.yaml") + require.NoError(t, err) + + for _, tt := range cases { + server := rpc.NewServer() + api := &MockAPI{ + header: tt.BlockHeader, + } + server.RegisterName("portal", api) + bn := NewStateNetwork(nil, server) + err = bn.validateContent(hexutil.MustDecode(tt.ContentKey), hexutil.MustDecode(tt.ContentValueOffer)) + require.NoError(t, err) + } +} diff --git a/trie/portal.go b/trie/portal.go index 8efe375f9a..8f4ab79aee 100644 --- a/trie/portal.go +++ b/trie/portal.go @@ -16,7 +16,7 @@ func DecodeTrieNode(hash, buf []byte) (node, error) { return decodeNodeUnsafe(hash, buf) } -func TraverseTrieNode(node node, path []byte) (hashNode, []byte, error) { +func TraverseTrieNode(node node, path []byte) ([]byte, []byte, error) { switch v := node.(type) { case *fullNode: first := path[0] @@ -33,7 +33,7 @@ func TraverseTrieNode(node node, path []byte) (hashNode, []byte, error) { if !bytes.Equal(prePath, path) { return nil, nil, ErrDifferentLeafPrefix } - return (v.Val).(hashNode), path, nil + return (v.Val).(valueNode), path, nil } else { for index, key := range v.Key { if path[index] != key {