mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 23:26:44 +00:00
feat: add validate AccountTrieNode
This commit is contained in:
parent
21cfff582e
commit
f00c767e54
11 changed files with 406 additions and 8 deletions
|
|
@ -290,7 +290,7 @@ func initState(config Config, server *rpc.Server, conn discover.UDPConn, localNo
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
historyNetwork := state.NewStateNetwork(protocol)
|
||||
historyNetwork := state.NewStateNetwork(protocol, server)
|
||||
return historyNetwork.Start()
|
||||
}
|
||||
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -78,6 +78,7 @@ require (
|
|||
golang.org/x/tools v0.20.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
|
|
@ -148,7 +149,6 @@ require (
|
|||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
rsc.io/tmplfunc v0.0.3 // indirect
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -58,8 +58,23 @@ func (p *BlockHeaderProof) MarshalSSZ() ([]byte, error) {
|
|||
return ssz.MarshalSSZ(p)
|
||||
}
|
||||
|
||||
func (p *BlockHeaderProof) MarshalSSZTo(_ []byte) (dst []byte, err error) {
|
||||
return ssz.MarshalSSZ(p)
|
||||
func (p *BlockHeaderProof) MarshalSSZTo(buf []byte) (dst []byte, err error) {
|
||||
dst = buf
|
||||
dst = append(dst, byte(p.Selector))
|
||||
if p.Selector != none {
|
||||
if len(p.Proof) != 15 {
|
||||
err = ssz.ErrBytesLengthFn("proofs size should be", len(p.Proof), 15)
|
||||
return
|
||||
}
|
||||
for _, item := range p.Proof {
|
||||
if len(item) != 32 {
|
||||
err = ssz.ErrBytesLengthFn("single proof size should be", len(item), 32)
|
||||
return
|
||||
}
|
||||
dst = append(dst, item...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *BlockHeaderProof) UnmarshalSSZ(buf []byte) (err error) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/portalnetwork/history"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/protolambda/zrnt/eth2/beacon/common"
|
||||
"github.com/protolambda/zrnt/eth2/configs"
|
||||
"github.com/protolambda/ztyp/codec"
|
||||
)
|
||||
|
||||
type StateNetwork struct {
|
||||
|
|
@ -12,16 +25,22 @@ type StateNetwork struct {
|
|||
closeCtx context.Context
|
||||
closeFunc context.CancelFunc
|
||||
log log.Logger
|
||||
spec *common.Spec
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func NewStateNetwork(portalProtocol *discover.PortalProtocol) *StateNetwork {
|
||||
func NewStateNetwork(portalProtocol *discover.PortalProtocol, rpcServer *rpc.Server) *StateNetwork {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
client := rpc.DialInProc(rpcServer)
|
||||
|
||||
return &StateNetwork{
|
||||
portalProtocol: portalProtocol,
|
||||
closeCtx: ctx,
|
||||
closeFunc: cancel,
|
||||
log: log.New("sub-protocol", "state"),
|
||||
spec: configs.Mainnet,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +91,143 @@ func (h *StateNetwork) processContentLoop(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (h *StateNetwork) validateContents(contentKeys [][]byte, contents [][]byte) error {
|
||||
// TODO
|
||||
panic("implement me")
|
||||
for i, content := range contents {
|
||||
contentKey := contentKeys[i]
|
||||
err := h.validateContent(contentKey, content)
|
||||
if err != nil {
|
||||
h.log.Error("content validate failed", "contentKey", hexutil.Encode(contentKey), "content", hexutil.Encode(content), "err", err)
|
||||
return fmt.Errorf("content validate failed with content key %x and content %x", contentKey, content)
|
||||
}
|
||||
contentId := h.portalProtocol.ToContentId(contentKey)
|
||||
_ = h.portalProtocol.Put(contentKey, contentId, content)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *StateNetwork) validateContent(contentKey []byte, content []byte) error {
|
||||
keyType := contentKey[0]
|
||||
switch keyType {
|
||||
case AccountTrieNodeType:
|
||||
return h.validateAccountTrieNode(contentKey[1:], content)
|
||||
case ContractStorageTrieNodeType:
|
||||
return validateContractStorageTrieNode(h.spec, contentKey[1:], content)
|
||||
case ContractByteCodeType:
|
||||
return validateContractByteCode(h.spec, contentKey[1:], content)
|
||||
}
|
||||
return errors.New("unknown content type")
|
||||
}
|
||||
|
||||
func (h *StateNetwork) validateAccountTrieNode(contentKey []byte, content []byte) error {
|
||||
accountKey := &AccountTrieNodeKey{}
|
||||
err := accountKey.Deserialize(codec.NewDecodingReader(bytes.NewReader(contentKey), uint64(len(contentKey))))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accountData := &AccountTrieNodeWithProof{}
|
||||
err = accountData.Deserialize(codec.NewDecodingReader(bytes.NewReader(content), uint64(len(content))))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// get HeaderWithProof in history network
|
||||
stateRoot, err := h.getStateRoot(accountData.BlockHash)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = validateNodeTrieProof(stateRoot, accountKey.NodeHash, &accountKey.Path, &accountData.Proof)
|
||||
return err
|
||||
}
|
||||
|
||||
func validateContractStorageTrieNode(spec *common.Spec, contentKey []byte, content []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateContractByteCode(spec *common.Spec, contentKey []byte, content []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *StateNetwork) getStateRoot(blockHash common.Bytes32) (common.Bytes32, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||
defer cancel()
|
||||
contentKey := make([]byte, 0)
|
||||
contentKey = append(contentKey, byte(history.BlockHeaderType))
|
||||
contentKey = append(contentKey, blockHash[:]...)
|
||||
|
||||
arg := hexutil.Encode(contentKey)
|
||||
res := &discover.ContentInfo{}
|
||||
err := h.client.CallContext(ctx, res, "portal_historyRecursiveFindContent", arg)
|
||||
if err != nil {
|
||||
return common.Bytes32{}, err
|
||||
}
|
||||
data, err := hexutil.Decode(res.Content)
|
||||
if err != nil {
|
||||
return common.Bytes32{}, err
|
||||
}
|
||||
headerWithProof, err := history.DecodeBlockHeaderWithProof(data)
|
||||
if err != nil {
|
||||
return common.Bytes32{}, err
|
||||
}
|
||||
header := new(types.Header)
|
||||
err = rlp.DecodeBytes(headerWithProof.Header, header)
|
||||
if err != nil {
|
||||
return common.Bytes32{}, err
|
||||
}
|
||||
return common.Bytes32(header.Root), nil
|
||||
}
|
||||
|
||||
func validateNodeTrieProof(rootHash common.Bytes32, nodeHash common.Bytes32, path *Nibbles, proof *TrieProof) error {
|
||||
lastNode, p, err := validateTrieProof(rootHash, path.Nibbles, proof)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(p) != 0 {
|
||||
return errors.New("path is too long")
|
||||
}
|
||||
err = checkNodeHash(&lastNode, nodeHash[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
firstNode := []EncodedTrieNode(*proof)[0]
|
||||
err := checkNodeHash(&firstNode, rootHash[:])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
node := firstNode
|
||||
remainingPath := path
|
||||
|
||||
for _, nextNode := range []EncodedTrieNode(*proof)[1:] {
|
||||
n, err := trie.DecodeTrieNode(nil, node)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
hashNode, p, err := trie.TraverseTrieNode(n, remainingPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = checkNodeHash(&nextNode, hashNode)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
node = nextNode
|
||||
remainingPath = p
|
||||
}
|
||||
return node, remainingPath, nil
|
||||
}
|
||||
|
||||
func checkNodeHash(node *EncodedTrieNode, hash []byte) error {
|
||||
nodeHash := node.NodeHash()
|
||||
if !bytes.Equal(nodeHash[:], hash[:]) {
|
||||
return fmt.Errorf("node hash is not equal, expect: %v, actual: %v", hash, nodeHash)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
72
portalnetwork/state/network_test.go
Normal file
72
portalnetwork/state/network_test.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/portalnetwork/history"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type TestCase struct {
|
||||
BlockHeader string `yaml:"block_header"`
|
||||
ContentKey string `yaml:"content_key"`
|
||||
ContentValueOffer string `yaml:"content_value_offer"`
|
||||
ContentValueRetrieval string `yaml:"content_value_retrieval"`
|
||||
}
|
||||
|
||||
func getTestCases(filename string) ([]TestCase, error) {
|
||||
file, err := os.ReadFile(fmt.Sprintf("./testdata/%s", filename))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]TestCase, 0)
|
||||
err = yaml.Unmarshal(file, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type MockAPI struct {
|
||||
header string
|
||||
}
|
||||
|
||||
func (p *MockAPI) HistoryRecursiveFindContent(contentKeyHex string) (*discover.ContentInfo, error) {
|
||||
headerWithProof := &history.BlockHeaderWithProof{
|
||||
Header: hexutil.MustDecode(p.header),
|
||||
Proof: &history.BlockHeaderProof{
|
||||
Selector: 0,
|
||||
Proof: [][]byte{},
|
||||
},
|
||||
}
|
||||
data, err := headerWithProof.MarshalSSZ()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &discover.ContentInfo{
|
||||
Content: hexutil.Encode(data),
|
||||
UtpTransfer: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestValidateAccountTrieNode(t *testing.T) {
|
||||
cases, err := getTestCases("account_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)
|
||||
}
|
||||
}
|
||||
43
portalnetwork/state/testdata/account_trie_node.yaml
vendored
Normal file
43
portalnetwork/state/testdata/account_trie_node.yaml
vendored
Normal file
File diff suppressed because one or more lines are too long
9
portalnetwork/state/testdata/contract_bytecode.yaml
vendored
Normal file
9
portalnetwork/state/testdata/contract_bytecode.yaml
vendored
Normal file
File diff suppressed because one or more lines are too long
28
portalnetwork/state/testdata/contract_storage_trie_node.yaml
vendored
Normal file
28
portalnetwork/state/testdata/contract_storage_trie_node.yaml
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -4,11 +4,18 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/protolambda/zrnt/eth2/beacon/common"
|
||||
"github.com/protolambda/ztyp/codec"
|
||||
"github.com/protolambda/ztyp/tree"
|
||||
)
|
||||
|
||||
const (
|
||||
AccountTrieNodeType byte = 0x20
|
||||
ContractStorageTrieNodeType byte = 0x21
|
||||
ContractByteCodeType byte = 0x22
|
||||
)
|
||||
|
||||
var _ common.SSZObj = (*Nibbles)(nil)
|
||||
|
||||
type Nibbles struct {
|
||||
|
|
@ -232,7 +239,7 @@ func (c *ContractBytecodeKey) HashTreeRoot(spec *common.Spec, hFn tree.HashFn) c
|
|||
)
|
||||
}
|
||||
|
||||
const MaxTrieNodeLength = 1026
|
||||
const MaxTrieNodeLength = 1024
|
||||
|
||||
type EncodedTrieNode []byte
|
||||
|
||||
|
|
@ -256,6 +263,10 @@ func (e EncodedTrieNode) HashTreeRoot(hFn tree.HashFn) tree.Root {
|
|||
return hFn.ByteListHTR(e, MaxTrieNodeLength)
|
||||
}
|
||||
|
||||
func (e *EncodedTrieNode) NodeHash() common.Bytes32 {
|
||||
return tree.Root(crypto.Keccak256(*e))
|
||||
}
|
||||
|
||||
// A content value type, used when retrieving a trie node.
|
||||
type TrieNode struct {
|
||||
Node EncodedTrieNode
|
||||
|
|
|
|||
49
trie/portal.go
Normal file
49
trie/portal.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package trie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyLeafKey = errors.New("the leaf node has empty key")
|
||||
ErrDifferentLeafPrefix = errors.New("the leaf prifix path is different")
|
||||
ErrEmptyExtensionPrefix = errors.New("the extension node has empty prefix")
|
||||
ErrDifferentExtensionPrefix = errors.New("the prifix path of extension node is different")
|
||||
)
|
||||
|
||||
func DecodeTrieNode(hash, buf []byte) (node, error) {
|
||||
return decodeNodeUnsafe(hash, buf)
|
||||
}
|
||||
|
||||
func TraverseTrieNode(node node, path []byte) (hashNode, []byte, error) {
|
||||
switch v := node.(type) {
|
||||
case *fullNode:
|
||||
first := path[0]
|
||||
remainingPath := path[1:]
|
||||
return TraverseTrieNode(v.Children[first], remainingPath)
|
||||
case *shortNode:
|
||||
length := len(v.Key)
|
||||
lastKey := v.Key[length-1]
|
||||
prePath := v.Key[:length-1]
|
||||
if lastKey == 16 {
|
||||
if len(prePath) == 0 {
|
||||
return nil, nil, ErrEmptyLeafKey
|
||||
}
|
||||
if !bytes.Equal(prePath, path) {
|
||||
return nil, nil, ErrDifferentLeafPrefix
|
||||
}
|
||||
return (v.Val).(hashNode), path, nil
|
||||
} else {
|
||||
for index, key := range v.Key {
|
||||
if path[index] != key {
|
||||
return nil, nil, ErrDifferentExtensionPrefix
|
||||
}
|
||||
}
|
||||
return TraverseTrieNode(v.Val, path[len(v.Key):])
|
||||
}
|
||||
case hashNode:
|
||||
return v, path, nil
|
||||
}
|
||||
return nil, nil, errors.New("unknown type")
|
||||
}
|
||||
15
trie/portal_test.go
Normal file
15
trie/portal_test.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package trie
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
const data = "0xf90211a0491f396d5d4768a01ee4282a3ab1127f2a4dc7d42e6c1dbb3e71ad4e9299f5e7a05b4645219e614b388ba9672452b40f291987b15e35bdbd3dfebfac9a085aeab2a0979ebca2a6a0df389fdfef5bfa4a31f2efa8d385bf2f43cd69c27e61165c3667a01bbe04543bb6bf8026ee3ec2a3ec3d6173a60c090008b59779f767e5769d7a0aa051d347ec61c7dde5c4149a943d0aa489544cbea511ed3d7f4fc6aaa52a420d3ea05358bc8e1e1f20e510887226d67efee73769d0b13ebb24a3e750c2214ef090d5a04df1c24ebf40befce60c8eb30a31894102881454fcdeb6f7b83e1fb916c953a8a00a85e04f30a4978712c58a825e7bd2f8a83731ab1aa3e234a0207e918790505fa0778a45437218486b7849aef890d483dcc2deb1423cb81e488b7be2921d505bf4a051dbaef3c3d3fe82bd1484954f38508f651d867f387f71dedbfb0f7355987ed5a061679f43f3db26673bb687c584f30ae3cff7261e71acf07ed1feaecae098fc79a099761ea7d94e01b14285b7ced7dcd1677fbd3ce093a627a8e1472074baad8bd9a0bb6d269fc61443aa28b14af0448aaf6f1f570477d8b4ef2eb7d31543202ce602a06c41cd2d1701b058853591a2f306ddcfd1e55d3e12011cadb100e7c525aaf460a0bcf37abaac566bb98e091575af403804dbb278fbfa5547de6805cbaa529ae137a0de9918a2a976a2b0b3f4aeff801fc79557426b19c15d414ddf8fae843785b0b780"
|
||||
|
||||
func TestDecodeNodePortal(t *testing.T) {
|
||||
dataBytes := hexutil.MustDecode(data)
|
||||
n, _ := DecodeTrieNode(nil, dataBytes)
|
||||
t.Log(n)
|
||||
}
|
||||
Loading…
Reference in a new issue