go-ethereum/portalnetwork/state/network.go
2024-09-21 00:17:23 +08:00

296 lines
8.6 KiB
Go

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 {
portalProtocol *discover.PortalProtocol
closeCtx context.Context
closeFunc context.CancelFunc
log log.Logger
spec *common.Spec
client *rpc.Client
}
func NewStateNetwork(portalProtocol *discover.PortalProtocol, client *rpc.Client) *StateNetwork {
ctx, cancel := context.WithCancel(context.Background())
return &StateNetwork{
portalProtocol: portalProtocol,
closeCtx: ctx,
closeFunc: cancel,
log: log.New("sub-protocol", "state"),
spec: configs.Mainnet,
client: client,
}
}
func (h *StateNetwork) Start() error {
err := h.portalProtocol.Start()
if err != nil {
return err
}
go h.processContentLoop(h.closeCtx)
h.log.Debug("state network start successfully")
return nil
}
func (h *StateNetwork) Stop() {
h.closeFunc()
h.portalProtocol.Stop()
}
func (h *StateNetwork) processContentLoop(ctx context.Context) {
contentChan := h.portalProtocol.GetContent()
for {
select {
case <-ctx.Done():
return
case contentElement := <-contentChan:
err := h.validateContents(contentElement.ContentKeys, contentElement.Contents)
if err != nil {
h.log.Error("validate content failed", "err", err)
continue
}
go func(ctx context.Context) {
select {
case <-ctx.Done():
return
default:
var gossippedNum int
gossippedNum, err = h.portalProtocol.Gossip(&contentElement.Node, contentElement.ContentKeys, contentElement.Contents)
h.log.Trace("gossippedNum", "gossippedNum", gossippedNum)
if err != nil {
h.log.Error("gossip failed", "err", err)
return
}
}
}(ctx)
}
}
}
func (h *StateNetwork) validateContents(contentKeys [][]byte, contents [][]byte) error {
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)
err = h.portalProtocol.Put(contentKey, contentId, content)
if err != nil {
return err
}
}
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 h.validateContractStorageTrieNode(contentKey[1:], content)
case ContractByteCodeType:
return h.validateContractByteCode(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 (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 (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
}
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 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")
}
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
}