feat: add validate AccountTrieNode

This commit is contained in:
fearlseefe 2024-09-13 15:06:38 +08:00 committed by Chen Kai
parent 21cfff582e
commit f00c767e54
11 changed files with 406 additions and 8 deletions

View file

@ -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
View file

@ -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
)

View file

@ -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) {

View file

@ -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
}

View 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)
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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
View 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
View 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)
}